use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use rusqlite::{params, Connection};
use crate::collections::Collection;
use crate::folders::{Folder, Folders, RootFolder};
use crate::fromdb::FromDb;
use crate::images::Image;
use crate::keywords::Keyword;
use crate::keywordtree::KeywordTree;
use crate::libraryfiles::LibraryFile;
use crate::lrobject::{LrId, LrObject};
const LR2_VERSION: i32 = 2;
const LR3_VERSION: i32 = 3;
const LR4_VERSION: i32 = 4;
const LR6_VERSION: i32 = 6;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CatalogVersion {
Unknown,
Lr2,
Lr3,
Lr4,
Lr6,
}
impl CatalogVersion {
pub fn is_supported(&self) -> bool {
(*self == Self::Lr2) || (*self == Self::Lr3) || (*self == Self::Lr4) || (*self == Self::Lr6)
}
}
pub struct Catalog {
path: PathBuf,
pub version: String,
pub catalog_version: CatalogVersion,
pub root_keyword_id: LrId,
keywords: BTreeMap<LrId, Keyword>,
folders: Folders,
images: Vec<Image>,
libfiles: Vec<LibraryFile>,
collections: Vec<Collection>,
dbconn: Option<Connection>,
}
impl Catalog {
pub fn new<P>(path: P) -> Catalog
where
P: AsRef<Path>,
{
Catalog {
path: path.as_ref().to_path_buf(),
version: "".to_string(),
catalog_version: CatalogVersion::Unknown,
root_keyword_id: 0,
keywords: BTreeMap::new(),
folders: Folders::new(),
images: vec![],
libfiles: vec![],
collections: vec![],
dbconn: None,
}
}
pub fn open(&mut self) -> bool {
let conn_attempt = Connection::open(&self.path);
if let Ok(conn) = conn_attempt {
self.dbconn = Some(conn);
return true;
}
false
}
fn get_variable<T>(&self, name: &str) -> Option<T>
where
T: rusqlite::types::FromSql,
{
let conn = self.dbconn.as_ref()?;
if let Ok(mut stmt) = conn.prepare("SELECT value FROM Adobe_variablesTable WHERE name=?1") {
let mut rows = stmt.query([&name]).unwrap();
if let Ok(Some(row)) = rows.next() {
return row.get(0).ok();
}
}
None
}
fn parse_version(mut v: String) -> i32 {
v.truncate(2);
if let Ok(version) = v.parse::<i32>() {
version
} else {
0
}
}
pub fn load_version(&mut self) {
if let Some(version) = self.get_variable::<String>("Adobe_DBVersion") {
self.version = version;
let v = Catalog::parse_version(self.version.clone());
self.catalog_version = match v {
LR6_VERSION => CatalogVersion::Lr6,
LR4_VERSION => CatalogVersion::Lr4,
LR3_VERSION => CatalogVersion::Lr3,
LR2_VERSION => CatalogVersion::Lr2,
_ => CatalogVersion::Unknown,
};
}
if let Some(root_keyword_id) = self.get_variable::<f64>("AgLibraryKeyword_rootTagID") {
self.root_keyword_id = root_keyword_id.round() as LrId;
}
}
fn load_objects<T: FromDb>(conn: &Connection, catalog_version: CatalogVersion) -> Vec<T> {
let mut query = format!(
"SELECT {} FROM {}",
T::read_db_columns(catalog_version),
T::read_db_tables(catalog_version)
);
let where_join = T::read_join_where(catalog_version);
if !where_join.is_empty() {
query += &format!(" WHERE {}", where_join);
}
if let Ok(mut stmt) = conn.prepare(&query) {
if let Ok(rows) =
stmt.query_and_then(params![], |row| T::read_from(catalog_version, row))
{
return rows
.into_iter()
.filter_map(|obj| obj.ok())
.collect();
}
}
vec![]
}
pub fn load_keywords_tree(&mut self) -> KeywordTree {
let keywords = self.load_keywords();
let mut tree = KeywordTree::new();
tree.add_children(keywords);
tree
}
pub fn load_keywords(&mut self) -> &BTreeMap<LrId, Keyword> {
if self.keywords.is_empty() {
if let Some(ref conn) = self.dbconn {
let result = Catalog::load_objects::<Keyword>(conn, self.catalog_version);
for keyword in result {
self.keywords.insert(keyword.id(), keyword);
}
}
}
&self.keywords
}
pub fn keywords(&self) -> &BTreeMap<LrId, Keyword> {
&self.keywords
}
pub fn load_folders(&mut self) -> &Folders {
if self.folders.is_empty() {
if let Some(ref conn) = self.dbconn {
let folders = Catalog::load_objects::<RootFolder>(conn, self.catalog_version);
self.folders.append_root_folders(folders);
let mut folders = Catalog::load_objects::<Folder>(conn, self.catalog_version);
for folder in &mut folders {
folder.content = Some(folder.read_content(conn));
}
self.folders.append_folders(folders);
}
}
&self.folders
}
pub fn folders(&self) -> &Folders {
&self.folders
}
pub fn load_library_files(&mut self) -> &Vec<LibraryFile> {
if self.libfiles.is_empty() {
if let Some(ref conn) = self.dbconn {
let mut result = Catalog::load_objects::<LibraryFile>(conn, self.catalog_version);
self.libfiles.append(&mut result);
}
}
&self.libfiles
}
pub fn libfiles(&self) -> &Vec<LibraryFile> {
&self.libfiles
}
pub fn load_images(&mut self) -> &Vec<Image> {
if self.images.is_empty() {
if let Some(ref conn) = self.dbconn {
let mut result = Catalog::load_objects::<Image>(conn, self.catalog_version);
self.images.append(&mut result);
}
}
&self.images
}
pub fn images(&self) -> &Vec<Image> {
&self.images
}
pub fn load_collections(&mut self) -> &Vec<Collection> {
if self.collections.is_empty() {
if let Some(ref conn) = self.dbconn {
let mut collections =
Catalog::load_objects::<Collection>(conn, self.catalog_version);
for collection in &mut collections {
collection.content = Some(collection.read_content(conn));
}
self.collections.append(&mut collections);
}
}
&self.collections
}
pub fn collections(&self) -> &Vec<Collection> {
&self.collections
}
const LR2_QUERY: &'static str =
"SELECT image FROM AgLibraryTagImage WHERE tag = ?1 AND tagKind = \"AgCollectionTagKind\"";
const LR4_QUERY: &'static str =
"SELECT image FROM AgLibraryCollectionImage WHERE collection = ?1";
fn images_for_collection_with_query(
&self,
query: &str,
collection_id: LrId,
) -> super::Result<Vec<LrId>> {
let conn = self.dbconn.as_ref().unwrap();
let mut stmt = conn.prepare(query)?;
let rows = stmt.query_map([&collection_id], |row| row.get::<usize, i64>(0))?;
let mut ids = Vec::new();
for id in rows {
ids.push(id?);
}
Ok(ids)
}
pub fn images_for_collection(&self, collection_id: LrId) -> super::Result<Vec<LrId>> {
match self.catalog_version {
CatalogVersion::Lr2 => {
self.images_for_collection_with_query(Self::LR2_QUERY, collection_id)
}
CatalogVersion::Lr3 | CatalogVersion::Lr4 | CatalogVersion::Lr6 => {
self.images_for_collection_with_query(Self::LR4_QUERY, collection_id)
}
_ => Err(super::Error::UnsupportedVersion),
}
}
}