mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 00:54:26 +08:00
245 lines
7.3 KiB
C#
245 lines
7.3 KiB
C#
|
|
using System.Text.Json;
|
||
|
|
using LanMountainDesktop.Launcher.Models;
|
||
|
|
|
||
|
|
namespace LanMountainDesktop.Launcher.Services;
|
||
|
|
|
||
|
|
internal sealed class DataLocationResolver
|
||
|
|
{
|
||
|
|
private const string ConfigFileName = "data-location.config.json";
|
||
|
|
private const string PortableDataFolderName = "AppData";
|
||
|
|
|
||
|
|
private readonly string _appRoot;
|
||
|
|
private readonly string _configPath;
|
||
|
|
private readonly string _defaultSystemDataPath;
|
||
|
|
|
||
|
|
public DataLocationResolver(string appRoot)
|
||
|
|
{
|
||
|
|
_appRoot = Path.GetFullPath(appRoot);
|
||
|
|
_configPath = Path.Combine(_appRoot, ConfigFileName);
|
||
|
|
_defaultSystemDataPath = Path.Combine(
|
||
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||
|
|
"LanMountainDesktop");
|
||
|
|
}
|
||
|
|
|
||
|
|
public string AppRoot => _appRoot;
|
||
|
|
|
||
|
|
public string ConfigPath => _configPath;
|
||
|
|
|
||
|
|
public string DefaultSystemDataPath => _defaultSystemDataPath;
|
||
|
|
|
||
|
|
public string DefaultPortableDataPath => Path.Combine(_appRoot, PortableDataFolderName);
|
||
|
|
|
||
|
|
public bool IsPortableModeAllowed()
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var testFile = Path.Combine(_appRoot, $".write-test-{Guid.NewGuid():N}.tmp");
|
||
|
|
File.WriteAllText(testFile, string.Empty);
|
||
|
|
File.Delete(testFile);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public DataLocationConfig? LoadConfig()
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
if (!File.Exists(_configPath))
|
||
|
|
{
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
var json = File.ReadAllText(_configPath);
|
||
|
|
return JsonSerializer.Deserialize(json, AppJsonContext.Default.DataLocationConfig);
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Logger.Warn($"Failed to load data location config from '{_configPath}'. Error='{ex.Message}'.");
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool SaveConfig(DataLocationConfig config)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var json = JsonSerializer.Serialize(config, AppJsonContext.Default.DataLocationConfig);
|
||
|
|
File.WriteAllText(_configPath, json);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Logger.Warn($"Failed to save data location config to '{_configPath}'. Error='{ex.Message}'.");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public string ResolveDataRoot()
|
||
|
|
{
|
||
|
|
var config = LoadConfig();
|
||
|
|
if (config is null)
|
||
|
|
{
|
||
|
|
return _defaultSystemDataPath;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (string.Equals(config.DataLocationMode, "Portable", StringComparison.OrdinalIgnoreCase))
|
||
|
|
{
|
||
|
|
var portablePath = !string.IsNullOrWhiteSpace(config.PortableDataPath)
|
||
|
|
? config.PortableDataPath
|
||
|
|
: DefaultPortableDataPath;
|
||
|
|
return Path.GetFullPath(portablePath);
|
||
|
|
}
|
||
|
|
|
||
|
|
return !string.IsNullOrWhiteSpace(config.SystemDataPath)
|
||
|
|
? Path.GetFullPath(config.SystemDataPath)
|
||
|
|
: _defaultSystemDataPath;
|
||
|
|
}
|
||
|
|
|
||
|
|
public DataLocationMode ResolveMode()
|
||
|
|
{
|
||
|
|
var config = LoadConfig();
|
||
|
|
if (config is null)
|
||
|
|
{
|
||
|
|
return DataLocationMode.System;
|
||
|
|
}
|
||
|
|
|
||
|
|
return string.Equals(config.DataLocationMode, "Portable", StringComparison.OrdinalIgnoreCase)
|
||
|
|
? DataLocationMode.Portable
|
||
|
|
: DataLocationMode.System;
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool HasExistingSystemData()
|
||
|
|
{
|
||
|
|
var systemPath = _defaultSystemDataPath;
|
||
|
|
if (!Directory.Exists(systemPath))
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
var markerFiles = new[]
|
||
|
|
{
|
||
|
|
Path.Combine(systemPath, "settings.json"),
|
||
|
|
Path.Combine(systemPath, "launcher-settings.json"),
|
||
|
|
Path.Combine(systemPath, "component-state.db"),
|
||
|
|
Path.Combine(systemPath, "app.db")
|
||
|
|
};
|
||
|
|
|
||
|
|
return markerFiles.Any(File.Exists);
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool ApplyLocationChoice(DataLocationMode mode, bool migrateExistingData)
|
||
|
|
{
|
||
|
|
var config = new DataLocationConfig
|
||
|
|
{
|
||
|
|
DataLocationMode = mode.ToString(),
|
||
|
|
SystemDataPath = _defaultSystemDataPath,
|
||
|
|
PortableDataPath = DefaultPortableDataPath
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!SaveConfig(config))
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
var targetDataRoot = mode == DataLocationMode.Portable
|
||
|
|
? DefaultPortableDataPath
|
||
|
|
: _defaultSystemDataPath;
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
Directory.CreateDirectory(targetDataRoot);
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Logger.Warn($"Failed to create data directory '{targetDataRoot}'. Error='{ex.Message}'.");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (migrateExistingData && mode == DataLocationMode.Portable)
|
||
|
|
{
|
||
|
|
MigrateSystemDataToPortable();
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void MigrateSystemDataToPortable()
|
||
|
|
{
|
||
|
|
if (!HasExistingSystemData())
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var sourcePath = _defaultSystemDataPath;
|
||
|
|
var targetPath = DefaultPortableDataPath;
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
Directory.CreateDirectory(targetPath);
|
||
|
|
|
||
|
|
var filesToMigrate = Directory.GetFiles(sourcePath, "*", SearchOption.TopDirectoryOnly);
|
||
|
|
foreach (var file in filesToMigrate)
|
||
|
|
{
|
||
|
|
var fileName = Path.GetFileName(file);
|
||
|
|
var destFile = Path.Combine(targetPath, fileName);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
File.Copy(file, destFile, overwrite: true);
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Logger.Warn($"Failed to migrate file '{fileName}'. Error='{ex.Message}'.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var dirsToMigrate = Directory.GetDirectories(sourcePath, "*", SearchOption.TopDirectoryOnly);
|
||
|
|
foreach (var dir in dirsToMigrate)
|
||
|
|
{
|
||
|
|
var dirName = Path.GetFileName(dir);
|
||
|
|
if (string.Equals(dirName, ".launcher", StringComparison.OrdinalIgnoreCase) &&
|
||
|
|
string.Equals(Path.GetFileName(sourcePath), "LanMountainDesktop", StringComparison.OrdinalIgnoreCase))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
var destDir = Path.Combine(targetPath, dirName);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
CopyDirectory(dir, destDir);
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Logger.Warn($"Failed to migrate directory '{dirName}'. Error='{ex.Message}'.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Logger.Info($"Data migration completed. Source='{sourcePath}'; Target='{targetPath}'.");
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Logger.Warn($"Data migration failed. Source='{sourcePath}'; Target='{targetPath}'. Error='{ex.Message}'.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void CopyDirectory(string sourceDir, string destDir)
|
||
|
|
{
|
||
|
|
Directory.CreateDirectory(destDir);
|
||
|
|
|
||
|
|
foreach (var file in Directory.GetFiles(sourceDir))
|
||
|
|
{
|
||
|
|
var destFile = Path.Combine(destDir, Path.GetFileName(file));
|
||
|
|
File.Copy(file, destFile, overwrite: true);
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (var subDir in Directory.GetDirectories(sourceDir))
|
||
|
|
{
|
||
|
|
var destSubDir = Path.Combine(destDir, Path.GetFileName(subDir));
|
||
|
|
CopyDirectory(subDir, destSubDir);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|