aboutsummaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--crates/yt/Cargo.toml2
-rw-r--r--crates/yt/src/storage/db/extractor_hash.rs3
-rw-r--r--crates/yt/src/storage/db/get/mod.rs1
-rw-r--r--crates/yt/src/storage/db/get/txn_log.rs33
-rw-r--r--crates/yt/src/storage/db/insert/mod.rs37
-rw-r--r--crates/yt/src/storage/db/insert/subscription.rs3
-rw-r--r--crates/yt/src/storage/db/insert/video/mod.rs3
-rw-r--r--crates/yt/src/storage/db/mod.rs1
-rw-r--r--crates/yt/src/storage/db/subscription.rs3
-rw-r--r--crates/yt/src/storage/db/txn_log.rs14
-rw-r--r--crates/yt/src/storage/migrate/mod.rs14
-rw-r--r--crates/yt/src/storage/migrate/sql/5_Four_to_Five.sql15
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;