1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use crate::key::{load_master_key, MasterKey};
use crate::note::NoteInfo;
use crate::JoplinReaderError;

use std::collections::HashMap;
use std::fs;
use std::path::Path;

use serde::Serialize;

/// Container `struct` which contains the references (and contents) to the
/// [`NoteInfo`]s as well as the [`MasterKey`]s.
#[derive(Debug, Serialize)]
pub struct JoplinNotebook {
    notes: HashMap<String, NoteInfo>,
    master_keys: HashMap<String, MasterKey>,
}

impl JoplinNotebook {
    /// Read a Joplin data folder. `passwords` need to be passed as comma-separated
    /// key-value (master_key_id,passphrase) pairs.
    pub fn new<'a, P: AsRef<Path>, I>(
        joplin_folder: P,
        passwords: I,
    ) -> Result<JoplinNotebook, JoplinReaderError>
    where
        I: IntoIterator<Item = &'a str>,
    {
        let mut master_keys: HashMap<String, MasterKey> = HashMap::new();
        for password in passwords.into_iter() {
            let mut iter = password.splitn(2, ",");
            let master_key_id = iter.next();
            let key = iter.next();
            if let (Some(master_key_id), Some(key)) = (master_key_id, key) {
                let mut key_filename = master_key_id.to_string();
                key_filename.push_str(".md");
                let key_path = joplin_folder.as_ref().join(key_filename);
                if key_path.is_file() {
                    let mk = load_master_key(&key_path, master_key_id.to_string(), key.to_string());
                    if let Ok(mk) = mk {
                        master_keys.insert(master_key_id.to_string(), mk);
                    }
                } else {
                    return Err(JoplinReaderError::NoEncryptionKey { key: format!("{:?}", key_path)});
                }
            }
        }

        let note_paths = match fs::read_dir(joplin_folder) {
            Ok(d) => d,
            Err(_) => return Err(JoplinReaderError::FolderReadError),
        };
        let mut notes: HashMap<String, NoteInfo> = HashMap::new();
        for note_path in note_paths {
            let note_path = note_path.expect("Unable to read path").path();
            let note_path = Path::new(&note_path);

            if note_path.is_file() {
                let item_id = note_path.file_stem().unwrap_or_default();
                if !master_keys.contains_key(item_id.to_str().unwrap_or_default()) {
                    if let Ok(note) = NoteInfo::new(note_path) {
                        match item_id.to_str() {
                            Some(note_id) => {
                                notes.insert(note_id.to_string(), note);
                            }
                            None => {}
                        }
                    }
                }
            }
        }

        Ok(JoplinNotebook { notes, master_keys })
    }

    /// Returns the content of a note.
    pub fn read_note(&mut self, note_id: &str) -> Result<&str, JoplinReaderError> {
        let note = match self.notes.get_mut(note_id) {
            Some(note) => note,
            None => {
                return Err(JoplinReaderError::NoteIdNotFound {
                    note_id: note_id.to_string(),
                })
            }
        };
        let mut encryption_key: Option<&str> = None;
        if note.is_encrypted() {
            let master_key_id = match note.get_encryption_key_id() {
                Some(key_id) => key_id.to_string(),
                None => {
                    return Err(JoplinReaderError::NoEncryptionKey {key: format!("{:?}", note.get_encryption_key_id())});
                }
            };

            encryption_key = match self.master_keys.get(&master_key_id) {
                Some(master_key) => Some(master_key.as_str()),
                None => {
                    return Err(JoplinReaderError::NoEncryptionKey {key: format!("{:?}", master_key_id)});
                }
            }
        }

        note.read(encryption_key)
    }

    /// Returns a [`NoteInfo`]
    pub fn get_note(&self, note_id: &str) -> Result<&NoteInfo, JoplinReaderError> {
        match self.notes.get(note_id) {
            Some(note) => Ok(note),
            None => Err(JoplinReaderError::NoteIdNotFound {
                note_id: note_id.to_string(),
            }),
        }
    }

    /// Iterate all item Ids stored
    pub fn iter(&self) -> impl Iterator<Item = &String> {
        self.notes.keys()
    }
}