From fee811df72b9e62a883e831c5e0ea3df8acd50dc Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Thu, 16 Feb 2023 23:19:10 -0500 Subject: [PATCH] backend: majority of version management implemented --- src-tauri/src/commands/versions.rs | 79 +++++++++++++++--- src-tauri/src/config.rs | 16 ++++ src-tauri/src/main.rs | 9 ++- src-tauri/src/textures.rs | 121 ++++++++++++++-------------- src-tauri/src/util.rs | 25 ++++++ src-tauri/tauri.conf.json | 3 - src/lib/rpc/versions.ts | 50 +++++++++++- src/routes/settings/Versions.svelte | 114 ++++++++++++++++---------- 8 files changed, 299 insertions(+), 118 deletions(-) create mode 100644 src-tauri/src/util.rs diff --git a/src-tauri/src/commands/versions.rs b/src-tauri/src/commands/versions.rs index 4b79f76..1dc08cd 100644 --- a/src-tauri/src/commands/versions.rs +++ b/src-tauri/src/commands/versions.rs @@ -1,18 +1,19 @@ use futures_util::StreamExt; -use std::{collections::HashMap, error::Error, path::Path}; +use std::{io::Cursor, path::Path}; use tokio::{fs::File, io::AsyncWriteExt}; -use crate::config::LauncherConfig; +use crate::{config::LauncherConfig, util}; #[tauri::command] -pub async fn list_downloaded_official_versions( +pub async fn list_downloaded_versions( config: tauri::State<'_, tokio::sync::Mutex>, + version_folder: String, ) -> Result, ()> { let config_lock = config.lock().await; match &config_lock.installation_dir { None => Ok(Vec::new()), Some(path) => { - let expected_path = Path::new(&path).join("/versions/official"); + let expected_path = Path::new(path).join("versions").join(version_folder); if !expected_path.is_dir() { Ok(Vec::new()) } else { @@ -24,7 +25,11 @@ pub async fn list_downloaded_official_versions( e.ok().and_then(|d| { let p = d.path(); if p.is_dir() { - Some(p.to_string_lossy().into_owned()) + Some( + p.file_name() + .map(|name| name.to_string_lossy().into_owned()) + .unwrap_or("".into()), + ) } else { None } @@ -48,15 +53,12 @@ pub async fn download_official_version( match &config_lock.installation_dir { None => Ok(()), Some(path) => { - println!("BLAH - {}", path); - // TODO - make the dir - let expected_path = Path::new(&path).join("versions/official/test"); - println!("{}", expected_path.display()); + // TODO - severe lack of safety here! + // TODO - make the dir and the file name + let expected_path = Path::new(&path).join("versions/official/test.zip"); let client = reqwest::Client::new(); - println!("{}", url); let mut req = client.get(url); let res = req.send().await.expect(""); - println!("{:?}", res); let total = res.content_length().expect(""); let mut file = File::create(expected_path).await.expect(""); @@ -66,7 +68,62 @@ pub async fn download_official_version( let chunk = chunk.expect(""); file.write_all(&chunk).await.expect(""); } + + let target_dir = Path::new(&path).join("versions/official/").join(version); + + let zip_path = Path::new(&path).join("versions/official/test.zip"); + + let archive: Vec = std::fs::read(&zip_path.clone()).unwrap(); + zip_extract::extract(Cursor::new(archive), &target_dir, true).expect(""); + + std::fs::remove_file(zip_path).expect("TODO"); + Ok(()) } } } + +#[tauri::command] +pub async fn go_to_version_folder( + config: tauri::State<'_, tokio::sync::Mutex>, + version_folder: String, +) -> Result<(), ()> { + let config_lock = config.lock().await; + match &config_lock.installation_dir { + None => Err(()), + Some(path) => { + let expected_path = Path::new(path).join("versions").join(version_folder); + util::open_dir_in_os(expected_path.to_string_lossy().into_owned()); + Ok(()) + } + } +} + +#[tauri::command] +pub async fn save_active_version_change( + config: tauri::State<'_, tokio::sync::Mutex>, + version_folder: String, + new_active_version: String, +) -> Result<(), ()> { + let mut config_lock = config.lock().await; + // TODO - error checking + config_lock.set_active_version_folder(version_folder); + config_lock.set_active_version(new_active_version); + Ok(()) +} + +#[tauri::command] +pub async fn get_active_version( + config: tauri::State<'_, tokio::sync::Mutex>, +) -> Result, ()> { + let config_lock = config.lock().await; + Ok(config_lock.active_version.clone()) +} + +#[tauri::command] +pub async fn get_active_version_folder( + config: tauri::State<'_, tokio::sync::Mutex>, +) -> Result, ()> { + let config_lock = config.lock().await; + Ok(config_lock.active_version_folder.clone()) +} diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index e87224f..e079ea3 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -38,6 +38,7 @@ impl SupportedGame { pub struct GameConfig { is_installed: bool, version: Option, + version_folder: Option, } impl GameConfig { @@ -45,6 +46,7 @@ impl GameConfig { Self { is_installed: false, version: None, + version_folder: None, } } } @@ -101,6 +103,8 @@ pub struct LauncherConfig { pub games: SupportedGames, pub last_active_game: Option, pub installation_dir: Option, + pub active_version: Option, + pub active_version_folder: Option, } // TODO - what is _loaded? @@ -118,6 +122,8 @@ impl LauncherConfig { games: SupportedGames::default(), last_active_game: None, installation_dir: None, + active_version: None, + active_version_folder: Some("official".to_string()), } } @@ -181,4 +187,14 @@ impl LauncherConfig { self.installation_dir = Some(new_dir); self.save_config(); } + + pub fn set_active_version(&mut self, new_version: String) { + self.active_version = Some(new_version); + self.save_config(); + } + + pub fn set_active_version_folder(&mut self, new_version_folder: String) { + self.active_version_folder = Some(new_version_folder); + self.save_config(); + } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index cdb4e0e..74cded9 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -10,6 +10,7 @@ use std::env; mod commands; mod config; mod textures; +mod util; use commands::{ close_splashscreen, copy_dir, get_highest_simd, get_install_directory, open_dir, open_repl, set_install_directory, @@ -19,6 +20,8 @@ use textures::{extract_textures, get_all_texture_packs}; pub type FFIResult = Result; fn main() { + // TODO - switch to https://github.com/daboross/fern so we can setup easy logging + // to a file as well if env::var_os("RUST_LOG").is_none() { env::set_var("RUST_LOG", "debug"); } @@ -44,8 +47,12 @@ fn main() { get_install_directory, set_install_directory, // Version Management, - commands::versions::list_downloaded_official_versions, + commands::versions::list_downloaded_versions, commands::versions::download_official_version, + commands::versions::go_to_version_folder, + commands::versions::save_active_version_change, + commands::versions::get_active_version, + commands::versions::get_active_version_folder, // Requirements Checking get_highest_simd, open_dir, diff --git a/src-tauri/src/textures.rs b/src-tauri/src/textures.rs index 90d1153..5dbe983 100644 --- a/src-tauri/src/textures.rs +++ b/src-tauri/src/textures.rs @@ -1,86 +1,87 @@ use serde::{Deserialize, Serialize}; use std::{ - fs, - io::{self, Cursor, Read}, - path::{Path, PathBuf}, + fs, + io::{self, Cursor, Read}, + path::{Path, PathBuf}, }; #[derive(Serialize, Deserialize, Debug)] pub struct TexturePack { - author: String, - description: String, - version: String, - path: Option, + author: String, + description: String, + version: String, + path: Option, } #[tauri::command] pub async fn extract_textures(app_handle: tauri::AppHandle, textures_array: Vec) { - let text_dir = app_handle - .path_resolver() - .app_dir() - .unwrap() - .join("data/texture_replacements"); + let text_dir = app_handle + .path_resolver() + .app_dir() + .unwrap() + .join("data/texture_replacements"); - let target_dir = PathBuf::from(text_dir.clone()); // Doesn't need to exist + let target_dir = PathBuf::from(text_dir.clone()); // Doesn't need to exist - for path in textures_array { - println!("Extracting texture pack: {:?}", path.clone()); + // for path in textures_array { + // println!("Extracting texture pack: {:?}", path.clone()); - let archive: Vec = fs::read(&path.clone()).unwrap(); - // The third parameter allows you to strip away toplevel directories. - // If `archive` contained a single directory, its contents would be extracted instead. - match zip_extract::extract(Cursor::new(archive), &target_dir, true) { - Ok(_) => continue, - Err(err) => println!("{:?}", err), - } - } + // let archive: Vec = fs::read(&path.clone()).unwrap(); + // // The third parameter allows you to strip away toplevel directories. + // // If `archive` contained a single directory, its contents would be extracted instead. + // match zip_extract::extract(Cursor::new(archive), &target_dir, true) { + // Ok(_) => continue, + // Err(err) => println!("{:?}", err), + // } + // } } fn read_texture_json_file(file_path: PathBuf) -> Result { - let zipfile = std::fs::File::open(&file_path)?; - let mut zip = zip::ZipArchive::new(zipfile).unwrap(); + let zipfile = std::fs::File::open(&file_path)?; + let mut zip = zip::ZipArchive::new(zipfile).unwrap(); - // TODO: Figure out some top level schenanigans here similar to the zip extract ignoring toplevel - let mut contents = String::new(); - zip.by_name("texture_replacements/about.json")? - .read_to_string(&mut contents)?; + // TODO: Figure out some top level schenanigans here similar to the zip extract ignoring toplevel + let mut contents = String::new(); + zip + .by_name("texture_replacements/about.json")? + .read_to_string(&mut contents)?; - let pack: TexturePack = TexturePack { - path: Some(file_path), - ..serde_json::from_str(&contents).unwrap() - }; - Ok(pack) + let pack: TexturePack = TexturePack { + path: Some(file_path), + ..serde_json::from_str(&contents).unwrap() + }; + Ok(pack) } #[tauri::command] pub fn get_all_texture_packs(dir: String) -> Vec { - let dir_path = Path::new(&dir).exists(); - if !dir_path { - println!("Textures directory doesn't exist, creating it now."); - fs::create_dir(dir.clone()).unwrap(); - return Vec::new(); - } + let dir_path = Path::new(&dir).exists(); + if !dir_path { + println!("Textures directory doesn't exist, creating it now."); + fs::create_dir(dir.clone()).unwrap(); + return Vec::new(); + } - let entries = fs::read_dir(dir).unwrap(); + let entries = fs::read_dir(dir).unwrap(); - let mut texture_pack_data: Vec = Vec::new(); - for entry in entries { - let path = entry.unwrap().path(); - match path.extension() { - Some(ext) if ext == "zip" => { - let files = match read_texture_json_file(path.clone()) { - Ok(pack) => pack, - Err(_e) => { - // if the about.json file isn't inside of the expected directory this error happens - // TODO: add this error to a logs file so players know when they install a bad texture pack - println!("File doesn't have proper about.json: {:?}", path); - continue; - } - }; - texture_pack_data.push(files); - } - _ => continue, - } + let mut texture_pack_data: Vec = Vec::new(); + for entry in entries { + let path = entry.unwrap().path(); + match path.extension() { + Some(ext) if ext == "zip" => { + let files = match read_texture_json_file(path.clone()) { + Ok(pack) => pack, + Err(_e) => { + // if the about.json file isn't inside of the expected directory this error happens + // TODO: add this error to a logs file so players know when they install a bad texture pack + println!("File doesn't have proper about.json: {:?}", path); + continue; + } + }; + texture_pack_data.push(files); + } + _ => continue, } - return texture_pack_data; + } + return texture_pack_data; } diff --git a/src-tauri/src/util.rs b/src-tauri/src/util.rs new file mode 100644 index 0000000..243aa18 --- /dev/null +++ b/src-tauri/src/util.rs @@ -0,0 +1,25 @@ +use std::process::Command; + +#[cfg(target_os = "windows")] +pub fn open_dir_in_os(dir: String) { + Command::new("explorer") + .arg(dir) // <- Specify the directory you'd like to open. + .spawn() + .unwrap(); +} + +#[cfg(target_os = "linux")] +pub fn open_dir_in_os(dir: String) { + Command::new("xdg-open") + .arg(dir) // <- Specify the directory you'd like to open. + .spawn() + .unwrap(); +} + +#[cfg(target_os = "macos")] +pub fn open_dir_in_os(dir: String) { + Command::new("open") + .arg(dir) // <- Specify the directory you'd like to open. + .spawn() + .unwrap(); +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 2f1610f..a3e79af 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -28,9 +28,6 @@ "icons/icon.icns", "icons/icon.ico" ], - "resources": [ - "data/" - ], "externalBin": [ "bin/extractor", "bin/gk", diff --git a/src/lib/rpc/versions.ts b/src/lib/rpc/versions.ts index 5acd2bc..3b90058 100644 --- a/src/lib/rpc/versions.ts +++ b/src/lib/rpc/versions.ts @@ -1,8 +1,16 @@ import { invoke } from "@tauri-apps/api/tauri"; -export async function listDownloadedOfficialVersions(): Promise { +export enum VersionFolders { + OFFICIAL = "official", + UNOFFICIAL = "unofficial", + DEVEL = "devel", +} + +export async function listDownloadedVersions( + folder: VersionFolders +): Promise { try { - return await invoke("list_downloaded_official_versions", {}); + return await invoke("list_downloaded_versions", { versionFolder: folder }); } catch (e) { console.log("TODO AH!"); } @@ -18,3 +26,41 @@ export async function downloadOfficialVersion( console.log("TODO AH!"); } } + +export async function openVersionFolder(folder: VersionFolders) { + try { + return await invoke("go_to_version_folder", { versionFolder: folder }); + } catch (e) { + console.log("TODO AH!"); + } +} + +export async function saveActiveVersionChange( + folder: VersionFolders, + newVersion: String +) { + try { + return await invoke("save_active_version_change", { + versionFolder: folder, + newActiveVersion: newVersion, + }); + } catch (e) { + console.log("TODO AH!"); + } +} + +export async function getActiveVersion() { + try { + return await invoke("get_active_version", {}); + } catch (e) { + console.log("TODO AH!"); + } +} + +export async function getActiveVersionFolder() { + try { + return await invoke("get_active_version_folder", {}); + } catch (e) { + console.log("TODO AH!"); + } +} diff --git a/src/routes/settings/Versions.svelte b/src/routes/settings/Versions.svelte index c24f98c..81f275f 100644 --- a/src/routes/settings/Versions.svelte +++ b/src/routes/settings/Versions.svelte @@ -11,11 +11,18 @@ import Icon from "@iconify/svelte"; import { onMount } from "svelte"; import { each } from "svelte/internal"; - import { downloadOfficialVersion, listDownloadedOfficialVersions } from "$lib/rpc/versions"; + import { + downloadOfficialVersion, + getActiveVersion, + listDownloadedVersions, + openVersionFolder, + saveActiveVersionChange, + VersionFolders, + } from "$lib/rpc/versions"; let componentLoaded = false; - let currentOfficialVersion = "v0.1.31"; - let selectedOfficialVersion = "v0.1.31"; + let currentOfficialVersion = undefined; + let selectedOfficialVersion = undefined; const tabItemActiveClasses = "inline-block text-sm font-bold text-center disabled:cursor-not-allowed p-4 text-orange-500 border-b-2 border-orange-500 dark:text-orange-500 dark:border-orange-500"; @@ -28,12 +35,14 @@ githubLink: string | undefined; downloadUrl: string | undefined; // TODO - windows/mac/linux isDownloaded: boolean; - isActive: boolean; } let officialReleases: Release[] = []; onMount(async () => { + // TODO - check when this is null + currentOfficialVersion = await getActiveVersion(); + selectedOfficialVersion = currentOfficialVersion; await refreshOfficialVersionList(undefined); // TODO - spinner componentLoaded = true; @@ -41,7 +50,9 @@ async function refreshOfficialVersionList(evt) { // Check the backend to see if the folder has any versions - const installedVersions = await listDownloadedOfficialVersions(); + const installedVersions = await listDownloadedVersions( + VersionFolders.OFFICIAL + ); officialReleases = []; for (const version of installedVersions) { officialReleases = [ @@ -51,8 +62,7 @@ date: undefined, githubLink: undefined, downloadUrl: undefined, - isDownloaded: true, - isActive: true, // TODO + isDownloaded: true }, ]; } @@ -75,7 +85,8 @@ if (existingRelease.version == release.tag_name) { existingRelease.date = release.published_at; existingRelease.githubLink = release.html_url; - existingRelease.downloadUrl = "https://github.com/open-goal/jak-project/releases/download/v0.1.32/opengoal-windows-v0.1.32.zip"; + existingRelease.downloadUrl = + "https://github.com/open-goal/jak-project/releases/download/v0.1.32/opengoal-windows-v0.1.32.zip"; foundExistingRelease = true; break; } @@ -89,24 +100,28 @@ version: release.tag_name, date: release.published_at, githubLink: release.html_url, - downloadUrl: "https://github.com/open-goal/jak-project/releases/download/v0.1.32/opengoal-windows-v0.1.32.zip", - isDownloaded: false, - isActive: false, // TODO + downloadUrl: + "https://github.com/open-goal/jak-project/releases/download/v0.1.32/opengoal-windows-v0.1.32.zip", + isDownloaded: false }, ]; } // Sort releases by published date - officialReleases.sort((a, b) => b.date.localeCompare(a.date)); + officialReleases = officialReleases.sort((a, b) => b.date.localeCompare(a.date)); + selectedOfficialVersion = "v0.1.32"; } async function saveOfficialVersionChange(evt) { - // TODO - tauri side - + await saveActiveVersionChange( + VersionFolders.OFFICIAL, + selectedOfficialVersion + ); + currentOfficialVersion = selectedOfficialVersion; } async function openOfficialVersionFolder(evt) { - // TODO - tauri side + openVersionFolder(VersionFolders.OFFICIAL); } async function onDownloadOfficialVersion(version: String, url: String) { @@ -135,16 +150,19 @@
{#if currentOfficialVersion != selectedOfficialVersion} - + {/if} - - - {release.version} - {new Date(release.date).toLocaleDateString()} -