Upload via GUI (309 Dateien)

This commit is contained in:
Git Manager GUI
2026-08-07 22:40:57 +02:00
parent 15bb4ad79a
commit f616086aad
5 changed files with 9309 additions and 34 deletions

View File

@@ -28,8 +28,9 @@ class MainForm : Form
private const string Host = "mystart.local";
private bool _pendingMaximize;
private System.Windows.Forms.Timer? _syncTimer;
private string? _syncFilePath; // OneDrive\MyStart\sync.json, or null if no OneDrive found
private string? _syncMarkerPath; // local record of what we last pushed/pulled, survives restarts
private string? _syncDir; // OneDrive\MyStart, or null if no OneDrive found
private string? _syncOwnFilePath; // OneDrive\MyStart\sync-<this PC's name>.json -- WE are the only writer of this file
private string? _syncMarkerPath; // local record of what we last pushed/pulled, survives restarts
private string? _lastSyncedDataRaw;
private bool _syncInProgress;
@@ -98,7 +99,13 @@ class MainForm : Form
// Optional OneDrive sync: if OneDrive is set up on this PC, drop a JSON
// snapshot of the whole app state (all Reiter/profiles) in a MyStart
// subfolder there. OneDrive itself does the actual transfer between
// PCs; we just poll that file. No OneDrive -> sync silently stays off.
// PCs; we just poll files there. No OneDrive -> sync silently stays off.
//
// Each PC writes ONLY its own file (sync-<machine name>.json) and reads
// everyone else's. Two devices never write the same path, so there is
// nothing for OneDrive to see as a conflict -- a shared single file that
// both PCs wrote to used to make OneDrive spawn "conflicted copy" files
// whenever both saved around the same moment.
string? oneDriveRoot = Environment.GetEnvironmentVariable("OneDrive")
?? Environment.GetEnvironmentVariable("OneDriveConsumer")
?? Environment.GetEnvironmentVariable("OneDriveCommercial");
@@ -108,11 +115,14 @@ class MainForm : Form
{
string syncDir = Path.Combine(oneDriveRoot, "MyStart");
Directory.CreateDirectory(syncDir);
_syncFilePath = Path.Combine(syncDir, "sync.json");
_syncDir = syncDir;
string device = Environment.MachineName;
foreach (char c in Path.GetInvalidFileNameChars()) device = device.Replace(c, '_');
_syncOwnFilePath = Path.Combine(syncDir, "sync-" + device + ".json");
_syncMarkerPath = Path.Combine(dataDir, "sync-marker.json");
_lastSyncedDataRaw = File.Exists(_syncMarkerPath) ? File.ReadAllText(_syncMarkerPath) : null;
}
catch { _syncFilePath = null; }
catch { _syncDir = null; _syncOwnFilePath = null; }
}
var env = await CoreWebView2Environment.CreateAsync(null, dataDir);
@@ -202,7 +212,7 @@ class MainForm : Form
{
if (!e.IsSuccess) return;
await SyncTickAsync(isClosing: false);
if (_syncTimer == null && _syncFilePath != null)
if (_syncTimer == null && _syncDir != null)
{
_syncTimer = new System.Windows.Forms.Timer { Interval = 30_000 };
_syncTimer.Tick += async (_, _) => await SyncTickAsync(isClosing: false);
@@ -214,14 +224,16 @@ class MainForm : Form
}
// --- OneDrive sync tick: export current state from the page (opaque JSON
// blob, the page understands its own structure -- we don't), compare with
// what's on disk, and push or pull whichever side is behind. "Local
// changed since last sync" always wins a push; a pull is skipped while
// the window is actively focused so we never yank a reload out from under
// someone mid-edit -- it'll catch up on the next tick after they click away. ---
// blob, the page understands its own structure -- we don't). If it's new
// since our last known-synced state, push it to OUR OWN file (never a
// shared one -- see the comment in InitAsync on why). Otherwise look at
// every other PC's file and pull in whichever is newest, if it actually
// differs -- skipped while the window is actively focused so we never
// yank a reload out from under someone mid-edit; it'll catch up on the
// next tick after they click away. ---
private async Task SyncTickAsync(bool isClosing)
{
if (_syncFilePath == null || _syncInProgress) return;
if (_syncDir == null || _syncOwnFilePath == null || _syncInProgress) return;
_syncInProgress = true;
try
{
@@ -232,39 +244,34 @@ class MainForm : Form
string? localDataRaw = ExtractDataRaw(exportJson);
if (localDataRaw == null) return;
string? fileFullJson = null;
string? fileDataRaw = null;
if (File.Exists(_syncFilePath))
{
fileFullJson = await File.ReadAllTextAsync(_syncFilePath);
fileDataRaw = ExtractDataRaw(fileFullJson);
}
if (localDataRaw != _lastSyncedDataRaw)
{
// Something changed here since we last synced -> push (last writer wins).
await File.WriteAllTextAsync(_syncFilePath, exportJson);
// Something changed here since we last synced -> push to our own file.
await File.WriteAllTextAsync(_syncOwnFilePath, exportJson);
_lastSyncedDataRaw = localDataRaw;
TryWriteMarker(localDataRaw);
PostSyncStatus(true, "gesendet");
}
else if (fileDataRaw != null && fileDataRaw != localDataRaw && !isClosing)
else if (!isClosing)
{
// Nothing changed locally, but the shared file moved on -> pull,
// unless we're actively being used right now.
if (!ContainsFocus || WindowState == FormWindowState.Minimized)
var peer = FindLatestPeer();
if (peer == null || peer.Value.DataRaw == localDataRaw)
{
PostSyncStatus(true, "aktuell");
}
else if (!ContainsFocus || WindowState == FormWindowState.Minimized)
{
string js = "window.__mystartSyncImport && window.__mystartSyncImport(" +
JsonSerializer.Serialize(fileFullJson) + ")";
JsonSerializer.Serialize(peer.Value.FullJson) + ")";
await _web.CoreWebView2.ExecuteScriptAsync(js);
_lastSyncedDataRaw = fileDataRaw;
TryWriteMarker(fileDataRaw);
_lastSyncedDataRaw = peer.Value.DataRaw;
TryWriteMarker(peer.Value.DataRaw);
PostSyncStatus(true, "aktualisiert");
}
}
else
{
PostSyncStatus(true, "aktuell");
else
{
PostSyncStatus(true, "wartet (Fenster gerade aktiv)");
}
}
}
catch (Exception ex)
@@ -277,6 +284,45 @@ class MainForm : Form
}
}
// Every other PC's sync-*.json (plus a one-time look at the old shared
// sync.json from before each PC had its own file, so nothing already
// written there gets silently stranded), newest "savedAt" wins.
private (string FullJson, string DataRaw)? FindLatestPeer()
{
if (_syncDir == null) return null;
string? bestSavedAt = null;
string? bestFullJson = null;
string? bestDataRaw = null;
var candidates = Directory.EnumerateFiles(_syncDir, "sync-*.json")
.Where(p => !string.Equals(p, _syncOwnFilePath, StringComparison.OrdinalIgnoreCase));
string legacyPath = Path.Combine(_syncDir, "sync.json");
if (File.Exists(legacyPath)) candidates = candidates.Append(legacyPath);
foreach (var path in candidates)
{
string json;
try { json = File.ReadAllText(path); } catch { continue; }
try
{
using var doc = JsonDocument.Parse(json);
if (!doc.RootElement.TryGetProperty("savedAt", out var saProp)) continue;
if (!doc.RootElement.TryGetProperty("data", out var dataProp)) continue;
string? savedAt = saProp.GetString();
if (savedAt == null) continue;
if (bestSavedAt == null || string.CompareOrdinal(savedAt, bestSavedAt) > 0)
{
bestSavedAt = savedAt;
bestFullJson = json;
bestDataRaw = dataProp.GetRawText();
}
}
catch { /* skip unreadable/mid-write file, try again next tick */ }
}
return bestFullJson != null && bestDataRaw != null ? (bestFullJson, bestDataRaw) : null;
}
private static string? ExtractDataRaw(string json)
{
try
@@ -309,7 +355,7 @@ class MainForm : Form
// Best-effort final push; not awaited/blocking (would risk deadlocking
// the UI thread against WebView2's own message pump on shutdown). The
// 30s poll already covers normal use -- this just shaves the tail end.
if (_syncFilePath != null) _ = SyncTickAsync(isClosing: true);
if (_syncDir != null) _ = SyncTickAsync(isClosing: true);
}
// --- Autostart (per-user HKCU Run key, points at THIS exe with --minimized) ---