mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-16 01:31:22 +10:00
Compare commits
10 Commits
v0.3.0-rc-
...
55-feature
| Author | SHA1 | Date | |
|---|---|---|---|
| a5082ee2f3 | |||
| c38f1fbad3 | |||
| 6a3029473c | |||
| 8dfb96406f | |||
| fd61903130 | |||
| abf371c9fc | |||
| e8bcc67ed4 | |||
| 8c403eb8d7 | |||
| 47f64a3c68 | |||
| ae04099daa |
23
.github/workflows/clippy.yml
vendored
23
.github/workflows/clippy.yml
vendored
@ -1,23 +0,0 @@
|
|||||||
on: push
|
|
||||||
name: Clippy check
|
|
||||||
jobs:
|
|
||||||
clippy_check:
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
permissions:
|
|
||||||
checks: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: install dependencies (ubuntu only)
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev
|
|
||||||
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: nightly
|
|
||||||
components: clippy
|
|
||||||
override: true
|
|
||||||
- uses: actions-rs/clippy-check@v1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
args: --manifest-path ./src-tauri/Cargo.toml
|
|
||||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@ -22,9 +22,9 @@ jobs:
|
|||||||
args: '--target aarch64-apple-darwin'
|
args: '--target aarch64-apple-darwin'
|
||||||
- platform: 'macos-latest' # for Intel based macs.
|
- platform: 'macos-latest' # for Intel based macs.
|
||||||
args: '--target x86_64-apple-darwin'
|
args: '--target x86_64-apple-darwin'
|
||||||
- platform: 'ubuntu-22.04' # for Tauri v1 you could replace this with ubuntu-20.04.
|
- platform: 'ubuntu-24.04' # for Tauri v1 you could replace this with ubuntu-20.04.
|
||||||
args: ''
|
args: ''
|
||||||
- platform: 'ubuntu-22.04-arm'
|
- platform: 'ubuntu-24.04-arm'
|
||||||
args: '--target aarch64-unknown-linux-gnu'
|
args: '--target aarch64-unknown-linux-gnu'
|
||||||
- platform: 'windows-latest'
|
- platform: 'windows-latest'
|
||||||
args: ''
|
args: ''
|
||||||
@ -48,11 +48,12 @@ jobs:
|
|||||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||||
|
|
||||||
- name: install dependencies (ubuntu only)
|
- name: install dependencies (ubuntu only)
|
||||||
if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm' # This must match the platform value defined above.
|
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libgtk2.0-dev libsoup3.0-dev
|
||||||
# webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2.
|
# webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2.
|
||||||
|
# You can remove the one that doesn't apply to your app to speed up the workflow a bit.
|
||||||
|
|
||||||
- name: install frontend dependencies
|
- name: install frontend dependencies
|
||||||
run: yarn install # change this to npm, pnpm or bun depending on which one you use.
|
run: yarn install # change this to npm, pnpm or bun depending on which one you use.
|
||||||
|
|||||||
@ -76,7 +76,6 @@ const gameStatusTextStyle: { [key in GameStatusEnum]: string } = {
|
|||||||
[GameStatusEnum.Updating]: "text-blue-500",
|
[GameStatusEnum.Updating]: "text-blue-500",
|
||||||
[GameStatusEnum.Uninstalling]: "text-zinc-100",
|
[GameStatusEnum.Uninstalling]: "text-zinc-100",
|
||||||
[GameStatusEnum.SetupRequired]: "text-yellow-500",
|
[GameStatusEnum.SetupRequired]: "text-yellow-500",
|
||||||
[GameStatusEnum.PartiallyInstalled]: "text-gray-600"
|
|
||||||
};
|
};
|
||||||
const gameStatusText: { [key in GameStatusEnum]: string } = {
|
const gameStatusText: { [key in GameStatusEnum]: string } = {
|
||||||
[GameStatusEnum.Remote]: "Not installed",
|
[GameStatusEnum.Remote]: "Not installed",
|
||||||
@ -87,7 +86,6 @@ const gameStatusText: { [key in GameStatusEnum]: string } = {
|
|||||||
[GameStatusEnum.Uninstalling]: "Uninstalling...",
|
[GameStatusEnum.Uninstalling]: "Uninstalling...",
|
||||||
[GameStatusEnum.SetupRequired]: "Setup required",
|
[GameStatusEnum.SetupRequired]: "Setup required",
|
||||||
[GameStatusEnum.Running]: "Running",
|
[GameStatusEnum.Running]: "Running",
|
||||||
[GameStatusEnum.PartiallyInstalled]: "Partially installed"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "drop-app",
|
"name": "drop-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.3.0-rc-7",
|
"version": "0.3.0-rc-3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<iframe src="server://drop.local/store" class="w-full h-full" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
|||||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@ -1247,7 +1247,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.0-rc-7"
|
version = "0.3.0-rc-3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-instant-full",
|
"atomic-instant-full",
|
||||||
"boxcar",
|
"boxcar",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.0-rc-7"
|
version = "0.3.0-rc-3"
|
||||||
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
||||||
authors = ["Drop OSS"]
|
authors = ["Drop OSS"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@ -16,7 +16,7 @@ pub fn cleanup_and_exit(app: &AppHandle, state: &tauri::State<'_, std::sync::Mut
|
|||||||
Ok(_) => debug!("download manager terminated correctly"),
|
Ok(_) => debug!("download manager terminated correctly"),
|
||||||
Err(_) => error!("download manager failed to terminate correctly"),
|
Err(_) => error!("download manager failed to terminate correctly"),
|
||||||
},
|
},
|
||||||
Err(e) => panic!("{e:?}"),
|
Err(e) => panic!("{:?}", e),
|
||||||
}
|
}
|
||||||
|
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
|
|||||||
@ -82,7 +82,7 @@ pub fn fetch_system_data() -> SystemData {
|
|||||||
SystemData::new(
|
SystemData::new(
|
||||||
db_handle.auth.as_ref().unwrap().client_id.clone(),
|
db_handle.auth.as_ref().unwrap().client_id.clone(),
|
||||||
db_handle.base_url.clone(),
|
db_handle.base_url.clone(),
|
||||||
DATA_ROOT_DIR.to_string_lossy().to_string(),
|
DATA_ROOT_DIR.lock().unwrap().to_string_lossy().to_string(),
|
||||||
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
|
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use std::{
|
|||||||
mem::ManuallyDrop,
|
mem::ManuallyDrop,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, LazyLock, RwLockReadGuard, RwLockWriteGuard},
|
sync::{LazyLock, Mutex, RwLockReadGuard, RwLockWriteGuard},
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@ -17,8 +17,8 @@ use crate::DB;
|
|||||||
|
|
||||||
use super::models::data::Database;
|
use super::models::data::Database;
|
||||||
|
|
||||||
pub static DATA_ROOT_DIR: LazyLock<Arc<PathBuf>> =
|
pub static DATA_ROOT_DIR: LazyLock<Mutex<PathBuf>> =
|
||||||
LazyLock::new(|| Arc::new(dirs::data_dir().unwrap().join("drop")));
|
LazyLock::new(|| Mutex::new(dirs::data_dir().unwrap().join("drop")));
|
||||||
|
|
||||||
// Custom JSON serializer to support everything we need
|
// Custom JSON serializer to support everything we need
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
@ -52,14 +52,15 @@ pub trait DatabaseImpls {
|
|||||||
}
|
}
|
||||||
impl DatabaseImpls for DatabaseInterface {
|
impl DatabaseImpls for DatabaseInterface {
|
||||||
fn set_up_database() -> DatabaseInterface {
|
fn set_up_database() -> DatabaseInterface {
|
||||||
let db_path = DATA_ROOT_DIR.join("drop.db");
|
let data_root_dir = DATA_ROOT_DIR.lock().unwrap();
|
||||||
let games_base_dir = DATA_ROOT_DIR.join("games");
|
let db_path = data_root_dir.join("drop.db");
|
||||||
let logs_root_dir = DATA_ROOT_DIR.join("logs");
|
let games_base_dir = data_root_dir.join("games");
|
||||||
let cache_dir = DATA_ROOT_DIR.join("cache");
|
let logs_root_dir = data_root_dir.join("logs");
|
||||||
let pfx_dir = DATA_ROOT_DIR.join("pfx");
|
let cache_dir = data_root_dir.join("cache");
|
||||||
|
let pfx_dir = data_root_dir.join("pfx");
|
||||||
|
|
||||||
debug!("creating data directory at {DATA_ROOT_DIR:?}");
|
debug!("creating data directory at {:?}", data_root_dir);
|
||||||
create_dir_all(DATA_ROOT_DIR.as_path()).unwrap();
|
create_dir_all(data_root_dir.clone()).unwrap();
|
||||||
create_dir_all(&games_base_dir).unwrap();
|
create_dir_all(&games_base_dir).unwrap();
|
||||||
create_dir_all(&logs_root_dir).unwrap();
|
create_dir_all(&logs_root_dir).unwrap();
|
||||||
create_dir_all(&cache_dir).unwrap();
|
create_dir_all(&cache_dir).unwrap();
|
||||||
@ -101,14 +102,17 @@ fn handle_invalid_database(
|
|||||||
games_base_dir: PathBuf,
|
games_base_dir: PathBuf,
|
||||||
cache_dir: PathBuf,
|
cache_dir: PathBuf,
|
||||||
) -> rustbreak::Database<Database, rustbreak::backend::PathBackend, DropDatabaseSerializer> {
|
) -> rustbreak::Database<Database, rustbreak::backend::PathBackend, DropDatabaseSerializer> {
|
||||||
warn!("{_e}");
|
warn!("{}", _e);
|
||||||
let new_path = {
|
let new_path = {
|
||||||
let time = Utc::now().timestamp();
|
let time = Utc::now().timestamp();
|
||||||
let mut base = db_path.clone();
|
let mut base = db_path.clone();
|
||||||
base.set_file_name(format!("drop.db.backup-{time}"));
|
base.set_file_name(format!("drop.db.backup-{}", time));
|
||||||
base
|
base
|
||||||
};
|
};
|
||||||
info!("old database stored at: {}", new_path.to_string_lossy());
|
info!(
|
||||||
|
"old database stored at: {}",
|
||||||
|
new_path.to_string_lossy().to_string()
|
||||||
|
);
|
||||||
fs::rename(&db_path, &new_path).unwrap();
|
fs::rename(&db_path, &new_path).unwrap();
|
||||||
|
|
||||||
let db = Database::new(
|
let db = Database::new(
|
||||||
@ -151,8 +155,8 @@ impl<'a> Drop for DBWrite<'a> {
|
|||||||
match DB.save() {
|
match DB.save() {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("database failed to save with error {e}");
|
error!("database failed to save with error {}", e);
|
||||||
panic!("database failed to save with error {e}")
|
panic!("database failed to save with error {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,8 +165,8 @@ pub fn borrow_db_checked<'a>() -> DBRead<'a> {
|
|||||||
match DB.borrow_data() {
|
match DB.borrow_data() {
|
||||||
Ok(data) => DBRead(data),
|
Ok(data) => DBRead(data),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("database borrow failed with error {e}");
|
error!("database borrow failed with error {}", e);
|
||||||
panic!("database borrow failed with error {e}");
|
panic!("database borrow failed with error {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,8 +175,8 @@ pub fn borrow_db_mut_checked<'a>() -> DBWrite<'a> {
|
|||||||
match DB.borrow_data_mut() {
|
match DB.borrow_data_mut() {
|
||||||
Ok(data) => DBWrite(ManuallyDrop::new(data)),
|
Ok(data) => DBWrite(ManuallyDrop::new(data)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("database borrow mut failed with error {e}");
|
error!("database borrow mut failed with error {}", e);
|
||||||
panic!("database borrow mut failed with error {e}");
|
panic!("database borrow mut failed with error {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use crate::database::models::data::Database;
|
|||||||
pub mod data {
|
pub mod data {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use native_model::native_model;
|
use native_model::{native_model, Model};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub type GameVersion = v1::GameVersion;
|
pub type GameVersion = v1::GameVersion;
|
||||||
@ -134,7 +134,7 @@ pub mod data {
|
|||||||
pub enum DownloadType {
|
pub enum DownloadType {
|
||||||
Game,
|
Game,
|
||||||
Tool,
|
Tool,
|
||||||
Dlc,
|
DLC,
|
||||||
Mod,
|
Mod,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,11 +38,16 @@ pub enum DownloadManagerSignal {
|
|||||||
Finish,
|
Finish,
|
||||||
/// Stops, removes, and tells a download to cleanup
|
/// Stops, removes, and tells a download to cleanup
|
||||||
Cancel(DownloadableMetadata),
|
Cancel(DownloadableMetadata),
|
||||||
|
/// Removes a given application
|
||||||
|
Remove(DownloadableMetadata),
|
||||||
/// Any error which occurs in the agent
|
/// Any error which occurs in the agent
|
||||||
Error(ApplicationDownloadError),
|
Error(ApplicationDownloadError),
|
||||||
/// Pushes UI update
|
/// Pushes UI update
|
||||||
UpdateUIQueue,
|
UpdateUIQueue,
|
||||||
UpdateUIStats(usize, usize), //kb/s and seconds
|
UpdateUIStats(usize, usize), //kb/s and seconds
|
||||||
|
/// Uninstall download
|
||||||
|
/// Takes download ID
|
||||||
|
Uninstall(DownloadableMetadata),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -50,7 +55,8 @@ pub enum DownloadManagerStatus {
|
|||||||
Downloading,
|
Downloading,
|
||||||
Paused,
|
Paused,
|
||||||
Empty,
|
Empty,
|
||||||
Error,
|
Error(ApplicationDownloadError),
|
||||||
|
Finished,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for DownloadManagerStatus {
|
impl Serialize for DownloadManagerStatus {
|
||||||
@ -58,7 +64,7 @@ impl Serialize for DownloadManagerStatus {
|
|||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
serializer.serialize_str(&format!["{self:?}"])
|
serializer.serialize_str(&format!["{:?}", self])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +72,6 @@ impl Serialize for DownloadManagerStatus {
|
|||||||
pub enum DownloadStatus {
|
pub enum DownloadStatus {
|
||||||
Queued,
|
Queued,
|
||||||
Downloading,
|
Downloading,
|
||||||
Validating,
|
|
||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +154,8 @@ impl DownloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"moving download at index {current_index} to index {new_index}"
|
"moving download at index {} to index {}",
|
||||||
|
current_index, new_index
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut queue = self.edit();
|
let mut queue = self.edit();
|
||||||
@ -180,6 +186,11 @@ impl DownloadManager {
|
|||||||
let terminator = self.terminator.lock().unwrap().take();
|
let terminator = self.terminator.lock().unwrap().take();
|
||||||
terminator.unwrap().join()
|
terminator.unwrap().join()
|
||||||
}
|
}
|
||||||
|
pub fn uninstall_application(&self, meta: DownloadableMetadata) {
|
||||||
|
self.command_sender
|
||||||
|
.send(DownloadManagerSignal::Uninstall(meta))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
pub fn get_sender(&self) -> Sender<DownloadManagerSignal> {
|
pub fn get_sender(&self) -> Sender<DownloadManagerSignal> {
|
||||||
self.command_sender.clone()
|
self.command_sender.clone()
|
||||||
}
|
}
|
||||||
@ -17,7 +17,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
download_manager_frontend::{DownloadManager, DownloadManagerSignal, DownloadManagerStatus},
|
download_manager::{DownloadManager, DownloadManagerSignal, DownloadManagerStatus},
|
||||||
downloadable::Downloadable,
|
downloadable::Downloadable,
|
||||||
util::{
|
util::{
|
||||||
download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag},
|
download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag},
|
||||||
@ -176,6 +176,7 @@ impl DownloadManagerBuilder {
|
|||||||
DownloadManagerSignal::Cancel(meta) => {
|
DownloadManagerSignal::Cancel(meta) => {
|
||||||
self.manage_cancel_signal(&meta);
|
self.manage_cancel_signal(&meta);
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,7 +184,7 @@ impl DownloadManagerBuilder {
|
|||||||
debug!("got signal Queue");
|
debug!("got signal Queue");
|
||||||
let meta = download_agent.metadata();
|
let meta = download_agent.metadata();
|
||||||
|
|
||||||
debug!("queue metadata: {meta:?}");
|
debug!("queue metadata: {:?}", meta);
|
||||||
|
|
||||||
if self.download_queue.exists(meta.clone()) {
|
if self.download_queue.exists(meta.clone()) {
|
||||||
warn!("download with same ID already exists");
|
warn!("download with same ID already exists");
|
||||||
@ -209,8 +210,8 @@ impl DownloadManagerBuilder {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.current_download_agent.is_some()
|
if self.current_download_agent.is_some() {
|
||||||
&& self.download_queue.read().front().unwrap()
|
if self.download_queue.read().front().unwrap()
|
||||||
== &self.current_download_agent.as_ref().unwrap().metadata()
|
== &self.current_download_agent.as_ref().unwrap().metadata()
|
||||||
{
|
{
|
||||||
debug!(
|
debug!(
|
||||||
@ -219,13 +220,14 @@ impl DownloadManagerBuilder {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debug!("current download queue: {:?}", self.download_queue.read());
|
debug!("current download queue: {:?}", self.download_queue.read());
|
||||||
|
|
||||||
// Should always be Some if the above two statements keep going
|
// Should always be Some if the above two statements keep going
|
||||||
let agent_data = self.download_queue.read().front().unwrap().clone();
|
let agent_data = self.download_queue.read().front().unwrap().clone();
|
||||||
|
|
||||||
info!("starting download for {agent_data:?}");
|
info!("starting download for {:?}", agent_data);
|
||||||
|
|
||||||
let download_agent = self
|
let download_agent = self
|
||||||
.download_agent_registry
|
.download_agent_registry
|
||||||
@ -262,8 +264,6 @@ impl DownloadManagerBuilder {
|
|||||||
download_agent.metadata(),
|
download_agent.metadata(),
|
||||||
&e
|
&e
|
||||||
);
|
);
|
||||||
download_agent.on_error(&app_handle, &e);
|
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,7 +311,7 @@ impl DownloadManagerBuilder {
|
|||||||
self.stop_and_wait_current_download();
|
self.stop_and_wait_current_download();
|
||||||
self.remove_and_cleanup_front_download(¤t_agent.metadata());
|
self.remove_and_cleanup_front_download(¤t_agent.metadata());
|
||||||
}
|
}
|
||||||
self.set_status(DownloadManagerStatus::Error);
|
self.set_status(DownloadManagerStatus::Error(error));
|
||||||
}
|
}
|
||||||
fn manage_cancel_signal(&mut self, meta: &DownloadableMetadata) {
|
fn manage_cancel_signal(&mut self, meta: &DownloadableMetadata) {
|
||||||
debug!("got signal Cancel");
|
debug!("got signal Cancel");
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
download_manager_frontend::DownloadStatus,
|
download_manager::DownloadStatus,
|
||||||
util::{download_thread_control_flag::DownloadThreadControl, progress_object::ProgressObject},
|
util::{download_thread_control_flag::DownloadThreadControl, progress_object::ProgressObject},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod download_manager_frontend;
|
pub mod download_manager;
|
||||||
pub mod download_manager_builder;
|
pub mod download_manager_builder;
|
||||||
pub mod downloadable;
|
pub mod downloadable;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use std::{
|
|||||||
use atomic_instant_full::AtomicInstant;
|
use atomic_instant_full::AtomicInstant;
|
||||||
use throttle_my_fn::throttle;
|
use throttle_my_fn::throttle;
|
||||||
|
|
||||||
use crate::download_manager::download_manager_frontend::DownloadManagerSignal;
|
use crate::download_manager::download_manager::DownloadManagerSignal;
|
||||||
|
|
||||||
use super::rolling_progress_updates::RollingProgressWindow;
|
use super::rolling_progress_updates::RollingProgressWindow;
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ pub fn calculate_update(progress: &ProgressObject) {
|
|||||||
|
|
||||||
let kilobytes_per_second = bytes_since_last_update / (time_since_last_update as usize).max(1);
|
let kilobytes_per_second = bytes_since_last_update / (time_since_last_update as usize).max(1);
|
||||||
|
|
||||||
let bytes_remaining = max.saturating_sub(current_bytes_downloaded); // bytes
|
let bytes_remaining = max - current_bytes_downloaded; // bytes
|
||||||
|
|
||||||
progress.update_window(kilobytes_per_second);
|
progress.update_window(kilobytes_per_second);
|
||||||
push_update(progress, bytes_remaining);
|
push_update(progress, bytes_remaining);
|
||||||
|
|||||||
@ -32,13 +32,49 @@ impl Queue {
|
|||||||
pub fn pop_front(&self) -> Option<DownloadableMetadata> {
|
pub fn pop_front(&self) -> Option<DownloadableMetadata> {
|
||||||
self.edit().pop_front()
|
self.edit().pop_front()
|
||||||
}
|
}
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.inner.lock().unwrap().len() == 0
|
||||||
|
}
|
||||||
pub fn exists(&self, meta: DownloadableMetadata) -> bool {
|
pub fn exists(&self, meta: DownloadableMetadata) -> bool {
|
||||||
self.read().contains(&meta)
|
self.read().contains(&meta)
|
||||||
}
|
}
|
||||||
|
/// Either inserts `interface` at the specified index, or appends to
|
||||||
|
/// the back of the deque if index is greater than the length of the deque
|
||||||
|
pub fn insert(&self, interface: DownloadableMetadata, index: usize) {
|
||||||
|
if self.read().len() > index {
|
||||||
|
self.append(interface);
|
||||||
|
} else {
|
||||||
|
self.edit().insert(index, interface);
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn append(&self, interface: DownloadableMetadata) {
|
pub fn append(&self, interface: DownloadableMetadata) {
|
||||||
self.edit().push_back(interface);
|
self.edit().push_back(interface);
|
||||||
}
|
}
|
||||||
|
pub fn pop_front_if_equal(&self, meta: &DownloadableMetadata) -> Option<DownloadableMetadata> {
|
||||||
|
let mut queue = self.edit();
|
||||||
|
let front = queue.front()?;
|
||||||
|
if front == meta {
|
||||||
|
return queue.pop_front();
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
pub fn get_by_meta(&self, meta: &DownloadableMetadata) -> Option<usize> {
|
pub fn get_by_meta(&self, meta: &DownloadableMetadata) -> Option<usize> {
|
||||||
self.read().iter().position(|data| data == meta)
|
self.read().iter().position(|data| data == meta)
|
||||||
}
|
}
|
||||||
|
pub fn move_to_index_by_meta(
|
||||||
|
&self,
|
||||||
|
meta: &DownloadableMetadata,
|
||||||
|
new_index: usize,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
let index = match self.get_by_meta(meta) {
|
||||||
|
Some(index) => index,
|
||||||
|
None => return Err(()),
|
||||||
|
};
|
||||||
|
let existing = match self.edit().remove(index) {
|
||||||
|
Some(existing) => existing,
|
||||||
|
None => return Err(()),
|
||||||
|
};
|
||||||
|
self.edit().insert(new_index, existing);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,13 +5,14 @@ use std::{
|
|||||||
|
|
||||||
use serde_with::SerializeDisplay;
|
use serde_with::SerializeDisplay;
|
||||||
|
|
||||||
use super::{remote_access_error::RemoteAccessError};
|
use super::{remote_access_error::RemoteAccessError, setup_error::SetupError};
|
||||||
|
|
||||||
// TODO: Rename / separate from downloads
|
// TODO: Rename / separate from downloads
|
||||||
#[derive(Debug, SerializeDisplay)]
|
#[derive(Debug, SerializeDisplay)]
|
||||||
pub enum ApplicationDownloadError {
|
pub enum ApplicationDownloadError {
|
||||||
Communication(RemoteAccessError),
|
Communication(RemoteAccessError),
|
||||||
Checksum,
|
Checksum,
|
||||||
|
Setup(SetupError),
|
||||||
Lock,
|
Lock,
|
||||||
IoError(io::ErrorKind),
|
IoError(io::ErrorKind),
|
||||||
DownloadError,
|
DownloadError,
|
||||||
@ -20,10 +21,11 @@ pub enum ApplicationDownloadError {
|
|||||||
impl Display for ApplicationDownloadError {
|
impl Display for ApplicationDownloadError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ApplicationDownloadError::Communication(error) => write!(f, "{error}"),
|
ApplicationDownloadError::Communication(error) => write!(f, "{}", error),
|
||||||
|
ApplicationDownloadError::Setup(error) => write!(f, "an error occurred while setting up the download: {}", error),
|
||||||
ApplicationDownloadError::Lock => write!(f, "failed to acquire lock. Something has gone very wrong internally. Please restart the application"),
|
ApplicationDownloadError::Lock => write!(f, "failed to acquire lock. Something has gone very wrong internally. Please restart the application"),
|
||||||
ApplicationDownloadError::Checksum => write!(f, "checksum failed to validate for download"),
|
ApplicationDownloadError::Checksum => write!(f, "checksum failed to validate for download"),
|
||||||
ApplicationDownloadError::IoError(error) => write!(f, "io error: {error}"),
|
ApplicationDownloadError::IoError(error) => write!(f, "{}", error),
|
||||||
ApplicationDownloadError::DownloadError => write!(f, "download failed. See Download Manager status for specific error"),
|
ApplicationDownloadError::DownloadError => write!(f, "download failed. See Download Manager status for specific error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
src-tauri/src/error/backup_error.rs
Normal file
21
src-tauri/src/error/backup_error.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde_with::SerializeDisplay;
|
||||||
|
|
||||||
|
#[derive(Debug, SerializeDisplay, Clone, Copy)]
|
||||||
|
pub enum BackupError {
|
||||||
|
InvalidSystem,
|
||||||
|
NotFound,
|
||||||
|
ParseError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for BackupError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
BackupError::InvalidSystem => "Attempted to generate path for invalid system",
|
||||||
|
BackupError::NotFound => "Could not generate or find path",
|
||||||
|
BackupError::ParseError => "Failed to parse path",
|
||||||
|
};
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,8 +10,8 @@ pub enum DownloadManagerError<T> {
|
|||||||
impl<T> Display for DownloadManagerError<T> {
|
impl<T> Display for DownloadManagerError<T> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
DownloadManagerError::IOError(error) => write!(f, "{error}"),
|
DownloadManagerError::IOError(error) => write!(f, "{}", error),
|
||||||
DownloadManagerError::SignalError(send_error) => write!(f, "{send_error}"),
|
DownloadManagerError::SignalError(send_error) => write!(f, "{}", send_error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,6 @@ use serde::Deserialize;
|
|||||||
pub struct DropServerError {
|
pub struct DropServerError {
|
||||||
pub status_code: usize,
|
pub status_code: usize,
|
||||||
pub status_message: String,
|
pub status_message: String,
|
||||||
// pub message: String,
|
pub message: String,
|
||||||
// pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,8 @@ impl Display for LibraryError {
|
|||||||
match self {
|
match self {
|
||||||
LibraryError::MetaNotFound(id) => write!(
|
LibraryError::MetaNotFound(id) => write!(
|
||||||
f,
|
f,
|
||||||
"Could not locate any installed version of game ID {id} in the database"
|
"Could not locate any installed version of game ID {} in the database",
|
||||||
|
id
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
pub mod application_download_error;
|
pub mod application_download_error;
|
||||||
|
pub mod backup_error;
|
||||||
pub mod download_manager_error;
|
pub mod download_manager_error;
|
||||||
pub mod drop_server_error;
|
pub mod drop_server_error;
|
||||||
pub mod library_error;
|
pub mod library_error;
|
||||||
pub mod process_error;
|
pub mod process_error;
|
||||||
pub mod remote_access_error;
|
pub mod remote_access_error;
|
||||||
|
pub mod setup_error;
|
||||||
|
|||||||
@ -26,8 +26,8 @@ impl Display for ProcessError {
|
|||||||
ProcessError::InvalidVersion => "Invalid Game version",
|
ProcessError::InvalidVersion => "Invalid Game version",
|
||||||
ProcessError::IOError(error) => &error.to_string(),
|
ProcessError::IOError(error) => &error.to_string(),
|
||||||
ProcessError::InvalidPlatform => "This Game cannot be played on the current platform",
|
ProcessError::InvalidPlatform => "This Game cannot be played on the current platform",
|
||||||
ProcessError::FormatError(e) => &format!("Failed to format template: {e}"),
|
ProcessError::FormatError(e) => &format!("Failed to format template: {}", e),
|
||||||
};
|
};
|
||||||
write!(f, "{s}")
|
write!(f, "{}", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ pub enum RemoteAccessError {
|
|||||||
HandshakeFailed(String),
|
HandshakeFailed(String),
|
||||||
GameNotFound(String),
|
GameNotFound(String),
|
||||||
InvalidResponse(DropServerError),
|
InvalidResponse(DropServerError),
|
||||||
UnparseableResponse(String),
|
InvalidRedirect,
|
||||||
ManifestDownloadFailed(StatusCode, String),
|
ManifestDownloadFailed(StatusCode, String),
|
||||||
OutOfSync,
|
OutOfSync,
|
||||||
Cache(cacache::Error),
|
Cache(cacache::Error),
|
||||||
@ -44,19 +44,20 @@ impl Display for RemoteAccessError {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
RemoteAccessError::ParsingError(parse_error) => {
|
RemoteAccessError::ParsingError(parse_error) => {
|
||||||
write!(f, "{parse_error}")
|
write!(f, "{}", parse_error)
|
||||||
}
|
}
|
||||||
RemoteAccessError::InvalidEndpoint => write!(f, "invalid drop endpoint"),
|
RemoteAccessError::InvalidEndpoint => write!(f, "invalid drop endpoint"),
|
||||||
RemoteAccessError::HandshakeFailed(message) => write!(f, "failed to complete handshake: {message}"),
|
RemoteAccessError::HandshakeFailed(message) => write!(f, "failed to complete handshake: {}", message),
|
||||||
RemoteAccessError::GameNotFound(id) => write!(f, "could not find game on server: {id}"),
|
RemoteAccessError::GameNotFound(id) => write!(f, "could not find game on server: {}", id),
|
||||||
RemoteAccessError::InvalidResponse(error) => write!(f, "server returned an invalid response: {}, {}", error.status_code, error.status_message),
|
RemoteAccessError::InvalidResponse(error) => write!(f, "server returned an invalid response: {} {}", error.status_code, error.status_message),
|
||||||
RemoteAccessError::UnparseableResponse(error) => write!(f, "server returned an invalid response: {error}"),
|
RemoteAccessError::InvalidRedirect => write!(f, "server redirect was invalid"),
|
||||||
RemoteAccessError::ManifestDownloadFailed(status, response) => write!(
|
RemoteAccessError::ManifestDownloadFailed(status, response) => write!(
|
||||||
f,
|
f,
|
||||||
"failed to download game manifest: {status} {response}"
|
"failed to download game manifest: {} {}",
|
||||||
|
status, response
|
||||||
),
|
),
|
||||||
RemoteAccessError::OutOfSync => write!(f, "server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"),
|
RemoteAccessError::OutOfSync => write!(f, "server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"),
|
||||||
RemoteAccessError::Cache(error) => write!(f, "Cache Error: {error}"),
|
RemoteAccessError::Cache(error) => write!(f, "Cache Error: {}", error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
src-tauri/src/error/setup_error.rs
Normal file
14
src-tauri/src/error/setup_error.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SetupError {
|
||||||
|
Context,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SetupError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
SetupError::Context => write!(f, "failed to generate contexts for download"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -41,7 +41,7 @@ pub fn create_collection(name: String) -> Result<Collection, RemoteAccessError>
|
|||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let base_url = DB.fetch_base_url();
|
let base_url = DB.fetch_base_url();
|
||||||
|
|
||||||
let base_url = Url::parse(&format!("{base_url}api/v1/client/collection/"))?;
|
let base_url = Url::parse(&format!("{}api/v1/client/collection/", base_url))?;
|
||||||
|
|
||||||
let response = client
|
let response = client
|
||||||
.post(base_url)
|
.post(base_url)
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::{db::borrow_db_checked, models::data::GameDownloadStatus},
|
database::{db::borrow_db_checked, models::data::GameDownloadStatus},
|
||||||
download_manager::{download_manager_frontend::DownloadManagerSignal, downloadable::Downloadable},
|
download_manager::{download_manager::DownloadManagerSignal, downloadable::Downloadable},
|
||||||
error::download_manager_error::DownloadManagerError,
|
error::download_manager_error::DownloadManagerError,
|
||||||
AppState,
|
AppState,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use crate::database::db::{borrow_db_checked, borrow_db_mut_checked};
|
|||||||
use crate::database::models::data::{
|
use crate::database::models::data::{
|
||||||
ApplicationTransientStatus, DownloadType, DownloadableMetadata,
|
ApplicationTransientStatus, DownloadType, DownloadableMetadata,
|
||||||
};
|
};
|
||||||
use crate::download_manager::download_manager_frontend::{DownloadManagerSignal, DownloadStatus};
|
use crate::download_manager::download_manager::{DownloadManagerSignal, DownloadStatus};
|
||||||
use crate::download_manager::downloadable::Downloadable;
|
use crate::download_manager::downloadable::Downloadable;
|
||||||
use crate::download_manager::util::download_thread_control_flag::{
|
use crate::download_manager::util::download_thread_control_flag::{
|
||||||
DownloadThreadControl, DownloadThreadControlFlag,
|
DownloadThreadControl, DownloadThreadControlFlag,
|
||||||
@ -212,7 +212,6 @@ impl GameDownloadAgent {
|
|||||||
let file = OpenOptions::new()
|
let file = OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.truncate(true)
|
|
||||||
.create(true)
|
.create(true)
|
||||||
.open(path.clone())
|
.open(path.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -268,7 +267,7 @@ impl GameDownloadAgent {
|
|||||||
let completed_indexes_loop_arc = completed_contexts.clone();
|
let completed_indexes_loop_arc = completed_contexts.clone();
|
||||||
|
|
||||||
let contexts = self.contexts.lock().unwrap();
|
let contexts = self.contexts.lock().unwrap();
|
||||||
debug!("{contexts:#?}");
|
debug!("{:#?}", contexts);
|
||||||
pool.scope(|scope| {
|
pool.scope(|scope| {
|
||||||
let client = &reqwest::blocking::Client::new();
|
let client = &reqwest::blocking::Client::new();
|
||||||
let context_map = self.context_map.lock().unwrap();
|
let context_map = self.context_map.lock().unwrap();
|
||||||
@ -285,6 +284,8 @@ impl GameDownloadAgent {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("Continuing download chunk {}", index);
|
||||||
|
|
||||||
let sender = self.sender.clone();
|
let sender = self.sender.clone();
|
||||||
|
|
||||||
let request = match make_request(
|
let request = match make_request(
|
||||||
@ -296,7 +297,7 @@ impl GameDownloadAgent {
|
|||||||
("name", &context.file_name),
|
("name", &context.file_name),
|
||||||
("chunk", &context.index.to_string()),
|
("chunk", &context.index.to_string()),
|
||||||
],
|
],
|
||||||
|r| r,
|
|r| r.header("Authorization", generate_authorization_header()),
|
||||||
) {
|
) {
|
||||||
Ok(request) => request,
|
Ok(request) => request,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -313,11 +314,20 @@ impl GameDownloadAgent {
|
|||||||
match download_game_chunk(context, &self.control_flag, progress_handle, request)
|
match download_game_chunk(context, &self.control_flag, progress_handle, request)
|
||||||
{
|
{
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
|
debug!(
|
||||||
|
"Finished context #{} with checksum {}",
|
||||||
|
index, context.checksum
|
||||||
|
);
|
||||||
completed_indexes.push(context.checksum.clone());
|
completed_indexes.push(context.checksum.clone());
|
||||||
}
|
}
|
||||||
Ok(false) => {}
|
Ok(false) => {
|
||||||
|
debug!(
|
||||||
|
"Didn't finish context #{} with checksum {}",
|
||||||
|
index, context.checksum
|
||||||
|
);
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{e}");
|
error!("{}", e);
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -341,7 +351,11 @@ impl GameDownloadAgent {
|
|||||||
.map(|x| {
|
.map(|x| {
|
||||||
(
|
(
|
||||||
x.checksum.clone(),
|
x.checksum.clone(),
|
||||||
context_map_lock.get(&x.checksum).cloned().unwrap_or(false),
|
context_map_lock
|
||||||
|
.get(&x.checksum)
|
||||||
|
.cloned()
|
||||||
|
.or(Some(false))
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<(String, bool)>>();
|
.collect::<Vec<(String, bool)>>();
|
||||||
@ -397,7 +411,7 @@ impl Downloadable for GameDownloadAgent {
|
|||||||
.emit("download_error", error.to_string())
|
.emit("download_error", error.to_string())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
error!("error while managing download: {error}");
|
error!("error while managing download: {}", error);
|
||||||
|
|
||||||
let mut handle = borrow_db_mut_checked();
|
let mut handle = borrow_db_mut_checked();
|
||||||
handle
|
handle
|
||||||
@ -421,8 +435,7 @@ impl Downloadable for GameDownloadAgent {
|
|||||||
&self.metadata(),
|
&self.metadata(),
|
||||||
self.stored_manifest.base_path.to_string_lossy().to_string(),
|
self.stored_manifest.base_path.to_string_lossy().to_string(),
|
||||||
app_handle,
|
app_handle,
|
||||||
)
|
);
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {}
|
fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {}
|
||||||
@ -432,7 +445,6 @@ impl Downloadable for GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self) -> Result<bool, ApplicationDownloadError> {
|
fn validate(&self) -> Result<bool, ApplicationDownloadError> {
|
||||||
*self.status.lock().unwrap() = DownloadStatus::Validating;
|
|
||||||
game_validate_logic(
|
game_validate_logic(
|
||||||
&self.stored_manifest,
|
&self.stored_manifest,
|
||||||
self.contexts.lock().unwrap().clone(),
|
self.contexts.lock().unwrap().clone(),
|
||||||
|
|||||||
@ -3,16 +3,15 @@ use crate::download_manager::util::download_thread_control_flag::{
|
|||||||
};
|
};
|
||||||
use crate::download_manager::util::progress_object::ProgressHandle;
|
use crate::download_manager::util::progress_object::ProgressHandle;
|
||||||
use crate::error::application_download_error::ApplicationDownloadError;
|
use crate::error::application_download_error::ApplicationDownloadError;
|
||||||
use crate::error::drop_server_error::DropServerError;
|
|
||||||
use crate::error::remote_access_error::RemoteAccessError;
|
use crate::error::remote_access_error::RemoteAccessError;
|
||||||
use crate::games::downloads::manifest::DropDownloadContext;
|
use crate::games::downloads::manifest::DropDownloadContext;
|
||||||
use crate::remote::auth::generate_authorization_header;
|
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use md5::{Context, Digest};
|
use md5::{Context, Digest};
|
||||||
use reqwest::blocking::{RequestBuilder, Response};
|
use reqwest::blocking::{RequestBuilder, Response};
|
||||||
|
|
||||||
use std::fs::{set_permissions, Permissions};
|
use std::fs::{set_permissions, Permissions};
|
||||||
use std::io::Read;
|
use std::io::{ErrorKind, Read};
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::{
|
use std::{
|
||||||
@ -41,9 +40,12 @@ 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
|
self.hasher.write_all(buf).map_err(|e| {
|
||||||
.write_all(buf)
|
io::Error::new(
|
||||||
.map_err(|e| io::Error::other(format!("Unable to write to hasher: {e}")))?;
|
ErrorKind::Other,
|
||||||
|
format!("Unable to write to hasher: {}", e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
self.destination.write(buf)
|
self.destination.write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,24 +97,13 @@ impl<'a> DropDownloadPipeline<'a, Response, File> {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bytes_read = self.source.read(&mut copy_buf)?;
|
let bytes_read = self.source.read(&mut copy_buf)?;
|
||||||
current_size += bytes_read;
|
current_size += bytes_read;
|
||||||
|
|
||||||
if current_size > self.size {
|
|
||||||
let over = current_size - self.size;
|
|
||||||
warn!("server sent too many bytes... {over} over");
|
|
||||||
bytes_read -= over;
|
|
||||||
current_size = self.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf_writer.write_all(©_buf[0..bytes_read])?;
|
buf_writer.write_all(©_buf[0..bytes_read])?;
|
||||||
self.progress.add(bytes_read);
|
self.progress.add(bytes_read);
|
||||||
|
|
||||||
if current_size >= self.size {
|
if current_size == self.size {
|
||||||
debug!(
|
|
||||||
"finished with final size of {} vs {}",
|
|
||||||
current_size, self.size
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,27 +124,25 @@ pub fn download_game_chunk(
|
|||||||
progress: ProgressHandle,
|
progress: ProgressHandle,
|
||||||
request: RequestBuilder,
|
request: RequestBuilder,
|
||||||
) -> Result<bool, ApplicationDownloadError> {
|
) -> Result<bool, ApplicationDownloadError> {
|
||||||
|
debug!(
|
||||||
|
"Starting download chunk {}, {}, {} #{}",
|
||||||
|
ctx.file_name, ctx.index, ctx.offset, ctx.checksum
|
||||||
|
);
|
||||||
// If we're paused
|
// If we're paused
|
||||||
if control_flag.get() == DownloadThreadControlFlag::Stop {
|
if control_flag.get() == DownloadThreadControlFlag::Stop {
|
||||||
progress.set(0);
|
progress.set(0);
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = request
|
let response = request
|
||||||
.header("Authorization", generate_authorization_header())
|
|
||||||
.send()
|
.send()
|
||||||
.map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
|
.map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
|
||||||
|
|
||||||
if response.status() != 200 {
|
if response.status() != 200 {
|
||||||
debug!("chunk request got status code: {}", response.status());
|
let err = response.json().unwrap();
|
||||||
let raw_res = response.text().unwrap();
|
|
||||||
if let Ok(err) = serde_json::from_str::<DropServerError>(&raw_res) {
|
|
||||||
return Err(ApplicationDownloadError::Communication(
|
return Err(ApplicationDownloadError::Communication(
|
||||||
RemoteAccessError::InvalidResponse(err),
|
RemoteAccessError::InvalidResponse(err),
|
||||||
));
|
));
|
||||||
};
|
|
||||||
return Err(ApplicationDownloadError::Communication(
|
|
||||||
RemoteAccessError::UnparseableResponse(raw_res),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut destination = DropWriter::new(ctx.path.clone());
|
let mut destination = DropWriter::new(ctx.path.clone());
|
||||||
|
|||||||
@ -43,7 +43,7 @@ impl DropData {
|
|||||||
let mut file = match File::open(base_path.join(DROP_DATA_PATH)) {
|
let mut file = match File::open(base_path.join(DROP_DATA_PATH)) {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
debug!("Generating new dropdata for game {game_id}");
|
debug!("Generating new dropdata for game {}", game_id);
|
||||||
return DropData::new(game_id, game_version, base_path);
|
return DropData::new(game_id, game_version, base_path);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -52,7 +52,7 @@ impl DropData {
|
|||||||
match file.read_to_end(&mut s) {
|
match file.read_to_end(&mut s) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{e}");
|
error!("{}", e);
|
||||||
return DropData::new(game_id, game_version, base_path);
|
return DropData::new(game_id, game_version, base_path);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -60,7 +60,7 @@ impl DropData {
|
|||||||
match native_model::rmp_serde_1_3::RmpSerde::decode(s) {
|
match native_model::rmp_serde_1_3::RmpSerde::decode(s) {
|
||||||
Ok(manifest) => manifest,
|
Ok(manifest) => manifest,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("{e}");
|
warn!("{}", e);
|
||||||
DropData::new(game_id, game_version, base_path)
|
DropData::new(game_id, game_version, base_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,14 +74,14 @@ impl DropData {
|
|||||||
let mut file = match File::create(self.base_path.join(DROP_DATA_PATH)) {
|
let mut file = match File::create(self.base_path.join(DROP_DATA_PATH)) {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{e}");
|
error!("{}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match file.write_all(&manifest_raw) {
|
match file.write_all(&manifest_raw) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => error!("{e}"),
|
Err(e) => error!("{}", e),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn set_contexts(&self, completed_contexts: &[(String, bool)]) {
|
pub fn set_contexts(&self, completed_contexts: &[(String, bool)]) {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use rayon::ThreadPoolBuilder;
|
|||||||
use crate::{
|
use crate::{
|
||||||
database::db::borrow_db_checked,
|
database::db::borrow_db_checked,
|
||||||
download_manager::{
|
download_manager::{
|
||||||
download_manager_frontend::DownloadManagerSignal,
|
download_manager::DownloadManagerSignal,
|
||||||
util::{
|
util::{
|
||||||
download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag},
|
download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag},
|
||||||
progress_object::{ProgressHandle, ProgressObject},
|
progress_object::{ProgressHandle, ProgressObject},
|
||||||
@ -19,6 +19,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
error::application_download_error::ApplicationDownloadError,
|
error::application_download_error::ApplicationDownloadError,
|
||||||
games::downloads::{drop_data::DropData, manifest::DropDownloadContext},
|
games::downloads::{drop_data::DropData, manifest::DropDownloadContext},
|
||||||
|
remote::{auth::generate_authorization_header, requests::make_request},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn game_validate_logic(
|
pub fn game_validate_logic(
|
||||||
@ -40,15 +41,40 @@ pub fn game_validate_logic(
|
|||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
debug!("{contexts:#?}");
|
debug!("{:#?}", contexts);
|
||||||
let invalid_chunks = Arc::new(boxcar::Vec::new());
|
let invalid_chunks = Arc::new(boxcar::Vec::new());
|
||||||
pool.scope(|scope| {
|
pool.scope(|scope| {
|
||||||
|
let client = &reqwest::blocking::Client::new();
|
||||||
for (index, context) in contexts.iter().enumerate() {
|
for (index, context) in contexts.iter().enumerate() {
|
||||||
|
let client = client.clone();
|
||||||
|
|
||||||
let current_progress = progress.get(index);
|
let current_progress = progress.get(index);
|
||||||
let progress_handle = ProgressHandle::new(current_progress, progress.clone());
|
let progress_handle = ProgressHandle::new(current_progress, progress.clone());
|
||||||
let invalid_chunks_scoped = invalid_chunks.clone();
|
let invalid_chunks_scoped = invalid_chunks.clone();
|
||||||
let sender = sender.clone();
|
let sender = sender.clone();
|
||||||
|
|
||||||
|
let request = match make_request(
|
||||||
|
&client,
|
||||||
|
&["/api/v1/client/chunk"],
|
||||||
|
&[
|
||||||
|
("id", &context.game_id),
|
||||||
|
("version", &context.version),
|
||||||
|
("name", &context.file_name),
|
||||||
|
("chunk", &context.index.to_string()),
|
||||||
|
],
|
||||||
|
|r| r.header("Authorization", generate_authorization_header()),
|
||||||
|
) {
|
||||||
|
Ok(request) => request,
|
||||||
|
Err(e) => {
|
||||||
|
sender
|
||||||
|
.send(DownloadManagerSignal::Error(
|
||||||
|
ApplicationDownloadError::Communication(e),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
scope.spawn(move |_| {
|
scope.spawn(move |_| {
|
||||||
match validate_game_chunk(context, control_flag, progress_handle) {
|
match validate_game_chunk(context, control_flag, progress_handle) {
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
@ -65,7 +91,7 @@ pub fn game_validate_logic(
|
|||||||
invalid_chunks_scoped.push(context.checksum.clone());
|
invalid_chunks_scoped.push(context.checksum.clone());
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{e}");
|
error!("{}", e);
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,8 +137,7 @@ pub fn validate_game_chunk(
|
|||||||
|
|
||||||
let mut hasher = md5::Context::new();
|
let mut hasher = md5::Context::new();
|
||||||
|
|
||||||
let completed =
|
let completed = validate_copy(&mut source, &mut hasher, control_flag, progress).unwrap();
|
||||||
validate_copy(&mut source, &mut hasher, ctx.length, control_flag, progress).unwrap();
|
|
||||||
if !completed {
|
if !completed {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
@ -137,14 +162,12 @@ pub fn validate_game_chunk(
|
|||||||
fn validate_copy(
|
fn validate_copy(
|
||||||
source: &mut File,
|
source: &mut File,
|
||||||
dest: &mut Context,
|
dest: &mut Context,
|
||||||
size: usize,
|
|
||||||
control_flag: &DownloadThreadControl,
|
control_flag: &DownloadThreadControl,
|
||||||
progress: ProgressHandle,
|
progress: ProgressHandle,
|
||||||
) -> Result<bool, io::Error> {
|
) -> Result<bool, io::Error> {
|
||||||
let copy_buf_size = 512;
|
let copy_buf_size = 512;
|
||||||
let mut copy_buf = vec![0; copy_buf_size];
|
let mut copy_buf = vec![0; copy_buf_size];
|
||||||
let mut buf_writer = BufWriter::with_capacity(1024 * 1024, dest);
|
let mut buf_writer = BufWriter::with_capacity(1024 * 1024, dest);
|
||||||
let mut total_bytes = 0;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if control_flag.get() == DownloadThreadControlFlag::Stop {
|
if control_flag.get() == DownloadThreadControlFlag::Stop {
|
||||||
@ -152,21 +175,12 @@ fn validate_copy(
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bytes_read = source.read(&mut copy_buf)?;
|
let bytes_read = source.read(&mut copy_buf)?;
|
||||||
total_bytes += bytes_read;
|
|
||||||
|
|
||||||
// If we read over (likely), truncate our read to
|
|
||||||
// the right size
|
|
||||||
if total_bytes > size {
|
|
||||||
let over = total_bytes - size;
|
|
||||||
bytes_read -= over;
|
|
||||||
total_bytes = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf_writer.write_all(©_buf[0..bytes_read])?;
|
buf_writer.write_all(©_buf[0..bytes_read])?;
|
||||||
progress.add(bytes_read);
|
progress.add(bytes_read);
|
||||||
|
|
||||||
if total_bytes >= size {
|
if bytes_read == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use crate::database::db::{borrow_db_checked, borrow_db_mut_checked};
|
|||||||
use crate::database::models::data::{
|
use crate::database::models::data::{
|
||||||
ApplicationTransientStatus, DownloadableMetadata, GameDownloadStatus, GameVersion,
|
ApplicationTransientStatus, DownloadableMetadata, GameDownloadStatus, GameVersion,
|
||||||
};
|
};
|
||||||
use crate::download_manager::download_manager_frontend::DownloadStatus;
|
use crate::download_manager::download_manager::DownloadStatus;
|
||||||
use crate::error::library_error::LibraryError;
|
use crate::error::library_error::LibraryError;
|
||||||
use crate::error::remote_access_error::RemoteAccessError;
|
use crate::error::remote_access_error::RemoteAccessError;
|
||||||
use crate::games::state::{GameStatusManager, GameStatusWithTransient};
|
use crate::games::state::{GameStatusManager, GameStatusWithTransient};
|
||||||
@ -85,7 +85,7 @@ pub fn fetch_library_logic(
|
|||||||
|
|
||||||
if response.status() != 200 {
|
if response.status() != 200 {
|
||||||
let err = response.json().unwrap();
|
let err = response.json().unwrap();
|
||||||
warn!("{err:?}");
|
warn!("{:?}", err);
|
||||||
return Err(RemoteAccessError::InvalidResponse(err));
|
return Err(RemoteAccessError::InvalidResponse(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,8 +106,8 @@ pub fn fetch_library_logic(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add games that are installed but no longer in library
|
// Add games that are installed but no longer in library
|
||||||
for meta in db_handle.applications.installed_game_version.values() {
|
for (_, meta) in &db_handle.applications.installed_game_version {
|
||||||
if games.iter().any(|e| e.id == meta.id) {
|
if games.iter().find(|e| e.id == meta.id).is_some() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// We should always have a cache of the object
|
// We should always have a cache of the object
|
||||||
@ -187,7 +187,7 @@ pub fn fetch_game_logic(
|
|||||||
}
|
}
|
||||||
if response.status() != 200 {
|
if response.status() != 200 {
|
||||||
let err = response.json().unwrap();
|
let err = response.json().unwrap();
|
||||||
warn!("{err:?}");
|
warn!("{:?}", err);
|
||||||
return Err(RemoteAccessError::InvalidResponse(err));
|
return Err(RemoteAccessError::InvalidResponse(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,7 +263,7 @@ pub fn fetch_game_verion_options_logic(
|
|||||||
|
|
||||||
if response.status() != 200 {
|
if response.status() != 200 {
|
||||||
let err = response.json().unwrap();
|
let err = response.json().unwrap();
|
||||||
warn!("{err:?}");
|
warn!("{:?}", err);
|
||||||
return Err(RemoteAccessError::InvalidResponse(err));
|
return Err(RemoteAccessError::InvalidResponse(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,7 +330,7 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
|
|||||||
let app_handle = app_handle.clone();
|
let app_handle = app_handle.clone();
|
||||||
spawn(move || match remove_dir_all(install_dir) {
|
spawn(move || match remove_dir_all(install_dir) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{e}");
|
error!("{}", e);
|
||||||
}
|
}
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let mut db_handle = borrow_db_mut_checked();
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
@ -347,7 +347,7 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
|
|||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
|
|
||||||
debug!("uninstalled game id {}", &meta.id);
|
debug!("uninstalled game id {}", &meta.id);
|
||||||
app_handle.emit("update_library", ()).unwrap();
|
app_handle.emit("update_library", {}).unwrap();
|
||||||
|
|
||||||
push_game_update(
|
push_game_update(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
@ -510,7 +510,7 @@ pub fn push_game_update(
|
|||||||
) {
|
) {
|
||||||
app_handle
|
app_handle
|
||||||
.emit(
|
.emit(
|
||||||
&format!("update_game/{game_id}"),
|
&format!("update_game/{}", game_id),
|
||||||
GameUpdateEvent {
|
GameUpdateEvent {
|
||||||
game_id: game_id.clone(),
|
game_id: game_id.clone(),
|
||||||
status,
|
status,
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
#![feature(fn_traits)]
|
|
||||||
#![deny(clippy::all)]
|
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
mod games;
|
mod games;
|
||||||
|
|
||||||
@ -25,7 +22,7 @@ use database::models::data::GameDownloadStatus;
|
|||||||
use download_manager::commands::{
|
use download_manager::commands::{
|
||||||
cancel_game, move_download_in_queue, pause_downloads, resume_downloads,
|
cancel_game, move_download_in_queue, pause_downloads, resume_downloads,
|
||||||
};
|
};
|
||||||
use download_manager::download_manager_frontend::DownloadManager;
|
use download_manager::download_manager::DownloadManager;
|
||||||
use download_manager::download_manager_builder::DownloadManagerBuilder;
|
use download_manager::download_manager_builder::DownloadManagerBuilder;
|
||||||
use games::collections::commands::{
|
use games::collections::commands::{
|
||||||
add_game_to_collection, create_collection, delete_collection, delete_game_in_collection,
|
add_game_to_collection, create_collection, delete_collection, delete_game_in_collection,
|
||||||
@ -52,18 +49,14 @@ use remote::commands::{
|
|||||||
use remote::fetch_object::{fetch_object, fetch_object_offline};
|
use remote::fetch_object::{fetch_object, fetch_object_offline};
|
||||||
use remote::server_proto::{handle_server_proto, handle_server_proto_offline};
|
use remote::server_proto::{handle_server_proto, handle_server_proto_offline};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs::File;
|
use std::env;
|
||||||
use std::io::Write;
|
|
||||||
use std::panic::PanicHookInfo;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::SystemTime;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::{LazyLock, Mutex},
|
sync::{LazyLock, Mutex},
|
||||||
};
|
};
|
||||||
use std::{env, panic};
|
|
||||||
use tauri::menu::{Menu, MenuItem, PredefinedMenuItem};
|
use tauri::menu::{Menu, MenuItem, PredefinedMenuItem};
|
||||||
use tauri::tray::TrayIconBuilder;
|
use tauri::tray::TrayIconBuilder;
|
||||||
use tauri::{AppHandle, Manager, RunEvent, WindowEvent};
|
use tauri::{AppHandle, Manager, RunEvent, WindowEvent};
|
||||||
@ -110,7 +103,7 @@ fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
"{d} | {l} | {f}:{L} - {m}{n}",
|
"{d} | {l} | {f}:{L} - {m}{n}",
|
||||||
)))
|
)))
|
||||||
.append(false)
|
.append(false)
|
||||||
.build(DATA_ROOT_DIR.join("./drop.log"))
|
.build(DATA_ROOT_DIR.lock().unwrap().join("./drop.log"))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let console = ConsoleAppender::builder()
|
let console = ConsoleAppender::builder()
|
||||||
@ -185,7 +178,7 @@ fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("detected games missing: {missing_games:?}");
|
info!("detected games missing: {:?}", missing_games);
|
||||||
|
|
||||||
let mut db_handle = borrow_db_mut_checked();
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
for game_id in missing_games {
|
for game_id in missing_games {
|
||||||
@ -202,7 +195,7 @@ fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
|
|
||||||
// Sync autostart state
|
// Sync autostart state
|
||||||
if let Err(e) = sync_autostart_on_startup(&handle) {
|
if let Err(e) = sync_autostart_on_startup(&handle) {
|
||||||
warn!("failed to sync autostart state: {e}");
|
warn!("failed to sync autostart state: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppState {
|
AppState {
|
||||||
@ -216,29 +209,8 @@ fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
|
|
||||||
pub static DB: LazyLock<DatabaseInterface> = LazyLock::new(DatabaseInterface::set_up_database);
|
pub static DB: LazyLock<DatabaseInterface> = LazyLock::new(DatabaseInterface::set_up_database);
|
||||||
|
|
||||||
pub fn custom_panic_handler(e: &PanicHookInfo) -> Option<()> {
|
|
||||||
let crash_file = DATA_ROOT_DIR.join(format!(
|
|
||||||
"crash-{}.log",
|
|
||||||
SystemTime::now()
|
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
|
||||||
.ok()?
|
|
||||||
.as_secs()
|
|
||||||
));
|
|
||||||
let mut file = File::create_new(crash_file).ok()?;
|
|
||||||
file.write_all(format!("Drop crashed with the following panic:\n{e}").as_bytes()).ok()?;
|
|
||||||
drop(file);
|
|
||||||
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
panic::set_hook(Box::new(|e| {
|
|
||||||
let _ = custom_panic_handler(e);
|
|
||||||
let dft = panic::take_hook();
|
|
||||||
dft.call((e,));
|
|
||||||
}));
|
|
||||||
|
|
||||||
let mut builder = tauri::Builder::default()
|
let mut builder = tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.plugin(tauri_plugin_dialog::init());
|
.plugin(tauri_plugin_dialog::init());
|
||||||
@ -330,7 +302,7 @@ pub fn run() {
|
|||||||
.inner_size(1536.0, 864.0)
|
.inner_size(1536.0, 864.0)
|
||||||
.decorations(false)
|
.decorations(false)
|
||||||
.shadow(false)
|
.shadow(false)
|
||||||
.data_directory(DATA_ROOT_DIR.join(".webview"))
|
.data_directory(DATA_ROOT_DIR.lock().unwrap().join(".webview"))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -382,7 +354,11 @@ pub fn run() {
|
|||||||
if let Some(original) = db_handle.prev_database.take() {
|
if let Some(original) = db_handle.prev_database.take() {
|
||||||
warn!(
|
warn!(
|
||||||
"Database corrupted. Original file at {}",
|
"Database corrupted. Original file at {}",
|
||||||
original.canonicalize().unwrap().to_string_lossy()
|
original
|
||||||
|
.canonicalize()
|
||||||
|
.unwrap()
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string()
|
||||||
);
|
);
|
||||||
app.dialog()
|
app.dialog()
|
||||||
.message(
|
.message(
|
||||||
@ -438,7 +414,7 @@ pub fn run() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_on_tray<T: FnOnce()>(f: T) {
|
fn run_on_tray<T: FnOnce() -> ()>(f: T) {
|
||||||
if match std::env::var("NO_TRAY_ICON") {
|
if match std::env::var("NO_TRAY_ICON") {
|
||||||
Ok(s) => s.to_lowercase() != "true",
|
Ok(s) => s.to_lowercase() != "true",
|
||||||
Err(_) => true,
|
Err(_) => true,
|
||||||
|
|||||||
@ -39,7 +39,9 @@ pub struct ProcessManager<'a> {
|
|||||||
|
|
||||||
impl ProcessManager<'_> {
|
impl ProcessManager<'_> {
|
||||||
pub fn new(app_handle: AppHandle) -> Self {
|
pub fn new(app_handle: AppHandle) -> Self {
|
||||||
let log_output_dir = DATA_ROOT_DIR.join("logs");
|
let root_dir_lock = DATA_ROOT_DIR.lock().unwrap();
|
||||||
|
let log_output_dir = root_dir_lock.join("logs");
|
||||||
|
drop(root_dir_lock);
|
||||||
|
|
||||||
ProcessManager {
|
ProcessManager {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@ -92,7 +94,7 @@ impl ProcessManager<'_> {
|
|||||||
|
|
||||||
fn on_process_finish(&mut self, game_id: String, result: Result<ExitStatus, std::io::Error>) {
|
fn on_process_finish(&mut self, game_id: String, result: Result<ExitStatus, std::io::Error>) {
|
||||||
if !self.processes.contains_key(&game_id) {
|
if !self.processes.contains_key(&game_id) {
|
||||||
warn!("process on_finish was called, but game_id is no longer valid. finished with result: {result:?}");
|
warn!("process on_finish was called, but game_id is no longer valid. finished with result: {:?}", result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,10 +112,11 @@ impl ProcessManager<'_> {
|
|||||||
db_handle.applications.transient_statuses.remove(&meta);
|
db_handle.applications.transient_statuses.remove(&meta);
|
||||||
|
|
||||||
let current_state = db_handle.applications.game_statuses.get(&game_id).cloned();
|
let current_state = db_handle.applications.game_statuses.get(&game_id).cloned();
|
||||||
if let Some(GameDownloadStatus::SetupRequired {
|
if let Some(saved_state) = current_state {
|
||||||
|
if let GameDownloadStatus::SetupRequired {
|
||||||
version_name,
|
version_name,
|
||||||
install_dir,
|
install_dir,
|
||||||
}) = current_state
|
} = saved_state
|
||||||
{
|
{
|
||||||
if let Ok(exit_code) = result {
|
if let Ok(exit_code) = result {
|
||||||
if exit_code.success() {
|
if exit_code.success() {
|
||||||
@ -127,6 +130,7 @@ impl ProcessManager<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
|
|
||||||
let status = GameStatusManager::fetch_state(&game_id);
|
let status = GameStatusManager::fetch_state(&game_id);
|
||||||
@ -138,7 +142,9 @@ impl ProcessManager<'_> {
|
|||||||
|
|
||||||
pub fn valid_platform(&self, platform: &Platform) -> Result<bool, String> {
|
pub fn valid_platform(&self, platform: &Platform) -> Result<bool, String> {
|
||||||
let current = &self.current_platform;
|
let current = &self.current_platform;
|
||||||
Ok(self.game_launchers.contains_key(&(*current, *platform)))
|
Ok(self
|
||||||
|
.game_launchers
|
||||||
|
.contains_key(&(current.clone(), platform.clone())))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn launch_process(&mut self, game_id: String) -> Result<(), ProcessError> {
|
pub fn launch_process(&mut self, game_id: String) -> Result<(), ProcessError> {
|
||||||
@ -229,8 +235,8 @@ impl ProcessManager<'_> {
|
|||||||
)))
|
)))
|
||||||
.map_err(ProcessError::IOError)?;
|
.map_err(ProcessError::IOError)?;
|
||||||
|
|
||||||
let current_platform = self.current_platform;
|
let current_platform = self.current_platform.clone();
|
||||||
let target_platform = game_version.platform;
|
let target_platform = game_version.platform.clone();
|
||||||
|
|
||||||
let game_launcher = self
|
let game_launcher = self
|
||||||
.game_launchers
|
.game_launchers
|
||||||
@ -247,13 +253,13 @@ impl ProcessManager<'_> {
|
|||||||
install_dir: _,
|
install_dir: _,
|
||||||
} => (&game_version.setup_command, &game_version.setup_args),
|
} => (&game_version.setup_command, &game_version.setup_args),
|
||||||
GameDownloadStatus::PartiallyInstalled {
|
GameDownloadStatus::PartiallyInstalled {
|
||||||
version_name: _,
|
version_name,
|
||||||
install_dir: _,
|
install_dir,
|
||||||
} => unreachable!("Game registered as 'Partially Installed'"),
|
} => unreachable!("Game registered as 'Partially Installed'"),
|
||||||
GameDownloadStatus::Remote {} => unreachable!("Game registered as 'Remote'"),
|
GameDownloadStatus::Remote {} => unreachable!("Game registered as 'Remote'"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let launch = PathBuf::from_str(install_dir).unwrap().join(launch);
|
let launch = PathBuf::from_str(&install_dir).unwrap().join(launch);
|
||||||
let launch = launch.to_str().unwrap();
|
let launch = launch.to_str().unwrap();
|
||||||
|
|
||||||
let launch_string = game_launcher.create_launch_process(
|
let launch_string = game_launcher.create_launch_process(
|
||||||
@ -277,7 +283,7 @@ impl ProcessManager<'_> {
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
command.raw_arg(format!("/C \"{}\"", &launch_string));
|
command.raw_arg(format!("/C \"{}\"", &launch_string));
|
||||||
|
|
||||||
info!("launching (in {install_dir}): {launch_string}",);
|
info!("launching (in {}): {}", install_dir, launch_string,);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let mut command: Command = Command::new("sh");
|
let mut command: Command = Command::new("sh");
|
||||||
@ -338,6 +344,9 @@ pub enum Platform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Platform {
|
impl Platform {
|
||||||
|
const WINDOWS: bool = cfg!(target_os = "windows");
|
||||||
|
const MAC: bool = cfg!(target_os = "macos");
|
||||||
|
const LINUX: bool = cfg!(target_os = "linux");
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub const HOST: Platform = Self::Windows;
|
pub const HOST: Platform = Self::Windows;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@ -413,13 +422,10 @@ impl ProcessHandler for UMULauncher {
|
|||||||
) -> String {
|
) -> String {
|
||||||
debug!("Game override: \"{:?}\"", &game_version.umu_id_override);
|
debug!("Game override: \"{:?}\"", &game_version.umu_id_override);
|
||||||
let game_id = match &game_version.umu_id_override {
|
let game_id = match &game_version.umu_id_override {
|
||||||
Some(game_override) => {
|
Some(game_override) => game_override
|
||||||
if game_override.is_empty() {
|
.is_empty()
|
||||||
game_version.game_id.clone()
|
.then_some(game_version.game_id.clone())
|
||||||
} else {
|
.unwrap_or(game_override.clone()),
|
||||||
game_override.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => game_version.game_id.clone(),
|
None => game_version.game_id.clone(),
|
||||||
};
|
};
|
||||||
format!(
|
format!(
|
||||||
|
|||||||
@ -72,7 +72,7 @@ pub fn fetch_user() -> Result<User, RemoteAccessError> {
|
|||||||
.send()?;
|
.send()?;
|
||||||
if response.status() != 200 {
|
if response.status() != 200 {
|
||||||
let err: DropServerError = response.json()?;
|
let err: DropServerError = response.json()?;
|
||||||
warn!("{err:?}");
|
warn!("{:?}", err);
|
||||||
|
|
||||||
if err.status_message == "Nonce expired" {
|
if err.status_message == "Nonce expired" {
|
||||||
return Err(RemoteAccessError::OutOfSync);
|
return Err(RemoteAccessError::OutOfSync);
|
||||||
@ -148,7 +148,7 @@ pub fn recieve_handshake(app: AppHandle, path: String) {
|
|||||||
|
|
||||||
let handshake_result = recieve_handshake_logic(&app, path);
|
let handshake_result = recieve_handshake_logic(&app, path);
|
||||||
if let Err(e) = handshake_result {
|
if let Err(e) = handshake_result {
|
||||||
warn!("error with authentication: {e}");
|
warn!("error with authentication: {}", e);
|
||||||
app.emit("auth/failed", e.to_string()).unwrap();
|
app.emit("auth/failed", e.to_string()).unwrap();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -212,7 +212,7 @@ pub fn setup() -> (AppStatus, Option<User>) {
|
|||||||
let user_result = match fetch_user() {
|
let user_result = match fetch_user() {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(RemoteAccessError::FetchError(_)) => {
|
Err(RemoteAccessError::FetchError(_)) => {
|
||||||
let user = get_cached_object::<_, User>("user").unwrap();
|
let user = get_cached_object::<String, User>("user".to_owned()).unwrap();
|
||||||
return (AppStatus::Offline, Some(user));
|
return (AppStatus::Offline, Some(user));
|
||||||
}
|
}
|
||||||
Err(_) => return (AppStatus::SignedInNeedsReauth, None),
|
Err(_) => return (AppStatus::SignedInNeedsReauth, None),
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use serde_binary::binary_stream::Endian;
|
|||||||
macro_rules! offline {
|
macro_rules! offline {
|
||||||
($var:expr, $func1:expr, $func2:expr, $( $arg:expr ),* ) => {
|
($var:expr, $func1:expr, $func2:expr, $( $arg:expr ),* ) => {
|
||||||
|
|
||||||
if $crate::borrow_db_checked().settings.force_offline || $var.lock().unwrap().status == $crate::AppStatus::Offline {
|
if crate::borrow_db_checked().settings.force_offline || $var.lock().unwrap().status == crate::AppStatus::Offline {
|
||||||
$func2( $( $arg ), *)
|
$func2( $( $arg ), *)
|
||||||
} else {
|
} else {
|
||||||
$func1( $( $arg ), *)
|
$func1( $( $arg ), *)
|
||||||
@ -19,24 +19,24 @@ macro_rules! offline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cache_object<K: AsRef<str>, D: Serialize + DeserializeOwned>(
|
pub fn cache_object<'a, K: AsRef<str>, D: Serialize + DeserializeOwned>(
|
||||||
key: K,
|
key: K,
|
||||||
data: &D,
|
data: &D,
|
||||||
) -> Result<Integrity, RemoteAccessError> {
|
) -> Result<Integrity, RemoteAccessError> {
|
||||||
let bytes = serde_binary::to_vec(data, Endian::Little).unwrap();
|
let bytes = serde_binary::to_vec(data, Endian::Little).unwrap();
|
||||||
cacache::write_sync(&borrow_db_checked().cache_dir, key, bytes)
|
cacache::write_sync(&borrow_db_checked().cache_dir, key, bytes)
|
||||||
.map_err(RemoteAccessError::Cache)
|
.map_err(|e| RemoteAccessError::Cache(e))
|
||||||
}
|
}
|
||||||
pub fn get_cached_object<K: AsRef<str>, D: Serialize + DeserializeOwned>(
|
pub fn get_cached_object<'a, K: AsRef<str>, D: Serialize + DeserializeOwned>(
|
||||||
key: K,
|
key: K,
|
||||||
) -> Result<D, RemoteAccessError> {
|
) -> Result<D, RemoteAccessError> {
|
||||||
get_cached_object_db::<K, D>(key, &borrow_db_checked())
|
get_cached_object_db::<K, D>(key, &borrow_db_checked())
|
||||||
}
|
}
|
||||||
pub fn get_cached_object_db<K: AsRef<str>, D: Serialize + DeserializeOwned>(
|
pub fn get_cached_object_db<'a, K: AsRef<str>, D: Serialize + DeserializeOwned>(
|
||||||
key: K,
|
key: K,
|
||||||
db: &Database,
|
db: &Database,
|
||||||
) -> Result<D, RemoteAccessError> {
|
) -> Result<D, RemoteAccessError> {
|
||||||
let bytes = cacache::read_sync(&db.cache_dir, key).map_err(RemoteAccessError::Cache)?;
|
let bytes = cacache::read_sync(&db.cache_dir, key).map_err(|e| RemoteAccessError::Cache(e))?;
|
||||||
let data = serde_binary::from_slice::<D>(&bytes, Endian::Little).unwrap();
|
let data = serde_binary::from_slice::<D>(&bytes, Endian::Little).unwrap();
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ use crate::{
|
|||||||
use super::{
|
use super::{
|
||||||
auth::{auth_initiate_logic, recieve_handshake, setup},
|
auth::{auth_initiate_logic, recieve_handshake, setup},
|
||||||
cache::{cache_object, get_cached_object},
|
cache::{cache_object, get_cached_object},
|
||||||
utils::use_remote_logic,
|
remote::use_remote_logic,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -54,7 +54,7 @@ pub fn fetch_drop_object(path: String) -> Result<Vec<u8>, RemoteAccessError> {
|
|||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("{e}");
|
debug!("{}", e);
|
||||||
get_cached_object::<&str, Vec<u8>>(&path)
|
get_cached_object::<&str, Vec<u8>>(&path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,5 +96,5 @@ pub fn auth_initiate() -> Result<(), RemoteAccessError> {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn manual_recieve_handshake(app: AppHandle, token: String) {
|
pub fn manual_recieve_handshake(app: AppHandle, token: String) {
|
||||||
recieve_handshake(app, format!("handshake/{token}"));
|
recieve_handshake(app, format!("handshake/{}", token));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ pub fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeRespond
|
|||||||
match data {
|
match data {
|
||||||
Ok(data) => responder.respond(data.into()),
|
Ok(data) => responder.respond(data.into()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("{e}")
|
warn!("{}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -48,6 +48,6 @@ pub fn fetch_object_offline(request: http::Request<Vec<u8>>, responder: UriSchem
|
|||||||
|
|
||||||
match data {
|
match data {
|
||||||
Ok(data) => responder.respond(data.into()),
|
Ok(data) => responder.respond(data.into()),
|
||||||
Err(e) => warn!("{e}"),
|
Err(e) => warn!("{}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,6 @@ pub mod auth;
|
|||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod fetch_object;
|
pub mod fetch_object;
|
||||||
pub mod utils;
|
pub mod remote;
|
||||||
pub mod requests;
|
pub mod requests;
|
||||||
pub mod server_proto;
|
pub mod server_proto;
|
||||||
|
|||||||
@ -19,7 +19,7 @@ pub fn use_remote_logic(
|
|||||||
url: String,
|
url: String,
|
||||||
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
||||||
) -> Result<(), RemoteAccessError> {
|
) -> Result<(), RemoteAccessError> {
|
||||||
debug!("connecting to url {url}");
|
debug!("connecting to url {}", url);
|
||||||
let base_url = Url::parse(&url)?;
|
let base_url = Url::parse(&url)?;
|
||||||
|
|
||||||
// Test Drop url
|
// Test Drop url
|
||||||
@ -26,16 +26,17 @@ pub fn handle_server_proto(request: Request<Vec<u8>>, responder: UriSchemeRespon
|
|||||||
|
|
||||||
let mut new_uri = request.uri().clone().into_parts();
|
let mut new_uri = request.uri().clone().into_parts();
|
||||||
new_uri.path_and_query =
|
new_uri.path_and_query =
|
||||||
Some(PathAndQuery::from_str(&format!("{path}?noWrapper=true")).unwrap());
|
Some(PathAndQuery::from_str(&format!("{}?noWrapper=true", path)).unwrap());
|
||||||
new_uri.authority = remote_uri.authority().cloned();
|
new_uri.authority = remote_uri.authority().cloned();
|
||||||
new_uri.scheme = remote_uri.scheme().cloned();
|
new_uri.scheme = remote_uri.scheme().cloned();
|
||||||
let new_uri = Uri::from_parts(new_uri).unwrap();
|
let new_uri = Uri::from_parts(new_uri).unwrap();
|
||||||
|
|
||||||
let whitelist_prefix = ["/store", "/api", "/_", "/fonts"];
|
let whitelist_prefix = vec!["/store", "/api", "/_", "/fonts"];
|
||||||
|
|
||||||
if whitelist_prefix
|
if whitelist_prefix
|
||||||
.iter()
|
.iter()
|
||||||
.all(|f| !path.starts_with(f))
|
.map(|f| !path.starts_with(f))
|
||||||
|
.all(|f| f)
|
||||||
{
|
{
|
||||||
webbrowser::open(&new_uri.to_string()).unwrap();
|
webbrowser::open(&new_uri.to_string()).unwrap();
|
||||||
return;
|
return;
|
||||||
@ -44,7 +45,7 @@ pub fn handle_server_proto(request: Request<Vec<u8>>, responder: UriSchemeRespon
|
|||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.request(request.method().clone(), new_uri.to_string())
|
.request(request.method().clone(), new_uri.to_string())
|
||||||
.header("Authorization", format!("Bearer {web_token}"))
|
.header("Authorization", format!("Bearer {}", web_token))
|
||||||
.headers(request.headers().clone())
|
.headers(request.headers().clone())
|
||||||
.send()
|
.send()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2.0.0",
|
"$schema": "https://schema.tauri.app/config/2.0.0",
|
||||||
"productName": "Drop Desktop Client",
|
"productName": "Drop Desktop Client",
|
||||||
"version": "0.3.0-rc-7",
|
"version": "0.3.0-rc-3",
|
||||||
"identifier": "dev.drop.app",
|
"identifier": "dev.drop.app",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "yarn dev --port 1432",
|
"beforeDevCommand": "yarn dev --port 1432",
|
||||||
|
|||||||
Reference in New Issue
Block a user