diff --git a/Duplicati/Library/Main/DatabaseLocator.cs b/Duplicati/Library/Main/DatabaseLocator.cs index 124add6b4..d2b0c14d7 100644 --- a/Duplicati/Library/Main/DatabaseLocator.cs +++ b/Duplicati/Library/Main/DatabaseLocator.cs @@ -22,8 +22,6 @@ using System; using System.Linq; using System.Collections.Generic; -using Newtonsoft.Json.Serialization; -using Duplicati.Library.Common; namespace Duplicati.Library.Main { @@ -38,10 +36,65 @@ namespace Duplicati.Library.Main public string Username; //public string Passwordhash; public int Port; - public string Databasepath; + public string Databasepath; public string ParameterFile; } - + + /// + /// The filename of the file with database configurations + /// + private const string CONFIG_FILE = "dbconfig.json"; + + /// + /// Finds a default storage folder, using the operating system specific locations. + /// The targetfilename is used to detect locations that are used in previous versions. + /// If the targetfilename is found in an old location, but not the current, the old location is used. + /// + /// The filename to look for + /// The name of the application + /// The default storage folder + public static string GetDefaultStorageFolder(string targetfilename, string appName = "Duplicati") + { + //Normal mode uses the systems "(Local) Application Data" folder + // %LOCALAPPDATA% on Windows, ~/.config on Linux + var folder = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), appName); + + if (OperatingSystem.IsWindows()) + { + // Special handling for Windows: + // - Older versions use %APPDATA% + // - but new versions use %LOCALAPPDATA% + var newlocation = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), appName); + + var prevfile = System.IO.Path.Combine(folder, targetfilename); + var curfile = System.IO.Path.Combine(newlocation, targetfilename); + + // 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(prevfile)) + folder = newlocation; + } + + if (OperatingSystem.IsMacOS()) + { + // Special handling for MacOS: + // - Older versions use ~/.config/ + // - but new versions use ~/Library/Application\ Support/ + var configfolder = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", appName); + + var prevfile = System.IO.Path.Combine(configfolder, targetfilename); + var curfile = System.IO.Path.Combine(folder, targetfilename); + + // If the old file exists, and not the new file, we use the old + // Otherwise we use the new location + if (!System.IO.File.Exists(curfile) && System.IO.File.Exists(prevfile)) + folder = configfolder; + } + + return folder; + } + public static string GetDatabasePath(string backend, Options options, bool autoCreate = true, bool anyUsername = false) { if (options == null) @@ -50,40 +103,15 @@ namespace Duplicati.Library.Main if (!string.IsNullOrEmpty(options.Dbpath)) return options.Dbpath; - //Normal mode uses the systems "(Local) Application Data" folder - // %LOCALAPPDATA% on Windows, ~/.config on Linux + var folder = GetDefaultStorageFolder(CONFIG_FILE); - // 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 - // - - var folder = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Duplicati"); - - if (OperatingSystem.IsWindows()) - { - var newlocation = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Duplicati"); - - var prevfile = System.IO.Path.Combine(folder, "dbconfig.json"); - var curfile = System.IO.Path.Combine(newlocation, "dbconfig.json"); - - // 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(prevfile)) - folder = newlocation; - } - - var file = System.IO.Path.Combine(folder, "dbconfig.json"); + var file = System.IO.Path.Combine(folder, CONFIG_FILE); List configs; if (!System.IO.File.Exists(file)) configs = new List(); else configs = Newtonsoft.Json.JsonConvert.DeserializeObject>(System.IO.File.ReadAllText(file, System.Text.Encoding.UTF8)); - + var uri = new Library.Utility.Uri(backend); string server = uri.Host; string path = uri.Path; @@ -91,64 +119,64 @@ namespace Duplicati.Library.Main int port = uri.Port; string username = uri.Username; string prefix = options.Prefix; - + if (username == null || uri.Password == null) { var sopts = DynamicLoader.BackendLoader.GetSupportedCommands(backend); var ropts = new Dictionary(options.RawOptions); - foreach(var k in uri.QueryParameters.AllKeys) + foreach (var k in uri.QueryParameters.AllKeys) ropts[k] = uri.QueryParameters[k]; - + if (sopts != null) { - foreach(var o in sopts) + foreach (var o in sopts) { if (username == null && o.Aliases != null && o.Aliases.Contains("auth-username", StringComparer.OrdinalIgnoreCase) && ropts.ContainsKey(o.Name)) username = ropts[o.Name]; } - - foreach(var o in sopts) + + foreach (var o in sopts) { if (username == null && o.Name.Equals("auth-username", StringComparison.OrdinalIgnoreCase) && ropts.ContainsKey("auth-username")) username = ropts["auth-username"]; } } } - + //Now find the one that matches :) var matches = (from n in configs - where - n.Type == type && - //n.Passwordhash == password && - n.Username == username && - n.Port == port && - n.Server == server && - n.Path == path && - n.Prefix == prefix - select n).ToList(); - + where + n.Type == type && + //n.Passwordhash == password && + n.Username == username && + n.Port == port && + n.Server == server && + n.Path == path && + n.Prefix == prefix + select n).ToList(); + if (matches.Count > 1) throw new Duplicati.Library.Interface.UserInformationException(string.Format("Multiple sources found for: {0}", backend), "MultipleLocalDatabaseSourcesFound"); - + // Re-select if (matches.Count == 0 && anyUsername && string.IsNullOrEmpty(username)) { matches = (from n in configs - where - n.Type == type && - n.Port == port && - n.Server == server && - n.Path == path && - n.Prefix == prefix - select n).ToList(); - + where + n.Type == type && + n.Port == port && + n.Server == server && + n.Path == path && + n.Prefix == prefix + select n).ToList(); + if (matches.Count > 1) throw new Duplicati.Library.Interface.UserInformationException(String.Format("Multiple sources found for \"{0}\", try supplying --{1}", backend, "auth-username"), "MultipleLocalDatabaseSourcesFound"); } - + if (matches.Count == 0 && !autoCreate) return null; - + if (matches.Count == 0) { var backupname = options.BackupName; @@ -156,20 +184,21 @@ namespace Duplicati.Library.Main backupname = GenerateRandomName(); else { - foreach(var c in System.IO.Path.GetInvalidFileNameChars()) + foreach (var c in System.IO.Path.GetInvalidFileNameChars()) backupname = backupname.Replace(c.ToString(), ""); } - + var newpath = System.IO.Path.Combine(folder, backupname + ".sqlite"); int max_tries = 100; while (System.IO.File.Exists(newpath) && max_tries-- > 0) newpath = System.IO.Path.Combine(folder, GenerateRandomName()); - + if (System.IO.File.Exists(newpath)) throw new Duplicati.Library.Interface.UserInformationException("Unable to find a unique name for the database, please use --dbpath", "CannotCreateRandomName"); - + //Create a new one, add it to the list, and save it - configs.Add(new BackendEntry() { + configs.Add(new BackendEntry() + { Type = type, Server = server, Path = path, @@ -177,7 +206,7 @@ namespace Duplicati.Library.Main Username = username, //Passwordhash = password, Port = port, - Databasepath = newpath, + Databasepath = newpath, ParameterFile = null }); @@ -187,16 +216,16 @@ namespace Duplicati.Library.Main var settings = new Newtonsoft.Json.JsonSerializerSettings(); settings.Formatting = Newtonsoft.Json.Formatting.Indented; System.IO.File.WriteAllText(file, Newtonsoft.Json.JsonConvert.SerializeObject(configs, settings), System.Text.Encoding.UTF8); - + return newpath; } else { return matches[0].Databasepath; } - + } - + public static string GenerateRandomName() { var rnd = new Random(); @@ -210,11 +239,7 @@ namespace Duplicati.Library.Main public static bool IsDatabasePathInUse(string path) { - var folder = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Duplicati"); - if (!System.IO.Directory.Exists(folder)) - return false; - - var file = System.IO.Path.Combine(folder, "dbconfig.json"); + var file = System.IO.Path.Combine(GetDefaultStorageFolder(CONFIG_FILE), CONFIG_FILE); if (!System.IO.File.Exists(file)) return false; diff --git a/Duplicati/Server/Program.cs b/Duplicati/Server/Program.cs index 1436cd48f..34904a14f 100644 --- a/Duplicati/Server/Program.cs +++ b/Duplicati/Server/Program.cs @@ -24,6 +24,7 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; using Duplicati.Library.Common.IO; +using Duplicati.Library.Main; using Duplicati.Library.Main.Database; using Duplicati.Library.RestAPI; using Duplicati.Server.Database; @@ -622,50 +623,7 @@ namespace Duplicati.Server { //Normal release mode uses the systems "(Local) Application Data" folder // %LOCALAPPDATA% on Windows, ~/.config on Linux, ~/Library/Application\ Support on MacOS - - serverDataFolder = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName); - if (OperatingSystem.IsWindows()) - { - // 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 - - var localappdata = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName); - - var prefile = System.IO.Path.Combine(serverDataFolder, SERVER_DATABASE_FILENAME); - var curfile = System.IO.Path.Combine(localappdata, SERVER_DATABASE_FILENAME); - - // 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; - } - - if (OperatingSystem.IsMacOS()) - { - // Special handling for MacOS: - // - Older versions use ~/.config/ - // - but new versions use ~/Library/Application\ Support/ - // - // If we find a new version, lets use that - // otherwise use the older location - - var homefolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - var configfolder = System.IO.Path.Combine(homefolder, ".config", Library.AutoUpdater.AutoUpdateSettings.AppName); - - var prevfile = System.IO.Path.Combine(configfolder, SERVER_DATABASE_FILENAME); - var curfile = System.IO.Path.Combine(serverDataFolder, SERVER_DATABASE_FILENAME); - - // If the old file exists and the new does not, we switch back to the old location - if (System.IO.File.Exists(prevfile) && !System.IO.File.Exists(curfile)) - serverDataFolder = configfolder; - } - - DataFolder = serverDataFolder; + DataFolder = DatabaseLocator.GetDefaultStorageFolder(SERVER_DATABASE_FILENAME, Library.AutoUpdater.AutoUpdateSettings.AppName); } } else