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-22 21:27:12 +01:00
using CoCoL ;
2019-02-22 20:46:12 -06:00
using Duplicati.Library.Interface ;
2016-02-22 21:27:12 +01:00
using Duplicati.Library.Main.Operation.Common ;
using Duplicati.Library.Main.Volumes ;
2019-02-10 12:16:59 -06:00
using Duplicati.Library.Utility ;
2019-02-22 20:46:12 -06:00
using Newtonsoft.Json ;
using System ;
using System.Collections.Generic ;
2019-02-10 12:16:59 -06:00
using System.IO ;
2019-02-22 20:46:12 -06:00
using System.Linq ;
2019-03-03 21:15:38 -06:00
using System.Runtime.ExceptionServices ;
2019-02-22 21:58:40 -06:00
using System.Threading ;
2019-02-22 20:46:12 -06:00
using System.Threading.Tasks ;
using static Duplicati . Library . Main . Operation . Common . BackendHandler ;
2016-02-22 21:27:12 +01:00
namespace Duplicati.Library.Main.Operation.Backup
{
2016-02-24 23:48:42 +01:00
internal interface IUploadRequest
{
}
internal class FlushRequest : IUploadRequest
{
2019-03-19 20:15:55 -07:00
public Task < long > LastWriteSizeAsync { get { return m_tcs . Task ; } }
2018-05-23 21:18:01 -07:00
private readonly TaskCompletionSource < long > m_tcs = new TaskCompletionSource < long >();
2020-10-28 19:03:09 -05:00
2019-04-06 21:42:01 -07:00
public void SetFlushed ( long size )
2019-03-22 19:11:54 -07:00
{
2019-04-06 21:42:01 -07:00
m_tcs . TrySetResult ( size );
2019-03-22 19:11:54 -07:00
}
2020-10-28 19:03:09 -05:00
2019-04-06 21:42:01 -07:00
public void TrySetCanceled ()
2016-02-24 23:48:42 +01:00
{
2019-04-06 21:42:01 -07:00
m_tcs . TrySetCanceled ();
2016-02-24 23:48:42 +01:00
}
}
internal class IndexVolumeUploadRequest : IUploadRequest
{
public IndexVolumeWriter IndexVolume { get ; private set ; }
public IndexVolumeUploadRequest ( IndexVolumeWriter indexVolume )
{
IndexVolume = indexVolume ;
}
}
internal class FilesetUploadRequest : IUploadRequest
{
public FilesetVolumeWriter Fileset { get ; private set ; }
public FilesetUploadRequest ( FilesetVolumeWriter fileset )
{
Fileset = fileset ;
}
}
internal class VolumeUploadRequest : IUploadRequest
2016-02-22 21:27:12 +01:00
{
2019-02-22 19:37:35 -06:00
public FileEntryItem BlockEntry { get ; }
public BlockVolumeWriter BlockVolume { get ; }
2019-10-04 22:02:20 -07:00
public TemporaryIndexVolume IndexVolume { get ; }
public Options Options { get ; }
public BackupDatabase Database { get ; }
2016-02-22 21:27:12 +01:00
2019-10-05 11:25:22 -07:00
public VolumeUploadRequest ( BlockVolumeWriter blockVolume , FileEntryItem blockEntry , TemporaryIndexVolume indexVolume , Options options , BackupDatabase database )
2016-02-22 21:27:12 +01:00
{
2019-10-05 11:25:22 -07:00
BlockVolume = blockVolume ;
2019-02-22 19:37:35 -06:00
BlockEntry = blockEntry ;
IndexVolume = indexVolume ;
2019-10-04 22:02:20 -07:00
Options = options ;
Database = database ;
2016-02-22 21:27:12 +01:00
}
}
2019-02-22 19:37:35 -06:00
2016-02-25 19:15:51 +01:00
/// <summary>
/// This class encapsulates all requests to the backend
/// and ensures that the <code>AsynchronousUploadLimit</code> is honored
/// </summary>
2019-02-10 12:16:59 -06:00
internal class BackendUploader
2016-02-22 21:27:12 +01:00
{
2019-02-10 12:16:59 -06:00
private static readonly string LOGTAG = Logging . Log . LogTagFromType < BackendUploader >();
2019-03-07 18:16:39 -06:00
private readonly Func < IBackend > m_backendFactory ;
2019-02-22 21:58:40 -06:00
private CancellationTokenSource m_cancelTokenSource ;
2019-03-07 18:16:39 -06:00
private readonly DatabaseCommon m_database ;
2019-03-09 15:33:40 -06:00
private long m_initialUploadThrottleSpeed ;
private int m_lastThrottleCheckTime ;
private long m_lastUploadThrottleSpeed ;
2019-02-26 21:17:33 -06:00
private int m_maxConcurrentUploads ;
2019-03-09 15:33:40 -06:00
private readonly Options m_options ;
private readonly FileProgressThrottler m_progressUpdater ;
private readonly StatsCollector m_stats ;
private readonly ITaskReader m_taskReader ;
public BackendUploader ( Func < IBackend > backendFactory , Options options , DatabaseCommon database , ITaskReader taskReader , StatsCollector stats )
2019-02-10 12:16:59 -06:00
{
2019-02-22 21:07:24 -06:00
m_backendFactory = backendFactory ;
m_options = options ;
2019-03-09 15:33:40 -06:00
m_taskReader = taskReader ;
2019-02-22 21:07:24 -06:00
m_stats = stats ;
m_database = database ;
2019-02-26 21:17:33 -06:00
m_progressUpdater = new FileProgressThrottler ( stats , options . MaxUploadPrSecond );
2019-02-10 12:16:59 -06:00
}
public Task Run ()
2016-02-22 21:27:12 +01:00
{
return AutomationExtensions . RunTask ( new
2016-02-24 23:48:42 +01:00
{
Input = Channels . BackendRequest . ForRead ,
},
2016-02-22 21:27:12 +01:00
2016-02-24 23:48:42 +01:00
async self =>
{
2019-02-22 21:07:24 -06:00
var workers = new List < Worker >();
2019-02-26 21:17:33 -06:00
m_maxConcurrentUploads = m_options . AsynchronousConcurrentUploadLimit <= 0 ? int . MaxValue : m_options . AsynchronousConcurrentUploadLimit ;
2019-03-09 15:33:40 -06:00
m_initialUploadThrottleSpeed = m_options . AsynchronousConcurrentUploadLimit <= 0 ? int . MaxValue : m_options . MaxUploadPrSecond / m_maxConcurrentUploads ;
2016-02-24 23:48:42 +01:00
var lastSize = - 1L ;
2019-02-22 21:07:24 -06:00
var uploadsInProgress = 0 ;
2019-02-22 21:58:40 -06:00
m_cancelTokenSource = new CancellationTokenSource ();
2019-02-26 21:17:33 -06:00
m_progressUpdater . Run ( m_cancelTokenSource . Token );
2020-10-28 19:03:09 -05:00
var filesetsToUpload = new List < FilesetUploadRequest >();
2019-02-22 20:46:12 -06:00
2019-03-03 21:15:38 -06:00
try
2016-02-22 21:27:12 +01:00
{
2019-03-09 15:33:40 -06:00
while (! await self . Input . IsRetiredAsync && await m_taskReader . ProgressAsync )
2016-02-22 21:27:12 +01:00
{
2016-02-24 23:48:42 +01:00
var req = await self . Input . ReadAsync ();
2016-02-27 02:15:42 +01:00
2019-03-09 15:33:40 -06:00
if (! await m_taskReader . ProgressAsync )
2019-03-03 21:15:38 -06:00
break ;
2019-02-22 20:46:12 -06:00
2020-10-28 19:03:09 -05:00
Worker worker = GetWorker ( workers );
2019-02-22 21:07:24 -06:00
2019-02-22 20:46:12 -06:00
if ( req is VolumeUploadRequest volumeUpload )
2016-02-24 23:48:42 +01:00
{
2019-02-22 20:46:12 -06:00
if ( volumeUpload . IndexVolume == null )
2019-02-22 21:58:40 -06:00
worker . Task = Task . Run (() => UploadFileAsync ( volumeUpload . BlockEntry , worker , m_cancelTokenSource . Token ));
2016-02-24 23:48:42 +01:00
else
2019-02-22 21:58:40 -06:00
worker . Task = Task . Run (() => UploadBlockAndIndexAsync ( volumeUpload , worker , m_cancelTokenSource . Token ));
2019-02-22 21:07:24 -06:00
lastSize = volumeUpload . BlockVolume . SourceSize ;
uploadsInProgress ++;
2016-02-24 23:48:42 +01:00
}
2019-02-22 20:46:12 -06:00
else if ( req is FilesetUploadRequest filesetUpload )
2019-02-22 21:07:24 -06:00
{
2020-10-28 19:03:09 -05:00
filesetsToUpload . Add ( filesetUpload );
2019-02-22 21:07:24 -06:00
}
2019-02-22 20:46:12 -06:00
else if ( req is IndexVolumeUploadRequest indexUpload )
2019-02-22 21:07:24 -06:00
{
2019-02-22 21:58:40 -06:00
worker . Task = Task . Run (() => UploadVolumeWriter ( indexUpload . IndexVolume , worker , m_cancelTokenSource . Token ));
2019-02-22 21:07:24 -06:00
uploadsInProgress ++;
}
2019-02-22 20:46:12 -06:00
else if ( req is FlushRequest flush )
2016-02-22 21:27:12 +01:00
{
2020-10-28 19:03:09 -05:00
await WaitForWorkers ( workers , flush ). ConfigureAwait ( false );
UploadFilesets ( workers , filesetsToUpload );
await WaitForWorkers ( workers , flush ). ConfigureAwait ( false );
2019-03-25 18:45:26 -07:00
flush . SetFlushed ( lastSize );
2019-03-22 19:08:49 -07:00
uploadsInProgress = 0 ;
2019-02-10 12:16:59 -06:00
break ;
2016-02-22 21:27:12 +01:00
}
2019-03-03 21:15:38 -06:00
if ( uploadsInProgress >= m_maxConcurrentUploads )
2019-02-26 21:17:33 -06:00
{
2019-03-03 21:15:38 -06:00
await Task . WhenAny ( workers . Select ( w => w . Task )). ConfigureAwait ( false );
uploadsInProgress --;
var failedUploads = workers . Where ( w => w . Task . IsFaulted ). Select ( w => GetInnerMostException ( w . Task . Exception )). ToList ();
if ( failedUploads . Any ())
{
if ( failedUploads . Count == 1 )
ExceptionDispatchInfo . Capture ( failedUploads . First ()). Throw ();
else
throw new AggregateException ( failedUploads );
}
2019-02-26 21:17:33 -06:00
}
2016-02-24 23:48:42 +01:00
}
2019-03-03 21:15:38 -06:00
}
2019-03-07 19:44:46 -06:00
catch ( Exception ex ) when (! ex . IsRetiredException ())
2019-03-03 21:15:38 -06:00
{
2019-03-07 19:44:46 -06:00
m_cancelTokenSource . Cancel ();
try
2016-02-24 23:48:42 +01:00
{
2019-03-07 19:44:46 -06:00
await Task . WhenAll ( workers . Select ( w => w . Task ));
2016-02-24 23:48:42 +01:00
}
2019-07-06 13:34:14 -07:00
catch
{
2019-07-06 15:34:46 -07:00
// As we are cancelling all threads we do not need to alert the user to any of these exceptions.
2019-07-06 13:34:14 -07:00
}
finally
{
workers . ForEach ( w => w . Dispose ());
}
2019-03-07 19:44:46 -06:00
throw ;
2016-02-24 23:48:42 +01:00
}
2016-02-22 21:27:12 +01:00
2017-09-19 14:18:54 +02:00
try
{
2019-02-10 12:16:59 -06:00
m_stats . SetBlocking ( true );
2019-02-22 21:07:24 -06:00
await Task . WhenAll ( workers . Select ( w => w . Task ));
2017-09-19 14:18:54 +02:00
}
finally
{
2019-02-10 12:16:59 -06:00
m_stats . SetBlocking ( false );
2019-07-06 13:34:14 -07:00
workers . ForEach ( w => w . Dispose ());
2017-09-19 14:18:54 +02:00
}
2016-02-24 23:48:42 +01:00
});
2016-02-22 21:27:12 +01:00
}
2019-02-10 12:16:59 -06:00
2020-10-28 19:03:09 -05:00
private Worker GetWorker ( List < Worker > workers )
{
var worker = workers . FirstOrDefault ( w => w . Task . IsCompleted && ! w . Task . IsFaulted );
if ( worker == null )
{
worker = new Worker ( m_backendFactory ());
workers . Add ( worker );
}
return worker ;
}
private async Task WaitForWorkers ( List < Worker > workers , FlushRequest flush )
{
while ( workers . Any ())
{
Task finishedTask = await Task . WhenAny ( workers . Select ( w => w . Task )). ConfigureAwait ( false );
if ( finishedTask . IsFaulted )
{
flush . TrySetCanceled ();
ExceptionDispatchInfo . Capture ( finishedTask . Exception ). Throw ();
}
Worker finishedWorker = workers . Single ( w => w . Task == finishedTask );
workers . Remove ( finishedWorker );
finishedWorker . Dispose ();
}
}
private void UploadFilesets ( List < Worker > workers , List < FilesetUploadRequest > filesets )
{
foreach ( var fileset in filesets )
{
Worker worker = GetWorker ( workers );
worker . Task = Task . Run (() => UploadVolumeWriter ( fileset . Fileset , worker , m_cancelTokenSource . Token ));
}
filesets . Clear ();
}
2019-03-07 18:16:39 -06:00
private static Exception GetInnerMostException ( Exception ex )
2019-03-03 21:15:38 -06:00
{
while ( ex . InnerException != null )
ex = ex . InnerException ;
return ex ;
}
2019-02-22 21:58:40 -06:00
private async Task UploadBlockAndIndexAsync ( VolumeUploadRequest upload , Worker worker , CancellationToken cancelToken )
2019-02-10 12:16:59 -06:00
{
2019-10-04 22:02:20 -07:00
if ( await UploadFileAsync ( upload . BlockEntry , worker , cancelToken ). ConfigureAwait ( false ))
{
// We must use the BlockEntry's RemoteFilename and not the BlockVolume's since the
// BlockEntry's' RemoteFilename reflects renamed files due to retries after errors.
IndexVolumeWriter indexVolumeWriter = await upload . IndexVolume . CreateVolume ( upload . BlockEntry . RemoteFilename , upload . BlockEntry . Hash , upload . BlockEntry . Size , upload . Options , upload . Database );
FileEntryItem indexEntry = indexVolumeWriter . CreateFileEntryForUpload ( upload . Options );
if ( await UploadFileAsync ( indexEntry , worker , cancelToken ). ConfigureAwait ( false ))
{
await m_database . AddIndexBlockLinkAsync ( indexVolumeWriter . VolumeID , upload . BlockVolume . VolumeID ). ConfigureAwait ( false );
}
}
2019-02-22 19:37:35 -06:00
}
2019-02-10 12:16:59 -06:00
2019-02-22 21:58:40 -06:00
private async Task UploadVolumeWriter ( VolumeWriterBase volumeWriter , Worker worker , CancellationToken cancelToken )
2019-02-22 19:37:35 -06:00
{
var fileEntry = new FileEntryItem ( BackendActionType . Put , volumeWriter . RemoteFilename );
fileEntry . SetLocalfilename ( volumeWriter . LocalFilename );
fileEntry . Encrypt ( m_options );
fileEntry . UpdateHashAndSize ( m_options );
2019-02-10 12:16:59 -06:00
2019-02-22 21:58:40 -06:00
await UploadFileAsync ( fileEntry , worker , cancelToken ). ConfigureAwait ( false );
2019-02-22 19:37:35 -06:00
}
2019-02-10 12:16:59 -06:00
2019-03-03 21:15:38 -06:00
private async Task < bool > UploadFileAsync ( FileEntryItem item , Worker worker , CancellationToken cancelToken )
2019-02-22 19:37:35 -06:00
{
2019-03-03 21:15:38 -06:00
if ( cancelToken . IsCancellationRequested )
return false ;
return await DoWithRetry ( async () =>
2019-02-10 12:16:59 -06:00
{
2019-02-22 19:37:35 -06:00
if ( item . IsRetry )
await RenameFileAfterErrorAsync ( item ). ConfigureAwait ( false );
2019-02-22 21:58:40 -06:00
await DoPut ( item , worker . Backend , cancelToken ). ConfigureAwait ( false );
2019-03-03 21:15:38 -06:00
},
item , worker , cancelToken ). ConfigureAwait ( false );
2019-02-10 12:16:59 -06:00
}
2019-03-03 21:15:38 -06:00
private async Task < bool > DoWithRetry ( Func < Task > method , FileEntryItem item , Worker worker , CancellationToken cancelToken )
2019-02-10 12:16:59 -06:00
{
item . IsRetry = false ;
2019-03-03 21:15:38 -06:00
var retryCount = 0 ;
2019-02-10 12:16:59 -06:00
2019-03-03 21:15:38 -06:00
for ( retryCount = 0 ; retryCount <= m_options . NumberOfRetries ; retryCount ++)
2019-02-10 12:16:59 -06:00
{
2019-03-03 21:15:38 -06:00
if ( m_options . RetryDelay . Ticks != 0 && retryCount != 0 )
2022-01-15 18:11:59 +01:00
{
var delay = Library . Utility . Utility . GetRetryDelay ( m_options . RetryDelay , retryCount , m_options . RetryWithExponentialBackoff );
await Task . Delay ( delay ). ConfigureAwait ( false );
}
2019-02-10 12:16:59 -06:00
2019-03-03 21:15:38 -06:00
if ( cancelToken . IsCancellationRequested )
return false ;
2019-02-10 12:16:59 -06:00
try
{
2019-02-22 21:07:24 -06:00
if ( worker . Backend == null )
worker . Backend = m_backendFactory ();
2019-02-22 20:46:12 -06:00
await method (). ConfigureAwait ( false );
2019-03-03 21:15:38 -06:00
return true ;
2019-02-10 12:16:59 -06:00
}
catch ( Exception ex )
{
item . IsRetry = true ;
2020-03-01 11:55:21 -08: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 , retryCount + 1 , m_options . NumberOfRetries + 1 , ex . Message );
2019-03-03 21:15:38 -06:00
if ( ex is ThreadAbortException || ex is OperationCanceledException )
2019-02-10 12:16:59 -06:00
break ;
2019-03-03 21:15:38 -06:00
await m_stats . SendEventAsync ( item . Operation , retryCount < m_options . NumberOfRetries ? BackendEventType . Retrying : BackendEventType . Failed , item . RemoteFilename , item . Size );
2019-02-10 12:16:59 -06:00
bool recovered = false ;
2019-02-22 20:46:12 -06:00
if ( m_options . AutocreateFolders && ex is FolderMissingException )
2019-02-10 12:16:59 -06:00
{
try
{
// If we successfully create the folder, we can re-use the connection
2019-02-22 21:07:24 -06:00
worker . Backend . CreateFolder ();
2019-02-10 12:16:59 -06:00
recovered = true ;
}
catch ( Exception dex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "FolderCreateError" , dex , "Failed to create folder: {0}" , ex . Message );
}
}
if (! recovered )
2019-02-22 21:07:24 -06:00
ResetBackend ( ex , worker );
2019-03-03 21:15:38 -06:00
if ( retryCount == m_options . NumberOfRetries )
throw ;
2019-02-10 12:16:59 -06:00
}
finally
{
if ( m_options . NoConnectionReuse )
2019-02-22 21:07:24 -06:00
ResetBackend ( null , worker );
2019-02-10 12:16:59 -06:00
}
}
2019-03-03 21:15:38 -06:00
return false ;
2019-02-10 12:16:59 -06:00
}
2019-02-22 21:07:24 -06:00
private void ResetBackend ( Exception ex , Worker worker )
2019-02-10 12:16:59 -06:00
{
try
{
2019-02-22 21:07:24 -06:00
worker . Backend ?. Dispose ();
2019-02-10 12:16:59 -06:00
}
catch ( Exception dex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "BackendDisposeError" , dex , "Failed to dispose backend instance: {0}" , ex ?. Message );
}
2019-02-22 21:07:24 -06:00
finally
{
worker . Backend = null ;
}
2019-02-10 12:16:59 -06:00
}
private async Task RenameFileAfterErrorAsync ( FileEntryItem item )
{
var p = VolumeBase . ParseFilename ( item . RemoteFilename );
var guid = VolumeWriterBase . GenerateGuid ();
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 ;
await m_stats . SendEventAsync ( item . Operation , BackendEventType . Rename , oldname , item . Size );
await m_stats . SendEventAsync ( item . Operation , BackendEventType . Rename , newname , item . Size );
Logging . Log . WriteInformationMessage ( LOGTAG , "RenameRemoteTargetFile" , "Renaming \"{0}\" to \"{1}\"" , oldname , newname );
await m_database . RenameRemoteFileAsync ( oldname , newname );
item . RemoteFilename = newname ;
}
2019-02-22 21:58:40 -06:00
private async Task DoPut ( FileEntryItem item , IBackend backend , CancellationToken cancelToken )
2019-02-10 12:16:59 -06:00
{
2019-03-03 21:15:38 -06:00
if ( cancelToken . IsCancellationRequested )
return ;
2019-02-22 19:37:35 -06:00
if ( item . TrackedInDb )
2019-02-10 12:16:59 -06:00
await m_database . UpdateRemoteVolumeAsync ( item . RemoteFilename , RemoteVolumeState . Uploading , item . Size , item . Hash );
if ( m_options . Dryrun )
{
Logging . Log . WriteDryrunMessage ( LOGTAG , "WouldUploadVolume" , "Would upload volume: {0}, size: {1}" , item . RemoteFilename , Library . Utility . Utility . FormatSizeString ( new FileInfo ( item . LocalFilename ). Length ));
item . DeleteLocalFile ();
2019-02-22 20:46:12 -06:00
return ;
2019-02-10 12:16:59 -06:00
}
await m_database . LogRemoteOperationAsync ( "put" , item . RemoteFilename , JsonConvert . SerializeObject ( new { Size = item . Size , Hash = item . Hash }));
2019-02-26 21:17:33 -06:00
await m_stats . SendEventAsync ( BackendActionType . Put , BackendEventType . Started , item . RemoteFilename , item . Size , updateProgress : false );
m_progressUpdater . StartFileProgress ( item . RemoteFilename , item . Size );
2019-02-10 12:16:59 -06:00
var begin = DateTime . Now ;
2019-02-22 21:07:24 -06:00
if (! m_options . DisableStreamingTransfers && backend is IStreamingBackend streamingBackend )
2019-02-10 12:16:59 -06:00
{
2019-03-09 15:33:40 -06:00
// A download throttle speed is not given to the ThrottledStream as we are only uploading data here
2019-02-22 20:46:12 -06:00
using ( var fs = File . OpenRead ( item . LocalFilename ))
2019-03-09 15:33:40 -06:00
using ( var ts = new ThrottledStream ( fs , m_initialUploadThrottleSpeed , 0 ))
2019-02-26 21:17:33 -06:00
using ( var pgs = new ProgressReportingStream ( ts , pg => HandleProgress ( ts , pg , item . RemoteFilename )))
2019-03-17 18:20:14 -05:00
await streamingBackend . PutAsync ( item . RemoteFilename , pgs , cancelToken ). ConfigureAwait ( false );
2019-02-10 12:16:59 -06:00
}
else
2019-03-17 18:20:14 -05:00
await backend . PutAsync ( item . RemoteFilename , item . LocalFilename , cancelToken ). ConfigureAwait ( false );
2019-02-10 12:16:59 -06:00
var duration = DateTime . Now - begin ;
2019-02-26 21:17:33 -06:00
m_progressUpdater . EndFileProgress ( item . RemoteFilename );
2019-02-10 12:16:59 -06: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 )));
if ( item . TrackedInDb )
await m_database . UpdateRemoteVolumeAsync ( item . RemoteFilename , RemoteVolumeState . Uploaded , item . Size , item . Hash );
await m_stats . SendEventAsync ( BackendActionType . Put , BackendEventType . Completed , item . RemoteFilename , item . Size );
if ( m_options . ListVerifyUploads )
{
2019-02-22 21:07:24 -06:00
var f = backend . List (). FirstOrDefault ( n => n . Name . Equals ( item . RemoteFilename , StringComparison . OrdinalIgnoreCase ));
2019-02-10 12:16:59 -06:00
if ( f == null )
throw new Exception ( string . Format ( "List verify failed, file was not found after upload: {0}" , item . RemoteFilename ));
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 ));
}
item . DeleteLocalFile ();
await m_database . CommitTransactionAsync ( "CommitAfterUpload" );
}
2019-03-09 15:33:40 -06:00
private void HandleProgress ( ThrottledStream stream , long progress , string path )
2019-02-10 12:16:59 -06:00
{
2019-03-09 15:33:40 -06:00
UpdateThrottleSpeed ();
m_progressUpdater . UpdateFileProgress ( path , progress , stream );
}
private void UpdateThrottleSpeed ()
{
var updateThrottleSpeed = false ;
lock ( m_progressUpdater )
2019-02-10 12:16:59 -06:00
{
2019-03-09 15:33:40 -06:00
var currentTick = Environment . TickCount ;
if ( currentTick - m_lastThrottleCheckTime > 2000 )
{
m_lastThrottleCheckTime = currentTick ;
updateThrottleSpeed = true ;
}
2019-02-10 12:16:59 -06:00
}
2019-03-09 15:33:40 -06:00
if (! updateThrottleSpeed )
return ;
var maxUploadPerSecond = m_options . MaxUploadPrSecond ;
if ( maxUploadPerSecond != m_lastUploadThrottleSpeed )
{
m_lastUploadThrottleSpeed = maxUploadPerSecond ;
m_initialUploadThrottleSpeed = maxUploadPerSecond / m_maxConcurrentUploads ;
2019-03-07 19:44:46 -06:00
m_progressUpdater . UpdateThrottleSpeeds ( maxUploadPerSecond );
2019-03-09 15:33:40 -06:00
}
2019-02-10 12:16:59 -06:00
}
2019-02-22 21:07:24 -06:00
2019-07-06 12:50:37 -07:00
private class Worker : IDisposable
2019-02-22 21:07:24 -06:00
{
public Task Task ;
public IBackend Backend ;
public Worker ( IBackend backend )
{
Backend = backend ;
Task = Task . FromResult ( true );
}
2019-07-06 12:50:37 -07:00
public void Dispose ()
{
this . Task ?. Dispose ();
this . Backend ?. Dispose ();
}
2019-02-22 21:07:24 -06:00
}
2016-02-22 21:27:12 +01:00
}
2020-10-28 19:03:09 -05:00
}