lrcat/
folders.rs

1/*
2 * Copyright © 2017-2026 Hubert Figuière
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 */
8
9use std::path::PathBuf;
10
11use rusqlite::{Connection, Row};
12
13use crate::catalog::CatalogVersion;
14use crate::content::Content;
15use crate::fromdb::FromDb;
16use crate::lrobject::{LrId, LrObject};
17
18/// A folder define the container for `LibraryFiles`
19/// They are all attached to a `RootFolder`
20#[derive(Clone)]
21pub struct Folder {
22    id: LrId,
23    uuid: String,
24    /// Path from the `RootFolder`
25    pub path_from_root: PathBuf,
26    /// Id of the `RootFolder`
27    pub root_folder: LrId,
28    pub content: Option<Content>,
29}
30
31impl LrObject for Folder {
32    fn id(&self) -> LrId {
33        self.id
34    }
35    fn uuid(&self) -> &str {
36        &self.uuid
37    }
38}
39
40impl FromDb for Folder {
41    fn read_from(_version: CatalogVersion, row: &Row) -> crate::Result<Self> {
42        Ok(Folder {
43            id: row.get(0)?,
44            uuid: row.get(1)?,
45            path_from_root: PathBuf::from(row.get::<_, String>(2)?),
46            root_folder: row.get(3)?,
47            content: None,
48        })
49    }
50    fn read_db_tables(_version: CatalogVersion) -> &'static str {
51        "AgLibraryFolder"
52    }
53    fn read_db_columns(_version: CatalogVersion) -> &'static str {
54        "id_local,id_global,pathFromRoot,rootFolder"
55    }
56}
57
58impl Folder {
59    pub fn new(id: LrId, uuid: &str) -> Folder {
60        Folder {
61            id,
62            uuid: String::from(uuid),
63            path_from_root: PathBuf::from(""),
64            root_folder: 0,
65            content: None,
66        }
67    }
68    pub fn read_content(&self, conn: &Connection) -> Content {
69        Content::from_db(conn, "AgFolderContent", "containingFolder", self.id)
70    }
71}
72
73/// Represent the ancestor of `Folder` and map to
74/// an absolute path
75#[derive(Clone)]
76pub struct RootFolder {
77    id: LrId,
78    uuid: String,
79    /// Absolute path of the `RootFolder`
80    pub absolute_path: PathBuf,
81    /// (User readable) name of the `RootFolder`
82    pub name: String,
83    /// Eventually if it is possible the path is relative
84    /// to the catalog file.
85    pub relative_path_from_catalog: Option<PathBuf>,
86}
87
88impl LrObject for RootFolder {
89    fn id(&self) -> LrId {
90        self.id
91    }
92    fn uuid(&self) -> &str {
93        &self.uuid
94    }
95}
96
97impl RootFolder {
98    /// Create a new `RootFolder` with an id and uuid
99    pub fn new(id: LrId, uuid: &str) -> RootFolder {
100        RootFolder {
101            id,
102            uuid: String::from(uuid),
103            absolute_path: PathBuf::from(""),
104            name: String::from(""),
105            relative_path_from_catalog: None,
106        }
107    }
108}
109
110impl FromDb for RootFolder {
111    fn read_from(_version: CatalogVersion, row: &Row) -> crate::Result<Self> {
112        Ok(RootFolder {
113            id: row.get(0)?,
114            uuid: row.get(1)?,
115            absolute_path: PathBuf::from(row.get::<_, String>(2)?),
116            name: row.get(3)?,
117            relative_path_from_catalog: row.get::<_, String>(4).map(PathBuf::from).ok(),
118        })
119    }
120
121    fn read_db_tables(_version: CatalogVersion) -> &'static str {
122        "AgLibraryRootFolder"
123    }
124
125    fn read_db_columns(_version: CatalogVersion) -> &'static str {
126        "id_local,id_global,absolutePath,name,relativePathFromCatalog"
127    }
128}
129
130/// Represent all the folders
131#[derive(Clone, Default)]
132pub struct Folders {
133    /// The `RootFolder` list
134    pub roots: Vec<RootFolder>,
135    /// The `Folder` list
136    pub folders: Vec<Folder>,
137}
138
139impl Folders {
140    pub fn new() -> Folders {
141        Folders::default()
142    }
143
144    /// Return `true` is it is empty
145    pub fn is_empty(&self) -> bool {
146        self.roots.is_empty() && self.folders.is_empty()
147    }
148
149    /// Add a `Folder`
150    pub fn add_folder(&mut self, folder: Folder) {
151        self.folders.push(folder);
152    }
153
154    /// Add a `RootFolder`
155    pub fn add_root_folder(&mut self, root_folder: RootFolder) {
156        self.roots.push(root_folder);
157    }
158
159    /// Append a vector of `Folder`
160    pub fn append_folders(&mut self, mut folders: Vec<Folder>) {
161        self.folders.append(&mut folders);
162    }
163
164    /// Append a vector of `RootFolder`
165    pub fn append_root_folders(&mut self, mut root_folders: Vec<RootFolder>) {
166        self.roots.append(&mut root_folders);
167    }
168
169    /// Return the eventual `RootFolder` with the id.
170    pub fn find_root_folder(&self, id: LrId) -> Option<&RootFolder> {
171        self.roots.iter().find(|&root| root.id() == id)
172    }
173
174    /// Resolve the folder path by providing an absolute path
175    /// This does not check if the path exist but merely combine
176    /// the `RootFolder` absolute_path and the `Folder` relative path
177    pub fn resolve_folder_path(&self, folder: &Folder) -> Option<PathBuf> {
178        let root_folder = self.find_root_folder(folder.root_folder)?;
179        let mut root_path = root_folder.absolute_path.clone();
180        root_path.push(&folder.path_from_root);
181        Some(root_path)
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use std::path::PathBuf;
188
189    use super::{Folder, Folders, RootFolder};
190
191    #[test]
192    fn test_resolve_folder_path() {
193        let mut folders = Folders::new();
194
195        let mut rfolder = RootFolder::new(24, "toplevel");
196        rfolder.absolute_path = PathBuf::from("/home/hub/Pictures/");
197        rfolder.name = String::from("Pictures");
198        folders.add_root_folder(rfolder);
199
200        let mut folder = Folder::new(42, "foobar");
201        folder.root_folder = 24;
202        folder.path_from_root = PathBuf::from("2017/10/");
203        folders.add_folder(folder);
204
205        let resolved = folders.resolve_folder_path(&folders.folders[0]);
206        assert!(resolved.is_some());
207        let resolved = resolved.unwrap();
208        assert_eq!(resolved, PathBuf::from("/home/hub/Pictures/2017/10/"));
209    }
210}