Merge remote-tracking branch 'upstream/main'
Some checks failed
Build / build-windows (push) Failing after 17s
Codex Template Compliance / template-compliance (push) Successful in 9s

This commit is contained in:
MrSphay
2026-05-16 13:38:08 +02:00
512 changed files with 35000 additions and 12435 deletions

View File

@@ -218,9 +218,8 @@ pub async fn generate_pack_from_version_id(
icon_url: Option<String>,
profile_path: String,
// Existing loading bar. Unlike when existing_loading_bar is used, this one is pre-initialized with PackFileDownload
// For example, you might use this if multiple packs are being downloaded at once and you want to use the same loading bar
initialized_loading_bar: Option<LoadingBarId>,
reason: DownloadReason,
) -> crate::Result<CreatePack> {
let state = State::get().await?;
let has_icon_url = icon_url.is_some();
@@ -301,7 +300,7 @@ pub async fn generate_pack_from_version_id(
})?;
let download_meta = DownloadMeta {
reason: DownloadReason::Modpack,
reason,
game_version: profile.game_version.clone(),
loader: profile.loader.as_str().to_string(),
};

View File

@@ -48,6 +48,7 @@ pub async fn install_zipped_mrpack(
icon_url,
profile_path.clone(),
None,
DownloadReason::Modpack,
)
.await?
}
@@ -57,7 +58,12 @@ pub async fn install_zipped_mrpack(
};
// Install pack files, and if it fails, fail safely by removing the profile
let result = install_zipped_mrpack_files(create_pack, false).await;
let result = install_zipped_mrpack_files(
create_pack,
false,
DownloadReason::Modpack,
)
.await;
match result {
Ok(profile) => Ok(profile),
@@ -74,6 +80,7 @@ pub async fn install_zipped_mrpack(
pub async fn install_zipped_mrpack_files(
create_pack: CreatePack,
ignore_lock: bool,
reason: DownloadReason,
) -> crate::Result<String> {
let state = &State::get().await?;
@@ -221,7 +228,7 @@ pub async fn install_zipped_mrpack_files(
})?;
let download_meta = DownloadMeta {
reason: DownloadReason::Modpack,
reason,
game_version: profile.game_version.clone(),
loader: profile.loader.as_str().to_string(),
};

View File

@@ -461,7 +461,7 @@ pub async fn update_project(
let mut path = Profile::add_project_version(
profile_path,
update_version,
fetch::DownloadReason::Standalone,
fetch::DownloadReason::Update,
&state.pool,
&state.fetch_semaphore,
&state.io_semaphore,

View File

@@ -1,4 +1,5 @@
use crate::state::CacheBehaviour;
use crate::util::fetch::DownloadReason;
use crate::{
LoadingBarType,
event::{
@@ -162,7 +163,8 @@ async fn replace_managed_modrinth(
profile.name.clone(),
None,
profile_path.to_string(),
Some(shared_loading_bar.clone())
Some(shared_loading_bar.clone()),
DownloadReason::Update,
),
generate_pack_from_version_id(
project_id.clone(),
@@ -170,7 +172,8 @@ async fn replace_managed_modrinth(
profile.name.clone(),
None,
profile_path.to_string(),
Some(shared_loading_bar)
Some(shared_loading_bar),
DownloadReason::Update,
)
)?
} else {
@@ -182,6 +185,7 @@ async fn replace_managed_modrinth(
None,
profile_path.to_string(),
None,
DownloadReason::Update,
)
.await?;
old_pack_creator.description.existing_loading_bar = None;
@@ -205,6 +209,7 @@ async fn replace_managed_modrinth(
pack::install_mrpack::install_zipped_mrpack_files(
new_pack_creator,
ignore_lock,
DownloadReason::Update,
)
.await?;

View File

@@ -890,17 +890,6 @@ impl CachedEntry {
}
}
remaining_keys.retain(|x| {
x != &&*row.id
&& !row.alias.as_ref().is_some_and(|y| {
if type_.case_sensitive_alias().unwrap_or(true) {
x == y
} else {
y.to_lowercase() == x.to_lowercase()
}
})
});
if let Some(data) = parsed_data {
if data.get_type() != type_ {
return Err(crate::ErrorKind::OtherError(format!(
@@ -912,6 +901,18 @@ impl CachedEntry {
.as_error());
}
remaining_keys.retain(|x| {
x != &&*row.id
&& !row.alias.as_ref().is_some_and(|y| {
if type_.case_sensitive_alias().unwrap_or(true)
{
x == y
} else {
y.to_lowercase() == x.to_lowercase()
}
})
});
return_vals.push(Self {
id: row.id,
alias: row.alias,

View File

@@ -213,7 +213,7 @@ impl DirectoryInfo {
.as_ref()
.map_or_else(|| app_dir.clone(), PathBuf::from);
async fn is_dir_writeable(
async fn is_dir_writable(
new_config_dir: &Path,
) -> crate::Result<bool> {
let temp_path = new_config_dir.join(".tmp");
@@ -259,8 +259,8 @@ impl DirectoryInfo {
)
.await?;
if !is_dir_writeable(&move_dir).await? {
return Err(crate::ErrorKind::DirectoryMoveError(format!("Cannot move directory to {}: directory is not writeable", move_dir.display())).into());
if !is_dir_writable(&move_dir).await? {
return Err(crate::ErrorKind::DirectoryMoveError(format!("Cannot move directory to {}: directory is not writable", move_dir.display())).into());
}
const MOVE_DIRS: &[&str] = &[

View File

@@ -144,18 +144,19 @@ pub(crate) async fn watch_profiles_init(
watcher: &FileWatcher,
dirs: &DirectoryInfo,
) {
if let Ok(profiles_dir) = std::fs::read_dir(dirs.profiles_dir()) {
for profile_dir in profiles_dir {
if let Ok(file_name) = profile_dir.map(|x| x.file_name())
&& let Some(file_name) = file_name.to_str()
{
if file_name.starts_with(".DS_Store") {
continue;
};
let Ok(mut profiles_dir) = tokio::fs::read_dir(dirs.profiles_dir()).await
else {
return;
};
watch_profile(file_name, watcher, dirs).await;
}
while let Ok(Some(profile_dir)) = profiles_dir.next_entry().await {
let file_name = profile_dir.file_name();
let file_name = file_name.to_string_lossy();
if file_name.starts_with(".DS_Store") {
continue;
}
watch_profile(&file_name, watcher, dirs).await;
}
}
@@ -166,46 +167,58 @@ pub(crate) async fn watch_profile(
) {
let profile_path = dirs.profiles_dir().join(profile_path);
if profile_path.exists() && profile_path.is_dir() {
for sub_path in ProjectType::iterator()
.map(|x| x.get_folder())
.chain(["crash-reports", "saves"])
{
let full_path = profile_path.join(sub_path);
let Ok(metadata) = tokio::fs::metadata(&profile_path).await else {
return;
};
if !full_path.exists()
&& !full_path.is_symlink()
&& !sub_path.contains(".")
&& let Err(e) =
crate::util::io::create_dir_all(&full_path).await
{
tracing::error!(
"Failed to create directory for watcher {full_path:?}: {e}"
);
return;
}
if !metadata.is_dir() {
return;
}
let mut watcher = watcher.write().await;
if let Err(e) = watcher
.watcher()
.watch(&full_path, RecursiveMode::Recursive)
{
tracing::error!(
"Failed to watch directory for watcher {full_path:?}: {e}"
);
return;
}
}
let mut to_watch = Vec::new();
for sub_path in ProjectType::iterator()
.map(|x| x.get_folder())
.chain(["crash-reports", "saves"])
{
let full_path = profile_path.join(sub_path);
let mut watcher = watcher.write().await;
if let Err(e) = watcher
.watcher()
.watch(&profile_path, RecursiveMode::NonRecursive)
let meta = tokio::fs::symlink_metadata(&full_path).await;
let exists = meta.is_ok();
let is_symlink = meta.ok().is_some_and(|m| m.file_type().is_symlink());
if !exists
&& !is_symlink
&& !sub_path.contains(".")
&& let Err(e) = crate::util::io::create_dir_all(&full_path).await
{
tracing::error!(
"Failed to watch root profile directory for watcher {profile_path:?}: {e}"
"Failed to create directory for watcher {full_path:?}: {e}"
);
return;
}
to_watch.push(full_path);
}
let mut watcher = watcher.write().await;
for full_path in &to_watch {
if let Err(e) =
watcher.watcher().watch(full_path, RecursiveMode::Recursive)
{
tracing::error!(
"Failed to watch directory for watcher {full_path:?}: {e}"
);
return;
}
}
if let Err(e) = watcher
.watcher()
.watch(&profile_path, RecursiveMode::NonRecursive)
{
tracing::error!(
"Failed to watch root profile directory for watcher {profile_path:?}: {e}"
);
}
}

View File

@@ -1,6 +1,7 @@
//! Theseus state management system
use crate::util::fetch::{FetchSemaphore, IoSemaphore};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use tokio::sync::{OnceCell, Semaphore};
use crate::state::fs_watcher::FileWatcher;
@@ -83,6 +84,8 @@ pub struct State {
/// Friends socket
pub friends_socket: FriendsSocket,
pub restart_after_pending_update: AtomicBool,
pub(crate) pool: SqlitePool,
pub(crate) file_watcher: FileWatcher,
@@ -95,6 +98,12 @@ impl State {
.await?;
tokio::task::spawn(async move {
fs_watcher::watch_profiles_init(
&state.file_watcher,
&state.directories,
)
.await;
let res = tokio::try_join!(
state.discord_rpc.clear_to_default(true),
Profile::refresh_all(),
@@ -140,6 +149,10 @@ impl State {
LAUNCHER_STATE.initialized()
}
pub fn get_if_initialized() -> Option<Arc<Self>> {
LAUNCHER_STATE.get().map(Arc::clone)
}
#[tracing::instrument]
async fn initialize_state(
app_identifier: String,
@@ -175,7 +188,6 @@ impl State {
tracing::info!("Initializing file watcher");
let file_watcher = fs_watcher::init_watcher().await?;
fs_watcher::watch_profiles_init(&file_watcher, &directories).await;
let process_manager = ProcessManager::new();
@@ -189,6 +201,7 @@ impl State {
discord_rpc,
process_manager,
friends_socket,
restart_after_pending_update: AtomicBool::new(false),
pool,
file_watcher,
// app_identifier,

View File

@@ -417,6 +417,25 @@ struct InitialScanFile {
cache_key: String,
}
fn is_scannable_project_file(
project_type: ProjectType,
file_name: &str,
) -> bool {
let Some(extension) = Path::new(file_name.trim_end_matches(".disabled"))
.extension()
.and_then(|ext| ext.to_str())
else {
return false;
};
match project_type {
ProjectType::Mod => extension.eq_ignore_ascii_case("jar"),
ProjectType::DataPack
| ProjectType::ResourcePack
| ProjectType::ShaderPack => extension.eq_ignore_ascii_case("zip"),
}
}
impl Profile {
pub async fn get(
path: &str,
@@ -648,8 +667,10 @@ impl Profile {
&& let Some(file_name) = subdirectory
.file_name()
.and_then(|x| x.to_str())
&& !(project_type == ProjectType::ShaderPack
&& file_name.ends_with(".txt"))
&& is_scannable_project_file(
project_type,
file_name,
)
{
let file_size = subdirectory
.metadata()
@@ -951,15 +972,13 @@ impl Profile {
InitialScanFile,
> = keys.into_iter().map(|k| (k.path.clone(), k)).collect();
let mut file_info_by_hash: std::collections::HashMap<
String,
CachedFile,
> = file_info.into_iter().map(|f| (f.hash.clone(), f)).collect();
let file_info_by_hash: std::collections::HashMap<String, CachedFile> =
file_info.into_iter().map(|f| (f.hash.clone(), f)).collect();
let files = DashMap::new();
for hash in file_hashes {
let file = file_info_by_hash.remove(&hash.hash);
let file = file_info_by_hash.get(&hash.hash).cloned();
let trimmed = hash.path.trim_end_matches(".disabled");
if let Some(initial_file) = keys_by_path.remove(trimmed) {
@@ -1054,8 +1073,7 @@ impl Profile {
if subdirectory.is_file()
&& let Some(file_name) =
subdirectory.file_name().and_then(|x| x.to_str())
&& !(project_type == ProjectType::ShaderPack
&& file_name.ends_with(".txt"))
&& is_scannable_project_file(project_type, file_name)
{
let file_size = subdirectory
.metadata()

View File

@@ -27,6 +27,7 @@ pub enum DownloadReason {
Standalone,
Dependency,
Modpack,
Update,
}
#[derive(Debug, Clone, Serialize)]