[KOE-159] Implemented start-up sync time frame checking

This commit is contained in:
Patrick Simpson 2018-06-07 12:23:15 +03:00
parent fdf62e0037
commit 0bce4d8c22
9 changed files with 414 additions and 35 deletions

View File

@ -397,6 +397,7 @@
<Compile Include="Utils\DisposableWrapper.cs" />
<Compile Include="Utils\ImageUtils.cs" />
<Compile Include="Utils\RegistryUtil.cs" />
<Compile Include="Utils\SizeUtil.cs" />
<Compile Include="ZPush\API\SharedFolders\AvailableFolder.cs" />
<Compile Include="ZPush\API\SharedFolders\SharedFolder.cs" />
<Compile Include="ZPush\API\SharedFolders\Types.cs" />

View File

@ -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<UserStoreInfo>
{
}
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<SyncSession>(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<SyncSession>(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<bool>
@ -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
}
}

View File

@ -1346,6 +1346,99 @@ namespace Acacia.Properties {
}
}
/// <summary>
/// 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}.
/// </summary>
internal static string SyncState_StoreSize_Body {
get {
return ResourceManager.GetString("SyncState_StoreSize_Body", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Store size.
/// </summary>
internal static string SyncState_StoreSize_Caption {
get {
return ResourceManager.GetString("SyncState_StoreSize_Caption", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to All.
/// </summary>
internal static string SyncTimeFrame_ALL {
get {
return ResourceManager.GetString("SyncTimeFrame_ALL", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 1 day.
/// </summary>
internal static string SyncTimeFrame_DAY_1 {
get {
return ResourceManager.GetString("SyncTimeFrame_DAY_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 3 days.
/// </summary>
internal static string SyncTimeFrame_DAY_3 {
get {
return ResourceManager.GetString("SyncTimeFrame_DAY_3", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 1 month.
/// </summary>
internal static string SyncTimeFrame_MONTH_1 {
get {
return ResourceManager.GetString("SyncTimeFrame_MONTH_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 3 months.
/// </summary>
internal static string SyncTimeFrame_MONTH_3 {
get {
return ResourceManager.GetString("SyncTimeFrame_MONTH_3", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 6 months.
/// </summary>
internal static string SyncTimeFrame_MONTH_6 {
get {
return ResourceManager.GetString("SyncTimeFrame_MONTH_6", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 1 week.
/// </summary>
internal static string SyncTimeFrame_WEEK_1 {
get {
return ResourceManager.GetString("SyncTimeFrame_WEEK_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 2 weeks.
/// </summary>
internal static string SyncTimeFrame_WEEK_2 {
get {
return ResourceManager.GetString("SyncTimeFrame_WEEK_2", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Kopano.
/// </summary>

View File

@ -551,4 +551,37 @@ Please contact your system administrator for any required changes.</value>
<data name="SharedFolders_TooManyFolders_Title" xml:space="preserve">
<value>Shared Folders</value>
</data>
<data name="SyncState_StoreSize_Body" xml:space="preserve">
<value>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}</value>
</data>
<data name="SyncState_StoreSize_Caption" xml:space="preserve">
<value>Store size</value>
</data>
<data name="SyncTimeFrame_ALL" xml:space="preserve">
<value>All</value>
</data>
<data name="SyncTimeFrame_DAY_1" xml:space="preserve">
<value>1 day</value>
</data>
<data name="SyncTimeFrame_DAY_3" xml:space="preserve">
<value>3 days</value>
</data>
<data name="SyncTimeFrame_MONTH_1" xml:space="preserve">
<value>1 month</value>
</data>
<data name="SyncTimeFrame_MONTH_3" xml:space="preserve">
<value>3 months</value>
</data>
<data name="SyncTimeFrame_MONTH_6" xml:space="preserve">
<value>6 months</value>
</data>
<data name="SyncTimeFrame_WEEK_1" xml:space="preserve">
<value>1 week</value>
</data>
<data name="SyncTimeFrame_WEEK_2" xml:space="preserve">
<value>2 weeks</value>
</data>
</root>

View File

@ -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<http://www.gnu.org/licenses/>.
///
/// 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());
}
}
}

View File

@ -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}</{0}>", 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());

View File

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

View File

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

View File

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