diff --git a/Storage/Storage.Test/FileTest.cs b/Storage/Storage.Test/FileTest.cs index 08ac1b7..45916dc 100644 --- a/Storage/Storage.Test/FileTest.cs +++ b/Storage/Storage.Test/FileTest.cs @@ -7,6 +7,7 @@ using LeanCloud.Storage; namespace LeanCloud.Test { public class FileTest { static readonly string AvatarFilePath = "../../../assets/hello.png"; + static readonly string APKFilePath = "../../../assets/test.apk"; [SetUp] public void SetUp() { @@ -31,7 +32,9 @@ namespace LeanCloud.Test { [Test] public async Task SaveFromPath() { LCFile file = new LCFile("avatar", AvatarFilePath); - await file.Save(); + await file.Save((count, total) => { + TestContext.WriteLine($"progress: {count}/{total}"); + }); TestContext.WriteLine(file.ObjectId); Assert.NotNull(file.ObjectId); } @@ -60,7 +63,7 @@ namespace LeanCloud.Test { [Test] public async Task Qiniu() { - LCFile file = new LCFile("avatar", AvatarFilePath); + LCFile file = new LCFile("avatar", APKFilePath); await file.Save(); TestContext.WriteLine(file.ObjectId); Assert.NotNull(file.ObjectId); @@ -70,7 +73,7 @@ namespace LeanCloud.Test { public async Task AWS() { Logger.LogDelegate += Utils.Print; LeanCloud.Initialize("UlCpyvLm8aMzQsW6KnP6W3Wt-MdYXbMMI", "PyCTYoNoxCVoKKg394PBeS4r", "https://ulcpyvlm.api.lncldglobal.com"); - LCFile file = new LCFile("avatar", "../../../assets/hello.png"); + LCFile file = new LCFile("avatar", APKFilePath); await file.Save(); TestContext.WriteLine(file.ObjectId); Assert.NotNull(file.ObjectId); diff --git a/Storage/Storage/Internal/File/LCAWSUploader.cs b/Storage/Storage/Internal/File/LCAWSUploader.cs index b1e891e..fba5d99 100644 --- a/Storage/Storage/Internal/File/LCAWSUploader.cs +++ b/Storage/Storage/Internal/File/LCAWSUploader.cs @@ -19,11 +19,13 @@ namespace LeanCloud.Storage.Internal.File { this.data = data; } - internal async Task Upload(Action onProgress) { + internal async Task Upload(Action onProgress) { + LCProgressableStreamContent content = new LCProgressableStreamContent(new ByteArrayContent(data), onProgress); + HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri(uploadUrl), Method = HttpMethod.Put, - Content = new ByteArrayContent(data) + Content = content }; HttpClient client = new HttpClient(); request.Headers.CacheControl = new CacheControlHeaderValue { diff --git a/Storage/Storage/Internal/File/LCProgressableStreamContent.cs b/Storage/Storage/Internal/File/LCProgressableStreamContent.cs new file mode 100644 index 0000000..161c273 --- /dev/null +++ b/Storage/Storage/Internal/File/LCProgressableStreamContent.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace LeanCloud.Storage.Internal.File { + internal class LCProgressableStreamContent : HttpContent { + const int defaultBufferSize = 5 * 4096; + + readonly HttpContent content; + + readonly int bufferSize; + + readonly Action progress; + + internal LCProgressableStreamContent(HttpContent content, Action progress) : this(content, defaultBufferSize, progress) { } + + internal LCProgressableStreamContent(HttpContent content, int bufferSize, Action progress) { + if (content == null) { + throw new ArgumentNullException("content"); + } + if (bufferSize <= 0) { + throw new ArgumentOutOfRangeException("bufferSize"); + } + + this.content = content; + this.bufferSize = bufferSize; + this.progress = progress; + + foreach (var h in content.Headers) { + Headers.Add(h.Key, h.Value); + } + } + + protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) { + + return Task.Run(async () => { + var buffer = new byte[bufferSize]; + TryComputeLength(out long size); + var uploaded = 0; + + using (var sinput = await content.ReadAsStreamAsync()) { + while (true) { + var length = sinput.Read(buffer, 0, buffer.Length); + if (length <= 0) break; + + uploaded += length; + progress?.Invoke(uploaded, size); + + stream.Write(buffer, 0, length); + stream.Flush(); + } + } + stream.Flush(); + }); + } + + protected override bool TryComputeLength(out long length) { + length = content.Headers.ContentLength.GetValueOrDefault(); + return true; + } + + protected override void Dispose(bool disposing) { + if (disposing) { + content.Dispose(); + } + base.Dispose(disposing); + } + } +} diff --git a/Storage/Storage/Internal/File/LCQiniuUploader.cs b/Storage/Storage/Internal/File/LCQiniuUploader.cs index ae700e0..69310ca 100644 --- a/Storage/Storage/Internal/File/LCQiniuUploader.cs +++ b/Storage/Storage/Internal/File/LCQiniuUploader.cs @@ -22,11 +22,14 @@ namespace LeanCloud.Storage.Internal.File { this.data = data; } - internal async Task Upload(Action onProgress) { - MultipartFormDataContent content = new MultipartFormDataContent(); - content.Add(new StringContent(key), "key"); - content.Add(new StringContent(token), "token"); - content.Add(new ByteArrayContent(data), "file"); + internal async Task Upload(Action onProgress) { + MultipartFormDataContent dataContent = new MultipartFormDataContent(); + dataContent.Add(new StringContent(key), "key"); + dataContent.Add(new StringContent(token), "token"); + dataContent.Add(new ByteArrayContent(data), "file"); + + LCProgressableStreamContent content = new LCProgressableStreamContent(dataContent, onProgress); + HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri(uploadUrl), Method = HttpMethod.Post, diff --git a/Storage/Storage/LCFile.cs b/Storage/Storage/LCFile.cs index c32774a..7902e11 100644 --- a/Storage/Storage/LCFile.cs +++ b/Storage/Storage/LCFile.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using LeanCloud.Storage.Internal.File; using LeanCloud.Storage.Internal.Object; +using LeanCloud.Common; namespace LeanCloud.Storage { public class LCFile : LCObject { @@ -67,7 +68,7 @@ namespace LeanCloud.Storage { MetaData[key] = value; } - public async Task Save() { + public async Task Save(Action onProgress = null) { if (!string.IsNullOrEmpty(Url)) { // 外链方式 await base.Save(); @@ -81,11 +82,11 @@ namespace LeanCloud.Storage { if (provider == "s3") { // AWS LCAWSUploader uploader = new LCAWSUploader(uploadUrl, MimeType, data); - await uploader.Upload(null); + await uploader.Upload(onProgress); } else if (provider == "qiniu") { // Qiniu LCQiniuUploader uploader = new LCQiniuUploader(uploadUrl, token, key, data); - await uploader.Upload(null); + await uploader.Upload(onProgress); } else { throw new Exception($"{provider} is not support."); }