post_archiver/manager/
file_meta.rs1use std::{collections::HashMap, fs::File, io::Write, path::PathBuf};
2
3use serde_json::Value;
4
5use crate::{
6 error::Result,
7 manager::{binded::Binded, PostArchiverConnection},
8 query::FromQuery,
9 FileMeta, FileMetaId, Post, PostId,
10};
11
12#[derive(Debug, Clone)]
16pub struct UpdateFileMeta<T> {
17 pub mime: Option<String>,
18 pub extra: Option<HashMap<String, Value>>,
19 pub content: Option<T>,
20}
21
22impl Default for UpdateFileMeta<()> {
23 fn default() -> Self {
24 UpdateFileMeta {
25 mime: None,
26 extra: None,
27 content: None,
28 }
29 }
30}
31
32impl<T> UpdateFileMeta<T> {
33 pub fn mime(mut self, mime: String) -> Self {
35 self.mime = Some(mime);
36 self
37 }
38 pub fn extra(mut self, extra: HashMap<String, Value>) -> Self {
40 self.extra = Some(extra);
41 self
42 }
43 pub fn content<U: WritableFileMeta>(self, content: U) -> UpdateFileMeta<U> {
44 UpdateFileMeta {
45 content: Some(content),
46 mime: self.mime,
47 extra: self.extra,
48 }
49 }
50}
51
52impl UpdateFileMeta<()> {
53 pub fn new() -> UpdateFileMeta<()> {
55 UpdateFileMeta {
56 content: None,
57 mime: None,
58 extra: None,
59 }
60 }
61}
62
63pub trait WritableFileMeta {
64 fn write_to_file(&self, file: &mut File) -> std::io::Result<()>;
65}
66
67macro_rules! can_be_content {
68 ($t:ty) => {
69 impl UpdateFileMeta<$t> {
70 pub fn new(content: $t) -> Self {
71 Self {
72 content: Some(content),
73 mime: None,
74 extra: None,
75 }
76 }
77 }
78 };
79}
80
81can_be_content!(File);
82impl WritableFileMeta for File {
83 fn write_to_file(&self, file: &mut File) -> std::io::Result<()> {
84 let mut src_file = self.try_clone()?;
85 std::io::copy(&mut src_file, file)?;
86 file.sync_data()?;
87 Ok(())
88 }
89}
90
91can_be_content!(Vec<u8>);
92impl WritableFileMeta for Vec<u8> {
93 fn write_to_file(&self, file: &mut File) -> std::io::Result<()> {
94 file.write_all(self)?;
95 file.sync_data()?;
96 Ok(())
97 }
98}
99
100can_be_content!(PathBuf);
101impl WritableFileMeta for PathBuf {
102 fn write_to_file(&self, file: &mut File) -> std::io::Result<()> {
103 let mut src_file = File::open(self)?;
104 std::io::copy(&mut src_file, file)?;
105 file.sync_data()?;
106 Ok(())
107 }
108}
109
110can_be_content!(String);
111impl WritableFileMeta for String {
112 fn write_to_file(&self, file: &mut File) -> std::io::Result<()> {
113 file.write_all(self.as_bytes())?;
114 file.sync_data()?;
115 Ok(())
116 }
117}
118
119impl<'a, C: PostArchiverConnection> Binded<'a, FileMetaId, C> {
123 pub fn value(&self) -> Result<FileMeta> {
125 let mut stmt = self
126 .conn()
127 .prepare_cached("SELECT * FROM file_metas WHERE id = ?")?;
128 Ok(stmt.query_row([self.id()], FileMeta::from_row)?)
129 }
130
131 pub fn delete(self) -> Result<()> {
136 let mut stmt = self
137 .conn()
138 .prepare_cached("DELETE FROM file_metas WHERE id = ?")?;
139 stmt.execute([self.id()])?;
140 Ok(())
141 }
142
143 pub fn update<T>(&self, update: UpdateFileMeta<T>) -> Result<()> {
147 use rusqlite::types::ToSql;
148
149 let extra_json = update.extra.map(|e| serde_json::to_string(&e).unwrap());
150
151 let mut sets: Vec<&str> = Vec::new();
152 let mut params: Vec<&dyn ToSql> = Vec::new();
153
154 macro_rules! push {
155 ($field:expr, $col:expr) => {
156 if let Some(ref v) = $field {
157 sets.push($col);
158 params.push(v);
159 }
160 };
161 }
162
163 push!(update.mime, "mime = ?");
164 push!(extra_json, "extra = ?");
165
166 let sql = format!("UPDATE file_metas SET {} WHERE id = ?", sets.join(", "));
167 let id = self.id();
168 params.push(&id);
169 self.conn().execute(&sql, params.as_slice())?;
170
171 Ok(())
172 }
173
174 pub fn update_with_content<T>(&self, mut update: UpdateFileMeta<T>) -> Result<()>
175 where
176 T: WritableFileMeta,
177 {
178 let content = update.content.take();
179 self.update(UpdateFileMeta {
180 content: None,
181 ..update
182 })?;
183
184 let path = self.get_path()?;
185
186 if let Some(content) = content {
187 let mut file = File::create(path)?;
188 content.write_to_file(&mut file)?;
189 }
190
191 Ok(())
192 }
193
194 pub fn get_path(&self) -> Result<PathBuf> {
196 let mut stmt = self
197 .conn()
198 .prepare_cached("SELECT post, filename FROM file_metas WHERE id = ?")?;
199 Ok(stmt.query_row([self.id()], |row| {
200 let post_id: PostId = row.get(0)?;
201 let filename: String = row.get(1)?;
202 Ok(Post::directory(post_id).join(filename))
203 })?)
204 }
205}