mirror of
https://github.com/duplicati/duplicati.git
synced 2025-11-28 03:20:25 +08:00
168 lines
7.5 KiB
C#
168 lines
7.5 KiB
C#
// Copyright (C) 2025, The Duplicati Team
|
|
// https://duplicati.com, hello@duplicati.com
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the "Software"),
|
|
// to deal in the Software without restriction, including without limitation
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using Duplicati.Library.Common.IO;
|
|
|
|
#nullable enable
|
|
|
|
namespace Duplicati.Library.AutoUpdater;
|
|
|
|
/// <summary>
|
|
/// Helper class to locate the default storage folder for the application
|
|
/// </summary>
|
|
public static class DataFolderLocator
|
|
{
|
|
/// <summary>
|
|
/// The log tag for this class
|
|
/// </summary>
|
|
private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(DataFolderLocator));
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// Note that the folder is not created, only the path is returned.
|
|
/// If the data folder is overriden, the overriden folder is used, and no search is performed.
|
|
/// </summary>
|
|
/// <param name="targetfilename">The filename to look for</param>
|
|
/// <param name="autoCreate">Whether to create the folder if it does not exist</param>
|
|
/// <returns>The default storage folder</returns>
|
|
public static string GetDefaultStorageFolder(string targetfilename, bool autoCreate, bool readOnly = false)
|
|
{
|
|
var folder = DataFolderManager.OVERRIDEN_DATAFOLDER
|
|
? DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)
|
|
: GetDefaultStorageFolderInternal(targetfilename, AutoUpdateSettings.AppName);
|
|
|
|
if (SystemIO.IO_OS.DirectoryExists(folder))
|
|
{
|
|
if (!SystemIO.IO_OS.FileExists(System.IO.Path.Combine(folder, Util.InsecurePermissionsMarkerFile)))
|
|
try
|
|
{
|
|
if (!readOnly)
|
|
SystemIO.IO_OS.DirectorySetPermissionUserRWOnly(folder);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", folder, ex.Message);
|
|
}
|
|
}
|
|
else if (autoCreate && !readOnly) // AutoCreate and readonly become incongruent
|
|
{
|
|
// Create the folder
|
|
SystemIO.IO_OS.DirectoryCreate(folder);
|
|
|
|
try
|
|
{
|
|
// Make sure the folder is only accessible by the current user
|
|
SystemIO.IO_OS.DirectorySetPermissionUserRWOnly(folder);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", folder, ex.Message);
|
|
}
|
|
}
|
|
|
|
return folder;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// Note that the folder is not created, only the path is returned.
|
|
/// </summary>
|
|
/// <param name="targetfilename">The filename to look for</param>
|
|
/// <param name="appName">The name of the application</param>
|
|
/// <returns>The default storage folder</returns>
|
|
internal static string GetDefaultStorageFolderInternal(string targetfilename, string appName)
|
|
{
|
|
//Normal mode uses the systems "(Local) Application Data" folder
|
|
// %LOCALAPPDATA% on Windows, ~/.config on Linux
|
|
var folder = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), appName);
|
|
|
|
if (OperatingSystem.IsWindows())
|
|
{
|
|
// Special handling for Windows:
|
|
// - Older versions use %APPDATA%
|
|
// - New versions use %LOCALAPPDATA%
|
|
// - And prevent using C:\Windows\
|
|
var newlocation = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), appName);
|
|
|
|
var folderOrder = new List<string>() {
|
|
folder,
|
|
newlocation
|
|
};
|
|
|
|
// If %LOCALAPPDATA% is inside the Windows folder, prefer the ProgramData folder instead
|
|
if (Common.IO.Util.IsPathUnderWindowsFolder(newlocation))
|
|
folderOrder.Add(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), appName));
|
|
|
|
// Prefer the most recent location
|
|
var matches = folderOrder.AsEnumerable()
|
|
.Reverse()
|
|
.Where(x => System.IO.File.Exists(System.IO.Path.Combine(x, targetfilename)))
|
|
.ToList();
|
|
|
|
// Use the most recent location found with content
|
|
// If none are found, use the most recent location
|
|
folder = matches.FirstOrDefault() ?? folderOrder.Last();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (OperatingSystem.IsLinux() && (folder == $"/{appName}" || folder == appName))
|
|
{
|
|
// Special handling for Linux with no home folder:
|
|
// - Older versions use /
|
|
// - but new versions use /var/lib/
|
|
var libfolder = System.IO.Path.Combine("var", "lib", appName);
|
|
|
|
var curfile = System.IO.Path.Combine(libfolder, targetfilename);
|
|
var prevfile = 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 = libfolder;
|
|
}
|
|
|
|
return folder;
|
|
}
|
|
}
|
|
|
|
|