You've already forked AstralRinth
forked from didirus/AstralRinth
Offline mode (#403)
* offline mode * fixes, mixpanels, etc * changes * prettier * rev * actions
This commit is contained in:
@@ -233,6 +233,22 @@ pub async fn emit_warning(message: &str) -> crate::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// emit_offline(bool)
|
||||
// This is used to emit an event to the frontend that the app is offline after a refresh (or online)
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn emit_offline(offline: bool) -> crate::Result<()> {
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
let event_state = crate::EventState::get().await?;
|
||||
event_state
|
||||
.app
|
||||
.emit_all("offline", offline)
|
||||
.map_err(EventError::from)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// emit_command(CommandPayload::Something { something })
|
||||
// ie: installing a pack, opening an .mrpack, etc
|
||||
// Generally used for url deep links and file opens that we we want to handle in the frontend
|
||||
|
||||
@@ -194,6 +194,11 @@ pub struct LoadingPayload {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct OfflinePayload {
|
||||
pub offline: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct WarningPayload {
|
||||
pub message: String,
|
||||
|
||||
@@ -542,7 +542,7 @@ pub async fn launch_minecraft(
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if !*state.offline.read().await {
|
||||
// Add game played to discord rich presence
|
||||
let _ = state
|
||||
.discord_rpc
|
||||
|
||||
@@ -58,6 +58,7 @@ impl Metadata {
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn init(
|
||||
dirs: &DirectoryInfo,
|
||||
fetch_online: bool,
|
||||
io_semaphore: &IoSemaphore,
|
||||
) -> crate::Result<Self> {
|
||||
let mut metadata = None;
|
||||
@@ -67,7 +68,7 @@ impl Metadata {
|
||||
read_json::<Metadata>(&metadata_path, io_semaphore).await
|
||||
{
|
||||
metadata = Some(metadata_json);
|
||||
} else {
|
||||
} else if fetch_online {
|
||||
let res = async {
|
||||
let metadata_fetch = Self::fetch().await?;
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
//! Theseus state management system
|
||||
use crate::event::emit::{emit_loading, init_loading_unsafe};
|
||||
use crate::event::emit::{emit_loading, emit_offline, init_loading_unsafe};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::event::LoadingBarType;
|
||||
use crate::loading_join;
|
||||
|
||||
use crate::state::users::Users;
|
||||
use crate::util::fetch::{FetchSemaphore, IoSemaphore};
|
||||
use crate::util::fetch::{self, FetchSemaphore, IoSemaphore};
|
||||
use notify::RecommendedWatcher;
|
||||
use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::join;
|
||||
use tokio::sync::{OnceCell, RwLock, Semaphore};
|
||||
|
||||
use futures::{channel::mpsc::channel, SinkExt, StreamExt};
|
||||
@@ -55,6 +56,9 @@ pub use self::discord::*;
|
||||
// RwLock on state only has concurrent reads, except for config dir change which takes control of the State
|
||||
static LAUNCHER_STATE: OnceCell<RwLock<State>> = OnceCell::const_new();
|
||||
pub struct State {
|
||||
/// Whether or not the launcher is currently operating in 'offline mode'
|
||||
pub offline: RwLock<bool>,
|
||||
|
||||
/// Information on the location of files used in the launcher
|
||||
pub directories: DirectoryInfo,
|
||||
|
||||
@@ -145,10 +149,17 @@ impl State {
|
||||
)));
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
let metadata_fut = Metadata::init(&directories, &io_semaphore);
|
||||
let is_offline = !fetch::check_internet(&fetch_semaphore, 3).await;
|
||||
|
||||
let metadata_fut =
|
||||
Metadata::init(&directories, !is_offline, &io_semaphore);
|
||||
let profiles_fut = Profiles::init(&directories, &mut file_watcher);
|
||||
let tags_fut =
|
||||
Tags::init(&directories, &io_semaphore, &fetch_semaphore);
|
||||
let tags_fut = Tags::init(
|
||||
&directories,
|
||||
!is_offline,
|
||||
&io_semaphore,
|
||||
&fetch_semaphore,
|
||||
);
|
||||
let users_fut = Users::init(&directories, &io_semaphore);
|
||||
// Launcher data
|
||||
let (metadata, profiles, tags, users) = loading_join! {
|
||||
@@ -165,9 +176,13 @@ impl State {
|
||||
|
||||
let discord_rpc = DiscordGuard::init().await?;
|
||||
|
||||
// Starts a loop of checking if we are online, and updating
|
||||
Self::offine_check_loop();
|
||||
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
Ok::<RwLock<Self>, crate::Error>(RwLock::new(Self {
|
||||
offline: RwLock::new(is_offline),
|
||||
directories,
|
||||
fetch_semaphore,
|
||||
fetch_semaphore_max: RwLock::new(
|
||||
@@ -190,13 +205,36 @@ impl State {
|
||||
}))
|
||||
}
|
||||
|
||||
/// Updates state with data from the web
|
||||
/// Starts a loop of checking if we are online, and updating
|
||||
pub fn offine_check_loop() {
|
||||
tokio::task::spawn(async {
|
||||
loop {
|
||||
let state = Self::get().await;
|
||||
if let Ok(state) = state {
|
||||
let _ = state.refresh_offline().await;
|
||||
}
|
||||
|
||||
// Wait 5 seconds
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Updates state with data from the web, if we are online
|
||||
pub fn update() {
|
||||
tokio::task::spawn(Metadata::update());
|
||||
tokio::task::spawn(Tags::update());
|
||||
tokio::task::spawn(Profiles::update_projects());
|
||||
tokio::task::spawn(Profiles::update_modrinth_versions());
|
||||
tokio::task::spawn(Settings::update_java());
|
||||
tokio::task::spawn(async {
|
||||
if let Ok(state) = crate::State::get().await {
|
||||
if !*state.offline.read().await {
|
||||
let res1 = Profiles::update_modrinth_versions();
|
||||
let res2 = Tags::update();
|
||||
let res3 = Metadata::update();
|
||||
let res4 = Profiles::update_projects();
|
||||
let res5 = Settings::update_java();
|
||||
|
||||
let _ = join!(res1, res2, res3, res4, res5);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
@@ -264,6 +302,21 @@ impl State {
|
||||
*total_permits = settings.max_concurrent_downloads as u32;
|
||||
*io_semaphore = Semaphore::new(settings.max_concurrent_downloads);
|
||||
}
|
||||
|
||||
/// Refreshes whether or not the launcher should be offline, by whether or not there is an internet connection
|
||||
pub async fn refresh_offline(&self) -> crate::Result<()> {
|
||||
let is_online = fetch::check_internet(&self.fetch_semaphore, 3).await;
|
||||
|
||||
let mut offline = self.offline.write().await;
|
||||
|
||||
if *offline != is_online {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
emit_offline(!is_online).await?;
|
||||
*offline = !is_online;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init_watcher() -> crate::Result<Debouncer<RecommendedWatcher>> {
|
||||
|
||||
@@ -24,6 +24,7 @@ impl Tags {
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn init(
|
||||
dirs: &DirectoryInfo,
|
||||
fetch_online: bool,
|
||||
io_semaphore: &IoSemaphore,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Self> {
|
||||
@@ -33,7 +34,7 @@ impl Tags {
|
||||
if let Ok(tags_json) = read_json::<Self>(&tags_path, io_semaphore).await
|
||||
{
|
||||
tags = Some(tags_json);
|
||||
} else {
|
||||
} else if fetch_online {
|
||||
match Self::fetch(fetch_semaphore).await {
|
||||
Ok(tags_fetch) => tags = Some(tags_fetch),
|
||||
Err(err) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ use reqwest::Method;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time;
|
||||
use std::time::{self, Duration};
|
||||
use tokio::sync::{RwLock, Semaphore};
|
||||
use tokio::{fs::File, io::AsyncWriteExt};
|
||||
|
||||
@@ -182,6 +182,16 @@ pub async fn fetch_mirrors(
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// Using labrinth API, checks if an internet response can be found, with a timeout in seconds
|
||||
#[tracing::instrument(skip(semaphore))]
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn check_internet(semaphore: &FetchSemaphore, timeout: u64) -> bool {
|
||||
let result = fetch("https://api.modrinth.com", None, semaphore);
|
||||
let result =
|
||||
tokio::time::timeout(Duration::from_secs(timeout), result).await;
|
||||
matches!(result, Ok(Ok(_)))
|
||||
}
|
||||
|
||||
pub async fn read_json<T>(
|
||||
path: &Path,
|
||||
semaphore: &IoSemaphore,
|
||||
|
||||
Reference in New Issue
Block a user