diff --git a/Duplicati/Library/AutoUpdater/UpdaterManager.cs b/Duplicati/Library/AutoUpdater/UpdaterManager.cs index c9fe4d1e3..59f3adb27 100644 --- a/Duplicati/Library/AutoUpdater/UpdaterManager.cs +++ b/Duplicati/Library/AutoUpdater/UpdaterManager.cs @@ -40,9 +40,10 @@ namespace Duplicati.Library.AutoUpdater public static readonly string INSTALLDIR; - private static readonly string INSTALLED_BASE_DIR = string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable(string.Format(INSTALLDIR_ENVNAME_TEMPLATE, APPNAME))) ? - System.IO.Path.GetDirectoryName( Duplicati.Library.Utility.Utility.getEntryAssembly().Location) - : System.Environment.GetEnvironmentVariable(string.Format(INSTALLDIR_ENVNAME_TEMPLATE, APPNAME)); + private static readonly string INSTALLED_BASE_DIR = + string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable(string.Format(BASEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME))) + ? System.IO.Path.GetDirectoryName(Duplicati.Library.Utility.Utility.getEntryAssembly().Location) + : Library.Utility.Utility.ExpandEnvironmentVariables(System.Environment.GetEnvironmentVariable(string.Format(BASEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME))); private static readonly bool DISABLE_UPDATE_DOMAIN = !string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable(string.Format(SKIPUPDATE_ENVNAME_TEMPLATE, APPNAME))); @@ -57,7 +58,8 @@ namespace Duplicati.Library.AutoUpdater public static event Action OnError; private const string DATETIME_FORMAT = "yyyymmddhhMMss"; - private const string INSTALLDIR_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_INSTALL_ROOT"; + private const string BASEINSTALLDIR_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_INSTALL_ROOT"; + private const string UPDATEINSTALLDIR_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_UPDATE_ROOT"; internal const string SKIPUPDATE_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_SKIP_UPDATE"; private const string RUN_UPDATED_FOLDER_PATH = "AUTOUPDATER_LOAD_UPDATE"; private const string SLEEP_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_SLEEP"; @@ -80,26 +82,104 @@ namespace Duplicati.Library.AutoUpdater static UpdaterManager() { - string installdir = null; - var attempts = new string[] { - System.IO.Path.Combine(InstalledBaseDir, "updates"), + // Update folder strategy is a bit complicated, + // because it depends on the actual system, + // and because it tries to find a good spot + // by probing for locations - // Not defined on Non-Windows - string.IsNullOrWhiteSpace(System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)) - ? null : System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), APPNAME, "updates"), + // The "overrides" paths are checked, + // to see if they exist and are writeable. + // The first existing and writeable path + // for "overrides" is chosen - System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), APPNAME, "updates"), - System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), APPNAME, "updates"), - }; + // If override was not found, the "legacypaths" + // are checked in the same way to see if + // we have previously used such a folder + // and if that folder has contents, + // which indicates that it has been used. - foreach(var p in attempts) - if (!string.IsNullOrWhiteSpace(p) && TestDirectoryIsWriteable(p)) - { - installdir = p; - break; - } + // Finally we check the "attempts", + // which are suitable candidates + // for storing the updates on each + // operating system + + if (string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable(string.Format(UPDATEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME)))) + { + string installdir = null; + var programfiles = System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + + // The user can override updates by having a local updates folder + var overrides = new List(new string [] { + System.IO.Path.Combine(InstalledBaseDir, "updates"), + }); + + if (Library.Utility.Utility.IsClientWindows) + { + overrides.Add(System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), APPNAME, "updates")); + overrides.Add(System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), APPNAME, "updates")); + } + else + { + if (Library.Utility.Utility.IsClientOSX) + overrides.Add(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Application Support", APPNAME, "updates")); + + overrides.Add(System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), APPNAME, "updates")); + } + + // Previous locations that we don't want to use, + // but we keep them active to avoid breaking the update syste + var legacypaths = new List(); + + if (!string.IsNullOrWhiteSpace(programfiles)) + legacypaths.Add(System.IO.Path.Combine(programfiles, APPNAME, "updates")); + if (Library.Utility.Utility.IsClientLinux) + legacypaths.Add(System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), APPNAME, "updates")); + + // The real attempts that we probe for + var attempts = new List(); + + // We do not want to install anything in the basedir, if the application is installed in "ProgramFiles" + if (!string.IsNullOrWhiteSpace(programfiles) && !InstalledBaseDir.StartsWith(Library.Utility.Utility.AppendDirSeparator(programfiles))) + attempts.Add(System.IO.Path.Combine(InstalledBaseDir, "updates")); + + if (Library.Utility.Utility.IsClientOSX) + attempts.Add(System.IO.Path.Combine("/", "Library", "Application Support", APPNAME, "updates")); + else + attempts.Add(System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), APPNAME, "updates")); + + attempts.AddRange(overrides.Skip(1)); + + // Check if the override folder exists, and choose that + foreach (var p in overrides) + if (!string.IsNullOrWhiteSpace(p) && System.IO.Directory.Exists(p) && TestDirectoryIsWriteable(p)) + { + installdir = p; + break; + } + + if (string.IsNullOrWhiteSpace(installdir)) + foreach (var p in legacypaths) + if (!string.IsNullOrWhiteSpace(p) && System.IO.Directory.Exists(p) && System.IO.Directory.EnumerateFiles(p, "*", System.IO.SearchOption.TopDirectoryOnly).Count() > 0 && TestDirectoryIsWriteable(p)) + { + installdir = p; + break; + } + + if (string.IsNullOrWhiteSpace(installdir)) + foreach (var p in attempts) + if (!string.IsNullOrWhiteSpace(p) && TestDirectoryIsWriteable(p)) + { + installdir = p; + break; + } - INSTALLDIR = installdir; + INSTALLDIR = installdir; + } + else + { + INSTALLDIR = Library.Utility.Utility.ExpandEnvironmentVariables(System.Environment.GetEnvironmentVariable(string.Format(UPDATEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME))); + } + if (INSTALLDIR != null) { @@ -963,7 +1043,7 @@ namespace Duplicati.Library.AutoUpdater } } - Environment.SetEnvironmentVariable(string.Format(INSTALLDIR_ENVNAME_TEMPLATE, APPNAME), InstalledBaseDir); + Environment.SetEnvironmentVariable(string.Format(BASEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME), InstalledBaseDir); var folder = best.Key; diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index 71165ffa9..44d070d50 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -751,6 +751,17 @@ namespace Duplicati.Library.Utility } } + /// + /// Gets a value indicating if the client is Windows based + /// + public static bool IsClientWindows + { + get + { + return !IsClientLinux; + } + } + /// /// Returns a value indicating if the filesystem, is case sensitive ///