diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj index 57fb0a8..0b8109f 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj @@ -397,6 +397,7 @@ + diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs index 5063508..20992eb 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs @@ -420,34 +420,58 @@ namespace Acacia.Features.SyncState } } + + private class UserStoreInfo + { + public string emailaddress; + public string fullname; + public long foldercount; + public long storesize; + + public override string ToString() + { + return string.Format("emailaddress={0}\nfullname={1}\nfoldercount={2}\nstoresize={3}", + emailaddress, fullname, foldercount, storesize); + } + } + private class GetUserStoreInfoRequest : 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()); - if (details != null) + // Check total size + CheckTotalSize(connection); + + // Update sync state + using (ZPushWebServiceDevice deviceService = connection.DeviceService) { - bool wasSyncing = false; - - // Create or update session - SyncSession session = account.GetFeatureData(this, null); - if (session == null) - session = new SyncSession(this, account); - else - wasSyncing = session.IsSyncing; - - session.Add(details); - - // Store with the account - account.SetFeatureData(this, null, session); - - if (wasSyncing != session.IsSyncing) + // Fetch + DeviceDetails details = deviceService.Execute(new GetDeviceDetailsRequest()); + if (details != null) { - // Sync state has changed, update the schedule - Watcher.Sync.SetTaskSchedule(_task, account, session.IsSyncing ? CheckPeriodSync : (TimeSpan?)null); + bool wasSyncing = false; + + // Create or update session + SyncSession session = account.GetFeatureData(this, null); + if (session == null) + session = new SyncSession(this, account); + else + wasSyncing = session.IsSyncing; + + session.Add(details); + + // Store with the account + account.SetFeatureData(this, null, session); + + if (wasSyncing != session.IsSyncing) + { + // Sync state has changed, update the schedule + Watcher.Sync.SetTaskSchedule(_task, account, session.IsSyncing ? CheckPeriodSync : (TimeSpan?)null); + } } } } @@ -739,7 +763,7 @@ namespace Acacia.Features.SyncState public bool SupportsSyncTimeFrame(ZPushAccount account) { - return account?.ZPushVersion.IsAtLeast(2, 4) == true; + return account?.ZPushVersion?.IsAtLeast(2, 4) == true; } private class SetDeviceOptionsRequest : SoapRequest @@ -752,7 +776,6 @@ namespace Acacia.Features.SyncState public void SetDeviceOptions(ZPushAccount account, SyncTimeFrame timeFrame) { - try { Logger.Instance.Debug(this, "Setting sync time frame for {0} to {1}", account, timeFrame); @@ -778,5 +801,126 @@ namespace Acacia.Features.SyncState } + + + #region Size checking + + [AcaciaOption("Disable checking of store size to suggest a sync time frame.")] + public bool CheckStoreSize + { + get { return GetOption(OPTION_CHECK_STORE_SIZE); } + set { SetOption(OPTION_CHECK_STORE_SIZE, value); } + } + private static readonly BoolOption OPTION_CHECK_STORE_SIZE = new BoolOption("CheckStoreSize", true); + + private bool _checkedStoreSize = false; + + private const string KEY_CHECKED_SIZE = "KOE Size Checked"; + + private void CheckTotalSize(ZPushConnection connection) + { + if (!CheckStoreSize || _checkedStoreSize) + return; + + // Only works on 2.5 + // If we don't have the version yet, try again later + if (connection.Account.ZPushVersion == null) + return; + + // Only check once + _checkedStoreSize = true; + + // If it's not 2.5, don't check again + if (!connection.Account.ZPushVersion.IsAtLeast(2, 5)) + return; + + try + { + Logger.Instance.Debug(this, "Fetching size information for account {0}", connection.Account); + using (ZPushWebServiceInfo infoService = connection.InfoService) + { + UserStoreInfo info = infoService.Execute(new GetUserStoreInfoRequest()); + Logger.Instance.Debug(this, "Size information: {0}", info); + SyncTimeFrame suggested = SuggestSyncTimeFrame(info); + + if (suggested.IsShorterThan(connection.Account.SyncTimeFrame)) + { + // Suggest shorter time frame, if not already done + string lastCheckedSize = connection.Account.Account[KEY_CHECKED_SIZE]; + if (!string.IsNullOrWhiteSpace(lastCheckedSize)) + { + try + { + SyncTimeFrame old = (SyncTimeFrame)int.Parse(lastCheckedSize); + if (old >= suggested) + { + Logger.Instance.Trace(this, "Not suggesting reduced sync time frame again: {0}: {2} -> {1}", info, suggested, old); + return; + } + } + catch(Exception e) + { + Logger.Instance.Warning(this, "Invalid lastCheckedSize: {0}: {1}", lastCheckedSize, e); + } + } + + Logger.Instance.Debug(this, "Suggesting reduced sync time frame: {0}: {2} -> {1}", info, suggested, connection.Account.SyncTimeFrame); + + // Suggest a shorter timeframe + DialogResult result = MessageBox.Show(ThisAddIn.Instance.Window, + string.Format(Properties.Resources.SyncState_StoreSize_Body, + info.storesize.ToSizeString(SizeUtil.Size.MB), + suggested.ToDisplayString()), + Properties.Resources.SyncState_StoreSize_Caption, + MessageBoxButtons.OKCancel, MessageBoxIcon.Information); + + if (result == DialogResult.OK) + { + // Set the sync time frame + Logger.Instance.Debug(this, "Applying reduced sync time frame: {0}: {2} -> {1}", info, suggested, connection.Account.SyncTimeFrame); + connection.Account.SyncTimeFrame = suggested; + } + } + + connection.Account.Account[KEY_CHECKED_SIZE] = ((int)suggested).ToString(); + } + } + catch(Exception e) + { + Logger.Instance.Warning(this, "Error suggesting size: {0}", e); + } + } + + private struct SuggestedSyncTimeFrame + { + public long storeSize; + public SyncTimeFrame timeFrame; + + public SuggestedSyncTimeFrame(long storeSize, SyncTimeFrame timeFrame) : this() + { + this.storeSize = storeSize; + this.timeFrame = timeFrame; + } + } + + private static readonly SuggestedSyncTimeFrame[] SUGGESTED_SYNC_TIME_FRAMES = + { + new SuggestedSyncTimeFrame(10.WithSize(SizeUtil.Size.GB), SyncTimeFrame.MONTH_1), + new SuggestedSyncTimeFrame(5.WithSize(SizeUtil.Size.GB), SyncTimeFrame.MONTH_3), + new SuggestedSyncTimeFrame(2.WithSize(SizeUtil.Size.GB), SyncTimeFrame.MONTH_6) + }; + + private SyncTimeFrame SuggestSyncTimeFrame(UserStoreInfo info) + { + foreach(SuggestedSyncTimeFrame suggested in SUGGESTED_SYNC_TIME_FRAMES) + { + if (info.storesize >= suggested.storeSize) + return suggested.timeFrame; + } + return SyncTimeFrame.ALL; + } + + #endregion } + } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs index b1ea2b7..6f2014a 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs @@ -1346,6 +1346,99 @@ namespace Acacia.Properties { } } + /// + /// Looks up a localized string similar to KOE has detected that the current store size exceeds to recommended value for synced data and will therefore reduce the time synced. + /// + ///Current store size: {0} + ///New sync window: {1}. + /// + internal static string SyncState_StoreSize_Body { + get { + return ResourceManager.GetString("SyncState_StoreSize_Body", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store size. + /// + internal static string SyncState_StoreSize_Caption { + get { + return ResourceManager.GetString("SyncState_StoreSize_Caption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All. + /// + internal static string SyncTimeFrame_ALL { + get { + return ResourceManager.GetString("SyncTimeFrame_ALL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 1 day. + /// + internal static string SyncTimeFrame_DAY_1 { + get { + return ResourceManager.GetString("SyncTimeFrame_DAY_1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 3 days. + /// + internal static string SyncTimeFrame_DAY_3 { + get { + return ResourceManager.GetString("SyncTimeFrame_DAY_3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 1 month. + /// + internal static string SyncTimeFrame_MONTH_1 { + get { + return ResourceManager.GetString("SyncTimeFrame_MONTH_1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 3 months. + /// + internal static string SyncTimeFrame_MONTH_3 { + get { + return ResourceManager.GetString("SyncTimeFrame_MONTH_3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 6 months. + /// + internal static string SyncTimeFrame_MONTH_6 { + get { + return ResourceManager.GetString("SyncTimeFrame_MONTH_6", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 1 week. + /// + internal static string SyncTimeFrame_WEEK_1 { + get { + return ResourceManager.GetString("SyncTimeFrame_WEEK_1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 2 weeks. + /// + internal static string SyncTimeFrame_WEEK_2 { + get { + return ResourceManager.GetString("SyncTimeFrame_WEEK_2", resourceCulture); + } + } + /// /// Looks up a localized string similar to Kopano. /// diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx index d8ad968..f026e3e 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx @@ -551,4 +551,37 @@ Please contact your system administrator for any required changes. Shared Folders + + KOE has detected that the current store size exceeds to recommended value for synced data and will therefore reduce the time synced. + +Current store size: {0} +New sync window: {1} + + + Store size + + + All + + + 1 day + + + 3 days + + + 1 month + + + 3 months + + + 6 months + + + 1 week + + + 2 weeks + \ No newline at end of file diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/SizeUtil.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/SizeUtil.cs new file mode 100644 index 0000000..d2cc63b --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/SizeUtil.cs @@ -0,0 +1,50 @@ +/// Copyright 2018 Kopano b.v. +/// +/// This program is free software: you can redistribute it and/or modify +/// it under the terms of the GNU Affero General Public License, version 3, +/// as published by the Free Software Foundation. +/// +/// This program is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +/// GNU Affero General Public License for more details. +/// +/// You should have received a copy of the GNU Affero General Public License +/// along with this program.If not, see. +/// +/// Consult LICENSE file for details +/// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Acacia.Utils +{ + public static class SizeUtil + { + public enum Size + { + KB = 1024, + MB = 1024 * 1024, + GB = 1024 * 1024 * 1024 + } + + public static long WithSize(this long size, Size unit) + { + return size * (int)unit; + } + + public static long WithSize(this int size, Size unit) + { + return WithSize((long)size, unit); + } + + public static string ToSizeString(this long size, Size unit) + { + double value = (double)size / (int)unit; + return string.Format("{0:0.00}{1}", value, unit.ToString()); + } + } +} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapSerializer.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapSerializer.cs index 9abd813..3687440 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapSerializer.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/Soap/SoapSerializer.cs @@ -201,6 +201,37 @@ namespace Acacia.ZPush.Connect.Soap public TypeHandlerLong() : base(SoapConstants.XMLNS_XSD, "int", typeof(long)) { } } + + abstract private class TypeHandlerReal : TypeHandler + { + public TypeHandlerReal(string xmlns, string name, Type baseType) : base(xmlns, name, baseType) + { + } + + public override void Serialize(string name, object value, StringBuilder s) + { + s.Append(string.Format("<{0} xsi:type=\"xsd:{2}\">{1}", name, value, HandlesType == typeof(float) ? "float" : "double")); + } + + protected override object DeserializeContents(XmlNode node, Type expectedType) + { + double value = double.Parse(node.InnerText); + if (expectedType == typeof(float)) + return (float)value; + return value; + } + } + + private class TypeHandlerFloat : TypeHandlerReal + { + public TypeHandlerFloat() : base(SoapConstants.XMLNS_XSD, "float", typeof(float)) { } + } + + private class TypeHandlerDouble : TypeHandlerReal + { + public TypeHandlerDouble() : base(SoapConstants.XMLNS_XSD, "double", typeof(double)) { } + } + private class TypeHandlerString : TypeHandler { public TypeHandlerString() : base(SoapConstants.XMLNS_XSD, "string", typeof(string)) { } @@ -486,6 +517,8 @@ namespace Acacia.ZPush.Connect.Soap // What is called an int in soap might actually be a long here. RegisterTypeHandler(new TypeHandlerLong()); RegisterTypeHandler(new TypeHandlerInt()); + RegisterTypeHandler(new TypeHandlerFloat()); + RegisterTypeHandler(new TypeHandlerDouble()); RegisterTypeHandler(new TypeHandlerString()); RegisterTypeHandler(new TypeHandlerList()); RegisterTypeHandler(new TypeHandlerStruct()); diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccount.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccount.cs index f1972ae..5be9a85 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccount.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccount.cs @@ -348,7 +348,7 @@ namespace Acacia.ZPush SyncTimeFrame frame = (SyncTimeFrame)val; // If the timeframe exceeds one month, but Outlook is set to one month, only one month will be synced. - if (!IsSyncOneMonthOrLess(frame) && EASSyncOneMonth) + if (!frame.IsOneMonthOrLess() && EASSyncOneMonth) return SyncTimeFrame.MONTH_1; return frame; } @@ -358,7 +358,7 @@ namespace Acacia.ZPush if (value != SyncTimeFrame) { // Set the outlook property - EASSyncOneMonth = IsSyncOneMonthOrLess(value); + EASSyncOneMonth = value.IsOneMonthOrLess(); // And the registry value RegistryUtil.SetValueDword(Account.RegistryBaseKey, OutlookConstants.REG_VAL_SYNC_TIMEFRAME, (int)value); } @@ -381,11 +381,6 @@ namespace Acacia.ZPush } } - private bool IsSyncOneMonthOrLess(SyncTimeFrame sync) - { - return sync <= SyncTimeFrame.MONTH_1 && sync != SyncTimeFrame.ALL; - } - #endregion #region Send as diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushTypes.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushTypes.cs index b81e512..9e9de9e 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushTypes.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushTypes.cs @@ -163,4 +163,30 @@ namespace Acacia.ZPush MONTH_3, MONTH_6 } + + public static class SyncTimeFrameMethods + { + public static bool IsOneMonthOrLess(this SyncTimeFrame sync) + { + return sync <= SyncTimeFrame.MONTH_1 && sync != SyncTimeFrame.ALL; + } + + public static bool IsShorterThan(this SyncTimeFrame _this, SyncTimeFrame other) + { + if (_this == SyncTimeFrame.ALL) + return false; // ALL can not be shorter than anything + if (other == SyncTimeFrame.ALL) + return true; // Always true, if this was ALL, already returned above, so this must be shorter + + return (int)_this < (int)other; + } + + public static string ToDisplayString(this SyncTimeFrame _this) + { + string s = Properties.Resources.ResourceManager.GetString("SyncTimeFrame_" + _this.ToString()); + if (s == null) + return _this.ToString(); + return s; + } + } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushVersion.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushVersion.cs index 1d89da0..984264f 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushVersion.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushVersion.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace Acacia.ZPush @@ -42,17 +43,20 @@ namespace Acacia.ZPush if (string.IsNullOrWhiteSpace(version)) return null; - string[] parts = version.Split('.'); try { - int major = int.Parse(parts[0]); - int minor = int.Parse(parts[1]); - return new ZPushVersion(major, minor, version); + Match match = new Regex(@"(\d+)[.](\d+)[.](\d+)[.]").Match(version); + if (match.Success) + { + int major = int.Parse(match.Groups[1].Value); + int minor = int.Parse(match.Groups[2].Value); + return new ZPushVersion(major, minor, version); + } } catch (Exception) { - return null; } + return null; } public bool IsAtLeast(int major, int minor)