entertainarr_adapter_sqlite/
media.rs1use 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}