feat(cli): cmd_import_lastpass — full data flow
Unlocks the vault, parses the CSV, encrypts each item, writes items/<id>.enc and manifest.enc, then a single `git add … && git commit` covers all of them. Stderr progress every 50 items + final summary. Exit non-zero only when zero items imported.
This commit is contained in:
@@ -1564,8 +1564,74 @@ fn cmd_import(action: ImportAction) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_import_lastpass(_csv: PathBuf) -> Result<()> {
|
fn cmd_import_lastpass(csv_path: PathBuf) -> Result<()> {
|
||||||
bail!("not implemented yet — Task 8 lands the body")
|
use std::fs;
|
||||||
|
use relicario_core::import_lastpass::parse_lastpass_csv;
|
||||||
|
|
||||||
|
let csv_bytes = fs::read(&csv_path)
|
||||||
|
.with_context(|| format!("failed to read CSV {}", csv_path.display()))?;
|
||||||
|
|
||||||
|
let (items, warnings) = parse_lastpass_csv(&csv_bytes)?;
|
||||||
|
|
||||||
|
if items.is_empty() {
|
||||||
|
// Print all warnings so the user sees why nothing imported.
|
||||||
|
for w in &warnings {
|
||||||
|
print_warning(w);
|
||||||
|
}
|
||||||
|
bail!(
|
||||||
|
"imported 0 items from {} — see warnings above",
|
||||||
|
csv_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let vault = crate::session::UnlockedVault::unlock_interactive()?;
|
||||||
|
let mut manifest = vault.load_manifest()?;
|
||||||
|
|
||||||
|
let total = items.len();
|
||||||
|
let mut written_paths: Vec<String> = Vec::with_capacity(items.len() + 1);
|
||||||
|
|
||||||
|
for (idx, item) in items.iter().enumerate() {
|
||||||
|
vault.save_item(item)?;
|
||||||
|
manifest.upsert(item);
|
||||||
|
written_paths.push(format!("items/{}.enc", item.id.as_str()));
|
||||||
|
|
||||||
|
let n = idx + 1;
|
||||||
|
if n % 50 == 0 || n == total {
|
||||||
|
eprintln!("[{n}/{total}] importing...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vault.save_manifest(&manifest)?;
|
||||||
|
written_paths.push("manifest.enc".into());
|
||||||
|
|
||||||
|
let path_refs: Vec<&str> = written_paths.iter().map(String::as_str).collect();
|
||||||
|
let csv_filename = csv_path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("lastpass.csv");
|
||||||
|
commit_paths(
|
||||||
|
&vault,
|
||||||
|
&format!("import: {} items from LastPass ({})", total, csv_filename),
|
||||||
|
&path_refs,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for w in &warnings {
|
||||||
|
print_warning(w);
|
||||||
|
}
|
||||||
|
eprintln!(
|
||||||
|
"Imported {}, skipped {} (see warnings above)",
|
||||||
|
total,
|
||||||
|
warnings.iter().filter(|w| w.message.contains("skipped")).count()
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_warning(w: &relicario_core::import_lastpass::ImportWarning) {
|
||||||
|
let prefix = match &w.title {
|
||||||
|
Some(t) => format!("row {} ({}):", w.row, t),
|
||||||
|
None => format!("row {}:", w.row),
|
||||||
|
};
|
||||||
|
eprintln!("warning: {prefix} {}", w.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_trash_empty() -> Result<()> {
|
fn cmd_trash_empty() -> Result<()> {
|
||||||
|
|||||||
Reference in New Issue
Block a user