Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,31 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

---

## [1.19.1] — 2026-09-15

> Bugfix-only release on the 1.19.0 stable line. Fixes a data-loss-looking
> bug affecting macOS users upgrading from a build that predates the #825
> path fix, surfaced by a real user report.

### Fixed

- **macOS: #825 migration left database paths pointing at the old,
now-gone location (fixes #867)** — The one-time migration that moves
existing macOS users' data from the old, buggy path (`~/.config`/
`~/.local/share`) to the correct `~/Library/Application Support/opensak/`
(#825) moved the files themselves correctly, but never updated the
absolute `databases.list`/`databases.active`/`databases.dir` path
strings stored inside the migrated `opensak.json`. On next launch,
`DatabaseManager` looked for the active database at the old (no longer
existing) path, found nothing, and silently created a fresh, empty
database there instead — while the user's real data sat fully intact,
just orphaned, one folder over. Affected users should have their real
databases automatically found again on first launch of a build
containing this fix; no manual recovery should be needed. Thanks to
Mike Wood for the report.

---

## [1.19.0] — 2026-09-15

> First stable release of the 1.19.0 cycle. Replaces the `1.19.0-beta.1`
Expand Down
Binary file modified site/assets/screenshots/main-window.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/assets/screenshots/map-maximized.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/assets/screenshots/map-popout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions site/user-guide.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenSAK User Guide — v1.19.0</title>
<title>OpenSAK User Guide — v1.19.1</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Source+Serif+4:ital,wght@0,300;0,400;0,600;1,400&family=JetBrains+Mono:wght@400;500&display=swap');

Expand Down Expand Up @@ -473,7 +473,7 @@
<nav>
<div class="nav-header">
<span class="nav-logo">OpenSAK</span>
<div class="nav-version">User Guide · v1.19.0</div>
<div class="nav-version">User Guide · v1.19.1</div>
</div>

<div class="nav-section-heading">Getting Started</div>
Expand Down Expand Up @@ -535,7 +535,7 @@
<div class="hero-eyebrow">Complete User Guide</div>
<h1>OpenSAK</h1>
<p class="hero-sub">The open-source geocache management tool for Windows, Linux, and macOS.</p>
<div class="hero-meta">Version 1.19.0 &nbsp;·&nbsp; MIT Licence &nbsp;·&nbsp; <a href="https://github.com/OpenSAK-Org/opensak">github.com/OpenSAK-Org/opensak</a></div>
<div class="hero-meta">Version 1.19.1 &nbsp;·&nbsp; MIT Licence &nbsp;·&nbsp; <a href="https://github.com/OpenSAK-Org/opensak">github.com/OpenSAK-Org/opensak</a></div>
</div>

<figure class="screenshot" style="margin-top:-0.5rem;">
Expand Down Expand Up @@ -1463,7 +1463,7 @@ <h3>Debug Log</h3>
<tr><td>Bug reports &amp; feature requests</td><td><a href="https://github.com/OpenSAK-Org/opensak/issues">github.com/OpenSAK-Org/opensak/issues</a></td></tr>
<tr><td>Community discussion</td><td><a href="https://www.facebook.com/groups/opensak">Facebook group: OpenSAK</a></td></tr>
<tr><td>Releases &amp; downloads</td><td><a href="https://github.com/OpenSAK-Org/opensak/releases">github.com/OpenSAK-Org/opensak/releases</a></td></tr>
<tr><td>Changelog</td><td><a href="https://github.com/OpenSAK-Org/opensak/blob/v1.19.0/CHANGELOG.md">CHANGELOG.md on GitHub</a></td></tr>
<tr><td>Changelog</td><td><a href="https://github.com/OpenSAK-Org/opensak/blob/v1.19.1/CHANGELOG.md">CHANGELOG.md on GitHub</a></td></tr>
<tr><td>Contributing</td><td><a href="https://github.com/OpenSAK-Org/opensak/blob/main/CONTRIBUTING.md">CONTRIBUTING.md on GitHub</a></td></tr>
<tr><td>Support the project</td><td><a href="https://opencollective.com/opensak">opencollective.com/opensak</a></td></tr>
<tr><td>Website</td><td><a href="https://opensak.com">opensak.com</a></td></tr>
Expand All @@ -1473,7 +1473,7 @@ <h3>Debug Log</h3>
<div class="callout tip"><div class="callout-icon">💡</div><div>OpenSAK is free and open-source software released under the MIT licence. Contributions of any kind — code, translations, documentation, or testing — are very welcome.</div></div>

<p style="margin-top:2rem;color:var(--ink-light);font-size:0.85rem;font-style:italic;">
This guide was generated from the OpenSAK source code (v1.19.0). Last updated August 2026.
This guide was generated from the OpenSAK source code (v1.19.1). Last updated August 2026.
</p>
</section>

Expand Down
2 changes: 1 addition & 1 deletion src/opensak/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""OpenSAK — cross-platform geocache management tool."""
__version__ = "1.19.0"
__version__ = "1.19.1"
__author__ = "OpenSAK Contributors"
__license__ = "MIT"
73 changes: 73 additions & 0 deletions src/opensak/settings_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,68 @@ def migrate_from_qsettings(store: SettingsStore) -> bool:
return False


def _rewrite_stale_install_dir_paths(
json_path: Path, old_prefix: Path, new_prefix: Path
) -> None:
"""
Ret absolutte sti-strenge i en flyttet opensak.json, der stadig peger
på den gamle installations-mappe efter migrate_macos_default_paths()
har flyttet selve filerne.

Baggrund: migrate_macos_default_paths() flytter kun filerne fysisk —
den rører ikke opensak.json's eget INDHOLD. Men databases.list[].path,
databases.active og databases.dir er absolutte sti-strenge gemt i
netop den fil, og DatabaseManager._migrate_path() genkender kun den
ældre "/geocacher/"-mappe-omdøbning, ikke denne macOS-migrering. Uden
denne rettelse leder DatabaseManager derfor efter sin aktive database
på en sti der ikke længere findes, og opretter stiltiende en frisk,
tom database der i stedet for at finde brugerens rigtige (og fuldt
intakte) data ved siden af.

Best-effort: hvis filen ikke findes eller ikke er gyldig JSON, gøres
intet — dette må aldrig kunne forhindre selve fil-migreringen i at
have fuldført korrekt.
"""
if not json_path.exists():
return
try:
data = json.loads(json_path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return
if not isinstance(data, dict):
return

old_str = str(old_prefix)
new_str = str(new_prefix)
changed = False

def _rewrite(value: Any) -> Any:
nonlocal changed
if isinstance(value, str) and value.startswith(old_str):
changed = True
return new_str + value[len(old_str):]
return value

db_list = data.get("databases.list")
if isinstance(db_list, list):
for entry in db_list:
if isinstance(entry, dict) and "path" in entry:
entry["path"] = _rewrite(entry["path"])

if "databases.active" in data:
data["databases.active"] = _rewrite(data["databases.active"])

if "databases.dir" in data:
data["databases.dir"] = _rewrite(data["databases.dir"])

if changed:
try:
_atomic_write(json_path, data)
except OSError as exc:
print(f"[settings] macOS-migration: kunne ikke genskrive "
f"stale stier i {json_path}: {exc}")


def migrate_macos_default_paths() -> bool:
"""
Én-gangs migration af eksisterende macOS-brugeres data fra den
Expand Down Expand Up @@ -552,6 +614,17 @@ def migrate_macos_default_paths() -> bool:
old_default_install.rmdir()
except OSError:
pass

# Issue #XXX: databases.list/.active/.dir i opensak.json
# indeholder absolutte stier under den gamle mappe, som
# ovenstående filflytning ikke selv retter — uden dette leder
# DatabaseManager efter databasen på en sti der ikke længere
# findes, og opretter en tom database i stedet.
_rewrite_stale_install_dir_paths(
new_install_dir / "opensak.json",
old_default_install,
new_install_dir,
)
else:
# Brugervalgt mappe — indholdet er ikke ramt af bug'en, kun
# bootstrap.json's egen (forkerte) placering skal rettes.
Expand Down
72 changes: 72 additions & 0 deletions tests/unit-tests/test_settings_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,3 +589,75 @@ def test_idempotent_second_call_is_noop(self, monkeypatch, tmp_path):

assert ss.migrate_macos_default_paths() is True
assert ss.migrate_macos_default_paths() is False # nothing left to do

@posix_only
def test_rewrites_stale_database_paths_after_move(self, monkeypatch, tmp_path):
"""
Regression test for the bug found while investigating a real macOS
user's report (Mike, Sep 2026): the file move alone left
databases.list/.active pointing at the now-gone old directory, so
DatabaseManager couldn't find the (fully intact, just moved)
database and silently created a fresh empty one instead.
"""
self._patch_platform(monkeypatch, tmp_path)
old_dir = ss._legacy_macos_default_install_dir()
old_dir.mkdir(parents=True)
old_db_path = str(old_dir / "Default.db")
(old_dir / "opensak.json").write_text(json.dumps({
"user.gc_username": "MikeWood",
"databases.list": [{"name": "Default", "path": old_db_path}],
"databases.active": old_db_path,
}), encoding="utf-8")
(old_dir / "Default.db").write_text("real-cache-data", encoding="utf-8")

assert ss.migrate_macos_default_paths() is True

new_dir = ss._default_install_dir()
migrated_data = json.loads((new_dir / "opensak.json").read_text(encoding="utf-8"))

# Plain settings values must survive untouched.
assert migrated_data["user.gc_username"] == "MikeWood"

# Path-shaped values must now point at the NEW directory, not the
# old one that no longer exists.
expected_db_path = str(new_dir / "Default.db")
assert migrated_data["databases.list"][0]["path"] == expected_db_path
assert migrated_data["databases.active"] == expected_db_path
assert Path(expected_db_path).read_text(encoding="utf-8") == "real-cache-data"

# And DatabaseManager must actually find it — this is the part
# that silently failed before the fix.
from opensak.db.manager import DatabaseManager
mgr = DatabaseManager()
assert mgr.active is not None
assert mgr.active.path == Path(expected_db_path)
assert mgr.active.path.exists()

@posix_only
def test_rewrite_helper_noop_when_no_stale_paths(self, monkeypatch, tmp_path):
"""A user with no databases.* keys at all must not error or change anything."""
self._patch_platform(monkeypatch, tmp_path)
old_dir = ss._legacy_macos_default_install_dir()
old_dir.mkdir(parents=True)
(old_dir / "opensak.json").write_text(
json.dumps({"display.theme": "dark"}), encoding="utf-8"
)

assert ss.migrate_macos_default_paths() is True

new_dir = ss._default_install_dir()
data = json.loads((new_dir / "opensak.json").read_text(encoding="utf-8"))
assert data == {"display.theme": "dark"}

def test_rewrite_helper_missing_file_is_safe(self, tmp_path):
"""Must not raise if called against a path that doesn't exist."""
ss._rewrite_stale_install_dir_paths(
tmp_path / "does-not-exist.json", tmp_path / "old", tmp_path / "new"
) # no exception == pass

def test_rewrite_helper_invalid_json_is_safe(self, tmp_path):
"""Must not raise on a corrupted/non-JSON opensak.json."""
bad = tmp_path / "opensak.json"
bad.write_text("{not valid json", encoding="utf-8")
ss._rewrite_stale_install_dir_paths(bad, tmp_path / "old", tmp_path / "new")
assert bad.read_text(encoding="utf-8") == "{not valid json" # left untouched