1use std::collections::BTreeMap;
8use std::path::{Path, PathBuf};
9
10use rusqlite::{params, Connection};
11
12use crate::collections::Collection;
13use crate::folders::{Folder, Folders, RootFolder};
14use crate::fromdb::FromDb;
15use crate::images::Image;
16use crate::keywords::Keyword;
17use crate::keywordtree::KeywordTree;
18use crate::libraryfiles::LibraryFile;
19use crate::lrobject::{LrId, LrObject};
20
21const LR2_VERSION: i32 = 2;
22const LR3_VERSION: i32 = 3;
23const LR4_VERSION: i32 = 4;
24const LR6_VERSION: i32 = 6;
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub enum CatalogVersion {
29 Unknown,
31 Lr2,
33 Lr3,
35 Lr4,
37 Lr6,
39}
40
41impl CatalogVersion {
42 pub fn is_supported(&self) -> bool {
44 (*self == Self::Lr2) || (*self == Self::Lr3) || (*self == Self::Lr4) || (*self == Self::Lr6)
45 }
46}
47
48pub struct Catalog {
51 path: PathBuf,
53 pub version: String,
55 pub catalog_version: CatalogVersion,
57 pub root_keyword_id: LrId,
59
60 keywords: BTreeMap<LrId, Keyword>,
62 folders: Folders,
64 images: Vec<Image>,
66 libfiles: Vec<LibraryFile>,
68 collections: Vec<Collection>,
70
71 dbconn: Option<Connection>,
73}
74
75impl Catalog {
76 pub fn new<P>(path: P) -> Catalog
78 where
79 P: AsRef<Path>,
80 {
81 Catalog {
82 path: path.as_ref().to_path_buf(),
83 version: "".to_string(),
84 catalog_version: CatalogVersion::Unknown,
85 root_keyword_id: 0,
86 keywords: BTreeMap::new(),
87 folders: Folders::new(),
88 images: vec![],
89 libfiles: vec![],
90 collections: vec![],
91 dbconn: None,
92 }
93 }
94
95 pub fn open(&mut self) -> crate::Result<()> {
98 let conn = Connection::open(&self.path)?;
99
100 self.dbconn = Some(conn);
101
102 Ok(())
103 }
104
105 fn get_variable<T>(&self, name: &str) -> Option<T>
107 where
108 T: rusqlite::types::FromSql,
109 {
110 let conn = self.dbconn.as_ref()?;
111 if let Ok(mut stmt) = conn.prepare("SELECT value FROM Adobe_variablesTable WHERE name=?1") {
112 let mut rows = stmt.query([&name]).unwrap();
113 if let Ok(Some(row)) = rows.next() {
114 return row.get(0).ok();
115 }
116 }
117 None
118 }
119
120 fn parse_version(mut v: String) -> i32 {
122 v.truncate(2);
123 v.parse::<i32>().unwrap_or_default()
124 }
125
126 pub fn load_version(&mut self) {
128 if let Some(version) = self.get_variable::<String>("Adobe_DBVersion") {
129 self.version = version;
130 let v = Catalog::parse_version(self.version.clone());
131 self.catalog_version = match v {
132 LR6_VERSION => CatalogVersion::Lr6,
133 LR4_VERSION => CatalogVersion::Lr4,
134 LR3_VERSION => CatalogVersion::Lr3,
135 LR2_VERSION => CatalogVersion::Lr2,
136 _ => CatalogVersion::Unknown,
137 };
138 }
139
140 if let Some(root_keyword_id) = self.get_variable::<f64>("AgLibraryKeyword_rootTagID") {
141 self.root_keyword_id = root_keyword_id.round() as LrId;
142 }
143 }
144
145 fn load_objects<T: FromDb>(conn: &Connection, catalog_version: CatalogVersion) -> Vec<T> {
147 let mut query = format!(
148 "SELECT {} FROM {}",
149 T::read_db_columns(catalog_version),
150 T::read_db_tables(catalog_version)
151 );
152 let where_join = T::read_join_where(catalog_version);
153 if !where_join.is_empty() {
154 query += &format!(" WHERE {where_join}");
155 }
156 if let Ok(mut stmt) = conn.prepare(&query) {
157 if let Ok(rows) =
158 stmt.query_and_then(params![], |row| T::read_from(catalog_version, row))
159 {
160 return rows.into_iter().filter_map(|obj| obj.ok()).collect();
161 }
162 }
163 vec![]
164 }
165
166 pub fn load_keywords_tree(&mut self) -> KeywordTree {
168 let keywords = self.load_keywords();
169
170 let mut tree = KeywordTree::new();
171 tree.add_children(keywords);
172
173 tree
174 }
175
176 pub fn load_keywords(&mut self) -> &BTreeMap<LrId, Keyword> {
178 if self.keywords.is_empty() {
179 if let Some(ref conn) = self.dbconn {
180 let result = Catalog::load_objects::<Keyword>(conn, self.catalog_version);
181 for keyword in result {
182 self.keywords.insert(keyword.id(), keyword);
183 }
184 }
185 }
186 &self.keywords
187 }
188
189 pub fn keywords(&self) -> &BTreeMap<LrId, Keyword> {
192 &self.keywords
193 }
194
195 pub fn load_folders(&mut self) -> &Folders {
197 if self.folders.is_empty() {
198 if let Some(ref conn) = self.dbconn {
199 let folders = Catalog::load_objects::<RootFolder>(conn, self.catalog_version);
200 self.folders.append_root_folders(folders);
201 let mut folders = Catalog::load_objects::<Folder>(conn, self.catalog_version);
202 for folder in &mut folders {
203 folder.content = Some(folder.read_content(conn));
204 }
205 self.folders.append_folders(folders);
206 }
207 }
208 &self.folders
209 }
210
211 pub fn folders(&self) -> &Folders {
214 &self.folders
215 }
216
217 pub fn load_library_files(&mut self) -> &Vec<LibraryFile> {
219 if self.libfiles.is_empty() {
220 if let Some(ref conn) = self.dbconn {
221 let mut result = Catalog::load_objects::<LibraryFile>(conn, self.catalog_version);
222 self.libfiles.append(&mut result);
223 }
224 }
225 &self.libfiles
226 }
227
228 pub fn libfiles(&self) -> &Vec<LibraryFile> {
231 &self.libfiles
232 }
233
234 pub fn load_images(&mut self) -> &Vec<Image> {
236 if self.images.is_empty() {
237 if let Some(ref conn) = self.dbconn {
238 let mut result = Catalog::load_objects::<Image>(conn, self.catalog_version);
239 self.images.append(&mut result);
240 }
241 }
242 &self.images
243 }
244
245 pub fn images(&self) -> &Vec<Image> {
248 &self.images
249 }
250
251 pub fn load_collections(&mut self) -> &Vec<Collection> {
253 if self.collections.is_empty() {
254 if let Some(ref conn) = self.dbconn {
255 let mut collections =
256 Catalog::load_objects::<Collection>(conn, self.catalog_version);
257 for collection in &mut collections {
258 collection.content = Some(collection.read_content(conn));
259 }
260 self.collections.append(&mut collections);
261 }
262 }
263 &self.collections
264 }
265
266 pub fn collections(&self) -> &Vec<Collection> {
269 &self.collections
270 }
271
272 const LR2_QUERY: &'static str =
274 "SELECT image FROM AgLibraryTagImage WHERE tag = ?1 AND tagKind = \"AgCollectionTagKind\"";
275 const LR4_QUERY: &'static str =
277 "SELECT image FROM AgLibraryCollectionImage WHERE collection = ?1";
278
279 fn images_for_collection_with_query(
281 &self,
282 query: &str,
283 collection_id: LrId,
284 ) -> super::Result<Vec<LrId>> {
285 let conn = self.dbconn.as_ref().unwrap();
286 let mut stmt = conn.prepare(query)?;
287 let rows = stmt.query_map([&collection_id], |row| row.get::<usize, i64>(0))?;
288 let mut ids = Vec::new();
289 for id in rows {
290 ids.push(id?);
291 }
292 Ok(ids)
293 }
294
295 pub fn images_for_collection(&self, collection_id: LrId) -> super::Result<Vec<LrId>> {
298 match self.catalog_version {
299 CatalogVersion::Lr2 => {
300 self.images_for_collection_with_query(Self::LR2_QUERY, collection_id)
301 }
302 CatalogVersion::Lr3 | CatalogVersion::Lr4 | CatalogVersion::Lr6 => {
303 self.images_for_collection_with_query(Self::LR4_QUERY, collection_id)
304 }
305 _ => Err(super::Error::UnsupportedVersion),
306 }
307 }
308}