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:
+72
-12
@@ -3,7 +3,7 @@ using System.IO;
|
||||
|
||||
namespace DeskCore;
|
||||
|
||||
public enum TransferStatus { Ok, Skipped, Error }
|
||||
public enum TransferStatus { Ok, Skipped, Error, Cancelled }
|
||||
|
||||
/// <param name="Copied">true = kopiert (Original bleibt), false = verschoben</param>
|
||||
public record TransferResult(TransferStatus Status, string Source, string? Dest = null, bool Copied = false, string? Message = null);
|
||||
@@ -140,8 +140,10 @@ public static class Storage
|
||||
/// wird kopiert – das Original kann danach gelöscht werden. Innerhalb der App und zurück auf den
|
||||
/// Desktop wird verschoben. Gleiche Elemente werden nicht doppelt angelegt.
|
||||
/// </summary>
|
||||
public static TransferResult Transfer(string source, string targetDir, bool forceMove = false)
|
||||
public static TransferResult Transfer(string source, string targetDir, bool forceMove = false,
|
||||
Action<long>? onBytes = null, CancellationToken ct = default)
|
||||
{
|
||||
if (ct.IsCancellationRequested) return new(TransferStatus.Cancelled, source);
|
||||
var name = Path.GetFileName(source.TrimEnd('\\'));
|
||||
try
|
||||
{
|
||||
@@ -160,8 +162,18 @@ public static class Storage
|
||||
|
||||
Directory.CreateDirectory(targetDir);
|
||||
var dest = UniquePath(targetDir, name);
|
||||
if (copy) CopyAny(source, dest);
|
||||
else MoveAny(source, dest);
|
||||
try
|
||||
{
|
||||
if (copy) CopyAny(source, dest, onBytes, ct);
|
||||
else MoveAny(source, dest, onBytes, ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// halb kopiertes Ziel wieder entfernen – die Quelle ist unverändert
|
||||
// (beim Verschieben zwischen Laufwerken wird die Quelle erst nach vollständiger Kopie gelöscht)
|
||||
DeletePartial(dest);
|
||||
return new(TransferStatus.Cancelled, source);
|
||||
}
|
||||
return new(TransferStatus.Ok, source, dest, copy);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
@@ -176,7 +188,7 @@ public static class Storage
|
||||
}
|
||||
|
||||
/// <summary>Verschiebt Datei oder Ordner – auch zwischen Laufwerken.</summary>
|
||||
public static void MoveAny(string source, string dest)
|
||||
public static void MoveAny(string source, string dest, Action<long>? onBytes = null, CancellationToken ct = default)
|
||||
{
|
||||
NoteCreated(dest);
|
||||
if (File.Exists(source))
|
||||
@@ -189,15 +201,15 @@ public static class Storage
|
||||
Directory.Move(source, dest);
|
||||
return;
|
||||
}
|
||||
CopyDirectory(source, dest);
|
||||
CopyDirectory(source, dest, onBytes, ct);
|
||||
Directory.Delete(source, recursive: true);
|
||||
}
|
||||
|
||||
public static void CopyAny(string source, string dest)
|
||||
public static void CopyAny(string source, string dest, Action<long>? onBytes = null, CancellationToken ct = default)
|
||||
{
|
||||
NoteCreated(dest);
|
||||
if (Directory.Exists(source)) CopyDirectory(source, dest);
|
||||
else File.Copy(source, dest);
|
||||
if (Directory.Exists(source)) CopyDirectory(source, dest, onBytes, ct);
|
||||
else CopyFile(source, dest, onBytes, ct);
|
||||
}
|
||||
|
||||
/// <summary>Benennt ein Element um. Gibt den neuen Pfad zurück.</summary>
|
||||
@@ -321,12 +333,60 @@ public static class Storage
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyDirectory(string source, string dest)
|
||||
private static void CopyDirectory(string source, string dest, Action<long>? onBytes, CancellationToken ct)
|
||||
{
|
||||
Directory.CreateDirectory(dest);
|
||||
foreach (var f in Directory.GetFiles(source))
|
||||
File.Copy(f, Path.Combine(dest, Path.GetFileName(f)));
|
||||
CopyFile(f, Path.Combine(dest, Path.GetFileName(f)), onBytes, ct);
|
||||
foreach (var d in Directory.GetDirectories(source))
|
||||
CopyDirectory(d, Path.Combine(dest, Path.GetFileName(d)));
|
||||
CopyDirectory(d, Path.Combine(dest, Path.GetFileName(d)), onBytes, ct);
|
||||
}
|
||||
|
||||
/// <summary>Kopiert in 1-MB-Stücken, damit Fortschritt angezeigt und abgebrochen werden kann.</summary>
|
||||
private static void CopyFile(string source, string dest, Action<long>? onBytes, CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
const int Chunk = 1 << 20;
|
||||
using (var input = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, Chunk, FileOptions.SequentialScan))
|
||||
using (var output = new FileStream(dest, FileMode.CreateNew, FileAccess.Write, FileShare.None, Chunk))
|
||||
{
|
||||
var buffer = new byte[Chunk];
|
||||
int read;
|
||||
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
output.Write(buffer, 0, read);
|
||||
onBytes?.Invoke(read);
|
||||
}
|
||||
}
|
||||
// Zeitstempel und Attribute wie beim normalen Kopieren übernehmen
|
||||
try
|
||||
{
|
||||
File.SetCreationTimeUtc(dest, File.GetCreationTimeUtc(source));
|
||||
File.SetLastWriteTimeUtc(dest, File.GetLastWriteTimeUtc(source));
|
||||
File.SetAttributes(dest, File.GetAttributes(source) & ~FileAttributes.ReadOnly);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static void DeletePartial(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(path)) Directory.Delete(path, recursive: true);
|
||||
else if (File.Exists(path)) File.Delete(path);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <summary>Wie viele Bytes müssen für diese Übertragung tatsächlich kopiert werden? (0 = nur umbenennen)</summary>
|
||||
public static long BytesToCopy(string source, string targetDir, bool forceMove)
|
||||
{
|
||||
bool copy = !forceMove && !IsInStorage(source) && IsInStorage(targetDir);
|
||||
bool crossDrive = !string.Equals(Path.GetPathRoot(Path.GetFullPath(source)), Path.GetPathRoot(Path.GetFullPath(targetDir)),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
if (!copy && !crossDrive) return 0;
|
||||
try { return Directory.Exists(source) ? DirectorySize(source) : new FileInfo(source).Length; }
|
||||
catch { return 0; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user