问题
I am converting an 8.1 App to Windows 10 UWP. The App uses OneDrive for its private backups and restores, using the new OneDrive SDK, since the LiveSDK does not function with UWP apps.
I have no problem in logging into my OneDrive, listing files and downloading them (using the code shown in the GitHub documentation but I have not yet been successful in uploading a file to my OneDrive.
The code I use is :
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;
}
again as specified in the GitHub documentation.
When the call is made, the app starts uploading the stream (about 30MB) but after about 30 seconds an exception is raised (not a OneDriveException, but a normal exception).
The exception message says that the upload has been cancelled, but offers no explaination of the reason.
The error message (from mscorlib) is "A task was canceled." and the error code is -2146233029
What is going on? What am I doing wrong?
回答1:
While waiting for SDK implementation i'm posting an non-resumable upload by chunk to 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();
}
}
}
回答2:
The zipfile object is a simple zip created by using the Framework 4.5 and 4.6 ZipArchiveEntry Class, with code:
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");
}
}
This code works fine in the 8.1 version of the app, and it also creates a correct zip file in the Windows 10 version (I have examined the resulting zip file separately).
Yes, I agree with you, it is probably a timeout that is reported incorrectly.
While the correct reporting of the timeout is important, it is in my opinion essential that some features are added to the SDK if it is to be useful to Windows Store app developers:
- Allow a timeout to be set for the upload of a file
- Allow progress of downloads/uploads to be reported to the app
While these features seem to be present in the REST Api, they are notoriously missing in the c# SDK (or perhaps the almost non-existing documentation does not mention them). As a consequence, the user experience is rather upsetting, with the progress ring turning and turning without any progress information being communicated to the app user.
PS I have modified the try block as follows in order to test if the await of the zip stream is causing the error. The try now reads: try { Stream strm = await zipfile.OpenStreamForReadAsync(); Item uploadedItem = await App .Client .Drive .Root .ItemWithPath(path) .Content .Request() .PutAsync(strm); }
The await zipfile.OpenStreamForReadAsync(); statement executes correctly, without raising an exception; the statement that causes the problem is indeed the uploading of the stream to OneDrive.
回答3:
I have definitely established that the problem is a timeout INSIDE the sdk upload code. I have gradually reduced the size of the zipfile from the original 70Mb, and the error is given until that size reaches between 17Mb and 18Mb; below that, the file uploads without any errors. I am using an ADSL connection with a max 2Mbit upload capability, so this should enable the developing team to establish the needed timeout quantity. I must say, however, that it seems to me that UWP apps are supposed to run on tablets and phones as well as PCs, and many of those tablets and certainly the phones will have a sim and will be able to upload and download data over a mobile connection (very few fiber connections out there in the street). As such, either the timeout value is left to the developer or the maximum value for uploading the max available file size (100Mb?) at an average connection speed (1Mb?) is set in the package.
I do not intend to criticize the SDK development team, but it seems to me that very little thought has gone in the preparation and documentation of the SDK, as well as it being primitive in its current form, several months after Windows 10 has been released. Waiting for a better version means that apps will not be released to the store...
回答4:
I understand this is a frustrating limitation and we are working on it. Unfortunately, we are limited to the functionality of HttpClient at the moment, which doesn't allow any granular timeout control. The best solution for you at the moment is most likely to implement resumable upload, especially if you want to support > 100 mb files.
The SDK does help with components of this right now. To create the chunked upload session:
var uploadSession = await client
.Drive
.Root
.ItemWithPath(path)
.CreateSession(sessionDescriptor)
.Request()
.PostAsync();
From there you can use uploadSession.UploadUrl
to upload your individual chunks. await client.AuthenticationProvider.AppendAuthHeaderAsync(httpRequestMessage)
will append the authentication header to each chunk request so you don't have to handle authentication.
We're working on building this into the SDK so developers don't have to implement this themselves but it should help solve your scenario.
来源:https://stackoverflow.com/questions/33408692/cannot-upload-to-onedrive-using-the-new-sdk