feat: support user persistence

oneRain 2021-04-07 18:07:00 +08:00
parent bead9a78fd
commit de83d22534
22 changed files with 276 additions and 81 deletions

View File

@ -0,0 +1,16 @@
using LeanCloud.Storage.Internal.Storage;
namespace LeanCloud {
public class LCApplication {
public static void Initialize(string appId,
string appKey,
string server = null,
string masterKey = null) {
LCLogger.Debug("Application Initializes on Standard.");
LCInternalApplication.Initialize(appId, appKey, server, masterKey);
LCInternalApplication.StorageController = new StorageController(null);
}
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace LeanCloud.Storage.Internal.Storage {
public class StandardStorage : IStorage {
public string GetStoragePath() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ReleaseVersion>0.7.2</ReleaseVersion>
<AssemblyName>Storage.Standard</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Storage\Storage.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,16 @@
using LeanCloud.Storage.Internal.Storage;
namespace LeanCloud {
public class LCApplication {
public static void Initialize(string appId,
string appKey,
string server = null,
string masterKey = null) {
LCLogger.Debug("Application Initializes on Unity.");
LCInternalApplication.Initialize(appId, appKey, server, masterKey);
LCInternalApplication.StorageController = new StorageController(new UnityStorage());
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ReleaseVersion>0.7.2</ReleaseVersion>
<AssemblyName>Storage.Unity</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Storage.AOT\Storage.AOT.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="UnityEngine">
<HintPath>..\..\Unity\UnityEngine.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
using UnityEngine;
namespace LeanCloud.Storage.Internal.Storage {
public class UnityStorage : IStorage {
public string GetStoragePath() {
return Application.persistentDataPath;
}
}
}

View File

@ -120,7 +120,7 @@ namespace LeanCloud.Storage.Internal.Http {
} }
async Task<string> BuildUrl(string path, Dictionary<string, object> queryParams = null) { async Task<string> BuildUrl(string path, Dictionary<string, object> queryParams = null) {
string apiServer = await LCApplication.AppRouter.GetApiServer(); string apiServer = await LCInternalApplication.AppRouter.GetApiServer();
string url = $"{apiServer}/{apiVersion}/{path}"; string url = $"{apiServer}/{apiVersion}/{path}";
if (queryParams != null) { if (queryParams != null) {
IEnumerable<string> queryPairs = queryParams.Select(kv => $"{kv.Key}={kv.Value}"); IEnumerable<string> queryPairs = queryParams.Select(kv => $"{kv.Key}={kv.Value}");
@ -137,9 +137,9 @@ namespace LeanCloud.Storage.Internal.Http {
headers.Add(kv.Key, kv.Value.ToString()); headers.Add(kv.Key, kv.Value.ToString());
} }
} }
if (LCApplication.UseMasterKey && !string.IsNullOrEmpty(LCApplication.MasterKey)) { if (LCInternalApplication.UseMasterKey && !string.IsNullOrEmpty(LCInternalApplication.MasterKey)) {
// Master Key // Master Key
headers.Add("X-LC-Key", $"{LCApplication.MasterKey},master"); headers.Add("X-LC-Key", $"{LCInternalApplication.MasterKey},master");
} else { } else {
// 签名 // 签名
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
@ -148,8 +148,8 @@ namespace LeanCloud.Storage.Internal.Http {
string sign = $"{hash},{timestamp}"; string sign = $"{hash},{timestamp}";
headers.Add("X-LC-Sign", sign); headers.Add("X-LC-Sign", sign);
} }
if (LCApplication.AdditionalHeaders.Count > 0) { if (LCInternalApplication.AdditionalHeaders.Count > 0) {
foreach (KeyValuePair<string, string> kv in LCApplication.AdditionalHeaders) { foreach (KeyValuePair<string, string> kv in LCInternalApplication.AdditionalHeaders) {
headers.Add(kv.Key, kv.Value); headers.Add(kv.Key, kv.Value);
} }
} }

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Storage {
public interface IStorage {
string GetStoragePath();
}
}

View File

@ -0,0 +1,69 @@
using System;
using System.Reflection;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using System.Text;
using System.Collections.Generic;
using IOFile = System.IO.File;
namespace LeanCloud.Storage.Internal.Storage {
public class StorageController {
private readonly IStorage storage;
public StorageController(IStorage storage) {
this.storage = storage;
}
public async Task WriteText(string filename, string text) {
if (storage == null) {
return;
}
string path = GetFileFullPath(filename);
LCLogger.Debug($"WRITE: {path}");
LCLogger.Debug($"WRITE: {text}");
using (FileStream fs = IOFile.OpenWrite(path)) {
byte[] buffer = Encoding.UTF8.GetBytes(text);
await fs.WriteAsync(buffer, 0, buffer.Length);
}
}
public async Task<string> ReadText(string filename) {
if (storage == null) {
return null;
}
string path = GetFileFullPath(filename);
LCLogger.Debug($"READ: {path}");
if (IOFile.Exists(path)) {
string text;
using (FileStream fs = IOFile.OpenRead(path)) {
byte[] buffer = new byte[fs.Length];
await fs.ReadAsync(buffer, 0, (int)fs.Length);
text = Encoding.UTF8.GetString(buffer);
}
LCLogger.Debug($"READ: {text}");
return text;
}
return null;
}
public void Delete(string filename) {
if (storage == null) {
return;
}
string path = GetFileFullPath(filename);
IOFile.Delete(path);
}
private string GetFileFullPath(string filename) {
if (storage == null) {
throw new Exception("no IStrorage.");
}
return Path.Combine(storage.GetStoragePath(), filename);
}
}
}

View File

@ -33,7 +33,7 @@ namespace LeanCloud.Storage {
{ "width", width }, { "width", width },
{ "height", height } { "height", height }
}; };
Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: queryParams); Dictionary<string, object> response = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: queryParams);
return new LCCapture { return new LCCapture {
Url = response["captcha_url"] as string, Url = response["captcha_url"] as string,
Token = response["captcha_token"] as string Token = response["captcha_token"] as string
@ -60,7 +60,7 @@ namespace LeanCloud.Storage {
{ "captcha_code", code }, { "captcha_code", code },
{ "captcha_token", token } { "captcha_token", token }
}; };
await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data);
} }
} }
} }

View File

@ -30,7 +30,7 @@ namespace LeanCloud.Storage {
{ PRODUCTION_KEY, IsProduction ? 1 : 0 } { PRODUCTION_KEY, IsProduction ? 1 : 0 }
}; };
object encodeParams = LCEncoder.Encode(parameters); object encodeParams = LCEncoder.Encode(parameters);
Dictionary<string, object> response = await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, Dictionary<string, object> response = await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path,
headers: headers, headers: headers,
data: encodeParams); data: encodeParams);
return response; return response;
@ -57,7 +57,7 @@ namespace LeanCloud.Storage {
{ PRODUCTION_KEY, IsProduction ? 1 : 0 } { PRODUCTION_KEY, IsProduction ? 1 : 0 }
}; };
object encodeParams = Encode(parameters); object encodeParams = Encode(parameters);
Dictionary<string, object> response = await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, Dictionary<string, object> response = await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path,
headers: headers, headers: headers,
data: encodeParams); data: encodeParams);
return LCDecoder.Decode(response["result"]); return LCDecoder.Decode(response["result"]);

View File

@ -92,12 +92,12 @@ namespace LeanCloud.Storage {
} }
LCObjectData objectData = LCObjectData.Decode(uploadToken); LCObjectData objectData = LCObjectData.Decode(uploadToken);
Merge(objectData); Merge(objectData);
_ = LCApplication.HttpClient.Post<Dictionary<string, object>>("fileCallback", data: new Dictionary<string, object> { _ = LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("fileCallback", data: new Dictionary<string, object> {
{ "result", true }, { "result", true },
{ "token", token } { "token", token }
}); });
} catch (Exception e) { } catch (Exception e) {
_ = LCApplication.HttpClient.Post<Dictionary<string, object>>("fileCallback", data: new Dictionary<string, object> { _ = LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("fileCallback", data: new Dictionary<string, object> {
{ "result", false }, { "result", false },
{ "token", token } { "token", token }
}); });
@ -112,7 +112,7 @@ namespace LeanCloud.Storage {
return; return;
} }
string path = $"files/{ObjectId}"; string path = $"files/{ObjectId}";
await LCApplication.HttpClient.Delete(path); await LCInternalApplication.HttpClient.Delete(path);
} }
public string GetThumbnailUrl(int width, int height, int quality = 100, bool scaleToFit = true, string format = "png") { public string GetThumbnailUrl(int width, int height, int quality = 100, bool scaleToFit = true, string format = "png") {
@ -128,7 +128,7 @@ namespace LeanCloud.Storage {
{ "mime_type", MimeType }, { "mime_type", MimeType },
{ "metaData", MetaData } { "metaData", MetaData }
}; };
return await LCApplication.HttpClient.Post<Dictionary<string, object>>("fileTokens", data: data); return await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("fileTokens", data: data);
} }
public static LCQuery<LCFile> GetQuery() { public static LCQuery<LCFile> GetQuery() {

View File

@ -19,7 +19,7 @@ namespace LeanCloud.Storage {
if (attributes != null) { if (attributes != null) {
data["friendship"] = attributes; data["friendship"] = attributes;
} }
await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data);
} }
public static async Task AcceptRequest(LCFriendshipRequest request, Dictionary<string, object> attributes = null) { public static async Task AcceptRequest(LCFriendshipRequest request, Dictionary<string, object> attributes = null) {
@ -33,7 +33,7 @@ namespace LeanCloud.Storage {
{ "friendship", attributes } { "friendship", attributes }
}; };
} }
await LCApplication.HttpClient.Put<Dictionary<string, object>>(path, data: data); await LCInternalApplication.HttpClient.Put<Dictionary<string, object>>(path, data: data);
} }
public static async Task DeclineRequest(LCFriendshipRequest request) { public static async Task DeclineRequest(LCFriendshipRequest request) {
@ -41,7 +41,7 @@ namespace LeanCloud.Storage {
throw new ArgumentNullException(nameof(request)); throw new ArgumentNullException(nameof(request));
} }
string path = $"users/friendshipRequests/{request.ObjectId}/decline"; string path = $"users/friendshipRequests/{request.ObjectId}/decline";
await LCApplication.HttpClient.Put<Dictionary<string, object>>(path); await LCInternalApplication.HttpClient.Put<Dictionary<string, object>>(path);
} }
} }
} }

View File

@ -3,12 +3,13 @@ using System.Collections.Generic;
using LeanCloud.Common; using LeanCloud.Common;
using LeanCloud.Storage; using LeanCloud.Storage;
using LeanCloud.Storage.Internal.Http; using LeanCloud.Storage.Internal.Http;
using LeanCloud.Storage.Internal.Storage;
namespace LeanCloud { namespace LeanCloud {
/// <summary> /// <summary>
/// LeanCloud Application /// LeanCloud Application
/// </summary> /// </summary>
public class LCApplication { public class LCInternalApplication {
// SDK 版本号,用于 User-Agent 统计 // SDK 版本号,用于 User-Agent 统计
public const string SDKVersion = "0.7.2"; public const string SDKVersion = "0.7.2";
@ -43,6 +44,10 @@ namespace LeanCloud {
get; set; get; set;
} }
public static StorageController StorageController {
get; set;
}
internal static Dictionary<string, string> AdditionalHeaders { internal static Dictionary<string, string> AdditionalHeaders {
get; get;
} = new Dictionary<string, string>(); } = new Dictionary<string, string>();

View File

@ -304,7 +304,7 @@ namespace LeanCloud.Storage {
{ "requests", LCEncoder.Encode(requestList) } { "requests", LCEncoder.Encode(requestList) }
}; };
List<Dictionary<string, object>> results = await LCApplication.HttpClient.Post<List<Dictionary<string, object>>>("batch", data: data); List<Dictionary<string, object>> results = await LCInternalApplication.HttpClient.Post<List<Dictionary<string, object>>>("batch", data: data);
List<LCObjectData> resultList = results.Select(item => { List<LCObjectData> resultList = results.Select(item => {
if (item.TryGetValue("error", out object error)) { if (item.TryGetValue("error", out object error)) {
Dictionary<string, object> err = error as Dictionary<string, object>; Dictionary<string, object> err = error as Dictionary<string, object>;
@ -342,8 +342,8 @@ namespace LeanCloud.Storage {
queryParams["where"] = query.BuildWhere(); queryParams["where"] = query.BuildWhere();
} }
Dictionary<string, object> response = ObjectId == null ? Dictionary<string, object> response = ObjectId == null ?
await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, data: LCEncoder.Encode(operationDict) as Dictionary<string, object>, queryParams: queryParams) : await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path, data: LCEncoder.Encode(operationDict) as Dictionary<string, object>, queryParams: queryParams) :
await LCApplication.HttpClient.Put<Dictionary<string, object>>(path, data: LCEncoder.Encode(operationDict) as Dictionary<string, object>, queryParams: queryParams); await LCInternalApplication.HttpClient.Put<Dictionary<string, object>>(path, data: LCEncoder.Encode(operationDict) as Dictionary<string, object>, queryParams: queryParams);
LCObjectData data = LCObjectData.Decode(response); LCObjectData data = LCObjectData.Decode(response);
Merge(data); Merge(data);
return this; return this;
@ -368,7 +368,7 @@ namespace LeanCloud.Storage {
return; return;
} }
string path = $"classes/{ClassName}/{ObjectId}"; string path = $"classes/{ClassName}/{ObjectId}";
await LCApplication.HttpClient.Delete(path); await LCInternalApplication.HttpClient.Delete(path);
} }
public static async Task DeleteAll(IEnumerable<LCObject> objects) { public static async Task DeleteAll(IEnumerable<LCObject> objects) {
@ -377,7 +377,7 @@ namespace LeanCloud.Storage {
} }
HashSet<LCObject> objectSet = new HashSet<LCObject>(objects.Where(item => item.ObjectId != null)); HashSet<LCObject> objectSet = new HashSet<LCObject>(objects.Where(item => item.ObjectId != null));
List<Dictionary<string, object>> requestList = objectSet.Select(item => { List<Dictionary<string, object>> requestList = objectSet.Select(item => {
string path = $"/{LCApplication.APIVersion}/classes/{item.ClassName}/{item.ObjectId}"; string path = $"/{LCInternalApplication.APIVersion}/classes/{item.ClassName}/{item.ObjectId}";
return new Dictionary<string, object> { return new Dictionary<string, object> {
{ "path", path }, { "path", path },
{ "method", "DELETE" } { "method", "DELETE" }
@ -386,7 +386,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> data = new Dictionary<string, object> { Dictionary<string, object> data = new Dictionary<string, object> {
{ "requests", LCEncoder.Encode(requestList) } { "requests", LCEncoder.Encode(requestList) }
}; };
await LCApplication.HttpClient.Post<List<object>>("batch", data: data); await LCInternalApplication.HttpClient.Post<List<object>>("batch", data: data);
} }
public async Task<LCObject> Fetch(IEnumerable<string> keys = null, IEnumerable<string> includes = null) { public async Task<LCObject> Fetch(IEnumerable<string> keys = null, IEnumerable<string> includes = null) {
@ -398,7 +398,7 @@ namespace LeanCloud.Storage {
queryParams["include"] = string.Join(",", includes); queryParams["include"] = string.Join(",", includes);
} }
string path = $"classes/{ClassName}/{ObjectId}"; string path = $"classes/{ClassName}/{ObjectId}";
Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: queryParams); Dictionary<string, object> response = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: queryParams);
LCObjectData objectData = LCObjectData.Decode(response); LCObjectData objectData = LCObjectData.Decode(response);
Merge(objectData); Merge(objectData);
return this; return this;
@ -411,7 +411,7 @@ namespace LeanCloud.Storage {
IEnumerable<LCObject> uniqueObjects = objects.Where(item => item.ObjectId != null); IEnumerable<LCObject> uniqueObjects = objects.Where(item => item.ObjectId != null);
List<Dictionary<string, object>> requestList = uniqueObjects.Select(item => { List<Dictionary<string, object>> requestList = uniqueObjects.Select(item => {
string path = $"/{LCApplication.APIVersion}/classes/{item.ClassName}/{item.ObjectId}"; string path = $"/{LCInternalApplication.APIVersion}/classes/{item.ClassName}/{item.ObjectId}";
return new Dictionary<string, object> { return new Dictionary<string, object> {
{ "path", path }, { "path", path },
{ "method", "GET" } { "method", "GET" }
@ -421,7 +421,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> data = new Dictionary<string, object> { Dictionary<string, object> data = new Dictionary<string, object> {
{ "requests", LCEncoder.Encode(requestList) } { "requests", LCEncoder.Encode(requestList) }
}; };
List<Dictionary<string, object>> results = await LCApplication.HttpClient.Post<List<Dictionary<string, object>>>("batch", List<Dictionary<string, object>> results = await LCInternalApplication.HttpClient.Post<List<Dictionary<string, object>>>("batch",
data: data); data: data);
Dictionary<string, LCObjectData> dict = new Dictionary<string, LCObjectData>(); Dictionary<string, LCObjectData> dict = new Dictionary<string, LCObjectData>();
foreach (Dictionary<string, object> item in results) { foreach (Dictionary<string, object> item in results) {
@ -455,7 +455,7 @@ namespace LeanCloud.Storage {
public override string ToString() { public override string ToString() {
Dictionary<string, object> originalData = LCObjectData.Encode(data); Dictionary<string, object> originalData = LCObjectData.Encode(data);
Dictionary<string, object> currentData = estimatedData.Union(originalData.Where(kv => !estimatedData.ContainsKey(kv.Key))) Dictionary<string, object> currentData = estimatedData.Union(originalData.Where(kv => !estimatedData.ContainsKey(kv.Key)))
.ToDictionary(k => k.Key, v => v.Value); .ToDictionary(k => k.Key, v => LCEncoder.Encode(v.Value));
return JsonConvert.SerializeObject(currentData); return JsonConvert.SerializeObject(currentData);
} }

View File

@ -342,7 +342,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> parameters = BuildParams(); Dictionary<string, object> parameters = BuildParams();
parameters["limit"] = 0; parameters["limit"] = 0;
parameters["count"] = 1; parameters["count"] = 1;
Dictionary<string, object> ret = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: parameters); Dictionary<string, object> ret = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: parameters);
return (int)ret["count"]; return (int)ret["count"];
} }
@ -358,14 +358,14 @@ namespace LeanCloud.Storage {
{ "include", includes } { "include", includes }
}; };
} }
Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: queryParams); Dictionary<string, object> response = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: queryParams);
return DecodeLCObject(response); return DecodeLCObject(response);
} }
public async Task<ReadOnlyCollection<T>> Find() { public async Task<ReadOnlyCollection<T>> Find() {
string path = $"classes/{ClassName}"; string path = $"classes/{ClassName}";
Dictionary<string, object> parameters = BuildParams(); Dictionary<string, object> parameters = BuildParams();
Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: parameters); Dictionary<string, object> response = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: parameters);
List<object> results = response["results"] as List<object>; List<object> results = response["results"] as List<object>;
List<T> list = new List<T>(); List<T> list = new List<T>();
foreach (object item in results) { foreach (object item in results) {

View File

@ -43,7 +43,7 @@ namespace LeanCloud.Storage {
data[kv.Key] = kv.Value; data[kv.Key] = kv.Value;
} }
} }
await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data);
} }
/// <summary> /// <summary>
@ -57,7 +57,7 @@ namespace LeanCloud.Storage {
{ "mobilePhoneNumber", mobile }, { "mobilePhoneNumber", mobile },
{ "smsType", "voice" } { "smsType", "voice" }
}; };
await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data);
} }
public static async Task VerifyMobilePhone(string mobile, string code) { public static async Task VerifyMobilePhone(string mobile, string code) {
@ -65,7 +65,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> data = new Dictionary<string, object> { Dictionary<string, object> data = new Dictionary<string, object> {
{ "mobilePhoneNumber", mobile } { "mobilePhoneNumber", mobile }
}; };
await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data);
} }
} }
} }

View File

@ -109,7 +109,7 @@ namespace LeanCloud.Storage {
} }
formData["query"] = queryData; formData["query"] = queryData;
} }
Dictionary<string, object> response = await LCApplication.HttpClient.Post<Dictionary<string, object>>("statuses", Dictionary<string, object> response = await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("statuses",
data: formData); data: formData);
LCObjectData objectData = LCObjectData.Decode(response); LCObjectData objectData = LCObjectData.Decode(response);
Merge(objectData); Merge(objectData);
@ -125,14 +125,14 @@ namespace LeanCloud.Storage {
LCUser source = (Data[SourceKey] ?? this[SourceKey]) as LCUser; LCUser source = (Data[SourceKey] ?? this[SourceKey]) as LCUser;
if (source != null && source.ObjectId == user.ObjectId) { if (source != null && source.ObjectId == user.ObjectId) {
await LCApplication.HttpClient.Delete($"statuses/{ObjectId}"); await LCInternalApplication.HttpClient.Delete($"statuses/{ObjectId}");
} else { } else {
Dictionary<string, object> data = new Dictionary<string, object> { Dictionary<string, object> data = new Dictionary<string, object> {
{ OwnerKey, JsonConvert.SerializeObject(LCEncoder.Encode(user)) }, { OwnerKey, JsonConvert.SerializeObject(LCEncoder.Encode(user)) },
{ InboxTypeKey, InboxType }, { InboxTypeKey, InboxType },
{ MessageIdKey, MessageId } { MessageIdKey, MessageId }
}; };
await LCApplication.HttpClient.Delete("subscribe/statuses/inbox", queryParams: data); await LCInternalApplication.HttpClient.Delete("subscribe/statuses/inbox", queryParams: data);
} }
} }
@ -148,7 +148,7 @@ namespace LeanCloud.Storage {
if (!string.IsNullOrEmpty(inboxType)) { if (!string.IsNullOrEmpty(inboxType)) {
queryParams[InboxTypeKey] = inboxType; queryParams[InboxTypeKey] = inboxType;
} }
Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>("subscribe/statuses/count", Dictionary<string, object> response = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>("subscribe/statuses/count",
queryParams: queryParams); queryParams: queryParams);
LCStatusCount statusCount = new LCStatusCount { LCStatusCount statusCount = new LCStatusCount {
Total = (int)response["total"], Total = (int)response["total"],
@ -169,7 +169,7 @@ namespace LeanCloud.Storage {
if (!string.IsNullOrEmpty(inboxType)) { if (!string.IsNullOrEmpty(inboxType)) {
queryParams[InboxTypeKey] = inboxType; queryParams[InboxTypeKey] = inboxType;
} }
await LCApplication.HttpClient.Post<Dictionary<string, object>>("subscribe/statuses/resetUnreadCount", await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("subscribe/statuses/resetUnreadCount",
queryParams:queryParams); queryParams:queryParams);
} }
} }

View File

@ -41,7 +41,7 @@ namespace LeanCloud.Storage {
{ "maxId", MaxId }, { "maxId", MaxId },
{ "limit", Condition.Limit } { "limit", Condition.Limit }
}; };
Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>("subscribe/statuses", Dictionary<string, object> response = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>("subscribe/statuses",
queryParams: queryParams); queryParams: queryParams);
List<object> results = response["results"] as List<object>; List<object> results = response["results"] as List<object>;
List<LCStatus> statuses = new List<LCStatus>(); List<LCStatus> statuses = new List<LCStatus>();

View File

@ -8,6 +8,8 @@ namespace LeanCloud.Storage {
public class LCUser : LCObject { public class LCUser : LCObject {
public const string CLASS_NAME = "_User"; public const string CLASS_NAME = "_User";
private const string USER_DATA = ".userdata";
public string Username { public string Username {
get { get {
return this["username"] as string; return this["username"] as string;
@ -76,10 +78,21 @@ namespace LeanCloud.Storage {
static LCUser currentUser; static LCUser currentUser;
public static Task<LCUser> GetCurrent() { public static async Task<LCUser> GetCurrent() {
// TODO 加载持久化数据 if (currentUser != null) {
return currentUser;
}
return Task.FromResult(currentUser); string data = await LCInternalApplication.StorageController.ReadText(USER_DATA);
if (!string.IsNullOrEmpty(data)) {
try {
currentUser = ParseObject(data) as LCUser;
} catch (Exception e) {
LCLogger.Error(e);
LCInternalApplication.StorageController.Delete(USER_DATA);
}
}
return currentUser;
} }
public LCUser() : base(CLASS_NAME) { public LCUser() : base(CLASS_NAME) {
@ -106,7 +119,8 @@ namespace LeanCloud.Storage {
} }
await Save(); await Save();
currentUser = this; currentUser = this;
// TODO Persistence
await SaveToLocal();
return this; return this;
} }
@ -123,7 +137,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> data = new Dictionary<string, object> { Dictionary<string, object> data = new Dictionary<string, object> {
{ "mobilePhoneNumber", mobile } { "mobilePhoneNumber", mobile }
}; };
await LCApplication.HttpClient.Post<Dictionary<string, object>>("requestLoginSmsCode", data: data); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("requestLoginSmsCode", data: data);
} }
/// <summary> /// <summary>
@ -143,9 +157,12 @@ namespace LeanCloud.Storage {
{ "mobilePhoneNumber", mobile }, { "mobilePhoneNumber", mobile },
{ "smsCode", code } { "smsCode", code }
}; };
Dictionary<string, object> response = await LCApplication.HttpClient.Post<Dictionary<string, object>>("usersByMobilePhone", data: data); Dictionary<string, object> response = await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("usersByMobilePhone", data: data);
LCObjectData objectData = LCObjectData.Decode(response); LCObjectData objectData = LCObjectData.Decode(response);
currentUser = new LCUser(objectData); currentUser = new LCUser(objectData);
await SaveToLocal();
return currentUser; return currentUser;
} }
@ -353,7 +370,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> data = new Dictionary<string, object> { Dictionary<string, object> data = new Dictionary<string, object> {
{ "email", email } { "email", email }
}; };
await LCApplication.HttpClient.Post<Dictionary<string, object>>("requestEmailVerify", data: data); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("requestEmailVerify", data: data);
} }
/// <summary> /// <summary>
@ -368,7 +385,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> data = new Dictionary<string, object> { Dictionary<string, object> data = new Dictionary<string, object> {
{ "mobilePhoneNumber", mobile } { "mobilePhoneNumber", mobile }
}; };
await LCApplication.HttpClient.Post<Dictionary<string, object>>("requestMobilePhoneVerify", data: data); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("requestMobilePhoneVerify", data: data);
} }
/// <summary> /// <summary>
@ -388,7 +405,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> data = new Dictionary<string, object> { Dictionary<string, object> data = new Dictionary<string, object> {
{ "mobilePhoneNumber", mobile } { "mobilePhoneNumber", mobile }
}; };
await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data);
} }
/// <summary> /// <summary>
@ -403,7 +420,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> headers = new Dictionary<string, object> { Dictionary<string, object> headers = new Dictionary<string, object> {
{ "X-LC-Session", sessionToken } { "X-LC-Session", sessionToken }
}; };
Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>("users/me", Dictionary<string, object> response = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>("users/me",
headers: headers); headers: headers);
LCObjectData objectData = LCObjectData.Decode(response); LCObjectData objectData = LCObjectData.Decode(response);
currentUser = new LCUser(objectData); currentUser = new LCUser(objectData);
@ -422,7 +439,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> data = new Dictionary<string, object> { Dictionary<string, object> data = new Dictionary<string, object> {
{ "email", email } { "email", email }
}; };
await LCApplication.HttpClient.Post<Dictionary<string, object>>("requestPasswordReset", await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("requestPasswordReset",
data: data); data: data);
} }
@ -438,7 +455,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> data = new Dictionary<string, object> { Dictionary<string, object> data = new Dictionary<string, object> {
{ "mobilePhoneNumber", mobile } { "mobilePhoneNumber", mobile }
}; };
await LCApplication.HttpClient.Post<Dictionary<string, object>>("requestPasswordResetBySmsCode", await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("requestPasswordResetBySmsCode",
data: data); data: data);
} }
@ -463,7 +480,7 @@ namespace LeanCloud.Storage {
{ "mobilePhoneNumber", mobile }, { "mobilePhoneNumber", mobile },
{ "password", newPassword } { "password", newPassword }
}; };
await LCApplication.HttpClient.Put<Dictionary<string, object>>($"resetPasswordBySmsCode/{code}", await LCInternalApplication.HttpClient.Put<Dictionary<string, object>>($"resetPasswordBySmsCode/{code}",
data: data); data: data);
} }
@ -484,7 +501,7 @@ namespace LeanCloud.Storage {
{ "old_password", oldPassword }, { "old_password", oldPassword },
{ "new_password", newPassword } { "new_password", newPassword }
}; };
Dictionary<string, object> response = await LCApplication.HttpClient.Put<Dictionary<string, object>>( Dictionary<string, object> response = await LCInternalApplication.HttpClient.Put<Dictionary<string, object>>(
$"users/{ObjectId}/updatePassword", data:data); $"users/{ObjectId}/updatePassword", data:data);
LCObjectData objectData = LCObjectData.Decode(response); LCObjectData objectData = LCObjectData.Decode(response);
Merge(objectData); Merge(objectData);
@ -495,8 +512,8 @@ namespace LeanCloud.Storage {
/// </summary> /// </summary>
public static Task Logout() { public static Task Logout() {
currentUser = null; currentUser = null;
// TODO 清理持久化数据 // 清理持久化数据
LCInternalApplication.StorageController.Delete(USER_DATA);
return Task.FromResult<object>(null); return Task.FromResult<object>(null);
} }
@ -509,7 +526,7 @@ namespace LeanCloud.Storage {
return false; return false;
} }
try { try {
await LCApplication.HttpClient.Get<Dictionary<string, object>>("users/me"); await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>("users/me");
return true; return true;
} catch (Exception) { } catch (Exception) {
return false; return false;
@ -532,7 +549,7 @@ namespace LeanCloud.Storage {
try { try {
await Save(); await Save();
oriAuthData[authType] = data; oriAuthData[authType] = data;
UpdateAuthData(oriAuthData); await UpdateAuthData(oriAuthData);
} catch (Exception e) { } catch (Exception e) {
AuthData = oriAuthData; AuthData = oriAuthData;
throw e; throw e;
@ -547,23 +564,27 @@ namespace LeanCloud.Storage {
try { try {
await Save(); await Save();
oriAuthData.Remove(authType); oriAuthData.Remove(authType);
UpdateAuthData(oriAuthData); await UpdateAuthData(oriAuthData);
} catch (Exception e) { } catch (Exception e) {
AuthData = oriAuthData; AuthData = oriAuthData;
throw e; throw e;
} }
} }
private void UpdateAuthData(Dictionary<string, object> authData) { private async Task UpdateAuthData(Dictionary<string, object> authData) {
LCObjectData objData = new LCObjectData(); LCObjectData objData = new LCObjectData();
objData.CustomPropertyDict["authData"] = authData; objData.CustomPropertyDict["authData"] = authData;
Merge(objData); Merge(objData);
await SaveToLocal();
} }
static async Task<LCUser> Login(Dictionary<string, object> data) { static async Task<LCUser> Login(Dictionary<string, object> data) {
Dictionary<string, object> response = await LCApplication.HttpClient.Post<Dictionary<string, object>>("login", data: data); Dictionary<string, object> response = await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>("login", data: data);
LCObjectData objectData = LCObjectData.Decode(response); LCObjectData objectData = LCObjectData.Decode(response);
currentUser = new LCUser(objectData); currentUser = new LCUser(objectData);
await SaveToLocal();
return currentUser; return currentUser;
} }
@ -572,11 +593,14 @@ namespace LeanCloud.Storage {
{ authType, data } { authType, data }
}; };
string path = failOnNotExist ? "users?failOnNotExist=true" : "users"; string path = failOnNotExist ? "users?failOnNotExist=true" : "users";
Dictionary<string, object> response = await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, data: new Dictionary<string, object> { Dictionary<string, object> response = await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path, data: new Dictionary<string, object> {
{ "authData", authData } { "authData", authData }
}); });
LCObjectData objectData = LCObjectData.Decode(response); LCObjectData objectData = LCObjectData.Decode(response);
currentUser = new LCUser(objectData); currentUser = new LCUser(objectData);
await SaveToLocal();
return currentUser; return currentUser;
} }
@ -586,6 +610,15 @@ namespace LeanCloud.Storage {
authData["unionid"] = unionId; authData["unionid"] = unionId;
} }
private static async Task SaveToLocal() {
try {
string json = currentUser.ToString();
await LCInternalApplication.StorageController.WriteText(USER_DATA, json);
} catch (Exception e) {
LCLogger.Error(e.Message);
}
}
/// <summary> /// <summary>
/// Requests an SMS code for updating phone number. /// Requests an SMS code for updating phone number.
/// </summary> /// </summary>
@ -602,7 +635,7 @@ namespace LeanCloud.Storage {
if (!string.IsNullOrEmpty(captchaToken)) { if (!string.IsNullOrEmpty(captchaToken)) {
data["validate_token"] = captchaToken; data["validate_token"] = captchaToken;
} }
await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data);
} }
/// <summary> /// <summary>
@ -617,7 +650,7 @@ namespace LeanCloud.Storage {
{ "mobilePhoneNumber", mobile }, { "mobilePhoneNumber", mobile },
{ "code", code } { "code", code }
}; };
await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path, data: data);
} }
/// <summary> /// <summary>
@ -631,7 +664,7 @@ namespace LeanCloud.Storage {
throw new ArgumentNullException(nameof(targetId)); throw new ArgumentNullException(nameof(targetId));
} }
string path = $"users/self/friendship/{targetId}"; string path = $"users/self/friendship/{targetId}";
await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, data: attrs); await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path, data: attrs);
} }
/// <summary> /// <summary>
@ -644,7 +677,7 @@ namespace LeanCloud.Storage {
throw new ArgumentNullException(nameof(targetId)); throw new ArgumentNullException(nameof(targetId));
} }
string path = $"users/self/friendship/{targetId}"; string path = $"users/self/friendship/{targetId}";
await LCApplication.HttpClient.Delete(path); await LCInternalApplication.HttpClient.Delete(path);
} }
/// <summary> /// <summary>
@ -684,7 +717,7 @@ namespace LeanCloud.Storage {
queryParams["include"] = string.Join(",", includes); queryParams["include"] = string.Join(",", includes);
} }
string path = $"users/{ObjectId}/followersAndFollowees"; string path = $"users/{ObjectId}/followersAndFollowees";
Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path, Dictionary<string, object> response = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>(path,
queryParams: queryParams); queryParams: queryParams);
LCFollowersAndFollowees result = new LCFollowersAndFollowees(); LCFollowersAndFollowees result = new LCFollowersAndFollowees();
if (response.TryGetValue("followers", out object followersObj) && if (response.TryGetValue("followers", out object followersObj) &&

View File

@ -68,7 +68,7 @@ namespace LeanCloud.Storage {
{ "updateStrategy", updateStrategy.ToString().ToLower() }, { "updateStrategy", updateStrategy.ToString().ToLower() },
}; };
string path = "leaderboard/leaderboards"; string path = "leaderboard/leaderboards";
Dictionary<string, object> result = await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, Dictionary<string, object> result = await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path,
data:data); data:data);
LCLeaderboard leaderboard = new LCLeaderboard(); LCLeaderboard leaderboard = new LCLeaderboard();
leaderboard.Merge(result); leaderboard.Merge(result);
@ -112,7 +112,7 @@ namespace LeanCloud.Storage {
if (overwrite) { if (overwrite) {
path = $"{path}?overwrite=1"; path = $"{path}?overwrite=1";
} }
Dictionary<string, object> result = await LCApplication.HttpClient.Post<Dictionary<string, object>>(path, Dictionary<string, object> result = await LCInternalApplication.HttpClient.Post<Dictionary<string, object>>(path,
data: data); data: data);
if (result.TryGetValue("results", out object results) && if (result.TryGetValue("results", out object results) &&
results is List<object> list) { results is List<object> list) {
@ -137,7 +137,7 @@ namespace LeanCloud.Storage {
string names = string.Join(",", statisticNames); string names = string.Join(",", statisticNames);
path = $"{path}?statistics={names}"; path = $"{path}?statistics={names}";
} }
Dictionary<string, object> result = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path); Dictionary<string, object> result = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>(path);
if (result.TryGetValue("results", out object results) && if (result.TryGetValue("results", out object results) &&
results is List<object> list) { results is List<object> list) {
List<LCStatistic> statistics = new List<LCStatistic>(); List<LCStatistic> statistics = new List<LCStatistic>();
@ -161,7 +161,7 @@ namespace LeanCloud.Storage {
} }
string names = string.Join(",", statisticNames); string names = string.Join(",", statisticNames);
string path = $"leaderboard/users/{user.ObjectId}/statistics?statistics={names}"; string path = $"leaderboard/users/{user.ObjectId}/statistics?statistics={names}";
await LCApplication.HttpClient.Delete(path); await LCInternalApplication.HttpClient.Delete(path);
} }
/// <summary> /// <summary>
@ -179,7 +179,7 @@ namespace LeanCloud.Storage {
throw new ArgumentOutOfRangeException(nameof(limit)); throw new ArgumentOutOfRangeException(nameof(limit));
} }
string path = $"leaderboard/leaderboards/{StatisticName}/archives?skip={skip}&limit={limit}"; string path = $"leaderboard/leaderboards/{StatisticName}/archives?skip={skip}&limit={limit}";
Dictionary<string, object> result = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path); Dictionary<string, object> result = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>(path);
if (result.TryGetValue("results", out object results) && if (result.TryGetValue("results", out object results) &&
results is List<object> list) { results is List<object> list) {
List<LCLeaderboardArchive> archives = new List<LCLeaderboardArchive>(); List<LCLeaderboardArchive> archives = new List<LCLeaderboardArchive>();
@ -235,7 +235,7 @@ namespace LeanCloud.Storage {
string statistics = string.Join(",", includeStatistics); string statistics = string.Join(",", includeStatistics);
path = $"{path}&includeStatistics={statistics}"; path = $"{path}&includeStatistics={statistics}";
} }
Dictionary<string, object> result = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path); Dictionary<string, object> result = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>(path);
if (result.TryGetValue("results", out object results) && if (result.TryGetValue("results", out object results) &&
results is List<object> list) { results is List<object> list) {
List<LCRanking> rankings = new List<LCRanking>(); List<LCRanking> rankings = new List<LCRanking>();
@ -254,7 +254,7 @@ namespace LeanCloud.Storage {
{ "updateStrategy", updateStrategy.ToString().ToLower() } { "updateStrategy", updateStrategy.ToString().ToLower() }
}; };
string path = $"leaderboard/leaderboards/{StatisticName}"; string path = $"leaderboard/leaderboards/{StatisticName}";
Dictionary<string, object> result = await LCApplication.HttpClient.Put<Dictionary<string, object>>(path, Dictionary<string, object> result = await LCInternalApplication.HttpClient.Put<Dictionary<string, object>>(path,
data: data); data: data);
if (result.TryGetValue("updateStrategy", out object strategy) && if (result.TryGetValue("updateStrategy", out object strategy) &&
Enum.TryParse(strategy as string, true, out LCLeaderboardUpdateStrategy s)) { Enum.TryParse(strategy as string, true, out LCLeaderboardUpdateStrategy s)) {
@ -269,7 +269,7 @@ namespace LeanCloud.Storage {
{ "versionChangeInterval", versionChangeInterval.ToString().ToLower() } { "versionChangeInterval", versionChangeInterval.ToString().ToLower() }
}; };
string path = $"leaderboard/leaderboards/{StatisticName}"; string path = $"leaderboard/leaderboards/{StatisticName}";
Dictionary<string, object> result = await LCApplication.HttpClient.Put<Dictionary<string, object>>(path, Dictionary<string, object> result = await LCInternalApplication.HttpClient.Put<Dictionary<string, object>>(path,
data: data); data: data);
if (result.TryGetValue("versionChangeInterval", out object interval) && if (result.TryGetValue("versionChangeInterval", out object interval) &&
Enum.TryParse(interval as string, true, out LCLeaderboardVersionChangeInterval i)) { Enum.TryParse(interval as string, true, out LCLeaderboardVersionChangeInterval i)) {
@ -284,7 +284,7 @@ namespace LeanCloud.Storage {
/// <returns></returns> /// <returns></returns>
public async Task<LCLeaderboard> Fetch() { public async Task<LCLeaderboard> Fetch() {
string path = $"leaderboard/leaderboards/{StatisticName}"; string path = $"leaderboard/leaderboards/{StatisticName}";
Dictionary<string, object> result = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path); Dictionary<string, object> result = await LCInternalApplication.HttpClient.Get<Dictionary<string, object>>(path);
Merge(result); Merge(result);
return this; return this;
} }
@ -295,7 +295,7 @@ namespace LeanCloud.Storage {
/// <returns></returns> /// <returns></returns>
public async Task<LCLeaderboard> Reset() { public async Task<LCLeaderboard> Reset() {
string path = $"leaderboard/leaderboards/{StatisticName}/incrementVersion"; string path = $"leaderboard/leaderboards/{StatisticName}/incrementVersion";
Dictionary<string, object> result = await LCApplication.HttpClient.Put<Dictionary<string, object>>(path); Dictionary<string, object> result = await LCInternalApplication.HttpClient.Put<Dictionary<string, object>>(path);
Merge(result); Merge(result);
return this; return this;
} }
@ -306,7 +306,7 @@ namespace LeanCloud.Storage {
/// <returns></returns> /// <returns></returns>
public async Task Destroy() { public async Task Destroy() {
string path = $"leaderboard/leaderboards/{StatisticName}"; string path = $"leaderboard/leaderboards/{StatisticName}";
await LCApplication.HttpClient.Delete(path); await LCInternalApplication.HttpClient.Delete(path);
} }
private void Merge(Dictionary<string, object> data) { private void Merge(Dictionary<string, object> data) {

View File

@ -7,9 +7,6 @@
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Class1.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Internal\" /> <Folder Include="Internal\" />
<Folder Include="Internal\Codec\" /> <Folder Include="Internal\Codec\" />
@ -19,8 +16,10 @@
<Folder Include="Internal\Operation\" /> <Folder Include="Internal\Operation\" />
<Folder Include="Internal\Query\" /> <Folder Include="Internal\Query\" />
<Folder Include="Leaderboard\" /> <Folder Include="Leaderboard\" />
<Folder Include="Internal\Storage\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Common\Common\Common.csproj" /> <ProjectReference Include="..\..\Common\Common\Common.csproj">
</ProjectReference>
</ItemGroup> </ItemGroup>
</Project> </Project>