using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Duplicati.Library.Utility;
using Duplicati.Library.Main.Database;
using Duplicati.Library.Main.Volumes;
using Newtonsoft.Json;
using Duplicati.Library.Localization.Short;
using CoCoL;
using System.Threading.Tasks;
namespace Duplicati.Library.Main
{
internal class BackendManager : IDisposable
{
///
/// Legacy interface to wait for a file download
///
public interface IDownloadWaitHandle
{
///
/// Waits for a file to download and returns it once it has completed
///
/// The temporary file.
TempFile Wait();
///
/// Waits for a file to download and returns it once it has completed
///
/// The temporary file.
/// The downloaded file hash.
/// The downloaded file size.
TempFile Wait(out string hash, out long size);
}
///
/// Implementation of on a Task based result
///
private class WaitHandleWrapper : IDownloadWaitHandle
{
///
/// The task to wrap
///
private readonly Task> m_task;
///
/// The parent instance
///
private readonly BackendManager m_parent;
///
/// Initializes a new instance of the
/// class.
///
/// The task to wrap.
/// The parent instance
public WaitHandleWrapper(Task> task, BackendManager parent)
{
m_task = task;
m_parent = parent;
}
///
/// Waits for a file to download and returns it once it has completed
///
/// The temporary file.
public TempFile Wait()
{
string hash;
long size;
return Wait(out hash, out size);
}
///
/// Waits for a file to download and returns it once it has completed
///
/// The temporary file.
/// The downloaded file hash.
/// The downloaded file size.
public TempFile Wait(out string hash, out long size)
{
do
{
m_task.Wait(500);
m_parent.m_db.ProcessAllPendingOperations();
}
while (!m_task.IsCompleted);
m_task.WaitForTaskOrThrow();
size = m_task.Result.Item2;
hash = m_task.Result.Item3;
return m_task.Result.Item1;
}
}
private volatile Exception m_lastException;
private readonly string m_backendurl;
private readonly Options m_options;
private readonly Operation.Common.BackendHandlerDatabaseGuard m_db;
private readonly Operation.Common.BackendHandler m_backend;
private readonly Operation.Common.StatsCollector m_stats;
private readonly object m_lock = new object();
private readonly Queue m_pendingTasks = new Queue();
public string BackendUrl { get { return m_backendurl; } }
public bool HasDied { get { return m_lastException != null; } }
public Exception LastException { get { return m_lastException; } }
public Operation.Common.BackendHandlerDatabaseGuard Database { get { return m_db; } }
public BackendManager(string backendurl, Options options, IBackendWriter statwriter, LocalDatabase database, Operation.Common.BackendHandler backend)
{
m_db = new Operation.Common.BackendHandlerDatabaseGuard(database, options.Dryrun);
m_backendurl = backendurl;
m_stats = new Operation.Common.StatsCollector(statwriter);
m_backend = backend;
m_options = options;
}
public BackendManager(string backendurl, Options options, IBackendWriter statwriter, LocalDatabase database, BasicResults res)
{
m_db = new Operation.Common.BackendHandlerDatabaseGuard(database, options.Dryrun);
m_backendurl = backendurl;
m_stats = new Operation.Common.StatsCollector(statwriter);
m_backend = new Operation.Common.BackendHandler(options, backendurl, m_db, m_stats, res.TaskReader);
m_options = options;
}
private Task WaitForTaskIfRequired(bool forcesync, Func cb)
{
if (m_lastException != null)
throw m_lastException;
m_db.ProcessAllPendingOperations();
var task = cb();
m_db.ProcessAllPendingOperations();
if (forcesync || m_options.SynchronousUpload)
{
m_stats.SetBlocking(true);
try
{
do
{
task.Wait(500);
m_db.ProcessAllPendingOperations();
}
while (!task.IsCompleted);
task.WaitForTaskOrThrow();
}
catch (Exception ex)
{
m_lastException = ex;
throw;
}
finally
{
m_stats.SetBlocking(false);
}
m_db.ProcessAllPendingOperations();
}
else
{
// Keep these so we can check for errors
lock (m_lock)
m_pendingTasks.Enqueue(task);
}
lock (m_lock)
while (m_pendingTasks.Count > 0 && m_pendingTasks.Peek().IsCompleted)
{
var p = m_pendingTasks.Dequeue();
if (p.IsFaulted)
m_lastException = p.Exception;
else if (p.IsCanceled)
m_lastException = new TaskCanceledException();
}
if (m_lastException != null)
throw m_lastException;
return task;
}
public void PutUnencrypted(string remotename, string localpath)
{
WaitForTaskIfRequired(false, () => m_backend.PutUnencryptedAsync(remotename, localpath));
}
public void Put(VolumeWriterBase item, Func> indexcreator, bool synchronous = false)
{
WaitForTaskIfRequired(synchronous, () => m_backend.UploadFileAsync(item, indexcreator));
}
public Library.Utility.TempFile GetWithInfo(string remotename, out long size, out string hash)
{
return
new WaitHandleWrapper(
(Task>)WaitForTaskIfRequired(true, () => m_backend.GetFileWithInfoAsync(remotename)),
this
).Wait(out hash, out size);
}
public Library.Utility.TempFile Get(string remotename, long size, string hash)
{
var t = (Task)WaitForTaskIfRequired(true, () => m_backend.GetFileAsync(remotename, size, hash));
return t.Result;
}
public IDownloadWaitHandle GetAsync(string remotename, long size, string hash)
{
return
new WaitHandleWrapper(
(Task>)WaitForTaskIfRequired(false, () => m_backend.GetFileWithInfoAsync(remotename)),
this
);
}
public void GetForTesting(string remotename, long size, string hash)
{
WaitForTaskIfRequired(true, async () =>
{
using (await m_backend.GetFileForTestingAsync(remotename, size, hash))
{ }
});
}
public IList List()
{
return
((Task>)
WaitForTaskIfRequired(true, () => m_backend.ListFilesAsync()))
.Result;
}
public Interface.IQuotaInfo QuotaInfo { get { return m_backend.QuotaInfo; } }
public void WaitForComplete()
{
WaitForEmpty();
m_backend.Dispose();
}
public void WaitForEmpty()
{
WaitForTaskIfRequired(true, async () => {
while (m_pendingTasks.Count > 0)
{
var top = m_pendingTasks.Peek();
if (await Task.WhenAny(top, Task.Delay(1000)) == top)
{
lock (m_lock)
top = m_pendingTasks.Dequeue();
await top;
}
}
});
}
public void CreateFolder(string remotename)
{
WaitForTaskIfRequired(true, () => m_backend.CreateFolder(remotename));
}
public void Delete(string remotename, long size, bool synchronous = false)
{
WaitForTaskIfRequired(synchronous, () => m_backend.DeleteFileAsync(remotename));
}
public void FlushDbMessages()
{
m_db.ProcessAllPendingOperations();
}
public void Dispose()
{
if (m_backend != null)
m_backend.Dispose();
if (m_db != null)
m_db.Dispose();
}
}
}