diff --git a/pages/store/index.vue b/pages/store/index.vue
index a236007..486b796 100644
--- a/pages/store/index.vue
+++ b/pages/store/index.vue
@@ -1,25 +1,38 @@
-
-
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index d4d4a6a..622e924 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -1039,6 +1039,7 @@ dependencies = [
"tauri-plugin-single-instance",
"tokio",
"url",
+ "urlencoding",
"uuid",
"versions",
"webbrowser",
@@ -4815,6 +4816,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
[[package]]
name = "urlpattern"
version = "0.3.0"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 46dd52f..edbcbbb 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -39,6 +39,7 @@ env_logger = "0.11.5"
http = "1.1.0"
tokio = { version = "1.40.0", features = ["rt", "tokio-macros"] }
versions = { version = "6.3.2", features = ["serde"] }
+urlencoding = "2.1.3"
[dependencies.uuid]
version = "1.10.0"
diff --git a/src-tauri/src/downloads/download_files.rs b/src-tauri/src/downloads/download_files.rs
deleted file mode 100644
index 97e3aea..0000000
--- a/src-tauri/src/downloads/download_files.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-use std::fs::File;
-use std::io::{Seek, SeekFrom, Write};
-use std::os::unix::fs::MetadataExt;
-use std::sync::{Arc, Mutex};
-use log::info;
-use uuid::Bytes;
-use crate::auth::generate_authorization_header;
-use crate::DB;
-use crate::db::DatabaseImpls;
-use crate::downloads::manifest::DropDownloadContext;
-
-const CHUNK_SIZE: u64 = 1024 * 1024 * 64;
-pub fn download_game_chunk(ctx: DropDownloadContext) {
- let base_url = DB.fetch_base_url();
-
-
- let client = reqwest::blocking::Client::new();
- let chunk_url = base_url.join(
- &format!(
- "/api/v1/client/chunk?id={}&version={}&name={}&chunk={}",
- ctx.game_id,
- ctx.version,
- ctx.file_name,
- ctx.index
- )).unwrap();
-
- let header = generate_authorization_header();
-
- let response = client
- .get(chunk_url)
- .header("Authorization", header)
- .send()
- .unwrap();
- let response_data = response.bytes().unwrap();
-
- write_to_file(ctx.file, ctx.index as u64, response_data.to_vec());
-}
-
-fn write_to_file(file: Arc>, index: u64, data: Vec) {
- let mut lock = file.lock().unwrap();
-
- if index != 0 {
- lock.seek(SeekFrom::Start(index * CHUNK_SIZE)).expect("Failed to seek to file offset");
- }
-
- lock.write_all(&data).unwrap();
-}
\ No newline at end of file
diff --git a/src-tauri/src/downloads/download_logic.rs b/src-tauri/src/downloads/download_logic.rs
new file mode 100644
index 0000000..bbd1c64
--- /dev/null
+++ b/src-tauri/src/downloads/download_logic.rs
@@ -0,0 +1,40 @@
+use crate::auth::generate_authorization_header;
+use crate::db::DatabaseImpls;
+use crate::downloads::manifest::DropDownloadContext;
+use crate::DB;
+use log::info;
+use urlencoding::encode;
+use std::io::{BufWriter, Seek, SeekFrom, Write};
+
+pub fn download_game_chunk(ctx: DropDownloadContext) {
+ let base_url = DB.fetch_base_url();
+
+ let client = reqwest::blocking::Client::new();
+ let chunk_url = base_url
+ .join(&format!(
+ "/api/v1/client/chunk?id={}&version={}&name={}&chunk={}",
+ // Encode the parts we don't trust
+ ctx.game_id, encode(&ctx.version), encode(&ctx.file_name), ctx.index
+ ))
+ .unwrap();
+
+ let header = generate_authorization_header();
+
+ let mut response = client
+ .get(chunk_url)
+ .header("Authorization", header)
+ .send()
+ .unwrap();
+
+ let mut file_lock = ctx.file.lock().unwrap();
+
+ if ctx.offset != 0 {
+ file_lock
+ .seek(SeekFrom::Start(ctx.offset))
+ .expect("Failed to seek to file offset");
+ }
+
+ let mut stream = BufWriter::with_capacity(1024, file_lock.try_clone().unwrap());
+
+ response.copy_to(&mut stream).unwrap();
+}
diff --git a/src-tauri/src/downloads/game_download.rs b/src-tauri/src/downloads/download_manager.rs
similarity index 67%
rename from src-tauri/src/downloads/game_download.rs
rename to src-tauri/src/downloads/download_manager.rs
index 331c34d..be0be95 100644
--- a/src-tauri/src/downloads/game_download.rs
+++ b/src-tauri/src/downloads/download_manager.rs
@@ -1,19 +1,19 @@
-use std::fs::File;
-use std::path::Path;
-use std::sync::{Arc, Mutex};
-use std::sync::atomic::AtomicUsize;
-use log::info;
-use serde::{Deserialize, Serialize};
-use crate::{AppState, DB};
use crate::auth::generate_authorization_header;
use crate::db::{DatabaseImpls, DATA_ROOT_DIR};
-use crate::downloads::download_files;
+use crate::downloads::download_logic;
use crate::downloads::manifest::{DropDownloadContext, DropManifest};
use crate::downloads::progress::ProgressChecker;
+use crate::{AppState, DB};
+use log::info;
+use serde::{Deserialize, Serialize};
+use std::fs::{create_dir_all, File};
+use std::path::Path;
+use std::sync::atomic::AtomicUsize;
+use std::sync::{Arc, Mutex};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
-pub struct GameDownload {
+pub struct GameDownloadManager {
id: String,
version: String,
progress: Arc,
@@ -40,14 +40,14 @@ pub enum GameDownloadError {
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
pub enum SystemError {
- MutexLockFailed
+ MutexLockFailed,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub struct GameChunkCtx {
chunk_id: usize,
}
-impl GameDownload {
+impl GameDownloadManager {
pub fn new(id: String, version: String) -> Self {
Self {
id,
@@ -64,11 +64,16 @@ impl GameDownload {
}
self.ensure_manifest_exists().await
}
- pub async fn download(&self, max_threads: usize, contexts: Vec) -> Result<(), GameDownloadError> {
+ pub fn begin_download(
+ &self,
+ max_threads: usize,
+ contexts: Vec,
+ ) -> Result<(), GameDownloadError> {
let progress = Arc::new(AtomicUsize::new(0));
self.change_state(GameDownloadState::Downloading);
- let progress = ProgressChecker::new(Box::new(download_files::download_game_chunk), progress);
- progress.run_contexts_parallel_async(contexts, max_threads).await;
+ let progress =
+ ProgressChecker::new(Box::new(download_logic::download_game_chunk), progress);
+ progress.run_contexts_parallel(contexts, max_threads);
Ok(())
}
pub async fn ensure_manifest_exists(&self) -> Result<(), GameDownloadError> {
@@ -85,10 +90,9 @@ impl GameDownload {
.join(
format!(
"/api/v1/client/metadata/manifest?id={}&version={}",
- self.id,
- self.version
+ self.id, self.version
)
- .as_str()
+ .as_str(),
)
.unwrap();
@@ -109,10 +113,11 @@ impl GameDownload {
}
let manifest_download = response.json::().await.unwrap();
- info!("Manifest: {:?}", manifest_download);
if let Ok(mut manifest) = self.manifest.lock() {
*manifest = Some(manifest_download)
- } else { return Err(GameDownloadError::System(SystemError::MutexLockFailed)); }
+ } else {
+ return Err(GameDownloadError::System(SystemError::MutexLockFailed));
+ }
Ok(())
}
@@ -122,26 +127,38 @@ impl GameDownload {
*lock = state;
}
}
-pub fn to_contexts(manifest: &DropManifest, version: String, game_id: String) -> Vec {
+pub fn generate_job_contexts(
+ manifest: &DropManifest,
+ version: String,
+ game_id: String,
+) -> Vec {
let mut contexts = Vec::new();
- let base_path = DATA_ROOT_DIR.clone();
- for key in manifest {
- let path = base_path.join(Path::new(key.0));
- let file = Arc::new(Mutex::new(File::create(path).unwrap()));
- for i in 0..key.1.ids.len() {
- contexts.push(DropDownloadContext {
- file_chunk: Arc::new(key.1.clone()),
+ let base_path = DATA_ROOT_DIR.join("games").join(game_id.clone()).clone();
+ create_dir_all(base_path.clone()).unwrap();
+ for (raw_path, chunk) in manifest {
+ let path = base_path.join(Path::new(raw_path));
- file_name: key.0.clone(),
+ let container = path.parent().unwrap();
+ create_dir_all(container).unwrap();
+
+ let file = Arc::new(Mutex::new(File::create(path).unwrap()));
+ let mut running_offset = 0;
+
+ for i in 0..chunk.ids.len() {
+ if i == 1 {
+ info!("woah a chunk bigger than 1")
+ }
+ contexts.push(DropDownloadContext {
+ file_name: raw_path.to_string(),
version: version.to_string(),
+ offset: running_offset,
index: i,
game_id: game_id.to_string(),
file: file.clone(),
});
-
+ running_offset += chunk.lengths[i] as u64;
}
}
- info!("Contexts: {:?}", contexts);
contexts
}
@@ -154,40 +171,21 @@ pub async fn start_game_download(
) -> Result<(), GameDownloadError> {
info!("Triggered Game Download");
- let download = Arc::new(GameDownload::new(game_id.clone(), game_version.clone()));
+ let download_manager = Arc::new(GameDownloadManager::new(
+ game_id.clone(),
+ game_version.clone(),
+ ));
- download.ensure_manifest_exists().await?;
+ download_manager.ensure_manifest_exists().await?;
let local_manifest = {
- let manifest = download.manifest.lock().unwrap();
+ let manifest = download_manager.manifest.lock().unwrap();
(*manifest).clone().unwrap()
};
- let contexts = to_contexts(&local_manifest, game_version.clone(), game_id);
+ let contexts = generate_job_contexts(&local_manifest, game_version.clone(), game_id);
- let _ = download.download(max_threads, contexts).await;
+ let _ = download_manager.begin_download(max_threads, contexts);
Ok(())
-
- /*
- let Some(unlocked) = manifest else { return Err(GameDownloadError::ManifestDoesNotExist) };
- let lock = unlocked.lock().unwrap();
-
- let chunks = lock.parse_to_chunks();
-
- /*
- let manifest = match d.manifest {
- Some(lock) => {
- let lock = lock.lock().unwrap();
- lock.parse_to_chunks()
- },
- None => { return Err(GameDownloadError::ManifestDoesNotExist) }
- };
- */
-
- app_state.game_downloads.push(download.clone());
- download.download(max_threads, chunks).await
-
- */
}
-
diff --git a/src-tauri/src/downloads/downloads.rs b/src-tauri/src/downloads/downloads.rs
deleted file mode 100644
index 8850ac6..0000000
--- a/src-tauri/src/downloads/downloads.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-/* GENERAL OUTLINE
-When downloading any game, the following details must be provided to the server:
- - Game ID
- - User token
- - TBC
-
-The steps to then download a game are as follows:
- 1. User requests
- */
-
-
diff --git a/src-tauri/src/downloads/manifest.rs b/src-tauri/src/downloads/manifest.rs
index a646179..7feb45c 100644
--- a/src-tauri/src/downloads/manifest.rs
+++ b/src-tauri/src/downloads/manifest.rs
@@ -14,10 +14,10 @@ pub struct DropChunk {
#[derive(Debug, Clone)]
pub struct DropDownloadContext {
- pub file_chunk: Arc,
pub file_name: String,
pub version: String,
pub index: usize,
+ pub offset: u64,
pub game_id: String,
pub file: Arc>
}
\ No newline at end of file
diff --git a/src-tauri/src/downloads/mod.rs b/src-tauri/src/downloads/mod.rs
index 907902b..a3129e7 100644
--- a/src-tauri/src/downloads/mod.rs
+++ b/src-tauri/src/downloads/mod.rs
@@ -1,5 +1,4 @@
-mod downloads;
mod manifest;
pub mod progress;
-pub mod game_download;
-mod download_files;
\ No newline at end of file
+pub mod download_manager;
+mod download_logic;
\ No newline at end of file
diff --git a/src-tauri/src/downloads/progress.rs b/src-tauri/src/downloads/progress.rs
index e8176f9..6194a90 100644
--- a/src-tauri/src/downloads/progress.rs
+++ b/src-tauri/src/downloads/progress.rs
@@ -30,19 +30,6 @@ where T: Send + Sync
self.counter.fetch_add(1, Ordering::Relaxed);
}
}
- pub async fn run_contexts_parallel_async(&self, contexts: Vec, max_threads: usize) {
- let threads = ThreadPoolBuilder::new()
- // If max_threads == 0, then the limit will be determined
- // by Rayon's internal RAYON_NUM_THREADS
- .num_threads(max_threads)
- .build()
- .unwrap();
-
- for context in contexts {
- let f = self.f.clone();
- threads.spawn(move || f(context));
- }
- }
pub fn run_contexts_parallel(&self, contexts: Vec, max_threads: usize) {
let threads = ThreadPoolBuilder::new()
// If max_threads == 0, then the limit will be determined
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index b4043c9..a793c0f 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -22,7 +22,7 @@ use std::{
use std::sync::Arc;
use tauri_plugin_deep_link::DeepLinkExt;
use crate::db::DatabaseImpls;
-use crate::downloads::game_download::{start_game_download, GameDownload};
+use crate::downloads::download_manager::{start_game_download, GameDownloadManager};
#[derive(Clone, Copy, Serialize)]
pub enum AppStatus {
@@ -47,7 +47,7 @@ pub struct AppState {
status: AppStatus,
user: Option,
games: HashMap,
- game_downloads: Vec>
+ game_downloads: Vec>
}
#[tauri::command]