Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

  • Loading branch information...
commit a2d276933b725130f5ab7a86587d7ce54de80869 0 parents
Karl Seguin authored
Showing with 2,248 additions and 0 deletions.
  1. +21 −0 .gitignore
  2. +73 −0 Parse.Tests/BaseFixture.cs
  3. +135 −0 Parse.Tests/FakeServer.cs
  4. +33 −0 Parse.Tests/ObjectsTests/DeleteTests.cs
  5. +24 −0 Parse.Tests/ObjectsTests/GetTests.cs
  6. +139 −0 Parse.Tests/ObjectsTests/QueryTests.cs
  7. +23 −0 Parse.Tests/ObjectsTests/SaveTests.cs
  8. +34 −0 Parse.Tests/ObjectsTests/UpdateTests.cs
  9. +65 −0 Parse.Tests/Parse.Tests.csproj
  10. +20 −0 Parse.Tests/ParseObjectClass.cs
  11. +36 −0 Parse.Tests/Properties/AssemblyInfo.cs
  12. +71 −0 Parse.sln
  13. +183 −0 Parse/Components/Communicator.cs
  14. +58 −0 Parse/Components/ParseConfiguration.cs
  15. +33 −0 Parse/Components/Response.cs
  16. +21 −0 Parse/Driver.cs
  17. +55 −0 Parse/Linq/ParseQuery.cs
  18. +42 −0 Parse/Linq/ParseQueryProvider.cs
  19. +139 −0 Parse/Linq/QueryTranslator.cs
  20. +51 −0 Parse/Linq/TypeSystem.cs
  21. +22 −0 Parse/Models/ParseObject.cs
  22. +10 −0 Parse/Models/ResultsContainer.cs
  23. +133 −0 Parse/Objects.cs
  24. +78 −0 Parse/Parse.csproj
  25. +18 −0 Parse/ParseException.cs
  26. +66 −0 Parse/ParseFull.csproj
  27. +11 −0 Parse/Properties/AssemblyInfo.cs
  28. +285 −0 Parse/Queries/ExpressionVisitor.cs
  29. +107 −0 Parse/Queries/ParseQuery.cs
  30. +136 −0 Parse/Queries/WhereTranslator.cs
  31. +11 −0 Parse/todo.txt
  32. +115 −0 readme.md
21 .gitignore
@@ -0,0 +1,21 @@
+TestResult.xml
+Tests.VisualState.xml
+[Bb]in
+[Oo]bj
+[Rr]elease
+[Dd]ebug
+*.bak
+*.manifest
+*.exe
+*.dll
+*.pdb
+*.cache
+*.suo
+*.orig
+*.user
+.svn
+_ReSharper*
+.DS_Store
+*.usertasks
+*.userprefs
+Parse.Playground
73 Parse.Tests/BaseFixture.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using NUnit.Framework;
+
+namespace Parse.Tests
+{
+ public abstract class BaseFixture
+ {
+ protected static readonly Func<IDictionary<string, object>> EmptyPayload = () => new Dictionary<string, object>();
+ protected virtual bool NeedAServer
+ {
+ get { return true; }
+ }
+ protected FakeServer Server;
+ protected AutoResetEvent Trigger;
+
+ [SetUp]
+ public void SetUp()
+ {
+ Trigger = new AutoResetEvent(false);
+ if (NeedAServer)
+ {
+ Server = new FakeServer();
+ ParseConfiguration.Configure("the-app-id", "shhh", c => c.ConnectTo("http://localhost:" + FakeServer.Port + "/"));
+ }
+ BeforeEachTest();
+ }
+ [TearDown]
+ public void TearDown()
+ {
+ if (Server != null)
+ {
+ Server.Dispose();
+ }
+ AfterEachTest();
+ }
+ public virtual void AfterEachTest() { }
+ public virtual void BeforeEachTest() { }
+
+ protected void AssertMogadeException(string expectedMessage, Action code)
+ {
+ var ex = Assert.Throws<ParseException>(() => code());
+ Assert.AreEqual(expectedMessage, ex.Message);
+ }
+
+ protected void SetIfSuccess(Response response)
+ {
+ if (response.Success) { Set(); }
+
+ Assert.Fail(response.Error.Message);
+ }
+ protected void Set()
+ {
+ Trigger.Set();
+ }
+ protected void WaitOne()
+ {
+ Assert.IsTrue(Trigger.WaitOne(3000), "Test terminated without properly signalling the trigger");
+ }
+ }
+
+ internal static class AutoResetEventExtensions
+ {
+ public static void Wait(this AutoResetEvent trigger, int count)
+ {
+ for (var i = 0; i < count; ++i)
+ {
+ trigger.WaitOne();
+ }
+ }
+ }
+}
135 Parse.Tests/FakeServer.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading;
+
+namespace Parse.Tests
+{
+ /// <summary>
+ /// A fake server for testing
+ /// </summary>
+ /// <remarks>
+ /// Kinda hate this...bound to setup a real/fake testing server somewhere at some point
+ /// </remarks>
+ public class FakeServer : IDisposable
+ {
+ private bool _disposed;
+ private readonly HttpListener _listener;
+ private readonly Thread _thread;
+ public const int Port = 9948;
+ private readonly IList<ApiExpectation> _expectations;
+ private Action _onInvoke;
+
+ public FakeServer()
+ {
+ _expectations = new List<ApiExpectation>(5);
+ _listener = new HttpListener();
+ _listener.Prefixes.Add("http://*:" + 9948 + "/");
+ _listener.Start();
+ _thread = new Thread(Listen) {IsBackground = true};
+ _thread.Start();
+ }
+
+ public void Stub(ApiExpectation expectation)
+ {
+ _expectations.Add(expectation);
+ }
+
+ private void Listen()
+ {
+ while (true)
+ {
+ var context = GetContext();
+ if (context == null) { return; }
+ var body = ExtractBody(context.Request);
+ var expectation = FindExpectation(context, body);
+ if (expectation == null)
+ {
+ SendResponse(context, string.Format("Unexpected call: {0} {1}{2}{3}", context.Request.HttpMethod, context.Request.Url, Environment.NewLine, body), new ApiExpectation { Status = 500 });
+ return;
+ }
+ if (_onInvoke != null)
+ {
+ _onInvoke();
+ }
+ SendResponse(context, body, expectation);
+ }
+ }
+
+ private static void SendResponse(HttpListenerContext context, string body, ApiExpectation expectation)
+ {
+ var response = context.Response;
+ response.StatusCode = expectation.Status ?? 200;
+ response.ContentLength64 = (expectation.Response ?? body).Length;
+ using (var sw = new StreamWriter(response.OutputStream))
+ {
+ sw.Write(expectation.Response ?? body);
+ }
+ response.Close();
+ }
+
+ private HttpListenerContext GetContext()
+ {
+ try { return _listener.GetContext(); }
+ catch (HttpListenerException) { return null; }
+ }
+
+ private ApiExpectation FindExpectation(HttpListenerContext context, string body)
+ {
+ var request = context.Request;
+ foreach (var expectation in _expectations)
+ {
+ if (expectation.Method != null && string.Compare(request.HttpMethod, expectation.Method, true) != 0) { continue; }
+ if (expectation.Url != null && string.Compare(request.Url.AbsolutePath, expectation.Url, true) != 0) { continue; }
+ if (expectation.Request != null && string.Compare(body, expectation.Request, true) != 0) { continue; }
+ return expectation; //we found a match!
+ }
+ return null;
+ }
+ private static string ExtractBody(HttpListenerRequest request)
+ {
+ if (request.HttpMethod == "GET")
+ {
+ return request.Url.Query.Length > 0 ? request.Url.Query.Substring(1) : null;
+ }
+ var buffer = new byte[request.ContentLength64];
+ request.InputStream.Read(buffer, 0, buffer.Length);
+ return Encoding.Default.GetString(buffer);
+ }
+
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ private void Dispose(bool disposing)
+ {
+ if (!_disposed && disposing)
+ {
+ _listener.Stop();
+ }
+ _disposed = true;
+ }
+
+ public void OnInvoke(Action callback)
+ {
+ _onInvoke = callback;
+ }
+ }
+
+ /// <remarks>
+ /// All of these default to safe values, the most interesting of which is a null response will act as an echo server (return the request)
+ /// </remarks>
+ public class ApiExpectation
+ {
+ public readonly static ApiExpectation EchoAll = new ApiExpectation();
+ public string Method { get; set; }
+ public string Url { get; set; }
+ public string Request { get; set; }
+ public int? Status { get; set; }
+ public string Response { get; set; }
+ }
+}
33 Parse.Tests/ObjectsTests/DeleteTests.cs
@@ -0,0 +1,33 @@
+using System;
+using NUnit.Framework;
+
+namespace Parse.Tests.ObjectsTests
+{
+ [TestFixture]
+ public class DeleteTests : BaseFixture
+ {
+ [Test]
+ public void SendsADeleteRequest()
+ {
+ Server.Stub(new ApiExpectation { Method = "DELETE", Url = "/1/classes/ParseObjectClass/Ed1nuqPvcm", Response = "{name: 'Goku', createdAt: '2011-08-20T02:06:57.931Z', updatedAt: '2012-09-21T03:07:58.932Z', objectId: 'Ed1nuqPvcm' }" });
+ new Driver().Objects.Delete<ParseObjectClass>("Ed1nuqPvcm", r =>
+ {
+ Assert.AreEqual("Ed1nuqPvcm", r.Data.Id);
+ Assert.AreEqual(new DateTime(2011, 8, 20, 2, 6, 57, 931), r.Data.CreatedAt.Value.ToUniversalTime());
+ Assert.AreEqual(new DateTime(2012, 9, 21, 3, 7, 58, 932), r.Data.UpdatedAt.Value.ToUniversalTime());
+ Assert.AreEqual("Goku", r.Data.Name);
+ SetIfSuccess(r);
+ });
+ WaitOne();
+ }
+
+ [Test]
+ public void SendsADeleteForAParseObject()
+ {
+ var o = new ParseObjectClass {Id = "abc123"};
+ Server.Stub(new ApiExpectation { Method = "DELETE", Url = "/1/classes/ParseObjectClass/abc123", Response = "{}" });
+ new Driver().Objects.Delete(o, SetIfSuccess);
+ WaitOne();
+ }
+ }
+}
24 Parse.Tests/ObjectsTests/GetTests.cs
@@ -0,0 +1,24 @@
+using System;
+using NUnit.Framework;
+
+namespace Parse.Tests.ObjectsTests
+{
+ [TestFixture]
+ public class GetTests : BaseFixture
+ {
+ [Test]
+ public void SendsAGetRequest()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ParseObjectClass/Ed1nuqPvcm", Response = "{name: 'Goku', createdAt: '2011-08-20T02:06:57.931Z', updatedAt: '2012-09-21T03:07:58.932Z', objectId: 'Ed1nuqPvcm' }" });
+ new Driver().Objects.Get<ParseObjectClass>("Ed1nuqPvcm", r =>
+ {
+ Assert.AreEqual("Ed1nuqPvcm", r.Data.Id);
+ Assert.AreEqual(new DateTime(2011, 8, 20, 2, 6, 57, 931), r.Data.CreatedAt.Value.ToUniversalTime());
+ Assert.AreEqual(new DateTime(2012, 9, 21, 3, 7, 58, 932), r.Data.UpdatedAt.Value.ToUniversalTime());
+ Assert.AreEqual("Goku", r.Data.Name);
+ SetIfSuccess(r);
+ });
+ WaitOne();
+ }
+ }
+}
139 Parse.Tests/ObjectsTests/QueryTests.cs
@@ -0,0 +1,139 @@
+using System;
+using NUnit.Framework;
+
+namespace Parse.Tests.ObjectsTests
+{
+ [TestFixture]
+ public class QueryTests : BaseFixture
+ {
+ private const string _blankResponse = "{results:[]}";
+
+ [Test]
+ public void SingleParameterEqualityQuery()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""Name"":""Jessica""}")), Response = _blankResponse});
+ new Driver().Objects.Query<ParseObjectClass>().Where(c => c.Name == "Jessica").Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SingleParameterNonEqualityQuery()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""Id"":{""$ne"":""Harkonnen""}}")), Response = _blankResponse });
+ new Driver().Objects.Query<ParseObjectClass>().Where(c => c.Id != "Harkonnen").Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SingleParameterGreaterThanQuery()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""PowerLevel"":{""$gt"":9000}}")), Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Where(c => c.PowerLevel > 9000).Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SingleParameterGreaterThanOrEqualQuery()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""PowerLevel"":{""$gte"":2}}")), Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Where(c => c.PowerLevel >= 2).Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SingleParameteLessThanQuery()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""PowerLevel"":{""$lt"":-44}}")), Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Where(c => c.PowerLevel < -44).Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SingleParameteLessThanOrEqualQuery()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""PowerLevel"":{""$lte"":-3}}")), Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Where(c => c.PowerLevel <= -3).Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void TrueBooleanParameterQuery()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""Sayan"":true}")), Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Where(c => c.Sayan).Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void FalseBooleanParameterQuery()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""Sayan"":false}")), Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Where(c => !c.Sayan).Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void StupidBoolean()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""Sayan"":true}")), Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Where(c => !!c.Sayan).Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SendsAComplexishQuery()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""PowerLevel"":{""$gte"":33},""Name"":{""$ne"":""test""}}")), Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Where(c => c.PowerLevel >= 33 && c.Name != "test").Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SendsAMoreComplexishQuery()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""PowerLevel"":{""$gt"":9000,""$lt"":10000},""Sayan"":false}")), Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Where(c => c.PowerLevel > 9000 && c.PowerLevel < 10000 && !c.Sayan).Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SendsPagingInformation()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = "skip=10&limit=100", Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Limit(100).Skip(10).Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SendsACountRequest()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = "count=1", Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Count().Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SortsAscending()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = "order=Sayan", Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().SortAscending(c => c.Sayan).Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SortsDecending()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = "order=-PowerLevel", Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().SortDescending(c => c.PowerLevel).Execute(SetIfSuccess);
+ WaitOne();
+ }
+
+ [Test]
+ public void SendsAlot()
+ {
+ Server.Stub(new ApiExpectation { Method = "GET", Url = "/1/classes/ComplexParseObjectClass", Request = string.Concat("where=", Uri.EscapeDataString(@"{""PowerLevel"":{""$gt"":9000,""$lt"":10000},""Sayan"":false}"), "&count=1&skip=10&limit=20&order=-Id"), Response = _blankResponse });
+ new Driver().Objects.Query<ComplexParseObjectClass>().Where(c => c.PowerLevel > 9000 && c.PowerLevel < 10000 && !c.Sayan).Count().Limit(20).Skip(10).Sort(c => c.Id, false).Execute(SetIfSuccess);
+ WaitOne();
+ }
+ }
+}
23 Parse.Tests/ObjectsTests/SaveTests.cs
@@ -0,0 +1,23 @@
+using System;
+using NUnit.Framework;
+
+namespace Parse.Tests.ObjectsTests
+{
+ [TestFixture]
+ public class SaveTests : BaseFixture
+ {
+ [Test]
+ public void SendsASaveRequest()
+ {
+ Server.Stub(new ApiExpectation { Method = "POST", Url = "/1/classes/ParseObjectClass", Request = "{\"Name\":\"Leto\",\"objectId\":null,\"createdAt\":null,\"updatedAt\":null}", Response = "{createdAt: '2011-08-20T02:06:57.931Z', objectId: 'Ed1nuqPvcm' }" });
+ new Driver().Objects.Save(new ParseObjectClass{Name = "Leto"}, r =>
+ {
+ Assert.AreEqual("Ed1nuqPvcm", r.Data.Id);
+ Assert.AreEqual(null, r.Data.UpdatedAt);
+ Assert.AreEqual(new DateTime(2011, 8, 20, 2, 6, 57, 931), r.Data.CreatedAt.Value.ToUniversalTime());
+ SetIfSuccess(r);
+ });
+ WaitOne();
+ }
+ }
+}
34 Parse.Tests/ObjectsTests/UpdateTests.cs
@@ -0,0 +1,34 @@
+using System;
+using NUnit.Framework;
+
+namespace Parse.Tests.ObjectsTests
+{
+ [TestFixture]
+ public class UpdateTests : BaseFixture
+ {
+ [Test]
+ public void SendsAnUpdateRequest()
+ {
+ var o = new ParseObjectClass {Id = "over9000", Name = "ouch"};
+ Server.Stub(new ApiExpectation { Method = "PUT", Url = "/1/classes/ParseObjectClass/over9000", Request = "{\"Name\":\"ouch\",\"objectId\":\"over9000\",\"createdAt\":null,\"updatedAt\":null}", Response = "{updatedAt: '2011-08-21T18:02:52.248Z' }" });
+ new Driver().Objects.Update(o, r =>
+ {
+ Assert.AreEqual(new DateTime(2011, 8, 21, 18, 2, 52, 248), r.Data.ToUniversalTime());
+ SetIfSuccess(r);
+ });
+ WaitOne();
+ }
+
+ [Test]
+ public void SendsAnUpdateRequestForAParseObject()
+ {
+ Server.Stub(new ApiExpectation { Method = "PUT", Url = "/1/classes/SimpleClass/9393", Request = "{\"PowerLevel\":44}", Response = "{updatedAt: '2012-09-22T19:03:53.249Z' }" });
+ new Driver().Objects.Update("9393", new SimpleClass{PowerLevel = 44}, r =>
+ {
+ Assert.AreEqual(new DateTime(2012, 9, 22, 19, 3, 53, 249), r.Data.ToUniversalTime());
+ SetIfSuccess(r);
+ });
+ WaitOne();
+ }
+ }
+}
65 Parse.Tests/Parse.Tests.csproj
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Parse.Tests</RootNamespace>
+ <AssemblyName>Parse.Tests</AssemblyName>
+ <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="nunit.framework">
+ <HintPath>..\references\nunit.framework.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="BaseFixture.cs" />
+ <Compile Include="ObjectsTests\QueryTests.cs" />
+ <Compile Include="ParseObjectClass.cs" />
+ <Compile Include="FakeServer.cs" />
+ <Compile Include="ObjectsTests\DeleteTests.cs" />
+ <Compile Include="ObjectsTests\GetTests.cs" />
+ <Compile Include="ObjectsTests\SaveTests.cs" />
+ <Compile Include="ObjectsTests\UpdateTests.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Parse\ParseFull.csproj">
+ <Project>{228A0996-6132-407D-8FC4-C74A7C3F0F13}</Project>
+ <Name>ParseFull</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
20 Parse.Tests/ParseObjectClass.cs
@@ -0,0 +1,20 @@
+namespace Parse.Tests
+{
+ public class ParseObjectClass : ParseObject
+ {
+ public string Name { get; set; }
+ }
+
+ public class ComplexParseObjectClass : ParseObject
+ {
+ public string Name { get; set; }
+ public int PowerLevel { get; set; }
+ public bool Sayan { get; set; }
+ }
+
+
+ public class SimpleClass
+ {
+ public int PowerLevel { get; set; }
+ }
+}
36 Parse.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Parse.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Parse.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("1df52cae-caf0-46e5-8531-2219fe9518b1")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
71 Parse.sln
@@ -0,0 +1,71 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Parse", "Parse\Parse.csproj", "{7C665CCD-7531-4AEA-9425-6831A3F84BB9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Parse.Tests", "Parse.Tests\Parse.Tests.csproj", "{7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParseFull", "Parse\ParseFull.csproj", "{228A0996-6132-407D-8FC4-C74A7C3F0F13}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Parse.Playground", "Parse.Playground\Parse.Playground.csproj", "{19D9C1E6-D76B-49C3-A598-F766BC843A39}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1C5668B6-F7B4-4F31-8482-C2070DC64AE9}"
+ ProjectSection(SolutionItems) = preProject
+ readme.md = readme.md
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|Mixed Platforms = Debug|Mixed Platforms
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|Mixed Platforms = Release|Mixed Platforms
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7C665CCD-7531-4AEA-9425-6831A3F84BB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C665CCD-7531-4AEA-9425-6831A3F84BB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C665CCD-7531-4AEA-9425-6831A3F84BB9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {7C665CCD-7531-4AEA-9425-6831A3F84BB9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {7C665CCD-7531-4AEA-9425-6831A3F84BB9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7C665CCD-7531-4AEA-9425-6831A3F84BB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7C665CCD-7531-4AEA-9425-6831A3F84BB9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7C665CCD-7531-4AEA-9425-6831A3F84BB9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {7C665CCD-7531-4AEA-9425-6831A3F84BB9}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {7C665CCD-7531-4AEA-9425-6831A3F84BB9}.Release|x86.ActiveCfg = Release|Any CPU
+ {7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {7365346E-E4C3-4EEA-9E9E-BB4DA82F7C08}.Release|x86.ActiveCfg = Release|Any CPU
+ {228A0996-6132-407D-8FC4-C74A7C3F0F13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {228A0996-6132-407D-8FC4-C74A7C3F0F13}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {228A0996-6132-407D-8FC4-C74A7C3F0F13}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {228A0996-6132-407D-8FC4-C74A7C3F0F13}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {228A0996-6132-407D-8FC4-C74A7C3F0F13}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {228A0996-6132-407D-8FC4-C74A7C3F0F13}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {228A0996-6132-407D-8FC4-C74A7C3F0F13}.Release|Any CPU.Build.0 = Release|Any CPU
+ {228A0996-6132-407D-8FC4-C74A7C3F0F13}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {228A0996-6132-407D-8FC4-C74A7C3F0F13}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {228A0996-6132-407D-8FC4-C74A7C3F0F13}.Release|x86.ActiveCfg = Release|Any CPU
+ {19D9C1E6-D76B-49C3-A598-F766BC843A39}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {19D9C1E6-D76B-49C3-A598-F766BC843A39}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {19D9C1E6-D76B-49C3-A598-F766BC843A39}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {19D9C1E6-D76B-49C3-A598-F766BC843A39}.Debug|x86.ActiveCfg = Debug|x86
+ {19D9C1E6-D76B-49C3-A598-F766BC843A39}.Debug|x86.Build.0 = Debug|x86
+ {19D9C1E6-D76B-49C3-A598-F766BC843A39}.Release|Any CPU.ActiveCfg = Release|x86
+ {19D9C1E6-D76B-49C3-A598-F766BC843A39}.Release|Mixed Platforms.ActiveCfg = Release|x86
+ {19D9C1E6-D76B-49C3-A598-F766BC843A39}.Release|Mixed Platforms.Build.0 = Release|x86
+ {19D9C1E6-D76B-49C3-A598-F766BC843A39}.Release|x86.ActiveCfg = Release|x86
+ {19D9C1E6-D76B-49C3-A598-F766BC843A39}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
183 Parse/Components/Communicator.cs
@@ -0,0 +1,183 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+using Newtonsoft.Json;
+
+namespace Parse
+{
+ internal static class Communicator
+ {
+ public const string Get = "GET";
+ public const string Put = "PUT";
+ public const string Post = "POST";
+ public const string Delete = "DELETE";
+
+ private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore, };
+
+ public static void SendQueryPayload<T>(string method, string endPoint, Action<Response<T>> callback)
+ {
+ SendQueryPayload(method, endPoint, (string)null, callback);
+ }
+
+ public static void SendQueryPayload<T>(string method, string endPoint, IDictionary<string, object> payload, Action<Response<T>> callback)
+ {
+ SendQueryPayload(method, endPoint, payload == null ? null : DictionaryToQueryString(payload), callback);
+ }
+
+ public static void SendQueryPayload<T>(string method, string endPoint, string query, Action<Response<T>> callback)
+ {
+ var request = BuildRequest(method, endPoint, query, callback);
+ if (request != null)
+ {
+ request.BeginGetResponse(GetResponseStream<T>, new RequestState<T> { Request = request, Callback = callback });
+ }
+ }
+
+ public static void SendDataPayload<T>(string method, string endPoint, string payload, Action<Response<T>> callback)
+ {
+ var request = BuildRequest(method, endPoint, null, callback);
+ if (request != null)
+ {
+ request.ContentType = "application/json";
+ request.BeginGetRequestStream(GetRequestStream<T>, new RequestState<T> { Request = request, Payload = Encoding.UTF8.GetBytes(payload), Callback = callback });
+ }
+ }
+
+ private static HttpWebRequest BuildRequest<T>(string method, string endPoint, string queryString, Action<Response<T>> callback)
+ {
+ var configuration = ParseConfiguration.Configuration;
+ if (!configuration.NetworkCheck())
+ {
+ if (callback != null) { callback(Response<T>.CreateError(new ErrorMessage { Message = "Network is not available" })); }
+ return null;
+ }
+ var url = string.Concat(configuration.Url, ParseConfiguration.ApiVersion, "/", endPoint);
+ if (queryString != null)
+ {
+ url += string.Concat('?', queryString);
+ }
+ var request = (HttpWebRequest)WebRequest.Create(url);
+ request.Method = method;
+ request.UserAgent = "parse-dotnet";
+ request.Credentials = new NetworkCredential(configuration.ApplicationId, configuration.ClientSecret);
+ return request;
+ }
+
+ private static void GetRequestStream<T>(IAsyncResult result)
+ {
+ var state = (RequestState<T>)result.AsyncState;
+ using (var requestStream = state.Request.EndGetRequestStream(result))
+ {
+ requestStream.Write(state.Payload, 0, state.Payload.Length);
+ requestStream.Flush();
+ requestStream.Close();
+ }
+ state.Request.BeginGetResponse(GetResponseStream<T>, state);
+ }
+
+ private static void GetResponseStream<T>(IAsyncResult result)
+ {
+ var state = (ResponseState<T>)result.AsyncState;
+ try
+ {
+ using (var response = (HttpWebResponse)state.Request.EndGetResponse(result))
+ {
+ if (state.Callback != null) { state.Callback(Response<T>.CreateSuccess(GetResponseBody(response))); }
+ }
+ }
+ catch (Exception ex)
+ {
+ if (state.Callback != null) { state.Callback(Response<T>.CreateError(HandleException(ex))); }
+ }
+ }
+
+ private static string DictionaryToQueryString(IEnumerable<KeyValuePair<string, object>> payload)
+ {
+ var sb = new StringBuilder();
+ foreach (var kvp in payload)
+ {
+ if (kvp.Value == null) { continue; }
+ var valueType = kvp.Value.GetType();
+ if (!typeof(string).IsAssignableFrom(valueType) && typeof(IEnumerable).IsAssignableFrom(valueType))
+ {
+ sb.Append(Serialize(kvp.Key, (IEnumerable) kvp.Value));
+ }
+ else
+ {
+ sb.Append(SerializeSingleParameter(kvp.Key, kvp.Value.ToString()));
+ }
+ }
+ return sb.Remove(sb.Length - 1, 1).ToString();
+ }
+
+ private static string Serialize(string key, IEnumerable values)
+ {
+ var sb = new StringBuilder();
+ foreach(var value in values)
+ {
+ sb.Append(SerializeSingleParameter(key, value.ToString()));
+ }
+ return sb.ToString();
+ }
+
+ private static string SerializeSingleParameter(string key, string value)
+ {
+ return string.Concat(key, '=', Uri.EscapeDataString(value), '&');
+ }
+
+ private static string GetResponseBody(WebResponse response)
+ {
+ using (var stream = response.GetResponseStream())
+ {
+ var sb = new StringBuilder();
+ int read;
+ var bufferSize = response.ContentLength == -1 ? 2048 : (int)response.ContentLength;
+ if (bufferSize == 0) { return null; }
+ do
+ {
+ var buffer = new byte[2048];
+ read = stream.Read(buffer, 0, buffer.Length);
+ sb.Append(Encoding.UTF8.GetString(buffer, 0, read));
+ } while (read > 0);
+ return sb.ToString();
+ }
+ }
+
+ private static ErrorMessage HandleException(Exception exception)
+ {
+ if (exception is WebException)
+ {
+ var response = ((WebException) exception).Response;
+ if (response == null)
+ {
+ return new ErrorMessage {Message = "Null response (wakeup from rehydrating (multitasking)?)", InnerException = exception};
+ }
+ var body = GetResponseBody(response);
+ try
+ {
+ var message = JsonConvert.DeserializeObject<ErrorMessage>(body, _jsonSettings);
+ message.InnerException = exception;
+ return message;
+ }
+ catch (Exception)
+ {
+ return new ErrorMessage { Message = body, InnerException = exception };
+ }
+ }
+ return new ErrorMessage { Message = "Unknown Error", InnerException = exception };
+ }
+
+ private class ResponseState<T>
+ {
+ public HttpWebRequest Request { get; set; }
+ public Action<Response<T>> Callback { get; set; }
+ }
+
+ private class RequestState<T> : ResponseState<T>
+ {
+ public byte[] Payload { get; set; }
+ }
+ }
+}
58 Parse/Components/ParseConfiguration.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Net.NetworkInformation;
+
+namespace Parse
+{
+ public interface IParseConfiguration
+ {
+ IParseConfiguration ConnectTo(string url);
+ IParseConfiguration NetworkAvailableCheck(Func<bool> networkCheck);
+ }
+
+ public class ParseConfiguration : IParseConfiguration
+ {
+ public const string ApiVersion = "1";
+ private static readonly ParseConfiguration _configuration = new ParseConfiguration();
+ public string Url { get; private set; }
+ public Func<bool> NetworkCheck { get; private set; }
+ public string ApplicationId { get; private set; }
+ public string ClientSecret { get; private set; }
+ public static ParseConfiguration Configuration
+ {
+ get { return _configuration; }
+ }
+
+ protected ParseConfiguration()
+ {
+ }
+
+ public IParseConfiguration ConnectTo(string url)
+ {
+ _configuration.Url = url;
+ return this;
+ }
+
+ public IParseConfiguration NetworkAvailableCheck(Func<bool> networkCheck)
+ {
+ _configuration.NetworkCheck = networkCheck;
+ return this;
+ }
+
+ public static void Configure(string applicationId, string clientSecret)
+ {
+ Configure(applicationId, clientSecret, null);
+ }
+
+ public static void Configure(string applicationId, string clientSecret, Action<IParseConfiguration> action)
+ {
+ _configuration.NetworkAvailableCheck(NetworkInterface.GetIsNetworkAvailable);
+ _configuration.ApplicationId = applicationId;
+ _configuration.ClientSecret = clientSecret;
+ _configuration.Url = "https://api.parse.com/";
+ if (action != null)
+ {
+ action(_configuration);
+ }
+ }
+ }
+}
33 Parse/Components/Response.cs
@@ -0,0 +1,33 @@
+using System;
+using Newtonsoft.Json;
+
+namespace Parse
+{
+ public class Response
+ {
+ public bool Success { get; internal set; }
+ internal string Raw { get; set; }
+ public ErrorMessage Error { get; internal set; }
+ }
+ public class Response<T> : Response
+ {
+ public T Data { get; internal set; }
+
+ public static Response<T> CreateSuccess(string raw)
+ {
+ return new Response<T> { Success = true, Raw = raw };
+ }
+ public static Response<T> CreateError(ErrorMessage error)
+ {
+ return new Response<T> { Success = false, Error = error };
+ }
+ }
+
+ public class ErrorMessage
+ {
+ [JsonProperty("error")]
+ public string Message { get; internal set; }
+ public int Code { get; internal set; }
+ public Exception InnerException { get; internal set; }
+ }
+}
21 Parse/Driver.cs
@@ -0,0 +1,21 @@
+namespace Parse
+{
+ public interface IDriver
+ {
+ IObjects Objects { get; }
+ }
+
+ public class Driver : IDriver
+ {
+ private readonly IObjects _objects;
+
+ public IObjects Objects
+ {
+ get { return _objects; }
+ }
+ public Driver()
+ {
+ _objects = new Objects();
+ }
+ }
+}
55 Parse/Linq/ParseQuery.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace Parse.Linq
+{
+ public class ParseQuery<T> : IQueryable<T>
+ {
+ private readonly IQueryProvider _provider;
+ private readonly Expression _expression;
+
+ public ParseQuery(IQueryProvider provider)
+ {
+ _provider = provider;
+ _expression = Expression.Constant(this);
+ }
+ public ParseQuery(IQueryProvider provider, Expression expression)
+ {
+ _provider = provider;
+ _expression = expression;
+ }
+
+ public Expression Expression
+ {
+ get { return _expression; }
+ }
+
+ public Type ElementType
+ {
+ get { return typeof (T); }
+ }
+
+ public IQueryProvider Provider
+ {
+ get { return _provider; }
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ return ((IEnumerable<T>)_provider.Execute(_expression)).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public override string ToString()
+ {
+ return _provider.GetQueryText(_expression);
+ }
+ }
+}
42 Parse/Linq/ParseQueryProvider.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace Parse.Linq
+{
+ public class ParseQueryProvider : IQueryProvider
+ {
+ public IQueryable CreateQuery(Expression expression)
+ {
+ try
+ {
+ return (IQueryable)Activator.CreateInstance(typeof(ParseQuery<>).MakeGenericType(TypeSystem.GetElementType(expression.Type)), new object[] { this, expression });
+ }
+ catch (TargetInvocationException e)
+ {
+ throw e.InnerException;
+ }
+ }
+
+ public IQueryable<T> CreateQuery<T>(Expression expression)
+ {
+ return new ParseQuery<T>(this, expression);
+ }
+
+ public T Execute<T>(Expression expression)
+ {
+ return (T) Execute(expression);
+ }
+
+ public object Execute(Expression expression)
+ {
+ return Execute(expression);
+ }
+
+ public string GetQueryText(Expression expression)
+ {
+
+ }
+ }
+}
139 Parse/Linq/QueryTranslator.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+
+namespace Parse.Linq
+{
+ internal class QueryTranslator : ExpressionVisitor
+ {
+ private StringBuilder _builder;
+
+ internal string Translate(Expression expression)
+ {
+ _builder = new StringBuilder();
+ Visit(expression);
+ return _builder.ToString();
+ }
+
+ private static Expression StripQuotes(Expression e)
+ {
+ while (e.NodeType == ExpressionType.Quote)
+ {
+ e = ((UnaryExpression) e).Operand;
+ }
+ return e;
+ }
+
+ protected override Expression VisitMethodCall(MethodCallExpression m)
+ {
+ if (m.Method.DeclaringType == typeof (Queryable) && m.Method.Name == "Where")
+ {
+ _builder.Append("SELECT * FROM (");
+ Visit(m.Arguments[0]);
+ _builder.Append(") AS T WHERE ");
+ var lambda = (LambdaExpression) StripQuotes(m.Arguments[1]);
+ Visit(lambda.Body);
+ return m;
+ }
+ throw new NotSupportedException(string.Format("The method '{0}' is not supported", m.Method.Name));
+ }
+
+ protected override Expression VisitUnary(UnaryExpression u)
+ {
+ switch (u.NodeType)
+ {
+ case ExpressionType.Not:
+ _builder.Append(" NOT ");
+ Visit(u.Operand);
+ break;
+ default:
+ throw new NotSupportedException(string.Format("The unary operator '{0}' is not supported", u.NodeType));
+ }
+ return u;
+ }
+
+ protected override Expression VisitBinary(BinaryExpression b)
+ {
+ _builder.Append("(");
+ Visit(b.Left);
+ switch (b.NodeType)
+ {
+ case ExpressionType.And:
+ _builder.Append(" AND ");
+ break;
+ case ExpressionType.Or:
+ _builder.Append(" OR");
+ break;
+ case ExpressionType.Equal:
+ _builder.Append(" = ");
+ break;
+ case ExpressionType.NotEqual:
+ _builder.Append(" <> ");
+ break;
+ case ExpressionType.LessThan:
+ _builder.Append(" < ");
+ break;
+ case ExpressionType.LessThanOrEqual:
+ _builder.Append(" <= ");
+ break;
+ case ExpressionType.GreaterThan:
+ _builder.Append(" > ");
+ break;
+ case ExpressionType.GreaterThanOrEqual:
+ _builder.Append(" >= ");
+ break;
+ default:
+ throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));
+ }
+ Visit(b.Right);
+ _builder.Append(")");
+ return b;
+ }
+
+ protected override Expression VisitConstant(ConstantExpression c)
+ {
+ var q = c.Value as IQueryable;
+ if (q != null)
+ {
+ // assume constant nodes w/ IQueryables are table references
+ _builder.Append("SELECT * FROM ");
+ _builder.Append(q.ElementType.Name);
+ }
+ else if (c.Value == null)
+ {
+ _builder.Append("NULL");
+ }
+ else
+ {
+ switch (Type.GetTypeCode(c.Value.GetType()))
+ {
+ case TypeCode.Boolean:
+ _builder.Append(((bool) c.Value) ? 1 : 0);
+ break;
+ case TypeCode.String:
+ _builder.Append("'");
+ _builder.Append(c.Value);
+ _builder.Append("'");
+ break;
+ case TypeCode.Object:
+ throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", c.Value));
+ default:
+ _builder.Append(c.Value);
+ break;
+ }
+ }
+ return c;
+ }
+
+ protected override Expression VisitMemberAccess(MemberExpression m)
+ {
+ if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter)
+ {
+ _builder.Append(m.Member.Name);
+ return m;
+ }
+ throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
+ }
+ }
+}
51 Parse/Linq/TypeSystem.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+
+namespace Parse.Linq
+{
+ internal static class TypeSystem
+ {
+ internal static Type GetElementType(Type seqType)
+ {
+ var ienum = FindIEnumerable(seqType);
+ return ienum == null ? seqType : ienum.GetGenericArguments()[0];
+ }
+
+ private static Type FindIEnumerable(Type seqType)
+ {
+ if (seqType == null || seqType == typeof(string))
+ {
+ return null;
+ }
+ if (seqType.IsArray)
+ {
+ return typeof (IEnumerable<>).MakeGenericType(seqType.GetElementType());
+ }
+ if (seqType.IsGenericType)
+ {
+ foreach (var arg in seqType.GetGenericArguments())
+ {
+ var ienum = typeof (IEnumerable<>).MakeGenericType(arg);
+ if (ienum.IsAssignableFrom(seqType))
+ {
+ return ienum;
+ }
+ }
+ }
+ var ifaces = seqType.GetInterfaces();
+ if (ifaces.Length > 0)
+ {
+ foreach (var iface in ifaces)
+ {
+ var ienum = FindIEnumerable(iface);
+ if (ienum != null) return ienum;
+ }
+ }
+ if (seqType.BaseType != null && seqType.BaseType != typeof (object))
+ {
+ return FindIEnumerable(seqType.BaseType);
+ }
+ return null;
+ }
+ }
+}
22 Parse/Models/ParseObject.cs
@@ -0,0 +1,22 @@
+using System;
+using Newtonsoft.Json;
+
+namespace Parse
+{
+ public interface IParseObject
+ {
+ string Id { get; set; }
+ DateTime? CreatedAt { get; set; }
+ DateTime? UpdatedAt { get; set; }
+ }
+
+ public class ParseObject : IParseObject
+ {
+ [JsonProperty("objectId")]
+ public string Id { get; set; }
+ [JsonProperty("createdAt")]
+ public DateTime? CreatedAt { get; set; }
+ [JsonProperty("updatedAt")]
+ public DateTime? UpdatedAt { get; set; }
+ }
+}
10 Parse/Models/ResultsContainer.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace Parse
+{
+ public class ResultsContainer<T>
+ {
+ public IList<T> Results { get; set; }
+ public int Count { get; set; }
+ }
+}
133 Parse/Objects.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Parse.Queries;
+
+namespace Parse
+{
+ public interface IObjects
+ {
+ void Save(object o);
+ void Save(object o, Action<Response<ParseObject>> callback);
+ void Update(string id, object ok);
+ void Update(IParseObject o);
+ void Update(string id, object o, Action<Response<DateTime>> callback);
+ void Update(IParseObject o, Action<Response<DateTime>> callback);
+ void Get<T>(string id, Action<Response<T>> callback);
+ void Delete<T>(T o) where T : IParseObject;
+ void Delete<T>(string id, Action<Response<T>> callback);
+ void Delete<T>(T o, Action<Response<T>> callback) where T : IParseObject;
+ IParseQuery<T> Query<T>();
+ }
+
+ public class Objects : IObjects
+ {
+ public void Save(object o)
+ {
+ Save(o, null);
+ }
+
+ public void Save(object o, Action<Response<ParseObject>> callback)
+ {
+ var payload = JsonConvert.SerializeObject(o);
+ Communicator.SendDataPayload<ParseObject>(Communicator.Post, UrlFor(o), payload, r =>
+ {
+ if (callback == null) return;
+ if (r.Success) { r.Data = JsonConvert.DeserializeObject<ParseObject>(r.Raw);}
+ callback(r);
+ });
+ }
+
+ public void Update(string id, object ok)
+ {
+ Update(id, ok, null);
+ }
+
+ public void Update(IParseObject o)
+ {
+ Update(o, null);
+ }
+
+ public void Update(IParseObject o, Action<Response<DateTime>> callback)
+ {
+ Update(o.Id, o, callback);
+ }
+
+ public void Update(string id, object o, Action<Response<DateTime>> callback)
+ {
+ var url = string.Concat(UrlFor(o), "/", id);
+ var payload = JsonConvert.SerializeObject(o);
+ Communicator.SendDataPayload<DateTime>(Communicator.Put, url, payload, r =>
+ {
+ if (callback == null) return;
+ if (r.Success) { r.Data = JsonConvert.DeserializeObject<UpdatedAtContainer>(r.Raw).UpdatedAt; }
+ callback(r);
+ });
+ }
+
+ public void Get<T>(string id, Action<Response<T>> callback)
+ {
+ var url = string.Concat(UrlFor<T>(), "/", id);
+ Communicator.SendQueryPayload(Communicator.Get, url, GotInstanceCallback(callback));
+ }
+
+ public void Delete<T>(T o) where T : IParseObject
+ {
+ Delete(o, null);
+ }
+
+ public void Delete<T>(T o, Action<Response<T>> callback) where T : IParseObject
+ {
+ Delete(o.Id, callback);
+ }
+
+ public void Delete<T>(string id, Action<Response<T>> callback)
+ {
+ var url = string.Concat(UrlFor<T>(), "/", id);
+ Communicator.SendQueryPayload(Communicator.Delete, url, GotInstanceCallback(callback));
+ }
+
+ public IParseQuery<T> Query<T>()
+ {
+ return new ParseQuery<T>(this);
+ }
+
+ public void Query<T>(IDictionary<string, object> selector, Action<Response<ResultsContainer<T>>> callback)
+ {
+ Communicator.SendQueryPayload<ResultsContainer<T>>(Communicator.Get, UrlFor<T>(), selector, r =>
+ {
+ if (r.Success) { r.Data = JsonConvert.DeserializeObject<ResultsContainer<T>>(r.Raw); }
+ callback(r);
+ });
+ }
+
+ private static Action<Response<T>> GotInstanceCallback<T>(Action<Response<T>> callback)
+ {
+ return r =>
+ {
+ if (callback == null) return;
+ if (r.Success) { r.Data = JsonConvert.DeserializeObject<T>(r.Raw); }
+ callback(r);
+ };
+ }
+
+ private static string UrlFor<T>()
+ {
+ return UrlFor(typeof (T));
+ }
+ private static string UrlFor(object o)
+ {
+ return UrlFor(o.GetType());
+ }
+ private static string UrlFor(Type t)
+ {
+ return string.Concat("classes/", t.Name);
+ }
+
+ private class UpdatedAtContainer
+ {
+ [JsonProperty("updatedAt")]
+ public DateTime UpdatedAt { get; set; }
+ }
+ }
+}
78 Parse/Parse.csproj
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>10.0.20506</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{7C665CCD-7531-4AEA-9425-6831A3F84BB9}</ProjectGuid>
+ <ProjectTypeGuids>{C089C8C0-30E0-4E22-80C0-CE093F111A43};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Parse</RootNamespace>
+ <AssemblyName>Parse</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <SilverlightVersion>$(TargetFrameworkVersion)</SilverlightVersion>
+ <TargetFrameworkProfile>WindowsPhone71</TargetFrameworkProfile>
+ <TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier>
+ <SilverlightApplication>false</SilverlightApplication>
+ <ValidateXaml>true</ValidateXaml>
+ <ThrowErrorsInValidation>true</ThrowErrorsInValidation>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>Bin\Debug</OutputPath>
+ <DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
+ <NoStdLib>true</NoStdLib>
+ <NoConfig>true</NoConfig>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>Bin\Release</OutputPath>
+ <DefineConstants>TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
+ <NoStdLib>true</NoStdLib>
+ <NoConfig>true</NoConfig>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Newtonsoft.Json.WindowsPhone">
+ <HintPath>..\references\Newtonsoft.Json.WindowsPhone.dll</HintPath>
+ </Reference>
+ <Reference Include="system" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Net" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Components\Communicator.cs" />
+ <Compile Include="Components\ParseConfiguration.cs" />
+ <Compile Include="Components\Response.cs" />
+ <Compile Include="Models\ResultsContainer.cs" />
+ <Compile Include="Objects.cs" />
+ <Compile Include="Driver.cs" />
+ <Compile Include="ParseException.cs" />
+ <Compile Include="Models\ParseObject.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Queries\ExpressionVisitor.cs" />
+ <Compile Include="Queries\ParseQuery.cs" />
+ <Compile Include="Queries\WhereTranslator.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="todo.txt" />
+ </ItemGroup>
+ <Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\$(TargetFrameworkVersion)\Microsoft.Silverlight.$(TargetFrameworkProfile).Overrides.targets" />
+ <Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\$(TargetFrameworkVersion)\Microsoft.Silverlight.CSharp.targets" />
+ <ProjectExtensions />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
18 Parse/ParseException.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Parse
+{
+ public class ParseException : Exception
+ {
+ public ErrorMessage Details { get; set; }
+
+ public ParseException() { }
+ public ParseException(ErrorMessage message) : this(message.Message, message.InnerException){}
+ public ParseException(string message) : base(message) { }
+ public ParseException(string message, Exception innerException) : base(message, innerException) { }
+ public ParseException(ErrorMessage message, Exception innerException) : base(message.Message, innerException)
+ {
+ Details = message;
+ }
+ }
+}
66 Parse/ParseFull.csproj
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{228A0996-6132-407D-8FC4-C74A7C3F0F13}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Parse2</RootNamespace>
+ <AssemblyName>Parse2</AssemblyName>
+ <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Newtonsoft.Json.Net35">
+ <HintPath>..\references\Newtonsoft.Json.Net35.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Data" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Components\Communicator.cs" />
+ <Compile Include="Components\ParseConfiguration.cs" />
+ <Compile Include="Components\Response.cs" />
+ <Compile Include="Driver.cs" />
+ <Compile Include="Models\ParseObject.cs" />
+ <Compile Include="Models\ResultsContainer.cs" />
+ <Compile Include="Objects.cs" />
+ <Compile Include="ParseException.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Queries\ExpressionVisitor.cs" />
+ <Compile Include="Queries\ParseQuery.cs" />
+ <Compile Include="Queries\WhereTranslator.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="Parse.csproj" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
11 Parse/Properties/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("parse.com for Windows Phone")]
+[assembly: AssemblyDescription("A Windows Phone library for accessing parse.com services")]
+[assembly: AssemblyProduct("parse_dotnet")]
+[assembly: AssemblyCopyright("Copyright © Karl Seguin 2012")]
+[assembly: ComVisible(false)]
+[assembly: Guid("6d421f43-0b24-4aa4-89a2-dc820ba0b2d8")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
285 Parse/Queries/ExpressionVisitor.cs
@@ -0,0 +1,285 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq.Expressions;
+
+namespace Parse.Queries
+{
+ public abstract class ExpressionVisitor
+ {
+ protected virtual Expression Visit(Expression exp)
+ {
+ if (exp == null)
+ return exp;
+ switch (exp.NodeType)
+ {
+ case ExpressionType.Negate:
+ case ExpressionType.NegateChecked:
+ case ExpressionType.Not:
+ case ExpressionType.Convert:
+ case ExpressionType.ConvertChecked:
+ case ExpressionType.ArrayLength:
+ case ExpressionType.Quote:
+ case ExpressionType.TypeAs:
+ return VisitUnary((UnaryExpression) exp);
+ case ExpressionType.Add:
+ case ExpressionType.AddChecked:
+ case ExpressionType.Subtract:
+ case ExpressionType.SubtractChecked:
+ case ExpressionType.Multiply:
+ case ExpressionType.MultiplyChecked:
+ case ExpressionType.Divide:
+ case ExpressionType.Modulo:
+ case ExpressionType.And:
+ case ExpressionType.AndAlso:
+ case ExpressionType.Or:
+ case ExpressionType.OrElse:
+ case ExpressionType.LessThan:
+ case ExpressionType.LessThanOrEqual:
+ case ExpressionType.GreaterThan:
+ case ExpressionType.GreaterThanOrEqual:
+ case ExpressionType.Equal:
+ case ExpressionType.NotEqual:
+ case ExpressionType.Coalesce:
+ case ExpressionType.ArrayIndex:
+ case ExpressionType.RightShift:
+ case ExpressionType.LeftShift:
+ case ExpressionType.ExclusiveOr:
+ return VisitBinary((BinaryExpression) exp);
+ case ExpressionType.TypeIs:
+ return VisitTypeIs((TypeBinaryExpression) exp);
+ case ExpressionType.Conditional:
+ return VisitConditional((ConditionalExpression) exp);
+ case ExpressionType.Constant:
+ return VisitConstant((ConstantExpression) exp);
+ case ExpressionType.Parameter:
+ return VisitParameter((ParameterExpression) exp);
+ case ExpressionType.MemberAccess:
+ return VisitMemberAccess((MemberExpression) exp);
+ case ExpressionType.Call:
+ return VisitMethodCall((MethodCallExpression) exp);
+ case ExpressionType.Lambda:
+ return VisitLambda((LambdaExpression) exp);
+ case ExpressionType.New:
+ return VisitNew((NewExpression) exp);
+ case ExpressionType.NewArrayInit:
+ case ExpressionType.NewArrayBounds:
+ return VisitNewArray((NewArrayExpression) exp);
+ case ExpressionType.Invoke:
+ return VisitInvocation((InvocationExpression) exp);
+ case ExpressionType.MemberInit:
+ return VisitMemberInit((MemberInitExpression) exp);
+ case ExpressionType.ListInit:
+ return VisitListInit((ListInitExpression) exp);
+ default:
+ throw new Exception(string.Format("Unhandled expression type: '{0}'", exp.NodeType));
+ }
+ }
+
+ protected virtual MemberBinding VisitBinding(MemberBinding binding)
+ {
+ switch (binding.BindingType)
+ {
+ case MemberBindingType.Assignment:
+ return VisitMemberAssignment((MemberAssignment) binding);
+ case MemberBindingType.MemberBinding:
+ return VisitMemberMemberBinding((MemberMemberBinding) binding);
+ case MemberBindingType.ListBinding:
+ return VisitMemberListBinding((MemberListBinding) binding);
+ default:
+ throw new Exception(string.Format("Unhandled binding type '{0}'", binding.BindingType));
+ }
+ }
+
+ protected virtual ElementInit VisitElementInitializer(ElementInit initializer)
+ {
+ var arguments = VisitExpressionList(initializer.Arguments);
+ return arguments != initializer.Arguments ? Expression.ElementInit(initializer.AddMethod, arguments) : initializer;
+ }
+
+ protected virtual Expression VisitUnary(UnaryExpression u)
+ {
+ var operand = Visit(u.Operand);
+ return operand != u.Operand ? Expression.MakeUnary(u.NodeType, operand, u.Type, u.Method) : u;
+ }
+
+ protected virtual Expression VisitBinary(BinaryExpression b)
+ {
+ var left = Visit(b.Left);
+ var right = Visit(b.Right);
+ var conversion = Visit(b.Conversion);
+ if (left != b.Left || right != b.Right || conversion != b.Conversion)
+ {
+ return b.NodeType == ExpressionType.Coalesce ? Expression.Coalesce(left, right, conversion as LambdaExpression) : Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method);
+ }
+ return b;
+ }
+
+ protected virtual Expression VisitTypeIs(TypeBinaryExpression b)
+ {
+ var expr = Visit(b.Expression);
+ return expr != b.Expression ? Expression.TypeIs(expr, b.TypeOperand) : b;
+ }
+
+ protected virtual Expression VisitConstant(ConstantExpression c)
+ {
+ return c;
+ }
+
+ protected virtual Expression VisitConditional(ConditionalExpression c)
+ {
+ var test = Visit(c.Test);
+ var ifTrue = Visit(c.IfTrue);
+ var ifFalse = Visit(c.IfFalse);
+ return test != c.Test || ifTrue != c.IfTrue || ifFalse != c.IfFalse ? Expression.Condition(test, ifTrue, ifFalse) : c;
+ }
+
+ protected virtual Expression VisitParameter(ParameterExpression p)
+ {
+ return p;
+ }
+
+ protected virtual Expression VisitMemberAccess(MemberExpression m)
+ {
+ var exp = Visit(m.Expression);
+ return exp != m.Expression ? Expression.MakeMemberAccess(exp, m.Member) : m;
+ }
+
+ protected virtual Expression VisitMethodCall(MethodCallExpression m)
+ {
+ var obj = Visit(m.Object);
+ var args = VisitExpressionList(m.Arguments);
+ return obj != m.Object || args != m.Arguments ? Expression.Call(obj, m.Method, args) : m;
+ }
+
+ protected virtual ReadOnlyCollection<Expression> VisitExpressionList(ReadOnlyCollection<Expression> original)
+ {
+ List<Expression> list = null;
+ for (int i = 0, n = original.Count; i < n; i++)
+ {
+ var p = Visit(original[i]);
+ if (list != null)
+ {
+ list.Add(p);
+ }
+ else if (p != original[i])
+ {
+ list = new List<Expression>(n);
+ for (var j = 0; j < i; j++)
+ {
+ list.Add(original[j]);
+ }
+ list.Add(p);
+ }
+ }
+ return list != null ? list.AsReadOnly() : original;
+ }
+
+ protected virtual MemberAssignment VisitMemberAssignment(MemberAssignment assignment)
+ {
+ var e = Visit(assignment.Expression);
+ return e != assignment.Expression ? Expression.Bind(assignment.Member, e) : assignment;
+ }
+
+ protected virtual MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding)
+ {
+ var bindings = VisitBindingList(binding.Bindings);
+ return bindings != binding.Bindings ? Expression.MemberBind(binding.Member, bindings) : binding;
+ }
+
+ protected virtual MemberListBinding VisitMemberListBinding(MemberListBinding binding)
+ {
+ var initializers = VisitElementInitializerList(binding.Initializers);
+ return initializers != binding.Initializers ? Expression.ListBind(binding.Member, initializers) : binding;
+ }
+
+ protected virtual IEnumerable<MemberBinding> VisitBindingList(ReadOnlyCollection<MemberBinding> original)
+ {
+ List<MemberBinding> list = null;
+ for (int i = 0, n = original.Count; i < n; i++)
+ {
+ var b = VisitBinding(original[i]);
+ if (list != null)
+ {
+ list.Add(b);
+ }
+ else if (b != original[i])
+ {
+ list = new List<MemberBinding>(n);
+ for (var j = 0; j < i; j++)
+ {
+ list.Add(original[j]);
+ }
+ list.Add(b);
+ }
+ }
+ return list != null ? (IEnumerable<MemberBinding>) list : original;
+ }
+
+ protected virtual IEnumerable<ElementInit> VisitElementInitializerList(ReadOnlyCollection<ElementInit> original)
+ {
+ List<ElementInit> list = null;
+ for (int i = 0, n = original.Count; i < n; i++)
+ {
+ var init = VisitElementInitializer(original[i]);
+ if (list != null)
+ {
+ list.Add(init);
+ }
+ else if (init != original[i])
+ {
+ list = new List<ElementInit>(n);
+ for (var j = 0; j < i; j++)
+ {
+ list.Add(original[j]);
+ }
+ list.Add(init);
+ }
+ }
+ return list != null ? (IEnumerable<ElementInit>) list : original;
+ }
+
+ protected virtual Expression VisitLambda(LambdaExpression lambda)
+ {
+ var body = Visit(lambda.Body);
+ return body != lambda.Body ? Expression.Lambda(lambda.Type, body, lambda.Parameters) : lambda;
+ }
+
+ protected virtual NewExpression VisitNew(NewExpression nex)
+ {
+ var args = VisitExpressionList(nex.Arguments);
+ return args != nex.Arguments ? (nex.Members != null ? Expression.New(nex.Constructor, args, nex.Members) : Expression.New(nex.Constructor, args)) : nex;
+ }
+
+ protected virtual Expression VisitMemberInit(MemberInitExpression init)
+ {
+ var n = VisitNew(init.NewExpression);
+ var bindings = VisitBindingList(init.Bindings);
+ return n != init.NewExpression || bindings != init.Bindings ? Expression.MemberInit(n, bindings) : init;
+ }
+
+ protected virtual Expression VisitListInit(ListInitExpression init)
+ {
+ var n = VisitNew(init.NewExpression);
+ var initializers = VisitElementInitializerList(init.Initializers);
+ return n != init.NewExpression || initializers != init.Initializers ? Expression.ListInit(n, initializers) : init;
+ }
+
+ protected virtual Expression VisitNewArray(NewArrayExpression na)
+ {
+ var exprs = VisitExpressionList(na.Expressions);
+ if (exprs != na.Expressions)
+ {
+ return na.NodeType == ExpressionType.NewArrayInit ? Expression.NewArrayInit(na.Type.GetElementType(), exprs) : Expression.NewArrayBounds(na.Type.GetElementType(), exprs);
+ }
+ return na;
+ }
+
+ protected virtual Expression VisitInvocation(InvocationExpression iv)
+ {
+ var args = VisitExpressionList(iv.Arguments);
+ var expr = Visit(iv.Expression);
+ return args != iv.Arguments || expr != iv.Expression ? Expression.Invoke(expr, args) : iv;
+ }
+ }
+}
107 Parse/Queries/ParseQuery.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Reflection;
+using Newtonsoft.Json;
+
+namespace Parse.Queries
+{
+ public interface IParseQuery<T>
+ {
+ IParseQuery<T> Where(Expression<Func<T, bool>> expression);
+ IParseQuery<T> Count();
+ IParseQuery<T> Skip(int skip);
+ IParseQuery<T> Limit(int limit);
+ IParseQuery<T> Sort(Expression<Func<T, object>> expression, bool ascending);
+ IParseQuery<T> SortAscending(Expression<Func<T, object>> expression);
+ IParseQuery<T> SortDescending(Expression<Func<T, object>> expression);
+
+ void Execute(Action<Response<ResultsContainer<T>>> callback);
+ }
+
+ public class ParseQuery<T> : ExpressionVisitor, IParseQuery<T>
+ {
+ private readonly Objects _objects;
+ private IDictionary<string, object> _where;
+ private bool _count;
+ private int? _skip;
+ private int? _limit;
+ private bool _sortDirection;
+ private string _sort;
+
+ public ParseQuery(Objects objects)
+ {
+ _objects = objects;
+ }
+
+ public IParseQuery<T> Where(Expression<Func<T, bool>> expression)
+ {
+ _where = new WhereTranslator().Translate(expression.Body);
+ return this;
+ }
+
+ public IParseQuery<T> Count()
+ {
+ _count = true;
+ return this;
+ }
+
+ public IParseQuery<T> Skip(int skip)
+ {
+ _skip = skip;
+ return this;
+ }
+
+ public IParseQuery<T> Limit(int limit)
+ {
+ _limit = limit;
+ return this;
+ }
+
+ public IParseQuery<T> SortAscending(Expression<Func<T, object>> expression)
+ {
+ return Sort(expression, true);
+ }
+
+ public IParseQuery<T> SortDescending(Expression<Func<T, object>> expression)
+ {
+ return Sort(expression, false);
+ }
+
+ public IParseQuery<T> Sort(Expression<Func<T, object>> expression, bool ascending)
+ {
+ var e = GetMemberExpression(expression);
+ if (e == null || e.Expression.NodeType != ExpressionType.Parameter)
+ {
+ throw new NotSupportedException(string.Format("The member '{0}' is not supported for sorting", expression.Body.NodeType));
+ }
+ _sort = e.Member.Name;
+ _sortDirection = ascending;
+ return this;
+ }
+
+ private static MemberExpression GetMemberExpression(Expression<Func<T, object>> expression)
+ {
+ if (expression.Body is MemberExpression)
+ {
+ return (MemberExpression) expression.Body;
+ }
+ if (expression.Body is UnaryExpression)
+ {
+ return ((UnaryExpression)expression.Body).Operand as MemberExpression;
+ }
+ return null;
+ }
+
+ public void Execute(Action<Response<ResultsContainer<T>>> callback)
+ {
+ var dictionary = new Dictionary<string, object>();
+ if (_where != null) { dictionary["where"] = JsonConvert.SerializeObject(_where); }
+ if (_count) { dictionary["count"] = '1'; }
+ if (_skip.HasValue) { dictionary["skip"] = _skip.Value; }
+ if (_limit.HasValue) { dictionary["limit"] = _limit.Value; }
+ if (_sort != null) { dictionary["order"] = _sortDirection ? _sort : string.Concat('-', _sort); }
+ _objects.Query(dictionary, callback);
+ }
+ }
+}
136 Parse/Queries/WhereTranslator.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace Parse.Queries
+{
+ internal class WhereTranslator : ExpressionVisitor
+ {
+ private IDictionary<string, object> _where;
+ private string _currentKey;
+ private string _currentOperation;
+ private bool _inversed;
+
+
+ internal IDictionary<string, object> Translate(Expression expression)
+ {
+ _where = new Dictionary<string, object>();
+ Visit(expression);
+ return _where;
+ }
+
+ protected override Expression VisitUnary(UnaryExpression u)
+ {
+ switch (u.NodeType)
+ {
+ case ExpressionType.Not:
+ _inversed = !_inversed;
+ Visit(u.Operand);
+ return u;
+ default:
+ throw new NotSupportedException(string.Format("The unary operator '{0}' is not supported", u.NodeType));
+ }
+ }
+
+ protected override Expression VisitBinary(BinaryExpression b)
+ {
+ _currentKey = _currentOperation = null;
+ _inversed = false;
+ Visit(b.Left);
+ switch (b.NodeType)
+ {
+ case ExpressionType.Equal:
+ break;
+ case ExpressionType.NotEqual:
+ SetNestedDictionary("$ne");
+ break;
+ case ExpressionType.LessThan:
+ SetNestedDictionary("$lt");
+ break;
+ case ExpressionType.LessThanOrEqual:
+ SetNestedDictionary("$lte");
+ break;
+ case ExpressionType.GreaterThan:
+ SetNestedDictionary("$gt");
+ break;
+ case ExpressionType.GreaterThanOrEqual:
+ SetNestedDictionary("$gte");
+ break;
+ case ExpressionType.AndAlso:
+ break;
+ default:
+ throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));
+ }
+ Visit(b.Right);
+ return b;
+ }
+
+ protected override Expression VisitConstant(ConstantExpression c)
+ {
+ SetValue(c.Value);
+ return c;
+ }
+
+ protected override Expression VisitMemberAccess(MemberExpression m)
+ {
+ if (m.Expression != null)
+ {
+ if (m.Expression.NodeType == ExpressionType.Constant)
+ {
+ var objectMember = Expression.Convert(m, typeof(object));
+ var getterLambda = Expression.Lambda<Func<object>>(objectMember, (ParameterExpression) m.Expression);
+ var getter = getterLambda.Compile();
+ SetValue(getter());
+ return m;
+ }
+ if (m.Expression.NodeType == ExpressionType.Parameter)
+ {
+ _currentKey = m.Member.Name;
+ if (!_where.ContainsKey(_currentKey))
+ {
+ if (m.Member is PropertyInfo && ((PropertyInfo)m.Member).PropertyType == typeof(bool))
+ {
+ _where[_currentKey] = !_inversed;
+ }
+ else
+ {
+ _where[_currentKey] = null;
+ }
+
+ }
+ return m;
+ }
+ }
+ throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
+ }
+
+ private void SetNestedDictionary(string operation)
+ {
+ var nested = _where[_currentKey] as Dictionary<string, object>;
+ if (nested == null)
+ {
+ nested = new Dictionary<string, object>(3);
+ _where[_currentKey] = nested;
+ }
+ _currentOperation = operation;
+ nested.Add(operation, null);
+ }
+
+ private void SetValue(object o)
+ {
+ if (string.IsNullOrEmpty(_currentKey))
+ {
+ throw new InvalidOperationException("Value can only be on the right-hand side of an operation");
+ }
+ if (_currentOperation == null)
+ {
+ _where[_currentKey] = o;
+ }
+ else
+ {
+ ((IDictionary<string, object>)_where[_currentKey])[_currentOperation] = o;
+ }
+ }
+ }
+}
11 Parse/todo.txt
@@ -0,0 +1,11 @@
+Support Id in queries
+ .Where(c => c.Name == "Jessica" && c.Id == "abc");
+ need to map this to objectId
+
+Support $in in queries
+Paging
+Count
+Cache
+regex
+dates
+sorting
115 readme.md
@@ -0,0 +1,115 @@
+
+# Parse for Windows Phone
+This is a Windows Phone library for accessing the <http://parse.com> web services via the REST api.
+
+This library is in very early development.
+
+## Configuration
+On application start you should configure the driver by supplying your application id and master key (authorization with the REST API is done via the master key and not the client key):
+
+ ParseConfiguration.Configure(APPLICATION_ID, MASTER_KEY);
+
+Once configured, you can create an instance of the parse driver:
+
+ var parse = new Driver();
+
+This instance is both thread-safe and inexpensive to create..you can either keep a single one around or create them as needed.
+
+## Objects
+Parse allows developers to easily persist data from a mobile device into the cloud. The data can be retrieved at a later time.
+
+### ParseObject & IParseObject
+Although Parse can deal with any serializable object, you'll gain some small benefits by inheriting from the `ParseObject` or, failing that, implementing `IParseObject`.
+
+### Saving Objects
+Parse can save any object, as long as it can be serialized to JSON (using [Json.NET](http://json.codeplex.com/))
+
+ var user = new User{Name = "Goku", PowerLevel = 9001};
+ parse.Objects.Save(user);
+
+A second optional callback can be provided:
+
+ var user = new User{Name = "Goku", PowerLevel = 9001};
+ parse.Objects.Save(user, r =>
+ {
+ if (r.Success)
+ {
+ var id = r.Data.Id;
+ var createdAt = r.Data.CreatedAt;
+ }
+ else
+ {
+ //log r.Error.Message;
+ }
+ });
+
+Due to the asynchronous nature of the driver, the original `user` instance won't be updated with the parse id and created at values (there's just something weird about having this updated at some unspecific time by a separate thread).
+
+### Updating Objects
+Updating works similar to saving. Again, the callback is optional. You can either pass in any object (along with the object's string id), or you can pass in an object of `IParseObject`:
+
+ var user = new User{Id = "123", Name = "Goku", PowerLevel = 9001};
+ parse.Objects.Update(user.Id, user);
+
+ //or
+ var blah = new ParseObjectChild{Id = "123", BadExample = true};
+ parse.Objects.Update(blah);
+
+ //with callback
+ var blah = new ParseObjectChild{Id = "123", BadExample = true};
+ parse.Objects.Update(blah, r =>
+ {
+ if (r.Success)
+ {
+ var updatedAt = r.Data.UpdatedAt;
+ }
+ });
+
+### Deleting Objects
+This should be obvious by now.
+
+
+### Getting an Object By Id
+
+ parse.Objects.Get<User>("9000!", r =>
+ {
+ if (r.Success)
+ {
+ var user = r.Data;
+ }
+ });
+
+### Querying Objects
+The library has a very basic query interface:
+
+ parse.Objects.Query<User>().Where(c => c.Name == "Leto" && c.Home == "Arakis").Execute(r =>
+ {
+ if (r.Success)
+ {
+ var found = r.Data.Results;
+ }
+ });
+
+Or (`||`) are not supported. Supported operators are `==`, `!=`, `>`, `>=`, `<` and `<=` and boolean (`c.IsGhola` and `!c.IsGhola`).
+
+`Skip` and `Limit` are supported for paging:
+
+ parse.Objects.Query<User>().Where(c => c.Name == "Leto").Skip(10).Limit(100).Execute(r =>
+ {
+ if (r.Success)
+ {
+ var found = r.Data.Results;
+ }
+ });
+
+
+When `Count` is called, a count of records is also returned. This can be combined with `Limit(0)` to only return a count...or `Limit(X)` to return the first X documents plus a count of documents:
+
+ parse.Objects.Query<User>().Where(c => c.Name == "Leto").Count().Limit(0).Execute(r =>
+ {
+ if (r.Success)
+ {
+ var found = r.Data.Results; //will be empty
+ var count = r.Data.Count;
+ }
+ });
Please sign in to comment.
Something went wrong with that request. Please try again.