7#include <emscripten/val.h>
8#include <condition_variable>
12#include "absl/strings/str_format.h"
18std::atomic<bool> WasmStorage::initialized_{
false};
22EM_JS(
int, idb_open_database, (
const char* db_name,
int version), {
23 return Asyncify.handleAsync(function() {
24 var dbName = UTF8ToString(db_name);
25 var operation = function() {
26 return new Promise(function(resolve, reject) {
27 var request = indexedDB.open(dbName, version);
28 request.onerror = function() {
29 console.error(
'Failed to open IndexedDB:', request.error);
32 request.onsuccess = function() {
33 var db = request.result;
37 request.onupgradeneeded = function(event) {
38 var db =
event.target.result;
39 if (!db.objectStoreNames.contains(
'roms')) {
40 db.createObjectStore(
'roms');
42 if (!db.objectStoreNames.contains(
'projects')) {
43 db.createObjectStore(
'projects');
45 if (!db.objectStoreNames.contains(
'preferences')) {
46 db.createObjectStore(
'preferences');
52 if (window.yazeAsyncQueue) {
53 return window.yazeAsyncQueue.enqueue(operation);
59EM_JS(
int, idb_save_binary, (
const char* store_name,
const char* key,
const uint8_t* data,
size_t size), {
60 return Asyncify.handleAsync(function() {
61 var storeName = UTF8ToString(store_name);
62 var keyStr = UTF8ToString(key);
63 var dataArray =
new Uint8Array(HEAPU8.subarray(data, data + size));
64 var operation = function() {
65 return new Promise(function(resolve, reject) {
66 if (!Module._yazeDB) {
67 console.error(
'Database not initialized');
71 var transaction = Module._yazeDB.transaction([storeName],
'readwrite');
72 var store = transaction.objectStore(storeName);
73 var request = store.put(dataArray, keyStr);
74 request.onsuccess = function() { resolve(0); };
75 request.onerror = function() {
76 console.error(
'Failed to save data:', request.error);
81 if (window.yazeAsyncQueue) {
82 return window.yazeAsyncQueue.enqueue(operation);
88EM_JS(
int, idb_load_binary, (
const char* store_name,
const char* key, uint8_t** out_data,
size_t* out_size), {
89 return Asyncify.handleAsync(function() {
90 if (!Module._yazeDB) {
91 console.error(
'Database not initialized');
94 var storeName = UTF8ToString(store_name);
95 var keyStr = UTF8ToString(key);
96 var operation = function() {
97 return new Promise(function(resolve) {
98 var transaction = Module._yazeDB.transaction([storeName],
'readonly');
99 var store = transaction.objectStore(storeName);
100 var request = store.get(keyStr);
101 request.onsuccess = function() {
102 var result = request.result;
103 if (result && result instanceof Uint8Array) {
104 var size = result.length;
105 var ptr = Module._malloc(size);
106 Module.HEAPU8.set(result, ptr);
107 Module.HEAPU32[out_data >> 2] = ptr;
108 Module.HEAPU32[out_size >> 2] = size;
114 request.onerror = function() {
115 console.error(
'Failed to load data:', request.error);
120 if (window.yazeAsyncQueue) {
121 return window.yazeAsyncQueue.enqueue(operation);
127EM_JS(
int, idb_save_string, (
const char* store_name,
const char* key,
const char* value), {
128 return Asyncify.handleAsync(function() {
129 var storeName = UTF8ToString(store_name);
130 var keyStr = UTF8ToString(key);
131 var valueStr = UTF8ToString(value);
132 var operation = function() {
133 return new Promise(function(resolve, reject) {
134 if (!Module._yazeDB) {
135 console.error(
'Database not initialized');
139 var transaction = Module._yazeDB.transaction([storeName],
'readwrite');
140 var store = transaction.objectStore(storeName);
141 var request = store.put(valueStr, keyStr);
142 request.onsuccess = function() { resolve(0); };
143 request.onerror = function() {
144 console.error(
'Failed to save string:', request.error);
149 if (window.yazeAsyncQueue) {
150 return window.yazeAsyncQueue.enqueue(operation);
156EM_JS(
char*, idb_load_string, (
const char* store_name,
const char* key), {
157 return Asyncify.handleAsync(function() {
158 if (!Module._yazeDB) {
159 console.error(
'Database not initialized');
162 var storeName = UTF8ToString(store_name);
163 var keyStr = UTF8ToString(key);
164 var operation = function() {
165 return new Promise(function(resolve) {
166 var transaction = Module._yazeDB.transaction([storeName],
'readonly');
167 var store = transaction.objectStore(storeName);
168 var request = store.get(keyStr);
169 request.onsuccess = function() {
170 var result = request.result;
171 if (result && typeof result ===
'string') {
172 var len = lengthBytesUTF8(result) + 1;
173 var ptr = Module._malloc(len);
174 stringToUTF8(result, ptr, len);
180 request.onerror = function() {
181 console.error(
'Failed to load string:', request.error);
186 if (window.yazeAsyncQueue) {
187 return window.yazeAsyncQueue.enqueue(operation);
193EM_JS(
int, idb_delete_entry, (
const char* store_name,
const char* key), {
194 return Asyncify.handleAsync(function() {
195 var storeName = UTF8ToString(store_name);
196 var keyStr = UTF8ToString(key);
197 var operation = function() {
198 return new Promise(function(resolve, reject) {
199 if (!Module._yazeDB) {
200 console.error(
'Database not initialized');
204 var transaction = Module._yazeDB.transaction([storeName],
'readwrite');
205 var store = transaction.objectStore(storeName);
206 var request = store.delete(keyStr);
207 request.onsuccess = function() { resolve(0); };
208 request.onerror = function() {
209 console.error(
'Failed to delete entry:', request.error);
214 if (window.yazeAsyncQueue) {
215 return window.yazeAsyncQueue.enqueue(operation);
221EM_JS(
char*, idb_list_keys, (
const char* store_name), {
222 return Asyncify.handleAsync(function() {
223 if (!Module._yazeDB) {
224 console.error(
'Database not initialized');
227 var storeName = UTF8ToString(store_name);
228 var operation = function() {
229 return new Promise(function(resolve) {
230 var transaction = Module._yazeDB.transaction([storeName],
'readonly');
231 var store = transaction.objectStore(storeName);
232 var request = store.getAllKeys();
233 request.onsuccess = function() {
234 var keys = request.result;
235 var jsonStr = JSON.stringify(keys);
236 var len = lengthBytesUTF8(jsonStr) + 1;
237 var ptr = Module._malloc(len);
238 stringToUTF8(jsonStr, ptr, len);
241 request.onerror = function() {
242 console.error(
'Failed to list keys:', request.error);
247 if (window.yazeAsyncQueue) {
248 return window.yazeAsyncQueue.enqueue(operation);
254EM_JS(
size_t, idb_get_storage_usage, (), {
255 return Asyncify.handleAsync(function() {
256 if (!Module._yazeDB) {
257 console.error(
'Database not initialized');
260 var operation = function() {
261 return new Promise(function(resolve) {
263 var storeNames = [
'roms',
'projects',
'preferences'];
266 storeNames.forEach(function(storeName) {
267 var transaction = Module._yazeDB.transaction([storeName],
'readonly');
268 var store = transaction.objectStore(storeName);
269 var request = store.openCursor();
271 request.onsuccess = function(event) {
272 var cursor =
event.target.result;
274 var value = cursor.value;
275 if (value instanceof Uint8Array) {
276 totalSize += value.length;
277 }
else if (typeof value ===
'string') {
278 totalSize += value.length * 2;
280 totalSize += JSON.stringify(value).length * 2;
285 if (completed === storeNames.length) {
291 request.onerror = function() {
293 if (completed === storeNames.length) {
300 if (window.yazeAsyncQueue) {
301 return window.yazeAsyncQueue.enqueue(operation);
308absl::Status WasmStorage::Initialize() {
310 bool expected =
false;
311 if (!initialized_.compare_exchange_strong(expected,
true)) {
312 return absl::OkStatus();
315 int result = idb_open_database(kDatabaseName, kDatabaseVersion);
317 initialized_.store(
false);
318 return absl::InternalError(
"Failed to initialize IndexedDB");
320 return absl::OkStatus();
323void WasmStorage::EnsureInitialized() {
324 if (!initialized_.load()) {
325 auto status = Initialize();
327 emscripten_log(EM_LOG_ERROR,
"Failed to initialize WasmStorage: %s", status.ToString().c_str());
332bool WasmStorage::IsStorageAvailable() {
334 return initialized_.load();
337bool WasmStorage::IsWebContext() {
339 return (typeof window !==
'undefined' && typeof indexedDB !==
'undefined') ? 1 : 0;
344absl::Status WasmStorage::SaveRom(
const std::string& name,
const std::vector<uint8_t>& data) {
346 if (!initialized_.load()) {
347 return absl::FailedPreconditionError(
"Storage not initialized");
349 int result = idb_save_binary(kRomStoreName,
name.c_str(), data.data(), data.size());
351 return absl::InternalError(absl::StrFormat(
"Failed to save ROM '%s'", name));
353 return absl::OkStatus();
356absl::StatusOr<std::vector<uint8_t>> WasmStorage::LoadRom(
const std::string& name) {
358 if (!initialized_.load()) {
359 return absl::FailedPreconditionError(
"Storage not initialized");
361 uint8_t* data_ptr =
nullptr;
362 size_t data_size = 0;
363 int result = idb_load_binary(kRomStoreName,
name.c_str(), &data_ptr, &data_size);
365 if (data_ptr) free(data_ptr);
366 return absl::NotFoundError(absl::StrFormat(
"ROM '%s' not found", name));
367 }
else if (result != 0) {
368 if (data_ptr) free(data_ptr);
369 return absl::InternalError(absl::StrFormat(
"Failed to load ROM '%s'", name));
371 std::vector<uint8_t> data(data_ptr, data_ptr + data_size);
376absl::Status WasmStorage::DeleteRom(
const std::string& name) {
378 if (!initialized_.load()) {
379 return absl::FailedPreconditionError(
"Storage not initialized");
381 int result = idb_delete_entry(kRomStoreName,
name.c_str());
383 return absl::InternalError(absl::StrFormat(
"Failed to delete ROM '%s'", name));
385 return absl::OkStatus();
388std::vector<std::string> WasmStorage::ListRoms() {
390 if (!initialized_.load()) {
393 char* keys_json = idb_list_keys(kRomStoreName);
397 std::vector<std::string> result;
399 nlohmann::json keys = nlohmann::json::parse(keys_json);
400 for (
const auto& key : keys) {
401 if (
key.is_string()) {
402 result.push_back(
key.get<std::string>());
405 }
catch (
const std::exception& e) {
406 emscripten_log(EM_LOG_ERROR,
"Failed to parse ROM list: %s", e.what());
413absl::Status WasmStorage::SaveProject(
const std::string& name,
const std::string& json) {
415 if (!initialized_.load()) {
416 return absl::FailedPreconditionError(
"Storage not initialized");
418 int result = idb_save_string(kProjectStoreName,
name.c_str(),
json.c_str());
420 return absl::InternalError(absl::StrFormat(
"Failed to save project '%s'", name));
422 return absl::OkStatus();
425absl::StatusOr<std::string> WasmStorage::LoadProject(
const std::string& name) {
427 if (!initialized_.load()) {
428 return absl::FailedPreconditionError(
"Storage not initialized");
430 char* json_ptr = idb_load_string(kProjectStoreName,
name.c_str());
434 return absl::NotFoundError(absl::StrFormat(
"Project '%s' not found", name));
436 std::string
json(json_ptr);
441absl::Status WasmStorage::DeleteProject(
const std::string& name) {
443 if (!initialized_.load()) {
444 return absl::FailedPreconditionError(
"Storage not initialized");
446 int result = idb_delete_entry(kProjectStoreName,
name.c_str());
448 return absl::InternalError(absl::StrFormat(
"Failed to delete project '%s'", name));
450 return absl::OkStatus();
453std::vector<std::string> WasmStorage::ListProjects() {
455 if (!initialized_.load()) {
458 char* keys_json = idb_list_keys(kProjectStoreName);
462 std::vector<std::string> result;
464 nlohmann::json keys = nlohmann::json::parse(keys_json);
465 for (
const auto& key : keys) {
466 if (
key.is_string()) {
467 result.push_back(
key.get<std::string>());
470 }
catch (
const std::exception& e) {
471 emscripten_log(EM_LOG_ERROR,
"Failed to parse project list: %s", e.what());
478absl::Status WasmStorage::SavePreferences(
const nlohmann::json& prefs) {
480 if (!initialized_.load()) {
481 return absl::FailedPreconditionError(
"Storage not initialized");
483 std::string json_str = prefs.dump();
484 int result = idb_save_string(kPreferencesStoreName, kPreferencesKey, json_str.c_str());
486 return absl::InternalError(
"Failed to save preferences");
488 return absl::OkStatus();
491absl::StatusOr<nlohmann::json> WasmStorage::LoadPreferences() {
493 if (!initialized_.load()) {
494 return absl::FailedPreconditionError(
"Storage not initialized");
496 char* json_ptr = idb_load_string(kPreferencesStoreName, kPreferencesKey);
498 return nlohmann::json::object();
501 nlohmann::json prefs = nlohmann::json::parse(json_ptr);
504 }
catch (
const std::exception& e) {
506 return absl::InvalidArgumentError(absl::StrFormat(
"Failed to parse preferences: %s", e.what()));
510absl::Status WasmStorage::ClearPreferences() {
512 if (!initialized_.load()) {
513 return absl::FailedPreconditionError(
"Storage not initialized");
515 int result = idb_delete_entry(kPreferencesStoreName, kPreferencesKey);
517 return absl::InternalError(
"Failed to clear preferences");
519 return absl::OkStatus();
523absl::StatusOr<size_t> WasmStorage::GetStorageUsage() {
525 if (!initialized_.load()) {
526 return absl::FailedPreconditionError(
"Storage not initialized");
528 return idb_get_storage_usage();
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");} })