diff --git a/README.md b/README.md index e63b585..0aca5bd 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Command line interface to manipulate ProtonLights projects. - `init `: Init empty project - `new-user `: Add user from public key - `new-sequence `: Init a sequence +- `remove-sequence `: Removes a sequence and deletes its files - `id-user `: Identify user by ssh key (public key in repo) - `list-permissions`: Get list of all available permissions - `set-permission (add | remove) []`: Change user permissions diff --git a/src/error.rs b/src/error.rs index 834d276..dba56b7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,6 +22,7 @@ pub enum Error { MusicFileNotFound(String), UnsupportedFileType(String), UserNotFound, + SequenceNotFound(String), UnauthorizedAction, TodoErr, } @@ -45,6 +46,7 @@ impl error::Error for Error { Error::MusicFileNotFound(_) => "Music file not found", Error::UnsupportedFileType(_) => "Unsupported file type", Error::UserNotFound => "User not found", + Error::SequenceNotFound(_) => "Sequence not found", Error::UnauthorizedAction => "Unauthorized action", Error::TodoErr => "Todo", } @@ -68,6 +70,7 @@ impl error::Error for Error { Error::MusicFileNotFound(_) => None, Error::UnsupportedFileType(_) => None, Error::UserNotFound => None, + Error::SequenceNotFound(_) => None, Error::UnauthorizedAction => None, Error::TodoErr => None, } @@ -107,6 +110,8 @@ impl fmt::Display for Error { Error::UnsupportedFileType(ref file_type) => write!(f, "Unsupported file type: {}", file_type), Error::UserNotFound => write!(f, "User not found"), + Error::SequenceNotFound(ref name) => write!(f, + "Sequence not found: '{}'", name), Error::UnauthorizedAction => write!(f, "Unauthorized action"), Error::TodoErr => write!(f, "TodoErr"), } diff --git a/src/main.rs b/src/main.rs index cfbc207..1663be5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ Usage: ./proton init ./proton new-user ./proton new-sequence + ./proton remove-sequence ./proton id-user ./proton list-permissions ./proton set-permission (add | remove) [] @@ -53,6 +54,7 @@ fn main() { "new-user" => run_new_user, "id-user" => run_id_user, "new-sequence" => run_new_sequence, + "remove-sequence" => run_remove_sequence, "list-permissions" => run_list_permissions, "set-permission" => run_set_permission, _ => panic!("Invalid first argument"), @@ -100,6 +102,13 @@ fn run_new_sequence(args: Args) -> Result<(), Error> { proton_cli::new_sequence(&admin_key_path, &name, &music_file_path) } +fn run_remove_sequence(args: Args) -> Result<(), Error> { + let admin_key = args.arg_admin_key.unwrap(); + let admin_key_path = Path::new(&admin_key); + let name = args.arg_name.unwrap(); + proton_cli::remove_sequence(&admin_key_path, &name) +} + #[allow(unused_variables)] fn run_list_permissions(args: Args) -> Result<(), Error> { let permissions = proton_cli::get_permissions(); @@ -117,6 +126,3 @@ fn run_set_permission(args: Args) -> Result<(), Error> { proton_cli::set_permission(&auth_user, added, &username, permission, target) } - - - diff --git a/src/project_types/project.rs b/src/project_types/project.rs index 8c4cb3e..501efca 100644 --- a/src/project_types/project.rs +++ b/src/project_types/project.rs @@ -137,6 +137,17 @@ impl Project { Ok(new_project) } + pub fn remove_sequence(&self, name: &str) -> Result { + let mut new_project = self.clone(); + for i in 0..new_project.sequences.len() { + if new_project.sequences[i].name == name { + new_project.sequences.remove(i); + return Ok(new_project); + } + } + Err(Error::SequenceNotFound(name.to_string())) + } + /// Changes a user's permissions pub fn set_user_permission( &mut self, diff --git a/src/sequence.rs b/src/sequence.rs index 74a86ee..1d055f9 100644 --- a/src/sequence.rs +++ b/src/sequence.rs @@ -31,7 +31,7 @@ pub fn new_sequence>( try!(validate_file_type(&music_file_path)); // Make the name of the sequence's directory - let mut sequence_dir = String::from("seq_"); + let mut sequence_dir = "seq_".to_owned(); sequence_dir.push_str(&name); let sequence_dir = sequence_dir; @@ -77,6 +77,42 @@ pub fn new_sequence>( .map(|_| ()) } +pub fn remove_sequence>(admin_key_path: P, name: &str) -> Result<(), Error> { + + // Check that the admin has sufficient privileges + try!(utils::validate_admin(&admin_key_path)); + + // Make sure the name is valid (needed since it will be used in a file path) + try!(validate_seq_name(name)); + + // Make the name of the sequence's directory + let mut sequence_dir = "seq_".to_owned(); + sequence_dir.push_str(&name); + let sequence_dir = sequence_dir; + + // Remove sequence from project + let project = try!(utils::read_protonfile(None::

)); + let new_project = try!(project.remove_sequence(name)); + + // Remove sequence's directory + let sequence_dir_path = Path::new(&sequence_dir); + if sequence_dir_path.exists() && sequence_dir_path.is_dir() { + let _ = fs::remove_dir_all(&sequence_dir_path) + .expect("Error removing sequence directory"); + } + + // Save project + try!(utils::write_protonfile(&new_project, None::

)); + + // Commit changes + let signature = Signature::now("Proton Lights", "proton@teslaworks.net").unwrap(); + let msg = format!("Removing sequence '{}'", name); + let repo_path: Option

= None; + + utils::commit_all(repo_path, &signature, &msg) + .map(|_| ()) +} + /// Check that the music file is a valid format /// Full list of supported formats can be found at /// http://www.rust-sfml.org/doc/rsfml/audio/struct.Music.html diff --git a/tests/common/setup.rs b/tests/common/setup.rs index b903317..bb28b4e 100644 --- a/tests/common/setup.rs +++ b/tests/common/setup.rs @@ -1,7 +1,7 @@ extern crate tempdir; use std::env; -use std::path::Path; +use std::path::{Path, PathBuf}; use self::tempdir::TempDir; @@ -68,5 +68,10 @@ pub fn try_make_sequence(admin_key_path: &Path, name: &str, music_file: &str) { let found_sequence = project.find_sequence_by_name(name); assert!(found_sequence.is_some()); - assert!(found_sequence.unwrap().num_sections == 1); + let seq = found_sequence.unwrap(); + assert!(seq.num_sections == 1); + let mut seq_dir_path = PathBuf::from(&seq.directory_name); + assert!(seq_dir_path.exists()); + seq_dir_path.push(&seq.music_file_name); + assert!(seq_dir_path.exists()); } diff --git a/tests/remove_sequence.rs b/tests/remove_sequence.rs new file mode 100644 index 0000000..e44a7dc --- /dev/null +++ b/tests/remove_sequence.rs @@ -0,0 +1,99 @@ +extern crate proton_cli; +extern crate tempdir; +extern crate git2; + +mod common; + +use std::path::{Path, PathBuf}; + +use common::setup; +use common::rsa_keys::TestKey; + + +#[test] +fn works_with_valid_key_and_name() { + let root = setup::setup_init_cd(); + let root_key_path = common::make_key_file(&root.path(), "root.pem", TestKey::RootKeyPem); + + setup::try_make_sequence(&root_key_path.as_path(), "asdf", "Dissonance.ogg"); + + proton_cli::remove_sequence(&root_key_path.as_path(), "asdf") + .expect("Error removing sequence"); + + let project = proton_cli::utils::read_protonfile(None::<&Path>) + .expect("Error reading project from file"); + + let found_sequence = project.find_sequence_by_name("asdf"); + + assert!(found_sequence.is_none()); + + let mut sequence_dir_path = PathBuf::from(root.path()); + sequence_dir_path.push("seq_asdf"); + assert!(!sequence_dir_path.exists()); + + common::assert_repo_no_modified_files(&root.path()); +} + +#[test] +#[should_panic(expected = "Error removing sequence: Ssl")] +fn fails_with_invalid_admin_key() { + let root = setup::setup_init_cd(); + let root_key_path = common::make_key_file(&root.path(), "root.pem", TestKey::RootKeyPem); + let root_key_bad_path = common::make_key_file(&root.path(), "root_bad.pub", TestKey::RootKeyPub); + + setup::try_make_sequence(&root_key_path.as_path(), "asdf", "Dissonance.ogg"); + proton_cli::remove_sequence(&root_key_bad_path.as_path(), "asdf") + .expect("Error removing sequence"); +} + +#[test] +#[should_panic(expected = "Error removing sequence: Io")] +fn fails_with_nonexistent_admin_key() { + let root = setup::setup_init_cd(); + let root_key_path = common::make_key_file(&root.path(), "root.pem", TestKey::RootKeyPem); + let root_key_bad_path = PathBuf::from("nonexistent"); + + setup::try_make_sequence(&root_key_path.as_path(), "asdf", "Dissonance.ogg"); + proton_cli::remove_sequence(&root_key_bad_path.as_path(), "asdf") + .expect("Error removing sequence"); +} + +#[test] +#[should_panic(expected = "Error removing sequence: UnauthorizedAction")] +fn fails_with_unauthorized_admin_key() { + let root = setup::setup_init_cd(); + let root_key_path = common::make_key_file(&root.path(), "root.pem", TestKey::RootKeyPem); + let normal_key_pub_path = common::make_key_file(&root.path(), "normal.pub", TestKey::GoodKeyPub); + let normal_key_priv_path = common::make_key_file(&root.path(), "normal.pem", TestKey::GoodKeyPem); + let _ = proton_cli::new_user( + &root_key_path.as_path(), + &normal_key_pub_path.as_path(), + "normal_user" + ).expect("Error creating user"); + + setup::try_make_sequence(&root_key_path.as_path(), "asdf", "Dissonance.ogg"); + proton_cli::remove_sequence(&normal_key_priv_path.as_path(), "asdf") + .expect("Error removing sequence"); +} + +#[test] +#[should_panic(expected = "Error removing sequence: SequenceNotFound")] +fn fails_with_nonexistent_sequence_name() { + let root = setup::setup_init_cd(); + let root_key_path = common::make_key_file(&root.path(), "root.pem", TestKey::RootKeyPem); + + setup::try_make_sequence(&root_key_path.as_path(), "asdf", "Dissonance.ogg"); + proton_cli::remove_sequence(&root_key_path.as_path(), "a") + .expect("Error removing sequence"); +} + +#[test] +#[should_panic(expected = "Error removing sequence: InvalidSequenceName")] +fn fails_with_bad_sequence_name() { + let root = setup::setup_init_cd(); + let root_key_path = common::make_key_file(&root.path(), "root.pem", TestKey::RootKeyPem); + + setup::try_make_sequence(&root_key_path.as_path(), "asdf", "Dissonance.ogg"); + proton_cli::remove_sequence(&root_key_path.as_path(), "as df") + .expect("Error removing sequence"); +}