diff --git a/FCLauncher/src-tauri/Cargo.toml b/FCLauncher/src-tauri/Cargo.toml index d907a5f..ddc5524 100644 --- a/FCLauncher/src-tauri/Cargo.toml +++ b/FCLauncher/src-tauri/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" tauri-build = { version = "1", features = [] } [dependencies] -tauri = { version = "1", features = [ "updater", "dialog-ask", "shell-open"] } +tauri = { version = "1", features = [ "dialog-open", "updater", "dialog-ask", "shell-open"] } serde = { version = "1", features = ["derive"] } serde_json = "1" suppaftp = { version = "6.0.1", features = ["native-tls"] } @@ -25,6 +25,7 @@ parking_lot = "0.12.3" reqwest = { version = "0.12.5", features = ["stream"] } futures-util = "0.3.30" ssh2 = "0.9.4" +chrono = "0.4.38" [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/FCLauncher/src-tauri/src/admin.rs b/FCLauncher/src-tauri/src/admin.rs index 2037578..1bc87ea 100644 --- a/FCLauncher/src-tauri/src/admin.rs +++ b/FCLauncher/src-tauri/src/admin.rs @@ -1,10 +1,14 @@ use std::io::Cursor; use std::path::PathBuf; +use crate::modpack::get_modpacks; +use crate::modpack::VersionEntry; use crate::sftp; use crate::modpack; +use crate::ModpackEntry; use ssh2::Sftp; use ssh2::Session; +use chrono; //static USERNAME: parking_lot::Mutex = parking_lot::const_mutex(String::new()); //static PASSWORD: parking_lot::Mutex = parking_lot::const_mutex(String::new()); @@ -42,6 +46,16 @@ async fn update_modpacks(modpacks: Vec) -> Result<(), Str Ok(()) } +async fn update_versions(id: String, versions: Vec) -> Result<(), String>{ + let data = serde_json::to_string(&versions).or(Err("Unable to serialize json"))?; + let reader = Cursor::new(data.as_bytes()); + let ref mut session = *SESSION.lock().await; + if let Some(session) = session { + sftp::uplaod(None, session.clone(), PathBuf::from(format!("/ftp/{}/versions.json", id)), reader, format!("modpacks.json"), data.as_bytes().len()).await; + } + Ok(()) +} + #[tauri::command] pub async fn shift_up(id: String){ let mut modpacks = modpack::get_modpacks().await; @@ -72,4 +86,45 @@ pub async fn shift_down(id: String){ modpacks.swap(index, index+1); } update_modpacks(modpacks).await; +} + +#[tauri::command] +pub async fn add_pack(id: String, name: String){ + { + let ref mut session = *SESSION.lock().await; + if let Some(session) = session{ + sftp::mkdir(session.clone(), PathBuf::from(format!("/ftp/{}", id))).await; + sftp::mkdir(session.clone(), PathBuf::from(format!("/ftp/{}/Versions", id))).await; + } + } + let versions: Vec = Vec::new(); + update_versions(id.clone(), versions).await; + let mut modpacks = get_modpacks().await; + modpacks.push(modpack::ModpackEntry{id: id, name: name, last_updated: format!("{:?}", chrono::offset::Utc::now())}); + update_modpacks(modpacks).await; +} + +#[tauri::command] +pub async fn remove_pack(id: String){ + let mut modpacks = get_modpacks().await; + let mut index = 0; + for pack in modpacks.clone() { + if pack.id == id { + modpacks.remove(index); + break; + } + index += 1; + } + update_modpacks(modpacks).await; + { + let ref mut session = *SESSION.lock().await; + if let Some(session) = session{ + sftp::rmdir(session.clone(), PathBuf::from(format!("/ftp/{}", id))).await; + } + } +} + +#[tauri::command] +pub async fn update_pack(id: String, path: String, version: String){ + println!("Update modpack {}, to version {}, from file {}", id, version, path); } \ No newline at end of file diff --git a/FCLauncher/src-tauri/src/main.rs b/FCLauncher/src-tauri/src/main.rs index af1bae5..04c0a69 100644 --- a/FCLauncher/src-tauri/src/main.rs +++ b/FCLauncher/src-tauri/src/main.rs @@ -36,7 +36,7 @@ fn main() { //modpack::get_modpacks(); //prism::install_prism(); tauri::Builder::default() - .invoke_handler(tauri::generate_handler![greet, modpack::get_modpacks, modpack::launch_modpack, modpack::get_versions, prism::launch_prism, prism::install_prism, admin::login, admin::drop_session, admin::shift_up, admin::shift_down]) + .invoke_handler(tauri::generate_handler![greet, modpack::get_modpacks, modpack::launch_modpack, modpack::get_versions, modpack::get_latest_version, prism::launch_prism, prism::install_prism, admin::login, admin::drop_session, admin::shift_up, admin::shift_down, admin::add_pack, admin::remove_pack, admin::update_pack]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/FCLauncher/src-tauri/src/modpack.rs b/FCLauncher/src-tauri/src/modpack.rs index 40a52a9..4d349be 100644 --- a/FCLauncher/src-tauri/src/modpack.rs +++ b/FCLauncher/src-tauri/src/modpack.rs @@ -20,16 +20,16 @@ use crate::https; #[derive(Serialize, Deserialize, Clone)] pub struct ModpackEntry{ - name: String, + pub name: String, pub id: String, - last_updated: String + pub last_updated: String } #[derive(Serialize, Deserialize, Clone)] pub struct VersionEntry{ - version: String, - file: String, - date: String + pub Version: String, + pub File: String, + pub Date: String } async fn get_modpack_name(id: String) -> String { @@ -56,7 +56,7 @@ async fn check_modpack_needs_update(id: String) -> bool{ return false; } let versions = versions.unwrap(); - let latest = versions[versions.len()-1].version.clone(); + let latest = versions[versions.len()-1].Version.clone(); let mut file = File::open(get_local_data_directory().join("prism").join("instances").join(instance_name).join(".minecraft").join("version.txt")).unwrap(); let mut current = String::new(); @@ -84,10 +84,10 @@ async fn install_modpack(window: tauri::Window, id: String){ let versions = get_versions(id.clone()).await.unwrap(); let path = env::temp_dir().join(format!("{}.mrpack", get_modpack_name(id.clone()).await)); let mut file = File::create(path.clone()).unwrap(); - let ftp_path = PathBuf::new().join(id.clone()).join(versions[versions.len()-1].file.clone().as_str()); + let ftp_path = PathBuf::new().join(id.clone()).join(versions[versions.len()-1].File.clone().as_str()); println!("Downloading file {}", ftp_path.to_str().unwrap()); //ftp::ftp_retr(Some(window.clone()), ftp_path, &mut file, |window, data, size| util::download_callback(name.clone(), window, data, size)).unwrap(); - https::download(Some(window.clone()), format!("https://gitea.piwalker.net/fclauncher/{}/{}", id.clone(), versions[versions.len()-1].file.clone()), &mut file, get_modpack_name(id.clone()).await).await; + https::download(Some(window.clone()), format!("https://gitea.piwalker.net/fclauncher/{}/{}", id.clone(), versions[versions.len()-1].File.clone()), &mut file, get_modpack_name(id.clone()).await).await; let mut child = Command::new(get_local_data_directory().join("prism").join(get_prism_executable())).arg("-I").arg(path).spawn().unwrap(); loop { let version_path = get_local_data_directory().join("prism").join("instances").join(get_modpack_name(id.clone()).await).join(".minecraft").join("version.txt"); @@ -95,7 +95,7 @@ async fn install_modpack(window: tauri::Window, id: String){ let mut ver_file = File::open(version_path).unwrap(); let mut buf = String::new(); ver_file.read_to_string(&mut buf).unwrap(); - if buf == versions[versions.len()-1].version.clone().as_str() { + if buf == versions[versions.len()-1].Version.clone().as_str() { break; } } @@ -181,10 +181,10 @@ pub async fn get_versions(id: String) -> Result,String> { //ftp::ftp_retr(None, PathBuf::new().join(id).join("versions.json"), &mut buf, |_, _, _| return); https::download(None, format!("https://gitea.piwalker.net/fclauncher/{}/versions.json", id.clone()), &mut buf, format!("{}/versions.json", id.clone())).await; buf.rewind(); - let v: Value = serde_json::from_reader(buf).or(Err(format!("Unable to parse json")))?; - for version in v.as_array().unwrap() { - versions.push(VersionEntry{version: version["Version"].as_str().unwrap().to_string(), file: version["File"].as_str().unwrap().to_string(), date: version["Date"].as_str().unwrap().to_string()}); - } + let versions: Vec = serde_json::from_reader(buf).or(Err(format!("Unable to parse json")))?; + //for version in v.as_array().unwrap() { + //versions.push(VersionEntry{version: version["Version"].as_str().unwrap().to_string(), file: version["File"].as_str().unwrap().to_string(), date: version["Date"].as_str().unwrap().to_string()}); + //} return Ok(versions.clone()); } @@ -205,6 +205,15 @@ fn get_java_version(mc_version: &str) -> u8{ return java; } +#[tauri::command] +pub async fn get_latest_version(id: String) -> Result{ + let versions = get_versions(id).await.unwrap(); + if(versions.len() == 0){ + return Ok(format!("")); + } + Ok(versions[versions.len()-1].Version.clone()) +} + //pub fn create_json(modpacks: Vec) -> Result { diff --git a/FCLauncher/src-tauri/src/sftp.rs b/FCLauncher/src-tauri/src/sftp.rs index 26a666d..5eeb951 100644 --- a/FCLauncher/src-tauri/src/sftp.rs +++ b/FCLauncher/src-tauri/src/sftp.rs @@ -2,8 +2,10 @@ use std::io::prelude::*; use std::net::TcpStream; use std::path::Path; use std::path::PathBuf; +use futures_util::future::BoxFuture; use futures_util::io::BufReader; use futures_util::io::Cursor; +use futures_util::FutureExt; use ssh2::OpenFlags; use ssh2::Session; use ssh2::Sftp; @@ -40,4 +42,25 @@ pub async fn uplaod(window: Option, sess: Session, path: PathBuf, } } Ok(()) +} + +pub async fn mkdir(sess: Session, path: PathBuf) -> Result<(), String>{ + let sftp = sess.sftp().or(Err("Unable to open sftp session"))?; + sftp.mkdir(path.as_path(), 0o775).or(Err(format!("Unable to create directory")))?; + Ok(()) +} + +pub fn rmdir(sess: Session, path: PathBuf) -> BoxFuture<'static, ()>{ + async move { + let sftp = sess.sftp().or(Err("Unable to open sftp session")).unwrap(); + let dirs = sftp.readdir(path.as_path()).or(Err("unable to stat directory")).unwrap(); + for dir in dirs { + if dir.1.is_dir() { + rmdir(sess.clone(), dir.0).await; + }else { + sftp.unlink(dir.0.as_path()).or(Err(format!("Unable to delete file"))).unwrap(); + } + } + sftp.rmdir(path.as_path()).or(Err(format!("Unable to delete directory"))).unwrap(); + }.boxed() } \ No newline at end of file diff --git a/FCLauncher/src-tauri/tauri.conf.json b/FCLauncher/src-tauri/tauri.conf.json index f5e0c0c..de22266 100644 --- a/FCLauncher/src-tauri/tauri.conf.json +++ b/FCLauncher/src-tauri/tauri.conf.json @@ -25,7 +25,8 @@ }, "dialog": { "all": false, - "ask": true + "ask": true, + "open": true } }, "windows": [ diff --git a/FCLauncher/src/Admin.html b/FCLauncher/src/Admin.html index 02792f5..9bb0ca9 100644 --- a/FCLauncher/src/Admin.html +++ b/FCLauncher/src/Admin.html @@ -31,16 +31,17 @@ -
+
- + +
+
+ + +
-
-
- -
diff --git a/FCLauncher/src/admin.js b/FCLauncher/src/admin.js index f019af7..3ab5b55 100644 --- a/FCLauncher/src/admin.js +++ b/FCLauncher/src/admin.js @@ -1,10 +1,12 @@ const { invoke } = window.__TAURI__.tauri; const { listen } = window.__TAURI__.event; -const { ask } = window.__TAURI__.dialog; +const { ask, open } = window.__TAURI__.dialog; const downBar = document.querySelector(".progressFinished"); //import { listen } from '@tauri-apps/api'; var selected_pack = ""; +var update_menu = document.getElementById("update"); +var create_menu = document.getElementById("create"); const download_progress = listen("download_progress", (progress) => { console.log("Downloading"); @@ -28,8 +30,12 @@ const download_finished = listen("download_finished", (event) => { }); window.addEventListener("DOMContentLoaded", () => { + document.getElementById("browse").addEventListener("click", browse); + document.getElementById("update_pack").addEventListener("click", update_pack); document.getElementById("up").addEventListener("click", up); document.getElementById("down").addEventListener("click", down); + document.getElementById("add").addEventListener("click", add); + document.getElementById("remove").addEventListener("click", remove); document.getElementById("back").addEventListener("click", back); }); @@ -52,6 +58,8 @@ function back(){ } function refresh(){ + update_menu.style.display = "none"; + create_menu.style.display = "none"; invoke("get_modpacks").then(addModpacks); } @@ -67,15 +75,70 @@ function addModpacks(modpacks) { div.id = modpacks[i].id; if(modpacks[i].id == selected_pack){ div.classList.add("modpack-selected"); + update_menu.style.display = "flex"; + invoke("get_latest_version", {id: selected_pack}).then(update_version); } div.addEventListener("click", function() { modpackClick(modpacks[i].id) }); modpacks_list.appendChild(div); } + + var div = document.createElement("div"); + div.textContent = ""; + div.className = "modpack"; + div.id = "*new*"; + div.addEventListener("click", function() { modpackClick("*new*") }); + modpacks_list.appendChild(div); } function modpackClick(id){ var old = selected_pack; document.getElementById(id).classList.add("modpack-selected"); selected_pack = id; + if (old == id){ + selected_pack = ""; + update_menu.style.display = "none"; + create_menu.style.display = "none"; + }else if (id == "*new*"){ + update_menu.style.display = "none"; + create_menu.style.display = "flex"; + }else{ + update_menu.style.display = "flex"; + create_menu.style.display = "none"; + invoke("get_latest_version", {id: selected_pack}).then(update_version); + } document.getElementById(old).classList.remove("modpack-selected"); +} + +function add(){ + var id = document.getElementById("pack_id").value; + var name = document.getElementById("pack_name").value; + selected_pack = id; + invoke("add_pack", {id: id, name: name}).then(refresh); +} + +function remove(){ + invoke("remove_pack", {id: selected_pack}).then(refresh); +} + +async function browse(){ + const selected = await open ({ + multiple: false, + filters: [{ + name: 'Modrinth Modpack', + extensions: ['mrpack'] + }] + }); + if (selected != null){ + document.getElementById("file_path").value = selected; + } +} + +function update_version(version){ + document.getElementById("pack_version").value = version; +} + +function update_pack(){ + var version = document.getElementById("pack_version").value; + var path = document.getElementById("file_path").value; + invoke("update_pack", {id: selected_pack, path: path, version: version}).then(refresh); } \ No newline at end of file diff --git a/FCLauncher/src/styles.css b/FCLauncher/src/styles.css index ddb6f24..bfa4e0a 100644 --- a/FCLauncher/src/styles.css +++ b/FCLauncher/src/styles.css @@ -359,18 +359,16 @@ button { background-repeat: no-repeat; } -#add{ - background-image: url('assets/add.png'); - background-size:cover; - background-repeat: no-repeat; +#create{ + display: none; +} + +#update{ + display: none; } #remove{ background-image: url('assets/remove.png'); background-size:cover; background-repeat: no-repeat; -} - -#pack_name{ - width: 13em; } \ No newline at end of file