Почему MtpDevice.ImportFile всегда терпит неудачу? - PullRequest
1 голос
/ 16 мая 2019

Я изменяю существующее приложение, написанное на C # для Xamarin, созданное исключительно для Android.

Существующее приложение ищет файлы на карте Wi-Fi в камере и загружает их через интерфейс HTTP. Я пытаюсь заставить его загружать файлы, используя вместо этого привязанный USB-кабель, но не могу загрузить файлы для загрузки.

Я пытаюсь загрузить MtpDevice, но функция ImportFile всегда дает сбой. К сожалению, он никогда не вызывает исключений и не дает какой-либо полезной информации о причинах.

Приведенный ниже код в значительной степени показывает, что я делаю, хотя я удалил кучу вещей, которые не имеют отношения к проблеме.

Поскольку я изменяю существующее приложение, код немного запутан, поскольку есть один метод для поиска файлов, затем фоновый поток вызывает другой метод для фактической загрузки, чтобы пользователь мог продолжить работу, пока файлы загружены.

Кроме того, часть кода, который я показал в GetFileListAsync, на самом деле используется в других методах, так как они используются несколько раз ... Я не включил материал разрешений, так как кажется, что все работает нормально.

Это метод, который вызывается первым, чтобы найти список дескрипторов объектов.

public async Task<List<MyFileClass>> GetFileListAsync()
{
    var results = new List<MyFileClass>();
    UsbDeviceConnection usbDeviceConnection = null;
    MtpDevice mtpDevice = null;
    UsbDevice usbDevice = null;

    // all this stuff works as expected...

    UsbManager usbManager = (UsbManager)Android.App.Application.Context.GetSystemService(Context.UsbService);

    try
    {
        if (usbManager.DeviceList != null && usbManager.DeviceList.Count > 0)
        {
            foreach (var usbAccessory in usbManager.DeviceList)
            {
                var device = usbAccessory.Value;
                usbDevice = usbAccessory.Value;
                break;  // should only ever be one, but break here anyway
            }
        }
    }
    catch (Exception ex)
    {
        Log.Error(ex, "Error getting USB devices");
    }

    if (usbDevice == null)
    {
        Log.Information("ConnectedDevice is null");
        return false;
    }

    if (!UsbManager.HasPermission(usbDevice))
    {
        Log.Information("Requesting permission must have failed");
        return false;
    }

    try
    {
        usbDeviceConnection = UsbManager.OpenDevice(usbDevice);
    }
    catch (Exception ex)
    {
        Log.Error(ex, "opening usb connection");
        return false;
    }

    try
    {
        mtpDevice = new MtpDevice(usbDevice);
    }
    catch (Exception ex)
    {
        Log.Error(ex, "creating mtpdevice");
        return false;
    }

    try
    {
        mtpDevice.Open(usbDeviceConnection);
    }
    catch (Exception ex)
    {
        Log.Error(ex, " opening mtpdevice");
        usbDeviceConnection.Close();
        return false;
    }

    // then start looking for files

    var storageUnits = mtpDevice.GetStorageIds();
    if (storageUnits == null || storageUnits.Length == 0)
    {
        Log.Information("StorageUnits is empty");
        mtpDevice.Close();
        usbDeviceConnection.Close();
        return false;
    }

    foreach (var storageUnitId in storageUnits)
    {
        var storageUnit = mtpDevice.GetStorageInfo(storageUnitId);
        if (storageUnit != null)
        {
            // recurse directories to get list of files
            await RecurseMTPDirectories(results, storageUnitId, docketId, docket, db);
        }
    }
}

private async Task RecurseMTPDirectories(List<MyFileClass> files, int storageUnitId, string parentFolder = "\\", int parentHandle = -1)
{
    var results = new List<FlashAirFile>();
    var directoryObjects = new List<MtpObjectInfo>();
    var objectHandles = mtpDevice.GetObjectHandles(storageUnitId, 0, parentHandle);

    if (objectHandles != null && objectHandles.Length > 0)
    {
        foreach (var objectHandle in objectHandles)
        {
            MtpObjectInfo objectInfo = mtpDevice.GetObjectInfo(objectHandle);
            if (objectInfo != null)
            {
                if (objectInfo.Format == MtpFormat.Association)
                {
                    // add to the list to recurse into, but do this at the end
                    directoryObjects.Add(objectInfo);
                }
                else
                {
                    // it' a file - add it only if it's one we want
                    try
                    {
                        var file = new MyFileClass()
                        {
                            TotalBytes = objectInfo.CompressedSize,
                            FileNameOnCard = objectInfo.Name,
                            DirectoryOnCard = parentFolder,
                            MTPHandle = objectHandle
                        };
                    }
                    catch (Exception e)
                    {
                        Log.Error(e, " trying to create MTP FlashAirFile");
                    }
                }
            }
        }
    }

    foreach (var directoryObject in directoryObjects)
    {
        string fullname = parentFolder + directoryObject.Name;
        await RecurseMTPDirectories(files, storageUnitId, $"{fullname}\\", directoryObject.ObjectHandle);
    }
    return results;
}

Я знаю, что можно получить все дескрипторы сразу, а не рекурсивно просматривать папки, но сейчас я делаю это, как это делал старый код.

Список объектов MyFileClass добавляется в базу данных SQLite, затем фоновый поток удаляет их из очереди по очереди и вызывает DownloadFileAsync для получения каждого файла. В этом методе используется то же устройство, что и в методе GetFileListAsync, и он также проверяет, что разрешение все еще доступно.

public async Task<int> DownloadFileAsync(MyFileClass file, string destination)
{
    int receivedBytes = 0;
    int objectHandle = file.MTPHandle;

    connectedDevice = await GetAttachedDevice();
    if (connectedDevice == null || !UsbManager.HasPermission(connectedDevice))
        return receivedBytes;

    if (!await OpenAttachedDevice())
        return receivedBytes;

    var rootFolder = await FileSystem.Current.GetFolderFromPathAsync(destination);
    var localFile = rootFolder.Path;

    try
    {
    Log.Information($"Attempting to download ID {objectHandle} to {localFile}");

        // try downloading just using path
        bool success = mtpDevice.ImportFile(objectHandle, localFile);
        if (!success)
        {
            // try it with a / on the end of the path
            localFile += '/';
            Log.Information($"Attempting to download ID {file.DownloadManagerId} to {localFile}");
            success = mtpDevice.ImportFile(objectHandle, localFile);
        }
        if (!success)
        {
            // try it with the filename on the end of the path as well
            localFile += file.FileNameOnSdCard;
            Log.Information($"Attempting to download ID {file.DownloadManagerId} to {localFile}");
            success = mtpDevice.ImportFile(objectHandle, localFile);
        }


        if (!success)
        {
            throw new Exception($"mtpDevice.ImportFile failed for {file.FileNameOnSdCard}");
        }

        // do stuff here to handle success
    }
    catch (Exception ex) when (ex is OperationCanceledException || ex is TaskCanceledException)
    {
        // do some other stuff here in the database

        //rethrow the exception so it can be handled further up the chain.
        throw;
    }

    return receivedBytes;
}

Я не могу найти ни одного примера, показывающего эту работу. Я видел один пост, в котором говорится, что файл должен быть импортирован во внешнюю папку кэша, и один, в котором говорится, что второй параметр должен включать имя файла, но ни один из них не работает.

Я выдернул себе волосы - помогите !!

1 Ответ

1 голос
/ 17 мая 2019

Итак, спасибо SushiHangover и за то, что указали мне на проблему, и за то, что открыли мне глаза на прелести logcat.Ответ заключается в том, что утверждение

о том, что файл должен быть импортирован во внешнюю папку кэша

, абсолютно верно, но на самом деле оно должно быть внешним.

GetExternalCacheDirs () на самом деле возвращает папку, даже если у вас нет физического внешнего носителя, что мне кажется сумасшедшим, но это так.

Кстати, верно и то, чтопуть назначения должен включать имя файла.Документация гласит:

destPath String : путь к месту назначения для передачи файла.Этот путь должен быть во внешнем хранилище, как определено Environment.getExternalStorageDirectory (). Это значение никогда не должно быть нулевым.

Мне это не совсем понятно.

...