refactor: remove telemetry and fix launcher workflows
Some checks failed
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled
Deploy telemetry dashboard / deploy (push) Has been cancelled
Prepare pnpm cache / prepare (push) Has been cancelled
Some checks failed
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled
Deploy telemetry dashboard / deploy (push) Has been cancelled
Prepare pnpm cache / prepare (push) Has been cancelled
This commit is contained in:
@ -1,238 +0,0 @@
|
||||
use std::sync::{
|
||||
Arc, Mutex, TryLockError, atomic::AtomicBool, atomic::Ordering,
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
use discord_rich_presence::{
|
||||
DiscordIpc, DiscordIpcClient,
|
||||
activity::{Activity, Assets},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::State;
|
||||
|
||||
pub struct DiscordGuard {
|
||||
client: Arc<Mutex<DiscordIpcClient>>,
|
||||
connected: Arc<AtomicBool>,
|
||||
launcher_activity: Arc<RwLock<String>>,
|
||||
}
|
||||
|
||||
const DISCORD_IPC_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
|
||||
async fn await_ipc_task<T>(
|
||||
operation: &'static str,
|
||||
timeout: Duration,
|
||||
task: JoinHandle<T>,
|
||||
) -> Option<T> {
|
||||
match tokio::time::timeout(timeout, task).await {
|
||||
Ok(Ok(result)) => Some(result),
|
||||
Ok(Err(error)) => {
|
||||
tracing::warn!(%error, operation, "Discord IPC worker failed");
|
||||
None
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::warn!(operation, "Discord IPC operation timed out");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DiscordGuard {
|
||||
/// Initialize discord IPC client, and attempt to connect to it
|
||||
/// If it fails, it will still return a DiscordGuard, but the client will be unconnected
|
||||
pub fn init() -> crate::Result<DiscordGuard> {
|
||||
let dipc = DiscordIpcClient::new("1533353147349864458");
|
||||
|
||||
Ok(DiscordGuard {
|
||||
client: Arc::new(Mutex::new(dipc)),
|
||||
connected: Arc::new(AtomicBool::new(false)),
|
||||
launcher_activity: Arc::new(RwLock::new("Idling...".to_string())),
|
||||
})
|
||||
}
|
||||
|
||||
async fn run_ipc<F>(
|
||||
&self,
|
||||
operation: &'static str,
|
||||
connect_if_needed: bool,
|
||||
action: F,
|
||||
) where
|
||||
F: FnOnce(&mut DiscordIpcClient) -> crate::Result<()> + Send + 'static,
|
||||
{
|
||||
let client = self.client.clone();
|
||||
let connected = self.connected.clone();
|
||||
let task = tokio::task::spawn_blocking(move || {
|
||||
let mut client = match client.try_lock() {
|
||||
Ok(client) => client,
|
||||
Err(TryLockError::WouldBlock) => {
|
||||
tracing::warn!(
|
||||
operation,
|
||||
"Discord IPC client is busy; skipping activity update"
|
||||
);
|
||||
return;
|
||||
}
|
||||
Err(TryLockError::Poisoned(error)) => {
|
||||
tracing::warn!(
|
||||
operation,
|
||||
"Discord IPC client lock was poisoned; recovering"
|
||||
);
|
||||
error.into_inner()
|
||||
}
|
||||
};
|
||||
|
||||
if !connected.load(Ordering::Relaxed) {
|
||||
if !connect_if_needed {
|
||||
return;
|
||||
}
|
||||
if client.connect().is_err() {
|
||||
return;
|
||||
}
|
||||
connected.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
if let Err(error) = action(&mut client) {
|
||||
connected.store(false, Ordering::Relaxed);
|
||||
tracing::warn!(%error, operation, "Discord IPC operation failed");
|
||||
}
|
||||
});
|
||||
|
||||
let _ = await_ipc_task(operation, DISCORD_IPC_TIMEOUT, task).await;
|
||||
}
|
||||
|
||||
/// Set the activity to the given message
|
||||
/// First checks if discord is disabled, and if so, clear the activity instead
|
||||
pub async fn set_activity(
|
||||
&self,
|
||||
msg: &str,
|
||||
reconnect_if_fail: bool,
|
||||
) -> crate::Result<()> {
|
||||
// Check if discord is disabled, and if so, clear the activity instead
|
||||
let state = State::get().await?;
|
||||
let settings = crate::state::Settings::get(&state.pool).await?;
|
||||
if !settings.discord_rpc {
|
||||
Ok(self.clear_activity(true).await?)
|
||||
} else {
|
||||
Ok(self.force_set_activity(msg, reconnect_if_fail).await?)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_launcher_activity(
|
||||
&self,
|
||||
msg: &str,
|
||||
reconnect_if_fail: bool,
|
||||
) -> crate::Result<()> {
|
||||
*self.launcher_activity.write().await = msg.to_string();
|
||||
|
||||
let state = State::get().await?;
|
||||
if state.process_manager.get_all().is_empty() {
|
||||
self.set_activity(msg, reconnect_if_fail).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the activity to the given message, regardless of if discord is disabled or offline
|
||||
/// Should not be used except for in the above method, or if it is already known that discord is enabled (specifically for state initialization) and we are connected to the internet
|
||||
pub async fn force_set_activity(
|
||||
&self,
|
||||
msg: &str,
|
||||
reconnect_if_fail: bool,
|
||||
) -> crate::Result<()> {
|
||||
let msg = msg.to_string();
|
||||
self.run_ipc("set activity", true, move |client| {
|
||||
let activity = Activity::new().state(&msg).assets(
|
||||
Assets::new()
|
||||
.large_image("modrinth_simple")
|
||||
.large_text("Modrinth Logo"),
|
||||
);
|
||||
let result = client.set_activity(activity.clone());
|
||||
|
||||
if reconnect_if_fail && result.is_err() {
|
||||
client.reconnect()?;
|
||||
client.set_activity(activity)?;
|
||||
} else {
|
||||
result?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear the activity entirely ('disabling' the RPC until the next set_activity)
|
||||
pub async fn clear_activity(
|
||||
&self,
|
||||
reconnect_if_fail: bool,
|
||||
) -> crate::Result<()> {
|
||||
self.run_ipc("clear activity", false, move |client| {
|
||||
let result = client.clear_activity();
|
||||
|
||||
if reconnect_if_fail && result.is_err() {
|
||||
client.reconnect()?;
|
||||
client.clear_activity()?;
|
||||
} else {
|
||||
result?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear the activity, but if there is a running profile, set the activity to that instead
|
||||
pub async fn clear_to_default(
|
||||
&self,
|
||||
reconnect_if_fail: bool,
|
||||
) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
|
||||
let settings = crate::state::Settings::get(&state.pool).await?;
|
||||
if !settings.discord_rpc {
|
||||
println!("Discord is disabled, clearing activity");
|
||||
return self.clear_activity(true).await;
|
||||
}
|
||||
|
||||
let running_instances = state.process_manager.get_all();
|
||||
if let Some(existing_child) = running_instances.first() {
|
||||
self.set_activity(
|
||||
&format!("Playing {}", existing_child.instance_name),
|
||||
reconnect_if_fail,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
let launcher_activity = self.launcher_activity.read().await.clone();
|
||||
self.set_activity(&launcher_activity, reconnect_if_fail)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::await_ipc_task;
|
||||
use std::time::Duration;
|
||||
|
||||
#[tokio::test]
|
||||
async fn ipc_task_returns_completed_result() {
|
||||
let task = tokio::task::spawn_blocking(|| 42);
|
||||
|
||||
assert_eq!(
|
||||
await_ipc_task("test", Duration::from_secs(1), task).await,
|
||||
Some(42)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ipc_task_stops_waiting_after_timeout() {
|
||||
let task = tokio::task::spawn_blocking(|| {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
42
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
await_ipc_task("test", Duration::from_millis(10), task).await,
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -357,7 +357,6 @@ impl FriendsSocket {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some("/v3/friend/:user_id"),
|
||||
semaphore,
|
||||
exec,
|
||||
@ -391,7 +390,6 @@ impl FriendsSocket {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some("/v3/friend/:user_id"),
|
||||
semaphore,
|
||||
exec,
|
||||
|
||||
@ -18,8 +18,8 @@ use crate::state::{
|
||||
Version, cache_file_hash, cache_file_hash_metadata,
|
||||
};
|
||||
use crate::util::fetch::{
|
||||
self, ContentValidation, DownloadMeta, DownloadReason, DownloadRequest,
|
||||
Integrity, ResourceClass, download_to_path,
|
||||
self, ContentValidation, DownloadRequest, Integrity, ResourceClass,
|
||||
download_to_path,
|
||||
};
|
||||
use crate::util::io;
|
||||
use crate::util::io::io_error_with_lock_info;
|
||||
@ -307,7 +307,6 @@ pub(crate) async fn install_resolved_content_plan_with_reporter(
|
||||
add_resolved_content_with_progress(
|
||||
instance_id,
|
||||
&plan.primary,
|
||||
DownloadReason::Standalone,
|
||||
false,
|
||||
primary_progress,
|
||||
state,
|
||||
@ -332,7 +331,6 @@ pub(crate) async fn install_resolved_content_plan_with_reporter(
|
||||
add_resolved_content_with_progress(
|
||||
instance_id,
|
||||
dependency,
|
||||
DownloadReason::Dependency,
|
||||
true,
|
||||
dependency_progress,
|
||||
state,
|
||||
@ -424,8 +422,6 @@ pub(crate) async fn switch_project_version_with_dependencies(
|
||||
let mut new_path = add_project_from_version(
|
||||
instance_id,
|
||||
&plan.primary.version_id,
|
||||
DownloadReason::Update,
|
||||
None,
|
||||
ContentSourceKind::Local,
|
||||
ownership_kind,
|
||||
state,
|
||||
@ -442,14 +438,7 @@ pub(crate) async fn switch_project_version_with_dependencies(
|
||||
installed_paths.push(new_path.clone());
|
||||
for dependency in &plan.dependencies {
|
||||
installed_paths.push(
|
||||
add_resolved_content(
|
||||
instance_id,
|
||||
dependency,
|
||||
DownloadReason::Dependency,
|
||||
true,
|
||||
state,
|
||||
)
|
||||
.await?,
|
||||
add_resolved_content(instance_id, dependency, true, state).await?,
|
||||
);
|
||||
}
|
||||
persist_resolved_plan_dependency_edges(
|
||||
@ -474,14 +463,12 @@ pub(crate) async fn switch_project_version_with_dependencies(
|
||||
pub(crate) async fn add_resolved_content(
|
||||
instance_id: &str,
|
||||
content: &ResolvedContent,
|
||||
reason: DownloadReason,
|
||||
auto_dependency: bool,
|
||||
state: &State,
|
||||
) -> crate::Result<String> {
|
||||
add_resolved_content_with_progress(
|
||||
instance_id,
|
||||
content,
|
||||
reason,
|
||||
auto_dependency,
|
||||
None,
|
||||
state,
|
||||
@ -492,7 +479,6 @@ pub(crate) async fn add_resolved_content(
|
||||
async fn add_resolved_content_with_progress(
|
||||
instance_id: &str,
|
||||
content: &ResolvedContent,
|
||||
reason: DownloadReason,
|
||||
auto_dependency: bool,
|
||||
progress: Option<ResolvedContentDownloadProgress>,
|
||||
state: &State,
|
||||
@ -500,8 +486,6 @@ async fn add_resolved_content_with_progress(
|
||||
let path = add_project_from_version_with_progress(
|
||||
instance_id,
|
||||
&content.version_id,
|
||||
reason,
|
||||
content.dependent_on_version_id.clone(),
|
||||
ContentSourceKind::Local,
|
||||
ContentOwnershipKind::UserAdded,
|
||||
progress,
|
||||
@ -536,15 +520,8 @@ pub(crate) async fn install_resolved_dependency(
|
||||
content: &ResolvedContent,
|
||||
state: &State,
|
||||
) -> crate::Result<String> {
|
||||
add_resolved_content_with_progress(
|
||||
instance_id,
|
||||
content,
|
||||
DownloadReason::Dependency,
|
||||
true,
|
||||
None,
|
||||
state,
|
||||
)
|
||||
.await
|
||||
add_resolved_content_with_progress(instance_id, content, true, None, state)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn persist_resolved_plan_dependency_edges(
|
||||
@ -728,8 +705,6 @@ pub(crate) async fn resolve_content_scope(
|
||||
pub(crate) async fn add_project_from_version(
|
||||
instance_id: &str,
|
||||
version_id: &str,
|
||||
reason: DownloadReason,
|
||||
dependent_on_version_id: Option<String>,
|
||||
source_kind: ContentSourceKind,
|
||||
ownership_kind: ContentOwnershipKind,
|
||||
state: &State,
|
||||
@ -737,8 +712,6 @@ pub(crate) async fn add_project_from_version(
|
||||
add_project_from_version_with_progress(
|
||||
instance_id,
|
||||
version_id,
|
||||
reason,
|
||||
dependent_on_version_id,
|
||||
source_kind,
|
||||
ownership_kind,
|
||||
None,
|
||||
@ -751,22 +724,14 @@ pub(crate) async fn add_project_from_version(
|
||||
pub(crate) async fn add_project_from_version_with_progress(
|
||||
instance_id: &str,
|
||||
version_id: &str,
|
||||
reason: DownloadReason,
|
||||
dependent_on_version_id: Option<String>,
|
||||
source_kind: ContentSourceKind,
|
||||
ownership_kind: ContentOwnershipKind,
|
||||
progress: Option<ResolvedContentDownloadProgress>,
|
||||
state: &State,
|
||||
) -> crate::Result<String> {
|
||||
let downloaded = download_project_version_with_progress(
|
||||
instance_id,
|
||||
version_id,
|
||||
reason,
|
||||
dependent_on_version_id,
|
||||
progress,
|
||||
state,
|
||||
)
|
||||
.await?;
|
||||
let downloaded =
|
||||
download_project_version_with_progress(version_id, progress, state)
|
||||
.await?;
|
||||
|
||||
add_downloaded_project_version(
|
||||
instance_id,
|
||||
@ -779,21 +744,10 @@ pub(crate) async fn add_project_from_version_with_progress(
|
||||
}
|
||||
|
||||
pub(crate) async fn download_project_version(
|
||||
instance_id: &str,
|
||||
version_id: &str,
|
||||
reason: DownloadReason,
|
||||
dependent_on_version_id: Option<String>,
|
||||
state: &State,
|
||||
) -> crate::Result<DownloadedProjectVersion> {
|
||||
download_project_version_with_progress(
|
||||
instance_id,
|
||||
version_id,
|
||||
reason,
|
||||
dependent_on_version_id,
|
||||
None,
|
||||
state,
|
||||
)
|
||||
.await
|
||||
download_project_version_with_progress(version_id, None, state).await
|
||||
}
|
||||
|
||||
/// Progress context for one file inside a multi-file content install.
|
||||
@ -807,38 +761,21 @@ pub(crate) struct ResolvedContentDownloadProgress {
|
||||
}
|
||||
|
||||
pub(crate) async fn download_project_version_with_progress(
|
||||
instance_id: &str,
|
||||
version_id: &str,
|
||||
reason: DownloadReason,
|
||||
dependent_on_version_id: Option<String>,
|
||||
progress: Option<ResolvedContentDownloadProgress>,
|
||||
state: &State,
|
||||
) -> crate::Result<DownloadedProjectVersion> {
|
||||
download_project_version_with_reporting(
|
||||
instance_id,
|
||||
version_id,
|
||||
reason,
|
||||
dependent_on_version_id,
|
||||
progress,
|
||||
None,
|
||||
state,
|
||||
)
|
||||
.await
|
||||
download_project_version_with_reporting(version_id, progress, None, state)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn download_project_version_with_reporter(
|
||||
instance_id: &str,
|
||||
version_id: &str,
|
||||
reason: DownloadReason,
|
||||
dependent_on_version_id: Option<String>,
|
||||
reporter: crate::install::InstallProgressReporter,
|
||||
state: &State,
|
||||
) -> crate::Result<DownloadedProjectVersion> {
|
||||
download_project_version_with_reporting(
|
||||
instance_id,
|
||||
version_id,
|
||||
reason,
|
||||
dependent_on_version_id,
|
||||
None,
|
||||
Some(reporter),
|
||||
state,
|
||||
@ -847,26 +784,15 @@ pub(crate) async fn download_project_version_with_reporter(
|
||||
}
|
||||
|
||||
async fn download_project_version_with_reporting(
|
||||
instance_id: &str,
|
||||
version_id: &str,
|
||||
reason: DownloadReason,
|
||||
dependent_on_version_id: Option<String>,
|
||||
progress: Option<ResolvedContentDownloadProgress>,
|
||||
reporter: Option<crate::install::InstallProgressReporter>,
|
||||
state: &State,
|
||||
) -> crate::Result<DownloadedProjectVersion> {
|
||||
let prepared = prepare_version_download(
|
||||
instance_id,
|
||||
version_id,
|
||||
reason,
|
||||
dependent_on_version_id,
|
||||
state,
|
||||
)
|
||||
.await?;
|
||||
let prepared = prepare_version_download(version_id, state).await?;
|
||||
let mut request =
|
||||
DownloadRequest::new(&prepared.url, ResourceClass::Modrinth)
|
||||
.with_integrity(prepared.integrity)
|
||||
.with_download_meta(prepared.download_meta);
|
||||
.with_integrity(prepared.integrity);
|
||||
let tracking_reporter = progress
|
||||
.as_ref()
|
||||
.map(|progress| progress.reporter.clone())
|
||||
@ -961,7 +887,6 @@ async fn download_project_version_with_reporting(
|
||||
struct PreparedVersionDownload {
|
||||
url: String,
|
||||
path: PathBuf,
|
||||
download_meta: DownloadMeta,
|
||||
integrity: Integrity,
|
||||
file_name: String,
|
||||
sha1: Option<String>,
|
||||
@ -973,22 +898,9 @@ struct PreparedVersionDownload {
|
||||
/// Resolves the content scope and version metadata for a download and
|
||||
/// validates the target path, without touching the network.
|
||||
async fn prepare_version_download(
|
||||
instance_id: &str,
|
||||
version_id: &str,
|
||||
reason: DownloadReason,
|
||||
dependent_on_version_id: Option<String>,
|
||||
state: &State,
|
||||
) -> crate::Result<PreparedVersionDownload> {
|
||||
let scope = resolve_content_scope(instance_id, None, state).await?;
|
||||
let content_set =
|
||||
content_rows::get_content_set(&scope.content_set_id, &state.pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::InputError(format!(
|
||||
"Unknown content set {}",
|
||||
scope.content_set_id
|
||||
))
|
||||
})?;
|
||||
let version = CachedEntry::get_version(
|
||||
&ModrinthVersionId::new(version_id.to_string())?,
|
||||
None,
|
||||
@ -1011,12 +923,6 @@ async fn prepare_version_download(
|
||||
"No files for input version present!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let download_meta = DownloadMeta {
|
||||
reason,
|
||||
game_version: content_set.game_version,
|
||||
loader: content_set.loader.as_str().to_string(),
|
||||
dependent_on: dependent_on_version_id,
|
||||
};
|
||||
let file_name_path = Path::new(&file.filename);
|
||||
if file_name_path.as_os_str().is_empty()
|
||||
|| file_name_path.is_absolute()
|
||||
@ -1057,7 +963,6 @@ async fn prepare_version_download(
|
||||
Ok(PreparedVersionDownload {
|
||||
url: file.url.clone(),
|
||||
path,
|
||||
download_meta,
|
||||
integrity,
|
||||
file_name: file.filename.clone(),
|
||||
sha1: file.hashes.get("sha1").cloned(),
|
||||
|
||||
@ -6,7 +6,6 @@ use crate::state::{
|
||||
CacheBehaviour, CachedEntry, ContentProviderRef, Dependency,
|
||||
DependencyType, ModrinthVersionId, ProjectType, State, Version,
|
||||
};
|
||||
use crate::util::fetch::DownloadReason;
|
||||
use futures::stream::{FuturesUnordered, StreamExt};
|
||||
use std::cmp::Reverse;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@ -31,7 +30,6 @@ struct BulkUpdatePlan {
|
||||
struct PlannedProjectUpdate {
|
||||
relative_path: String,
|
||||
project_id: String,
|
||||
current_version_id: String,
|
||||
update_version_id: String,
|
||||
}
|
||||
|
||||
@ -107,7 +105,6 @@ async fn apply_content_update(
|
||||
let mut new_path = match update {
|
||||
ContentUpdate::Modrinth {
|
||||
project_id,
|
||||
current_version_id,
|
||||
update_version_id,
|
||||
..
|
||||
} => {
|
||||
@ -149,8 +146,6 @@ async fn apply_content_update(
|
||||
add_project_from_version(
|
||||
instance_id,
|
||||
&plan.primary.version_id,
|
||||
DownloadReason::Update,
|
||||
Some(current_version_id.to_string()),
|
||||
ContentSourceKind::Local,
|
||||
ownership_kind,
|
||||
state,
|
||||
@ -159,14 +154,8 @@ async fn apply_content_update(
|
||||
);
|
||||
for dependency in &plan.dependencies {
|
||||
paths.push(
|
||||
add_resolved_content(
|
||||
instance_id,
|
||||
dependency,
|
||||
DownloadReason::Dependency,
|
||||
true,
|
||||
state,
|
||||
)
|
||||
.await?,
|
||||
add_resolved_content(instance_id, dependency, true, state)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
persist_resolved_plan_dependency_edges(
|
||||
@ -449,10 +438,7 @@ async fn download_planned_projects(
|
||||
match download {
|
||||
PlannedDownload::ProjectUpdate(update) => {
|
||||
let downloaded = download_project_version(
|
||||
instance_id,
|
||||
&update.update_version_id,
|
||||
DownloadReason::Update,
|
||||
Some(update.current_version_id.clone()),
|
||||
state,
|
||||
)
|
||||
.await?;
|
||||
@ -462,14 +448,9 @@ async fn download_planned_projects(
|
||||
))
|
||||
}
|
||||
PlannedDownload::DependencyAddition(dependency) => {
|
||||
let downloaded = download_project_version(
|
||||
instance_id,
|
||||
&dependency.version_id,
|
||||
DownloadReason::Dependency,
|
||||
Some(dependency.parent_version_id.clone()),
|
||||
state,
|
||||
)
|
||||
.await?;
|
||||
let downloaded =
|
||||
download_project_version(&dependency.version_id, state)
|
||||
.await?;
|
||||
|
||||
Ok::<_, crate::Error>(
|
||||
DownloadedBulkProject::DependencyAddition(
|
||||
@ -636,11 +617,10 @@ async fn plan_bulk_update(
|
||||
let project_updates = updates
|
||||
.into_iter()
|
||||
.filter_map(|update| {
|
||||
let (project_id, current, target) = update.modrinth_ids()?;
|
||||
let (project_id, _, target) = update.modrinth_ids()?;
|
||||
Some(PlannedProjectUpdate {
|
||||
relative_path: update.relative_path().to_string(),
|
||||
project_id: project_id.to_string(),
|
||||
current_version_id: current.to_string(),
|
||||
update_version_id: target.to_string(),
|
||||
})
|
||||
})
|
||||
|
||||
@ -294,15 +294,8 @@ async fn fetch_icon_bytes(
|
||||
return Ok(response.bytes().await?);
|
||||
}
|
||||
|
||||
fetch::fetch(
|
||||
icon_url,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&state.fetch_semaphore,
|
||||
&state.pool,
|
||||
)
|
||||
.await
|
||||
fetch::fetch(icon_url, None, None, &state.fetch_semaphore, &state.pool)
|
||||
.await
|
||||
}
|
||||
|
||||
fn is_direct_cdn_icon_url(url: &str) -> bool {
|
||||
|
||||
@ -507,47 +507,6 @@ pub(crate) async fn add_instance_recent_playtime(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn mark_instance_playtime_submitted(
|
||||
instance_id: &str,
|
||||
recent_time_played: u64,
|
||||
pool: &SqlitePool,
|
||||
) -> crate::Result<()> {
|
||||
if recent_time_played == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let recent_time_played =
|
||||
playtime_to_storage(recent_time_played, "recent_time_played")?;
|
||||
let max_playtime = i64::MAX;
|
||||
let max_playtime_before_increment = max_playtime - recent_time_played;
|
||||
let modified = Utc::now().timestamp();
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE instances
|
||||
SET
|
||||
submitted_time_played = CASE
|
||||
WHEN submitted_time_played < 0 THEN ?
|
||||
WHEN submitted_time_played > ? THEN ?
|
||||
ELSE submitted_time_played + ?
|
||||
END,
|
||||
recent_time_played = 0,
|
||||
modified = ?
|
||||
WHERE id = ?
|
||||
",
|
||||
recent_time_played,
|
||||
max_playtime_before_increment,
|
||||
max_playtime,
|
||||
recent_time_played,
|
||||
modified,
|
||||
instance_id,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::{TimeZone, Utc};
|
||||
|
||||
@ -20,8 +20,8 @@ use crate::state::{
|
||||
ReleaseChannel, TeamMember, Version, VersionV3,
|
||||
};
|
||||
use crate::util::fetch::{
|
||||
ContentValidation, DownloadMeta, DownloadReason, DownloadRequest,
|
||||
FetchSemaphore, Integrity, ResourceClass, download_to_path, sha1_async,
|
||||
ContentValidation, DownloadRequest, FetchSemaphore, Integrity,
|
||||
ResourceClass, download_to_path, sha1_async,
|
||||
};
|
||||
use async_zip::tokio::read::fs::ZipFileReader;
|
||||
use dashmap::DashMap;
|
||||
@ -507,7 +507,6 @@ pub(crate) async fn list_linked_modpack_content(
|
||||
};
|
||||
let ids = match get_modpack_identifiers(
|
||||
version_id,
|
||||
&resolved.content_set,
|
||||
&state.pool,
|
||||
&state.api_semaphore,
|
||||
)
|
||||
@ -2520,7 +2519,6 @@ async fn get_cached_modpack_identifiers(
|
||||
|
||||
async fn get_modpack_identifiers(
|
||||
version_id: &str,
|
||||
content_set: &ContentSet,
|
||||
pool: &SqlitePool,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<ModpackIdentifiers> {
|
||||
@ -2585,12 +2583,6 @@ async fn get_modpack_identifiers(
|
||||
"No files found for modpack version {version_id}"
|
||||
))
|
||||
})?;
|
||||
let download_meta = DownloadMeta {
|
||||
reason: DownloadReason::Modpack,
|
||||
game_version: content_set.game_version.clone(),
|
||||
loader: content_set.loader.as_str().to_string(),
|
||||
dependent_on: Some(version_id.to_string()),
|
||||
};
|
||||
let state = State::get().await?;
|
||||
let file_name = Path::new(&primary_file.filename);
|
||||
if file_name.components().count() != 1
|
||||
@ -2619,8 +2611,7 @@ async fn get_modpack_identifiers(
|
||||
sha512: primary_file.hashes.get("sha512").cloned(),
|
||||
content: ContentValidation::Jar,
|
||||
..Integrity::default()
|
||||
})
|
||||
.with_download_meta(download_meta),
|
||||
}),
|
||||
&pack_path,
|
||||
&state.download_semaphore,
|
||||
pool,
|
||||
|
||||
@ -61,8 +61,6 @@ where
|
||||
settings.collapsed_navigation = legacy_settings.collapsed_navigation;
|
||||
settings.advanced_rendering = legacy_settings.advanced_rendering;
|
||||
settings.native_decorations = legacy_settings.native_decorations;
|
||||
settings.telemetry = !legacy_settings.opt_out_analytics;
|
||||
settings.discord_rpc = !legacy_settings.disable_discord_rpc;
|
||||
settings.developer_mode = legacy_settings.developer_mode;
|
||||
settings.onboarded = legacy_settings.fully_onboarded;
|
||||
settings.extra_launch_args = legacy_settings.custom_java_args;
|
||||
@ -660,8 +658,6 @@ struct LegacySettings {
|
||||
pub max_concurrent_writes: usize,
|
||||
pub collapsed_navigation: bool,
|
||||
#[serde(default)]
|
||||
pub disable_discord_rpc: bool,
|
||||
#[serde(default)]
|
||||
pub hide_on_process: bool,
|
||||
#[serde(default)]
|
||||
pub native_decorations: bool,
|
||||
@ -670,8 +666,6 @@ struct LegacySettings {
|
||||
#[serde(default)]
|
||||
pub developer_mode: bool,
|
||||
#[serde(default)]
|
||||
pub opt_out_analytics: bool,
|
||||
#[serde(default)]
|
||||
pub advanced_rendering: bool,
|
||||
#[serde(default)]
|
||||
pub fully_onboarded: bool,
|
||||
|
||||
@ -42,9 +42,6 @@ pub use self::java_globals::*;
|
||||
mod discovered_javas;
|
||||
pub use self::discovered_javas::*;
|
||||
|
||||
mod discord;
|
||||
pub use self::discord::*;
|
||||
|
||||
mod minecraft_auth;
|
||||
pub use self::minecraft_auth::*;
|
||||
|
||||
@ -124,9 +121,6 @@ pub struct State {
|
||||
pub(crate) install_job_operation_locks:
|
||||
DashMap<Uuid, Arc<AsyncMutex<InstallJobOperationState>>>,
|
||||
|
||||
/// Discord RPC
|
||||
pub discord_rpc: DiscordGuard,
|
||||
|
||||
/// Process manager
|
||||
pub process_manager: ProcessManager,
|
||||
|
||||
@ -501,8 +495,6 @@ impl State {
|
||||
concurrency_state.run_auto_concurrency_controller().await;
|
||||
});
|
||||
|
||||
crate::telemetry::start(Arc::clone(state));
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
crate::google_ip::preload().await;
|
||||
});
|
||||
@ -521,14 +513,13 @@ impl State {
|
||||
.await;
|
||||
|
||||
let res = tokio::try_join!(
|
||||
state.discord_rpc.clear_to_default(true),
|
||||
instances::refresh_all_instances(),
|
||||
Settings::migrate(&state.pool),
|
||||
ModrinthCredentials::refresh_all(),
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
tracing::error!("Error running discord RPC: {e}");
|
||||
tracing::error!("Error refreshing startup state: {e}");
|
||||
}
|
||||
|
||||
// Axolotl does not connect to Modrinth's private friends socket.
|
||||
@ -861,8 +852,6 @@ impl State {
|
||||
let directories =
|
||||
DirectoryInfo::init(settings.custom_dir, &app_identifier).await?;
|
||||
|
||||
let discord_rpc = DiscordGuard::init()?;
|
||||
|
||||
tracing::info!("Initializing file watcher");
|
||||
let file_watcher = instances::watcher::init_watcher().await?;
|
||||
|
||||
@ -915,7 +904,6 @@ impl State {
|
||||
install_db_semaphore: Semaphore::new(1),
|
||||
install_job_cancellations: DashMap::new(),
|
||||
install_job_operation_locks: DashMap::new(),
|
||||
discord_rpc,
|
||||
process_manager,
|
||||
friends_socket,
|
||||
restart_after_pending_update: AtomicBool::new(false),
|
||||
@ -983,7 +971,6 @@ pub(crate) async fn test_state(
|
||||
install_db_semaphore: Semaphore::new(1),
|
||||
install_job_cancellations: DashMap::new(),
|
||||
install_job_operation_locks: DashMap::new(),
|
||||
discord_rpc: DiscordGuard::init()?,
|
||||
process_manager: ProcessManager::new(),
|
||||
friends_socket: FriendsSocket::new(),
|
||||
restart_after_pending_update: AtomicBool::new(false),
|
||||
|
||||
@ -35,7 +35,6 @@ impl ModrinthCredentials {
|
||||
None,
|
||||
Some(("Authorization", &*creds.session)),
|
||||
None,
|
||||
None,
|
||||
Some("/v2/session/refresh"),
|
||||
semaphore,
|
||||
exec,
|
||||
@ -228,7 +227,6 @@ async fn fetch_info(
|
||||
None,
|
||||
Some(("Authorization", token)),
|
||||
None,
|
||||
None,
|
||||
Some("/v2/user"),
|
||||
semaphore,
|
||||
exec,
|
||||
|
||||
@ -1194,25 +1194,6 @@ impl Process {
|
||||
record_post_upgrade_launch_best_effort(&instance_id, clean_launch)
|
||||
.await;
|
||||
|
||||
// Publish play time update
|
||||
// Allow failure, it will be stored locally and sent next time
|
||||
// Sent in another thread as first call may take a couple seconds and hold up process ending
|
||||
let playtime_instance_id = instance_id.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) =
|
||||
crate::api::instance::try_update_playtime_by_instance_id(
|
||||
&playtime_instance_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(
|
||||
"Failed to update playtime for instance {}: {}",
|
||||
playtime_instance_id,
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let log_path = logs_folder.join(LAUNCHER_LOG_PATH);
|
||||
|
||||
if log_path.exists()
|
||||
@ -1248,8 +1229,6 @@ impl Process {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let _ = state.discord_rpc.clear_to_default(true).await;
|
||||
|
||||
if mc_exit_status.success() {
|
||||
// We do not wait on the post exist command to finish running! We let it spawn + run on its own.
|
||||
// This behaviour may be changed in the future
|
||||
|
||||
@ -154,10 +154,6 @@ pub struct Settings {
|
||||
#[serde(default = "default_terracotta_public_nodes")]
|
||||
pub terracotta_public_nodes: Vec<String>,
|
||||
|
||||
pub telemetry: bool,
|
||||
#[serde(default)]
|
||||
pub telemetry_consent_version: u32,
|
||||
pub discord_rpc: bool,
|
||||
#[serde(skip, default)]
|
||||
pub personalized_ads: bool,
|
||||
|
||||
@ -190,13 +186,6 @@ pub struct Settings {
|
||||
pub version: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
pub struct PrivacySettings {
|
||||
pub telemetry: bool,
|
||||
pub discord_rpc: bool,
|
||||
pub consent_version: u32,
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
@ -330,9 +319,6 @@ impl Settings {
|
||||
.as_ref()
|
||||
.and_then(|value| serde_json::from_str(value).ok())
|
||||
.unwrap_or_else(default_terracotta_public_nodes),
|
||||
telemetry: res.telemetry == 1,
|
||||
telemetry_consent_version: res.telemetry_consent_version as u32,
|
||||
discord_rpc: res.discord_rpc == 1,
|
||||
developer_mode: res.developer_mode == 1,
|
||||
personalized_ads: res.personalized_ads == 1,
|
||||
onboarded: res.onboarded == 1,
|
||||
@ -521,9 +507,9 @@ impl Settings {
|
||||
self.collapsed_navigation,
|
||||
self.advanced_rendering,
|
||||
self.native_decorations,
|
||||
self.discord_rpc,
|
||||
false,
|
||||
self.developer_mode,
|
||||
self.telemetry,
|
||||
false,
|
||||
self.personalized_ads,
|
||||
self.onboarded,
|
||||
extra_launch_args,
|
||||
@ -573,7 +559,7 @@ impl Settings {
|
||||
home_widgets,
|
||||
mojang_auth_source,
|
||||
terracotta_public_nodes,
|
||||
self.telemetry_consent_version,
|
||||
0_i64,
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
@ -1355,16 +1341,20 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn telemetry_schema_migrates_fresh_and_existing_settings_databases() {
|
||||
async fn removed_data_collection_schema_is_cleaned_up() {
|
||||
let fresh = sqlx::sqlite::SqlitePoolOptions::new()
|
||||
.max_connections(1)
|
||||
.connect("sqlite::memory:")
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::migrate!().run(&fresh).await.unwrap();
|
||||
let settings = Settings::get(&fresh).await.unwrap();
|
||||
assert!(!settings.telemetry);
|
||||
assert_eq!(settings.telemetry_consent_version, 0);
|
||||
let telemetry_tables = sqlx::query_scalar::<_, String>(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE 'telemetry_%'",
|
||||
)
|
||||
.fetch_all(&fresh)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(telemetry_tables.is_empty());
|
||||
assert!(
|
||||
sqlx::query("PRAGMA foreign_key_check")
|
||||
.fetch_all(&fresh)
|
||||
@ -1372,44 +1362,5 @@ mod tests {
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
|
||||
let upgrade = sqlx::sqlite::SqlitePoolOptions::new()
|
||||
.max_connections(1)
|
||||
.connect("sqlite::memory:")
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(
|
||||
"CREATE TABLE settings (id INTEGER PRIMARY KEY CHECK (id = 0), telemetry INTEGER NOT NULL DEFAULT 0, discord_rpc INTEGER NOT NULL DEFAULT 1)",
|
||||
)
|
||||
.execute(&upgrade)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(
|
||||
"INSERT INTO settings (id, telemetry, discord_rpc) VALUES (0, 0, 1)",
|
||||
)
|
||||
.execute(&upgrade)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::raw_sql(include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/migrations/20260814120000_telemetry.sql"
|
||||
)))
|
||||
.execute(&upgrade)
|
||||
.await
|
||||
.unwrap();
|
||||
let consent_version = sqlx::query_scalar::<_, i64>(
|
||||
"SELECT telemetry_consent_version FROM settings WHERE id = 0",
|
||||
)
|
||||
.fetch_one(&upgrade)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(consent_version, 0);
|
||||
assert!(
|
||||
sqlx::query("PRAGMA foreign_key_check")
|
||||
.fetch_all(&upgrade)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user