mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-10 04:22:13 +10:00
refactor: Converting useGame to DownloadableMetadata
Signed-off-by: quexeky <git@quexeky.dev>
This commit is contained in:
@ -1,19 +1,19 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import type { Game, GameStatus, GameStatusEnum, GameVersion } from "~/types";
|
||||
import { type Game, type GameStatus as DownloadStatus, type GameStatusEnum as DownloadStatusEnum, type GameVersion, type DownloadableMetadata, DownloadableType } from "~/types";
|
||||
|
||||
const gameRegistry: { [key: string]: { game: Game; version?: GameVersion } } =
|
||||
{};
|
||||
|
||||
const gameStatusRegistry: { [key: string]: Ref<GameStatus> } = {};
|
||||
const downloadStatusRegistry: Map<DownloadableMetadata, Ref<DownloadStatus>> = new Map();
|
||||
|
||||
type OptionGameStatus = { [key in GameStatusEnum]: { version_name?: string } };
|
||||
export type SerializedGameStatus = [
|
||||
{ type: GameStatusEnum },
|
||||
OptionGameStatus | null
|
||||
type OptionDownloadStatus = { [key in DownloadStatusEnum]: { version_name?: string } };
|
||||
export type SerializedDownloadStatus = [
|
||||
{ type: DownloadStatusEnum },
|
||||
OptionDownloadStatus | null
|
||||
];
|
||||
|
||||
export const parseStatus = (status: SerializedGameStatus): GameStatus => {
|
||||
export const parseStatus = (status: SerializedDownloadStatus): DownloadStatus => {
|
||||
console.log(status);
|
||||
if (status[0]) {
|
||||
return {
|
||||
@ -22,7 +22,7 @@ export const parseStatus = (status: SerializedGameStatus): GameStatus => {
|
||||
} else if (status[1]) {
|
||||
const [[gameStatus, options]] = Object.entries(status[1]);
|
||||
return {
|
||||
type: gameStatus as GameStatusEnum,
|
||||
type: gameStatus as DownloadStatusEnum,
|
||||
...options,
|
||||
};
|
||||
} else {
|
||||
@ -30,43 +30,54 @@ export const parseStatus = (status: SerializedGameStatus): GameStatus => {
|
||||
}
|
||||
};
|
||||
|
||||
export const useGame = async (gameId: string) => {
|
||||
if (!gameRegistry[gameId]) {
|
||||
const data: {
|
||||
game: Game;
|
||||
status: SerializedGameStatus;
|
||||
version?: GameVersion;
|
||||
} = await invoke("fetch_game", {
|
||||
gameId,
|
||||
});
|
||||
gameRegistry[gameId] = { game: data.game, version: data.version };
|
||||
if (!gameStatusRegistry[gameId]) {
|
||||
gameStatusRegistry[gameId] = ref(parseStatus(data.status));
|
||||
export const useStatus = (meta: DownloadableMetadata) => {
|
||||
return downloadStatusRegistry.get(meta)
|
||||
}
|
||||
|
||||
listen(`update_game/${gameId}`, (event) => {
|
||||
const payload: {
|
||||
status: SerializedGameStatus;
|
||||
version?: GameVersion;
|
||||
} = event.payload as any;
|
||||
console.log(payload.status);
|
||||
gameStatusRegistry[gameId].value = parseStatus(payload.status);
|
||||
|
||||
/**
|
||||
* I am not super happy about this.
|
||||
*
|
||||
* This will mean that we will still have a version assigned if we have a game installed then uninstall it.
|
||||
* It is necessary because a flag to check if we should overwrite seems excessive, and this function gets called
|
||||
* on transient state updates.
|
||||
*/
|
||||
if (payload.version) {
|
||||
gameRegistry[gameId].version = payload.version;
|
||||
}
|
||||
});
|
||||
}
|
||||
export const useGame = async (gameId: string) => {
|
||||
const data: {
|
||||
game: Game;
|
||||
status: SerializedDownloadStatus;
|
||||
version?: GameVersion;
|
||||
} = await invoke("fetch_game", {
|
||||
gameId,
|
||||
});
|
||||
const meta = {
|
||||
id: gameId,
|
||||
version: data.version?.versionName,
|
||||
downloadType: DownloadableType.Game
|
||||
} satisfies DownloadableMetadata;
|
||||
if (!gameRegistry[gameId]) {
|
||||
|
||||
gameRegistry[gameId] = { game: data.game, version: data.version };
|
||||
}
|
||||
if (!downloadStatusRegistry.has(meta)) {
|
||||
downloadStatusRegistry.set(meta, ref(parseStatus(data.status)));
|
||||
|
||||
listen(`update_game/${gameId}`, (event) => {
|
||||
const payload: {
|
||||
status: SerializedDownloadStatus;
|
||||
version?: GameVersion;
|
||||
} = event.payload as any;
|
||||
|
||||
downloadStatusRegistry.get(meta)!.value = parseStatus(payload.status);
|
||||
|
||||
/**
|
||||
* I am not super happy about this.
|
||||
*
|
||||
* This will mean that we will still have a version assigned if we have a game installed then uninstall it.
|
||||
* It is necessary because a flag to check if we should overwrite seems excessive, and this function gets called
|
||||
* on transient state updates.
|
||||
*/
|
||||
if (payload.version) {
|
||||
gameRegistry[gameId].version = payload.version;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const game = gameRegistry[gameId];
|
||||
const status = gameStatusRegistry[gameId];
|
||||
const status = downloadStatusRegistry.get(meta)!;
|
||||
return { ...game, status };
|
||||
};
|
||||
|
||||
|
||||
@ -471,6 +471,7 @@ const router = useRouter();
|
||||
const id = route.params.id.toString();
|
||||
|
||||
const { game: rawGame, status } = await useGame(id);
|
||||
console.log("status: ", status);
|
||||
const game = ref(rawGame);
|
||||
|
||||
const remoteUrl: string = await invoke("gen_drop_url", {
|
||||
|
||||
@ -1,46 +1,32 @@
|
||||
<template>
|
||||
<div class="bg-zinc-950 p-4 min-h-full space-y-4">
|
||||
<div
|
||||
class="h-16 overflow-hidden relative rounded-xl flex flex-row border border-zinc-900"
|
||||
>
|
||||
<div class="h-16 overflow-hidden relative rounded-xl flex flex-row border border-zinc-900">
|
||||
<div
|
||||
class="bg-zinc-900 z-10 w-32 flex flex-col gap-x-2 text-blue-400 font-display items-left justify-center pl-2"
|
||||
>
|
||||
class="bg-zinc-900 z-10 w-32 flex flex-col gap-x-2 text-blue-400 font-display items-left justify-center pl-2">
|
||||
<span class="font-semibold">{{ formatKilobytes(stats.speed) }}/s</span>
|
||||
<span v-if="stats.time > 0" class="text-sm"
|
||||
>{{ formatTime(stats.time) }} left</span
|
||||
>
|
||||
<span v-if="stats.time > 0" class="text-sm">{{ formatTime(stats.time) }} left</span>
|
||||
</div>
|
||||
<div class="absolute inset-0 h-full flex flex-row items-end justify-end">
|
||||
<div
|
||||
v-for="bar in speedHistory"
|
||||
:style="{ height: `${(bar / speedMax) * 100}%` }"
|
||||
class="w-[8px] bg-blue-600/40"
|
||||
/>
|
||||
<div v-for="bar in speedHistory" :style="{ height: `${(bar / speedMax) * 100}%` }"
|
||||
class="w-[8px] bg-blue-600/40" />
|
||||
</div>
|
||||
</div>
|
||||
<draggable v-model="queue.queue" @end="onEnd">
|
||||
<template #item="{ element }: { element: (typeof queue.value.queue)[0] }">
|
||||
<li
|
||||
v-if="games[element.meta.id]"
|
||||
:key="element.meta.id"
|
||||
class="mb-4 bg-zinc-900 rounded-lg flex flex-row justify-between gap-x-6 py-5 px-4"
|
||||
>
|
||||
<li v-if="downloads.has(element.meta)" :key="element.meta.id"
|
||||
class="mb-4 bg-zinc-900 rounded-lg flex flex-row justify-between gap-x-6 py-5 px-4">
|
||||
<div class="w-full flex items-center max-w-md gap-x-4 relative">
|
||||
<img
|
||||
class="size-24 flex-none bg-zinc-800 object-cover rounded"
|
||||
:src="games[element.meta.id].cover"
|
||||
alt=""
|
||||
/>
|
||||
<img class="size-24 flex-none bg-zinc-800 object-cover rounded" :src="downloads.get(element.meta)!.queueMeta.cover"
|
||||
alt="" />
|
||||
<div class="min-w-0 flex-auto">
|
||||
<p class="text-xl font-semibold text-zinc-100">
|
||||
<NuxtLink :href="`/library/${element.meta.id}`" class="">
|
||||
<span class="absolute inset-x-0 -top-px bottom-0" />
|
||||
{{ games[element.meta.id].game.mName }}
|
||||
{{ downloads.get(element.meta)!.queueMeta.mName }}
|
||||
</NuxtLink>
|
||||
</p>
|
||||
<p class="mt-1 flex text-xs/5 text-gray-500">
|
||||
{{ games[element.meta.id].game.mShortDescription }}
|
||||
{{ downloads.get(element.meta)!.queueMeta.mShortDescription }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -49,40 +35,28 @@
|
||||
<p class="text-md text-zinc-500 uppercase font-display font-bold">
|
||||
{{ element.status }}
|
||||
</p>
|
||||
<div
|
||||
v-if="element.progress"
|
||||
class="mt-1 w-96 bg-zinc-800 rounded-lg overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="h-2 bg-blue-600"
|
||||
:style="{ width: `${element.progress * 100}%` }"
|
||||
/>
|
||||
<div v-if="element.progress" class="mt-1 w-96 bg-zinc-800 rounded-lg overflow-hidden">
|
||||
<div class="h-2 bg-blue-600" :style="{ width: `${element.progress * 100}%` }" />
|
||||
</div>
|
||||
<span
|
||||
class="mt-2 inline-flex items-center gap-x-1 text-zinc-400 text-sm font-display"
|
||||
><span class="text-zinc-300">{{
|
||||
formatKilobytes(element.current / 1000)
|
||||
}}</span>
|
||||
<span class="mt-2 inline-flex items-center gap-x-1 text-zinc-400 text-sm font-display"><span
|
||||
class="text-zinc-300">{{
|
||||
formatKilobytes(element.current / 1000)
|
||||
}}</span>
|
||||
/
|
||||
<span class="">{{ formatKilobytes(element.max / 1000) }}</span
|
||||
><ServerIcon class="size-5"
|
||||
/></span>
|
||||
<span class="">{{ formatKilobytes(element.max / 1000) }}</span>
|
||||
<ServerIcon class="size-5" />
|
||||
</span>
|
||||
</div>
|
||||
<button @click="() => cancelGame(element.meta)" class="group">
|
||||
<XMarkIcon
|
||||
class="transition size-8 flex-none text-zinc-600 group-hover:text-zinc-300"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<XMarkIcon class="transition size-8 flex-none text-zinc-600 group-hover:text-zinc-300"
|
||||
aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<p v-else>Loading...</p>
|
||||
</template>
|
||||
</draggable>
|
||||
<div
|
||||
class="text-zinc-600 uppercase font-semibold font-display w-full text-center"
|
||||
v-if="queue.queue.length == 0"
|
||||
>
|
||||
<div class="text-zinc-600 uppercase font-semibold font-display w-full text-center" v-if="queue.queue.length == 0">
|
||||
No items in the queue
|
||||
</div>
|
||||
</div>
|
||||
@ -91,7 +65,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ServerIcon, XMarkIcon } from "@heroicons/vue/20/solid";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { DownloadableMetadata, Game, GameStatus } from "~/types";
|
||||
import { useStatus } from "~/composables/game";
|
||||
import type { DownloadableMetadata, Game, GameStatus, QueueMetadata } from "~/types";
|
||||
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
window.addEventListener("resize", (event) => {
|
||||
@ -107,9 +82,7 @@ const speedMax = computed(
|
||||
);
|
||||
const previousGameId = ref<string | undefined>();
|
||||
|
||||
const games: Ref<{
|
||||
[key: string]: { game: Game; status: Ref<GameStatus>; cover: string };
|
||||
}> = ref({});
|
||||
const downloads: Ref<Map<DownloadableMetadata, { queueMeta: QueueMetadata, status: Ref<GameStatus> }>> = ref(new Map());
|
||||
|
||||
function resetHistoryGraph() {
|
||||
speedHistory.value = [];
|
||||
@ -153,13 +126,14 @@ watch(stats, (v) => {
|
||||
|
||||
function loadGamesForQueue(v: typeof queue.value) {
|
||||
for (const {
|
||||
meta: { id },
|
||||
meta,
|
||||
} of v.queue) {
|
||||
if (games.value[id]) return;
|
||||
if (downloads.value.get(meta)) return;
|
||||
(async () => {
|
||||
const gameData = await useGame(id);
|
||||
const cover = await useObject(gameData.game.mCoverObjectId);
|
||||
games.value[id] = { ...gameData, cover };
|
||||
const queueMeta: QueueMetadata = await invoke("get_queue_metadata", {meta});
|
||||
const status = useStatus(meta)!;
|
||||
const cover = await useObject(queueMeta.cover);
|
||||
downloads.value.set(meta, { queueMeta: { ...queueMeta, cover }, status });
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
use std::{
|
||||
fs::{self, create_dir_all},
|
||||
io::{self, copy},
|
||||
path::{Path, PathBuf},
|
||||
sync::{mpsc::Sender, Arc, Mutex},
|
||||
u64, usize,
|
||||
usize,
|
||||
};
|
||||
|
||||
use log::{debug, error, warn};
|
||||
@ -65,7 +63,7 @@ impl URLDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||
fn download(&self, _app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||
// TODO: Fix these unwraps and implement From<io::Error> for ApplicationDownloadError
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.redirect(Policy::default())
|
||||
@ -80,8 +78,6 @@ impl URLDownloader {
|
||||
.unwrap_or(usize::MAX);
|
||||
let response = client.get(&self.url).send().unwrap();
|
||||
|
||||
println!("{:?}, content length: {}", response, content_length);
|
||||
|
||||
self.set_progress_object_params(content_length);
|
||||
|
||||
let progress = self.progress.get(0);
|
||||
@ -159,7 +155,7 @@ impl Downloadable for URLDownloader {
|
||||
.remove(&self.metadata());
|
||||
}
|
||||
|
||||
fn on_complete(&self, app_handle: &tauri::AppHandle) {
|
||||
fn on_complete(&self, _app_handle: &tauri::AppHandle) {
|
||||
println!("Completed url download");
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::{database::models::data::DownloadableMetadata, AppState};
|
||||
use crate::{
|
||||
database::models::data::{DownloadType, DownloadableMetadata},
|
||||
download_manager::download_manager::QueueMetadata,
|
||||
AppState,
|
||||
};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn pause_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
||||
@ -29,3 +33,28 @@ pub fn move_download_in_queue(
|
||||
pub fn cancel_game(state: tauri::State<'_, Mutex<AppState>>, meta: DownloadableMetadata) {
|
||||
state.lock().unwrap().download_manager.cancel(meta)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_queue_metadata(
|
||||
state: tauri::State<'_, Mutex<AppState>>,
|
||||
meta: DownloadableMetadata,
|
||||
) -> Option<QueueMetadata> {
|
||||
match meta.download_type {
|
||||
DownloadType::Game => {
|
||||
let state = state.lock().unwrap();
|
||||
let game = state.games.get(&meta.id).unwrap();
|
||||
Some(QueueMetadata {
|
||||
cover: game.m_cover_object_id.clone(),
|
||||
m_short_description: game.m_short_description.clone(),
|
||||
name: game.m_name.clone(),
|
||||
})
|
||||
}
|
||||
DownloadType::Tool => Some(QueueMetadata {
|
||||
cover: "IDK Man".to_string(),
|
||||
m_short_description: "This is a tool".to_string(),
|
||||
name: "Download".to_string(),
|
||||
}),
|
||||
DownloadType::DLC => unimplemented!(),
|
||||
DownloadType::Mod => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,6 +74,14 @@ pub enum DownloadStatus {
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QueueMetadata {
|
||||
pub cover: String,
|
||||
pub m_short_description: String,
|
||||
pub name: String
|
||||
}
|
||||
|
||||
/// Accessible front-end for the DownloadManager
|
||||
///
|
||||
/// The system works entirely through signals, both internally and externally,
|
||||
|
||||
@ -4,7 +4,7 @@ use tauri::AppHandle;
|
||||
|
||||
use crate::{
|
||||
database::models::data::DownloadableMetadata,
|
||||
error::application_download_error::ApplicationDownloadError,
|
||||
error::{application_download_error::ApplicationDownloadError, remote_access_error::RemoteAccessError},
|
||||
};
|
||||
|
||||
use super::{
|
||||
@ -23,3 +23,4 @@ pub trait Downloadable: Send + Sync {
|
||||
fn on_incomplete(&self, app_handle: &AppHandle);
|
||||
fn on_cancelled(&self, app_handle: &AppHandle);
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,8 @@ 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, push_game_update, Game, GameUpdateEvent};
|
||||
use crate::remote::cache::get_cached_object;
|
||||
use crate::remote::requests::make_request;
|
||||
use crate::DB;
|
||||
use log::{debug, error, info};
|
||||
|
||||
@ -31,14 +31,14 @@ pub struct FetchGameStruct {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Game {
|
||||
id: String,
|
||||
m_name: String,
|
||||
m_short_description: String,
|
||||
pub m_name: String,
|
||||
pub m_short_description: String,
|
||||
m_description: String,
|
||||
// mDevelopers
|
||||
// mPublishers
|
||||
m_icon_object_id: String,
|
||||
m_banner_object_id: String,
|
||||
m_cover_object_id: String,
|
||||
pub m_cover_object_id: String,
|
||||
m_image_library_object_ids: Vec<String>,
|
||||
m_image_carousel_object_ids: Vec<String>,
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ mod error;
|
||||
mod process;
|
||||
mod remote;
|
||||
|
||||
use crate::{client::commands::queue_url_download, database::db::DatabaseImpls};
|
||||
use crate::{client::commands::queue_url_download, database::db::DatabaseImpls, download_manager::commands::get_queue_metadata};
|
||||
use client::{
|
||||
autostart::{get_autostart_enabled, sync_autostart_on_startup, toggle_autostart},
|
||||
cleanup::{cleanup_and_exit, quit},
|
||||
@ -265,6 +265,7 @@ pub fn run() {
|
||||
cancel_game,
|
||||
uninstall_game,
|
||||
queue_url_download,
|
||||
get_queue_metadata,
|
||||
// Processes
|
||||
launch_game,
|
||||
kill_game,
|
||||
|
||||
10
types.ts
10
types.ts
@ -38,6 +38,7 @@ export type Game = {
|
||||
};
|
||||
|
||||
export type GameVersion = {
|
||||
versionName: string;
|
||||
launchCommandTemplate: string;
|
||||
};
|
||||
|
||||
@ -75,7 +76,7 @@ export enum DownloadableType {
|
||||
|
||||
export type DownloadableMetadata = {
|
||||
id: string;
|
||||
version: string;
|
||||
version?: string;
|
||||
downloadType: DownloadableType;
|
||||
};
|
||||
|
||||
@ -84,3 +85,10 @@ export type Settings = {
|
||||
maxDownloadThreads: number;
|
||||
forceOffline: boolean;
|
||||
};
|
||||
|
||||
|
||||
export type QueueMetadata = {
|
||||
mName: string;
|
||||
cover: string;
|
||||
mShortDescription: string;
|
||||
}
|
||||
Reference in New Issue
Block a user