/// Copyright 2017 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 Acacia.Features;
using Acacia.Stubs;
using Acacia.Utils;
using Acacia.ZPush.Connect;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.ZPush
{
///
/// Helper for periodically synchronising state with ZPush servers
///
public class ZPushSync : DisposableWrapper
{
#region SyncTask
///
/// Represents a SyncTask. This is not specific for an account. When tasks are executed, GetInstance is
/// used to create a task instance for each account, which will be executed by the system task manager.
///
private class SyncTask
{
private readonly Feature _owner;
private readonly string _name;
private readonly SyncAction _action;
private readonly SyncActionConnection _actionConnection;
public SyncTask(Feature owner, string name, SyncAction action, SyncActionConnection actionConnection)
{
this._owner = owner;
this._name = name;
this._action = action;
this._actionConnection = actionConnection;
}
public AcaciaTask GetInstance(ZPushAccount account)
{
if (_actionConnection != null)
{
return new AcaciaTask(null, _owner, _name, () =>
{
// TODO: reuse connections
using (ZPushConnection con = account.Connect())
{
_actionConnection(con);
}
});
}
else
{
return new AcaciaTask(null, _owner, _name, () => _action(account));
}
}
}
#endregion
#region Setup
private readonly ISyncObject _syncObject;
private readonly Timer _timer;
private ZPushWatcher _watcher;
private bool _started;
private readonly List _tasks = new List();
public readonly bool Enabled;
public readonly TimeSpan Period;
public readonly TimeSpan PeriodThrottle;
public DateTime LastSyncTime;
public ZPushSync(ZPushWatcher watcher, IAddIn addIn)
{
// Get the settings
Enabled = GlobalOptions.INSTANCE.ZPushSync;
Period = GlobalOptions.INSTANCE.ZPushSync_Period;
if (Period.Ticks == 0)
Period = Constants.ZPUSH_SYNC_DEFAULT_PERIOD;
PeriodThrottle = GlobalOptions.INSTANCE.ZPushSync_PeriodThrottle;
if (PeriodThrottle.Ticks == 0)
PeriodThrottle = Constants.ZPUSH_SYNC_DEFAULT_PERIOD_THROTTLE;
// Set up a timer and events if enabled
if (Enabled)
{
_watcher = watcher;
_timer = new Timer();
_timer.Interval = (int)Period.TotalMilliseconds;
_timer.Tick += _timer_Tick;
_timer.Start();
// Need to keep a reference to keep receiving events
_syncObject = addIn.GetSyncObject();
_syncObject.SyncStart += SyncObject_SyncStart;
watcher.AccountDiscovered += Watcher_AccountDiscovered;
}
}
protected override void DoRelease()
{
_syncObject.Dispose();
}
#endregion
#region Public interface
///
/// Starts the synchronisation engine. After it is started, new tasks can no longer be added.
///
public void Start()
{
_started = true;
}
///
/// Delegate for an account-specific task.
///
/// The account
public delegate void SyncAction(ZPushAccount account);
///
/// Delegate for an account-specific task that will connect to the ZPush server.
/// The account can be obtained from the connection.
///
/// The connection to the ZPush server. This must not be dispose by
/// the task, as it may be reused for different tasks
public delegate void SyncActionConnection(ZPushConnection connection);
///
/// Adds a synchronisation task.
///
/// The feature owning the task.
/// The task's name, for logging.
/// The action to execute.
public void AddTask(Feature owner, string name, SyncAction action)
{
if (_started)
throw new Exception("Already started, cannot add task");
_tasks.Add(new SyncTask(owner, name, action, null));
}
///
/// Adds a synchronisation task that will use a connection to ZPUsh.
///
/// The feature owning the task.
/// The task's name, for logging.
/// The action to execute.
public void AddTask(Feature owner, string name, SyncActionConnection action)
{
if (_started)
throw new Exception("Already started, cannot add task");
_tasks.Add(new SyncTask(owner, name, null, action));
}
#endregion
#region Task execution
///
/// Executes the tasks for all known ZPush accounts. Only executed if enough time has passed since the last check.
///
private void PossiblyExecuteTasks()
{
// Don't contact the network if Outlook is offline
if (ThisAddIn.Instance.IsOffline)
return;
// Check for time
DateTime now = DateTime.Now;
if (LastSyncTime != null && now.Subtract(LastSyncTime) < PeriodThrottle)
{
// Back off
return;
}
LastSyncTime = now;
// Execute tasks for all accounts
foreach (ZPushAccount account in _watcher.Accounts.GetAccounts())
ExecuteTasks(account, false);
}
public void ExecuteTasks(ZPushAccount[] accounts, bool synchronous = false)
{
foreach (ZPushAccount account in accounts)
ExecuteTasks(account, synchronous);
}
///
/// Executes the tasks for the specified ZPush account. The tasks are pushed into the system
/// task queue.
///
private void ExecuteTasks(ZPushAccount account, bool synchronous = false)
{
// Don't contact the network if Outlook is offline
if (ThisAddIn.Instance.IsOffline)
return;
// Execute the tasks for the account
foreach (SyncTask task in _tasks)
{
Tasks.Task(task.GetInstance(account), synchronous);
}
}
///
/// Timer callback, executes any tasks.
///
private void _timer_Tick(object sender, EventArgs e)
{
PossiblyExecuteTasks();
}
///
/// Invoked when a new ZPush account is discovered, runs tasks for that account.
/// This is also invoked at start-up, so triggers initial sync.
///
private void Watcher_AccountDiscovered(ZPushAccount account)
{
ExecuteTasks(account);
}
///
/// Invoked when an explicit send and receive is performed, invokes any tasks.
///
private void SyncObject_SyncStart()
{
// TODO: this checks _started, others don't. Also, this is probably invoked on
// start-up, as is AccountDiscoverd. Does that mean tasks are invoked twice
// on start-up?
if (_started)
{
// Explicit sync, run tasks
PossiblyExecuteTasks();
}
}
#endregion
}
}