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();
38 'You have unsaved changes. Are you sure you want to leave?';
39 return event.returnValue;
41 window.addEventListener(
'beforeunload',
42 window._yazeAutoSaveHandlers.beforeunload);
45 window._yazeAutoSaveHandlers.visibilitychange = function() {
46 if (document.hidden && Module._yazeEmergencySave) {
48 Module._yazeEmergencySave();
51 document.addEventListener(
'visibilitychange',
52 window._yazeAutoSaveHandlers.visibilitychange);
55 window._yazeAutoSaveHandlers.pagehide = function(event) {
56 if (Module._yazeEmergencySave) {
57 Module._yazeEmergencySave();
60 window.addEventListener(
'pagehide', window._yazeAutoSaveHandlers.pagehide);
62 console.log(
'AutoSave event handlers registered');
65EM_JS(
void, unregister_beforeunload_handler, (), {
67 if (window._yazeAutoSaveHandlers) {
68 if (window._yazeAutoSaveHandlers.beforeunload) {
69 window.removeEventListener(
'beforeunload',
70 window._yazeAutoSaveHandlers.beforeunload);
72 if (window._yazeAutoSaveHandlers.visibilitychange) {
73 document.removeEventListener(
74 'visibilitychange', window._yazeAutoSaveHandlers.visibilitychange);
76 if (window._yazeAutoSaveHandlers.pagehide) {
77 window.removeEventListener(
'pagehide',
78 window._yazeAutoSaveHandlers.pagehide);
80 window._yazeAutoSaveHandlers = null;
82 console.log(
'AutoSave event handlers unregistered');
85EM_JS(
void, set_recovery_flag, (
int has_recovery), {
88 sessionStorage.setItem(
'yaze_has_recovery',
'true');
90 sessionStorage.removeItem(
'yaze_has_recovery');
93 console.error(
'Failed to set recovery flag:', e);
97EM_JS(
int, get_recovery_flag, (), {
99 return sessionStorage.getItem(
'yaze_has_recovery') ==
'true' ? 1 : 0;
101 console.error(
'Failed to get recovery flag:', e);
109void yazeEmergencySave() {
110 if (!AutoSaveManager::emergency_save_triggered_) {
111 AutoSaveManager::emergency_save_triggered_ =
true;
112 AutoSaveManager::Instance().EmergencySave();
117int yazeRecoverSession() {
118 auto status = AutoSaveManager::Instance().RecoverLastSession();
120 emscripten_log(EM_LOG_INFO,
"Session recovery successful");
123 emscripten_log(EM_LOG_WARN,
"Session recovery failed: %s",
124 std::string(status.message()).c_str());
130int yazeHasRecoveryData() {
131 return AutoSaveManager::Instance().HasRecoveryData() ? 1 : 0;
135void yazeClearRecoveryData() {
136 AutoSaveManager::Instance().ClearRecoveryData();
142AutoSaveManager::AutoSaveManager()
144 app::platform::WasmConfig::Get().autosave.interval_seconds),
151 event_handlers_initialized_(false) {
154 Module._yazeEmergencySave = Module.cwrap(
'yazeEmergencySave', null, []);
158AutoSaveManager::~AutoSaveManager() {
160 CleanupEventHandlers();
163AutoSaveManager& AutoSaveManager::Instance() {
164 static AutoSaveManager instance;
168void AutoSaveManager::InitializeEventHandlers() {
169 if (!event_handlers_initialized_) {
170 register_beforeunload_handler();
171 event_handlers_initialized_ =
true;
175void AutoSaveManager::CleanupEventHandlers() {
176 if (event_handlers_initialized_) {
177 unregister_beforeunload_handler();
178 event_handlers_initialized_ =
false;
182void AutoSaveManager::TimerCallback(
void* user_data) {
183 AutoSaveManager* manager =
static_cast<AutoSaveManager*
>(user_data);
184 if (manager && manager->IsRunning()) {
185 auto status = manager->PerformSave();
187 emscripten_log(EM_LOG_WARN,
"Auto-save failed: %s",
188 status.ToString().c_str());
192 if (manager->IsRunning()) {
193 manager->timer_id_ = emscripten_set_timeout(
194 TimerCallback, manager->interval_seconds_ * 1000, manager);
199absl::Status AutoSaveManager::Start(
int interval_seconds) {
200 std::lock_guard<std::mutex> lock(mutex_);
203 return absl::AlreadyExistsError(
"Auto-save is already running");
206 interval_seconds_ = interval_seconds;
207 InitializeEventHandlers();
211 emscripten_set_timeout(TimerCallback, interval_seconds_ * 1000,
this);
214 emscripten_log(EM_LOG_INFO,
"Auto-save started with %d second interval",
217 return absl::OkStatus();
220absl::Status AutoSaveManager::Stop() {
221 std::lock_guard<std::mutex> lock(mutex_);
224 return absl::OkStatus();
228 if (timer_id_ >= 0) {
229 emscripten_clear_timeout(timer_id_);
234 emscripten_log(EM_LOG_INFO,
"Auto-save stopped");
236 return absl::OkStatus();
239bool AutoSaveManager::IsRunning()
const {
240 std::lock_guard<std::mutex> lock(mutex_);
244void AutoSaveManager::RegisterComponent(
const std::string& component_id,
245 SaveCallback save_fn,
246 RestoreCallback restore_fn) {
247 std::lock_guard<std::mutex> lock(mutex_);
248 components_[component_id] = {save_fn, restore_fn};
249 emscripten_log(EM_LOG_INFO,
"Registered component for auto-save: %s",
250 component_id.c_str());
253void AutoSaveManager::UnregisterComponent(
const std::string& component_id) {
254 std::lock_guard<std::mutex> lock(mutex_);
255 components_.erase(component_id);
256 emscripten_log(EM_LOG_INFO,
"Unregistered component from auto-save: %s",
257 component_id.c_str());
260absl::Status AutoSaveManager::SaveNow() {
261 std::lock_guard<std::mutex> lock(mutex_);
262 return PerformSave();
265nlohmann::json AutoSaveManager::CollectComponentData() {
266 nlohmann::json data = nlohmann::json::object();
268 for (
const auto& [
id, component] : components_) {
270 if (component.save_fn) {
271 data[id] = component.save_fn();
273 }
catch (
const std::exception& e) {
274 emscripten_log(EM_LOG_ERROR,
"Failed to save component %s: %s",
275 id.c_str(), e.what());
283absl::Status AutoSaveManager::PerformSave() {
284 nlohmann::json save_data = nlohmann::json::object();
287 save_data[
"components"] = CollectComponentData();
290 auto now = std::chrono::system_clock::now();
291 save_data[
"timestamp"] =
292 std::chrono::duration_cast<std::chrono::milliseconds>(
293 now.time_since_epoch())
295 save_data[
"version"] = 1;
298 auto status = SaveToStorage(save_data);
300 last_save_time_ = now;
302 set_recovery_flag(1);
310absl::Status AutoSaveManager::SaveToStorage(
const nlohmann::json& data) {
313 std::string json_str = data.dump();
316 auto status = WasmStorage::SaveProject(kAutoSaveDataKey, json_str);
323 meta[
"timestamp"] = data[
"timestamp"];
324 meta[
"component_count"] = data[
"components"].size();
325 meta[
"save_count"] = save_count_;
327 return WasmStorage::SaveProject(kAutoSaveMetaKey, meta.dump());
328 }
catch (
const std::exception& e) {
329 return absl::InternalError(
330 absl::StrFormat(
"Failed to save auto-save data: %s", e.what()));
334absl::StatusOr<nlohmann::json> AutoSaveManager::LoadFromStorage() {
335 auto result = WasmStorage::LoadProject(kAutoSaveDataKey);
337 return result.status();
341 return nlohmann::json::parse(*result);
342 }
catch (
const std::exception& e) {
343 return absl::InvalidArgumentError(
344 absl::StrFormat(
"Failed to parse auto-save data: %s", e.what()));
348void AutoSaveManager::EmergencySave() {
351 std::unique_lock<std::mutex> lock(mutex_, std::try_to_lock);
354 nlohmann::json emergency_data = nlohmann::json::object();
355 emergency_data[
"emergency"] =
true;
358 if (lock.owns_lock()) {
359 emergency_data[
"components"] = CollectComponentData();
362 emergency_data[
"components"] = nlohmann::json::object();
363 emergency_data[
"incomplete"] =
true;
366 emergency_data[
"timestamp"] =
367 std::chrono::duration_cast<std::chrono::milliseconds>(
368 std::chrono::system_clock::now().time_since_epoch())
375 const data = UTF8ToString($0);
376 localStorage.setItem(
'yaze_emergency_save', data);
377 console.log(
'Emergency save completed');
379 console.error(
'Emergency save failed:', e);
382 emergency_data.dump().c_str());
384 set_recovery_flag(1);
390bool AutoSaveManager::HasRecoveryData() {
392 if (get_recovery_flag() == 0) {
397 auto meta = WasmStorage::LoadProject(kAutoSaveMetaKey);
403 int has_emergency = EM_ASM_INT(
404 {
return localStorage.getItem(
'yaze_emergency_save') != null ? 1 : 0; });
406 return has_emergency == 1;
409absl::StatusOr<nlohmann::json> AutoSaveManager::GetRecoveryInfo() {
411 auto meta_result = WasmStorage::LoadProject(kAutoSaveMetaKey);
412 if (meta_result.ok()) {
414 return nlohmann::json::parse(*meta_result);
421 char* emergency_data =
nullptr;
424 const data = localStorage.getItem(
'yaze_emergency_save');
426 const len = lengthBytesUTF8(data) + 1;
427 const ptr = _malloc(len);
428 stringToUTF8(data, ptr, len);
429 setValue($0, ptr,
'i32');
434 if (emergency_data) {
436 nlohmann::json data = nlohmann::json::parse(emergency_data);
437 free(emergency_data);
440 info[
"type"] =
"emergency";
441 info[
"timestamp"] = data[
"timestamp"];
442 info[
"component_count"] = data[
"components"].size();
444 }
catch (
const std::exception& e) {
445 free(emergency_data);
446 return absl::InvalidArgumentError(
"Failed to parse emergency save data");
450 return absl::NotFoundError(
"No recovery data found");
453absl::Status AutoSaveManager::RecoverLastSession() {
454 std::lock_guard<std::mutex> lock(mutex_);
457 auto data_result = LoadFromStorage();
458 if (data_result.ok()) {
459 const auto& data = *data_result;
460 if (data.contains(
"components") && data[
"components"].is_object()) {
461 for (
const auto& [
id, component_data] : data[
"components"].items()) {
462 auto it = components_.find(
id);
463 if (it != components_.end() && it->second.restore_fn) {
465 it->second.restore_fn(component_data);
466 emscripten_log(EM_LOG_INFO,
"Restored component: %s",
id.c_str());
467 }
catch (
const std::exception& e) {
468 emscripten_log(EM_LOG_ERROR,
"Failed to restore component %s: %s",
469 id.c_str(), e.what());
474 set_recovery_flag(0);
475 return absl::OkStatus();
480 char* emergency_data =
nullptr;
483 const data = localStorage.getItem(
'yaze_emergency_save');
485 const len = lengthBytesUTF8(data) + 1;
486 const ptr = _malloc(len);
487 stringToUTF8(data, ptr, len);
488 setValue($0, ptr,
'i32');
493 if (emergency_data) {
495 nlohmann::json data = nlohmann::json::parse(emergency_data);
496 free(emergency_data);
498 if (data.contains(
"components") && data[
"components"].is_object()) {
499 for (
const auto& [
id, component_data] : data[
"components"].items()) {
500 auto it = components_.find(
id);
501 if (it != components_.end() && it->second.restore_fn) {
503 it->second.restore_fn(component_data);
504 emscripten_log(EM_LOG_INFO,
505 "Restored component from emergency save: %s",
507 }
catch (
const std::exception& e) {
508 emscripten_log(EM_LOG_ERROR,
"Failed to restore component %s: %s",
509 id.c_str(), e.what());
515 EM_ASM({ localStorage.removeItem(
'yaze_emergency_save'); });
518 set_recovery_flag(0);
519 return absl::OkStatus();
521 }
catch (
const std::exception& e) {
522 return absl::InvalidArgumentError(
523 absl::StrFormat(
"Failed to recover session: %s", e.what()));
527 return absl::NotFoundError(
"No recovery data available");
530absl::Status AutoSaveManager::ClearRecoveryData() {
532 WasmStorage::DeleteProject(kAutoSaveDataKey);
533 WasmStorage::DeleteProject(kAutoSaveMetaKey);
536 EM_ASM({ localStorage.removeItem(
'yaze_emergency_save'); });
539 set_recovery_flag(0);
541 emscripten_log(EM_LOG_INFO,
"Recovery data cleared");
542 return absl::OkStatus();
545void AutoSaveManager::SetInterval(
int seconds) {
546 bool was_running = IsRunning();
551 interval_seconds_ = seconds;
553 if (was_running && enabled_) {
558void AutoSaveManager::SetEnabled(
bool enabled) {
560 if (!enabled && IsRunning()) {
562 }
else if (enabled && !IsRunning()) {
563 Start(interval_seconds_);
567nlohmann::json AutoSaveManager::GetStatistics()
const {
568 std::lock_guard<std::mutex> lock(mutex_);
570 nlohmann::json stats;
571 stats[
"save_count"] = save_count_;
572 stats[
"error_count"] = error_count_;
573 stats[
"recovery_count"] = recovery_count_;
574 stats[
"components_registered"] = components_.size();
575 stats[
"is_running"] = running_;
576 stats[
"interval_seconds"] = interval_seconds_;
577 stats[
"enabled"] = enabled_;
579 if (last_save_time_.time_since_epoch().count() > 0) {
580 stats[
"last_save_timestamp"] =
581 std::chrono::duration_cast<std::chrono::milliseconds>(
582 last_save_time_.time_since_epoch())
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");} })