2024-02-28 15:45:30 +01:00
// 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.
2016-02-28 21:39:36 +01:00
using System ;
2013-03-27 16:06:45 +01:00
using System.Collections.Generic ;
using System.Linq ;
2018-09-09 14:42:40 +02:00
using System.Security.Cryptography ;
2013-03-27 16:06:45 +01:00
using System.Text ;
using Duplicati.Library.Utility ;
2013-05-08 20:17:07 +02:00
using Duplicati.Library.Main.Database ;
using Duplicati.Library.Main.Volumes ;
2013-03-27 16:06:45 +01:00
using Newtonsoft.Json ;
2014-11-15 15:01:01 +01:00
using Duplicati.Library.Localization.Short ;
2019-02-22 21:58:40 -06:00
using System.Threading ;
2019-12-25 18:21:24 +01:00
using System.Net ;
2013-03-27 16:06:45 +01:00
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main
2013-03-27 16:06:45 +01:00
{
2013-08-20 22:16:30 +02:00
internal class BackendManager : IDisposable
2013-03-27 16:06:45 +01:00
{
2018-03-12 14:07:11 +01:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType < BackendManager >();
2013-05-05 17:54:59 +02:00
/// <summary>
/// Class to represent hash failures
/// </summary>
[Serializable]
2016-03-02 23:01:38 +01:00
public class HashMismatchException : Exception
2013-05-05 17:54:59 +02:00
{
/// <summary>
/// Default constructor, sets a generic string as the message
/// </summary>
2016-03-02 23:01:38 +01:00
public HashMismatchException () : base () { }
2013-05-05 17:54:59 +02:00
/// <summary>
/// Constructor with non-default message
/// </summary>
/// <param name="message">The exception message</param>
2016-03-02 23:01:38 +01:00
public HashMismatchException ( string message ) : base ( message ) { }
2013-05-05 17:54:59 +02:00
/// <summary>
/// Constructor with non-default message and inner exception details
/// </summary>
/// <param name="message">The exception message</param>
/// <param name="innerException">The exception that caused this exception</param>
2016-03-02 23:01:38 +01:00
public HashMismatchException ( string message , Exception innerException ) : base ( message , innerException ) { }
2013-05-05 17:54:59 +02:00
}
2017-12-25 04:12:19 +07:00
2013-03-27 16:06:45 +01:00
private enum OperationType
{
Get ,
Put ,
List ,
Delete ,
CreateFolder ,
2015-11-17 12:37:33 +01:00
Terminate ,
Nothing
2013-03-27 16:06:45 +01:00
}
public interface IDownloadWaitHandle
{
TempFile Wait ();
2013-04-04 20:34:26 +02:00
TempFile Wait ( out string hash , out long size );
2013-03-27 16:06:45 +01:00
}
private class FileEntryItem : IDownloadWaitHandle
{
2013-07-23 18:56:01 +02:00
/// <summary>
/// The current operation this entry represents
/// </summary>
2019-10-19 10:56:21 -07:00
public readonly OperationType Operation ;
2013-07-23 18:56:01 +02:00
/// <summary>
/// The name of the remote file
/// </summary>
2013-03-27 16:06:45 +01:00
public string RemoteFilename ;
2013-07-23 18:56:01 +02:00
/// <summary>
/// The name of the local file
/// </summary>
public string LocalFilename { get { return LocalTempfile ; } }
/// <summary>
/// A reference to a temporary file that is disposed upon
/// failure or completion of the item
/// </summary>
public TempFile LocalTempfile ;
/// <summary>
/// True if the item has been encrypted
/// </summary>
2013-03-27 16:06:45 +01:00
public bool Encrypted ;
2013-07-23 18:56:01 +02:00
/// <summary>
/// The result object
/// </summary>
2013-03-27 16:06:45 +01:00
public object Result ;
2013-07-23 18:56:01 +02:00
/// <summary>
/// The expected hash value of the file
/// </summary>
2013-03-27 16:06:45 +01:00
public string Hash ;
2013-07-23 18:56:01 +02:00
/// <summary>
/// The expected size of the file
/// </summary>
2013-03-27 16:06:45 +01:00
public long Size ;
2013-07-23 18:56:01 +02:00
/// <summary>
/// Reference to the index file entry that is updated if this entry changes
/// </summary>
public Tuple < IndexVolumeWriter , FileEntryItem > Indexfile ;
/// <summary>
/// A flag indicating if the final hash and size of the block volume has been written to the index file
/// </summary>
public bool IndexfileUpdated ;
/// <summary>
/// An exception that this item has caused
/// </summary>
2013-05-20 15:00:44 +02:00
public Exception Exception ;
2013-07-23 18:56:01 +02:00
/// <summary>
/// True if an exception ultimately kills the handler,
/// false if the item is returned with an exception
/// </summary>
2019-10-19 10:56:21 -07:00
public readonly bool ExceptionKillsHandler ;
2013-07-23 18:56:01 +02:00
/// <summary>
/// A flag indicating if the file is a extra metadata file
/// that has no entry in the database
/// </summary>
2013-07-11 18:08:47 +02:00
public bool NotTrackedInDb ;
2014-03-10 14:16:27 +01:00
/// <summary>
/// A flag that indicates that the download is only checked for the hash and the file is not decrypted or returned
/// </summary>
public bool VerifyHashOnly ;
2013-03-27 16:06:45 +01:00
2013-07-23 18:56:01 +02:00
/// <summary>
/// The event that is signaled once the operation is complete or has failed
/// </summary>
2018-05-23 21:18:01 -07:00
private readonly System . Threading . ManualResetEvent DoneEvent ;
2013-03-27 16:06:45 +01:00
2013-07-23 18:56:01 +02:00
public FileEntryItem ( OperationType operation , string remotefilename , Tuple < IndexVolumeWriter , FileEntryItem > indexfile = null )
2013-03-27 16:06:45 +01:00
{
Operation = operation ;
RemoteFilename = remotefilename ;
2013-04-29 23:50:38 +02:00
Indexfile = indexfile ;
2013-05-20 15:00:44 +02:00
ExceptionKillsHandler = operation != OperationType . Get ;
2013-05-25 16:40:15 +02:00
Size = - 1 ;
2013-03-27 16:06:45 +01:00
DoneEvent = new System . Threading . ManualResetEvent ( false );
}
2013-07-23 18:56:01 +02:00
public FileEntryItem ( OperationType operation , string remotefilename , long size , string hash , Tuple < IndexVolumeWriter , FileEntryItem > indexfile = null )
2013-04-29 23:50:38 +02:00
: this ( operation , remotefilename , indexfile )
2013-03-27 16:06:45 +01:00
{
Size = size ;
Hash = hash ;
}
2013-07-23 18:56:01 +02:00
public void SetLocalfilename ( string name )
{
this . LocalTempfile = Library . Utility . TempFile . WrapExistingFile ( name );
this . LocalTempfile . Protected = true ;
}
2013-03-27 16:06:45 +01:00
public void SignalComplete ()
{
DoneEvent . Set ();
}
public void WaitForComplete ()
{
DoneEvent . WaitOne ();
}
TempFile IDownloadWaitHandle . Wait ()
{
this . WaitForComplete ();
2013-05-20 15:00:44 +02:00
if ( Exception != null )
2016-09-15 11:39:27 +02:00
throw Exception ;
2017-12-25 04:12:19 +07:00
2013-03-27 16:06:45 +01:00
return ( TempFile ) this . Result ;
}
2013-04-04 20:34:26 +02:00
TempFile IDownloadWaitHandle . Wait ( out string hash , out long size )
{
this . WaitForComplete ();
2017-12-25 04:12:19 +07:00
2013-05-20 15:00:44 +02:00
if ( Exception != null )
2016-09-15 11:39:27 +02:00
throw Exception ;
2017-12-25 04:12:19 +07:00
2013-04-04 20:34:26 +02:00
hash = this . Hash ;
size = this . Size ;
2017-12-25 04:12:19 +07:00
2013-04-04 20:34:26 +02:00
return ( TempFile ) this . Result ;
}
2013-05-25 16:40:15 +02:00
public void Encrypt ( Library . Interface . IEncryption encryption , IBackendWriter stat )
2013-03-27 16:06:45 +01:00
{
if ( encryption != null && ! this . Encrypted )
{
2013-07-23 18:56:01 +02:00
var tempfile = new Library . Utility . TempFile ();
encryption . Encrypt ( this . LocalFilename , tempfile );
2013-04-16 22:36:19 +02:00
this . DeleteLocalFile ( stat );
2013-07-23 18:56:01 +02:00
this . LocalTempfile = tempfile ;
2013-03-27 16:06:45 +01:00
this . Hash = null ;
this . Size = 0 ;
this . Encrypted = true ;
}
}
2017-12-25 04:12:19 +07:00
2013-05-06 09:58:19 +02:00
public bool UpdateHashAndSize ( Options options )
2013-03-27 16:06:45 +01:00
{
if ( Hash == null || Size < 0 )
{
Hash = CalculateFileHash ( this . LocalFilename );
Size = new System . IO . FileInfo ( this . LocalFilename ). Length ;
return true ;
}
return false ;
}
2017-12-25 04:12:19 +07:00
2013-05-25 16:40:15 +02:00
public void DeleteLocalFile ( IBackendWriter stat )
2013-04-16 22:36:19 +02:00
{
2013-07-23 18:56:01 +02:00
if ( this . LocalTempfile != null )
try { this . LocalTempfile . Dispose (); }
2018-03-12 14:07:11 +01:00
catch ( Exception ex ) { Logging . Log . WriteWarningMessage ( LOGTAG , "DeleteTemporaryFileError" , ex , "Failed to dispose temporary file: {0}" , this . LocalTempfile ); }
2013-07-23 18:56:01 +02:00
finally { this . LocalTempfile = null ; }
2013-04-16 22:36:19 +02:00
}
2017-12-25 04:12:19 +07:00
2013-05-25 16:40:15 +02:00
public BackendActionType BackendActionType
2017-12-25 04:12:19 +07:00
{
2013-05-25 16:40:15 +02:00
get
{
switch ( this . Operation )
{
case OperationType . Get :
return BackendActionType . Get ;
case OperationType . Put :
return BackendActionType . Put ;
case OperationType . Delete :
return BackendActionType . Delete ;
case OperationType . List :
return BackendActionType . List ;
case OperationType . CreateFolder :
return BackendActionType . CreateFolder ;
default :
throw new Exception ( string . Format ( "Unexpected operation type: {0}" , this . Operation ));
}
}
}
2016-02-28 21:39:36 +01:00
}
2013-04-09 20:43:27 +02:00
private class DatabaseCollector
{
2018-03-17 19:59:23 -07:00
private readonly object m_dbqueuelock = new object ();
2018-05-23 21:18:01 -07:00
private readonly LocalDatabase m_database ;
private readonly System . Threading . Thread m_callerThread ;
2016-09-15 11:39:27 +02:00
private List < IDbEntry > m_dbqueue ;
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
private interface IDbEntry { }
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
private class DbOperation : IDbEntry
{
public string Action ;
public string File ;
public string Result ;
}
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
private class DbUpdate : IDbEntry
{
public string Remotename ;
public RemoteVolumeState State ;
public long Size ;
public string Hash ;
}
2017-12-25 04:12:19 +07:00
2013-07-23 18:56:01 +02:00
private class DbRename : IDbEntry
{
public string Oldname ;
public string Newname ;
}
2017-12-25 04:12:19 +07:00
2018-10-06 13:30:13 -07:00
public DatabaseCollector ( LocalDatabase database )
2016-09-15 11:39:27 +02:00
{
m_database = database ;
2017-12-25 04:12:19 +07:00
m_dbqueue = new List < IDbEntry >();
2016-09-15 11:39:27 +02:00
if ( m_database != null )
m_callerThread = System . Threading . Thread . CurrentThread ;
}
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
public void LogDbOperation ( string action , string file , string result )
{
2017-12-25 04:12:19 +07:00
lock ( m_dbqueuelock )
2016-09-15 11:39:27 +02:00
m_dbqueue . Add ( new DbOperation () { Action = action , File = file , Result = result });
}
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
public void LogDbUpdate ( string remotename , RemoteVolumeState state , long size , string hash )
{
2017-12-25 04:12:19 +07:00
lock ( m_dbqueuelock )
2016-09-15 11:39:27 +02:00
m_dbqueue . Add ( new DbUpdate () { Remotename = remotename , State = state , Size = size , Hash = hash });
}
2013-04-09 20:43:27 +02:00
2013-07-23 18:56:01 +02:00
public void LogDbRename ( string oldname , string newname )
{
2017-12-25 04:12:19 +07:00
lock ( m_dbqueuelock )
2013-07-23 18:56:01 +02:00
m_dbqueue . Add ( new DbRename () { Oldname = oldname , Newname = newname });
}
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
public bool FlushDbMessages ( bool checkThread = false )
{
if ( m_database != null && ( checkThread == false || m_callerThread == System . Threading . Thread . CurrentThread ))
return FlushDbMessages ( m_database , null );
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
return false ;
}
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
public bool FlushDbMessages ( LocalDatabase db , System . Data . IDbTransaction transaction )
{
List < IDbEntry > entries ;
2017-12-25 04:12:19 +07:00
lock ( m_dbqueuelock )
2016-09-15 11:39:27 +02:00
if ( m_dbqueue . Count == 0 )
return false ;
else
{
entries = m_dbqueue ;
m_dbqueue = new List < IDbEntry >();
}
2016-03-16 00:49:28 +01:00
// collect removed volumes for final db cleanup.
HashSet < string > volsRemoved = new HashSet < string >();
2016-09-15 11:39:27 +02:00
//As we replace the list, we can now freely access the elements without locking
2017-12-25 04:12:19 +07:00
foreach ( var e in entries )
2019-09-29 20:16:28 -07:00
if ( e is DbOperation operation )
db . LogRemoteOperation ( operation . Action , operation . File , operation . Result , transaction );
else if ( e is DbUpdate update && update . State == RemoteVolumeState . Deleted )
2016-03-16 00:49:28 +01:00
{
2019-09-29 20:16:28 -07:00
db . UpdateRemoteVolume ( update . Remotename , RemoteVolumeState . Deleted , update . Size , update . Hash , true , TimeSpan . FromHours ( 2 ), transaction );
volsRemoved . Add ( update . Remotename );
2016-03-16 00:49:28 +01:00
}
2019-09-29 20:16:28 -07:00
else if ( e is DbUpdate dbUpdate )
db . UpdateRemoteVolume ( dbUpdate . Remotename , dbUpdate . State , dbUpdate . Size , dbUpdate . Hash , transaction );
else if ( e is DbRename rename )
db . RenameRemoteFile ( rename . Oldname , rename . Newname , transaction );
2016-03-16 00:49:28 +01:00
else if ( e != null )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteErrorMessage ( LOGTAG , "InvalidQueueElement" , null , "Queue had element of type: {0}, {1}" , e . GetType (), e );
2016-03-16 00:49:28 +01:00
// Finally remove volumes from DB.
if ( volsRemoved . Count > 0 )
db . RemoveRemoteVolumes ( volsRemoved );
2016-09-15 11:39:27 +02:00
return true ;
}
2013-04-09 20:43:27 +02:00
}
2013-03-27 16:06:45 +01:00
2017-03-03 20:50:36 +01:00
private readonly BlockingQueue < FileEntryItem > m_queue ;
2018-05-23 21:18:01 -07:00
private readonly Options m_options ;
2013-03-27 16:06:45 +01:00
private volatile Exception m_lastException ;
2018-05-23 21:18:01 -07:00
private readonly Library . Interface . IEncryption m_encryption ;
2013-08-31 14:30:58 +02:00
private readonly object m_encryptionLock = new object ();
2013-03-27 16:06:45 +01:00
private Library . Interface . IBackend m_backend ;
2018-05-23 21:18:01 -07:00
private readonly string m_backendurl ;
private readonly IBackendWriter m_statwriter ;
2013-04-09 20:43:27 +02:00
private System . Threading . Thread m_thread ;
2018-05-23 21:18:01 -07:00
private readonly BasicResults m_taskControl ;
2017-03-03 20:50:36 +01:00
private readonly DatabaseCollector m_db ;
2017-10-30 16:12:44 +01:00
// Cache these
private readonly int m_numberofretries ;
private readonly TimeSpan m_retrydelay ;
2022-01-15 18:11:59 +01:00
private readonly Boolean m_retrywithexponentialbackoff ;
2017-12-25 04:12:19 +07:00
2013-04-01 15:05:33 +02:00
public string BackendUrl { get { return m_backendurl ; } }
2017-12-25 04:12:19 +07:00
2013-05-25 16:40:15 +02:00
public BackendManager ( string backendurl , Options options , IBackendWriter statwriter , LocalDatabase database )
2013-03-27 16:06:45 +01:00
{
m_options = options ;
m_backendurl = backendurl ;
2013-05-25 16:40:15 +02:00
m_statwriter = statwriter ;
2014-05-15 12:47:16 +02:00
m_taskControl = statwriter as BasicResults ;
2017-10-30 16:12:44 +01:00
m_numberofretries = options . NumberOfRetries ;
m_retrydelay = options . RetryDelay ;
2022-01-15 18:11:59 +01:00
m_retrywithexponentialbackoff = options . RetryWithExponentialBackoff ;
2017-10-30 16:12:44 +01:00
2018-10-06 13:30:13 -07:00
m_db = new DatabaseCollector ( database );
2013-03-27 16:06:45 +01:00
m_backend = DynamicLoader . BackendLoader . GetBackend ( m_backendurl , m_options . RawOptions );
2016-09-15 11:39:27 +02:00
if ( m_backend == null )
{
string shortname = m_backendurl ;
2016-04-18 12:03:51 +02:00
2016-09-15 11:39:27 +02:00
// Try not to leak hostnames or other information in the error messages
try { shortname = new Library . Utility . Uri ( shortname ). Scheme ; }
catch { }
2016-04-18 12:03:51 +02:00
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( string . Format ( "Backend not supported: {0}" , shortname ), "BackendNotSupported" );
2016-09-15 11:39:27 +02:00
}
2013-03-27 16:06:45 +01:00
if (! m_options . NoEncryption )
2013-04-06 13:46:58 +02:00
{
2013-03-27 16:06:45 +01:00
m_encryption = DynamicLoader . EncryptionLoader . GetModule ( m_options . EncryptionModule , m_options . Passphrase , m_options . RawOptions );
2013-04-06 13:46:58 +02:00
if ( m_encryption == null )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( string . Format ( "Encryption method not supported: {0}" , m_options . EncryptionModule ), "EncryptionMethodNotSupported" );
2013-04-06 13:46:58 +02:00
}
2013-03-27 16:06:45 +01:00
2014-05-15 12:47:16 +02:00
if ( m_taskControl != null )
2017-12-25 04:12:19 +07:00
m_taskControl . StateChangedEvent += ( state ) => {
2014-05-15 12:47:16 +02:00
if ( state == TaskControlState . Abort )
2021-04-03 20:54:47 -07:00
m_thread . Interrupt ();
2014-05-15 12:47:16 +02:00
};
2013-05-08 19:57:13 +02:00
m_queue = new BlockingQueue < FileEntryItem >( options . SynchronousUpload ? 1 : ( options . AsynchronousUploadLimit == 0 ? int . MaxValue : options . AsynchronousUploadLimit ));
2013-03-27 16:06:45 +01:00
m_thread = new System . Threading . Thread ( this . ThreadRun );
2013-04-09 20:43:27 +02:00
m_thread . Name = "Backend Async Worker" ;
2013-03-27 16:06:45 +01:00
m_thread . IsBackground = true ;
m_thread . Start ();
}
2014-08-19 20:20:45 +02:00
public static string CalculateFileHash ( string filename )
{
using ( System . IO . FileStream fs = System . IO . File . OpenRead ( filename ))
2020-12-28 12:18:58 -08:00
using ( var hasher = VolumeHashFactory . CreateHasher ())
2014-08-19 20:20:45 +02:00
return Convert . ToBase64String ( hasher . ComputeHash ( fs ));
}
2016-02-28 21:39:36 +01:00
/// <summary> Calculate file hash directly on stream object (for piping) </summary>
public static string CalculateFileHash ( System . IO . Stream stream )
{
2020-12-28 12:18:58 -08:00
using ( var hasher = VolumeHashFactory . CreateHasher ())
2016-02-28 21:39:36 +01:00
return Convert . ToBase64String ( hasher . ComputeHash ( stream ));
}
/// <summary>
/// Returns a stream for hashing that can be part of a stream stack together
/// with a callback to retrieve the hash when done.
/// </summary>
public static System . Security . Cryptography . CryptoStream GetFileHasherStream
2020-12-28 12:18:58 -08:00
( System . IO . Stream stream , System . Security . Cryptography . CryptoStreamMode mode , HashAlgorithm hasher , out Func < string > getHash )
2016-02-28 21:39:36 +01:00
{
System . Security . Cryptography . CryptoStream retHasherStream =
new System . Security . Cryptography . CryptoStream ( stream , hasher , mode );
getHash = () =>
{
if ( mode == System . Security . Cryptography . CryptoStreamMode . Write
&& ! retHasherStream . HasFlushedFinalBlock )
retHasherStream . FlushFinalBlock ();
string retHash = Convert . ToBase64String ( hasher . Hash );
return retHash ;
};
return retHasherStream ;
}
2013-03-27 16:06:45 +01:00
private void ThreadRun ()
{
2013-08-31 15:29:07 +02:00
var uploadSuccess = false ;
2013-03-27 16:06:45 +01:00
while (! m_queue . Completed )
{
var item = m_queue . Dequeue ();
if ( item != null )
{
int retries = 0 ;
Exception lastException = null ;
do
{
try
{
2014-05-15 12:47:16 +02:00
if ( m_taskControl != null )
m_taskControl . TaskControlRendevouz ();
2017-12-25 04:12:19 +07:00
2013-03-27 16:06:45 +01:00
if ( m_options . NoConnectionReuse && m_backend != null )
{
m_backend . Dispose ();
m_backend = null ;
}
if ( m_backend == null )
m_backend = DynamicLoader . BackendLoader . GetBackend ( m_backendurl , m_options . RawOptions );
2013-08-31 15:29:07 +02:00
if ( m_backend == null )
throw new Exception ( "Backend failed to re-load" );
2013-03-27 16:06:45 +01:00
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , string . Format ( "RemoteOperation{0}" , item . Operation ), string . Format ( "RemoteOperation{0}" , item . Operation )))
2013-06-09 15:26:17 +02:00
switch ( item . Operation )
{
case OperationType . Put :
DoPut ( item );
2013-08-31 15:29:07 +02:00
// We do not auto create folders,
// because we know the folder exists
uploadSuccess = true ;
2013-06-09 15:26:17 +02:00
break ;
case OperationType . Get :
DoGet ( item );
break ;
case OperationType . List :
DoList ( item );
break ;
case OperationType . Delete :
DoDelete ( item );
break ;
case OperationType . CreateFolder :
DoCreateFolder ( item );
break ;
case OperationType . Terminate :
m_queue . SetCompleted ();
break ;
2015-11-17 12:37:33 +01:00
case OperationType . Nothing :
item . SignalComplete ();
break ;
2013-06-09 15:26:17 +02:00
}
2013-03-27 16:06:45 +01:00
lastException = null ;
2017-10-30 16:12:44 +01:00
retries = m_numberofretries ;
2013-03-27 16:06:45 +01:00
}
catch ( Exception ex )
{
retries ++;
lastException = ex ;
2018-03-12 14:07:11 +01:00
Logging . Log . WriteRetryMessage ( LOGTAG , $"Retry{item.Operation}" , ex , "Operation {0} with file {1} attempt {2} of {3} failed with message: {4}" , item . Operation , item . RemoteFilename , retries , m_numberofretries , ex . Message );
2017-12-25 04:12:19 +07:00
2014-05-15 12:47:16 +02:00
// If the thread is aborted, we exit here
if ( ex is System . Threading . ThreadAbortException )
{
m_queue . SetCompleted ();
item . Exception = ex ;
item . SignalComplete ();
throw ;
}
2017-12-25 04:12:19 +07:00
2019-09-29 20:16:28 -07:00
if ( ex is WebException exception )
2018-02-18 01:30:05 +01:00
{
2018-02-20 20:34:09 +01:00
// Refresh DNS name if we fail to connect in order to prevent issues with incorrect DNS entries
2019-09-29 20:16:28 -07:00
if ( exception . Status == System . Net . WebExceptionStatus . NameResolutionFailure )
2018-02-18 01:30:05 +01:00
{
2018-02-20 20:34:09 +01:00
try
{
2018-02-26 11:37:10 +01:00
var names = m_backend . DNSName ?? new string [ 0 ];
foreach ( var name in names )
if (! string . IsNullOrWhiteSpace ( name ))
System . Net . Dns . GetHostEntry ( name );
2018-02-20 20:34:09 +01:00
}
catch
{
}
2018-02-18 01:30:05 +01:00
}
}
2017-10-30 16:12:44 +01:00
m_statwriter . SendEvent ( item . BackendActionType , retries < m_numberofretries ? BackendEventType . Retrying : BackendEventType . Failed , item . RemoteFilename , item . Size );
2013-03-27 16:06:45 +01:00
2016-09-15 11:39:27 +02:00
bool recovered = false ;
2013-08-31 15:29:07 +02:00
if (! uploadSuccess && ex is Duplicati . Library . Interface . FolderMissingException && m_options . AutocreateFolders )
2013-04-25 19:44:55 +02:00
{
2017-12-25 04:12:19 +07:00
try
{
2016-09-15 11:39:27 +02:00
// If we successfully create the folder, we can re-use the connection
2017-12-25 04:12:19 +07:00
m_backend . CreateFolder ();
2016-09-15 11:39:27 +02:00
recovered = true ;
}
2017-12-25 04:12:19 +07:00
catch ( Exception dex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "FolderCreateError" , dex , "Failed to create folder: {0}" , ex . Message );
2016-09-15 11:39:27 +02:00
}
2013-04-25 19:44:55 +02:00
}
2017-12-25 04:12:19 +07:00
2013-07-23 18:56:01 +02:00
// To work around the Apache WEBDAV issue, we rename the file here
2017-10-30 16:12:44 +01:00
if ( item . Operation == OperationType . Put && retries < m_numberofretries && ! item . NotTrackedInDb )
2013-07-23 18:56:01 +02:00
RenameFileAfterError ( item );
2017-12-25 04:12:19 +07:00
2013-04-25 19:44:55 +02:00
if (! recovered )
{
2013-07-23 18:56:01 +02:00
try { m_backend . Dispose (); }
2018-03-12 14:07:11 +01:00
catch ( Exception dex ) { Logging . Log . WriteWarningMessage ( LOGTAG , "BackendDisposeError" , dex , "Failed to dispose backend instance: {0}" , ex . Message ); }
2017-12-25 04:12:19 +07:00
2013-07-23 18:56:01 +02:00
m_backend = null ;
2017-02-14 10:28:59 +01:00
2017-10-30 16:12:44 +01:00
if ( retries < m_numberofretries && m_retrydelay . Ticks != 0 )
2017-02-14 10:28:59 +01:00
{
2022-01-15 18:11:59 +01:00
var delay = Library . Utility . Utility . GetRetryDelay ( m_retrydelay , retries , m_retrywithexponentialbackoff );
var target = DateTime . Now . Add ( delay );
2017-02-14 10:28:59 +01:00
while ( target > DateTime . Now )
{
if ( m_taskControl != null && m_taskControl . IsAbortRequested ())
break ;
2017-12-25 04:12:19 +07:00
2017-02-14 10:28:59 +01:00
System . Threading . Thread . Sleep ( 500 );
}
}
2013-07-23 18:56:01 +02:00
}
2013-03-27 16:06:45 +01:00
}
2017-12-25 04:12:19 +07:00
2013-03-27 16:06:45 +01:00
2017-10-30 16:12:44 +01:00
} while ( retries < m_numberofretries );
2013-03-27 16:06:45 +01:00
2014-11-15 15:01:01 +01:00
if ( lastException != null && !( lastException is Duplicati . Library . Interface . FileMissingException ) && item . Operation == OperationType . Delete )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "DeleteFileFailed" , LC . L ( "Failed to delete file {0}, testing if file exists" , item . RemoteFilename ));
2014-11-15 15:01:01 +01:00
try
{
if (! m_backend . List (). Select ( x => x . Name ). Contains ( item . RemoteFilename ))
{
lastException = null ;
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "DeleteFileFailureRecovered" , LC . L ( "Recovered from problem with attempting to delete non-existing file {0}" , item . RemoteFilename ));
2014-11-15 15:01:01 +01:00
}
}
2017-12-25 04:12:19 +07:00
catch ( Exception ex )
2014-11-15 15:01:01 +01:00
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "DeleteFileFailure" , ex , LC . L ( "Failed to recover from error deleting file {0}" , item . RemoteFilename ), ex );
2014-11-15 15:01:01 +01:00
}
}
2013-03-27 16:06:45 +01:00
if ( lastException != null )
{
2016-09-15 11:39:27 +02:00
item . Exception = lastException ;
2013-04-16 22:36:19 +02:00
if ( item . Operation == OperationType . Put )
2016-09-15 11:39:27 +02:00
item . DeleteLocalFile ( m_statwriter );
2017-12-25 04:12:19 +07:00
2013-05-20 15:00:44 +02:00
if ( item . ExceptionKillsHandler )
{
2016-09-15 11:39:27 +02:00
m_lastException = lastException ;
2013-04-16 22:36:19 +02:00
2016-09-15 11:39:27 +02:00
//TODO: If there are temp files in the queue, we must delete them
m_queue . SetCompleted ();
2017-12-25 04:12:19 +07:00
}
2013-03-27 16:06:45 +01:00
}
2017-12-25 04:12:19 +07:00
2013-03-27 16:06:45 +01:00
item . SignalComplete ();
}
}
//Make sure everything in the queue is signalled
FileEntryItem i ;
while (( i = m_queue . Dequeue ()) != null )
i . SignalComplete ();
}
2013-07-23 18:56:01 +02:00
private void RenameFileAfterError ( FileEntryItem item )
{
var p = VolumeBase . ParseFilename ( item . RemoteFilename );
2018-10-06 13:30:13 -07:00
var guid = VolumeWriterBase . GenerateGuid ();
2013-07-23 18:56:01 +02:00
var time = p . Time . Ticks == 0 ? p . Time : p . Time . AddSeconds ( 1 );
var newname = VolumeBase . GenerateFilename ( p . FileType , p . Prefix , guid , time , p . CompressionModule , p . EncryptionModule );
var oldname = item . RemoteFilename ;
2017-12-25 04:12:19 +07:00
2013-07-23 18:56:01 +02:00
m_statwriter . SendEvent ( item . BackendActionType , BackendEventType . Rename , oldname , item . Size );
m_statwriter . SendEvent ( item . BackendActionType , BackendEventType . Rename , newname , item . Size );
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "RenameRemoteTargetFile" , "Renaming \"{0}\" to \"{1}\"" , oldname , newname );
2013-07-23 18:56:01 +02:00
m_db . LogDbRename ( oldname , newname );
item . RemoteFilename = newname ;
2017-12-25 04:12:19 +07:00
2013-07-23 18:56:01 +02:00
// If there is an index file attached to the block file,
// it references the block filename, so we create a new index file
// which is a copy of the current, but with the new name
if ( item . Indexfile != null )
{
if (! item . IndexfileUpdated )
{
item . Indexfile . Item1 . FinishVolume ( item . Hash , item . Size );
item . Indexfile . Item1 . Close ();
item . IndexfileUpdated = true ;
}
2017-12-25 04:12:19 +07:00
2020-03-15 09:53:21 -07:00
IndexVolumeWriter wr = null ;
try
2013-07-23 18:56:01 +02:00
{
2021-04-04 11:17:13 -07:00
var hashsize = HashFactory . HashSizeBytes ( m_options . BlockHashAlgorithm );
2020-03-15 09:53:21 -07:00
wr = new IndexVolumeWriter ( m_options );
2017-12-25 04:12:19 +07:00
using ( var rd = new IndexVolumeReader ( p . CompressionModule , item . Indexfile . Item2 . LocalFilename , m_options , hashsize ))
2013-07-23 18:56:01 +02:00
wr . CopyFrom ( rd , x => x == oldname ? newname : x );
item . Indexfile . Item1 . Dispose ();
item . Indexfile = new Tuple < IndexVolumeWriter , FileEntryItem >( wr , item . Indexfile . Item2 );
2013-08-30 22:39:04 +02:00
item . Indexfile . Item2 . LocalTempfile . Dispose ();
2013-07-23 18:56:01 +02:00
item . Indexfile . Item2 . LocalTempfile = wr . TempFile ;
wr . Close ();
}
2020-03-15 09:53:21 -07:00
catch
{
2020-03-15 09:58:14 -07:00
wr ?. Dispose ();
2020-03-15 09:53:21 -07:00
throw ;
}
2013-07-23 18:56:01 +02:00
}
}
2017-06-20 13:00:52 +02:00
2017-09-18 23:23:45 -06:00
private string m_lastThrottleUploadValue = null ;
private string m_lastThrottleDownloadValue = null ;
private void HandleProgress ( ThrottledStream ts , long pg )
2013-08-20 22:16:30 +02:00
{
2014-05-15 12:47:16 +02:00
// TODO: Should we pause here as well?
// It might give annoying timeouts for transfers
if ( m_taskControl != null )
m_taskControl . TaskControlRendevouz ();
2017-06-20 13:00:52 +02:00
// Update the throttle speeds if they have changed
string tmp ;
m_options . RawOptions . TryGetValue ( "throttle-upload" , out tmp );
if ( tmp != m_lastThrottleUploadValue )
{
ts . WriteSpeed = m_options . MaxUploadPrSecond ;
m_lastThrottleUploadValue = tmp ;
2017-09-18 23:23:45 -06:00
}
m_options . RawOptions . TryGetValue ( "throttle-download" , out tmp );
if ( tmp != m_lastThrottleDownloadValue )
2017-06-20 13:00:52 +02:00
{
ts . ReadSpeed = m_options . MaxDownloadPrSecond ;
m_lastThrottleDownloadValue = tmp ;
}
2013-08-20 22:16:30 +02:00
m_statwriter . BackendProgressUpdater . UpdateProgress ( pg );
}
2013-07-23 18:56:01 +02:00
2013-03-27 16:06:45 +01:00
private void DoPut ( FileEntryItem item )
{
2013-08-31 14:30:58 +02:00
if ( m_encryption != null )
2017-12-25 04:12:19 +07:00
lock ( m_encryptionLock )
2013-08-31 14:30:58 +02:00
item . Encrypt ( m_encryption , m_statwriter );
2017-12-25 04:12:19 +07:00
2013-07-11 18:08:47 +02:00
if ( item . UpdateHashAndSize ( m_options ) && ! item . NotTrackedInDb )
2016-09-15 11:39:27 +02:00
m_db . LogDbUpdate ( item . RemoteFilename , RemoteVolumeState . Uploading , item . Size , item . Hash );
2013-03-27 16:06:45 +01:00
2013-07-23 18:56:01 +02:00
if ( item . Indexfile != null && ! item . IndexfileUpdated )
2013-03-27 16:06:45 +01:00
{
2013-07-23 18:56:01 +02:00
item . Indexfile . Item1 . FinishVolume ( item . Hash , item . Size );
item . Indexfile . Item1 . Close ();
item . IndexfileUpdated = true ;
2017-12-25 04:12:19 +07:00
}
2013-03-27 16:06:45 +01:00
2013-04-09 20:43:27 +02:00
m_db . LogDbOperation ( "put" , item . RemoteFilename , JsonConvert . SerializeObject ( new { Size = item . Size , Hash = item . Hash }));
2013-05-25 16:40:15 +02:00
m_statwriter . SendEvent ( BackendActionType . Put , BackendEventType . Started , item . RemoteFilename , item . Size );
2013-03-27 16:06:45 +01:00
2015-01-05 11:12:41 +01:00
var begin = DateTime . Now ;
2013-05-08 19:57:13 +02:00
if ( m_backend is Library . Interface . IStreamingBackend && ! m_options . DisableStreamingTransfers )
2013-03-27 16:06:45 +01:00
{
using ( var fs = System . IO . File . OpenRead ( item . LocalFilename ))
2020-03-04 19:57:48 -06:00
using ( var ts = new ThrottledStream ( fs , m_options . MaxUploadPrSecond , 0 ))
2018-10-06 13:30:13 -07:00
using ( var pgs = new Library . Utility . ProgressReportingStream ( ts , pg => HandleProgress ( ts , pg )))
2019-03-17 18:20:14 -05:00
(( Library . Interface . IStreamingBackend ) m_backend ). PutAsync ( item . RemoteFilename , pgs , CancellationToken . None ). Wait ();
2013-03-27 16:06:45 +01:00
}
else
2019-03-17 18:20:14 -05:00
m_backend . PutAsync ( item . RemoteFilename , item . LocalFilename , CancellationToken . None ). Wait ();
2013-03-27 16:06:45 +01:00
2015-01-05 11:12:41 +01:00
var duration = DateTime . Now - begin ;
2018-03-12 14:07:11 +01:00
Logging . Log . WriteProfilingMessage ( LOGTAG , "UploadSpeed" , "Uploaded {0} in {1}, {2}/s" , Library . Utility . Utility . FormatSizeString ( item . Size ), duration , Library . Utility . Utility . FormatSizeString (( long )( item . Size / duration . TotalSeconds )));
2015-01-05 11:12:41 +01:00
2013-07-11 18:08:47 +02:00
if (! item . NotTrackedInDb )
2016-09-15 11:39:27 +02:00
m_db . LogDbUpdate ( item . RemoteFilename , RemoteVolumeState . Uploaded , item . Size , item . Hash );
2017-12-25 04:12:19 +07:00
2013-05-25 16:40:15 +02:00
m_statwriter . SendEvent ( BackendActionType . Put , BackendEventType . Completed , item . RemoteFilename , item . Size );
2013-04-03 21:08:54 +02:00
2013-05-08 19:57:13 +02:00
if ( m_options . ListVerifyUploads )
{
2018-10-06 15:50:05 -07:00
var f = m_backend . List (). FirstOrDefault ( n => n . Name . Equals ( item . RemoteFilename , StringComparison . OrdinalIgnoreCase ));
2013-05-08 19:57:13 +02:00
if ( f == null )
2016-10-16 22:02:55 +02:00
throw new Exception ( string . Format ( "List verify failed, file was not found after upload: {0}" , item . RemoteFilename ));
2013-05-08 19:57:13 +02:00
else if ( f . Size != item . Size && f . Size >= 0 )
throw new Exception ( string . Format ( "List verify failed for file: {0}, size was {1} but expected to be {2}" , f . Name , f . Size , item . Size ));
}
2016-09-15 11:39:27 +02:00
item . DeleteLocalFile ( m_statwriter );
2013-03-27 16:06:45 +01:00
}
2016-02-28 21:39:36 +01:00
private TempFile coreDoGetPiping ( FileEntryItem item , Interface . IEncryption useDecrypter , out long retDownloadSize , out string retHashcode )
{
// With piping allowed, we will parallelize the operation with buffered pipes to maximize throughput:
// Separated: Download (only for streaming) - Hashing - Decryption
// The idea is to use DirectStreamLink's that are inserted in the stream stack, creating a fork to run
// the crypto operations on.
retDownloadSize = - 1 ;
retHashcode = null ;
bool enableStreaming = ( m_backend is Library . Interface . IStreamingBackend && ! m_options . DisableStreamingTransfers );
System . Threading . Tasks . Task < string > taskHasher = null ;
DirectStreamLink linkForkHasher = null ;
System . Threading . Tasks . Task taskDecrypter = null ;
DirectStreamLink linkForkDecryptor = null ;
// keep potential temp files and their streams for cleanup (cannot use using here).
2016-03-02 23:01:38 +01:00
TempFile retTarget = null , dlTarget = null , decryptTarget = null ;
2016-02-28 21:39:36 +01:00
System . IO . Stream dlToStream = null , decryptToStream = null ;
try
{
System . IO . Stream nextTierWriter = null ; // target of our stacked streams
2016-03-02 23:01:38 +01:00
if (! enableStreaming ) // we will always need dlTarget if not streaming...
2016-02-28 21:39:36 +01:00
dlTarget = new TempFile ();
2016-03-02 23:01:38 +01:00
else if ( enableStreaming && useDecrypter == null )
2016-02-28 21:39:36 +01:00
{
2016-03-02 23:01:38 +01:00
dlTarget = new TempFile ();
2016-02-28 21:39:36 +01:00
dlToStream = System . IO . File . OpenWrite ( dlTarget );
2016-03-02 23:01:38 +01:00
nextTierWriter = dlToStream ; // actually write through to file.
2016-02-28 21:39:36 +01:00
}
// setup decryption: fork off a StreamLink from stack, and setup decryptor task
if ( useDecrypter != null )
{
linkForkDecryptor = new DirectStreamLink ( 1 << 16 , false , false , nextTierWriter );
nextTierWriter = linkForkDecryptor . WriterStream ;
linkForkDecryptor . SetKnownLength ( item . Size , false ); // Set length to allow AES-decryption (not streamable yet)
decryptTarget = new TempFile ();
decryptToStream = System . IO . File . OpenWrite ( decryptTarget );
taskDecrypter = new System . Threading . Tasks . Task (() =>
{
using ( var input = linkForkDecryptor . ReaderStream )
using ( var output = decryptToStream )
lock ( m_encryptionLock ) { useDecrypter . Decrypt ( input , output ); }
}
);
}
// setup hashing: fork off a StreamLink from stack, then task computes hash
linkForkHasher = new DirectStreamLink ( 1 << 16 , false , false , nextTierWriter );
nextTierWriter = linkForkHasher . WriterStream ;
taskHasher = new System . Threading . Tasks . Task < string >(() =>
{
using ( var input = linkForkHasher . ReaderStream )
return CalculateFileHash ( input );
}
);
// OK, forks with tasks are set up, so let's do the download which is performed in main thread.
bool hadException = false ;
try
{
if ( enableStreaming )
{
using ( var ss = new ShaderStream ( nextTierWriter , false ))
{
2020-03-04 19:57:48 -06:00
using ( var ts = new ThrottledStream ( ss , 0 , m_options . MaxDownloadPrSecond ))
2018-10-06 13:30:13 -07:00
using ( var pgs = new Library . Utility . ProgressReportingStream ( ts , pg => HandleProgress ( ts , pg )))
2016-03-02 23:01:38 +01:00
{
taskHasher . Start (); // We do not start tasks earlier to be sure the input always gets closed.
if ( taskDecrypter != null ) taskDecrypter . Start ();
2016-02-28 21:39:36 +01:00
(( Library . Interface . IStreamingBackend ) m_backend ). Get ( item . RemoteFilename , pgs );
2016-03-02 23:01:38 +01:00
}
2016-02-28 21:39:36 +01:00
retDownloadSize = ss . TotalBytesWritten ;
}
}
else
{
m_backend . Get ( item . RemoteFilename , dlTarget );
retDownloadSize = new System . IO . FileInfo ( dlTarget ). Length ;
using ( dlToStream = System . IO . File . OpenRead ( dlTarget ))
2016-03-02 23:01:38 +01:00
{
taskHasher . Start (); // We do not start tasks earlier to be sure the input always gets closed.
if ( taskDecrypter != null ) taskDecrypter . Start ();
2016-02-28 21:39:36 +01:00
new DirectStreamLink . DataPump ( dlToStream , nextTierWriter ). Run ();
2016-03-02 23:01:38 +01:00
}
2016-02-28 21:39:36 +01:00
}
}
catch ( Exception )
{ hadException = true ; throw ; }
finally
{
// This nested try-catch-finally blocks will make sure we do not miss any exceptions ans all started tasks
// are properly ended and tidied up. For what is thrown: If exceptions in main thread occured (download) it is thrown,
// then hasher task is checked and last decryption. This resembles old logic.
try { retHashcode = taskHasher . Result ; }
2018-10-13 17:15:47 -07:00
catch ( AggregateException ex ) { if (! hadException ) { hadException = true ; throw ex . Flatten (). InnerException ; } }
2016-02-28 21:39:36 +01:00
finally
{
if ( taskDecrypter != null )
{
try { taskDecrypter . Wait (); }
catch ( AggregateException ex )
{
if (! hadException )
{
hadException = true ;
2018-10-13 17:15:47 -07:00
AggregateException flattenedException = ex . Flatten ();
if ( flattenedException . InnerException is System . Security . Cryptography . CryptographicException )
throw flattenedException . InnerException ;
2016-02-28 21:39:36 +01:00
else
2018-10-13 17:15:47 -07:00
throw new System . Security . Cryptography . CryptographicException ( flattenedException . InnerException . Message , flattenedException . InnerException );
2016-02-28 21:39:36 +01:00
}
}
}
}
}
if ( useDecrypter != null ) // return decrypted temp file
{ retTarget = decryptTarget ; decryptTarget = null ; }
else // return downloaded file
2016-03-23 14:51:58 +01:00
{ retTarget = dlTarget ; dlTarget = null ; }
2016-02-28 21:39:36 +01:00
}
finally
{
2019-11-30 11:35:43 -08:00
// Be tidy: manually do some cleanup to temp files, as we could not use usings.
2016-02-28 21:39:36 +01:00
// Unclosed streams should only occur if we failed even before tasks were started.
if ( dlToStream != null ) dlToStream . Dispose ();
if ( dlTarget != null ) dlTarget . Dispose ();
if ( decryptToStream != null ) decryptToStream . Dispose ();
if ( decryptTarget != null ) decryptTarget . Dispose ();
}
return retTarget ;
}
private TempFile coreDoGetSequential ( FileEntryItem item , Interface . IEncryption useDecrypter , out long retDownloadSize , out string retHashcode )
{
retHashcode = null ;
retDownloadSize = - 1 ;
TempFile retTarget , dlTarget = null , decryptTarget = null ;
try
{
dlTarget = new Library . Utility . TempFile ();
if ( m_backend is Library . Interface . IStreamingBackend && ! m_options . DisableStreamingTransfers )
{
// extended to use stacked streams
using ( var fs = System . IO . File . OpenWrite ( dlTarget ))
2020-12-28 12:18:58 -08:00
using ( var hasher = VolumeHashFactory . CreateHasher ())
using ( var hs = GetFileHasherStream ( fs , System . Security . Cryptography . CryptoStreamMode . Write , hasher , out var getFileHash ))
2016-02-28 21:39:36 +01:00
using ( var ss = new ShaderStream ( hs , true ))
{
2020-03-04 19:57:48 -06:00
using ( var ts = new ThrottledStream ( ss , 0 , m_options . MaxDownloadPrSecond ))
2018-10-06 13:30:13 -07:00
using ( var pgs = new Library . Utility . ProgressReportingStream ( ts , pg => HandleProgress ( ts , pg )))
2016-02-28 21:39:36 +01:00
{ (( Library . Interface . IStreamingBackend ) m_backend ). Get ( item . RemoteFilename , pgs ); }
ss . Flush ();
retDownloadSize = ss . TotalBytesWritten ;
retHashcode = getFileHash ();
}
}
else
{
m_backend . Get ( item . RemoteFilename , dlTarget );
retDownloadSize = new System . IO . FileInfo ( dlTarget ). Length ;
retHashcode = CalculateFileHash ( dlTarget );
}
// Decryption is not placed in the stream stack because there seemed to be an effort
// to throw a CryptographicException on fail. If in main stack, we cannot differentiate
// in which part of the stack the source of an exception resides.
if ( useDecrypter != null )
{
decryptTarget = new Library . Utility . TempFile ();
lock ( m_encryptionLock )
{
try { useDecrypter . Decrypt ( dlTarget , decryptTarget ); }
// If we fail here, make sure that we throw a crypto exception
catch ( System . Security . Cryptography . CryptographicException ) { throw ; }
catch ( Exception ex ) { throw new System . Security . Cryptography . CryptographicException ( ex . Message , ex ); }
}
retTarget = decryptTarget ;
decryptTarget = null ;
}
else
{
retTarget = dlTarget ;
dlTarget = null ;
}
}
finally
{
if ( dlTarget != null ) dlTarget . Dispose ();
2016-03-02 23:01:38 +01:00
if ( decryptTarget != null ) decryptTarget . Dispose ();
2016-02-28 21:39:36 +01:00
}
return retTarget ;
}
2013-03-27 16:06:45 +01:00
private void DoGet ( FileEntryItem item )
{
2013-05-08 21:29:59 +02:00
Library . Utility . TempFile tmpfile = null ;
2013-05-25 16:40:15 +02:00
m_statwriter . SendEvent ( BackendActionType . Get , BackendEventType . Started , item . RemoteFilename , item . Size );
2013-04-03 21:08:54 +02:00
2013-03-27 16:06:45 +01:00
try
{
2015-01-05 11:12:41 +01:00
var begin = DateTime . Now ;
2016-02-28 21:39:36 +01:00
// We already know the filename, so we put the decision about if and which decryptor to
// use prior to download. This allows to set up stacked streams or a pipe doing decryption
Interface . IEncryption useDecrypter = null ;
if (! item . VerifyHashOnly && ! m_options . NoEncryption )
2013-03-27 16:06:45 +01:00
{
2016-02-28 21:39:36 +01:00
useDecrypter = m_encryption ;
{
lock ( m_encryptionLock )
{
try
{
// Auto-guess the encryption module
var ext = ( System . IO . Path . GetExtension ( item . RemoteFilename ) ?? "" ). TrimStart ( '.' );
2017-09-18 23:23:45 -06:00
if (! m_encryption . FilenameExtension . Equals ( ext , StringComparison . OrdinalIgnoreCase ))
2016-02-28 21:39:36 +01:00
{
// Check if the file is encrypted with something else
2017-09-18 23:23:45 -06:00
if ( DynamicLoader . EncryptionLoader . Keys . Contains ( ext , StringComparer . OrdinalIgnoreCase ))
2016-02-28 21:39:36 +01:00
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "AutomaticDecryptionDetection" , "Filename extension \"{0}\" does not match encryption module \"{1}\", using matching encryption module" , ext , m_options . EncryptionModule );
2016-02-28 21:39:36 +01:00
useDecrypter = DynamicLoader . EncryptionLoader . GetModule ( ext , m_options . Passphrase , m_options . RawOptions );
useDecrypter = useDecrypter ?? m_encryption ;
}
// Check if the file is not encrypted
2017-09-18 23:23:45 -06:00
else if ( DynamicLoader . CompressionLoader . Keys . Contains ( ext , StringComparer . OrdinalIgnoreCase ))
2016-02-28 21:39:36 +01:00
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "AutomaticDecryptionDetection" , "Filename extension \"{0}\" does not match encryption module \"{1}\", guessing that it is not encrypted" , ext , m_options . EncryptionModule );
2016-02-28 21:39:36 +01:00
useDecrypter = null ;
}
// Fallback, lets see what happens...
else
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "AutomaticDecryptionDetection" , "Filename extension \"{0}\" does not match encryption module \"{1}\", attempting to use specified encryption module as no others match" , ext , m_options . EncryptionModule );
2016-02-28 21:39:36 +01:00
}
}
}
// If we fail here, make sure that we throw a crypto exception
catch ( System . Security . Cryptography . CryptographicException ) { throw ; }
catch ( Exception ex ) { throw new System . Security . Cryptography . CryptographicException ( ex . Message , ex ); }
}
}
2013-03-27 16:06:45 +01:00
}
2016-02-28 21:39:36 +01:00
string fileHash ;
long dataSizeDownloaded ;
2016-03-19 22:27:46 +01:00
if ( m_options . DisablePipedStreaming )
2016-02-28 21:39:36 +01:00
tmpfile = coreDoGetSequential ( item , useDecrypter , out dataSizeDownloaded , out fileHash );
2016-03-19 22:27:46 +01:00
else
tmpfile = coreDoGetPiping ( item , useDecrypter , out dataSizeDownloaded , out fileHash );
2015-01-05 11:12:41 +01:00
var duration = DateTime . Now - begin ;
2018-03-12 14:07:11 +01:00
Logging . Log . WriteProfilingMessage ( LOGTAG , "DownloadSpeed" , "Downloaded {3}{0} in {1}, {2}/s" , Library . Utility . Utility . FormatSizeString ( dataSizeDownloaded ),
2016-03-02 23:01:38 +01:00
duration , Library . Utility . Utility . FormatSizeString (( long )( dataSizeDownloaded / duration . TotalSeconds )),
2018-03-12 14:07:11 +01:00
useDecrypter == null ? "" : "and decrypted " );
2016-02-18 19:42:45 +01:00
2016-03-02 23:01:38 +01:00
m_db . LogDbOperation ( "get" , item . RemoteFilename , JsonConvert . SerializeObject ( new { Size = dataSizeDownloaded , Hash = fileHash }));
m_statwriter . SendEvent ( BackendActionType . Get , BackendEventType . Completed , item . RemoteFilename , dataSizeDownloaded );
2013-03-27 16:06:45 +01:00
if (! m_options . SkipFileHashChecks )
{
if ( item . Size >= 0 )
{
2016-02-28 21:39:36 +01:00
if ( dataSizeDownloaded != item . Size )
throw new Exception ( Strings . Controller . DownloadedFileSizeError ( item . RemoteFilename , dataSizeDownloaded , item . Size ));
2013-03-27 16:06:45 +01:00
}
2013-04-04 20:34:26 +02:00
else
2016-02-28 21:39:36 +01:00
item . Size = dataSizeDownloaded ;
2013-03-27 16:06:45 +01:00
if (! string . IsNullOrEmpty ( item . Hash ))
{
2016-02-28 21:39:36 +01:00
if ( fileHash != item . Hash )
2016-03-02 23:01:38 +01:00
throw new HashMismatchException ( Strings . Controller . HashMismatchError ( tmpfile , item . Hash , fileHash ));
2013-03-27 16:06:45 +01:00
}
2013-04-04 20:34:26 +02:00
else
2016-02-28 21:39:36 +01:00
item . Hash = fileHash ;
2013-03-27 16:06:45 +01:00
}
2017-09-19 11:20:26 +02:00
if ( item . VerifyHashOnly )
{
tmpfile . Dispose ();
}
else
2013-03-27 16:06:45 +01:00
{
2014-03-10 14:16:27 +01:00
item . Result = tmpfile ;
tmpfile = null ;
2013-03-27 16:06:45 +01:00
}
2017-12-25 04:12:19 +07:00
2013-03-27 16:06:45 +01:00
}
catch
{
if ( tmpfile != null )
tmpfile . Dispose ();
throw ;
}
}
private void DoList ( FileEntryItem item )
{
2013-05-25 16:40:15 +02:00
m_statwriter . SendEvent ( BackendActionType . List , BackendEventType . Started , null , - 1 );
2013-04-03 21:08:54 +02:00
2017-09-25 23:17:45 -06:00
var r = m_backend . List (). ToList ();
2013-03-27 16:06:45 +01:00
StringBuilder sb = new StringBuilder ();
sb . AppendLine ( "[" );
2013-04-03 21:08:54 +02:00
long count = 0 ;
2013-03-27 16:06:45 +01:00
foreach ( var e in r )
{
2013-04-03 21:08:54 +02:00
if ( count != 0 )
2013-03-27 16:06:45 +01:00
sb . AppendLine ( "," );
2013-04-03 21:08:54 +02:00
count ++;
2013-03-27 16:06:45 +01:00
sb . Append ( JsonConvert . SerializeObject ( e ));
}
sb . AppendLine ();
sb . Append ( "]" );
2013-04-09 20:43:27 +02:00
m_db . LogDbOperation ( "list" , "" , sb . ToString ());
2013-03-27 16:06:45 +01:00
item . Result = r ;
2013-04-03 21:08:54 +02:00
2013-05-25 16:40:15 +02:00
m_statwriter . SendEvent ( BackendActionType . List , BackendEventType . Completed , null , r . Count );
2013-03-27 16:06:45 +01:00
}
2013-05-20 15:00:44 +02:00
private void DoDelete ( FileEntryItem item )
2013-03-27 16:06:45 +01:00
{
2013-05-25 16:40:15 +02:00
m_statwriter . SendEvent ( BackendActionType . Delete , BackendEventType . Started , item . RemoteFilename , item . Size );
2013-03-27 16:06:45 +01:00
string result = null ;
try
{
m_backend . Delete ( item . RemoteFilename );
2014-11-14 14:41:36 +01:00
}
2015-09-16 21:20:21 +02:00
catch ( Exception ex )
2014-11-14 14:41:36 +01:00
{
2015-11-17 12:36:00 +01:00
var isFileMissingException = ex is Library . Interface . FileMissingException || ex is System . IO . FileNotFoundException ;
2015-09-16 21:20:21 +02:00
var wr = ex as System . Net . WebException == null ? null : ( ex as System . Net . WebException ). Response as System . Net . HttpWebResponse ;
2015-11-17 12:36:00 +01:00
if ( isFileMissingException || ( wr != null && wr . StatusCode == System . Net . HttpStatusCode . NotFound ))
2014-11-15 15:01:01 +01:00
{
2019-11-19 20:28:14 -08:00
Logging . Log . WriteInformationMessage ( LOGTAG , "DeleteRemoteFileFailed" , LC . L ( "Delete operation failed for {0} with FileNotFound, listing contents" , item . RemoteFilename ));
2015-09-16 21:20:21 +02:00
bool success = false ;
try
{
success = ! m_backend . List (). Select ( x => x . Name ). Contains ( item . RemoteFilename );
}
catch
{
}
if ( success )
{
2019-11-19 20:28:14 -08:00
Logging . Log . WriteInformationMessage ( LOGTAG , "DeleteRemoteFileSuccess" , LC . L ( "Listing indicates file {0} was deleted correctly" , item . RemoteFilename ));
2015-09-16 21:20:21 +02:00
return ;
}
2019-11-19 20:28:14 -08:00
else
{
Logging . Log . WriteWarningMessage ( LOGTAG , "DeleteRemoteFileFailed" , ex , LC . L ( "Listing confirms file {0} was not deleted" , item . RemoteFilename ));
}
2014-11-15 15:01:01 +01:00
}
2013-03-27 16:06:45 +01:00
result = ex . ToString ();
throw ;
}
finally
{
2013-04-09 20:43:27 +02:00
m_db . LogDbOperation ( "delete" , item . RemoteFilename , result );
2013-03-27 16:06:45 +01:00
}
2017-12-25 04:12:19 +07:00
2013-04-09 20:43:27 +02:00
m_db . LogDbUpdate ( item . RemoteFilename , RemoteVolumeState . Deleted , - 1 , null );
2013-05-25 16:40:15 +02:00
m_statwriter . SendEvent ( BackendActionType . Delete , BackendEventType . Completed , item . RemoteFilename , item . Size );
2013-03-27 16:06:45 +01:00
}
2017-12-25 04:12:19 +07:00
2013-05-20 15:00:44 +02:00
private void DoCreateFolder ( FileEntryItem item )
2013-03-27 16:06:45 +01:00
{
2013-05-25 16:40:15 +02:00
m_statwriter . SendEvent ( BackendActionType . CreateFolder , BackendEventType . Started , null , - 1 );
2013-04-03 21:08:54 +02:00
2013-03-27 16:06:45 +01:00
string result = null ;
try
{
2013-05-05 17:54:59 +02:00
m_backend . CreateFolder ();
2017-12-25 04:12:19 +07:00
}
2013-03-27 16:06:45 +01:00
catch ( Exception ex )
{
result = ex . ToString ();
throw ;
}
finally
{
2013-04-09 20:43:27 +02:00
m_db . LogDbOperation ( "createfolder" , item . RemoteFilename , result );
2013-03-27 16:06:45 +01:00
}
2017-12-25 04:12:19 +07:00
2013-05-25 16:40:15 +02:00
m_statwriter . SendEvent ( BackendActionType . CreateFolder , BackendEventType . Completed , null , - 1 );
2013-03-27 16:06:45 +01:00
}
2017-12-25 04:12:19 +07:00
2013-07-01 14:40:45 +02:00
public void PutUnencrypted ( string remotename , string localpath )
{
if ( m_lastException != null )
throw m_lastException ;
2017-12-25 04:12:19 +07:00
2013-07-01 14:40:45 +02:00
var req = new FileEntryItem ( OperationType . Put , remotename , null );
2013-07-23 18:56:01 +02:00
req . SetLocalfilename ( localpath );
2013-07-01 14:40:45 +02:00
req . Encrypted = true ; //Prevent encryption
2013-07-11 18:08:47 +02:00
req . NotTrackedInDb = true ; //Prevent Db updates
2017-09-04 12:15:47 +02:00
try
2013-07-01 14:40:45 +02:00
{
2017-09-04 12:15:47 +02:00
m_statwriter . BackendProgressUpdater . SetBlocking ( true );
if ( m_queue . Enqueue ( req ) && m_options . SynchronousUpload )
{
req . WaitForComplete ();
if ( req . Exception != null )
throw req . Exception ;
}
}
finally
{
m_statwriter . BackendProgressUpdater . SetBlocking ( false );
2013-07-01 14:40:45 +02:00
}
2017-12-25 04:12:19 +07:00
2013-07-01 14:40:45 +02:00
if ( m_lastException != null )
throw m_lastException ;
}
2013-03-27 16:06:45 +01:00
2020-01-19 21:57:08 -06:00
public void Put ( VolumeWriterBase item , IndexVolumeWriter indexfile = null , Action indexVolumeFinishedCallback = null , bool synchronous = false )
2016-09-15 11:39:27 +02:00
{
if ( m_lastException != null )
throw m_lastException ;
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
item . Close ();
var req = new FileEntryItem ( OperationType . Put , item . RemoteFilename , null );
req . LocalTempfile = item . TempFile ;
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
if ( m_lastException != null )
throw m_lastException ;
2013-05-20 15:00:44 +02:00
2013-07-23 18:56:01 +02:00
FileEntryItem req2 = null ;
2017-12-25 04:12:19 +07:00
2013-08-31 14:30:58 +02:00
// As the network link is the bottleneck,
// we encrypt the dblock volume before the
2014-12-30 17:15:58 +01:00
// upload is enqueued (i.e. on the worker thread)
2013-08-31 14:30:58 +02:00
if ( m_encryption != null )
lock ( m_encryptionLock )
req . Encrypt ( m_encryption , m_statwriter );
2017-12-25 04:12:19 +07:00
2013-08-31 14:30:58 +02:00
req . UpdateHashAndSize ( m_options );
2014-12-30 17:15:58 +01:00
m_db . LogDbUpdate ( item . RemoteFilename , RemoteVolumeState . Uploading , req . Size , req . Hash );
2013-08-31 14:30:58 +02:00
// We do not encrypt the dindex volume, because it is small,
// and may need to be re-written if the dblock upload is retried
2016-09-15 11:39:27 +02:00
if ( indexfile != null )
{
m_db . LogDbUpdate ( indexfile . RemoteFilename , RemoteVolumeState . Uploading , - 1 , null );
req2 = new FileEntryItem ( OperationType . Put , indexfile . RemoteFilename );
req2 . LocalTempfile = indexfile . TempFile ;
2013-07-23 18:56:01 +02:00
req . Indexfile = new Tuple < IndexVolumeWriter , FileEntryItem >( indexfile , req2 );
2020-01-19 21:57:08 -06:00
indexfile . FinishVolume ( req . Hash , req . Size );
indexVolumeFinishedCallback ?. Invoke ();
indexfile . Close ();
req . IndexfileUpdated = true ;
2013-03-27 16:06:45 +01:00
}
2014-12-30 17:15:58 +01:00
2017-09-04 12:15:47 +02:00
try
2013-07-23 18:56:01 +02:00
{
2017-09-04 12:15:47 +02:00
m_statwriter . BackendProgressUpdater . SetBlocking ( true );
m_db . FlushDbMessages ( true );
if ( m_queue . Enqueue ( req ) && ( m_options . SynchronousUpload || synchronous ))
{
req . WaitForComplete ();
if ( req . Exception != null )
throw req . Exception ;
}
if ( req2 != null && m_queue . Enqueue ( req2 ) && ( m_options . SynchronousUpload || synchronous ))
{
req2 . WaitForComplete ();
if ( req2 . Exception != null )
throw req2 . Exception ;
}
2013-07-23 18:56:01 +02:00
}
2017-09-04 12:15:47 +02:00
finally
2013-07-23 18:56:01 +02:00
{
2017-09-04 12:15:47 +02:00
m_statwriter . BackendProgressUpdater . SetBlocking ( false );
2013-07-23 18:56:01 +02:00
}
2017-12-25 04:12:19 +07:00
2013-07-23 18:56:01 +02:00
if ( m_lastException != null )
throw m_lastException ;
2013-03-27 16:06:45 +01:00
}
2014-08-19 20:20:45 +02:00
public Library . Utility . TempFile GetWithInfo ( string remotename , out long size , out string hash )
{
if ( m_lastException != null )
throw m_lastException ;
2016-03-17 19:53:51 +01:00
hash = null ; size = - 1 ;
2014-08-19 20:20:45 +02:00
var req = new FileEntryItem ( OperationType . Get , remotename , - 1 , null );
2017-09-04 12:15:47 +02:00
try
{
m_statwriter . BackendProgressUpdater . SetBlocking ( true );
if ( m_queue . Enqueue ( req ))
(( IDownloadWaitHandle ) req ). Wait ( out hash , out size );
}
finally
{
m_statwriter . BackendProgressUpdater . SetBlocking ( false );
}
2014-08-19 20:20:45 +02:00
if ( m_lastException != null )
throw m_lastException ;
return ( Library . Utility . TempFile ) req . Result ;
}
2013-03-27 16:06:45 +01:00
public Library . Utility . TempFile Get ( string remotename , long size , string hash )
2016-09-15 11:39:27 +02:00
{
if ( m_lastException != null )
throw m_lastException ;
2013-03-27 16:06:45 +01:00
2016-09-15 11:39:27 +02:00
var req = new FileEntryItem ( OperationType . Get , remotename , size , hash );
2017-09-04 12:15:47 +02:00
try
{
m_statwriter . BackendProgressUpdater . SetBlocking ( true );
if ( m_queue . Enqueue ( req ))
(( IDownloadWaitHandle ) req ). Wait ();
}
finally
{
m_statwriter . BackendProgressUpdater . SetBlocking ( false );
2017-09-18 23:23:45 -06:00
}
if ( m_lastException != null )
2016-09-15 11:39:27 +02:00
throw m_lastException ;
2013-03-27 16:06:45 +01:00
return ( Library . Utility . TempFile ) req . Result ;
}
public IDownloadWaitHandle GetAsync ( string remotename , long size , string hash )
{
if ( m_lastException != null )
throw m_lastException ;
var req = new FileEntryItem ( OperationType . Get , remotename , size , hash );
2017-09-04 12:15:47 +02:00
try
{
m_statwriter . BackendProgressUpdater . SetBlocking ( true );
if ( m_queue . Enqueue ( req ))
return req ;
2017-09-18 23:23:45 -06:00
}
finally
{
m_statwriter . BackendProgressUpdater . SetBlocking ( false );
}
if ( m_lastException != null )
2016-09-15 11:39:27 +02:00
throw m_lastException ;
2013-03-27 16:06:45 +01:00
else
throw new InvalidOperationException ( "GetAsync called after backend is shut down" );
}
2017-12-25 04:12:19 +07:00
2014-03-10 14:16:27 +01:00
public void GetForTesting ( string remotename , long size , string hash )
{
if ( m_lastException != null )
throw m_lastException ;
2017-12-25 04:12:19 +07:00
2014-08-19 20:20:45 +02:00
if ( string . IsNullOrWhiteSpace ( hash ))
2014-03-10 14:16:27 +01:00
throw new InvalidOperationException ( "Cannot test a file without the hash" );
var req = new FileEntryItem ( OperationType . Get , remotename , size , hash );
req . VerifyHashOnly = true ;
2017-09-04 12:15:47 +02:00
try
2014-03-10 14:16:27 +01:00
{
2017-09-04 12:15:47 +02:00
m_statwriter . BackendProgressUpdater . SetBlocking ( true );
if ( m_queue . Enqueue ( req ))
{
req . WaitForComplete ();
if ( req . Exception != null )
throw req . Exception ;
}
}
finally
{
m_statwriter . BackendProgressUpdater . SetBlocking ( false );
2014-03-10 14:16:27 +01:00
}
if ( m_lastException != null )
throw m_lastException ;
2017-12-25 04:12:19 +07:00
}
2013-03-27 16:06:45 +01:00
public IList < Library . Interface . IFileEntry > List ()
2016-09-15 11:39:27 +02:00
{
if ( m_lastException != null )
throw m_lastException ;
2013-03-27 16:06:45 +01:00
2016-09-15 11:39:27 +02:00
var req = new FileEntryItem ( OperationType . List , null );
2017-09-04 12:15:47 +02:00
try
2016-09-15 11:39:27 +02:00
{
2017-09-04 12:15:47 +02:00
m_statwriter . BackendProgressUpdater . SetBlocking ( true );
if ( m_queue . Enqueue ( req ))
{
req . WaitForComplete ();
if ( req . Exception != null )
throw req . Exception ;
}
}
finally
{
m_statwriter . BackendProgressUpdater . SetBlocking ( false );
2016-09-15 11:39:27 +02:00
}
2013-03-27 16:06:45 +01:00
2016-09-15 11:39:27 +02:00
if ( m_lastException != null )
throw m_lastException ;
2013-03-27 16:06:45 +01:00
return ( IList < Library . Interface . IFileEntry >) req . Result ;
}
2013-04-09 20:43:27 +02:00
public void WaitForComplete ( LocalDatabase db , System . Data . IDbTransaction transation )
2013-03-27 16:06:45 +01:00
{
2018-12-31 13:45:41 -08:00
m_statwriter . BackendProgressUpdater . SetBlocking ( true );
m_db . FlushDbMessages ( db , transation );
if ( m_lastException != null )
throw m_lastException ;
2013-03-27 16:06:45 +01:00
2018-12-31 13:45:41 -08:00
var item = new FileEntryItem ( OperationType . Terminate , null );
if ( m_queue . Enqueue ( item ))
item . WaitForComplete ();
2013-03-27 16:06:45 +01:00
2018-12-31 13:45:41 -08:00
m_db . FlushDbMessages ( db , transation );
2013-04-09 20:43:27 +02:00
2018-12-31 13:45:41 -08:00
if ( m_lastException != null )
throw m_lastException ;
2013-03-27 16:06:45 +01:00
}
2015-11-17 12:37:33 +01:00
public void WaitForEmpty ( LocalDatabase db , System . Data . IDbTransaction transation )
{
2017-09-04 12:15:47 +02:00
try
{
m_statwriter . BackendProgressUpdater . SetBlocking ( true );
m_db . FlushDbMessages ( db , transation );
if ( m_lastException != null )
throw m_lastException ;
2015-11-17 12:37:33 +01:00
2017-09-04 12:15:47 +02:00
var item = new FileEntryItem ( OperationType . Nothing , null );
if ( m_queue . Enqueue ( item ))
item . WaitForComplete ();
2015-11-17 12:37:33 +01:00
2017-09-04 12:15:47 +02:00
m_db . FlushDbMessages ( db , transation );
2015-11-17 12:37:33 +01:00
2017-09-04 12:15:47 +02:00
if ( m_lastException != null )
throw m_lastException ;
}
finally
{
m_statwriter . BackendProgressUpdater . SetBlocking ( false );
}
2015-11-17 12:37:33 +01:00
}
2013-08-25 07:49:31 +02:00
public void Delete ( string remotename , long size , bool synchronous = false )
2016-09-15 11:39:27 +02:00
{
if ( m_lastException != null )
throw m_lastException ;
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
m_db . LogDbUpdate ( remotename , RemoteVolumeState . Deleting , size , null );
var req = new FileEntryItem ( OperationType . Delete , remotename , size , null );
2017-09-04 12:15:47 +02:00
try
{
m_statwriter . BackendProgressUpdater . SetBlocking ( true );
if ( m_queue . Enqueue ( req ) && synchronous )
{
req . WaitForComplete ();
if ( req . Exception != null )
throw req . Exception ;
}
}
finally
2016-09-15 11:39:27 +02:00
{
2017-09-04 12:15:47 +02:00
m_statwriter . BackendProgressUpdater . SetBlocking ( false );
2016-09-15 11:39:27 +02:00
}
if ( m_lastException != null )
throw m_lastException ;
}
2017-12-25 04:12:19 +07:00
2013-04-09 20:43:27 +02:00
public bool FlushDbMessages ()
{
2016-09-15 11:39:27 +02:00
return m_db . FlushDbMessages ( false );
2013-04-09 20:43:27 +02:00
}
2013-03-27 16:06:45 +01:00
public void Dispose ()
{
if ( m_queue != null && ! m_queue . Completed )
m_queue . SetCompleted ();
2017-12-25 04:12:19 +07:00
2013-03-27 16:06:45 +01:00
if ( m_thread != null )
{
if (! m_thread . Join ( TimeSpan . FromSeconds ( 10 )))
{
2021-04-03 20:54:47 -07:00
m_thread . Interrupt ();
2013-03-27 16:06:45 +01:00
m_thread . Join ( TimeSpan . FromSeconds ( 10 ));
}
m_thread = null ;
}
2017-12-25 04:12:19 +07:00
2013-08-31 15:29:26 +02:00
//TODO: We cannot null this, because it will be recreated
//Should we wait for queue completion or abort immediately?
if ( m_backend != null )
{
m_backend . Dispose ();
m_backend = null ;
}
2017-12-25 04:12:19 +07:00
2016-09-15 11:39:27 +02:00
try { m_db . FlushDbMessages ( true ); }
2018-03-12 14:07:11 +01:00
catch ( Exception ex ) { Logging . Log . WriteErrorMessage ( LOGTAG , "ShutdownError" , ex , "Backend Shutdown error: {0}" , ex . Message ); }
2013-03-27 16:06:45 +01:00
}
}
}