chiark / gitweb /
Save an "ldb" file in the config directory.
authorSimon Tatham <anakin@pobox.com>
Sat, 6 Jan 2024 13:10:33 +0000 (13:10 +0000)
committerSimon Tatham <anakin@pobox.com>
Sat, 6 Jan 2024 14:06:13 +0000 (14:06 +0000)
This is updated whenever we exit a File. I think that's a reasonable
balance between updating it on every single press of Space when paging
through the file (excessive disk churn), and waiting until the whole
client exits (excessive risk of forgetting to save it at all).

Perhaps an even better idea would be to schedule a timer to checkpoint
the LDB every few minutes if it had changed.

TODO.md
src/config.rs
src/file.rs
src/tui.rs

diff --git a/TODO.md b/TODO.md
index 6e511e2bda5dbe5de73d53e97fd2f90fe3769533..df7f094439231c724698d2449cb0dce135ca2163 100644 (file)
--- a/TODO.md
+++ b/TODO.md
@@ -26,6 +26,7 @@ This is already marked in the code with multiple FIXMEs:
 * errors in `get_user_to_examine` and `get_post_id_to_read`, although
   again if it's just 'no such thing' then the Error Log is overkill.
   Should only be for 'help, the server is totally confused'.
+* errors in `save_ldb`
 
 Each of those should instead catch the error, make an Error Log
 record, and transfer you to the error-log viewer with a beep, just the
index 5c6cc29944242d279455b8f1748fa58abdd5b98d..d60ed4f1a477e2f244849cc931797f7c14f1e9da 100644 (file)
@@ -55,6 +55,7 @@ impl From<std::convert::Infallible> for ConfigError {
     }
 }
 
+#[derive(Clone)]
 pub struct ConfigLocation {
     dir: PathBuf,
 }
index b49c417f398a8855091ffc13db7ab3190bfee513..8110e0f88a5237286bf2ef147074651c431b09f1 100644 (file)
@@ -1308,19 +1308,16 @@ impl<Type: FileType, Source: FileDataSource>
         }
     }
 
-    fn save_file_position(
-        &self, file_positions: &mut HashMap<FeedId, SavedFilePos>)
-    {
-        if let Some(id) = self.file_desc.feed_id() {
+    fn save_file_position(&self) -> Option<(FeedId, SavedFilePos)> {
+        self.file_desc.feed_id().map(|id| {
             let sfp = SavedFilePos {
                 file_pos: Some(self.pos),
                 latest_read_id: self.latest_read_index
                     .and_then(|i| self.contents.id_at_index(i))
                     .map(|s| s.to_owned()),
             };
-            file_positions.insert(id.clone(), sfp);
-            todo!();
-        }
+            (id.clone(), sfp)
+        })
     }
 
     fn got_search_expression(&mut self, dir: SearchDirection, regex: String)
index bdac95a81e01cca4994142f291902f57e66510d2..16a7236b0eb6e20ec8fdf58521a8404be06b095d 100644 (file)
@@ -11,7 +11,7 @@ use ratatui::{
     style::{Style, Color, Modifier},
 };
 use std::cmp::min;
-use std::collections::{HashMap, HashSet};
+use std::collections::{BTreeMap, HashMap, HashSet};
 use std::io::{Stdout, Write, stdout};
 use std::fs::File;
 use unicode_width::UnicodeWidthStr;
@@ -160,6 +160,7 @@ pub enum OurKey {
     PgUp, PgDn, Home, End, Ins, Del,
 }
 
+#[derive(Debug)]
 pub struct TuiError {
     message: String,
 }
@@ -195,6 +196,13 @@ impl From<std::sync::mpsc::RecvError> for TuiError {
         }
     }
 }
+impl From<serde_json::Error> for TuiError {
+    fn from(err: serde_json::Error) -> Self {
+        TuiError {
+            message: err.to_string(),
+        }
+    }
+}
 
 impl std::fmt::Display for TuiError {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) ->
@@ -231,7 +239,7 @@ impl Tui {
         let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
         terminal.clear()?;
 
-        let state = TuiLogicalState::new(&client);
+        let state = TuiLogicalState::new(&client, cfgloc.clone());
 
         let mut tui = Tui {
             terminal,
@@ -429,6 +437,7 @@ pub enum LogicalAction {
     PostReEdit(Post),
 }
 
+#[derive(PartialEq, Eq, Debug, Clone)]
 pub struct SavedFilePos {
     // The current position we're _looking at_ within the file, within
     // this run
@@ -453,8 +462,7 @@ pub trait ActivityState {
         LogicalAction;
     fn handle_feed_updates(&mut self, _feeds_updated: &HashSet<FeedId>,
                            _client: &mut Client) {}
-    fn save_file_position(
-        &self, _file_positions: &mut HashMap<FeedId, SavedFilePos>) {}
+    fn save_file_position(&self) -> Option<(FeedId, SavedFilePos)> { None }
     fn got_search_expression(&mut self, _dir: SearchDirection, _regex: String)
                              -> LogicalAction
     {
@@ -468,10 +476,11 @@ struct TuiLogicalState {
     overlay_activity_state: Option<Box<dyn ActivityState>>,
     last_area: Option<Rect>,
     file_positions: HashMap<FeedId, SavedFilePos>,
+    cfgloc: ConfigLocation,
 }
 
 impl TuiLogicalState {
-    fn new(client: &Client) -> Self {
+    fn new(client: &Client, cfgloc: ConfigLocation) -> Self {
         let activity_stack = ActivityStack::new();
         let activity_state = main_menu(client);
 
@@ -481,6 +490,7 @@ impl TuiLogicalState {
             overlay_activity_state: None,
             last_area: None,
             file_positions: HashMap::new(),
+            cfgloc,
         }
     }
 
@@ -638,7 +648,17 @@ impl TuiLogicalState {
     }
 
     fn changed_activity(&mut self, client: &mut Client, post: Option<Post>) {
-        self.activity_state.save_file_position(&mut self.file_positions);
+        if let Some((feed_id, saved_pos)) =
+            self.activity_state.save_file_position()
+        {
+            let changed = self.file_positions.get(&feed_id) != Some(&saved_pos);
+            self.file_positions.insert(feed_id, saved_pos);
+            if changed {
+                // FIXME: maybe suddenly change our mind and go to the
+                // Error Log
+                self.save_ldb().unwrap();
+            }
+        }
         self.activity_state = self.new_activity_state(
             self.activity_stack.top(), client, post);
         self.overlay_activity_state = match self.activity_stack.overlay() {
@@ -733,4 +753,30 @@ impl TuiLogicalState {
 
         result.expect("FIXME: need to implement the Error Log here")
     }
+
+    fn save_ldb(&self) -> Result<(), TuiError> {
+        let mut ldb = BTreeMap::new();
+        for (key, value) in &self.file_positions {
+            let keystr = match key {
+                FeedId::Home => "home".to_owned(),
+                FeedId::Mentions => "mentions".to_owned(),
+                FeedId::Ego => "ego".to_owned(),
+
+                // For all other feeds, we don't persist the last-read
+                // position.
+                _ => continue,
+            };
+
+            let valstr = match &value.latest_read_id {
+                Some(s) => s.clone(),
+                None => continue,
+            };
+
+            ldb.insert(keystr, valstr);
+        }
+        let mut json = serde_json::to_string_pretty(&ldb)?;
+        json.push('\n');
+        self.cfgloc.create_file("ldb", &json)?;
+        Ok(())
+    }
 }