chore: 同步修复华东节点上传文件的 bug
parent
ba39a81eb5
commit
991446940a
|
@ -23,7 +23,6 @@ namespace LeanCloud.Storage.Internal {
|
|||
private AVFileController fileController;
|
||||
private AVObjectController objectController;
|
||||
private AVQueryController queryController;
|
||||
private AVSessionController sessionController;
|
||||
private AVUserController userController;
|
||||
private ObjectSubclassingController subclassingController;
|
||||
|
||||
|
@ -43,7 +42,6 @@ namespace LeanCloud.Storage.Internal {
|
|||
CloudCodeController = null;
|
||||
FileController = null;
|
||||
ObjectController = null;
|
||||
SessionController = null;
|
||||
UserController = null;
|
||||
SubclassingController = null;
|
||||
|
||||
|
@ -98,20 +96,8 @@ namespace LeanCloud.Storage.Internal {
|
|||
if (fileController != null) {
|
||||
return fileController;
|
||||
}
|
||||
lock (mutex) {
|
||||
switch (AVClient.CurrentConfiguration.RegionValue) {
|
||||
case 0:
|
||||
fileController = new QiniuFileController();
|
||||
break;
|
||||
case 2:
|
||||
fileController = new QCloudCosFileController();
|
||||
break;
|
||||
case 1:
|
||||
fileController = new AWSS3FileController();
|
||||
break;
|
||||
}
|
||||
return fileController;
|
||||
}
|
||||
fileController = new AVFileController();
|
||||
return fileController;
|
||||
}
|
||||
set {
|
||||
lock (mutex) {
|
||||
|
@ -150,20 +136,6 @@ namespace LeanCloud.Storage.Internal {
|
|||
}
|
||||
}
|
||||
|
||||
public AVSessionController SessionController {
|
||||
get {
|
||||
lock (mutex) {
|
||||
sessionController = sessionController ?? new AVSessionController();
|
||||
return sessionController;
|
||||
}
|
||||
}
|
||||
set {
|
||||
lock (mutex) {
|
||||
sessionController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AVUserController UserController {
|
||||
get {
|
||||
lock (mutex) {
|
||||
|
|
|
@ -8,12 +8,40 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public abstract class AVFileController {
|
||||
public abstract Task<FileState> SaveAsync(FileState state,
|
||||
public interface IFileUploader {
|
||||
Task<FileState> Upload(FileState state, Stream dataStream, IDictionary<string, object> fileToken, IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public class AVFileController {
|
||||
const string QCloud = "qcloud";
|
||||
const string AWS = "s3";
|
||||
|
||||
public Task<FileState> SaveAsync(FileState state,
|
||||
Stream dataStream,
|
||||
String sessionToken,
|
||||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken = default(CancellationToken));
|
||||
CancellationToken cancellationToken = default(CancellationToken)) {
|
||||
if (state.Url != null) {
|
||||
// !isDirty
|
||||
return Task<FileState>.FromResult(state);
|
||||
}
|
||||
|
||||
return GetFileToken(state, cancellationToken).OnSuccess(t => {
|
||||
// 根据 provider 区分 cdn
|
||||
var ret = t.Result;
|
||||
var fileToken = ret.Item2;
|
||||
var provider = fileToken["provider"] as string;
|
||||
switch (provider) {
|
||||
case QCloud:
|
||||
return new QCloudUploader().Upload(state, dataStream, fileToken, progress, cancellationToken);
|
||||
case AWS:
|
||||
return new AWSUploader().Upload(state, dataStream, fileToken, progress, cancellationToken);
|
||||
default:
|
||||
return new QiniuUploader().Upload(state, dataStream, fileToken, progress, cancellationToken);
|
||||
}
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
public Task DeleteAsync(FileState state, string sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand {
|
||||
|
@ -24,7 +52,6 @@ namespace LeanCloud.Storage.Internal {
|
|||
}
|
||||
|
||||
internal Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetFileToken(FileState fileState, CancellationToken cancellationToken) {
|
||||
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> rtn;
|
||||
string currentSessionToken = AVUser.CurrentSessionToken;
|
||||
string str = fileState.Name;
|
||||
IDictionary<string, object> parameters = new Dictionary<string, object>();
|
||||
|
|
|
@ -6,21 +6,14 @@ using System.Collections.Generic;
|
|||
using System.Net.Http;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
internal class AWSS3FileController : AVFileController {
|
||||
public override Task<FileState> SaveAsync(FileState state, Stream dataStream, string sessionToken, IProgress<AVUploadProgressEventArgs> progress, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) {
|
||||
if (state.Url != null) {
|
||||
return Task.FromResult(state);
|
||||
}
|
||||
|
||||
return GetFileToken(state, cancellationToken).OnSuccess(t => {
|
||||
var fileToken = t.Result.Item2;
|
||||
var uploadUrl = fileToken["upload_url"].ToString();
|
||||
state.ObjectId = fileToken["objectId"].ToString();
|
||||
string url = fileToken["url"] as string;
|
||||
state.Url = new Uri(url, UriKind.Absolute);
|
||||
return PutFile(state, uploadUrl, dataStream);
|
||||
|
||||
}).Unwrap().OnSuccess(s => {
|
||||
internal class AWSUploader : IFileUploader {
|
||||
public Task<FileState> Upload(FileState state, Stream dataStream, IDictionary<string, object> fileToken, IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken) {
|
||||
var uploadUrl = fileToken["upload_url"].ToString();
|
||||
state.ObjectId = fileToken["objectId"].ToString();
|
||||
string url = fileToken["url"] as string;
|
||||
state.Url = new Uri(url, UriKind.Absolute);
|
||||
return PutFile(state, uploadUrl, dataStream).OnSuccess(s => {
|
||||
return s.Result;
|
||||
});
|
||||
}
|
|
@ -6,10 +6,9 @@ using System.Net.Http;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
internal class QCloudCosFileController : AVFileController {
|
||||
internal class QCloudUploader : IFileUploader {
|
||||
private object mutex = new object();
|
||||
|
||||
FileState fileState;
|
||||
|
@ -20,33 +19,22 @@ namespace LeanCloud.Storage.Internal {
|
|||
bool done;
|
||||
private long sliceSize = (long)CommonSize.KB512;
|
||||
|
||||
public override Task<FileState> SaveAsync(FileState state,
|
||||
Stream dataStream,
|
||||
string sessionToken,
|
||||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken) {
|
||||
if (state.Url != null) {
|
||||
return Task<FileState>.FromResult(state);
|
||||
}
|
||||
public Task<FileState> Upload(FileState state, Stream dataStream, IDictionary<string, object> fileToken, IProgress<AVUploadProgressEventArgs> progress, CancellationToken cancellationToken) {
|
||||
fileState = state;
|
||||
data = dataStream;
|
||||
return GetFileToken(fileState, cancellationToken).OnSuccess(_ => {
|
||||
var fileToken = _.Result.Item2;
|
||||
uploadUrl = fileToken["upload_url"].ToString();
|
||||
token = fileToken["token"].ToString();
|
||||
fileState.ObjectId = fileToken["objectId"].ToString();
|
||||
bucket = fileToken["bucket"].ToString();
|
||||
|
||||
return FileSlice(cancellationToken).OnSuccess(t => {
|
||||
if (done) return Task<FileState>.FromResult(state);
|
||||
var response = t.Result.Item2;
|
||||
var resumeData = response["data"] as IDictionary<string, object>;
|
||||
if (resumeData.ContainsKey("access_url")) return Task<FileState>.FromResult(state);
|
||||
var sliceSession = resumeData["session"].ToString();
|
||||
var sliceOffset = long.Parse(resumeData["offset"].ToString());
|
||||
return UploadSlice(sliceSession, sliceOffset, dataStream, progress, cancellationToken);
|
||||
}).Unwrap();
|
||||
uploadUrl = fileToken["upload_url"].ToString();
|
||||
token = fileToken["token"].ToString();
|
||||
fileState.ObjectId = fileToken["objectId"].ToString();
|
||||
bucket = fileToken["bucket"].ToString();
|
||||
|
||||
return FileSlice(cancellationToken).OnSuccess(t => {
|
||||
if (done) return Task<FileState>.FromResult(state);
|
||||
var response = t.Result.Item2;
|
||||
var resumeData = response["data"] as IDictionary<string, object>;
|
||||
if (resumeData.ContainsKey("access_url")) return Task<FileState>.FromResult(state);
|
||||
var sliceSession = resumeData["session"].ToString();
|
||||
var sliceOffset = long.Parse(resumeData["offset"].ToString());
|
||||
return UploadSlice(sliceSession, sliceOffset, dataStream, progress, cancellationToken);
|
||||
}).Unwrap();
|
||||
}
|
||||
|
|
@ -9,104 +9,75 @@ using System.Threading.Tasks;
|
|||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
internal enum CommonSize : long
|
||||
{
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
internal enum CommonSize : long {
|
||||
MB4 = 1024 * 1024 * 4,
|
||||
MB1 = 1024 * 1024,
|
||||
KB512 = 1024 * 1024 / 2,
|
||||
KB256 = 1024 * 1024 / 4
|
||||
}
|
||||
|
||||
internal class QiniuFileController : AVFileController
|
||||
{
|
||||
internal class QiniuUploader : IFileUploader {
|
||||
private static int BLOCKSIZE = 1024 * 1024 * 4;
|
||||
private const int blockMashk = (1 << blockBits) - 1;
|
||||
private const int blockBits = 22;
|
||||
private int CalcBlockCount(long fsize)
|
||||
{
|
||||
private int CalcBlockCount(long fsize) {
|
||||
return (int)((fsize + blockMashk) >> blockBits);
|
||||
}
|
||||
internal static string UP_HOST = "https://up.qbox.me";
|
||||
private object mutex = new object();
|
||||
|
||||
public override Task<FileState> SaveAsync(FileState state,
|
||||
Stream dataStream,
|
||||
String sessionToken,
|
||||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (state.Url != null)
|
||||
{
|
||||
return Task<FileState>.FromResult(state);
|
||||
}
|
||||
public Task<FileState> Upload(FileState state, Stream dataStream, IDictionary<string, object> fileToken, IProgress<AVUploadProgressEventArgs> progress, CancellationToken cancellationToken) {
|
||||
state.frozenData = dataStream;
|
||||
state.CloudName = GetUniqueName(state);
|
||||
return GetQiniuToken(state, CancellationToken.None).ContinueWith(t =>
|
||||
{
|
||||
MergeFromJSON(state, t.Result.Item2);
|
||||
return UploadNextChunk(state, dataStream, string.Empty, 0, progress);
|
||||
}).Unwrap().OnSuccess<FileState>(s =>
|
||||
{
|
||||
MergeFromJSON(state, fileToken);
|
||||
return UploadNextChunk(state, dataStream, string.Empty, 0, progress).OnSuccess(_ => {
|
||||
return state;
|
||||
});
|
||||
}
|
||||
Task UploadNextChunk(FileState state, Stream dataStream, string context, long offset, IProgress<AVUploadProgressEventArgs> progress)
|
||||
{
|
||||
|
||||
Task UploadNextChunk(FileState state, Stream dataStream, string context, long offset, IProgress<AVUploadProgressEventArgs> progress) {
|
||||
var totalSize = dataStream.Length;
|
||||
var remainingSize = totalSize - state.completed;
|
||||
|
||||
if (progress != null)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
progress.Report(new AVUploadProgressEventArgs()
|
||||
{
|
||||
if (progress != null) {
|
||||
lock (mutex) {
|
||||
progress.Report(new AVUploadProgressEventArgs() {
|
||||
Progress = AVFileController.CalcProgress(state.completed, totalSize)
|
||||
});
|
||||
}
|
||||
}
|
||||
if (state.completed == totalSize)
|
||||
{
|
||||
if (state.completed == totalSize) {
|
||||
return QiniuMakeFile(state, state.frozenData, state.token, state.CloudName, totalSize, state.block_ctxes.ToArray(), CancellationToken.None);
|
||||
|
||||
}
|
||||
else if (state.completed % BLOCKSIZE == 0)
|
||||
{
|
||||
} else if (state.completed % BLOCKSIZE == 0) {
|
||||
var firstChunkBinary = GetChunkBinary(state.completed, dataStream);
|
||||
|
||||
var blockSize = remainingSize > BLOCKSIZE ? BLOCKSIZE : remainingSize;
|
||||
return MakeBlock(state, firstChunkBinary, blockSize).ContinueWith(t =>
|
||||
{
|
||||
return MakeBlock(state, firstChunkBinary, blockSize).ContinueWith(t => {
|
||||
var dict = JsonConvert.DeserializeObject<IDictionary<string, object>>(t.Result.Item2, new LeanCloudJsonConverter());
|
||||
var ctx = dict["ctx"].ToString();
|
||||
offset = long.Parse(dict["offset"].ToString());
|
||||
var host = dict["host"].ToString();
|
||||
|
||||
state.completed += firstChunkBinary.Length;
|
||||
if (state.completed % BLOCKSIZE == 0 || state.completed == totalSize)
|
||||
{
|
||||
if (state.completed % BLOCKSIZE == 0 || state.completed == totalSize) {
|
||||
state.block_ctxes.Add(ctx);
|
||||
}
|
||||
|
||||
return UploadNextChunk(state, dataStream, ctx, offset, progress);
|
||||
}).Unwrap();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
var chunkBinary = GetChunkBinary(state.completed, dataStream);
|
||||
return PutChunk(state, chunkBinary, context, offset).ContinueWith(t =>
|
||||
{
|
||||
return PutChunk(state, chunkBinary, context, offset).ContinueWith(t => {
|
||||
var dict = JsonConvert.DeserializeObject<IDictionary<string, object>>(t.Result.Item2, new LeanCloudJsonConverter());
|
||||
var ctx = dict["ctx"].ToString();
|
||||
|
||||
offset = long.Parse(dict["offset"].ToString());
|
||||
var host = dict["host"].ToString();
|
||||
state.completed += chunkBinary.Length;
|
||||
if (state.completed % BLOCKSIZE == 0 || state.completed == totalSize)
|
||||
{
|
||||
if (state.completed % BLOCKSIZE == 0 || state.completed == totalSize) {
|
||||
state.block_ctxes.Add(ctx);
|
||||
}
|
||||
//if (AVClient.fileUploaderDebugLog)
|
||||
|
@ -119,11 +90,9 @@ namespace LeanCloud.Storage.Internal
|
|||
}
|
||||
}
|
||||
|
||||
byte[] GetChunkBinary(long completed, Stream dataStream)
|
||||
{
|
||||
byte[] GetChunkBinary(long completed, Stream dataStream) {
|
||||
long chunkSize = (long)CommonSize.MB1;
|
||||
if (completed + chunkSize > dataStream.Length)
|
||||
{
|
||||
if (completed + chunkSize > dataStream.Length) {
|
||||
chunkSize = dataStream.Length - completed;
|
||||
}
|
||||
byte[] chunkBinary = new byte[chunkSize];
|
||||
|
@ -132,16 +101,13 @@ namespace LeanCloud.Storage.Internal
|
|||
return chunkBinary;
|
||||
}
|
||||
|
||||
internal string GetUniqueName(FileState state)
|
||||
{
|
||||
internal string GetUniqueName(FileState state) {
|
||||
string key = Guid.NewGuid().ToString();//file Key in Qiniu.
|
||||
string extension = Path.GetExtension(state.Name);
|
||||
key += extension;
|
||||
return key;
|
||||
}
|
||||
internal Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetQiniuToken(FileState state, CancellationToken cancellationToken)
|
||||
{
|
||||
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> rtn;
|
||||
internal Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetQiniuToken(FileState state, CancellationToken cancellationToken) {
|
||||
string currentSessionToken = AVUser.CurrentSessionToken;
|
||||
string str = state.Name;
|
||||
|
||||
|
@ -162,8 +128,7 @@ namespace LeanCloud.Storage.Internal
|
|||
};
|
||||
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>>();
|
||||
|
||||
string authHead = "UpToken " + state.token;
|
||||
|
@ -212,19 +177,16 @@ namespace LeanCloud.Storage.Internal
|
|||
return await JsonUtils.DeserializeObjectAsync<Tuple<HttpStatusCode, string>>(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) {
|
||||
StringBuilder urlBuilder = new StringBuilder();
|
||||
urlBuilder.AppendFormat("{0}/mkfile/{1}", UP_HOST, fsize);
|
||||
if (key != null)
|
||||
{
|
||||
if (key != null) {
|
||||
urlBuilder.AppendFormat("/key/{0}", ToBase64URLSafe(key));
|
||||
}
|
||||
var metaData = GetMetaData(state, dataStream);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (string _key in metaData.Keys)
|
||||
{
|
||||
foreach (string _key in metaData.Keys) {
|
||||
sb.AppendFormat("/{0}/{1}", _key, ToBase64URLSafe(metaData[_key].ToString()));
|
||||
}
|
||||
urlBuilder.Append(sb.ToString());
|
||||
|
@ -238,12 +200,10 @@ namespace LeanCloud.Storage.Internal
|
|||
int proCount = ctxes.Length;
|
||||
Stream body = new MemoryStream();
|
||||
|
||||
for (int i = 0; i < proCount; i++)
|
||||
{
|
||||
for (int i = 0; i < proCount; i++) {
|
||||
byte[] bctx = StringToAscii(ctxes[i]);
|
||||
body.Write(bctx, 0, bctx.Length);
|
||||
if (i != proCount - 1)
|
||||
{
|
||||
if (i != proCount - 1) {
|
||||
body.WriteByte((byte)',');
|
||||
}
|
||||
}
|
||||
|
@ -263,10 +223,8 @@ namespace LeanCloud.Storage.Internal
|
|||
return await JsonUtils.DeserializeObjectAsync<Tuple<HttpStatusCode, string>>(content);
|
||||
}
|
||||
|
||||
internal void MergeFromJSON(FileState state, IDictionary<string, object> jsonData)
|
||||
{
|
||||
lock (this.mutex)
|
||||
{
|
||||
internal void MergeFromJSON(FileState state, IDictionary<string, object> jsonData) {
|
||||
lock (this.mutex) {
|
||||
string url = jsonData["url"] as string;
|
||||
state.Url = new Uri(url, UriKind.Absolute);
|
||||
state.bucketId = FetchBucketId(url);
|
||||
|
@ -276,17 +234,14 @@ namespace LeanCloud.Storage.Internal
|
|||
}
|
||||
}
|
||||
|
||||
string FetchBucketId(string url)
|
||||
{
|
||||
string FetchBucketId(string url) {
|
||||
var elements = url.Split('/');
|
||||
|
||||
return elements[elements.Length - 1];
|
||||
}
|
||||
public static byte[] StringToAscii(string s)
|
||||
{
|
||||
public static byte[] StringToAscii(string s) {
|
||||
byte[] retval = new byte[s.Length];
|
||||
for (int ix = 0; ix < s.Length; ++ix)
|
||||
{
|
||||
for (int ix = 0; ix < s.Length; ++ix) {
|
||||
char ch = s[ix];
|
||||
if (ch <= 0x7f)
|
||||
retval[ix] = (byte)ch;
|
||||
|
@ -295,20 +250,17 @@ namespace LeanCloud.Storage.Internal
|
|||
}
|
||||
return retval;
|
||||
}
|
||||
public static string ToBase64URLSafe(string str)
|
||||
{
|
||||
public static string ToBase64URLSafe(string str) {
|
||||
return Encode(str);
|
||||
}
|
||||
public static string Encode(byte[] bs)
|
||||
{
|
||||
public static string Encode(byte[] bs) {
|
||||
if (bs == null || bs.Length == 0)
|
||||
return "";
|
||||
string encodedStr = Convert.ToBase64String(bs);
|
||||
encodedStr = encodedStr.Replace('+', '-').Replace('/', '_');
|
||||
return encodedStr;
|
||||
}
|
||||
public static string Encode(string text)
|
||||
{
|
||||
public static string Encode(string text) {
|
||||
if (String.IsNullOrEmpty(text))
|
||||
return "";
|
||||
byte[] bs = Encoding.UTF8.GetBytes(text);
|
||||
|
@ -317,28 +269,23 @@ namespace LeanCloud.Storage.Internal
|
|||
return encodedStr;
|
||||
}
|
||||
|
||||
internal static string GetMD5Code(Stream data)
|
||||
{
|
||||
internal static string GetMD5Code(Stream data) {
|
||||
MD5 md5 = new MD5CryptoServiceProvider();
|
||||
byte[] retVal = md5.ComputeHash(data);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < retVal.Length; i++)
|
||||
{
|
||||
for (int i = 0; i < retVal.Length; i++) {
|
||||
sb.Append(retVal[i].ToString("x2"));
|
||||
}
|
||||
return sb.ToString();
|
||||
|
||||
}
|
||||
|
||||
internal IDictionary<string, object> GetMetaData(FileState state, Stream data)
|
||||
{
|
||||
internal IDictionary<string, object> GetMetaData(FileState state, Stream data) {
|
||||
IDictionary<string, object> rtn = new Dictionary<string, object>();
|
||||
|
||||
if (state.MetaData != null)
|
||||
{
|
||||
foreach (var meta in state.MetaData)
|
||||
{
|
||||
if (state.MetaData != null) {
|
||||
foreach (var meta in state.MetaData) {
|
||||
rtn.Add(meta.Key, meta.Value);
|
||||
}
|
||||
}
|
||||
|
@ -351,14 +298,10 @@ namespace LeanCloud.Storage.Internal
|
|||
|
||||
return rtn;
|
||||
}
|
||||
internal void MergeDic(IDictionary<string, object> dic, string key, object value)
|
||||
{
|
||||
if (dic.ContainsKey(key))
|
||||
{
|
||||
internal void MergeDic(IDictionary<string, object> dic, string key, object value) {
|
||||
if (dic.ContainsKey(key)) {
|
||||
dic[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
dic.Add(key, value);
|
||||
}
|
||||
}
|
|
@ -97,9 +97,7 @@ namespace LeanCloud.Storage.Internal {
|
|||
registerActions.TryGetValue(className, out toPerform);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
if (toPerform != null) {
|
||||
toPerform();
|
||||
}
|
||||
toPerform?.Invoke();
|
||||
}
|
||||
|
||||
public void UnregisterSubclass(Type type) {
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVSessionController {
|
||||
public Task<IObjectState> GetSessionAsync(string sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand {
|
||||
Path = "sessions/me",
|
||||
Method = HttpMethod.Get
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken).OnSuccess(t => {
|
||||
return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance);
|
||||
});
|
||||
}
|
||||
|
||||
public Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand {
|
||||
Path = "logout",
|
||||
Method = HttpMethod.Post
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IObjectState> UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand {
|
||||
Path = "upgradeToRevocableSession",
|
||||
Method = HttpMethod.Post,
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken).OnSuccess(t => {
|
||||
return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance);
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsRevocableSessionToken(string sessionToken) {
|
||||
return sessionToken.Contains("r:");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc.
|
||||
///
|
||||
/// These cannot be 'internal' anymore if we are fully modularizing things out, because
|
||||
/// they are no longer a part of the same library, especially as we create things like
|
||||
/// Installation inside push library.
|
||||
///
|
||||
/// So this class contains a bunch of extension methods that can live inside another
|
||||
/// namespace, which 'wrap' the intenral APIs that already exist.
|
||||
/// </summary>
|
||||
public static class AVSessionExtensions {
|
||||
public static Task<string> UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) {
|
||||
return AVSession.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) {
|
||||
return AVSession.RevokeAsync(sessionToken, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -251,7 +251,6 @@ namespace LeanCloud {
|
|||
|
||||
AVObject.RegisterSubclass<AVUser>();
|
||||
AVObject.RegisterSubclass<AVRole>();
|
||||
AVObject.RegisterSubclass<AVSession>();
|
||||
}
|
||||
|
||||
internal static void Config(Configuration configuration) {
|
||||
|
|
|
@ -350,129 +350,4 @@ namespace LeanCloud {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// AVRPCC loud function base.
|
||||
/// </summary>
|
||||
public class AVRPCCloudFunctionBase<P, R> {
|
||||
/// <summary>
|
||||
/// AVRPCD eserialize.
|
||||
/// </summary>
|
||||
public delegate R AVRPCDeserialize<R>(IDictionary<string, object> result);
|
||||
/// <summary>
|
||||
/// AVRPCS erialize.
|
||||
/// </summary>
|
||||
public delegate IDictionary<string, object> AVRPCSerialize<P>(P parameters);
|
||||
|
||||
public AVRPCCloudFunctionBase()
|
||||
: this(true) {
|
||||
|
||||
}
|
||||
|
||||
public AVRPCCloudFunctionBase(bool noneParameters) {
|
||||
if (noneParameters) {
|
||||
this.Encode = n => {
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private AVRPCDeserialize<R> _decode;
|
||||
public AVRPCDeserialize<R> Decode {
|
||||
get {
|
||||
return _decode;
|
||||
}
|
||||
set {
|
||||
_decode = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private AVRPCSerialize<P> _encode;
|
||||
public AVRPCSerialize<P> Encode {
|
||||
get {
|
||||
if (_encode == null) {
|
||||
_encode = n => {
|
||||
if (n != null) {
|
||||
return JsonConvert.DeserializeObject<Dictionary<string, object>>(n.ToString(), new LeanCloudJsonConverter());
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
return _encode;
|
||||
}
|
||||
set {
|
||||
_encode = value;
|
||||
}
|
||||
|
||||
}
|
||||
public string FunctionName { get; set; }
|
||||
|
||||
public Task<R> ExecuteAsync(P parameters) {
|
||||
var encodedParameters = Encode(parameters);
|
||||
var command = new EngineCommand {
|
||||
Path = $"call/{Uri.EscapeUriString(FunctionName)}",
|
||||
Method = HttpMethod.Post,
|
||||
Content = encodedParameters
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command).OnSuccess(s => {
|
||||
var responseBody = s.Result.Item2;
|
||||
if (!responseBody.ContainsKey("result")) {
|
||||
return default(R);
|
||||
}
|
||||
|
||||
return Decode(responseBody);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class AVObjectRPCCloudFunction : AVObjectRPCCloudFunction<AVObject> {
|
||||
|
||||
}
|
||||
public class AVObjectListRPCCloudFunction : AVObjectListRPCCloudFunction<AVObject> {
|
||||
|
||||
}
|
||||
|
||||
public class AVObjectListRPCCloudFunction<R> : AVRPCCloudFunctionBase<object, IList<R>> where R : AVObject {
|
||||
public AVObjectListRPCCloudFunction()
|
||||
: base(true) {
|
||||
this.Decode = this.AVObjectListDeserializer();
|
||||
}
|
||||
|
||||
public AVRPCDeserialize<IList<R>> AVObjectListDeserializer() {
|
||||
AVRPCDeserialize<IList<R>> del = data => {
|
||||
var items = data["result"] as IList<object>;
|
||||
|
||||
return items.Select(item => {
|
||||
var state = AVObjectCoder.Instance.Decode(item as IDictionary<string, object>, AVDecoder.Instance);
|
||||
return AVObject.FromState<AVObject>(state, state.ClassName);
|
||||
}).ToList() as IList<R>;
|
||||
|
||||
};
|
||||
return del;
|
||||
}
|
||||
}
|
||||
|
||||
public class AVObjectRPCCloudFunction<R> : AVRPCCloudFunctionBase<object, R> where R : AVObject {
|
||||
public AVObjectRPCCloudFunction()
|
||||
: base(true) {
|
||||
this.Decode = this.AVObjectDeserializer();
|
||||
}
|
||||
|
||||
|
||||
public AVRPCDeserialize<R> AVObjectDeserializer() {
|
||||
AVRPCDeserialize<R> del = data => {
|
||||
var item = data["result"] as object;
|
||||
var state = AVObjectCoder.Instance.Decode(item as IDictionary<string, object>, AVDecoder.Instance);
|
||||
|
||||
return AVObject.FromState<R>(state, state.ClassName);
|
||||
};
|
||||
return del;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a session of a user for a LeanCloud application.
|
||||
/// </summary>
|
||||
[AVClassName("_Session")]
|
||||
public class AVSession : AVObject
|
||||
{
|
||||
private static readonly HashSet<string> readOnlyKeys = new HashSet<string> {
|
||||
"sessionToken", "createdWith", "restricted", "user", "expiresAt", "installationId"
|
||||
};
|
||||
|
||||
protected override bool IsKeyMutable(string key)
|
||||
{
|
||||
return !readOnlyKeys.Contains(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the session token for a user, if they are logged in.
|
||||
/// </summary>
|
||||
[AVFieldName("sessionToken")]
|
||||
public string SessionToken
|
||||
{
|
||||
get { return GetProperty<string>(null, "SessionToken"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="AVQuery{AVSession}"/> for AVSession.
|
||||
/// </summary>
|
||||
public static AVQuery<AVSession> Query
|
||||
{
|
||||
get
|
||||
{
|
||||
return new AVQuery<AVSession>();
|
||||
}
|
||||
}
|
||||
|
||||
internal static AVSessionController SessionController
|
||||
{
|
||||
get
|
||||
{
|
||||
return AVPlugins.Instance.SessionController;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current <see cref="AVSession"/> object related to the current user.
|
||||
/// </summary>
|
||||
public static Task<AVSession> GetCurrentSessionAsync()
|
||||
{
|
||||
return GetCurrentSessionAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current <see cref="AVSession"/> object related to the current user.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
public static Task<AVSession> GetCurrentSessionAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
AVUser user = AVUser.CurrentUser;
|
||||
if (user == null) {
|
||||
return Task<AVSession>.FromResult((AVSession)null);
|
||||
}
|
||||
|
||||
string sessionToken = user.SessionToken;
|
||||
if (sessionToken == null) {
|
||||
return Task<AVSession>.FromResult((AVSession)null);
|
||||
}
|
||||
|
||||
return SessionController.GetSessionAsync(sessionToken, cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
AVSession session = AVObject.FromState<AVSession>(t.Result, "_Session");
|
||||
return session;
|
||||
});
|
||||
}
|
||||
|
||||
internal static Task RevokeAsync(string sessionToken, CancellationToken cancellationToken)
|
||||
{
|
||||
if (sessionToken == null || !SessionController.IsRevocableSessionToken(sessionToken))
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
return SessionController.RevokeAsync(sessionToken, cancellationToken);
|
||||
}
|
||||
|
||||
internal static Task<string> UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken)
|
||||
{
|
||||
if (sessionToken == null || SessionController.IsRevocableSessionToken(sessionToken))
|
||||
{
|
||||
return Task<string>.FromResult(sessionToken);
|
||||
}
|
||||
|
||||
return SessionController.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
AVSession session = AVObject.FromState<AVSession>(t.Result, "_Session");
|
||||
return session.SessionToken;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -343,47 +343,12 @@ namespace LeanCloud {
|
|||
/// Typically, you should use <see cref="LogOutAsync()"/>, unless you are managing your own threading.
|
||||
/// </remarks>
|
||||
public static void LogOut() {
|
||||
CurrentUser = null;
|
||||
|
||||
// TODO (hallucinogen): this will without a doubt fail in Unity. But what else can we do?
|
||||
//LogOutAsync().Wait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs out the currently logged in user session. This will remove the session from disk, log out of
|
||||
/// linked services, and future calls to <see cref="CurrentUser"/> will return <c>null</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is preferable to using <see cref="LogOut()"/>, unless your code is already running from a
|
||||
/// background thread.
|
||||
/// </remarks>
|
||||
public static void LogOutAsync() {
|
||||
//return LogOutAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs out the currently logged in user session. This will remove the session from disk, log out of
|
||||
/// linked services, and future calls to <see cref="CurrentUser"/> will return <c>null</c>.
|
||||
///
|
||||
/// This is preferable to using <see cref="LogOut()"/>, unless your code is already running from a
|
||||
/// background thread.
|
||||
/// </summary>
|
||||
public static void LogOutAsync(CancellationToken cancellationToken) {
|
||||
CurrentUser = null;
|
||||
}
|
||||
|
||||
//internal Task LogOutAsync(Task toAwait, CancellationToken cancellationToken) {
|
||||
// string oldSessionToken = SessionToken;
|
||||
// if (oldSessionToken == null) {
|
||||
// return Task.FromResult(0);
|
||||
// }
|
||||
|
||||
// // Cleanup in-memory session.
|
||||
// MutateState(mutableClone => {
|
||||
// mutableClone.ServerData.Remove("sessionToken");
|
||||
// });
|
||||
// var revokeSessionTask = AVSession.RevokeAsync(oldSessionToken, cancellationToken);
|
||||
// return Task.WhenAll(revokeSessionTask, CurrentUserController.LogOutAsync(cancellationToken));
|
||||
//}
|
||||
|
||||
private static void LogOutWithProviders() {
|
||||
foreach (var provider in authProviders.Values) {
|
||||
provider.Deauthenticate();
|
||||
|
@ -391,8 +356,8 @@ namespace LeanCloud {
|
|||
}
|
||||
|
||||
public static AVUser CurrentUser {
|
||||
// TODO 线程问题
|
||||
get; internal set;
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -404,27 +369,6 @@ namespace LeanCloud {
|
|||
}
|
||||
}
|
||||
|
||||
#region Legacy / Revocable Session Tokens
|
||||
|
||||
private static readonly object isRevocableSessionEnabledMutex = new object();
|
||||
private static bool isRevocableSessionEnabled;
|
||||
|
||||
internal static void DisableRevocableSession() {
|
||||
lock (isRevocableSessionEnabledMutex) {
|
||||
isRevocableSessionEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsRevocableSessionEnabled {
|
||||
get {
|
||||
lock (isRevocableSessionEnabledMutex) {
|
||||
return isRevocableSessionEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Requests a password reset email to be sent to the specified email address associated with the
|
||||
/// user account. This email allows the user to securely reset their password on the LeanCloud site.
|
||||
|
@ -803,7 +747,7 @@ namespace LeanCloud {
|
|||
/// <param name="mobilePhoneNumber">注册时使用的手机号</param>
|
||||
/// <returns></returns>
|
||||
public static Task RequestPasswordResetBySmsCode(string mobilePhoneNumber) {
|
||||
return AVUser.RequestPasswordResetBySmsCode(mobilePhoneNumber, null, CancellationToken.None);
|
||||
return RequestPasswordResetBySmsCode(mobilePhoneNumber, null, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -823,7 +767,7 @@ namespace LeanCloud {
|
|||
/// <param name="validateToken">Validate token.</param>
|
||||
/// <returns></returns>
|
||||
public static Task RequestPasswordResetBySmsCode(string mobilePhoneNumber, string validateToken) {
|
||||
return AVUser.RequestPasswordResetBySmsCode(mobilePhoneNumber, validateToken, CancellationToken.None);
|
||||
return RequestPasswordResetBySmsCode(mobilePhoneNumber, validateToken, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -834,9 +778,7 @@ namespace LeanCloud {
|
|||
/// <param name="cancellationToken">cancellationToken</param>
|
||||
/// <returns></returns>
|
||||
public static Task RequestPasswordResetBySmsCode(string mobilePhoneNumber, string validateToken, CancellationToken cancellationToken) {
|
||||
string currentSessionToken = AVUser.CurrentSessionToken;
|
||||
Dictionary<string, object> strs = new Dictionary<string, object>()
|
||||
{
|
||||
Dictionary<string, object> strs = new Dictionary<string, object> {
|
||||
{ "mobilePhoneNumber", mobilePhoneNumber },
|
||||
};
|
||||
if (String.IsNullOrEmpty(validateToken)) {
|
||||
|
@ -868,9 +810,7 @@ namespace LeanCloud {
|
|||
/// <param name="cancellationToken">cancellationToken</param>
|
||||
/// <returns></returns>
|
||||
public static Task ResetPasswordBySmsCodeAsync(string newPassword, string smsCode, CancellationToken cancellationToken) {
|
||||
string currentSessionToken = AVUser.CurrentSessionToken;
|
||||
Dictionary<string, object> strs = new Dictionary<string, object>()
|
||||
{
|
||||
Dictionary<string, object> strs = new Dictionary<string, object> {
|
||||
{ "password", newPassword }
|
||||
};
|
||||
var command = new AVCommand {
|
||||
|
@ -918,7 +858,6 @@ namespace LeanCloud {
|
|||
/// <param name="cancellationToken">CancellationToken</param>
|
||||
/// <returns></returns>
|
||||
public static Task RequestMobilePhoneVerifyAsync(string mobilePhoneNumber, string validateToken, CancellationToken cancellationToken) {
|
||||
string currentSessionToken = AVUser.CurrentSessionToken;
|
||||
Dictionary<string, object> strs = new Dictionary<string, object> {
|
||||
{ "mobilePhoneNumber", mobilePhoneNumber }
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue