原文链接:https://developer.chrome.com/native-client/devguide/coding/file-io
注意:已针对ChromeOS以外的平台公布了此处所述技术的弃用。
请访问我们的 迁移指南 了解详情。
文件I / O.
介绍
本节介绍如何使用FileIO API使用本地安全数据存储读取和写入文件。
您可以将File IO API与URL Loading API一起使用,为您的NaCl应用程序创建整体数据下载和缓存解决方案。例如:
- 使用文件IO API检查本地磁盘以查看是否存在程序所需的文件。
- 如果文件存在于本地,请使用File IO API将其加载到内存中。如果文件在本地不存在,请使用URL Loading API从服务器检索文件。
- 使用File IO API将文件写入磁盘。
- 在应用程序需要时使用File IO API将文件加载到内存中。
本节中讨论的示例包含在目录中的SDK中 examples/api/file_io
。
参考信息
有关FileIO的参考信息,请参阅以下文档:
- file_io.h - 用于创建FileIO对象的API
- file_ref.h - 用于为文件系统中的文件创建文件引用或“弱指针”的API
- file_system.h - 用于创建与文件关联的文件系统的API
本地文件I / O.
Chrome在磁盘上提供了一个模糊的限制区域,Web应用程序可以安全地读取和写入文件。Pepper FileIO,FileRef和FileSystem API(统称为文件IO API)允许您访问此沙盒本地磁盘,以便您可以自己读取和写入文件以及管理缓存。数据在启动Chrome之间保持不变,除非您的应用程序删除它或用户手动删除它,否则不会删除。除了本地驱动器上的可用实际空间之外,您可以使用的本地数据量没有限制。
启用本地文件I / O.
启用持久性本地数据编写的最简单方法是在Chrome Web Store清单文件中包含unlimitedStorage权限。使用此权限,您可以使用Pepper FileIO API,而无需在运行时请求磁盘空间。当用户安装应用时,Chrome会显示一条消息,通知该应用已写入本地磁盘。
如果您不使用该unlimitedStorage
权限,则必须包含调用HTML5配额管理API的 JavaScript代码,以便在使用FileIO API之前显式请求本地磁盘空间。在这种情况下,Chrome会在每次制作时提示用户接受requestQuota调用。
测试本地文件I / O.
您应该知道,使用unlimitedStorage
清单权限会限制您测试应用程序的方式。运行本机客户端应用程序中描述的四种技术中的三种 读取Chrome Web Store清单文件并unlimitedStorage
在出现时启用权限,但第一种技术(本地服务器)不会。如果要使用简单的本地服务器测试应用程序的文件IO部分,则需要包含调用HTML5配额管理API的JavaScript代码。在交付应用程序时,您可以使用unlimitedStorage
清单权限替换此代码 。
这个file_io
例子
Native Client SDK包含一个示例,file_io
演示如何读取和写入本地磁盘文件。由于您可能从没有Chrome Web Store清单文件的本地服务器运行该示例,因此该示例的索引文件使用JavaScript执行上述配额管理设置。该示例包含以下主要文件:
index.html
- 启动Native Client模块并显示用户界面的HTML代码。example.js
- 请求配额的JavaScript代码(如上所述)。它还监听用户与用户界面的交互,并将请求转发给Native Client模块。file_io.cc
- 设置并提供Native Client模块入口点的代码。
本节的其余部分介绍了file_io.cc
文件中用于读写文件的代码。
文件I / O概述
与许多Pepper API一样,File IO API包含一组异步执行并在Native Client模块中调用回调函数的方法。与大多数其他示例不同,该file_io
示例还演示了如何在工作线程上同步进行Pepper调用。
在模块的主线程上对Pepper进行阻塞调用是违法的。在工作线程上运行时,此限制被取消 - 这称为“从主线程调用Pepper”。这通常简化了代码的逻辑; 可以从工作线程上的一个函数调用多个异步Pepper函数,因此您可以正常使用堆栈和标准控制流结构。
该file_io
示例的高级流程如下所述。请注意,命名空间pp
中的方法是Pepper C ++ API的一部分。
创建和编写文件
以下是创建和写入文件所涉及的高级步骤:
pp::FileIO::Open
用PP_FILEOPEN_FLAG_CREATE
标志调用来创建文件。因为回调函数是pp::BlockUntilComplete
,所以此线程被阻塞直到Open
成功或失败。pp::FileIO::Write
被调用来写内容。同样,线程被阻塞,直到Write
完成调用。如果要写入更多数据,Write
则再次调用。- 当没有更多数据要写时,请致电
pp::FileIO::Flush
。
打开并读取文件
以下是打开和读取文件所涉及的高级步骤:
pp::FileIO::Open
被调用来打开文件。因为回调函数是pp::BlockUntilComplete
,所以在Open成功或失败之前该线程被阻塞。pp::FileIO::Query
被调用来查询有关文件的信息,例如文件大小。线程被阻塞直到Query
完成。pp::FileIO::Read
被叫来阅读内容。线程被阻塞直到Read
完成。如果有更多数据要读取,Read
则再次调用。
删除文件
删除文件非常简单:调用pp::FileRef::Delete
。线程被阻塞直到Delete
完成。
制作目录
制作目录也很简单:打电话pp::File::MakeDirectory
。线程被阻塞直到MakeDirectory
完成。
列出目录的内容
以下是列出目录所涉及的高级步骤:
pp::FileRef::ReadDirectoryEntries
被调用,并给出一个列表的目录条目。还给出了回调; 许多其他函数使用pp::BlockUntilComplete
,但ReadDirectoryEntries
返回结果在其回调中,因此必须指定它。- 当调用
ReadDirectoryEntries
完成时,它调用ListCallback
将结果打包成字符串消息,并将其发送到JavaScript。
file_io
深潜
该file_io
示例显示具有几个字段和多个按钮的用户界面。以下是该file_io
示例的屏幕截图:
每个单选按钮都是您可以执行的文件操作,文件名有一些合理的默认值。尝试在大输入框中键入消息并单击Save
,然后切换到该Load File
操作,然后单击Load
。
让我们来看看幕后发生了什么。
打开文件系统并准备文件I / O.
pp::Instance::Init
在创建模块的实例时调用。在这个例子中,Init
启动一个新线程(通过pp::SimpleThread
类),并告诉它打开文件系统:
virtual bool Init(uint32_t /*argc*/,
const char * /*argn*/ [],
const char * /*argv*/ []) {
file_thread_.Start();
// Open the file system on the file_thread_. Since this is the first
// operation we perform there, and because we do everything on the
// file_thread_ synchronously, this ensures that the FileSystem is open
// before any FileIO operations execute.
file_thread_.message_loop().PostWork(
callback_factory_.NewCallback(&FileIoInstance::OpenFileSystem));
return true;
}
当文件线程开始运行时,它将调用OpenFileSystem
。这将调用pp::FileSystem::Open
并阻止文件线程,直到函数返回。
需要注意的是调用pp::FileSystem::Open
使用 pp::BlockUntilComplete
它的回调。这是唯一可能的,因为我们正在运行主线程; 如果您尝试从主线程进行阻塞调用,该函数将返回错误 PP_ERROR_BLOCKS_MAIN_THREAD
。
void OpenFileSystem(int32_t /*result*/) {
int32_t rv = file_system_.Open(1024 * 1024, pp::BlockUntilComplete());
if (rv == PP_OK) {
file_system_ready_ = true;
// Notify the user interface that we're ready
PostMessage("READY|");
} else {
ShowErrorMessage("Failed to open file system", rv);
}
}
处理来自JavaScript的消息
当您单击该Save
按钮时,JavaScript会将消息发布到NaCl模块,并将文件操作作为字符串发送(有关消息传递的详细信息,请参阅消息传递系统)。该字符串由解析HandleMessage
,并将新工作添加到文件线程:
virtual void HandleMessage(const pp::Var& var_message) {
if (!var_message.is_string())
return;
// Parse message into: instruction file_name_length file_name [file_text]
std::string message = var_message.AsString();
std::string instruction;
std::string file_name;
std::stringstream reader(message);
int file_name_length;
reader >> instruction >> file_name_length;
file_name.resize(file_name_length);
reader.ignore(1); // Eat the delimiter
reader.read(&file_name[0], file_name_length);
...
// Dispatch the instruction
if (instruction == kLoadPrefix) {
file_thread_.message_loop().PostWork(
callback_factory_.NewCallback(&FileIoInstance::Load, file_name));
} else if (instruction == kSavePrefix) {
...
}
}
保存文件
FileIoInstance::Save
Save
按下按钮时调用。首先,它检查文件系统是否已成功打开:
if (!file_system_ready_) {
ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
return;
}
然后,它pp::FileRef
使用文件名创建资源。一个 FileRef
资源是一个弱引用到文件系统中的文件; 也就是说,即使存在未完成的FileRef
资源,仍然可以删除文件。
pp::FileRef ref(file_system_, file_name.c_str());
接下来,pp::FileIO
创建并打开资源。调用 pp::FileIO::Open
pass PP_FILEOPEFLAG_WRITE
以打开文件进行写入,PP_FILEOPENFLAG_CREATE
如果尚未存在则创建新文件并PP_FILEOPENFLAG_TRUNCATE
清除以前任何内容的文件:
pp::FileIO file(this);
int32_t open_result =
file.Open(ref,
PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_CREATE |
PP_FILEOPENFLAG_TRUNCATE,
pp::BlockUntilComplete());
if (open_result != PP_OK) {
ShowErrorMessage("File open for write failed", open_result);
return;
}
现在该文件已打开,它将以块的形式写入。在异步模型中,这需要编写一个单独的函数,将当前状态存储在免费存储和一系列回调中。因为这个函数是从主线程pp::FileIO::Write
调用的,所以可以同步调用,并且可以使用传统的do / while循环:
int64_t offset = 0;
int32_t bytes_written = 0;
do {
bytes_written = file.Write(offset,
file_contents.data() + offset,
file_contents.length(),
pp::BlockUntilComplete());
if (bytes_written > 0) {
offset += bytes_written;
} else {
ShowErrorMessage("File write failed", bytes_written);
return;
}
} while (bytes_written < static_cast<int64_t>(file_contents.length()));
最后,刷新文件以将所有更改推送到磁盘:
int32_t flush_result = file.Flush(pp::BlockUntilComplete());
if (flush_result != PP_OK) {
ShowErrorMessage("File fail to flush", flush_result);
return;
}
加载文件
FileIoInstance::Load
Load
按下按钮时调用。与Save
函数一样,Load
首先检查FileSystem是否已成功打开,并创建一个新的FileRef
:
if (!file_system_ready_) {
ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
return;
}
pp::FileRef ref(file_system_, file_name.c_str());
接下来,Load
创建并打开一个新FileIO
资源,传递 PP_FILEOPENFLAG_READ
打开文件进行读取。比较结果,PP_ERROR_FILENOTFOUND
以便在文件不存在时提供更好的错误消息:
int32_t open_result =
file.Open(ref, PP_FILEOPENFLAG_READ, pp::BlockUntilComplete());
if (open_result == PP_ERROR_FILENOTFOUND) {
ShowErrorMessage("File not found", open_result);
return;
} else if (open_result != PP_OK) {
ShowErrorMessage("File open for read failed", open_result);
return;
}
然后Load
调用pp::FileIO::Query
以获取有关文件的元数据,例如其大小。这用于分配一个std::vector
缓冲区,用于保存内存中文件的数据:
int32_t query_result = file.Query(&info, pp::BlockUntilComplete());
if (query_result != PP_OK) {
ShowErrorMessage("File query failed", query_result);
return;
}
...
std::vector<char> data(info.size);
类似于Save
,传统的while循环用于将文件读入新分配的缓冲区:
int64_t offset = 0;
int32_t bytes_read = 0;
int32_t bytes_to_read = info.size;
while (bytes_to_read > 0) {
bytes_read = file.Read(offset,
&data[offset],
data.size() - offset,
pp::BlockUntilComplete());
if (bytes_read > 0) {
offset += bytes_read;
bytes_to_read -= bytes_read;
} else if (bytes_read < 0) {
// If bytes_read < PP_OK then it indicates the error code.
ShowErrorMessage("File read failed", bytes_read);
return;
}
}
最后,文件的内容被发送回JavaScript,以显示在页面上。此示例使用“ DISP|
”作为显示信息的前缀命令:
std::string string_data(data.begin(), data.end());
PostMessage("DISP|" + string_data);
ShowStatusMessage("Load success");
删除文件
FileIoInstance::Delete
Delete
按下按钮时调用。首先,它检查文件系统是否已打开,并创建一个新的FileRef
:
if (!file_system_ready_) {
ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
return;
}
pp::FileRef ref(file_system_, file_name.c_str());
与Save
和不同Load
,Delete
在FileRef
资源上调用,而不是FileIO
资源。请注意,PP_ERROR_FILENOTFOUND
在尝试删除不存在的文件时,会检查结果 以提供更好的错误消息:
int32_t result = ref.Delete(pp::BlockUntilComplete());
if (result == PP_ERROR_FILENOTFOUND) {
ShowStatusMessage("File/Directory not found");
return;
} else if (result != PP_OK) {
ShowErrorMessage("Deletion failed", result);
return;
}
列出目录中的文件
FileIoInstance::List
List Directory
按下按钮时调用。与所有其他操作一样,它会检查FileSystem是否已打开并创建新的FileRef
:
if (!file_system_ready_) {
ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
return;
}
pp::FileRef ref(file_system_, dir_name.c_str());
与其他操作不同,它不会进行阻塞调用 pp::FileRef::ReadDirectoryEntries
。由于ReadDirectoryEntries
在其回调中返回结果目录条目,因此创建了一个指向的新回调对象FileIoInstance::ListCallback
。
该pp::CompletionCallbackFactory
模板类用于实例化一个新的回调。请注意,FileRef
资源作为参数传递; 这将向回调对象添加引用计数,以防止FileRef
在函数完成时销毁资源。
// Pass ref along to keep it alive.
ref.ReadDirectoryEntries(callback_factory_.NewCallbackWithOutput(
&FileIoInstance::ListCallback, ref));
FileIoInstance::ListCallback
然后得到作为传递的结果 std::vector
的pp::DirectoryEntry
对象,并将其发送给JavaScript:
void ListCallback(int32_t result,
const std::vector<pp::DirectoryEntry>& entries,
pp::FileRef /*unused_ref*/) {
if (result != PP_OK) {
ShowErrorMessage("List failed", result);
return;
}
std::stringstream ss;
ss << "LIST";
for (size_t i = 0; i < entries.size(); ++i) {
pp::Var name = entries[i].file_ref().GetName();
if (name.is_string()) {
ss << "|" << name.AsString();
}
}
PostMessage(ss.str());
ShowStatusMessage("List success");
}
创建一个新目录
FileIoInstance::MakeDir
Make Directory
按下按钮时调用。与所有其他操作一样,它会检查FileSystem是否已打开并创建新的FileRef
:
if (!file_system_ready_) {
ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
return;
}
pp::FileRef ref(file_system_, dir_name.c_str());
然后pp::FileRef::MakeDirectory
调用该函数。
int32_t result = ref.MakeDirectory(
PP_MAKEDIRECTORYFLAG_NONE, pp::BlockUntilComplete());
if (result != PP_OK) {
ShowErrorMessage("Make directory failed", result);
return;
}
ShowStatusMessage("Make directory success");
CC-By 3.0许可下提供的内容