use std::fs;
use std::path::{Path, PathBuf};
use anyhow::Result;
use thiserror::Error;
use super::{prelude::*, recipients::Recipients, util, ContextPool, Key, Proto};
use crate::Store;
const STORE_GPG_IDS_FILE: &str = ".gpg-id";
const STORE_PUB_KEY_DIR: &str = ".public-keys/";
pub fn store_gpg_ids_file(store: &Store) -> PathBuf {
store.root.join(STORE_GPG_IDS_FILE)
}
pub fn store_public_keys_dir(store: &Store) -> PathBuf {
store.root.join(STORE_PUB_KEY_DIR)
}
pub fn store_read_gpg_fingerprints(store: &Store) -> Result<Vec<String>> {
let path = store_gpg_ids_file(store);
if path.is_file() {
read_fingerprints(path)
} else {
Ok(vec![])
}
}
pub fn store_write_gpg_fingerprints<S: AsRef<str>>(
store: &Store,
fingerprints: &[S],
) -> Result<()> {
write_fingerprints(store_gpg_ids_file(store), fingerprints)
}
fn read_fingerprints<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
Ok(fs::read_to_string(path)
.map_err(Err::ReadFile)?
.lines()
.filter(|fp| !fp.trim().is_empty())
.map(|fp| fp.into())
.collect())
}
fn write_fingerprints<P: AsRef<Path>, S: AsRef<str>>(path: P, fingerprints: &[S]) -> Result<()> {
fs::write(
path,
fingerprints
.into_iter()
.map(|k| k.as_ref())
.collect::<Vec<_>>()
.join("\n"),
)
.map_err(|err| Err::WriteFile(err).into())
}
pub fn store_load_keys(store: &Store) -> Result<Vec<Key>> {
let mut keys = Vec::new();
let fingerprints = store_read_gpg_fingerprints(store)?;
if !fingerprints.is_empty() {
let mut context = super::context(Proto::Gpg)?;
let fingerprints: Vec<_> = fingerprints.iter().map(|fp| fp.as_str()).collect();
keys.extend(context.find_public_keys(&fingerprints)?);
}
Ok(keys)
}
pub fn store_load_recipients(store: &Store) -> Result<Recipients> {
Ok(Recipients::from(store_load_keys(store)?))
}
pub fn store_save_keys(store: &Store, keys: &[Key]) -> Result<()> {
let gpg_fingerprints: Vec<_> = keys
.iter()
.filter(|key| key.proto() == Proto::Gpg)
.map(|key| key.fingerprint(false))
.collect();
store_write_gpg_fingerprints(store, &gpg_fingerprints)?;
store_sync_public_key_files(store, keys)?;
Ok(())
}
pub fn store_save_recipients(store: &Store, recipients: &Recipients) -> Result<()> {
store_save_keys(store, recipients.keys())
}
pub fn store_sync_public_key_files(store: &Store, keys: &[Key]) -> Result<()> {
let dir = store_public_keys_dir(store);
fs::create_dir_all(&dir).map_err(Err::SyncKeyFiles)?;
let files: Vec<(PathBuf, String)> = dir
.read_dir()
.map_err(Err::SyncKeyFiles)?
.filter_map(|e| e.ok())
.filter(|e| e.file_type().map(|f| f.is_file()).unwrap_or(false))
.filter_map(|e| {
e.file_name()
.to_str()
.map(|fp| (e.path(), util::format_fingerprint(fp)))
})
.collect();
for (path, _) in files
.iter()
.filter(|(_, fp)| !util::keys_contain_fingerprint(keys, fp))
{
fs::remove_file(path).map_err(Err::SyncKeyFiles)?;
}
let mut contexts = ContextPool::empty();
for (key, fp) in keys
.into_iter()
.map(|k| (k, k.fingerprint(false)))
.filter(|(_, fp)| !files.iter().any(|(_, other)| fp == other))
{
let proto = key.proto();
let context = contexts.get_mut(proto)?;
let path = dir.join(&fp);
context.export_key_file(key.clone(), &path)?;
}
Ok(())
}
pub fn import_missing_keys_from_store(store: &Store) -> Result<Vec<ImportResult>> {
let dir = store_public_keys_dir(store);
if !dir.is_dir() {
return Ok(vec![]);
}
let mut contexts = ContextPool::empty();
let mut results = Vec::new();
let gpg_fingerprints = store_read_gpg_fingerprints(store)?;
for fingerprint in gpg_fingerprints {
let context = contexts.get_mut(Proto::Gpg)?;
if let Err(_) = context.get_public_key(&fingerprint) {
let path = &store_public_keys_dir(store).join(&fingerprint);
if path.is_file() {
context.import_key_file(path)?;
results.push(ImportResult::Imported(fingerprint));
} else {
results.push(ImportResult::Unavailable(fingerprint));
}
}
}
Ok(results)
}
pub enum ImportResult {
Imported(String),
Unavailable(String),
}
pub trait StoreRecipients {
fn load(store: &Store) -> Result<Recipients>;
fn save(&self, store: &Store) -> Result<()>;
}
impl StoreRecipients for Recipients {
fn load(store: &Store) -> Result<Recipients> {
store_load_recipients(store)
}
fn save(&self, store: &Store) -> Result<()> {
store_save_recipients(store, self)
}
}
#[derive(Debug, Error)]
pub enum Err {
#[error("failed to write to file")]
WriteFile(#[source] std::io::Error),
#[error("failed to read from file")]
ReadFile(#[source] std::io::Error),
#[error("failed to sync public key files")]
SyncKeyFiles(#[source] std::io::Error),
}