7#include <emscripten/bind.h>
21struct CallbackManager {
22 static CallbackManager& Get() {
23 static CallbackManager instance;
27 int RegisterStorageCallback(
28 std::function<
void(
const WasmStorageQuota::StorageInfo&)> cb) {
29 std::lock_guard<std::mutex> lock(mutex_);
31 storage_callbacks_[id] = cb;
35 int RegisterCompressCallback(
36 std::function<
void(std::vector<uint8_t>)> cb) {
37 std::lock_guard<std::mutex> lock(mutex_);
39 compress_callbacks_[id] = cb;
43 void InvokeStorageCallback(
int id,
size_t used,
size_t quota,
bool persistent) {
44 std::lock_guard<std::mutex> lock(mutex_);
45 auto it = storage_callbacks_.find(
id);
46 if (it != storage_callbacks_.end()) {
47 WasmStorageQuota::StorageInfo info;
48 info.used_bytes = used;
49 info.quota_bytes = quota;
50 info.usage_percent = quota > 0 ? (float(used) / float(quota) * 100.0f) : 0.0f;
51 info.persistent = persistent;
53 storage_callbacks_.erase(it);
57 void InvokeCompressCallback(
int id, uint8_t* data,
size_t size) {
58 std::lock_guard<std::mutex> lock(mutex_);
59 auto it = compress_callbacks_.find(
id);
60 if (it != compress_callbacks_.end()) {
61 std::vector<uint8_t> result;
62 if (data && size > 0) {
63 result.assign(data, data + size);
67 compress_callbacks_.erase(it);
74 std::map<int, std::function<void(
const WasmStorageQuota::StorageInfo&)>> storage_callbacks_;
75 std::map<int, std::function<void(std::vector<uint8_t>)>> compress_callbacks_;
84void wasm_storage_quota_estimate_callback(
int callback_id,
double used,
85 double quota,
int persistent) {
86 CallbackManager::Get().InvokeStorageCallback(
87 callback_id,
size_t(used),
size_t(quota), persistent != 0);
91void wasm_compress_callback(
int callback_id, uint8_t* data,
size_t size) {
92 CallbackManager::Get().InvokeCompressCallback(callback_id, data, size);
96void wasm_decompress_callback(
int callback_id, uint8_t* data,
size_t size) {
97 CallbackManager::Get().InvokeCompressCallback(callback_id, data, size);
103extern void wasm_storage_quota_estimate(
int callback_id);
104extern void wasm_compress_data(
const uint8_t* data,
size_t size,
int callback_id);
105extern void wasm_decompress_data(
const uint8_t* data,
size_t size,
int callback_id);
106extern double wasm_get_timestamp_ms();
107extern int wasm_compression_available();
112 static WasmStorageQuota instance;
119 return (navigator.storage &&
120 navigator.storage.estimate &&
126 std::function<
void(
const StorageInfo&)> callback) {
127 if (!callback)
return;
130 double now = wasm_get_timestamp_ms();
131 if (now - last_quota_check_time_.load() < 5000.0 &&
132 last_storage_info_.quota_bytes > 0) {
133 callback(last_storage_info_);
137 int callback_id = CallbackManager::Get().RegisterStorageCallback(
138 [
this, callback](
const StorageInfo& info) {
139 last_storage_info_ = info;
140 last_quota_check_time_.store(wasm_get_timestamp_ms());
144 wasm_storage_quota_estimate(callback_id);
147void WasmStorageQuota::CompressData(
148 const std::vector<uint8_t>& input,
149 std::function<
void(std::vector<uint8_t>)> callback) {
150 if (!callback || input.empty()) {
151 if (callback) callback(std::vector<uint8_t>());
155 int callback_id = CallbackManager::Get().RegisterCompressCallback(callback);
156 wasm_compress_data(input.data(), input.size(), callback_id);
159void WasmStorageQuota::DecompressData(
160 const std::vector<uint8_t>& input,
161 std::function<
void(std::vector<uint8_t>)> callback) {
162 if (!callback || input.empty()) {
163 if (callback) callback(std::vector<uint8_t>());
167 int callback_id = CallbackManager::Get().RegisterCompressCallback(callback);
168 wasm_decompress_data(input.data(), input.size(), callback_id);
172 const std::string& key,
173 const std::vector<uint8_t>& data,
174 std::function<
void(
bool success)> callback) {
175 if (
key.empty() || data.empty()) {
176 if (callback) callback(
false);
180 size_t original_size = data.size();
183 CompressData(data, [
this, key, original_size, callback](
184 const std::vector<uint8_t>& compressed) {
185 if (compressed.empty()) {
186 if (callback) callback(false);
191 CheckQuotaAndEvict(compressed.size(), [
this, key, compressed, original_size, callback](
194 if (callback) callback(false);
199 StoreCompressedData(key, compressed, original_size, callback);
204void WasmStorageQuota::LoadAndDecompress(
205 const std::string& key,
206 std::function<
void(std::vector<uint8_t>)> callback) {
208 if (callback) callback(std::vector<uint8_t>());
213 LoadCompressedData(key, [
this, key, callback](
214 const std::vector<uint8_t>& compressed,
215 size_t original_size) {
216 if (compressed.empty()) {
217 if (callback) callback(std::vector<uint8_t>());
222 UpdateAccessTime(key);
225 DecompressData(compressed, callback);
229void WasmStorageQuota::StoreCompressedData(
230 const std::string& key,
231 const std::vector<uint8_t>& compressed_data,
232 size_t original_size,
233 std::function<
void(
bool)> callback) {
237 var
key = UTF8ToString($0);
240 var originalSize = $3;
241 var callbackPtr = $4;
243 if (!Module._yazeDB) {
244 console.error(
'[StorageQuota] Database not initialized');
245 Module.dynCall_vi(callbackPtr, 0);
249 var data =
new Uint8Array(Module.HEAPU8.buffer, dataPtr, dataSize);
251 compressed_size: dataSize,
252 original_size: originalSize,
253 last_access: Date.now(),
254 compression_ratio: originalSize > 0 ? (dataSize / originalSize) : 1.0
257 var transaction = Module._yazeDB.transaction([
'roms',
'metadata'],
'readwrite');
258 var romStore = transaction.objectStore(
'roms');
259 var metaStore = transaction.objectStore(
'metadata');
262 var dataRequest = romStore.put(data, key);
264 var metaRequest = metaStore.put(metadata, key);
266 transaction.oncomplete = function() {
267 Module.dynCall_vi(callbackPtr, 1);
270 transaction.onerror = function() {
271 console.error(
'[StorageQuota] Failed to store compressed data');
272 Module.dynCall_vi(callbackPtr, 0);
274 },
key.c_str(), compressed_data.data(), compressed_data.size(),
275 original_size, callback ?
new std::function<void(bool)>(callback) : nullptr);
278 UpdateMetadata(key, compressed_data.size(), original_size);
281void WasmStorageQuota::LoadCompressedData(
282 const std::string& key,
283 std::function<
void(std::vector<uint8_t>,
size_t)> callback) {
286 var
key = UTF8ToString($0);
287 var callbackPtr = $1;
289 if (!Module._yazeDB) {
290 console.error(
'[StorageQuota] Database not initialized');
291 Module.dynCall_viii(callbackPtr, 0, 0, 0);
295 var transaction = Module._yazeDB.transaction([
'roms',
'metadata'],
'readonly');
296 var romStore = transaction.objectStore(
'roms');
297 var metaStore = transaction.objectStore(
'metadata');
299 var dataRequest = romStore.get(key);
300 var metaRequest = metaStore.get(key);
305 dataRequest.onsuccess = function() {
306 romData = dataRequest.result;
310 metaRequest.onsuccess = function() {
311 metadata = metaRequest.result;
315 function checkComplete() {
316 if (romData !== null && metadata !== null) {
317 if (romData && metadata) {
318 var ptr = Module._malloc(romData.length);
319 Module.HEAPU8.set(romData, ptr);
320 Module.dynCall_viii(callbackPtr, ptr, romData.length,
321 metadata.original_size || romData.length);
323 Module.dynCall_viii(callbackPtr, 0, 0, 0);
328 transaction.onerror = function() {
329 console.error(
'[StorageQuota] Failed to load compressed data');
330 Module.dynCall_viii(callbackPtr, 0, 0, 0);
332 },
key.c_str(), callback ?
new std::function<void(std::vector<uint8_t>,
size_t)>(
333 callback) : nullptr);
336void WasmStorageQuota::UpdateAccessTime(
const std::string& key) {
337 double now = wasm_get_timestamp_ms();
340 std::lock_guard<std::mutex> lock(mutex_);
341 access_times_[
key] = now;
342 if (item_metadata_.count(key)) {
343 item_metadata_[
key].last_access_time = now;
349 var
key = UTF8ToString($0);
352 if (!Module._yazeDB)
return;
354 var transaction = Module._yazeDB.transaction([
'metadata'],
'readwrite');
355 var store = transaction.objectStore(
'metadata');
357 var request = store.get(key);
358 request.onsuccess = function() {
359 var metadata = request.result || {};
360 metadata.last_access = timestamp;
361 store.put(metadata, key);
363 },
key.c_str(), now);
366void WasmStorageQuota::UpdateMetadata(
const std::string& key,
367 size_t compressed_size,
368 size_t original_size) {
369 std::lock_guard<std::mutex> lock(mutex_);
373 item.compressed_size = compressed_size;
374 item.original_size = original_size;
375 item.last_access_time = wasm_get_timestamp_ms();
376 item.compression_ratio = original_size > 0 ?
377 float(compressed_size) / float(original_size) : 1.0f;
379 item_metadata_[
key] = item;
380 access_times_[
key] = item.last_access_time;
383void WasmStorageQuota::GetStoredItems(
384 std::function<
void(std::vector<StorageItem>)> callback) {
385 if (!callback)
return;
388 std::lock_guard<std::mutex> lock(mutex_);
389 std::vector<StorageItem> items;
390 items.reserve(item_metadata_.size());
392 for (
const auto& [key, item] : item_metadata_) {
393 items.push_back(item);
397 std::sort(items.begin(), items.end(),
398 [](
const StorageItem& a,
const StorageItem& b) {
399 return a.last_access_time > b.last_access_time;
406void WasmStorageQuota::LoadMetadata(std::function<
void()> callback) {
407 if (metadata_loaded_.load()) {
408 if (callback) callback();
413 var callbackPtr = $0;
415 if (!Module._yazeDB) {
416 if (callbackPtr) Module.dynCall_v(callbackPtr);
420 var transaction = Module._yazeDB.transaction([
'metadata'],
'readonly');
421 var store = transaction.objectStore(
'metadata');
422 var request = store.getAllKeys();
424 request.onsuccess = function() {
425 var keys = request.result;
427 var pending = keys.length;
430 if (callbackPtr) Module.dynCall_v(callbackPtr);
434 keys.forEach(function(key) {
435 var getRequest = store.get(key);
436 getRequest.onsuccess = function() {
437 metadata[
key] = getRequest.result;
441 Module.storageQuotaMetadata = metadata;
442 if (callbackPtr) Module.dynCall_v(callbackPtr);
447 }, callback ?
new std::function<void()>(callback) : nullptr);
450 std::lock_guard<std::mutex> lock(mutex_);
453 emscripten::val metadata = emscripten::val::global(
"Module")[
"storageQuotaMetadata"];
454 if (metadata.as<
bool>()) {
457 metadata_loaded_.store(
true);
461void WasmStorageQuota::EvictOldest(
int count,
462 std::function<
void(
int evicted)> callback) {
464 if (callback) callback(0);
468 GetStoredItems([
this, count, callback](
const std::vector<StorageItem>& items) {
471 int to_evict = std::min(count,
static_cast<int>(items.size()));
474 for (
int i = items.size() - to_evict; i < items.size(); ++i) {
475 DeleteItem(items[i].key, [&evicted](
bool success) {
476 if (success) evicted++;
480 if (callback) callback(evicted);
484void WasmStorageQuota::EvictToTarget(
float target_percent,
485 std::function<
void(
int evicted)> callback) {
486 if (target_percent <= 0 || target_percent >= 100) {
487 if (callback) callback(0);
491 GetStorageInfo([
this, target_percent, callback](
const StorageInfo& info) {
492 if (info.usage_percent <= target_percent) {
493 if (callback) callback(0);
498 size_t target_bytes =
size_t(info.quota_bytes * target_percent / 100.0f);
499 size_t bytes_to_free = info.used_bytes - target_bytes;
501 GetStoredItems([
this, bytes_to_free, callback](
502 const std::vector<StorageItem>& items) {
507 for (
auto it = items.rbegin(); it != items.rend(); ++it) {
508 if (freed >= bytes_to_free)
break;
510 DeleteItem(it->key, [&evicted, &freed, it](
bool success) {
513 freed += it->compressed_size;
518 if (callback) callback(evicted);
523void WasmStorageQuota::CheckQuotaAndEvict(
size_t new_size_bytes,
524 std::function<
void(
bool)> callback) {
525 GetStorageInfo([
this, new_size_bytes, callback](
const StorageInfo& info) {
527 size_t projected_usage = info.used_bytes + new_size_bytes;
528 float projected_percent = info.quota_bytes > 0 ?
529 (float(projected_usage) / float(info.quota_bytes) * 100.0f) : 100.0f;
531 if (projected_percent <= kWarningThreshold) {
533 if (callback) callback(
true);
537 if (projected_percent > kCriticalThreshold) {
539 std::cerr <<
"[StorageQuota] Approaching quota limit, evicting old ROMs..."
542 EvictToTarget(kDefaultTargetUsage, [callback](
int evicted) {
543 std::cerr <<
"[StorageQuota] Evicted " << evicted <<
" items"
545 if (callback) callback(evicted > 0);
549 std::cerr <<
"[StorageQuota] Warning: Storage at " << projected_percent
550 <<
"% after this operation" << std::endl;
551 if (callback) callback(
true);
556void WasmStorageQuota::DeleteItem(
const std::string& key,
557 std::function<
void(
bool success)> callback) {
559 var
key = UTF8ToString($0);
560 var callbackPtr = $1;
562 if (!Module._yazeDB) {
563 if (callbackPtr) Module.dynCall_vi(callbackPtr, 0);
567 var transaction = Module._yazeDB.transaction([
'roms',
'metadata'],
'readwrite');
568 var romStore = transaction.objectStore(
'roms');
569 var metaStore = transaction.objectStore(
'metadata');
571 romStore.delete(key);
572 metaStore.delete(key);
574 transaction.oncomplete = function() {
575 if (callbackPtr) Module.dynCall_vi(callbackPtr, 1);
578 transaction.onerror = function() {
579 if (callbackPtr) Module.dynCall_vi(callbackPtr, 0);
581 },
key.c_str(), callback ?
new std::function<void(bool)>(callback) : nullptr);
585 std::lock_guard<std::mutex> lock(mutex_);
586 access_times_.erase(key);
587 item_metadata_.erase(key);
591void WasmStorageQuota::ClearAll(std::function<
void()> callback) {
593 var callbackPtr = $0;
595 if (!Module._yazeDB) {
596 if (callbackPtr) Module.dynCall_v(callbackPtr);
600 var transaction = Module._yazeDB.transaction([
'roms',
'metadata'],
'readwrite');
601 var romStore = transaction.objectStore(
'roms');
602 var metaStore = transaction.objectStore(
'metadata');
607 transaction.oncomplete = function() {
608 if (callbackPtr) Module.dynCall_v(callbackPtr);
610 }, callback ?
new std::function<void()>(callback) : nullptr);
614 std::lock_guard<std::mutex> lock(mutex_);
615 access_times_.clear();
616 item_metadata_.clear();
absl::Status LoadMetadata(const Rom &rom, GameData &data)