mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2026-07-11 21:44:34 +10:00
feat: Resume button and PartiallyInstalled status
Signed-off-by: quexeky <git@quexeky.dev>
This commit is contained in:
@@ -1,78 +1,52 @@
|
||||
<template>
|
||||
<!-- Do not add scale animations to this: https://stackoverflow.com/a/35683068 -->
|
||||
<div class="inline-flex divide-x divide-zinc-900">
|
||||
<button
|
||||
type="button"
|
||||
@click="() => buttonActions[props.status.type]()"
|
||||
:class="[
|
||||
styles[props.status.type],
|
||||
showDropdown ? 'rounded-l-md' : 'rounded-md',
|
||||
'inline-flex uppercase font-display items-center gap-x-2 px-4 py-3 text-md font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||
]"
|
||||
>
|
||||
<component
|
||||
:is="buttonIcons[props.status.type]"
|
||||
class="-mr-0.5 size-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<button type="button" @click="() => buttonActions[props.status.type]()" :class="[
|
||||
styles[props.status.type],
|
||||
showDropdown ? 'rounded-l-md' : 'rounded-md',
|
||||
'inline-flex uppercase font-display items-center gap-x-2 px-4 py-3 text-md font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||
]">
|
||||
<component :is="buttonIcons[props.status.type]" class="-mr-0.5 size-5" aria-hidden="true" />
|
||||
{{ buttonNames[props.status.type] }}
|
||||
</button>
|
||||
<Menu
|
||||
v-if="showDropdown"
|
||||
as="div"
|
||||
class="relative inline-block text-left grow"
|
||||
>
|
||||
<Menu v-if="showDropdown" as="div" class="relative inline-block text-left grow">
|
||||
<div class="h-full">
|
||||
<MenuButton
|
||||
:class="[
|
||||
styles[props.status.type],
|
||||
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm group',
|
||||
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||
]"
|
||||
>
|
||||
<MenuButton :class="[
|
||||
styles[props.status.type],
|
||||
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm group',
|
||||
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||
]">
|
||||
<ChevronDownIcon class="size-5" aria-hidden="true" />
|
||||
</MenuButton>
|
||||
</div>
|
||||
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-100"
|
||||
enter-from-class="transform opacity-0 scale-95"
|
||||
enter-to-class="transform opacity-100 scale-100"
|
||||
leave-active-class="transition ease-in duration-75"
|
||||
leave-from-class="transform opacity-100 scale-100"
|
||||
leave-to-class="transform opacity-0 scale-95"
|
||||
>
|
||||
<transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95"
|
||||
enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75"
|
||||
leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
|
||||
<MenuItems
|
||||
class="absolute right-0 z-[500] mt-2 w-32 origin-top-right rounded-md bg-zinc-900 shadow-lg ring-1 ring-zinc-100/5 focus:outline-none"
|
||||
>
|
||||
class="absolute right-0 z-[500] mt-2 w-32 origin-top-right rounded-md bg-zinc-900 shadow-lg ring-1 ring-zinc-100/5 focus:outline-none">
|
||||
<div class="py-1">
|
||||
<MenuItem v-slot="{ active }">
|
||||
<button
|
||||
@click="() => emit('options')"
|
||||
:class="[
|
||||
active
|
||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||
: 'text-zinc-400',
|
||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||
]"
|
||||
>
|
||||
Options
|
||||
<Cog6ToothIcon class="size-5" />
|
||||
</button>
|
||||
<button @click="() => emit('options')" :class="[
|
||||
active
|
||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||
: 'text-zinc-400',
|
||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||
]">
|
||||
Options
|
||||
<Cog6ToothIcon class="size-5" />
|
||||
</button>
|
||||
</MenuItem>
|
||||
<MenuItem v-slot="{ active }">
|
||||
<button
|
||||
@click="() => emit('uninstall')"
|
||||
:class="[
|
||||
active
|
||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||
: 'text-zinc-400',
|
||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||
]"
|
||||
>
|
||||
Uninstall
|
||||
<TrashIcon class="size-5" />
|
||||
</button>
|
||||
<button @click="() => emit('uninstall')" :class="[
|
||||
active
|
||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||
: 'text-zinc-400',
|
||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||
]">
|
||||
Uninstall
|
||||
<TrashIcon class="size-5" />
|
||||
</button>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</MenuItems>
|
||||
@@ -164,7 +138,7 @@ const buttonActions: { [key in GameStatusEnum]: () => void } = {
|
||||
[GameStatusEnum.SetupRequired]: () => emit("launch"),
|
||||
[GameStatusEnum.Installed]: () => emit("launch"),
|
||||
[GameStatusEnum.Updating]: () => emit("queue"),
|
||||
[GameStatusEnum.Uninstalling]: () => {},
|
||||
[GameStatusEnum.Uninstalling]: () => { },
|
||||
[GameStatusEnum.Running]: () => emit("kill"),
|
||||
[GameStatusEnum.PartiallyInstalled]: () => emit("resume")
|
||||
};
|
||||
|
||||
@@ -250,7 +250,9 @@ impl DownloadManagerBuilder {
|
||||
}
|
||||
// Ok(false) is for incomplete but exited properly
|
||||
Ok(false) => {
|
||||
debug!("Donwload agent finished incomplete");
|
||||
download_agent.on_incomplete(&app_handle);
|
||||
|
||||
}
|
||||
Err(e) => {
|
||||
error!("download {:?} has error {}", download_agent.metadata(), &e);
|
||||
|
||||
@@ -44,7 +44,6 @@ pub fn fetch_game(
|
||||
game_id,
|
||||
state
|
||||
);
|
||||
println!("Res: {:?}", &res);
|
||||
res
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,15 @@ use crate::download_manager::util::progress_object::{ProgressHandle, ProgressObj
|
||||
use crate::error::application_download_error::ApplicationDownloadError;
|
||||
use crate::error::remote_access_error::RemoteAccessError;
|
||||
use crate::games::downloads::manifest::{DropDownloadContext, DropManifest};
|
||||
use crate::games::library::{on_game_complete, push_game_update, GameUpdateEvent};
|
||||
use crate::games::library::{
|
||||
on_game_complete, on_game_incomplete, push_game_update, GameUpdateEvent,
|
||||
};
|
||||
use crate::remote::requests::make_request;
|
||||
use crate::DB;
|
||||
use log::{debug, error, info};
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use slice_deque::SliceDeque;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{create_dir_all, File, OpenOptions};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::Sender;
|
||||
@@ -36,7 +39,7 @@ pub struct GameDownloadAgent {
|
||||
pub version: String,
|
||||
pub control_flag: DownloadThreadControl,
|
||||
contexts: Mutex<Vec<DropDownloadContext>>,
|
||||
completed_contexts: Mutex<SliceDeque<String>>,
|
||||
context_map: Mutex<HashMap<String, bool>>,
|
||||
pub manifest: Mutex<Option<DropManifest>>,
|
||||
pub progress: Arc<ProgressObject>,
|
||||
sender: Sender<DownloadManagerSignal>,
|
||||
@@ -78,7 +81,7 @@ impl GameDownloadAgent {
|
||||
control_flag,
|
||||
manifest: Mutex::new(None),
|
||||
contexts: Mutex::new(Vec::new()),
|
||||
completed_contexts: Mutex::new(SliceDeque::new()),
|
||||
context_map: Mutex::new(HashMap::new()),
|
||||
progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
|
||||
sender,
|
||||
stored_manifest,
|
||||
@@ -183,11 +186,15 @@ impl GameDownloadAgent {
|
||||
}
|
||||
|
||||
pub fn ensure_contexts(&self) -> Result<(), ApplicationDownloadError> {
|
||||
if !self.contexts.lock().unwrap().is_empty() {
|
||||
return Ok(());
|
||||
if self.contexts.lock().unwrap().is_empty() {
|
||||
self.generate_contexts()?;
|
||||
}
|
||||
|
||||
self.generate_contexts()?;
|
||||
self.context_map
|
||||
.lock()
|
||||
.unwrap()
|
||||
.extend(self.stored_manifest.get_contexts());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -199,13 +206,6 @@ impl GameDownloadAgent {
|
||||
let base_path = Path::new(&self.stored_manifest.base_path);
|
||||
create_dir_all(base_path).unwrap();
|
||||
|
||||
{
|
||||
let mut completed_contexts_lock = self.completed_contexts.lock().unwrap();
|
||||
completed_contexts_lock.clear();
|
||||
completed_contexts_lock
|
||||
.extend_from_slice(&self.stored_manifest.get_completed_contexts());
|
||||
}
|
||||
|
||||
for (raw_path, chunk) in manifest {
|
||||
let path = base_path.join(Path::new(&raw_path));
|
||||
|
||||
@@ -240,6 +240,14 @@ impl GameDownloadAgent {
|
||||
let _ = fallocate(file, FallocateFlags::empty(), 0, running_offset);
|
||||
}
|
||||
}
|
||||
let existing_contexts = self.stored_manifest.get_completed_contexts();
|
||||
self.stored_manifest.set_contexts(
|
||||
&contexts
|
||||
.iter()
|
||||
.map(|x| (x.checksum.clone(), existing_contexts.contains(&x.checksum)))
|
||||
.collect::<Vec<(String, bool)>>(),
|
||||
);
|
||||
|
||||
*self.contexts.lock().unwrap() = contexts;
|
||||
|
||||
Ok(())
|
||||
@@ -258,13 +266,14 @@ impl GameDownloadAgent {
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let completed_indexes = Arc::new(boxcar::Vec::new());
|
||||
let completed_indexes_loop_arc = completed_indexes.clone();
|
||||
let completed_contexts = Arc::new(boxcar::Vec::new());
|
||||
let completed_indexes_loop_arc = completed_contexts.clone();
|
||||
|
||||
let contexts = self.contexts.lock().unwrap();
|
||||
debug!("{:#?}", contexts);
|
||||
pool.scope(|scope| {
|
||||
let client = &reqwest::blocking::Client::new();
|
||||
let context_map = self.context_map.lock().unwrap();
|
||||
for (index, context) in contexts.iter().enumerate() {
|
||||
let client = client.clone();
|
||||
let completed_indexes = completed_indexes_loop_arc.clone();
|
||||
@@ -273,12 +282,7 @@ impl GameDownloadAgent {
|
||||
let progress_handle = ProgressHandle::new(progress, self.progress.clone());
|
||||
|
||||
// If we've done this one already, skip it
|
||||
if self
|
||||
.completed_contexts
|
||||
.lock()
|
||||
.unwrap()
|
||||
.contains(&context.checksum)
|
||||
{
|
||||
if Some(&true) == context_map.get(&context.checksum) {
|
||||
progress_handle.skip(context.length);
|
||||
continue;
|
||||
}
|
||||
@@ -334,23 +338,33 @@ impl GameDownloadAgent {
|
||||
}
|
||||
});
|
||||
|
||||
let newly_completed = completed_indexes.to_owned();
|
||||
let newly_completed = completed_contexts.to_owned();
|
||||
|
||||
let completed_lock_len = {
|
||||
let mut completed_contexts_lock = self.completed_contexts.lock().unwrap();
|
||||
let mut context_map_lock = self.context_map.lock().unwrap();
|
||||
for (_, item) in newly_completed.iter() {
|
||||
completed_contexts_lock.push_front(item.clone());
|
||||
context_map_lock.insert(item.clone(), true);
|
||||
}
|
||||
|
||||
completed_contexts_lock.len()
|
||||
context_map_lock.values().filter(|x| **x).count()
|
||||
};
|
||||
let context_map_lock = self.context_map.lock().unwrap();
|
||||
let contexts = contexts
|
||||
.iter()
|
||||
.map(|x| {
|
||||
(
|
||||
x.checksum.clone(),
|
||||
context_map_lock.get(&x.checksum).cloned().or(Some(false)).unwrap(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(String, bool)>>();
|
||||
drop(context_map_lock);
|
||||
|
||||
self.stored_manifest
|
||||
.set_completed_contexts(self.completed_contexts.lock().unwrap().as_slice());
|
||||
self.stored_manifest.set_contexts(&contexts);
|
||||
self.stored_manifest.write();
|
||||
|
||||
// If we're not out of contexts, we're not done, so we don't fire completed
|
||||
if completed_lock_len != contexts.len() {
|
||||
// If there are any contexts left which are false
|
||||
if !contexts.iter().all(|x| x.1) {
|
||||
info!(
|
||||
"download agent for {} exited without completing ({}/{})",
|
||||
self.id.clone(),
|
||||
@@ -359,10 +373,6 @@ impl GameDownloadAgent {
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
// We've completed
|
||||
self.sender
|
||||
.send(DownloadManagerSignal::Completed(self.metadata()))
|
||||
.unwrap();
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
@@ -420,47 +430,11 @@ impl Downloadable for GameDownloadAgent {
|
||||
|
||||
// TODO: fix this function. It doesn't restart the download properly, nor does it reset the state properly
|
||||
fn on_incomplete(&self, app_handle: &tauri::AppHandle) {
|
||||
let meta = self.metadata();
|
||||
*self.status.lock().unwrap() = DownloadStatus::Queued;
|
||||
borrow_db_mut_checked().applications.game_statuses.insert(
|
||||
self.id.clone(),
|
||||
GameDownloadStatus::PartiallyInstalled {
|
||||
version_name: self.version.clone(),
|
||||
install_dir: self.stored_manifest.base_path.to_string_lossy().to_string(),
|
||||
},
|
||||
on_game_incomplete(
|
||||
&self.metadata(),
|
||||
self.stored_manifest.base_path.to_string_lossy().to_string(),
|
||||
app_handle,
|
||||
);
|
||||
borrow_db_mut_checked()
|
||||
.applications
|
||||
.installed_game_version
|
||||
.insert(self.id.clone(), self.metadata());
|
||||
borrow_db_mut_checked().applications.game_statuses.insert(
|
||||
self.id.clone(),
|
||||
GameDownloadStatus::PartiallyInstalled {
|
||||
version_name: self.version.clone(),
|
||||
install_dir: self.stored_manifest.base_path.to_string_lossy().to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
app_handle
|
||||
.emit(
|
||||
&format!("update_game/{}", meta.id),
|
||||
GameUpdateEvent {
|
||||
game_id: meta.id.clone(),
|
||||
status: (
|
||||
Some(GameDownloadStatus::PartiallyInstalled {
|
||||
version_name: self.version.clone(),
|
||||
install_dir: self
|
||||
.stored_manifest
|
||||
.base_path
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
}),
|
||||
None,
|
||||
),
|
||||
version: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {}
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
use crate::download_manager::util::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag};
|
||||
use crate::download_manager::util::download_thread_control_flag::{
|
||||
DownloadThreadControl, DownloadThreadControlFlag,
|
||||
};
|
||||
use crate::download_manager::util::progress_object::ProgressHandle;
|
||||
use crate::error::application_download_error::ApplicationDownloadError;
|
||||
use crate::error::remote_access_error::RemoteAccessError;
|
||||
use crate::games::downloads::drop_data::DropData;
|
||||
use crate::games::downloads::manifest::DropDownloadContext;
|
||||
use log::{debug, warn};
|
||||
use md5::{Context, Digest};
|
||||
use native_model::Decode;
|
||||
use reqwest::blocking::{RequestBuilder, Response};
|
||||
|
||||
use std::fs::{set_permissions, Permissions};
|
||||
use std::io::{ErrorKind, Read};
|
||||
use std::io::{copy, ErrorKind, Read};
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
io::{self, BufWriter, Seek, SeekFrom, Write},
|
||||
@@ -121,7 +128,10 @@ pub fn download_game_chunk(
|
||||
progress: ProgressHandle,
|
||||
request: RequestBuilder,
|
||||
) -> Result<bool, ApplicationDownloadError> {
|
||||
debug!("Starting download chunk {}, {}, {} #{}", ctx.file_name, ctx.index, ctx.offset, ctx.checksum);
|
||||
debug!(
|
||||
"Starting download chunk {}, {}, {} #{}",
|
||||
ctx.file_name, ctx.index, ctx.offset, ctx.checksum
|
||||
);
|
||||
// If we're paused
|
||||
if control_flag.get() == DownloadThreadControlFlag::Stop {
|
||||
progress.set(0);
|
||||
@@ -161,13 +171,8 @@ pub fn download_game_chunk(
|
||||
return Err(ApplicationDownloadError::DownloadError);
|
||||
}
|
||||
|
||||
let mut pipeline = DropDownloadPipeline::new(
|
||||
response,
|
||||
destination,
|
||||
control_flag,
|
||||
progress,
|
||||
length,
|
||||
);
|
||||
let mut pipeline =
|
||||
DropDownloadPipeline::new(response, destination, control_flag, progress, length);
|
||||
|
||||
let completed = pipeline
|
||||
.copy()
|
||||
@@ -192,7 +197,91 @@ pub fn download_game_chunk(
|
||||
return Err(ApplicationDownloadError::Checksum);
|
||||
}
|
||||
|
||||
debug!("Successfully finished download #{}, copied {} bytes", ctx.checksum, length);
|
||||
debug!(
|
||||
"Successfully finished download #{}, copied {} bytes",
|
||||
ctx.checksum, length
|
||||
);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn validate(path: PathBuf) -> Result<Vec<String>, ApplicationDownloadError> {
|
||||
let mut dropdata = File::open(path.join(".dropdata")).unwrap();
|
||||
let mut buf = Vec::new();
|
||||
dropdata.read_to_end(&mut buf);
|
||||
let manifest: DropData = native_model::rmp_serde_1_3::RmpSerde::decode(buf).unwrap();
|
||||
let completed_contexts = manifest.get_completed_contexts();
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn validate_game_chunk(
|
||||
ctx: &DropDownloadContext,
|
||||
control_flag: &DownloadThreadControl,
|
||||
progress: ProgressHandle,
|
||||
) -> Result<bool, ApplicationDownloadError> {
|
||||
debug!(
|
||||
"Starting chunk validation {}, {}, {} #{}",
|
||||
ctx.file_name, ctx.index, ctx.offset, ctx.checksum
|
||||
);
|
||||
// If we're paused
|
||||
if control_flag.get() == DownloadThreadControlFlag::Stop {
|
||||
progress.set(0);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut source = File::open(&ctx.path).unwrap();
|
||||
|
||||
if ctx.offset != 0 {
|
||||
source
|
||||
.seek(SeekFrom::Start(ctx.offset))
|
||||
.expect("Failed to seek to file offset");
|
||||
}
|
||||
|
||||
let mut hasher = md5::Context::new();
|
||||
|
||||
let completed = validate_copy(&mut source, &mut hasher, control_flag, progress).unwrap();
|
||||
if !completed {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let res = hex::encode(hasher.compute().0);
|
||||
if res != ctx.checksum {
|
||||
return Err(ApplicationDownloadError::Checksum);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Successfully finished verification #{}, copied {} bytes",
|
||||
ctx.checksum, ctx.length
|
||||
);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn validate_copy(
|
||||
source: &mut File,
|
||||
dest: &mut Context,
|
||||
control_flag: &DownloadThreadControl,
|
||||
progress: ProgressHandle,
|
||||
) -> Result<bool, io::Error> {
|
||||
let copy_buf_size = 512;
|
||||
let mut copy_buf = vec![0; copy_buf_size];
|
||||
let mut buf_writer = BufWriter::with_capacity(1024 * 1024, dest);
|
||||
|
||||
loop {
|
||||
if control_flag.get() == DownloadThreadControlFlag::Stop {
|
||||
buf_writer.flush()?;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let bytes_read = source.read(&mut copy_buf)?;
|
||||
|
||||
buf_writer.write_all(©_buf[0..bytes_read])?;
|
||||
progress.add(bytes_read);
|
||||
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
buf_writer.flush()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{fs::File, io::{Read, Write}, path::PathBuf};
|
||||
|
||||
use log::{error, warn};
|
||||
use log::{debug, error, info, warn};
|
||||
use native_model::{Decode, Encode};
|
||||
|
||||
pub type DropData = v1::DropData;
|
||||
@@ -19,7 +19,7 @@ pub mod v1 {
|
||||
pub struct DropData {
|
||||
game_id: String,
|
||||
game_version: String,
|
||||
pub completed_contexts: Mutex<Vec<String>>,
|
||||
pub contexts: Mutex<Vec<(String, bool)>>,
|
||||
pub base_path: PathBuf,
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ pub mod v1 {
|
||||
base_path,
|
||||
game_id,
|
||||
game_version,
|
||||
completed_contexts: Mutex::new(Vec::new()),
|
||||
contexts: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,10 @@ impl DropData {
|
||||
pub fn generate(game_id: String, game_version: String, base_path: PathBuf) -> Self {
|
||||
let mut file = match File::open(base_path.join(DROP_DATA_PATH)) {
|
||||
Ok(file) => file,
|
||||
Err(_) => return DropData::new(game_id, game_version, base_path),
|
||||
Err(_) => {
|
||||
debug!("Generating new dropdata for game {}", game_id);
|
||||
return DropData::new(game_id, game_version, base_path)
|
||||
},
|
||||
};
|
||||
|
||||
let mut s = Vec::new();
|
||||
@@ -78,10 +81,14 @@ impl DropData {
|
||||
Err(e) => error!("{}", e),
|
||||
};
|
||||
}
|
||||
pub fn set_completed_contexts(&self, completed_contexts: &[String]) {
|
||||
*self.completed_contexts.lock().unwrap() = completed_contexts.to_owned();
|
||||
pub fn set_contexts(&self, completed_contexts: &[(String, bool)]) {
|
||||
*self.contexts.lock().unwrap() = completed_contexts.to_owned();
|
||||
}
|
||||
pub fn get_completed_contexts(&self) -> Vec<String> {
|
||||
self.completed_contexts.lock().unwrap().clone()
|
||||
self.contexts.lock().unwrap().iter().filter_map(|x| { if x.1 { Some(x.0.clone()) } else { None } }).collect()
|
||||
}
|
||||
pub fn get_contexts(&self) -> Vec<(String, bool)> {
|
||||
info!("Any contexts which are complete? {}", self.contexts.lock().unwrap().iter().any(|x| x.1));
|
||||
self.contexts.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::thread::spawn;
|
||||
|
||||
use log::{debug, error, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::Emitter;
|
||||
use tauri::AppHandle;
|
||||
use tauri::Emitter;
|
||||
|
||||
use crate::database::db::{borrow_db_checked, borrow_db_mut_checked};
|
||||
use crate::database::models::data::{
|
||||
@@ -364,6 +364,66 @@ pub fn get_current_meta(game_id: &String) -> Option<DownloadableMetadata> {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn on_game_incomplete(
|
||||
meta: &DownloadableMetadata,
|
||||
install_dir: String,
|
||||
app_handle: &AppHandle,
|
||||
) -> Result<(), RemoteAccessError> {
|
||||
// Fetch game version information from remote
|
||||
if meta.version.is_none() {
|
||||
return Err(RemoteAccessError::GameNotFound(meta.id.clone()));
|
||||
}
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let response = make_request(
|
||||
&client,
|
||||
&["/api/v1/client/game/version"],
|
||||
&[
|
||||
("id", &meta.id),
|
||||
("version", meta.version.as_ref().unwrap()),
|
||||
],
|
||||
|f| f.header("Authorization", generate_authorization_header()),
|
||||
)?
|
||||
.send()?;
|
||||
|
||||
let game_version: GameVersion = response.json()?;
|
||||
|
||||
let mut handle = borrow_db_mut_checked();
|
||||
handle
|
||||
.applications
|
||||
.game_versions
|
||||
.entry(meta.id.clone())
|
||||
.or_default()
|
||||
.insert(meta.version.clone().unwrap(), game_version.clone());
|
||||
handle
|
||||
.applications
|
||||
.installed_game_version
|
||||
.insert(meta.id.clone(), meta.clone());
|
||||
|
||||
let status = GameDownloadStatus::PartiallyInstalled {
|
||||
version_name: meta.version.clone().unwrap(),
|
||||
install_dir,
|
||||
};
|
||||
|
||||
handle
|
||||
.applications
|
||||
.game_statuses
|
||||
.insert(meta.id.clone(), status.clone());
|
||||
drop(handle);
|
||||
app_handle
|
||||
.emit(
|
||||
&format!("update_game/{}", meta.id),
|
||||
GameUpdateEvent {
|
||||
game_id: meta.id.clone(),
|
||||
status: (Some(status), None),
|
||||
version: Some(game_version),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn on_game_complete(
|
||||
meta: &DownloadableMetadata,
|
||||
install_dir: String,
|
||||
@@ -400,7 +460,7 @@ pub fn on_game_complete(
|
||||
handle
|
||||
.applications
|
||||
.installed_game_version
|
||||
.insert(meta.id.clone(), meta.clone());
|
||||
.insert(meta.id.clone(), meta.clone());
|
||||
|
||||
drop(handle);
|
||||
|
||||
@@ -496,6 +556,5 @@ pub fn update_game_configuration(
|
||||
.unwrap()
|
||||
.insert(version.to_string(), existing_configuration);
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user