diff --git a/FCLauncher/src-tauri/Cargo.toml b/FCLauncher/src-tauri/Cargo.toml index ac06dba..d907a5f 100644 --- a/FCLauncher/src-tauri/Cargo.toml +++ b/FCLauncher/src-tauri/Cargo.toml @@ -24,6 +24,7 @@ self_update = "0.40.0" parking_lot = "0.12.3" reqwest = { version = "0.12.5", features = ["stream"] } futures-util = "0.3.30" +ssh2 = "0.9.4" [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 af9936f..2037578 100644 --- a/FCLauncher/src-tauri/src/admin.rs +++ b/FCLauncher/src-tauri/src/admin.rs @@ -1,13 +1,75 @@ -static USERNAME: parking_lot::Mutex = parking_lot::const_mutex(String::new()); -static PASSWORD: parking_lot::Mutex = parking_lot::const_mutex(String::new()); +use std::io::Cursor; +use std::path::PathBuf; + +use crate::sftp; +use crate::modpack; +use ssh2::Sftp; +use ssh2::Session; + +//static USERNAME: parking_lot::Mutex = parking_lot::const_mutex(String::new()); +//static PASSWORD: parking_lot::Mutex = parking_lot::const_mutex(String::new()); +static SESSION: tauri::async_runtime::Mutex> = tauri::async_runtime::Mutex::const_new(None); #[tauri::command] -pub fn login(username: String, password: String, window: tauri::Window) { - //if(test_cred(username.as_str(), password.as_str())){ +pub async fn login(username: String, password: String, window: tauri::Window) { + let res = sftp::connect(username, password); + if(res.is_ok()){ //*USERNAME.lock() = username; //*PASSWORD.lock() = password; - //window.emit("Login_Success", {}); - //}else{ + *SESSION.lock().await = Some(res.unwrap()); + window.emit("Login_Success", {}); + }else{ window.emit("Login_Failed", {}); - //} + } +} + +#[tauri::command] +pub async fn drop_session(){ + let ref mut session = *SESSION.lock().await; + if let Some(session) = session { + session.disconnect(None, "disconnecting", None); + } + *session = None; +} + +async fn update_modpacks(modpacks: Vec) -> Result<(), String>{ + let data = serde_json::to_string(&modpacks).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("/ftp/modpacks.json"), 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; + let mut index = 0; + for pack in modpacks.as_slice() { + if(pack.id == id){ + break; + } + index += 1; + } + if index != 0 { + modpacks.swap(index, index-1); + } + update_modpacks(modpacks).await; +} + +#[tauri::command] +pub async fn shift_down(id: String){ + let mut modpacks = modpack::get_modpacks().await; + let mut index = 0; + for pack in modpacks.as_slice() { + if(pack.id == id){ + break; + } + index += 1; + } + if index != modpacks.len()-1 { + modpacks.swap(index, index+1); + } + update_modpacks(modpacks).await; } \ No newline at end of file diff --git a/FCLauncher/src-tauri/src/https.rs b/FCLauncher/src-tauri/src/https.rs index 0b50b49..4072879 100644 --- a/FCLauncher/src-tauri/src/https.rs +++ b/FCLauncher/src-tauri/src/https.rs @@ -7,10 +7,10 @@ use serde::Serialize; #[derive(Clone, Serialize)] pub struct DownloadStatus { - downloaded: usize, - total: usize, - time_elapsed: usize, - download_name: String + pub downloaded: usize, + pub total: usize, + pub time_elapsed: usize, + pub download_name: String } diff --git a/FCLauncher/src-tauri/src/main.rs b/FCLauncher/src-tauri/src/main.rs index 3539a9d..af1bae5 100644 --- a/FCLauncher/src-tauri/src/main.rs +++ b/FCLauncher/src-tauri/src/main.rs @@ -17,6 +17,7 @@ mod util; mod modpack; mod admin; mod https; +mod sftp; #[derive(Serialize, Deserialize)] struct ModpackEntry{ @@ -35,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, prism::launch_prism, prism::install_prism, admin::login]) + .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]) .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 082331d..40a52a9 100644 --- a/FCLauncher/src-tauri/src/modpack.rs +++ b/FCLauncher/src-tauri/src/modpack.rs @@ -21,10 +21,11 @@ use crate::https; #[derive(Serialize, Deserialize, Clone)] pub struct ModpackEntry{ name: String, - id: String + pub id: String, + last_updated: String } -#[derive(Clone)] +#[derive(Serialize, Deserialize, Clone)] pub struct VersionEntry{ version: String, file: String, @@ -147,6 +148,7 @@ pub async fn get_modpacks() -> Vec { buf.rewind(); let res = serde_json::from_reader(buf); if !res.is_ok() { + println!("Result not ok!"); let paths = fs::read_dir(get_local_data_directory().join("prism").join("instances")).unwrap(); for path in paths { let path = path.unwrap(); @@ -158,27 +160,28 @@ pub async fn get_modpacks() -> Vec { continue; } - modpacks.push(ModpackEntry{name: name.clone(), id: name}) + modpacks.push(ModpackEntry{name: name.clone(), id: name, last_updated: format!("")}) } return modpacks.clone() } - let v: Value = res.unwrap(); - println!("{}", v[0]["name"]); - for pack in v.as_array().unwrap() { - modpacks.push(ModpackEntry{name: pack["name"].as_str().unwrap().to_string(), id: pack["id"].as_str().unwrap().to_string()}); - } + let modpacks: Vec = res.unwrap(); + //println!("{}", v[0].name); + //for pack in v.as_array().unwrap() { + //modpacks.push(ModpackEntry{name: pack["name"].as_str().unwrap().to_string(), id: pack["id"].as_str().unwrap().to_string(), last_updated: pack["last-updated"].as_str().unwrap().to_string()}); + //} //} return modpacks.clone(); //} } -async fn get_versions(id: String) -> Result,Box> { +#[tauri::command] +pub async fn get_versions(id: String) -> Result,String> { let mut versions: Vec = Vec::new(); let mut buf = Cursor::new(vec![]); //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)?; + 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()}); } @@ -201,3 +204,8 @@ fn get_java_version(mc_version: &str) -> u8{ } return java; } + + +//pub fn create_json(modpacks: Vec) -> Result { + +//} \ No newline at end of file diff --git a/FCLauncher/src-tauri/src/sftp.rs b/FCLauncher/src-tauri/src/sftp.rs new file mode 100644 index 0000000..26a666d --- /dev/null +++ b/FCLauncher/src-tauri/src/sftp.rs @@ -0,0 +1,43 @@ +use std::io::prelude::*; +use std::net::TcpStream; +use std::path::Path; +use std::path::PathBuf; +use futures_util::io::BufReader; +use futures_util::io::Cursor; +use ssh2::OpenFlags; +use ssh2::Session; +use ssh2::Sftp; + +use crate::https; + +pub fn connect(username: String, password: String) -> Result { + let tcp = TcpStream::connect("gitea.piwalker.net:22").or(Err(format!("Unable to connect to host")))?; + let mut sess = Session::new().or(Err(format!("Unable to creat stream")))?; + sess.set_tcp_stream(tcp); + sess.handshake().unwrap(); + sess.userauth_password(username.as_str(), password.as_str()).or(Err(format!("Invalid username or password")))?; + Ok(sess) +} + +pub async fn uplaod(window: Option, sess: Session, path: PathBuf, mut reader: impl Read, upload_name: String, total_size: usize) -> Result<(), String>{ + let sftp = sess.sftp().or(Err("unable to open sftp session"))?; + let mut file = sftp.create(path.as_path()).or(Err("Unable to open file"))?; + let mut uploaded = 0; + while true { + let mut buf = vec![0u8; 1024]; + let res = reader.read(&mut buf).unwrap(); + if res <= 0 { + if let Some(window) = window.clone() { + window.emit("download_finished", true).or(Err(format!("Unable to signal window!")))?; + } + break; + } + file.write_all(buf.split_at(res).0).unwrap(); + uploaded += res; + println!("Uploading {} {}MB / {}MB", upload_name, uploaded/(1024*1024), total_size/(1024*1024)); + if let Some(window) = window.clone() { + window.emit("download_progress", https::DownloadStatus{downloaded: uploaded as usize, total: total_size as usize, time_elapsed: 0, download_name: upload_name.clone()}).or(Err(format!("Unable to signal window")))?; + } + } + Ok(()) +} \ No newline at end of file diff --git a/FCLauncher/src/Admin.html b/FCLauncher/src/Admin.html index b3cfc72..02792f5 100644 --- a/FCLauncher/src/Admin.html +++ b/FCLauncher/src/Admin.html @@ -23,8 +23,24 @@ -
-

Administration

+
+
+
+
+ + + +
+
+ + + + +
+
+
+ +
diff --git a/FCLauncher/src/admin.js b/FCLauncher/src/admin.js index d9f5c3b..f019af7 100644 --- a/FCLauncher/src/admin.js +++ b/FCLauncher/src/admin.js @@ -4,6 +4,8 @@ const { ask } = window.__TAURI__.dialog; const downBar = document.querySelector(".progressFinished"); //import { listen } from '@tauri-apps/api'; +var selected_pack = ""; + const download_progress = listen("download_progress", (progress) => { console.log("Downloading"); //console.log("Downloaded "+progress.payload.downloaded/(1024*1024) +"MB / " + progress.payload.total/(1024*1024) + "MB"); @@ -26,11 +28,54 @@ const download_finished = listen("download_finished", (event) => { }); window.addEventListener("DOMContentLoaded", () => { - + document.getElementById("up").addEventListener("click", up); + document.getElementById("down").addEventListener("click", down); document.getElementById("back").addEventListener("click", back); }); +window.onload = async function() { + refresh(); +} + +function up(){ + invoke("shift_up", { id: selected_pack }).then(refresh); +} + +function down(){ + invoke("shift_down", { id: selected_pack }).then(refresh); +} + function back(){ + invoke("drop_session"); window.location.href = "index.html"; +} + +function refresh(){ + invoke("get_modpacks").then(addModpacks); +} + +function addModpacks(modpacks) { + var modpacks_list = document.getElementById("modpacks"); + while (modpacks_list.firstChild) { + modpacks_list.removeChild(modpacks_list.lastChild); + } + for (let i = 0; i < modpacks.length; i++){ + var div = document.createElement("div"); + div.textContent = modpacks[i].name; + div.className = "modpack"; + div.id = modpacks[i].id; + if(modpacks[i].id == selected_pack){ + div.classList.add("modpack-selected"); + } + div.addEventListener("click", function() { modpackClick(modpacks[i].id) }); + modpacks_list.appendChild(div); + } +} + +function modpackClick(id){ + var old = selected_pack; + document.getElementById(id).classList.add("modpack-selected"); + selected_pack = id; + document.getElementById(old).classList.remove("modpack-selected"); } \ No newline at end of file diff --git a/FCLauncher/src/assets/add.png b/FCLauncher/src/assets/add.png new file mode 100644 index 0000000..eeb1e23 Binary files /dev/null and b/FCLauncher/src/assets/add.png differ diff --git a/FCLauncher/src/assets/add.svg b/FCLauncher/src/assets/add.svg new file mode 100644 index 0000000..9e2743c --- /dev/null +++ b/FCLauncher/src/assets/add.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/FCLauncher/src/assets/down.png b/FCLauncher/src/assets/down.png new file mode 100644 index 0000000..66c4f5e Binary files /dev/null and b/FCLauncher/src/assets/down.png differ diff --git a/FCLauncher/src/assets/remove.png b/FCLauncher/src/assets/remove.png new file mode 100644 index 0000000..dc36fcc Binary files /dev/null and b/FCLauncher/src/assets/remove.png differ diff --git a/FCLauncher/src/assets/up.png b/FCLauncher/src/assets/up.png new file mode 100644 index 0000000..80634c2 Binary files /dev/null and b/FCLauncher/src/assets/up.png differ diff --git a/FCLauncher/src/login.js b/FCLauncher/src/login.js index f959680..137b754 100644 --- a/FCLauncher/src/login.js +++ b/FCLauncher/src/login.js @@ -14,6 +14,7 @@ window.addEventListener("DOMContentLoaded", () => { }); function back(){ + invoke("drop_session"); window.location.href = "index.html"; } diff --git a/FCLauncher/src/main.js b/FCLauncher/src/main.js index 9c73265..c8ecca9 100644 --- a/FCLauncher/src/main.js +++ b/FCLauncher/src/main.js @@ -69,8 +69,8 @@ window.onload = async function() { function addModpacks(modpacks) { var dropdown = document.getElementById("Modpacks"); - modpacks.sort((a, b) => a.name.localeCompare(b.name)); - modpacks.reverse(); + //modpacks.sort((a, b) => a.name.localeCompare(b.name)); + //modpacks.reverse(); for (let i = 0; i < modpacks.length; i++){ var opt = document.createElement("option"); opt.text = modpacks[i].name; diff --git a/FCLauncher/src/styles.css b/FCLauncher/src/styles.css index c704f69..ddb6f24 100644 --- a/FCLauncher/src/styles.css +++ b/FCLauncher/src/styles.css @@ -27,6 +27,39 @@ text-align: center; } +.vertical { + margin: 0; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + gap: 1em; +} + +.container-horizontal { + margin: 0; + padding-top: 30px; + display: flex; + flex-direction: row; + justify-content: center; + text-align: center; + align-items: left; + justify-content: left; + gap: 20px; +} + +.horizontal-input { + margin: 0; + margin-left: 5px; + display: flex; + flex-direction: row; + justify-content: center; + text-align: center; + align-items: left; + justify-content: left; + gap: 20px; +} + body { margin: 0px; padding: 0px; @@ -273,4 +306,71 @@ button { background-color: red; margin: 0; visibility: hidden; +} + +#modpacks{ + width: 30%; + height: 13em; + background-color: #666565; + overflow: auto; + margin-left: 10px; + border: 1px solid; +} + +.modpack { + width: 100%; +} + +.modpack:hover { + background-color: lightgray; + color: black; +} + +.modpack-selected { + background-color: blue; + color: white; +} + +.vertical-buttons { + display: flex; + margin-left: 0.5em; + margin-top: 0; + flex-direction: column; + height: 15em; + gap: 1.5em; +} + +.square-button { + width: 2em; + height: 2.5em; + top: 5px; + left: 5px; +} + +#up{ + background-image: url('assets/up.png'); + background-size:cover; + background-repeat: no-repeat; +} + +#down{ + background-image: url('assets/down.png'); + background-size:cover; + background-repeat: no-repeat; +} + +#add{ + background-image: url('assets/add.png'); + background-size:cover; + background-repeat: no-repeat; +} + +#remove{ + background-image: url('assets/remove.png'); + background-size:cover; + background-repeat: no-repeat; +} + +#pack_name{ + width: 13em; } \ No newline at end of file