Add persistence module

This will allow us to save the last chapter the user read, thus making it easier to track progress.

Implement persistence module

saveLastRead saves the chapter after each new one is read and writes it to xdgdata
This commit is contained in:
Marko Andjelic 2026-01-26 08:32:20 +00:00
commit fab1d6b84c
2 changed files with 57 additions and 10 deletions

View file

@ -1,4 +1,40 @@
module Persistence where
module Persistence
( saveLastRead
, loadLastRead
) where
-- TODO: Add book state persistence
import System.Directory (getXdgDirectory, XdgDirectory(XdgData), createDirectoryIfMissing)
import System.FilePath ((</>))
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Control.Exception (try, SomeException)
import Text.Read (readMaybe)
saveLastRead :: FilePath -> Int -> IO ()
saveLastRead bPath chapterIdx = do
dataDir <- getXdgDirectory XdgData "svitak"
createDirectoryIfMissing True dataDir
let configFile = dataDir </> "last_read.txt"
-- Line 1 = Path, Line 2 = Index
let content = T.pack bPath <> T.pack "\n" <> T.pack (show chapterIdx)
TIO.writeFile configFile content
putStrLn $ "Progress saved to: " ++ configFile
loadLastRead :: IO (Maybe (FilePath, Int))
loadLastRead = do
dataDir <- getXdgDirectory XdgData "svitak"
let configFile = dataDir </> "last_read.txt"
result <- try (TIO.readFile configFile) :: IO (Either SomeException T.Text)
case result of
Left _ -> pure Nothing
Right content -> do
let linesOfFile = T.lines content
case linesOfFile of
[path, idxStr]
| Just idx <- readMaybe (T.unpack idxStr) ->
pure $ Just (T.unpack path, idx)
_ -> pure Nothing

View file

@ -12,7 +12,8 @@ import Control.Monad.Reader (runReaderT)
import Data.IORef (newIORef, readIORef, writeIORef)
import Text.HTML.TagSoup (renderTags)
import EpubParser (EpubEnv, resolveSpine, getChapter)
import EpubParser (EpubEnv, resolveSpine, getChapter, bookPath)
import Persistence (saveLastRead, loadLastRead)
runApp :: EpubEnv -> IO ()
runApp env = do
@ -20,7 +21,7 @@ runApp env = do
app <- Gtk.applicationNew (Just "com.svitak.reader.v2") []
_ <- Gtk.on app #activate $ do
putStrLn "DEBUG: App Activated"
putStrLn "App Activated"
-- Setup window
window <- Gtk.applicationWindowNew app
@ -34,30 +35,40 @@ runApp env = do
-- Get the spine
spine <- runReaderT resolveSpine env
let totalChapters = length spine
putStrLn $ "DEBUG: Spine loaded. Total chapters: " ++ show totalChapters
putStrLn $ "Spine loaded. Total chapters: " ++ show totalChapters
if totalChapters == 0
then putStrLn "ERROR: This book has no chapters!"
else do
currentIndex <- newIORef 0
let loadChapter index = do
let maxIdx = totalChapters - 1
let safeIdx = max 0 (min index maxIdx)
-- Save state after every chapter
writeIORef currentIndex safeIdx
putStrLn $ "DEBUG: Loading Chapter " ++ show (safeIdx + 1)
putStrLn $ "Loading Chapter " ++ show (safeIdx + 1)
saveLastRead (bookPath env) safeIdx
let filename = spine !! safeIdx
tags <- runReaderT (getChapter filename) env
let htmlContent = renderTags tags
-- Load HTML
WebKit.webViewLoadHtml webView htmlContent (Just "file:///")
-- Load the first chapter
loadChapter 0
-- If there's no state saved then load the first chapter
maybeSaved <- loadLastRead
case maybeSaved of
Just (path, index)
| path == bookPath env -> loadChapter index
| otherwise -> loadChapter 0
Nothing -> loadChapter 0
-- Setup keyboard input
keyController <- Gtk.eventControllerKeyNew