Skip to content

Commit

Permalink
fix: path issues on windows
Browse files Browse the repository at this point in the history
  • Loading branch information
DaRacci committed Jun 19, 2023
1 parent 1fcce75 commit 748624e
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 121 deletions.
1 change: 1 addition & 0 deletions crates/backup/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#![feature(thin_box)]
#![feature(async_closure)]
#![feature(file_create_new)]
#![feature(const_trait_impl)]

extern crate core;

Expand Down
26 changes: 11 additions & 15 deletions crates/backup/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
#![feature(result_option_inspect)]

use backup::application;
use inquire::{PathSelect, PathSelectionMode};
use lib::anyhow::{anyhow, Context, Result};
use lib::anyhow::{Context, Result};
use lib::clap::Parser;
use lib::simplelog::{error, trace};
use lib::simplelog::{error, info, trace};
use std::env;
use std::env::VarError;
use std::error::Error;
use std::ops::Deref;
use std::path::PathBuf;

#[tokio::main]
async fn main() -> Result<()> {
let cli = application::Cli::parse();
pub async fn main() -> Result<()> {
let cli = application::Cli::try_parse()?;
lib::log::init("backup-interactive", &cli.flags)?;
// let _ = required_elevated_privileges().is_some_and(|code| code.exit());

// TODO :: Save this in a config on the machine

let destination = select_location()?;
trace!("Selected destination: {}", &destination.display());

application::main(destination, cli, true).await
application::main(select_location()?, cli, true).await

// TODO :: Verify writable
// TODO :: Verify enough space
Expand All @@ -31,6 +21,12 @@ async fn main() -> Result<()> {

// TODO :: maybe drop this and have the binary placed in the directory where it will be used?
fn select_location() -> Result<PathBuf> {
let working_dir = env::current_dir().context("Failed to get current directory")?;
if working_dir.join("settings.json").exists() {
trace!("Running from working dir {}", working_dir.display());
return Ok(working_dir);
}

env::var("BACKUP_DIR")
.map(PathBuf::from)
// TODO :: Verify writable
Expand Down
24 changes: 11 additions & 13 deletions crates/backup/src/sources/bitwarden.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
use crate::config::backend::Backend;
use crate::config::runtime::RuntimeConfig;
use crate::sources::auto_prune::Prune;
use crate::sources::downloader::Downloader;
use crate::sources::exporter::Exporter;
use anyhow::Result;
use async_trait::async_trait;
use const_format::formatcp;
use indicatif::{MultiProgress, ProgressBar};
use lib::anyhow;
use lib::anyhow::{anyhow, Context};
use lib::simplelog::{debug, error, info};
use lib::fs::normalise_path;
use lib::simplelog::{error, info};
use serde::{Deserialize, Serialize};
use std::env;
use std::fmt::{Display, Formatter};
use std::path::PathBuf;
use std::process::Command;
use indicatif::{MultiProgress, ProgressBar};
use crate::config::backend::Backend;
use crate::config::runtime::RuntimeConfig;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BitWardenCore {
Expand All @@ -25,17 +29,10 @@ impl BitWardenCore {
const BW_SESSION: &'static str = "BW_SESSION";
const BW_DIRECTORY: &'static str = "BITWARDENCLI_APPDATA_DIR";

#[inline]
fn base_dir(config: &RuntimeConfig) -> PathBuf {
config.directory.join("BitWarden")
}

#[inline]
fn data_dir(&self, config: &RuntimeConfig) -> PathBuf {
Self::_data_dir(&config, &self.user)
}

#[inline]
fn backup_dir(&self, config: &RuntimeConfig) -> PathBuf {
Self::_backup_dir(&config, &self.org_name)
}
Expand All @@ -58,12 +55,14 @@ impl Prune for BitWardenCore {
))
.unwrap(); // TODO: Handle this better.

glob.filter_map(|entry| entry.ok()).collect()
glob.flatten().collect()
}
}

#[async_trait]
impl Exporter for BitWardenCore {
const DIRECTORY: &'static str = "Bitwarden";

async fn interactive(config: &RuntimeConfig) -> Result<Vec<Backend>> {
let username = inquire::Text::new("BitWarden Username").prompt()?;
let data_dir = Self::_data_dir(&config, &username);
Expand Down Expand Up @@ -147,7 +146,6 @@ impl Exporter for BitWardenCore {
.collect(),
};

info!("{:?}", &organisations);
Ok(organisations)
}

Expand Down
7 changes: 7 additions & 0 deletions crates/backup/src/sources/exporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ use indicatif::{MultiProgress, ProgressBar};
use lib::anyhow::Result;
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display, Formatter};
use std::path::PathBuf;

#[async_trait]
pub trait Exporter {
const DIRECTORY: &'static str;

fn base_dir(config: &RuntimeConfig) -> PathBuf {
config.directory.join(Self::DIRECTORY)
}

/// Used to attempt to interactively interactive a new exporter.
async fn interactive(config: &RuntimeConfig) -> Result<Vec<Backend>>;

Expand Down
21 changes: 14 additions & 7 deletions crates/backup/src/sources/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use bytes::Bytes;
use futures_util::stream::BoxStream;
use futures_util::StreamExt;
use indicatif::{ProgressBar, ProgressStyle};
use indicatif::ProgressBar;
use lib::anyhow;
use lib::anyhow::{anyhow, Context};
use lib::progress::download_style;
use lib::{anyhow, progress};
use lib::fs::{create_parents, normalise_path};
use lib::simplelog::debug;
use rand::RngCore;
use std::cmp::min;
use std::error::Error;
Expand All @@ -18,25 +19,30 @@ pub mod exporter;
pub(crate) mod interactive;
pub mod op;
pub mod s3;
pub mod downloader;

async fn download_to<E: Error>(
total_size: u64,
mut stream: BoxStream<'_, Result<Bytes, E>>,
path: &PathBuf,
progress: &ProgressBar,
) -> anyhow::Result<()> {
debug!("Creating parent dir for {}", &path.display());
create_parents(&path)?;

progress.set_length(total_size);
let mut file = fs::File::create(&path)?;
let mut downloaded = 0u64;
let mut file =
fs::File::create(&path).with_context(|| format!("Create file {}", path.display()))?;

progress.set_message(format!(
"Downloading {}...",
&path.file_name().unwrap().to_str().unwrap()
&path.file_name().context("Get file name")?.to_str().context("Convert to string")?
));

while let Some(item) = stream.next().await {
let chunk = item.or(Err(anyhow!("Error downloading file")))?;
file.write_all(&chunk).context("Error writing to temporary file")?;
file.write_all(&chunk).context("Error writing to file")?;
let new = min(downloaded + (chunk.len() as u64), total_size);
downloaded = new;
progress.set_position(new);
Expand All @@ -49,10 +55,11 @@ async fn download_to<E: Error>(

async fn download<E: Error>(
total_size: u64,
mut stream: BoxStream<'_, Result<Bytes, E>>,
stream: BoxStream<'_, Result<Bytes, E>>,
progress: &ProgressBar,
) -> anyhow::Result<PathBuf> {
let path = env::temp_dir().join(format!("download-{}", rand::thread_rng().next_u64()));
let path = normalise_path(path);
download_to(total_size, stream, &path, progress).await?;
Ok(path)
}
58 changes: 44 additions & 14 deletions crates/backup/src/sources/op/account.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
use crate::config::runtime::RuntimeConfig;
use crate::sources::interactive::Interactive;
use crate::sources::op::cli;
use crate::sources::op::core::OnePasswordCore;
use async_trait::async_trait;
use inquire::list_option::ListOption;
use inquire::validator::Validation;
use inquire::{MultiSelect, Password, Select, Text};
use lib::anyhow::{anyhow, Context, Result};
use lib::fs::normalise_path;
use lib::simplelog::{info, trace};
use serde::{Deserialize, Serialize};
use serde_json::from_slice;
use std::fmt::{Debug, Display, Formatter};
use std::fs;
use std::path::PathBuf;
use std::process::Command;

use crate::sources::op::cli;
use inquire::list_option::ListOption;
use serde_json::from_slice;
#[cfg(unix)]
use std::os::unix::prelude::PermissionsExt;

Expand All @@ -36,8 +37,16 @@ impl OnePasswordAccount {
impl Display for OnePasswordAccount {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Service(account) => write!(f, "Service Account: {}-{}", &account.attrs.name, &account.attrs.id),
Self::Personal(account) => write!(f, "Personal Account: {}-{}", &account.attrs.email, &account.attrs.id), // TODO :: Domain
Self::Service(account) => write!(
f,
"Service Account: {}-{}",
&account.attrs.name, &account.attrs.id
),
Self::Personal(account) => write!(
f,
"Personal Account: {}-{}",
&account.attrs.email, &account.attrs.id
), // TODO :: Domain
}
}
}
Expand Down Expand Up @@ -157,7 +166,9 @@ impl Interactive<OnePasswordAccount> for ServiceAccount {

account.vaults = MultiSelect::new("Select the vaults you want to use", vaults)
.with_validator(|selections: &[ListOption<&Vault>]| match selections.len() {
0 => Ok(Validation::Invalid("You must select at least one vault.".into())),
0 => Ok(Validation::Invalid(
"You must select at least one vault.".into(),
)),
_ => Ok(Validation::Valid),
})
.prompt()?;
Expand All @@ -175,8 +186,9 @@ impl Interactive<OnePasswordAccount> for PersonalAccount {

if false {
trace!("Getting list of accounts from 1Password");
let output =
Command::new(OnePasswordCore::binary(config)).args(&["account", "list", "--format=json"]).output()?;
let output = Command::new(OnePasswordCore::binary(config))
.args(&["account", "list", "--format=json"])
.output()?;

let accounts = match output.status.success() {
true => output.stdout,
Expand Down Expand Up @@ -206,12 +218,16 @@ impl Interactive<OnePasswordAccount> for PersonalAccount {
}

let domain = Text::new("Enter your 1Password account domain")
.with_help_message("This is the domain you use to login to 1Password, e.g. `https://my.1password.com`")
.with_help_message(
"This is the domain you use to login to 1Password, e.g. `https://my.1password.com`",
)
.with_default("https://my.1password.com")
// TODO :: Better Validator
.with_validator(|url: &str| match url.starts_with("https://") {
true => Ok(Validation::Valid),
false => Ok(Validation::Invalid("The URL must start with https://".into())),
false => Ok(Validation::Invalid(
"The URL must start with https://".into(),
)),
})
.prompt()?;

Expand Down Expand Up @@ -254,7 +270,13 @@ impl AccountCommon for ServiceAccount {
}

fn directory(&self, config: &RuntimeConfig) -> PathBuf {
OnePasswordCore::base_dir(config).join(format!("{name}-{id}", name = &self.attrs.name, id = &self.attrs.id))
let dir = OnePasswordCore::base_dir(config).join(format!(
"{name}-{id}",
name = &self.attrs.name,
id = &self.attrs.id
));

normalise_path(dir)
}

fn vaults(&self) -> Vec<Vault> {
Expand Down Expand Up @@ -284,11 +306,19 @@ impl AccountCommon for PersonalAccount {

// TODO :: Ensure this is a valid directory name on windows
fn directory(&self, config: &RuntimeConfig) -> PathBuf {
OnePasswordCore::base_dir(config).join(format!(
let dir = OnePasswordCore::base_dir(config).join(format!(
"{email}-{domain}",
email = &self.attrs.email,
domain = &self.attrs.id.split('.').next().map(|s| s.strip_prefix("https://").unwrap()).unwrap()
))
domain = &self
.attrs
.id
.split('.')
.next()
.map(|s| s.strip_prefix("https://").unwrap())
.unwrap()
));

normalise_path(dir)
}

fn vaults(&self) -> Vec<Vault> {
Expand Down
10 changes: 3 additions & 7 deletions crates/backup/src/sources/op/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,8 @@ pub mod item {
let login_fields = fields
.clone()
.into_iter()
.enumerate()
.inspect(|(_, f)| trace!("Testing {:?} for login field", f))
.filter(|(_, f)| f.is_login_field())
.map(|(i, _)| fields.remove(i))
.inspect(|f| trace!("Testing {:?} for login field", f))
.filter(|f| f.is_login_field())
.inspect(|f| {
// TODO :: This is a bit of a hack, but it works for now
if f.password_details.as_ref().is_some() {
Expand All @@ -176,9 +174,7 @@ pub mod item {
let notes_plain = fields
.clone()
.into_iter()
.enumerate()
.find(|(_, f)| f.is_notes_field())
.map(|(i, _)| fields.remove(i))
.find(|f| f.is_notes_field())
.and_then(|f| f.attrs.value)
.unwrap_or_default();

Expand Down
Loading

0 comments on commit 748624e

Please sign in to comment.