From fab1d6b84cec61b8ee6430fce3cb8545f2951503 Mon Sep 17 00:00:00 2001 From: Marko Andjelic Date: Mon, 26 Jan 2026 08:32:20 +0000 Subject: [PATCH] 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 --- app/Persistence.hs | 40 ++++++++++++++++++++++++++++++++++++++-- app/UI.hs | 27 +++++++++++++++++++-------- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/app/Persistence.hs b/app/Persistence.hs index c4b0998..3007ab8 100644 --- a/app/Persistence.hs +++ b/app/Persistence.hs @@ -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 diff --git a/app/UI.hs b/app/UI.hs index 892a44b..3916db1 100644 --- a/app/UI.hs +++ b/app/UI.hs @@ -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