fix: more work on background tasks
This commit is contained in:
@@ -12,6 +12,7 @@ impl BdsApp {
|
||||
let on_progress: engine::rebuild::ProgressFn =
|
||||
Arc::new(move |pct, event| {
|
||||
tm.report_progress(tid, Some(pct), Some(event.localized(locale)));
|
||||
!tm.is_cancelled(tid)
|
||||
});
|
||||
let report = engine::rebuild::rebuild_from_filesystem_with_progress(
|
||||
db.conn(),
|
||||
@@ -42,14 +43,28 @@ impl BdsApp {
|
||||
"engine.calendarStarted",
|
||||
move |db_path, project_id, data_dir, tm, tid| {
|
||||
let db = Database::open(&db_path).map_err(|e| e.to_string())?;
|
||||
tm.report_progress(tid, Some(0.20), Some(t(locale, "engine.loadingPosts")));
|
||||
engine::calendar::regenerate_calendar(db.conn(), &data_dir, &project_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
tm.report_progress(
|
||||
tid,
|
||||
Some(0.90),
|
||||
Some(t(locale, "engine.writingCalendar")),
|
||||
);
|
||||
engine::calendar::regenerate_calendar_with_progress(
|
||||
db.conn(),
|
||||
&data_dir,
|
||||
&project_id,
|
||||
|current, total, name| {
|
||||
tm.report_progress(
|
||||
tid,
|
||||
Some(current as f32 / total.max(1) as f32),
|
||||
Some(tw(
|
||||
locale,
|
||||
"engine.checkingItem",
|
||||
&[
|
||||
("current", ¤t.to_string()),
|
||||
("total", &total.to_string()),
|
||||
("name", name),
|
||||
],
|
||||
)),
|
||||
);
|
||||
!tm.is_cancelled(tid)
|
||||
},
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok("done".to_string())
|
||||
},
|
||||
)
|
||||
@@ -59,24 +74,39 @@ impl BdsApp {
|
||||
TabType::TranslationValidation,
|
||||
"tabBar.translationValidation",
|
||||
);
|
||||
let (Some(project), Some(data_dir)) = (&self.active_project, &self.data_dir) else {
|
||||
if self.active_project.is_none() || self.data_dir.is_none() {
|
||||
return Task::none();
|
||||
};
|
||||
}
|
||||
self.translation_validation_state.is_running = true;
|
||||
self.translation_validation_state.error_message = None;
|
||||
let db_path = self.db_path.clone();
|
||||
let project_id = project.id.clone();
|
||||
let data_dir = data_dir.clone();
|
||||
Task::perform(
|
||||
async move {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let db = Database::open(&db_path).map_err(|e| e.to_string())?;
|
||||
let meta = engine::meta::read_project_json(&data_dir)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let main_lang = meta.main_language.as_deref().unwrap_or("en");
|
||||
let blog_langs = meta.blog_languages.clone();
|
||||
let on_item: engine::validate_translations::ItemProgressFn =
|
||||
Box::new(move |_current, _total, _name| {});
|
||||
let locale = self.ui_locale;
|
||||
self.spawn_result_task(
|
||||
"menu.item.validateTranslations",
|
||||
move |db_path, project_id, data_dir, tm, tid| {
|
||||
let db = Database::open(&db_path).map_err(|e| e.to_string())?;
|
||||
let meta = engine::meta::read_project_json(&data_dir)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let main_lang = meta.main_language.as_deref().unwrap_or("en");
|
||||
let blog_langs = meta.blog_languages.clone();
|
||||
let progress_tm = Arc::clone(&tm);
|
||||
let on_item: engine::validate_translations::ItemProgressFn =
|
||||
Box::new(move |current, total, name| {
|
||||
progress_tm.report_progress(
|
||||
tid,
|
||||
Some(current as f32 / total.max(1) as f32),
|
||||
Some(tw(
|
||||
locale,
|
||||
"engine.checkingItem",
|
||||
&[
|
||||
("current", ¤t.to_string()),
|
||||
("total", &total.to_string()),
|
||||
("name", name),
|
||||
],
|
||||
)),
|
||||
);
|
||||
!progress_tm.is_cancelled(tid)
|
||||
});
|
||||
let report =
|
||||
engine::validate_translations::validate_translations_with_progress(
|
||||
db.conn(),
|
||||
&data_dir,
|
||||
@@ -85,16 +115,31 @@ impl BdsApp {
|
||||
main_lang,
|
||||
Some(on_item),
|
||||
)
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
.unwrap_or_else(|error| Err(format!("task panicked: {error}")))
|
||||
.map_err(|e| e.to_string())?;
|
||||
tm.report_progress(
|
||||
tid,
|
||||
Some(1.0),
|
||||
Some(tw(
|
||||
locale,
|
||||
"engine.validateTranslationsComplete",
|
||||
&[
|
||||
("dbIssues", &report.db_issues.len().to_string()),
|
||||
("fsIssues", &report.fs_issues.len().to_string()),
|
||||
],
|
||||
)),
|
||||
);
|
||||
Ok(report)
|
||||
},
|
||||
Message::TranslationValidationLoaded,
|
||||
|task_id, result| Message::TranslationValidationLoaded { task_id, result },
|
||||
)
|
||||
}
|
||||
Message::TranslationValidationLoaded(result) => {
|
||||
Message::TranslationValidationLoaded { task_id, result } => {
|
||||
let cancelled = self.finish_result_task(task_id, &result);
|
||||
self.refresh_task_snapshots();
|
||||
self.translation_validation_state.is_running = false;
|
||||
if cancelled {
|
||||
return Task::none();
|
||||
}
|
||||
match result {
|
||||
Ok(report) => {
|
||||
self.translation_validation_state.report = Some(report);
|
||||
@@ -149,8 +194,13 @@ impl BdsApp {
|
||||
self.open_singleton_tab(TabType::MetadataDiff, "tabBar.metadataDiff");
|
||||
self.start_metadata_diff()
|
||||
}
|
||||
Message::MetadataDiffLoaded(result) => {
|
||||
Message::MetadataDiffLoaded { task_id, result } => {
|
||||
let cancelled = self.finish_result_task(task_id, &result);
|
||||
self.refresh_task_snapshots();
|
||||
self.metadata_diff_state.is_running = false;
|
||||
if cancelled {
|
||||
return Task::none();
|
||||
}
|
||||
match result {
|
||||
Ok(report) => {
|
||||
self.metadata_diff_state.report = Some(report);
|
||||
@@ -170,36 +220,38 @@ impl BdsApp {
|
||||
else {
|
||||
return Task::none();
|
||||
};
|
||||
let (Some(project), Some(data_dir)) =
|
||||
(self.active_project.as_ref(), self.data_dir.as_ref())
|
||||
else {
|
||||
if self.active_project.is_none() || self.data_dir.is_none() {
|
||||
return Task::none();
|
||||
};
|
||||
}
|
||||
self.metadata_diff_state.is_repairing = true;
|
||||
let db_path = self.db_path.clone();
|
||||
let project_id = project.id.clone();
|
||||
let data_dir = data_dir.clone();
|
||||
Task::perform(
|
||||
async move {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let db = Database::open(&db_path).map_err(|error| error.to_string())?;
|
||||
engine::metadata_diff::repair_metadata_diff_item(
|
||||
db.conn(),
|
||||
&data_dir,
|
||||
&project_id,
|
||||
direction,
|
||||
&item,
|
||||
)
|
||||
.map_err(|error| error.to_string())
|
||||
})
|
||||
.await
|
||||
.map_err(|error| error.to_string())?
|
||||
let locale = self.ui_locale;
|
||||
self.spawn_result_task(
|
||||
"metadataDiff.repair",
|
||||
move |db_path, project_id, data_dir, tm, tid| {
|
||||
tm.report_progress(tid, Some(0.0), Some(t(locale, "metadataDiff.repair")));
|
||||
if tm.is_cancelled(tid) {
|
||||
return Err("operation cancelled".into());
|
||||
}
|
||||
let db = Database::open(&db_path).map_err(|error| error.to_string())?;
|
||||
engine::metadata_diff::repair_metadata_diff_item(
|
||||
db.conn(),
|
||||
&data_dir,
|
||||
&project_id,
|
||||
direction,
|
||||
&item,
|
||||
)
|
||||
.map_err(|error| error.to_string())
|
||||
},
|
||||
Message::MetadataDiffItemRepaired,
|
||||
|task_id, result| Message::MetadataDiffItemRepaired { task_id, result },
|
||||
)
|
||||
}
|
||||
Message::MetadataDiffItemRepaired(result) => {
|
||||
Message::MetadataDiffItemRepaired { task_id, result } => {
|
||||
let cancelled = self.finish_result_task(task_id, &result);
|
||||
self.refresh_task_snapshots();
|
||||
self.metadata_diff_state.is_repairing = false;
|
||||
if cancelled {
|
||||
return Task::none();
|
||||
}
|
||||
match result {
|
||||
Ok(()) => {
|
||||
self.notify(
|
||||
@@ -214,6 +266,62 @@ impl BdsApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ImportMetadataOrphan(index) => {
|
||||
let Some(orphan) = self
|
||||
.metadata_diff_state
|
||||
.report
|
||||
.as_ref()
|
||||
.and_then(|report| report.orphans.get(index))
|
||||
.cloned()
|
||||
else {
|
||||
return Task::none();
|
||||
};
|
||||
self.metadata_diff_state.is_repairing = true;
|
||||
let locale = self.ui_locale;
|
||||
self.spawn_result_task(
|
||||
"metadataDiff.importOrphan",
|
||||
move |db_path, project_id, data_dir, tm, tid| {
|
||||
tm.report_progress(
|
||||
tid,
|
||||
Some(0.0),
|
||||
Some(t(locale, "metadataDiff.importOrphan")),
|
||||
);
|
||||
if tm.is_cancelled(tid) {
|
||||
return Err("operation cancelled".into());
|
||||
}
|
||||
let db = Database::open(&db_path).map_err(|error| error.to_string())?;
|
||||
engine::metadata_diff::import_orphan_file(
|
||||
db.conn(),
|
||||
&data_dir,
|
||||
&project_id,
|
||||
&orphan,
|
||||
)
|
||||
.map_err(|error| error.to_string())
|
||||
},
|
||||
|task_id, result| Message::MetadataOrphanImported { task_id, result },
|
||||
)
|
||||
}
|
||||
Message::MetadataOrphanImported { task_id, result } => {
|
||||
let cancelled = self.finish_result_task(task_id, &result);
|
||||
self.refresh_task_snapshots();
|
||||
self.metadata_diff_state.is_repairing = false;
|
||||
if cancelled {
|
||||
return Task::none();
|
||||
}
|
||||
match result {
|
||||
Ok(()) => {
|
||||
self.notify(
|
||||
ToastLevel::Success,
|
||||
&t(self.ui_locale, "metadataDiff.orphanImported"),
|
||||
);
|
||||
self.start_metadata_diff()
|
||||
}
|
||||
Err(error) => {
|
||||
self.notify(ToastLevel::Error, &error);
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::RunSiteValidation => self.start_site_validation(),
|
||||
Message::ApplySiteValidation => self.apply_site_validation(),
|
||||
Message::EngineTaskDone {
|
||||
@@ -223,15 +331,14 @@ impl BdsApp {
|
||||
result,
|
||||
} => {
|
||||
let search_rebuild_finished = self.search_index_rebuild_task_id == Some(task_id);
|
||||
let cancelled = self.task_manager.status(task_id) == Some(TaskStatus::Cancelled);
|
||||
let refresh_semantic_tags = !cancelled
|
||||
let cancellation_requested = self.task_manager.is_cancelled(task_id);
|
||||
let refresh_semantic_tags = !cancellation_requested
|
||||
&& result.is_ok()
|
||||
&& matches!(
|
||||
operation,
|
||||
"embeddings.indexing" | "menu.item.rebuildEmbeddingIndex"
|
||||
);
|
||||
match &result {
|
||||
_ if cancelled => {}
|
||||
Ok(detail) => {
|
||||
self.task_manager.complete(task_id);
|
||||
if operation == "engine.rebuildStarted" {
|
||||
@@ -258,12 +365,14 @@ impl BdsApp {
|
||||
}
|
||||
Err(err) => {
|
||||
self.task_manager.fail(task_id, err.clone());
|
||||
let message = tw(
|
||||
self.ui_locale,
|
||||
"common.operationFailed",
|
||||
&[("operation", &label), ("error", err)],
|
||||
);
|
||||
self.notify(ToastLevel::Error, &message);
|
||||
if !cancellation_requested {
|
||||
let message = tw(
|
||||
self.ui_locale,
|
||||
"common.operationFailed",
|
||||
&[("operation", &label), ("error", err)],
|
||||
);
|
||||
self.notify(ToastLevel::Error, &message);
|
||||
}
|
||||
}
|
||||
}
|
||||
if search_rebuild_finished {
|
||||
@@ -293,12 +402,41 @@ impl BdsApp {
|
||||
self.refresh_task_snapshots();
|
||||
Task::batch([sidebar_task, semantic_task])
|
||||
}
|
||||
Message::SiteGenerationPrepared {
|
||||
task_id,
|
||||
validation,
|
||||
force,
|
||||
result,
|
||||
} => {
|
||||
let cancelled = self.finish_result_task(task_id, &result);
|
||||
self.refresh_task_snapshots();
|
||||
if cancelled {
|
||||
if validation.is_some() {
|
||||
self.site_validation_state.is_applying = false;
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
match result {
|
||||
Ok((prepared, counts)) => {
|
||||
self.queue_prepared_site_generation(validation, force, prepared, counts)
|
||||
}
|
||||
Err(error) => {
|
||||
if validation.is_some() {
|
||||
self.site_validation_state.is_applying = false;
|
||||
self.site_validation_state.error_message = Some(error.clone());
|
||||
}
|
||||
self.notify(ToastLevel::Error, &error);
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::SiteGenerationSectionDone {
|
||||
group_id,
|
||||
task_id,
|
||||
result,
|
||||
} => {
|
||||
if self.task_manager.status(task_id) == Some(TaskStatus::Cancelled) {
|
||||
if self.task_manager.is_cancelled(task_id) {
|
||||
self.finish_result_task(task_id, &result);
|
||||
self.refresh_task_snapshots();
|
||||
return Task::none();
|
||||
}
|
||||
@@ -335,21 +473,54 @@ impl BdsApp {
|
||||
}
|
||||
}
|
||||
|
||||
let should_index =
|
||||
let next_phase =
|
||||
self.site_generation_workflows
|
||||
.get(&group_id)
|
||||
.is_some_and(|workflow| {
|
||||
workflow.index_task_id.is_none()
|
||||
.and_then(|workflow| {
|
||||
(workflow.calendar_task_id.is_none()
|
||||
&& workflow.index_task_id.is_none()
|
||||
&& workflow.render_task_ids.iter().all(|task_id| {
|
||||
self.task_manager.status(*task_id)
|
||||
== Some(TaskStatus::Completed)
|
||||
})
|
||||
}))
|
||||
.then_some(workflow.calendar_needed)
|
||||
});
|
||||
self.refresh_task_snapshots();
|
||||
if should_index {
|
||||
self.queue_site_search_index(&group_id)
|
||||
} else {
|
||||
Task::none()
|
||||
match next_phase {
|
||||
Some(true) => self.queue_site_calendar(&group_id),
|
||||
Some(false) => self.queue_site_search_index(&group_id),
|
||||
None => Task::none(),
|
||||
}
|
||||
}
|
||||
Message::SiteGenerationCalendarDone {
|
||||
group_id,
|
||||
task_id,
|
||||
result,
|
||||
} => {
|
||||
if self.task_manager.is_cancelled(task_id) {
|
||||
self.finish_result_task(task_id, &result);
|
||||
self.refresh_task_snapshots();
|
||||
return Task::none();
|
||||
}
|
||||
match result {
|
||||
Ok(()) => {
|
||||
self.task_manager.complete(task_id);
|
||||
self.refresh_task_snapshots();
|
||||
self.queue_site_search_index(&group_id)
|
||||
}
|
||||
Err(error) => {
|
||||
self.task_manager.fail(task_id, error.clone());
|
||||
self.task_manager.cancel_group(&group_id);
|
||||
if let Some(workflow) = self.site_generation_workflows.remove(&group_id)
|
||||
&& workflow.kind == SiteGenerationKind::Validation
|
||||
{
|
||||
self.site_validation_state.is_applying = false;
|
||||
self.site_validation_state.error_message = Some(error.clone());
|
||||
}
|
||||
self.notify(ToastLevel::Error, &error);
|
||||
self.refresh_task_snapshots();
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::SiteGenerationIndexDone {
|
||||
@@ -357,7 +528,8 @@ impl BdsApp {
|
||||
task_id,
|
||||
result,
|
||||
} => {
|
||||
if self.task_manager.status(task_id) == Some(TaskStatus::Cancelled) {
|
||||
if self.task_manager.is_cancelled(task_id) {
|
||||
self.finish_result_task(task_id, &result);
|
||||
self.refresh_task_snapshots();
|
||||
return Task::none();
|
||||
}
|
||||
@@ -415,8 +587,13 @@ impl BdsApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::SiteValidationLoaded(result) => {
|
||||
Message::SiteValidationLoaded { task_id, result } => {
|
||||
let cancelled = self.finish_result_task(task_id, &result);
|
||||
self.refresh_task_snapshots();
|
||||
self.site_validation_state.is_running = false;
|
||||
if cancelled {
|
||||
return Task::none();
|
||||
}
|
||||
self.site_validation_state.has_run = true;
|
||||
match result {
|
||||
Ok(report) => {
|
||||
|
||||
@@ -259,10 +259,11 @@ impl BdsApp {
|
||||
|
||||
Task::perform(
|
||||
async move {
|
||||
let Some(worker) = task_manager.admit(task_id).await else {
|
||||
return Err("cancelled".to_string());
|
||||
};
|
||||
tokio::task::spawn_blocking(move || {
|
||||
if !task_manager.wait_until_runnable(task_id) {
|
||||
return Err("cancelled".to_string());
|
||||
}
|
||||
let _worker = worker;
|
||||
let engine = GitEngine::new(&data_dir);
|
||||
let old_head = (operation == GitOperation::Pull)
|
||||
.then(|| engine.head())
|
||||
|
||||
@@ -14,35 +14,40 @@ fn shorten_task_url(url: &str) -> String {
|
||||
}
|
||||
|
||||
impl BdsApp {
|
||||
pub(super) fn finish_result_task<T>(
|
||||
&self,
|
||||
task_id: TaskId,
|
||||
result: &Result<T, String>,
|
||||
) -> bool {
|
||||
let cancellation_requested = self.task_manager.is_cancelled(task_id);
|
||||
match result {
|
||||
Ok(_) => self.task_manager.complete(task_id),
|
||||
Err(error) => self.task_manager.fail(task_id, error.clone()),
|
||||
}
|
||||
cancellation_requested && result.is_err()
|
||||
}
|
||||
|
||||
pub(super) fn refresh_task_snapshots(&mut self) {
|
||||
self.task_snapshots = self
|
||||
.task_manager
|
||||
.snapshots()
|
||||
.into_iter()
|
||||
.map(|snapshot| {
|
||||
let status_str = match &snapshot.status {
|
||||
TaskStatus::Pending => t(self.ui_locale, "tasks.statusPending"),
|
||||
TaskStatus::Running => t(self.ui_locale, "tasks.statusRunning"),
|
||||
TaskStatus::Completed => t(self.ui_locale, "tasks.statusCompleted"),
|
||||
TaskStatus::Failed(error) => {
|
||||
tw(self.ui_locale, "tasks.statusFailed", &[("error", error)])
|
||||
}
|
||||
TaskStatus::Cancelled => t(self.ui_locale, "tasks.statusCancelled"),
|
||||
};
|
||||
TaskSnapshot {
|
||||
id: snapshot.id,
|
||||
label: snapshot.label,
|
||||
group_id: snapshot.group_id,
|
||||
group_name: snapshot.group_name,
|
||||
status: status_str,
|
||||
progress: snapshot.progress,
|
||||
message: snapshot.message,
|
||||
is_cancellable: matches!(
|
||||
snapshot.status,
|
||||
TaskStatus::Pending | TaskStatus::Running
|
||||
),
|
||||
}
|
||||
.map(|snapshot| TaskSnapshot {
|
||||
id: snapshot.id,
|
||||
source: crate::state::navigation::TaskSource::Local,
|
||||
label: snapshot.label,
|
||||
group_id: snapshot.group_id,
|
||||
group_name: snapshot.group_name,
|
||||
status: snapshot.status.clone(),
|
||||
progress: snapshot.progress,
|
||||
message: snapshot.message,
|
||||
cancellation_requested: snapshot.cancellation_requested,
|
||||
is_cancellable: matches!(
|
||||
snapshot.status,
|
||||
TaskStatus::Pending | TaskStatus::Running
|
||||
) && !snapshot.cancellation_requested,
|
||||
})
|
||||
.chain(self.remote_task_snapshots.iter().cloned())
|
||||
.collect();
|
||||
}
|
||||
|
||||
@@ -69,7 +74,7 @@ impl BdsApp {
|
||||
} else {
|
||||
SiteGenerationKind::Full
|
||||
};
|
||||
let (Some(project), Some(data_dir)) = (&self.active_project, &self.data_dir) else {
|
||||
let Some(data_dir) = &self.data_dir else {
|
||||
if kind == SiteGenerationKind::Validation {
|
||||
self.site_validation_state.is_applying = false;
|
||||
}
|
||||
@@ -79,6 +84,16 @@ impl BdsApp {
|
||||
);
|
||||
return Task::none();
|
||||
};
|
||||
if self.active_project.is_none() {
|
||||
if kind == SiteGenerationKind::Validation {
|
||||
self.site_validation_state.is_applying = false;
|
||||
}
|
||||
self.notify(
|
||||
ToastLevel::Error,
|
||||
&t(self.ui_locale, "engine.generateSiteNoProject"),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
let metadata = match engine::meta::read_project_json(data_dir) {
|
||||
Ok(metadata) => metadata,
|
||||
Err(error) => {
|
||||
@@ -107,37 +122,122 @@ impl BdsApp {
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
let sections = validation.as_ref().map_or_else(
|
||||
|| engine::generation::GenerationSection::ALL.to_vec(),
|
||||
|validation| engine::generation::sections_from_validation_report(validation, &metadata),
|
||||
);
|
||||
if sections.is_empty() {
|
||||
return Task::none();
|
||||
}
|
||||
let task_validation = validation.clone();
|
||||
let locale = self.ui_locale;
|
||||
self.spawn_result_task(
|
||||
"engine.prepareGeneration",
|
||||
move |db_path, project_id, data_dir, tm, tid| {
|
||||
tm.report_progress(
|
||||
tid,
|
||||
Some(0.0),
|
||||
Some(t(locale, "engine.preparingGeneration")),
|
||||
);
|
||||
let db = Database::open(&db_path).map_err(|error| error.to_string())?;
|
||||
let metadata = engine::meta::read_project_json(&data_dir)
|
||||
.map_err(|error| error.to_string())?;
|
||||
if tm.is_cancelled(tid) {
|
||||
return Err("operation cancelled".into());
|
||||
}
|
||||
let posts =
|
||||
bds_core::db::queries::post::list_posts_by_project(db.conn(), &project_id)
|
||||
.map_err(|error| error.to_string())?
|
||||
.into_iter()
|
||||
.filter(engine::generation::has_published_snapshot)
|
||||
.collect::<Vec<_>>();
|
||||
let total_posts = posts.len();
|
||||
let mut sources = Vec::with_capacity(total_posts);
|
||||
for (index, post) in posts.into_iter().enumerate() {
|
||||
tm.report_progress(
|
||||
tid,
|
||||
Some(0.05 + (index + 1) as f32 / total_posts.max(1) as f32 * 0.35),
|
||||
Some(t(locale, "engine.loadingPosts")),
|
||||
);
|
||||
if tm.is_cancelled(tid) {
|
||||
return Err("operation cancelled".into());
|
||||
}
|
||||
if let Some(source) =
|
||||
engine::generation::load_published_post_source(&data_dir, post)
|
||||
.map_err(|error| error.to_string())?
|
||||
{
|
||||
sources.push(source);
|
||||
}
|
||||
}
|
||||
tm.report_progress(
|
||||
tid,
|
||||
Some(0.45),
|
||||
Some(t(locale, "engine.preparingGeneration")),
|
||||
);
|
||||
let prepared = Arc::new(
|
||||
engine::generation::prepare_site_generation(
|
||||
db.conn(),
|
||||
&data_dir,
|
||||
&project_id,
|
||||
&metadata,
|
||||
&sources,
|
||||
)
|
||||
.map_err(|error| error.to_string())?,
|
||||
);
|
||||
if tm.is_cancelled(tid) {
|
||||
return Err("operation cancelled".into());
|
||||
}
|
||||
let sections = task_validation.as_ref().map_or_else(
|
||||
|| engine::generation::GenerationSection::ALL.to_vec(),
|
||||
|report| engine::generation::sections_from_validation_report(report, &metadata),
|
||||
);
|
||||
let counts = sections
|
||||
.into_iter()
|
||||
.map(|section| {
|
||||
(
|
||||
section,
|
||||
engine::generation::prepared_section_page_count(
|
||||
&prepared,
|
||||
task_validation.as_ref(),
|
||||
section,
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
tm.report_progress(tid, Some(1.0), Some(t(locale, "engine.generationPrepared")));
|
||||
Ok((prepared, counts))
|
||||
},
|
||||
move |task_id, result| Message::SiteGenerationPrepared {
|
||||
task_id,
|
||||
validation: validation.clone(),
|
||||
force,
|
||||
result,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn queue_prepared_site_generation(
|
||||
&mut self,
|
||||
validation: Option<engine::validate_site::SiteValidationReport>,
|
||||
force: bool,
|
||||
prepared: Arc<engine::generation::PreparedSiteGeneration>,
|
||||
page_estimates: HashMap<engine::generation::GenerationSection, usize>,
|
||||
) -> Task<Message> {
|
||||
let kind = if validation.is_some() {
|
||||
SiteGenerationKind::Validation
|
||||
} else if force {
|
||||
SiteGenerationKind::Forced
|
||||
} else {
|
||||
SiteGenerationKind::Full
|
||||
};
|
||||
let (Some(project), Some(data_dir)) = (&self.active_project, &self.data_dir) else {
|
||||
return Task::none();
|
||||
};
|
||||
let sections = page_estimates.keys().copied().collect::<Vec<_>>();
|
||||
let calendar_needed = kind == SiteGenerationKind::Validation
|
||||
&& sections.iter().any(|section| {
|
||||
matches!(
|
||||
section,
|
||||
engine::generation::GenerationSection::Core
|
||||
| engine::generation::GenerationSection::Date
|
||||
)
|
||||
});
|
||||
let project_id = project.id.clone();
|
||||
let db_path = self.db_path.clone();
|
||||
let data_dir = data_dir.clone();
|
||||
let published_posts = match self.db.as_ref().and_then(|db| {
|
||||
bds_core::db::queries::post::list_posts_by_project(db.conn(), &project_id).ok()
|
||||
}) {
|
||||
Some(posts) => posts
|
||||
.into_iter()
|
||||
.filter(engine::generation::has_published_snapshot)
|
||||
.collect::<Vec<_>>(),
|
||||
None => return Task::none(),
|
||||
};
|
||||
let page_estimates = match bds_core::render::estimate_site_render_pages(
|
||||
&data_dir,
|
||||
&metadata,
|
||||
&published_posts,
|
||||
) {
|
||||
Ok(estimates) => estimates,
|
||||
Err(error) => {
|
||||
self.notify(ToastLevel::Error, &error.to_string());
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
let group_id = format!("site-generation:{}", Uuid::new_v4());
|
||||
let group_name = t(
|
||||
self.ui_locale,
|
||||
@@ -150,7 +250,10 @@ impl BdsApp {
|
||||
let mut render_task_ids = Vec::new();
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
for section in sections {
|
||||
for section in engine::generation::GenerationSection::ALL
|
||||
.into_iter()
|
||||
.filter(|section| sections.contains(section))
|
||||
{
|
||||
let label = t(self.ui_locale, generation_section_label_key(section));
|
||||
self.add_output(&label);
|
||||
let page_work = page_estimates[§ion];
|
||||
@@ -173,14 +276,20 @@ impl BdsApp {
|
||||
let task_data_dir = data_dir.clone();
|
||||
let task_group_id = group_id.clone();
|
||||
let task_validation = validation.clone();
|
||||
let task_prepared = Arc::clone(&prepared);
|
||||
let locale = self.ui_locale;
|
||||
tasks.push(Task::perform(
|
||||
async move {
|
||||
let Some(worker) = task_manager.admit(task_id).await else {
|
||||
return Err("cancelled".to_string());
|
||||
};
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let _worker = worker;
|
||||
run_site_generation_section(
|
||||
task_db_path,
|
||||
task_project_id,
|
||||
task_data_dir,
|
||||
task_prepared,
|
||||
task_manager,
|
||||
task_id,
|
||||
section,
|
||||
@@ -210,6 +319,8 @@ impl BdsApp {
|
||||
data_dir,
|
||||
group_name,
|
||||
render_task_ids,
|
||||
calendar_needed,
|
||||
calendar_task_id: None,
|
||||
index_task_id: None,
|
||||
report: engine::generation::GenerationReport::default(),
|
||||
},
|
||||
@@ -218,6 +329,75 @@ impl BdsApp {
|
||||
Task::batch(tasks)
|
||||
}
|
||||
|
||||
pub(super) fn queue_site_calendar(&mut self, group_id: &str) -> Task<Message> {
|
||||
let Some(workflow) = self.site_generation_workflows.get(group_id).cloned() else {
|
||||
return Task::none();
|
||||
};
|
||||
let label = t(self.ui_locale, "engine.calendarStarted");
|
||||
let task_id = self
|
||||
.task_manager
|
||||
.submit_grouped(&label, group_id, &workflow.group_name);
|
||||
if let Some(workflow) = self.site_generation_workflows.get_mut(group_id) {
|
||||
workflow.calendar_task_id = Some(task_id);
|
||||
}
|
||||
self.refresh_task_snapshots();
|
||||
let task_manager = Arc::clone(&self.task_manager);
|
||||
let task_group_id = group_id.to_string();
|
||||
let locale = self.ui_locale;
|
||||
Task::perform(
|
||||
async move {
|
||||
let Some(worker) = task_manager.admit(task_id).await else {
|
||||
return Err("cancelled".to_string());
|
||||
};
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let _worker = worker;
|
||||
task_manager.report_progress(
|
||||
task_id,
|
||||
Some(0.0),
|
||||
Some(t(locale, "engine.loadingPosts")),
|
||||
);
|
||||
let db =
|
||||
Database::open(&workflow.db_path).map_err(|error| error.to_string())?;
|
||||
engine::calendar::regenerate_calendar_with_progress(
|
||||
db.conn(),
|
||||
&workflow.data_dir,
|
||||
&workflow.project_id,
|
||||
|current, total, name| {
|
||||
task_manager.report_progress(
|
||||
task_id,
|
||||
Some(current as f32 / total.max(1) as f32),
|
||||
Some(tw(
|
||||
locale,
|
||||
"engine.checkingItem",
|
||||
&[
|
||||
("current", ¤t.to_string()),
|
||||
("total", &total.to_string()),
|
||||
("name", name),
|
||||
],
|
||||
)),
|
||||
);
|
||||
!task_manager.is_cancelled(task_id)
|
||||
},
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
task_manager.report_progress(
|
||||
task_id,
|
||||
Some(1.0),
|
||||
Some(t(locale, "engine.writingCalendar")),
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.unwrap_or_else(|error| Err(format!("task panicked: {error}")))
|
||||
},
|
||||
move |result| Message::SiteGenerationCalendarDone {
|
||||
group_id: task_group_id.clone(),
|
||||
task_id,
|
||||
result,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn queue_site_search_index(&mut self, group_id: &str) -> Task<Message> {
|
||||
let Some(workflow) = self.site_generation_workflows.get(group_id).cloned() else {
|
||||
return Task::none();
|
||||
@@ -237,10 +417,11 @@ impl BdsApp {
|
||||
let task_group_id = group_id.to_string();
|
||||
Task::perform(
|
||||
async move {
|
||||
let Some(worker) = task_manager.admit(task_id).await else {
|
||||
return Err("cancelled".to_string());
|
||||
};
|
||||
tokio::task::spawn_blocking(move || {
|
||||
if !task_manager.wait_until_runnable(task_id) {
|
||||
return Err("cancelled".to_string());
|
||||
}
|
||||
let _worker = worker;
|
||||
let db =
|
||||
Database::open(&workflow.db_path).map_err(|error| error.to_string())?;
|
||||
let metadata = engine::meta::read_project_json(&workflow.data_dir)
|
||||
@@ -312,7 +493,7 @@ impl BdsApp {
|
||||
if workflow.kind == SiteGenerationKind::Validation {
|
||||
self.site_validation_state.is_applying = false;
|
||||
}
|
||||
self.add_output(&t(self.ui_locale, "engine.generationCancelled"));
|
||||
self.add_output(&t(self.ui_locale, "engine.generationCancellationRequested"));
|
||||
self.refresh_task_snapshots();
|
||||
true
|
||||
}
|
||||
@@ -337,7 +518,10 @@ impl BdsApp {
|
||||
let task_id = self.task_manager.submit(&label);
|
||||
self.search_index_rebuild_running = true;
|
||||
self.search_index_rebuild_task_id = Some(task_id);
|
||||
self.active_modal = Some(modal::ModalState::SearchIndexRebuilding);
|
||||
self.active_modal = Some(modal::ModalState::SearchIndexRebuilding {
|
||||
task_id,
|
||||
cancellation_requested: false,
|
||||
});
|
||||
self.refresh_task_snapshots();
|
||||
self.sync_menu_state();
|
||||
|
||||
@@ -346,10 +530,11 @@ impl BdsApp {
|
||||
let task_manager = Arc::clone(&self.task_manager);
|
||||
Task::perform(
|
||||
async move {
|
||||
let Some(worker) = task_manager.admit(task_id).await else {
|
||||
return Err("cancelled".to_string());
|
||||
};
|
||||
tokio::task::spawn_blocking(move || {
|
||||
if !task_manager.wait_until_runnable(task_id) {
|
||||
return Err("cancelled".to_string());
|
||||
}
|
||||
let _worker = worker;
|
||||
let db = Database::open(&db_path).map_err(|error| error.to_string())?;
|
||||
let progress_manager = Arc::clone(&task_manager);
|
||||
let on_item: engine::search::ItemProgressFn =
|
||||
@@ -373,6 +558,7 @@ impl BdsApp {
|
||||
Some(progress),
|
||||
Some(message),
|
||||
);
|
||||
!progress_manager.is_cancelled(task_id)
|
||||
});
|
||||
let report = engine::search::rebuild_search_index(db.conn(), Some(on_item))
|
||||
.map_err(|error| error.to_string())?;
|
||||
@@ -403,6 +589,49 @@ impl BdsApp {
|
||||
self.spawn_engine_task_in_group(label_key, None, work)
|
||||
}
|
||||
|
||||
pub(super) fn spawn_result_task<T, F, M>(
|
||||
&mut self,
|
||||
label_key: &'static str,
|
||||
work: F,
|
||||
message: M,
|
||||
) -> Task<Message>
|
||||
where
|
||||
T: Send + 'static,
|
||||
F: FnOnce(PathBuf, String, PathBuf, Arc<TaskManager>, TaskId) -> Result<T, String>
|
||||
+ Send
|
||||
+ 'static,
|
||||
M: Fn(TaskId, Result<T, String>) -> Message + Send + Sync + 'static,
|
||||
{
|
||||
let (Some(project_id), Some(data_dir)) = (
|
||||
self.active_project
|
||||
.as_ref()
|
||||
.map(|project| project.id.clone()),
|
||||
self.data_dir.clone(),
|
||||
) else {
|
||||
return Task::none();
|
||||
};
|
||||
let label = t(self.ui_locale, label_key);
|
||||
self.add_output(&label);
|
||||
let task_id = self.task_manager.submit(&label);
|
||||
self.refresh_task_snapshots();
|
||||
let task_manager = Arc::clone(&self.task_manager);
|
||||
let db_path = self.db_path.clone();
|
||||
Task::perform(
|
||||
async move {
|
||||
let Some(worker) = task_manager.admit(task_id).await else {
|
||||
return Err("cancelled".to_string());
|
||||
};
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let _worker = worker;
|
||||
work(db_path, project_id, data_dir, task_manager, task_id)
|
||||
})
|
||||
.await
|
||||
.unwrap_or_else(|error| Err(format!("task panicked: {error}")))
|
||||
},
|
||||
move |result| message(task_id, result),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn spawn_grouped_engine_task<F>(
|
||||
&mut self,
|
||||
label_key: &'static str,
|
||||
@@ -453,10 +682,11 @@ impl BdsApp {
|
||||
|
||||
Task::perform(
|
||||
async move {
|
||||
let Some(worker) = tm.admit(task_id).await else {
|
||||
return Err("cancelled".to_string());
|
||||
};
|
||||
tokio::task::spawn_blocking(move || {
|
||||
if !tm.wait_until_runnable(task_id) {
|
||||
return Err("cancelled".to_string());
|
||||
}
|
||||
let _worker = worker;
|
||||
work(db_path, project_id, data_dir, tm, task_id)
|
||||
})
|
||||
.await
|
||||
@@ -490,6 +720,7 @@ fn run_site_generation_section(
|
||||
db_path: PathBuf,
|
||||
project_id: String,
|
||||
data_dir: PathBuf,
|
||||
prepared: Arc<engine::generation::PreparedSiteGeneration>,
|
||||
task_manager: Arc<TaskManager>,
|
||||
task_id: TaskId,
|
||||
section: engine::generation::GenerationSection,
|
||||
@@ -498,9 +729,6 @@ fn run_site_generation_section(
|
||||
locale: UiLocale,
|
||||
expected_pages: usize,
|
||||
) -> Result<engine::generation::GenerationReport, String> {
|
||||
if !task_manager.wait_until_runnable(task_id) {
|
||||
return Err("cancelled".to_string());
|
||||
}
|
||||
task_manager.report_progress(
|
||||
task_id,
|
||||
Some(if expected_pages == 0 { 1.0 } else { 0.0 }),
|
||||
@@ -511,26 +739,6 @@ fn run_site_generation_section(
|
||||
)),
|
||||
);
|
||||
let db = Database::open(&db_path).map_err(|error| error.to_string())?;
|
||||
let metadata = engine::meta::read_project_json(&data_dir).map_err(|error| error.to_string())?;
|
||||
let posts = bds_core::db::queries::post::list_posts_by_project(db.conn(), &project_id)
|
||||
.map_err(|error| error.to_string())?;
|
||||
let sources = posts
|
||||
.into_iter()
|
||||
.filter(engine::generation::has_published_snapshot)
|
||||
.map(|post| engine::generation::load_published_post_source(&data_dir, post))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|error| error.to_string())?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
let prepared = engine::generation::prepare_site_generation(
|
||||
db.conn(),
|
||||
&data_dir,
|
||||
&project_id,
|
||||
&metadata,
|
||||
&sources,
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
let output_dir = data_dir.join("html");
|
||||
std::fs::create_dir_all(&output_dir).map_err(|error| error.to_string())?;
|
||||
let render_manager = Arc::clone(&task_manager);
|
||||
@@ -565,7 +773,7 @@ fn run_site_generation_section(
|
||||
db.conn(),
|
||||
&output_dir,
|
||||
&project_id,
|
||||
&prepared,
|
||||
prepared.as_ref(),
|
||||
&validation,
|
||||
section,
|
||||
on_page,
|
||||
@@ -576,7 +784,7 @@ fn run_site_generation_section(
|
||||
db.conn(),
|
||||
&output_dir,
|
||||
&project_id,
|
||||
&prepared,
|
||||
prepared.as_ref(),
|
||||
section,
|
||||
force,
|
||||
&on_rendered,
|
||||
|
||||
Reference in New Issue
Block a user