* FileTest.cs: chore: 完善 File 模块

* Utils.cs:
* AVFile.cs:
* AWSUploader.cs:
* QiniuUploader.cs:
oneRain 2019-08-27 11:52:53 +08:00
parent 15e6a5d9c3
commit b511d832c7
5 changed files with 108 additions and 76 deletions

View File

@ -0,0 +1,31 @@
using NUnit.Framework;
using LeanCloud;
using System.IO;
using System.Threading.Tasks;
namespace LeanCloudTests {
public class FileTest {
[SetUp]
public void SetUp() {
//Utils.InitNorthChina();
Utils.InitEastChina();
//Utils.InitUS();
}
[Test]
public async Task SaveImage() {
AVFile file = new AVFile("hello.png", File.ReadAllBytes("hello.png"));
await file.SaveAsync();
Assert.NotNull(file.ObjectId);
TestContext.Out.WriteLine($"file: {file.ObjectId}, {file.Url}");
}
[Test]
public async Task SaveBigFile() {
AVFile file = new AVFile("test.apk", File.ReadAllBytes("test.apk"));
await file.SaveAsync();
Assert.NotNull(file.ObjectId);
TestContext.Out.WriteLine($"file: {file.ObjectId}, {file.Url}");
}
}
}

View File

@ -0,0 +1,34 @@
using System;
using LeanCloud;
using NUnit.Framework;
namespace LeanCloudTests {
public static class Utils {
public static void InitNorthChina() {
AVClient.Initialize(new AVClient.Configuration {
ApplicationId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz",
ApplicationKey = "pbf6Nk5seyjilexdpyrPwjSp",
ApiServer = "https://avoscloud.com"
});
AVClient.HttpLog(TestContext.Out.WriteLine);
}
public static void InitEastChina() {
AVClient.Initialize(new AVClient.Configuration {
ApplicationId = "4eTwHdYhMaNBUpl1SrTr7GLC-9Nh9j0Va",
ApplicationKey = "GSD6DtdgGWlWolivN4qhWtlE",
ApiServer = "https://4eTwHdYh.api.lncldapi.com"
});
AVClient.HttpLog(TestContext.Out.WriteLine);
}
public static void InitUS() {
AVClient.Initialize(new AVClient.Configuration {
ApplicationId = "MFAS1GnOyomRLSQYRaxdgdPz-MdYXbMMI",
ApplicationKey = "p42JUxdxb95K5G8187t5ba3l",
ApiServer = "https://MFAS1GnO.api.lncldglobal.com"
});
AVClient.HttpLog(TestContext.Out.WriteLine);
}
}
}

View File

@ -4,6 +4,7 @@ using System.Threading;
using System.IO; using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
namespace LeanCloud.Storage.Internal { namespace LeanCloud.Storage.Internal {
internal class AWSUploader : IFileUploader { internal class AWSUploader : IFileUploader {
@ -19,21 +20,15 @@ namespace LeanCloud.Storage.Internal {
} }
internal async Task<FileState> PutFile(FileState state, string uploadUrl, Stream dataStream) { internal async Task<FileState> PutFile(FileState state, string uploadUrl, Stream dataStream) {
IList<KeyValuePair<string, string>> makeBlockHeaders = new List<KeyValuePair<string, string>> {
new KeyValuePair<string, string>("Content-Type", state.MimeType),
new KeyValuePair<string, string>("Cache-Control", "public, max-age=31536000"),
new KeyValuePair<string, string>("Content-Length", dataStream.Length.ToString())
};
HttpClient client = new HttpClient(); HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage { HttpRequestMessage request = new HttpRequestMessage {
RequestUri = new Uri(uploadUrl), RequestUri = new Uri(uploadUrl),
Method = HttpMethod.Put, Method = HttpMethod.Put,
Content = new StreamContent(dataStream) Content = new StreamContent(dataStream)
}; };
foreach (var header in makeBlockHeaders) { request.Headers.Add("Cache-Control", "public, max-age=31536000");
request.Headers.Add(header.Key, header.Value); request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(state.MimeType);
} request.Content.Headers.ContentLength = dataStream.Length;
await client.SendAsync(request); await client.SendAsync(request);
client.Dispose(); client.Dispose();
request.Dispose(); request.Dispose();

View File

@ -1,5 +1,4 @@
using LeanCloud.Storage.Internal; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
@ -7,6 +6,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace LeanCloud.Storage.Internal { namespace LeanCloud.Storage.Internal {
@ -18,14 +18,9 @@ namespace LeanCloud.Storage.Internal {
} }
internal class QiniuUploader : IFileUploader { internal class QiniuUploader : IFileUploader {
private static int BLOCKSIZE = 1024 * 1024 * 4; private static readonly int BLOCKSIZE = 1024 * 1024 * 4;
private const int blockMashk = (1 << blockBits) - 1;
private const int blockBits = 22;
private int CalcBlockCount(long fsize) {
return (int)((fsize + blockMashk) >> blockBits);
}
internal static string UP_HOST = "https://up.qbox.me"; internal static string UP_HOST = "https://up.qbox.me";
private object mutex = new object(); private readonly object mutex = new object();
public Task<FileState> Upload(FileState state, Stream dataStream, IDictionary<string, object> fileToken, IProgress<AVUploadProgressEventArgs> progress, CancellationToken cancellationToken) { public Task<FileState> Upload(FileState state, Stream dataStream, IDictionary<string, object> fileToken, IProgress<AVUploadProgressEventArgs> progress, CancellationToken cancellationToken) {
state.frozenData = dataStream; state.frozenData = dataStream;
@ -49,13 +44,13 @@ namespace LeanCloud.Storage.Internal {
} }
if (state.completed == totalSize) { if (state.completed == totalSize) {
return QiniuMakeFile(state, state.frozenData, state.token, state.CloudName, totalSize, state.block_ctxes.ToArray(), CancellationToken.None); return QiniuMakeFile(state, state.frozenData, state.token, state.CloudName, totalSize, state.block_ctxes.ToArray(), CancellationToken.None);
}
} else if (state.completed % BLOCKSIZE == 0) { if (state.completed % BLOCKSIZE == 0) {
var firstChunkBinary = GetChunkBinary(state.completed, dataStream); var firstChunkBinary = GetChunkBinary(state.completed, dataStream);
var blockSize = remainingSize > BLOCKSIZE ? BLOCKSIZE : remainingSize; var blockSize = remainingSize > BLOCKSIZE ? BLOCKSIZE : remainingSize;
return MakeBlock(state, firstChunkBinary, blockSize).ContinueWith(t => { return MakeBlock(state, firstChunkBinary, blockSize).OnSuccess(t => {
var dict = JsonConvert.DeserializeObject<IDictionary<string, object>>(t.Result.Item2, new LeanCloudJsonConverter()); var dict = t.Result;
var ctx = dict["ctx"].ToString(); var ctx = dict["ctx"].ToString();
offset = long.Parse(dict["offset"].ToString()); offset = long.Parse(dict["offset"].ToString());
var host = dict["host"].ToString(); var host = dict["host"].ToString();
@ -67,11 +62,10 @@ namespace LeanCloud.Storage.Internal {
return UploadNextChunk(state, dataStream, ctx, offset, progress); return UploadNextChunk(state, dataStream, ctx, offset, progress);
}).Unwrap(); }).Unwrap();
}
} else {
var chunkBinary = GetChunkBinary(state.completed, dataStream); var chunkBinary = GetChunkBinary(state.completed, dataStream);
return PutChunk(state, chunkBinary, context, offset).ContinueWith(t => { return PutChunk(state, chunkBinary, context, offset).OnSuccess(t => {
var dict = JsonConvert.DeserializeObject<IDictionary<string, object>>(t.Result.Item2, new LeanCloudJsonConverter()); var dict = t.Result;
var ctx = dict["ctx"].ToString(); var ctx = dict["ctx"].ToString();
offset = long.Parse(dict["offset"].ToString()); offset = long.Parse(dict["offset"].ToString());
@ -80,15 +74,10 @@ namespace LeanCloud.Storage.Internal {
if (state.completed % BLOCKSIZE == 0 || state.completed == totalSize) { if (state.completed % BLOCKSIZE == 0 || state.completed == totalSize) {
state.block_ctxes.Add(ctx); state.block_ctxes.Add(ctx);
} }
//if (AVClient.fileUploaderDebugLog)
//{
// AVClient.LogTracker(state.counter + "|completed=" + state.completed + "stream:position=" + dataStream.Position + "|");
//}
return UploadNextChunk(state, dataStream, ctx, offset, progress); return UploadNextChunk(state, dataStream, ctx, offset, progress);
}).Unwrap(); }).Unwrap();
} }
}
byte[] GetChunkBinary(long completed, Stream dataStream) { byte[] GetChunkBinary(long completed, Stream dataStream) {
long chunkSize = (long)CommonSize.MB1; long chunkSize = (long)CommonSize.MB1;
@ -101,56 +90,37 @@ namespace LeanCloud.Storage.Internal {
return chunkBinary; return chunkBinary;
} }
internal Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetQiniuToken(FileState state, CancellationToken cancellationToken) {
string str = state.Name;
IDictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("name", str);
parameters.Add("key", state.CloudName);
parameters.Add("__type", "File");
parameters.Add("mime_type", AVFile.GetMIMEType(str));
state.MetaData = GetMetaData(state, state.frozenData);
parameters.Add("metaData", state.MetaData);
var command = new AVCommand {
Path = "qiniu",
Method = HttpMethod.Post,
Content = parameters
};
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
}
IList<KeyValuePair<string, string>> GetQiniuRequestHeaders(FileState state) { IList<KeyValuePair<string, string>> GetQiniuRequestHeaders(FileState state) {
IList<KeyValuePair<string, string>> makeBlockHeaders = new List<KeyValuePair<string, string>>(); IList<KeyValuePair<string, string>> makeBlockHeaders = new List<KeyValuePair<string, string>>();
string authHead = "UpToken " + state.token; string authHead = "UpToken " + state.token;
makeBlockHeaders.Add(new KeyValuePair<string, string>("Authorization", authHead)); makeBlockHeaders.Add(new KeyValuePair<string, string>("Authorization", authHead));
return makeBlockHeaders; return makeBlockHeaders;
} }
async Task<Tuple<HttpStatusCode, string>> MakeBlock(FileState state, byte[] firstChunkBinary, long blcokSize = 4194304) { async Task<Dictionary<string, object>> MakeBlock(FileState state, byte[] firstChunkBinary, long blcokSize = 4194304) {
MemoryStream firstChunkData = new MemoryStream(firstChunkBinary, 0, firstChunkBinary.Length); MemoryStream firstChunkData = new MemoryStream(firstChunkBinary, 0, firstChunkBinary.Length);
var headers = GetQiniuRequestHeaders(state);
headers.Add(new KeyValuePair<string, string>("Content-Type", "application/octet-stream"));
var client = new HttpClient(); var client = new HttpClient();
var request = new HttpRequestMessage { var request = new HttpRequestMessage {
RequestUri = new Uri($"{UP_HOST}/mkblk/{blcokSize}"), RequestUri = new Uri($"{UP_HOST}/mkblk/{blcokSize}"),
Method = HttpMethod.Post, Method = HttpMethod.Post,
Content = new StreamContent(firstChunkData) Content = new StreamContent(firstChunkData)
}; };
var headers = GetQiniuRequestHeaders(state);
foreach (var header in headers) { foreach (var header in headers) {
request.Headers.Add(header.Key, header.Value); request.Headers.Add(header.Key, header.Value);
} }
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
var response = await client.SendAsync(request); var response = await client.SendAsync(request);
client.Dispose(); client.Dispose();
request.Dispose(); request.Dispose();
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
response.Dispose(); response.Dispose();
return await JsonUtils.DeserializeObjectAsync<Tuple<HttpStatusCode, string>>(content); return await JsonUtils.DeserializeObjectAsync<Dictionary<string, object>>(content, new LeanCloudJsonConverter());
} }
async Task<Tuple<HttpStatusCode, string>> PutChunk(FileState state, byte[] chunkBinary, string LastChunkctx, long currentChunkOffsetInBlock) { async Task<Dictionary<string, object>> PutChunk(FileState state, byte[] chunkBinary, string LastChunkctx, long currentChunkOffsetInBlock) {
MemoryStream chunkData = new MemoryStream(chunkBinary, 0, chunkBinary.Length); MemoryStream chunkData = new MemoryStream(chunkBinary, 0, chunkBinary.Length);
var client = new HttpClient(); var client = new HttpClient();
var request = new HttpRequestMessage { var request = new HttpRequestMessage {
@ -167,7 +137,7 @@ namespace LeanCloud.Storage.Internal {
request.Dispose(); request.Dispose();
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
response.Dispose(); response.Dispose();
return await JsonUtils.DeserializeObjectAsync<Tuple<HttpStatusCode, string>>(content); return await JsonUtils.DeserializeObjectAsync<Dictionary<string, object>>(content);
} }
internal async Task<Tuple<HttpStatusCode, string>> QiniuMakeFile(FileState state, Stream dataStream, string upToken, string key, long fsize, string[] ctxes, CancellationToken cancellationToken) { internal async Task<Tuple<HttpStatusCode, string>> QiniuMakeFile(FileState state, Stream dataStream, string upToken, string key, long fsize, string[] ctxes, CancellationToken cancellationToken) {
@ -184,12 +154,6 @@ namespace LeanCloud.Storage.Internal {
} }
urlBuilder.Append(sb.ToString()); urlBuilder.Append(sb.ToString());
IList<KeyValuePair<string, string>> headers = new List<KeyValuePair<string, string>>();
//makeBlockDic.Add("Content-Type", "application/octet-stream");
string authHead = "UpToken " + upToken;
headers.Add(new KeyValuePair<string, string>("Authorization", authHead));
headers.Add(new KeyValuePair<string, string>("Content-Type", "text/plain"));
int proCount = ctxes.Length; int proCount = ctxes.Length;
Stream body = new MemoryStream(); Stream body = new MemoryStream();
@ -208,6 +172,9 @@ namespace LeanCloud.Storage.Internal {
Method = HttpMethod.Post, Method = HttpMethod.Post,
Content = new StreamContent(body) Content = new StreamContent(body)
}; };
request.Headers.Add("Authorization", $"UpToken {upToken}");
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("text/plain");
var response = await client.SendAsync(request); var response = await client.SendAsync(request);
client.Dispose(); client.Dispose();
request.Dispose(); request.Dispose();
@ -232,6 +199,7 @@ namespace LeanCloud.Storage.Internal {
return elements[elements.Length - 1]; return elements[elements.Length - 1];
} }
public static byte[] StringToAscii(string s) { public static byte[] StringToAscii(string s) {
byte[] retval = new byte[s.Length]; byte[] retval = new byte[s.Length];
for (int ix = 0; ix < s.Length; ++ix) { for (int ix = 0; ix < s.Length; ++ix) {
@ -243,9 +211,11 @@ namespace LeanCloud.Storage.Internal {
} }
return retval; return retval;
} }
public static string ToBase64URLSafe(string str) { public static string ToBase64URLSafe(string str) {
return Encode(str); return Encode(str);
} }
public static string Encode(byte[] bs) { public static string Encode(byte[] bs) {
if (bs == null || bs.Length == 0) if (bs == null || bs.Length == 0)
return ""; return "";
@ -253,6 +223,7 @@ namespace LeanCloud.Storage.Internal {
encodedStr = encodedStr.Replace('+', '-').Replace('/', '_'); encodedStr = encodedStr.Replace('+', '-').Replace('/', '_');
return encodedStr; return encodedStr;
} }
public static string Encode(string text) { public static string Encode(string text) {
if (String.IsNullOrEmpty(text)) if (String.IsNullOrEmpty(text))
return ""; return "";
@ -291,6 +262,7 @@ namespace LeanCloud.Storage.Internal {
return rtn; return rtn;
} }
internal void MergeDic(IDictionary<string, object> dic, string key, object value) { internal void MergeDic(IDictionary<string, object> dic, string key, object value) {
if (dic.ContainsKey(key)) { if (dic.ContainsKey(key)) {
dic[key] = value; dic[key] = value;

View File

@ -300,7 +300,7 @@ namespace LeanCloud {
return this.SaveExternal(); return this.SaveExternal();
return taskQueue.Enqueue( return taskQueue.Enqueue(
toAwait => FileController.SaveAsync(state, dataStream, AVUser.CurrentUser.SessionToken, progress, cancellationToken), cancellationToken) toAwait => FileController.SaveAsync(state, dataStream, AVUser.CurrentUser?.SessionToken, progress, cancellationToken), cancellationToken)
.OnSuccess(t => { .OnSuccess(t => {
state = t.Result; state = t.Result;
}); });