Skip to content

Commit

Permalink
Track recent projects via UUID (#2218)
Browse files Browse the repository at this point in the history
  • Loading branch information
vkbo authored Jan 30, 2025
2 parents f103288 + 247702b commit 7adc36e
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 27 deletions.
44 changes: 28 additions & 16 deletions novelwriter/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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,
Expand All @@ -47,6 +48,9 @@
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__)


Expand Down Expand Up @@ -845,29 +849,30 @@ 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),
}
puuid = str(entry.get("uuid", ""))
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(puuid, path, title, words, saved)
except Exception:
logger.error("Could not load recent project cache")
logException()
return False

return True

def saveCache(self) -> bool:
Expand All @@ -882,7 +887,6 @@ def saveCache(self) -> bool:
logger.error("Could not save recent project cache")
logException()
return False

return True

def listEntries(self) -> list[tuple[str, str, int, int]]:
Expand All @@ -892,14 +896,15 @@ def listEntries(self) -> list[tuple[str, str, int, int]]:
for k, e in self._data.items()
]

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:
if (remove := self._map.get(data.uuid)) and (remove != str(path)):
self.remove(remove)
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:
Expand All @@ -909,6 +914,13 @@ def remove(self, path: str | Path) -> None:
self.saveCache()
return

def _setEntry(self, puuid: str, path: str, title: str, words: int, saved: int) -> None:
"""Set an entry in the recent projects record."""
self._data[path] = {"uuid": puuid, "title": title, "words": words, "time": saved}
if puuid:
self._map[puuid] = path
return


class RecentPaths:

Expand Down
8 changes: 2 additions & 6 deletions novelwriter/core/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,7 @@ def openProject(self, projPath: str | Path, clearLock: bool = False) -> bool:

# 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
Expand Down Expand Up @@ -421,9 +419,7 @@ def saveProject(self, autoSave: bool = False) -> bool:

# 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)
Expand Down
25 changes: 22 additions & 3 deletions tests/test_base/test_base_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from novelwriter import CONFIG
from novelwriter.config import Config, RecentPaths, RecentProjects
from novelwriter.constants import nwFiles
from novelwriter.core.project import NWProject

from tests.mocked import MockApp, causeOSError
from tests.tools import cmpFiles, writeFile
Expand Down Expand Up @@ -382,7 +383,7 @@ def testBaseConfig_Internal(monkeypatch, fncPath):


@pytest.mark.base
def testBaseConfig_RecentCache(monkeypatch, tstPaths):
def testBaseConfig_RecentCache(monkeypatch, tstPaths, nwGUI):
"""Test recent cache file."""
cacheFile = tstPaths.cnfDir / nwFiles.RECENT_FILE
recent = RecentProjects(CONFIG)
Expand All @@ -396,8 +397,18 @@ def testBaseConfig_RecentCache(monkeypatch, tstPaths):
pathOne = tstPaths.cnfDir / "projPathOne" / nwFiles.PROJ_FILE
pathTwo = tstPaths.cnfDir / "projPathTwo" / nwFiles.PROJ_FILE

recent.update(pathOne, "Proj One", 100, 1600002000)
recent.update(pathTwo, "Proj Two", 200, 1600005600)
prjOne = NWProject()
prjTwo = NWProject()

prjOne.data.setUuid(None)
prjTwo.data.setUuid(None)
prjOne.data.setName("Proj One")
prjTwo.data.setName("Proj Two")
prjOne.data.setCurrCounts(100, 0)
prjTwo.data.setCurrCounts(200, 0)

recent.update(pathOne, prjOne.data, 1600002000)
recent.update(pathTwo, prjTwo.data, 1600005600)
assert recent.listEntries() == [
(str(pathOne), "Proj One", 100, 1600002000),
(str(pathTwo), "Proj Two", 200, 1600005600),
Expand Down Expand Up @@ -429,6 +440,14 @@ def testBaseConfig_RecentCache(monkeypatch, tstPaths):
(str(pathTwo), "Proj Two", 200, 1600005600),
]

# Pass Invalid
recent.update(None, None, 0) # type: ignore
recent.update(None, None, 0) # type: ignore
assert recent.listEntries() == [
(str(pathOne), "Proj One", 100, 1600002000),
(str(pathTwo), "Proj Two", 200, 1600005600),
]

# Remove Non-Existent Entry
recent.remove("stuff")
assert recent.listEntries() == [
Expand Down
15 changes: 13 additions & 2 deletions tests/test_tools/test_tools_welcome.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwFiles
from novelwriter.core.projectdata import NWProjectData
from novelwriter.enum import nwItemClass
from novelwriter.tools.welcome import GuiWelcome
from novelwriter.types import QtMouseLeft
Expand Down Expand Up @@ -72,8 +73,18 @@ def testToolWelcome_Open(qtbot: QtBot, monkeypatch, nwGUI, fncPath):
monkeypatch.setattr(QMenu, "exec", lambda *a: None)
monkeypatch.setattr(QMenu, "setParent", lambda *a: None)

CONFIG.recentProjects.update("/stuff/project_one", "Project One", 12345, 1690000000)
CONFIG.recentProjects.update("/stuff/project_two", "Project Two", 54321, 1700000000)
data1 = NWProjectData(SHARED.project)
data2 = NWProjectData(SHARED.project)

data1.setUuid(None)
data2.setUuid(None)
data1.setName("Project One")
data2.setName("Project Two")
data1.setCurrCounts(12345, 0)
data2.setCurrCounts(54321, 0)

CONFIG.recentProjects.update("/stuff/project_one", data1, 1690000000)
CONFIG.recentProjects.update("/stuff/project_two", data2, 1700000000)
dateOne = CONFIG.localDate(datetime.fromtimestamp(1700000000))
dateTwo = CONFIG.localDate(datetime.fromtimestamp(1690000000))

Expand Down

0 comments on commit 7adc36e

Please sign in to comment.