From f686ec6a5a5f5dd9e993b0b4f7a962a4b4e65322 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 30 Jan 2025 23:11:10 +0100 Subject: [PATCH] Save recent projects by their ID instead of path (#2217) --- novelwriter/common.py | 2 +- novelwriter/config.py | 53 +++++++++++++++++++++++-------------- novelwriter/core/project.py | 8 ++---- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/novelwriter/common.py b/novelwriter/common.py index 317aced5..7587b2ea 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -110,7 +110,7 @@ def checkBool(value: Any, default: bool) -> bool: return default -def checkUuid(value: Any, default: str) -> str: +def checkUuid(value: Any, default: str = "") -> str: """Try to process a value as an UUID, or return a default.""" try: return str(uuid.UUID(value)) diff --git a/novelwriter/config.py b/novelwriter/config.py index bfcc73a7..95efdc55 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -32,6 +32,7 @@ import sys from datetime import datetime from pathlib import Path from time import time +from typing import TYPE_CHECKING from PyQt5.QtCore import ( PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo, @@ -47,6 +48,9 @@ from novelwriter.common import ( from novelwriter.constants import nwFiles, nwUnicode from novelwriter.error import formatException, logException +if TYPE_CHECKING: # pragma: no cover + from novelwriter.core.projectdata import NWProjectData + logger = logging.getLogger(__name__) @@ -845,29 +849,31 @@ class RecentProjects: def __init__(self, config: Config) -> None: self._conf = config - self._data = {} + self._data: dict[str, dict[str, str | int]] = {} + self._map: dict[str, str] = {} return def loadCache(self) -> bool: """Load the cache file for recent projects.""" self._data = {} - + self._map = {} cacheFile = self._conf.dataPath(nwFiles.RECENT_FILE) if cacheFile.is_file(): try: with open(cacheFile, mode="r", encoding="utf-8") as inFile: data = json.load(inFile) - for path, entry in data.items(): - self._data[path] = { - "title": entry.get("title", ""), - "words": entry.get("words", 0), - "time": entry.get("time", 0), - } + for key, entry in data.items(): + path = str(entry.get("path", key)) + title = str(entry.get("title", "")) + words = checkInt(entry.get("words", 0), 0) + saved = checkInt(entry.get("time", 0), 0) + if path and title: + self._setEntry(key, path, title, words, saved) + self._map[path] = key except Exception: logger.error("Could not load recent project cache") logException() return False - return True def saveCache(self) -> bool: @@ -882,33 +888,40 @@ class RecentProjects: logger.error("Could not save recent project cache") logException() return False - return True def listEntries(self) -> list[tuple[str, str, int, int]]: """List all items in the cache.""" return [ - (str(k), str(e["title"]), checkInt(e["words"], 0), checkInt(e["time"], 0)) - for k, e in self._data.items() + (str(e["path"]), str(e["title"]), checkInt(e["words"], 0), checkInt(e["time"], 0)) + for e in self._data.values() ] - def update(self, path: str | Path, title: str, words: int, saved: float | int) -> None: + def update(self, path: str | Path, data: NWProjectData, saved: float | int) -> None: """Add or update recent cache information on a given project.""" - self._data[str(path)] = { - "title": title, - "words": int(words), - "time": int(saved), - } - self.saveCache() + try: + self.remove(path) + self._setEntry(data.uuid, str(path), data.name, sum(data.currCounts), int(saved)) + self.saveCache() + except Exception: + pass return def remove(self, path: str | Path) -> None: """Try to remove a path from the recent projects cache.""" - if self._data.pop(str(path), None) is not None: + if remove := self._map.get(str(path)): + self._data.pop(remove, None) + self._map.pop(str(path), None) logger.debug("Removed recent: %s", path) self.saveCache() return + def _setEntry(self, key: str, path: str, title: str, words: int, saved: int) -> None: + """Set an entry in the projects list.""" + self._data[key] = {"path": path, "title": title, "words": words, "time": saved} + self._map[path] = key + return + class RecentPaths: diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index e568e1cc..f16cd49d 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -353,9 +353,7 @@ class NWProject: # Update recent projects if storePath := self._storage.storagePath: - CONFIG.recentProjects.update( - storePath, self._data.name, sum(self._data.initCounts), time() - ) + CONFIG.recentProjects.update(storePath, self._data, time()) # Check the project tree consistency # This also handles any orphaned files found @@ -421,9 +419,7 @@ class NWProject: # Update recent projects if storagePath := self._storage.storagePath: - CONFIG.recentProjects.update( - storagePath, self._data.name, sum(self._data.currCounts), saveTime - ) + CONFIG.recentProjects.update(storagePath, self._data, saveTime) SHARED.newStatusMessage(self.tr("Saved Project: {0}").format(self._data.name)) self.setProjectChanged(False)