Upload via GUI (298 Dateien)
This commit is contained in:
@@ -27,6 +27,11 @@ class MainForm : Form
|
||||
private readonly WebView2 _web = new() { Dock = DockStyle.Fill };
|
||||
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? _lastSyncedDataRaw;
|
||||
private bool _syncInProgress;
|
||||
|
||||
public MainForm(bool startMinimized = false)
|
||||
{
|
||||
@@ -90,6 +95,26 @@ class MainForm : Form
|
||||
"MyStart");
|
||||
Directory.CreateDirectory(dataDir);
|
||||
|
||||
// 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.
|
||||
string? oneDriveRoot = Environment.GetEnvironmentVariable("OneDrive")
|
||||
?? Environment.GetEnvironmentVariable("OneDriveConsumer")
|
||||
?? Environment.GetEnvironmentVariable("OneDriveCommercial");
|
||||
if (!string.IsNullOrEmpty(oneDriveRoot))
|
||||
{
|
||||
try
|
||||
{
|
||||
string syncDir = Path.Combine(oneDriveRoot, "MyStart");
|
||||
Directory.CreateDirectory(syncDir);
|
||||
_syncFilePath = Path.Combine(syncDir, "sync.json");
|
||||
_syncMarkerPath = Path.Combine(dataDir, "sync-marker.json");
|
||||
_lastSyncedDataRaw = File.Exists(_syncMarkerPath) ? File.ReadAllText(_syncMarkerPath) : null;
|
||||
}
|
||||
catch { _syncFilePath = null; }
|
||||
}
|
||||
|
||||
var env = await CoreWebView2Environment.CreateAsync(null, dataDir);
|
||||
await _web.EnsureCoreWebView2Async(env);
|
||||
|
||||
@@ -171,9 +196,122 @@ class MainForm : Form
|
||||
}
|
||||
};
|
||||
|
||||
// Kick a sync check after every completed load (first start, and any
|
||||
// reload the sync pull/push itself triggers), then keep polling.
|
||||
core.NavigationCompleted += async (_, e) =>
|
||||
{
|
||||
if (!e.IsSuccess) return;
|
||||
await SyncTickAsync(isClosing: false);
|
||||
if (_syncTimer == null && _syncFilePath != null)
|
||||
{
|
||||
_syncTimer = new System.Windows.Forms.Timer { Interval = 30_000 };
|
||||
_syncTimer.Tick += async (_, _) => await SyncTickAsync(isClosing: false);
|
||||
_syncTimer.Start();
|
||||
}
|
||||
};
|
||||
|
||||
core.Navigate("https://" + Host + "/index.html");
|
||||
}
|
||||
|
||||
// --- 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. ---
|
||||
private async Task SyncTickAsync(bool isClosing)
|
||||
{
|
||||
if (_syncFilePath == null || _syncInProgress) return;
|
||||
_syncInProgress = true;
|
||||
try
|
||||
{
|
||||
string raw = await _web.CoreWebView2.ExecuteScriptAsync(
|
||||
"window.__mystartSyncExport && window.__mystartSyncExport()");
|
||||
string? exportJson = JsonSerializer.Deserialize<string>(raw);
|
||||
if (string.IsNullOrEmpty(exportJson)) return;
|
||||
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);
|
||||
_lastSyncedDataRaw = localDataRaw;
|
||||
TryWriteMarker(localDataRaw);
|
||||
PostSyncStatus(true, "gesendet");
|
||||
}
|
||||
else if (fileDataRaw != null && fileDataRaw != localDataRaw && !isClosing)
|
||||
{
|
||||
// Nothing changed locally, but the shared file moved on -> pull,
|
||||
// unless we're actively being used right now.
|
||||
if (!ContainsFocus || WindowState == FormWindowState.Minimized)
|
||||
{
|
||||
string js = "window.__mystartSyncImport && window.__mystartSyncImport(" +
|
||||
JsonSerializer.Serialize(fileFullJson) + ")";
|
||||
await _web.CoreWebView2.ExecuteScriptAsync(js);
|
||||
_lastSyncedDataRaw = fileDataRaw;
|
||||
TryWriteMarker(fileDataRaw);
|
||||
PostSyncStatus(true, "aktualisiert");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PostSyncStatus(true, "aktuell");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PostSyncStatus(false, ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ExtractDataRaw(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return doc.RootElement.TryGetProperty("data", out var data) ? data.GetRawText() : null;
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
private void TryWriteMarker(string dataRaw)
|
||||
{
|
||||
try { if (_syncMarkerPath != null) File.WriteAllText(_syncMarkerPath, dataRaw); }
|
||||
catch { /* best effort */ }
|
||||
}
|
||||
|
||||
private void PostSyncStatus(bool ok, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(new { type = "sync-status", ok, message });
|
||||
_web.CoreWebView2.PostWebMessageAsJson(json);
|
||||
}
|
||||
catch { /* page may be mid-reload */ }
|
||||
}
|
||||
|
||||
protected override void OnFormClosing(FormClosingEventArgs e)
|
||||
{
|
||||
base.OnFormClosing(e);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// --- Autostart (per-user HKCU Run key, points at THIS exe with --minimized) ---
|
||||
private const string RunKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
||||
private const string RunValueName = "MyStart";
|
||||
|
||||
Reference in New Issue
Block a user