2024-02-28 15:45:30 +01:00
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
2013-05-02 17:24:38 +02:00
using System ;
2013-05-11 12:03:15 +02:00
using System.Linq ;
2013-05-02 17:24:38 +02:00
using System.Collections.Generic ;
2017-04-04 16:56:34 +02:00
using System.IO ;
2020-09-04 01:01:56 -07:00
using System.Text.RegularExpressions ;
2021-05-26 09:39:34 -07:00
using System.Threading ;
using System.Threading.Tasks ;
2020-01-25 17:07:02 -08:00
using Duplicati.Library.Common.IO ;
2020-09-06 21:30:46 -07:00
using Duplicati.Library.Utility ;
2020-01-25 17:07:02 -08:00
2013-05-02 17:24:38 +02:00
namespace Duplicati.CommandLine
{
public static class Commands
{
2013-08-23 22:18:13 +02:00
private class PeriodicOutput : IDisposable
{
public event Action < float , long , long , bool > WriteOutput ;
2018-05-23 21:18:01 -07:00
private readonly System . Threading . ManualResetEvent m_readyEvent ;
private readonly System . Threading . ManualResetEvent m_finishEvent ;
private readonly ConsoleOutput m_output ;
private readonly TimeSpan m_updateFrequency ;
2021-05-26 09:39:34 -07:00
private Task m_task ;
private readonly CancellationTokenSource m_cancellationTokenSource ;
2018-11-02 17:45:00 +01:00
2013-08-23 22:18:13 +02:00
public PeriodicOutput ( ConsoleOutput messageSink , TimeSpan updateFrequency )
{
m_output = messageSink ;
m_readyEvent = new System . Threading . ManualResetEvent ( false );
m_finishEvent = new System . Threading . ManualResetEvent ( false );
m_updateFrequency = updateFrequency ;
2021-05-26 09:39:34 -07:00
m_cancellationTokenSource = new CancellationTokenSource ();
m_task = Task . Run (() => this . ThreadMain ( m_cancellationTokenSource . Token ), m_cancellationTokenSource . Token );
2013-08-23 22:18:13 +02:00
}
2018-11-02 17:45:00 +01:00
2013-08-23 22:18:13 +02:00
public void SetReady () { m_readyEvent . Set (); }
public void SetFinished () { m_finishEvent . Set (); }
2018-11-02 17:45:00 +01:00
2013-08-23 22:18:13 +02:00
public bool Join ( TimeSpan wait )
{
2021-05-26 09:39:34 -07:00
if ( m_task != null )
return this . m_task . Wait ( wait );
2018-11-02 17:45:00 +01:00
2013-08-23 22:18:13 +02:00
return true ;
}
2018-11-02 17:45:00 +01:00
2021-05-26 09:39:34 -07:00
private void ThreadMain ( CancellationToken cancellationToken )
2013-08-23 22:18:13 +02:00
{
m_readyEvent . WaitOne ();
if ( m_finishEvent . WaitOne ( TimeSpan . FromMilliseconds ( 10 ), true ))
return ;
2018-11-02 17:45:00 +01:00
2013-08-23 22:18:13 +02:00
var last_count = - 1L ;
var finished = false ;
2018-11-02 17:45:00 +01:00
2021-05-26 09:39:34 -07:00
while (! cancellationToken . IsCancellationRequested )
2013-08-23 22:18:13 +02:00
{
float progress ;
long filesprocessed ;
long filesizeprocessed ;
long filecount ;
long filesize ;
bool counting ;
2019-04-16 21:35:02 -07:00
m_output . OperationProgress . UpdateOverall ( out _ , out progress , out filesprocessed , out filesizeprocessed , out filecount , out filesize , out counting );
2018-11-02 17:45:00 +01:00
2013-08-23 22:18:13 +02:00
var files = Math . Max ( 0 , filecount - filesprocessed );
var size = Math . Max ( 0 , filesize - filesizeprocessed );
2018-11-02 17:45:00 +01:00
2013-08-24 13:43:33 +02:00
if ( finished )
{
files = 0 ;
size = 0 ;
}
else if ( size > 0 )
2013-08-24 21:16:24 +02:00
files = Math . Max ( 1 , files );
2018-11-02 17:45:00 +01:00
2013-08-24 13:43:33 +02:00
if ( last_count < 0 || files != last_count )
2013-08-23 22:18:13 +02:00
if ( WriteOutput != null )
WriteOutput ( progress , files , size , counting );
2018-11-02 17:45:00 +01:00
2013-08-23 22:18:13 +02:00
if (! counting )
last_count = files ;
2018-11-02 17:45:00 +01:00
2013-08-23 22:18:13 +02:00
if ( finished )
return ;
2018-11-02 17:45:00 +01:00
finished = m_finishEvent . WaitOne ( m_updateFrequency , true );
2013-08-23 22:18:13 +02:00
}
}
2018-11-02 17:45:00 +01:00
2013-08-23 22:18:13 +02:00
public void Dispose ()
{
2021-05-26 09:39:34 -07:00
if ( m_task != null )
2013-08-23 22:18:13 +02:00
{
try
{
m_finishEvent . Set ();
m_readyEvent . Set ();
2018-11-02 17:45:00 +01:00
2021-05-26 09:39:34 -07:00
if ( m_task != null )
2013-08-23 22:18:13 +02:00
{
2021-05-26 09:39:34 -07:00
m_cancellationTokenSource . Cancel ();
m_task . Wait ( 500 );
2013-08-23 22:18:13 +02:00
}
}
finally
{
2021-05-26 09:39:34 -07:00
m_task = null ;
2013-08-23 22:18:13 +02:00
}
}
}
}
2016-09-23 10:47:59 +02:00
2017-04-04 16:56:34 +02:00
public static int Examples ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2016-09-23 14:11:53 +02:00
{
2017-04-04 16:56:34 +02:00
Duplicati . CommandLine . Help . PrintUsage ( outwriter , "example" , options );
2016-09-23 14:11:53 +02:00
return 0 ;
}
2018-11-02 17:45:00 +01:00
2017-04-04 16:56:34 +02:00
public static int Help ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2013-05-02 17:24:38 +02:00
{
2018-05-23 20:09:03 +02:00
Duplicati . CommandLine . Help . PrintUsage ( outwriter , args . Count > 1 ? args [ 1 ] : "help" , options );
2013-05-02 17:24:38 +02:00
return 0 ;
}
2017-04-04 16:56:34 +02:00
public static int Affected ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2014-08-19 20:27:14 +02:00
{
2018-03-12 14:07:11 +01:00
var fullresult = Duplicati . Library . Utility . Utility . ParseBoolOption ( options , "full-result" );
2014-08-19 20:27:14 +02:00
var backend = args [ 0 ];
args . RemoveAt ( 0 );
if ( args . Count == 0 )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "You must specify at least a remote filename" );
2014-08-19 20:27:14 +02:00
return 200 ;
}
// Support for not adding the --auth-username if possible
string dbpath ;
options . TryGetValue ( "dbpath" , out dbpath );
if ( string . IsNullOrEmpty ( dbpath ))
{
dbpath = Library . Main . DatabaseLocator . GetDatabasePath ( backend , new Duplicati . Library . Main . Options ( options ), false , true );
if ( dbpath != null )
options [ "dbpath" ] = dbpath ;
}
// Don't ask for passphrase if we have a local db
if (! string . IsNullOrEmpty ( dbpath ) && System . IO . File . Exists ( dbpath ) && ! options . ContainsKey ( "no-encryption" ) && ! Duplicati . Library . Utility . Utility . ParseBoolOption ( options , "no-local-db" ))
{
string passphrase ;
options . TryGetValue ( "passphrase" , out passphrase );
if ( string . IsNullOrEmpty ( passphrase ))
options [ "no-encryption" ] = "true" ;
}
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Library . Main . Controller ( backend , options , console ))
2014-08-19 20:27:14 +02:00
{
2017-04-04 16:56:34 +02:00
setup ( i );
2018-11-02 17:45:00 +01:00
i . ListAffected ( args , res =>
{
2018-10-06 16:02:36 -07:00
if ( res . Filesets != null && res . Filesets . Any ())
2017-04-04 23:53:36 +02:00
{
outwriter . WriteLine ( "The following filesets are affected:" );
foreach ( var e in res . Filesets )
outwriter . WriteLine ( "{0}\t: {1}" , e . Version , e . Time );
outwriter . WriteLine ();
}
2014-08-19 20:27:14 +02:00
2017-04-04 23:53:36 +02:00
if ( res . Files != null )
{
var filecount = res . Files . Count ();
if ( filecount == 0 )
{
outwriter . WriteLine ( "No files are affected" );
}
else
{
var c = 0 ;
outwriter . WriteLine ( "A total of {0} file(s) are affected:" , filecount );
foreach ( var file in res . Files )
{
c ++;
outwriter . WriteLine ( file . Path );
if ( c > 10 && filecount > 10 && ! fullresult )
{
outwriter . WriteLine ( " ... and {0} more (use --{1} to see all filenames)" , filecount - c , "full-result" );
break ;
}
}
2014-08-19 20:27:14 +02:00
2017-04-04 23:53:36 +02:00
}
2014-08-19 20:27:14 +02:00
2017-04-04 23:53:36 +02:00
outwriter . WriteLine ();
}
2014-08-19 20:27:14 +02:00
2017-04-04 23:53:36 +02:00
if ( res . LogMessages != null )
{
var logcount = res . LogMessages . Count ();
if ( logcount == 0 || ( logcount > 10 && ! fullresult ))
outwriter . WriteLine ( "Found {0} related log messages (use --{1} to see the data)" , res . Files . Count (), "full-result" );
2014-08-19 20:27:14 +02:00
else
2017-04-04 23:53:36 +02:00
{
outwriter . WriteLine ( "The following related log messages were found:" );
foreach ( var log in res . LogMessages )
if ( log . Message . Length > 100 && ! fullresult )
outwriter . WriteLine ( "{0}: {1}" , log . Timestamp , log . Message . Substring ( 0 , 96 ) + " ..." );
else
outwriter . WriteLine ( "{0}: {1}" , log . Timestamp , log . Message );
}
outwriter . WriteLine ();
}
});
2014-08-19 20:27:14 +02:00
return 0 ;
}
}
2018-11-02 17:45:00 +01:00
2020-09-04 01:01:56 -07:00
/// <summary>
2020-09-04 09:54:33 -07:00
/// For bare file names with no wildcards, replace argument with
/// the equivalent of prefixing with "*/" so we search all folders.
2020-09-04 01:01:56 -07:00
/// </summary>
2020-09-07 14:45:08 -07:00
public static IEnumerable < string > PrefixArgsWithAsterisk ( IEnumerable < string > argList ) => argList . Select ( PrefixArgWithAsterisk );
2020-09-04 01:01:56 -07:00
/// <summary>
2020-09-04 09:54:33 -07:00
/// For bare file names with no wildcards, return argument with
/// the equivalent of prefixing with "*/" so we search all folders.
2020-09-04 01:01:56 -07:00
/// </summary>
private static string PrefixArgWithAsterisk ( string arg )
2018-11-02 17:45:00 +01:00
{
2020-09-04 01:01:56 -07:00
var containsSeparators = ContainsDirectorySeparators ( arg );
var containsWildcards = ContainsWildcards ( arg );
if (! containsSeparators && arg . StartsWith ( "@" , StringComparison . Ordinal ))
{
// Convert to Regexp filter and prefix with ".*/"
2020-09-06 21:30:46 -07:00
return $"[.*{Utility.ConvertLiteralToRegExp(Util.DirectorySeparatorString + arg.Substring(1))}]" ;
2020-09-04 01:01:56 -07:00
}
else if (! containsSeparators && ! containsWildcards && ! arg . StartsWith ( "[" , StringComparison . Ordinal ))
{
// Prefix files with "*/"
return "*" + Util . DirectorySeparatorString + arg ;
}
else
{
return arg ;
}
}
/// <summary>
2020-09-04 09:54:33 -07:00
/// For folders, replace argument with the equivalent of suffixing
/// with "*" so we restore contents in the folder.
2020-09-04 01:01:56 -07:00
/// </summary>
2020-09-07 14:45:08 -07:00
public static IEnumerable < string > SuffixArgsWithAsterisk ( IEnumerable < string > argList ) => argList . Select ( SuffixArgWithAsterisk );
2020-09-04 01:01:56 -07:00
/// <summary>
2020-09-04 09:54:33 -07:00
/// For folders, return argument with the equivalent of suffixing
/// with "*" so we restore contents in the folder.
2020-09-04 01:01:56 -07:00
/// </summary>
private static string SuffixArgWithAsterisk ( string arg )
{
var containsWildcards = ContainsWildcards ( arg );
2020-09-06 21:30:46 -07:00
var endsWithSeparator = arg . EndsWith ( Util . DirectorySeparatorString , StringComparison . Ordinal );
2020-09-04 01:01:56 -07:00
if ( endsWithSeparator && arg . StartsWith ( "@" , StringComparison . Ordinal ))
{
2020-09-04 09:54:33 -07:00
// Convert to Regexp filter and suffix with ".*"
2020-09-06 21:30:46 -07:00
return $"[{Utility.ConvertLiteralToRegExp(arg.Substring(1))}.*]" ;
2020-09-04 01:01:56 -07:00
}
else if ( endsWithSeparator && ! containsWildcards && ! arg . StartsWith ( "[" , StringComparison . Ordinal ))
{
2020-09-04 09:54:33 -07:00
// Suffix with "*"
2020-09-04 01:01:56 -07:00
return arg + "*" ;
}
else
{
return arg ;
}
2018-11-02 17:45:00 +01:00
}
2020-09-04 01:01:56 -07:00
private static readonly char [] pathSeparatorsCharacters = new [] { Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar };
/// <summary>
/// Returns true if <paramref name="s"/> contains directory separator characters.
/// </summary>
2020-09-06 21:30:46 -07:00
public static bool ContainsDirectorySeparators ( string s ) => s . IndexOfAny ( pathSeparatorsCharacters ) >= 0 ;
2020-09-04 01:01:56 -07:00
private static readonly char [] wildcardCharacters = new [] { '*' , '?' };
/// <summary>
/// Returns true if <paramref name="s"/> contains wildcard characters.
/// </summary>
2020-09-06 21:30:46 -07:00
public static bool ContainsWildcards ( string s ) => s . IndexOfAny ( wildcardCharacters ) >= 0 ;
2020-09-04 01:01:56 -07:00
2017-04-04 16:56:34 +02:00
public static int List ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2013-05-02 17:24:38 +02:00
{
2014-08-19 20:24:54 +02:00
filter = filter ?? new Duplicati . Library . Utility . FilterExpression ();
if ( Duplicati . Library . Utility . Utility . ParseBoolOption ( options , "list-sets-only" ))
filter = new Duplicati . Library . Utility . FilterExpression ();
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Library . Main . Controller ( args [ 0 ], options , console ))
2013-05-30 21:54:43 +02:00
{
2017-04-04 16:56:34 +02:00
setup ( i );
2013-09-27 20:49:36 +02:00
var backend = args [ 0 ];
2013-05-11 13:04:01 +02:00
args . RemoveAt ( 0 );
2018-11-02 17:45:00 +01:00
2013-05-30 21:54:43 +02:00
if ( args . Count == 1 )
{
2013-05-11 13:04:01 +02:00
long v ;
2013-05-30 21:54:43 +02:00
if ( long . TryParse ( args [ 0 ], out v ))
{
if (! options . ContainsKey ( "version" ))
{
2013-05-11 13:04:01 +02:00
args . RemoveAt ( 0 );
args . Add ( "*" );
options [ "version" ] = v . ToString ();
}
2013-05-30 21:54:43 +02:00
}
2020-09-07 14:45:08 -07:00
else if (! ContainsWildcards ( args [ 0 ]) && ! args [ 0 ]. StartsWith ( "[" , StringComparison . Ordinal ) && ! args [ 0 ]. StartsWith ( "@" , StringComparison . Ordinal ))
2013-05-30 21:54:43 +02:00
{
try
{
2013-05-11 13:04:01 +02:00
var t = Library . Utility . Timeparser . ParseTimeInterval ( args [ 0 ], DateTime . Now , true );
args . RemoveAt ( 0 );
args . Add ( "*" );
options [ "time" ] = t . ToString ();
2013-05-30 21:54:43 +02:00
}
catch
{
2013-05-11 13:04:01 +02:00
}
}
}
2018-11-02 17:45:00 +01:00
2020-09-04 01:01:56 -07:00
args = PrefixArgsWithAsterisk ( args ). ToList ();
2018-11-02 17:45:00 +01:00
2013-09-13 13:24:11 +02:00
// Support for not adding the --auth-username if possible
string dbpath ;
options . TryGetValue ( "dbpath" , out dbpath );
if ( string . IsNullOrEmpty ( dbpath ))
{
2013-09-27 20:49:36 +02:00
dbpath = Library . Main . DatabaseLocator . GetDatabasePath ( backend , new Duplicati . Library . Main . Options ( options ), false , true );
2013-09-13 13:24:11 +02:00
if ( dbpath != null )
options [ "dbpath" ] = dbpath ;
}
// Don't ask for passphrase if we have a local db
if (! string . IsNullOrEmpty ( dbpath ) && System . IO . File . Exists ( dbpath ) && ! options . ContainsKey ( "no-encryption" ) && ! Duplicati . Library . Utility . Utility . ParseBoolOption ( options , "no-local-db" ))
{
string passphrase ;
options . TryGetValue ( "passphrase" , out passphrase );
if ( string . IsNullOrEmpty ( passphrase ))
2016-10-19 11:28:22 +02:00
{
string existing ;
options . TryGetValue ( "disable-module" , out existing );
if ( string . IsNullOrWhiteSpace ( existing ))
options [ "disable-module" ] = "console-password-input" ;
else
options [ "disable-module" ] = string . Join ( "," , new string [] { existing , "console-password-input" });
}
2013-09-13 13:24:11 +02:00
}
2014-08-19 20:24:54 +02:00
2018-11-02 17:45:00 +01:00
2013-05-30 21:54:43 +02:00
bool controlFiles = Library . Utility . Utility . ParseBoolOption ( options , "control-files" );
options . Remove ( "control-files" );
2018-11-02 17:45:00 +01:00
2013-05-30 21:54:43 +02:00
var res = controlFiles ? i . ListControlFiles ( args , filter ) : i . List ( args , filter );
2015-09-15 19:28:02 +02:00
2018-11-02 17:45:00 +01:00
//If there are no files matching, and we are looking for one or more files,
2013-06-30 12:50:30 +02:00
// try again with all-versions set
2015-09-15 19:28:02 +02:00
var compareFilter = Library . Utility . JoinedFilterExpression . Join ( new Library . Utility . FilterExpression ( args ), filter );
2018-11-02 17:45:00 +01:00
var isRequestForFiles =
! controlFiles && res . Filesets . Any () &&
( res . Files == null || ! res . Files . Any ()) &&
2015-09-15 19:28:02 +02:00
! compareFilter . Empty ;
2018-11-02 17:45:00 +01:00
2013-08-06 23:17:08 +02:00
if ( isRequestForFiles && ! Library . Utility . Utility . ParseBoolOption ( options , "all-versions" ))
2013-06-30 12:50:30 +02:00
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "No files matching, looking in all versions" );
2013-06-30 12:50:30 +02:00
options [ "all-versions" ] = "true" ;
2013-08-06 23:17:08 +02:00
options . Remove ( "time" );
options . Remove ( "version" );
2013-06-30 12:50:30 +02:00
res = i . List ( args , filter );
}
2015-09-15 19:28:02 +02:00
2018-10-06 16:02:36 -07:00
if ( res . Filesets . Any () && ( res . Files == null || ! res . Files . Any ()) && compareFilter . Empty )
2013-05-30 21:54:43 +02:00
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Listing filesets:" );
2018-11-02 17:45:00 +01:00
2013-05-30 21:54:43 +02:00
foreach ( var e in res . Filesets )
{
if ( e . FileCount >= 0 )
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "{0}\t: {1} ({2} files, {3})" , e . Version , e . Time , e . FileCount , Library . Utility . Utility . FormatSizeString ( e . FileSizes ));
2013-05-30 21:54:43 +02:00
else
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "{0}\t: {1}" , e . Version , e . Time );
2013-05-30 21:54:43 +02:00
}
2013-08-06 23:17:08 +02:00
}
2015-09-15 19:28:02 +02:00
else if ( isRequestForFiles )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "No files matched expression" );
2015-09-15 19:28:02 +02:00
}
2013-08-06 23:17:08 +02:00
else
2013-05-30 21:54:43 +02:00
{
2018-10-06 16:02:36 -07:00
if (! res . Filesets . Any ())
2013-08-06 23:17:08 +02:00
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "No time or version matched a fileset" );
2013-08-06 23:17:08 +02:00
}
2018-10-06 16:02:36 -07:00
else if ( res . Files == null || ! res . Files . Any ())
2013-05-11 13:04:01 +02:00
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Found {0} filesets, but no files matched" , res . Filesets . Count ());
2013-05-11 13:04:01 +02:00
}
else if ( res . Filesets . Count () == 1 )
2013-05-11 12:03:15 +02:00
{
2013-05-11 13:04:01 +02:00
var f = res . Filesets . First ();
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Listing contents {0} ({1}):" , f . Version , f . Time );
2018-11-02 17:45:00 +01:00
foreach ( var e in res . Files )
outwriter . WriteLine ( "{0} {1}" , e . Path , e . Path . EndsWith ( Util . DirectorySeparatorString , StringComparison . Ordinal ) ? "" : "(" + Library . Utility . Utility . FormatSizeString ( e . Sizes . First ()) + ")" );
2013-05-11 12:03:15 +02:00
}
else
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Listing files and versions:" );
2013-05-11 12:03:15 +02:00
foreach ( var e in res . Files )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( e . Path );
2013-05-30 21:54:43 +02:00
foreach ( var nx in res . Filesets . Zip ( e . Sizes , ( a , b ) => new { Index = a . Version , Time = a . Time , Size = b } ))
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "{0}\t: {1} {2}" , nx . Index , nx . Time , nx . Size < 0 ? " - " : Library . Utility . Utility . FormatSizeString ( nx . Size ));
2018-11-02 17:45:00 +01:00
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ();
2013-05-11 12:03:15 +02:00
}
2018-11-02 17:45:00 +01:00
2013-05-11 12:03:15 +02:00
}
}
}
2018-11-02 17:45:00 +01:00
2013-05-02 17:24:38 +02:00
return 0 ;
}
2018-11-02 17:45:00 +01:00
2017-04-04 16:56:34 +02:00
public static int Delete ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2016-09-15 11:39:27 +02:00
{
var requiredOptions = new string [] { "keep-time" , "keep-versions" , "version" };
2018-11-02 17:45:00 +01:00
2018-10-06 15:50:05 -07:00
if (! options . Keys . Any ( x => requiredOptions . Contains ( x , StringComparer . OrdinalIgnoreCase )))
2016-09-15 11:39:27 +02:00
{
2018-11-02 17:45:00 +01:00
outwriter . WriteLine ( Strings . Program . DeleteCommandNeedsOptions ( "delete" , requiredOptions ));
2016-09-15 11:39:27 +02:00
return 200 ;
}
2018-11-02 17:45:00 +01:00
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Library . Main . Controller ( args [ 0 ], options , console ))
2016-09-15 11:39:27 +02:00
{
2017-04-04 16:56:34 +02:00
setup ( i );
2016-09-15 11:39:27 +02:00
args . RemoveAt ( 0 );
var res = i . Delete ();
2018-11-02 17:45:00 +01:00
2018-10-06 16:02:36 -07:00
if (! res . DeletedSets . Any ())
2016-09-15 11:39:27 +02:00
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( Strings . Program . NoFilesetsMatching );
2016-09-15 11:39:27 +02:00
}
else
{
if ( res . Dryrun )
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( Strings . Program . WouldDeleteBackups );
2016-09-15 11:39:27 +02:00
else
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( Strings . Program . DeletedBackups );
2018-11-02 17:45:00 +01:00
2016-09-15 11:39:27 +02:00
foreach ( var f in res . DeletedSets )
2020-01-25 17:07:02 -08:00
outwriter . WriteLine ( "{0}: {1}" , f . Item1 , f . Item2 );
2016-09-15 11:39:27 +02:00
}
2013-05-02 17:24:38 +02:00
}
2018-11-02 17:45:00 +01:00
2013-05-02 17:24:38 +02:00
return 0 ;
2018-11-02 17:45:00 +01:00
2013-05-02 17:24:38 +02:00
}
2017-04-04 16:56:34 +02:00
public static int Repair ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2013-05-02 17:24:38 +02:00
{
if ( args . Count != 1 )
2017-04-04 16:56:34 +02:00
return PrintWrongNumberOfArguments ( outwriter , args , 1 );
2013-05-02 17:24:38 +02:00
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Duplicati . Library . Main . Controller ( args [ 0 ], options , console ))
2017-04-04 16:56:34 +02:00
{
setup ( i );
2015-04-08 20:19:46 +02:00
i . Repair ( filter );
2017-04-04 16:56:34 +02:00
}
2013-05-08 21:29:59 +02:00
2013-05-02 17:24:38 +02:00
return 0 ;
}
2017-04-04 16:56:34 +02:00
public static int Restore ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2013-05-02 17:24:38 +02:00
{
2013-05-13 22:32:05 +02:00
if ( args . Count < 1 )
2017-04-04 16:56:34 +02:00
return PrintWrongNumberOfArguments ( outwriter , args , 1 );
2018-11-02 17:45:00 +01:00
2013-05-13 22:32:05 +02:00
string backend = args [ 0 ];
2013-05-20 13:44:18 +02:00
args . RemoveAt ( 0 );
2018-11-02 17:45:00 +01:00
2013-05-30 23:00:09 +02:00
bool controlFiles = Library . Utility . Utility . ParseBoolOption ( options , "control-files" );
options . Remove ( "control-files" );
2013-08-25 13:15:40 +02:00
2020-09-04 01:01:56 -07:00
args = SuffixArgsWithAsterisk ( PrefixArgsWithAsterisk ( args )). ToList ();
2018-11-02 17:45:00 +01:00
2018-03-12 14:07:11 +01:00
using ( var output = new ConsoleOutput ( outwriter , options ))
2017-04-04 16:56:34 +02:00
using ( var i = new Library . Main . Controller ( backend , options , output ))
{
2018-03-12 14:07:11 +01:00
output . MessageEvent ( string . Format ( "Restore started at {0}" , DateTime . Now ));
2017-04-04 16:56:34 +02:00
setup ( i );
2013-05-30 23:00:09 +02:00
if ( controlFiles )
{
var res = i . RestoreControlFiles ( args . ToArray (), filter );
2013-08-22 20:52:54 +02:00
output . MessageEvent ( "Restore control files completed:" );
2017-04-04 16:56:34 +02:00
foreach ( var s in res . Files )
outwriter . WriteLine ( s );
2013-05-30 23:00:09 +02:00
}
else
{
2017-04-04 16:56:34 +02:00
using ( var periodicOutput = new PeriodicOutput ( output , TimeSpan . FromSeconds ( 5 )))
2013-08-23 22:18:13 +02:00
{
2017-04-04 16:56:34 +02:00
output . PhaseChanged += ( phase , previousPhase ) =>
{
2017-09-26 21:01:33 -07:00
switch ( phase )
2013-08-23 22:18:13 +02:00
{
2017-09-26 21:01:33 -07:00
case Duplicati . Library . Main . OperationPhase . Restore_PreRestoreVerify :
output . MessageEvent ( "Checking remote backup ..." );
break ;
case Duplicati . Library . Main . OperationPhase . Restore_ScanForExistingFiles :
output . MessageEvent ( "Checking existing target files ..." );
break ;
case Duplicati . Library . Main . OperationPhase . Restore_PatchWithLocalBlocks :
output . MessageEvent ( "Updating target files with local data ..." );
break ;
case Duplicati . Library . Main . OperationPhase . Restore_PostRestoreVerify :
periodicOutput . SetFinished ();
periodicOutput . Join ( TimeSpan . FromMilliseconds ( 100 ));
output . MessageEvent ( "Verifying restored files ..." );
break ;
case Duplicati . Library . Main . OperationPhase . Restore_ScanForLocalBlocks :
output . MessageEvent ( "Scanning local files for needed data ..." );
break ;
case Duplicati . Library . Main . OperationPhase . Restore_CreateTargetFolders :
periodicOutput . SetReady ();
break ;
2013-08-23 22:18:13 +02:00
}
};
2017-04-04 16:56:34 +02:00
periodicOutput . WriteOutput += ( progress , files , size , counting ) =>
{
2013-08-24 13:43:33 +02:00
output . MessageEvent ( string . Format ( " {0} files need to be restored ({1})" , files , Library . Utility . Utility . FormatSizeString ( size )));
2013-08-23 22:18:13 +02:00
};
2017-04-04 16:56:34 +02:00
2013-08-23 22:18:13 +02:00
var res = i . Restore ( args . ToArray (), filter );
string restorePath ;
options . TryGetValue ( "restore-path" , out restorePath );
2017-04-04 16:56:34 +02:00
2018-12-12 12:14:11 -02:00
output . MessageEvent ( string . Format ( "Restored {0} ({1}) files to {2}" , res . RestoredFiles , Library . Utility . Utility . FormatSizeString ( res . SizeOfRestoredFiles ), string . IsNullOrEmpty ( restorePath ) ? "original path" : restorePath ));
2013-08-24 13:43:33 +02:00
output . MessageEvent ( string . Format ( "Duration of restore: {0:hh\\:mm\\:ss}" , res . Duration ));
2017-04-04 16:56:34 +02:00
2018-03-12 14:07:11 +01:00
if ( output . FullResults )
2017-04-04 16:56:34 +02:00
Library . Utility . Utility . PrintSerializeObject ( res , outwriter );
2015-01-27 22:06:23 +01:00
2018-10-06 16:02:36 -07:00
if ( res . Warnings . Any ())
2015-01-27 22:06:23 +01:00
return 2 ;
2013-08-23 22:18:13 +02:00
}
2013-05-30 23:00:09 +02:00
}
2017-04-04 16:56:34 +02:00
}
2018-11-02 17:45:00 +01:00
2013-05-02 17:24:38 +02:00
return 0 ;
}
2017-04-04 16:56:34 +02:00
public static int Backup ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2013-05-02 17:24:38 +02:00
{
2013-05-13 22:32:05 +02:00
if ( args . Count < 2 )
2017-04-04 16:56:34 +02:00
return PrintWrongNumberOfArguments ( outwriter , args , 2 );
2018-11-02 17:45:00 +01:00
2013-05-13 22:32:05 +02:00
var backend = args [ 0 ];
args . RemoveAt ( 0 );
2013-08-20 22:52:50 +02:00
var dirs = args . ToArray ();
2013-05-25 16:40:15 +02:00
Library . Interface . IBackupResults result ;
2018-03-12 14:07:11 +01:00
using ( var output = new ConsoleOutput ( outwriter , options ))
2013-08-20 22:52:50 +02:00
{
2018-03-12 14:07:11 +01:00
using ( var periodicOutput = new PeriodicOutput ( output , TimeSpan . FromSeconds ( 5 )))
2013-08-20 22:52:50 +02:00
{
2018-03-11 10:37:52 -07:00
if (( new Duplicati . Library . Main . Options ( options )). DisableOnBattery && ( Duplicati . Library . Utility . Power . PowerSupply . GetSource () == Duplicati . Library . Utility . Power . PowerSupply . Source . Battery ))
{
output . MessageEvent ( "The \"disable-on-battery\" option only affects scheduled backups and is ignored by backups run manually or from the command line." );
}
2018-03-12 14:07:11 +01:00
output . MessageEvent ( string . Format ( "Backup started at {0}" , DateTime . Now ));
2017-09-26 21:32:58 -07:00
2018-03-12 14:07:11 +01:00
output . PhaseChanged += ( phase , previousPhase ) =>
2013-09-10 20:41:28 +02:00
{
2018-03-12 14:07:11 +01:00
if ( previousPhase == Duplicati . Library . Main . OperationPhase . Backup_PostBackupTest )
output . MessageEvent ( "Remote backup verification completed" );
2017-04-04 16:56:34 +02:00
2018-03-12 14:07:11 +01:00
switch ( phase )
{
case Duplicati . Library . Main . OperationPhase . Backup_ProcessingFiles :
output . MessageEvent ( "Scanning local files ..." );
periodicOutput . SetReady ();
break ;
case Duplicati . Library . Main . OperationPhase . Backup_Finalize :
periodicOutput . SetFinished ();
break ;
case Duplicati . Library . Main . OperationPhase . Backup_PreBackupVerify :
output . MessageEvent ( "Checking remote backup ..." );
break ;
case Duplicati . Library . Main . OperationPhase . Backup_PostBackupVerify :
output . MessageEvent ( "Checking remote backup ..." );
break ;
case Duplicati . Library . Main . OperationPhase . Backup_PostBackupTest :
output . MessageEvent ( "Verifying remote backup ..." );
break ;
case Duplicati . Library . Main . OperationPhase . Backup_Compact :
output . MessageEvent ( "Compacting remote backup ..." );
break ;
}
};
2018-02-07 10:35:16 -07:00
2018-03-12 14:07:11 +01:00
periodicOutput . WriteOutput += ( progress , files , size , counting ) =>
{
output . MessageEvent ( string . Format ( " {0} files need to be examined ({1}){2}" , files , Library . Utility . Utility . FormatSizeString ( size ), counting ? " (still counting)" : "" ));
};
using ( var i = new Library . Main . Controller ( backend , options , output ))
{
setup ( i );
result = i . Backup ( dirs , filter );
}
2018-02-07 10:35:16 -07:00
}
2017-09-22 00:30:10 -06:00
2018-03-12 14:07:11 +01:00
if ( output . FullResults )
2018-02-07 10:35:16 -07:00
{
2018-03-12 14:07:11 +01:00
Library . Utility . Utility . PrintSerializeObject ( result , outwriter );
outwriter . WriteLine ();
2018-02-07 10:35:16 -07:00
}
2017-09-22 00:30:10 -06:00
2018-03-12 14:07:11 +01:00
var parsedStats = result . BackendStatistics as Duplicati . Library . Interface . IParsedBackendStatistics ;
output . MessageEvent ( string . Format ( " Duration of backup: {0:hh\\:mm\\:ss}" , result . Duration ));
if ( parsedStats != null )
2018-02-07 10:35:16 -07:00
{
2018-03-12 14:07:11 +01:00
if ( parsedStats . KnownFileCount > 0 )
{
output . MessageEvent ( string . Format ( " Remote files: {0}" , parsedStats . KnownFileCount ));
output . MessageEvent ( string . Format ( " Remote size: {0}" , Library . Utility . Utility . FormatSizeString ( parsedStats . KnownFileSize )));
}
if ( parsedStats . TotalQuotaSpace >= 0 )
{
output . MessageEvent ( string . Format ( " Total remote quota: {0}" , Library . Utility . Utility . FormatSizeString ( parsedStats . TotalQuotaSpace )));
}
if ( parsedStats . FreeQuotaSpace >= 0 )
{
output . MessageEvent ( string . Format ( " Available remote quota: {0}" , Library . Utility . Utility . FormatSizeString ( parsedStats . FreeQuotaSpace )));
}
2013-08-21 22:23:58 +02:00
}
2017-09-22 00:30:10 -06:00
2018-03-12 14:07:11 +01:00
output . MessageEvent ( string . Format ( " Files added: {0}" , result . AddedFiles ));
output . MessageEvent ( string . Format ( " Files deleted: {0}" , result . DeletedFiles ));
output . MessageEvent ( string . Format ( " Files changed: {0}" , result . ModifiedFiles ));
2013-05-02 17:24:38 +02:00
2018-03-12 14:07:11 +01:00
output . MessageEvent ( string . Format ( " Data uploaded: {0}" , Library . Utility . Utility . FormatSizeString ( result . BackendStatistics . BytesUploaded )));
output . MessageEvent ( string . Format ( " Data downloaded: {0}" , Library . Utility . Utility . FormatSizeString ( result . BackendStatistics . BytesDownloaded )));
2014-07-15 14:15:26 +02:00
2018-09-29 16:49:05 -07:00
if ( result . ExaminedFiles == 0 && ( filter != null && ! filter . Empty ))
2018-03-12 14:07:11 +01:00
output . MessageEvent ( "No files were processed. If this was not intentional you may want to use the \"test-filters\" command" );
2013-05-02 17:24:38 +02:00
2018-03-12 14:07:11 +01:00
output . MessageEvent ( "Backup completed successfully!" );
2013-05-02 17:24:38 +02:00
2018-03-12 14:07:11 +01:00
//Interrupted = 50
if ( result . PartialBackup )
return 50 ;
//Completed with errors = 3
if ( result . ParsedResult == Library . Interface . ParsedResultType . Error )
return 3 ;
//Completed with warnings = 2
if ( result . ParsedResult == Library . Interface . ParsedResultType . Warning )
return 2 ;
//Success, but no upload = 1
if ( result . BackendStatistics . BytesUploaded == 0 )
return 1 ;
return 0 ;
}
2013-05-02 17:24:38 +02:00
}
2018-11-02 17:45:00 +01:00
2017-04-04 16:56:34 +02:00
public static int Compact ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2013-05-15 21:50:16 +02:00
{
if ( args . Count != 1 )
2017-04-04 16:56:34 +02:00
return PrintWrongNumberOfArguments ( outwriter , args , 1 );
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Library . Main . Controller ( args [ 0 ], options , console ))
2017-04-04 16:56:34 +02:00
{
setup ( i );
2013-05-21 21:16:01 +02:00
i . Compact ();
2017-04-04 16:56:34 +02:00
}
2013-05-15 21:50:16 +02:00
return 0 ;
}
2013-06-26 21:59:01 +02:00
2017-04-04 16:56:34 +02:00
public static int Test ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2013-06-26 21:59:01 +02:00
{
2018-03-12 14:07:11 +01:00
var fullResults = Duplicati . Library . Utility . Utility . ParseBoolOption ( options , "full-result" );
2013-06-26 21:59:01 +02:00
if ( args . Count != 1 && args . Count != 2 )
2017-04-04 16:56:34 +02:00
return PrintWrongNumberOfArguments ( outwriter , args , 1 );
2018-11-02 17:45:00 +01:00
2013-07-26 12:05:23 +02:00
var tests = 1L ;
if ( args . Count == 2 )
{
2017-09-18 23:23:45 -06:00
if ( new string [] { "all" , "everything" }. Contains ( args [ 1 ], StringComparer . OrdinalIgnoreCase ))
2013-07-26 12:05:23 +02:00
tests = long . MaxValue ;
else
tests = Convert . ToInt64 ( args [ 1 ]);
}
2018-11-02 17:45:00 +01:00
2013-06-26 21:59:01 +02:00
Library . Interface . ITestResults result ;
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Library . Main . Controller ( args [ 0 ], options , console ))
2017-04-04 16:56:34 +02:00
{
setup ( i );
2013-07-26 12:05:23 +02:00
result = i . Test ( tests );
2017-04-04 16:56:34 +02:00
}
2018-11-02 17:45:00 +01:00
2016-12-02 11:52:40 +01:00
var totalFiles = result . Verifications . Count ();
2013-06-26 21:59:01 +02:00
if ( totalFiles == 0 )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "No files examined, is the remote destination is empty?" );
2017-03-07 23:09:04 +01:00
return 100 ;
2013-06-26 21:59:01 +02:00
}
else
{
2018-10-06 16:02:36 -07:00
var filtered = from n in result . Verifications where n . Value . Any () select n ;
if (! filtered . Any ())
2017-03-07 23:09:04 +01:00
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Examined {0} files and found no errors" , totalFiles );
2017-03-07 23:09:04 +01:00
return 0 ;
}
2013-06-26 21:59:01 +02:00
else
{
2017-04-04 17:26:26 +02:00
foreach ( var n in result . Verifications )
2013-06-26 21:59:01 +02:00
{
2017-04-04 17:26:26 +02:00
var changecount = n . Value . Count ();
if ( changecount == 0 )
2013-06-26 21:59:01 +02:00
{
2018-03-12 14:07:11 +01:00
if ( fullResults )
2018-06-20 10:57:11 +02:00
outwriter . WriteLine ( "{0}: No errors" , n . Key );
2017-04-04 17:26:26 +02:00
}
else
{
2018-06-20 10:57:11 +02:00
outwriter . WriteLine ( "{0}: {1} errors" , n . Key , changecount );
2017-04-04 17:26:26 +02:00
var count = 0 ;
foreach ( var c in n . Value )
2013-06-26 21:59:01 +02:00
{
2017-04-04 17:26:26 +02:00
count ++;
2018-06-20 10:57:11 +02:00
outwriter . WriteLine ( "\t{0}: {1}" , c . Key , c . Value );
2018-03-12 14:07:11 +01:00
if (! fullResults && count == 10 && changecount > 10 )
2017-04-04 17:26:26 +02:00
{
2018-06-20 10:57:11 +02:00
outwriter . WriteLine ( "\t... and {0} more" , changecount - count );
2017-04-04 17:26:26 +02:00
break ;
}
2013-06-26 21:59:01 +02:00
}
2017-04-04 17:26:26 +02:00
2018-06-20 10:57:11 +02:00
outwriter . WriteLine ();
2013-06-26 21:59:01 +02:00
}
}
2017-03-07 23:09:04 +01:00
return 3 ;
}
2013-06-26 21:59:01 +02:00
}
}
2018-11-02 17:45:00 +01:00
2017-04-04 16:56:34 +02:00
private static int PrintWrongNumberOfArguments ( TextWriter outwriter , List < string > args , int expected )
2013-05-02 17:24:38 +02:00
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( Strings . Program . WrongNumberOfCommandsError_v2 ( args . Count , expected , args . Select ( n => "\"" + n + "\"" ). ToArray ()));
2013-05-02 17:24:38 +02:00
return 200 ;
}
2018-10-06 13:30:13 -07:00
public static int PrintInvalidCommand ( TextWriter outwriter , string command )
2013-05-02 17:24:38 +02:00
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( Strings . Program . InvalidCommandError ( command ));
2013-05-02 17:24:38 +02:00
return 200 ;
}
2013-05-04 14:07:04 +02:00
2017-04-04 16:56:34 +02:00
public static int CreateBugReport ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2013-05-04 14:07:04 +02:00
{
2013-09-06 23:44:39 +02:00
// Support for not adding the --auth-username if possible
2013-09-25 23:29:35 +02:00
string dbpath = null ;
2013-09-06 23:44:39 +02:00
options . TryGetValue ( "dbpath" , out dbpath );
if ( string . IsNullOrEmpty ( dbpath ))
{
2013-09-25 23:29:35 +02:00
if ( args . Count > 0 )
dbpath = Library . Main . DatabaseLocator . GetDatabasePath ( args [ 0 ], new Duplicati . Library . Main . Options ( options ), false , true );
2018-11-02 17:45:00 +01:00
2013-09-06 23:44:39 +02:00
if ( dbpath == null )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "No local database found, please add --{0}" , "dbpath" );
2013-09-06 23:44:39 +02:00
return 100 ;
}
else
options [ "dbpath" ] = dbpath ;
2018-11-02 17:45:00 +01:00
2013-09-06 23:44:39 +02:00
}
2018-11-02 17:45:00 +01:00
2013-09-25 23:29:35 +02:00
if ( args . Count == 0 )
args = new List < string >( new string [] { "file://unused" , "report" });
else if ( args . Count == 1 )
args . Add ( "report" );
2017-04-04 16:56:34 +02:00
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Library . Main . Controller ( args [ 0 ], options , console ))
2017-04-04 16:56:34 +02:00
{
setup ( i );
2013-05-25 16:40:15 +02:00
i . CreateLogDatabase ( args [ 1 ]);
2017-04-04 16:56:34 +02:00
}
2013-05-08 21:29:59 +02:00
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Completed!" );
outwriter . WriteLine ();
outwriter . WriteLine ( "Please examine the log table of the database to see that no filenames are accidentially left over." );
outwriter . WriteLine ( "If you are concerned that your filenames may contain sensitive information," );
outwriter . WriteLine ( " do not attach the database to an issue!!!" );
outwriter . WriteLine ();
2013-08-24 14:02:12 +02:00
2013-05-04 14:07:04 +02:00
return 0 ;
}
2018-11-02 17:45:00 +01:00
2017-04-04 16:56:34 +02:00
public static int ListChanges ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2013-06-20 20:17:10 +02:00
{
2018-03-12 14:07:11 +01:00
var fullresult = Duplicati . Library . Utility . Utility . ParseBoolOption ( options , "full-result" );
2017-04-04 23:51:40 +02:00
2013-06-20 20:17:10 +02:00
if ( args . Count < 1 )
2017-04-04 16:56:34 +02:00
return PrintWrongNumberOfArguments ( outwriter , args , 1 );
2018-11-02 17:45:00 +01:00
2013-09-13 13:24:11 +02:00
// Support for not adding the --auth-username if possible
string dbpath ;
options . TryGetValue ( "dbpath" , out dbpath );
if ( string . IsNullOrEmpty ( dbpath ))
{
dbpath = Library . Main . DatabaseLocator . GetDatabasePath ( args [ 0 ], new Duplicati . Library . Main . Options ( options ), false , true );
if ( dbpath != null )
options [ "dbpath" ] = dbpath ;
}
2018-11-02 17:45:00 +01:00
2013-09-13 13:24:11 +02:00
// Don't ask for passphrase if we have a local db
if (! string . IsNullOrEmpty ( dbpath ) && System . IO . File . Exists ( dbpath ) && ! options . ContainsKey ( "no-encryption" ) && ! Duplicati . Library . Utility . Utility . ParseBoolOption ( options , "no-local-db" ))
{
string passphrase ;
options . TryGetValue ( "passphrase" , out passphrase );
if ( string . IsNullOrEmpty ( passphrase ))
options [ "no-encryption" ] = "true" ;
}
2017-04-04 23:53:36 +02:00
2018-11-02 17:45:00 +01:00
Action < Duplicati . Library . Interface . IListChangesResults , IEnumerable < Tuple < Library . Interface . ListChangesChangeType , Library . Interface . ListChangesElementType , string >>> handler =
( result , items ) =>
{
2017-04-04 23:53:36 +02:00
outwriter . WriteLine ( "Listing changes" );
outwriter . WriteLine ( " {0}: {1}" , result . BaseVersionIndex , result . BaseVersionTimestamp );
outwriter . WriteLine ( " {0}: {1}" , result . CompareVersionIndex , result . CompareVersionTimestamp );
outwriter . WriteLine ();
outwriter . WriteLine ( "Size of backup {0}: {1}" , result . BaseVersionIndex , Library . Utility . Utility . FormatSizeString ( result . PreviousSize ));
if ( items != null )
{
outwriter . WriteLine ();
var added = result . ChangeDetails . Where ( x => x . Item1 == Library . Interface . ListChangesChangeType . Added );
var deleted = result . ChangeDetails . Where ( x => x . Item1 == Library . Interface . ListChangesChangeType . Deleted );
var modified = result . ChangeDetails . Where ( x => x . Item1 == Library . Interface . ListChangesChangeType . Modified );
var count = added . Count ();
if ( count > 0 )
{
var c = 0 ;
outwriter . WriteLine ( " {0} added entries:" , count );
foreach ( var n in added )
{
c ++;
outwriter . WriteLine ( " + {0}" , n . Item3 );
if ( c > 10 && count > 10 && ! fullresult )
{
outwriter . WriteLine ( " ... and {0} more" , count - c );
break ;
}
}
outwriter . WriteLine ();
}
count = modified . Count ();
if ( count > 0 )
{
var c = 0 ;
outwriter . WriteLine ( " {0} modified entries:" , count );
foreach ( var n in modified )
{
c ++;
outwriter . WriteLine ( " ~ {0}" , n . Item3 );
if ( c > 10 && count > 10 && ! fullresult )
{
outwriter . WriteLine ( " ... and {0} more" , count - c );
break ;
}
}
outwriter . WriteLine ();
}
count = deleted . Count ();
if ( count > 0 )
{
var c = 0 ;
outwriter . WriteLine ( "{0} deleted entries:" , count );
foreach ( var n in deleted )
{
c ++;
outwriter . WriteLine ( " - {0}" , n . Item3 );
if ( c > 10 && count > 10 && ! fullresult )
{
outwriter . WriteLine ( " ... and {0} more" , count - c );
break ;
}
}
outwriter . WriteLine ();
}
outwriter . WriteLine ();
}
if ( result . AddedFolders > 0 )
outwriter . WriteLine ( " Added folders: {0}" , result . AddedFolders );
if ( result . AddedSymlinks > 0 )
outwriter . WriteLine ( " Added symlinks: {0}" , result . AddedSymlinks );
if ( result . AddedFiles > 0 )
outwriter . WriteLine ( " Added files: {0}" , result . AddedFiles );
if ( result . DeletedFolders > 0 )
outwriter . WriteLine ( " Deleted folders: {0}" , result . DeletedFolders );
if ( result . DeletedSymlinks > 0 )
outwriter . WriteLine ( " Deleted symlinks: {0}" , result . DeletedSymlinks );
if ( result . DeletedFiles > 0 )
outwriter . WriteLine ( " Deleted files: {0}" , result . DeletedFiles );
if ( result . ModifiedFolders > 0 )
outwriter . WriteLine ( " Modified folders: {0}" , result . ModifiedFolders );
if ( result . ModifiedSymlinks > 0 )
outwriter . WriteLine ( " Modified symlinka: {0}" , result . ModifiedSymlinks );
if ( result . ModifiedFiles > 0 )
outwriter . WriteLine ( " Modified files: {0}" , result . ModifiedFiles );
if ( result . AddedFolders + result . AddedSymlinks + result . AddedFolders +
result . ModifiedFolders + result . ModifiedSymlinks + result . ModifiedFiles +
result . DeletedFolders + result . DeletedSymlinks + result . DeletedFiles == 0 )
outwriter . WriteLine ( " No changes found" );
2018-11-02 17:45:00 +01:00
outwriter . WriteLine ( "Size of backup {0}: {1}" , result . CompareVersionIndex , Library . Utility . Utility . FormatSizeString ( result . CurrentSize ));
2017-04-04 23:53:36 +02:00
};
2018-11-02 17:45:00 +01:00
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Library . Main . Controller ( args [ 0 ], options , console ))
2017-04-04 16:56:34 +02:00
{
setup ( i );
2013-06-20 20:17:10 +02:00
if ( args . Count == 2 )
2017-04-04 23:53:36 +02:00
i . ListChanges ( null , args [ 1 ], null , filter , handler );
2017-04-04 16:56:34 +02:00
else
2017-04-04 23:53:36 +02:00
i . ListChanges ( args . Count > 1 ? args [ 1 ] : null , args . Count > 2 ? args [ 2 ] : null , null , filter , handler );
2017-04-04 16:56:34 +02:00
}
2017-04-04 23:53:36 +02:00
2013-06-20 20:17:10 +02:00
return 0 ;
}
2018-11-02 17:45:00 +01:00
2017-04-04 16:56:34 +02:00
public static int TestFilters ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2013-12-07 15:22:51 +01:00
{
if ( args == null || args . Count < 1 )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "No source paths given" );
2013-12-07 15:22:51 +01:00
return 200 ;
}
2018-11-02 17:45:00 +01:00
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Library . Main . Controller ( "dummy://" , options , console ))
2013-12-07 15:22:51 +01:00
{
2017-04-04 16:56:34 +02:00
setup ( i );
2013-12-07 15:22:51 +01:00
var result = i . TestFilter ( args . ToArray (), filter );
2018-11-02 17:45:00 +01:00
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Matched {0} files ({1})" , result . FileCount , Duplicati . Library . Utility . Utility . FormatSizeString ( result . FileSize ));
2013-12-07 15:22:51 +01:00
}
2018-11-02 17:45:00 +01:00
2013-12-07 15:22:51 +01:00
return 0 ;
}
2015-09-11 10:57:31 +02:00
2017-04-04 16:56:34 +02:00
public static int SystemInfo ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2015-09-11 10:57:31 +02:00
{
if ( args != null && args . Count != 0 )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Command takes no arguments" );
2015-09-11 10:57:31 +02:00
return 200 ;
}
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Library . Main . Controller ( "dummy://" , options , console ))
2017-04-04 16:56:34 +02:00
{
setup ( i );
foreach ( var line in i . SystemInfo (). Lines )
outwriter . WriteLine ( line );
}
2015-09-11 10:57:31 +02:00
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Know locales: {0}" , string . Join ( ", " , Library . Localization . LocalizationService . AllLocales ));
outwriter . WriteLine ( "Translated locales: {0}" , string . Join ( ", " , Library . Localization . LocalizationService . SupportedCultures ));
2016-09-27 20:44:19 +02:00
2015-09-11 10:57:31 +02:00
return 0 ;
}
2016-12-29 22:41:13 +01:00
2017-04-04 16:56:34 +02:00
public static int PurgeFiles ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2016-12-29 22:41:13 +01:00
{
if ( args . Count < 1 )
2017-04-04 16:56:34 +02:00
return PrintWrongNumberOfArguments ( outwriter , args , 1 );
2016-12-29 22:41:13 +01:00
var backend = args [ 0 ];
var paths = args . Skip ( 1 ). ToArray ();
if ( paths . Length > 0 )
{
if ( filter == null || filter . Empty )
filter = new Library . Utility . FilterExpression ( paths );
else
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "You cannot combine filters and paths on the commandline" );
2016-12-29 22:41:13 +01:00
return 200 ;
}
}
else if ( filter == null || filter . Empty )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "You must provide either filename filters, or a list of paths to remove" );
2016-12-29 22:41:13 +01:00
return 200 ;
}
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
2018-09-18 21:52:31 -07:00
using ( var i = new Library . Main . Controller ( backend , options , console ))
2017-04-04 16:56:34 +02:00
{
setup ( i );
2016-12-29 22:41:13 +01:00
i . PurgeFiles ( filter );
2017-04-04 16:56:34 +02:00
}
2018-11-02 17:45:00 +01:00
2016-12-29 22:41:13 +01:00
return 0 ;
}
2017-04-04 16:56:34 +02:00
public static int ListBrokenFiles ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2017-01-05 16:46:01 +01:00
{
if ( args . Count != 1 )
2017-04-04 16:56:34 +02:00
return PrintWrongNumberOfArguments ( outwriter , args , 1 );
2017-01-05 16:46:01 +01:00
var previd = - 1L ;
var outputcount = 0L ;
2018-03-12 14:07:11 +01:00
var fullresult = Duplicati . Library . Utility . Utility . ParseBoolOption ( options , "full-result" );
2017-01-05 16:46:01 +01:00
2018-03-12 14:07:11 +01:00
using ( var con = new ConsoleOutput ( outwriter , options ))
2017-01-05 16:46:01 +01:00
using ( var i = new Library . Main . Controller ( args [ 0 ], options , con ))
2017-04-04 16:56:34 +02:00
{
setup ( i );
i . ListBrokenFiles ( filter , ( id , time , count , path , size ) =>
2017-01-05 16:46:01 +01:00
{
if ( previd != id )
{
previd = id ;
outputcount = 0 ;
con . MessageEvent ( string . Format ( "{0}\t: {1}\t({2} match(es))" , id , time . ToLocalTime (), count ));
}
con . MessageEvent ( string . Format ( "\t{0} ({1})" , path , Library . Utility . Utility . FormatSizeString ( size )));
outputcount ++;
2018-03-12 14:07:11 +01:00
if ( outputcount >= 5 && ! fullresult && count != outputcount )
2017-01-05 16:46:01 +01:00
{
2017-04-04 23:51:40 +02:00
con . MessageEvent ( string . Format ( "\t ... and {0} more, (use --{1} to list all)" , count - outputcount , "full-result" ));
2017-01-05 16:46:01 +01:00
return false ;
}
return true ;
});
2017-04-04 16:56:34 +02:00
}
2017-01-05 16:46:01 +01:00
return 0 ;
}
2017-04-04 16:56:34 +02:00
public static int PurgeBrokenFiles ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2017-01-05 16:46:01 +01:00
{
if ( args . Count != 1 )
2017-04-04 16:56:34 +02:00
return PrintWrongNumberOfArguments ( outwriter , args , 1 );
2017-01-05 16:46:01 +01:00
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Library . Main . Controller ( args [ 0 ], options , console ))
2017-01-05 16:46:01 +01:00
{
2017-04-04 16:56:34 +02:00
setup ( i );
2018-09-26 21:12:13 -07:00
i . PurgeBrokenFiles ( filter );
2017-01-05 16:46:01 +01:00
}
return 0 ;
}
2017-04-04 16:56:34 +02:00
public static int SendMail ( TextWriter outwriter , Action < Duplicati . Library . Main . Controller > setup , List < string > args , Dictionary < string , string > options , Library . Utility . IFilter filter )
2017-01-09 23:21:00 +01:00
{
if ( args != null && args . Count != 0 )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( "Command takes no arguments" );
2017-01-09 23:21:00 +01:00
return 200 ;
}
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var i = new Library . Main . Controller ( "dummy://" , options , console ))
2017-01-09 23:21:00 +01:00
{
2017-04-04 16:56:34 +02:00
setup ( i );
2017-01-09 23:21:00 +01:00
foreach ( var l in i . SendMail (). Lines )
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( l );
2017-01-09 23:21:00 +01:00
}
return 0 ;
}
2017-08-04 11:24:18 +01:00
public static int Vacuum (
2018-11-02 17:45:00 +01:00
TextWriter outwriter ,
2017-08-04 11:24:18 +01:00
Action < Duplicati . Library . Main . Controller > setup ,
2018-11-02 17:45:00 +01:00
List < string > args , Dictionary < string , string > options ,
2017-08-04 11:24:18 +01:00
Library . Utility . IFilter filter )
{
if ( args . Count != 1 )
return PrintWrongNumberOfArguments ( outwriter , args , 1 );
2018-03-12 14:07:11 +01:00
using ( var console = new ConsoleOutput ( outwriter , options ))
using ( var controller = new Library . Main . Controller ( args [ 0 ], options , console ))
2017-08-04 11:24:18 +01:00
{
setup ( controller );
controller . Vacuum ();
}
return 0 ;
}
2013-05-02 17:24:38 +02:00
}
}