Skip to content

Commit fed2851

Browse files
authored
Fix for multi-version downloads (#125)
* fix: multi version downloads * fix: remove debug utils * fix: clippy
1 parent 5fb0025 commit fed2851

File tree

3 files changed

+74
-34
lines changed

3 files changed

+74
-34
lines changed

src-tauri/src/error/remote_access_error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub enum RemoteAccessError {
2323
ManifestDownloadFailed(StatusCode, String),
2424
OutOfSync,
2525
Cache(std::io::Error),
26+
CorruptedState,
2627
}
2728

2829
impl Display for RemoteAccessError {
@@ -81,6 +82,10 @@ impl Display for RemoteAccessError {
8182
"server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"
8283
),
8384
RemoteAccessError::Cache(error) => write!(f, "Cache Error: {error}"),
85+
RemoteAccessError::CorruptedState => write!(
86+
f,
87+
"Drop encountered a corrupted internal state. Please report this to the developers, with details of reproduction."
88+
),
8489
}
8590
}
8691
}

src-tauri/src/games/downloads/download_agent.rs

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ use crate::remote::requests::generate_url;
2222
use crate::remote::utils::{DROP_CLIENT_ASYNC, DROP_CLIENT_SYNC};
2323
use log::{debug, error, info, warn};
2424
use rayon::ThreadPoolBuilder;
25-
use std::collections::HashMap;
26-
use std::fs::{OpenOptions, create_dir_all};
25+
use std::collections::{HashMap, HashSet};
26+
use std::fs::{create_dir_all, OpenOptions};
2727
use std::path::{Path, PathBuf};
2828
use std::sync::mpsc::Sender;
2929
use std::sync::{Arc, Mutex};
@@ -242,12 +242,8 @@ impl GameDownloadAgent {
242242

243243
let mut buckets = Vec::new();
244244

245-
let mut current_bucket = DownloadBucket {
246-
game_id: game_id.clone(),
247-
version: self.version.clone(),
248-
drops: Vec::new(),
249-
};
250-
let mut current_bucket_size = 0;
245+
let mut current_buckets = HashMap::<String, DownloadBucket>::new();
246+
let mut current_bucket_sizes = HashMap::<String, usize>::new();
251247

252248
for (raw_path, chunk) in manifest {
253249
let path = base_path.join(Path::new(&raw_path));
@@ -282,28 +278,41 @@ impl GameDownloadAgent {
282278

283279
buckets.push(DownloadBucket {
284280
game_id: game_id.clone(),
285-
version: self.version.clone(),
281+
version: chunk.version_name.clone(),
286282
drops: vec![drop],
287283
});
288284

289285
continue;
290286
}
291287

292-
if current_bucket_size + *length >= TARGET_BUCKET_SIZE
288+
let current_bucket_size = current_bucket_sizes
289+
.entry(chunk.version_name.clone())
290+
.or_insert_with(|| 0);
291+
let c_version_name = chunk.version_name.clone();
292+
let c_game_id = game_id.clone();
293+
let current_bucket = current_buckets
294+
.entry(chunk.version_name.clone())
295+
.or_insert_with(|| DownloadBucket {
296+
game_id: c_game_id,
297+
version: c_version_name,
298+
drops: vec![],
299+
});
300+
301+
if *current_bucket_size + length >= TARGET_BUCKET_SIZE
293302
&& !current_bucket.drops.is_empty()
294303
{
295304
// Move current bucket into list and make a new one
296-
buckets.push(current_bucket);
297-
current_bucket = DownloadBucket {
305+
buckets.push(current_bucket.clone());
306+
*current_bucket = DownloadBucket {
298307
game_id: game_id.clone(),
299-
version: self.version.clone(),
300-
drops: Vec::new(),
308+
version: chunk.version_name.clone(),
309+
drops: vec![],
301310
};
302-
current_bucket_size = 0;
311+
*current_bucket_size = 0;
303312
}
304313

305314
current_bucket.drops.push(drop);
306-
current_bucket_size += *length;
315+
*current_bucket_size += *length;
307316
}
308317

309318
#[cfg(target_os = "linux")]
@@ -312,8 +321,10 @@ impl GameDownloadAgent {
312321
}
313322
}
314323

315-
if !current_bucket.drops.is_empty() {
316-
buckets.push(current_bucket);
324+
for (_, bucket) in current_buckets.into_iter() {
325+
if !bucket.drops.is_empty() {
326+
buckets.push(bucket);
327+
}
317328
}
318329

319330
info!("buckets: {}", buckets.len());
@@ -348,27 +359,46 @@ impl GameDownloadAgent {
348359
.build()
349360
.unwrap();
350361

362+
let buckets = self.buckets.lock().unwrap();
363+
364+
let mut download_contexts = HashMap::<String, DownloadContext>::new();
365+
366+
let versions = buckets
367+
.iter()
368+
.map(|e| &e.version)
369+
.collect::<HashSet<_>>()
370+
.into_iter().cloned()
371+
.collect::<Vec<String>>();
372+
373+
info!("downloading across these versions: {versions:?}");
374+
351375
let completed_contexts = Arc::new(boxcar::Vec::new());
352376
let completed_indexes_loop_arc = completed_contexts.clone();
353377

354-
let download_context = DROP_CLIENT_SYNC
355-
.post(generate_url(&["/api/v2/client/context"], &[]).unwrap())
356-
.json(&ManifestBody {
357-
game: self.id.clone(),
358-
version: self.version.clone(),
359-
})
360-
.header("Authorization", generate_authorization_header())
361-
.send()?;
378+
for version in versions {
379+
let download_context = DROP_CLIENT_SYNC
380+
.post(generate_url(&["/api/v2/client/context"], &[]).unwrap())
381+
.json(&ManifestBody {
382+
game: self.id.clone(),
383+
version: version.clone(),
384+
})
385+
.header("Authorization", generate_authorization_header())
386+
.send()?;
362387

363-
if download_context.status() != 200 {
364-
return Err(RemoteAccessError::InvalidResponse(download_context.json()?));
365-
}
388+
if download_context.status() != 200 {
389+
return Err(RemoteAccessError::InvalidResponse(download_context.json()?));
390+
}
366391

367-
let download_context = &download_context.json::<DownloadContext>()?;
392+
let download_context = download_context.json::<DownloadContext>()?;
393+
info!(
394+
"download context: ({}) {}",
395+
&version, download_context.context
396+
);
397+
download_contexts.insert(version, download_context);
398+
}
368399

369-
info!("download context: {}", download_context.context);
400+
let download_contexts = &download_contexts;
370401

371-
let buckets = self.buckets.lock().unwrap();
372402
pool.scope(|scope| {
373403
let context_map = self.context_map.lock().unwrap();
374404
for (index, bucket) in buckets.iter().enumerate() {
@@ -400,6 +430,11 @@ impl GameDownloadAgent {
400430

401431
let sender = self.sender.clone();
402432

433+
let download_context = download_contexts
434+
.get(&bucket.version)
435+
.ok_or(RemoteAccessError::CorruptedState)
436+
.unwrap();
437+
403438
scope.spawn(move |_| {
404439
// 3 attempts
405440
for i in 0..RETRY_COUNT {

src-tauri/src/games/downloads/manifest.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
22
use std::collections::HashMap;
33
use std::path::PathBuf;
44

5-
#[derive(Debug, Clone)]
5+
#[derive(Debug, Clone, Serialize)]
66
// Drops go in buckets
77
pub struct DownloadDrop {
88
pub index: usize,
@@ -14,7 +14,7 @@ pub struct DownloadDrop {
1414
pub permissions: u32,
1515
}
1616

17-
#[derive(Debug, Clone)]
17+
#[derive(Debug, Clone, Serialize)]
1818
pub struct DownloadBucket {
1919
pub game_id: String,
2020
pub version: String,

0 commit comments

Comments
 (0)