Невозможно загрузить в 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:
- Разрешить установить тайм-аут для загрузки файла
- Разрешить приложению сообщать о прогрессе загрузок / загрузок
Хотя эти функции присутствуют в 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, чтобы разработчикам не приходилось реализовывать это самим, но это должно помочь решить ваш сценарий.