entertainarr_adapter_sqlite/
media.rs

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