post_archiver/manager/
file_meta.rs1use std::{
2 collections::HashMap,
3 fs::{self, File},
4 io::Write,
5 path::{Path, PathBuf},
6};
7
8use serde_json::Value;
9
10use crate::{
11 error::Result,
12 manager::{binded::Binded, PostArchiverConnection},
13 query::FromQuery,
14 FileMeta, FileMetaId, Post, PostId,
15};
16
17#[derive(Debug, Clone)]
21pub struct UpdateFileMeta {
22 pub mime: Option<String>,
23 pub extra: Option<HashMap<String, Value>>,
24 pub content: Option<FileMetaContent>,
25}
26
27impl Default for UpdateFileMeta {
28 fn default() -> Self {
29 UpdateFileMeta {
30 mime: None,
31 extra: None,
32 content: None,
33 }
34 }
35}
36
37impl UpdateFileMeta {
38 pub fn new() -> UpdateFileMeta {
40 UpdateFileMeta {
41 content: None,
42 mime: None,
43 extra: None,
44 }
45 }
46
47 pub fn mime(mut self, mime: String) -> Self {
49 self.mime = Some(mime);
50 self
51 }
52 pub fn extra(mut self, extra: HashMap<String, Value>) -> Self {
54 self.extra = Some(extra);
55 self
56 }
57 pub fn path(mut self, content: impl AsRef<Path>) -> UpdateFileMeta {
59 self.content = Some(FileMetaContent::path(content));
60 self
61 }
62
63 pub fn content(mut self, content: Vec<u8>) -> Self {
65 self.content = Some(FileMetaContent::content(content));
66 self
67 }
68}
69
70impl<'a, C: PostArchiverConnection> Binded<'a, FileMetaId, C> {
74 pub fn value(&self) -> Result<FileMeta> {
76 let mut stmt = self
77 .conn()
78 .prepare_cached("SELECT * FROM file_metas WHERE id = ?")?;
79 Ok(stmt.query_row([self.id()], FileMeta::from_row)?)
80 }
81
82 pub fn delete(self) -> Result<()> {
87 let mut stmt = self
88 .conn()
89 .prepare_cached("DELETE FROM file_metas WHERE id = ?")?;
90 stmt.execute([self.id()])?;
91 Ok(())
92 }
93
94 pub fn update(&self, update: UpdateFileMeta) -> Result<()> {
98 use rusqlite::types::ToSql;
99
100 let extra_json = update.extra.map(|e| serde_json::to_string(&e).unwrap());
101
102 let mut sets: Vec<&str> = Vec::new();
103 let mut params: Vec<&dyn ToSql> = Vec::new();
104
105 macro_rules! push {
106 ($field:expr, $col:expr) => {
107 if let Some(ref v) = $field {
108 sets.push($col);
109 params.push(v);
110 }
111 };
112 }
113
114 push!(update.mime, "mime = ?");
115 push!(extra_json, "extra = ?");
116
117 if sets.is_empty() {
118 return Ok(());
119 }
120
121 let sql = format!("UPDATE file_metas SET {} WHERE id = ?", sets.join(", "));
122 let id = self.id();
123 params.push(&id);
124 self.conn().execute(&sql, params.as_slice())?;
125
126 Ok(())
127 }
128
129 pub fn get_path(&self) -> Result<PathBuf> {
131 let mut stmt = self
132 .conn()
133 .prepare_cached("SELECT post, filename FROM file_metas WHERE id = ?")?;
134 Ok(stmt.query_row([self.id()], |row| {
135 let post_id: PostId = row.get(0)?;
136 let filename: String = row.get(1)?;
137 Ok(Post::directory(post_id).join(filename))
138 })?)
139 }
140}
141
142#[derive(Debug, Clone, PartialEq)]
147pub enum FileMetaContent {
148 Content(Vec<u8>),
149 Path(PathBuf),
150}
151
152impl FileMetaContent {
153 pub fn content(content: Vec<u8>) -> Self {
154 FileMetaContent::Content(content)
155 }
156 pub fn path(path: impl AsRef<Path>) -> Self {
157 FileMetaContent::Path(path.as_ref().to_path_buf())
158 }
159 pub fn write(&self, path: &Path) -> Result<()> {
160 match self {
161 FileMetaContent::Content(content) => {
162 let mut file = File::create(path)?;
163 file.write_all(content)?;
164 }
165 FileMetaContent::Path(src_path) => {
166 fs::copy(src_path, path)?;
167 }
168 };
169 Ok(())
170 }
171}