duplicati/Duplicati/Library/AutoUpdater/DataFolderLocator.cs
2025-09-16 09:54:51 +02:00

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;
}
}