mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2c25d21c3 | ||
|
|
d46cdf9b2a |
@@ -106,6 +106,7 @@ pub(crate) async fn create_instance(
|
||||
tx.commit().await?;
|
||||
|
||||
crate::state::instances::watcher::watch_instance_folder(
|
||||
&instance.id,
|
||||
&instance.path,
|
||||
&state.file_watcher,
|
||||
&state.directories,
|
||||
|
||||
@@ -7,15 +7,20 @@ use crate::state::{
|
||||
use crate::worlds::WorldType;
|
||||
use notify::{RecommendedWatcher, RecursiveMode};
|
||||
use notify_debouncer_mini::{DebounceEventResult, Debouncer, new_debouncer};
|
||||
use std::time::Duration;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use tokio::sync::{RwLock, mpsc::channel};
|
||||
|
||||
use super::adapters::sqlite::instance_rows;
|
||||
|
||||
pub type FileWatcher = RwLock<Debouncer<RecommendedWatcher>>;
|
||||
pub struct FileWatcher {
|
||||
watcher: RwLock<Debouncer<RecommendedWatcher>>,
|
||||
instance_ids: Arc<RwLock<HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
pub async fn init_watcher() -> crate::Result<FileWatcher> {
|
||||
let (tx, mut rx) = channel(1);
|
||||
let instance_ids = Arc::new(RwLock::new(HashMap::new()));
|
||||
let event_instance_ids = instance_ids.clone();
|
||||
|
||||
let file_watcher = new_debouncer(
|
||||
Duration::from_secs_f32(1.0),
|
||||
@@ -32,88 +37,85 @@ pub async fn init_watcher() -> crate::Result<FileWatcher> {
|
||||
|
||||
match res {
|
||||
Ok(events) => {
|
||||
let instance_ids = event_instance_ids.read().await;
|
||||
let mut visited_instances = Vec::new();
|
||||
|
||||
events.iter().for_each(|e| {
|
||||
let mut instance_path = None;
|
||||
for e in &events {
|
||||
let mut instance_path = None;
|
||||
|
||||
let mut found = false;
|
||||
for component in e.path.components() {
|
||||
if found {
|
||||
instance_path = Some(component.as_os_str());
|
||||
break;
|
||||
}
|
||||
let mut found = false;
|
||||
for component in e.path.components() {
|
||||
if found {
|
||||
instance_path = Some(component.as_os_str());
|
||||
break;
|
||||
}
|
||||
|
||||
if component.as_os_str()
|
||||
== crate::state::dirs::INSTANCES_FOLDER_NAME
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if component.as_os_str()
|
||||
== crate::state::dirs::INSTANCES_FOLDER_NAME
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(instance_path) = instance_path {
|
||||
let instance_path_str =
|
||||
instance_path.to_string_lossy().to_string();
|
||||
let first_file_name = e
|
||||
.path
|
||||
.components()
|
||||
.skip_while(|x| x.as_os_str() != instance_path)
|
||||
.nth(1)
|
||||
.map(|x| x.as_os_str());
|
||||
if first_file_name
|
||||
.as_ref()
|
||||
.is_some_and(|x| *x == "crash-reports")
|
||||
&& e.path
|
||||
.extension()
|
||||
.as_ref()
|
||||
.is_some_and(|x| *x == "txt")
|
||||
{
|
||||
crash_task(instance_path_str);
|
||||
} else if !visited_instances.contains(&instance_path)
|
||||
{
|
||||
let event = if first_file_name
|
||||
.as_ref()
|
||||
.is_some_and(|x| *x == "servers.dat")
|
||||
{
|
||||
Some(InstancePayloadType::ServersUpdated)
|
||||
} else if first_file_name.as_ref().is_some_and(|x| {
|
||||
*x == "saves"
|
||||
&& e.path
|
||||
.file_name()
|
||||
.as_ref()
|
||||
.is_some_and(|x| *x == "level.dat")
|
||||
}) {
|
||||
tracing::info!(
|
||||
"World updated: {}",
|
||||
e.path.display()
|
||||
);
|
||||
let world = e
|
||||
.path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
if !e.path.is_file() {
|
||||
let instance_path_str = instance_path_str.clone();
|
||||
let world = world.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Ok(state) = State::get().await {
|
||||
let instance_id = sqlx::query_scalar!(
|
||||
"
|
||||
SELECT id
|
||||
FROM instances
|
||||
WHERE path = ?
|
||||
",
|
||||
instance_path_str,
|
||||
)
|
||||
.fetch_optional(&state.pool)
|
||||
.await;
|
||||
let Ok(Some(instance_id)) = instance_id else {
|
||||
return;
|
||||
};
|
||||
if let Err(e) = attached_world_data::AttachedWorldData::remove_for_world(
|
||||
if let Some(instance_path) = instance_path {
|
||||
let instance_path_str =
|
||||
instance_path.to_string_lossy().to_string();
|
||||
let Some(instance_id) =
|
||||
instance_ids.get(&instance_path_str).cloned()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let first_file_name = e
|
||||
.path
|
||||
.components()
|
||||
.skip_while(|x| x.as_os_str() != instance_path)
|
||||
.nth(1)
|
||||
.map(|x| x.as_os_str());
|
||||
if first_file_name
|
||||
.as_ref()
|
||||
.is_some_and(|x| *x == "crash-reports")
|
||||
&& e.path
|
||||
.extension()
|
||||
.as_ref()
|
||||
.is_some_and(|x| *x == "txt")
|
||||
{
|
||||
crash_task(instance_id);
|
||||
} else if !visited_instances.contains(&instance_id)
|
||||
{
|
||||
let event = if first_file_name
|
||||
.as_ref()
|
||||
.is_some_and(|x| *x == "servers.dat")
|
||||
{
|
||||
Some(InstancePayloadType::ServersUpdated)
|
||||
} else if first_file_name.as_ref().is_some_and(
|
||||
|x| {
|
||||
*x == "saves"
|
||||
&& e.path
|
||||
.file_name()
|
||||
.as_ref()
|
||||
.is_some_and(|x| {
|
||||
*x == "level.dat"
|
||||
})
|
||||
},
|
||||
) {
|
||||
tracing::info!(
|
||||
"World updated: {}",
|
||||
e.path.display()
|
||||
);
|
||||
let world = e
|
||||
.path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
if !e.path.is_file() {
|
||||
let instance_id = instance_id.clone();
|
||||
let world = world.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Ok(state) = State::get().await
|
||||
&& let Err(e) = attached_world_data::AttachedWorldData::remove_for_world(
|
||||
&instance_id,
|
||||
WorldType::Singleplayer,
|
||||
&world,
|
||||
@@ -121,68 +123,69 @@ pub async fn init_watcher() -> crate::Result<FileWatcher> {
|
||||
).await {
|
||||
tracing::warn!("Failed to remove AttachedWorldData for '{world}': {e}")
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Some(InstancePayloadType::WorldUpdated { world })
|
||||
} else if first_file_name
|
||||
.as_ref()
|
||||
.is_none_or(|x| *x != "saves")
|
||||
{
|
||||
Some(InstancePayloadType::Synced)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(event) = event {
|
||||
tokio::spawn(async move {
|
||||
let _ = emit_instance(
|
||||
&instance_path_str,
|
||||
event,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
visited_instances.push(instance_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Some(InstancePayloadType::WorldUpdated {
|
||||
world,
|
||||
})
|
||||
} else if first_file_name
|
||||
.as_ref()
|
||||
.is_none_or(|x| *x != "saves")
|
||||
{
|
||||
Some(InstancePayloadType::Synced)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(event) = event {
|
||||
let emit_instance_id = instance_id.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = emit_instance(
|
||||
&emit_instance_id,
|
||||
event,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
visited_instances.push(instance_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => tracing::warn!("Unable to watch file: {error}"),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(RwLock::new(file_watcher))
|
||||
Ok(FileWatcher {
|
||||
watcher: RwLock::new(file_watcher),
|
||||
instance_ids,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn watch_instances_init(
|
||||
watcher: &FileWatcher,
|
||||
dirs: &DirectoryInfo,
|
||||
pool: &sqlx::SqlitePool,
|
||||
) {
|
||||
let Ok(mut instances_dir) = tokio::fs::read_dir(dirs.instances_dir()).await
|
||||
else {
|
||||
let Ok(instances) = instance_rows::list_instances(pool).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
while let Ok(Some(instance_dir)) = instances_dir.next_entry().await {
|
||||
let file_name = instance_dir.file_name();
|
||||
let file_name = file_name.to_string_lossy();
|
||||
if file_name.starts_with(".DS_Store") {
|
||||
continue;
|
||||
}
|
||||
|
||||
watch_instance_folder(&file_name, watcher, dirs).await;
|
||||
for instance in instances {
|
||||
watch_instance_folder(&instance.id, &instance.path, watcher, dirs)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn watch_instance_folder(
|
||||
instance_id: &str,
|
||||
instance_path: &str,
|
||||
watcher: &FileWatcher,
|
||||
dirs: &DirectoryInfo,
|
||||
) {
|
||||
let instance_path = dirs.instances_dir().join(instance_path);
|
||||
let full_instance_path = dirs.instances_dir().join(instance_path);
|
||||
|
||||
let Ok(metadata) = tokio::fs::metadata(&instance_path).await else {
|
||||
let Ok(metadata) = tokio::fs::metadata(&full_instance_path).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -195,7 +198,7 @@ pub(crate) async fn watch_instance_folder(
|
||||
.map(|x| x.get_folder())
|
||||
.chain(["crash-reports", "saves"])
|
||||
{
|
||||
let full_path = instance_path.join(sub_path);
|
||||
let full_path = full_instance_path.join(sub_path);
|
||||
|
||||
let meta = tokio::fs::symlink_metadata(&full_path).await;
|
||||
let exists = meta.is_ok();
|
||||
@@ -215,10 +218,11 @@ pub(crate) async fn watch_instance_folder(
|
||||
to_watch.push(full_path);
|
||||
}
|
||||
|
||||
let mut watcher = watcher.write().await;
|
||||
let mut debouncer = watcher.watcher.write().await;
|
||||
for full_path in &to_watch {
|
||||
if let Err(e) =
|
||||
watcher.watcher().watch(full_path, RecursiveMode::Recursive)
|
||||
if let Err(e) = debouncer
|
||||
.watcher()
|
||||
.watch(full_path, RecursiveMode::Recursive)
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to watch directory for watcher {full_path:?}: {e}"
|
||||
@@ -227,22 +231,29 @@ pub(crate) async fn watch_instance_folder(
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = watcher
|
||||
if let Err(e) = debouncer
|
||||
.watcher()
|
||||
.watch(&instance_path, RecursiveMode::NonRecursive)
|
||||
.watch(&full_instance_path, RecursiveMode::NonRecursive)
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to watch root instance directory for watcher {instance_path:?}: {e}"
|
||||
"Failed to watch root instance directory for watcher {full_instance_path:?}: {e}"
|
||||
);
|
||||
}
|
||||
|
||||
watcher
|
||||
.instance_ids
|
||||
.write()
|
||||
.await
|
||||
.insert(instance_path.to_string(), instance_id.to_string());
|
||||
}
|
||||
|
||||
fn crash_task(path: String) {
|
||||
fn crash_task(instance_id: String) {
|
||||
tokio::task::spawn(async move {
|
||||
let res = async {
|
||||
let state = State::get().await?;
|
||||
let Some(instance) =
|
||||
instance_rows::get_instance_by_path(&path, &state.pool).await?
|
||||
instance_rows::get_instance_by_id(&instance_id, &state.pool)
|
||||
.await?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
@@ -107,6 +107,7 @@ impl State {
|
||||
instances::watcher::watch_instances_init(
|
||||
&state.file_watcher,
|
||||
&state.directories,
|
||||
&state.pool,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user