2013-02-12 21:43:14 +00:00
#region Disclaimer / License
2015-01-20 21:44:52 +01:00
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
2016-04-09 11:45:15 +02:00
//
2013-02-12 21:43:14 +00:00
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
2016-04-09 11:45:15 +02:00
//
2013-02-12 21:43:14 +00:00
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
2016-04-09 11:45:15 +02:00
//
2013-02-12 21:43:14 +00:00
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2016-04-09 11:45:15 +02:00
//
2015-09-04 15:04:04 +02:00
using System.Linq ;
2013-02-12 21:43:14 +00:00
#endregion
using System ;
using System.Collections.Generic ;
using System.Text ;
using Duplicati.Library.Utility ;
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>
private 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>
/// Holds various keys that need to be reset after running the task
/// </summary>
private Dictionary < string , string > m_resetKeys = new Dictionary < string , string >();
/// <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
/// <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 )
{
if ( m_messageSink is MultiMessageSink )
(( MultiMessageSink ) m_messageSink ). Append ( sink );
else
m_messageSink = new MultiMessageSink ( m_messageSink , sink );
}
2017-10-30 15:30:43 +01:00
/// <summary>
/// Helper method that expands the users chosen source input paths,
/// and removes duplicate paths
/// </summary>
/// <returns>The expanded and filtered sources.</returns>
/// <param name="inputsources">The paths to use.</param>
2018-03-12 14:07:11 +01:00
public string [] ExpandInputSources ( string [] inputsources , IFilter filter )
2016-09-15 11:39:27 +02:00
{
2017-10-30 15:30:43 +01:00
if ( inputsources == null || inputsources . Length == 0 )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Controller . NoSourceFoldersError , "NoSourceFolders" );
2016-04-09 11:45:15 +02:00
2017-10-30 15:30:43 +01:00
var sources = new List < string >( inputsources . Length );
2013-05-25 16:40:15 +02:00
2017-10-30 15:30:43 +01:00
System . IO . DriveInfo [] drives = null ;
2017-09-10 13:18:49 -07:00
2017-10-30 15:30:43 +01:00
//Make sure they all have the same format and exist
for ( int i = 0 ; i < inputsources . Length ; i ++)
{
List < string > expandedSources = new List < string >();
2013-06-29 12:17:52 +02:00
2017-11-26 10:53:14 -08:00
if ( Library . Utility . Utility . IsClientWindows && ( inputsources [ i ]. StartsWith ( "*:" , StringComparison . Ordinal ) || inputsources [ i ]. StartsWith ( "?:" , StringComparison . Ordinal )))
2016-09-15 11:39:27 +02:00
{
2017-10-30 15:30:43 +01:00
// *: drive paths are only supported on Windows clients
// Lazily load the drive info
drives = drives ?? System . IO . DriveInfo . GetDrives ();
2017-09-14 23:16:07 -07:00
2017-10-30 15:30:43 +01:00
// Replace the drive letter with each available drive
string sourcePath = inputsources [ i ]. Substring ( 1 );
foreach ( System . IO . DriveInfo drive in drives )
2016-09-15 11:39:27 +02:00
{
2017-10-30 15:30:43 +01:00
string expandedSource = drive . Name [ 0 ] + sourcePath ;
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "AddingSourcePathFromWildcard" , @"Adding source path ""{0}"" due to wildcard source path ""{1}""" , expandedSource , inputsources [ i ]);
2017-10-30 15:30:43 +01:00
expandedSources . Add ( expandedSource );
2017-09-14 23:16:07 -07:00
}
2017-10-30 15:30:43 +01:00
}
else if ( Library . Utility . Utility . IsClientWindows && inputsources [ i ]. StartsWith ( @"\\?\Volume{" , StringComparison . OrdinalIgnoreCase ))
{
// 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 ( inputsources [ i ]. Substring ( @"\\?\Volume{" . Length , @"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" . Length ), out volumeGuid ))
2017-09-14 23:16:07 -07:00
{
2017-10-30 15:30:43 +01:00
string driveLetter = Library . Utility . Utility . GetDriveLetterFromVolumeGuid ( volumeGuid );
if (! string . IsNullOrEmpty ( driveLetter ))
2017-09-14 23:16:07 -07:00
{
2017-10-30 15:30:43 +01:00
string expandedSource = driveLetter + inputsources [ i ]. Substring ( @"\\?\Volume{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" . Length );
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "AddingSourceFromGuid" , @"Adding source path ""{0}"" in place of volume guid source path ""{1}""" , expandedSource , inputsources [ i ]);
2017-10-30 15:30:43 +01:00
expandedSources . Add ( expandedSource );
2017-09-10 13:18:49 -07:00
}
2017-09-14 23:16:07 -07:00
else
2017-09-10 13:18:49 -07:00
{
2017-10-30 15:30:43 +01:00
// If we aren't allow to have missing sources, throw an exception indicating we couldn't find a drive where this volume is mounted
2017-09-14 23:16:07 -07:00
if (! m_options . AllowMissingSource )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Controller . SourceVolumeNameNotFoundError ( inputsources [ i ], volumeGuid ), "MissingSourceFolder" );
2017-09-10 13:18:49 -07:00
}
2016-09-15 11:39:27 +02:00
}
2017-09-10 13:18:49 -07:00
else
2016-09-15 11:39:27 +02:00
{
2017-10-30 15:30:43 +01:00
// If we aren't allow to have missing sources, throw an exception indicating we couldn't find this volume
if (! m_options . AllowMissingSource )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Controller . SourceVolumeNameInvalidError ( inputsources [ i ]), "SourceVolumeNameInvalid" );
2016-09-15 11:39:27 +02:00
}
2017-10-30 15:30:43 +01:00
}
else
{
expandedSources . Add ( inputsources [ i ]);
}
2016-04-09 11:45:15 +02:00
2017-10-30 15:30:43 +01:00
bool foundAnyPaths = false ;
foreach ( string expandedSource in expandedSources )
{
string source ;
try
2017-09-10 13:18:49 -07:00
{
2017-10-30 15:30:43 +01:00
source = System . IO . Path . GetFullPath ( expandedSource );
}
catch ( Exception ex )
{
// Note that we use the original source (with the *) in the error
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Controller . InvalidPathError ( expandedSource , ex . Message ), "InputSourceInvalid" , ex );
2017-10-30 15:30:43 +01:00
}
2017-09-10 13:18:49 -07:00
2017-10-30 15:30:43 +01:00
var fi = new System . IO . FileInfo ( source );
var di = new System . IO . DirectoryInfo ( source );
if ( fi . Exists || di . Exists )
{
foundAnyPaths = true ;
2017-09-10 13:18:49 -07:00
2017-10-30 15:30:43 +01:00
if (! fi . Exists )
source = Library . Utility . Utility . AppendDirSeparator ( source );
2016-04-09 11:45:15 +02:00
2017-10-30 15:30:43 +01:00
sources . Add ( source );
2017-09-10 13:18:49 -07:00
}
2016-09-15 11:39:27 +02:00
}
2017-10-30 15:30:43 +01:00
// If no paths were found, and we aren't allowed to have missing sources, throw an error
if (! foundAnyPaths && ! m_options . AllowMissingSource )
throw new System . IO . IOException ( Strings . Controller . SourceIsMissingError ( inputsources [ i ]));
}
2015-11-17 10:08:16 +01:00
2017-10-30 15:30:43 +01:00
//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 ();
2015-11-17 10:08:16 +01:00
2017-10-30 15:30:43 +01:00
foreach ( var pathDuplicate in pathDuplicates )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "RemoveDuplicateSource" , "Removing duplicate source: {0}" , pathDuplicate );
2015-11-17 10:08:16 +01:00
2017-10-30 15:30:43 +01:00
//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-05-16 19:40:51 +02:00
if ( i != j && sources [ i ]. StartsWith ( sources [ j ], Library . Utility . Utility . ClientFilenameStringComparison ) && sources [ i ]. EndsWith ( System . IO . Path . DirectorySeparatorChar . ToString (), Library . Utility . Utility . ClientFilenameStringComparison ))
2017-10-30 15:30:43 +01:00
{
if ( filter != null )
2016-10-09 11:47:36 +02:00
{
2017-10-30 15:30:43 +01:00
bool includes ;
bool excludes ;
2016-10-09 11:47:36 +02:00
2017-10-30 15:30:43 +01:00
FilterExpression . AnalyzeFilters ( filter , out includes , out excludes );
2016-10-09 11:47:36 +02:00
2017-10-30 15:30:43 +01:00
// If there are no excludes, there is no need to keep the folder as a filter
if ( excludes )
{
2018-03-12 14:07:11 +01:00
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 ]);
2017-10-30 15:30:43 +01:00
filter = Library . Utility . JoinedFilterExpression . Join ( new FilterExpression ( sources [ i ]), filter );
2015-11-17 10:08:16 +01:00
}
else
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "RemovingSubfolderSource" , "Removing source \"{0}\" because it is a subfolder or subfile of \"{1}\"" , sources [ i ], sources [ j ]);
2013-06-29 12:17:52 +02:00
}
2017-10-30 15:30:43 +01:00
else
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "RemovingSubfolderSource" , "Removing source \"{0}\" because it is a subfolder or subfile of \"{1}\"" , sources [ i ], sources [ j ]);
2017-10-30 15:30:43 +01:00
sources . RemoveAt ( i );
i --;
break ;
}
return sources . ToArray ();
}
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 );
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 ))
2018-03-12 14:07:11 +01:00
h . Run ( ExpandInputSources ( inputsources , filter ), filter );
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
Library . UsageReporter . Reporter . Report ( "RESTORE_FILECOUNT" , result . FilesRestored );
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
2013-05-30 21:54:43 +02:00
public Duplicati . Library . Interface . IListResults List ( Library . Utility . IFilter filter = null )
{
return List (( IEnumerable < string >) null , filter );
}
2013-05-05 17:54:59 +02:00
2013-05-30 21:54:43 +02:00
public Duplicati . Library . Interface . IListResults List ( string filterstring , Library . Utility . IFilter filter = null )
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
2013-05-30 21:54:43 +02:00
public Duplicati . Library . Interface . IListResults List ( IEnumerable < string > filterstrings , Library . Utility . IFilter filter = null )
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
2013-05-30 21:54:43 +02:00
public Duplicati . Library . Interface . IListResults ListControlFiles ( IEnumerable < string > filterstrings = null , Library . Utility . IFilter filter = null )
{
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 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 ))
{
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 ();
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
{
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 20:19:46 +02:00
public Duplicati . Library . Interface . IRecreateDatabaseResults RecreateDatabase ( string targetpath , Library . Utility . IFilter filter = null )
2013-05-25 16:40:15 +02:00
{
var t = new string [] { string . IsNullOrEmpty ( targetpath ) ? m_options . Dbpath : targetpath };
2015-04-08 20:19:46 +02:00
var filelistfilter = Operation . RestoreHandler . FilterNumberedFilelist ( m_options . Time , m_options . Version );
2015-12-27 00:16:45 +01:00
return RunAction ( new RecreateDatabaseResults (), ref t , ref filter , ( result ) => {
2013-05-25 16:40:15 +02:00
using ( var h = new Operation . RecreateDatabaseHandler ( m_backend , m_options , result ))
2015-04-08 20:19:46 +02:00
h . Run ( t [ 0 ], filter , filelistfilter );
});
}
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 ) =>
{
new Operation . TestFilterHandler ( m_options , result ). Run ( ExpandInputSources ( paths , filter ), filter );
});
}
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 ()
{
return RunAction ( new VacuumResult (), result => {
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 );
2016-09-28 09:55:04 +02:00
2014-05-15 12:47:16 +02:00
OnOperationComplete ( result );
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
2017-01-11 23:35:42 +01:00
try { ( result as BasicResults ). OperationProgressUpdater . UpdatePhase ( OperationPhase . Error ); }
catch { }
2016-04-09 11:45:15 +02:00
2017-01-09 23:27:56 +01:00
OnOperationComplete ( ex );
2016-09-28 09:55:04 +02:00
2018-03-12 14:07:11 +01:00
Logging . Log . WriteErrorMessage ( LOGTAG , "FailedOperation" , ex , Strings . Controller . FailedOperationMessage ( m_options . MainAction , ex . Message ));
2017-01-11 23:35:42 +01:00
throw ;
}
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
2016-09-15 11:39:27 +02:00
private void OnOperationComplete ( object result )
{
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 )
if ( mx . Key && mx . Value is Duplicati . Library . Interface . IGenericCallbackModule )
try { (( Duplicati . Library . Interface . IGenericCallbackModule ) mx . Value ). OnFinish ( result ); }
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 )
if ( mx . Key && mx . Value is IDisposable )
try { (( IDisposable ) mx . Value ). 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
}
2015-09-04 15:04:04 +02:00
if ( m_resetKeys != null )
{
var keys = m_resetKeys . Keys . ToArray ();
foreach ( var k in keys )
{
try
{
Environment . SetEnvironmentVariable ( k , m_resetKeys [ k ]);
2016-04-09 11:45:15 +02:00
}
2015-09-04 15:04:04 +02:00
catch { }
2016-04-09 11:45:15 +02:00
2015-09-04 15:04:04 +02:00
m_resetKeys . Remove ( k );
}
}
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 )
m_options . LoadedModules . Add ( new KeyValuePair < bool , Library . Interface . IGenericModule >( Array . IndexOf < string >( m_options . DisableModules , m . Key . ToLower ()) < 0 && ( m . LoadAsDefault || Array . IndexOf < string >( m_options . EnableModules , m . Key . ToLower ()) >= 0 ), 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-01-12 09:43:07 -05:00
if ( mx . Value . ToString (). ToLower (). Contains ( "runscript" ))
{
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
2016-10-15 16:18:26 +02:00
if ( mx . Value is Library . Interface . IGenericSourceModule )
2016-10-15 23:55:50 +02:00
{
2016-11-02 00:17:49 +01:00
var sourcemodule = ( Library . Interface . IGenericSourceModule ) mx . Value ;
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
2013-02-12 21:43:14 +00:00
if ( mx . Value is Library . Interface . IGenericCallbackModule )
2013-05-25 16:40:15 +02:00
(( Library . Interface . IGenericCallbackModule ) mx . Value ). 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
if ( Library . Utility . Utility . IsClientLinux )
{
m_resetKeys [ "TMPDIR" ] = Environment . GetEnvironmentVariable ( "TMPDIR" );
Environment . SetEnvironmentVariable ( "TMPDIR" , m_options . TempDir );
}
else
{
m_resetKeys [ "TMP" ] = Environment . GetEnvironmentVariable ( "TMP" );
m_resetKeys [ "TEMP" ] = Environment . GetEnvironmentVariable ( "TEMP" );
Environment . SetEnvironmentVariable ( "TMP" , m_options . TempDir );
Environment . SetEnvironmentVariable ( "TEMP" , m_options . TempDir );
}
}
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" );
}
if ( m_options . RetentionPolicy . Count () > 0 )
{
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 )
{
if ( supportedOptions . ContainsKey ( a . Name ) && Array . IndexOf ( Options . KnownDuplicates , a . Name . ToLower ()) < 0 )
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 )
{
if ( supportedOptions . ContainsKey ( s ) && Array . IndexOf ( Options . KnownDuplicates , s . ToLower ()) < 0 )
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 ))
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "7zModuleHasIssues" , null , "The 7z compression module has known issues and should only be used for experimental purposes" );
2018-02-22 17:11:07 +01:00
2013-02-12 21:43:14 +00:00
//TODO: Based on the action, see if all options are relevant
}
/// <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>
public 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 )
{
long l ;
if (! long . TryParse ( value , out l ))
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
}
}
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 ();
}
public void Stop ()
{
var ct = m_currentTask ;
if ( ct != null )
ct . Stop ();
}
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 )
t . Abort ();
}
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 ; }
}
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
}
}