diff --git a/src/args/purge.rs b/src/args/purge.rs index ecb2c4e..a601c6b 100644 --- a/src/args/purge.rs +++ b/src/args/purge.rs @@ -3,6 +3,41 @@ use std::{fs::{read_dir, remove_dir_all, remove_file, metadata}, process::exit, const HELP_TEXT: &str = "\nUsage: --purge [type]\nPurges cache or installs, useful for a fresh start or if you are having issues\n\nOptions:\n\tcache\tDeletes all compressed files that were downloaded from the CDN\n\tinstalls\tNukes every install of Roblox you have\n\tinstall\tDeletes a specific version of Roblox, can purge multiple versions at once"; +fn check_location(folder: &str) { + if !setup::confirm_existence(folder) { + error!("{folder} directory does not exist! Did you forget to initialise?"); + exit(1); + } + + let paths = read_dir(format!("{}/cache", setup::get_applejuice_dir())).unwrap(); + if paths.count() == 0 { + error!("{folder} directory is empty!"); + exit(1); + } +} + +fn get_all_valid_installations() { + // Vec format: (location, size, installation_date, special_mods) + let mut valid_player_versions: Vec<(String, String, String, String)> = vec![]; + let mut valid_studio_versions: Vec<(String, String, String, String)> = vec![]; + + let paths = read_dir(format!("{}/roblox", setup::get_applejuice_dir())).unwrap(); + for deployment_path in paths { // "roblox/" + let studio_player = read_dir(deployment_path.unwrap().path()).unwrap(); + + for binary_type in studio_player { // "roblox//" + let version = read_dir(binary_type.unwrap().path()).unwrap(); + + for version_path in version { // "roblox///" + //let path_unwrap = version_path.unwrap().path().to_str().unwrap(); + + //println!("{}", path_unwrap); + println!("{:?}", version_path.unwrap().path()); + } + } + } +} + pub fn main(args: Vec>) { let binding = argparse::get_param_value(args, "purge"); let parsed_args = binding.split(' ').collect::>(); @@ -16,168 +51,117 @@ pub fn main(args: Vec>) { match install_type { "cache" => { status!("Purging cache..."); - - if setup::confirm_existence("cache") { - let paths = read_dir(format!("{}/cache", setup::get_applejuice_dir())).unwrap(); - if paths.count() == 0 { - error!("Cache directory is empty!"); + + match remove_dir_all(format!("{}/cache", setup::get_applejuice_dir())) { + Ok(_) => { + success!("Removed cache directory"); + }, + Err(errmsg) => { + error!("Failed to remove the cache directory!\nError: {}", errmsg); exit(1); } - - match remove_dir_all(format!("{}/cache", setup::get_applejuice_dir())) { - Ok(_) => { - success!("Removed cache directory"); - }, - Err(errmsg) => { - error!("Failed to remove the cache directory!\nError: {}", errmsg); - exit(1); - } - } - - setup::create_dir("cache"); - success!("Created cache directory"); - } else { - error!("Cache directory does not exist! Did you forget to initialise?"); - exit(1); } + + setup::create_dir("cache"); + success!("Created cache directory"); success!("Purged cache successfully"); }, "installs" => { status!("Purging Roblox installations..."); - - if setup::confirm_existence("roblox") { - let paths = read_dir(format!("{}/roblox", setup::get_applejuice_dir())).unwrap(); - if paths.count() == 0 { - error!("Roblox directory is empty!"); - exit(1); - } - - let mut removing = Vec::new(); - match read_dir(format!("{}/roblox", setup::get_applejuice_dir())) { - Ok(paths) => { - for deployment_path in paths { - let path_unwrap = deployment_path.unwrap(); - match read_dir(path_unwrap.path()) { - Ok(paths) => { - for binary_type in paths { - let path_unwrap = binary_type.unwrap(); - + check_location("roblox"); + + let mut removing = Vec::new(); + match read_dir(format!("{}/roblox", setup::get_applejuice_dir())) { + Ok(paths) => { + for deployment_path in paths { + let path_unwrap = deployment_path.unwrap(); + match read_dir(path_unwrap.path()) { + Ok(paths) => { + for binary_type in paths { + let path_unwrap = binary_type.unwrap(); match read_dir(path_unwrap.path()) { - Ok(paths) => { - for version_path in paths { - let path_unwrap = version_path.unwrap(); - let binding = path_unwrap.path(); - removing.push(path_unwrap.file_name().into_string().unwrap()); - - status!("Removing {:?}...", binding.clone()); - match remove_dir_all(binding.clone()) { - Ok(_) => { - success!("Removed Roblox directory for version {}", binding.to_str().unwrap()); + Ok(paths) => { + for version_path in paths { + let path_unwrap = version_path.unwrap(); + let binding = path_unwrap.path(); + removing.push(path_unwrap.file_name().into_string().unwrap()); + + status!("Removing {:?}...", binding.clone()); + match remove_dir_all(binding.clone()) { + Ok(_) => { + success!("Removed Roblox directory for version {}", binding.to_str().unwrap()); }, - Err(errmsg) => { - error!("Failed to remove the Roblox directory for version {}!\nError: {}", path_unwrap.path().to_str().unwrap(), errmsg); - exit(1); - } + Err(errmsg) => { + error!("Failed to remove the Roblox directory for version {}!\nError: {}", path_unwrap.path().to_str().unwrap(), errmsg); + exit(1); } } - }, - Err(errmsg) => { - error!("Failed to read the Roblox directory!\nError: {}", errmsg); - exit(1); } + }, + Err(errmsg) => { + error!("Failed to read the Roblox directory!\nError: {}", errmsg); + exit(1); } } - }, - Err(errmsg) => { - error!("Failed to read the Roblox directory!\nError: {}", errmsg); - exit(1); } + }, + Err(errmsg) => { + error!("Failed to read the Roblox directory!\nError: {}", errmsg); + exit(1); } } - }, - Err(errmsg) => { - error!("Failed to read the Roblox directory!\nError: {}", errmsg); - exit(1); - } - } - - status!("Cleaning up..."); - match remove_dir_all(format!("{}/roblox", setup::get_applejuice_dir())) { - Ok(_) => { - success!("Removed Roblox directory"); - }, - Err(errmsg) => { - format!("Failed to remove the Roblox directory!\nError: {}", errmsg); - exit(1); } + }, + Err(errmsg) => { + error!("Failed to read the Roblox directory!\nError: {}", errmsg); + exit(1); } - - status!("Removing shortcuts to deployments..."); - removing.iter().for_each(|version| { - let config = get_config(version); - let binary_type = config.get("binary_type").unwrap().as_str().unwrap(); - let clean_version_hash = version.replace("version-", ""); - let desktop_shortcut_path = format!("{}/.local/share/applications/roblox-{}-{}.desktop", var("HOME").expect("$HOME not set"), binary_type.to_lowercase(), clean_version_hash); - if metadata(desktop_shortcut_path.clone()).is_ok() { - remove_file(desktop_shortcut_path).unwrap(); - } - }); - status!("Updating desktop database..."); - process::Command::new("update-desktop-database") - .arg(format!("{}/.local/share/applications", var("HOME").expect("$HOME not set"))) - .spawn() - .expect("Failed to execute update-desktop-database"); - - status!("Removing installation entires from configuration file..."); - removing.iter().for_each(|version| { - update_config(serde_json::Value::Null, version); - }); - - setup::create_dir("roblox"); - success!("Created Roblox directory"); - } else { - error!("Roblox directory does not exist! Did you forget to initialise?"); - exit(1); } - success!("Purged Roblox installations successfully"); - }, - "install" => { - // status("Searching for remnant entries in config..."); - /*status(format!("Purging Roblox installation(s) for version(s) {}...", parsed_args[1..].join(", "))); - - if setup::confirm_existence("roblox") { - let paths = read_dir(format!("{}/roblox", setup::get_applejuice_dir())).unwrap(); - if paths.count() == 0 { - error("Roblox directory is empty!"); + status!("Cleaning up..."); + match remove_dir_all(format!("{}/roblox", setup::get_applejuice_dir())) { + Ok(_) => { + success!("Removed Roblox directory"); + }, + Err(errmsg) => { + format!("Failed to remove the Roblox directory!\nError: {}", errmsg); exit(1); } + } - for version in parsed_args[1..].iter() { - for path in read_dir(format!("{}/roblox", setup::get_applejuice_dir())).unwrap() { - let path_unwrap = path.unwrap(); - if path_unwrap.path().to_str().unwrap().contains(version) { - match remove_dir_all(path_unwrap.path()) { - Ok(_) => { - success(format!("Removed Roblox directory for version {}", version)); - }, - Err(errmsg) => { - error(format!("Failed to remove the Roblox directory for version {}!\nError: {}", version, errmsg)); - exit(1); - } - } - } - } + status!("Removing shortcuts to deployments..."); + removing.iter().for_each(|version| { + let config = get_config(version); + let binary_type = config.get("binary_type").unwrap().as_str().unwrap(); + let clean_version_hash = version.replace("version-", ""); + let desktop_shortcut_path = format!("{}/.local/share/applications/roblox-{}-{}.desktop", var("HOME").expect("$HOME not set"), binary_type.to_lowercase(), clean_version_hash); + if metadata(desktop_shortcut_path.clone()).is_ok() { + remove_file(desktop_shortcut_path).unwrap(); } + }); + status!("Updating desktop database..."); + process::Command::new("update-desktop-database") + .arg(format!("{}/.local/share/applications", var("HOME").expect("$HOME not set"))) + .spawn() + .expect("Failed to execute update-desktop-database"); - status("Searching for remnant entries in config..."); - } else { - error("Roblox directory does not exist! Did you forget to initialise?"); - exit(1); - } + status!("Removing installation entires from configuration file..."); + removing.iter().for_each(|version| { + update_config(serde_json::Value::Null, version); + }); + + setup::create_dir("roblox"); + success!("Created Roblox directory"); + + success!("Purged Roblox installations successfully"); + }, + "install" => { + status!("Discovering Roblox installations..."); + check_location("roblox"); + + get_all_valid_installations(); - success("Purged Roblox installation(s) successfully");*/ error!("Not implemented yet!"); }, _ => { diff --git a/src/utils/argparse.rs b/src/utils/argparse.rs index 775ff6b..2601070 100644 --- a/src/utils/argparse.rs +++ b/src/utils/argparse.rs @@ -1,4 +1,4 @@ -pub fn get_param_value(command_vector: Vec>, value_to_find: &str) -> String { +pub fn get_param_value(command_vector: Vec>, value_to_find: &str) -> String { // TODO: Replace all `get_param_value` calls with `get_param_value_new` for command in command_vector.iter() { if command[0].0 == *value_to_find { if command[0].1.is_empty() { diff --git a/src/utils/installation.rs b/src/utils/installation.rs index 9913b5a..d43918e 100644 --- a/src/utils/installation.rs +++ b/src/utils/installation.rs @@ -67,7 +67,6 @@ struct Response { #[serde(rename_all = "camelCase")] struct ResponseErrorMeat { code: i32, - message: String } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] @@ -164,7 +163,7 @@ pub fn fetch_latest_version(version: LatestVersion) -> ExactVersion { match errors[0].code { 1 => { error!("Could not find version details for channel {}, make sure you have spelt the deployment channel name correctly.", channel); }, 5 => { error!("The deployment channel {} is restricted by Roblox!", channel); }, - _ => { error!("Unknown error response.\nResponse: {}\nError: {}", output, error); } + _ => { error!("Unknown error response when attempting to resolve channel {}!\nResponse: {}\nError: {}", channel, output, error); } } exit(1); diff --git a/src/utils/notification.rs b/src/utils/notification.rs index 97a1bb8..929d683 100644 --- a/src/utils/notification.rs +++ b/src/utils/notification.rs @@ -4,7 +4,7 @@ use crate::utils::terminal::*; pub fn create_notification(icon: &str, expire_time: &str, title: &str, body: &str) { let output = process::Command::new("notify-send") .arg("--app-name=Applejuice") - .arg(format!("--icon={}", icon)) + .arg(format!("--icon={}", "aaaaa")) .arg("--urgency=normal") .arg(format!("--expire-time={}", expire_time)) .arg(title) @@ -14,8 +14,13 @@ pub fn create_notification(icon: &str, expire_time: &str, title: &str, body: &st match output { Ok(_) => { }, Err(errmsg) => { // Do not quit or panic here, since it's a non-critical error - warning!("Failed to create notification, raw: '{}'\nError: {}", icon, errmsg); + warning!("Failed to create notification, icon: '{}'\nError: {}", icon, errmsg); + if icon.is_empty() { + warning!("Failed to create notification twice; You may not have libnotify installed on your system, ignoring..."); + return; + } + if errmsg.to_string().contains("No such file or directory (os error 2)") { // Fallback to default/no icon if we detect a missing file error warning!("Assuming a asset was missing; falling back to no icon..."); create_notification("", expire_time, title, body); diff --git a/src/utils/rpc.rs b/src/utils/rpc.rs index ca8b961..7f5f06c 100644 --- a/src/utils/rpc.rs +++ b/src/utils/rpc.rs @@ -33,7 +33,7 @@ struct RichPresenceData { #[serde(rename_all = "camelCase")] struct RichPresence { command: Option, - data: RichPresenceData, + data: Option, } #[derive(Serialize, Deserialize, Debug)] @@ -161,7 +161,7 @@ pub fn init_rpc(binary_type: String, debug_notifications: Option<&str>) { let mut detected_bloxstrap = false; let mut last_successful_rec_unwrap: (u32, Value) = (0, Value::Null); - //let mut old_presence = activity::Activity::new(); + //let mut old_presence = activity::Activity::new(); TODO for result in rx { match result { Ok(_event) => { @@ -175,6 +175,7 @@ pub fn init_rpc(binary_type: String, debug_notifications: Option<&str>) { let reader = BufReader::new(&file); for line in reader.lines() { let line_usable = line.unwrap_or_default(); // Sometimes Roblox likes to just throw vomit into the log file causing a panic + let mut was_rpc_updated = false; if line_usable.contains("[BloxstrapRPC] ") { if detected_bloxstrap == false { @@ -183,26 +184,37 @@ pub fn init_rpc(binary_type: String, debug_notifications: Option<&str>) { } status!("Parsing Log line for RPC: {}", line_usable); let line_split = line_usable.split("[BloxstrapRPC] ").collect::>()[1]; - let parsed_data: RichPresence = serde_json::from_str(line_split).unwrap(); - // Make our lives easier, and move all elements in `data` to the root + let parsed_data: RichPresence = match serde_json::from_str(line_split) { + Ok(parsed_data) => parsed_data, + Err(error) => { + warning!("Error occurred when attempting to parse RPC data: {error}"); + continue; + } + }; + // Make our lives easier, and move all elements in `data` to the root + let command = parsed_data.command; - let state = parsed_data.data.state; - let details = parsed_data.data.details; - let time_start = parsed_data.data.time_start; - let time_end = parsed_data.data.time_end; - let small_image = parsed_data.data.small_image; - let large_image = parsed_data.data.large_image; + let data: RichPresenceData = parsed_data.data.unwrap(); + let state = data.state; + let details = data.details; + let time_start = data.time_start; + let time_end = data.time_end; + let small_image = data.small_image; + let large_image = data.large_image; - status!("RPC Protocol command: {}", command.unwrap_or_default()); - status!("Updating RPC based on BloxstrapRPC protocol data from server... {}", line_split); + if command.is_none() { + warning!("RPC command is none"); + continue; + } else if command.unwrap() != "SetRichPresence" { + warning!("RPC command is not SetRichPresence; ignoring"); + continue; + } construct_rpc_assets!(rpc_assets, small_image, large_image); - let state = &state.unwrap(); - let details = &details.unwrap(); + let state = &state.unwrap_or_default(); + let details = &details.unwrap_or_default(); let mut activity = activity::Activity::new() - .state(state) - .details(details) .timestamps( activity::Timestamps::new().start( if time_start.unwrap_or_default() == 0 { @@ -217,11 +229,46 @@ pub fn init_rpc(binary_type: String, debug_notifications: Option<&str>) { ) .assets(rpc_assets); + if !state.is_empty() { + activity = activity.state(state); + } + if !details.is_empty() { + activity = activity.details(details); + } if time_end.unwrap_or_default() != 0 && time_end.is_some() { activity = activity.timestamps(activity::Timestamps::new().end(time_end.unwrap())); } let _ = rpc_handler.set_activity(activity); + was_rpc_updated = true; + } else if line_usable.contains("leaveUGCGameInternal") { + status!("Detected game leave; resetting RPC"); + + let state = format!("Using Roblox {} on Linux!", binary_type.clone()); // TODO: move this into it's own function to avoid violating D.R.Y + let payload = activity::Activity::new() + .state(&state) + .details("With Applejuice") + .assets( + activity::Assets::new() + .large_image("crudejuice") + .large_text("Bitdancer Approved"), + ) + .timestamps( + activity::Timestamps::new() + .start( + time::SystemTime::now() + .duration_since(time::SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as i64, + ) + ); + + let _ = rpc_handler.set_activity(payload); + was_rpc_updated = true; + } + + if was_rpc_updated == true { + was_rpc_updated = false; match rpc_handler.recv() { Ok(output) => { let output_string = format!("{:?}", output);