2025-01-27 11:46:24 +01:00
// Copyright (C) 2025, 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
2024-04-02 22:31:55 +02:00
// 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 ;
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 ;
2024-10-24 15:56:39 +02:00
using System.Threading.Tasks ;
2024-12-18 08:27:59 +01:00
using Duplicati.Library.Main.Operation.Common ;
2025-01-28 08:54:50 +01:00
using System.IO ;
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>
2025-01-28 08:54:50 +01:00
private string m_backendUrl ;
2013-02-12 21:43:14 +00:00
/// <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>
2024-10-24 15:56:39 +02:00
/// The optional secret provider, if none provided in options
/// </summary>
public ISecretProvider SecretProvider { get ; private set ; }
/// <summary>
2013-08-18 23:16:58 +02:00
/// 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>
2024-12-18 08:27:59 +01:00
private ITaskControl m_currentTaskControl = null ;
2015-09-04 15:04:04 +02:00
2015-09-11 11:21:57 +02:00
/// <summary>
2023-06-23 13:30:46 +02:00
/// If not null, active locale change that needs to be reset
2015-09-11 11:21:57 +02:00
/// </summary>
2023-06-23 13:30:46 +02:00
private LocaleChange m_localeChange = null ;
2015-09-11 11:21:57 +02:00
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>
2024-12-18 08:27:59 +01:00
/// Callback method invoked when an operation is started
2019-07-23 10:35:33 -04:00
/// </summary>
2024-12-18 08:27:59 +01:00
public Action < IBasicResults > OnOperationStarted { get ; set ; }
/// <summary>
/// Callback method invoked when an operation is completed
/// </summary>
public Action < IBasicResults , Exception > OnOperationCompleted { get ; set ; }
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>
2025-01-28 08:54:50 +01:00
/// <param name="backendUrl">The url for the backend to use</param>
2013-02-12 21:43:14 +00:00
/// <param name="options">All required options</param>
2025-01-28 08:54:50 +01:00
public Controller ( string backendUrl , Dictionary < string , string > options , IMessageSink messageSink )
2013-02-12 21:43:14 +00:00
{
2025-01-28 08:54:50 +01:00
m_backendUrl = backendUrl ;
2013-02-12 21:43:14 +00:00
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 );
}
2024-10-24 15:56:39 +02:00
/// <summary>
/// Sets a secret provider to use for all operations
/// </summary>
/// <param name="secretProvider">The secret provider to use</param>
public void SetSecretProvider ( ISecretProvider secretProvider )
{
SecretProvider = secretProvider ;
}
2017-10-30 15:30:43 +01:00
public Duplicati . Library . Interface . IBackupResults Backup ( string [] inputsources , IFilter filter = null )
{
2025-01-28 08:54:50 +01:00
Library . UsageReporter . Reporter . Report ( "USE_BACKEND" , new Library . Utility . Uri ( m_backendUrl ). Scheme );
2017-10-30 15:30:43 +01:00
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
2025-01-28 08:54:50 +01:00
return RunAction ( new BackupResults (), ref inputsources , ref filter , ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2013-05-25 16:40:15 +02:00
2025-01-28 08:54:50 +01:00
using ( var h = new Operation . BackupHandler ( m_backendUrl , m_options , result ))
2019-07-23 10:35:33 -04:00
{
2025-01-28 08:54:50 +01:00
h . RunAsync ( ExpandInputSources ( inputsources , filter ), backendManager , filter ). Await ();
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
{
2025-01-28 08:54:50 +01:00
return RunAction ( new RestoreResults (), ref paths , ref filter , ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2025-01-28 08:54:50 +01:00
new Operation . RestoreHandler ( m_options , result ). Run ( paths , backendManager , 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
{
2025-01-28 08:54:50 +01:00
return RunAction ( new RestoreControlFilesResults (), ref filter , ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2025-01-28 08:54:50 +01:00
new Operation . RestoreControlFilesHandler ( m_options , result ). Run ( files , backendManager , 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
{
2025-01-28 08:54:50 +01:00
return RunAction ( new DeleteResults (), ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2025-01-28 08:54:50 +01:00
new Operation . DeleteHandler ( m_options , result ). Run ( backendManager );
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
{
2025-01-28 08:54:50 +01:00
return RunAction ( new RepairResults (), ref filter , ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2025-01-28 08:54:50 +01:00
new Operation . RepairHandler ( m_options , result ). Run ( backendManager , 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
{
2025-01-28 08:54:50 +01:00
return RunAction ( new ListResults (), ref filter , ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2025-01-28 08:54:50 +01:00
new Operation . ListFilesHandler ( m_options , result ). Run ( backendManager , filterstrings , filter ). Await ();
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
{
2025-01-28 08:54:50 +01:00
return RunAction ( new ListResults (), ref filter , ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2025-01-28 08:54:50 +01:00
new Operation . ListControlFilesHandler ( m_options , result ). Run ( backendManager , 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 ()
{
2025-01-28 08:54:50 +01:00
return RunAction ( new ListRemoteResults (), ( result , backendManager ) =>
2016-10-13 21:45:31 +02:00
{
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 ))
2025-01-28 08:54:50 +01:00
result . SetResult ( backendManager . ListAsync ( CancellationToken . None ). Await ());
2016-10-13 21:45:31 +02:00
});
}
public Duplicati . Library . Interface . IListRemoteResults DeleteAllRemoteFiles ()
{
2025-01-28 08:54:50 +01:00
return RunAction ( new ListRemoteResults (), ( result , backendManager ) =>
2016-10-13 21:45:31 +02:00
{
2025-01-28 08:54:50 +01:00
var cancelToken = CancellationToken . None ;
2016-10-13 21:45:31 +02:00
result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Delete_Listing );
{
2017-10-10 16:44:06 +02:00
// Only delete files that match the expected pattern and prefix
2025-01-28 08:54:50 +01:00
var list = backendManager . ListAsync ( cancelToken ). Await ()
2017-10-10 16:44:06 +02:00
. 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
2024-07-26 00:08:45 -04:00
// from the backend. Otherwise, we may accidentally delete non-Duplicati files, or
2020-07-21 09:19:01 -07:00
// 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
{
2025-01-28 08:54:50 +01:00
backendManager . DeleteAsync ( list [ i ]. File . Name , list [ i ]. File . Size , true , cancelToken ). Await ();
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 ();
2025-01-28 08:54:50 +01:00
return RunAction ( new CompactResults (), ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2025-01-28 08:54:50 +01:00
new Operation . CompactHandler ( m_options , result ). Run ( backendManager ). Await ();
2013-05-25 16:40:15 +02:00
});
}
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 );
2025-01-28 08:54:50 +01:00
return RunAction ( new RecreateDatabaseResults (), ref filter , ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2025-01-28 08:54:50 +01:00
using ( var h = new Operation . RecreateDatabaseHandler ( m_options , result ))
h . RunUpdate ( backendManager , filter , filelistfilter , null );
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
2025-01-28 08:54:50 +01:00
return RunAction ( new CreateLogDatabaseResults (), ref t , ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2013-05-25 16:40:15 +02:00
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
2025-01-28 08:54:50 +01:00
return RunAction ( new ListChangesResults (), ref t , ref filter , ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2025-01-28 08:54:50 +01:00
new Operation . ListChangesHandler ( m_options , result ). Run ( t [ 0 ], t [ 1 ], backendManager , 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
{
2025-01-28 08:54:50 +01:00
return RunAction ( new ListAffectedResults (), ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
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" ;
2024-04-26 14:32:41 +02:00
2025-01-28 08:54:50 +01:00
return RunAction ( new TestResults (), ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2025-01-28 08:54:50 +01:00
new Operation . TestHandler ( m_options , result ). Run ( samples , backendManager );
2013-06-26 21:59:01 +02:00
});
}
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
{
2025-01-28 08:54:50 +01:00
return RunAction ( new TestFilterResults (), ref paths , ref filter , ( result , backendManager ) =>
2018-03-12 14:07:11 +01:00
{
2024-12-18 08:27:59 +01:00
new Operation . TestFilterHandler ( m_options , result ). RunAsync ( ExpandInputSources ( paths , filter ), filter ). Await ();
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 ()
{
2025-01-28 08:54:50 +01:00
return RunAction ( new SystemInfoResults (), ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2015-09-11 10:57:31 +02:00
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 )
{
2025-01-28 08:54:50 +01:00
return RunAction ( new PurgeFilesResults (), ( result , backendManager ) =>
2016-12-29 22:41:13 +01:00
{
2025-01-28 08:54:50 +01:00
new Operation . PurgeFilesHandler ( m_options , result ). Run ( backendManager , filter );
2016-12-29 22:41:13 +01:00
});
}
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 )
{
2025-01-28 08:54:50 +01:00
return RunAction ( new ListBrokenFilesResults (), ( result , backendManager ) =>
2017-01-05 16:46:01 +01:00
{
2025-01-28 08:54:50 +01:00
new Operation . ListBrokenFilesHandler ( m_options , result ). Run ( backendManager , filter , callbackhandler );
2017-01-05 16:46:01 +01:00
});
}
public Library . Interface . IPurgeBrokenFilesResults PurgeBrokenFiles ( Library . Utility . IFilter filter )
{
2025-01-28 08:54:50 +01:00
return RunAction ( new PurgeBrokenFilesResults (), ( result , backendManager ) =>
2017-01-05 16:46:01 +01:00
{
2025-01-28 08:54:50 +01:00
new Operation . PurgeBrokenFilesHandler ( m_options , result ). Run ( backendManager , filter );
2017-01-05 16:46:01 +01:00
});
}
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 )
);
2024-04-26 14:32:41 +02:00
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
{
2025-01-28 08:54:50 +01:00
return RunAction ( new SendMailResults (), ( result , backendManager ) =>
2018-03-12 14:07:11 +01:00
{
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 ()
{
2025-01-28 08:54:50 +01:00
return RunAction ( new VacuumResults (), ( result , backendManager ) =>
2024-04-26 14:32:41 +02:00
{
2017-08-04 11:24:18 +01:00
new Operation . VacuumHandler ( m_options , result ). Run ();
});
}
2025-01-28 08:54:50 +01:00
private T RunAction < T >( T result , Action < T , IBackendManager > method )
where T : ISetCommonOptions , ITaskControlProvider , Logging . ILogDestination , IBasicResults , IBackendWriterProvider
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
2025-01-28 08:54:50 +01:00
private T RunAction < T >( T result , ref string [] paths , Action < T , IBackendManager > method )
where T : ISetCommonOptions , ITaskControlProvider , Logging . ILogDestination , IBasicResults , IBackendWriterProvider
2015-12-27 00:16:45 +01:00
{
IFilter tempfilter = null ;
return RunAction < T >( result , ref paths , ref tempfilter , method );
}
2025-01-28 08:54:50 +01:00
private T RunAction < T >( T result , ref IFilter filter , Action < T , IBackendManager > method )
where T : ISetCommonOptions , ITaskControlProvider , Logging . ILogDestination , IBasicResults , IBackendWriterProvider
2015-12-27 00:16:45 +01:00
{
var tmp = new string [ 0 ];
return RunAction < T >( result , ref tmp , ref filter , method );
}
2025-01-28 08:54:50 +01:00
private T RunAction < T >( T result , ref string [] paths , ref IFilter filter , Action < T , IBackendManager > method )
where T : ISetCommonOptions , ITaskControlProvider , Logging . ILogDestination , IBasicResults , IBackendWriterProvider
2014-05-15 12:47:16 +02:00
{
2024-12-18 08:27:59 +01:00
OnOperationStarted ?. Invoke ( result );
var resultSetter = result as ISetCommonOptions ;
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
{
2024-12-18 08:27:59 +01:00
m_currentTaskControl = result . TaskControl ;
2024-10-24 15:56:39 +02:00
m_options . MainAction = result . MainOperation ;
ApplySecretProvider ( CancellationToken . None ). Await ();
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 )))
2024-04-26 14:32:41 +02:00
using ( new CoCoL . IsolatedChannelScope ())
using ( m_options . ConcurrencyMaxThreads <= 0 ? null : new CoCoL . CappedThreadedThreadPool ( m_options . ConcurrencyMaxThreads ))
2025-01-28 08:54:50 +01:00
using ( var backend = new Backend . BackendManager ( m_backendUrl , m_options , result . BackendWriter , result . TaskControl ))
{
method ( result , backend );
// TODO: Should also have a single shared database connection for all operations
// The transactions should be managed inside the connection, and not passed around
// This would allow us to pass the database instance to the backend manager
// And safeguard against remote operations not being logged in the database
if ( File . Exists ( m_options . Dbpath ))
{
using ( var db = new LocalDatabase ( m_options . Dbpath , result . MainOperation . ToString (), true ))
backend . StopRunnerAndFlushMessages ( db , null ). Await ();
}
else
{
backend . StopRunnerAndDiscardMessages ();
}
}
2016-04-09 11:45:15 +02:00
2024-12-18 08:27:59 +01:00
if ( resultSetter . EndTime . Ticks == 0 )
resultSetter . EndTime = DateTime . UtcNow ;
2013-05-25 16:40:15 +02:00
result . SetDatabase ( null );
2024-04-26 14:32:41 +02:00
if ( result is BasicResults r )
2023-09-21 19:58:27 +02:00
{
r . Interrupted = false ;
}
2016-09-28 09:55:04 +02:00
2024-12-18 08:27:59 +01:00
OperationComplete ( 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 )
{
2024-12-18 08:27:59 +01:00
resultSetter . 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
2024-12-18 08:27:59 +01:00
OperationComplete ( result , null );
2023-09-21 19:58:27 +02:00
}
catch { }
}
else
{
// Perform the module shutdown
2024-12-18 08:27:59 +01:00
OperationComplete ( result , 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
2024-12-18 08:27:59 +01:00
OperationComplete ( result , ex );
2018-08-14 11:09:16 +02:00
2024-04-02 22:31:55 +02:00
}
catch { }
}
else
{
// Perform the module shutdown
2024-12-18 08:27:59 +01:00
OperationComplete ( result , ex );
2024-04-02 22:31:55 +02:00
}
2018-08-14 11:09:16 +02:00
throw ;
}
2017-01-11 23:35:42 +01:00
}
finally
{
2024-12-18 08:27:59 +01:00
m_currentTaskControl = 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
2024-12-18 08:27:59 +01:00
private void OperationComplete ( IBasicResults 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 ();
}
2024-09-18 09:24:55 +02:00
if ( m_localeChange != null )
2015-09-11 11:21:57 +02:00
{
2023-06-23 13:30:46 +02:00
m_localeChange . Dispose ();
m_localeChange = 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
}
2024-12-18 08:27:59 +01:00
OnOperationCompleted ?. Invoke ( result , exception );
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 ;
2024-04-26 14:32:41 +02:00
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 );
2025-01-28 08:54:50 +01:00
var qp = new Library . Utility . Uri ( m_backendUrl ). 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 )
2025-01-28 08:54:50 +01:00
module . OnStart ( result . MainOperation . ToString (), ref m_backendUrl , 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
{
2023-06-23 13:30:46 +02:00
m_localeChange = new LocaleChange ( m_options . ForcedLocale );
2016-03-26 11:34:37 +01:00
}
2023-06-23 13:30:46 +02:00
catch ( Exception ex )
2016-03-26 11:34:37 +01:00
{
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
}
2015-09-11 11:21:57 +02:00
}
2013-05-08 19:57:13 +02:00
if ( string . IsNullOrEmpty ( m_options . Dbpath ))
2025-01-28 08:58:26 +01:00
m_options . Dbpath = CLIDatabaseLocator . GetDatabasePathForCLI ( m_backendUrl , 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
}
2024-10-24 15:56:39 +02:00
private async Task ApplySecretProvider ( CancellationToken cancellationToken )
{
2025-01-28 08:54:50 +01:00
var args = new [] { new Library . Utility . Uri ( m_backendUrl ) };
2024-11-04 15:57:56 +01:00
await SecretProviderHelper . ApplySecretProviderAsync ([], args , m_options . RawOptions , TempFolder . SystemTempPath , SecretProvider , cancellationToken );
2024-10-24 15:56:39 +02:00
// Write back the backend argument, if it was modified by the secret provider
2025-01-28 08:54:50 +01:00
m_backendUrl = args [ 0 ]. ToString ();
2024-10-24 15:56:39 +02:00
}
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
2025-01-28 13:01:35 +01:00
var 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
2025-01-28 13:01:35 +01:00
var supportedOptions = new Dictionary < string , Library . Interface . ICommandLineArgument >();
2013-02-12 21:43:14 +00:00
//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
2025-01-28 13:01:35 +01:00
var moduleOptions = new List < Duplicati . Library . Interface . ICommandLineArgument >();
var disabledModuleOptions = new Dictionary < string , string >();
2013-02-12 21:43:14 +00:00
2025-01-28 13:01:35 +01:00
foreach ( var m in m_options . LoadedModules )
2013-02-12 21:43:14 +00:00
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
2025-01-28 08:54:50 +01:00
var ext = new Library . Utility . Uri ( m_backendUrl ). QueryParameters ;
2024-04-26 14:32:41 +02:00
foreach ( var k in ext . AllKeys )
2013-05-05 23:46:00 +02:00
ropts [ k ] = ext [ k ];
2013-02-12 21:43:14 +00:00
//Now run through all supported options, and look for deprecated options
2024-04-26 14:32:41 +02:00
foreach ( var l in new IEnumerable < ICommandLineArgument >[] {
2016-04-09 11:45:15 +02:00
m_options . SupportedCommands ,
2025-01-28 08:54:50 +01:00
DynamicLoader . BackendLoader . GetSupportedCommands ( m_backendUrl ),
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
2025-01-28 13:01:35 +01:00
foreach ( var s in ropts . Keys )
2013-02-12 21:43:14 +00:00
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
2025-01-28 13:01:35 +01:00
foreach ( var s in ropts . Keys )
2013-02-12 21:43:14 +00:00
{
2025-01-28 13:01:35 +01:00
if ( supportedOptions . TryGetValue ( s , out var arg ) && arg != null )
2013-02-12 21:43:14 +00:00
{
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
}
}
2021-07-15 17:37:22 +02:00
//Inform the user about the deprecated Tardigrade-Backend. They should switch to Storj DCS instead.
2025-01-28 08:54:50 +01:00
if ( string . Equals ( new Library . Utility . Uri ( m_backendUrl ). 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
2025-02-06 21:53:51 +01:00
//Inform the user about the unmaintained Mega support library
if ( string . Equals ( new Library . Utility . Uri ( m_backendUrl ). Scheme , "mega" , StringComparison . OrdinalIgnoreCase ))
Logging . Log . WriteWarningMessage ( LOGTAG , "MegaUnmaintained" , null , "The Mega support library is currently unmaintained and may not work as expected. Mega has not published an official API so it may break at any moment. Please consider migrating to another backend." );
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
2024-04-26 14:32:41 +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 >();
2024-05-30 01:50:02 +02:00
if ( OperatingSystem . IsWindows () && ( 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 );
}
}
2024-05-30 01:50:02 +02:00
else if ( OperatingSystem . IsWindows () && 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
{
2025-02-21 16:06:55 +01:00
// Check if this is a mounted path
if ( expandedSource . StartsWith ( "@" ))
2025-02-21 13:20:52 +01:00
{
// TODO: If the remote source fails to load,
// this will be an enumeration warning, but will result
// in the backup being recorded without files from the source
2025-02-21 16:06:55 +01:00
// Eventually, this could lead to retention deletion,
// causing the last backup with the data from the source to be deleted
2025-02-21 13:20:52 +01:00
foundAnyPaths = true ;
sources . Add ( expandedSource );
continue ;
}
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 ;
2025-02-24 12:53:48 +01:00
sources = Library . Utility . Utility . GetUniqueItems ( sources , Library . Utility . Utility . ClientFilenameStringComparer , out pathDuplicates ). ToList ();
2018-06-11 08:55:31 +02:00
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 ])
2024-09-18 09:24:55 +02:00
if ( string . Equals ( v , value , StringComparison . OrdinalIgnoreCase ))
2016-09-15 11:39:27 +02:00
{
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 ;
2024-04-26 14:32:41 +02:00
var flags = ( value ?? string . Empty ). ToLowerInvariant (). Split ( new [] { "," }, StringSplitOptions . None ). Select ( flag => flag . Trim ()). Distinct ();
2016-09-15 11:39:27 +02:00
var validFlags = arg . ValidValues ?? new string [ 0 ];
foreach ( var flag in flags )
{
2024-09-18 09:24:55 +02:00
if (! validFlags . Any ( validFlag => string . Equals ( validFlag , flag , StringComparison . OrdinalIgnoreCase )))
2016-09-15 11:39:27 +02:00
{
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
2024-12-18 08:27:59 +01:00
public void Pause ( bool alsoTransfers )
2014-05-15 12:47:16 +02:00
{
2024-12-18 08:27:59 +01:00
var ct = m_currentTaskControl ;
2014-05-15 12:47:16 +02:00
if ( ct != null )
2024-12-18 08:27:59 +01:00
ct . Pause ( alsoTransfers );
2014-05-15 12:47:16 +02:00
}
public void Resume ()
{
2024-12-18 08:27:59 +01:00
var ct = m_currentTaskControl ;
2014-05-15 12:47:16 +02:00
if ( ct != null )
ct . Resume ();
}
2024-12-18 08:27:59 +01:00
public void Stop ()
2014-05-15 12:47:16 +02:00
{
2024-12-18 08:27:59 +01:00
var ct = m_currentTaskControl ;
2020-02-29 16:33:32 -06:00
if ( ct == null )
return ;
Logging . Log . WriteVerboseMessage ( LOGTAG , "CancellationRequested" , "Cancellation Requested" );
2024-12-18 08:27:59 +01:00
ct . Stop ();
2014-05-15 12:47:16 +02:00
}
public void Abort ()
{
2024-12-18 08:27:59 +01:00
m_currentTaskControl ?. Terminate ();
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
}
}