6#include <emscripten/html5.h>
10#include "absl/strings/str_format.h"
17using ::yaze::app::platform::WasmConfig;
20bool AutoSaveManager::emergency_save_triggered_ =
false;
23EM_JS(
void, register_beforeunload_handler, (), {
25 if (!window._yazeAutoSaveHandlers) {
26 window._yazeAutoSaveHandlers = {};
30 window._yazeAutoSaveHandlers.beforeunload = function(event) {
32 if (Module._yazeEmergencySave) {
33 Module._yazeEmergencySave();
37 event.returnValue =
'You have unsaved changes. Are you sure you want to leave?';
38 return event.returnValue;
40 window.addEventListener(
'beforeunload', window._yazeAutoSaveHandlers.beforeunload);
43 window._yazeAutoSaveHandlers.visibilitychange = function() {
44 if (document.hidden && Module._yazeEmergencySave) {
46 Module._yazeEmergencySave();
49 document.addEventListener(
'visibilitychange', window._yazeAutoSaveHandlers.visibilitychange);
52 window._yazeAutoSaveHandlers.pagehide = function(event) {
53 if (Module._yazeEmergencySave) {
54 Module._yazeEmergencySave();
57 window.addEventListener(
'pagehide', window._yazeAutoSaveHandlers.pagehide);
59 console.log(
'AutoSave event handlers registered');
62EM_JS(
void, unregister_beforeunload_handler, (), {
64 if (window._yazeAutoSaveHandlers) {
65 if (window._yazeAutoSaveHandlers.beforeunload) {
66 window.removeEventListener(
'beforeunload', window._yazeAutoSaveHandlers.beforeunload);
68 if (window._yazeAutoSaveHandlers.visibilitychange) {
69 document.removeEventListener(
'visibilitychange', window._yazeAutoSaveHandlers.visibilitychange);
71 if (window._yazeAutoSaveHandlers.pagehide) {
72 window.removeEventListener(
'pagehide', window._yazeAutoSaveHandlers.pagehide);
74 window._yazeAutoSaveHandlers = null;
76 console.log(
'AutoSave event handlers unregistered');
79EM_JS(
void, set_recovery_flag, (
int has_recovery), {
82 sessionStorage.setItem(
'yaze_has_recovery',
'true');
84 sessionStorage.removeItem(
'yaze_has_recovery');
87 console.error(
'Failed to set recovery flag:', e);
91EM_JS(
int, get_recovery_flag, (), {
93 return sessionStorage.getItem(
'yaze_has_recovery') ===
'true' ? 1 : 0;
95 console.error(
'Failed to get recovery flag:', e);
103void yazeEmergencySave() {
104 if (!AutoSaveManager::emergency_save_triggered_) {
105 AutoSaveManager::emergency_save_triggered_ =
true;
106 AutoSaveManager::Instance().EmergencySave();
111int yazeRecoverSession() {
112 auto status = AutoSaveManager::Instance().RecoverLastSession();
114 emscripten_log(EM_LOG_INFO,
"Session recovery successful");
117 emscripten_log(EM_LOG_WARN,
"Session recovery failed: %s",
118 std::string(status.message()).c_str());
124int yazeHasRecoveryData() {
125 return AutoSaveManager::Instance().HasRecoveryData() ? 1 : 0;
129void yazeClearRecoveryData() {
130 AutoSaveManager::Instance().ClearRecoveryData();
136AutoSaveManager::AutoSaveManager()
137 : interval_seconds_(app::platform::WasmConfig::Get().autosave.interval_seconds),
144 event_handlers_initialized_(false) {
147 Module._yazeEmergencySave = Module.cwrap(
'yazeEmergencySave', null, []);
151AutoSaveManager::~AutoSaveManager() {
153 CleanupEventHandlers();
156AutoSaveManager& AutoSaveManager::Instance() {
157 static AutoSaveManager instance;
161void AutoSaveManager::InitializeEventHandlers() {
162 if (!event_handlers_initialized_) {
163 register_beforeunload_handler();
164 event_handlers_initialized_ =
true;
168void AutoSaveManager::CleanupEventHandlers() {
169 if (event_handlers_initialized_) {
170 unregister_beforeunload_handler();
171 event_handlers_initialized_ =
false;
175void AutoSaveManager::TimerCallback(
void* user_data) {
176 AutoSaveManager* manager =
static_cast<AutoSaveManager*
>(user_data);
177 if (manager && manager->IsRunning()) {
178 auto status = manager->PerformSave();
180 emscripten_log(EM_LOG_WARN,
"Auto-save failed: %s",
181 status.ToString().c_str());
185 if (manager->IsRunning()) {
186 manager->timer_id_ = emscripten_set_timeout(
187 TimerCallback, manager->interval_seconds_ * 1000, manager);
192absl::Status AutoSaveManager::Start(
int interval_seconds) {
193 std::lock_guard<std::mutex> lock(mutex_);
196 return absl::AlreadyExistsError(
"Auto-save is already running");
199 interval_seconds_ = interval_seconds;
200 InitializeEventHandlers();
203 timer_id_ = emscripten_set_timeout(
204 TimerCallback, interval_seconds_ * 1000,
this);
207 emscripten_log(EM_LOG_INFO,
"Auto-save started with %d second interval",
210 return absl::OkStatus();
213absl::Status AutoSaveManager::Stop() {
214 std::lock_guard<std::mutex> lock(mutex_);
217 return absl::OkStatus();
221 if (timer_id_ >= 0) {
222 emscripten_clear_timeout(timer_id_);
227 emscripten_log(EM_LOG_INFO,
"Auto-save stopped");
229 return absl::OkStatus();
232bool AutoSaveManager::IsRunning()
const {
233 std::lock_guard<std::mutex> lock(mutex_);
237void AutoSaveManager::RegisterComponent(
const std::string& component_id,
238 SaveCallback save_fn,
239 RestoreCallback restore_fn) {
240 std::lock_guard<std::mutex> lock(mutex_);
241 components_[component_id] = {save_fn, restore_fn};
242 emscripten_log(EM_LOG_INFO,
"Registered component for auto-save: %s",
243 component_id.c_str());
246void AutoSaveManager::UnregisterComponent(
const std::string& component_id) {
247 std::lock_guard<std::mutex> lock(mutex_);
248 components_.erase(component_id);
249 emscripten_log(EM_LOG_INFO,
"Unregistered component from auto-save: %s",
250 component_id.c_str());
253absl::Status AutoSaveManager::SaveNow() {
254 std::lock_guard<std::mutex> lock(mutex_);
255 return PerformSave();
258nlohmann::json AutoSaveManager::CollectComponentData() {
259 nlohmann::json data = nlohmann::json::object();
261 for (
const auto& [
id, component] : components_) {
263 if (component.save_fn) {
264 data[id] = component.save_fn();
266 }
catch (
const std::exception& e) {
267 emscripten_log(EM_LOG_ERROR,
"Failed to save component %s: %s",
268 id.c_str(), e.what());
276absl::Status AutoSaveManager::PerformSave() {
277 nlohmann::json save_data = nlohmann::json::object();
280 save_data[
"components"] = CollectComponentData();
283 auto now = std::chrono::system_clock::now();
284 save_data[
"timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(
285 now.time_since_epoch()).count();
286 save_data[
"version"] = 1;
289 auto status = SaveToStorage(save_data);
291 last_save_time_ = now;
293 set_recovery_flag(1);
301absl::Status AutoSaveManager::SaveToStorage(
const nlohmann::json& data) {
304 std::string json_str = data.dump();
307 auto status = WasmStorage::SaveProject(kAutoSaveDataKey, json_str);
314 meta[
"timestamp"] = data[
"timestamp"];
315 meta[
"component_count"] = data[
"components"].size();
316 meta[
"save_count"] = save_count_;
318 return WasmStorage::SaveProject(kAutoSaveMetaKey, meta.dump());
319 }
catch (
const std::exception& e) {
320 return absl::InternalError(
321 absl::StrFormat(
"Failed to save auto-save data: %s", e.what()));
325absl::StatusOr<nlohmann::json> AutoSaveManager::LoadFromStorage() {
326 auto result = WasmStorage::LoadProject(kAutoSaveDataKey);
328 return result.status();
332 return nlohmann::json::parse(*result);
333 }
catch (
const std::exception& e) {
334 return absl::InvalidArgumentError(
335 absl::StrFormat(
"Failed to parse auto-save data: %s", e.what()));
339void AutoSaveManager::EmergencySave() {
342 std::unique_lock<std::mutex> lock(mutex_, std::try_to_lock);
345 nlohmann::json emergency_data = nlohmann::json::object();
346 emergency_data[
"emergency"] =
true;
349 if (lock.owns_lock()) {
350 emergency_data[
"components"] = CollectComponentData();
353 emergency_data[
"components"] = nlohmann::json::object();
354 emergency_data[
"incomplete"] =
true;
357 emergency_data[
"timestamp"] =
358 std::chrono::duration_cast<std::chrono::milliseconds>(
359 std::chrono::system_clock::now().time_since_epoch()).count();
364 const data = UTF8ToString($0);
365 localStorage.setItem(
'yaze_emergency_save', data);
366 console.log(
'Emergency save completed');
368 console.error(
'Emergency save failed:', e);
370 }, emergency_data.dump().c_str());
372 set_recovery_flag(1);
378bool AutoSaveManager::HasRecoveryData() {
380 if (get_recovery_flag() == 0) {
385 auto meta = WasmStorage::LoadProject(kAutoSaveMetaKey);
391 int has_emergency = EM_ASM_INT({
392 return localStorage.getItem(
'yaze_emergency_save') !== null ? 1 : 0;
395 return has_emergency == 1;
398absl::StatusOr<nlohmann::json> AutoSaveManager::GetRecoveryInfo() {
400 auto meta_result = WasmStorage::LoadProject(kAutoSaveMetaKey);
401 if (meta_result.ok()) {
403 return nlohmann::json::parse(*meta_result);
410 char* emergency_data =
nullptr;
412 const data = localStorage.getItem(
'yaze_emergency_save');
414 const len = lengthBytesUTF8(data) + 1;
415 const ptr = _malloc(len);
416 stringToUTF8(data, ptr, len);
417 setValue($0, ptr,
'i32');
421 if (emergency_data) {
423 nlohmann::json data = nlohmann::json::parse(emergency_data);
424 free(emergency_data);
427 info[
"type"] =
"emergency";
428 info[
"timestamp"] = data[
"timestamp"];
429 info[
"component_count"] = data[
"components"].size();
431 }
catch (
const std::exception& e) {
432 free(emergency_data);
433 return absl::InvalidArgumentError(
"Failed to parse emergency save data");
437 return absl::NotFoundError(
"No recovery data found");
440absl::Status AutoSaveManager::RecoverLastSession() {
441 std::lock_guard<std::mutex> lock(mutex_);
444 auto data_result = LoadFromStorage();
445 if (data_result.ok()) {
446 const auto& data = *data_result;
447 if (data.contains(
"components") && data[
"components"].is_object()) {
448 for (
const auto& [
id, component_data] : data[
"components"].items()) {
449 auto it = components_.find(
id);
450 if (it != components_.end() && it->second.restore_fn) {
452 it->second.restore_fn(component_data);
453 emscripten_log(EM_LOG_INFO,
"Restored component: %s",
id.c_str());
454 }
catch (
const std::exception& e) {
455 emscripten_log(EM_LOG_ERROR,
"Failed to restore component %s: %s",
456 id.c_str(), e.what());
461 set_recovery_flag(0);
462 return absl::OkStatus();
467 char* emergency_data =
nullptr;
469 const data = localStorage.getItem(
'yaze_emergency_save');
471 const len = lengthBytesUTF8(data) + 1;
472 const ptr = _malloc(len);
473 stringToUTF8(data, ptr, len);
474 setValue($0, ptr,
'i32');
478 if (emergency_data) {
480 nlohmann::json data = nlohmann::json::parse(emergency_data);
481 free(emergency_data);
483 if (data.contains(
"components") && data[
"components"].is_object()) {
484 for (
const auto& [
id, component_data] : data[
"components"].items()) {
485 auto it = components_.find(
id);
486 if (it != components_.end() && it->second.restore_fn) {
488 it->second.restore_fn(component_data);
489 emscripten_log(EM_LOG_INFO,
"Restored component from emergency save: %s",
491 }
catch (
const std::exception& e) {
492 emscripten_log(EM_LOG_ERROR,
"Failed to restore component %s: %s",
493 id.c_str(), e.what());
500 localStorage.removeItem(
'yaze_emergency_save');
504 set_recovery_flag(0);
505 return absl::OkStatus();
507 }
catch (
const std::exception& e) {
508 return absl::InvalidArgumentError(
509 absl::StrFormat(
"Failed to recover session: %s", e.what()));
513 return absl::NotFoundError(
"No recovery data available");
516absl::Status AutoSaveManager::ClearRecoveryData() {
518 WasmStorage::DeleteProject(kAutoSaveDataKey);
519 WasmStorage::DeleteProject(kAutoSaveMetaKey);
523 localStorage.removeItem(
'yaze_emergency_save');
527 set_recovery_flag(0);
529 emscripten_log(EM_LOG_INFO,
"Recovery data cleared");
530 return absl::OkStatus();
533void AutoSaveManager::SetInterval(
int seconds) {
534 bool was_running = IsRunning();
539 interval_seconds_ = seconds;
541 if (was_running && enabled_) {
546void AutoSaveManager::SetEnabled(
bool enabled) {
548 if (!enabled && IsRunning()) {
550 }
else if (enabled && !IsRunning()) {
551 Start(interval_seconds_);
555nlohmann::json AutoSaveManager::GetStatistics()
const {
556 std::lock_guard<std::mutex> lock(mutex_);
558 nlohmann::json stats;
559 stats[
"save_count"] = save_count_;
560 stats[
"error_count"] = error_count_;
561 stats[
"recovery_count"] = recovery_count_;
562 stats[
"components_registered"] = components_.size();
563 stats[
"is_running"] = running_;
564 stats[
"interval_seconds"] = interval_seconds_;
565 stats[
"enabled"] = enabled_;
567 if (last_save_time_.time_since_epoch().count() > 0) {
568 stats[
"last_save_timestamp"] =
569 std::chrono::duration_cast<std::chrono::milliseconds>(
570 last_save_time_.time_since_epoch()).count();
EM_JS(void, CallJsAiDriver,(const char *history_json), { if(window.yaze &&window.yaze.ai &&window.yaze.ai.processAgentRequest) { window.yaze.ai.processAgentRequest(UTF8ToString(history_json));} else { console.error("AI Driver not found in window.yaze.ai.processAgentRequest");} })