2008-09-13 16:24:43 +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
2008-09-13 16:24:43 +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.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System ;
using System.Collections.Generic ;
using System.Text ;
2013-05-13 22:32:05 +02:00
using System.Linq ;
2014-08-17 15:45:44 +02:00
using Duplicati.Library.Localization.Short ;
2017-09-13 22:21:12 -07:00
using Duplicati.Library.Utility ;
2017-04-04 16:56:34 +02:00
using System.IO ;
2008-09-13 16:24:43 +00:00
namespace Duplicati.CommandLine
{
2015-04-10 22:29:52 +02:00
public class Program
2008-09-13 16:24:43 +00:00
{
2017-04-04 16:56:34 +02:00
public static bool FROM_COMMANDLINE = false ;
2014-06-26 22:37:58 +02:00
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static int Main ( string [] args )
{
2017-03-08 14:50:59 +01:00
Duplicati . Library . AutoUpdater . UpdaterManager . IgnoreWebrootFolder = true ;
2014-07-02 20:46:50 +02:00
return Duplicati . Library . AutoUpdater . UpdaterManager . RunFromMostRecent ( typeof ( Program ). GetMethod ( "RealMain" ), args );
2014-06-26 22:37:58 +02:00
}
2014-07-09 22:15:27 +02:00
public static int RealMain ( string [] args )
2008-09-13 16:24:43 +00:00
{
2015-12-22 00:35:00 +01:00
Library . UsageReporter . Reporter . Initialize ();
2017-04-04 16:56:34 +02:00
FROM_COMMANDLINE = true ;
2010-05-24 09:00:03 +00:00
try
{
2010-09-01 21:06:33 +00:00
//If we are on Windows, append the bundled "win-tools" programs to the search path
//We add it last, to allow the user to override with other versions
2016-10-11 20:00:50 +02:00
if ( Library . Utility . Utility . IsClientWindows )
2010-09-01 21:06:33 +00:00
{
2012-02-03 07:34:25 +00:00
string wintools = System . IO . Path . Combine ( System . IO . Path . GetDirectoryName ( System . Reflection . Assembly . GetExecutingAssembly (). Location ), "win-tools" );
2010-09-01 21:06:33 +00:00
Environment . SetEnvironmentVariable ( "PATH" ,
Environment . GetEnvironmentVariable ( "PATH" ) +
System . IO . Path . PathSeparator . ToString () +
2012-02-03 07:34:25 +00:00
wintools +
System . IO . Path . PathSeparator . ToString () +
System . IO . Path . Combine ( wintools , "gpg" ) //GPG needs to be in a subfolder for wrapping reasons
2010-09-01 21:06:33 +00:00
);
}
2017-04-04 16:56:34 +02:00
return RunCommandLine ( Console . Out , Console . Error , c => { }, args );
}
finally
{
Library . UsageReporter . Reporter . ShutDown ();
}
}
private static Dictionary < string , Func < TextWriter , Action < Library . Main . Controller >, List < string >, Dictionary < string , string >, Library . Utility . IFilter , int >> CommandMap
{
get
{
2017-09-18 23:23:45 -06:00
var knownCommands = new Dictionary < string , Func < TextWriter , Action < Library . Main . Controller >, List < string >, Dictionary < string , string >, Library . Utility . IFilter , int >>( StringComparer . OrdinalIgnoreCase );
2017-04-04 16:56:34 +02:00
knownCommands [ "help" ] = Commands . Help ;
knownCommands [ "example" ] = Commands . Examples ;
knownCommands [ "examples" ] = Commands . Examples ;
knownCommands [ "find" ] = Commands . List ;
knownCommands [ "list" ] = Commands . List ;
knownCommands [ "delete" ] = Commands . Delete ;
knownCommands [ "backup" ] = Commands . Backup ;
knownCommands [ "restore" ] = Commands . Restore ;
knownCommands [ "repair" ] = Commands . Repair ;
knownCommands [ "purge" ] = Commands . PurgeFiles ;
knownCommands [ "list-broken-files" ] = Commands . ListBrokenFiles ;
knownCommands [ "purge-broken-files" ] = Commands . PurgeBrokenFiles ;
knownCommands [ "compact" ] = Commands . Compact ;
knownCommands [ "create-report" ] = Commands . CreateBugReport ;
knownCommands [ "compare" ] = Commands . ListChanges ;
knownCommands [ "test" ] = Commands . Test ;
knownCommands [ "verify" ] = Commands . Test ;
knownCommands [ "test-filters" ] = Commands . TestFilters ;
knownCommands [ "test-filter" ] = Commands . TestFilters ;
knownCommands [ "affected" ] = Commands . Affected ;
2017-08-04 11:24:18 +01:00
knownCommands [ "vacuum" ] = Commands . Vacuum ;
2017-04-04 16:56:34 +02:00
knownCommands [ "system-info" ] = Commands . SystemInfo ;
knownCommands [ "systeminfo" ] = Commands . SystemInfo ;
knownCommands [ "send-mail" ] = Commands . SendMail ;
return knownCommands ;
}
}
public static IEnumerable < string > SupportedCommands { get { return CommandMap . Keys ; } }
public static int RunCommandLine ( TextWriter outwriter , TextWriter errwriter , Action < Library . Main . Controller > setup , string [] args )
{
bool verboseErrors = false ;
bool verbose = false ;
try
{
List < string > cargs = new List < string >( args );
var tmpparsed = Library . Utility . FilterCollector . ExtractOptions ( cargs );
var options = tmpparsed . Item1 ;
var filter = tmpparsed . Item2 ;
verboseErrors = Library . Utility . Utility . ParseBoolOption ( options , "debug-output" );
verbose = Library . Utility . Utility . ParseBoolOption ( options , "verbose" );
2017-09-18 23:23:45 -06:00
if ( cargs . Count == 1 && string . Equals ( cargs [ 0 ], "changelog" , StringComparison . OrdinalIgnoreCase ))
2014-07-09 22:40:11 +02:00
{
var path = System . IO . Path . Combine ( System . IO . Path . GetDirectoryName ( System . Reflection . Assembly . GetExecutingAssembly (). Location ), "changelog.txt" );
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( System . IO . File . ReadAllText ( path ));
2014-07-09 22:40:11 +02:00
return 0 ;
}
2012-03-17 12:00:32 +00:00
foreach ( string internaloption in Library . Main . Options . InternalOptions )
if ( options . ContainsKey ( internaloption ))
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( Strings . Program . InternalOptionUsedError ( internaloption ));
2012-08-07 19:36:49 +00:00
return 200 ;
2012-03-17 12:00:32 +00:00
}
2016-12-01 23:16:15 +01:00
2014-05-12 11:47:28 +02:00
// Probe for "help" to avoid extra processing
2017-09-18 23:23:45 -06:00
bool isHelp = cargs . Count == 0 || ( cargs . Count >= 1 && string . Equals ( cargs [ 0 ], "help" , StringComparison . OrdinalIgnoreCase ));
2014-05-12 11:47:28 +02:00
if (! isHelp && (( options . ContainsKey ( "parameters-file" ) && ! string . IsNullOrEmpty ( "parameters-file" )) || ( options . ContainsKey ( "parameter-file" ) && ! string . IsNullOrEmpty ( "parameter-file" )) || ( options . ContainsKey ( "parameterfile" ) && ! string . IsNullOrEmpty ( "parameterfile" ))))
2013-01-22 16:12:24 +00:00
{
string filename ;
if ( options . ContainsKey ( "parameters-file" ) && ! string . IsNullOrEmpty ( "parameters-file" ))
{
filename = options [ "parameters-file" ];
options . Remove ( "parameters-file" );
}
2015-09-05 09:14:21 +02:00
else if ( options . ContainsKey ( "parameter-file" ) && ! string . IsNullOrEmpty ( "parameter-file" ))
2013-01-22 16:12:24 +00:00
{
filename = options [ "parameter-file" ];
options . Remove ( "parameter-file" );
}
2015-09-05 09:14:21 +02:00
else
{
filename = options [ "parameterfile" ];
options . Remove ( "parameterfile" );
}
2013-01-22 16:12:24 +00:00
2017-04-04 16:56:34 +02:00
if (! ReadOptionsFromFile ( outwriter , filename , ref filter , cargs , options ))
2013-01-22 16:12:24 +00:00
return 100 ;
}
2012-03-17 12:00:32 +00:00
2013-08-24 20:59:33 +02:00
string command ;
if ( cargs . Count > 0 )
{
command = cargs [ 0 ];
cargs . RemoveAt ( 0 );
}
else
command = "help" ;
2016-12-01 23:16:15 +01:00
2014-05-12 11:47:28 +02:00
// Update probe for help
2017-09-18 23:23:45 -06:00
isHelp = string . Equals ( command , "help" , StringComparison . OrdinalIgnoreCase );
2010-05-12 08:41:54 +00:00
2014-05-12 11:47:28 +02:00
// Skip the env read if the command is help, otherwise we may report weirdness
if (! isHelp )
{
if (! options . ContainsKey ( "passphrase" ))
if (! string . IsNullOrEmpty ( System . Environment . GetEnvironmentVariable ( "PASSPHRASE" )))
options [ "passphrase" ] = System . Environment . GetEnvironmentVariable ( "PASSPHRASE" );
2016-12-01 23:16:15 +01:00
2014-05-12 11:47:28 +02:00
if (! options . ContainsKey ( "auth-password" ))
if (! string . IsNullOrEmpty ( System . Environment . GetEnvironmentVariable ( "AUTH_PASSWORD" )))
options [ "auth-password" ] = System . Environment . GetEnvironmentVariable ( "AUTH_PASSWORD" );
2016-12-01 23:16:15 +01:00
2014-05-12 11:47:28 +02:00
if (! options . ContainsKey ( "auth-username" ))
if (! string . IsNullOrEmpty ( System . Environment . GetEnvironmentVariable ( "AUTH_USERNAME" )))
options [ "auth-username" ] = System . Environment . GetEnvironmentVariable ( "AUTH_USERNAME" );
}
2016-12-01 23:16:15 +01:00
2017-04-04 16:56:34 +02:00
var knownCommands = CommandMap ;
2017-01-09 23:21:00 +01:00
2014-05-12 11:47:28 +02:00
if (! isHelp && verbose )
2013-05-30 21:18:03 +02:00
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Input command: {0}" , command );
2016-12-01 23:16:15 +01:00
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Input arguments: " );
2016-12-01 23:16:15 +01:00
foreach ( var a in cargs )
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "\t{0}" , a );
outwriter . WriteLine ();
2016-12-01 23:16:15 +01:00
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Input options: " );
2016-12-01 23:16:15 +01:00
foreach ( var n in options )
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "{0}: {1}" , n . Key , n . Value );
outwriter . WriteLine ();
2013-05-30 21:18:03 +02:00
}
2014-08-06 10:30:46 +02:00
2016-12-01 23:16:15 +01:00
Duplicati . Library . Utility . TempFile . RemoveOldApplicationTempFiles (( path , ex ) =>
{
2014-08-06 10:30:46 +02:00
if ( verbose )
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( string . Format ( "Failed to delete temp file: {0}" , path ));
2014-08-06 10:30:46 +02:00
});
2014-08-17 15:45:44 +02:00
var autoupdate = Library . Utility . Utility . ParseBoolOption ( options , "auto-update" );
options . Remove ( "auto-update" );
2013-05-02 17:24:38 +02:00
if ( knownCommands . ContainsKey ( command ))
2009-03-21 12:46:54 +00:00
{
2017-04-04 16:56:34 +02:00
var res = knownCommands [ command ]( outwriter , setup , cargs , options , filter );
2014-08-17 15:45:44 +02:00
2017-04-04 16:56:34 +02:00
if ( autoupdate && FROM_COMMANDLINE )
2014-08-17 15:45:44 +02:00
{
var update = Library . AutoUpdater . UpdaterManager . LastUpdateCheckVersion ;
if ( update == null )
update = Library . AutoUpdater . UpdaterManager . CheckForUpdate ();
if ( update != null && update . Version != Library . AutoUpdater . UpdaterManager . SelfVersion . Version )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Found update \"{0}\", downloading ..." , update . Displayname );
2014-08-17 15:45:44 +02:00
long lastpg = 0 ;
2016-12-01 23:16:15 +01:00
Library . AutoUpdater . UpdaterManager . DownloadAndUnpackUpdate ( update , f =>
{
var npg = ( long )( f * 100 );
2014-08-17 15:45:44 +02:00
if ( Math . Abs ( npg - lastpg ) >= 5 || ( npg == 100 && lastpg != 100 ))
{
lastpg = npg ;
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Downloading {0}% ..." , npg );
2014-08-17 15:45:44 +02:00
}
});
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Update \"{0}\" ({1}) installed, using on next launch" , update . Displayname , update . Version );
2014-08-17 15:45:44 +02:00
}
}
return res ;
2009-03-21 12:46:54 +00:00
}
2010-05-24 09:00:03 +00:00
else
{
2017-04-04 16:56:34 +02:00
Commands . PrintInvalidCommand ( outwriter , command , cargs );
2013-05-02 17:24:38 +02:00
return 200 ;
2010-05-24 09:00:03 +00:00
}
2009-03-21 12:46:54 +00:00
}
2010-05-24 09:00:03 +00:00
catch ( Exception ex )
2009-01-14 16:19:54 +00:00
{
2015-12-22 00:35:00 +01:00
Library . UsageReporter . Reporter . Report ( ex );
2010-05-24 09:00:03 +00:00
while ( ex is System . Reflection . TargetInvocationException && ex . InnerException != null )
ex = ex . InnerException ;
2009-01-14 16:19:54 +00:00
2017-01-09 11:35:38 +01:00
if ( ex is Duplicati . Library . Interface . UserInformationException && ! verboseErrors )
2010-05-24 09:00:03 +00:00
{
2017-04-04 16:56:34 +02:00
errwriter . WriteLine ();
errwriter . WriteLine ( ex . Message );
2017-01-09 11:35:38 +01:00
}
else if (!( ex is Library . Interface . CancelException ))
{
2017-04-04 16:56:34 +02:00
errwriter . WriteLine ();
2017-11-26 15:24:53 -08:00
errwriter . WriteLine ( ex );
2010-05-24 09:00:03 +00:00
}
2010-01-30 20:37:27 +00:00
else
2009-01-14 16:19:54 +00:00
{
2017-04-04 16:56:34 +02:00
errwriter . WriteLine ( Strings . Program . UnhandledException ( ex . ToString ()));
2009-01-14 16:19:54 +00:00
2010-09-17 16:20:16 +00:00
while ( ex . InnerException != null )
{
ex = ex . InnerException ;
2017-04-04 16:56:34 +02:00
errwriter . WriteLine ();
errwriter . WriteLine ( Strings . Program . UnhandledInnerException ( ex . ToString ()));
2010-09-17 16:20:16 +00:00
}
2009-01-14 16:19:54 +00:00
}
2012-08-07 19:36:49 +00:00
//Error = 100
return 100 ;
2009-01-14 16:19:54 +00:00
}
2008-09-13 16:24:43 +00:00
}
2014-06-30 11:31:06 +02:00
2017-04-04 16:56:34 +02:00
public static IList < Library . Interface . ICommandLineArgument > SupportedOptions
2013-01-22 16:12:24 +00:00
{
get
{
return new List < Library . Interface . ICommandLineArgument >( new Library . Interface . ICommandLineArgument [] {
2015-12-27 00:16:45 +01:00
new Library . Interface . CommandLineArgument ( "parameters-file" , Library . Interface . CommandLineArgument . ArgumentType . Path , Strings . Program . ParametersFileOptionShort , Strings . Program . ParametersFileOptionLong2 , "" , new string [] { "parameter-file" , "parameterfile" }),
2013-05-13 22:32:05 +02:00
new Library . Interface . CommandLineArgument ( "include" , Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . IncludeShort , Strings . Program . IncludeLong ),
new Library . Interface . CommandLineArgument ( "exclude" , Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . ExcludeShort , Strings . Program . ExcludeLong ),
2013-05-30 21:54:43 +02:00
new Library . Interface . CommandLineArgument ( "control-files" , Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . ControlFilesOptionShort , Strings . Program . ControlFilesOptionLong , "false" ),
2013-08-18 23:16:58 +02:00
new Library . Interface . CommandLineArgument ( "quiet-console" , Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . QuietConsoleOptionShort , Strings . Program . QuietConsoleOptionLong , "false" ),
2014-08-17 15:45:44 +02:00
new Library . Interface . CommandLineArgument ( "auto-update" , Library . Interface . CommandLineArgument . ArgumentType . Boolean , LC . L ( "Toggle automatic updates" ), LC . L ( "Set this option if you prefer to have the commandline version automatically update" ), "false" ),
2013-01-22 16:12:24 +00:00
});
}
}
2011-07-23 14:57:32 +00:00
2017-04-04 16:56:34 +02:00
private static bool ReadOptionsFromFile ( TextWriter outwriter , string filename , ref Library . Utility . IFilter filter , List < string > cargs , Dictionary < string , string > options )
2013-01-22 16:12:24 +00:00
{
try
{
2015-09-05 09:14:21 +02:00
List < string > fargs = new List < string >( Library . Utility . Utility . ReadFileWithDefaultEncoding ( Library . Utility . Utility . ExpandEnvironmentVariables ( filename )). Replace ( "\r\n" , "\n" ). Replace ( "\r" , "\n" ). Split ( new String [] { "\n" }, StringSplitOptions . RemoveEmptyEntries ). Select ( x => x . Trim ()));
2013-05-13 22:32:05 +02:00
var newsource = new List < string >();
string newtarget = null ;
2015-12-27 00:16:45 +01:00
string prependfilter = null ;
string appendfilter = null ;
string replacefilter = null ;
2013-01-22 16:12:24 +00:00
2015-08-21 21:59:12 +02:00
var tmpparsed = Library . Utility . FilterCollector . ExtractOptions ( fargs , ( key , value ) => {
2015-12-27 00:16:45 +01:00
if ( key . Equals ( "source" , StringComparison . OrdinalIgnoreCase ))
2016-09-15 11:39:27 +02:00
{
newsource . Add ( value );
return false ;
}
2015-12-27 00:16:45 +01:00
else if ( key . Equals ( "target" , StringComparison . OrdinalIgnoreCase ))
2016-09-15 11:39:27 +02:00
{
newtarget = value ;
return false ;
}
2015-12-27 00:16:45 +01:00
else if ( key . Equals ( "append-filter" , StringComparison . OrdinalIgnoreCase ))
{
appendfilter = value ;
return false ;
}
else if ( key . Equals ( "prepend-filter" , StringComparison . OrdinalIgnoreCase ))
{
prependfilter = value ;
return false ;
}
2015-12-27 22:28:03 +01:00
else if ( key . Equals ( "replace-filter" , StringComparison . OrdinalIgnoreCase ))
2015-12-27 00:16:45 +01:00
{
replacefilter = value ;
return false ;
}
2016-09-15 11:39:27 +02:00
return true ;
});
2013-05-13 22:32:05 +02:00
var opt = tmpparsed . Item1 ;
var newfilter = tmpparsed . Item2 ;
2013-01-22 16:12:24 +00:00
// If the user specifies parameters-file, all filters must be in the file.
// Allowing to specify some filters on the command line could result in wrong filter ordering
2013-05-13 22:32:05 +02:00
if (! filter . Empty && ! newfilter . Empty )
2017-01-09 11:35:38 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Program . FiltersCannotBeUsedWithFileError2 );
2013-01-22 16:12:24 +00:00
2016-09-15 11:39:27 +02:00
if (! newfilter . Empty )
filter = newfilter ;
2013-01-22 16:12:24 +00:00
2015-12-27 00:16:45 +01:00
if (! string . IsNullOrWhiteSpace ( prependfilter ))
filter = Library . Utility . FilterExpression . Combine ( Library . Utility . FilterExpression . Deserialize ( prependfilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString ()}, StringSplitOptions . RemoveEmptyEntries )), filter );
if (! string . IsNullOrWhiteSpace ( appendfilter ))
filter = Library . Utility . FilterExpression . Combine ( filter , Library . Utility . FilterExpression . Deserialize ( appendfilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString ()}, StringSplitOptions . RemoveEmptyEntries )));
if (! string . IsNullOrWhiteSpace ( replacefilter ))
filter = Library . Utility . FilterExpression . Deserialize ( replacefilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString ()}, StringSplitOptions . RemoveEmptyEntries ));
2013-01-22 16:12:24 +00:00
foreach ( KeyValuePair < String , String > keyvalue in opt )
2013-05-13 22:32:05 +02:00
options [ keyvalue . Key ] = keyvalue . Value ;
if (! string . IsNullOrEmpty ( newtarget ))
2016-09-15 11:39:27 +02:00
{
if ( cargs . Count <= 1 )
cargs . Add ( newtarget );
else
cargs [ 1 ] = newtarget ;
}
2015-09-17 20:03:26 +02:00
2017-09-18 23:23:45 -06:00
if ( cargs . Count >= 1 && cargs [ 0 ]. Equals ( "backup" , StringComparison . OrdinalIgnoreCase ))
2016-09-15 11:39:27 +02:00
cargs . AddRange ( newsource );
2015-09-17 20:03:26 +02:00
else if ( newsource . Count > 0 && Library . Utility . Utility . ParseBoolOption ( options , "verbose" ))
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( Strings . Program . SkippingSourceArgumentsOnNonBackupOperation );
2013-01-22 16:12:24 +00:00
return true ;
}
catch ( Exception e )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( Strings . Program . FailedToParseParametersFileError ( filename , e . Message ));
2013-01-22 16:12:24 +00:00
return false ;
}
2011-07-23 14:57:32 +00:00
}
2008-09-13 16:24:43 +00:00
}
}