Fortschritt, .NET-Prüfung, Updates, Fenster merken, Box-Funktionen
- Große Kopien laufen im Hintergrund mit Fortschritt und Abbrechen; abgebrochene Kopien werden aufgeräumt, das Original bleibt unverändert - Sichern/Wiederherstellen mit Fortschritt, keine halben ZIP-Dateien - Installer auf .NET Framework 4.8: erkennt fehlende .NET 9 Desktop Runtime und installiert sie auf Wunsch (Microsoft-Signatur wird geprüft) - Update-Hinweis über Gitea-Releases, Update mit automatischem Neustart - Fenstergröße und -position werden gemerkt - Desktop-Boxen: umbenennen, löschen, verschieben, herausziehen - Fenster und Boxen kehren auf sichtbare Bildschirme zurück - README ergänzt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+84
-15
@@ -32,6 +32,7 @@ public partial class MainWindow : Window
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
RestorePlacement();
|
||||
StateChanged += (_, _) => UpdateWindowState();
|
||||
Closing += OnClosing;
|
||||
PreviewKeyDown += OnPreviewKeyDown;
|
||||
@@ -73,6 +74,7 @@ public partial class MainWindow : Window
|
||||
private void OnClosing(object? sender, CancelEventArgs e)
|
||||
{
|
||||
if (App.Quitting) return;
|
||||
SavePlacement();
|
||||
if (S.TrayEnabled && S.CloseToTray)
|
||||
{
|
||||
e.Cancel = true;
|
||||
@@ -189,7 +191,7 @@ public partial class MainWindow : Window
|
||||
/// <summary>Vom Watcher: Dateien haben sich geändert.</summary>
|
||||
public void OnStorageChanged()
|
||||
{
|
||||
if (!_dragging) Refresh();
|
||||
if (!_dragging && !IsBusy) Refresh(); // während großer Kopien nicht ständig neu laden
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
@@ -792,7 +794,7 @@ public partial class MainWindow : Window
|
||||
IconMenu.Items.Add(MenuItem(fromApp ? "Aus der App löschen" : "Vom Desktop löschen", "", () => DeleteIcons(sel), gesture: "Entf"));
|
||||
}
|
||||
|
||||
private void RenameIcon(IconItem item)
|
||||
public void RenameIcon(IconItem item)
|
||||
{
|
||||
var ext = Storage.HiddenExtension(item.Path);
|
||||
var newName = Dialog.Prompt(this, "Umbenennen", "Neuer Name:", item.Name, "Umbenennen", text =>
|
||||
@@ -816,7 +818,7 @@ public partial class MainWindow : Window
|
||||
catch (Exception ex) { Error("Umbenennen fehlgeschlagen", ex.Message); }
|
||||
}
|
||||
|
||||
private void DeleteIcons(List<IconItem> sel)
|
||||
public void DeleteIcons(List<IconItem> sel)
|
||||
{
|
||||
// Ungesicherte Desktop-Icons: nachfragen. Alles andere geht direkt (Strg+Z holt es zurück).
|
||||
var unsorted = sel.Count(i => !Storage.IsInStorage(i.Path) && !i.IsSorted);
|
||||
@@ -842,19 +844,31 @@ public partial class MainWindow : Window
|
||||
else ShowToast($"{Plural(sel.Count, "Element", "Elemente")} gelöscht", "", undoable: true);
|
||||
}
|
||||
|
||||
/// <summary>Kopiert/verschiebt Dateien in einen Ordner – mit Rückgängig und Rückmeldung.</summary>
|
||||
public void TransferAll(IEnumerable<string> paths, string targetDir)
|
||||
/// <summary>
|
||||
/// Kopiert/verschiebt Dateien in einen Ordner – mit Rückgängig und Rückmeldung.
|
||||
/// Läuft im Hintergrund; bei großen Datenmengen erscheint ein Fortschritt mit Abbrechen.
|
||||
/// </summary>
|
||||
public async void TransferAll(IEnumerable<string> paths, string targetDir)
|
||||
{
|
||||
if (IsBusy) { ShowToast("Bitte warten, bis der laufende Vorgang fertig ist", "\uE946", info: true); return; }
|
||||
var list = paths.ToList();
|
||||
if (list.Count == 0) return;
|
||||
var target = Storage.IsDesktopDir(targetDir) ? DesktopName : Path.GetFileName(targetDir.TrimEnd('\\'));
|
||||
|
||||
// Große Ordner würden beim Kopieren doppelt Platz belegen → lieber verschieben?
|
||||
var bigFolders = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
if (Storage.IsInStorage(targetDir))
|
||||
{
|
||||
const long Limit = 200L * 1024 * 1024;
|
||||
var big = list.Where(p => Directory.Exists(p) && !Storage.IsInStorage(p))
|
||||
.Select(p => (Path: p, Size: Storage.DirectorySize(p, Limit * 50)))
|
||||
.Where(x => x.Size > Limit).ToList();
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
List<(string Path, long Size)> big;
|
||||
try
|
||||
{
|
||||
big = await Task.Run(() => list.Where(p => Directory.Exists(p) && !Storage.IsInStorage(p))
|
||||
.Select(p => (Path: p, Size: Storage.DirectorySize(p, Limit * 50)))
|
||||
.Where(x => x.Size > Limit).ToList());
|
||||
}
|
||||
finally { Mouse.OverrideCursor = null; }
|
||||
if (big.Count > 0)
|
||||
{
|
||||
var what = big.Count == 1
|
||||
@@ -867,8 +881,31 @@ public partial class MainWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
bool anyCopy = list.Any(p => !bigFolders.Contains(p) && !Storage.IsInStorage(p) && Storage.IsInStorage(targetDir));
|
||||
var detail = list.Count == 1 ? $"„{Storage.DisplayName(list[0])}“ → {target}" : $"{list.Count} Elemente → {target}";
|
||||
List<TransferResult> results;
|
||||
try
|
||||
{
|
||||
results = await RunWithProgress(anyCopy ? "Wird kopiert …" : "Wird verschoben …", detail, (sink, ct) =>
|
||||
{
|
||||
sink.SetTotal(list.Sum(p => Storage.BytesToCopy(p, targetDir, bigFolders.Contains(p))));
|
||||
var r = new List<TransferResult>();
|
||||
foreach (var p in list)
|
||||
{
|
||||
if (list.Count > 1) sink.Detail = $"„{Storage.DisplayName(p)}“ → {target}";
|
||||
r.Add(Storage.Transfer(p, targetDir, bigFolders.Contains(p), sink.Add, ct));
|
||||
}
|
||||
return Task.FromResult(r);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Refresh();
|
||||
Error("Übertragen fehlgeschlagen", ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
var entry = new UndoEntry($"{Plural(list.Count, "Element", "Elemente")} nach „{target}“");
|
||||
var results = list.Select(p => Storage.Transfer(p, targetDir, forceMove: bigFolders.Contains(p))).ToList();
|
||||
foreach (var r in results.Where(r => r.Status == TransferStatus.Ok))
|
||||
{
|
||||
if (r.Copied) entry.AddCopy(r.Source, r.Dest!);
|
||||
@@ -876,25 +913,33 @@ public partial class MainWindow : Window
|
||||
}
|
||||
Undo.Push(entry);
|
||||
Refresh();
|
||||
FenceManager.RefreshAll();
|
||||
|
||||
var ok = results.Where(r => r.Status == TransferStatus.Ok).ToList();
|
||||
var skipped = results.Where(r => r.Status == TransferStatus.Skipped).ToList();
|
||||
var cancelled = results.Count(r => r.Status == TransferStatus.Cancelled);
|
||||
var errors = results.Where(r => r.Status == TransferStatus.Error).Select(r => r.Message!).ToList();
|
||||
if (errors.Count > 0) { ShowErrors(errors); return; }
|
||||
|
||||
if (cancelled > 0)
|
||||
{
|
||||
ShowToast(ok.Count > 0 ? $"Abgebrochen – {Plural(ok.Count, "Element", "Elemente")} wurden noch übertragen" : "Abgebrochen – es wurde nichts verändert",
|
||||
"\uE711", undoable: ok.Count > 0, info: true);
|
||||
return;
|
||||
}
|
||||
if (ok.Count == 0 && skipped.Count > 0)
|
||||
{
|
||||
ShowToast(skipped.Count == 1 ? skipped[0].Message! : $"Alle {skipped.Count} Elemente sind schon in „{target}“", "", info: true);
|
||||
ShowToast(skipped.Count == 1 ? skipped[0].Message! : $"Alle {skipped.Count} Elemente sind schon in „{target}“", "\uE946", info: true);
|
||||
return;
|
||||
}
|
||||
var n = Plural(ok.Count, "Element", "Elemente");
|
||||
var extra = skipped.Count > 0 ? $" · {skipped.Count} waren schon da" : "";
|
||||
if (Storage.IsDesktopDir(targetDir))
|
||||
ShowToast($"{n} zurück auf den Desktop gelegt{extra}", "", undoable: true);
|
||||
ShowToast($"{n} zurück auf den Desktop gelegt{extra}", "\uE7F4", undoable: true);
|
||||
else if (ok.All(r => r.Copied))
|
||||
ShowToast($"{n} in „{target}“ gesichert{extra}", undoable: true);
|
||||
else
|
||||
ShowToast($"{n} nach „{target}“ verschoben{extra}", "", undoable: true);
|
||||
ShowToast($"{n} nach „{target}“ verschoben{extra}", "\uE8DE", undoable: true);
|
||||
}
|
||||
|
||||
// ---------- Rückgängig ----------
|
||||
@@ -1095,6 +1140,7 @@ public partial class MainWindow : Window
|
||||
HotkeyBox.IsEnabled = S.HotkeyEnabled;
|
||||
|
||||
AutoSortToggle.IsChecked = S.AutoSort;
|
||||
AutoUpdateToggle.IsChecked = S.AutoUpdateCheck;
|
||||
AutoRemoveToggle.IsChecked = S.AutoSortRemoveFromDesktop;
|
||||
RulesList.ItemsSource = null;
|
||||
RulesList.ItemsSource = S.Rules;
|
||||
@@ -1258,18 +1304,30 @@ public partial class MainWindow : Window
|
||||
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
|
||||
};
|
||||
if (dlg.ShowDialog(this) != true) return;
|
||||
if (Storage.IsUnder(dlg.FileName, Storage.Root))
|
||||
{
|
||||
Error("Anderen Ort wählen", "Die Sicherung kann nicht im DeskCore-Ordner selbst gespeichert werden – sie würde sich sonst mitsichern. " +
|
||||
"Am besten z. B. unter Dokumente oder auf einem USB-Stick speichern.");
|
||||
return;
|
||||
}
|
||||
var size = await Task.Run(Backup.StorageSize);
|
||||
if (size > 2L * 1024 * 1024 * 1024 &&
|
||||
!Dialog.Confirm(this, "Große Sicherung",
|
||||
$"Deine Kategorien sind zusammen {Backup.FormatSize(size)} groß (vor allem durch Ordner). Das Sichern kann eine Weile dauern " +
|
||||
"und braucht entsprechend Platz.", "Trotzdem sichern"))
|
||||
return;
|
||||
ShowToast("Sicherung läuft …", "", info: true);
|
||||
if (IsBusy) return;
|
||||
try
|
||||
{
|
||||
var count = await Task.Run(() => Backup.Export(dlg.FileName));
|
||||
var file = dlg.FileName;
|
||||
var count = await RunWithProgress("Sicherung wird erstellt …", Path.GetFileName(file), (sink, ct) =>
|
||||
{
|
||||
sink.SetTotal(size);
|
||||
return Task.FromResult(Backup.Export(file, sink.Add, ct));
|
||||
});
|
||||
ShowToast($"{Plural(count, "Datei", "Dateien")} gesichert in {Path.GetFileName(dlg.FileName)}", "");
|
||||
}
|
||||
catch (OperationCanceledException) { ShowToast("Sicherung abgebrochen – es wurde keine Datei angelegt", "\uE711", info: true); }
|
||||
catch (Exception ex) { Error("Sichern fehlgeschlagen", ex.Message); }
|
||||
}
|
||||
|
||||
@@ -1286,14 +1344,25 @@ public partial class MainWindow : Window
|
||||
"Die Kategorien aus der Sicherung werden zu deinen vorhandenen hinzugefügt. Vorhandene Dateien werden nicht überschrieben.",
|
||||
"Wiederherstellen"))
|
||||
return;
|
||||
if (IsBusy) return;
|
||||
try
|
||||
{
|
||||
var (added, skipped) = await Task.Run(() => Backup.Import(dlg.FileName));
|
||||
var file = dlg.FileName;
|
||||
var (added, skipped) = await RunWithProgress("Sicherung wird wiederhergestellt …", Path.GetFileName(file), (sink, ct) =>
|
||||
{
|
||||
sink.SetTotal(Backup.ArchiveSize(file));
|
||||
return Task.FromResult(Backup.Import(file, sink.Add, ct));
|
||||
});
|
||||
Refresh();
|
||||
FenceManager.Sync();
|
||||
LoadSettingsView();
|
||||
ShowToast($"{Plural(added, "Datei", "Dateien")} wiederhergestellt" + (skipped > 0 ? $" · {skipped} waren schon da" : ""), "");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Refresh();
|
||||
ShowToast("Wiederherstellen abgebrochen – bereits übernommene Dateien bleiben erhalten", "\uE711", info: true);
|
||||
}
|
||||
catch (Exception ex) { Error("Wiederherstellen fehlgeschlagen", $"Die Datei ist keine gültige Sicherung.\n\n{ex.Message}"); }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user