为什么无法通过保存在 IndexedDB 中的 emscriptens fopen() 打开文件?

Why can't open a file via emscriptens fopen() saved in IndexedDB?

我正在试用 Emscriptens IndexedDB,但无法使用 运行。无法加载文件,"cannot open file"。使用 EMSCRIPTEN_FETCH_LOAD_TO_MEMORY 一切正常。

  1. 通过emscripten的emscripten_fetch_t
  2. 下载文件
  3. 通过EMSCRIPTEN_FETCH_PERSIST_FILE
  4. 直接在IndexedDB中保存文件
  5. 稍后加载到内存中
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iostream>

#include <emscripten.h>
#include <emscripten/fetch.h>

using namespace std;

void downloadSucceeded(emscripten_fetch_t *fetch)
{
    printf("URL %s\n", fetch->url);
    printf("bytes %llu\n", fetch->numBytes);

    FILE *file = fopen("/data/test.txt", "r"); // also tried it with 'test.txt', '/test.txt', 'data/test.txt'
    if (!file) {
        printf("cannot open file\n");
        return;
    }

    fclose (file);

    emscripten_fetch_close(fetch);
}

void downloadFailed(emscripten_fetch_t *fetch) {
  printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status);
  emscripten_fetch_close(fetch);
}

extern "C" {
void EMSCRIPTEN_KEEPALIVE download() {
    cout << "download" << endl;

    emscripten_fetch_attr_t attr;
    emscripten_fetch_attr_init(&attr);
    strcpy(attr.requestMethod, "GET");
    attr.attributes = EMSCRIPTEN_FETCH_PERSIST_FILE;
    attr.onsuccess = downloadSucceeded;
    attr.onerror = downloadFailed;
    emscripten_fetch(&attr, "http://localhost/test.txt");
}
}

int main() {
    cout << "main" << endl;
    EM_ASM(
        FS.mkdir('/data');
        FS.mount(IDBFS, {}, '/data');

        FS.syncfs(true, function (err) {
            console.log("syncfs");
            Module._download();
            assert(!err);
        });
    );

    emscripten_exit_with_live_runtime();
}

输出

main
syncfs
download
URL http://localhost/test.txt
bytes 185
cannot open file

建成

emcc code.cpp -o index.js --shell-file shell.html -lidbfs.js -s EXPORTED_FUNCTIONS="['_main','_download']" -s FETCH=1 -O3

If an application wants to download a file for local access, but does not immediately need to use the file, e.g. when preloading data up front for later access, it is a good idea to avoid the EMSCRIPTEN_FETCH_LOAD_TO_MEMORY flag altogether, and only pass the EMSCRIPTEN_FETCH_PERSIST_FILE flag instead. This causes the fetch to download the file directly to IndexedDB, which avoids temporarily populating the file in memory after the download finishes. In this scenario, the onsuccess() handler will only report the total downloaded file size, but will not contain the data bytes to the file.

但我仍然可以读取数据字节,为什么?

有人能帮帮我吗,我做错了什么?

EMSCRIPTEN_FETCH_PERSIST_FILE 实际上做了两件事:

  1. 检查文件是否已经存储在IndexDB中。如果是,从那里取回它,根本不要打扰服务器。
  2. 如果本地IndexDB中没有该文件,则从服务器下载并缓存。

特别是缓存控制非常少:您can use EM_IDB_DELETE删除缓存版本,仅此而已。

回答

所以,您似乎不应该使用 IDBFS 来访问获取的文件。请改用 Fetch 库,它将使用缓存版本,无需任何网络往返。

-s FETCH_DEBUG 添加到编译行可能也很有用。

但是为什么没有用呢?

理解先决条件:通读 IndexDB Basic Concepts 以理解 "database" 和 "object storage"。

查看 Emscripten's source code 以及浏览器开发人员控制台中的 "Storage" 选项卡:

  • IDBFS 安装在 /data-foo-bar will provide access to a database called /data-foo-bar. Specifically, its FILE_DATA object storage (see here)。这里的命名是任意的和硬编码的。 /data-foo-bar 文件夹中的无文件存储存储在该对象存储中的 /data-foo-bar/my-file.txt 等键中。
  • 特别是,您不能访问任意 IndexDB 数据库或对象存储。
  • 另一方面,Fetch 库stores files to the emscripten_filesystem database, inside FILES 对象存储。同样,名称看起来是任意的并且是硬编码的。

因此,您无法通过 IDBFS 访问 Fetch 的缓存,因为它们访问具有不同命名约定的不同数据库中的不同存储对象。

例如,这是 FS.writeFile('/data/my-file.txt', 'hello') 在我的 Firefox 中的结果:

这里是 Fetch 缓存所在的位置:

很遗憾,我不知道为什么 http://localhost:8000/test.txt 的内容显示为空对象。

@yeputons:谢谢你的解释!这帮助我理解了我的观察...

  1. 看了IDBFS的内容,有/tmp、/home、/dev、/proc、/data文件夹,没有test.txt文件
EM_ASM(
    var l = FS.readdir("/");
    for(var i in l) { 
        var item = l[i];
        console.log(item);
    }
    );
  1. 如果我清除浏览器缓存然后重新加载网站,fetch->numBytes=0 和下一次重新加载 test.txt 的数据可用。

所以结论是,正如您已经提到的,不要混合使用 'Fetch Cache' 和 IDBFS。如果使用 EMSCRIPTEN_FETCH_PERSIST_FILE,您必须获取文件两次,1. 下载并保存在 'Fetch Cache' 中,稍后 2. 将其读入内存并通过 fetch->data 访问它。