diff --git a/README.md b/README.md index cefe4eb..ef1c072 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,11 @@ Command line interface to manipulate ProtonLights projects. - `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 user's permissions -- `set-permission (add | remove) []`: Change user permissions -- `resection-sequence [TODO]`: (Re-)Section a sequence - - On init, section as section1. - - Number each section, and don't delete. - - Use patch to copy changes. - - Use git --find-renames=100%? +- `set-permission (add | remove) Administrate`: Set admin permission +- `set-permission (add | remove) EditSeq `: Set permission to edit a sequence +- `set-permission (add | remove) EditSeqSec `: Set permission to edit a sequence section +- `resection-sequence `: (Re-)Section a sequence + Permissions include: - edit sequence - edit sequence section diff --git a/src/error.rs b/src/error.rs index dba56b7..97f7426 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,7 +15,10 @@ pub enum Error { InvalidPublicKey(String), InvalidFileName, InvalidSequenceName(String), - InvalidPermissionTarget, + InvalidSequenceSection(u32), + InvalidPermissionName(String), + InvalidFrameDuration(u32), + InvalidNumSequenceSections(u32), LoadProjectError, DuplicateUser(String, String), DuplicateSequence(String), @@ -23,6 +26,7 @@ pub enum Error { UnsupportedFileType(String), UserNotFound, SequenceNotFound(String), + SequenceSectionNotFound(String), UnauthorizedAction, TodoErr, } @@ -39,7 +43,10 @@ impl error::Error for Error { Error::InvalidPublicKey(_) => "Invalid public key", Error::InvalidFileName => "Invalid file name", Error::InvalidSequenceName(_) => "Invalid sequence name", - Error::InvalidPermissionTarget => "Invalid permission target", + Error::InvalidSequenceSection(_) => "Invalid sequence section", + Error::InvalidPermissionName(_) => "Invalid permission name", + Error::InvalidFrameDuration(_) => "Invalid frame duration", + Error::InvalidNumSequenceSections(_) => "Invalid number of sequence sections", Error::LoadProjectError => "Loading project failed", Error::DuplicateUser(_, _) => "User already exists", Error::DuplicateSequence(_) => "Sequence already exists", @@ -47,6 +54,7 @@ impl error::Error for Error { Error::UnsupportedFileType(_) => "Unsupported file type", Error::UserNotFound => "User not found", Error::SequenceNotFound(_) => "Sequence not found", + Error::SequenceSectionNotFound(_) => "Sequence section not found", Error::UnauthorizedAction => "Unauthorized action", Error::TodoErr => "Todo", } @@ -63,7 +71,10 @@ impl error::Error for Error { Error::InvalidPublicKey(_) => None, Error::InvalidFileName => None, Error::InvalidSequenceName(_) => None, - Error::InvalidPermissionTarget => None, + Error::InvalidSequenceSection(_) => None, + Error::InvalidPermissionName(_) => None, + Error::InvalidFrameDuration(_) => None, + Error::InvalidNumSequenceSections(_) => None, Error::LoadProjectError => None, Error::DuplicateUser(_, _) => None, Error::DuplicateSequence(_) => None, @@ -71,6 +82,7 @@ impl error::Error for Error { Error::UnsupportedFileType(_) => None, Error::UserNotFound => None, Error::SequenceNotFound(_) => None, + Error::SequenceSectionNotFound(_) => None, Error::UnauthorizedAction => None, Error::TodoErr => None, } @@ -98,8 +110,14 @@ impl fmt::Display for Error { "File name provided is invalid and cannot be retrieved"), Error::InvalidSequenceName(ref seq_name) => write!(f, "Sequence name had invalid characters: {}", seq_name), - Error::InvalidPermissionTarget => write!(f, - "Invalid permission target provided"), + Error::InvalidSequenceSection(ref section) => write!(f, + "Invalid sequence section: {}", section), + Error::InvalidPermissionName(ref name) => write!(f, + "Invalid permission name provided: {}", name), + Error::InvalidFrameDuration(ref duration) => write!(f, + "Invalid frame duration: {}", duration), + Error::InvalidNumSequenceSections(ref num_sections) => write!(f, + "Invalid number of sequence sections: {}", num_sections), Error::LoadProjectError => write!(f, "Loading project failed"), Error::DuplicateUser(ref key, ref user) => write!(f, "Duplicate user '{}' or key '{}'", user, key), @@ -112,6 +130,8 @@ impl fmt::Display for Error { Error::UserNotFound => write!(f, "User not found"), Error::SequenceNotFound(ref name) => write!(f, "Sequence not found: '{}'", name), + Error::SequenceSectionNotFound(ref path) => write!(f, + "Sequence section not found: '{}'", path), Error::UnauthorizedAction => write!(f, "Unauthorized action"), Error::TodoErr => write!(f, "TodoErr"), } diff --git a/src/main.rs b/src/main.rs index 4b8c2fd..60f442e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,6 @@ use rustc_serialize::json; use docopt::Docopt; use proton_cli::error::Error; -use proton_cli::project_types::PermissionEnum; use proton_cli::utils; @@ -22,9 +21,12 @@ Usage: ./proton remove-user ./proton new-sequence ./proton remove-sequence + ./proton resection-sequence ./proton id-user ./proton list-permissions - ./proton set-permission (add | remove) [] + ./proton set-permission (add | remove) Administrate + ./proton set-permission (add | remove) EditSeq + ./proton set-permission (add | remove) EditSeqSec ./proton (-h | --help) Options: @@ -40,8 +42,9 @@ struct Args { arg_admin_key: Option, arg_name: Option, arg_music_file: Option, - arg_permission: Option, - arg_target: Option, + arg_target_sequence: Option, + arg_target_section: Option, + arg_num_sections: Option, } fn main() { @@ -58,6 +61,7 @@ fn main() { "id-user" => run_id_user, "new-sequence" => run_new_sequence, "remove-sequence" => run_remove_sequence, + "resection-sequence" => run_resection_sequence, "list-permissions" => run_list_permissions, "set-permission" => run_set_permission, _ => panic!("Invalid first argument"), @@ -119,6 +123,14 @@ fn run_remove_sequence(args: Args) -> Result<(), Error> { proton_cli::remove_sequence(&admin_key_path, &name) } +fn run_resection_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(); + let num_sections = args.arg_num_sections.unwrap(); + proton_cli::resection_sequence(&admin_key_path, &name, num_sections) +} + fn run_list_permissions(args: Args) -> Result<(), Error> { let private_key = args.arg_private_key; proton_cli::get_permissions(&private_key.unwrap()) @@ -131,8 +143,15 @@ fn run_set_permission(args: Args) -> Result<(), Error> { let added = env::args().nth(3).unwrap() == "add"; let username = args.arg_name.unwrap(); - let permission = args.arg_permission.unwrap(); - let target = args.arg_target; - - proton_cli::set_permission(&auth_user, added, &username, permission, target) + let permission_name = env::args().nth(5).unwrap(); + let target_sequence = args.arg_target_sequence; + let target_section = args.arg_target_section; + + proton_cli::set_permission( + &auth_user, + added, + &username, + &permission_name, + target_sequence, + target_section) } diff --git a/src/permissions.rs b/src/permissions.rs index edc5a06..4d5c880 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -1,10 +1,11 @@ use std::path::Path; +use std::ascii::AsciiExt; use git2::Signature; use error::Error; -use project_types::{User, Permission, PermissionEnum}; +use project_types::{User, Permission}; use utils; use user; @@ -13,8 +14,9 @@ pub fn set_permission( auth_user: &User, add: bool, target_username: &str, - permission: PermissionEnum, - target: Option + permission_name: &str, + target_sequence: Option, + target_section: Option ) -> Result<(), Error> { // Only admins (those with GrantPerm permission) can change permissions @@ -23,12 +25,12 @@ pub fn set_permission( } // Make sure root isn't losing admin privileges - if target_username == "root" && !add && permission == PermissionEnum::Administrate { + if target_username == "root" && !add && permission_name.eq_ignore_ascii_case("Administrate") { return Err(Error::UnauthorizedAction); } // Validate and create permission - let perm = try!(Permission::new(permission, target)); + let perm = try!(Permission::new(permission_name, target_sequence, target_section)); // Get project that will be modified let mut project = try!(utils::read_protonfile(None::<&Path>)); diff --git a/src/project_types/mod.rs b/src/project_types/mod.rs index 9cd9dfa..bc89d6b 100644 --- a/src/project_types/mod.rs +++ b/src/project_types/mod.rs @@ -5,7 +5,7 @@ mod sequence; mod sequence_section; mod user; -pub use self::permissions::{Permission, PermissionEnum}; +pub use self::permissions::Permission; pub use self::project::Project; pub use self::sequence::Sequence; pub use self::sequence_section::SequenceSection; diff --git a/src/project_types/permissions.rs b/src/project_types/permissions.rs index 24ae5b5..fc993c6 100644 --- a/src/project_types/permissions.rs +++ b/src/project_types/permissions.rs @@ -1,94 +1,55 @@ - use std::path::Path; +use std::ascii::AsciiExt; use error::Error; use utils; #[derive(Clone, Debug, Eq, PartialEq, RustcEncodable, RustcDecodable)] -pub enum PermissionEnum { +pub enum Permission { Administrate, - EditSeq, - EditSeqSec, -} - -#[derive(Clone, Debug, Eq, PartialEq, RustcEncodable, RustcDecodable)] -pub struct Permission { - pub which: PermissionEnum, - pub target: Option, + EditSeq(String), + EditSeqSec(String, u32), } impl Permission { - /// Creates a new Permission, joining a permission type with a target - /// Returns an error if the target is invalid - pub fn new(which_enum: PermissionEnum, t: Option) -> Result { - // Make sure the target is valid for the given permission type - try!(Permission::validate_permission(&which_enum, &t)); - - // Create permission if valid - Ok(Permission { - which: which_enum, - target: t, - }) - } - - /// Validates the target for the given permission type - /// Returns error if invalid target - fn validate_permission(permission: &PermissionEnum, target: &Option) -> Result<(), Error> { - - let valid = match permission { - &PermissionEnum::Administrate => { - target == &None:: - }, - &PermissionEnum::EditSeq => { - if target.is_none() { - false + /// Creates a new Permission + /// Assumes target options are from Docopt, and are therefore safe to unwrap + /// if the permission name is correct + pub fn new( + perm_name: &str, + target_sequence: Option, + target_section: Option + ) -> Result { + + match perm_name.to_ascii_lowercase().as_ref() { + "administrate" => Ok(Permission::Administrate), + "editseq" => { + let sequence_name = target_sequence.unwrap(); + let project = try!(utils::read_protonfile(None::<&Path>)); + if project.find_sequence_by_name(&sequence_name).is_none() { + Err(Error::SequenceNotFound(sequence_name)) } else { - let seq_name = target.to_owned().unwrap(); - let project = try!(utils::read_protonfile(None::<&Path>)); - project.find_sequence_by_name(&seq_name).is_some() + Ok(Permission::EditSeq(sequence_name)) } }, - &PermissionEnum::EditSeqSec => { - if target.is_none() { - false + "editseqsec" => { + let sequence_name = target_sequence.unwrap(); + let section_idx = target_section.unwrap(); + let project = try!(utils::read_protonfile(None::<&Path>)); + let sequence_opt = project.find_sequence_by_name(&sequence_name); + if sequence_opt.is_none() { + Err(Error::SequenceNotFound(sequence_name)) } else { - let target_str = target.to_owned().unwrap(); - let targets: Vec<&str> = target_str.split(",").collect(); - if targets.len() != 2 { - println!("EditSeqSec target must be of the form \"name,section\""); - false + let sequence = sequence_opt.unwrap().to_owned(); + if !sequence.section_in_range(section_idx) { + Err(Error::InvalidSequenceSection(section_idx)) } else { - let seq_name = targets[0]; - let section_num_str = targets[1]; - let section_num = match section_num_str.parse::() { - Ok(n) => n, - Err(_) => return Err(Error::InvalidPermissionTarget), - }; - let project = try!(utils::read_protonfile(None::<&Path>)); - match project.find_sequence_by_name(&seq_name) { - Some(seq) => { - let in_range = section_num > 0 && section_num <= seq.num_sections; - if !in_range { - println!("EditSeqSec target must be of the form \"name,section\""); - } - in_range - }, - None => { - println!("EditSeqSec target must be of the form \"name,section\""); - false - }, - } - + Ok(Permission::EditSeqSec(sequence_name, section_idx)) } } }, - }; - - if valid { - Ok(()) - } else { - Err(Error::InvalidPermissionTarget) + _ => Err(Error::InvalidPermissionName(perm_name.to_owned())) } } } diff --git a/src/project_types/project.rs b/src/project_types/project.rs index 0d3d3b2..fdd2291 100644 --- a/src/project_types/project.rs +++ b/src/project_types/project.rs @@ -1,5 +1,5 @@ -use project_types::{Sequence, User, Permission, PermissionEnum}; +use project_types::{Sequence, User, Permission}; use error::Error; @@ -17,7 +17,7 @@ impl Project { pub fn empty(root_pub_key: &str) -> Result { let mut root = try!(User::new("root", &root_pub_key)); - let root_permission = try!(Permission::new(PermissionEnum::Administrate, None::)); + let root_permission = Permission::Administrate; root.add_permission(root_permission); Ok(Project { @@ -30,76 +30,55 @@ impl Project { /// Finds a sequence by its name /// Returns the sequence if found, else None pub fn find_sequence_by_name(&self, name: &str) -> Option<&Sequence> { - for s in &self.sequences { - if s.name == name { - return Some(s); - } - } - - None::<&Sequence> + self.sequences.iter().find(|seq| seq.name == name) } /// Finds a user with the given public key /// Returns the user if found, else None fn find_user_by_public_key(&self, pub_key: &str) -> Option<&User> { - for u in &self.users { - if u.public_key == pub_key { - return Some(u); - } - } - None::<&User> + self.users.iter().find(|user| user.public_key == pub_key) } /// Finds a user with the given name /// Returns the user if found, else None // TODO: make private? pub fn find_user_by_name(&self, name: &str) -> Option<&User> { - for u in &self.users { - if u.name == name { - return Some(u); - } - } - None::<&User> + self.users.iter().find(|user| user.name == name) } /// Finds a user in the users vector /// Returns true if found, else false pub fn user_exists(&self, user: &User) -> bool { - for u in &self.users { - if user == u { - return true; - } - } - return false; + self.users.iter().find(|u| u == &user).is_some() } /// Adds a user to the project /// Returns a new project with the user added pub fn add_user(&self, name: &str, pub_key: &str) -> Result { - - let user = try!(User::new(name, pub_key)); + let mut new_project = self.clone(); + let user = try!(User::new(name, pub_key)); + if self.find_user_by_name(name).is_some() || self.find_user_by_public_key(pub_key).is_some() { - Err(Error::DuplicateUser(pub_key.to_string(), name.to_string())) + return Err(Error::DuplicateUser(pub_key.to_owned(), name.to_owned())); } else { - let mut new_project = self.clone(); new_project.users.push(user); - Ok(new_project) } + + Ok(new_project) } /// Removes a user from the project /// Returns a new project with the user removed pub fn remove_user(&self, name: &str) -> Result { let mut new_project = self.clone(); - for i in 0..new_project.users.len() { - if new_project.users[i].name == name { + for (i, user) in self.users.iter().enumerate() { + if user.name == name { new_project.users.remove(i); return Ok(new_project); } } - Err(Error::UserNotFound) } @@ -123,11 +102,11 @@ impl Project { )); // Check if duplicate - for s in &self.sequences { - if s.name == name - || s.directory_name == directory_name { - return Err(Error::DuplicateSequence(name.to_string())); - } + let duplicate_sequence = self.sequences + .iter() + .find(|seq| seq.name == name || seq.directory_name == directory_name); + if duplicate_sequence.is_some() { + return Err(Error::DuplicateSequence(name.to_owned())); } let mut new_project = self.clone(); @@ -137,13 +116,22 @@ impl 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 { + for (i, seq) in self.sequences.iter().enumerate() { + if seq.name == name { new_project.sequences.remove(i); return Ok(new_project); } } - Err(Error::SequenceNotFound(name.to_string())) + Err(Error::SequenceNotFound(name.to_owned())) + } + + pub fn resection_sequence(&self, name: &str, num_sections: u32) -> Result { + let mut new_project = self.clone(); + match new_project.sequences.iter_mut().find(|seq| seq.name == name) { + None => return Err(Error::SequenceNotFound(name.to_owned())), + Some(sequence) => try!(sequence.resection(num_sections)), + } + Ok(new_project) } /// Changes a user's permissions @@ -154,20 +142,16 @@ impl Project { add: bool ) -> Result<(), Error> { - for mut u in &mut self.users { - if u.name == name { + match self.users.iter_mut().find(|u| u.name == name) { + None => Err(Error::UserNotFound), + Some(user) => { if add { - u.add_permission(perm); + user.add_permission(perm.clone()) } else { - u.remove_permission(perm); + user.remove_permission(perm.clone()) } - - return Ok(()); + Ok(()) } } - - Err(Error::UserNotFound) } - } - diff --git a/src/project_types/sequence.rs b/src/project_types/sequence.rs index 2ba7675..3f1c96b 100644 --- a/src/project_types/sequence.rs +++ b/src/project_types/sequence.rs @@ -1,6 +1,11 @@ +use std::path::{Path, PathBuf}; +use std::cmp; + +use git2::Signature; use error::Error; use project_types::SequenceSection; +use utils; #[derive(Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)] @@ -26,42 +31,179 @@ impl Sequence { ) -> Result { // Defaults let frame_dur_ms = frame_duration_ms.unwrap_or(50); + if frame_dur_ms < 25 { + return Err(Error::InvalidFrameDuration(frame_dur_ms)); + } let num_sects = num_sections.unwrap_or(1); + if num_sects < 1 { + return Err(Error::InvalidNumSequenceSections(num_sects)); + } // Create sequence - let sequence = Sequence { + let mut sequence = Sequence { name: name.to_string(), directory_name: seq_directory_name.to_string(), music_file_name: music_file_name.to_string(), music_duration_sec: music_duration_sec, frame_duration_ms: frame_dur_ms, - num_sections: num_sects, + num_sections: 0 // Updated in resection }; // Section sequence - try!(sequence.resection()); + try!(sequence.resection(num_sects)); Ok(sequence) } - /// Resection a sequence - pub fn resection(&self) -> Result<(), Error> { - if self.num_sections == 1 { - let num_frames_f32: f32 = (self.music_duration_sec as f32 * 1000_f32) / self.frame_duration_ms as f32; - let num_frames = num_frames_f32.ceil() as u32; - let num_channels = 1; // TODO: change when add layout - let sequence_section = SequenceSection { - seq_name: self.name.to_string(), - index: 1, - num_frames: num_frames, - editor: None, - data: vec![vec![0; num_frames as usize]; num_channels], - }; - sequence_section.write_to_file(&self.directory_name) + /// Reads in a sequence section from its file + pub fn get_section(&self, section: u32) -> Result { + if self.section_in_range(section) { + let section_path = self.get_section_path(section); + utils::read_sequence_section(§ion_path) } else { - Err(Error::TodoErr) + Err(Error::InvalidSequenceSection(section)) } } + /// Checks to see if the given section is valid/in range + pub fn section_in_range(&self, section: u32) -> bool { + section < self.num_sections + } + + /// Reads in all sequence sections from their files into a vec + fn get_all_sections(&self) -> Result, Error> { + (0..self.num_sections).map(|i| self.get_section(i)).collect() + } + + /// Generate and return a sequence section with sane defaults + /// Also writes it to file, so it can be read later + fn create_default_section( + &self, + num_channels: u32, + num_frames: u32 + ) -> Result { + self.create_section( + 1, + num_frames, + vec![vec![0; num_frames as usize]; num_channels as usize] + ) + } + + /// Creates a sequence section, writes it to a file, and returns it + fn create_section( + &self, + index: u32, + num_frames: u32, + data: Vec> + ) -> Result { + let section_path = self.get_section_path(index); + let section = SequenceSection { + seq_name: self.name.to_string(), + index: index, + path: section_path.clone(), + num_frames: num_frames, + data: data + }; + let _ = try!(section.write_to_file()); + Ok(section) + } + + /// Sets the data for a sequence section + /// Writes the changes and commits + pub fn set_section_data(&self, index: u32, data: Vec>) -> Result<(), Error> { + let mut seq_sec = try!(self.get_section(index)); + seq_sec.data = data; + let _ = try!(seq_sec.write_to_file()); + + let signature = Signature::now("Proton Lights", "proton@teslaworks.net").unwrap(); + let msg = format!("Setting section data for sequence '{}', section {}", self.name, index); + let repo_path: Option<&Path> = None; + + utils::commit_all(repo_path, &signature, &msg) + .map(|_| ()) + } + + /// Resection a sequence + pub fn resection(&mut self, num_sections: u32) -> Result<(), Error> { + // No need to resection if same number of sections + if self.num_sections == num_sections { + return Ok(()); + } + // Cannot resection into 0 sections + if num_sections == 0 { + return Err(Error::InvalidSequenceSection(num_sections)); + } + + let num_channels: u32 = 3; // TODO change when add layout + let music_duration_ms = self.music_duration_sec as f32 * 1000_f32; + let num_frames_f32: f32 = music_duration_ms as f32 / self.frame_duration_ms as f32; + let num_frames = num_frames_f32.ceil() as u32; + let num_frames_per_section_f32 = num_frames_f32 / num_sections as f32; + let num_frames_per_section = num_frames_per_section_f32.ceil() as u32; + + // Turn into one section if not already, then split up + let mut sections = try!(self.get_all_sections()); + // If no sections (first run?), initialize with default + if sections.len() < 1 { + let default_section = try!(self.create_default_section(num_channels, num_frames)); + sections = vec![default_section]; + } + // Function to combine data vectors in the fold + fn combine_data(mut accumulated: Vec>, sec: &SequenceSection) -> Vec> { + let mut sec_data = sec.data.clone(); + let min_num_ch = cmp::min(accumulated.len(), sec_data.len()); + for channel in 0..min_num_ch { + accumulated[channel].append(&mut sec_data[channel]); + } + accumulated + } + // Fold together all data vectors + let all_data = sections.iter() + .fold(vec![vec![]; num_channels as usize], combine_data); + // Break single chunk into sections, one channel at a time + let mut sectioned_data = vec![vec![vec![]; num_channels as usize]; num_sections as usize]; + for (channel_idx, channel_data) in all_data.iter().enumerate() { + let mut chunked_data = channel_data.chunks(num_frames_per_section as usize); + for section_idx in 0..num_sections { + let new_data = chunked_data.next() + .expect("Miscalculation when chunking sequence section data"); + sectioned_data[section_idx as usize][channel_idx as usize] = new_data.to_vec(); + } + } + + // Create SequenceSections out of the chunks + for (sec_idx, sec_data) in sectioned_data.iter().enumerate() { + // This is safe, since there is always at least one channel + let sec_frames = sec_data[0].len(); + + let _ = self.create_section( + sec_idx as u32, + sec_frames as u32, + sec_data.to_owned()); + } + + Ok(self.num_sections = num_sections) + } + + /// Get the path to this specific section, starting with the sequence directory + /// E.g. sequence/sequence_section1.json + /// Assumes the current directory is the project directory + /// Returns as string + fn get_section_path( + &self, + index: u32 + ) -> String { + + let mut filename = String::new(); + filename.push_str(&self.name); + filename.push_str("_section"); + filename.push_str(&index.to_string()); + + let mut section_path = PathBuf::from(&self.directory_name); + section_path.push(&filename); + + section_path.to_str().expect("Path is invalid unicode").to_owned() + } + } diff --git a/src/project_types/sequence_section.rs b/src/project_types/sequence_section.rs index dedc91a..6dce498 100644 --- a/src/project_types/sequence_section.rs +++ b/src/project_types/sequence_section.rs @@ -6,43 +6,27 @@ use std::path::PathBuf; use rustc_serialize::json; use error::Error; -use project_types::User; #[derive(Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)] pub struct SequenceSection { pub seq_name: String, - pub index: u32, + pub index: u32, // Starts at 1 + pub path: String, pub num_frames: u32, - pub editor: Option, pub data: Vec>, // Row is channel, column is frame } impl SequenceSection { /// Write the sequence section to a file - pub fn write_to_file(&self, seq_directory_name: &str) -> Result<(), Error> { + pub fn write_to_file(&self) -> Result<(), Error> { let pretty_json = json::as_pretty_json(&self); - let section_path = &self.get_path(&seq_directory_name); + let section_path = PathBuf::from(&self.path); File::create(§ion_path) .and_then(|mut section_file| write!(section_file, "{}\n", pretty_json)) .map_err(Error::Io) } - /// Get the path to this specific section, starting with the sequence directory - /// E.g. sequence/sequence_section1.json - /// Assumes the current directory is the project directory - fn get_path(&self, directory_name: &str) -> PathBuf { - - let mut filename = String::new(); - filename.push_str(&self.seq_name); - filename.push_str("_section"); - filename.push_str(&self.index.to_string()); - - let mut section_path = PathBuf::from(&directory_name); - section_path.push(&filename); - - section_path - } } diff --git a/src/project_types/user.rs b/src/project_types/user.rs index 76dbef4..ff414df 100644 --- a/src/project_types/user.rs +++ b/src/project_types/user.rs @@ -3,7 +3,7 @@ use std::io::Cursor; use openssl::crypto::rsa::RSA as openssl_RSA; use error::Error; -use project_types::{Permission, PermissionEnum}; +use project_types::Permission; #[derive(Clone, Debug, Eq, RustcEncodable, RustcDecodable)] @@ -75,7 +75,7 @@ impl User { /// Determines whether the user has the Administrate permission pub fn is_admin(&self) -> bool { for p in &self.permissions { - if p.which == PermissionEnum::Administrate { + if p == &Permission::Administrate { return true; } } diff --git a/src/sequence.rs b/src/sequence.rs index d122749..fceb554 100644 --- a/src/sequence.rs +++ b/src/sequence.rs @@ -4,10 +4,12 @@ use std::path::{Path, PathBuf}; use std::fs; use git2::Signature; -use sfml::audio::Music; use regex::Regex; +use sfml::audio::Music; use error::Error; +use project_types::Permission; +use user; use utils; @@ -77,6 +79,8 @@ pub fn new_sequence>( .map(|_| ()) } +/// Removes the sequence with the given name from the project +/// and deletes its files pub fn remove_sequence>(admin_key_path: P, name: &str) -> Result<(), Error> { // Check that the admin has sufficient privileges @@ -113,6 +117,39 @@ pub fn remove_sequence>(admin_key_path: P, name: &str) -> Result< .map(|_| ()) } +/// Resections an existing sequence with the given name +/// Returns a new sequence with the changes +pub fn resection_sequence>( + admin_key_path: P, + name: &str, + num_sections: u32 +) -> Result<(), Error> { + // Check that the admin has sufficient privileges + let admin_user = try!(user::id_user(admin_key_path)); + let perm = try!(Permission::new("EditSeq", Some(name.to_owned()), None::)); + if !admin_user.has_permission(&perm) { + return Err(Error::UnauthorizedAction); + } + + // Make sure the name is valid (needed since it will be used in a file path) + try!(validate_seq_name(name)); + + // Get project + let project = try!(utils::read_protonfile(None::

)); + + // Resection sequence + let new_project = try!(project.resection_sequence(name, num_sections)); + try!(utils::write_protonfile(&new_project, None::

)); + + // Commit changes + let signature = Signature::now("Proton Lights", "proton@teslaworks.net").unwrap(); + let msg = format!("Resectioning 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 @@ -136,7 +173,7 @@ fn validate_file_type>(music_file_path: P) -> Result<(), Error> { } /// Makes sure the name has only valid characters in it -/// A valid character is upper and lower alpha, numbers, and underscores +/// A valid character is upper and lower alpha, numbers, and underscores` fn validate_seq_name(name: &str) -> Result<(), Error> { let seq_name_regex = Regex::new("^[0-9A-Za-z_]+$").expect("Regex failed to compile"); diff --git a/src/utils.rs b/src/utils.rs index 58c1026..b38e3d5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,7 +6,7 @@ use std::env; use rustc_serialize::json; use git2::{self, Repository, Signature}; -use project_types::{User, Project, Permission, PermissionEnum}; +use project_types::{User, Project, Permission, SequenceSection}; use error::Error; use user; @@ -16,8 +16,7 @@ use user; /// Returns this user if found and has permission, else error pub fn validate_admin>(admin_key_path: P) -> Result { let admin_user = try!(user::id_user(admin_key_path)); - let perm = try!(Permission::new(PermissionEnum::Administrate, None::)); - if !admin_user.has_permission(&perm) { + if !admin_user.has_permission(&Permission::Administrate) { return Err(Error::UnauthorizedAction); } Ok(admin_user) @@ -126,7 +125,6 @@ pub fn create_empty_directory>(dir_path: P) -> Result<(), Error> } /// Reads a Project from a Protonfile. -/// Wraps any errors in proton_cli::Error /// Assumes Protonfile.json resides in the current directory /// unless a path to the Protonfile is given. pub fn read_protonfile>(pf_path: Option

) -> Result { @@ -147,6 +145,16 @@ pub fn write_protonfile>(project: &Project, pf_path: Option

) - .map_err(Error::Io) } +/// Reads and decodes a SequenceSection from a file +pub fn read_sequence_section>(seq_sec_path: P) -> Result { + if !seq_sec_path.as_ref().is_file() { + let seq_sec_path_str = seq_sec_path.as_ref().to_str().expect("Path is invalid unicode"); + return Err(Error::SequenceSectionNotFound(seq_sec_path_str.to_owned())); + } + let sequence_section = try!(file_as_string(&seq_sec_path)); + json::decode(&sequence_section).map_err(Error::JsonDecode) +} + /// Reads a file as a string. /// Wraps Read::read_to_string errors in proton_cli::Error pub fn file_as_string>(path: P) -> Result { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index c222c24..f93d94d 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -6,6 +6,7 @@ extern crate proton_cli; extern crate git2; pub mod rsa_keys; +pub mod sequence_sections; pub mod setup; use std::env; @@ -27,16 +28,27 @@ pub fn make_key_file>( test_key: rsa_keys::TestKey ) -> PathBuf { - let mut key_path = PathBuf::new(); - key_path.push(root_dir); - key_path.push(file_name); - let file_content = rsa_keys::get_test_key(test_key); - File::create(&key_path) - .and_then(|mut file| write!(file, "{}\n", file_content)) - .expect("Error creating key file"); + write_to_file(root_dir, file_name, &file_content) +} + +/// Write a string to a file, replacing it's current contents if it exists +/// Returns the path to the file +fn write_to_file>( + root_dir: P, + file_name: &str, + content: &str +) -> PathBuf { + + let mut file_path = PathBuf::new(); + file_path.push(root_dir); + file_path.push(file_name); + + File::create(&file_path) + .and_then(|mut file| write!(file, "{}\n", content)) + .expect("Error writing to file"); - key_path + file_path } /// Returns the path to a music file in /.../cli/tests/music/ diff --git a/tests/common/sequence_sections.rs b/tests/common/sequence_sections.rs new file mode 100644 index 0000000..2fb9cd5 --- /dev/null +++ b/tests/common/sequence_sections.rs @@ -0,0 +1,50 @@ + +#![allow(dead_code)] + +#[derive(Clone)] +pub enum TestSeqSec { + Good1of1, + Good1of2, + Good2of2, + Good1of3, + Good2of3, + Good3of3, +} + +/// Retrieves a pre-generated sequence section for testing +/// One second file, 50ms frames, 20 frames per row +/// 3 channels +pub fn get_test_seq_sec(key: TestSeqSec) -> Vec> { + match key { + TestSeqSec::Good1of1 => vec![ + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], + vec![1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8], + vec![1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 122, 100, 222, 67, 34, 101, 135] + ], + TestSeqSec::Good1of2 => vec![ + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + vec![1, 2, 4, 8, 16, 32, 64, 128, 1, 2], + vec![1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + ], + TestSeqSec::Good2of2 => vec![ + vec![10, 11, 12, 13, 14, 15, 16, 17, 18, 19], + vec![4, 8, 16, 32, 64, 128, 1, 2, 4, 8], + vec![89, 144, 233, 122, 100, 222, 67, 34, 101, 135] + ], + TestSeqSec::Good1of3 => vec![ + vec![0, 1, 2, 3, 4, 5, 6], + vec![1, 2, 4, 8, 16, 32, 64], + vec![1, 1, 2, 3, 5, 8, 13] + ], + TestSeqSec::Good2of3 => vec![ + vec![7, 8, 9, 10, 11, 12, 13], + vec![128, 1, 2, 4, 8, 16, 32], + vec![21, 34, 55, 89, 144, 233, 122] + ], + TestSeqSec::Good3of3 => vec![ + vec![14, 15, 16, 17, 18, 19], + vec![64, 128, 1, 2, 4, 8], + vec![100, 222, 67, 34, 101, 135] + ], + } +} diff --git a/tests/common/setup.rs b/tests/common/setup.rs index 58fcc0d..ea36f5a 100644 --- a/tests/common/setup.rs +++ b/tests/common/setup.rs @@ -6,7 +6,7 @@ use std::path::{Path, PathBuf}; use self::tempdir::TempDir; use proton_cli; -use proton_cli::project_types::PermissionEnum; +use proton_cli::project_types::{Sequence, User}; use super::rsa_keys::{self, TestKey}; @@ -44,7 +44,7 @@ pub fn try_new_user( user_name: &str, key_name: &str, key: TestKey -) { +) -> User { let user_key_path = super::make_key_file(&root_path, &key_name, key); @@ -54,10 +54,17 @@ pub fn try_new_user( super::assert_user_added(user_key_path.as_path(), &user_name); super::assert_repo_no_modified_files(&root_path); + + let project = proton_cli::utils::read_protonfile(None::<&Path>).expect("Error reading project"); + project.find_user_by_name(&user_name).unwrap().to_owned() } /// Attempts to make a new sequence with the given name and music file -pub fn try_make_sequence(admin_key_path: &Path, name: &str, music_file: &str) { +pub fn try_make_sequence( + admin_key_path: &Path, + name: &str, + music_file: &str +) -> Sequence { let music_file_path = super::get_music_file_path(music_file); let _ = proton_cli::new_sequence(&admin_key_path, &name, &music_file_path.as_path()) @@ -70,11 +77,15 @@ pub fn try_make_sequence(admin_key_path: &Path, name: &str, music_file: &str) { assert!(found_sequence.is_some()); 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()); + + seq.to_owned() } /// Tries to modify a user's permission @@ -86,8 +97,9 @@ pub fn try_set_permission>( auth_private_key_path: P, add: bool, target_username: &str, - permission: PermissionEnum, - target: Option, + permission_name: &str, + target_sequence: Option, + target_section: Option ) { let auth_user = proton_cli::id_user(&auth_private_key_path) .expect("Auth user not found"); @@ -96,8 +108,9 @@ pub fn try_set_permission>( &auth_user, add, &target_username, - permission.to_owned(), - target.to_owned() + permission_name, + target_sequence, + target_section ).expect("Error setting permission"); super::assert_repo_no_modified_files(&root_path); diff --git a/tests/get_permissions.rs b/tests/get_permissions.rs index 0be70ed..524aa34 100644 --- a/tests/get_permissions.rs +++ b/tests/get_permissions.rs @@ -6,7 +6,7 @@ use std::path::Path; use common::setup; use common::rsa_keys::TestKey; -use proton_cli::project_types::PermissionEnum; +use proton_cli::project_types::Permission; #[test] @@ -54,16 +54,15 @@ fn works_with_valid_key_one_permission() { &root_key_path, true, &name, - PermissionEnum::EditSeq, - Some("asdf".to_owned())); + "EditSeq", + Some("asdf".to_owned()), + None::); let permissions = proton_cli::get_permissions(&user_key_path) .expect("Error getting permissions"); assert_eq!(permissions.len(), 1); - assert_eq!(&permissions[0].which, &PermissionEnum::EditSeq); - assert!(&permissions[0].target.is_some()); - assert_eq!(permissions[0].target, Some("asdf".to_owned())); + assert_eq!(permissions[0], Permission::EditSeq("asdf".to_owned())); } #[test] @@ -97,37 +96,35 @@ fn works_with_valid_key_all_permissions() { &root_key_path, true, &name, - PermissionEnum::Administrate, - None::); + "Administrate", + None::, + None::); setup::try_set_permission( &root.path(), &root_key_path, true, &name, - PermissionEnum::EditSeq, - Some("asdf".to_owned())); + "EditSeq", + Some("asdf".to_owned()), + None::); setup::try_set_permission( &root.path(), &root_key_path, true, &name, - PermissionEnum::EditSeqSec, - Some("ghjk,1".to_owned())); + "EditSeqSec", + Some("ghjk".to_owned()), + Some(0)); let permissions = proton_cli::get_permissions(&user_key_path) .expect("Error getting permissions"); assert_eq!(permissions.len(), 3); - assert_eq!(&permissions[0].which, &PermissionEnum::Administrate); - assert!(&permissions[0].target.is_none()); - assert_eq!(&permissions[1].which, &PermissionEnum::EditSeq); - assert!(&permissions[1].target.is_some()); - assert_eq!(permissions[1].target, Some("asdf".to_owned())); - assert_eq!(&permissions[2].which, &PermissionEnum::EditSeqSec); - assert!(&permissions[2].target.is_some()); - assert_eq!(permissions[2].target, Some("ghjk,1".to_owned())); + assert_eq!(permissions[0], Permission::Administrate); + assert_eq!(permissions[1], Permission::EditSeq("asdf".to_owned())); + assert_eq!(permissions[2], Permission::EditSeqSec("ghjk".to_owned(), 0)); } #[test] diff --git a/tests/initialize_project.rs b/tests/initialize_project.rs index 111f043..07862ec 100644 --- a/tests/initialize_project.rs +++ b/tests/initialize_project.rs @@ -10,7 +10,7 @@ use std::path::Path; use git2::Repository; -use proton_cli::project_types::{Project, User, Permission, PermissionEnum}; +use proton_cli::project_types::{Project, User, Permission}; use proton_cli::initialize_project; use proton_cli::utils; @@ -32,9 +32,7 @@ fn assert_admin_created>(root: P, root_pub_key: &str) { .expect("Loading project from file failed"); let mut admin_user = User::new("admin".as_ref(), &root_pub_key) .expect("Error creating admin user for comparison"); - let admin_permission = Permission::new(PermissionEnum::Administrate, None::) - .expect("Error creating default admin permission"); - admin_user.add_permission(admin_permission); + admin_user.add_permission(Permission::Administrate); assert_eq!(project.users.len(), 1); assert_eq!(project.users[0], admin_user); } diff --git a/tests/music/test_1sec.ogg b/tests/music/test_1sec.ogg new file mode 100644 index 0000000..60de864 Binary files /dev/null and b/tests/music/test_1sec.ogg differ diff --git a/tests/new_sequence.rs b/tests/new_sequence.rs index e08a7dd..789dbd9 100644 --- a/tests/new_sequence.rs +++ b/tests/new_sequence.rs @@ -7,7 +7,6 @@ mod common; use std::path::{Path, PathBuf}; use common::setup; -use proton_cli::utils; use common::rsa_keys::TestKey; @@ -15,26 +14,21 @@ use common::rsa_keys::TestKey; fn works_with_valid_path_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(), "New_Sequence", "Dissonance.ogg"); + let sequence = setup::try_make_sequence( + &root_key_path.as_path(), + "New_Sequence", + "Dissonance.ogg"); // Make sure the calculated music duration is correct // and check that the sequence folder is named correctly - match utils::read_protonfile(Some(&root.path())) { - Ok(project) => { - let sequence = &project.sequences[0]; - // Dissonance is 5 min, 4 sec - assert_eq!(sequence.music_duration_sec, 304); - assert_eq!(sequence.directory_name, "seq_New_Sequence"); - - // Make sure section1 was created - let mut section_path = PathBuf::from(&sequence.directory_name); - section_path.push("New_Sequence_section1"); - let section_path = section_path; - assert!(section_path.exists()); + assert_eq!(sequence.music_duration_sec, 304); // Dissonance is 5 min, 4 sec + assert_eq!(sequence.directory_name, "seq_New_Sequence"); - }, - Err(e) => panic!(e.to_string()), - }; + // Make sure section1 was created + let mut section_path = PathBuf::from(&sequence.directory_name); + section_path.push("New_Sequence_section1"); + let section_path = section_path; + assert!(section_path.exists()); // Make sure changes were committed common::assert_repo_no_modified_files(&root.path()); @@ -47,7 +41,7 @@ fn works_with_valid_path_and_name() { fn fails_with_nonexistent_private_key() { let root = setup::setup_init_cd(); let root_key_path = Path::new("nonexistent"); - setup::try_make_sequence(&root_key_path, "New_Sequence", "Dissonance.ogg"); + let _ = setup::try_make_sequence(&root_key_path, "New_Sequence", "Dissonance.ogg"); } #[test] @@ -55,7 +49,7 @@ fn fails_with_nonexistent_private_key() { fn fails_with_no_user_private_key() { let root = setup::setup_init_cd(); let key_path = common::make_key_file(&root.path(), "a.pem", TestKey::GoodKeyPem); - setup::try_make_sequence(&key_path, "New_Sequence", "Dissonance.ogg"); + let _ = setup::try_make_sequence(&key_path, "New_Sequence", "Dissonance.ogg"); } #[test] @@ -66,7 +60,7 @@ fn fails_with_no_admin_private_key() { let key_path = common::make_key_file(&root.path(), "a.pem", TestKey::GoodKeyPem); setup::try_new_user(&root_key_path, &root.path(), "Test user", "a.pub", TestKey::GoodKeyPub); - setup::try_make_sequence(&key_path, "New_Sequence", "Dissonance.ogg"); + let _ = setup::try_make_sequence(&key_path, "New_Sequence", "Dissonance.ogg"); } #[test] @@ -74,7 +68,7 @@ fn fails_with_no_admin_private_key() { fn fails_with_invalid_private_key() { let root = setup::setup_init_cd(); let root_key_path = common::make_key_file(&root.path(), "root.pem", TestKey::GoodKeyPub); - setup::try_make_sequence(&root_key_path, "New_Sequence", "Dissonance.ogg"); + let _ = setup::try_make_sequence(&root_key_path, "New_Sequence", "Dissonance.ogg"); } #[test] @@ -84,7 +78,7 @@ fn fails_with_invalid_private_key() { fn fails_with_invalid_file_extension() { 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(), "New_Sequence", "Dissonance.mp3"); + let _ = setup::try_make_sequence(&root_key_path.as_path(), "New_Sequence", "Dissonance.mp3"); } #[test] @@ -95,7 +89,7 @@ fn fails_with_duplicate_sequence_name() { let name = "New_Sequence"; let root_key_path = common::make_key_file(&root.path(), "root.pem", TestKey::RootKeyPem); - setup::try_make_sequence(&root_key_path.as_path(), &name, "Dissonance.ogg"); + let _ = setup::try_make_sequence(&root_key_path.as_path(), &name, "Dissonance.ogg"); let music_file_path = common::get_music_file_path("GlorytotheBells.ogg"); @@ -117,7 +111,7 @@ fn fails_with_duplicate_sequence_name() { fn fails_with_invalid_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(), "New Sequence", "Dissonance.ogg"); + let _ = setup::try_make_sequence(&root_key_path.as_path(), "New Sequence", "Dissonance.ogg"); } #[test] @@ -127,6 +121,6 @@ fn fails_with_invalid_sequence_name() { fn fails_with_nonexistent_music_file_path() { 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(), "New_Sequence", "nonexistent.ogg"); + let _ = setup::try_make_sequence(&root_key_path.as_path(), "New_Sequence", "nonexistent.ogg"); } diff --git a/tests/remove_sequence.rs b/tests/remove_sequence.rs index 2dab794..114a607 100644 --- a/tests/remove_sequence.rs +++ b/tests/remove_sequence.rs @@ -15,7 +15,7 @@ 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"); + let _ = 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"); @@ -41,7 +41,7 @@ fn fails_with_invalid_admin_key() { 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"); + let _ = 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"); } @@ -53,7 +53,7 @@ fn fails_with_nonexistent_admin_key() { 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"); + let _ = 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"); } @@ -72,7 +72,7 @@ fn fails_with_unauthorized_admin_key() { "normal.pub", TestKey::GoodKeyPub); - setup::try_make_sequence(&root_key_path.as_path(), "asdf", "Dissonance.ogg"); + let _ = setup::try_make_sequence(&root_key_path.as_path(), "asdf", "Dissonance.ogg"); proton_cli::remove_sequence(&normal_key_path.as_path(), "asdf") .expect("Error removing sequence"); } @@ -83,7 +83,7 @@ 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"); + let _ = 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"); } @@ -94,7 +94,7 @@ 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"); + let _ = 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"); } diff --git a/tests/resection_sequence.rs b/tests/resection_sequence.rs new file mode 100644 index 0000000..62c159c --- /dev/null +++ b/tests/resection_sequence.rs @@ -0,0 +1,190 @@ +extern crate proton_cli; +extern crate tempdir; +extern crate git2; + +mod common; + +use std::path::{Path, PathBuf}; +use std::fs; +use tempdir::TempDir; + +use common::setup; +use common::rsa_keys::TestKey; +use common::sequence_sections::{self, TestSeqSec}; +use proton_cli::project_types::Sequence; + + +fn setup_resection(user_key: TestKey, has_perm: bool) -> (TempDir, PathBuf, Sequence) { + let root = setup::setup_init_cd(); + let root_key_path = common::make_key_file(&root.path(), "root.pem", TestKey::RootKeyPem); + let user_key_path = common::make_key_file(&root.path(), "usera.pem", user_key); + let name = "SequenceA"; + let sequence = setup::try_make_sequence(&root_key_path.as_path(), &name, "test_1sec.ogg"); + assert_eq!(sequence.num_sections, 1); + let _ = sequence.set_section_data(0, sequence_sections::get_test_seq_sec(TestSeqSec::Good1of1)) + .expect("Error setting sequence data"); + assert_section_correct(&sequence, 0, TestSeqSec::Good1of1); + + setup::try_new_user( + &root_key_path.as_path(), + &root.path(), + &"UserA", + &"usera.pub", + TestKey::GoodKeyPub); + + if has_perm { + setup::try_set_permission( + &root.path(), + &root_key_path, + true, + "UserA", + "EditSeq", + Some(name.to_owned()), + None::); + } + + (root, user_key_path, sequence) +} + +/// Get a section from the sequence and make sure its data is the same as the test_section given +fn assert_section_correct(sequence: &Sequence, section_idx: u32, test_section: TestSeqSec) { + let section = sequence.get_section(section_idx).expect("Error retrieving sequence section"); + println!("index {}: {:?}", section_idx, section.data); + assert_eq!(section.data, sequence_sections::get_test_seq_sec(test_section)); +} + +/// Gets the sequence named SequenceA, created in setup_resection +fn get_sequence() -> Sequence { + let project = proton_cli::utils::read_protonfile(None::<&Path>) + .expect("Error reading protonfile"); + project.find_sequence_by_name(&"SequenceA") + .expect("Sequence somehow removed during resection") + .to_owned() +} + +#[test] +#[allow(unused_variables)] +// root reference must be kept to keep temp directory in scope, but is never used +fn works_with_valid_inputs() { + let (root, user_key_path, sequence) = setup_resection(TestKey::GoodKeyPem, true); + let name = &sequence.name; + + // Try different resections + let _ = proton_cli::resection_sequence(&user_key_path.as_path(), name, 2) + .expect("Error resectioning sequence"); + let seq_1_to_2 = get_sequence(); + assert_eq!(seq_1_to_2.num_sections, 2); + assert_section_correct(&seq_1_to_2, 0, TestSeqSec::Good1of2); + assert_section_correct(&seq_1_to_2, 1, TestSeqSec::Good2of2); + common::assert_repo_no_modified_files(&root.path()); + + let _ = proton_cli::resection_sequence(&user_key_path.as_path(), name, 3) + .expect("Error resectioning sequence"); + let seq_2_to_3 = get_sequence(); + assert_eq!(seq_2_to_3.num_sections, 3); + assert_section_correct(&seq_2_to_3, 0, TestSeqSec::Good1of3); + assert_section_correct(&seq_2_to_3, 1, TestSeqSec::Good2of3); + assert_section_correct(&seq_2_to_3, 2, TestSeqSec::Good3of3); + common::assert_repo_no_modified_files(&root.path()); + + let _ = proton_cli::resection_sequence(&user_key_path.as_path(), name, 1) + .expect("Error resectioning sequence"); + let seq_3_to_1 = get_sequence(); + assert_eq!(seq_3_to_1.num_sections, 1); + assert_section_correct(&seq_3_to_1, 0, TestSeqSec::Good1of1); + common::assert_repo_no_modified_files(&root.path()); + + let _ = proton_cli::resection_sequence(&user_key_path.as_path(), name, 3) + .expect("Error resectioning sequence"); + let seq_1_to_3 = get_sequence(); + assert_eq!(seq_1_to_3.num_sections, 3); + assert_section_correct(&seq_1_to_3, 0, TestSeqSec::Good1of3); + assert_section_correct(&seq_1_to_3, 1, TestSeqSec::Good2of3); + assert_section_correct(&seq_1_to_3, 2, TestSeqSec::Good3of3); + common::assert_repo_no_modified_files(&root.path()); + + let _ = proton_cli::resection_sequence(&user_key_path.as_path(), name, 2) + .expect("Error resectioning sequence"); + let seq_3_to_2 = get_sequence(); + assert_eq!(seq_3_to_2.num_sections, 2); + assert_section_correct(&seq_3_to_2, 0, TestSeqSec::Good1of2); + assert_section_correct(&seq_3_to_2, 1, TestSeqSec::Good2of2); + common::assert_repo_no_modified_files(&root.path()); +} + +#[test] +#[allow(unused_variables)] +// root reference must be kept to keep temp directory in scope, but is never used +#[should_panic(expected = "Error resectioning sequence: Ssl")] +fn fails_with_bad_user_key() { + let (root, user_key_path, sequence) = setup_resection(TestKey::GoodKey2Pub, true); + let _ = proton_cli::resection_sequence(&user_key_path.as_path(), &sequence.name, 2) + .expect("Error resectioning sequence"); +} + +#[test] +#[allow(unused_variables)] +// root reference must be kept to keep temp directory in scope, but is never used +#[should_panic(expected = "Error resectioning sequence: Io")] +fn fails_with_nonexistent_user_key() { + let (root, _, sequence) = setup_resection(TestKey::GoodKeyPem, true); + let bad_key_path = PathBuf::from("nonexistent"); + let _ = proton_cli::resection_sequence(&bad_key_path.as_path(), &sequence.name, 2) + .expect("Error resectioning sequence"); +} + +#[test] +#[allow(unused_variables)] +// root reference must be kept to keep temp directory in scope, but is never used +#[should_panic(expected = "Error resectioning sequence: UnauthorizedAction")] +fn fails_with_unprivileged_user_key() { + let (root, user_key_path, sequence) = setup_resection(TestKey::GoodKeyPem, false); + let _ = proton_cli::resection_sequence(&user_key_path.as_path(), &sequence.name, 2) + .expect("Error resectioning sequence"); +} + +#[test] +#[allow(unused_variables)] +// root reference must be kept to keep temp directory in scope, but is never used +#[should_panic(expected = "Error resectioning sequence: SequenceNotFound")] +fn fails_with_nonexistent_sequence_name() { + let (root, user_key_path, _) = setup_resection(TestKey::GoodKeyPem, false); + let bad_name = &"notasequencename"; + let _ = proton_cli::resection_sequence(&user_key_path.as_path(), bad_name, 2) + .expect("Error resectioning sequence"); +} + +#[test] +#[allow(unused_variables)] +// root reference must be kept to keep temp directory in scope, but is never used +#[should_panic(expected = "Error resectioning sequence: SequenceNotFound")] +fn fails_with_invalid_sequence_name() { + let (root, user_key_path, _) = setup_resection(TestKey::GoodKeyPem, false); + let invalid_name = &"Not a valid seq name! ;)"; + let _ = proton_cli::resection_sequence(&user_key_path.as_path(), invalid_name, 2) + .expect("Error resectioning sequence"); +} + +#[test] +#[allow(unused_variables)] +// root reference must be kept to keep temp directory in scope, but is never used +#[should_panic(expected = "Error resectioning sequence: SequenceSectionNotFound")] +fn fails_if_section_file_deleted_after_creation() { + // Create sequence, then delete sequence/sequence_sec1 + // Check vector of paths on sequence load, warn/remove dead paths + let (root, user_key_path, sequence) = setup_resection(TestKey::GoodKeyPem, true); + let section = sequence.get_section(0).expect("Error getting sequence section"); + let _ = fs::remove_file(§ion.path).expect("Error removing seq sec file"); + let _ = proton_cli::resection_sequence(&user_key_path.as_path(), &sequence.name, 2) + .expect("Error resectioning sequence"); +} + +#[test] +#[allow(unused_variables)] +// root reference must be kept to keep temp directory in scope, but is never used +#[should_panic(expected = "Error resectioning sequence: InvalidSequenceSection")] +fn fails_0_sections() { + let (root, user_key_path, sequence) = setup_resection(TestKey::GoodKeyPem, true); + let _ = proton_cli::resection_sequence(&user_key_path.as_path(), &sequence.name, 0) + .expect("Error resectioning sequence"); +} diff --git a/tests/set_permission.rs b/tests/set_permission.rs index 603b287..9942913 100644 --- a/tests/set_permission.rs +++ b/tests/set_permission.rs @@ -6,7 +6,6 @@ use std::path::Path; use common::setup; use common::rsa_keys::TestKey; -use proton_cli::project_types::PermissionEnum; #[test] @@ -30,8 +29,9 @@ fn works_with_administrate() { &root_private_key_path, true, "Test User", - PermissionEnum::Administrate, - None); + "Administrate", + None::, + None::); // Now try to remove the permission setup::try_set_permission( @@ -39,8 +39,9 @@ fn works_with_administrate() { &root_private_key_path, false, "Test User", - PermissionEnum::Administrate, - None); + "Administrate", + None::, + None::); } @@ -60,7 +61,7 @@ fn works_with_editseq() { TestKey::GoodKeyPub); // Create sequence - setup::try_make_sequence(&root_private_key_path, "test_seq", "Dissonance.ogg"); + let _ = setup::try_make_sequence(&root_private_key_path, "test_seq", "Dissonance.ogg"); // Try to add permission to user setup::try_set_permission( @@ -68,8 +69,9 @@ fn works_with_editseq() { &root_private_key_path, true, "Test User", - PermissionEnum::EditSeq, - Some("test_seq".to_string())); + "EditSeq", + Some("test_seq".to_owned()), + None::); // Now try removing the permission setup::try_set_permission( @@ -77,8 +79,9 @@ fn works_with_editseq() { &root_private_key_path, false, "Test User", - PermissionEnum::EditSeq, - Some("test_seq".to_string())); + "EditSeq", + Some("test_seq".to_owned()), + None::); } #[test] @@ -89,7 +92,7 @@ fn works_with_editseqsec() { let root_private_key_path = common::make_key_file(root.path(), "root.pem", TestKey::RootKeyPem); // Create user - setup::try_new_user( + let user = setup::try_new_user( root_private_key_path.as_path(), root.path(), "Test User", @@ -97,7 +100,7 @@ fn works_with_editseqsec() { TestKey::GoodKeyPub); // Create sequence - setup::try_make_sequence(&root_private_key_path.as_path(), "test_seq", "Dissonance.ogg"); + let _ = setup::try_make_sequence(&root_private_key_path.as_path(), "test_seq", "Dissonance.ogg"); // Try to add permission to user setup::try_set_permission( @@ -105,8 +108,9 @@ fn works_with_editseqsec() { &root_private_key_path, true, "Test User", - PermissionEnum::EditSeqSec, - Some("test_seq,1".to_string())); + "EditSeqSec", + Some("test_seq".to_owned()), + Some(0)); // Now try removing the permission setup::try_set_permission( @@ -114,8 +118,9 @@ fn works_with_editseqsec() { &root_private_key_path, false, "Test User", - PermissionEnum::EditSeqSec, - Some("test_seq,1".to_string())); + "EditSeqSec", + Some("test_seq".to_owned()), + Some(0)); } #[test] @@ -138,8 +143,9 @@ fn fails_removing_root_admin() { &root_private_key_path, true, "Admin2", - PermissionEnum::Administrate, - None); + "Administrate", + None::, + None::); // Now have that new user take away root's Administrate permission setup::try_set_permission( @@ -147,12 +153,13 @@ fn fails_removing_root_admin() { &admin2_private_key_path, false, "root", - PermissionEnum::Administrate, - None); + "Administrate", + None::, + None::); } #[test] -#[should_panic(expected = "InvalidPermissionTarget")] +#[should_panic(expected = "SequenceNotFound")] fn fails_with_bad_target_editseq() { let root = setup::setup_init_cd(); let root_private_key_path = common::make_key_file(root.path(), "root.pem", TestKey::RootKeyPem); @@ -166,7 +173,7 @@ fn fails_with_bad_target_editseq() { TestKey::GoodKeyPub); // Create sequence - setup::try_make_sequence(&root_private_key_path, "test_seq", "Dissonance.ogg"); + let _ = setup::try_make_sequence(&root_private_key_path, "test_seq", "Dissonance.ogg"); // Try to add permission to user setup::try_set_permission( @@ -174,13 +181,14 @@ fn fails_with_bad_target_editseq() { &root_private_key_path, true, "Test User", - PermissionEnum::EditSeq, - Some("nonexistent".to_string())); + "EditSeq", + Some("nonexistent".to_owned()), + None::); } #[test] -#[should_panic(expected = "InvalidPermissionTarget")] +#[should_panic(expected = "InvalidSequenceSection")] fn fails_with_bad_target_editseqsec() { let root = setup::setup_init_cd(); let root_private_key_path = common::make_key_file(root.path(), "root.pem", TestKey::RootKeyPem); @@ -194,7 +202,7 @@ fn fails_with_bad_target_editseqsec() { TestKey::GoodKeyPub); // Create sequence - setup::try_make_sequence(&root_private_key_path, "test_seq", "Dissonance.ogg"); + let _ = setup::try_make_sequence(&root_private_key_path, "test_seq", "Dissonance.ogg"); // Try to add permission to user setup::try_set_permission( @@ -202,8 +210,9 @@ fn fails_with_bad_target_editseqsec() { &root_private_key_path, true, "Test User", - PermissionEnum::EditSeqSec, - Some("section999".to_string())); + "EditSeqSec", + Some("test_seq".to_owned()), + Some(999)); } #[test] @@ -224,8 +233,9 @@ fn fails_with_bad_path_to_private_key() { &root_private_key_path, true, "Test User", - PermissionEnum::Administrate, - None); + "Administrate", + None::, + None::); } #[test] @@ -247,8 +257,9 @@ fn fails_with_unused_private_key() { &root_private_key_path, true, "Test User", - PermissionEnum::Administrate, - None); + "Administrate", + None::, + None::); } #[test] @@ -262,8 +273,9 @@ fn fails_with_nonexistent_username() { &root_private_key_path, true, "Test User", - PermissionEnum::Administrate, - None); + "Administrate", + None::, + None::); } @@ -287,6 +299,7 @@ fn fails_with_unauthorized_authority() { &private_key_path, true, "root", - PermissionEnum::Administrate, - None); + "Administrate", + None::, + None::); }