// Copyright (C) 2024, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Linq; using Duplicati.Library.Logging; namespace Duplicati.Library.Main { /// /// Interface for receiving messages from the Duplicati operations /// public interface IMessageSink : Logging.ILogDestination { /// /// Handles an event from the backend /// /// The backend action. /// The event type. /// The target path. /// The size of the element. void BackendEvent(BackendActionType action, BackendEventType type, string path, long size); /// /// Sets the backend progress update object /// /// The backend progress update object void SetBackendProgress(IBackendProgress progress); /// /// Sets the operation progress update object /// /// The operation progress update object void SetOperationProgress(IOperationProgress progress); } /// /// Helper class to allow setting multiple message sinks on a single controller /// public class MultiMessageSink : IMessageSink { /// /// The sinks in this instance /// private IMessageSink[] m_sinks; /// /// Initializes a new instance of the class. /// /// The sinks to use. public MultiMessageSink(params IMessageSink[] sinks) { m_sinks = (sinks ?? new IMessageSink[0]).Where(x => x != null).ToArray(); } /// /// Appends a new sink to the list /// /// The sink to append. public void Append(IMessageSink sink) { if (sink == null) return; var na = new IMessageSink[m_sinks.Length + 1]; Array.Copy(m_sinks, na, m_sinks.Length); na[na.Length - 1] = sink; m_sinks = na; } public void SetBackendProgress(IBackendProgress progress) { foreach (var s in m_sinks) s.SetBackendProgress(progress); } public void SetOperationProgress(IOperationProgress progress) { foreach (var s in m_sinks) s.SetOperationProgress(progress); } public void BackendEvent(BackendActionType action, BackendEventType type, string path, long size) { foreach (var s in m_sinks) s.BackendEvent(action, type, path, size); } public void WriteMessage(LogEntry entry) { foreach (var s in m_sinks) s.WriteMessage(entry); } } /// /// Backend progress update object. /// The engine updates these statistics very often, /// so an event based system would take up too many resources. /// Instead, this interface allows the client to poll /// for updates as often as desired. /// public interface IBackendProgress { /// /// Update with the current action, path, size, progress and bytes_pr_second. /// /// The current action /// The current path /// The current size /// The current number of transferred bytes /// Transfer speed in bytes pr second, -1 for unknown /// A value indicating if the backend is blocking operation progress void Update(out BackendActionType action, out string path, out long size, out long progress, out long bytes_pr_second, out bool isBlocking); } /// /// Interface for updating the backend progress /// internal interface IBackendProgressUpdater { /// /// Register the start of a new action /// /// The action that is starting /// The path being operated on /// The size of the file being transferred void StartAction(BackendActionType action, string path, long size); /// /// Updates the current progress /// /// The current number of transferred bytes void UpdateProgress(long progress); /// /// Updates the total size /// /// The new total size void UpdateTotalSize(long size); /// /// Sets a flag indicating if the backend operation is blocking progress /// /// If set to true the backend is blocking. void SetBlocking(bool isBlocking); } /// /// Combined interface for the backend progress updater and the backend progress item /// internal interface IBackendProgressUpdaterAndReporter : IBackendProgressUpdater, IBackendProgress { } /// /// Backend progress updater instance /// internal class BackendProgressUpdater : IBackendProgressUpdaterAndReporter { /// /// Lock object to provide snapshot-like access to the data /// private readonly object m_lock = new object(); /// /// The current action /// private BackendActionType m_action; /// /// The current path /// private string m_path; /// /// The current file size /// private long m_size; /// /// The current number of transferred bytes /// private long m_progress; /// /// The time the last action started /// private DateTime m_actionStart; /// /// A value indicating when the last blocking was done /// private DateTime m_blockingSince; /// /// Register the start of a new action /// /// The action that is starting /// The path being operated on /// The size of the file being transferred public void StartAction(BackendActionType action, string path, long size) { lock (m_lock) { m_action = action; m_path = path; m_size = size; m_progress = 0; m_actionStart = DateTime.Now; } } /// /// Updates the progress /// /// The current number of transferred bytes public void UpdateProgress(long progress) { lock (m_lock) m_progress = progress; } /// /// Updates the total size /// /// The new total size public void UpdateTotalSize(long size) { lock (m_lock) m_size = size; } /// /// Update with the current action, path, size, progress and bytes_pr_second. /// /// The current action /// The current path /// The current size /// The current number of transferred bytes /// Transfer speed in bytes pr second, -1 for unknown /// A value indicating if the backend is blocking operation progress public void Update(out BackendActionType action, out string path, out long size, out long progress, out long bytes_pr_second, out bool isBlocking) { lock(m_lock) { action = m_action; path = m_path; size = m_size; progress = m_progress; isBlocking = m_blockingSince.Ticks > 0 && (DateTime.Now - m_blockingSince).TotalSeconds > 1; //TODO: The speed should be more dynamic, // so we need a sample window instead of always // calculating from the beginning if (m_progress <= 0 || m_size <= 0 || m_actionStart.Ticks == 0) bytes_pr_second = -1; else bytes_pr_second = (long)(m_progress / (DateTime.Now - m_actionStart).TotalSeconds); } } /// /// Sets a flag indicating if the backend operation is blocking progress /// /// If set to true the backend is blocking. public void SetBlocking(bool isBlocking) { lock (m_lock) m_blockingSince = isBlocking ? DateTime.Now : new DateTime(0); } } public delegate void PhaseChangedDelegate(OperationPhase phase, OperationPhase previousPhase); /// /// Operation progress update object. /// The engine updates these statistics very often, /// so an event based system would take up too many resources. /// Instead, this interface allows the client to poll /// for updates as often as desired. /// public interface IOperationProgress { /// /// Update the phase, progress, filesprocessed, filesizeprocessed, filecount, filesize and countingfiles. /// /// Phase. /// Progress. /// Filesprocessed. /// Filesizeprocessed. /// Filecount. /// Filesize. /// True if the filecount and filesize is incomplete, false otherwise void UpdateOverall(out OperationPhase phase, out float progress, out long filesprocessed, out long filesizeprocessed, out long filecount, out long filesize, out bool countingfiles); /// /// Update the filename, filesize, and fileoffset. /// /// Filename. /// Filesize. /// Fileoffset. void UpdateFile(out string filename, out long filesize, out long fileoffset, out bool filecomplete); /// /// Occurs when the phase has changed /// event PhaseChangedDelegate PhaseChanged; } /// /// Interface for updating the backend progress /// internal interface IOperationProgressUpdater { void UpdatePhase(OperationPhase phase); void UpdateProgress(float progress); void StartFile(string filename, long size); void UpdateFileProgress(long offset); void UpdatefileCount(long filecount, long filesize, bool done); void UpdatefilesProcessed(long count, long size); } internal interface IOperationProgressUpdaterAndReporter : IOperationProgressUpdater, IOperationProgress { } internal class OperationProgressUpdater : IOperationProgressUpdaterAndReporter { private readonly object m_lock = new object(); private OperationPhase m_phase; private float m_progress; private string m_curfilename; private long m_curfilesize; private long m_curfileoffset; private bool m_curfilecomplete; private long m_filesprocessed; private long m_filesizeprocessed; private long m_filecount; private long m_filesize; private bool m_countingFiles; public event PhaseChangedDelegate PhaseChanged; public void UpdatePhase(OperationPhase phase) { OperationPhase prev_phase; lock(m_lock) { prev_phase = m_phase; m_phase = phase; m_curfilename = null; m_curfilesize = 0; m_curfileoffset = 0; m_curfilecomplete = false; } if (prev_phase != phase && PhaseChanged != null) PhaseChanged(phase, prev_phase); } public void UpdateProgress(float progress) { lock(m_lock) m_progress = progress; } public void StartFile(string filename, long size) { lock(m_lock) { m_curfilename = filename; m_curfilesize = size; m_curfileoffset = 0; m_curfilecomplete = false; } } public void UpdateFileProgress(long offset) { lock(m_lock) m_curfileoffset = offset; } public void UpdatefileCount(long filecount, long filesize, bool done) { lock(m_lock) { m_filecount = filecount; m_filesize = filesize; m_countingFiles = !done; } } public void UpdatefilesProcessed(long count, long size) { lock(m_lock) { m_filesprocessed = count; m_filesizeprocessed = size; m_curfilecomplete = true; } } /// /// Update the phase, progress, filesprocessed, filesizeprocessed, filecount, filesize and countingfiles. /// /// Phase. /// Progress. /// Filesprocessed. /// Filesizeprocessed. /// Filecount. /// Filesize. /// True if the filecount and filesize is incomplete, false otherwise public void UpdateOverall(out OperationPhase phase, out float progress, out long filesprocessed, out long filesizeprocessed, out long filecount, out long filesize, out bool countingfiles) { lock(m_lock) { phase = m_phase; filesize = m_filesize; progress = m_progress; filesprocessed = m_filesprocessed; filesizeprocessed = m_filesizeprocessed; filecount = m_filecount; countingfiles = m_countingFiles; } } /// /// Update the filename, filesize, and fileoffset. /// /// Filename. /// Filesize. /// Fileoffset. public void UpdateFile(out string filename, out long filesize, out long fileoffset, out bool filecomplete) { lock(m_lock) { filename = m_curfilename; filesize = m_curfilesize; fileoffset = m_curfileoffset; filecomplete = m_curfilecomplete; } } } }