DeskCore 1.0.0
Desktop-Icons in eigene Kategorien sortieren: Windows-11-Design (hell/dunkel), Desktop-Boxen, automatisches Einsortieren nach Regeln, Rückgängig, Suche, Infobereich mit Tastenkürzel, Sicherung als ZIP und Installer ohne Adminrechte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace DeskCore;
|
||||
|
||||
/// <summary>Export/Import aller Kategorien samt Einstellungen als ZIP.</summary>
|
||||
public static class Backup
|
||||
{
|
||||
private const string CategoriesPrefix = "Kategorien/";
|
||||
private const string SettingsEntry = "einstellungen.json";
|
||||
|
||||
public static long StorageSize()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Directory.EnumerateFiles(Storage.Root, "*", SearchOption.AllDirectories)
|
||||
.Sum(f => { try { return new FileInfo(f).Length; } catch { return 0L; } });
|
||||
}
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
public static string FormatSize(long bytes) => bytes switch
|
||||
{
|
||||
< 1024 * 1024 => $"{Math.Max(1, bytes / 1024)} KB",
|
||||
< 1024L * 1024 * 1024 => $"{bytes / 1024.0 / 1024:0.#} MB",
|
||||
_ => $"{bytes / 1024.0 / 1024 / 1024:0.##} GB",
|
||||
};
|
||||
|
||||
/// <summary>Gibt die Anzahl gesicherter Dateien zurück.</summary>
|
||||
public static int Export(string zipPath)
|
||||
{
|
||||
if (File.Exists(zipPath)) File.Delete(zipPath);
|
||||
int count = 0;
|
||||
using var zip = ZipFile.Open(zipPath, ZipArchiveMode.Create);
|
||||
foreach (var cat in Storage.Categories())
|
||||
{
|
||||
var dir = Storage.CategoryPath(cat);
|
||||
zip.CreateEntry(CategoriesPrefix + cat + "/"); // auch leere Kategorien sichern
|
||||
foreach (var f in Directory.EnumerateFiles(dir, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
var rel = Path.GetRelativePath(Storage.Root, f).Replace('\\', '/');
|
||||
zip.CreateEntryFromFile(f, CategoriesPrefix + rel, CompressionLevel.Optimal);
|
||||
count++;
|
||||
}
|
||||
foreach (var d in Directory.EnumerateDirectories(dir, "*", SearchOption.AllDirectories))
|
||||
if (!Directory.EnumerateFileSystemEntries(d).Any())
|
||||
zip.CreateEntry(CategoriesPrefix + Path.GetRelativePath(Storage.Root, d).Replace('\\', '/') + "/");
|
||||
}
|
||||
var entry = zip.CreateEntry(SettingsEntry);
|
||||
using (var w = new StreamWriter(entry.Open()))
|
||||
w.Write(JsonSerializer.Serialize(AppSettings.Current, AppSettings.Json));
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>Führt eine Sicherung mit dem Bestand zusammen. Vorhandenes wird nicht überschrieben.</summary>
|
||||
public static (int Added, int Skipped) Import(string zipPath)
|
||||
{
|
||||
int added = 0, skipped = 0;
|
||||
using var zip = ZipFile.OpenRead(zipPath);
|
||||
var root = Path.GetFullPath(Storage.Root);
|
||||
|
||||
foreach (var e in zip.Entries)
|
||||
{
|
||||
if (!e.FullName.StartsWith(CategoriesPrefix, StringComparison.OrdinalIgnoreCase)) continue;
|
||||
var rel = e.FullName[CategoriesPrefix.Length..];
|
||||
if (rel.Length == 0) continue;
|
||||
var target = Path.GetFullPath(Path.Combine(root, rel));
|
||||
if (!Storage.IsUnder(target, root)) continue; // Schutz vor manipulierten ZIPs
|
||||
|
||||
if (e.FullName.EndsWith('/'))
|
||||
{
|
||||
Directory.CreateDirectory(target);
|
||||
continue;
|
||||
}
|
||||
if (File.Exists(target)) { skipped++; continue; }
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(target)!);
|
||||
e.ExtractToFile(target);
|
||||
added++;
|
||||
}
|
||||
|
||||
if (zip.GetEntry(SettingsEntry) is { } se)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var r = new StreamReader(se.Open());
|
||||
var imported = JsonSerializer.Deserialize<AppSettings>(r.ReadToEnd(), AppSettings.Json);
|
||||
if (imported is not null) Merge(imported);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return (added, skipped);
|
||||
}
|
||||
|
||||
private static void Merge(AppSettings from)
|
||||
{
|
||||
var s = AppSettings.Current;
|
||||
foreach (var (name, meta) in from.Categories ?? [])
|
||||
if (!s.Categories.ContainsKey(name)) s.Categories[name] = meta;
|
||||
foreach (var n in from.CategoryOrder ?? [])
|
||||
if (!s.CategoryOrder.Contains(n, StringComparer.OrdinalIgnoreCase)) s.CategoryOrder.Add(n);
|
||||
foreach (var r in from.Rules ?? [])
|
||||
if (!s.Rules.Any(x => string.Equals(x.Contains, r.Contains, StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(x.Category, r.Category, StringComparison.OrdinalIgnoreCase)))
|
||||
s.Rules.Add(r);
|
||||
s.Save();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user