diff --git a/Storage/Source/Public/AVObject.cs b/Storage/Source/Public/AVObject.cs index 5c2e7a4..0f9cde7 100644 --- a/Storage/Source/Public/AVObject.cs +++ b/Storage/Source/Public/AVObject.cs @@ -1181,7 +1181,7 @@ string propertyName // The task produced by taskStart. By running this immediately, we allow everything prior // to toAwait to run before waiting for all of the queues on all of the objects. Task fullTask = taskStart(readyToStart.Task); - + // Add fullTask to each of the objects' queues. var childTasks = new List(); foreach (AVObject obj in objects) diff --git a/Storage/Storage.Test/AVObjectTest.cs b/Storage/Storage.Test/AVObjectTest.cs new file mode 100644 index 0000000..627fb88 --- /dev/null +++ b/Storage/Storage.Test/AVObjectTest.cs @@ -0,0 +1,189 @@ +using NUnit.Framework; +using System; +using System.Text; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Collections; +using System.Collections.Generic; + +namespace LeanCloud.Test { + public class LCObject { + public string Id { + get; set; + } + + public Dictionary Data { + get; set; + } + + public LCObject(string id) { + Data = new Dictionary(); + Id = id; + } + + public static Stack Batch(IEnumerable objects) { + Stack batches = new Stack(); + + IEnumerable deps = objects; + do { + // 只添加本层依赖的 LCObject + IEnumerable avObjects = deps.OfType(); + if (avObjects.Any()) { + batches.Push(new Batch(avObjects)); + } + + HashSet childSets = new HashSet(); + foreach (object dep in deps) { + IEnumerable children = null; + if (dep is IList) { + children = dep as IList; + } else if (dep is IDictionary) { + children = dep as IDictionary; + } else if (dep is LCObject) { + children = (dep as LCObject).Data.Values; + } + if (children != null) { + foreach (object child in children) { + childSets.Add(child); + } + } + } + deps = childSets; + } while (deps != null && deps.Any()); + + return batches; + } + + public static bool HasCircleReference(object obj, HashSet parents) { + if (parents.Contains(obj)) { + return true; + } + IEnumerable deps = null; + if (obj is IList) { + deps = obj as IList; + } else if (obj is IDictionary) { + deps = (obj as IDictionary).Values; + } else if (obj is LCObject) { + deps = (obj as LCObject).Data.Values; + } + HashSet depParent = new HashSet(parents); + if (obj is LCObject) { + depParent.Add((LCObject) obj); + } + if (deps != null) { + foreach (object dep in deps) { + HashSet set = new HashSet(depParent); + if (HasCircleReference(dep, set)) { + return true; + } + } + } + return false; + } + + public Stack Batch() { + return Batch(new List { this }); + } + + public bool HasCircleReference() { + return HasCircleReference(this, new HashSet()); + } + } + + public class Batch { + HashSet ObjectSet { + get; set; + } + + public Batch() { + ObjectSet = new HashSet(); + } + + public Batch(IEnumerable objects) : this() { + foreach (LCObject obj in objects) { + ObjectSet.Add(obj); + } + } + + public override string ToString() { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("----------------------------"); + foreach (LCObject obj in ObjectSet) { + sb.AppendLine(obj.Id); + } + sb.AppendLine("----------------------------"); + return sb.ToString(); + } + } + + public class AVObjectTest { + void PrintBatches(Stack batches) { + while (batches.Any()) { + Batch batch = batches.Pop(); + TestContext.WriteLine(batch); + } + } + + [Test] + public void Simple() { + LCObject a = new LCObject("a"); + LCObject b = new LCObject("b"); + LCObject c = new LCObject("c"); + a.Data["child"] = b; + b.Data["child"] = c; + + Assert.IsFalse(a.HasCircleReference()); + + Stack batches = a.Batch(); + PrintBatches(batches); + } + + [Test] + public void Array() { + LCObject a = new LCObject("a"); + LCObject b = new LCObject("b"); + LCObject c = new LCObject("c"); + a.Data["children"] = new List { b, c }; + + Assert.IsFalse(a.HasCircleReference()); + + Stack batches = a.Batch(); + PrintBatches(batches); + } + + [Test] + public void SimpleCircleReference() { + LCObject a = new LCObject("a"); + LCObject b = new LCObject("b"); + a.Data["child"] = b; + b.Data["child"] = a; + + Assert.IsTrue(a.HasCircleReference()); + } + + [Test] + public void ComplexCircleReference() { + LCObject a = new LCObject("a"); + LCObject b = new LCObject("b"); + LCObject c = new LCObject("c"); + a.Data["arr"] = new List { 1, b }; + a.Data["child"] = c; + b.Data["arr"] = new List { 2, a }; + + Assert.IsTrue(a.HasCircleReference()); + } + + [Test] + public void ComplexCircleReference2() { + LCObject a = new LCObject("a"); + LCObject b = new LCObject("b"); + List list = new List(); + a.Data["list"] = list; + b.Data["list"] = list; + a.Data["child"] = b; + + Assert.IsFalse(a.HasCircleReference()); + } + } +} diff --git a/Storage/Storage.Test/ObjectTest.cs b/Storage/Storage.Test/ObjectTest.cs index 6b10b29..05610e1 100644 --- a/Storage/Storage.Test/ObjectTest.cs +++ b/Storage/Storage.Test/ObjectTest.cs @@ -3,14 +3,21 @@ using System; using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; +using LeanCloud.Common; namespace LeanCloud.Test { public class ObjectTests { [SetUp] public void SetUp() { + Logger.LogDelegate += Utils.Print; Utils.InitNorthChina(); } + [TearDown] + public void TearDown() { + Logger.LogDelegate -= Utils.Print; + } + [Test] public async Task Save() { AVObject obj = AVObject.Create("Foo"); @@ -20,7 +27,7 @@ namespace LeanCloud.Test { { "hello", 1 }, { "world", 2 } }; - await obj.SaveAsync(); + await obj.Save(); Assert.NotNull(obj.ObjectId); Assert.NotNull(obj.CreatedAt); Assert.NotNull(obj.UpdatedAt); @@ -38,32 +45,57 @@ namespace LeanCloud.Test { TestContext.Out.WriteLine($"balance: {account["balance"]}"); } + //[Test] + //public async Task SaveWithPointer() { + // AVObject comment = new AVObject("Comment") { + // { "content", "Hello, Comment" } + // }; + + // AVObject post = new AVObject("Post") { + // { "name", "New Post" }, + // { "category", new AVObject("Category") { + // { "name", "new post category" } + // } } + // }; + // comment["post"] = post; + + // AVObject testPost = new AVObject("Post") { + // { "name", "Test Post" }, + // { "category", new AVObject("Category") { + // { "name", "test post category" } + // } } + // }; + // comment["test_post"] = testPost; + + // await comment.Save(); + // TestContext.Out.WriteLine(post); + // TestContext.Out.WriteLine(testPost); + // TestContext.Out.WriteLine(comment); + //} + [Test] public async Task SaveWithPointer() { - AVObject comment = new AVObject("Comment") { - { "content", "Hello, Comment" } - }; + AVObject parent = new AVObject("Parent"); + AVObject c1 = new AVObject("C1"); + AVObject c2 = new AVObject("C2"); + parent["c1"] = c1; + parent["c2"] = c2; + await parent.Save(); + } - AVObject post = new AVObject("Post") { - { "name", "New Post" }, - { "category", new AVObject("Category") { - { "name", "new post category" } - } } + [Test] + public async Task SaveWithPointerArray() { + AVObject parent = new AVObject("Parent"); + AVObject c1 = new AVObject("C1"); + AVObject c2 = new AVObject("C2"); + parent["iList"] = new List { 1, 1, 2, 3 }; + parent["cList"] = new List { c1, c2 }; + parent["cDict"] = new Dictionary { + { "c1", c1 }, + { "c2", c2 } }; - comment["post"] = post; + await parent.SaveAsync(); - AVObject testPost = new AVObject("Post") { - { "name", "Test Post" }, - { "category", new AVObject("Category") { - { "name", "test post category" } - } } - }; - comment["test_post"] = testPost; - - await comment.SaveAsync(); - TestContext.Out.WriteLine(post); - TestContext.Out.WriteLine(testPost); - TestContext.Out.WriteLine(comment); } [Test] @@ -199,5 +231,54 @@ namespace LeanCloud.Test { } }); } + + [Test] + public void SimpleCircleReference() { + AVObject a = new AVObject("A"); + AVObject b = new AVObject("B"); + a["b"] = b; + b["a"] = a; + + Assert.ThrowsAsync(async () => await a.Save()); + } + + [Test] + public void IndirectCircleReference() { + AVObject a = new AVObject("A"); + AVObject b = new AVObject("B"); + AVObject c = new AVObject("C"); + a["b"] = b; + b["c"] = c; + c["a"] = a; + + Assert.ThrowsAsync(async () => await a.Save()); + } + + [Test] + public void SimpleCollectionPointerCircleReference() { + AVObject a = new AVObject("A"); + AVObject b = new AVObject("B"); + a["children"] = new List { 1, b }; + b["children"] = new Dictionary { + { "c", a } + }; + + Assert.ThrowsAsync(async () => await a.Save()); + } + + [Test] + public void IndirectCollectionPointerCircleReference() { + AVObject a = new AVObject("A"); + AVObject b = new AVObject("B"); + AVObject c = new AVObject("C"); + + a["children"] = new List { 1, b }; + b["children"] = new List { 2, c }; + c["children"] = new Dictionary { + { "c", a } + }; + + Assert.ThrowsAsync(async () => await a.Save()); + } } } \ No newline at end of file diff --git a/Storage/Storage.Test/Utils.cs b/Storage/Storage.Test/Utils.cs index ac12b16..8b93ff2 100644 --- a/Storage/Storage.Test/Utils.cs +++ b/Storage/Storage.Test/Utils.cs @@ -1,5 +1,6 @@ using System; using LeanCloud; +using LeanCloud.Common; using NUnit.Framework; namespace LeanCloud.Test { @@ -47,5 +48,22 @@ namespace LeanCloud.Test { AVClient.UseMasterKey = !string.IsNullOrEmpty(masterKey); AVClient.HttpLog(TestContext.Out.WriteLine); } + + internal static void Print(LogLevel level, string info) { + switch (level) { + case LogLevel.Debug: + TestContext.Out.WriteLine($"[DEBUG] {info}"); + break; + case LogLevel.Warn: + TestContext.Out.WriteLine($"[WARNING] {info}"); + break; + case LogLevel.Error: + TestContext.Out.WriteLine($"[ERROR] {info}"); + break; + default: + TestContext.Out.WriteLine(info); + break; + } + } } } diff --git a/Storage/Storage/Public/AVException.cs b/Storage/Storage/Public/AVException.cs index 641803b..125363f 100644 --- a/Storage/Storage/Public/AVException.cs +++ b/Storage/Storage/Public/AVException.cs @@ -264,6 +264,11 @@ namespace LeanCloud /// 按条件更新/删除失败 /// NoEffectOnUpdatingOrDeleting = 305, + + /// + /// 循环引用 + /// + CircleReference = 400, } internal AVException(ErrorCode code, string message, Exception cause = null) diff --git a/Storage/Storage/Public/AVObject.cs b/Storage/Storage/Public/AVObject.cs index b82794b..d69593f 100644 --- a/Storage/Storage/Public/AVObject.cs +++ b/Storage/Storage/Public/AVObject.cs @@ -1,6 +1,7 @@ using LeanCloud.Storage.Internal; using LeanCloud.Utilities; using System; +using System.Text; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -8,19 +9,49 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Collections; +using System.Collections.Concurrent; namespace LeanCloud { /// /// AVObject /// public class AVObject : IEnumerable>, INotifyPropertyChanged, INotifyPropertyUpdated, INotifyCollectionPropertyUpdated { + internal class Batch { + internal HashSet Objects { + get; set; + } + + public Batch() { + Objects = new HashSet(); + } + + public Batch(IEnumerable objects) : this() { + foreach (AVObject obj in objects) { + Objects.Add(obj); + } + } + + public override string ToString() { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("----------------------------"); + foreach (AVObject obj in Objects) { + sb.AppendLine(obj.ClassName); + } + sb.AppendLine("----------------------------"); + return sb.ToString(); + } + } + + private static readonly string AutoClassName = "_Automatic"; internal readonly object mutex = new object(); private readonly LinkedList> operationSetQueue = new LinkedList>(); - private readonly IDictionary estimatedData = new Dictionary(); + + private readonly ConcurrentDictionary operationDict = new ConcurrentDictionary(); + private readonly ConcurrentDictionary estimatedData = new ConcurrentDictionary(); private static readonly ThreadLocal isCreatingPointer = new ThreadLocal(() => false); @@ -210,7 +241,7 @@ namespace LeanCloud { this[GetFieldForPropertyName(ClassName, propertyName)] = value; } - /// + /// /// Gets a relation for a property based upon its associated AVFieldName attribute. /// /// The AVRelation for the property. @@ -974,9 +1005,9 @@ string propertyName if (oldValue != value) { converdKeys.Add(key); } - estimatedData.Remove(key); + estimatedData.TryRemove(key, out _); } - estimatedData.Add(item); + estimatedData.TryAdd(item.Key, item.Value); } changedKeys = converdKeys; foreach (var operations in operationSetQueue) { @@ -1003,7 +1034,7 @@ string propertyName if (newValue != AVDeleteOperation.DeleteToken) { estimatedData[key] = newValue; } else { - estimatedData.Remove(key); + estimatedData.TryRemove(key, out _); } IAVFieldOperation oldOperation; @@ -1622,6 +1653,106 @@ string propertyName protected virtual void OnCollectionPropertyUpdated(string propertyName, NotifyCollectionUpdatedAction action, IEnumerable oldValues, IEnumerable newValues) { collectionUpdated?.Invoke(this, new CollectionPropertyUpdatedEventArgs(propertyName, action, oldValues, newValues)); } + + + + + + + + + + #region refactor + + static bool HasCircleReference(object obj, HashSet parents) { + if (parents.Contains(obj)) { + return true; + } + IEnumerable deps = null; + if (obj is IList) { + deps = obj as IList; + } else if (obj is IDictionary) { + deps = (obj as IDictionary).Values; + } else if (obj is AVObject) { + deps = (obj as AVObject).estimatedData.Values; + } + HashSet depParent = new HashSet(parents); + if (obj is AVObject) { + depParent.Add(obj as AVObject); + } + if (deps != null) { + foreach (object dep in deps) { + HashSet p = new HashSet(depParent); + if (HasCircleReference(dep, p)) { + return true; + } + } + } + return false; + } + + static Stack BatchObjects(IEnumerable avObjects) { + Stack batches = new Stack(); + + IEnumerable deps = avObjects; + do { + // 只添加本层依赖的 LCObject + IEnumerable depAVObjs = deps.OfType(); + if (depAVObjs.Any()) { + batches.Push(new Batch(depAVObjs)); + } + + HashSet childSets = new HashSet(); + foreach (object dep in deps) { + IEnumerable children = null; + if (dep is IList) { + children = dep as IList; + } else if (dep is IDictionary) { + children = (dep as IDictionary).Values; + } else if (dep is AVObject && (dep as AVObject).ObjectId == null) { + // 如果依赖是 AVObject 类型并且还没有保存过,则应该遍历其依赖 + children = (dep as AVObject).estimatedData.Values; + } + if (children != null) { + foreach (object child in children) { + childSets.Add(child); + } + } + } + deps = childSets; + } while (deps != null && deps.Any()); + + return batches; + } + + public virtual async Task Save() { + if (HasCircleReference(this, new HashSet())) { + throw new AVException(AVException.ErrorCode.CircleReference, "Found a circle dependency when save"); + } + Stack batches = BatchObjects(new List { this }); + while (batches.Any()) { + Batch batch = batches.Pop(); + IList dirtyObjects = batch.Objects.Where(o => o.IsDirty).ToList(); + IList states = (from item in dirtyObjects + select item.state).ToList(); + IList> operationList = (from item in dirtyObjects + select item.StartSave()).ToList(); + var serverStates = await ObjectController.SaveAllAsync(states, operationList, CancellationToken.None); + + try { + foreach (var pair in dirtyObjects.Zip(serverStates, (item, state) => new { item, state })) { + pair.item.HandleSave(pair.state); + } + } catch (Exception e) { + foreach (var pair in dirtyObjects.Zip(operationList, (item, ops) => new { item, ops })) { + pair.item.HandleFailedSave(pair.ops); + } + throw e; + } + } + } + + #endregion } public interface INotifyPropertyUpdated { diff --git a/Test/Common.Test/AppRouterTest.cs b/Test/Common.Test/AppRouterTest.cs index 06a9038..b037029 100644 --- a/Test/Common.Test/AppRouterTest.cs +++ b/Test/Common.Test/AppRouterTest.cs @@ -1,9 +1,10 @@ +using System; using System.Threading.Tasks; using NUnit.Framework; using LeanCloud.Common; namespace Common.Test { - public class Tests { + public class AppRouterTest { static void Print(LogLevel level, string info) { switch (level) { case LogLevel.Debug: @@ -23,23 +24,43 @@ namespace Common.Test { [SetUp] public void SetUp() { - TestContext.Out.WriteLine("Set up"); Logger.LogDelegate += Print; } [TearDown] public void TearDown() { - TestContext.Out.WriteLine("Tear down"); Logger.LogDelegate -= Print; } [Test] - public async Task AppRouter() { - var appRouter = new AppRouterController(); - for (int i = 0; i < 100; i++) { - var state = await appRouter.Get("BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz"); - TestContext.Out.WriteLine(state.ApiServer); - } + public void ChineseApp() { + Exception e = Assert.Catch(() => { + string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz"; + AppRouterController appRouter = new AppRouterController(appId, null); + TestContext.WriteLine("init done"); + }); + TestContext.WriteLine(e.Message); + } + + [Test] + public async Task ChineseAppWithDomain() { + string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz"; + string server = "https://bmyv4rks.lc-cn-n1-shared.com"; + AppRouterController appRouterController = new AppRouterController(appId, server); + AppRouter appRouterState = await appRouterController.Get(); + Assert.AreEqual(appRouterState.ApiServer, server); + Assert.AreEqual(appRouterState.EngineServer, server); + Assert.AreEqual(appRouterState.PushServer, server); + Assert.AreEqual(appRouterState.RTMServer, server); + Assert.AreEqual(appRouterState.StatsServer, server); + Assert.AreEqual(appRouterState.PlayServer, server); + } + + [Test] + public void InternationalApp() { + string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-MdYXbMMI"; + _ = new AppRouterController(appId, null); + TestContext.WriteLine("International app init done"); } } } \ No newline at end of file diff --git a/Test/Common.Test/Common.Test.csproj b/Test/Common.Test/Common.Test.csproj index 89c6f61..e7ec085 100644 --- a/Test/Common.Test/Common.Test.csproj +++ b/Test/Common.Test/Common.Test.csproj @@ -4,6 +4,7 @@ netcoreapp3.0 false + 0.1.0 diff --git a/Test/Common.Test/Test.cs b/Test/Common.Test/Test.cs index 0951d12..53bc17e 100644 --- a/Test/Common.Test/Test.cs +++ b/Test/Common.Test/Test.cs @@ -1,7 +1,73 @@ using System; +using System.Linq; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using NUnit.Framework; + namespace Common.Test { public class Test { - public Test() { + [Test] + public async Task AsyncFor() { + for (int i = 0; i < 5; i++) { + await Task.Delay(1000); + TestContext.WriteLine($"{i} done at {DateTimeOffset.UtcNow}"); + } + } + + [Test] + public void ConcurrentCollection() { + List list = new List(); + for (int i = 0; i < 1000; i++) { + Task.Run(() => { + list.Add(i); + }); + } + TestContext.WriteLine($"{list.Count}"); + + ConcurrentQueue queue = new ConcurrentQueue(); + for (int i = 0; i < 1000; i++) { + Task.Run(() => { + queue.Enqueue(i); + }); + } + TestContext.WriteLine($"{queue.Count}"); + } + + [Test] + public void ObjectType() { + List list = new List { 1, "hello", 2, "world" }; + TestContext.WriteLine(list is IList); + object[] objs = { 1, "hi", 3 }; + TestContext.WriteLine(objs is IList); + List subList = list.OfType().ToList(); + foreach (object obj in subList) { + TestContext.WriteLine(obj); + } + } + + [Test] + public void CollectionExcept() { + List list1 = new List { 1, 2, 3, 4, 5 }; + List list2 = new List { 4, 5, 6 }; + IEnumerable deltaList = list1.Except(list2).ToList(); + foreach (int delta in deltaList) { + TestContext.WriteLine(delta); + } + + Dictionary dict1 = new Dictionary { + { "a", 1 }, + { "b", 2 } + }; + Dictionary dict2 = new Dictionary { + { "b", 2 }, + { "c", 3 } + }; + IEnumerable> deltaDict = dict1.Except(dict2); + foreach (KeyValuePair delta in deltaDict) { + TestContext.WriteLine($"{delta.Key} : {delta.Value}"); + } } } } diff --git a/csharp-sdk.sln b/csharp-sdk.sln index b774817..af711ed 100644 --- a/csharp-sdk.sln +++ b/csharp-sdk.sln @@ -31,12 +31,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage", "Storage\Storage\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RTM", "RTM\RTM\RTM.csproj", "{D4A30F70-AAED-415D-B940-023B3D7241EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Test", "Test\Common.Test\Common.Test.csproj", "{4DF4E0F4-1013-477F-ADA6-BFAFD9312335}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{758DE75D-37D7-4392-B564-9484348B505C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -87,14 +87,14 @@ Global {D4A30F70-AAED-415D-B940-023B3D7241EE}.Debug|Any CPU.Build.0 = Debug|Any CPU {D4A30F70-AAED-415D-B940-023B3D7241EE}.Release|Any CPU.ActiveCfg = Release|Any CPU {D4A30F70-AAED-415D-B940-023B3D7241EE}.Release|Any CPU.Build.0 = Release|Any CPU - {14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Release|Any CPU.Build.0 = Release|Any CPU {4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Debug|Any CPU.Build.0 = Debug|Any CPU {4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Release|Any CPU.ActiveCfg = Release|Any CPU {4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Release|Any CPU.Build.0 = Release|Any CPU + {758DE75D-37D7-4392-B564-9484348B505C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {758DE75D-37D7-4392-B564-9484348B505C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {758DE75D-37D7-4392-B564-9484348B505C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {758DE75D-37D7-4392-B564-9484348B505C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {659D19F0-9A40-42C0-886C-555E64F16848} = {CD6B6669-1A56-437A-932E-BCE7F5D4CD18} @@ -105,9 +105,7 @@ Global {EA1C601E-D853-41F7-B9EB-276CBF7D1FA5} = {5B895B7A-1F6E-40A5-8081-43B334D2C076} {3251B4D8-D11A-4D90-8626-27FEE266B066} = {5B895B7A-1F6E-40A5-8081-43B334D2C076} {F907012C-74DF-4575-AFE6-E8DAACC26D24} = {5B895B7A-1F6E-40A5-8081-43B334D2C076} - {BE05B492-78CD-47CA-9F48-C3E9B4813AFF} = {CD6B6669-1A56-437A-932E-BCE7F5D4CD18} - {59DA32A0-4CD3-424A-8584-D08B8D1E2B98} = {CD6B6669-1A56-437A-932E-BCE7F5D4CD18} - {D4A30F70-AAED-415D-B940-023B3D7241EE} = {64D8F9A1-BA44-459C-817C-788B4EBC0B9F} + {BE05B492-78CD-47CA-9F48-C3E9B4813AFF} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933} {4DF4E0F4-1013-477F-ADA6-BFAFD9312335} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution