diff --git a/Cargo.lock b/Cargo.lock index d707e8c0f..5db151a69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -603,13 +609,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", "serde", "time 0.1.45", @@ -4274,6 +4280,7 @@ dependencies = [ name = "theseus_gui" version = "0.0.0" dependencies = [ + "chrono", "daedalus", "futures", "os_info", diff --git a/theseus/src/api/auth.rs b/theseus/src/api/auth.rs index f6d835e8a..4db321239 100644 --- a/theseus/src/api/auth.rs +++ b/theseus/src/api/auth.rs @@ -1,6 +1,6 @@ //! Authentication flow interface use crate::{launcher::auth as inner, State}; -use futures::prelude::*; +use chrono::Utc; use tokio::sync::oneshot; use crate::state::AuthTask; @@ -71,22 +71,20 @@ pub async fn refresh(user: uuid::Uuid) -> crate::Result { let state = State::get().await?; let mut users = state.users.write().await; - let fetch_semaphore = &state.fetch_semaphore; - futures::future::ready(users.get(user).ok_or_else(|| { - crate::ErrorKind::OtherError(format!( - "You are not logged in with a Minecraft account!" - )) + let mut credentials = users.get(user).ok_or_else(|| { + crate::ErrorKind::OtherError( + "You are not logged in with a Minecraft account!".to_string(), + ) .as_error() - })) - .and_then(|mut credentials| async move { - if chrono::offset::Utc::now() > credentials.expires { - inner::refresh_credentials(&mut credentials, fetch_semaphore) - .await?; - } - users.insert(&credentials).await?; - Ok(credentials) - }) - .await + })?; + + let fetch_semaphore = &state.fetch_semaphore; + if Utc::now() > credentials.expires { + inner::refresh_credentials(&mut credentials, fetch_semaphore).await?; + } + users.insert(&credentials).await?; + + Ok(credentials) } /// Remove a user account from the database diff --git a/theseus/src/launcher/mod.rs b/theseus/src/launcher/mod.rs index 163c51f4c..7b080d772 100644 --- a/theseus/src/launcher/mod.rs +++ b/theseus/src/launcher/mod.rs @@ -9,6 +9,7 @@ use crate::{ state::{self as st, MinecraftChild}, State, }; +use chrono::Utc; use daedalus as d; use daedalus::minecraft::VersionInfo; use dunce::canonicalize; @@ -432,6 +433,13 @@ pub async fn launch_minecraft( let stdout_log_path = logs_dir.join("stdout.log"); let stderr_log_path = logs_dir.join("stderr.log"); + crate::api::profile::edit(&profile.path, |prof| { + prof.metadata.last_played = Some(Utc::now()); + + async { Ok(()) } + }) + .await?; + // Create Minecraft child by inserting it into the state // This also spawns the process and prepares the subsequent processes let mut state_children = state.children.write().await; diff --git a/theseus/src/state/profiles.rs b/theseus/src/state/profiles.rs index a58dde509..5dbfe3958 100644 --- a/theseus/src/state/profiles.rs +++ b/theseus/src/state/profiles.rs @@ -10,6 +10,7 @@ use crate::util::fetch::{ fetch, fetch_json, write, write_cached_icon, IoSemaphore, }; use crate::State; +use chrono::{DateTime, Utc}; use daedalus::get_hash; use daedalus::modded::LoaderVersion; use dunce::canonicalize; @@ -30,9 +31,6 @@ const PROFILE_JSON_PATH: &str = "profile.json"; pub(crate) struct Profiles(pub HashMap); -// TODO: possibly add defaults to some of these values -pub const CURRENT_FORMAT_VERSION: u32 = 1; - #[derive( Serialize, Deserialize, Clone, Copy, Debug, Default, Eq, PartialEq, )] @@ -75,13 +73,24 @@ pub struct ProfileMetadata { pub icon: Option, #[serde(skip_serializing_if = "Option::is_none")] pub icon_url: Option, + #[serde(default)] + pub groups: Vec, + pub game_version: String, #[serde(default)] pub loader: ModLoader, #[serde(skip_serializing_if = "Option::is_none")] pub loader_version: Option, - pub format_version: u32, + + #[serde(skip_serializing_if = "Option::is_none")] pub linked_data: Option, + + #[serde(default)] + pub date_created: DateTime, + #[serde(default)] + pub date_modified: DateTime, + #[serde(skip_serializing_if = "Option::is_none")] + pub last_played: Option>, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -157,11 +166,14 @@ impl Profile { name, icon: None, icon_url: None, + groups: vec![], game_version: version, loader: ModLoader::Vanilla, loader_version: None, - format_version: CURRENT_FORMAT_VERSION, linked_data: None, + date_created: Utc::now(), + date_modified: Utc::now(), + last_played: None, }, projects: HashMap::new(), java: None, @@ -182,6 +194,7 @@ impl Profile { let file = write_cached_icon(file_name, cache_dir, icon, semaphore).await?; self.metadata.icon = Some(file); + self.metadata.date_modified = Utc::now(); Ok(()) } @@ -400,6 +413,7 @@ impl Profile { file_name: file_name.to_string(), }, ); + profile.metadata.date_modified = Utc::now(); } } @@ -446,6 +460,7 @@ impl Profile { let mut profiles = state.profiles.write().await; if let Some(profile) = profiles.0.get_mut(&self.path) { profile.projects.insert(new_path.clone(), project); + profile.metadata.date_modified = Utc::now(); } Ok(new_path) @@ -471,6 +486,7 @@ impl Profile { if let Some(profile) = profiles.0.get_mut(&self.path) { profile.projects.remove(path); + profile.metadata.date_modified = Utc::now(); } } } else { diff --git a/theseus/src/state/settings.rs b/theseus/src/state/settings.rs index 265fdbcc0..647e6690a 100644 --- a/theseus/src/state/settings.rs +++ b/theseus/src/state/settings.rs @@ -26,6 +26,8 @@ pub struct Settings { pub max_concurrent_writes: usize, pub version: u32, pub collapsed_navigation: bool, + #[serde(default)] + pub developer_mode: bool, } impl Default for Settings { @@ -43,6 +45,7 @@ impl Default for Settings { max_concurrent_writes: 10, version: CURRENT_FORMAT_VERSION, collapsed_navigation: false, + developer_mode: false, } } } diff --git a/theseus_gui/.eslintignore b/theseus_gui/.eslintignore new file mode 100644 index 000000000..e4be06364 --- /dev/null +++ b/theseus_gui/.eslintignore @@ -0,0 +1,93 @@ +node_modules +*.log* +.nuxt +.nitro +.cache +.output +.env +dist +*.md +package.json +dist/ + +generated/ +!.gitkeep + +# Created by .ignore support plugin (hsz.mobi) +### Node template +# Logs +/logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# Serverless directories +.serverless + +# IDE / Editor +.idea + +# Service worker +sw.* + +# macOS +.DS_Store + +# Vim swap files +*.swp + +# pnpm files +pnpm-lock.yaml +/.npmrc + +src-tauri/ +*.rs diff --git a/theseus_gui/.prettierignore b/theseus_gui/.prettierignore index d162033a9..e4be06364 100644 --- a/theseus_gui/.prettierignore +++ b/theseus_gui/.prettierignore @@ -88,3 +88,6 @@ sw.* # pnpm files pnpm-lock.yaml /.npmrc + +src-tauri/ +*.rs diff --git a/theseus_gui/src-tauri/Cargo.toml b/theseus_gui/src-tauri/Cargo.toml index 5e85e7b1b..981ca9850 100644 --- a/theseus_gui/src-tauri/Cargo.toml +++ b/theseus_gui/src-tauri/Cargo.toml @@ -27,6 +27,7 @@ thiserror = "1.0" tokio-stream = { version = "0.1", features = ["fs"] } futures = "0.3" daedalus = {version = "0.1.15", features = ["bincode"] } +chrono = "0.4.26" url = "2.2" uuid = { version = "1.1", features = ["serde", "v4"] } diff --git a/theseus_gui/src-tauri/src/api/profile.rs b/theseus_gui/src-tauri/src/api/profile.rs index bfff7d7b6..5ae4cb5e7 100644 --- a/theseus_gui/src-tauri/src/api/profile.rs +++ b/theseus_gui/src-tauri/src/api/profile.rs @@ -195,6 +195,7 @@ pub struct EditProfileMetadata { pub game_version: Option, pub loader: Option, pub loader_version: Option, + pub groups: Option>, } // Edits a profile @@ -207,15 +208,19 @@ pub async fn profile_edit( profile::edit(path, |prof| { if let Some(metadata) = edit_profile.metadata.clone() { if let Some(name) = metadata.name { - prof.metadata.name = name + prof.metadata.name = name; } if let Some(game_version) = metadata.game_version { - prof.metadata.game_version = game_version + prof.metadata.game_version = game_version; } if let Some(loader) = metadata.loader { - prof.metadata.loader = loader + prof.metadata.loader = loader; + } + prof.metadata.loader_version = metadata.loader_version; + + if let Some(groups) = metadata.groups { + prof.metadata.groups = groups; } - prof.metadata.loader_version = metadata.loader_version } prof.java = edit_profile.java.clone(); @@ -223,6 +228,8 @@ pub async fn profile_edit( prof.resolution = edit_profile.resolution; prof.hooks = edit_profile.hooks.clone(); + prof.metadata.date_modified = chrono::Utc::now(); + async { Ok(()) } }) .await?; diff --git a/theseus_gui/src/components/GridDisplay.vue b/theseus_gui/src/components/GridDisplay.vue index a6d5a7dd0..449df3064 100644 --- a/theseus_gui/src/components/GridDisplay.vue +++ b/theseus_gui/src/components/GridDisplay.vue @@ -1,5 +1,8 @@