Goofy first approach with application.

Separate into copy module.
pull/3/head
Joshua Potter 2022-01-08 15:34:09 -05:00
parent cbac40e4ef
commit 4bff745573
5 changed files with 211 additions and 91 deletions

179
src/copy.rs Normal file
View File

@ -0,0 +1,179 @@
use super::{config::PathConfig, path, path::ResPathBuf};
use simplelog::{info, paris};
use std::{
collections::HashMap,
env::VarError,
error, fmt, fs, io,
path::{Path, PathBuf},
result,
};
// ========================================
// Error
// ========================================
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
IOError(io::Error),
VarError(VarError),
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::IOError(err)
}
}
impl From<VarError> for Error {
fn from(err: VarError) -> Error {
Error::VarError(err)
}
}
impl From<path::Error> for Error {
fn from(err: path::Error) -> Error {
match err {
path::Error::IOError(e) => Error::IOError(e),
path::Error::VarError(e) => Error::VarError(e),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::IOError(e) => write!(f, "{}", e),
Error::VarError(e) => write!(f, "{}", e),
}
}
}
impl error::Error for Error {}
// ========================================
// Application
// ========================================
fn apply_all(pc: &PathConfig) -> Result<()> {
let workdir = path::resolve(&pc.config.repos.local)?;
let repo_lookup = get_repo_lookup(workdir.as_ref(), workdir.as_ref())?;
let package_lookup = get_package_lookup(pc);
for (repo_unresolved, repo_resolved) in &repo_lookup {
if let Some(package_resolved) = package_lookup.get(repo_unresolved) {
fs::copy(repo_resolved, package_resolved)?;
info!(
"Copied `{}` from local repository.",
repo_unresolved.display(),
);
}
}
Ok(())
}
fn apply_one(pc: &PathConfig, target: &Path) -> Result<()> {
let workdir = path::resolve(&pc.config.repos.local)?;
let repo_lookup = get_repo_lookup(workdir.as_ref(), workdir.as_ref())?;
let package_lookup = get_package_lookup(pc);
// The user must specify a path that matches the unresolved one.
if let Some(repo_resolved) = repo_lookup.get(target) {
if let Some(package_resolved) = package_lookup.get(target) {
fs::copy(repo_resolved, package_resolved)?;
info!("Copied `{}` from local repository.", target.display(),);
}
}
Ok(())
}
pub fn apply(pc: &PathConfig, file: Option<&str>) -> Result<()> {
if let Some(file) = file {
apply_one(pc, Path::new(file))
} else {
apply_all(pc)
}
}
// ========================================
// Staging
// ========================================
pub fn stage(pc: &PathConfig) -> Result<()> {
let workdir = path::resolve(&pc.config.repos.local)?;
let repo_lookup = get_repo_lookup(workdir.as_ref(), workdir.as_ref())?;
let package_lookup = get_package_lookup(pc);
// Find all files in our repository that are no longer being referenced in
// our primary config file. They should be removed from the repository.
for (repo_unresolved, repo_resolved) in &repo_lookup {
if !package_lookup.contains_key(repo_unresolved) {
fs::remove_file(repo_resolved)?;
}
if let Some(p) = repo_resolved.resolved().parent() {
if p.read_dir()?.next().is_none() {
fs::remove_dir(p)?;
}
}
}
// Find all resolvable files in our primary config and copy them into the
// repository.
for (package_unresolved, package_resolved) in &package_lookup {
let mut copy = package_resolved.resolved().to_path_buf();
copy.push(package_unresolved);
if let Some(p) = copy.parent() {
fs::create_dir_all(p)?;
}
fs::copy(package_resolved.resolved(), copy)?;
}
info!(
"Staged files. Run <italic>git -C <green>{}</> <italic>status</> to see what changed.",
&pc.config.repos.local.display()
);
Ok(())
}
// ========================================
// Utility
// ========================================
fn get_repo_lookup(root: &Path, path: &Path) -> Result<HashMap<PathBuf, ResPathBuf>> {
let mut seen = HashMap::new();
if path.is_dir() {
for entry in fs::read_dir(path)? {
let nested = entry?.path();
if nested.is_dir() {
if nested.ends_with(".git") {
continue;
}
let nested = get_repo_lookup(root, &nested)?;
seen.extend(nested);
} else {
let relative = nested
.strip_prefix(root)
.expect("Relative git file could not be stripped properly.")
.to_path_buf();
seen.insert(relative, ResPathBuf::new(&nested)?);
}
}
}
Ok(seen)
}
fn get_package_lookup(pc: &PathConfig) -> HashMap<PathBuf, ResPathBuf> {
let mut seen = HashMap::new();
for (_, packages) in &pc.config.packages {
for path in packages {
if let Ok(resolved) = path::resolve(path) {
seen.insert(path.to_path_buf(), resolved);
}
}
}
seen
}

View File

@ -1,5 +1,4 @@
use super::{config, config::PathConfig, git, path, path::ResPathBuf};
use git2::Repository;
use super::{config, config::PathConfig, copy, path, path::ResPathBuf};
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use simplelog::{error, paris, trace, warn};
use std::{
@ -139,7 +138,7 @@ impl<'a> WatchState<'a> {
// Daemon
// ========================================
pub fn launch(mut pc: PathConfig, repo: Repository, freq_secs: u64) -> Result<(), Box<dyn Error>> {
pub fn launch(mut pc: PathConfig, freq_secs: u64) -> Result<(), Box<dyn Error>> {
let (poll_tx, poll_rx) = channel();
let (watch_tx, watch_rx) = channel();
let watch_tx1 = watch_tx.clone();
@ -157,7 +156,7 @@ pub fn launch(mut pc: PathConfig, repo: Repository, freq_secs: u64) -> Result<()
let mut state = WatchState::new(poll_tx, &mut watcher)?;
state.update(&pc);
loop {
git::stage(&pc, &repo)?;
copy::stage(&pc)?;
// Received paths should always be fully resolved.
match watch_rx.recv() {
Ok(DebouncedEvent::NoticeWrite(p)) => {

View File

@ -4,12 +4,11 @@ use git2::{
ObjectType, PushOptions, Remote, RemoteCallbacks, Repository, Signature, StashApplyOptions,
StashFlags,
};
use path::ResPathBuf;
use simplelog::{info, paris, warn};
use std::{
collections::HashSet,
env::VarError,
error, fmt, fs, io,
error, fmt, io,
path::{Path, PathBuf},
result,
};
@ -135,87 +134,6 @@ pub fn init(pc: &PathConfig) -> Result<Repository> {
}
}
// ========================================
// Staging
// ========================================
fn find_repo_files(path: &Path) -> Result<Vec<ResPathBuf>> {
let mut seen = Vec::new();
if path.is_dir() {
for entry in fs::read_dir(path)? {
let nested = entry?.path();
if nested.is_dir() {
if nested.ends_with(".git") {
continue;
}
let nested = find_repo_files(&nested)?;
seen.extend_from_slice(&nested);
} else if !nested.ends_with(".homesync") {
seen.push(ResPathBuf::new(&nested)?);
}
}
}
Ok(seen)
}
fn find_package_files(pc: &PathConfig) -> Vec<ResPathBuf> {
let mut seen = Vec::new();
for (_, packages) in &pc.config.packages {
for path in packages {
if let Ok(resolved) = path::resolve(path) {
seen.push(resolved);
}
}
}
seen
}
pub fn stage(pc: &PathConfig, repo: &Repository) -> Result<()> {
let workdir = check_working_repo(repo)?;
let repo_files = find_repo_files(&workdir)?;
let package_files = find_package_files(pc);
// Find all files in our repository that are no longer being referenced in
// our primary config file. They should be removed from the repository.
let lookup_files: HashSet<PathBuf> = package_files
.iter()
.map(|m| m.unresolved().to_path_buf())
.collect();
for repo_file in &repo_files {
let relative = repo_file
.resolved()
.strip_prefix(&workdir)
.expect("Relative git file could not be stripped properly.")
.to_path_buf();
if !lookup_files.contains(&relative) {
fs::remove_file(repo_file)?;
}
if let Some(p) = repo_file.resolved().parent() {
if p.read_dir()?.next().is_none() {
fs::remove_dir(p)?;
}
}
}
// Find all resolvable files in our primary config and copy them into the
// repository.
for package_file in &package_files {
let mut copy = workdir.to_path_buf();
copy.push(package_file.unresolved());
if let Some(p) = copy.parent() {
fs::create_dir_all(p)?;
}
fs::copy(package_file.resolved(), copy)?;
}
info!(
"Staged files. Run <italic>git -C <green>{}</> <italic>status</> to see what changed.",
&pc.config.repos.local.display()
);
Ok(())
}
// ========================================
// Syncing
// ========================================

View File

@ -1,4 +1,5 @@
pub mod config;
pub mod copy;
pub mod daemon;
pub mod git;
pub mod path;
@ -8,9 +9,13 @@ use std::error::Error;
type Result = std::result::Result<(), Box<dyn Error>>;
pub fn run_apply(config: PathConfig, file: Option<&str>) -> Result {
copy::apply(&config, file)?;
Ok(())
}
pub fn run_daemon(config: PathConfig, freq_secs: u64) -> Result {
let repo = git::init(&config)?;
daemon::launch(config, repo, freq_secs)?;
daemon::launch(config, freq_secs)?;
Ok(())
}
@ -32,7 +37,6 @@ pub fn run_pull(config: PathConfig) -> Result {
}
pub fn run_stage(config: PathConfig) -> Result {
let repo = git::init(&config)?;
git::stage(&config, &repo)?;
copy::stage(&config)?;
Ok(())
}

View File

@ -38,6 +38,25 @@ fn main() {
.help("Specify a configuration file to use in place of defaults")
.takes_value(true),
)
.subcommand(
App::new("apply")
.about("Copy files from local repository to rest of desktop")
.arg(
Arg::new("file")
.value_name("FILE")
.conflicts_with("all")
.required_unless_present("all")
.help("The file we want to overwrite from the local repository")
.takes_value(true),
)
.arg(
Arg::new("all")
.long("all")
.conflicts_with("file")
.help("Indicates we want to copy all files from the local repository")
.takes_value(false),
),
)
.subcommand(
App::new("daemon")
.about("Start up a new homesync daemon")
@ -74,6 +93,7 @@ fn dispatch(matches: clap::ArgMatches) -> Result<(), Box<dyn Error>> {
let candidates = find_candidates(&matches)?;
let config = homesync::config::load(&candidates)?;
match matches.subcommand() {
Some(("apply", matches)) => Ok(homesync::run_apply(config, matches.value_of("file"))?),
Some(("daemon", matches)) => {
let freq_secs: u64 = match matches.value_of("frequency") {
Some(f) => f.parse().unwrap_or(0),