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 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
24 return asyncify.handleAsync(function() {
25 var dbName = UTF8ToString(db_name);
26 var operation = function() {
27 return new Promise(function(resolve, reject) {
28 var request = indexedDB.open(dbName, version);
29 request.onerror = function() {
30 console.error(
'Failed to open IndexedDB:', request.error);
33 request.onsuccess = function() {
34 var db = request.result;
38 request.onupgradeneeded = function(event) {
39 var db =
event.target.result;
40 if (!db.objectStoreNames.contains(
'roms')) {
41 db.createObjectStore(
'roms');
43 if (!db.objectStoreNames.contains(
'projects')) {
44 db.createObjectStore(
'projects');
46 if (!db.objectStoreNames.contains(
'preferences')) {
47 db.createObjectStore(
'preferences');
53 if (window.yazeAsyncQueue) {
54 return window.yazeAsyncQueue.enqueue(operation);
60EM_JS(
int, idb_save_binary, (
const char* store_name,
const char* key,
const uint8_t* data,
size_t size), {
61 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
62 return asyncify.handleAsync(function() {
63 var storeName = UTF8ToString(store_name);
64 var keyStr = UTF8ToString(key);
65 var dataArray =
new Uint8Array(HEAPU8.subarray(data, data + size));
66 var operation = function() {
67 return new Promise(function(resolve, reject) {
68 if (!Module._yazeDB) {
69 console.error(
'Database not initialized');
73 var transaction = Module._yazeDB.transaction([storeName],
'readwrite');
74 var store = transaction.objectStore(storeName);
75 var request = store.put(dataArray, keyStr);
76 request.onsuccess = function() { resolve(0); };
77 request.onerror = function() {
78 console.error(
'Failed to save data:', request.error);
83 if (window.yazeAsyncQueue) {
84 return window.yazeAsyncQueue.enqueue(operation);
90EM_JS(
int, idb_load_binary, (
const char* store_name,
const char* key, uint8_t** out_data,
size_t* out_size), {
91 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
92 return asyncify.handleAsync(function() {
93 if (!Module._yazeDB) {
94 console.error(
'Database not initialized');
97 var storeName = UTF8ToString(store_name);
98 var keyStr = UTF8ToString(key);
99 var operation = function() {
100 return new Promise(function(resolve) {
101 var transaction = Module._yazeDB.transaction([storeName],
'readonly');
102 var store = transaction.objectStore(storeName);
103 var request = store.get(keyStr);
104 request.onsuccess = function() {
105 var result = request.result;
107 if (result instanceof Uint8Array) {
109 }
else if (result instanceof ArrayBuffer) {
110 bytes =
new Uint8Array(result);
111 }
else if (result && ArrayBuffer.isView(result)) {
112 bytes =
new Uint8Array(result.buffer, result.byteOffset, result.byteLength);
115 var size = bytes.length;
116 var ptr = Module._malloc(size);
117 Module.HEAPU8.set(bytes, ptr);
118 Module.HEAPU32[out_data >> 2] = ptr;
119 Module.HEAPU32[out_size >> 2] = size;
125 request.onerror = function() {
126 console.error(
'Failed to load data:', request.error);
131 if (window.yazeAsyncQueue) {
132 return window.yazeAsyncQueue.enqueue(operation);
138EM_JS(
int, idb_save_string, (
const char* store_name,
const char* key,
const char* value), {
139 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
140 return asyncify.handleAsync(function() {
141 var storeName = UTF8ToString(store_name);
142 var keyStr = UTF8ToString(key);
143 var valueStr = UTF8ToString(value);
144 var operation = function() {
145 return new Promise(function(resolve, reject) {
146 if (!Module._yazeDB) {
147 console.error(
'Database not initialized');
151 var transaction = Module._yazeDB.transaction([storeName],
'readwrite');
152 var store = transaction.objectStore(storeName);
153 var request = store.put(valueStr, keyStr);
154 request.onsuccess = function() { resolve(0); };
155 request.onerror = function() {
156 console.error(
'Failed to save string:', request.error);
161 if (window.yazeAsyncQueue) {
162 return window.yazeAsyncQueue.enqueue(operation);
168EM_JS(
char*, idb_load_string, (
const char* store_name,
const char* key), {
169 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
170 return asyncify.handleAsync(function() {
171 if (!Module._yazeDB) {
172 console.error(
'Database not initialized');
175 var storeName = UTF8ToString(store_name);
176 var keyStr = UTF8ToString(key);
177 var operation = function() {
178 return new Promise(function(resolve) {
179 var transaction = Module._yazeDB.transaction([storeName],
'readonly');
180 var store = transaction.objectStore(storeName);
181 var request = store.get(keyStr);
182 request.onsuccess = function() {
183 var result = request.result;
184 if (result && typeof result ===
'string') {
185 var len = lengthBytesUTF8(result) + 1;
186 var ptr = Module._malloc(len);
187 stringToUTF8(result, ptr, len);
193 request.onerror = function() {
194 console.error(
'Failed to load string:', request.error);
199 if (window.yazeAsyncQueue) {
200 return window.yazeAsyncQueue.enqueue(operation);
206EM_JS(
int, idb_delete_entry, (
const char* store_name,
const char* key), {
207 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
208 return asyncify.handleAsync(function() {
209 var storeName = UTF8ToString(store_name);
210 var keyStr = UTF8ToString(key);
211 var operation = function() {
212 return new Promise(function(resolve, reject) {
213 if (!Module._yazeDB) {
214 console.error(
'Database not initialized');
218 var transaction = Module._yazeDB.transaction([storeName],
'readwrite');
219 var store = transaction.objectStore(storeName);
220 var request = store.delete(keyStr);
221 request.onsuccess = function() { resolve(0); };
222 request.onerror = function() {
223 console.error(
'Failed to delete entry:', request.error);
228 if (window.yazeAsyncQueue) {
229 return window.yazeAsyncQueue.enqueue(operation);
235EM_JS(
char*, idb_list_keys, (
const char* store_name), {
236 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
237 return asyncify.handleAsync(function() {
238 if (!Module._yazeDB) {
239 console.error(
'Database not initialized');
242 var storeName = UTF8ToString(store_name);
243 var operation = function() {
244 return new Promise(function(resolve) {
245 var transaction = Module._yazeDB.transaction([storeName],
'readonly');
246 var store = transaction.objectStore(storeName);
247 var request = store.getAllKeys();
248 request.onsuccess = function() {
249 var keys = request.result;
250 var jsonStr = JSON.stringify(keys);
251 var len = lengthBytesUTF8(jsonStr) + 1;
252 var ptr = Module._malloc(len);
253 stringToUTF8(jsonStr, ptr, len);
256 request.onerror = function() {
257 console.error(
'Failed to list keys:', request.error);
262 if (window.yazeAsyncQueue) {
263 return window.yazeAsyncQueue.enqueue(operation);
269EM_JS(
size_t, idb_get_storage_usage, (), {
270 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
271 return asyncify.handleAsync(function() {
272 if (!Module._yazeDB) {
273 console.error(
'Database not initialized');
276 var operation = function() {
277 return new Promise(function(resolve) {
279 var storeNames = [
'roms',
'projects',
'preferences'];
282 storeNames.forEach(function(storeName) {
283 var transaction = Module._yazeDB.transaction([storeName],
'readonly');
284 var store = transaction.objectStore(storeName);
285 var request = store.openCursor();
287 request.onsuccess = function(event) {
288 var cursor =
event.target.result;
290 var value = cursor.value;
291 if (value instanceof Uint8Array) {
292 totalSize += value.byteLength;
293 }
else if (value instanceof ArrayBuffer) {
294 totalSize += value.byteLength;
295 }
else if (value && ArrayBuffer.isView(value)) {
296 totalSize += value.byteLength;
297 }
else if (typeof value ===
'string') {
298 totalSize += value.length * 2;
300 totalSize += JSON.stringify(value).length * 2;
305 if (completed === storeNames.length) {
311 request.onerror = function() {
313 if (completed === storeNames.length) {
320 if (window.yazeAsyncQueue) {
321 return window.yazeAsyncQueue.enqueue(operation);
328absl::Status WasmStorage::Initialize() {
330 bool expected =
false;
331 if (!initialized_.compare_exchange_strong(expected,
true)) {
332 return absl::OkStatus();
335 int result = idb_open_database(kDatabaseName, kDatabaseVersion);
337 initialized_.store(
false);
338 return absl::InternalError(
"Failed to initialize IndexedDB");
340 return absl::OkStatus();
343void WasmStorage::EnsureInitialized() {
344 if (!initialized_.load()) {
345 auto status = Initialize();
347 emscripten_log(EM_LOG_ERROR,
"Failed to initialize WasmStorage: %s", status.ToString().c_str());
352bool WasmStorage::IsStorageAvailable() {
354 return initialized_.load();
357bool WasmStorage::IsWebContext() {
359 return (typeof window !==
'undefined' && typeof indexedDB !==
'undefined') ? 1 : 0;
364absl::Status WasmStorage::SaveRom(
const std::string& name,
const std::vector<uint8_t>& data) {
366 if (!initialized_.load()) {
367 return absl::FailedPreconditionError(
"Storage not initialized");
369 int result = idb_save_binary(kRomStoreName,
name.c_str(), data.data(), data.size());
371 return absl::InternalError(absl::StrFormat(
"Failed to save ROM '%s'", name));
373 return absl::OkStatus();
376absl::StatusOr<std::vector<uint8_t>> WasmStorage::LoadRom(
const std::string& name) {
378 if (!initialized_.load()) {
379 return absl::FailedPreconditionError(
"Storage not initialized");
381 uint8_t* data_ptr =
nullptr;
382 size_t data_size = 0;
383 int result = idb_load_binary(kRomStoreName,
name.c_str(), &data_ptr, &data_size);
385 if (data_ptr) free(data_ptr);
386 return absl::NotFoundError(absl::StrFormat(
"ROM '%s' not found", name));
387 }
else if (result != 0) {
388 if (data_ptr) free(data_ptr);
389 return absl::InternalError(absl::StrFormat(
"Failed to load ROM '%s'", name));
391 std::vector<uint8_t> data(data_ptr, data_ptr + data_size);
396absl::Status WasmStorage::DeleteRom(
const std::string& name) {
398 if (!initialized_.load()) {
399 return absl::FailedPreconditionError(
"Storage not initialized");
401 int result = idb_delete_entry(kRomStoreName,
name.c_str());
403 return absl::InternalError(absl::StrFormat(
"Failed to delete ROM '%s'", name));
405 return absl::OkStatus();
408std::vector<std::string> WasmStorage::ListRoms() {
410 if (!initialized_.load()) {
413 char* keys_json = idb_list_keys(kRomStoreName);
417 std::vector<std::string> result;
419 nlohmann::json keys = nlohmann::json::parse(keys_json);
420 for (
const auto& key : keys) {
421 if (
key.is_string()) {
422 result.push_back(
key.get<std::string>());
425 }
catch (
const std::exception& e) {
426 emscripten_log(EM_LOG_ERROR,
"Failed to parse ROM list: %s", e.what());
433absl::Status WasmStorage::SaveProject(
const std::string& name,
const std::string& json) {
435 if (!initialized_.load()) {
436 return absl::FailedPreconditionError(
"Storage not initialized");
438 int result = idb_save_string(kProjectStoreName,
name.c_str(),
json.c_str());
440 return absl::InternalError(absl::StrFormat(
"Failed to save project '%s'", name));
442 return absl::OkStatus();
445absl::StatusOr<std::string> WasmStorage::LoadProject(
const std::string& name) {
447 if (!initialized_.load()) {
448 return absl::FailedPreconditionError(
"Storage not initialized");
450 char* json_ptr = idb_load_string(kProjectStoreName,
name.c_str());
454 return absl::NotFoundError(absl::StrFormat(
"Project '%s' not found", name));
456 std::string
json(json_ptr);
461absl::Status WasmStorage::DeleteProject(
const std::string& name) {
463 if (!initialized_.load()) {
464 return absl::FailedPreconditionError(
"Storage not initialized");
466 int result = idb_delete_entry(kProjectStoreName,
name.c_str());
468 return absl::InternalError(absl::StrFormat(
"Failed to delete project '%s'", name));
470 return absl::OkStatus();
473std::vector<std::string> WasmStorage::ListProjects() {
475 if (!initialized_.load()) {
478 char* keys_json = idb_list_keys(kProjectStoreName);
482 std::vector<std::string> result;
484 nlohmann::json keys = nlohmann::json::parse(keys_json);
485 for (
const auto& key : keys) {
486 if (
key.is_string()) {
487 result.push_back(
key.get<std::string>());
490 }
catch (
const std::exception& e) {
491 emscripten_log(EM_LOG_ERROR,
"Failed to parse project list: %s", e.what());
498absl::Status WasmStorage::SavePreferences(
const nlohmann::json& prefs) {
500 if (!initialized_.load()) {
501 return absl::FailedPreconditionError(
"Storage not initialized");
503 std::string json_str = prefs.dump();
504 int result = idb_save_string(kPreferencesStoreName, kPreferencesKey, json_str.c_str());
506 return absl::InternalError(
"Failed to save preferences");
508 return absl::OkStatus();
511absl::StatusOr<nlohmann::json> WasmStorage::LoadPreferences() {
513 if (!initialized_.load()) {
514 return absl::FailedPreconditionError(
"Storage not initialized");
516 char* json_ptr = idb_load_string(kPreferencesStoreName, kPreferencesKey);
518 return nlohmann::json::object();
521 nlohmann::json prefs = nlohmann::json::parse(json_ptr);
524 }
catch (
const std::exception& e) {
526 return absl::InvalidArgumentError(absl::StrFormat(
"Failed to parse preferences: %s", e.what()));
530absl::Status WasmStorage::ClearPreferences() {
532 if (!initialized_.load()) {
533 return absl::FailedPreconditionError(
"Storage not initialized");
535 int result = idb_delete_entry(kPreferencesStoreName, kPreferencesKey);
537 return absl::InternalError(
"Failed to clear preferences");
539 return absl::OkStatus();
543absl::StatusOr<size_t> WasmStorage::GetStorageUsage() {
545 if (!initialized_.load()) {
546 return absl::FailedPreconditionError(
"Storage not initialized");
548 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");} })