[KOE-24] Partial implementation of sync state display. Committing with disabled feature for backup and to allow build to succeed.

This commit is contained in:
Patrick Simpson 2017-05-03 14:43:36 +02:00
parent 55e4109c8c
commit 1279a62e42
7 changed files with 221 additions and 44 deletions

View File

@ -242,6 +242,7 @@
<Compile Include="Controls\KUIUtil.cs" />
<Compile Include="DebugOptions.cs" />
<Compile Include="Features\BCC\FeatureBCC.cs" />
<Compile Include="Features\DeliveryReceipts\FeatureDeliveryReceipts.cs" />
<Compile Include="Features\FreeBusy\FreeBusyServlet.cs" />
<Compile Include="Features\FreeBusy\Servlet.cs" />
<Compile Include="Features\SecondaryContacts\FeatureSecondaryContacts.cs" />
@ -352,6 +353,7 @@
<Compile Include="ZPush\API\SharedFolders\SharedFolder.cs" />
<Compile Include="ZPush\API\SharedFolders\Types.cs" />
<Compile Include="ZPush\Connect\Soap\SoapException.cs" />
<Compile Include="ZPush\Connect\Soap\SoapFieldAttribute.cs" />
<Compile Include="ZPush\Connect\Soap\SoapParameters.cs" />
<Compile Include="ZPush\API\SharedFolders\SharedFoldersAPI.cs" />
<Compile Include="Features\SharedFolders\SharedFoldersDialog.cs">
@ -652,6 +654,7 @@
<Content Include="Resources\Icons\Ribbon_WebMeetings.png" />
<Content Include="Resources\Icons\Ribbon_WebMeetings_Small.png" />
</ItemGroup>
<ItemGroup />
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@ -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
/// <summary>
/// Number in range [0,1]
/// </summary>
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<DeviceDetails.Data>
{
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<string, ContentData> 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<string, ContentData> Content
{
get { return _data.data.contentdata; }
}
}
private class GetDeviceDetailsRequest : SoapRequest<DeviceDetails>
{
}
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<string, DeviceDetails.ContentData> 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()

View File

@ -35,10 +35,10 @@ namespace Acacia.ZPush.API.SharedFolders
/// </summary>
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; }

View File

@ -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
{
/// <summary>
/// Specifies a Soap field id for a field. If used, the class must also be annotated with SoapField.
/// </summary>
public class SoapFieldAttribute : Attribute
{
public object FieldId { get; set; }
public SoapFieldAttribute(object fieldId = null)
{
this.FieldId = fieldId;
}
}
}

View File

@ -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<SoapFieldAttribute>()?.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<string, object> dict;
if (typeof(Dictionary<string, object>).IsAssignableFrom(value.GetType()))
{
dict = (Dictionary < string, object> )value;
dict = (Dictionary<string, object>)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<SoapFieldAttribute>() != null)
// Check for an attriubte
prop = type.GetFields().FirstOrDefault(f => f.GetCustomAttribute<SoapFieldAttribute>()?.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;

View File

@ -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;
}
}

View File

@ -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)