#region Disclaimer / License // Copyright (C) 2015, The Duplicati Team // http://www.duplicati.com, info@duplicati.com // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion using System; using System.Collections.Generic; using System.Text; using Duplicati.Library.Common; namespace Duplicati.Server { /// /// This class keeps track of the users modifications regarding /// throttling and pause/resume /// public class LiveControls { /// /// The tag used for logging /// private static readonly string LOGTAG = Duplicati.Library.Logging.Log.LogTagFromType(); /// /// An event that is activated when the pause state changes /// public event EventHandler StateChanged; /// /// An event that is activated when the thread priority changes /// public event EventHandler ThreadPriorityChanged; /// /// An event that is activated when the throttle speed changes /// public event EventHandler ThrottleSpeedChanged; /// /// The possible states for the live control /// public enum LiveControlState { /// /// Indicates that the backups are running /// Running, /// /// Indicates that the backups are currently suspended /// Paused } /// /// The current control state /// private LiveControlState m_state; /// /// A value that indicates if the current pause state is caused by being suspended /// private bool m_pausedForSuspend = false; /// /// The time to pause for, used to ensure that a user set pause can override the suspend pause /// private DateTime m_suspendMinimumPause = new DateTime(0); /// /// Gets the current state for the control /// public LiveControlState State { get { return m_state; } } /// /// The internal variable that tracks the the priority /// private System.Threading.ThreadPriority? m_priority; /// /// The internal variable that tracks the upload limit /// private long? m_uploadLimit; /// /// The internal variable that tracks the download limit /// private long? m_downloadLimit; /// /// The object that ensures concurrent operations /// private readonly object m_lock = new object(); /// /// Gets the current overridden thread priority /// public System.Threading.ThreadPriority? ThreadPriority { get { return m_priority; } set { if (m_priority != value) { m_priority = value; if (ThreadPriorityChanged != null) ThreadPriorityChanged(this, null); } } } /// /// Gets the current upload limit in bps /// public long? UploadLimit { get { return m_uploadLimit; } set { if (m_uploadLimit != value) { m_uploadLimit = value; if (ThrottleSpeedChanged != null) ThrottleSpeedChanged(this, null); } } } /// /// Gets the download limit in bps /// public long? DownloadLimit { get { return m_downloadLimit; } set { if (m_downloadLimit != value) { m_downloadLimit = value; if (ThrottleSpeedChanged != null) ThrottleSpeedChanged(this, null); } } } /// /// The timer that is activated after a pause period. /// private readonly System.Threading.Timer m_waitTimer; /// /// The time that the current pause is expected to expire /// private DateTime m_waitTimeExpiration = new DateTime(0); /// /// Constructs a new instance of the LiveControl /// public LiveControls(Database.ServerSettings settings) { m_state = LiveControlState.Running; m_waitTimer = new System.Threading.Timer(m_waitTimer_Tick, this, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); if (!string.IsNullOrEmpty(settings.StartupDelayDuration) && settings.StartupDelayDuration != "0") { long milliseconds = 0; try { milliseconds = (long)Duplicati.Library.Utility.Timeparser.ParseTimeSpan(settings.StartupDelayDuration).TotalMilliseconds; } catch {} if (milliseconds > 0) { m_waitTimeExpiration = DateTime.Now.AddMilliseconds(milliseconds); m_waitTimer.Change(milliseconds, System.Threading.Timeout.Infinite); m_state = LiveControlState.Paused; } } m_priority = settings.ThreadPriorityOverride; if (!string.IsNullOrEmpty(settings.DownloadSpeedLimit)) try { m_downloadLimit = Library.Utility.Sizeparser.ParseSize(settings.DownloadSpeedLimit, "kb"); } catch (Exception ex) { Library.Logging.Log.WriteErrorMessage(LOGTAG, "ParseDownloadLimitError", ex, "Failed to parse download limit: {0}", settings.DownloadSpeedLimit); } if (!string.IsNullOrEmpty(settings.UploadSpeedLimit)) try { m_uploadLimit = Library.Utility.Sizeparser.ParseSize(settings.UploadSpeedLimit, "kb"); } catch (Exception ex) { Library.Logging.Log.WriteErrorMessage(LOGTAG, "ParseUploadLimitError", ex, "Failed to parse upload limit: {0}", settings.UploadSpeedLimit); } try { if (!Platform.IsClientPosix) RegisterHibernateMonitor(); } catch { } } /// /// Event that occurs when the timeout duration is exceeded /// /// The sender of the event private void m_waitTimer_Tick(object sender) { lock (m_lock) Resume(); } /// /// Internal helper to reset the timeout timer /// /// The time to wait private void ResetTimer(string timeout) { lock (m_lock) if (!string.IsNullOrEmpty(timeout)) { long milliseconds = (long)Duplicati.Library.Utility.Timeparser.ParseTimeSpan(timeout).TotalMilliseconds; m_waitTimeExpiration = DateTime.Now.AddMilliseconds(milliseconds); m_waitTimer.Change(milliseconds, System.Threading.Timeout.Infinite); } else { m_waitTimeExpiration = new DateTime(0); m_waitTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); } } /// /// Internal helper to set the pause mode /// private void SetPauseMode() { lock (m_lock) { if (m_state == LiveControlState.Running) { m_state = LiveControlState.Paused; if (StateChanged != null) StateChanged(this, null); } } } /// /// Pauses the backups until resumed /// public void Pause() { lock(m_lock) { var fireEvent = m_waitTimeExpiration.Ticks != 0 && m_state == LiveControlState.Paused && StateChanged != null; ResetTimer(null); if (fireEvent) StateChanged(this, null); else SetPauseMode(); } } /// /// Resumes a backups to the running state /// public void Resume() { lock (m_lock) { if (m_state == LiveControlState.Paused) { //Make sure that the timer is cleared ResetTimer(null); m_state = LiveControlState.Running; if (StateChanged != null) StateChanged(this, null); } } } /// /// Suspends the backups for a given period /// /// The duration to wait public void Pause(string timeout) { Pause(Duplicati.Library.Utility.Timeparser.ParseTimeSpan(timeout)); } /// /// Suspends the backups for a given period /// /// The duration to wait public void Pause(TimeSpan timeout) { lock (m_lock) { m_waitTimeExpiration = DateTime.Now.AddMilliseconds((long)timeout.TotalMilliseconds); m_waitTimer.Change((long)timeout.TotalMilliseconds, System.Threading.Timeout.Infinite); //We change the time, so we issue a new event if (m_state == LiveControlState.Paused && StateChanged != null) StateChanged(this, null); else SetPauseMode(); } } /// /// Gets the time the current pause is expected to end /// public DateTime EstimatedPauseEnd { get { return m_waitTimeExpiration; } } /// /// Method for calling a Win32 API /// private void RegisterHibernateMonitor() { Microsoft.Win32.SystemEvents.PowerModeChanged += new Microsoft.Win32.PowerModeChangedEventHandler(SystemEvents_PowerModeChanged); } /// /// A monitor for detecting when the system hibernates or resumes /// /// Unused sender parameter /// The event information private void SystemEvents_PowerModeChanged(object sender, object _e) { Microsoft.Win32.PowerModeChangedEventArgs e = _e as Microsoft.Win32.PowerModeChangedEventArgs; if (e == null) return; if (e.Mode == Microsoft.Win32.PowerModes.Suspend) { //If we are running, register as being paused due to suspending if (this.m_state == LiveControlState.Running) { this.SetPauseMode(); m_pausedForSuspend = true; m_suspendMinimumPause = new DateTime(0); } else { if (m_waitTimeExpiration.Ticks != 0) { m_pausedForSuspend = true; m_suspendMinimumPause = this.EstimatedPauseEnd; ResetTimer(null); } } } else if (e.Mode == Microsoft.Win32.PowerModes.Resume) { //If we have been been paused due to suspending, we un-pause now if (m_pausedForSuspend) { long delayTicks = (m_suspendMinimumPause - DateTime.Now).Ticks; var appset = Program.DataConnection.ApplicationSettings; if (!string.IsNullOrEmpty(appset.StartupDelayDuration) && appset.StartupDelayDuration != "0") try { delayTicks = Math.Max(delayTicks, Library.Utility.Timeparser.ParseTimeSpan(appset.StartupDelayDuration).Ticks); } catch { } if (delayTicks > 0) { this.Pause(TimeSpan.FromTicks(delayTicks)); } else { this.Resume(); } } m_pausedForSuspend = false; m_suspendMinimumPause = new DateTime(0); } } } }