mod git; use std::fs::{create_dir_all, remove_file, write, File}; use std::io::{Error, ErrorKind::Other, Read, Result}; use std::path::Path; use git::Git; pub trait Remote { fn can_handle(&self, remote: &str) -> bool; fn sync_up(&self, remote: &str, directory: &Path) -> Result<()>; fn sync_down(&self, remote: &str, directory: &Path) -> Result<()>; } pub fn add(config_directory: &Path, remote_config: &Path, remote: &str) -> Result<()> { create_dir_all(config_directory)?; write(remote_config, remote)?; Ok(()) } pub fn remove(remote_config: &Path) -> Result<()> { if remote_config.exists() { remove_file(remote_config)?; } Ok(()) } pub fn sync_up(data_directory: &Path, remote_config: &Path) -> Result<()> { let remote_address = read_remote(remote_config).ok_or_else(|| Error::new(Other, "No remote is configured"))?; create_dir_all(data_directory)?; let remotes = available_remotes(); for remote in remotes { if remote.can_handle(&remote_address) { return remote.sync_up(&remote_address, data_directory); } } Err(Error::new( Other, "No valid strategies found for your configured remote.", )) } pub fn sync_down(data_directory: &Path, remote_config: &Path) -> Result<()> { let remote_address = read_remote(remote_config).ok_or_else(|| Error::new(Other, "No remote is configured"))?; create_dir_all(data_directory)?; let remotes = available_remotes(); for remote in remotes { if remote.can_handle(&remote_address) { return remote.sync_down(&remote_address, data_directory); } } Err(Error::new( Other, "No valid strategies found for your configured remote.", )) } fn available_remotes() -> Vec> { vec![Box::new(Git::new())] } fn read_remote(file_path: &Path) -> Option { let mut file = File::open(file_path).ok()?; let mut contents = String::new(); file.read_to_string(&mut contents).ok()?; Some(contents) } #[cfg(test)] mod tests { use std::fs::create_dir_all; use std::io::Write; use std::path::PathBuf; use super::*; use test_utilities::*; #[test] fn test_adds_a_remote() { let test_dir = setup_test_dir(); let config_dir = test_dir.join("config"); let remote_config = config_dir.join("remoteconfig"); assert!(!&config_dir.exists()); add(&config_dir, &remote_config, "whaaat").expect("Could not add a remote"); assert!(&config_dir.exists()); assert_file_contents(&remote_config, "whaaat"); cleanup_test_dir(&test_dir); } #[test] fn test_removes_a_remote() { let test_dir = setup_test_dir(); let remote_config = test_dir.join("remoteconfig"); create_test_file(&remote_config, "remote control"); assert_file_contents(&remote_config, "remote control"); remove(&remote_config).expect("Could not remove a remote"); assert!(!&remote_config.exists()); cleanup_test_dir(&test_dir); } #[test] fn test_syncs_remote_up() { let test_dir = setup_test_dir(); let local_dir = test_dir.join("modlocal"); let remote_dir = test_dir.join("modremote"); create_dir_all(&local_dir).expect("Could not create local test directory"); create_dir_all(&remote_dir).expect("Could not create remote test directory"); create_remote(&remote_dir); let remote_dir_as_string = remote_dir.display().to_string(); create_test_file(&test_dir.join("remoteconfig"), &remote_dir_as_string); create_test_file(&local_dir.join("file1.txt"), "I exist"); sync_up(&local_dir, &test_dir.join("remoteconfig")).expect("Could not sync up to remote"); assert_file_in_repo_with_contents(&remote_dir_as_string, "file1.txt", "I exist"); assert_file_in_repo_does_not_exist(&remote_dir_as_string, "file2.txt"); create_test_file(&local_dir.join("file2.txt"), "I also exist now, btw"); sync_up(&local_dir, &test_dir.join("remoteconfig")).expect("Could not sync up to remote"); assert_file_in_repo_with_contents(&remote_dir_as_string, "file1.txt", "I exist"); assert_file_in_repo_with_contents( &remote_dir_as_string, "file2.txt", "I also exist now, btw", ); cleanup_test_dir(&test_dir); } #[test] fn test_syncs_remote_up_even_if_directory_does_not_exist() { let test_dir = setup_test_dir(); let local_dir = test_dir.join("modlocal"); let remote_dir = test_dir.join("modremote"); create_dir_all(&remote_dir).expect("Could not create remote test directory"); create_remote(&remote_dir); let remote_dir_as_string = remote_dir.display().to_string(); create_test_file(&test_dir.join("remoteconfig"), &remote_dir_as_string); sync_up(&local_dir, &test_dir.join("remoteconfig")).expect("Could not sync up to remote"); assert!(local_dir.exists()); cleanup_test_dir(&test_dir); } #[test] fn test_sync_up_down_fails_if_local_not_creatable() { let test_dir = setup_test_dir(); let local_dir = &PathBuf::from("/lasjkdlaksjdklasjdlaks/asldkj/asjdlaksdj"); let remote_dir = test_dir.join("modremote"); create_dir_all(&remote_dir).expect("Could not create remote test directory"); create_remote(&remote_dir); let remote_dir_as_string = remote_dir.display().to_string(); create_test_file(&test_dir.join("remoteconfig"), &remote_dir_as_string); let result = sync_up(&local_dir, &test_dir.join("remoteconfig")); assert!(result.is_err()); cleanup_test_dir(&test_dir); } #[test] fn test_sync_up_fails_on_invalid_strategies() { let test_dir = setup_test_dir(); let local_dir = test_dir.join("modlocal"); create_test_file(&test_dir.join("remoteconfig"), "AAAAAH"); let result = sync_up(&local_dir, &test_dir.join("remoteconfig")); assert!(result.is_err()); cleanup_test_dir(&test_dir); } #[test] fn test_syncs_remote_down() { let test_dir = setup_test_dir(); let local_dir = test_dir.join("modownlocal"); let remote_dir = test_dir.join("modownremote"); create_dir_all(&local_dir).expect("Could not create local test directory"); create_dir_all(&remote_dir).expect("Could not create remote test directory"); create_remote(&remote_dir); let remote_dir_as_string = remote_dir.display().to_string(); create_test_file(&test_dir.join("remoteconfig"), &remote_dir_as_string); commit_file_to_remote(&remote_dir_as_string, "file 1.txt", "I exist, but remotely"); assert!(!&local_dir.join("file 1.txt").exists()); assert!(!&local_dir.join("file2.txt").exists()); sync_down(&local_dir, &test_dir.join("remoteconfig")) .expect("Could not sync down from remote"); assert_file_contents(&local_dir.join("file 1.txt"), "I exist, but remotely"); assert!(!&local_dir.join("file2.txt").exists()); commit_file_to_remote( &remote_dir_as_string, "file2.txt", "I also exist, but remotely", ); assert!(!&local_dir.join("file2.txt").exists()); sync_down(&local_dir, &test_dir.join("remoteconfig")) .expect("Could not sync down from remote"); assert_file_contents(&local_dir.join("file 1.txt"), "I exist, but remotely"); assert_file_contents(&local_dir.join("file2.txt"), "I also exist, but remotely"); cleanup_test_dir(&test_dir); } #[test] fn test_syncs_remote_down_even_if_directory_does_not_exist() { let test_dir = setup_test_dir(); let local_dir = test_dir.join("modlocal"); let remote_dir = test_dir.join("modremote"); create_dir_all(&remote_dir).expect("Could not create remote test directory"); create_remote(&remote_dir); let remote_dir_as_string = remote_dir.display().to_string(); create_test_file(&test_dir.join("remoteconfig"), &remote_dir_as_string); commit_file_to_remote(&remote_dir_as_string, "onions", "Make me cry"); sync_down(&local_dir, &test_dir.join("remoteconfig")).expect("Could not sync up to remote"); assert_file_contents(&local_dir.join("onions"), "Make me cry"); cleanup_test_dir(&test_dir); } #[test] fn test_sync_down_fails_if_local_not_creatable() { let test_dir = setup_test_dir(); let local_dir = &PathBuf::from("/lasjkdlaksjdklasjdlaks/asldkj/asjdlaksdj"); let remote_dir = test_dir.join("modremote"); create_dir_all(&remote_dir).expect("Could not create remote test directory"); create_remote(&remote_dir); let remote_dir_as_string = remote_dir.display().to_string(); create_test_file(&test_dir.join("remoteconfig"), &remote_dir_as_string); let result = sync_down(&local_dir, &test_dir.join("remoteconfig")); assert!(result.is_err()); cleanup_test_dir(&test_dir); } #[test] fn test_sync_down_fails_on_invalid_strategies() { let test_dir = setup_test_dir(); let local_dir = test_dir.join("modlocal"); create_test_file(&test_dir.join("remoteconfig"), "AAAAAH"); let result = sync_down(&local_dir, &test_dir.join("remoteconfig")); assert!(result.is_err()); cleanup_test_dir(&test_dir); } #[test] fn test_it_should_fail_if_remote_is_invalid() { let test_dir = setup_test_dir(); let bad_file_path = &test_dir.join("BADFILE OH NO"); let invalid_bytes = vec![0xFF, 0xFF]; let mut bad_file = File::create(&bad_file_path).expect("Could not create bad file."); bad_file .write_all(&invalid_bytes) .expect("Could not write bad bytes to bad file."); assert_eq!(read_remote(&bad_file_path), None); cleanup_test_dir(&test_dir); } }