Release v0.6.59: clarify sync results and refresh downloaded settings

This commit is contained in:
agrynco committed 2026-11-09 11:22:27 +02:00
1 parent d0387904a0
commit 4d8d8d47b9
16 files changed
+107 -28

No files matched your search

+16 -2
View File
@@ -13,7 +13,7 @@ std::wstring App::syncCommand(int action, const std::vector<SyncChoice> &choices
validateSyncConfig(c);
if (action == 4 && (c != conflictConnection || choices.empty()))
throw std::runtime_error("Connection changed or no choices selected. Sync again.");
if (busy) return L"Wait for the current text operation to finish.";
if (busy) return syncStatus = L"Sync did not start. Wait for the current text operation to finish, then try again.";
syncRetryAfter = 0; // Explicit retries may bypass the automatic error backoff.
if (syncWorker.joinable()) syncWorker.join();
syncConnection = c;
@@ -22,7 +22,8 @@ std::wstring App::syncCommand(int action, const std::vector<SyncChoice> &choices
auto availableLayouts = layouts;
syncRunning = true;
syncDone = false;
syncStatus = L"Connecting to OpenBao...";
syncStatus = action == 2 ? L"Downloading remote settings from OpenBao..." :
action == 3 ? L"Uploading local settings to OpenBao..." : L"Connecting to OpenBao...";
nextSync = GetTickCount64() + SyncIntervalMs;
try
{
@@ -42,6 +43,9 @@ std::wstring App::syncCommand(int action, const std::vector<SyncChoice> &choices
}
catch (const nlohmann::json::exception &) { result.status = L"Invalid sync data or server response."; }
catch (const std::exception &e) { result.status = wide(e.what()); }
if (!result.success)
result.status = std::wstring(action == 2 ? L"Download failed. Local settings were not changed. " :
action == 3 ? L"Upload failed. " : L"Synchronization failed. ") + result.status;
syncResult = std::move(result);
syncDone.store(true);
});
@@ -76,8 +80,18 @@ void App::pollSync()
{
atomicWrite(data / L"settings-before-sync.bin", protect(snapshot));
applySettings(next);
syncStatus += L" Changes applied on this PC; local backup saved.";
}
else syncStatus += result.conflicts.empty() ? L" This PC's settings are already up to date; no local changes needed." :
L" No local settings were changed; conflicts still need your choice.";
saveSyncBase(data, syncConnection, result.base.empty() ? result.settings : result.base);
if (result.downloaded)
{
if (settingsWindow) SendMessageW(settingsWindow, ConfigSettingsDownloaded, 0, 0);
if (auto config = FindWindowW(L"KSwitcher.Config", nullptr);
config && configurationPeer(config, L"KSwitcher.Config.exe"))
PostMessageW(config, ConfigSettingsDownloaded, 0, 0);
}
pendingSyncConflicts = std::move(result.conflicts);
conflictConnection = syncConnection;
SYSTEMTIME time{}; GetLocalTime(&time);
+13
View File
@@ -52,6 +52,19 @@ LRESULT CALLBACK controller(HWND window, UINT message, WPARAM wp, LPARAM lp)
}
return FALSE;
}
if (message == ConfigSettingsDownloaded)
{
try
{
app->settings = decodeSettings(request(*app, ConfigRead), app->layouts);
if (app->settingsWindow) SendMessageW(app->settingsWindow, ConfigSettingsDownloaded, 0, 0);
}
catch (const std::exception &e)
{
MessageBoxW(app->settingsWindow, wide(e.what()).c_str(), L"Unable to refresh downloaded settings", MB_OK | MB_ICONERROR);
}
return 0;
}
if (message == WM_APP + 20)
{
try
+1
View File
@@ -2,6 +2,7 @@
#include "app.h"
namespace ks
{
inline constexpr UINT ConfigSettingsDownloaded = WM_APP + 24;
inline constexpr ULONG_PTR ConfigRead = 0x4b530100, ConfigSave = ConfigRead + 1,
ConfigLayouts = ConfigRead + 2, ConfigClearHistory = ConfigRead + 3,
ConfigCapture = ConfigRead + 4, ConfigLogs = ConfigRead + 5,
+14 -6
View File
@@ -39,7 +39,7 @@ class OpenBao final : public SyncProvider
SyncConfig config;
SyncTransport transport;
std::wstring token;
Json response(unsigned status, const Bytes &result, bool missing)
Json response(unsigned status, const Bytes &result, bool missing, const std::wstring &path, const wchar_t *method)
{
if (result.size() > Limit) throw std::runtime_error("OpenBao response is too large.");
if (status == 404 && missing)
@@ -50,7 +50,14 @@ class OpenBao final : public SyncProvider
return nullptr;
}
if (status == 401 || status == 403) throw std::runtime_error("OpenBao denied access. Check login and profile policy.");
if (status == 400) throw std::runtime_error("OpenBao rejected the write or its version changed. Sync again; check KV v2 configuration.");
if (status == 400)
{
if (path.starts_with(L"/v1/auth/"))
throw std::runtime_error("OpenBao rejected the authentication request (HTTP 400). Check username, password and login mount.");
if (std::wstring_view(method) == L"GET")
throw std::runtime_error("OpenBao rejected the download request (HTTP 400). Check KV v2 mount and profile path.");
throw std::runtime_error("OpenBao rejected the write (HTTP 400) or its version changed. Sync again; check KV v2 configuration.");
}
if (status < 200 || status >= 300) throw std::runtime_error("OpenBao HTTP error " + std::to_string(status) + ".");
return result.empty() ? Json::object() : parse(result);
}
@@ -59,7 +66,7 @@ class OpenBao final : public SyncProvider
if (transport)
{
auto reply = transport(method, path, token, body ? bytes(*body) : Bytes{});
return response(reply.status, reply.body, missing);
return response(reply.status, reply.body, missing, path, method);
}
URL_COMPONENTS url{sizeof(url)};
url.dwHostNameLength = url.dwUrlPathLength = url.dwExtraInfoLength = (DWORD)-1;
@@ -98,7 +105,7 @@ class OpenBao final : public SyncProvider
if (result.size() + received > Limit) throw std::runtime_error("OpenBao response is too large.");
result.insert(result.end(), chunk, chunk + received);
}
return response(status, result, missing);
return response(status, result, missing, path, method);
}
public:
explicit OpenBao(const SyncConfig &c, SyncTransport t) : config(c), transport(std::move(t))
@@ -306,9 +313,10 @@ SyncResult synchronize(SyncProvider &provider, const Bytes &base, const Bytes &l
auto revision = remote.revision;
if (parse(merged.remote) != parse(remote.settings) && action != SyncAction::Download) revision = provider.write(merged.remote, revision);
return {true, merged.local, revision,
merged.conflicts.empty() ? L"Synchronized. Server version " + std::to_wstring(revision) + L"." :
merged.conflicts.empty() ? std::wstring(action == SyncAction::Download ? L"Downloaded remote settings. Server version " :
action == SyncAction::Upload ? L"Uploaded local settings. Server version " : L"Synchronized. Server version ") + std::to_wstring(revision) + L"." :
L"Other changes synchronized. Resolve " + std::to_wstring(merged.conflicts.size()) + L" conflicting setting(s).",
merged.base, merged.conflicts};
merged.base, merged.conflicts, action == SyncAction::Download};
}
Bytes encodeSyncConflicts(const std::vector<SyncConflict> &conflicts)
{
+1
View File
@@ -63,6 +63,7 @@ struct SyncResult
std::wstring status;
Bytes base;
std::vector<SyncConflict> conflicts;
bool downloaded{};
};
SyncResult synchronize(SyncProvider &, const Bytes &base, const Bytes &local, SyncAction,
const std::function<void(const Bytes &)> &validate = {}, const std::vector<SyncChoice> &choices = {},
+4
View File
@@ -1688,6 +1688,10 @@ LRESULT CALLBACK settingsProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case ConfigSettingsDownloaded:
v->draft = v->app->settings;
fillSettings(w, v);
return 0;
case WM_ENABLE:
if (wp) updateSettingsState(w, v);
break;
+9 -4
View File
@@ -1,5 +1,5 @@
#include "ui_sync_conflicts.inl"
struct SyncView { HWND parent; App *app; };
struct SyncView { HWND parent; App *app; bool actionError{}; };
void updateSyncControls(HWND w, App &app)
{
bool enabled = checked(w, 601);
@@ -17,7 +17,8 @@ void updateSyncControls(HWND w, App &app)
status = L"Configure the connection, then click Save / Sign in / Sync to enable synchronization.";
else
status = app.syncCommand();
if (value(w, 608) != status) value(w, 608, status);
auto *view = reinterpret_cast<SyncView *>(GetWindowLongPtrW(w, GWLP_USERDATA));
if ((!view || !view->actionError) && value(w, 608) != status) value(w, 608, status);
}
SyncConfig syncFields(HWND w)
{
@@ -47,6 +48,7 @@ LRESULT CALLBACK syncProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
int id = LOWORD(wp);
if (id == 601 && HIWORD(wp) == BN_CLICKED)
{
v->actionError = false;
updateSyncControls(w, *v->app);
return 0;
}
@@ -56,6 +58,7 @@ LRESULT CALLBACK syncProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
if (id == IDCANCEL) { DestroyWindow(w); return 0; }
if (id == 609)
{
v->actionError = false;
auto c = loadSyncConfig(v->app->data);
c.enabled = false;
saveSyncConfig(v->app->data, c);
@@ -69,6 +72,7 @@ LRESULT CALLBACK syncProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
}
if (id == IDOK || id == 610 || id == 611)
{
v->actionError = false;
if (!checked(w, 601))
{
if (id != IDOK) return 0;
@@ -80,7 +84,7 @@ LRESULT CALLBACK syncProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
return 0;
}
if (id != IDOK && MessageBoxW(w, id == 610 ?
L"Replace this PC's shared settings with the remote profile? A local backup will be saved. Unsaved edits in Settings are not included." :
L"Replace this PC's shared settings with the remote profile? A local backup will be saved. The open Settings window will refresh automatically; its unsaved edits will be discarded." :
L"Replace the remote profile with this PC's saved settings? OpenBao retains older versions according to its retention policy. Unsaved edits are not included.",
L"Resolve synchronization", MB_YESNO | MB_ICONWARNING) != IDYES) return 0;
auto c = syncFields(w);
@@ -100,6 +104,7 @@ LRESULT CALLBACK syncProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
}
catch (const std::exception &e)
{
v->actionError = true; // Keep this action's error visible across status polling.
// Never display parser exceptions containing connection file contents.
value(w, 608, L"Unable to complete sync action. Check connection fields and saved configuration.");
if (dynamic_cast<const std::runtime_error *>(&e))
@@ -130,7 +135,7 @@ void showSyncOptions(HWND parent, App &app)
label(w, L"KV v2 &mount", 18, 210, 160); edit(w, 605, 190, 206, 450); value(w, 605, c.mount);
label(w, L"Profile pa&th", 18, 250, 160); edit(w, 606, 190, 246, 450); value(w, 606, c.path);
label(w, L"&Login mount", 18, 290, 160); edit(w, 607, 190, 286, 450); value(w, 607, c.authMount);
label(w, L"Sync uses saved settings. Close and reopen Settings after downloading changes.", 18, 330, 622);
label(w, L"Sync uses saved settings. Download remote refreshes the open Settings window.", 18, 330, 622);
edit(w, 608, 18, 362, 622, 76, ES_MULTILINE | ES_READONLY | WS_VSCROLL | ES_AUTOVSCROLL);
value(w, 608, app.syncCommand());
checkbox(w, L"&Keep this PC's layouts and layout shortcuts (sync other settings)", 613, 18, 446, 622);
+1 -1
View File
@@ -2,7 +2,7 @@
#include "platform.h"
namespace ks
{
inline constexpr wchar_t AppVersion[] = L"0.6.58";
inline constexpr wchar_t AppVersion[] = L"0.6.59";
inline constexpr UINT UpdateMessage = WM_APP + 7;
inline constexpr UINT UpdateCheckIntervalMs = 6 * 60 * 60 * 1000;
inline constexpr UINT UpdateNotificationRetryMs = 5 * 60 * 1000;