diff --git a/Storage/Storage.Test/FileTest.cs b/Storage/Storage.Test/FileTest.cs index d20e104..08ac1b7 100644 --- a/Storage/Storage.Test/FileTest.cs +++ b/Storage/Storage.Test/FileTest.cs @@ -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 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 query = new AVQuery(); - 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(); } } } diff --git a/Storage/Storage/Internal/File/LCAWSUploader.cs b/Storage/Storage/Internal/File/LCAWSUploader.cs index 6f7cf23..b1e891e 100644 --- a/Storage/Storage/Internal/File/LCAWSUploader.cs +++ b/Storage/Storage/Internal/File/LCAWSUploader.cs @@ -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 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; + } } } diff --git a/Storage/Storage/Internal/File/LCMimeTypeMap.cs b/Storage/Storage/Internal/File/LCMimeTypeMap.cs new file mode 100644 index 0000000..99bcce2 --- /dev/null +++ b/Storage/Storage/Internal/File/LCMimeTypeMap.cs @@ -0,0 +1,209 @@ +using System.IO; +using System.Collections.Generic; + +namespace LeanCloud.Storage.Internal.File { + internal static class LCMimeTypeMap { + static readonly Dictionary MIMETypesDictionary = new Dictionary { + { "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"; + } + } + } +} diff --git a/Storage/Storage/Internal/File/LCQiniuUploader.cs b/Storage/Storage/Internal/File/LCQiniuUploader.cs index 4357253..ae700e0 100644 --- a/Storage/Storage/Internal/File/LCQiniuUploader.cs +++ b/Storage/Storage/Internal/File/LCQiniuUploader.cs @@ -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 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; } } } diff --git a/Storage/Storage/Internal_/InstallationId/Controller/InstallationIdController.cs b/Storage/Storage/Internal_/InstallationId/Controller/InstallationIdController.cs index 64c8ad7..b722d4c 100644 --- a/Storage/Storage/Internal_/InstallationId/Controller/InstallationIdController.cs +++ b/Storage/Storage/Internal_/InstallationId/Controller/InstallationIdController.cs @@ -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) { diff --git a/Storage/Storage/LCFile.cs b/Storage/Storage/LCFile.cs index da24ff6..c32774a 100644 --- a/Storage/Storage/LCFile.cs +++ b/Storage/Storage/LCFile.cs @@ -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 MetaData { + get { + return this["metaData"] as Dictionary; + } set { + this["metaData"] = value; + } + } + + readonly byte[] data; + public LCFile() : base(CLASS_NAME) { + MetaData = new Dictionary(); + } + + 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 Save() { + if (!string.IsNullOrEmpty(Url)) { + // 外链方式 + await base.Save(); + } else { + // 上传文件 + Dictionary 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> GetUploadToken() { + Dictionary data = new Dictionary { + { "name", Name }, + { "key", Guid.NewGuid().ToString() }, + { "__type", "File" }, + { "mime_type", MimeType }, + { "metaData", MetaData } + }; + return await LeanCloud.HttpClient.Post>("fileTokens", data: data); + } + + public static LCQuery GetQuery() { + return new LCQuery(CLASS_NAME); } } }