Compare commits
89 Commits
basic-arg-
...
main
Author | SHA1 | Date |
---|---|---|
Coin de Gamma | 0d6fcbbd11 | 3 months ago |
Coin de Gamma | c3c5a9c11a | 3 months ago |
Coin de Gamma | 19717f8f22 | 3 months ago |
Coin de Gamma | c5f6cdd485 | 3 months ago |
Coin de Gamma | f71562ab91 | 3 months ago |
Coin de Gamma | 391b456459 | 3 months ago |
Coin de Gamma | 9da8c60340 | 3 months ago |
Coin de Gamma | e19cd571f9 | 3 months ago |
Coin de Gamma | d021281449 | 3 months ago |
Coin de Gamma | 3d8b5fdfdd | 3 months ago |
Coin de Gamma | ed40720295 | 3 months ago |
Coin de Gamma | 0e854941bf | 3 months ago |
Coin de Gamma | d8eb9018a7 | 3 months ago |
rozetko | d438efde15 | 3 months ago |
Coin de Gamma | 956a964303 | 3 months ago |
Coin de Gamma | c66fa2792d | 3 months ago |
Coin de Gamma | 60d4f53ce1 | 3 months ago |
Coin de Gamma | 1d4a94eefc | 3 months ago |
Coin de Gamma | 83dd5c3b83 | 3 months ago |
Coin de Gamma | 4013a9dd08 | 3 months ago |
Coin de Gamma | feb2a7d272 | 3 months ago |
Coin de Gamma | 2dfe467b4d | 3 months ago |
Coin de Gamma | 0296a1fb0e | 3 months ago |
Coin de Gamma | b1de728eb9 | 3 months ago |
Coin de Gamma | cc6b069082 | 3 months ago |
Coin de Gamma | e4d2217d6e | 3 months ago |
Coin de Gamma | be2bb6a9b4 | 3 months ago |
Coin de Gamma | d7a6259a7e | 3 months ago |
Coin de Gamma | 1b96335853 | 3 months ago |
Coin de Gamma | 6dd1bc7d04 | 3 months ago |
Coin de Gamma | 76ff813f98 | 3 months ago |
Coin de Gamma | 4190c93e9e | 3 months ago |
Coin de Gamma | 381fc1f6aa | 3 months ago |
Coin de Gamma | 209e49abe8 | 3 months ago |
Coin de Gamma | de6a028e8b | 3 months ago |
Coin de Gamma | 3cea889755 | 3 months ago |
Coin de Gamma | 9d7a82d75c | 3 months ago |
Coin de Gamma | 22a25a3565 | 3 months ago |
Coin de Gamma | 174244fcef | 3 months ago |
Coin de Gamma | ef2537e204 | 3 months ago |
Coin de Gamma | 8eb62289a1 | 3 months ago |
Coin de Gamma | 421d861b2e | 3 months ago |
Coin de Gamma | c679d4bf74 | 3 months ago |
Coin de Gamma | d0adf49fee | 3 months ago |
Coin de Gamma | 3d4d657f87 | 3 months ago |
Coin de Gamma | 791606ad1b | 3 months ago |
Coin de Gamma | a7f8d8d689 | 3 months ago |
Coin de Gamma | 02ab45c9c7 | 3 months ago |
Coin de Gamma | dae49bcb61 | 3 months ago |
Coin de Gamma | 542ecd60ce | 3 months ago |
Coin de Gamma | 1e738bb907 | 3 months ago |
Coin de Gamma | 1e6fcb56c0 | 3 months ago |
Coin de Gamma | 5008e7b45c | 3 months ago |
Coin de Gamma | 8e671dbe08 | 3 months ago |
Coin de Gamma | 9d79a58414 | 3 months ago |
Coin de Gamma | ac9896f256 | 3 months ago |
Coin de Gamma | 9a0acf3e27 | 3 months ago |
Coin de Gamma | 807c40ee18 | 3 months ago |
Coin de Gamma | 966a0797dc | 3 months ago |
Coin de Gamma | ee2c2faeba | 3 months ago |
Coin de Gamma | 1b779459f1 | 3 months ago |
Coin de Gamma | 069bc02f80 | 3 months ago |
Coin de Gamma | 25b0125893 | 3 months ago |
Coin de Gamma | a0da78c885 | 3 months ago |
Coin de Gamma | 433c09c7a5 | 4 months ago |
Coin de Gamma | ef9f6f5f1b | 4 months ago |
Coin de Gamma | bf88196b40 | 4 months ago |
Coin de Gamma | 13e67ea136 | 4 months ago |
Coin de Gamma | a3b5f0bcea | 4 months ago |
Coin de Gamma | 0cbb6b43ca | 4 months ago |
Coin de Gamma | 497436579f | 4 months ago |
Coin de Gamma | bc4278efe1 | 4 months ago |
Coin de Gamma | 1156f71ab8 | 4 months ago |
Coin de Gamma | dc5c5c5207 | 4 months ago |
Coin de Gamma | 42a1e05b6f | 4 months ago |
Coin de Gamma | 701def6089 | 4 months ago |
Coin de Gamma | 826a87fa3e | 4 months ago |
Coin de Gamma | e1b194d599 | 4 months ago |
Coin de Gamma | 10bd6772fa | 4 months ago |
Coin de Gamma | bd70695b77 | 4 months ago |
Coin de Gamma | 8ce1380ea3 | 4 months ago |
Coin de Gamma | 54c6503e09 | 4 months ago |
Coin de Gamma | e2dd62ad6b | 4 months ago |
Coin de Gamma | 6896827b3c | 4 months ago |
Coin de Gamma | eb3e6f338f | 4 months ago |
Coin de Gamma | 27bef910a8 | 4 months ago |
Coin de Gamma | 9d1a834f22 | 4 months ago |
Coin de Gamma | 66e300a4c8 | 4 months ago |
Coin de Gamma | 0617f54e92 | 4 months ago |
8 changed files with 854 additions and 13 deletions
@ -1,3 +1,50 @@ |
|||||||
# mps |
# MPS |
||||||
|
|
||||||
|
A small tool for storing passwords locally with git sync |
||||||
|
|
||||||
|
## Installation |
||||||
|
1. Clone MPS repo: |
||||||
|
``` |
||||||
|
git clone git@code.corpglory.net:corpglory/mps.git |
||||||
|
``` |
||||||
|
|
||||||
|
2. Build MPS |
||||||
|
``` |
||||||
|
cd mps |
||||||
|
cargo build --release |
||||||
|
``` |
||||||
|
|
||||||
|
3. Run init |
||||||
|
``` |
||||||
|
mps init |
||||||
|
``` |
||||||
|
|
||||||
|
4. Add your storage under git |
||||||
|
```bash |
||||||
|
cd storage |
||||||
|
git init |
||||||
|
git checkout -b main |
||||||
|
git add db.mps |
||||||
|
git commit -m "init mps" |
||||||
|
git remote add origin <your_git_storage_url> |
||||||
|
git push -u origin main |
||||||
|
``` |
||||||
|
|
||||||
|
### Intilization with exisitng storage db repo |
||||||
|
|
||||||
|
1. Create empty repository for storing your passwords on gitlab or somewhere you prefer |
||||||
|
2. Clone to your home folder `<mps_path>/storage`: `git clone <your_git_storage_url> storage` |
||||||
|
|
||||||
|
#### Exporting `MPS_HOME` |
||||||
|
You can export variable `$MPS_HOME` to init storage for example in your home directory: |
||||||
|
|
||||||
|
`export MPS_HOME="/home/<your_username>/.mps"` in your `~/.zshenv` |
||||||
|
|
||||||
|
now `mps` will try to get storage from `$MPS_HOME/storage` |
||||||
|
|
||||||
|
#### Add your mps build to PATH in your ~/.zshenv |
||||||
|
|
||||||
|
``` |
||||||
|
export PATH=$PATH:"<mps_path>/target/release" |
||||||
|
``` |
||||||
|
|
||||||
A small tool for storing passwords locally with git sync |
|
@ -0,0 +1,69 @@ |
|||||||
|
use std::env; |
||||||
|
use std::fs::read_to_string; |
||||||
|
use std::process::Command; |
||||||
|
use tempfile::NamedTempFile; |
||||||
|
use std::io::{self, Write}; |
||||||
|
|
||||||
|
const ENV_MPS_EDITOR: &str = "MPS_EDITOR"; |
||||||
|
const DEFAULT_EDITOR: &str = "nano"; |
||||||
|
|
||||||
|
|
||||||
|
// Use with content "" (empty string) for adding new item
|
||||||
|
pub fn open_to_edit(content: &String) -> io::Result<String> { |
||||||
|
// Create a temporary file
|
||||||
|
let mut temp_file = NamedTempFile::new()?; |
||||||
|
|
||||||
|
// Write content
|
||||||
|
write!(temp_file, "{}", content)?; |
||||||
|
|
||||||
|
// Get the user's preferred editor from the $EDITOR environment variable
|
||||||
|
// Default is 'nano'
|
||||||
|
let editor = env::var(ENV_MPS_EDITOR).unwrap_or_else(|_| DEFAULT_EDITOR.to_string()); |
||||||
|
|
||||||
|
// Get the path of the temp file
|
||||||
|
let file_path = temp_file.path().to_str().unwrap().to_string(); |
||||||
|
|
||||||
|
// Open the file in the external editor
|
||||||
|
Command::new(editor) |
||||||
|
.arg(&file_path) |
||||||
|
.status() |
||||||
|
.map_err(|e| io::Error::new( |
||||||
|
io::ErrorKind::Other, |
||||||
|
format!("Failed to launch editor: {}", e) |
||||||
|
))?; |
||||||
|
|
||||||
|
// Read the file content after editing
|
||||||
|
let mut edited_content = read_to_string(&file_path)?; |
||||||
|
|
||||||
|
// Remove only one trailing newline if it exists
|
||||||
|
// because editor like vim or nano adds to the end new line
|
||||||
|
if edited_content.ends_with('\n') { |
||||||
|
edited_content.pop(); |
||||||
|
} |
||||||
|
|
||||||
|
// Print the edited content
|
||||||
|
Ok(edited_content) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn open_to_show(content: &String) -> io::Result<()> { |
||||||
|
let mut temp_file = NamedTempFile::new()?; |
||||||
|
|
||||||
|
// Write the content to the file
|
||||||
|
write!(temp_file, "{}", content)?; |
||||||
|
|
||||||
|
// Default is 'nano'
|
||||||
|
let editor = env::var(ENV_MPS_EDITOR).unwrap_or_else(|_| DEFAULT_EDITOR.to_string()); |
||||||
|
|
||||||
|
let file_path = temp_file.path().to_str().unwrap().to_string(); |
||||||
|
|
||||||
|
// Open the file in the external editor
|
||||||
|
Command::new(editor) |
||||||
|
.arg(&file_path) |
||||||
|
.status() |
||||||
|
.map_err(|e| io::Error::new( |
||||||
|
io::ErrorKind::Other, |
||||||
|
format!("Failed to launch editor: {}", e) |
||||||
|
))?; |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
@ -0,0 +1,95 @@ |
|||||||
|
use aes_gcm::{
|
||||||
|
aead::{Aead, AeadCore, KeyInit, OsRng}, |
||||||
|
Aes256Gcm, Key, Nonce |
||||||
|
}; |
||||||
|
|
||||||
|
use std::io; |
||||||
|
|
||||||
|
|
||||||
|
static PASSWORD_TEST: &str = "MyPasswordStorage"; // will be added with nonce for storing each time
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Encoder { |
||||||
|
// will be stored with padding 32 bytes
|
||||||
|
passphrase: String |
||||||
|
} |
||||||
|
|
||||||
|
impl Encoder { |
||||||
|
pub fn from(passphrase: &String) -> Encoder { |
||||||
|
// TODO: throw error if password longer that 32 bytes
|
||||||
|
let padded_passphrase = Encoder::get_passhrase_with_padding(passphrase); |
||||||
|
Encoder { passphrase: padded_passphrase } |
||||||
|
} |
||||||
|
|
||||||
|
fn get_passhrase_with_padding(passphrase: &String) -> String { |
||||||
|
let mut result = passphrase.clone(); |
||||||
|
while result.len() < 32 { |
||||||
|
// use '-' for padding, can be anything else
|
||||||
|
result.push('-'); |
||||||
|
} |
||||||
|
result |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: error type
|
||||||
|
pub fn encrypt(&self, plain_text: &String) -> io::Result<String> { |
||||||
|
let key = Key::<Aes256Gcm>::from_slice(self.passphrase.as_bytes());
|
||||||
|
let nonce = Aes256Gcm::generate_nonce(&mut OsRng); |
||||||
|
|
||||||
|
let cipher = Aes256Gcm::new(key); |
||||||
|
|
||||||
|
// TODO: mar error inted of expect
|
||||||
|
let ciphered_data = cipher.encrypt(&nonce, plain_text.as_bytes()) |
||||||
|
.map_err(|_| io::Error::new( |
||||||
|
io::ErrorKind::Other, |
||||||
|
"Failed to encrypt" |
||||||
|
))?; |
||||||
|
|
||||||
|
// combining nonce and encrypted data together
|
||||||
|
// for storage purpose
|
||||||
|
let mut encrypted_data: Vec<u8> = nonce.to_vec(); |
||||||
|
encrypted_data.extend_from_slice(&ciphered_data); |
||||||
|
|
||||||
|
Ok(hex::encode(encrypted_data)) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// TODO: review error type
|
||||||
|
pub fn decrypt(&self, encrypted_data: String) -> io::Result<String> { |
||||||
|
let encrypted_data = hex::decode(encrypted_data) |
||||||
|
.map_err(|_| io::Error::new( |
||||||
|
io::ErrorKind::Other, |
||||||
|
"failed to decode hex string into vec" |
||||||
|
))?; |
||||||
|
|
||||||
|
let key = Key::<Aes256Gcm>::from_slice(self.passphrase.as_bytes()); |
||||||
|
let (nonce_arr, ciphered_data) = encrypted_data.split_at(12); |
||||||
|
let nonce = Nonce::from_slice(nonce_arr); |
||||||
|
let cipher = Aes256Gcm::new(key); |
||||||
|
let plaintext = cipher.decrypt(nonce, ciphered_data) |
||||||
|
.map_err(|_| io::Error::new( |
||||||
|
io::ErrorKind::InvalidData, |
||||||
|
"failed to decrypt data" |
||||||
|
))?; |
||||||
|
|
||||||
|
let result = String::from_utf8(plaintext) |
||||||
|
.map_err(|_| io::Error::new( |
||||||
|
io::ErrorKind::InvalidData, |
||||||
|
"failed to convert vector of bytes to string" |
||||||
|
))?; |
||||||
|
Ok(result) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn test_encoded_passphrase(&self, passphrase_encrypted: String) -> io::Result<bool> { |
||||||
|
// TODO: better way to check error
|
||||||
|
let decrypted = match self.decrypt(passphrase_encrypted) { |
||||||
|
Ok(decrypted) => decrypted, |
||||||
|
Err(_) => return Ok(false) |
||||||
|
}; |
||||||
|
Ok(PASSWORD_TEST == decrypted) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_encoded_test_passphrase(&self) -> io::Result<String> { |
||||||
|
self.encrypt(&PASSWORD_TEST.to_string()) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
use crate::paths; |
||||||
|
|
||||||
|
use std::process::Command; |
||||||
|
use std::io; |
||||||
|
|
||||||
|
|
||||||
|
pub struct Git { |
||||||
|
} |
||||||
|
|
||||||
|
impl Git { |
||||||
|
pub fn sync() -> io::Result<()> { |
||||||
|
let sp = paths::get_storage_path()?; |
||||||
|
Command::new("git") |
||||||
|
.arg("add") |
||||||
|
.arg(paths::PATH_DB) |
||||||
|
.current_dir(&sp) |
||||||
|
.status() |
||||||
|
.map_err(|e| io::Error::new( |
||||||
|
io::ErrorKind::Other, |
||||||
|
format!("Failed to execute git add: {}", e) |
||||||
|
))?; |
||||||
|
Command::new("git") |
||||||
|
.arg("commit") |
||||||
|
.arg("-m") |
||||||
|
.arg("\"sync\"") |
||||||
|
.current_dir(&sp) |
||||||
|
.status() |
||||||
|
.map_err(|e| io::Error::new( |
||||||
|
io::ErrorKind::Other, |
||||||
|
format!("Failed to execute git commit: {}", e) |
||||||
|
))?; |
||||||
|
Command::new("git") |
||||||
|
.arg("push") |
||||||
|
.current_dir(&sp) |
||||||
|
.status() |
||||||
|
.map_err(|e| io::Error::new( |
||||||
|
io::ErrorKind::Other, |
||||||
|
format!("Failed to execute git push: {}", e) |
||||||
|
))?; |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,24 @@ |
|||||||
|
use std::env; |
||||||
|
use std::io; |
||||||
|
|
||||||
|
|
||||||
|
pub static ENV_MPS_HOME: &str = "MPS_HOME"; |
||||||
|
|
||||||
|
pub static PATH_STORAGE: &str = "storage"; // should be under git
|
||||||
|
pub static PATH_DB: &str = "db.mps"; |
||||||
|
|
||||||
|
|
||||||
|
pub fn get_storage_path() -> io::Result<String> { |
||||||
|
let result = match env::var(ENV_MPS_HOME) { |
||||||
|
Ok(mps_home) => format!("{}/{}", mps_home, PATH_STORAGE), |
||||||
|
Err(_) => PATH_STORAGE.to_string() |
||||||
|
}; |
||||||
|
Ok(result) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_db_path() -> io::Result<String> { |
||||||
|
let st = get_storage_path()?; |
||||||
|
let result = format!("{}/{}", st, PATH_DB); |
||||||
|
Ok(result) |
||||||
|
} |
||||||
|
|
@ -0,0 +1,253 @@ |
|||||||
|
use crate::paths; |
||||||
|
use crate::encoder; |
||||||
|
use crate::git; |
||||||
|
|
||||||
|
use encoder::Encoder; |
||||||
|
|
||||||
|
use std::collections::HashSet; |
||||||
|
use std::hash::{Hash, Hasher}; |
||||||
|
|
||||||
|
use std::fs; |
||||||
|
use std::path::Path; |
||||||
|
use std::io::{self, Write, BufRead}; |
||||||
|
use std::fmt; |
||||||
|
use std::cmp::{PartialEq, Ordering}; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)] |
||||||
|
pub struct Item { |
||||||
|
pub id: String, |
||||||
|
pub content: String |
||||||
|
} |
||||||
|
|
||||||
|
impl Item { |
||||||
|
pub fn from(s: String, c: String) -> Item { |
||||||
|
Item { id: s, content: c } |
||||||
|
} |
||||||
|
// used only to search in HashSet
|
||||||
|
pub fn from_empty(s: String) -> Item { |
||||||
|
Item { id: s, content: String::from("") } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialEq for Item { |
||||||
|
fn eq(&self, other: &Self) -> bool { |
||||||
|
self.id == other.id
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Eq for Item {} |
||||||
|
|
||||||
|
impl Hash for Item { |
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) { |
||||||
|
self.id.hash(state); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl fmt::Display for Item { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
||||||
|
writeln!(f, "{}", self.id)?; |
||||||
|
writeln!(f, "---------")?; |
||||||
|
writeln!(f, "{}", self.content) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialOrd for Item { |
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
||||||
|
self.id.partial_cmp(&other.id) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Ord for Item { |
||||||
|
fn cmp(&self, other: &Self) -> Ordering { |
||||||
|
self.id.cmp(&other.id) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub struct Storage { |
||||||
|
items: HashSet::<Item>, |
||||||
|
encoder: Encoder |
||||||
|
} |
||||||
|
|
||||||
|
impl Storage { |
||||||
|
pub fn new(passphrase: String) -> Storage { |
||||||
|
Storage { |
||||||
|
items: HashSet::<Item>::new(), |
||||||
|
encoder: Encoder::from(&passphrase) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn from_db(passphrase: String) -> io::Result<Storage> { |
||||||
|
if !Storage::is_inited()? { |
||||||
|
return Err(io::Error::new( |
||||||
|
io::ErrorKind::Other, |
||||||
|
"Storage is not initialized" |
||||||
|
)); |
||||||
|
} |
||||||
|
let encoder = Encoder::from(&passphrase); |
||||||
|
let file = fs::File::open(paths::get_db_path()?)?; |
||||||
|
let reader = io::BufReader::new(file); |
||||||
|
let mut items = HashSet::<Item>::new(); |
||||||
|
let mut id: Option<String> = None; |
||||||
|
let mut lines = reader.lines(); |
||||||
|
let passtest = match lines.next() { |
||||||
|
Some(line) => line?, |
||||||
|
None => return Err( |
||||||
|
io::Error::new(io::ErrorKind::InvalidData, |
||||||
|
"Bad storage db format: no passphrase in the beginnning" |
||||||
|
)), |
||||||
|
}; |
||||||
|
// TODO: only in debug mode
|
||||||
|
//println!("passphrase ok");
|
||||||
|
if !encoder.test_encoded_passphrase(passtest)? { |
||||||
|
return Err(io::Error::new(io::ErrorKind::InvalidData, "Wrong passphrase")); |
||||||
|
} |
||||||
|
for line in lines { |
||||||
|
match line { |
||||||
|
Ok(line) => { |
||||||
|
if id.is_none() { |
||||||
|
let line = encoder.decrypt(line)?; |
||||||
|
// TODO: only in debug mode
|
||||||
|
//println!("{}", line);
|
||||||
|
id = Some(line); |
||||||
|
} else { |
||||||
|
let content = encoder.decrypt(line)?; |
||||||
|
// TODO: only in debug
|
||||||
|
//println!("{}", content);
|
||||||
|
items.insert(Item::from(id.unwrap(), content)); |
||||||
|
id = None; |
||||||
|
} |
||||||
|
}, |
||||||
|
Err(e) => { |
||||||
|
eprintln!("Error reading line, {}", e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Ok(Storage { |
||||||
|
items, |
||||||
|
encoder |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn init(passphrase: String) -> io::Result<()> { |
||||||
|
//Storage::check_installed()?;
|
||||||
|
let sp = paths::get_storage_path()?; |
||||||
|
fs::create_dir(sp)?; |
||||||
|
let st = Storage::new(passphrase); |
||||||
|
st.dump_db()?; |
||||||
|
println!("Storage db created"); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn check_installed() -> io::Result<()> { |
||||||
|
let sp = paths::get_storage_path()?; |
||||||
|
let storage_path = Path::new(&sp); |
||||||
|
// Check if the folder exists and is a directory
|
||||||
|
if !storage_path.exists() || !storage_path.is_dir() { |
||||||
|
return Err(io::Error::new( |
||||||
|
io::ErrorKind::NotFound, |
||||||
|
format!("{} does not exist or not a dir", sp) |
||||||
|
)); |
||||||
|
} |
||||||
|
let git_path = storage_path.join(".git"); |
||||||
|
if !git_path.exists() || !git_path.is_dir() { |
||||||
|
return Err(io::Error::new( |
||||||
|
io::ErrorKind::NotFound, |
||||||
|
format!("{} not under git", sp) |
||||||
|
)); |
||||||
|
} |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn is_inited() -> io::Result<bool> { |
||||||
|
//Storage::check_installed()?;
|
||||||
|
let db = paths::get_db_path()?; |
||||||
|
let db_path = Path::new(&db); |
||||||
|
Ok(db_path.exists()) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn ids(&self) -> Vec<String> { |
||||||
|
let mut result = Vec::new(); |
||||||
|
for item in self.items.iter() { |
||||||
|
result.push(item.id.clone()); |
||||||
|
} |
||||||
|
result.sort(); |
||||||
|
result |
||||||
|
} |
||||||
|
|
||||||
|
pub fn contains(&self, id: &String) -> bool { |
||||||
|
let item = Item::from_empty(id.clone()); |
||||||
|
self.items.contains(&item) |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: return Result<Item>
|
||||||
|
pub fn get(&self, id: &String) -> &Item { |
||||||
|
let item = Item::from_empty(id.clone()); |
||||||
|
self.items.get(&item).unwrap() |
||||||
|
} |
||||||
|
|
||||||
|
/// Counting starts from 1, according to UI
|
||||||
|
pub fn get_id_by_number(&self, number: u32) -> io::Result<String> { |
||||||
|
let number = number as usize; |
||||||
|
if number == 0 { |
||||||
|
return Err(io::Error::new( |
||||||
|
io::ErrorKind::InvalidInput, |
||||||
|
"Items numbering starts from 1" |
||||||
|
)); |
||||||
|
} |
||||||
|
let ids = self.ids(); |
||||||
|
if number > ids.len() { |
||||||
|
return Err(io::Error::new( |
||||||
|
io::ErrorKind::InvalidInput, |
||||||
|
format!("There are only {} items, but asked with number {}", ids.len(), number) |
||||||
|
)); |
||||||
|
} |
||||||
|
Ok(ids[number - 1].clone()) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn add(&mut self, item: Item) { |
||||||
|
self.items.insert(item); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn update(&mut self, item: Item) { |
||||||
|
self.items.remove(&item); |
||||||
|
self.items.insert(item); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn remove(&mut self, id: &String) { |
||||||
|
let item = Item::from_empty(id.clone()); |
||||||
|
self.items.remove(&item); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn dump(&self) -> io::Result<()> { |
||||||
|
self.dump_db()?; |
||||||
|
git::Git::sync()?; |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn dump_db(&self) -> io::Result<()> { |
||||||
|
let mut file = fs::OpenOptions::new() |
||||||
|
.write(true) |
||||||
|
.truncate(true) // Clear the file content before writing
|
||||||
|
.create(true)
|
||||||
|
.open(paths::get_db_path()?)?; |
||||||
|
writeln!(file, "{}", self.encoder.get_encoded_test_passphrase()?)?; |
||||||
|
|
||||||
|
for item in self.items.iter() { |
||||||
|
writeln!(file, "{}", self.encoder.encrypt(&item.id)?)?; |
||||||
|
let content = self.encoder.encrypt(&item.content)?; |
||||||
|
writeln!(file, "{}", content)?; |
||||||
|
} |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn new_from_passphrase(&self, passphrase: &String) -> Storage { |
||||||
|
return Storage { |
||||||
|
encoder: Encoder::from(passphrase), |
||||||
|
items: self.items.clone() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
Loading…
Reference in new issue