feat(download widget): download widget and queue fix

This commit is contained in:
DecDuck
2024-12-08 12:33:45 +11:00
parent 5cbeb3bdb6
commit 532d13e96f
9 changed files with 85 additions and 45 deletions
+1 -1
View File
@@ -26,4 +26,4 @@ dist-ssr
.output .output
src-tauri/flamegraph.svg src-tauri/flamegraph.svg
src-tuair/perf* src-tauri/perf*
+26 -20
View File
@@ -1,53 +1,58 @@
<template> <template>
<div <div class="h-16 bg-zinc-950 flex flex-row justify-between">
class="h-16 bg-zinc-950 flex flex-row justify-between"
>
<div class="flex flex-row grow items-center pl-5 pr-2 py-3"> <div class="flex flex-row grow items-center pl-5 pr-2 py-3">
<div class="inline-flex items-center gap-x-10"> <div class="inline-flex items-center gap-x-10">
<NuxtLink to="/store"> <NuxtLink to="/store">
<Wordmark class="h-8 mb-0.5"/> <Wordmark class="h-8 mb-0.5" />
</NuxtLink> </NuxtLink>
<nav class="inline-flex items-center mt-0.5"> <nav class="inline-flex items-center mt-0.5">
<ol class="inline-flex items-center gap-x-6"> <ol class="inline-flex items-center gap-x-6">
<NuxtLink <NuxtLink
v-for="(nav, navIdx) in navigation" v-for="(nav, navIdx) in navigation"
:class="[ :class="[
'transition uppercase font-display font-semibold text-md', 'transition uppercase font-display font-semibold text-md',
navIdx === currentPageIndex navIdx === currentPageIndex
? 'text-zinc-100' ? 'text-zinc-100'
: 'text-zinc-400 hover:text-zinc-200', : 'text-zinc-400 hover:text-zinc-200',
]" ]"
:href="nav.route" :href="nav.route"
> >
{{ nav.label }} {{ nav.label }}
</NuxtLink> </NuxtLink>
</ol> </ol>
</nav> </nav>
</div> </div>
<div @mousedown="() => window.startDragging()" class="flex cursor-pointer grow h-full" /> <div
@mousedown="() => window.startDragging()"
class="flex cursor-pointer grow h-full"
/>
<div class="inline-flex items-center"> <div class="inline-flex items-center">
<ol class="inline-flex gap-3"> <ol class="inline-flex gap-3">
<HeaderQueueWidget
v-if="currentQueueObject"
:object="currentQueueObject"
/>
<li v-for="(item, itemIdx) in quickActions"> <li v-for="(item, itemIdx) in quickActions">
<HeaderWidget <HeaderWidget
@click="item.action" @click="item.action"
:notifications="item.notifications" :notifications="item.notifications"
> >
<component class="h-5" :is="item.icon"/> <component class="h-5" :is="item.icon" />
</HeaderWidget> </HeaderWidget>
</li> </li>
<HeaderUserWidget/> <HeaderUserWidget />
</ol> </ol>
</div> </div>
</div> </div>
<WindowControl class="h-16 w-16 p-4"/> <WindowControl class="h-16 w-16 p-4" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {BellIcon, UserGroupIcon} from "@heroicons/vue/16/solid"; import { BellIcon, UserGroupIcon } from "@heroicons/vue/16/solid";
import type {NavigationItem, QuickActionNav} from "../types"; import type { NavigationItem, QuickActionNav } from "../types";
import HeaderWidget from "./HeaderWidget.vue"; import HeaderWidget from "./HeaderWidget.vue";
import {getCurrentWindow} from "@tauri-apps/api/window"; import { getCurrentWindow } from "@tauri-apps/api/window";
const window = getCurrentWindow(); const window = getCurrentWindow();
@@ -79,13 +84,14 @@ const currentPageIndex = useCurrentNavigationIndex(navigation);
const quickActions: Array<QuickActionNav> = [ const quickActions: Array<QuickActionNav> = [
{ {
icon: UserGroupIcon, icon: UserGroupIcon,
action: async () => { action: async () => {},
},
}, },
{ {
icon: BellIcon, icon: BellIcon,
action: async () => { action: async () => {},
},
}, },
]; ];
const queue = useQueueState();
const currentQueueObject = computed(() => queue.value.queue.at(0));
</script> </script>
+17
View File
@@ -0,0 +1,17 @@
<script setup lang="ts">
import { ArrowDownTrayIcon } from "@heroicons/vue/20/solid";
const props = defineProps<{ object: QueueState["queue"][0] }>();
</script>
<template>
<div
class="transition inline-flex items-center cursor-pointer rounded-sm px-4 py-1.5 bg-zinc-900 hover:bg-zinc-800 hover:text-zinc-300 relative"
>
<ArrowDownTrayIcon class="h-5 z-50 text-zinc-100 mix-blend-difference" />
<div
class="transition-all absolute left-0 top-0 bottom-0 bg-blue-600 z-10"
:style="{ width: `${props.object.progress * 100}%` }"
/>
</div>
</template>
+1 -1
View File
@@ -1,7 +1,7 @@
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
export type QueueState = { export type QueueState = {
queue: Array<{ id: string; status: string }>; queue: Array<{ id: string; status: string, progress: number }>;
}; };
export const useQueueState = () => export const useQueueState = () =>
-2
View File
@@ -2,8 +2,6 @@
<div class="flex flex-col bg-zinc-900 overflow-hidden"> <div class="flex flex-col bg-zinc-900 overflow-hidden">
<Header class="select-none" /> <Header class="select-none" />
<div class="grow overflow-y-auto"> <div class="grow overflow-y-auto">
<span class="text-white">{{ queueState }}</span>
<slot /> <slot />
</div> </div>
</div> </div>
+1 -1
View File
@@ -227,7 +227,7 @@
: 'font-normal', : 'font-normal',
'block truncate', 'block truncate',
]" ]"
>{{ dir }}}</span >{{ dir }}</span
> >
<span <span
+5 -1
View File
@@ -41,17 +41,19 @@ impl DropWriter<File> {
// Write automatically pushes to file and hasher // Write automatically pushes to file and hasher
impl Write for DropWriter<File> { impl Write for DropWriter<File> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
/*
self.hasher.write_all(buf).map_err(|e| { self.hasher.write_all(buf).map_err(|e| {
io::Error::new( io::Error::new(
ErrorKind::Other, ErrorKind::Other,
format!("Unable to write to hasher: {}", e), format!("Unable to write to hasher: {}", e),
) )
})?; })?;
*/
self.destination.write(buf) self.destination.write(buf)
} }
fn flush(&mut self) -> io::Result<()> { fn flush(&mut self) -> io::Result<()> {
self.hasher.flush()?; // self.hasher.flush()?;
self.destination.flush() self.destination.flush()
} }
} }
@@ -185,6 +187,7 @@ pub fn download_game_chunk(
return Ok(false); return Ok(false);
}; };
/*
let checksum = pipeline let checksum = pipeline
.finish() .finish()
.map_err(|e| GameDownloadError::IoError(e))?; .map_err(|e| GameDownloadError::IoError(e))?;
@@ -193,6 +196,7 @@ pub fn download_game_chunk(
if res != ctx.checksum { if res != ctx.checksum {
return Err(GameDownloadError::Checksum); return Err(GameDownloadError::Checksum);
} }
*/
Ok(true) Ok(true)
} }
@@ -76,7 +76,7 @@ pub struct DownloadManagerBuilder {
status: Arc<Mutex<DownloadManagerStatus>>, status: Arc<Mutex<DownloadManagerStatus>>,
app_handle: AppHandle, app_handle: AppHandle,
current_game_interface: Option<Arc<GameDownloadAgentQueueStandin>>, // Should be the only game download agent in the map with the "Go" flag current_download_agent: Option<Arc<GameDownloadAgentQueueStandin>>, // Should be the only game download agent in the map with the "Go" flag
active_control_flag: Option<DownloadThreadControl>, active_control_flag: Option<DownloadThreadControl>,
} }
@@ -91,7 +91,7 @@ impl DownloadManagerBuilder {
download_agent_registry: HashMap::new(), download_agent_registry: HashMap::new(),
download_queue: queue.clone(), download_queue: queue.clone(),
command_receiver, command_receiver,
current_game_interface: None, current_download_agent: None,
active_control_flag: None, active_control_flag: None,
status: status.clone(), status: status.clone(),
sender: command_sender.clone(), sender: command_sender.clone(),
@@ -138,6 +138,16 @@ impl DownloadManagerBuilder {
self.app_handle.emit("update_queue", event_data).unwrap(); self.app_handle.emit("update_queue", event_data).unwrap();
} }
fn cleanup_current_game(&mut self, game_id: &String) -> Arc<GameDownloadAgent> {
self.download_queue.pop_front();
let download_agent = self.download_agent_registry.remove(game_id).unwrap();
self.active_control_flag = None;
*self.progress.lock().unwrap() = None;
self.current_download_agent = None;
return download_agent;
}
fn manage_queue(mut self) -> Result<(), ()> { fn manage_queue(mut self) -> Result<(), ()> {
loop { loop {
let signal = match self.command_receiver.recv() { let signal = match self.command_receiver.recv() {
@@ -186,14 +196,11 @@ impl DownloadManagerBuilder {
fn manage_completed_signal(&mut self, game_id: String) { fn manage_completed_signal(&mut self, game_id: String) {
info!("Got signal 'Completed'"); info!("Got signal 'Completed'");
if let Some(interface) = &self.current_game_interface { if let Some(interface) = &self.current_download_agent {
// When if let chains are stabilised, combine these two statements // When if let chains are stabilised, combine these two statements
if interface.id == game_id { if interface.id == game_id {
info!("Popping consumed data"); info!("Popping consumed data");
self.download_queue.pop_front(); let download_agent = self.cleanup_current_game(&game_id);
let download_agent = self.download_agent_registry.remove(&game_id).unwrap();
self.active_control_flag = None;
*self.progress.lock().unwrap() = None;
if let Err(error) = if let Err(error) =
on_game_complete(game_id, download_agent.version.clone(), &self.app_handle) on_game_complete(game_id, download_agent.version.clone(), &self.app_handle)
@@ -240,6 +247,11 @@ impl DownloadManagerBuilder {
return; return;
} }
if self.current_download_agent.is_some() {
info!("skipping go signal due to existing download job");
return;
}
info!("Starting download agent"); info!("Starting download agent");
let agent_data = self.download_queue.read().front().unwrap().clone(); let agent_data = self.download_queue.read().front().unwrap().clone();
let download_agent = self let download_agent = self
@@ -247,9 +259,9 @@ impl DownloadManagerBuilder {
.get(&agent_data.id) .get(&agent_data.id)
.unwrap() .unwrap()
.clone(); .clone();
self.current_game_interface = Some(agent_data); self.current_download_agent = Some(agent_data);
// Cloning option should be okay because it only clones the Arc inside, not the AgentInterfaceData // Cloning option should be okay because it only clones the Arc inside, not the AgentInterfaceData
let agent_data = self.current_game_interface.clone().unwrap(); let agent_data = self.current_download_agent.clone().unwrap();
let version_name = download_agent.version.clone(); let version_name = download_agent.version.clone();
@@ -284,31 +296,34 @@ impl DownloadManagerBuilder {
active_control_flag.set(DownloadThreadControlFlag::Go); active_control_flag.set(DownloadThreadControlFlag::Go);
self.set_status(DownloadManagerStatus::Downloading); self.set_status(DownloadManagerStatus::Downloading);
self.set_game_status( self.set_game_status(
self.current_game_interface.as_ref().unwrap().id.clone(), self.current_download_agent.as_ref().unwrap().id.clone(),
DatabaseGameStatus::Downloading { version_name }, DatabaseGameStatus::Downloading { version_name },
); );
} }
fn manage_error_signal(&self, error: GameDownloadError) { fn manage_error_signal(&mut self, error: GameDownloadError) {
let current_status = self.current_game_interface.clone().unwrap(); let current_status = self.current_download_agent.clone().unwrap();
self.cleanup_current_game(&current_status.id); // Remove all the locks and shit
let mut lock = current_status.status.lock().unwrap(); let mut lock = current_status.status.lock().unwrap();
*lock = GameDownloadStatus::Error; *lock = GameDownloadStatus::Error;
self.set_status(DownloadManagerStatus::Error(error)); self.set_status(DownloadManagerStatus::Error(error));
self.set_game_status( let game_id = self.current_download_agent.as_ref().unwrap().id.clone();
self.current_game_interface.as_ref().unwrap().id.clone(), self.set_game_status(game_id, DatabaseGameStatus::Remote {});
DatabaseGameStatus::Remote {},
);
self.sender.send(DownloadManagerSignal::Update).unwrap(); self.sender.send(DownloadManagerSignal::Update).unwrap();
} }
fn manage_cancel_signal(&mut self, game_id: String) { fn manage_cancel_signal(&mut self, game_id: String) {
if let Some(current_flag) = &self.active_control_flag { if let Some(current_flag) = &self.active_control_flag {
current_flag.set(DownloadThreadControlFlag::Stop); current_flag.set(DownloadThreadControlFlag::Stop);
self.active_control_flag = None;
*self.progress.lock().unwrap() = None;
} }
// TODO wait until current download exits // TODO wait until current download exits
// This cleanup function might break things because it
// unsets the control flag
self.cleanup_current_game(&game_id);
self.download_agent_registry.remove(&game_id); self.download_agent_registry.remove(&game_id);
let mut lock = self.download_queue.edit(); let mut lock = self.download_queue.edit();
let index = match lock.iter().position(|interface| interface.id == game_id) { let index = match lock.iter().position(|interface| interface.id == game_id) {
+1 -1
View File
@@ -49,7 +49,7 @@ static PROGRESS_UPDATES: usize = 100;
impl ProgressObject { impl ProgressObject {
pub fn new(max: usize, length: usize, sender: Sender<DownloadManagerSignal>) -> Self { pub fn new(max: usize, length: usize, sender: Sender<DownloadManagerSignal>) -> Self {
let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect()); let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect());
// TODO: consolidate this calculate with the set_max function below // TODO: consolidate this calculation with the set_max function below
let points_to_push_update = max / PROGRESS_UPDATES; let points_to_push_update = max / PROGRESS_UPDATES;
Self { Self {
max: Arc::new(Mutex::new(max)), max: Arc::new(Mutex::new(max)),