Merge remote-tracking branch 'upstream/main'
This commit is contained in:
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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] = &[
|
||||
|
||||
@@ -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}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -27,6 +27,7 @@ pub enum DownloadReason {
|
||||
Standalone,
|
||||
Dependency,
|
||||
Modpack,
|
||||
Update,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
|
||||
Reference in New Issue
Block a user