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,
139 const char* value,
int replace_existing), {
140 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
141 return asyncify.handleAsync(function() {
142 var storeName = UTF8ToString(store_name);
143 var keyStr = UTF8ToString(key);
144 var valueStr = UTF8ToString(value);
145 var operation = function() {
146 return new Promise(function(resolve, reject) {
147 if (!Module._yazeDB) {
148 console.error(
'Database not initialized');
152 var transaction = Module._yazeDB.transaction([storeName],
'readwrite');
153 var store = transaction.objectStore(storeName);
154 var request = replace_existing
155 ? store.put(valueStr, keyStr)
156 : store.add(valueStr, keyStr);
157 request.onsuccess = function() { resolve(0); };
158 request.onerror = function(event) {
159 if (!replace_existing && request.error &&
160 request.error.name ===
'ConstraintError') {
161 event.preventDefault();
162 event.stopPropagation();
166 console.error(
'Failed to save string:', request.error);
171 if (window.yazeAsyncQueue) {
172 return window.yazeAsyncQueue.enqueue(operation);
178EM_JS(
char*, idb_load_string, (
const char* store_name,
const char* key), {
179 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
180 return asyncify.handleAsync(function() {
181 if (!Module._yazeDB) {
182 console.error(
'Database not initialized');
185 var storeName = UTF8ToString(store_name);
186 var keyStr = UTF8ToString(key);
187 var operation = function() {
188 return new Promise(function(resolve) {
189 var transaction = Module._yazeDB.transaction([storeName],
'readonly');
190 var store = transaction.objectStore(storeName);
191 var request = store.get(keyStr);
192 request.onsuccess = function() {
193 var result = request.result;
194 if (result && typeof result ===
'string') {
195 var len = lengthBytesUTF8(result) + 1;
196 var ptr = Module._malloc(len);
197 stringToUTF8(result, ptr, len);
203 request.onerror = function() {
204 console.error(
'Failed to load string:', request.error);
209 if (window.yazeAsyncQueue) {
210 return window.yazeAsyncQueue.enqueue(operation);
216EM_JS(
int, idb_delete_entry, (
const char* store_name,
const char* key), {
217 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
218 return asyncify.handleAsync(function() {
219 var storeName = UTF8ToString(store_name);
220 var keyStr = UTF8ToString(key);
221 var operation = function() {
222 return new Promise(function(resolve, reject) {
223 if (!Module._yazeDB) {
224 console.error(
'Database not initialized');
228 var transaction = Module._yazeDB.transaction([storeName],
'readwrite');
229 var store = transaction.objectStore(storeName);
230 var request = store.delete(keyStr);
231 request.onsuccess = function() { resolve(0); };
232 request.onerror = function() {
233 console.error(
'Failed to delete entry:', request.error);
238 if (window.yazeAsyncQueue) {
239 return window.yazeAsyncQueue.enqueue(operation);
245EM_JS(
char*, idb_list_keys, (
const char* store_name), {
246 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
247 return asyncify.handleAsync(function() {
248 if (!Module._yazeDB) {
249 console.error(
'Database not initialized');
252 var storeName = UTF8ToString(store_name);
253 var operation = function() {
254 return new Promise(function(resolve) {
255 var transaction = Module._yazeDB.transaction([storeName],
'readonly');
256 var store = transaction.objectStore(storeName);
257 var request = store.getAllKeys();
258 request.onsuccess = function() {
259 var keys = request.result;
260 var jsonStr = JSON.stringify(keys);
261 var len = lengthBytesUTF8(jsonStr) + 1;
262 var ptr = Module._malloc(len);
263 stringToUTF8(jsonStr, ptr, len);
266 request.onerror = function() {
267 console.error(
'Failed to list keys:', request.error);
272 if (window.yazeAsyncQueue) {
273 return window.yazeAsyncQueue.enqueue(operation);
279EM_JS(
size_t, idb_get_storage_usage, (), {
280 const asyncify = typeof Asyncify !==
'undefined' ? Asyncify : Module.Asyncify;
281 return asyncify.handleAsync(function() {
282 if (!Module._yazeDB) {
283 console.error(
'Database not initialized');
286 var operation = function() {
287 return new Promise(function(resolve) {
289 var storeNames = [
'roms',
'projects',
'preferences'];
292 storeNames.forEach(function(storeName) {
293 var transaction = Module._yazeDB.transaction([storeName],
'readonly');
294 var store = transaction.objectStore(storeName);
295 var request = store.openCursor();
297 request.onsuccess = function(event) {
298 var cursor =
event.target.result;
300 var value = cursor.value;
301 if (value instanceof Uint8Array) {
302 totalSize += value.byteLength;
303 }
else if (value instanceof ArrayBuffer) {
304 totalSize += value.byteLength;
305 }
else if (value && ArrayBuffer.isView(value)) {
306 totalSize += value.byteLength;
307 }
else if (typeof value ===
'string') {
308 totalSize += value.length * 2;
310 totalSize += JSON.stringify(value).length * 2;
315 if (completed === storeNames.length) {
321 request.onerror = function() {
323 if (completed === storeNames.length) {
330 if (window.yazeAsyncQueue) {
331 return window.yazeAsyncQueue.enqueue(operation);
338absl::Status WasmStorage::Initialize() {
340 bool expected =
false;
341 if (!initialized_.compare_exchange_strong(expected,
true)) {
342 return absl::OkStatus();
345 int result = idb_open_database(kDatabaseName, kDatabaseVersion);
347 initialized_.store(
false);
348 return absl::InternalError(
"Failed to initialize IndexedDB");
350 return absl::OkStatus();
353void WasmStorage::EnsureInitialized() {
354 if (!initialized_.load()) {
355 auto status = Initialize();
357 emscripten_log(EM_LOG_ERROR,
"Failed to initialize WasmStorage: %s", status.ToString().c_str());
362bool WasmStorage::IsStorageAvailable() {
364 return initialized_.load();
367bool WasmStorage::IsWebContext() {
369 return (typeof window !==
'undefined' && typeof indexedDB !==
'undefined') ? 1 : 0;
374absl::Status WasmStorage::SaveRom(
const std::string& name,
const std::vector<uint8_t>& data) {
376 if (!initialized_.load()) {
377 return absl::FailedPreconditionError(
"Storage not initialized");
379 int result = idb_save_binary(kRomStoreName,
name.c_str(), data.data(), data.size());
381 return absl::InternalError(absl::StrFormat(
"Failed to save ROM '%s'", name));
383 return absl::OkStatus();
386absl::StatusOr<std::vector<uint8_t>> WasmStorage::LoadRom(
const std::string& name) {
388 if (!initialized_.load()) {
389 return absl::FailedPreconditionError(
"Storage not initialized");
391 uint8_t* data_ptr =
nullptr;
392 size_t data_size = 0;
393 int result = idb_load_binary(kRomStoreName,
name.c_str(), &data_ptr, &data_size);
395 if (data_ptr) free(data_ptr);
396 return absl::NotFoundError(absl::StrFormat(
"ROM '%s' not found", name));
397 }
else if (result != 0) {
398 if (data_ptr) free(data_ptr);
399 return absl::InternalError(absl::StrFormat(
"Failed to load ROM '%s'", name));
401 std::vector<uint8_t> data(data_ptr, data_ptr + data_size);
406absl::Status WasmStorage::DeleteRom(
const std::string& name) {
408 if (!initialized_.load()) {
409 return absl::FailedPreconditionError(
"Storage not initialized");
411 int result = idb_delete_entry(kRomStoreName,
name.c_str());
413 return absl::InternalError(absl::StrFormat(
"Failed to delete ROM '%s'", name));
415 return absl::OkStatus();
418std::vector<std::string> WasmStorage::ListRoms() {
420 if (!initialized_.load()) {
423 char* keys_json = idb_list_keys(kRomStoreName);
427 std::vector<std::string> result;
429 nlohmann::json keys = nlohmann::json::parse(keys_json);
430 for (
const auto& key : keys) {
431 if (
key.is_string()) {
432 result.push_back(
key.get<std::string>());
435 }
catch (
const std::exception& e) {
436 emscripten_log(EM_LOG_ERROR,
"Failed to parse ROM list: %s", e.what());
443absl::Status WasmStorage::SaveProject(
const std::string& name,
444 const std::string& json,
445 bool replace_existing) {
447 if (!initialized_.load()) {
448 return absl::FailedPreconditionError(
"Storage not initialized");
450 int result = idb_save_string(kProjectStoreName,
name.c_str(),
json.c_str(),
452 if (!replace_existing && result == 1) {
453 return absl::AlreadyExistsError(
454 absl::StrFormat(
"Project '%s' already exists", name));
457 return absl::InternalError(absl::StrFormat(
"Failed to save project '%s'", name));
459 return absl::OkStatus();
462absl::StatusOr<std::string> WasmStorage::LoadProject(
const std::string& name) {
464 if (!initialized_.load()) {
465 return absl::FailedPreconditionError(
"Storage not initialized");
467 char* json_ptr = idb_load_string(kProjectStoreName,
name.c_str());
471 return absl::NotFoundError(absl::StrFormat(
"Project '%s' not found", name));
473 std::string
json(json_ptr);
478absl::Status WasmStorage::DeleteProject(
const std::string& name) {
480 if (!initialized_.load()) {
481 return absl::FailedPreconditionError(
"Storage not initialized");
483 int result = idb_delete_entry(kProjectStoreName,
name.c_str());
485 return absl::InternalError(absl::StrFormat(
"Failed to delete project '%s'", name));
487 return absl::OkStatus();
490std::vector<std::string> WasmStorage::ListProjects() {
492 if (!initialized_.load()) {
495 char* keys_json = idb_list_keys(kProjectStoreName);
499 std::vector<std::string> result;
501 nlohmann::json keys = nlohmann::json::parse(keys_json);
502 for (
const auto& key : keys) {
503 if (
key.is_string()) {
504 result.push_back(
key.get<std::string>());
507 }
catch (
const std::exception& e) {
508 emscripten_log(EM_LOG_ERROR,
"Failed to parse project list: %s", e.what());
515absl::Status WasmStorage::SavePreferences(
const nlohmann::json& prefs) {
517 if (!initialized_.load()) {
518 return absl::FailedPreconditionError(
"Storage not initialized");
520 std::string json_str = prefs.dump();
521 int result = idb_save_string(kPreferencesStoreName, kPreferencesKey,
522 json_str.c_str(),
true);
524 return absl::InternalError(
"Failed to save preferences");
526 return absl::OkStatus();
529absl::StatusOr<nlohmann::json> WasmStorage::LoadPreferences() {
531 if (!initialized_.load()) {
532 return absl::FailedPreconditionError(
"Storage not initialized");
534 char* json_ptr = idb_load_string(kPreferencesStoreName, kPreferencesKey);
536 return nlohmann::json::object();
539 nlohmann::json prefs = nlohmann::json::parse(json_ptr);
542 }
catch (
const std::exception& e) {
544 return absl::InvalidArgumentError(absl::StrFormat(
"Failed to parse preferences: %s", e.what()));
548absl::Status WasmStorage::ClearPreferences() {
550 if (!initialized_.load()) {
551 return absl::FailedPreconditionError(
"Storage not initialized");
553 int result = idb_delete_entry(kPreferencesStoreName, kPreferencesKey);
555 return absl::InternalError(
"Failed to clear preferences");
557 return absl::OkStatus();
561absl::StatusOr<size_t> WasmStorage::GetStorageUsage() {
563 if (!initialized_.load()) {
564 return absl::FailedPreconditionError(
"Storage not initialized");
566 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");} })