2016-02-09 09:17:31 +01:00
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System ;
using System.Threading.Tasks ;
using System.Collections.Generic ;
using Duplicati.Library.Interface ;
using Duplicati.Library.Main.Volumes ;
using Duplicati.Library.Utility ;
using CoCoL ;
using Duplicati.Library.Localization.Short ;
using Newtonsoft.Json ;
using System.Linq ;
using System.Text ;
2016-02-27 02:15:42 +01:00
using System.IO ;
2016-02-09 09:17:31 +01:00
namespace Duplicati.Library.Main.Operation.Common
{
2016-02-25 19:15:51 +01:00
/// <summary>
/// This class encapsulates access to the backend and ensures at most one connection is active at a time
/// </summary>
2016-02-18 16:46:53 +01:00
internal class BackendHandler : SingleRunner
2016-02-09 09:17:31 +01:00
{
2018-04-11 23:02:47 +02:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType < BackendHandler >();
2016-02-09 09:17:31 +01:00
public const string VOLUME_HASH = "SHA256" ;
2016-02-18 16:46:53 +01:00
/// <summary>
/// Data storage for an ongoing backend operation
/// </summary>
protected class FileEntryItem
2016-02-09 09:17:31 +01:00
{
/// <summary>
/// The current operation this entry represents
/// </summary>
public BackendActionType Operation ;
/// <summary>
/// The name of the remote file
/// </summary>
public string RemoteFilename ;
/// <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>
public bool Encrypted ;
/// <summary>
/// The expected hash value of the file
/// </summary>
public string Hash ;
/// <summary>
/// The expected size of the file
/// </summary>
public long Size ;
/// <summary>
/// A flag indicating if the file is a extra metadata file
/// that has no entry in the database
/// </summary>
public bool TrackedInDb = true ;
/// <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 ;
/// <summary>
/// A flag indicating if the operation is a retry run
/// </summary>
public bool IsRetry ;
2016-02-22 21:27:12 +01:00
public FileEntryItem ( BackendActionType operation , string remotefilename )
2016-02-09 09:17:31 +01:00
{
Operation = operation ;
RemoteFilename = remotefilename ;
Size = - 1 ;
}
2016-02-22 21:27:12 +01:00
public FileEntryItem ( BackendActionType operation , string remotefilename , long size , string hash )
: this ( operation , remotefilename )
2016-02-09 09:17:31 +01:00
{
Size = size ;
Hash = hash ;
}
public void SetLocalfilename ( string name )
{
this . LocalTempfile = Library . Utility . TempFile . WrapExistingFile ( name );
this . LocalTempfile . Protected = true ;
}
2018-04-11 23:02:47 +02:00
public async Task Encrypt ( Options options )
2016-02-09 09:17:31 +01:00
{
2016-02-25 19:15:51 +01:00
if (! this . Encrypted && ! options . NoEncryption )
2016-02-09 09:17:31 +01:00
{
var tempfile = new Library . Utility . TempFile ();
2016-02-25 19:15:51 +01:00
using ( var enc = DynamicLoader . EncryptionLoader . GetModule ( options . EncryptionModule , options . Passphrase , options . RawOptions ))
enc . Encrypt ( this . LocalFilename , tempfile );
2018-04-11 23:02:47 +02:00
await this . DeleteLocalFile ();
2016-02-25 19:15:51 +01:00
2016-02-09 09:17:31 +01:00
this . LocalTempfile = tempfile ;
this . Hash = null ;
this . Size = 0 ;
this . Encrypted = true ;
}
}
2016-02-18 16:46:53 +01:00
public static string CalculateFileHash ( string filename )
2016-02-09 09:17:31 +01:00
{
using ( System . IO . FileStream fs = System . IO . File . OpenRead ( filename ))
2017-09-20 21:03:24 +02:00
using ( var hasher = Duplicati . Library . Utility . HashAlgorithmHelper . Create ( VOLUME_HASH ))
2016-02-09 09:17:31 +01:00
return Convert . ToBase64String ( hasher . ComputeHash ( fs ));
}
public bool UpdateHashAndSize ( Options options )
{
if ( Hash == null || Size < 0 )
{
Hash = CalculateFileHash ( this . LocalFilename );
Size = new System . IO . FileInfo ( this . LocalFilename ). Length ;
return true ;
}
return false ;
}
2018-04-11 23:02:47 +02:00
public async Task DeleteLocalFile ()
2016-02-09 09:17:31 +01:00
{
if ( this . LocalTempfile != null )
try { this . LocalTempfile . Dispose (); }
2018-04-11 23:02:47 +02:00
catch ( Exception ex ) { Logging . Log . WriteWarningMessage ( LOGTAG , "DeleteTemporaryFileError" , ex , "Failed to dispose temporary file: {0}" , this . LocalTempfile ); }
2016-02-09 09:17:31 +01:00
finally { this . LocalTempfile = null ; }
}
}
private DatabaseCommon m_database ;
private IBackend m_backend ;
private Options m_options ;
private string m_backendurl ;
private bool m_uploadSuccess ;
2016-02-18 16:46:53 +01:00
private StatsCollector m_stats ;
2016-02-27 02:15:42 +01:00
private ITaskReader m_taskreader ;
2016-02-09 09:17:31 +01:00
2016-02-27 02:15:42 +01:00
public BackendHandler ( Options options , string backendUrl , DatabaseCommon database , StatsCollector stats , ITaskReader taskreader )
2016-02-09 09:17:31 +01:00
: base ()
{
m_backendurl = backendUrl ;
m_database = database ;
m_options = options ;
m_backendurl = backendUrl ;
2016-02-18 16:46:53 +01:00
m_stats = stats ;
2016-02-27 02:15:42 +01:00
m_taskreader = taskreader ;
2017-09-19 14:24:59 +02:00
m_backend = DynamicLoader . BackendLoader . GetBackend ( backendUrl , options . RawOptions );
var shortname = m_backendurl ;
// Try not to leak hostnames or other information in the error messages
try { shortname = new Library . Utility . Uri ( shortname ). Scheme ; }
catch { }
if ( m_backend == null )
2018-04-11 23:02:47 +02:00
throw new Duplicati . Library . Interface . UserInformationException ( string . Format ( "Backend not supported: {0}" , shortname ), "BackendNotSupported" );
2017-09-19 14:24:59 +02:00
}
2016-02-09 09:17:31 +01:00
2016-02-18 16:46:53 +01:00
protected Task < T > RunRetryOnMain < T >( FileEntryItem fe , Func < Task < T >> method )
2016-02-09 09:17:31 +01:00
{
2016-02-18 16:46:53 +01:00
return RunOnMain < T >(() =>
DoWithRetry < T >( fe , method )
2016-02-09 09:17:31 +01:00
);
}
public Task PutUnencryptedAsync ( string remotename , string localpath )
{
2016-02-22 21:27:12 +01:00
var fe = new FileEntryItem ( BackendActionType . Put , remotename );
2016-02-09 09:17:31 +01:00
fe . SetLocalfilename ( localpath );
fe . Encrypted = true ; //Prevent encryption
fe . TrackedInDb = false ; //Prevent Db updates
2016-02-18 16:46:53 +01:00
return RunRetryOnMain < bool >( fe , async () =>
2016-02-09 09:17:31 +01:00
{
await DoPut ( fe );
m_uploadSuccess = true ;
return true ;
});
}
2016-02-22 21:27:12 +01:00
public async Task UploadFileAsync ( VolumeWriterBase item , Func < string , Task < IndexVolumeWriter >> createIndexFile = null )
2016-02-09 09:17:31 +01:00
{
2016-02-22 21:27:12 +01:00
var fe = new FileEntryItem ( BackendActionType . Put , item . RemoteFilename );
fe . SetLocalfilename ( item . LocalFilename );
2016-02-09 09:17:31 +01:00
2016-02-22 21:27:12 +01:00
var tcs = new TaskCompletionSource < bool >();
2016-02-09 09:17:31 +01:00
2016-02-24 23:48:42 +01:00
var backgroundhashAndEncrypt = Task . Run ( async () =>
{
2018-04-11 23:02:47 +02:00
await fe . Encrypt ( m_options ). ConfigureAwait ( false );
2016-02-24 23:48:42 +01:00
return fe . UpdateHashAndSize ( m_options );
});
2016-02-22 21:27:12 +01:00
await RunOnMain ( async () =>
2016-02-09 09:17:31 +01:00
{
2016-02-22 21:27:12 +01:00
try
{
await DoWithRetry ( fe , async () => {
if ( fe . IsRetry )
await RenameFileAfterErrorAsync ( fe );
2016-02-09 09:17:31 +01:00
2016-02-24 23:48:42 +01:00
// Make sure the encryption and hashing has completed
await backgroundhashAndEncrypt ;
2016-02-22 21:27:12 +01:00
return await DoPut ( fe );
});
if ( createIndexFile != null )
{
var ix = await createIndexFile ( fe . RemoteFilename );
var indexFile = new FileEntryItem ( BackendActionType . Put , ix . RemoteFilename );
indexFile . SetLocalfilename ( ix . LocalFilename );
await m_database . UpdateRemoteVolumeAsync ( indexFile . RemoteFilename , RemoteVolumeState . Uploading , - 1 , null );
await DoWithRetry ( indexFile , async () => {
if ( indexFile . IsRetry )
await RenameFileAfterErrorAsync ( indexFile );
return await DoPut ( indexFile );
});
}
tcs . TrySetResult ( true );
}
catch ( Exception ex )
{
if ( ex is System . Threading . ThreadAbortException )
tcs . TrySetCanceled ();
else
tcs . TrySetException ( ex );
}
2016-02-09 09:17:31 +01:00
});
2016-02-22 21:27:12 +01:00
await tcs . Task ;
2016-02-09 09:17:31 +01:00
}
2017-09-19 14:24:59 +02:00
public Task DeleteFileAsync ( string remotename , bool suppressCleanup = false )
2016-02-09 09:17:31 +01:00
{
2016-02-22 21:27:12 +01:00
var fe = new FileEntryItem ( BackendActionType . Delete , remotename );
2016-02-09 09:17:31 +01:00
return RunRetryOnMain ( fe , () =>
2017-09-19 14:24:59 +02:00
DoDelete ( fe , suppressCleanup )
2016-02-09 09:17:31 +01:00
);
}
public Task CreateFolder ( string remotename )
{
2016-02-22 21:27:12 +01:00
var fe = new FileEntryItem ( BackendActionType . CreateFolder , remotename );
2016-02-09 09:17:31 +01:00
return RunRetryOnMain ( fe , () =>
DoCreateFolder ( fe )
);
}
public Task < IList < Library . Interface . IFileEntry >> ListFilesAsync ()
{
2016-02-22 21:27:12 +01:00
var fe = new FileEntryItem ( BackendActionType . List , null );
2016-02-09 09:17:31 +01:00
return RunRetryOnMain ( fe , () =>
DoList ( fe )
);
}
public Task < Library . Utility . TempFile > GetFileAsync ( string remotename , long size , string remotehash )
{
var fe = new FileEntryItem ( BackendActionType . Get , remotename , size , remotehash );
2016-02-25 19:15:51 +01:00
return RunRetryOnMain ( fe , () => DoGet ( fe ) );
2016-02-09 09:17:31 +01:00
}
2016-02-18 16:46:53 +01:00
public Task < Tuple < Library . Utility . TempFile , long , string >> GetFileWithInfoAsync ( string remotename )
2016-02-09 09:17:31 +01:00
{
2016-02-22 21:27:12 +01:00
var fe = new FileEntryItem ( BackendActionType . Get , remotename );
2016-02-09 09:17:31 +01:00
return RunRetryOnMain ( fe , async () => {
2016-02-18 16:46:53 +01:00
var res = await DoGet ( fe );
return new Tuple < Library . Utility . TempFile , long , string >(
res ,
fe . Size ,
fe . Hash
);
2016-02-09 09:17:31 +01:00
});
}
2016-02-25 19:15:51 +01:00
public Task < Library . Utility . TempFile > GetFileForTestingAsync ( string remotename , long size , string remotehash )
2016-02-09 09:17:31 +01:00
{
2016-02-25 19:15:51 +01:00
var fe = new FileEntryItem ( BackendActionType . Get , remotename );
fe . VerifyHashOnly = true ;
return RunRetryOnMain ( fe , () => DoGet ( fe ));
}
2016-02-09 09:17:31 +01:00
2016-02-18 16:46:53 +01:00
private async Task ResetBackendAsync ( Exception ex )
2016-02-09 09:17:31 +01:00
{
try
{
if ( m_backend != null )
m_backend . Dispose ();
}
2018-04-11 23:02:47 +02:00
catch ( Exception dex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "BackendDisposeError" , dex , "Failed to dispose backend instance: {0}" , ex . Message );
2016-02-09 09:17:31 +01:00
}
m_backend = null ;
}
2016-02-18 16:46:53 +01:00
private async Task < T > DoWithRetry < T >( FileEntryItem item , Func < Task < T >> method )
2016-02-09 09:17:31 +01:00
{
2016-02-22 21:27:12 +01:00
item . IsRetry = false ;
2016-02-09 09:17:31 +01:00
Exception lastException = null ;
2016-02-27 02:15:42 +01:00
if (! await m_taskreader . TransferProgressAsync )
throw new OperationCanceledException ();
2016-04-06 21:16:34 +02:00
if ( m_workerSource . IsCancellationRequested )
throw new OperationCanceledException ();
2016-02-27 02:15:42 +01:00
2016-02-09 09:17:31 +01:00
for ( var i = 0 ; i < m_options . NumberOfRetries ; i ++)
{
if ( m_options . RetryDelay . Ticks != 0 && i != 0 )
2016-02-22 21:27:12 +01:00
await Task . Delay ( m_options . RetryDelay );
2016-02-25 23:33:55 +01:00
2016-02-27 02:15:42 +01:00
if (! await m_taskreader . TransferProgressAsync )
throw new OperationCanceledException ();
2016-04-06 21:16:34 +02:00
if ( m_workerSource . IsCancellationRequested )
throw new OperationCanceledException ();
2016-02-09 09:17:31 +01:00
try
{
2016-02-25 23:33:55 +01:00
if ( m_backend == null )
m_backend = DynamicLoader . BackendLoader . GetBackend ( m_backendurl , m_options . RawOptions );
if ( m_backend == null )
throw new Exception ( "Backend failed to re-load" );
2016-02-22 21:27:12 +01:00
var r = await method ();
return r ;
2016-02-09 09:17:31 +01:00
}
catch ( Exception ex )
{
2016-02-22 21:27:12 +01:00
item . IsRetry = true ;
2016-02-09 09:17:31 +01:00
lastException = ex ;
2018-04-11 23:02:47 +02: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 , i + 1 , m_options . NumberOfRetries , ex . Message );
2016-02-09 09:17:31 +01:00
// If the thread is aborted, we exit here
2016-04-06 21:16:34 +02:00
if ( ex is System . Threading . ThreadAbortException || ex is OperationCanceledException )
2016-02-09 09:17:31 +01:00
break ;
2016-02-18 16:46:53 +01:00
await m_stats . SendEventAsync ( item . Operation , i < m_options . NumberOfRetries ? BackendEventType . Retrying : BackendEventType . Failed , item . RemoteFilename , item . Size );
2016-02-09 09:17:31 +01:00
bool recovered = false ;
if (! m_uploadSuccess && ex is Duplicati . Library . Interface . FolderMissingException && m_options . AutocreateFolders )
{
try
{
// If we successfully create the folder, we can re-use the connection
m_backend . CreateFolder ();
recovered = true ;
}
catch ( Exception dex )
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteWarningMessage ( LOGTAG , "FolderCreateError" , dex , "Failed to create folder: {0}" , ex . Message );
2016-02-09 09:17:31 +01:00
}
}
if (! recovered )
2016-02-18 16:46:53 +01:00
await ResetBackendAsync ( ex );
2016-02-09 09:17:31 +01:00
}
finally
{
if ( m_options . NoConnectionReuse )
2016-02-18 16:46:53 +01:00
await ResetBackendAsync ( null );
2016-02-09 09:17:31 +01:00
}
}
throw lastException ;
}
2016-02-18 16:46:53 +01:00
private async Task RenameFileAfterErrorAsync ( FileEntryItem item )
2016-02-09 09:17:31 +01:00
{
var p = VolumeBase . ParseFilename ( item . RemoteFilename );
var guid = VolumeWriterBase . GenerateGuid ( m_options );
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 ;
2016-02-18 16:46:53 +01:00
await m_stats . SendEventAsync ( item . Operation , BackendEventType . Rename , oldname , item . Size );
await m_stats . SendEventAsync ( item . Operation , BackendEventType . Rename , newname , item . Size );
2018-04-11 23:02:47 +02:00
Logging . Log . WriteInformationMessage ( LOGTAG , "RenameRemoteTargetFile" , "Renaming \"{0}\" to \"{1}\"" , oldname , newname );
2016-02-18 16:46:53 +01:00
await m_database . RenameRemoteFileAsync ( oldname , newname );
2016-02-09 09:17:31 +01:00
item . RemoteFilename = newname ;
}
2016-02-24 23:48:42 +01:00
private async Task < bool > DoPut ( FileEntryItem item , bool updatedHash = false )
2016-02-09 09:17:31 +01:00
{
2016-02-25 19:15:51 +01:00
// If this is not already encrypted, do it now
2018-04-11 23:02:47 +02:00
await item . Encrypt ( m_options );
2016-02-09 09:17:31 +01:00
2016-02-24 23:48:42 +01:00
updatedHash |= item . UpdateHashAndSize ( m_options );
if ( updatedHash && item . TrackedInDb )
2016-02-22 21:27:12 +01:00
await m_database . UpdateRemoteVolumeAsync ( item . RemoteFilename , RemoteVolumeState . Uploading , item . Size , item . Hash );
2016-02-09 09:17:31 +01:00
2016-02-27 02:15:42 +01:00
if ( m_options . Dryrun )
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldUploadVolume" , "Would upload volume: {0}, size: {1}" , item . RemoteFilename , Library . Utility . Utility . FormatSizeString ( new FileInfo ( item . LocalFilename ). Length ));
await item . DeleteLocalFile ();
2016-02-27 02:15:42 +01:00
return true ;
}
2016-02-18 16:46:53 +01:00
await m_database . LogRemoteOperationAsync ( "put" , item . RemoteFilename , JsonConvert . SerializeObject ( new { Size = item . Size , Hash = item . Hash }));
await m_stats . SendEventAsync ( BackendActionType . Put , BackendEventType . Started , item . RemoteFilename , item . Size );
2016-02-09 09:17:31 +01:00
var begin = DateTime . Now ;
if ( m_backend is Library . Interface . IStreamingBackend && ! m_options . DisableStreamingTransfers )
{
using ( var fs = System . IO . File . OpenRead ( item . LocalFilename ))
using ( var ts = new ThrottledStream ( fs , m_options . MaxUploadPrSecond , m_options . MaxDownloadPrSecond ))
2017-09-19 14:24:59 +02:00
using ( var pgs = new Library . Utility . ProgressReportingStream ( ts , item . Size , pg => HandleProgress ( ts , pg )))
2016-02-09 09:17:31 +01:00
(( Library . Interface . IStreamingBackend ) m_backend ). Put ( item . RemoteFilename , pgs );
}
else
m_backend . Put ( item . RemoteFilename , item . LocalFilename );
var duration = DateTime . Now - begin ;
2018-04-11 23:02:47 +02: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 )));
2016-02-09 09:17:31 +01:00
if ( item . TrackedInDb )
2016-02-22 21:27:12 +01:00
await m_database . UpdateRemoteVolumeAsync ( item . RemoteFilename , RemoteVolumeState . Uploaded , item . Size , item . Hash );
2016-02-09 09:17:31 +01:00
2016-02-18 16:46:53 +01:00
await m_stats . SendEventAsync ( BackendActionType . Put , BackendEventType . Completed , item . RemoteFilename , item . Size );
2016-02-09 09:17:31 +01:00
if ( m_options . ListVerifyUploads )
{
2017-09-19 14:24:59 +02:00
var f = m_backend . List (). Where ( n => n . Name . Equals ( item . RemoteFilename , StringComparison . OrdinalIgnoreCase )). FirstOrDefault ();
2016-02-09 09:17:31 +01:00
if ( f == null )
2017-09-19 14:24:59 +02:00
throw new Exception ( string . Format ( "List verify failed, file was not found after upload: {0}" , item . RemoteFilename ));
2016-02-09 09:17:31 +01: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-02-22 21:27:12 +01:00
2018-04-11 23:02:47 +02:00
await item . DeleteLocalFile ();
2016-02-22 21:27:12 +01:00
await m_database . CommitTransactionAsync ( "CommitAfterUpload" );
2016-02-09 09:17:31 +01:00
return true ;
}
private async Task < IList < Library . Interface . IFileEntry >> DoList ( FileEntryItem item )
{
2016-02-18 16:46:53 +01:00
await m_stats . SendEventAsync ( BackendActionType . List , BackendEventType . Started , null , - 1 );
2016-02-09 09:17:31 +01:00
2018-01-20 15:02:41 +01:00
var r = m_backend . List (). ToList ();
2016-02-09 09:17:31 +01:00
var sb = new StringBuilder ();
sb . AppendLine ( "[" );
long count = 0 ;
foreach ( var e in r )
{
if ( count != 0 )
sb . AppendLine ( "," );
count ++;
sb . Append ( JsonConvert . SerializeObject ( e ));
}
sb . AppendLine ();
sb . Append ( "]" );
2016-02-18 16:46:53 +01:00
await m_database . LogRemoteOperationAsync ( "list" , "" , sb . ToString ());
2016-02-09 09:17:31 +01:00
2016-02-18 16:46:53 +01:00
await m_stats . SendEventAsync ( BackendActionType . List , BackendEventType . Completed , null , r . Count );
2016-02-09 09:17:31 +01:00
return r ;
}
2017-09-19 14:24:59 +02:00
private async Task < bool > DoDelete ( FileEntryItem item , bool suppressCleanup )
2016-02-09 09:17:31 +01:00
{
2016-02-27 02:15:42 +01:00
if ( m_options . Dryrun )
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldDeleteRemoteFile" , "Would delete remote file: {0}, size: {1}" , item . RemoteFilename , Library . Utility . Utility . FormatSizeString ( item . Size ));
2016-02-27 02:15:42 +01:00
return true ;
}
2016-02-18 16:46:53 +01:00
await m_stats . SendEventAsync ( BackendActionType . Delete , BackendEventType . Started , item . RemoteFilename , item . Size );
2016-02-09 09:17:31 +01:00
string result = null ;
try
{
m_backend . Delete ( item . RemoteFilename );
}
catch ( Exception ex )
{
var isFileMissingException = ex is Library . Interface . FileMissingException || ex is System . IO . FileNotFoundException ;
var wr = ex as System . Net . WebException == null ? null : ( ex as System . Net . WebException ). Response as System . Net . HttpWebResponse ;
if ( isFileMissingException || ( wr != null && wr . StatusCode == System . Net . HttpStatusCode . NotFound ))
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteWarningMessage ( LOGTAG , "DeleteRemoteFileFailed" , ex , LC . L ( "Delete operation failed for {0} with FileNotFound, listing contents" , item . RemoteFilename ));
2016-02-09 09:17:31 +01:00
bool success = false ;
try
{
success = ! m_backend . List (). Select ( x => x . Name ). Contains ( item . RemoteFilename );
}
catch
{
}
if ( success )
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteInformationMessage ( LOGTAG , "DeleteRemoteFileSuccess" , LC . L ( "Listing indicates file {0} is deleted correctly" , item . RemoteFilename ));
2016-02-18 16:46:53 +01:00
return true ;
2016-02-09 09:17:31 +01:00
}
}
result = ex . ToString ();
throw ;
}
finally
{
2016-02-18 16:46:53 +01:00
await m_database . LogRemoteOperationAsync ( "delete" , item . RemoteFilename , result );
2016-02-09 09:17:31 +01:00
}
2017-09-19 14:24:59 +02:00
await m_database . UpdateRemoteVolumeAsync ( item . RemoteFilename , RemoteVolumeState . Deleted , - 1 , null , suppressCleanup , TimeSpan . FromHours ( 2 ));
2016-02-18 16:46:53 +01:00
await m_stats . SendEventAsync ( BackendActionType . Delete , BackendEventType . Completed , item . RemoteFilename , item . Size );
2016-02-09 09:17:31 +01:00
return true ;
}
private async Task < bool > DoCreateFolder ( FileEntryItem item )
{
2016-02-18 16:46:53 +01:00
await m_stats . SendEventAsync ( BackendActionType . CreateFolder , BackendEventType . Started , null , - 1 );
2016-02-09 09:17:31 +01:00
string result = null ;
try
{
m_backend . CreateFolder ();
}
catch ( Exception ex )
{
result = ex . ToString ();
throw ;
}
finally
{
2016-02-18 16:46:53 +01:00
await m_database . LogRemoteOperationAsync ( "createfolder" , item . RemoteFilename , result );
2016-02-09 09:17:31 +01:00
}
2016-02-18 16:46:53 +01:00
await m_stats . SendEventAsync ( BackendActionType . CreateFolder , BackendEventType . Completed , null , - 1 );
2016-02-09 09:17:31 +01:00
return true ;
}
private async Task < Library . Utility . TempFile > DoGet ( FileEntryItem item )
{
Library . Utility . TempFile tmpfile = null ;
2016-02-18 16:46:53 +01:00
await m_stats . SendEventAsync ( BackendActionType . Get , BackendEventType . Started , item . RemoteFilename , item . Size );
2016-02-09 09:17:31 +01:00
try
{
var begin = DateTime . Now ;
tmpfile = new Library . Utility . TempFile ();
if ( m_backend is Library . Interface . IStreamingBackend && ! m_options . DisableStreamingTransfers )
{
using ( var fs = System . IO . File . OpenWrite ( tmpfile ))
using ( var ts = new ThrottledStream ( fs , m_options . MaxUploadPrSecond , m_options . MaxDownloadPrSecond ))
2017-09-19 14:24:59 +02:00
using ( var pgs = new Library . Utility . ProgressReportingStream ( ts , item . Size , pg => HandleProgress ( ts , pg )))
2016-02-09 09:17:31 +01:00
(( Library . Interface . IStreamingBackend ) m_backend ). Get ( item . RemoteFilename , pgs );
}
else
m_backend . Get ( item . RemoteFilename , tmpfile );
var duration = DateTime . Now - begin ;
2016-02-18 22:33:12 +01:00
var filehash = FileEntryItem . CalculateFileHash ( tmpfile );
2018-04-11 23:02:47 +02:00
Logging . Log . WriteProfilingMessage ( LOGTAG , "DownloadSpeed" , "Downloaded {0} in {1}, {2}/s" , Library . Utility . Utility . FormatSizeString ( item . Size ), duration , Library . Utility . Utility . FormatSizeString (( long )( item . Size / duration . TotalSeconds )));
2016-02-09 09:17:31 +01:00
2016-02-18 22:33:12 +01:00
await m_database . LogRemoteOperationAsync ( "get" , item . RemoteFilename , JsonConvert . SerializeObject ( new { Size = new System . IO . FileInfo ( tmpfile ). Length , Hash = filehash }));
2016-02-18 16:46:53 +01:00
await m_stats . SendEventAsync ( BackendActionType . Get , BackendEventType . Completed , item . RemoteFilename , new System . IO . FileInfo ( tmpfile ). Length );
2016-02-09 09:17:31 +01:00
if (! m_options . SkipFileHashChecks )
{
var nl = new System . IO . FileInfo ( tmpfile ). Length ;
if ( item . Size >= 0 )
{
if ( nl != item . Size )
throw new Exception ( Strings . Controller . DownloadedFileSizeError ( item . RemoteFilename , nl , item . Size ));
}
else
item . Size = nl ;
if (! string . IsNullOrEmpty ( item . Hash ))
{
2016-02-18 22:33:12 +01:00
if ( filehash != item . Hash )
2016-03-14 09:38:19 +01:00
throw new Duplicati . Library . Main . BackendManager . HashMismatchException ( Strings . Controller . HashMismatchError ( tmpfile , item . Hash , filehash ));
2016-02-09 09:17:31 +01:00
}
else
2016-02-18 22:33:12 +01:00
item . Hash = filehash ;
2016-02-09 09:17:31 +01:00
}
2016-02-18 16:46:53 +01:00
// Fast exit
if ( item . VerifyHashOnly )
return null ;
// Decrypt before returning
if (! m_options . NoEncryption )
2016-02-09 09:17:31 +01:00
{
2016-02-18 16:46:53 +01:00
try
2016-02-09 09:17:31 +01:00
{
2016-02-18 16:46:53 +01:00
using ( var tmpfile2 = tmpfile )
{
tmpfile = new Library . Utility . TempFile ();
// Auto-guess the encryption module
var ext = ( System . IO . Path . GetExtension ( item . RemoteFilename ) ?? "" ). TrimStart ( '.' );
2017-09-19 14:24:59 +02:00
if (! string . Equals ( m_options . EncryptionModule , ext , StringComparison . OrdinalIgnoreCase ))
2016-02-18 16:46:53 +01:00
{
// Check if the file is encrypted with something else
2017-09-19 14:24:59 +02:00
if ( DynamicLoader . EncryptionLoader . Keys . Contains ( ext , StringComparer . OrdinalIgnoreCase ))
2016-02-18 16:46:53 +01:00
{
using ( var encmodule = DynamicLoader . EncryptionLoader . GetModule ( ext , m_options . Passphrase , m_options . RawOptions ))
2016-02-25 19:15:51 +01:00
if ( encmodule != null )
{
2018-04-11 23:02:47 +02:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "AutomaticDecryptionDetection" , "Filename extension \"{0}\" does not match encryption module \"{1}\", using matching encryption module" , ext , m_options . EncryptionModule );
encmodule . Decrypt ( tmpfile2 , tmpfile );
2016-02-25 19:15:51 +01:00
}
2016-02-18 16:46:53 +01:00
}
// Check if the file is not encrypted
2017-09-19 14:24:59 +02:00
else if ( DynamicLoader . CompressionLoader . Keys . Contains ( ext , StringComparer . OrdinalIgnoreCase ))
2016-02-09 09:17:31 +01:00
{
2018-04-11 23:02:47 +02: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-09 09:17:31 +01:00
}
2016-02-18 16:46:53 +01:00
// Fallback, lets see what happens...
2016-02-09 09:17:31 +01:00
else
{
2018-04-11 23:02:47 +02: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-25 19:15:51 +01:00
using ( var encmodule = DynamicLoader . EncryptionLoader . GetModule ( m_options . EncryptionModule , m_options . Passphrase , m_options . RawOptions ))
encmodule . Decrypt ( tmpfile2 , tmpfile );
2016-02-09 09:17:31 +01:00
}
}
else
2016-02-18 16:46:53 +01:00
{
2016-02-25 19:15:51 +01:00
using ( var encmodule = DynamicLoader . EncryptionLoader . GetModule ( m_options . EncryptionModule , m_options . Passphrase , m_options . RawOptions ))
encmodule . Decrypt ( tmpfile2 , tmpfile );
2016-02-18 16:46:53 +01:00
}
2016-02-09 09:17:31 +01:00
}
}
2016-02-18 16:46:53 +01:00
catch ( Exception ex )
{
//If we fail here, make sure that we throw a crypto exception
if ( ex is System . Security . Cryptography . CryptographicException )
throw ;
else
throw new System . Security . Cryptography . CryptographicException ( ex . Message , ex );
}
2016-02-09 09:17:31 +01:00
}
2016-02-18 16:46:53 +01:00
var res = tmpfile ;
tmpfile = null ;
return res ;
2016-02-09 09:17:31 +01:00
}
2016-02-18 16:46:53 +01:00
finally
2016-02-09 09:17:31 +01:00
{
2016-02-18 16:46:53 +01:00
try
{
if ( tmpfile != null )
tmpfile . Dispose ();
}
catch
{
}
2016-02-09 09:17:31 +01:00
}
2017-09-19 14:24:59 +02:00
}
private string m_lastThrottleUploadValue = null ;
private string m_lastThrottleDownloadValue = null ;
private void HandleProgress ( ThrottledStream ts , long pg )
{
2016-02-27 02:15:42 +01:00
if (! m_taskreader . TransferProgressAsync . WaitForTask (). Result )
2017-09-19 14:24:59 +02:00
throw new OperationCanceledException ();
// 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 ;
}
m_options . RawOptions . TryGetValue ( "throttle-download" , out tmp );
if ( tmp != m_lastThrottleDownloadValue )
{
ts . ReadSpeed = m_options . MaxDownloadPrSecond ;
m_lastThrottleDownloadValue = tmp ;
}
m_stats . UpdateBackendProgress ( pg );
2016-02-18 16:46:53 +01:00
}
2016-02-09 09:17:31 +01:00
protected override void Dispose ( bool disposing )
{
base . Dispose ( disposing );
if ( m_backend != null )
try { m_backend . Dispose (); }
catch {}
finally { m_backend = null ; }
}
}
}