330 lines
13 KiB
C#
330 lines
13 KiB
C#
using Ink_Canvas.Helpers;
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
|
using Page = iNKORE.UI.WPF.Modern.Controls.Page;
|
|
|
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
|
{
|
|
public partial class StoragePage : Page
|
|
{
|
|
// 已知子目录定义。其余顶层文件归入“核心文件”或“其他”。
|
|
private static readonly string[] LogDirs = { "Logs", "Crashs" };
|
|
private static readonly string[] InkDirs = { "Saves" };
|
|
private static readonly string[] BackupDirs = { "Backups" };
|
|
private static readonly string[] CustomDirs = { "icons", "backgrounds" };
|
|
private static readonly string[] PluginDirs = { "plugins", "Plugins" };
|
|
private static readonly string[] UpdateDirs = { "AutoUpdate" };
|
|
private static readonly string[] ConfigDirs = { "Configs" };
|
|
|
|
// 视为核心文件的扩展名(位于应用根目录下)
|
|
private static readonly string[] CoreFileExtensions =
|
|
{
|
|
".exe", ".dll", ".json", ".enc", ".dat", ".config", ".pdb",
|
|
".xml", ".manifest", ".runtimeconfig.json", ".deps.json"
|
|
};
|
|
|
|
private long _coreSize, _logsSize, _inkSize, _backupsSize,
|
|
_customSize, _pluginsSize, _updateSize, _otherSize;
|
|
|
|
public StoragePage()
|
|
{
|
|
InitializeComponent();
|
|
Loaded += StoragePage_Loaded;
|
|
}
|
|
|
|
private async void StoragePage_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
await RefreshAsync();
|
|
}
|
|
|
|
private void BtnRefresh_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_ = RefreshAsync();
|
|
}
|
|
|
|
private void BtnOpenAppFolder_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
|
{
|
|
FileName = App.RootPath,
|
|
UseShellExecute = true
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"打开应用目录失败: {ex.Message}", LogHelper.LogType.Error);
|
|
}
|
|
}
|
|
|
|
private async Task RefreshAsync()
|
|
{
|
|
SetAllSizesText(LocalizationHelper.GetString("Storage_Calculating"));
|
|
try
|
|
{
|
|
await Task.Run(() => CalculateSizes());
|
|
UpdateUI();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"计算存储占用失败: {ex.Message}", LogHelper.LogType.Error);
|
|
SetAllSizesText(LocalizationHelper.GetString("Storage_CalculateFailed"));
|
|
}
|
|
}
|
|
|
|
private void CalculateSizes()
|
|
{
|
|
_coreSize = _logsSize = _inkSize = _backupsSize =
|
|
_customSize = _pluginsSize = _updateSize = _otherSize = 0;
|
|
|
|
string root = App.RootPath;
|
|
if (string.IsNullOrEmpty(root) || !Directory.Exists(root)) return;
|
|
|
|
// 顶层目录归类
|
|
foreach (var dir in SafeEnumerateDirectories(root))
|
|
{
|
|
string name = Path.GetFileName(dir);
|
|
long size = GetDirectorySize(dir);
|
|
|
|
if (LogDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
|
|
_logsSize += size;
|
|
else if (InkDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
|
|
_inkSize += size;
|
|
else if (BackupDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
|
|
_backupsSize += size;
|
|
else if (CustomDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
|
|
_customSize += size;
|
|
else if (PluginDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
|
|
_pluginsSize += size;
|
|
else if (UpdateDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
|
|
_updateSize += size;
|
|
else if (ConfigDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
|
|
_coreSize += size;
|
|
else
|
|
_otherSize += size;
|
|
}
|
|
|
|
// 顶层文件按扩展名归类
|
|
foreach (var file in SafeEnumerateFiles(root))
|
|
{
|
|
long size = SafeFileLength(file);
|
|
string ext = Path.GetExtension(file).ToLowerInvariant();
|
|
if (CoreFileExtensions.Contains(ext))
|
|
_coreSize += size;
|
|
else
|
|
_otherSize += size;
|
|
}
|
|
}
|
|
|
|
private void UpdateUI()
|
|
{
|
|
long total = _coreSize + _logsSize + _inkSize + _backupsSize
|
|
+ _customSize + _pluginsSize + _updateSize + _otherSize;
|
|
|
|
TotalSizeTextBlock.Text = FormatSize(total);
|
|
|
|
CoreSizeText.Text = FormatSize(_coreSize);
|
|
LogsSizeText.Text = FormatSize(_logsSize);
|
|
InkSizeText.Text = FormatSize(_inkSize);
|
|
BackupsSizeText.Text = FormatSize(_backupsSize);
|
|
CustomSizeText.Text = FormatSize(_customSize);
|
|
PluginsSizeText.Text = FormatSize(_pluginsSize);
|
|
UpdateSizeText.Text = FormatSize(_updateSize);
|
|
OtherSizeText.Text = FormatSize(_otherSize);
|
|
|
|
// 更新柱状图列宽
|
|
if (total > 0)
|
|
{
|
|
BarCoreCol.Width = new GridLength(_coreSize, GridUnitType.Star);
|
|
BarLogsCol.Width = new GridLength(_logsSize, GridUnitType.Star);
|
|
BarInkCol.Width = new GridLength(_inkSize, GridUnitType.Star);
|
|
BarBackupsCol.Width = new GridLength(_backupsSize, GridUnitType.Star);
|
|
BarCustomCol.Width = new GridLength(_customSize, GridUnitType.Star);
|
|
BarPluginsCol.Width = new GridLength(_pluginsSize, GridUnitType.Star);
|
|
BarUpdateCol.Width = new GridLength(_updateSize, GridUnitType.Star);
|
|
BarOtherCol.Width = new GridLength(_otherSize, GridUnitType.Star);
|
|
}
|
|
else
|
|
{
|
|
BarCoreCol.Width = BarLogsCol.Width = BarInkCol.Width =
|
|
BarBackupsCol.Width = BarCustomCol.Width =
|
|
BarPluginsCol.Width = BarUpdateCol.Width =
|
|
BarOtherCol.Width = new GridLength(0, GridUnitType.Star);
|
|
}
|
|
|
|
// 占磁盘比例
|
|
try
|
|
{
|
|
var driveLetter = Path.GetPathRoot(App.RootPath);
|
|
if (!string.IsNullOrEmpty(driveLetter))
|
|
{
|
|
var drive = new DriveInfo(driveLetter);
|
|
if (drive.IsReady && drive.TotalSize > 0)
|
|
{
|
|
double pct = (double)total / drive.TotalSize * 100.0;
|
|
DiskPercentTextBlock.Text = pct < 0.01 ? "<0.01 %" : $"{pct:0.##} %";
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
DiskPercentTextBlock.Text = "—";
|
|
}
|
|
|
|
private void SetAllSizesText(string text)
|
|
{
|
|
TotalSizeTextBlock.Text = text;
|
|
CoreSizeText.Text = text;
|
|
LogsSizeText.Text = text;
|
|
InkSizeText.Text = text;
|
|
BackupsSizeText.Text = text;
|
|
CustomSizeText.Text = text;
|
|
PluginsSizeText.Text = text;
|
|
UpdateSizeText.Text = text;
|
|
OtherSizeText.Text = text;
|
|
}
|
|
|
|
#region 清理操作
|
|
|
|
private void BtnCleanLogs_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
CleanWithConfirm(LocalizationHelper.GetString("Storage_Logs_Header"), LogDirs, keepRoot: true);
|
|
}
|
|
|
|
private void BtnCleanInk_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
CleanWithConfirm(LocalizationHelper.GetString("Storage_Ink_Header"), InkDirs, keepRoot: true);
|
|
}
|
|
|
|
private void BtnCleanBackups_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
CleanWithConfirm(LocalizationHelper.GetString("Storage_Backups_Header"), BackupDirs, keepRoot: true);
|
|
}
|
|
|
|
private void BtnCleanUpdate_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
CleanWithConfirm(LocalizationHelper.GetString("Storage_Update_Header"), UpdateDirs, keepRoot: true);
|
|
}
|
|
|
|
private async void CleanWithConfirm(string displayName, string[] subDirs, bool keepRoot)
|
|
{
|
|
var result = MessageBox.Show(
|
|
string.Format(LocalizationHelper.GetString("Storage_Confirm_Body"), displayName).Replace("\\n", "\n"),
|
|
LocalizationHelper.GetString("Storage_Confirm_Title"),
|
|
MessageBoxButton.OKCancel,
|
|
MessageBoxImage.Warning);
|
|
if (result != MessageBoxResult.OK) return;
|
|
|
|
var second = MessageBox.Show(
|
|
string.Format(LocalizationHelper.GetString("Storage_Confirm_Second_Body"), displayName),
|
|
LocalizationHelper.GetString("Storage_Confirm_Second_Title"),
|
|
MessageBoxButton.OKCancel,
|
|
MessageBoxImage.Warning);
|
|
if (second != MessageBoxResult.OK) return;
|
|
|
|
try
|
|
{
|
|
await Task.Run(() =>
|
|
{
|
|
foreach (var sub in subDirs)
|
|
{
|
|
string path = Path.Combine(App.RootPath, sub);
|
|
if (!Directory.Exists(path)) continue;
|
|
|
|
if (keepRoot)
|
|
{
|
|
foreach (var file in SafeEnumerateFiles(path, recursive: true))
|
|
TryDeleteFile(file);
|
|
foreach (var dir in SafeEnumerateDirectories(path).Reverse())
|
|
TryDeleteDirectory(dir);
|
|
}
|
|
else
|
|
{
|
|
TryDeleteDirectory(path);
|
|
}
|
|
}
|
|
});
|
|
LogHelper.WriteLogToFile($"已清理「{displayName}」分类");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"清理「{displayName}」失败: {ex.Message}", LogHelper.LogType.Error);
|
|
MessageBox.Show(
|
|
string.Format(LocalizationHelper.GetString("Storage_CleanFailed"), ex.Message),
|
|
LocalizationHelper.GetString("Storage_Confirm_Title"),
|
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
|
|
await RefreshAsync();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 工具方法
|
|
|
|
private static long GetDirectorySize(string path)
|
|
{
|
|
long total = 0;
|
|
try
|
|
{
|
|
foreach (var file in SafeEnumerateFiles(path, recursive: true))
|
|
total += SafeFileLength(file);
|
|
}
|
|
catch { }
|
|
return total;
|
|
}
|
|
|
|
private static System.Collections.Generic.IEnumerable<string> SafeEnumerateDirectories(string path)
|
|
{
|
|
try { return Directory.EnumerateDirectories(path); }
|
|
catch { return System.Linq.Enumerable.Empty<string>(); }
|
|
}
|
|
|
|
private static System.Collections.Generic.IEnumerable<string> SafeEnumerateFiles(string path, bool recursive = false)
|
|
{
|
|
try
|
|
{
|
|
return Directory.EnumerateFiles(path, "*",
|
|
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
|
|
}
|
|
catch { return System.Linq.Enumerable.Empty<string>(); }
|
|
}
|
|
|
|
private static long SafeFileLength(string path)
|
|
{
|
|
try { return new FileInfo(path).Length; }
|
|
catch { return 0; }
|
|
}
|
|
|
|
private static void TryDeleteFile(string path)
|
|
{
|
|
try { File.Delete(path); } catch { }
|
|
}
|
|
|
|
private static void TryDeleteDirectory(string path)
|
|
{
|
|
try { Directory.Delete(path, true); } catch { }
|
|
}
|
|
|
|
private static string FormatSize(long bytes)
|
|
{
|
|
if (bytes <= 0) return "0 B";
|
|
string[] units = { "B", "KB", "MB", "GB", "TB" };
|
|
double size = bytes;
|
|
int u = 0;
|
|
while (size >= 1024 && u < units.Length - 1)
|
|
{
|
|
size /= 1024;
|
|
u++;
|
|
}
|
|
return u == 0 ? $"{(long)size} {units[u]}" : $"{size:0.##} {units[u]}";
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |