2024-04-02 22:31:55 +02: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.
2024-02-28 15:45:30 +01:00
2013-02-12 21:43:14 +00:00
using System ;
using System.Collections.Generic ;
2019-07-23 10:35:33 -04:00
using System.Linq ;
using System.Threading ;
2013-02-12 21:43:14 +00:00
using Duplicati.Library.Utility ;
2019-07-23 10:35:33 -04:00
using Duplicati.Library.Common.IO ;
using Duplicati.Library.Common ;
2019-09-29 20:16:28 -07:00
using Duplicati.Library.Interface ;
2020-07-21 09:19:01 -07:00
using Duplicati.Library.Main.Database ;
2019-07-23 10:35:33 -04:00
2013-02-12 21:43:14 +00:00
namespace Duplicati.Library.Main
{
2013-05-08 21:29:59 +02:00
public class Controller : IDisposable
2013-02-12 21:43:14 +00:00
{
2018-03-12 14:07:11 +01:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType < Controller >();
2013-02-12 21:43:14 +00:00
/// <summary>
/// The backend url
/// </summary>
private string m_backend ;
/// <summary>
/// The parsed type-safe version of the commandline options
/// </summary>
2018-05-23 21:18:01 -07:00
private readonly Options m_options ;
2013-08-18 23:16:58 +02:00
/// <summary>
/// The destination for all output messages during execution
/// </summary>
private IMessageSink m_messageSink ;
2013-02-12 21:43:14 +00:00
2014-05-15 12:47:16 +02:00
/// <summary>
/// The current executing task
/// </summary>
private ITaskControl m_currentTask = null ;
2016-04-09 11:45:15 +02:00
2014-05-15 12:47:16 +02:00
/// <summary>
/// The thread running the current task
/// </summary>
private System . Threading . Thread m_currentTaskThread = null ;
2013-02-12 21:43:14 +00:00
2015-09-04 15:04:04 +02:00
/// <summary>
/// The thread priority to reset to
/// </summary>
private System . Threading . ThreadPriority ? m_resetPriority ;
2015-09-11 11:21:57 +02:00
/// <summary>
/// The localization culture to reset to
/// </summary>
private System . Globalization . CultureInfo m_resetLocale ;
/// <summary>
/// The localization UI culture to reset to
/// </summary>
private System . Globalization . CultureInfo m_resetLocaleUI ;
/// <summary>
/// True if the locale should be reset
/// </summary>
private bool m_doResetLocale ;
2016-10-04 11:53:51 +02:00
/// <summary>
2018-03-12 14:07:11 +01:00
/// The multi-controller log target
2016-10-04 11:53:51 +02:00
/// </summary>
2018-03-12 14:07:11 +01:00
private ControllerMultiLogTarget m_logTarget ;
2013-02-12 21:43:14 +00:00
2019-07-23 10:35:33 -04:00
/// <summary>
/// The cancellation token for the running task
/// </summary>
2019-07-23 11:14:06 -04:00
private readonly CancellationTokenSource m_cancellationTokenSource = new CancellationTokenSource ();
2019-07-23 10:35:33 -04:00
2013-02-12 21:43:14 +00:00
/// <summary>
/// Constructs a new interface for performing backup and restore operations
/// </summary>
/// <param name="backend">The url for the backend to use</param>
/// <param name="options">All required options</param>
2013-08-18 23:16:58 +02:00
public Controller ( string backend , Dictionary < string , string > options , IMessageSink messageSink )
2013-02-12 21:43:14 +00:00
{
m_backend = backend ;
m_options = new Options ( options );
2013-08-18 23:16:58 +02:00
m_messageSink = messageSink ;
2013-02-12 21:43:14 +00:00
}
2017-04-06 10:10:22 +02:00
/// <summary>
/// Appends another message sink to the controller
/// </summary>
/// <param name="sink">The sink to use.</param>
public void AppendSink ( IMessageSink sink )
{
2019-09-29 20:16:28 -07:00
if ( this . m_messageSink is MultiMessageSink messageSink )
messageSink . Append ( sink );
2017-04-06 10:10:22 +02:00
else
m_messageSink = new MultiMessageSink ( m_messageSink , sink );
}
2017-10-30 15:30:43 +01:00
public Duplicati . Library . Interface . IBackupResults Backup ( string [] inputsources , IFilter filter = null )
{
Library . UsageReporter . Reporter . Report ( "USE_BACKEND" , new Library . Utility . Uri ( m_backend ). Scheme );
Library . UsageReporter . Reporter . Report ( "USE_COMPRESSION" , m_options . CompressionModule );
Library . UsageReporter . Reporter . Report ( "USE_ENCRYPTION" , m_options . EncryptionModule );
2019-07-21 07:25:32 -07:00
2019-07-29 17:56:43 -07:00
CheckAutoCompactInterval ();
CheckAutoVacuumInterval ();
2019-07-21 07:25:32 -07:00
2017-10-30 15:30:43 +01:00
return RunAction ( new BackupResults (), ref inputsources , ref filter , ( result ) => {
2013-05-25 16:40:15 +02:00
2016-10-09 11:47:36 +02:00
using ( var h = new Operation . BackupHandler ( m_backend , m_options , result ))
2019-07-23 10:35:33 -04:00
{
2019-07-23 11:14:06 -04:00
h . Run ( ExpandInputSources ( inputsources , filter ), filter , m_cancellationTokenSource . Token );
2019-07-23 10:35:33 -04:00
}
2015-12-22 02:22:56 +01:00
Library . UsageReporter . Reporter . Report ( "BACKUP_FILECOUNT" , result . ExaminedFiles );
Library . UsageReporter . Reporter . Report ( "BACKUP_FILESIZE" , result . SizeOfExaminedFiles );
Library . UsageReporter . Reporter . Report ( "BACKUP_DURATION" , ( long ) result . Duration . TotalSeconds );
2013-05-25 16:40:15 +02:00
});
2013-02-12 21:43:14 +00:00
}
2013-05-25 16:40:15 +02:00
public Library . Interface . IRestoreResults Restore ( string [] paths , Library . Utility . IFilter filter = null )
2016-09-15 11:39:27 +02:00
{
2015-12-27 00:16:45 +01:00
return RunAction ( new RestoreResults (), ref paths , ref filter , ( result ) => {
2016-09-15 11:39:27 +02:00
new Operation . RestoreHandler ( m_backend , m_options , result ). Run ( paths , filter );
2015-12-27 22:34:05 +01:00
2018-12-12 12:14:11 -02:00
Library . UsageReporter . Reporter . Report ( "RESTORE_FILECOUNT" , result . RestoredFiles );
2015-12-27 22:34:05 +01:00
Library . UsageReporter . Reporter . Report ( "RESTORE_FILESIZE" , result . SizeOfRestoredFiles );
Library . UsageReporter . Reporter . Report ( "RESTORE_DURATION" , ( long ) result . Duration . TotalSeconds );
2013-05-25 16:40:15 +02:00
});
2013-02-12 21:43:14 +00:00
}
2013-05-30 23:00:09 +02:00
public Duplicati . Library . Interface . IRestoreControlFilesResults RestoreControlFiles ( IEnumerable < string > files = null , Library . Utility . IFilter filter = null )
2013-02-12 21:43:14 +00:00
{
2015-12-27 00:16:45 +01:00
return RunAction ( new RestoreControlFilesResults (), ref filter , ( result ) => {
2013-05-30 23:00:09 +02:00
new Operation . RestoreControlFilesHandler ( m_backend , m_options , result ). Run ( files , filter );
2013-05-25 16:40:15 +02:00
});
2013-02-12 21:43:14 +00:00
}
2013-05-25 16:40:15 +02:00
public Duplicati . Library . Interface . IDeleteResults Delete ()
2016-09-15 11:39:27 +02:00
{
2013-05-25 16:40:15 +02:00
return RunAction ( new DeleteResults (), ( result ) => {
2016-09-15 11:39:27 +02:00
new Operation . DeleteHandler ( m_backend , m_options , result ). Run ();
2013-05-25 16:40:15 +02:00
});
2013-02-12 21:43:14 +00:00
}
2015-04-08 20:19:46 +02:00
public Duplicati . Library . Interface . IRepairResults Repair ( Library . Utility . IFilter filter = null )
2013-02-12 21:43:14 +00:00
{
2015-12-27 00:16:45 +01:00
return RunAction ( new RepairResults (), ref filter , ( result ) => {
2015-04-08 20:19:46 +02:00
new Operation . RepairHandler ( m_backend , m_options , result ). Run ( filter );
2013-05-25 16:40:15 +02:00
});
2013-02-12 21:43:14 +00:00
}
2016-04-09 11:45:15 +02:00
2019-01-22 17:44:55 -08:00
public Duplicati . Library . Interface . IListResults List ()
2013-05-30 21:54:43 +02:00
{
2019-01-22 17:44:55 -08:00
return List ( null , null );
2013-05-30 21:54:43 +02:00
}
2013-05-05 17:54:59 +02:00
2018-10-06 13:30:13 -07:00
public Duplicati . Library . Interface . IListResults List ( string filterstring )
2013-05-11 13:04:01 +02:00
{
2013-05-30 21:54:43 +02:00
return List ( filterstring == null ? null : new string [] { filterstring }, null );
2013-05-11 13:04:01 +02:00
}
2016-04-09 11:45:15 +02:00
2019-01-22 17:49:08 -08:00
public Duplicati . Library . Interface . IListResults List ( IEnumerable < string > filterstrings , Library . Utility . IFilter filter )
2016-09-15 11:39:27 +02:00
{
2015-12-27 00:16:45 +01:00
return RunAction ( new ListResults (), ref filter , ( result ) => {
2016-09-15 11:39:27 +02:00
new Operation . ListFilesHandler ( m_backend , m_options , result ). Run ( filterstrings , filter );
2013-05-30 21:54:43 +02:00
});
}
2016-04-09 11:45:15 +02:00
2019-01-25 20:08:36 -08:00
public Duplicati . Library . Interface . IListResults ListControlFiles ( IEnumerable < string > filterstrings , Library . Utility . IFilter filter )
2013-05-30 21:54:43 +02:00
{
2015-12-27 00:16:45 +01:00
return RunAction ( new ListResults (), ref filter , ( result ) => {
2013-05-30 21:54:43 +02:00
new Operation . ListControlFilesHandler ( m_backend , m_options , result ). Run ( filterstrings , filter );
2013-05-25 16:40:15 +02:00
});
}
2016-04-09 11:45:15 +02:00
2016-10-13 21:45:31 +02:00
public Duplicati . Library . Interface . IListRemoteResults ListRemote ()
{
return RunAction ( new ListRemoteResults (), ( result ) =>
{
using ( var tf = System . IO . File . Exists ( m_options . Dbpath ) ? null : new Library . Utility . TempFile ())
using ( var db = new Database . LocalDatabase ((( string ) tf ) ?? m_options . Dbpath , "list-remote" , true ))
using ( var bk = new BackendManager ( m_backend , m_options , result . BackendWriter , null ))
result . SetResult ( bk . List ());
});
}
public Duplicati . Library . Interface . IListRemoteResults DeleteAllRemoteFiles ()
{
return RunAction ( new ListRemoteResults (), ( result ) =>
{
result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Delete_Listing );
using ( var bk = new BackendManager ( m_backend , m_options , result . BackendWriter , null ))
{
2017-10-10 16:44:06 +02:00
// Only delete files that match the expected pattern and prefix
var list = bk . List ()
. Select ( x => Volumes . VolumeBase . ParseFilename ( x ))
. Where ( x => x != null )
. Where ( x => x . Prefix == m_options . Prefix )
. ToList ();
2020-07-21 09:19:01 -07:00
// If the local database is available, we will use it to avoid deleting unrelated files
// from the backend. Otherwise, we may accidentally delete non-Duplicati files, or
// files from a different Duplicati configuration that points to the same backend location
// and uses the same prefix (see issues #2678, #3845, and #4244).
if ( System . IO . File . Exists ( m_options . Dbpath ))
{
using ( LocalDatabase db = new LocalDatabase ( m_options . Dbpath , "list-remote" , true ))
{
IEnumerable < RemoteVolumeEntry > dbRemoteVolumes = db . GetRemoteVolumes ();
HashSet < string > dbRemoteFiles = new HashSet < string >( dbRemoteVolumes . Select ( x => x . Name ));
list = list . Where ( x => dbRemoteFiles . Contains ( x . File . Name )). ToList ();
}
}
2016-10-13 21:45:31 +02:00
result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Delete_Deleting );
result . OperationProgressUpdater . UpdateProgress ( 0 );
for ( var i = 0 ; i < list . Count ; i ++)
{
try
{
2017-10-10 16:44:06 +02:00
bk . Delete ( list [ i ]. File . Name , list [ i ]. File . Size , true );
2016-10-13 21:45:31 +02:00
}
catch ( Exception ex )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "DeleteFilesetError" , ex , "Failed to delete remote file: {0}" , list [ i ]. File . Name );
2016-10-13 21:45:31 +02:00
}
result . OperationProgressUpdater . UpdateProgress (( float ) i / list . Count );
}
result . OperationProgressUpdater . UpdateProgress ( 1 );
}
});
}
2013-05-25 16:40:15 +02:00
public Duplicati . Library . Interface . ICompactResults Compact ()
2013-02-12 21:43:14 +00:00
{
2019-07-29 17:56:43 -07:00
CheckAutoVacuumInterval ();
2013-05-25 16:40:15 +02:00
return RunAction ( new CompactResults (), ( result ) => {
new Operation . CompactHandler ( m_backend , m_options , result ). Run ();
});
}
2016-04-09 11:45:15 +02:00
2015-04-08 21:01:36 +02:00
public Duplicati . Library . Interface . IRecreateDatabaseResults UpdateDatabaseWithVersions ( Library . Utility . IFilter filter = null )
{
var filelistfilter = Operation . RestoreHandler . FilterNumberedFilelist ( m_options . Time , m_options . Version , singleTimeMatch : true );
2015-12-27 00:16:45 +01:00
return RunAction ( new RecreateDatabaseResults (), ref filter , ( result ) => {
2015-04-08 21:01:36 +02:00
using ( var h = new Operation . RecreateDatabaseHandler ( m_backend , m_options , result ))
h . RunUpdate ( filter , filelistfilter );
2013-05-25 16:40:15 +02:00
});
2013-02-12 21:43:14 +00:00
}
2013-05-25 16:40:15 +02:00
public Duplicati . Library . Interface . ICreateLogDatabaseResults CreateLogDatabase ( string targetpath )
2013-02-12 21:43:14 +00:00
{
2013-05-25 16:40:15 +02:00
var t = new string [] { targetpath };
2016-04-09 11:45:15 +02:00
2013-05-25 16:40:15 +02:00
return RunAction ( new CreateLogDatabaseResults (), ref t , ( result ) => {
new Operation . CreateBugReportHandler ( t [ 0 ], m_options , result ). Run ();
});
2013-02-12 21:43:14 +00:00
}
2013-06-20 20:17:10 +02:00
2017-04-04 23:53:36 +02:00
public Duplicati . Library . Interface . IListChangesResults ListChanges ( string baseVersion , string targetVersion , IEnumerable < string > filterstrings = null , Library . Utility . IFilter filter = null , Action < Duplicati . Library . Interface . IListChangesResults , IEnumerable < Tuple < Library . Interface . ListChangesChangeType , Library . Interface . ListChangesElementType , string >>> callback = null )
2013-06-20 20:17:10 +02:00
{
var t = new string [] { baseVersion , targetVersion };
2016-04-09 11:45:15 +02:00
2015-12-27 00:16:45 +01:00
return RunAction ( new ListChangesResults (), ref t , ref filter , ( result ) => {
2017-04-04 23:53:36 +02:00
new Operation . ListChangesHandler ( m_backend , m_options , result ). Run ( t [ 0 ], t [ 1 ], filterstrings , filter , callback );
2013-06-20 20:17:10 +02:00
});
}
2013-06-26 21:59:01 +02:00
2017-04-04 23:53:36 +02:00
public Duplicati . Library . Interface . IListAffectedResults ListAffected ( List < string > args , Action < Duplicati . Library . Interface . IListAffectedResults > callback = null )
2014-08-19 20:27:14 +02:00
{
return RunAction ( new ListAffectedResults (), ( result ) => {
2017-04-04 23:53:36 +02:00
new Operation . ListAffected ( m_options , result ). Run ( args , callback );
2014-08-19 20:27:14 +02:00
});
}
2013-06-26 21:59:01 +02:00
public Duplicati . Library . Interface . ITestResults Test ( long samples = 1 )
2016-04-09 11:45:15 +02:00
{
2017-03-03 20:52:04 +01:00
if (! m_options . RawOptions . ContainsKey ( "full-remote-verification" ))
m_options . RawOptions [ "full-remote-verification" ] = "true" ;
2013-06-26 21:59:01 +02:00
return RunAction ( new TestResults (), ( result ) => {
new Operation . TestHandler ( m_backend , m_options , result ). Run ( samples );
});
}
2016-04-09 11:45:15 +02:00
2013-12-07 15:22:51 +01:00
public Library . Interface . ITestFilterResults TestFilter ( string [] paths , Library . Utility . IFilter filter = null )
{
m_options . RawOptions [ "dry-run" ] = "true" ;
m_options . RawOptions [ "dbpath" ] = "INVALID!" ;
2016-04-09 11:45:15 +02:00
2018-03-12 14:07:11 +01:00
// Redirect all messages from the filter to the message sink
2018-04-11 23:02:47 +02:00
var filtertag = Logging . Log . LogTagFromType ( typeof ( Operation . Backup . FileEnumerationProcess ));
2018-04-09 14:28:30 +02:00
using ( Logging . Log . StartScope ( m_messageSink . WriteMessage , x => x . FilterTag . Contains ( filtertag )))
2018-03-12 14:07:11 +01:00
{
return RunAction ( new TestFilterResults (), ref paths , ref filter , ( result ) =>
{
2019-07-23 11:14:06 -04:00
new Operation . TestFilterHandler ( m_options , result ). Run ( ExpandInputSources ( paths , filter ), filter , m_cancellationTokenSource . Token );
2018-03-12 14:07:11 +01:00
});
}
2013-12-07 15:22:51 +01:00
}
2015-09-11 10:57:31 +02:00
public Library . Interface . ISystemInfoResults SystemInfo ()
{
return RunAction ( new SystemInfoResults (), result => {
Operation . SystemInfoHandler . Run ( result );
});
}
2016-04-09 11:45:15 +02:00
2016-12-29 22:41:13 +01:00
public Library . Interface . IPurgeFilesResults PurgeFiles ( Library . Utility . IFilter filter )
{
return RunAction ( new PurgeFilesResults (), result =>
{
new Operation . PurgeFilesHandler ( m_backend , m_options , result ). Run ( filter );
});
}
2017-01-05 16:46:01 +01:00
public Library . Interface . IListBrokenFilesResults ListBrokenFiles ( Library . Utility . IFilter filter , Func < long , DateTime , long , string , long , bool > callbackhandler = null )
{
return RunAction ( new ListBrokenFilesResults (), result =>
{
new Operation . ListBrokenFilesHandler ( m_backend , m_options , result ). Run ( filter , callbackhandler );
});
}
public Library . Interface . IPurgeBrokenFilesResults PurgeBrokenFiles ( Library . Utility . IFilter filter )
{
return RunAction ( new PurgeBrokenFilesResults (), result =>
{
new Operation . PurgeBrokenFilesHandler ( m_backend , m_options , result ). Run ( filter );
});
}
2017-01-09 23:21:00 +01:00
public Library . Interface . ISendMailResults SendMail ()
{
m_options . RawOptions [ "send-mail-level" ] = "all" ;
m_options . RawOptions [ "send-mail-any-operation" ] = "true" ;
string targetmail ;
m_options . RawOptions . TryGetValue ( "send-mail-to" , out targetmail );
if ( string . IsNullOrWhiteSpace ( targetmail ))
throw new Exception ( string . Format ( "No email specified, please use --{0}" , "send-mail-to" ));
m_options . RawOptions [ "disable-module" ] = string . Join (
"," ,
DynamicLoader . GenericLoader . Modules
. Where ( m =>
2018-03-12 14:07:11 +01:00
!( m is Modules . Builtin . SendMail )
2017-01-09 23:21:00 +01:00
)
. Select ( x => x . Key )
);
2018-03-12 14:07:11 +01:00
/// Forward all messages from the email module to the message sink
var filtertag = Logging . Log . LogTagFromType < Modules . Builtin . SendMail >();
2018-04-09 14:28:30 +02:00
using ( Logging . Log . StartScope ( m_messageSink . WriteMessage , x => x . FilterTag . Contains ( filtertag )))
2017-01-09 23:21:00 +01:00
{
2018-03-12 14:07:11 +01:00
return RunAction ( new SendMailResults (), result =>
{
result . Lines = new string [ 0 ];
System . Threading . Thread . Sleep ( 5 );
});
}
2017-01-09 23:21:00 +01:00
}
2017-08-04 11:24:18 +01:00
public Library . Interface . IVacuumResults Vacuum ()
{
2019-07-29 17:56:43 -07:00
return RunAction ( new VacuumResults (), result => {
2017-08-04 11:24:18 +01:00
new Operation . VacuumHandler ( m_options , result ). Run ();
});
}
2013-05-25 16:40:15 +02:00
private T RunAction < T >( T result , Action < T > method )
2018-03-12 14:07:11 +01:00
where T : ISetCommonOptions , ITaskControl , Logging . ILogDestination
2013-05-25 16:40:15 +02:00
{
var tmp = new string [ 0 ];
2015-12-27 00:16:45 +01:00
IFilter tempfilter = null ;
return RunAction < T >( result , ref tmp , ref tempfilter , method );
2013-05-25 16:40:15 +02:00
}
2015-12-27 00:16:45 +01:00
2013-05-25 16:40:15 +02:00
private T RunAction < T >( T result , ref string [] paths , Action < T > method )
2018-03-12 14:07:11 +01:00
where T : ISetCommonOptions , ITaskControl , Logging . ILogDestination
2015-12-27 00:16:45 +01:00
{
IFilter tempfilter = null ;
return RunAction < T >( result , ref paths , ref tempfilter , method );
}
private T RunAction < T >( T result , ref IFilter filter , Action < T > method )
2018-03-12 14:07:11 +01:00
where T : ISetCommonOptions , ITaskControl , Logging . ILogDestination
2015-12-27 00:16:45 +01:00
{
var tmp = new string [ 0 ];
return RunAction < T >( result , ref tmp , ref filter , method );
}
private T RunAction < T >( T result , ref string [] paths , ref IFilter filter , Action < T > method )
2018-03-12 14:07:11 +01:00
where T : ISetCommonOptions , ITaskControl , Logging . ILogDestination
2014-05-15 12:47:16 +02:00
{
2018-03-12 14:07:11 +01:00
m_logTarget = new ControllerMultiLogTarget ( result , Logging . LogMessageType . Information , null );
using ( Logging . Log . StartScope ( m_logTarget , null ))
2014-05-15 12:47:16 +02:00
{
2018-03-12 14:07:11 +01:00
m_logTarget . AddTarget ( m_messageSink , m_options . ConsoleLoglevel , m_options . ConsoleLogFilter );
result . MessageSink = m_messageSink ;
2017-01-11 23:35:42 +01:00
try
2013-05-25 16:40:15 +02:00
{
2017-01-11 23:35:42 +01:00
m_currentTask = result ;
m_currentTaskThread = System . Threading . Thread . CurrentThread ;
2016-04-09 11:45:15 +02:00
2018-03-16 10:33:26 +01:00
SetupCommonOptions ( result , ref paths , ref filter );
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "StartingOperation" , Strings . Controller . StartingOperationMessage ( m_options . MainAction ));
2016-04-09 11:45:15 +02:00
2018-03-16 10:33:26 +01:00
using ( new ProcessController ( m_options ))
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , string . Format ( "Run{0}" , result . MainOperation ), string . Format ( "Running {0}" , result . MainOperation )))
2018-04-11 23:02:47 +02:00
using ( new CoCoL . IsolatedChannelScope ())
using ( m_options . ConcurrencyMaxThreads <= 0 ? null : new CoCoL . CappedThreadedThreadPool ( m_options . ConcurrencyMaxThreads ))
2017-01-11 23:35:42 +01:00
method ( result );
2016-04-09 11:45:15 +02:00
2016-12-01 23:59:54 +01:00
if ( result . EndTime . Ticks == 0 )
result . EndTime = DateTime . UtcNow ;
2013-05-25 16:40:15 +02:00
result . SetDatabase ( null );
2023-09-21 19:58:27 +02:00
if ( result is BasicResults r )
{
r . Interrupted = false ;
}
2016-09-28 09:55:04 +02:00
2024-04-02 22:31:55 +02:00
OnOperationComplete ( result , null );
2016-09-28 09:55:04 +02:00
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "CompletedOperation" , Strings . Controller . CompletedOperationMessage ( m_options . MainAction ));
2016-09-28 09:55:04 +02:00
2014-05-15 12:47:16 +02:00
return result ;
2013-05-25 16:40:15 +02:00
}
2017-01-11 23:35:42 +01:00
catch ( Exception ex )
{
result . EndTime = DateTime . UtcNow ;
2016-04-09 11:45:15 +02:00
2018-08-14 11:09:16 +02:00
if ( ex is Library . Interface . OperationAbortException oae )
{
2019-11-30 11:35:43 -08:00
// Log this as a normal operation, as the script raising the exception,
2018-08-14 11:09:16 +02:00
// has already populated either warning or log messages as required
Logging . Log . WriteInformationMessage ( LOGTAG , "AbortOperation" , "Aborting operation by request, requested result: {0}" , oae . AbortReason );
2016-04-09 11:45:15 +02:00
2023-09-21 19:58:27 +02:00
if ( result is BasicResults basicResults )
{
basicResults . Interrupted = true ;
try
{
// No operation was started in database, so write logs to new operation
using ( var db = new LocalDatabase ( m_options . Dbpath , result . MainOperation . ToString (), true ))
{
basicResults . SetDatabase ( db );
db . WriteResults ();
}
2024-04-02 22:31:55 +02:00
// Do not propagate the cancel exception
OnOperationComplete ( result , null );
2023-09-21 19:58:27 +02:00
}
catch { }
}
else
{
// Perform the module shutdown
2024-04-02 22:31:55 +02:00
OnOperationComplete ( ex , ex );
2023-09-21 19:58:27 +02:00
}
2018-08-14 11:09:16 +02:00
return result ;
}
else
{
2024-04-02 22:31:55 +02:00
Logging . Log . WriteErrorMessage ( LOGTAG , "FailedOperation" , ex , Strings . Controller . FailedOperationMessage ( m_options . MainAction , ex . Message ));
if ( result is BasicResults basicResults )
2023-09-21 19:58:27 +02:00
{
2024-04-02 22:31:55 +02:00
try
2023-09-21 19:58:27 +02:00
{
basicResults . OperationProgressUpdater . UpdatePhase ( OperationPhase . Error );
basicResults . Fatal = true ;
2024-02-09 17:10:05 +01:00
// Write logs to previous operation if database exists
if ( LocalDatabase . Exists ( m_options . Dbpath ))
2023-09-21 19:58:27 +02:00
{
2024-02-09 17:10:05 +01:00
using ( var db = new LocalDatabase ( m_options . Dbpath , null , true ))
{
basicResults . SetDatabase ( db );
db . WriteResults ();
}
2023-09-21 19:58:27 +02:00
}
2016-09-28 09:55:04 +02:00
2024-04-02 22:31:55 +02:00
// Report the result, and the failure
OnOperationComplete ( result , ex );
2018-08-14 11:09:16 +02:00
2024-04-02 22:31:55 +02:00
}
catch { }
}
else
{
// Perform the module shutdown
OnOperationComplete ( ex , ex );
}
2018-08-14 11:09:16 +02:00
throw ;
}
2017-01-11 23:35:42 +01:00
}
finally
{
m_currentTask = null ;
2017-05-30 00:35:31 +02:00
m_currentTaskThread = null ;
2017-01-11 23:35:42 +01:00
}
2014-05-15 12:47:16 +02:00
}
2016-09-15 11:39:27 +02:00
}
2016-03-10 21:53:28 +01:00
2016-03-26 11:03:06 +01:00
/// <summary>
/// Attempts to get the locale, but delays linking to the calls as they are missing in some environments
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
private static void DoGetLocale ( out System . Globalization . CultureInfo locale , out System . Globalization . CultureInfo uiLocale )
{
locale = System . Globalization . CultureInfo . DefaultThreadCurrentCulture ;
uiLocale = System . Globalization . CultureInfo . DefaultThreadCurrentUICulture ;
}
2016-04-09 11:45:15 +02:00
2016-03-10 21:53:28 +01:00
/// <summary>
/// Attempts to set the locale, but delays linking to the calls as they are missing in some environments
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
private static void DoSetLocale ( System . Globalization . CultureInfo locale , System . Globalization . CultureInfo uiLocale )
{
System . Globalization . CultureInfo . DefaultThreadCurrentCulture = locale ;
System . Globalization . CultureInfo . DefaultThreadCurrentUICulture = uiLocale ;
}
2016-04-09 11:45:15 +02:00
2024-04-02 22:31:55 +02:00
private void OnOperationComplete ( object result , Exception exception )
2016-09-15 11:39:27 +02:00
{
2013-05-25 16:40:15 +02:00
if ( m_options != null && m_options . LoadedModules != null )
{
foreach ( KeyValuePair < bool , Library . Interface . IGenericModule > mx in m_options . LoadedModules )
2019-09-29 20:16:28 -07:00
if ( mx . Key && mx . Value is IGenericCallbackModule module )
2024-04-02 22:31:55 +02:00
try { module . OnFinish ( result , exception ); }
2018-03-12 14:07:11 +01:00
catch ( Exception ex ) { Logging . Log . WriteWarningMessage ( LOGTAG , $"OnFinishError{mx.Key}" , ex , "OnFinish callback {0} failed: {1}" , mx . Key , ex . Message ); }
2013-02-12 21:43:14 +00:00
2013-05-25 16:40:15 +02:00
foreach ( KeyValuePair < bool , Library . Interface . IGenericModule > mx in m_options . LoadedModules )
2019-09-29 20:16:28 -07:00
if ( mx . Key && mx . Value is IDisposable disposable )
try { disposable . Dispose (); }
2018-03-12 14:07:11 +01:00
catch ( Exception ex ) { Logging . Log . WriteWarningMessage ( LOGTAG , $"DisposeError{mx.Key}" , ex , "Dispose for {0} failed: {1}" , mx . Key , ex . Message ); }
2013-05-25 16:40:15 +02:00
m_options . LoadedModules . Clear ();
}
2015-09-04 15:04:04 +02:00
if ( m_resetPriority != null )
{
System . Threading . Thread . CurrentThread . Priority = m_resetPriority . Value ;
m_resetPriority = null ;
}
2015-09-11 11:21:57 +02:00
if ( m_doResetLocale )
{
2016-03-10 21:53:28 +01:00
// Wrap the call to avoid loading issues for the setLocale method
DoSetLocale ( m_resetLocale , m_resetLocaleUI );
2015-09-11 11:21:57 +02:00
m_doResetLocale = false ;
m_resetLocale = null ;
2016-03-10 21:53:28 +01:00
m_resetLocaleUI = null ;
2015-09-11 11:21:57 +02:00
}
2018-03-12 14:07:11 +01:00
if ( m_logTarget != null )
2013-05-25 16:40:15 +02:00
{
2018-03-12 14:07:11 +01:00
m_logTarget . Dispose ();
m_logTarget = null ;
2016-04-09 11:45:15 +02:00
}
2016-09-15 11:39:27 +02:00
}
2013-05-25 16:40:15 +02:00
2015-12-27 00:16:45 +01:00
private void SetupCommonOptions ( ISetCommonOptions result , ref string [] paths , ref IFilter filter )
2013-02-12 21:43:14 +00:00
{
2013-05-25 16:40:15 +02:00
m_options . MainAction = result . MainOperation ;
2016-04-09 11:45:15 +02:00
2013-02-12 21:43:14 +00:00
switch ( m_options . MainAction )
{
2013-05-08 21:29:59 +02:00
case OperationMode . Backup :
2013-02-12 21:43:14 +00:00
break ;
2016-04-09 11:45:15 +02:00
2013-02-12 21:43:14 +00:00
default :
//It only makes sense to enable auto-creation if we are writing files.
if (! m_options . RawOptions . ContainsKey ( "disable-autocreate-folder" ))
m_options . RawOptions [ "disable-autocreate-folder" ] = "true" ;
break ;
}
//Load all generic modules
m_options . LoadedModules . Clear ();
foreach ( Library . Interface . IGenericModule m in DynamicLoader . GenericLoader . Modules )
2018-09-19 14:29:40 -07:00
m_options . LoadedModules . Add ( new KeyValuePair < bool , Library . Interface . IGenericModule >(! m_options . DisableModules . Contains ( m . Key , StringComparer . OrdinalIgnoreCase ) && ( m . LoadAsDefault || m_options . EnableModules . Contains ( m . Key , StringComparer . OrdinalIgnoreCase )), m ));
2016-04-09 11:45:15 +02:00
2017-06-07 21:56:01 +02:00
// Make the filter read-n-write able in the generic modules
var pristinefilter = string . Join ( System . IO . Path . PathSeparator . ToString (), FilterExpression . Serialize ( filter ));
m_options . RawOptions [ "filter" ] = pristinefilter ;
2017-06-14 20:38:09 +02:00
// Store the URL connection options separately, as these should only be visible to modules implementing IConnectionModule
2014-07-25 16:36:26 +02:00
var conopts = new Dictionary < string , string >( m_options . RawOptions );
var qp = new Library . Utility . Uri ( m_backend ). QueryParameters ;
2017-06-14 20:38:09 +02:00
foreach ( var k in qp . Keys )
2014-07-25 16:36:26 +02:00
conopts [( string ) k ] = qp [( string ) k ];
2013-02-12 21:43:14 +00:00
2018-01-12 09:43:07 -05:00
//// Since Configure in RunScript can alter the RawOptions, make sure it is first in the list for Configure
var LoadedModules = new List < KeyValuePair < bool , Interface . IGenericModule >>();
2016-10-15 23:55:50 +02:00
foreach ( var mx in m_options . LoadedModules )
2018-09-19 14:29:40 -07:00
if ( mx . Value . ToString (). IndexOf ( "runscript" , StringComparison . OrdinalIgnoreCase ) >= 0 )
2018-01-12 09:43:07 -05:00
{
LoadedModules . Insert ( 0 , mx );
}
else
{
LoadedModules . Add ( mx );
}
foreach ( var mx in LoadedModules )
2013-02-12 21:43:14 +00:00
if ( mx . Key )
{
2014-07-25 16:36:26 +02:00
if ( mx . Value is Library . Interface . IConnectionModule )
mx . Value . Configure ( conopts );
else
mx . Value . Configure ( m_options . RawOptions );
2016-04-09 11:45:15 +02:00
2019-09-29 20:16:28 -07:00
if ( mx . Value is IGenericSourceModule sourcemodule )
2016-10-15 23:55:50 +02:00
{
2016-11-02 00:17:49 +01:00
if ( sourcemodule . ContainFilesForBackup ( paths ))
{
var sourceoptions = sourcemodule . ParseSourcePaths ( ref paths , ref pristinefilter , m_options . RawOptions );
foreach ( var sourceoption in sourceoptions )
m_options . RawOptions [ sourceoption . Key ] = sourceoption . Value ;
}
2016-10-15 23:55:50 +02:00
}
2016-10-15 16:18:26 +02:00
2019-09-29 20:16:28 -07:00
if ( mx . Value is IGenericCallbackModule module )
module . OnStart ( result . MainOperation . ToString (), ref m_backend , ref paths );
2013-02-12 21:43:14 +00:00
}
2017-06-14 20:38:09 +02:00
// If the filters were changed by a module, read them back in
2017-06-07 21:56:01 +02:00
if ( pristinefilter != m_options . RawOptions [ "filter" ])
{
filter = FilterExpression . Deserialize ( m_options . RawOptions [ "filter" ]. Split ( new string [] { System . IO . Path . PathSeparator . ToString () }, StringSplitOptions . RemoveEmptyEntries ));
}
m_options . RawOptions . Remove ( "filter" ); // "--filter" is not a supported command line option
2015-12-27 00:16:45 +01:00
2013-02-12 21:43:14 +00:00
if (! string . IsNullOrEmpty ( m_options . Logfile ))
{
2013-03-08 23:04:19 +01:00
var path = System . IO . Path . GetDirectoryName ( System . IO . Path . GetFullPath ( m_options . Logfile ));
if (! System . IO . Directory . Exists ( path ))
System . IO . Directory . CreateDirectory ( path );
2017-01-09 23:27:56 +01:00
2018-03-12 14:07:11 +01:00
m_logTarget . AddTarget (
new Library . Logging . StreamLogDestination ( m_options . Logfile ),
m_options . LogFileLoglevel ,
m_options . LogFileLogFilter
);
2013-02-12 21:43:14 +00:00
}
2018-03-15 23:30:24 +01:00
2013-02-12 21:43:14 +00:00
2013-03-08 12:25:35 +01:00
if ( m_options . HasTempDir )
2015-09-04 15:04:04 +02:00
{
2013-05-08 21:29:59 +02:00
Library . Utility . TempFolder . SystemTempPath = m_options . TempDir ;
2015-09-04 15:04:04 +02:00
}
2013-02-12 21:43:14 +00:00
2015-09-11 11:21:57 +02:00
if ( m_options . HasForcedLocale )
{
2016-03-26 11:34:37 +01:00
try
{
var locale = m_options . ForcedLocale ;
DoGetLocale ( out m_resetLocale , out m_resetLocaleUI );
m_doResetLocale = true ;
// Wrap the call to avoid loading issues for the setLocale method
DoSetLocale ( locale , locale );
}
catch ( Exception ex ) // or only: MissingMethodException
{
2018-03-12 14:07:11 +01:00
Library . Logging . Log . WriteWarningMessage ( LOGTAG , "LocaleChangeError" , ex , Strings . Controller . FailedForceLocaleError ( ex . Message ));
2016-03-26 11:34:37 +01:00
m_doResetLocale = false ;
m_resetLocale = m_resetLocaleUI = null ;
}
2015-09-11 11:21:57 +02:00
}
2013-02-12 21:43:14 +00:00
if (! string . IsNullOrEmpty ( m_options . ThreadPriority ))
2015-09-04 15:04:04 +02:00
{
m_resetPriority = System . Threading . Thread . CurrentThread . Priority ;
2013-05-08 21:29:59 +02:00
System . Threading . Thread . CurrentThread . Priority = Library . Utility . Utility . ParsePriority ( m_options . ThreadPriority );
2015-09-04 15:04:04 +02:00
}
2013-02-12 21:43:14 +00:00
2013-05-08 19:57:13 +02:00
if ( string . IsNullOrEmpty ( m_options . Dbpath ))
2013-05-08 20:17:07 +02:00
m_options . Dbpath = DatabaseLocator . GetDatabasePath ( m_backend , m_options );
2013-05-06 09:58:19 +02:00
2018-03-12 14:07:11 +01:00
ValidateOptions ();
2013-02-12 21:43:14 +00:00
}
/// <summary>
/// This function will examine all options passed on the commandline, and test for unsupported or deprecated values.
/// Any errors will be logged into the statistics module.
/// </summary>
2018-03-12 14:07:11 +01:00
private void ValidateOptions ()
2013-02-12 21:43:14 +00:00
{
2018-02-11 10:13:34 +01:00
// Check if only one of the retention options is set
var selectedRetentionOptions = new List < String >();
2016-05-19 00:14:09 +02:00
2018-02-11 10:13:34 +01:00
if ( m_options . KeepTime . Ticks > 0 )
{
selectedRetentionOptions . Add ( "keep-time" );
}
if ( m_options . KeepVersions > 0 )
{
selectedRetentionOptions . Add ( "keep-versions" );
}
2018-10-06 16:02:36 -07:00
if ( m_options . RetentionPolicy . Any ())
2018-02-11 10:13:34 +01:00
{
selectedRetentionOptions . Add ( "retention-policy" );
}
if ( selectedRetentionOptions . Count () > 1 )
{
throw new Interface . UserInformationException ( string . Format ( "Setting multiple retention options ({0}) is not permitted" ,
2018-03-12 14:07:11 +01:00
String . Join ( ", " , selectedRetentionOptions . Select ( x => "--" + x ))), "MultipleRetentionOptionsNotSupported" );
2018-02-11 10:13:34 +01:00
}
// Check Prefix
2017-02-27 09:09:02 +01:00
if (! string . IsNullOrWhiteSpace ( m_options . Prefix ) && m_options . Prefix . Contains ( "-" ))
2018-03-12 14:07:11 +01:00
throw new Interface . UserInformationException ( "The prefix cannot contain hyphens (-)" , "PrefixCannotContainHyphens" );
2016-05-19 00:14:09 +02:00
2017-09-30 13:14:28 +02:00
//Check validity of retention-policy option value
2017-09-09 14:11:59 +02:00
try
{
2017-09-30 13:14:28 +02:00
foreach ( var configEntry in m_options . RetentionPolicy )
2017-09-09 14:11:59 +02:00
{
2018-02-18 16:47:04 +01:00
if (! configEntry . IsKeepAllVersions () && ! configEntry . IsUnlimtedTimeframe () &&
configEntry . Interval >= configEntry . Timeframe )
2017-09-09 14:11:59 +02:00
{
2018-03-12 14:07:11 +01:00
throw new Interface . UserInformationException ( "An interval cannot be bigger than the timeframe it is in" , "IntervalCannotBeBiggerThanTimeFrame" );
2017-09-09 14:11:59 +02:00
}
}
}
catch ( Exception e ) // simply reading the option value might also result in an exception due to incorrect formatting
{
2018-03-12 14:07:11 +01:00
throw new Interface . UserInformationException ( string . Format ( "An error occoured while processing the value of --{0}" , "retention-policy" ), "RetentionPolicyParseError" , e );
2017-09-09 14:11:59 +02:00
}
2013-02-12 21:43:14 +00:00
//Keep a list of all supplied options
2013-05-05 13:09:55 +02:00
Dictionary < string , string > ropts = new Dictionary < string , string >( m_options . RawOptions );
2016-04-09 11:45:15 +02:00
2013-02-12 21:43:14 +00:00
//Keep a list of all supported options
Dictionary < string , Library . Interface . ICommandLineArgument > supportedOptions = new Dictionary < string , Library . Interface . ICommandLineArgument >();
//There are a few internal options that are not accessible from outside, and thus not listed
foreach ( string s in Options . InternalOptions )
supportedOptions [ s ] = null ;
//Figure out what module options are supported in the current setup
List < Library . Interface . ICommandLineArgument > moduleOptions = new List < Duplicati . Library . Interface . ICommandLineArgument >();
Dictionary < string , string > disabledModuleOptions = new Dictionary < string , string >();
foreach ( KeyValuePair < bool , Library . Interface . IGenericModule > m in m_options . LoadedModules )
if ( m . Value . SupportedCommands != null )
if ( m . Key )
moduleOptions . AddRange ( m . Value . SupportedCommands );
else
foreach ( Library . Interface . ICommandLineArgument c in m . Value . SupportedCommands )
{
disabledModuleOptions [ c . Name ] = m . Value . DisplayName + " (" + m . Value . Key + ")" ;
if ( c . Aliases != null )
foreach ( string s in c . Aliases )
disabledModuleOptions [ s ] = disabledModuleOptions [ c . Name ];
}
2016-04-09 11:45:15 +02:00
2013-05-05 13:09:55 +02:00
// Throw url-encoded options into the mix
//TODO: This can hide values if both commandline and url-parameters supply the same key
2013-05-08 21:29:59 +02:00
var ext = new Library . Utility . Uri ( m_backend ). QueryParameters ;
2013-05-05 23:46:00 +02:00
foreach ( var k in ext . AllKeys )
ropts [ k ] = ext [ k ];
2013-02-12 21:43:14 +00:00
//Now run through all supported options, and look for deprecated options
2016-04-09 11:45:15 +02:00
foreach ( IList < Library . Interface . ICommandLineArgument > l in new IList < Library . Interface . ICommandLineArgument >[] {
m_options . SupportedCommands ,
DynamicLoader . BackendLoader . GetSupportedCommands ( m_backend ),
2013-02-12 21:43:14 +00:00
m_options . NoEncryption ? null : DynamicLoader . EncryptionLoader . GetSupportedCommands ( m_options . EncryptionModule ),
moduleOptions ,
DynamicLoader . CompressionLoader . GetSupportedCommands ( m_options . CompressionModule ) })
{
if ( l != null )
foreach ( Library . Interface . ICommandLineArgument a in l )
{
2018-09-19 14:29:40 -07:00
if ( supportedOptions . ContainsKey ( a . Name ) && ! Options . KnownDuplicates . Contains ( a . Name , StringComparer . OrdinalIgnoreCase ))
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "DuplicateOption" , null , Strings . Controller . DuplicateOptionNameWarning ( a . Name ));
2013-02-12 21:43:14 +00:00
supportedOptions [ a . Name ] = a ;
if ( a . Aliases != null )
foreach ( string s in a . Aliases )
{
2018-09-19 14:29:40 -07:00
if ( supportedOptions . ContainsKey ( s ) && ! Options . KnownDuplicates . Contains ( s , StringComparer . OrdinalIgnoreCase ))
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "DuplicateOption" , null , Strings . Controller . DuplicateOptionNameWarning ( s ));
2013-02-12 21:43:14 +00:00
supportedOptions [ s ] = a ;
}
if ( a . Deprecated )
{
List < string > aliases = new List < string >();
aliases . Add ( a . Name );
if ( a . Aliases != null )
aliases . AddRange ( a . Aliases );
foreach ( string s in aliases )
if ( ropts . ContainsKey ( s ))
{
string optname = a . Name ;
if ( a . Name != s )
optname += " (" + s + ")" ;
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "DeprecatedOption" , null , Strings . Controller . DeprecatedOptionUsedWarning ( optname , a . DeprecationMessage ), null );
2013-02-12 21:43:14 +00:00
}
}
}
}
//Now look for options that were supplied but not supported
foreach ( string s in ropts . Keys )
if (! supportedOptions . ContainsKey ( s ))
if ( disabledModuleOptions . ContainsKey ( s ))
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "UnsupportedDisabledModule" , null , Strings . Controller . UnsupportedOptionDisabledModuleWarning ( s , disabledModuleOptions [ s ]), null );
2013-02-12 21:43:14 +00:00
else
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "UnsupportedOption" , null , Strings . Controller . UnsupportedOptionWarning ( s ), null );
2013-02-12 21:43:14 +00:00
//Look at the value supplied for each argument and see if is valid according to its type
foreach ( string s in ropts . Keys )
{
Library . Interface . ICommandLineArgument arg ;
if ( supportedOptions . TryGetValue ( s , out arg ) && arg != null )
{
string validationMessage = ValidateOptionValue ( arg , s , ropts [ s ]);
if ( validationMessage != null )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "OptionValidationError" , null , validationMessage );
2013-02-12 21:43:14 +00:00
}
}
2018-02-22 17:11:07 +01:00
// For now, warn not to use 7z
if ( string . Equals ( m_options . CompressionModule , "7z" , StringComparison . OrdinalIgnoreCase ))
2019-07-23 10:35:33 -04:00
Logging . Log . WriteWarningMessage ( LOGTAG , "7zModuleHasIssues" , null , "The 7z compression module has known issues and should only be used for experimental purposes" );
2021-07-15 17:37:22 +02:00
//Inform the user about the deprecated Tardigrade-Backend. They should switch to Storj DCS instead.
if ( string . Equals ( new Library . Utility . Uri ( m_backend ). Scheme , "tardigrade" , StringComparison . OrdinalIgnoreCase ))
2021-07-16 08:34:01 +02:00
Logging . Log . WriteWarningMessage ( LOGTAG , "TardigradeRename" , null , "The Tardigrade-backend got renamed to Storj DCS - please migrate your backups to the new configuration by changing the destination storage type to Storj DCS." );
2021-07-15 17:37:22 +02:00
//TODO: Based on the action, see if all options are relevant
}
2019-07-23 10:35:33 -04:00
2019-06-25 13:16:18 +02:00
/// <summary>
/// Helper method that expands the users chosen source input paths,
/// and removes duplicate paths
/// </summary>
/// <returns>The expanded and filtered sources.</returns>
private string [] ExpandInputSources ( string [] inputsources , IFilter filter )
2018-06-11 08:55:31 +02:00
{
if ( inputsources == null || inputsources . Length == 0 )
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Controller . NoSourceFoldersError , "NoSourceFolders" );
var sources = new List < string >( inputsources . Length );
System . IO . DriveInfo [] drives = null ;
//Make sure they all have the same format and exist
foreach ( var inputsource in inputsources )
{
List < string > expandedSources = new List < string >();
2018-11-02 22:13:25 +01:00
if ( Platform . IsClientWindows && ( inputsource . StartsWith ( "*:" , StringComparison . Ordinal ) || inputsource . StartsWith ( "?:" , StringComparison . Ordinal )))
2018-06-11 08:55:31 +02:00
{
// *: drive paths are only supported on Windows clients
// Lazily load the drive info
drives = drives ?? System . IO . DriveInfo . GetDrives ();
// Replace the drive letter with each available drive
string sourcePath = inputsource . Substring ( 1 );
foreach ( System . IO . DriveInfo drive in drives )
{
string expandedSource = drive . Name [ 0 ] + sourcePath ;
Logging . Log . WriteVerboseMessage ( LOGTAG , "AddingSourcePathFromWildcard" , @"Adding source path ""{0}"" due to wildcard source path ""{1}""" , expandedSource , inputsource );
expandedSources . Add ( expandedSource );
}
}
2018-11-02 22:13:25 +01:00
else if ( Platform . IsClientWindows && inputsource . StartsWith ( @"\\?\Volume{" , StringComparison . OrdinalIgnoreCase ))
2018-06-11 08:55:31 +02:00
{
// In order to specify a drive by it's volume name, adopt the volume guid path syntax:
// \\?\Volume{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
// The volume guid can be found using the 'mountvol' commandline tool.
// However, instead of using this path with Windows APIs directory, it is adapted here to a standard path.
Guid volumeGuid ;
if ( Guid . TryParse ( inputsource . Substring ( @"\\?\Volume{" . Length , @"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" . Length ), out volumeGuid ))
{
string driveLetter = Library . Utility . Utility . GetDriveLetterFromVolumeGuid ( volumeGuid );
if (! string . IsNullOrEmpty ( driveLetter ))
{
string expandedSource = driveLetter + inputsource . Substring ( @"\\?\Volume{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" . Length );
Logging . Log . WriteVerboseMessage ( LOGTAG , "AddingSourceFromGuid" , @"Adding source path ""{0}"" in place of volume guid source path ""{1}""" , expandedSource , inputsource );
expandedSources . Add ( expandedSource );
}
else
{
// If we aren't allow to have missing sources, throw an exception indicating we couldn't find a drive where this volume is mounted
if (! m_options . AllowMissingSource )
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Controller . SourceVolumeNameNotFoundError ( inputsource , volumeGuid ), "MissingSourceFolder" );
}
}
else
{
// If we aren't allow to have missing sources, throw an exception indicating we couldn't find this volume
if (! m_options . AllowMissingSource )
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Controller . SourceVolumeNameInvalidError ( inputsource ), "SourceVolumeNameInvalid" );
}
}
else
{
expandedSources . Add ( inputsource );
}
bool foundAnyPaths = false ;
2018-06-11 09:07:32 +02:00
bool unauthorized = false ;
2018-06-11 08:55:31 +02:00
foreach ( string expandedSource in expandedSources )
{
string source ;
try
{
2018-06-30 18:48:20 +02:00
// TODO: This expands "C:" to CWD, but not C:\
2018-06-11 08:55:31 +02:00
source = System . IO . Path . GetFullPath ( expandedSource );
}
catch ( Exception ex )
{
// Note that we use the original source (with the *) in the error
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Controller . InvalidPathError ( expandedSource , ex . Message ), "InputSourceInvalid" , ex );
}
var fi = new System . IO . FileInfo ( source );
var di = new System . IO . DirectoryInfo ( source );
if ( fi . Exists || di . Exists )
{
foundAnyPaths = true ;
if (! fi . Exists )
2018-10-27 12:17:07 +02:00
source = Util . AppendDirSeparator ( source );
2018-06-11 08:55:31 +02:00
sources . Add ( source );
}
else
{
try
{
// Try to get attributes. Returns -1 if source doesn't exist, otherwise throws an exception.
// In this case, it is irrelevant to use fileinfo or directoryinfo to retrieve attributes.
2018-12-31 14:58:58 -08:00
var unused = fi . Attributes ;
2018-06-11 08:55:31 +02:00
}
catch ( UnauthorizedAccessException ex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "AddingSourceFolder" ,
ex , @"Insufficient permissions to read ""{0}"", skipping" , expandedSource );
2018-06-11 09:07:32 +02:00
unauthorized = true ;
2018-06-11 08:55:31 +02:00
}
}
}
// If no paths were found, and we aren't allowed to have missing sources, throw an error
if (! foundAnyPaths && ! m_options . AllowMissingSource )
{
2018-06-11 09:07:32 +02:00
if ( unauthorized )
2018-06-11 08:55:31 +02:00
{
throw new System . IO . IOException ( Strings . Controller . SourceUnauthorizedError ( inputsource ));
}
throw new System . IO . IOException ( Strings . Controller . SourceIsMissingError ( inputsource ));
}
}
//Sanity check for duplicate files/folders
ISet < string > pathDuplicates ;
sources = Library . Utility . Utility . GetUniqueItems ( sources , Library . Utility . Utility . ClientFilenameStringComparer , out pathDuplicates ). OrderBy ( a => a ). ToList ();
foreach ( var pathDuplicate in pathDuplicates )
Logging . Log . WriteVerboseMessage ( LOGTAG , "RemoveDuplicateSource" , "Removing duplicate source: {0}" , pathDuplicate );
//Sanity check for multiple inclusions of the same folder
for ( int i = 0 ; i < sources . Count ; i ++)
for ( int j = 0 ; j < sources . Count ; j ++)
2018-11-02 17:45:00 +01:00
if ( i != j && sources [ i ]. StartsWith ( sources [ j ], Library . Utility . Utility . ClientFilenameStringComparison ) && sources [ i ]. EndsWith ( Util . DirectorySeparatorString , Library . Utility . Utility . ClientFilenameStringComparison ))
2018-06-11 08:55:31 +02:00
{
if ( filter != null )
{
bool excludes ;
2019-04-16 21:35:02 -07:00
FilterExpression . AnalyzeFilters ( filter , out _ , out excludes );
2018-06-11 08:55:31 +02:00
// If there are no excludes, there is no need to keep the folder as a filter
if ( excludes )
{
Logging . Log . WriteVerboseMessage ( LOGTAG , "RemovingSubfolderSource" , "Removing source \"{0}\" because it is a subfolder of \"{1}\", and using it as an include filter" , sources [ i ], sources [ j ]);
filter = Library . Utility . JoinedFilterExpression . Join ( new FilterExpression ( sources [ i ]), filter );
}
else
Logging . Log . WriteVerboseMessage ( LOGTAG , "RemovingSubfolderSource" , "Removing source \"{0}\" because it is a subfolder or subfile of \"{1}\"" , sources [ i ], sources [ j ]);
}
else
Logging . Log . WriteVerboseMessage ( LOGTAG , "RemovingSubfolderSource" , "Removing source \"{0}\" because it is a subfolder or subfile of \"{1}\"" , sources [ i ], sources [ j ]);
sources . RemoveAt ( i );
i --;
break ;
}
return sources . ToArray ();
}
2013-02-12 21:43:14 +00:00
/// <summary>
/// Checks if the value passed to an option is actually valid.
/// </summary>
/// <param name="arg">The argument being validated</param>
/// <param name="optionname">The name of the option to validate</param>
/// <param name="value">The value to check</param>
/// <returns>Null if no errors are found, an error message otherwise</returns>
2018-06-11 08:55:31 +02:00
private static string ValidateOptionValue ( Library . Interface . ICommandLineArgument arg , string optionname , string value )
2016-09-15 11:39:27 +02:00
{
if ( arg . Type == Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Enumeration )
{
bool found = false ;
foreach ( string v in arg . ValidValues ?? new string [ 0 ])
if ( string . Equals ( v , value , StringComparison . CurrentCultureIgnoreCase ))
{
found = true ;
break ;
}
if (! found )
return Strings . Controller . UnsupportedEnumerationValue ( optionname , value , arg . ValidValues ?? new string [ 0 ]);
}
else if ( arg . Type == Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Flags )
{
bool validatedAllFlags = false ;
var flags = ( value ?? string . Empty ). ToLowerInvariant (). Split ( new [] { "," }, StringSplitOptions . None ). Select ( flag => flag . Trim ()). Distinct ();
var validFlags = arg . ValidValues ?? new string [ 0 ];
foreach ( var flag in flags )
{
if (! validFlags . Any ( validFlag => string . Equals ( validFlag , flag , StringComparison . CurrentCultureIgnoreCase )))
{
validatedAllFlags = false ;
break ;
}
validatedAllFlags = true ;
}
if (! validatedAllFlags )
{
return Strings . Controller . UnsupportedFlagsValue ( optionname , value , validFlags );
}
}
else if ( arg . Type == Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean )
2013-02-12 21:43:14 +00:00
{
2013-05-08 21:29:59 +02:00
if (! string . IsNullOrEmpty ( value ) && Library . Utility . Utility . ParseBool ( value , true ) != Library . Utility . Utility . ParseBool ( value , false ))
2015-01-20 21:07:24 +01:00
return Strings . Controller . UnsupportedBooleanValue ( optionname , value );
2013-02-12 21:43:14 +00:00
}
else if ( arg . Type == Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Integer )
{
2019-04-16 21:35:02 -07:00
if (! long . TryParse ( value , out _ ))
2015-01-20 21:07:24 +01:00
return Strings . Controller . UnsupportedIntegerValue ( optionname , value );
2013-02-12 21:43:14 +00:00
}
else if ( arg . Type == Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Path )
{
foreach ( string p in value . Split ( System . IO . Path . DirectorySeparatorChar ))
if ( p . IndexOfAny ( System . IO . Path . GetInvalidPathChars ()) >= 0 )
2015-01-20 21:07:24 +01:00
return Strings . Controller . UnsupportedPathValue ( optionname , p );
2013-02-12 21:43:14 +00:00
}
else if ( arg . Type == Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Size )
{
try
{
2013-05-08 21:29:59 +02:00
Library . Utility . Sizeparser . ParseSize ( value );
2013-02-12 21:43:14 +00:00
}
catch
{
2015-01-20 21:07:24 +01:00
return Strings . Controller . UnsupportedSizeValue ( optionname , value );
2013-02-12 21:43:14 +00:00
}
2018-08-08 11:58:35 +02:00
if (! string . IsNullOrWhiteSpace ( value ) && char . IsDigit ( value . Last ()))
return Strings . Controller . NonQualifiedSizeValue ( optionname , value );
2013-02-12 21:43:14 +00:00
}
else if ( arg . Type == Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Timespan )
{
try
{
2013-05-08 21:29:59 +02:00
Library . Utility . Timeparser . ParseTimeSpan ( value );
2013-02-12 21:43:14 +00:00
}
catch
{
2015-01-20 21:07:24 +01:00
return Strings . Controller . UnsupportedTimeValue ( optionname , value );
2013-02-12 21:43:14 +00:00
}
}
return null ;
}
2016-04-09 11:45:15 +02:00
2014-05-15 12:47:16 +02:00
public void Pause ()
{
var ct = m_currentTask ;
if ( ct != null )
ct . Pause ();
}
public void Resume ()
{
var ct = m_currentTask ;
if ( ct != null )
ct . Resume ();
}
2019-07-23 10:35:33 -04:00
public void Stop ( bool allowCurrentFileToFinish )
2014-05-15 12:47:16 +02:00
{
var ct = m_currentTask ;
2020-02-29 16:33:32 -06:00
if ( ct == null )
return ;
Logging . Log . WriteVerboseMessage ( LOGTAG , "CancellationRequested" , "Cancellation Requested" );
m_cancellationTokenSource . Cancel ();
2019-07-23 10:35:33 -04:00
ct . Stop ( allowCurrentFileToFinish );
2014-05-15 12:47:16 +02:00
}
public void Abort ()
{
var ct = m_currentTask ;
if ( ct != null )
ct . Abort ();
2016-04-09 11:45:15 +02:00
2014-05-15 12:47:16 +02:00
var t = m_currentTaskThread ;
if ( t != null )
2021-01-03 08:54:09 -08:00
t . Interrupt ();
2014-05-15 12:47:16 +02:00
}
2017-06-20 13:00:52 +02:00
public long MaxUploadSpeed
{
get { return m_options . MaxUploadPrSecond ; }
set { m_options . MaxUploadPrSecond = value ; }
2017-09-13 23:23:15 -07:00
}
public long MaxDownloadSpeed
{
get { return m_options . MaxDownloadPrSecond ; }
set { m_options . MaxDownloadPrSecond = value ; }
}
2019-07-25 19:51:45 -07:00
/// <summary>
/// Time of last compact operation
/// </summary>
public DateTime LastCompact { get ; set ; }
2019-07-21 07:25:32 -07:00
2019-07-29 17:56:43 -07:00
/// <summary>
/// Time of last vacuum operation
/// </summary>
public DateTime LastVacuum { get ; set ; }
private void CheckAutoCompactInterval ()
{
if (! m_options . NoAutoCompact && ( LastCompact > DateTime . MinValue ) && ( LastCompact . Add ( m_options . AutoCompactInterval ) > DateTime . Now ))
{
Logging . Log . WriteInformationMessage ( LOGTAG , "CompactResults" , "Skipping auto compaction until {0}" , LastCompact . Add ( m_options . AutoCompactInterval ));
m_options . RawOptions [ "no-auto-compact" ] = "true" ;
}
}
private void CheckAutoVacuumInterval ()
{
if ( m_options . AutoVacuum && ( LastVacuum > DateTime . MinValue ) && ( LastVacuum . Add ( m_options . AutoVacuumInterval ) > DateTime . Now ))
{
Logging . Log . WriteInformationMessage ( LOGTAG , "VacuumResults" , "Skipping auto vacuum until {0}" , LastVacuum . Add ( m_options . AutoVacuumInterval ));
m_options . RawOptions [ "auto-vacuum" ] = "false" ;
}
}
2017-09-13 22:30:12 -07:00
#region IDisposable Members
2017-09-13 23:23:15 -07:00
2017-09-13 22:30:12 -07:00
public void Dispose ()
2013-02-12 21:43:14 +00:00
{
}
#endregion
}
}