Upload via GUI (309 Dateien)
This commit is contained in:
@@ -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) ---
|
||||
|
||||
Reference in New Issue
Block a user