mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2026-07-14 06:46:34 +10:00
Download fixes (#63)
* refactor: Rename StoredManifest to DropData Signed-off-by: quexeky <git@quexeky.dev> * fix: Downloads when resuming would truncate files which had not been finished Signed-off-by: quexeky <git@quexeky.dev> * chore: Didn't import debug macro Signed-off-by: quexeky <git@quexeky.dev> * fix: Download chunks with wrong indexes Migrated to using checksums as indexes instead Signed-off-by: quexeky <git@quexeky.dev> * feat: Resume download button Also added DBWrite and DBRead structs to make database management easier Signed-off-by: quexeky <git@quexeky.dev> * feat: Download resuming Signed-off-by: quexeky <git@quexeky.dev> * feat: Resume button and PartiallyInstalled status Signed-off-by: quexeky <git@quexeky.dev> * feat: Download validation Signed-off-by: quexeky <git@quexeky.dev> * chore: Ran cargo fix & cargo fmt Signed-off-by: quexeky <git@quexeky.dev> * fix: download validation, installs, etc * chore: version bump --------- Signed-off-by: quexeky <git@quexeky.dev> Co-authored-by: quexeky <git@quexeky.dev>
This commit is contained in:
@@ -6,10 +6,12 @@ use std::{
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{database::db::borrow_db_mut_checked, error::download_manager_error::DownloadManagerError};
|
||||
use crate::{
|
||||
database::db::borrow_db_mut_checked, error::download_manager_error::DownloadManagerError,
|
||||
};
|
||||
|
||||
use super::{
|
||||
db::{borrow_db_checked, save_db, DATA_ROOT_DIR},
|
||||
db::{borrow_db_checked, DATA_ROOT_DIR},
|
||||
debug::SystemData,
|
||||
models::data::Settings,
|
||||
};
|
||||
@@ -26,8 +28,6 @@ pub fn fetch_download_dir_stats() -> Vec<PathBuf> {
|
||||
pub fn delete_download_dir(index: usize) {
|
||||
let mut lock = borrow_db_mut_checked();
|
||||
lock.applications.install_dirs.remove(index);
|
||||
drop(lock);
|
||||
save_db();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -58,7 +58,6 @@ pub fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerError<()>
|
||||
}
|
||||
lock.applications.install_dirs.push(new_dir);
|
||||
drop(lock);
|
||||
save_db();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -72,8 +71,6 @@ pub fn update_settings(new_settings: Value) {
|
||||
}
|
||||
let new_settings: Settings = serde_json::from_value(current_settings).unwrap();
|
||||
db_lock.settings = new_settings;
|
||||
drop(db_lock);
|
||||
save_db();
|
||||
}
|
||||
#[tauri::command]
|
||||
pub fn fetch_settings() -> Settings {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::{
|
||||
fs::{self, create_dir_all},
|
||||
mem::ManuallyDrop,
|
||||
ops::{Deref, DerefMut},
|
||||
path::PathBuf,
|
||||
sync::{LazyLock, Mutex, RwLockReadGuard, RwLockWriteGuard},
|
||||
};
|
||||
@@ -18,7 +20,6 @@ use super::models::data::Database;
|
||||
pub static DATA_ROOT_DIR: LazyLock<Mutex<PathBuf>> =
|
||||
LazyLock::new(|| Mutex::new(dirs::data_dir().unwrap().join("drop")));
|
||||
|
||||
|
||||
// Custom JSON serializer to support everything we need
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DropDatabaseSerializer;
|
||||
@@ -27,15 +28,16 @@ impl<T: native_model::Model + Serialize + DeserializeOwned> DeSerializer<T>
|
||||
for DropDatabaseSerializer
|
||||
{
|
||||
fn serialize(&self, val: &T) -> rustbreak::error::DeSerResult<Vec<u8>> {
|
||||
native_model::rmp_serde_1_3::RmpSerde::encode(val).map_err(|e| DeSerError::Internal(e.to_string()))
|
||||
native_model::rmp_serde_1_3::RmpSerde::encode(val)
|
||||
.map_err(|e| DeSerError::Internal(e.to_string()))
|
||||
}
|
||||
|
||||
fn deserialize<R: std::io::Read>(&self, mut s: R) -> rustbreak::error::DeSerResult<T> {
|
||||
let mut buf = Vec::new();
|
||||
s.read_to_end(&mut buf)
|
||||
.map_err(|e| rustbreak::error::DeSerError::Other(e.into()))?;
|
||||
let val =
|
||||
native_model::rmp_serde_1_3::RmpSerde::decode(buf).map_err(|e| DeSerError::Internal(e.to_string()))?;
|
||||
let val = native_model::rmp_serde_1_3::RmpSerde::decode(buf)
|
||||
.map_err(|e| DeSerError::Internal(e.to_string()))?;
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
@@ -122,9 +124,46 @@ fn handle_invalid_database(
|
||||
PathDatabase::create_at_path(db_path, db).expect("Database could not be created")
|
||||
}
|
||||
|
||||
pub fn borrow_db_checked<'a>() -> RwLockReadGuard<'a, Database> {
|
||||
// To automatically save the database upon drop
|
||||
pub struct DBRead<'a>(RwLockReadGuard<'a, Database>);
|
||||
pub struct DBWrite<'a>(ManuallyDrop<RwLockWriteGuard<'a, Database>>);
|
||||
impl<'a> Deref for DBWrite<'a> {
|
||||
type Target = RwLockWriteGuard<'a, Database>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<'a> Deref for DBRead<'a> {
|
||||
type Target = RwLockReadGuard<'a, Database>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<'a> DerefMut for DBWrite<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
impl<'a> Drop for DBWrite<'a> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.0);
|
||||
}
|
||||
|
||||
match DB.save() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("database failed to save with error {}", e);
|
||||
panic!("database failed to save with error {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn borrow_db_checked<'a>() -> DBRead<'a> {
|
||||
match DB.borrow_data() {
|
||||
Ok(data) => data,
|
||||
Ok(data) => DBRead(data),
|
||||
Err(e) => {
|
||||
error!("database borrow failed with error {}", e);
|
||||
panic!("database borrow failed with error {}", e);
|
||||
@@ -132,22 +171,12 @@ pub fn borrow_db_checked<'a>() -> RwLockReadGuard<'a, Database> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn borrow_db_mut_checked<'a>() -> RwLockWriteGuard<'a, Database> {
|
||||
pub fn borrow_db_mut_checked<'a>() -> DBWrite<'a> {
|
||||
match DB.borrow_data_mut() {
|
||||
Ok(data) => data,
|
||||
Ok(data) => DBWrite(ManuallyDrop::new(data)),
|
||||
Err(e) => {
|
||||
error!("database borrow mut failed with error {}", e);
|
||||
panic!("database borrow mut failed with error {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_db() {
|
||||
match DB.save() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("database failed to save with error {}", e);
|
||||
panic!("database failed to save with error {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
use crate::database::models::data::Database;
|
||||
|
||||
pub mod data {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use native_model::{native_model, Model};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type GameVersion = v1::GameVersion;
|
||||
pub type Database = v2::Database;
|
||||
pub type Database = v3::Database;
|
||||
pub type Settings = v1::Settings;
|
||||
pub type DatabaseAuth = v1::DatabaseAuth;
|
||||
|
||||
pub type GameDownloadStatus = v1::GameDownloadStatus;
|
||||
pub type GameDownloadStatus = v2::GameDownloadStatus;
|
||||
pub type ApplicationTransientStatus = v1::ApplicationTransientStatus;
|
||||
pub type DownloadableMetadata = v1::DownloadableMetadata;
|
||||
pub type DownloadType = v1::DownloadType;
|
||||
pub type DatabaseApplications = v1::DatabaseApplications;
|
||||
pub type DatabaseApplications = v2::DatabaseApplications;
|
||||
pub type DatabaseCompatInfo = v2::DatabaseCompatInfo;
|
||||
|
||||
use std::{collections::HashMap, process::Command};
|
||||
|
||||
use crate::process::process_manager::UMU_LAUNCHER_EXECUTABLE;
|
||||
|
||||
pub mod v1 {
|
||||
use crate::process::process_manager::Platform;
|
||||
use serde_with::serde_as;
|
||||
@@ -102,7 +110,7 @@ pub mod data {
|
||||
}
|
||||
|
||||
// Stuff that shouldn't be synced to disk
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub enum ApplicationTransientStatus {
|
||||
Downloading { version_name: String },
|
||||
Uninstalling {},
|
||||
@@ -162,14 +170,122 @@ pub mod data {
|
||||
}
|
||||
|
||||
pub mod v2 {
|
||||
use std::{collections::HashMap, path::PathBuf, process::Command};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use crate::process::process_manager::UMU_LAUNCHER_EXECUTABLE;
|
||||
use serde_with::serde_as;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||
pub struct Database {
|
||||
#[serde(default)]
|
||||
pub settings: Settings,
|
||||
pub auth: Option<DatabaseAuth>,
|
||||
pub base_url: String,
|
||||
pub applications: v1::DatabaseApplications,
|
||||
#[serde(skip)]
|
||||
pub prev_database: Option<PathBuf>,
|
||||
pub cache_dir: PathBuf,
|
||||
pub compat_info: Option<DatabaseCompatInfo>,
|
||||
}
|
||||
|
||||
#[native_model(id = 8, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||
|
||||
pub struct DatabaseCompatInfo {
|
||||
pub umu_installed: bool,
|
||||
}
|
||||
|
||||
impl From<v1::Database> for Database {
|
||||
fn from(value: v1::Database) -> Self {
|
||||
Self {
|
||||
settings: value.settings,
|
||||
auth: value.auth,
|
||||
base_url: value.base_url,
|
||||
applications: value.applications,
|
||||
prev_database: value.prev_database,
|
||||
cache_dir: value.cache_dir,
|
||||
compat_info: crate::database::models::Database::create_new_compat_info(),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Strings are version names for a particular game
|
||||
#[derive(Serialize, Clone, Deserialize, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
#[native_model(id = 5, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
pub enum GameDownloadStatus {
|
||||
Remote {},
|
||||
SetupRequired {
|
||||
version_name: String,
|
||||
install_dir: String,
|
||||
},
|
||||
Installed {
|
||||
version_name: String,
|
||||
install_dir: String,
|
||||
},
|
||||
PartiallyInstalled {
|
||||
version_name: String,
|
||||
install_dir: String,
|
||||
},
|
||||
}
|
||||
impl From<v1::GameDownloadStatus> for GameDownloadStatus {
|
||||
fn from(value: v1::GameDownloadStatus) -> Self {
|
||||
match value {
|
||||
v1::GameDownloadStatus::Remote {} => Self::Remote {},
|
||||
v1::GameDownloadStatus::SetupRequired {
|
||||
version_name,
|
||||
install_dir,
|
||||
} => Self::SetupRequired {
|
||||
version_name,
|
||||
install_dir,
|
||||
},
|
||||
v1::GameDownloadStatus::Installed {
|
||||
version_name,
|
||||
install_dir,
|
||||
} => Self::Installed {
|
||||
version_name,
|
||||
install_dir,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Clone, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[native_model(id = 3, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
pub struct DatabaseApplications {
|
||||
pub install_dirs: Vec<PathBuf>,
|
||||
// Guaranteed to exist if the game also exists in the app state map
|
||||
pub game_statuses: HashMap<String, GameDownloadStatus>,
|
||||
pub game_versions: HashMap<String, HashMap<String, GameVersion>>,
|
||||
pub installed_game_version: HashMap<String, DownloadableMetadata>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub transient_statuses: HashMap<DownloadableMetadata, ApplicationTransientStatus>,
|
||||
}
|
||||
impl From<v1::DatabaseApplications> for DatabaseApplications {
|
||||
fn from(value: v1::DatabaseApplications) -> Self {
|
||||
Self {
|
||||
game_statuses: value
|
||||
.game_statuses
|
||||
.into_iter()
|
||||
.map(|x| (x.0, x.1.into()))
|
||||
.collect::<HashMap<String, GameDownloadStatus>>(),
|
||||
install_dirs: value.install_dirs,
|
||||
game_versions: value.game_versions,
|
||||
installed_game_version: value.installed_game_version,
|
||||
transient_statuses: value.transient_statuses,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mod v3 {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
#[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||
pub struct Database {
|
||||
#[serde(default)]
|
||||
pub settings: Settings,
|
||||
@@ -181,55 +297,13 @@ pub mod data {
|
||||
pub cache_dir: PathBuf,
|
||||
pub compat_info: Option<DatabaseCompatInfo>,
|
||||
}
|
||||
|
||||
#[native_model(id = 8, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||
|
||||
pub struct DatabaseCompatInfo {
|
||||
umu_installed: bool,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
fn create_new_compat_info() -> Option<DatabaseCompatInfo> {
|
||||
#[cfg(target_os = "windows")]
|
||||
return None;
|
||||
|
||||
let has_umu_installed = Command::new(UMU_LAUNCHER_EXECUTABLE).spawn().is_ok();
|
||||
Some(DatabaseCompatInfo {
|
||||
umu_installed: has_umu_installed,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new<T: Into<PathBuf>>(
|
||||
games_base_dir: T,
|
||||
prev_database: Option<PathBuf>,
|
||||
cache_dir: PathBuf,
|
||||
) -> Self {
|
||||
Self {
|
||||
applications: DatabaseApplications {
|
||||
install_dirs: vec![games_base_dir.into()],
|
||||
game_statuses: HashMap::new(),
|
||||
game_versions: HashMap::new(),
|
||||
installed_game_version: HashMap::new(),
|
||||
transient_statuses: HashMap::new(),
|
||||
},
|
||||
prev_database,
|
||||
base_url: "".to_owned(),
|
||||
auth: None,
|
||||
settings: Settings::default(),
|
||||
cache_dir,
|
||||
compat_info: Database::create_new_compat_info(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v1::Database> for Database {
|
||||
fn from(value: v1::Database) -> Self {
|
||||
impl From<v2::Database> for Database {
|
||||
fn from(value: v2::Database) -> Self {
|
||||
Self {
|
||||
settings: value.settings,
|
||||
auth: value.auth,
|
||||
base_url: value.base_url,
|
||||
applications: value.applications,
|
||||
applications: value.applications.into(),
|
||||
prev_database: value.prev_database,
|
||||
cache_dir: value.cache_dir,
|
||||
compat_info: Database::create_new_compat_info(),
|
||||
@@ -237,4 +311,37 @@ pub mod data {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Database {
|
||||
fn create_new_compat_info() -> Option<DatabaseCompatInfo> {
|
||||
#[cfg(target_os = "windows")]
|
||||
return None;
|
||||
|
||||
let has_umu_installed = Command::new(UMU_LAUNCHER_EXECUTABLE).spawn().is_ok();
|
||||
Some(DatabaseCompatInfo {
|
||||
umu_installed: has_umu_installed,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new<T: Into<PathBuf>>(
|
||||
games_base_dir: T,
|
||||
prev_database: Option<PathBuf>,
|
||||
cache_dir: PathBuf,
|
||||
) -> Self {
|
||||
Self {
|
||||
applications: DatabaseApplications {
|
||||
install_dirs: vec![games_base_dir.into()],
|
||||
game_statuses: HashMap::new(),
|
||||
game_versions: HashMap::new(),
|
||||
installed_game_version: HashMap::new(),
|
||||
transient_statuses: HashMap::new(),
|
||||
},
|
||||
prev_database,
|
||||
base_url: "".to_owned(),
|
||||
auth: None,
|
||||
settings: Settings::default(),
|
||||
cache_dir,
|
||||
compat_info: Database::create_new_compat_info(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user