using LeanCloud.Storage.Internal; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; namespace LeanCloud { /// /// AVFile is a local representation of a file that is saved to the LeanCloud. /// /// /// The workflow is to construct a with data and a filename, /// then save it and set it as a field on a AVObject: /// /// /// var file = new AVFile("hello.txt", /// new MemoryStream(Encoding.UTF8.GetBytes("hello"))); /// await file.SaveAsync(); /// var obj = new AVObject("TestObject"); /// obj["file"] = file; /// await obj.SaveAsync(); /// /// public partial class AVFile : IJsonConvertible { internal static int objectCounter = 0; internal static readonly object Mutex = new object(); private FileState state; private readonly Stream dataStream; private readonly TaskQueue taskQueue = new TaskQueue(); #region Constructor /// /// 通过文件名,数据流,文件类型,文件源信息构建一个 AVFile /// /// 文件名 /// 数据流 /// 文件类型 /// 文件源信息 public AVFile(string name, Stream data, string mimeType = null, IDictionary metaData = null) { mimeType = mimeType == null ? GetMIMEType(name) : mimeType; state = new FileState { Name = name, MimeType = mimeType, MetaData = metaData }; this.dataStream = data; lock (Mutex) { objectCounter++; state.counter = objectCounter; } } /// /// 根据文件名,文件 Byte 数组,以及文件类型构建 AVFile /// /// 文件名 /// 文件 Byte 数组 /// 文件类型 public AVFile(string name, byte[] data, string mimeType = null) : this(name, new MemoryStream(data), mimeType) { } /// /// 根据文件名,文件流数据,文件类型构建 AVFile /// /// 文件名 /// 文件流数据 /// 文件类型 public AVFile(string name, Stream data, string mimeType = null) : this(name, data, mimeType, new Dictionary()) { } /// /// 根据 byte 数组以及文件名创建文件 /// /// 文件名 /// 文件的 byte[] 数据 public AVFile(string name, byte[] data) : this(name, new MemoryStream(data), new Dictionary()) { } /// /// 根据文件名,数据 byte[] 数组以及元数据创建文件 /// /// 文件名 /// 文件的 byte[] 数据 /// 元数据 public AVFile(string name, byte[] data, IDictionary metaData) : this(name, new MemoryStream(data), metaData) { } /// /// 根据文件名,数据流以及元数据创建文件 /// /// 文件名 /// 文件的数据流 /// 元数据 public AVFile(string name, Stream data, IDictionary metaData) : this(name, data, GetMIMEType(name), metaData) { } /// /// 根据文件名,数据流以及元数据创建文件 /// /// 文件名 /// 文件的数据流 public AVFile(string name, Stream data) : this(name, data, new Dictionary()) { } #region created by url or uri /// /// 根据文件名,Uri,文件类型以及文件源信息 /// /// 文件名 /// 文件Uri /// 文件类型 /// 文件源信息 public AVFile(string name, Uri uri, string mimeType = null, IDictionary metaData = null) { mimeType = mimeType == null ? GetMIMEType(name) : mimeType; state = new FileState { Name = name, Url = uri, MetaData = metaData, MimeType = mimeType }; lock (Mutex) { objectCounter++; state.counter = objectCounter; } this.isExternal = true; } /// /// 根据文件名,文件 Url,文件类型,文件源信息构建 AVFile /// /// 文件名 /// 文件 Url /// 文件类型 /// 文件源信息 public AVFile(string name, string url, string mimeType = null, IDictionary metaData = null) : this(name, new Uri(url), mimeType, metaData) { } /// /// 根据文件名,文件 Url以及文件的源信息构建 AVFile /// /// 文件名 /// 文件 Url /// 文件源信息 public AVFile(string name, string url, IDictionary metaData) : this(name, url, null, metaData) { } /// /// 根据文件名,文件 Uri,以及文件类型构建 AVFile /// /// 文件名 /// 文件 Uri /// 文件类型 public AVFile(string name, Uri uri, string mimeType = null) : this(name, uri, mimeType, new Dictionary()) { } /// /// 根据文件名以及文件 Uri 构建 AVFile /// /// 文件名 /// 文件 Uri public AVFile(string name, Uri uri) : this(name, uri, null, new Dictionary()) { } /// /// 根据文件名和 Url 创建文件 /// /// 文件名 /// 文件的 Url public AVFile(string name, string url) : this(name, new Uri(url)) { } internal AVFile(FileState filestate) { this.state = filestate; } internal AVFile(string objectId) : this(new FileState() { ObjectId = objectId }) { } #endregion #endregion #region Properties /// /// Gets whether the file still needs to be saved. /// public bool IsDirty { get { return state.Url == null; } } /// /// Gets the name of the file. Before save is called, this is the filename given by /// the user. After save is called, that name gets prefixed with a unique identifier. /// [AVFieldName("name")] public string Name { get { return state.Name; } } /// /// Gets the MIME type of the file. This is either passed in to the constructor or /// inferred from the file extension. "unknown/unknown" will be used if neither is /// available. /// public string MimeType { get { return state.MimeType; } } /// /// Gets the url of the file. It is only available after you save the file or after /// you get the file from a . /// [AVFieldName("url")] public Uri Url { get { return state.Url; } } internal static IAVFileController FileController { get { return AVPlugins.Instance.FileController; } } #endregion IDictionary IJsonConvertible.ToJSON() { if (this.IsDirty) { throw new InvalidOperationException( "AVFile must be saved before it can be serialized."); } return new Dictionary { {"__type", "File"}, { "id", ObjectId }, {"name", Name}, {"url", Url.AbsoluteUri} }; } #region Save /// /// Saves the file to the LeanCloud cloud. /// public Task SaveAsync() { return SaveAsync(null, CancellationToken.None); } /// /// Saves the file to the LeanCloud cloud. /// /// The cancellation token. public Task SaveAsync(CancellationToken cancellationToken) { return SaveAsync(null, cancellationToken); } /// /// Saves the file to the LeanCloud cloud. /// /// The progress callback. public Task SaveAsync(IProgress progress) { return SaveAsync(progress, CancellationToken.None); } /// /// Saves the file to the LeanCloud cloud. /// /// The progress callback. /// The cancellation token. public Task SaveAsync(IProgress progress, CancellationToken cancellationToken) { if (this.isExternal) return this.SaveExternal(); return taskQueue.Enqueue( toAwait => FileController.SaveAsync(state, dataStream, AVUser.CurrentSessionToken, progress, cancellationToken), cancellationToken) .OnSuccess(t => { state = t.Result; }); } internal Task SaveExternal() { Dictionary strs = new Dictionary() { { "url", this.Url.ToString() }, { "name",this.Name }, { "mime_type",this.MimeType}, { "metaData",this.MetaData} }; AVCommand cmd = null; if (!string.IsNullOrEmpty(this.ObjectId)) { cmd = new AVCommand("files/" + this.ObjectId, method: "PUT", sessionToken: AVUser.CurrentSessionToken, data: strs); } else { cmd = new AVCommand("files", method: "POST", sessionToken: AVUser.CurrentSessionToken, data: strs); } return AVPlugins.Instance.CommandRunner.RunCommandAsync(cmd).ContinueWith(t => { var result = t.Result.Item2; this.state.ObjectId = result["objectId"].ToString(); return AVClient.IsSuccessStatusCode(t.Result.Item1); }); } #endregion #region Compatible private readonly static Dictionary MIMETypesDictionary; private object mutex = new object(); private bool isExternal; /// /// 文件在 LeanCloud 的唯一Id 标识 /// public string ObjectId { get { string str; lock (this.mutex) { str = state.ObjectId; } return str; } } /// /// 文件的元数据。 /// public IDictionary MetaData { get { return state.MetaData; } } /// /// 文件是否为外链文件。 /// /// /// public bool IsExternal { get { return isExternal; } } static AVFile() { Dictionary strs = 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" }, }; AVFile.MIMETypesDictionary = strs; } internal static string GetMIMEType(string fileName) { try { string str = Path.GetExtension(fileName).Remove(0, 1); if (!AVFile.MIMETypesDictionary.ContainsKey(str)) { return "unknown/unknown"; } return AVFile.MIMETypesDictionary[str]; } catch { return "unknown/unknown"; } } /// /// 根据 ObjectId 获取文件 /// /// 获取之后并没有实际执行下载,只是加载了文件的元信息以及物理地址(Url) /// public static Task GetFileWithObjectIdAsync(string objectId, CancellationToken cancellationToken) { string currentSessionToken = AVUser.CurrentSessionToken; return FileController.GetAsync(objectId, currentSessionToken, cancellationToken).OnSuccess(_ => { var filestate = _.Result; return new AVFile(filestate); }); } public static AVFile CreateWithoutData(string objectId) { return new AVFile(objectId); } public static AVFile CreateWithState(FileState state) { return new AVFile(state); } public static AVFile CreateWithData(string objectId,string name, string url,IDictionary metaData) { var fileState = new FileState(); fileState.Name = name; fileState.ObjectId = objectId; fileState.Url = new Uri(url); fileState.MetaData = metaData; return CreateWithState(fileState); } /// /// 根据 ObjectId 获取文件 /// /// 获取之后并没有实际执行下载,只是加载了文件的元信息以及物理地址(Url) /// public static Task GetFileWithObjectIdAsync(string objectId) { return GetFileWithObjectIdAsync(objectId, CancellationToken.None); } internal void MergeFromJSON(IDictionary jsonData) { lock (this.mutex) { state.ObjectId = jsonData["objectId"] as string; state.Url = new Uri(jsonData["url"] as string, UriKind.Absolute); if (jsonData.ContainsKey("name")) { state.Name = jsonData["name"] as string; } if (jsonData.ContainsKey("metaData")) { state.MetaData = jsonData["metaData"] as Dictionary; } } } /// /// 删除文件 /// /// Task public Task DeleteAsync() { return DeleteAsync(CancellationToken.None); } internal Task DeleteAsync(CancellationToken cancellationToken) { return taskQueue.Enqueue(toAwait => DeleteAsync(toAwait, cancellationToken), cancellationToken); } internal Task DeleteAsync(Task toAwait, CancellationToken cancellationToken) { if (ObjectId == null) { return Task.FromResult(0); } string sessionToken = AVUser.CurrentSessionToken; return toAwait.OnSuccess(_ => { return FileController.DeleteAsync(state, sessionToken, cancellationToken); }).Unwrap().OnSuccess(_ => { }); } #endregion } }