context: 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 { return Library.Utility.Utility.AppendDirSeparator(Library.Utility.Utility.ExpandEnvironmentVariables("%" + DATAFOLDER_ENV_NAME + "%").TrimStart('"').TrimEnd('"')); } }
  
          /// <summary>
          /// The single instance
          /// </summary>
          public static SingleInstance Instance = null;
  
          /// <summary>
          /// A flag indicating if database encryption is in use
          /// </summary>
          public static bool UseDatabaseEncryption;
  
          /// <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
              bool writeConsole = System.Reflection.Assembly.GetEntryAssembly() == System.Reflection.Assembly.GetExecutingAssembly();
  
              //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
              if (!Library.Utility.Utility.IsClientLinux && string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DB_KEY_ENV_NAME)))
              {
                  //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.
                  Environment.SetEnvironmentVariable(DB_KEY_ENV_NAME, Library.AutoUpdater.AutoUpdateSettings.AppName + "_Key_42");
              }
  
  
              //Find commandline options here for handling special startup cases
              Dictionary<string, string> commandlineOptions = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(new List<string>(args));
  
              foreach(string s in args)
                  if (
                      s.Equals("help", StringComparison.InvariantCultureIgnoreCase) ||
                      s.Equals("/help", StringComparison.InvariantCultureIgnoreCase) ||
                      s.Equals("usage", StringComparison.InvariantCultureIgnoreCase) ||
                      s.Equals("/usage", StringComparison.InvariantCultureIgnoreCase))
                      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
              // Allow override of the environment variables from the commandline
              if (commandlineOptions.ContainsKey("server-datafolder"))
                  Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, commandlineOptions["server-datafolder"]);
              if (commandlineOptions.ContainsKey("server-encryption-key"))
                  Environment.SetEnvironmentVariable(DB_KEY_ENV_NAME, commandlineOptions["server-encryption-key"]);
  
              //Set the %DUPLICATI_HOME% env variable, if it is not already set
              if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DATAFOLDER_ENV_NAME)))
              {
  #if DEBUG
                  //debug mode uses a lock file located in the app folder
                  Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, 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
                      Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, System.IO.Path.Combine(StartupPath, "data"));
                      System.IO.Directory.SetCurrentDirectory(StartupPath);
                  }
                  else
                  {
                      //Normal release mode uses the systems "Application Data" folder
                      Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName));
                  }
  #endif
              }
  
              try
              {
                  try
                  {
                      //This will also create Program.DATAFOLDER if it does not exist
                      Instance = new SingleInstance(Duplicati.Library.AutoUpdater.AutoUpdateSettings.AppName, Program.DATAFOLDER);
                  }
                  catch (Exception ex)
                  {
                      if (writeConsole)
                      {
                          Console.WriteLine(Strings.Program.StartupFailure(ex));
                          return;
                      }
                      else
                      {
                          throw new Exception(Strings.Program.StartupFailure(ex));
                      }
                  }
  
                  if (!Instance.IsFirstInstance)
                  {
                      if (writeConsole)
                      {
                          Console.WriteLine(Strings.Program.AnotherInstanceDetected);
                          return;
                      }
                      else
                      {
                          throw new SingleInstance.MultipleInstanceException(Strings.Program.AnotherInstanceDetected);
                      }
                  }
  
                  // Setup the log redirect
                  Duplicati.Library.Logging.Log.CurrentLog = Program.LogHandler;
  
                  if (commandlineOptions.ContainsKey("log-file"))
                  {
                      if (System.IO.File.Exists(commandlineOptions["log-file"]))
                          System.IO.File.Delete(commandlineOptions["log-file"]);
  
                      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); 
                  }
  
                  Version sqliteVersion = new Version((string)Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteConnectionType.GetProperty("SQLiteVersion").GetValue(null, null));
                  if (sqliteVersion < new Version(3, 6, 3))
                  {
                      if (writeConsole)
                      {
                          //The official Mono SQLite provider is also broken with less than 3.6.3
                          Console.WriteLine(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3"));
                          return;
                      }
                      else
                      {
                          throw new Exception(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3"));
                      }
                  }
  
                  //Create the connection instance
                  System.Data.IDbConnection con = (System.Data.IDbConnection)Activator.CreateInstance(Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteConnectionType);
  
                  try
                  {
                      DatabasePath = System.IO.Path.Combine(Program.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
                      Program.UseDatabaseEncryption = commandlineOptions.ContainsKey("unencrypted-database") ? !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true) : false;
  #else
                      Program.UseDatabaseEncryption = commandlineOptions.ContainsKey("unencrypted-database") ? !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true) : true;
  #endif
                      con.ConnectionString = "Data Source=" + DatabasePath;
  
                      //Attempt to open the database, handling any encryption present
                      OpenDatabase(con);
  
                      Duplicati.Library.SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(con, DatabasePath, typeof(Duplicati.Server.Database.Connection));
                  }
                  catch (Exception ex)
                  {
                      //Unwrap the reflection exceptions
                      if (ex is System.Reflection.TargetInvocationException && ex.InnerException != null)
                          ex = ex.InnerException;
  
                      if (writeConsole)
                      {
                          Console.WriteLine(Strings.Program.DatabaseOpenError(ex.Message));
                          return;
                      }
                      else
                      {
                          throw new Exception(Strings.Program.DatabaseOpenError(ex.Message), ex);
                      }
                  }
  
                  DataConnection = new Duplicati.Server.Database.Connection(con);
  
                  if (!DataConnection.ApplicationSettings.FixedInvalidBackupId)
                      DataConnection.FixInvalidBackupId();
  
                  StartOrStopUsageReporter();
  
                  if (commandlineOptions.ContainsKey("webservice-password"))
                      Program.DataConnection.ApplicationSettings.SetWebserverPassword(commandlineOptions["webservice-password"]);
  
                  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;
  
gt:                         if (!commandlineOptions.TryGetValue("log-retention", out pts))