Simple command line test tool for MTP host.

Change-Id: Ifd13e1ca5d49a5477a9850d94d443a50bbc32ff1
Signed-off-by: Mike Lockwood <lockwood@android.com>
This commit is contained in:
Mike Lockwood
2010-07-26 20:44:42 -04:00
parent ad0643a330
commit 5fd1ff0aa3
4 changed files with 671 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
mtp.cpp \
MtpFile.cpp \
LOCAL_C_INCLUDES += \
frameworks/base/media/mtp \
LOCAL_CFLAGS := -DMTP_HOST
LOCAL_MODULE := mtp
LOCAL_STATIC_LIBRARIES := libmtp libusbhost libutils libcutils
include $(BUILD_EXECUTABLE)
ifeq ($(HOST_OS),linux)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
mtp.cpp \
MtpFile.cpp \
../../../libs/utils/RefBase.cpp \
../../../libs/utils/SharedBuffer.cpp \
../../../libs/utils/Threads.cpp \
../../../libs/utils/VectorImpl.cpp \
LOCAL_C_INCLUDES += \
frameworks/base/media/mtp \
LOCAL_CFLAGS := -DMTP_HOST -g -O0
have_readline := $(wildcard /usr/include/readline/readline.h)
have_history := $(wildcard /usr/lib/libhistory*)
ifneq ($(strip $(have_readline)),)
LOCAL_CFLAGS += -DHAVE_READLINE=1
endif
LOCAL_LDLIBS += -lpthread
ifneq ($(strip $(have_readline)),)
LOCAL_LDLIBS += -lreadline -lncurses
endif
ifneq ($(strip $(have_history)),)
LOCAL_LDLIBS += -lhistory
endif
LOCAL_MODULE := mtp
LOCAL_STATIC_LIBRARIES := libmtp libusbhost libcutils
include $(BUILD_HOST_EXECUTABLE)
endif

187
media/tests/mtp/MtpFile.cpp Normal file
View File

@@ -0,0 +1,187 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MtpClient.h"
#include "MtpDevice.h"
#include "MtpDeviceInfo.h"
#include "MtpObjectInfo.h"
#include "MtpStorage.h"
#include "MtpUtils.h"
#include "MtpFile.h"
namespace android {
MtpClient* MtpFile::sClient = NULL;
MtpFile::MtpFile(MtpDevice* device)
: mDevice(device),
mStorage(0),
mHandle(0)
{
}
MtpFile::MtpFile(MtpDevice* device, MtpStorageID storage)
: mDevice(device),
mStorage(storage),
mHandle(0)
{
}
MtpFile::MtpFile(MtpDevice* device, MtpStorageID storage, MtpObjectHandle handle)
: mDevice(device),
mStorage(storage),
mHandle(handle)
{
}
MtpFile::MtpFile(MtpFile* file)
: mDevice(file->mDevice),
mStorage(file->mStorage),
mHandle(file->mHandle)
{
}
MtpFile::~MtpFile() {
}
void MtpFile::print() {
if (mHandle) {
} else if (mStorage) {
printf("%x\n", mStorage);
} else {
int id = mDevice->getID();
MtpDeviceInfo* info = mDevice->getDeviceInfo();
if (info)
printf("%d\t%s %s %s\n", id, info->mManufacturer, info->mModel, info->mSerial);
else
printf("%d\t(no device info available)\n", id);
delete info;
}
}
MtpObjectInfo* MtpFile::getObjectInfo() {
return mDevice->getObjectInfo(mHandle);
}
void MtpFile::list() {
if (mStorage) {
MtpObjectHandleList* handles = mDevice->getObjectHandles(mStorage, 0,
(mHandle ? mHandle : -1));
if (handles) {
for (int i = 0; i < handles->size(); i++) {
MtpObjectHandle handle = (*handles)[i];
MtpObjectInfo* info = mDevice->getObjectInfo(handle);
if (info) {
char modified[100];
struct tm tm;
gmtime_r(&info->mDateModified, &tm);
strftime(modified, sizeof(modified), "%a %b %e %H:%M:%S GMT %Y", &tm);
printf("%s Handle: %d Format: %04X Size: %d Modified: %s\n",
info->mName, handle, info->mFormat, info->mCompressedSize, modified);
delete info;
}
}
delete handles;
}
} else {
// list storage units for device
MtpStorageIDList* storageList = mDevice->getStorageIDs();
for (int i = 0; i < storageList->size(); i++) {
MtpStorageID storageID = (*storageList)[i];
printf("%x\n", storageID);
}
}
}
void MtpFile::init(MtpClient* client) {
sClient = client;
}
MtpFile* MtpFile::parsePath(MtpFile* base, char* path) {
MtpDevice* device = NULL;
MtpStorageID storage = 0;
MtpObjectHandle handle = 0;
if (path[0] != '/' && base) {
device = base->mDevice;
storage = base->mStorage;
handle = base->mHandle;
}
// parse an absolute path
if (path[0] == '/')
path++;
char* tok = strtok(path, "/");
while (tok) {
if (storage) {
// find child of current handle
MtpObjectHandleList* handles = device->getObjectHandles(storage, 0,
(handle ? handle : -1));
MtpObjectHandle childHandle = 0;
if (handles) {
for (int i = 0; i < handles->size() && !childHandle; i++) {
MtpObjectHandle handle = (*handles)[i];
MtpObjectInfo* info = device->getObjectInfo(handle);
if (info && !strcmp(tok, info->mName))
childHandle = handle;
delete info;
}
delete handles;
}
if (childHandle)
handle = childHandle;
else
return NULL;
} else if (device) {
unsigned int id;
// find storage for the device
if (sscanf(tok, "%x", &id) == 1) {
MtpStorageIDList* storageList = device->getStorageIDs();
bool found = false;
for (int i = 0; i < storageList->size(); i++) {
if ((*storageList)[i] == id) {
found = true;
break;
}
}
if (found)
storage = id;
else
return NULL;
}
} else {
// find device
unsigned int id;
if (sscanf(tok, "%d", &id) == 1)
device = sClient->getDevice(id);
if (!device)
return NULL;
}
tok = strtok(NULL, "/");
}
if (device)
return new MtpFile(device, storage, handle);
else
return NULL;
}
}

57
media/tests/mtp/MtpFile.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _MTP_FILE_H
#define _MTP_FILE_H
#include "MtpTypes.h"
namespace android {
class MtpClient;
class MtpDevice;
class MtpObjectInfo;
// File-like abstraction for the interactive shell.
// This can be used to represent an MTP device, storage unit or object
// (either file or association).
class MtpFile {
private:
MtpDevice* mDevice;
MtpStorageID mStorage;
MtpObjectHandle mHandle;
static MtpClient* sClient;
public:
MtpFile(MtpDevice* device);
MtpFile(MtpDevice* device, MtpStorageID storage);
MtpFile(MtpDevice* device, MtpStorageID storage, MtpObjectHandle handle);
MtpFile(MtpFile* file);
virtual ~MtpFile();
MtpObjectInfo* getObjectInfo();
void print();
void list();
inline MtpDevice* getDevice() const { return mDevice; }
static void init(MtpClient* client);
static MtpFile* parsePath(MtpFile* base, char* path);
};
}
#endif // _MTP_DIRECTORY_H

370
media/tests/mtp/mtp.cpp Normal file
View File

@@ -0,0 +1,370 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#if HAVE_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif
#include "MtpClient.h"
#include "MtpDevice.h"
#include "MtpObjectInfo.h"
#include "MtpFile.h"
#define PROMPT "mtp> "
using namespace android;
static MtpClient* sClient = NULL;
// current working directory information for interactive shell
static MtpFile* sCurrentDirectory = NULL;
static MtpFile* parse_path(char* path) {
return MtpFile::parsePath(sCurrentDirectory, path);
}
class MyClient : public MtpClient {
private:
virtual void deviceAdded(MtpDevice *device) {
}
virtual void deviceRemoved(MtpDevice *device) {
}
public:
};
static void init() {
sClient = new MyClient;
sClient->start();
MtpFile::init(sClient);
}
static int set_cwd(int argc, char* argv[]) {
if (argc != 1) {
fprintf(stderr, "cd should have one argument\n");
return -1;
}
if (!strcmp(argv[0], "/")) {
delete sCurrentDirectory;
sCurrentDirectory = NULL;
}
else {
MtpFile* file = parse_path(argv[0]);
if (file) {
delete sCurrentDirectory;
sCurrentDirectory = file;
} else {
fprintf(stderr, "could not find %s\n", argv[0]);
return -1;
}
}
return 0;
}
static void list_devices() {
// TODO - need to make sure the list will not change while iterating
MtpDeviceList& devices = sClient->getDeviceList();
for (int i = 0; i < devices.size(); i++) {
MtpDevice* device = devices[i];
MtpFile* file = new MtpFile(device);
file->print();
delete file;
}
}
static int list(int argc, char* argv[]) {
if (argc == 0) {
// list cwd
if (sCurrentDirectory) {
sCurrentDirectory->list();
} else {
list_devices();
}
}
for (int i = 0; i < argc; i++) {
char* path = argv[i];
if (!strcmp(path, "/")) {
list_devices();
} else {
MtpFile* file = parse_path(path);
if (!file) {
fprintf(stderr, "could not find %s\n", path);
return -1;
}
file->list();
}
}
return 0;
}
static int get_file(int argc, char* argv[]) {
int ret = -1;
int srcFD = -1;
int destFD = -1;
MtpFile* srcFile = NULL;
MtpObjectInfo* info = NULL;
char* dest;
if (argc < 1) {
fprintf(stderr, "not enough arguments\n");
return -1;
} else if (argc > 2) {
fprintf(stderr, "too many arguments\n");
return -1;
}
// find source object
char* src = argv[0];
srcFile = parse_path(src);
if (!srcFile) {
fprintf(stderr, "could not find %s\n", src);
return -1;
}
info = srcFile->getObjectInfo();
if (!info) {
fprintf(stderr, "could not find object info for %s\n", src);
goto fail;
}
if (info->mFormat == MTP_FORMAT_ASSOCIATION) {
fprintf(stderr, "copying directories not implemented yet\n");
goto fail;
}
dest = (argc > 1 ? argv[1] : info->mName);
destFD = open(dest, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (destFD < 0) {
fprintf(stderr, "could not create %s\n", dest);
goto fail;
}
srcFD = srcFile->getDevice()->readObject(info->mHandle, info->mCompressedSize);
if (srcFD < 0)
goto fail;
char buffer[65536];
while (1) {
int count = read(srcFD, buffer, sizeof(buffer));
if (count <= 0)
break;
write(destFD, buffer, count);
}
// FIXME - error checking and reporting
ret = 0;
fail:
delete srcFile;
delete info;
if (srcFD >= 0)
close(srcFD);
if (destFD >= 0)
close(destFD);
return ret;
}
static int put_file(int argc, char* argv[]) {
int ret = -1;
int srcFD = -1;
MtpFile* destFile = NULL;
MtpObjectInfo* srcInfo = NULL;
MtpObjectInfo* destInfo = NULL;
MtpObjectHandle handle;
struct stat statbuf;
const char* lastSlash;
if (argc < 1) {
fprintf(stderr, "not enough arguments\n");
return -1;
} else if (argc > 2) {
fprintf(stderr, "too many arguments\n");
return -1;
}
const char* src = argv[0];
srcFD = open(src, O_RDONLY);
if (srcFD < 0) {
fprintf(stderr, "could not open %s\n", src);
goto fail;
}
if (argc == 2) {
char* dest = argv[1];
destFile = parse_path(dest);
if (!destFile) {
fprintf(stderr, "could not find %s\n", dest);
goto fail;
}
} else {
if (!sCurrentDirectory) {
fprintf(stderr, "current working directory not set\n");
goto fail;
}
destFile = new MtpFile(sCurrentDirectory);
}
destInfo = destFile->getObjectInfo();
if (!destInfo) {
fprintf(stderr, "could not find object info destination directory\n");
goto fail;
}
if (destInfo->mFormat != MTP_FORMAT_ASSOCIATION) {
fprintf(stderr, "destination not a directory\n");
goto fail;
}
if (fstat(srcFD, &statbuf))
goto fail;
srcInfo = new MtpObjectInfo(0);
srcInfo->mStorageID = destInfo->mStorageID;
srcInfo->mFormat = MTP_FORMAT_EXIF_JPEG; // FIXME
srcInfo->mCompressedSize = statbuf.st_size;
srcInfo->mParent = destInfo->mHandle;
lastSlash = strrchr(src, '/');
srcInfo->mName = strdup(lastSlash ? lastSlash + 1 : src);
srcInfo->mDateModified = statbuf.st_mtime;
handle = destFile->getDevice()->sendObjectInfo(srcInfo);
if (handle <= 0) {
printf("sendObjectInfo returned %04X\n", handle);
goto fail;
}
if (destFile->getDevice()->sendObject(srcInfo, srcFD))
ret = 0;
fail:
delete destFile;
delete srcInfo;
delete destInfo;
if (srcFD >= 0)
close(srcFD);
printf("returning %d\n", ret);
return ret;
}
typedef int (* command_func)(int argc, char* argv[]);
struct command_table_entry {
const char* name;
command_func func;
};
const command_table_entry command_list[] = {
{ "cd", set_cwd },
{ "ls", list },
{ "get", get_file },
{ "put", put_file },
{ NULL, NULL },
};
static int do_command(int argc, char* argv[]) {
const command_table_entry* command = command_list;
const char* name = *argv++;
argc--;
while (command->name) {
if (!strcmp(command->name, name))
return command->func(argc, argv);
else
command++;
}
fprintf(stderr, "unknown command %s\n", name);
return -1;
}
static int shell() {
int argc;
int result = 0;
#define MAX_ARGS 100
char* argv[MAX_ARGS];
#if HAVE_READLINE
using_history();
#endif
while (1) {
#if HAVE_READLINE
char* line = readline(PROMPT);
if (!line) {
printf("\n");
exit(0);
}
#else
char buffer[1000];
printf("%s", PROMPT);
char* line = NULL;
size_t length = 0;
buffer[0] = 0;
fgets(buffer, sizeof(buffer), stdin);
int count = strlen(buffer);
if (count > 0 && buffer[0] == EOF) {
printf("\n");
exit(0);
}
if (count > 0 && line[count - 1] == '\n')
line[count - 1] == 0;
#endif
char* tok = strtok(line, " \t\n\r");
if (!tok)
continue;
if (!strcmp(tok, "quit") || !strcmp(tok, "exit")) {
exit(0);
}
#if HAVE_READLINE
add_history(line);
#endif
argc = 0;
while (tok) {
if (argc + 1 == MAX_ARGS) {
fprintf(stderr, "too many arguments\n");
result = -1;
goto bottom_of_loop;
}
argv[argc++] = strdup(tok);
tok = strtok(NULL, " \t\n\r");
}
result = do_command(argc, argv);
bottom_of_loop:
for (int i = 0; i < argc; i++)
free(argv[i]);
free(line);
}
return result;
}
int main(int argc, char* argv[]) {
init();
if (argc == 1)
return shell();
else
return do_command(argc - 1, argv + 1);
}