编写你的应用程序(五)、文件I/O

原文链接:https://developer.chrome.com/native-client/devguide/coding/file-io

注意:已针对ChromeOS以外的平台公布了此处所述技术的弃用。
请访问我们的 迁移指南 了解详情。


文件I / O.

介绍

本节介绍如何使用FileIO API使用本地安全数据存储读取和写入文件。

您可以将File IO API与URL Loading API一起使用,为您的NaCl应用程序创建整体数据下载和缓存解决方案。例如:

  1. 使用文件IO API检查本地磁盘以查看是否存在程序所需的文件。
  2. 如果文件存在于本地,请使用File IO API将其加载到内存中。如果文件在本地不存在,请使用URL Loading API从服务器检索文件。
  3. 使用File IO API将文件写入磁盘。
  4. 在应用程序需要时使用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的一部分。

创建和编写文件

以下是创建和写入文件所涉及的高级步骤:

  1. pp::FileIO::OpenPP_FILEOPEN_FLAG_CREATE标志调用来创建文件。因为回调函数是pp::BlockUntilComplete,所以此线程被阻塞直到Open成功或失败。
  2. pp::FileIO::Write被调用来写内容。同样,线程被阻塞,直到Write完成调用。如果要写入更多数据,Write则再次调用。
  3. 当没有更多数据要写时,请致电pp::FileIO::Flush

打开并读取文件

以下是打开和读取文件所涉及的高级步骤:

  1. pp::FileIO::Open被调用来打开文件。因为回调函数是pp::BlockUntilComplete,所以在Open成功或失败之前该线程被阻塞。
  2. pp::FileIO::Query被调用来查询有关文件的信息,例如文件大小。线程被阻塞直到Query完成。
  3. pp::FileIO::Read被叫来阅读内容。线程被阻塞直到Read完成。如果有更多数据要读取,Read则再次调用。

删除文件

删除文件非常简单:调用pp::FileRef::Delete。线程被阻塞直到Delete完成。

制作目录

制作目录也很简单:打电话pp::File::MakeDirectory。线程被阻塞直到MakeDirectory完成。

列出目录的内容

以下是列出目录所涉及的高级步骤:

  1. pp::FileRef::ReadDirectoryEntries被调用,并给出一个列表的目录条目。还给出了回调; 许多其他函数使用 pp::BlockUntilComplete,但ReadDirectoryEntries返回结果在其回调中,因此必须指定它。
  2. 当调用ReadDirectoryEntries完成时,它调用 ListCallback将结果打包成字符串消息,并将其发送到JavaScript。

file_io 深潜

file_io示例显示具有几个字段和多个按钮的用户界面。以下是该file_io示例的屏幕截图:

/native-client/images/fileioexample.png

每个单选按钮都是您可以执行的文件操作,文件名有一些合理的默认值。尝试在大输入框中键入消息并单击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::SaveSave按下按钮时调用。首先,它检查文件系统是否已成功打开:

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::Openpass 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::LoadLoad按下按钮时调用。与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::DeleteDelete按下按钮时调用。首先,它检查文件系统是否已打开,并创建一个新的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和不同LoadDeleteFileRef资源上调用,而不是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::ListList 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::vectorpp::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::MakeDirMake 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许可下提供的内容

猜你喜欢

转载自blog.csdn.net/y601500359/article/details/81179805