* FileTest.cs:
* AVFile.cs: * FileState.cs: * AVFileController.cs: chore: 简化 File 模块
parent
e6280b828d
commit
db35fa69c2
|
|
@ -28,6 +28,14 @@ namespace LeanCloudTests {
|
|||
TestContext.Out.WriteLine($"file: {file.ObjectId}, {file.Url}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SaveUrl() {
|
||||
AVFile file = new AVFile("test.jpg", "http://pic33.nipic.com/20131007/13639685_123501617185_2.jpg");
|
||||
await file.SaveAsync();
|
||||
Assert.NotNull(file.ObjectId);
|
||||
TestContext.Out.WriteLine($"file: {file.ObjectId}, {file.Url}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Thumbnail() {
|
||||
AVFile file = await AVFile.GetFileWithObjectIdAsync("5d64ac55d5de2b006c1fe3d8");
|
||||
|
|
@ -35,5 +43,14 @@ namespace LeanCloudTests {
|
|||
TestContext.Out.WriteLine($"url: {file.Url}");
|
||||
TestContext.Out.WriteLine($"thumbnail url: {file.GetThumbnailUrl(28, 28)}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeleteFile() {
|
||||
await AVUser.LogInAsync("111111", "111111");
|
||||
AVFile file = new AVFile("hello.png", File.ReadAllBytes("../../../assets/hello.png"));
|
||||
await file.SaveAsync();
|
||||
Assert.NotNull(file.ObjectId);
|
||||
await file.DeleteAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ namespace LeanCloud.Storage.Internal {
|
|||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken = default) {
|
||||
if (state.Url != null) {
|
||||
// !isDirty
|
||||
return Task<FileState>.FromResult(state);
|
||||
return SaveWithUrl(state);
|
||||
}
|
||||
|
||||
return GetFileToken(state, cancellationToken).OnSuccess(t => {
|
||||
|
|
@ -50,14 +49,45 @@ namespace LeanCloud.Storage.Internal {
|
|||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
internal Task<FileState> SaveWithUrl(FileState state) {
|
||||
Dictionary<string, object> strs = new Dictionary<string, object> {
|
||||
{ "url", state.Url.ToString() },
|
||||
{ "name", state.Name },
|
||||
{ "mime_type", state.MimeType },
|
||||
{ "metaData", state.MetaData }
|
||||
};
|
||||
AVCommand cmd = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(state.ObjectId)) {
|
||||
cmd = new AVCommand {
|
||||
Path = $"files/{state.ObjectId}",
|
||||
Method = HttpMethod.Put,
|
||||
Content = strs
|
||||
};
|
||||
} else {
|
||||
cmd = new AVCommand {
|
||||
Path = "files",
|
||||
Method = HttpMethod.Post,
|
||||
Content = strs
|
||||
};
|
||||
}
|
||||
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(cmd).OnSuccess(t => {
|
||||
var result = t.Result.Item2;
|
||||
state.ObjectId = result["objectId"].ToString();
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
internal Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetFileToken(FileState fileState, CancellationToken cancellationToken) {
|
||||
string str = fileState.Name;
|
||||
IDictionary<string, object> parameters = new Dictionary<string, object>();
|
||||
parameters.Add("name", str);
|
||||
parameters.Add("key", GetUniqueName(fileState));
|
||||
parameters.Add("__type", "File");
|
||||
parameters.Add("mime_type", AVFile.GetMIMEType(str));
|
||||
parameters.Add("metaData", fileState.MetaData);
|
||||
IDictionary<string, object> parameters = new Dictionary<string, object> {
|
||||
{ "name", str },
|
||||
{ "key", GetUniqueName(fileState) },
|
||||
{ "__type", "File" },
|
||||
{ "mime_type", AVFile.GetMIMEType(str) },
|
||||
{ "metaData", fileState.MetaData }
|
||||
};
|
||||
|
||||
var command = new AVCommand {
|
||||
Path = "fileTokens",
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class FileState
|
||||
{
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class FileState {
|
||||
public string ObjectId { get; internal set; }
|
||||
public string Name { get; internal set; }
|
||||
public string CloudName { get; set; }
|
||||
|
|
@ -23,5 +21,18 @@ namespace LeanCloud.Storage.Internal
|
|||
public long completed;
|
||||
public List<string> block_ctxes = new List<string>();
|
||||
|
||||
public static FileState NewFromDict(IDictionary<string, object> dict) {
|
||||
FileState state = new FileState {
|
||||
ObjectId = dict["objectId"] as string,
|
||||
Url = new Uri(dict["url"] as string, UriKind.Absolute)
|
||||
};
|
||||
if (dict.TryGetValue("name", out object name)) {
|
||||
state.Name = name as string;
|
||||
}
|
||||
if (dict.TryGetValue("metaData", out object metaData)) {
|
||||
state.MetaData = metaData as Dictionary<string, object>;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,24 +8,9 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// AVFile is a local representation of a file that is saved to the LeanCloud.
|
||||
/// AVFile 存储于 LeanCloud 的文件类
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The workflow is to construct a <see cref="AVFile"/> with data and a filename,
|
||||
/// then save it and set it as a field on a AVObject:
|
||||
///
|
||||
/// <code>
|
||||
/// 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();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public 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();
|
||||
|
|
@ -39,17 +24,13 @@ namespace LeanCloud {
|
|||
/// <param name="mimeType">文件类型</param>
|
||||
/// <param name="metaData">文件源信息</param>
|
||||
public AVFile(string name, Stream data, string mimeType = null, IDictionary<string, object> metaData = null) {
|
||||
mimeType = mimeType == null ? GetMIMEType(name) : mimeType;
|
||||
mimeType = mimeType ?? GetMIMEType(name);
|
||||
state = new FileState {
|
||||
Name = name,
|
||||
MimeType = mimeType,
|
||||
MetaData = metaData
|
||||
};
|
||||
dataStream = data;
|
||||
lock (Mutex) {
|
||||
objectCounter++;
|
||||
state.counter = objectCounter;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -112,7 +93,10 @@ namespace LeanCloud {
|
|||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region created by url or uri
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,Uri,文件类型以及文件源信息
|
||||
/// </summary>
|
||||
|
|
@ -128,11 +112,7 @@ namespace LeanCloud {
|
|||
MetaData = metaData,
|
||||
MimeType = mimeType
|
||||
};
|
||||
lock (Mutex) {
|
||||
objectCounter++;
|
||||
state.counter = objectCounter;
|
||||
}
|
||||
isExternal = true;
|
||||
IsExternal = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -189,13 +169,13 @@ namespace LeanCloud {
|
|||
internal AVFile(FileState filestate) {
|
||||
state = filestate;
|
||||
}
|
||||
|
||||
internal AVFile(string objectId)
|
||||
: this(new FileState() {
|
||||
: this(new FileState {
|
||||
ObjectId = objectId
|
||||
}) {
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -243,6 +223,15 @@ namespace LeanCloud {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取缩略图
|
||||
/// </summary>
|
||||
/// <param name="width">宽</param>
|
||||
/// <param name="height">高</param>
|
||||
/// <param name="quality">质量 1-100</param>
|
||||
/// <param name="scaleToFit">等比缩放,是否裁剪</param>
|
||||
/// <param name="format">格式</param>
|
||||
/// <returns></returns>
|
||||
public string GetThumbnailUrl(int width, int height,
|
||||
int quality = 100, bool scaleToFit = true, string format = "png") {
|
||||
int mode = scaleToFit ? 2 : 1;
|
||||
|
|
@ -263,78 +252,37 @@ namespace LeanCloud {
|
|||
"AVFile must be saved before it can be serialized.");
|
||||
}
|
||||
return new Dictionary<string, object> {
|
||||
{"__type", "File"},
|
||||
{ "__type", "File"} ,
|
||||
{ "id", ObjectId },
|
||||
{"name", Name},
|
||||
{"url", Url.AbsoluteUri}
|
||||
{ "name", Name },
|
||||
{ "url", Url.AbsoluteUri }
|
||||
};
|
||||
}
|
||||
|
||||
#region Save
|
||||
|
||||
/// <summary>
|
||||
/// Saves the file to the LeanCloud cloud.
|
||||
/// 保存文件
|
||||
/// </summary>
|
||||
/// <param name="progress">The progress callback.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public Task SaveAsync(IProgress<AVUploadProgressEventArgs> progress = null,
|
||||
CancellationToken cancellationToken = default) {
|
||||
if (isExternal)
|
||||
return SaveExternal();
|
||||
|
||||
return taskQueue.Enqueue(
|
||||
toAwait => FileController.SaveAsync(state, dataStream, progress, cancellationToken), cancellationToken)
|
||||
public Task SaveAsync(IProgress<AVUploadProgressEventArgs> progress = null, CancellationToken cancellationToken = default) {
|
||||
return FileController.SaveAsync(state, dataStream, progress, cancellationToken)
|
||||
.OnSuccess(t => {
|
||||
state = t.Result;
|
||||
});
|
||||
}
|
||||
|
||||
internal Task SaveExternal() {
|
||||
Dictionary<string, object> strs = new Dictionary<string, object>()
|
||||
{
|
||||
{ "url", Url.ToString() },
|
||||
{ "name", Name },
|
||||
{ "mime_type", MimeType},
|
||||
{ "metaData", MetaData}
|
||||
};
|
||||
AVCommand cmd = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(ObjectId)) {
|
||||
cmd = new AVCommand {
|
||||
Path = $"files/{ObjectId}",
|
||||
Method = HttpMethod.Put,
|
||||
Content = strs
|
||||
};
|
||||
} else {
|
||||
cmd = new AVCommand {
|
||||
Path = "files",
|
||||
Method = HttpMethod.Post,
|
||||
Content = strs
|
||||
};
|
||||
}
|
||||
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(cmd).OnSuccess(t => {
|
||||
var result = t.Result.Item2;
|
||||
state.ObjectId = result["objectId"].ToString();
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Compatible
|
||||
private readonly static Dictionary<string, string> MIMETypesDictionary;
|
||||
private object mutex = new object();
|
||||
private bool isExternal;
|
||||
|
||||
/// <summary>
|
||||
/// 文件在 LeanCloud 的唯一Id 标识
|
||||
/// </summary>
|
||||
public string ObjectId {
|
||||
get {
|
||||
string str;
|
||||
lock (mutex) {
|
||||
str = state.ObjectId;
|
||||
}
|
||||
return str;
|
||||
return state.ObjectId;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -353,13 +301,68 @@ namespace LeanCloud {
|
|||
/// <value>
|
||||
/// </value>
|
||||
public bool IsExternal {
|
||||
get {
|
||||
return isExternal;
|
||||
get; private set;
|
||||
}
|
||||
|
||||
internal static string GetMIMEType(string fileName) {
|
||||
try {
|
||||
string str = Path.GetExtension(fileName).Remove(0, 1);
|
||||
if (!MIMETypesDictionary.ContainsKey(str)) {
|
||||
return "unknown/unknown";
|
||||
}
|
||||
return MIMETypesDictionary[str];
|
||||
} catch {
|
||||
return "unknown/unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static AVFile() {
|
||||
Dictionary<string, string> strs = new Dictionary<string, string> {
|
||||
/// <summary>
|
||||
/// 根据 ObjectId 获取文件
|
||||
/// </summary>
|
||||
/// <remarks>获取之后并没有实际执行下载,只是加载了文件的元信息以及物理地址(Url)
|
||||
/// </remarks>
|
||||
public static Task<AVFile> GetFileWithObjectIdAsync(string objectId, CancellationToken cancellationToken = default) {
|
||||
return FileController.GetAsync(objectId, 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<string, object> metaData) {
|
||||
var fileState = new FileState {
|
||||
Name = name,
|
||||
ObjectId = objectId,
|
||||
Url = new Uri(url),
|
||||
MetaData = metaData
|
||||
};
|
||||
return CreateWithState(fileState);
|
||||
}
|
||||
|
||||
internal void MergeFromJSON(IDictionary<string, object> data) {
|
||||
state = FileState.NewFromDict(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除文件
|
||||
/// </summary>
|
||||
/// <returns>Task</returns>
|
||||
public Task DeleteAsync(CancellationToken cancellationToken = default) {
|
||||
if (ObjectId == null) {
|
||||
return Task.FromException(new ArgumentNullException(nameof(ObjectId)));
|
||||
}
|
||||
return FileController.DeleteAsync(state, cancellationToken);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private readonly static Dictionary<string, string> MIMETypesDictionary = new Dictionary<string, string> {
|
||||
{ "ai", "application/postscript" },
|
||||
{ "aif", "audio/x-aiff" },
|
||||
{ "aifc", "audio/x-aiff" },
|
||||
|
|
@ -547,95 +550,7 @@ namespace LeanCloud {
|
|||
{ "xul", "application/vnd.mozilla.xul+xml" },
|
||||
{ "xwd", "image/x-xwindowdump" },
|
||||
{ "xyz", "chemical/x-xyz" },
|
||||
{ "zip", "application/zip" },
|
||||
{ "zip", "application/zip" }
|
||||
};
|
||||
MIMETypesDictionary = strs;
|
||||
}
|
||||
internal static string GetMIMEType(string fileName) {
|
||||
try {
|
||||
string str = Path.GetExtension(fileName).Remove(0, 1);
|
||||
if (!MIMETypesDictionary.ContainsKey(str)) {
|
||||
return "unknown/unknown";
|
||||
}
|
||||
return MIMETypesDictionary[str];
|
||||
} catch {
|
||||
return "unknown/unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 ObjectId 获取文件
|
||||
/// </summary>
|
||||
/// <remarks>获取之后并没有实际执行下载,只是加载了文件的元信息以及物理地址(Url)
|
||||
/// </remarks>
|
||||
public static Task<AVFile> GetFileWithObjectIdAsync(string objectId, CancellationToken cancellationToken) {
|
||||
return FileController.GetAsync(objectId, 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<string, object> metaData) {
|
||||
var fileState = new FileState {
|
||||
Name = name,
|
||||
ObjectId = objectId,
|
||||
Url = new Uri(url),
|
||||
MetaData = metaData
|
||||
};
|
||||
return CreateWithState(fileState);
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据 ObjectId 获取文件
|
||||
/// </summary>
|
||||
/// <remarks>获取之后并没有实际执行下载,只是加载了文件的元信息以及物理地址(Url)
|
||||
/// </remarks>
|
||||
public static Task<AVFile> GetFileWithObjectIdAsync(string objectId) {
|
||||
return GetFileWithObjectIdAsync(objectId, CancellationToken.None);
|
||||
}
|
||||
|
||||
internal void MergeFromJSON(IDictionary<string, object> jsonData) {
|
||||
lock (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<string, object>;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除文件
|
||||
/// </summary>
|
||||
/// <returns>Task</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
return toAwait.OnSuccess(_ => {
|
||||
return FileController.DeleteAsync(state, cancellationToken);
|
||||
}).Unwrap().OnSuccess(_ => { });
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue