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}", 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)