context: #region Disclaimer / License
  // Copyright (C) 2011, Kenneth Skovhede
  // http://www.hexad.dk, opensource@hexad.dk
  // 
  // This library is free software; you can redistribute it and/or
  // modify it under the terms of the GNU Lesser General Public
  // License as published by the Free Software Foundation; either
  // version 2.1 of the License, or (at your option) any later version.
  // 
  // This library is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  // Lesser General Public License for more details.
  // 
  // You should have received a copy of the GNU Lesser General Public
  // License along with this library; if not, write to the Free Software
  // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  // 
  #endregion
  using System;
  using System.Collections.Generic;
  using System.Text;
  using Duplicati.Library.Interface;
  
  namespace Duplicati.CommandLine.BackendTester
  {
      class Program
      {
          class TempFile
          {
              public readonly string remotefilename;
              public readonly string localfilename;
              public readonly byte[] hash;
              public readonly long length;
              public bool found = false;
  
              public TempFile(string remotefilename, string localfilename, byte[] hash, long length)
              {
                  this.remotefilename = remotefilename;
                  this.localfilename = localfilename;
                  this.hash = hash;
                  this.length = length;
              }
          }
  
          private const string ValidFilenameChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
          private const string ExtendedChars = "-_',=)(&%$#@! +";
  
          static void Main(string[] _args)
          {
              try
              {
                  List<string> args = new List<string>(_args);
                  Dictionary<string, string> options = Library.Utility.CommandLineParser.ExtractOptions(args);
  
                  if (!options.ContainsKey("auth_password") && !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("AUTH_PASSWORD")))
                      options["auth_password"] = System.Environment.GetEnvironmentVariable("AUTH_PASSWORD");
  
                  if (!options.ContainsKey("auth_username") && !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("AUTH_USERNAME")))
                      options["auth_username"] = System.Environment.GetEnvironmentVariable("AUTH_USERNAME");
  
                  if (options.ContainsKey("tempdir") && !string.IsNullOrEmpty(options["tempdir"]))
                      Library.Utility.TempFolder.SystemTempPath = options["tempdir"];
  
  
                  if (args.Count != 1 || args[0].ToLower() == "help" || args[0] == "?")
                  {
                      Console.WriteLine("Usage: <protocol>://<username>:<password>@<path>");
                      Console.WriteLine("Example: ftp://user:pass@server/folder");
                      Console.WriteLine();
                      Console.WriteLine("Supported backends: " + string.Join(",", Duplicati.Library.DynamicLoader.BackendLoader.Keys));
  
                      Console.WriteLine();
                      List<string> lines = new List<string>();
                      foreach (Library.Interface.ICommandLineArgument arg in SupportedCommands)
                          Library.Interface.CommandLineArgument.PrintArgument(lines, arg);
  
                      foreach (string s in lines)
                          Console.WriteLine(s);
  
                      return;
                  }
  
                  int reruns = 5;
                  if (options.ContainsKey("reruns"))
                      reruns = int.Parse(options["reruns"]);
  
                  for (int i = 0; i < reruns; i++)
                  {
                      Console.WriteLine("Starting run no {0}", i);
                      if (!Run(args, options, i == 0))
                          return;
                  }
                  Console.WriteLine("Unittest complete!");
              }
              catch (Exception ex)
              {
                  Console.WriteLine("Unittest failed: " + ex.ToString());
              }
          }
  
          static bool Run(List<string> args, Dictionary<string, string> options, bool first)
          {
              string allowedChars = ValidFilenameChars;
              if (options.ContainsKey("extended-chars"))
                  allowedChars += options["extended-chars"];
              else
                  allowedChars += ExtendedChars;
  
              bool autoCreateFolders = Library.Utility.Utility.ParseBoolOption(options, "auto-create-folder");
  
              Library.Interface.IBackend backend = Library.DynamicLoader.BackendLoader.GetBackend(args[0], options);
              if (backend == null)
              {
                  Console.WriteLine("Unsupported backend");
                  Console.WriteLine();
                  Console.WriteLine("Supported backends: " + string.Join(",", Duplicati.Library.DynamicLoader.BackendLoader.Keys));
                  return false;
              }
  
              string disabledModulesValue;
              string enabledModulesValue;
              options.TryGetValue("enable-module", out enabledModulesValue);
              options.TryGetValue("disable-module", out disabledModulesValue);
              string[] enabledModules = enabledModulesValue == null ? new string[0] : enabledModulesValue.Trim().ToLower().Split(',');
              string[] disabledModules = disabledModulesValue == null ? new string[0] : disabledModulesValue.Trim().ToLower().Split(',');
  
              List<Library.Interface.IGenericModule> loadedModules = new List<IGenericModule>();
              foreach (Library.Interface.IGenericModule m in Library.DynamicLoader.GenericLoader.Modules)
                  if (Array.IndexOf<string>(disabledModules, m.Key.ToLower()) < 0 && (m.LoadAsDefault || Array.IndexOf<string>(enabledModules, m.Key.ToLower()) >= 0))
                  {
                      m.Configure(options);
                      loadedModules.Add(m);
                  }
  
              try
              {
                  List<Library.Interface.IFileEntry> curlist = null;
                  try
                  {
                      curlist = backend.List();
                  }
                  catch (FolderMissingException fex)
                  {
                      if (autoCreateFolders)
                      {
                          try
                          {
                              backend.CreateFolder();
                              curlist = backend.List();
                          }
                          catch (Exception ex)
                          {
                              Console.WriteLine("Autocreate folder failed with message: " + ex.Message);
                          }
                      }
  
                      if (curlist == null)
                          throw fex;
                  }
  
                  foreach (Library.Interface.IFileEntry fe in curlist)
                      if (!fe.IsFolder)
                      {
                          if (Library.Utility.Utility.ParseBoolOption(options, "auto-clean") && first)
                              if (Library.Utility.Utility.ParseBoolOption(options, "force"))
                              {
                                  Console.WriteLine("Auto clean, removing file: {0}", fe.Name);
                                  backend.Delete(fe.Name);
                                  continue;
                              }
                              else
                                  Console.WriteLine("Specify the --force flag to actually delete files");
  
                          Console.WriteLine("*** Remote folder is not empty, aborting");
                          return false;
                      }
  
  
                  int number_of_files = 10;
                  int min_file_size = 1024;
                  int max_file_size = 1024 * 1024 * 50;
                  int min_filename_size = 5;
                  int max_filename_size = 80;
                  bool disableStreaming = Library.Utility.Utility.ParseBoolOption(options, "disable-streaming-transfers");
                  bool skipOverwriteTest = Library.Utility.Utility.ParseBoolOption(options, "skip-overwrite-test");
  
                  if (options.ContainsKey("number-of-files"))
                      number_of_files = int.Parse(options["number-of-files"]);
                  if (options.ContainsKey("min-file-size"))
                      min_file_size = (int)Duplicati.Library.Utility.Sizeparser.ParseSize(options["min-file-size"], "mb");
                  if (options.ContainsKey("max-file-size"))
                      max_file_size = (int)Duplicati.Library.Utility.Sizeparser.ParseSize(options["max-file-size"], "mb");
  
                  if (options.ContainsKey("min-filename-length"))
                      min_filename_size = int.Parse(options["min-filename-length"]);
                  if (options.ContainsKey("max-filename-length"))
                      max_filename_size = int.Parse(options["max-filename-length"]);
  
                  Random rnd = new Random();
                  System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create();
  
                  //Create random files
                  using (Library.Utility.TempFolder tf = new Duplicati.Library.Utility.TempFolder())
                  {
                      List<TempFile> files = new List<TempFile>();
                      for (int i = 0; i < number_of_files; i++)
                      {
  
                          StringBuilder filename = new StringBuilder();
                          int filenamelen = rnd.Next(min_filename_size, max_filename_size);
                          for (int j = 0; j < filenamelen; j++)
                              filename.Append(allowedChars[rnd.Next(0, allowedChars.Length)]);
  
                          string localfilename = CreateRandomFile(tf, i, min_file_size, max_file_size, rnd);
  
                          //Calculate local hash and length
                          using (System.IO.FileStream fs = new System.IO.FileStream(localfilename, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                              files.Add(new TempFile(filename.ToString(), localfilename, sha.ComputeHash(fs), fs.Length));
                      }
  
                      byte[] dummyFileHash = null;
                      if (!skipOverwriteTest)
                      {
                          Console.WriteLine("Uploading wrong files ...");
                          using (Library.Utility.TempFile dummy = Library.Utility.TempFile.WrapExistingFile(CreateRandomFile(tf, files.Count, 1024, 2048, rnd)))
                          {
                              using (System.IO.FileStream fs = new System.IO.FileStream(dummy, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                                  dummyFileHash = sha.ComputeHash(fs);
  
                              //Upload a dummy file for entry 0 and the last one, they will be replaced by the real files afterwards
                              //We upload entry 0 twice just to try to freak any internal cache list
                              Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming);
                              Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming);
                              Uploadfile(dummy, files.Count - 1, files[files.Count - 1].remotefilename, backend, disableStreaming);
                          }
  
                      }
  
                      Console.WriteLine("Uploading files ...");
  
                      for (int i = 0; i < files.Count; i++)
                          Uploadfile(files[i].localfilename, i, files[i].remotefilename, backend, disableStreaming);
  
                      Console.WriteLine("Verifying file list ...");
  
                      curlist = backend.List();
                      foreach (Library.Interface.IFileEntry fe in curlist)
                          if (!fe.IsFolder)
                          {
                              bool found = false;
                              foreach (TempFile tx in files)
                                  if (tx.remotefilename == fe.Name)
                                  {
                                      if (tx.found)
                                          Console.WriteLine("*** File with name {0} was found more than once", tx.remotefilename);
                                      found = true;
                                      tx.found = true;
  
                                      if (fe.Size > 0 && tx.length != fe.Size)
                                          Console.WriteLine("*** File with name {0} has size {1} but the size was reported as {2}", tx.remotefilename, tx.length, fe.Size);
  
                                      break;
                                  }
  
                              if (!found)
                                  Console.WriteLine("*** File with name {0} was found on server but not uploaded!", fe.Name);
                          }
  
                      foreach (TempFile tx in files)
                          if (!tx.found)
                              Console.WriteLine("*** File with name {0} was uploaded but not found afterwards", tx.remotefilename);
  
                      Console.WriteLine("Downloading files");
  
                      for (int i = 0; i < files.Count; i++)
                      {
                          using (Duplicati.Library.Utility.TempFile cf = new Duplicati.Library.Utility.TempFile())
                          {
                              Exception e = null;
                              Console.Write("Downloading file {0} ... ", i);
  
                              try
                              {
                                  if (backend is Library.Interface.IStreamingBackend && !disableStreaming)
                                  {
                                      using (System.IO.FileStream fs = new System.IO.FileStream(cf, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
                                      using (NonSeekableStream nss = new NonSeekableStream(fs))
                                          (backend as Library.Interface.IStreamingBackend).Get(files[i].remotefilename, nss);
                                  }
                                  else
                                      backend.Get(files[i].remotefilename, cf);
  
                                  e = null;
                              }
                              catch (Exception ex)
                              {
                                  e = ex;
                              }
  
  
gt:                             if (e != null)