From 1279a62e42589febbe22e5de450dfdafbd4a4c50 Mon Sep 17 00:00:00 2001
From: Patrick Simpson
Date: Wed, 3 May 2017 14:43:36 +0200
Subject: [PATCH] [KOE-24] Partial implementation of sync state display.
Committing with disabled feature for backup and to allow build to succeed.
---
.../AcaciaZPushPlugin.csproj | 3 +
.../Features/SyncState/FeatureSyncState.cs | 131 +++++++++++++++++-
.../API/SharedFolders/AvailableFolder.cs | 16 +--
.../ZPush/Connect/Soap/SoapFieldAttribute.cs | 21 +++
.../ZPush/Connect/Soap/SoapSerializer.cs | 91 ++++++++----
.../ZPush/Connect/ZPushWebService.cs | 1 +
.../AcaciaZPushPlugin/ZPush/ZPushFolder.cs | 2 +-
7 files changed, 221 insertions(+), 44 deletions(-)
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapFieldAttribute.cs
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
index 1fada67..9a9ccd2 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
@@ -242,6 +242,7 @@
+
@@ -352,6 +353,7 @@
+
@@ -652,6 +654,7 @@
+
10.0
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs
index 0d840d2..a6f26f5 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs
@@ -27,11 +27,18 @@ using Acacia.ZPush.API.SharedFolders;
using static Acacia.DebugOptions;
using Acacia.UI.Outlook;
using System.Drawing;
+using Acacia.ZPush.Connect;
+using Acacia.ZPush.Connect.Soap;
+
+// Prevent field assignment warnings
+#pragma warning disable 0649
namespace Acacia.Features.SyncState
-{
+{
+
public class FeatureSyncState : FeatureDisabled, FeatureWithRibbon
{
+ // TODO: this is largely about progress bars, separate that?
private class SyncStateData : DataProvider
{
private FeatureSyncState _feature;
@@ -39,7 +46,7 @@ namespace Acacia.Features.SyncState
///
/// Number in range [0,1]
///
- private double _syncProgress;
+ private double _syncProgress = 1;
public double SyncProgress
{
get { return _syncProgress; }
@@ -142,10 +149,12 @@ namespace Acacia.Features.SyncState
{
_button = RegisterButton(this, "Progress", true, ShowSyncState, ZPushBehaviour.None);
_button.DataProvider = new SyncStateData(this);
+ Watcher.Sync.AddTask(this, Name, CheckSyncState);
+
// Debug timer to increase progress
var timer = new System.Windows.Forms.Timer();
- timer.Interval = 1000;
+ timer.Interval = 15000;
timer.Tick += (o, args) =>
{
SyncStateData data = (SyncStateData)_button.DataProvider;
@@ -153,9 +162,121 @@ namespace Acacia.Features.SyncState
if (val > 1.01)
val = 0;
data.SyncProgress = val;
- };
- timer.Start();
+ foreach(ZPushAccount account in Watcher.Accounts.GetAccounts())
+ {
+ CheckSyncState(account);
+ }
+ };
+ //timer.Start();
+ }
+
+ private class DeviceDetails : ISoapSerializable
+ {
+ public class SyncData
+ {
+ public string status;
+ public long total;
+ public long done;
+ public long todo;
+
+ override public string ToString()
+ {
+ return string.Format("{0}: {1}/{2}={3}", status, total, done, todo);
+ }
+ }
+
+ [SoapField]
+ public struct ContentData
+ {
+ [SoapField(1)]
+ public string synckey;
+
+ [SoapField(2)]
+ public int type; // TODO: SyncType
+
+ [SoapField(3)]
+ public string[] flags;
+
+ [SoapField(4)]
+ public SyncData Sync;
+
+ public bool IsSyncing { get { return Sync != null; } }
+
+ [SoapField(5)]
+ public string id; // TODO: backend folder id
+ }
+
+ public struct Data2
+ {
+ public string deviceid;
+ public string devicetype;
+ public string domain;
+ public string deviceuser;
+ public string useragent;
+ public DateTime firstsynctime;
+ public string announcedasversion;
+ public string hierarchyuuid;
+
+ public string asversion;
+ public DateTime lastupdatetime;
+ public string koeversion;
+ public string koebuild;
+ public DateTime koebuilddate;
+ public string[] koecapabilities;
+ public string koegabbackendfolderid;
+
+ public bool changed;
+ public DateTime lastsynctime;
+ public bool hasfolderidmapping;
+
+ public Dictionary contentdata;
+
+ // TODO: additionalfolders
+ // TODO: hierarchycache
+ // TODO: useragenthistory
+ }
+
+ public struct Data
+ {
+ public Data2 data;
+ public bool changed;
+ }
+
+ private readonly Data _data;
+
+ public DeviceDetails(Data data)
+ {
+ this._data = data;
+ }
+
+ public Data SoapSerialize()
+ {
+ return _data;
+ }
+
+ public Dictionary Content
+ {
+ get { return _data.data.contentdata; }
+ }
+ }
+
+ private class GetDeviceDetailsRequest : SoapRequest
+ {
+ }
+
+ private void CheckSyncState(ZPushAccount account)
+ {
+ // TODO: we probably want one invocation for all accounts
+ using (ZPushConnection connection = account.Connect())
+ using (ZPushWebServiceDevice deviceService = connection.DeviceService)
+ {
+ // Fetch
+ DeviceDetails details = deviceService.Execute(new GetDeviceDetailsRequest());
+
+ foreach(KeyValuePair cd in details.Content)
+ Logger.Instance.Trace(this, "SYNC: {0}: {1}: {2} -- {3}", cd.Key, cd.Value.IsSyncing, cd.Value.Sync, cd.Value.synckey);
+ }
}
private void ShowSyncState()
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/AvailableFolder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/AvailableFolder.cs
index 82e18ee..89daed6 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/AvailableFolder.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/AvailableFolder.cs
@@ -35,10 +35,10 @@ namespace Acacia.ZPush.API.SharedFolders
///
public struct SoapData
{
- public SyncId ServerId;
- public SyncId ParentId;
- public string DisplayName;
- public OutlookConstants.SyncType Type;
+ public SyncId serverid;
+ public SyncId parentid;
+ public string displayname;
+ public OutlookConstants.SyncType type;
public BackendId BackendId;
// TODO: are there ever flags on available folders? They are sent by the server
@@ -71,12 +71,12 @@ namespace Acacia.ZPush.API.SharedFolders
#region Ids and properties
- public SyncId ServerId { get { return _data.ServerId; } }
- public SyncId ParentId { get { return _data.ParentId; } }
+ public SyncId ServerId { get { return _data.serverid; } }
+ public SyncId ParentId { get { return _data.parentid; } }
public BackendId ParentIdAsBackend { get { return Parent?.BackendId ?? BackendId.NONE; } }
public BackendId BackendId { get { return _data.BackendId; } }
- public string Name { get { return _data.DisplayName; } }
+ public string Name { get { return _data.displayname; } }
public string DefaultName
{
@@ -90,7 +90,7 @@ namespace Acacia.ZPush.API.SharedFolders
}
}
- public OutlookConstants.SyncType Type { get { return _data.Type; } }
+ public OutlookConstants.SyncType Type { get { return _data.type; } }
public GABUser Store { get; private set; }
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapFieldAttribute.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapFieldAttribute.cs
new file mode 100644
index 0000000..ab7c8a3
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapFieldAttribute.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.ZPush.Connect.Soap
+{
+ ///
+ /// Specifies a Soap field id for a field. If used, the class must also be annotated with SoapField.
+ ///
+ public class SoapFieldAttribute : Attribute
+ {
+ public object FieldId { get; set; }
+
+ public SoapFieldAttribute(object fieldId = null)
+ {
+ this.FieldId = fieldId;
+ }
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapSerializer.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapSerializer.cs
index 6b3c51a..9abd813 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapSerializer.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapSerializer.cs
@@ -167,9 +167,12 @@ namespace Acacia.ZPush.Connect.Soap
return node.InnerText.ToLower().Equals("true");
}
}
- private class TypeHandlerInt : TypeHandler
+
+ abstract private class TypeHandlerIntegral : TypeHandler
{
- public TypeHandlerInt() : base(SoapConstants.XMLNS_XSD, null, typeof(int)) { }
+ public TypeHandlerIntegral(string xmlns, string name, Type baseType) : base(xmlns, name, baseType)
+ {
+ }
public override void Serialize(string name, object value, StringBuilder s)
{
@@ -178,22 +181,24 @@ namespace Acacia.ZPush.Connect.Soap
protected override object DeserializeContents(XmlNode node, Type expectedType)
{
- return long.Parse(node.InnerText);
+ long value = long.Parse(node.InnerText);
+ if (expectedType == typeof(DateTime))
+ {
+ // TODO: make a util function for this?
+ DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+ return origin.AddSeconds(value);
+ }
+ return value;
}
}
- private class TypeHandlerLong : TypeHandler
+
+ private class TypeHandlerInt : TypeHandlerIntegral
+ {
+ public TypeHandlerInt() : base(SoapConstants.XMLNS_XSD, null, typeof(int)) { }
+ }
+ private class TypeHandlerLong : TypeHandlerIntegral
{
public TypeHandlerLong() : base(SoapConstants.XMLNS_XSD, "int", typeof(long)) { }
-
- public override void Serialize(string name, object value, StringBuilder s)
- {
- s.Append(string.Format("<{0} xsi:type=\"xsd:int\">{1}{0}>", name, value));
- }
-
- protected override object DeserializeContents(XmlNode node, Type expectedType)
- {
- return long.Parse(node.InnerText);
- }
}
private class TypeHandlerString : TypeHandler
@@ -221,17 +226,36 @@ namespace Acacia.ZPush.Connect.Soap
protected override object DeserializeContents(XmlNode node, Type expectedType)
{
- // Create a list instance
- System.Collections.IList list = (System.Collections.IList)Activator.CreateInstance(expectedType);
- Type entryType = expectedType.GetGenericArguments()[0];
+ if (expectedType == null)
+ return null;
- foreach (XmlNode child in node.ChildNodes)
+ if (expectedType.IsArray)
{
- object element = DeserializeNode(child, entryType);
- list.Add(element);
+ // Decode into array
+ Array array = Array.CreateInstance(expectedType.GetElementType(), node.ChildNodes.Count);
+ int index = 0;
+ foreach (XmlNode child in node.ChildNodes)
+ {
+ object element = DeserializeNode(child, expectedType.GetElementType());
+ array.SetValue(element, index);
+ ++index;
+ }
+ return array;
}
+ else
+ {
- return list;
+ // Decode into list
+ System.Collections.IList list = (System.Collections.IList)Activator.CreateInstance(expectedType);
+ Type elementType = expectedType.GetGenericArguments()[0];
+
+ foreach (XmlNode child in node.ChildNodes)
+ {
+ object element = DeserializeNode(child, elementType);
+ list.Add(element);
+ }
+ return list;
+ }
}
public override void Serialize(string name, object value, StringBuilder s)
@@ -260,6 +284,9 @@ namespace Acacia.ZPush.Connect.Soap
protected override object DeserializeContents(XmlNode node, Type expectedType)
{
+ if (expectedType == null)
+ return null;
+
// Determine if the expected type is an ISoapSerializable
if (!typeof(ISoapSerializable<>).IsGenericAssignableFrom(expectedType))
{
@@ -294,8 +321,9 @@ namespace Acacia.ZPush.Connect.Soap
object instance = Activator.CreateInstance(serializationType);
foreach (FieldInfo field in serializationType.GetFields())
{
+ string key = field.GetCustomAttribute()?.FieldId?.ToString() ?? field.Name;
object value = null;
- if (node.TryGetValue(field.Name.ToLower(), out value))
+ if (node.TryGetValue(key, out value))
{
value = SoapConvert(field.FieldType, value);
field.SetValue(instance, value);
@@ -328,7 +356,7 @@ namespace Acacia.ZPush.Connect.Soap
Dictionary dict;
if (typeof(Dictionary).IsAssignableFrom(value.GetType()))
{
- dict = (Dictionary < string, object> )value;
+ dict = (Dictionary)value;
}
else
{
@@ -352,11 +380,13 @@ namespace Acacia.ZPush.Connect.Soap
if (type == null)
return null;
+ // Check if the field is a simple field name
FieldInfo prop = type.GetField(field);
- if (prop == null)
- return null;
-
- return prop.FieldType;
+ if (prop == null && type.GetCustomAttribute() != null)
+ // Check for an attriubte
+ prop = type.GetFields().FirstOrDefault(f => f.GetCustomAttribute()?.FieldId?.ToString() == field);
+
+ return prop?.FieldType;
}
}
@@ -371,7 +401,7 @@ namespace Acacia.ZPush.Connect.Soap
{
foreach (XmlNode child in node.ChildNodes)
{
- string key = child.Name.ToLower();
+ string key = child.Name;
object value = DeserializeNode(child, DetermineChildType(expectedType, key));
dict.Add(key, value);
}
@@ -396,7 +426,8 @@ namespace Acacia.ZPush.Connect.Soap
foreach (XmlNode child in node.ChildNodes)
{
string key = (string)DeserializeNode(child.SelectSingleNode("key"), typeof(string));
- object value = DeserializeNode(child.SelectSingleNode("value"), DetermineChildType(expectedType, key));
+ Type childType = DetermineChildType(expectedType, key);
+ object value = DeserializeNode(child.SelectSingleNode("value"), childType);
dict.Add(key, value);
}
}
@@ -494,7 +525,7 @@ namespace Acacia.ZPush.Connect.Soap
XmlAttribute typeAttr = part.Attributes["type", SoapConstants.XMLNS_XSI];
if (typeAttr == null)
- throw new Exception("Missing type");
+ throw new Exception("Missing type: " + part.OuterXml);
string value = typeAttr.Value;
string[] parts = value.Split(new char[] { ':' }, 2);
string fullName;
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushWebService.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushWebService.cs
index d0b0983..bd48d71 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushWebService.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushWebService.cs
@@ -86,6 +86,7 @@ namespace Acacia.ZPush.Connect
{
SoapParameters parameters = new SoapParameters();
parameters.Add("devid", _connection.Account.Account.DeviceId.ToLower());
+ //parameters.Add("deviceId", _connection.Account.Account.DeviceId.ToLower());
return parameters;
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs
index 6540a65..6da41a1 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs
@@ -245,7 +245,7 @@ namespace Acacia.ZPush
private void Cleanup()
{
- Logger.Instance.Trace(this, "Unwatching folder: {0}", _folder.Name);
+ Logger.Instance.Trace(this, "Unwatching folder"); // Cannot log name, as folder is now invalid
// The events need to be unhooked explicitly, otherwise we get double notifications if a folder is moved
HookEvents(false);
foreach (ZPushFolder child in _children.Values)