Невозможно загрузить в OneDrive с использованием нового SDK

Я конвертирую приложение 8.1 в Windows 10 UWP. Приложение использует OneDrive для своих личных резервных копий и восстанавливает, используя новый OneDrive SDK, поскольку LiveSDK не работает с приложениями UWP.

У меня нет проблем при входе в мой OneDrive, перечислении файлов и их загрузке (с использованием кода, показанного в документации GitHub, но мне пока не удалось загрузить файл в мой OneDrive.

Код, который я использую:

string path = "/EpubReader_Backups/" + zipfile.Name;
string s = "";
try
{
    Item uploadedItem = await App.Client
                  .Drive
                  .Root
                  .ItemWithPath(path)
                  .Content
                  .Request()
                  .PutAsync<Item>
                (await zipfile.OpenStreamForReadAsync());
}
catch (Exception ex)
{
    s = ex.Message;
}

снова, как указано в документации GitHub.

Когда вызов сделан, приложение начинает загрузку потока (около 30 МБ), но примерно через 30 секунд возникает исключение (не OneDriveException, а обычное исключение).

Сообщение об исключении говорит, что загрузка была отменена, но не предлагает объяснения причины.

Сообщение об ошибке (от mscorlib): "Задача была отменена". и код ошибки -2146233029

Что здесь происходит? Что я делаю неправильно?

4 ответа

В ожидании реализации SDK я отправляю невозобновляемую загрузку по чанку в OneDrive

using Microsoft.OneDrive.Sdk;
using Newtonsoft.Json;
using SimpleAuthenticationProvider;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.OneDrive.Sdk
{

    public static class Extend
    {
        public static async Task<Item> UploadChunks(this OneDriveClient client, string folder, string fileName, Stream stream, int chunkSize, LiveToken token)
        {
            var session = await client.Drive.Root.ItemWithPath(folder + "/" + fileName).CreateSession(new ChunkedUploadSessionDescriptor() { Name = fileName }).Request().PostAsync();
            using (OneDriveChunkUpload chunks = new OneDriveChunkUpload(stream, session.UploadUrl, chunkSize, token))
            {
                return await client.Drive.Items[chunks.Upload().id].Request().GetAsync();
            }
        }
    }
    public class LiveToken
    {
        public string token_type { get; set; }
        public int expires_in { get; set; }
        public string scope { get; set; }
        public string access_token { get; set; }
        public string refresh_token { get; set; }
        public string user_id { get; set; }
    }

    public class itemCreated
    {
        public string id { get; set; }
        public string name { get; set; }
        public int size { get; set; }
    }

    class responseChunk
    {
        public string uploadUrl { get; set; }
        public DateTime expirationDateTime { get; set; }
        public List<string> nextExpectedRanges { get; set; }
        public List<Range> Ranges
        {
            get
            {
                List<Range> ret = new List<Range>();
                foreach (var r in nextExpectedRanges)
                {
                    string[] parsed = r.Split('-');
                    Range ra = new Range(parsed[0], parsed[1]);
                    ret.Add(ra);
                }
                return ret;
            }
        }
    }
    class Range
    {
        public Range() { }
        public Range(string start, string end)
        {
            Start = int.Parse(start);
            if (!string.IsNullOrEmpty(end))
                End = int.Parse(end);
        }
        public int Start { get; set; }
        public int? End { get; set; }
    }
    public class OneDriveChunkUpload : IDisposable
    {
        Stream source;
        string Url;
        int ChunkSize;
        responseChunk lastResponse;
        LiveToken Token;
        public itemCreated item = null;
        public OneDriveChunkUpload(Stream str, string url, int chunkSize, LiveToken token)
        {
            Token = token;
            source = str;
            Url = url;
            ChunkSize = chunkSize;
            lastResponse = new responseChunk() { nextExpectedRanges = new List<string>() { "0-" + (str.Length -1).ToString() } };
        }

        public itemCreated Upload()
        {
            long position = 0;
            while (lastResponse != null)
            {
                Range r = new Range();
                r.Start = lastResponse.Ranges[0].Start;
                r.End = (int)Math.Min(((lastResponse.Ranges[0].End ?? int.MinValue) + 1) - r.Start, ChunkSize);
                r.End += r.Start -1;
                byte[] buffer = new byte[r.End.Value - r.Start + 1];
                source.Position = r.Start;
                //source.Seek(r.Start, SeekOrigin.Begin);
                position += source.Read(buffer, 0, buffer.Length);
                Put(buffer, r);
            }
            return item;
        }

        void Put(byte[] bytes, Range r)
        {

            WebRequest req = HttpWebRequest.Create(Url);
            req.Method = "PUT";
            req.Headers.Add(HttpRequestHeader.Authorization, string.Format("bearer {0}", Token.access_token));
            req.ContentLength = bytes.Length;
            string ContentRange = string.Format("bytes {0}-{1}/{2}", r.Start, r.End.Value, source.Length);
            req.Headers.Add(HttpRequestHeader.ContentRange, ContentRange);
            Stream requestStream = req.GetRequestStream();
            requestStream.Write(bytes, 0, bytes.Length);
            HttpWebResponse res = null;
            try
            {
                res = (HttpWebResponse)req.GetResponse();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            string responseBody = null;

            using (StreamReader sr = new StreamReader(res.GetResponseStream(), Encoding.UTF8))
            {
                responseBody = sr.ReadToEnd();
            }
            if (res.StatusCode == HttpStatusCode.Accepted)
            {
                lastResponse = JsonConvert.DeserializeObject<responseChunk>(responseBody);
            }
            else if (res.StatusCode == HttpStatusCode.Created)
            {
                lastResponse = null;
                item = JsonConvert.DeserializeObject<itemCreated>(responseBody);
            }
            else
            {
                throw new Exception("bad format");
            }
        }

        public void Dispose()
        {
            WebRequest req = HttpWebRequest.Create(Url);
            req.Method = "DELETE";
            WebResponse res = req.GetResponse();
        }
    }

}

Объект zipfile представляет собой простой zip-файл, созданный с использованием классов ZipArchiveEntry Framework 4.5 и 4.6, с кодом:

        public static async Task<StorageFile> ZipFileFromFiles(List<StorageFile> files)
        {
            DatabaseUtilities.CloseDatabases();
            using (MemoryStream zipStream = new MemoryStream())
            {
                using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create))
                {
                    foreach (StorageFile file in files)
                    {
                        byte[] data;
                        ZipArchiveEntry entry = zip.CreateEntry(file.Name);
                        using (Stream zipFile = entry.Open())
                        {
                            data = await ReadFileBytes(file);
                            zipFile.Write(data, 0, data.Length);
                        }
                    }

                    await zipStream.FlushAsync();
                }

                byte[] zipfile = zipStream.ToArray();
                String ss = "Backup from ";
                ss += DateTime.Now.Year.ToString() + "-";
                ss += DateTime.Now.Month.ToString("D2") + "-";
                ss += DateTime.Now.Day.ToString("D2") + " ";
                ss += DateTime.Now.Hour.ToString("D2") + "H";
                ss += DateTime.Now.Minute.ToString("D2") + "M";
                ss += DateTime.Now.Second.ToString("D2") + "S";
                await WriteFile(App.Folder, ss + ".zip", zipfile);
                return await App.Folder.GetFileAsync(ss + ".zip");
            }
        }

Этот код прекрасно работает в версии 8.1 приложения, а также создает правильный zip-файл в версии Windows 10 (я изучил полученный zip-файл отдельно).

Да, я согласен с вами, вероятно, это неверный тайм-аут.

Хотя правильный отчет о времени ожидания важен, на мой взгляд, важно, чтобы некоторые функции были добавлены в SDK, если это будет полезно для разработчиков приложений Магазина Windows:

  1. Разрешить установить тайм-аут для загрузки файла
  2. Разрешить приложению сообщать о прогрессе загрузок / загрузок

Хотя эти функции присутствуют в REST Api, они, как известно, отсутствуют в C# SDK (или, возможно, почти несуществующая документация не упоминает их). Как следствие, пользовательский опыт довольно расстраивает, кольцо прогресса поворачивается и поворачивается без какой-либо информации о прогрессе, передаваемой пользователю приложения.

PS Я изменил блок try следующим образом, чтобы проверить, является ли ожидание потока zip причиной ошибки. Try теперь читает: try { Stream strm = await zipfile.OpenStreamForReadAsync(); Элемент uploadedItem = await App .Client .Drive .Root .ItemWithPath(path) .Content .Request() .PutAsync(strm); }

В ожидании zipfile.OpenStreamForReadAsync (); оператор выполняется правильно, не вызывая исключения; утверждение, которое вызывает проблему, действительно является загрузкой потока в OneDrive.

Я определенно установил, что проблема в тайм-ауте внутри кода загрузки SDK. Я постепенно уменьшил размер zip-файла с исходных 70 МБ, и ошибка дается до тех пор, пока этот размер не достигнет между 17 МБ и 18 МБ; ниже этого, файл загружается без каких-либо ошибок. Я использую соединение ADSL с возможностью загрузки до 2 Мбит, так что это должно позволить команде разработчиков установить необходимое количество тайм-аутов. Я должен сказать, однако, что мне кажется, что приложения UWP должны работать на планшетах и ​​телефонах, а также на ПК, и многие из этих планшетов и, конечно же, телефоны будут иметь сим-карту и смогут загружать и скачивать данные через мобильная связь (очень мало оптоволоконных соединений на улице). Таким образом, либо значение тайм-аута остается для разработчика, либо в пакете устанавливается максимальное значение для загрузки максимально доступного размера файла (100 МБ?) При средней скорости соединения (1 МБ?).

Я не собираюсь критиковать команду разработчиков SDK, но мне кажется, что при подготовке и документировании SDK очень мало думал, а также о том, что он примитивен в его нынешнем виде, спустя несколько месяцев после выпуска Windows 10, Ожидание лучшей версии означает, что приложения не будут выпущены в магазин...

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

SDK действительно помогает с компонентами этого прямо сейчас. Чтобы создать сеанс частичной загрузки:

var uploadSession = await client
    .Drive
    .Root
    .ItemWithPath(path)
    .CreateSession(sessionDescriptor)
    .Request()
    .PostAsync();

Оттуда вы можете использовать uploadSession.UploadUrl загрузить ваши отдельные куски. await client.AuthenticationProvider.AppendAuthHeaderAsync(httpRequestMessage) добавит заголовок аутентификации к каждому запросу чанка, чтобы вам не приходилось обрабатывать аутентификацию.

Мы работаем над встраиванием этого в SDK, чтобы разработчикам не приходилось реализовывать это самим, но это должно помочь решить ваш сценарий.

Другие вопросы по тегам