entertainarr_adapter_sqlite/
media.rs

1use std::path::PathBuf;
2
3use anyhow::Context;
4use entertainarr_domain::media::entity::MediaFile;
5
6use crate::Wrapper;
7
8impl<'r> crate::Wrapper<MediaFile> {
9    pub(crate) fn from_index_row(
10        row: &'r sqlx::sqlite::SqliteRow,
11        idx: &mut crate::IndexIter,
12    ) -> Result<Self, sqlx::Error> {
13        use sqlx::Row;
14
15        Ok(crate::Wrapper(MediaFile {
16            id: row.try_get(idx.next())?,
17            disk: row.try_get(idx.next())?,
18            filepath: row
19                .try_get(idx.next())
20                .map(|value: String| PathBuf::from(value))?,
21            filename: row.try_get(idx.next())?,
22            content_type: row.try_get(idx.next())?,
23            file_size: row.try_get(idx.next())?,
24            created_at: row.try_get(idx.next())?,
25            updated_at: row.try_get(idx.next())?,
26        }))
27    }
28}
29
30impl<'r> sqlx::FromRow<'r, sqlx::sqlite::SqliteRow> for crate::Wrapper<MediaFile> {
31    fn from_row(row: &'r sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
32        let mut idx = crate::IndexIter::default();
33        Self::from_index_row(row, &mut idx)
34    }
35}
36
37const FIND_MEDIA_FILE_QUERY: &str = r#"select mf.id, mf.disk, mf.filepath, mf.filename, mf.content_type, mf.file_size, mf.created_at, mf.updated_at
38from media_files as mf
39where mf.id = ?"#;
40
41impl entertainarr_domain::media::prelude::MediaRepository for crate::Pool {
42    #[tracing::instrument(
43        skip_all,
44        fields(
45            otel.kind = "client",
46            db.system = "sqlite",
47            db.name = "media",
48            db.operation = "insert",
49            db.sql.table = "media_files",
50            db.query.text = FIND_MEDIA_FILE_QUERY,
51            db.response.returned_rows = tracing::field::Empty,
52            error.type = tracing::field::Empty,
53            error.message = tracing::field::Empty,
54            error.stacktrace = tracing::field::Empty,
55        ),
56        err(Debug),
57    )]
58    async fn find_by_id(&self, media_id: u64) -> anyhow::Result<Option<MediaFile>> {
59        sqlx::query_as(FIND_MEDIA_FILE_QUERY)
60            .bind(media_id as i64)
61            .fetch_optional(self.as_ref())
62            .await
63            .inspect(crate::record_optional)
64            .inspect_err(crate::record_error)
65            .map(Wrapper::maybe_inner)
66            .context("unable to fetch media")
67    }
68
69    #[tracing::instrument(
70        skip_all,
71        fields(
72            otel.kind = "client",
73            db.system = "sqlite",
74            db.name = "media",
75            db.operation = "insert",
76            db.sql.table = "media_files",
77            db.query.text = tracing::field::Empty,
78            db.response.returned_rows = tracing::field::Empty,
79            error.type = tracing::field::Empty,
80            error.message = tracing::field::Empty,
81            error.stacktrace = tracing::field::Empty,
82        ),
83        err(Debug),
84    )]
85    async fn upsert(
86        &self,
87        disk: &str,
88        input: &[entertainarr_domain::media::entity::MediaFileInput],
89    ) -> anyhow::Result<u64> {
90        let mut qb = sqlx::QueryBuilder::new(
91            "insert into media_files (disk, filepath, filename, content_type, file_size) ",
92        );
93        qb.push_values(input.iter(), |mut b, input| {
94            b.push_bind(disk)
95                .push_bind(input.filepath.to_string_lossy())
96                .push_bind(&input.filename)
97                .push_bind(&input.content_type)
98                .push_bind(input.file_size as i64);
99        });
100        qb.push(" on conflict (disk, filepath) do update set updated_at = current_timestamp");
101        tracing::Span::current().record("db.query.text", qb.sql());
102        qb.build()
103            .execute(self.as_ref())
104            .await
105            .map(|res| res.rows_affected())
106            .context("unable to insert files")
107    }
108}