// Copyright (C) 2013, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using Duplicati.Library.Logging;
namespace Duplicati.Library.Main
{
///
/// Interface for recieving 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
IBackendProgress BackendProgress { set; }
///
/// Sets the operation progress update object
///
/// The operation progress update object
IOperationProgress OperationProgress { set; }
}
///
/// 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 IBackendProgress BackendProgress
{
set
{
foreach (var s in m_sinks)
s.BackendProgress = value;
}
}
public IOperationProgress OperationProgress
{
set
{
foreach (var s in m_sinks)
s.OperationProgress = value;
}
}
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
///
/// he current number of transferred bytes
void UpdateProgress(long progress);
///
/// 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;
}
///
/// 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);
///
/// 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 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;
}
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;
}
}
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;
}
}
///
/// 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)
{
lock(m_lock)
{
filename = m_curfilename;
filesize = m_curfilesize;
fileoffset = m_curfileoffset;
}
}
}
}