* FileTest.cs: chore: 完善文件相关功能和测试

* LCFile.cs:
* LCAWSUploader.cs:
* LCMimeTypeMap.cs:
* LCQiniuUploader.cs:
* InstallationIdController.cs:
oneRain 2020-03-02 17:13:02 +08:00
parent 30c0fc2243
commit 4df8179e5a
6 changed files with 468 additions and 45 deletions

View File

@ -1,61 +1,79 @@
using NUnit.Framework;
using LeanCloud;
using System.IO;
using System.Threading;
using System;
using System.Text;
using System.Threading.Tasks;
using LeanCloud.Storage;
namespace LeanCloud.Test {
public class FileTest {
string saveFileId;
static readonly string AvatarFilePath = "../../../assets/hello.png";
[SetUp]
public void SetUp() {
//Utils.InitNorthChina();
//Utils.InitEastChina();
//Utils.InitOldEastChina();
Utils.InitUS();
Logger.LogDelegate += Utils.Print;
LeanCloud.Initialize("ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz", "NUKmuRbdAhg1vrb2wexYo1jo", "https://ikggdre2.lc-cn-n1-shared.com");
}
[Test, Order(0)]
public async Task SaveImage() {
AVFile file = new AVFile("hello.png", File.ReadAllBytes("../../../assets/hello.png"));
await file.SaveAsync();
[TearDown]
public void TearDown() {
Logger.LogDelegate -= Utils.Print;
}
[Test]
public async Task QueryFile() {
LCQuery<LCFile> query = LCFile.GetQuery();
LCFile file = await query.Get("5e0dbfa0562071008e21c142");
Assert.NotNull(file.Url);
TestContext.WriteLine(file.Url);
TestContext.WriteLine(file.GetThumbnailUrl(32, 32));
}
[Test]
public async Task SaveFromPath() {
LCFile file = new LCFile("avatar", AvatarFilePath);
await file.Save();
TestContext.WriteLine(file.ObjectId);
Assert.NotNull(file.ObjectId);
saveFileId = file.ObjectId;
TestContext.Out.WriteLine($"file: {file.ObjectId}, {file.Url}");
}
[Test, Order(1)]
public async Task SaveBigFile() {
AVFile file = new AVFile("test.apk", File.ReadAllBytes("../../../assets/test.apk"));
await file.SaveAsync();
[Test]
public async Task SaveFromMemory() {
string text = "hello, world";
byte[] data = Encoding.UTF8.GetBytes(text);
LCFile file = new LCFile("text", data);
await file.Save();
TestContext.WriteLine(file.ObjectId);
Assert.NotNull(file.ObjectId);
TestContext.Out.WriteLine($"file: {file.ObjectId}, {file.Url}");
}
[Test, Order(2)]
public async Task SaveUrl() {
AVFile file = new AVFile("test.jpg", "http://pic33.nipic.com/20131007/13639685_123501617185_2.jpg");
await file.SaveAsync();
[Test]
public async Task SaveFromUrl() {
LCFile file = new LCFile("scene", new Uri("http://img95.699pic.com/photo/50015/9034.jpg_wh300.jpg"));
file.AddMetaData("size", 1024);
file.AddMetaData("width", 128);
file.AddMetaData("height", 256);
file.MimeType = "image/jpg";
await file.Save();
TestContext.WriteLine(file.ObjectId);
Assert.NotNull(file.ObjectId);
TestContext.Out.WriteLine($"file: {file.ObjectId}, {file.Url}");
}
[Test, Order(3)]
public async Task Thumbnail() {
AVQuery<AVFile> query = new AVQuery<AVFile>();
AVFile file = await query.GetAsync(saveFileId, CancellationToken.None);
Assert.NotNull(file);
TestContext.Out.WriteLine($"url: {file.Url}");
TestContext.Out.WriteLine($"thumbnail url: {file.GetThumbnailUrl(28, 28)}");
}
[Test, Order(4)]
public async Task DeleteFile() {
AVFile file = new AVFile("hello.png", File.ReadAllBytes("../../../assets/hello.png"));
await file.SaveAsync();
[Test]
public async Task Qiniu() {
LCFile file = new LCFile("avatar", AvatarFilePath);
await file.Save();
TestContext.WriteLine(file.ObjectId);
Assert.NotNull(file.ObjectId);
}
[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");
await file.Save();
TestContext.WriteLine(file.ObjectId);
Assert.NotNull(file.ObjectId);
await file.DeleteAsync();
}
}
}

View File

@ -1,7 +1,46 @@
using System;
namespace LeanCloud.Storage.Internal {
public class LCAWSUploader {
public LCAWSUploader() {
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using LeanCloud.Common;
namespace LeanCloud.Storage.Internal.File {
internal class LCAWSUploader {
string uploadUrl;
string mimeType;
byte[] data;
internal LCAWSUploader(string uploadUrl, string mimeType, byte[] data) {
this.uploadUrl = uploadUrl;
this.mimeType = mimeType;
this.data = data;
}
internal async Task Upload(Action<int, int> onProgress) {
HttpRequestMessage request = new HttpRequestMessage {
RequestUri = new Uri(uploadUrl),
Method = HttpMethod.Put,
Content = new ByteArrayContent(data)
};
HttpClient client = new HttpClient();
request.Headers.CacheControl = new CacheControlHeaderValue {
Public = true,
MaxAge = TimeSpan.FromMilliseconds(31536000)
};
request.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
HttpUtils.PrintRequest(client, request);
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
request.Dispose();
string resultString = await response.Content.ReadAsStringAsync();
response.Dispose();
HttpUtils.PrintResponse(response, resultString);
HttpStatusCode statusCode = response.StatusCode;
}
}
}

View File

@ -0,0 +1,209 @@
using System.IO;
using System.Collections.Generic;
namespace LeanCloud.Storage.Internal.File {
internal static class LCMimeTypeMap {
static readonly Dictionary<string, string> MIMETypesDictionary = new Dictionary<string, string> {
{ "ai", "application/postscript" },
{ "aif", "audio/x-aiff" },
{ "aifc", "audio/x-aiff" },
{ "aiff", "audio/x-aiff" },
{ "asc", "text/plain" },
{ "atom", "application/atom+xml" },
{ "au", "audio/basic" },
{ "avi", "video/x-msvideo" },
{ "bcpio", "application/x-bcpio" },
{ "bin", "application/octet-stream" },
{ "bmp", "image/bmp" },
{ "cdf", "application/x-netcdf" },
{ "cgm", "image/cgm" },
{ "class", "application/octet-stream" },
{ "cpio", "application/x-cpio" },
{ "cpt", "application/mac-compactpro" },
{ "csh", "application/x-csh" },
{ "css", "text/css" },
{ "dcr", "application/x-director" },
{ "dif", "video/x-dv" },
{ "dir", "application/x-director" },
{ "djv", "image/vnd.djvu" },
{ "djvu", "image/vnd.djvu" },
{ "dll", "application/octet-stream" },
{ "dmg", "application/octet-stream" },
{ "dms", "application/octet-stream" },
{ "doc", "application/msword" },
{ "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
{ "dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
{ "docm", "application/vnd.ms-word.document.macroEnabled.12" },
{ "dotm", "application/vnd.ms-word.template.macroEnabled.12" },
{ "dtd", "application/xml-dtd" },
{ "dv", "video/x-dv" },
{ "dvi", "application/x-dvi" },
{ "dxr", "application/x-director" },
{ "eps", "application/postscript" },
{ "etx", "text/x-setext" },
{ "exe", "application/octet-stream" },
{ "ez", "application/andrew-inset" },
{ "gif", "image/gif" },
{ "gram", "application/srgs" },
{ "grxml", "application/srgs+xml" },
{ "gtar", "application/x-gtar" },
{ "hdf", "application/x-hdf" },
{ "hqx", "application/mac-binhex40" },
{ "htm", "text/html" },
{ "html", "text/html" },
{ "ice", "x-conference/x-cooltalk" },
{ "ico", "image/x-icon" },
{ "ics", "text/calendar" },
{ "ief", "image/ief" },
{ "ifb", "text/calendar" },
{ "iges", "model/iges" },
{ "igs", "model/iges" },
{ "jnlp", "application/x-java-jnlp-file" },
{ "jp2", "image/jp2" },
{ "jpe", "image/jpeg" },
{ "jpeg", "image/jpeg" },
{ "jpg", "image/jpeg" },
{ "js", "application/x-javascript" },
{ "kar", "audio/midi" },
{ "latex", "application/x-latex" },
{ "lha", "application/octet-stream" },
{ "lzh", "application/octet-stream" },
{ "m3u", "audio/x-mpegurl" },
{ "m4a", "audio/mp4a-latm" },
{ "m4b", "audio/mp4a-latm" },
{ "m4p", "audio/mp4a-latm" },
{ "m4u", "video/vnd.mpegurl" },
{ "m4v", "video/x-m4v" },
{ "mac", "image/x-macpaint" },
{ "man", "application/x-troff-man" },
{ "mathml", "application/mathml+xml" },
{ "me", "application/x-troff-me" },
{ "mesh", "model/mesh" },
{ "mid", "audio/midi" },
{ "midi", "audio/midi" },
{ "mif", "application/vnd.mif" },
{ "mov", "video/quicktime" },
{ "movie", "video/x-sgi-movie" },
{ "mp2", "audio/mpeg" },
{ "mp3", "audio/mpeg" },
{ "mp4", "video/mp4" },
{ "mpe", "video/mpeg" },
{ "mpeg", "video/mpeg" },
{ "mpg", "video/mpeg" },
{ "mpga", "audio/mpeg" },
{ "ms", "application/x-troff-ms" },
{ "msh", "model/mesh" },
{ "mxu", "video/vnd.mpegurl" },
{ "nc", "application/x-netcdf" },
{ "oda", "application/oda" },
{ "ogg", "application/ogg" },
{ "pbm", "image/x-portable-bitmap" },
{ "pct", "image/pict" },
{ "pdb", "chemical/x-pdb" },
{ "pdf", "application/pdf" },
{ "pgm", "image/x-portable-graymap" },
{ "pgn", "application/x-chess-pgn" },
{ "pic", "image/pict" },
{ "pict", "image/pict" },
{ "png", "image/png" },
{ "pnm", "image/x-portable-anymap" },
{ "pnt", "image/x-macpaint" },
{ "pntg", "image/x-macpaint" },
{ "ppm", "image/x-portable-pixmap" },
{ "ppt", "application/vnd.ms-powerpoint" },
{ "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
{ "potx", "application/vnd.openxmlformats-officedocument.presentationml.template" },
{ "ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" },
{ "ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12" },
{ "pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12" },
{ "potm", "application/vnd.ms-powerpoint.template.macroEnabled.12" },
{ "ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" },
{ "ps", "application/postscript" },
{ "qt", "video/quicktime" },
{ "qti", "image/x-quicktime" },
{ "qtif", "image/x-quicktime" },
{ "ra", "audio/x-pn-realaudio" },
{ "ram", "audio/x-pn-realaudio" },
{ "ras", "image/x-cmu-raster" },
{ "rdf", "application/rdf+xml" },
{ "rgb", "image/x-rgb" },
{ "rm", "application/vnd.rn-realmedia" },
{ "roff", "application/x-troff" },
{ "rtf", "text/rtf" },
{ "rtx", "text/richtext" },
{ "sgm", "text/sgml" },
{ "sgml", "text/sgml" },
{ "sh", "application/x-sh" },
{ "shar", "application/x-shar" },
{ "silo", "model/mesh" },
{ "sit", "application/x-stuffit" },
{ "skd", "application/x-koan" },
{ "skm", "application/x-koan" },
{ "skp", "application/x-koan" },
{ "skt", "application/x-koan" },
{ "smi", "application/smil" },
{ "smil", "application/smil" },
{ "snd", "audio/basic" },
{ "so", "application/octet-stream" },
{ "spl", "application/x-futuresplash" },
{ "src", "application/x-wais-Source" },
{ "sv4cpio", "application/x-sv4cpio" },
{ "sv4crc", "application/x-sv4crc" },
{ "svg", "image/svg+xml" },
{ "swf", "application/x-shockwave-flash" },
{ "t", "application/x-troff" },
{ "tar", "application/x-tar" },
{ "tcl", "application/x-tcl" },
{ "tex", "application/x-tex" },
{ "texi", "application/x-texinfo" },
{ "texinfo", "application/x-texinfo" },
{ "tif", "image/tiff" },
{ "tiff", "image/tiff" },
{ "tr", "application/x-troff" },
{ "tsv", "text/tab-separated-values" },
{ "txt", "text/plain" },
{ "ustar", "application/x-ustar" },
{ "vcd", "application/x-cdlink" },
{ "vrml", "model/vrml" },
{ "vxml", "application/voicexml+xml" },
{ "wav", "audio/x-wav" },
{ "wbmp", "image/vnd.wap.wbmp" },
{ "wbmxl", "application/vnd.wap.wbxml" },
{ "wml", "text/vnd.wap.wml" },
{ "wmlc", "application/vnd.wap.wmlc" },
{ "wmls", "text/vnd.wap.wmlscript" },
{ "wmlsc", "application/vnd.wap.wmlscriptc" },
{ "wrl", "model/vrml" },
{ "xbm", "image/x-xbitmap" },
{ "xht", "application/xhtml+xml" },
{ "xhtml", "application/xhtml+xml" },
{ "xls", "application/vnd.ms-excel" },
{ "xml", "application/xml" },
{ "xpm", "image/x-xpixmap" },
{ "xsl", "application/xml" },
{ "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
{ "xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" },
{ "xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12" },
{ "xltm", "application/vnd.ms-excel.template.macroEnabled.12" },
{ "xlam", "application/vnd.ms-excel.addin.macroEnabled.12" },
{ "xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12" },
{ "xslt", "application/xslt+xml" },
{ "xul", "application/vnd.mozilla.xul+xml" },
{ "xwd", "image/x-xwindowdump" },
{ "xyz", "chemical/x-xyz" },
{ "zip", "application/zip" }
};
internal static string GetMimeType(string fileName) {
try {
string suffix = Path.GetExtension(fileName).Substring(1);
if (MIMETypesDictionary.TryGetValue(suffix, out string type)) {
return type;
}
return "unknown/unknown";
} catch {
return "unknown/unknown";
}
}
}
}

View File

@ -1,7 +1,47 @@
using System;
namespace LeanCloud.Storage.Internal {
public class LCQiniuUploader {
public LCQiniuUploader() {
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using LeanCloud.Common;
namespace LeanCloud.Storage.Internal.File {
internal class LCQiniuUploader {
string uploadUrl;
string token;
string key;
byte[] data;
internal LCQiniuUploader(string uploadUrl, string token, string key, byte[] data) {
this.uploadUrl = uploadUrl;
this.token = token;
this.key = key;
this.data = data;
}
internal async Task Upload(Action<int, int> onProgress) {
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(new StringContent(key), "key");
content.Add(new StringContent(token), "token");
content.Add(new ByteArrayContent(data), "file");
HttpRequestMessage request = new HttpRequestMessage {
RequestUri = new Uri(uploadUrl),
Method = HttpMethod.Post,
Content = content
};
HttpClient client = new HttpClient();
HttpUtils.PrintRequest(client, request);
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
request.Dispose();
string resultString = await response.Content.ReadAsStringAsync();
response.Dispose();
HttpUtils.PrintResponse(response, resultString);
HttpStatusCode statusCode = response.StatusCode;
}
}
}

View File

@ -15,7 +15,7 @@ namespace LeanCloud.Storage.Internal {
if (installationId == null) {
string installationPath = "installation.conf";
// 文件读取或从 Native 平台读取
if (File.Exists(installationPath)) {
if (System.IO.File.Exists(installationPath)) {
using (StreamReader reader = new StreamReader(installationPath)) {
installationId = reader.ReadToEnd();
if (installationId != null) {

View File

@ -1,9 +1,126 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Threading.Tasks;
using LeanCloud.Storage.Internal.File;
using LeanCloud.Storage.Internal.Object;
namespace LeanCloud.Storage {
public class LCFile : LCObject {
public const string CLASS_NAME = "_File";
public string Name {
get {
return this["name"] as string;
} set {
this["name"] = value;
}
}
public string MimeType {
get {
return this["mime_type"] as string;
} set {
this["mime_type"] = value;
}
}
public string Url {
get {
return this["url"] as string;
} set {
this["url"] = value;
}
}
public Dictionary<string, object> MetaData {
get {
return this["metaData"] as Dictionary<string, object>;
} set {
this["metaData"] = value;
}
}
readonly byte[] data;
public LCFile() : base(CLASS_NAME) {
MetaData = new Dictionary<string, object>();
}
public LCFile(string name, byte[] bytes) : this() {
Name = name;
data = bytes;
}
public LCFile(string name, string path) : this() {
Name = name;
MimeType = LCMimeTypeMap.GetMimeType(path);
data = File.ReadAllBytes(path);
}
public LCFile(string name, Uri url) : this() {
Name = name;
Url = url.AbsoluteUri;
}
public void AddMetaData(string key, object value) {
MetaData[key] = value;
}
public async Task<LCFile> Save() {
if (!string.IsNullOrEmpty(Url)) {
// 外链方式
await base.Save();
} else {
// 上传文件
Dictionary<string, object> uploadToken = await GetUploadToken();
string uploadUrl = uploadToken["upload_url"] as string;
string key = uploadToken["key"] as string;
string token = uploadToken["token"] as string;
string provider = uploadToken["provider"] as string;
if (provider == "s3") {
// AWS
LCAWSUploader uploader = new LCAWSUploader(uploadUrl, MimeType, data);
await uploader.Upload(null);
} else if (provider == "qiniu") {
// Qiniu
LCQiniuUploader uploader = new LCQiniuUploader(uploadUrl, token, key, data);
await uploader.Upload(null);
} else {
throw new Exception($"{provider} is not support.");
}
LCObjectData objectData = LCObjectData.Decode(uploadToken);
Merge(objectData);
}
return this;
}
public new async Task Delete() {
if (string.IsNullOrEmpty(ObjectId)) {
return;
}
string path = $"files/{ObjectId}";
await LeanCloud.HttpClient.Delete(path);
}
public string GetThumbnailUrl(int width, int height, int quality = 100, bool scaleToFit = true, string format = "png") {
int mode = scaleToFit ? 2 : 1;
return $"{Url}?imageView/{mode}/w/{width}/h/{height}/q/{quality}/format/{format}";
}
async Task<Dictionary<string, object>> GetUploadToken() {
Dictionary<string, object> data = new Dictionary<string, object> {
{ "name", Name },
{ "key", Guid.NewGuid().ToString() },
{ "__type", "File" },
{ "mime_type", MimeType },
{ "metaData", MetaData }
};
return await LeanCloud.HttpClient.Post<Dictionary<string, object>>("fileTokens", data: data);
}
public static LCQuery<LCFile> GetQuery() {
return new LCQuery<LCFile>(CLASS_NAME);
}
}
}