/// 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 } }