Files
duplicati/Duplicati/Server/Program.cs
T
Tyler Gill 83a1dcfb64 Replace all instances of InvariantCultureIgnoreCase with OrdinalIgnoreCase in string comparisons.
InvariantCulture is useful when comparing / sorting human language strings in a culturely correct way. It handles things like accented letters in a way that makes sense to humans (e.g., 'a' should be sorted next to 'á', rather than after 'z').
Ordinal looks just at the raw code points of the characters. As such, it is recommended for use in cases when comparing system strings (file paths, command line parameters, config settings, etc.). Since it doesn't need to use the culture specific sorting rules, this method can often be faster.

For more information, see https://stackoverflow.com/questions/492799/difference-between-invariantculture-and-ordinal-string-comparison (and other related questions)
2017-09-18 23:55:08 -06:00

775 lines
38 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Duplicati.Server
{
public class Program
{
/// <summary>
/// The path to the directory that contains the main executable
/// </summary>
public static readonly string StartupPath = Duplicati.Library.AutoUpdater.UpdaterManager.InstalledBaseDir;
/// <summary>
/// The name of the environment variable that holds the path to the data folder used by Duplicati
/// </summary>
public static readonly string DATAFOLDER_ENV_NAME = Duplicati.Library.AutoUpdater.AutoUpdateSettings.AppName.ToUpper() + "_HOME";
/// <summary>
/// The environment variable that holdes the database key used to encrypt the SQLite database
/// </summary>
public static readonly string DB_KEY_ENV_NAME = Duplicati.Library.AutoUpdater.AutoUpdateSettings.AppName.ToUpper() + "_DB_KEY";
/// <summary>
/// Gets the folder where Duplicati data is stored
/// </summary>
public static string DataFolder { get; private set; }
/// <summary>
/// The single instance
/// </summary>
public static SingleInstance Instance = null;
/// <summary>
/// This is the only access to the database
/// </summary>
public static Database.Connection DataConnection;
/// <summary>
/// This is the lock to be used before manipulating the shared resources
/// </summary>
public static object MainLock = new object();
/// <summary>
/// This is the scheduling thread
/// </summary>
public static Scheduler Scheduler;
/// <summary>
/// This is the working thread
/// </summary>
public static Duplicati.Library.Utility.WorkerThread<Runner.IRunnerData> WorkThread;
/// <summary>
/// List of completed task results
/// </summary>
public static List<KeyValuePair<long, Exception>> TaskResultCache = new List<KeyValuePair<long, Exception>>();
/// <summary>
/// The maximum number of completed task results to keep in memory
/// </summary>
private static int MAX_TASK_RESULT_CACHE_SIZE = 100;
/// <summary>
/// The thread running the ping-pong handler
/// </summary>
public static System.Threading.Thread PingPongThread;
/// <summary>
/// The path to the file that contains the current database
/// </summary>
public static string DatabasePath;
/// <summary>
/// The controller interface for pause/resume and throttle options
/// </summary>
public static LiveControls LiveControl;
/// <summary>
/// The application exit event
/// </summary>
public static System.Threading.ManualResetEvent ApplicationExitEvent;
/// <summary>
/// The webserver instance
/// </summary>
public static WebServer.Server WebServer;
/// <summary>
/// The update poll thread.
/// </summary>
public static UpdatePollThread UpdatePoller;
/// <summary>
/// An event that is set once the server is ready to respond to requests
/// </summary>
public static System.Threading.ManualResetEvent ServerStartedEvent = new System.Threading.ManualResetEvent(false);
/// <summary>
/// The status event signaler, used to controll long polling of status updates
/// </summary>
public static EventPollNotify StatusEventNotifyer = new EventPollNotify();
/// <summary>
/// A delegate method for creating a copy of the current progress state
/// </summary>
public static Func<Duplicati.Server.Serialization.Interface.IProgressEventData> GenerateProgressState;
/// <summary>
/// An event ID that increases whenever the database is updated
/// </summary>
public static long LastDataUpdateID = 0;
/// <summary>
/// An event ID that increases whenever a notification is updated
/// </summary>
public static long LastNotificationUpdateID = 0;
/// <summary>
/// The log redirect handler
/// </summary>
public static LogWriteHandler LogHandler = new LogWriteHandler();
private static System.Threading.Timer PurgeTempFilesTimer = null;
public static int ServerPort
{
get
{
return WebServer.Port;
}
}
public static bool IsFirstRun
{
get { return DataConnection.ApplicationSettings.IsFirstRun; }
set { DataConnection.ApplicationSettings.IsFirstRun = value; }
}
public static bool ServerPortChanged
{
get { return DataConnection.ApplicationSettings.ServerPortChanged; }
set { DataConnection.ApplicationSettings.ServerPortChanged = value; }
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static int Main(string[] args)
{
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args, Duplicati.Library.AutoUpdater.AutoUpdateStrategy.Never);
}
public static void RealMain(string[] args)
{
//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
if (Library.Utility.Utility.IsClientWindows)
{
Environment.SetEnvironmentVariable("PATH",
Environment.GetEnvironmentVariable("PATH") +
System.IO.Path.PathSeparator.ToString() +
System.IO.Path.Combine(
System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location),
"win-tools")
);
}
//If this executable is invoked directly, write to console, otherwise throw exceptions
var writeConsole = System.Reflection.Assembly.GetEntryAssembly() == System.Reflection.Assembly.GetExecutingAssembly();
//Find commandline options here for handling special startup cases
var commandlineOptions = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(new List<string>(args));
foreach(var s in args)
if (
s.Equals("help", StringComparison.OrdinalIgnoreCase) ||
s.Equals("/help", StringComparison.OrdinalIgnoreCase) ||
s.Equals("usage", StringComparison.OrdinalIgnoreCase) ||
s.Equals("/usage", StringComparison.OrdinalIgnoreCase))
commandlineOptions["help"] = "";
//If the commandline issues --help, just stop here
if (commandlineOptions.ContainsKey("help"))
{
if (writeConsole)
{
Console.WriteLine(Strings.Program.HelpDisplayDialog);
foreach(Library.Interface.ICommandLineArgument arg in SupportedCommands)
Console.WriteLine(Strings.Program.HelpDisplayFormat(arg.Name, arg.LongDescription));
return;
}
else
{
throw new Exception("Server invoked with --help");
}
}
#if DEBUG
//Log various information in the logfile
if (!commandlineOptions.ContainsKey("log-file"))
{
commandlineOptions["log-file"] = System.IO.Path.Combine(StartupPath, "Duplicati.debug.log");
commandlineOptions["log-level"] = Duplicati.Library.Logging.LogMessageType.Profiling.ToString();
}
#endif
try
{
// Setup the log redirect
Duplicati.Library.Logging.Log.CurrentLog = Program.LogHandler;
if (commandlineOptions.ContainsKey("log-file"))
{
#if DEBUG
// Only delete the --log-file when in DEBUG mode. Otherwise, don't delete and just append logs.
if (System.IO.File.Exists(commandlineOptions["log-file"]))
System.IO.File.Delete(commandlineOptions["log-file"]);
#endif
var loglevel = Duplicati.Library.Logging.LogMessageType.Error;
if (commandlineOptions.ContainsKey("log-level"))
Enum.TryParse<Duplicati.Library.Logging.LogMessageType>(commandlineOptions["log-level"], true, out loglevel);
Program.LogHandler.SetServerFile(commandlineOptions["log-file"], loglevel);
}
DataConnection = GetDatabaseConnection(commandlineOptions);
if (!DataConnection.ApplicationSettings.FixedInvalidBackupId)
DataConnection.FixInvalidBackupId();
try
{
//This will also create DATAFOLDER if it does not exist
Instance = new SingleInstance(Duplicati.Library.AutoUpdater.AutoUpdateSettings.AppName, DataFolder);
}
catch (Exception ex)
{
if (writeConsole)
{
Console.WriteLine(Strings.Program.StartupFailure(ex));
return;
}
throw new Exception(Strings.Program.StartupFailure(ex));
}
if (!Instance.IsFirstInstance)
{
if (writeConsole)
{
Console.WriteLine(Strings.Program.AnotherInstanceDetected);
return;
}
throw new SingleInstance.MultipleInstanceException(Strings.Program.AnotherInstanceDetected);
}
StartOrStopUsageReporter();
if (commandlineOptions.ContainsKey("webservice-password"))
Program.DataConnection.ApplicationSettings.SetWebserverPassword(commandlineOptions["webservice-password"]);
Program.DataConnection.ApplicationSettings.GenerateWebserverPasswordTrayIcon();
ApplicationExitEvent = new System.Threading.ManualResetEvent(false);
Duplicati.Library.AutoUpdater.UpdaterManager.OnError += (Exception obj) =>
{
Program.DataConnection.LogError(null, "Error in updater", obj);
};
UpdatePoller = new UpdatePollThread();
DateTime lastPurge = new DateTime(0);
System.Threading.TimerCallback purgeTempFilesCallback = (x) => {
try
{
if (Math.Abs((DateTime.Now - lastPurge).TotalHours) < 23)
return;
lastPurge = DateTime.Now;
foreach(var e in Program.DataConnection.GetTempFiles().Where((f) => f.Expires < DateTime.Now))
{
try
{
if (System.IO.File.Exists(e.Path))
System.IO.File.Delete(e.Path);
}
catch (Exception ex)
{
Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", e.Path), ex);
}
Program.DataConnection.DeleteTempFile(e.ID);
}
Duplicati.Library.Utility.TempFile.RemoveOldApplicationTempFiles((path, ex) => {
Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", path), ex);
});
string pts;
if (!commandlineOptions.TryGetValue("log-retention", out pts))
pts = DEFAULT_LOG_RETENTION;
Program.DataConnection.PurgeLogData(Library.Utility.Timeparser.ParseTimeInterval(pts, DateTime.Now, true));
}
catch (Exception ex)
{
Program.DataConnection.LogError(null, "Failed during temp file cleanup", ex);
}
};
try
{
PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromDays(1));
}
catch (ArgumentOutOfRangeException)
{
//Bugfix for older Mono, slightly more resources used to avoid large values in the period field
PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
}
LiveControl = new LiveControls(DataConnection.ApplicationSettings);
LiveControl.StateChanged += new EventHandler(LiveControl_StateChanged);
LiveControl.ThreadPriorityChanged += new EventHandler(LiveControl_ThreadPriorityChanged);
LiveControl.ThrottleSpeedChanged += new EventHandler(LiveControl_ThrottleSpeedChanged);
Program.WorkThread = new Duplicati.Library.Utility.WorkerThread<Runner.IRunnerData>((x) =>
{
Runner.Run(x, true);
}, LiveControl.State == LiveControls.LiveControlState.Paused);
Program.Scheduler = new Scheduler(WorkThread);
Program.WorkThread.StartingWork += (worker, task) => { SignalNewEvent(null, null); };
Program.WorkThread.CompletedWork += (worker, task) => { SignalNewEvent(null, null); };
Program.WorkThread.WorkQueueChanged += (worker) => { SignalNewEvent(null, null); };
Program.Scheduler.NewSchedule += new EventHandler(SignalNewEvent);
Program.WorkThread.OnError += (worker, task, exception) => { Program.DataConnection.LogError(task == null ? null : task.BackupID, "Error in worker", exception); };
var lastscheduleid = LastDataUpdateID;
Program.StatusEventNotifyer.NewEvent += (sender, e) =>
{
if (lastscheduleid != LastDataUpdateID)
{
lastscheduleid = LastDataUpdateID;
Program.Scheduler.Reschedule();
}
};
Action<long, Exception> registerTaskResult = (id, ex) => {
lock(Program.MainLock) {
// If the new results says it crashed, we store that instead of success
if (Program.TaskResultCache.Count > 0 && Program.TaskResultCache.Last().Key == id)
{
if (ex != null && Program.TaskResultCache.Last().Value == null)
Program.TaskResultCache.RemoveAt(Program.TaskResultCache.Count - 1);
else
return;
}
Program.TaskResultCache.Add(new KeyValuePair<long, Exception>(id, ex));
while(Program.TaskResultCache.Count > MAX_TASK_RESULT_CACHE_SIZE)
Program.TaskResultCache.RemoveAt(0);
}
};
Program.WorkThread.CompletedWork += (worker, task) => { registerTaskResult(task.TaskID, null); };
Program.WorkThread.OnError += (worker, task, exception) => { registerTaskResult(task.TaskID, exception); };
Program.WebServer = new WebServer.Server(commandlineOptions);
if (Program.WebServer.Port != DataConnection.ApplicationSettings.LastWebserverPort)
ServerPortChanged = true;
DataConnection.ApplicationSettings.LastWebserverPort = Program.WebServer.Port;
if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, "ping-pong-keepalive"))
{
Program.PingPongThread = new System.Threading.Thread(PingPongMethod);
Program.PingPongThread.IsBackground = true;
Program.PingPongThread.Start();
}
ServerStartedEvent.Set();
ApplicationExitEvent.WaitOne();
}
catch (SingleInstance.MultipleInstanceException mex)
{
System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(mex.ToString()));
if (writeConsole)
Console.WriteLine(Strings.Program.SeriousError(mex.ToString()));
else
throw mex;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(ex.ToString()));
if (writeConsole)
Console.WriteLine(Strings.Program.SeriousError(ex.ToString()));
else
throw new Exception(Strings.Program.SeriousError(ex.ToString()), ex);
}
finally
{
StatusEventNotifyer.SignalNewEvent();
if (UpdatePoller != null)
UpdatePoller.Terminate();
if (Scheduler != null)
Scheduler.Terminate(true);
if (WorkThread != null)
WorkThread.Terminate(true);
if (Instance != null)
Instance.Dispose();
if (PurgeTempFilesTimer != null)
PurgeTempFilesTimer.Dispose();
Library.UsageReporter.Reporter.ShutDown();
if (PingPongThread != null)
try { PingPongThread.Abort(); }
catch { }
if (LogHandler != null)
LogHandler.Dispose();
}
}
public static Database.Connection GetDatabaseConnection(Dictionary<string, string> commandlineOptions)
{
var dbPassword = Environment.GetEnvironmentVariable(DB_KEY_ENV_NAME);
//If we are on windows we encrypt the database by default
//We do not encrypt on Linux as most distros use a SQLite library without encryption support,
//Linux users can use an encrypted home folder, or install a SQLite library with encryption support
//Note that the password here is a default password and public knowledge
//
//The purpose of this is to prevent casual read of the database, as well
// as protect from harddisk string scans, not to protect from determined
// attacks.
//
//If you desire better security, start Duplicati once with the commandline option
// --unencrypted-database to decrypt the database.
//Then set the environment variable DUPLICATI_DB_KEY to the desired key,
// and run Duplicati again without the --unencrypted-database option
// to re-encrypt it with the new key
//
//If you change the key, please note that you need to supply the same
// key when restoring the setup, as the setup being backed up will
// be encrypted as well.
if (!Library.Utility.Utility.IsClientLinux && string.IsNullOrEmpty(dbPassword))
dbPassword = Library.AutoUpdater.AutoUpdateSettings.AppName + "_Key_42";
// Allow override of the environment variables from the commandline
if (commandlineOptions.ContainsKey("server-encryption-key"))
dbPassword = commandlineOptions["server-encryption-key"];
var serverDataFolder = Environment.GetEnvironmentVariable(DATAFOLDER_ENV_NAME);
if (commandlineOptions.ContainsKey("server-datafolder"))
serverDataFolder = commandlineOptions["server-datafolder"];
if (string.IsNullOrEmpty(serverDataFolder))
{
#if DEBUG
//debug mode uses a lock file located in the app folder
DataFolder = StartupPath;
#else
bool portableMode = commandlineOptions.ContainsKey("portable-mode") ? Library.Utility.Utility.ParseBool(commandlineOptions["portable-mode"], true) : false;
if (portableMode)
{
//Portable mode uses a data folder in the application home dir
DataFolder = System.IO.Path.Combine(StartupPath, "data");
System.IO.Directory.SetCurrentDirectory(StartupPath);
}
else
{
//Normal release mode uses the systems "(Local) Application Data" folder
// %LOCALAPPDATA% on Windows, ~/.config on Linux
// Special handling for Windows:
// - Older versions use %APPDATA%
// - but new versions use %LOCALAPPDATA%
//
// If we find a new version, lets use that
// otherwise use the older location
//
serverDataFolder = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName);
if (Duplicati.Library.Utility.Utility.IsClientWindows)
{
var localappdata = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName);
var prefile = System.IO.Path.Combine(serverDataFolder, "Duplicati-server.sqlite");
var curfile = System.IO.Path.Combine(localappdata, "Duplicati-server.sqlite");
// If the new file exists, we use that
// If the new file does not exist, and the old file exists we use the old
// Otherwise we use the new location
if (System.IO.File.Exists(curfile) || !System.IO.File.Exists(prefile))
serverDataFolder = localappdata;
}
DataFolder = serverDataFolder;
}
#endif
}
else
DataFolder = Library.Utility.Utility.AppendDirSeparator(Library.Utility.Utility.ExpandEnvironmentVariables(serverDataFolder).Trim('"'));
var sqliteVersion = new Version((string)Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteConnectionType.GetProperty("SQLiteVersion").GetValue(null, null));
if (sqliteVersion < new Version(3, 6, 3))
{
//The official Mono SQLite provider is also broken with less than 3.6.3
throw new Exception(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3"));
}
//Create the connection instance
var con = Library.SQLiteHelper.SQLiteLoader.LoadConnection();
try
{
DatabasePath = System.IO.Path.Combine(DataFolder, "Duplicati-server.sqlite");
if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(DatabasePath)))
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(DatabasePath));
#if DEBUG
//Default is to not use encryption for debugging
var useDatabaseEncryption = commandlineOptions.ContainsKey("unencrypted-database") && !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true);
#else
var useDatabaseEncryption = !commandlineOptions.ContainsKey("unencrypted-database") || !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true);
#endif
con.ConnectionString = "Data Source=" + DatabasePath;
//Attempt to open the database, handling any encryption present
OpenDatabase(con, useDatabaseEncryption, dbPassword);
Duplicati.Library.SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(con, DatabasePath, typeof(Database.Connection));
}
catch (Exception ex)
{
//Unwrap the reflection exceptions
if (ex is System.Reflection.TargetInvocationException && ex.InnerException != null)
ex = ex.InnerException;
throw new Exception(Strings.Program.DatabaseOpenError(ex.Message));
}
return new Database.Connection(con);
}
public static void StartOrStopUsageReporter()
{
var disableUsageReporter =
string.Equals(DataConnection.ApplicationSettings.UsageReporterLevel, "none", StringComparison.OrdinalIgnoreCase)
||
string.Equals(DataConnection.ApplicationSettings.UsageReporterLevel, "disabled", StringComparison.OrdinalIgnoreCase);
Library.UsageReporter.ReportType reportLevel;
if (!Enum.TryParse<Library.UsageReporter.ReportType>(DataConnection.ApplicationSettings.UsageReporterLevel, true, out reportLevel))
Library.UsageReporter.Reporter.SetReportLevel(null, disableUsageReporter);
else
Library.UsageReporter.Reporter.SetReportLevel(reportLevel, disableUsageReporter);
}
public static void UpdateThrottleSpeeds()
{
if (Program.WorkThread == null)
return;
var cur = Program.WorkThread.CurrentTask;
if (cur != null)
cur.UpdateThrottleSpeed();
}
private static void SignalNewEvent(object sender, EventArgs e)
{
StatusEventNotifyer.SignalNewEvent();
}
/// <summary>
/// Handles a change in the LiveControl and updates the Runner
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void LiveControl_ThreadPriorityChanged(object sender, EventArgs e)
{
StatusEventNotifyer.SignalNewEvent();
}
/// <summary>
/// Handles a change in the LiveControl and updates the Runner
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void LiveControl_ThrottleSpeedChanged(object sender, EventArgs e)
{
StatusEventNotifyer.SignalNewEvent();
}
/// <summary>
/// This event handler updates the trayicon menu with the current state of the runner.
/// </summary>
static void LiveControl_StateChanged(object sender, EventArgs e)
{
switch (LiveControl.State)
{
case LiveControls.LiveControlState.Paused:
{
WorkThread.Pause();
var t = WorkThread.CurrentTask;
if (t != null)
t.Pause();
break;
}
case LiveControls.LiveControlState.Running:
{
WorkThread.Resume();
var t = WorkThread.CurrentTask;
if (t != null)
t.Resume();
break;
}
}
StatusEventNotifyer.SignalNewEvent();
}
/// <summary>
/// Helper method with logic to handle opening a database in possibly encrypted format
/// </summary>
/// <param name="con">The SQLite connection object</param>
/// <param name="useDatabaseEncryption">Specify if database is encrypted</param>
/// <param name="password">Encryption password</param>
public static void OpenDatabase(System.Data.IDbConnection con, bool useDatabaseEncryption, string password)
{
System.Reflection.MethodInfo setPwdMethod = con.GetType().GetMethod("SetPassword", new Type[] { typeof(string) });
string attemptedPassword;
if (!useDatabaseEncryption || string.IsNullOrEmpty(password))
attemptedPassword = null; //No encryption specified, attempt to open without
else
attemptedPassword = password; //Encryption specified, attempt to open with
if (setPwdMethod != null)
setPwdMethod.Invoke(con, new object[] { attemptedPassword });
try
{
//Attempt to open in preferred state
con.Open();
// Do a dummy query to make sure we have a working db
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "SELECT COUNT(*) FROM SQLITE_MASTER";
cmd.ExecuteScalar();
}
}
catch
{
try
{
//We can't try anything else without a password
if (string.IsNullOrEmpty(password))
throw;
//Open failed, now try the reverse
attemptedPassword = attemptedPassword == null ? password : null;
con.Close();
if (setPwdMethod != null)
setPwdMethod.Invoke(con, new object[] { attemptedPassword });
con.Open();
// Do a dummy query to make sure we have a working db
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "SELECT COUNT(*) FROM SQLITE_MASTER";
cmd.ExecuteScalar();
}
}
catch
{
try { con.Close(); }
catch { }
}
//If the db is not open now, it won't open
if (con.State != System.Data.ConnectionState.Open)
throw; //Report original error
//The open method succeeded with the non-default method, now change the password
System.Reflection.MethodInfo changePwdMethod = con.GetType().GetMethod("ChangePassword", new Type[] { typeof(string) });
changePwdMethod.Invoke(con, new object[] { useDatabaseEncryption ? password : null });
}
}
/// <summary>
/// Simple method for tracking if the server has crashed
/// </summary>
private static void PingPongMethod()
{
var rd = new System.IO.StreamReader(Console.OpenStandardInput());
var wr = new System.IO.StreamWriter(Console.OpenStandardOutput());
string line;
while ((line = rd.ReadLine()) != null)
{
if (string.Equals("shutdown", line, StringComparison.OrdinalIgnoreCase))
{
// TODO: All calls to ApplicationExitEvent and TrayIcon->Quit
// should check if we are running something
ApplicationExitEvent.Set();
}
else
{
wr.WriteLine("pong");
wr.Flush();
}
}
}
/// <summary>
/// The default log retention
/// </summary>
private static string DEFAULT_LOG_RETENTION = "30D";
/// <summary>
/// Gets a list of all supported commandline options
/// </summary>
public static Library.Interface.ICommandLineArgument[] SupportedCommands
{
get
{
var lst = new List<Duplicati.Library.Interface.ICommandLineArgument> (new Duplicati.Library.Interface.ICommandLineArgument[] {
new Duplicati.Library.Interface.CommandLineArgument("help", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.HelpCommandDescription, Strings.Program.HelpCommandDescription),
new Duplicati.Library.Interface.CommandLineArgument("unencrypted-database", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.UnencrypteddatabaseCommandDescription, Strings.Program.UnencrypteddatabaseCommandDescription),
new Duplicati.Library.Interface.CommandLineArgument("portable-mode", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.PortablemodeCommandDescription, Strings.Program.PortablemodeCommandDescription),
new Duplicati.Library.Interface.CommandLineArgument("log-file", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.LogfileCommandDescription, Strings.Program.LogfileCommandDescription),
new Duplicati.Library.Interface.CommandLineArgument("log-level", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Enumeration, Strings.Program.LoglevelCommandDescription, Strings.Program.LoglevelCommandDescription, "Warning", null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))),
new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_WEBROOT, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverWebrootDescription, Strings.Program.WebserverWebrootDescription, Duplicati.Server.WebServer.Server.DEFAULT_OPTION_WEBROOT),
new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_PORT, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverPortDescription, Strings.Program.WebserverPortDescription, Duplicati.Server.WebServer.Server.DEFAULT_OPTION_PORT.ToString()),
new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILE, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificateFileDescription, Strings.Program.WebserverCertificateFileDescription, Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILE.ToString()),
new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILEPASSWORD, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificatePasswordDescription, Strings.Program.WebserverCertificatePasswordDescription, Duplicati.Server.WebServer.Server.OPTION_SSLCERTIFICATEFILEPASSWORD.ToString()),
new Duplicati.Library.Interface.CommandLineArgument(Duplicati.Server.WebServer.Server.OPTION_INTERFACE, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverInterfaceDescription, Strings.Program.WebserverInterfaceDescription, Duplicati.Server.WebServer.Server.DEFAULT_OPTION_INTERFACE),
new Duplicati.Library.Interface.CommandLineArgument("webservice-password", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Password, Strings.Program.WebserverPasswordDescription, Strings.Program.WebserverPasswordDescription),
new Duplicati.Library.Interface.CommandLineArgument("ping-pong-keepalive", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.PingpongkeepaliveShort, Strings.Program.PingpongkeepaliveLong),
new Duplicati.Library.Interface.CommandLineArgument("log-retention", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Timespan, Strings.Program.LogretentionShort, Strings.Program.LogretentionLong, DEFAULT_LOG_RETENTION),
new Duplicati.Library.Interface.CommandLineArgument("server-datafolder", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.ServerdatafolderShort, Strings.Program.ServerdatafolderLong(DATAFOLDER_ENV_NAME), System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName)),
});
if (!Duplicati.Library.Utility.Utility.IsClientLinux)
lst.Add(new Duplicati.Library.Interface.CommandLineArgument("server-encryption-key", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Password, Strings.Program.ServerencryptionkeyShort, Strings.Program.ServerencryptionkeyLong(DB_KEY_ENV_NAME, "unencrypted-database"), Library.AutoUpdater.AutoUpdateSettings.AppName + "_Key_42"));
return lst.ToArray();
}
}
}
}