-
Notifications
You must be signed in to change notification settings - Fork 171
fix: infinite import #689
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: infinite import #689
Changes from 4 commits
b9bef11
e053785
1d1ecab
93c9c5e
ffec0dc
0bdb82e
6499dcb
10a275c
0c4b99e
b05719d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,7 @@ use std::{ | |
| ops::DerefMut, | ||
| path::Path, | ||
| }; | ||
| use tracing::debug; | ||
| use tracing::{debug, warn}; | ||
|
|
||
| #[derive(Clone, Serialize, Deserialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
|
|
@@ -99,10 +99,44 @@ pub struct Data { | |
|
|
||
| #[serde(default)] | ||
| pub collections: Vec<LocalCollection>, | ||
| #[serde(default)] | ||
| pub import_scan_retry: HashMap<String, u8>, | ||
| } | ||
|
|
||
| impl Data { | ||
| pub async fn init(&mut self) -> Result<()> { | ||
| fn persist_retry_state(data: &Data) { | ||
| let res = (|| -> Result<()> { | ||
| let root = dir::root().map_err(|e| anyhow::anyhow!("failed to get root directory: {}", e))?; | ||
| let path = format!("{}/data.json", root); | ||
| std::fs::write(&path, serde_json::to_string(data)?).map_err(|e| anyhow::anyhow!("failed to write to {}: {}", path, e))?; | ||
| Ok(()) | ||
| })(); | ||
| if let Err(err) = res { | ||
| warn!(?err, "failed to persist import scan retry state"); | ||
| } | ||
| } | ||
|
|
||
| fn remove_failed_entry(path: &Path, key: &str, retry_map: &mut HashMap<String, u8>) { | ||
| let remove_res = if path.is_dir() { | ||
| std::fs::remove_dir_all(path) | ||
| } else if path.exists() { | ||
| std::fs::remove_file(path) | ||
| } else { | ||
| Ok(()) | ||
| }; | ||
| if let Err(err) = remove_res { | ||
| warn!(?err, "failed to remove exhausted import entry: {}", key); | ||
| } | ||
| retry_map.remove(key); | ||
| } | ||
|
|
||
| fn bump_retry(map: &mut HashMap<String, u8>, key: &str) { | ||
| const MAX_RETRIES: u8 = 2; | ||
| let entry = map.entry(key.to_owned()).or_default(); | ||
| *entry = (*entry + 1).min(MAX_RETRIES); | ||
| } | ||
|
|
||
| let charts = dir::charts()?; | ||
| self.charts.retain(|it| Path::new(&format!("{}/{}", charts, it.local_path)).exists()); | ||
| let occurred: HashSet<_> = self.charts.iter().map(|it| it.local_path.clone()).collect(); | ||
|
|
@@ -111,22 +145,38 @@ impl Data { | |
| let filename = entry.file_name(); | ||
| let filename = filename.to_str().unwrap(); | ||
| let filename = format!("custom/{filename}"); | ||
| let path = entry.path(); | ||
| if occurred.contains(&filename) { | ||
| self.import_scan_retry.remove(&filename); | ||
| continue; | ||
| } | ||
| let path = entry.path(); | ||
| if self.import_scan_retry.get(&filename).copied().unwrap_or_default() >= 2 { | ||
|
||
| remove_failed_entry(&path, &filename, &mut self.import_scan_retry); | ||
| persist_retry_state(self); | ||
| warn!("skip startup import scan after retry limit reached: {filename}"); | ||
| continue; | ||
| } | ||
| // Persist retry count before parsing so crashes during parsing still consume one retry. | ||
| bump_retry(&mut self.import_scan_retry, &filename); | ||
| persist_retry_state(self); | ||
| let Ok(mut fs) = prpr::fs::fs_from_file(&path) else { | ||
| continue; | ||
| }; | ||
| let result = prpr::fs::load_info(fs.deref_mut()).await; | ||
| if let Ok(info) = result { | ||
| self.charts.push(LocalChart { | ||
| info: BriefChartInfo { id: None, ..info.into() }, | ||
| local_path: filename, | ||
| record: None, | ||
| mods: Mods::default(), | ||
| played_unlock: false, | ||
| }); | ||
| match result { | ||
| Ok(info) => { | ||
| self.import_scan_retry.remove(&filename); | ||
| self.charts.push(LocalChart { | ||
| info: BriefChartInfo { id: None, ..info.into() }, | ||
| local_path: filename, | ||
| record: None, | ||
| mods: Mods::default(), | ||
| played_unlock: false, | ||
| }); | ||
| } | ||
| Err(err) => { | ||
| warn!(?err, "failed to parse startup custom import candidate: {}", filename); | ||
| } | ||
| } | ||
| } | ||
| for entry in std::fs::read_dir(dir::downloaded_charts()?)? { | ||
|
|
@@ -135,22 +185,39 @@ impl Data { | |
| let filename = filename.to_str().unwrap(); | ||
| let Ok(id): Result<i32, _> = filename.parse() else { continue }; | ||
| let filename = format!("download/{filename}"); | ||
| let path = entry.path(); | ||
| if occurred.contains(&filename) { | ||
| self.import_scan_retry.remove(&filename); | ||
| continue; | ||
| } | ||
| let path = entry.path(); | ||
| if self.import_scan_retry.get(&filename).copied().unwrap_or_default() >= 2 { | ||
| remove_failed_entry(&path, &filename, &mut self.import_scan_retry); | ||
| persist_retry_state(self); | ||
| warn!("skip startup import scan after retry limit reached: {filename}"); | ||
| continue; | ||
| } | ||
| // Persist retry count before parsing so crashes during parsing still consume one retry. | ||
| bump_retry(&mut self.import_scan_retry, &filename); | ||
| persist_retry_state(self); | ||
| let Ok(mut fs) = prpr::fs::fs_from_file(&path) else { | ||
| warn!("failed to open file system for downloaded chart: {}", filename); | ||
| continue; | ||
| }; | ||
| let result = prpr::fs::load_info(fs.deref_mut()).await; | ||
| if let Ok(info) = result { | ||
| self.charts.push(LocalChart { | ||
| info: BriefChartInfo { id: Some(id), ..info.into() }, | ||
| local_path: filename, | ||
| record: None, | ||
| mods: Mods::default(), | ||
| played_unlock: false, | ||
| }); | ||
| match result { | ||
| Ok(info) => { | ||
| self.import_scan_retry.remove(&filename); | ||
| self.charts.push(LocalChart { | ||
| info: BriefChartInfo { id: Some(id), ..info.into() }, | ||
| local_path: filename, | ||
| record: None, | ||
| mods: Mods::default(), | ||
| played_unlock: false, | ||
| }); | ||
| } | ||
| Err(err) => { | ||
| warn!(?err, "failed to parse startup downloaded import candidate: {}", filename); | ||
| } | ||
| } | ||
| } | ||
| let respacks: HashSet<_> = self.respacks.iter().cloned().collect(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is there a need to persist this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to know what path caused the problem when restarting the program next time