diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-07-24 16:01:21 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-07-24 16:03:08 +0200 |
commit | f6eb32ae50a21d0d3b0ed0e992f3871d59966743 (patch) | |
tree | 0737fb67ffd779fc907a74d0788ea90e64f20c36 | |
parent | feat(crates/yt/commands/watch/mpv_commands): Hook-up the new show commands (diff) | |
download | yt-f6eb32ae50a21d0d3b0ed0e992f3871d59966743.zip |
feat(crates/yt/storage/db/insert): Track all inserted operations
-rw-r--r-- | crates/yt/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/yt/src/storage/db/extractor_hash.rs | 3 | ||||
-rw-r--r-- | crates/yt/src/storage/db/get/mod.rs | 1 | ||||
-rw-r--r-- | crates/yt/src/storage/db/get/txn_log.rs | 33 | ||||
-rw-r--r-- | crates/yt/src/storage/db/insert/mod.rs | 37 | ||||
-rw-r--r-- | crates/yt/src/storage/db/insert/subscription.rs | 3 | ||||
-rw-r--r-- | crates/yt/src/storage/db/insert/video/mod.rs | 3 | ||||
-rw-r--r-- | crates/yt/src/storage/db/mod.rs | 1 | ||||
-rw-r--r-- | crates/yt/src/storage/db/subscription.rs | 3 | ||||
-rw-r--r-- | crates/yt/src/storage/db/txn_log.rs | 14 | ||||
-rw-r--r-- | crates/yt/src/storage/migrate/mod.rs | 14 | ||||
-rw-r--r-- | crates/yt/src/storage/migrate/sql/5_Four_to_Five.sql | 15 |
12 files changed, 119 insertions, 10 deletions
diff --git a/crates/yt/Cargo.toml b/crates/yt/Cargo.toml index 95f8270..14d25a4 100644 --- a/crates/yt/Cargo.toml +++ b/crates/yt/Cargo.toml @@ -25,7 +25,7 @@ publish = false [dependencies] anyhow = "1.0.98" -blake3 = "1.8.2" +blake3 = {version = "1.8.2", features = ["serde"]} chrono = { version = "0.4.41", features = ["now"] } chrono-humanize = "0.2.3" clap = { version = "4.5.41", features = ["derive"] } diff --git a/crates/yt/src/storage/db/extractor_hash.rs b/crates/yt/src/storage/db/extractor_hash.rs index b828348..15b57e7 100644 --- a/crates/yt/src/storage/db/extractor_hash.rs +++ b/crates/yt/src/storage/db/extractor_hash.rs @@ -14,6 +14,7 @@ use std::{collections::HashSet, fmt::Display, str::FromStr}; use anyhow::{Context, Result, bail}; use blake3::Hash; use log::debug; +use serde::{Deserialize, Serialize}; use tokio::sync::OnceCell; use yt_dlp::{info_json::InfoJson, json_cast, json_get}; @@ -21,7 +22,7 @@ use crate::app::App; static EXTRACTOR_HASH_LENGTH: OnceCell<usize> = OnceCell::const_new(); -#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)] pub(crate) struct ExtractorHash { hash: Hash, } diff --git a/crates/yt/src/storage/db/get/mod.rs b/crates/yt/src/storage/db/get/mod.rs index 8ca3075..58f1dd6 100644 --- a/crates/yt/src/storage/db/get/mod.rs +++ b/crates/yt/src/storage/db/get/mod.rs @@ -2,3 +2,4 @@ pub(crate) mod subscription; pub(crate) mod video; pub(crate) mod extractor_hash; pub(crate) mod playlist; +pub(crate) mod txn_log; diff --git a/crates/yt/src/storage/db/get/txn_log.rs b/crates/yt/src/storage/db/get/txn_log.rs new file mode 100644 index 0000000..e4a1dcb --- /dev/null +++ b/crates/yt/src/storage/db/get/txn_log.rs @@ -0,0 +1,33 @@ +use crate::{ + app::App, + storage::db::{insert::Committable, txn_log::TxnLog, video::TimeStamp}, +}; + +use anyhow::Result; +use sqlx::query; + +impl<O: Committable> TxnLog<O> { + /// Get the log of all operations that have been performed. + pub(crate) async fn get(app: &App) -> Result<Self> { + let raw_ops = query!( + " + SELECT * + FROM txn_log + ORDER BY timestamp ASC; + " + ) + .fetch_all(&app.database) + .await?; + + let inner = raw_ops + .into_iter() + .filter_map(|raw_op| { + serde_json::from_str(&raw_op.operation) + .map(|parsed_op| (TimeStamp::from_secs(raw_op.timestamp), parsed_op)) + .ok() + }) + .collect(); + + Ok(TxnLog::new(inner)) + } +} diff --git a/crates/yt/src/storage/db/insert/mod.rs b/crates/yt/src/storage/db/insert/mod.rs index a3139ac..60bbc0a 100644 --- a/crates/yt/src/storage/db/insert/mod.rs +++ b/crates/yt/src/storage/db/insert/mod.rs @@ -3,15 +3,19 @@ use std::mem; use crate::app::App; use anyhow::Result; -use log::trace; -use sqlx::SqliteConnection; +use chrono::Utc; +use log::{debug, trace}; +use serde::{Serialize, de::DeserializeOwned}; +use sqlx::{SqliteConnection, query}; pub(crate) mod maintenance; pub(crate) mod playlist; pub(crate) mod subscription; pub(crate) mod video; -pub(crate) trait Committable: Sized + std::fmt::Debug { +pub(crate) trait Committable: + Sized + std::fmt::Debug + Serialize + DeserializeOwned +{ async fn commit(self, txn: &mut SqliteConnection) -> Result<()>; } @@ -48,6 +52,7 @@ impl<O: Committable> Operations<O> { for op in ops { trace!("Commiting operation: {op:?}"); + add_operation_to_txn_log(&op, &mut txn).await?; op.commit(&mut txn).await?; } @@ -72,3 +77,29 @@ impl<O: Committable> Drop for Operations<O> { ); } } + +async fn add_operation_to_txn_log<O: Committable>( + operation: &O, + txn: &mut SqliteConnection, +) -> Result<()> { + debug!("Adding operation to txn log: {operation:?}"); + + let now = Utc::now().timestamp(); + let operation = serde_json::to_string(&operation).expect("should be serializable"); + + query!( + r#" + INSERT INTO txn_log ( + timestamp, + operation + ) + VALUES (?, ?); + "#, + now, + operation, + ) + .execute(txn) + .await?; + + Ok(()) +} diff --git a/crates/yt/src/storage/db/insert/subscription.rs b/crates/yt/src/storage/db/insert/subscription.rs index ba9a3e1..8499966 100644 --- a/crates/yt/src/storage/db/insert/subscription.rs +++ b/crates/yt/src/storage/db/insert/subscription.rs @@ -4,9 +4,10 @@ use crate::storage::db::{ }; use anyhow::Result; +use serde::{Deserialize, Serialize}; use sqlx::query; -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub(crate) enum Operation { Add(Subscription), Remove(Subscription), diff --git a/crates/yt/src/storage/db/insert/video/mod.rs b/crates/yt/src/storage/db/insert/video/mod.rs index b57f043..4cc7358 100644 --- a/crates/yt/src/storage/db/insert/video/mod.rs +++ b/crates/yt/src/storage/db/insert/video/mod.rs @@ -12,6 +12,7 @@ use crate::storage::db::{ use anyhow::{Context, Result}; use chrono::Utc; use log::debug; +use serde::{Deserialize, Serialize}; use sqlx::query; use tokio::fs; @@ -21,7 +22,7 @@ const fn is_focused_to_value(is_focused: bool) -> Option<i8> { if is_focused { Some(1) } else { None } } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub(crate) enum Operation { Add { description: Option<String>, diff --git a/crates/yt/src/storage/db/mod.rs b/crates/yt/src/storage/db/mod.rs index c0e16b0..5da28ed 100644 --- a/crates/yt/src/storage/db/mod.rs +++ b/crates/yt/src/storage/db/mod.rs @@ -5,3 +5,4 @@ pub(crate) mod extractor_hash; pub(crate) mod subscription; pub(crate) mod video; pub(crate) mod playlist; +pub(crate) mod txn_log; diff --git a/crates/yt/src/storage/db/subscription.rs b/crates/yt/src/storage/db/subscription.rs index 07e5eec..0d9e160 100644 --- a/crates/yt/src/storage/db/subscription.rs +++ b/crates/yt/src/storage/db/subscription.rs @@ -2,10 +2,11 @@ use std::collections::HashMap; use anyhow::Result; use log::debug; +use serde::{Deserialize, Serialize}; use url::Url; use yt_dlp::{json_cast, options::YoutubeDLOptions}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub(crate) struct Subscription { /// The human readable name of this subscription pub(crate) name: String, diff --git a/crates/yt/src/storage/db/txn_log.rs b/crates/yt/src/storage/db/txn_log.rs new file mode 100644 index 0000000..8d6c305 --- /dev/null +++ b/crates/yt/src/storage/db/txn_log.rs @@ -0,0 +1,14 @@ +use crate::storage::db::{insert::Committable, video::TimeStamp}; + +pub(crate) struct TxnLog<O: Committable> { + inner: Vec<(TimeStamp, O)>, +} + +impl<O: Committable> TxnLog<O> { + pub(crate) fn new(inner: Vec<(TimeStamp, O)>) -> Self { + Self { inner } + } + pub(crate) fn inner(&self) -> &[(TimeStamp, O)] { + &self.inner + } +} diff --git a/crates/yt/src/storage/migrate/mod.rs b/crates/yt/src/storage/migrate/mod.rs index e2d93bd..418c893 100644 --- a/crates/yt/src/storage/migrate/mod.rs +++ b/crates/yt/src/storage/migrate/mod.rs @@ -94,8 +94,11 @@ pub(crate) enum DbVersion { /// Introduced: 2025-07-05. Four, + + /// Introduced: 2025-07-20. + Five, } -const CURRENT_VERSION: DbVersion = DbVersion::Four; +const CURRENT_VERSION: DbVersion = DbVersion::Five; async fn add_error_context( function: impl Future<Output = Result<()>>, @@ -147,6 +150,7 @@ impl DbVersion { DbVersion::Two => 2, DbVersion::Three => 3, DbVersion::Four => 4, + DbVersion::Five => 5, DbVersion::Empty => unreachable!("A empty version does not have an associated integer"), } @@ -159,12 +163,14 @@ impl DbVersion { (2, "yt") => Ok(DbVersion::Two), (3, "yt") => Ok(DbVersion::Three), (4, "yt") => Ok(DbVersion::Four), + (5, "yt") => Ok(DbVersion::Five), (0, other) => bail!("Db version is Zero, but got unknown namespace: '{other}'"), (1, other) => bail!("Db version is One, but got unknown namespace: '{other}'"), (2, other) => bail!("Db version is Two, but got unknown namespace: '{other}'"), (3, other) => bail!("Db version is Three, but got unknown namespace: '{other}'"), (4, other) => bail!("Db version is Four, but got unknown namespace: '{other}'"), + (5, other) => bail!("Db version is Five, but got unknown namespace: '{other}'"), (other, "yt") => bail!("Got unkown version for 'yt' namespace: {other}"), (num, nasp) => bail!("Got unkown version number ({num}) and namespace ('{nasp}')"), @@ -198,8 +204,12 @@ impl DbVersion { make_upgrade! {app, Self::Three, Self::Four, "./sql/4_Three_to_Four.sql"} } - // This is the current_version Self::Four => { + make_upgrade! {app, Self::Four, Self::Five, "./sql/5_Four_to_Five.sql"} + } + + // This is the current_version + Self::Five => { assert_eq!(self, CURRENT_VERSION); assert_eq!(self, get_version(app).await?); Ok(()) diff --git a/crates/yt/src/storage/migrate/sql/5_Four_to_Five.sql b/crates/yt/src/storage/migrate/sql/5_Four_to_Five.sql new file mode 100644 index 0000000..6c4b7cc --- /dev/null +++ b/crates/yt/src/storage/migrate/sql/5_Four_to_Five.sql @@ -0,0 +1,15 @@ +-- yt - A fully featured command line YouTube client +-- +-- Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +-- SPDX-License-Identifier: GPL-3.0-or-later +-- +-- This file is part of Yt. +-- +-- You should have received a copy of the License along with this program. +-- If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + + +CREATE TABLE txn_log ( + timestamp INTEGER NOT NULL, + operation TEXT NOT NULL +) STRICT; |