Native Client

File IO

Introduction

This chapter describes how to use the FileIO API to read and write files using a local secure data store.

The example discussed in this chapter is included in the SDK in the directory examples/file_io (for the Native Client SDK version 21 and above).

Reference Information

For reference information related to loading data from URLs, see the following documentation:

  • file_io.h - API to create a FileIO object
  • file_ref.h - API to create a file reference or "weak pointer" to a file in a file system
  • file_system.h - API to create a file system associated with a file

Background

Chrome provides an obfuscated, restricted area on disk to which a web app can safely read and write files. The Pepper FileIO, FileRef, and FileSystem APIs (collectively called the File IO APIs) allow you to access this sandboxed local disk so that you can write your file data to the disk and manage caching yourself. The data is persistent between launches of Chrome, and most importantly, will not be evicted unless your application deletes it or the user manually deletes it. Also, there is no storage cap for the amount of data you can use when using persistent data.

You might use the File IO API with the URL Loading APIs to create an overall data download and caching solution for your NaCl applications. For example:

  1. Use the File IO APIs to check the local disk to see if a file exists that your program needs.
  2. If the file exists locally, load it into memory using the File IO API. If the file doesn't exist locally, use the URL Loading API to retrieve the file from the server.
  3. Use the File IO API to write the file to disk.
  4. Load the file into memory using the File IO API when needed by your application.

The file_io example

Version 21 and above of the Native Client SDK includes an example, called file_io that demonstrates how to read from and write a file to and from local disk. This example has three primary files:

  • index.html - The HTML code that launches the Native Client module and displays the user interface (a field for text, a field for a file name, a Save button, Load button, and Delete button).
  • file_io.cc - The code that sets up and provides and entry point into the Native client module.
  • file_io.nmf - The Native Client manifest file (in newlib and glibc directories).

The remainder of this section covers the code in the file_io.cc file for reading and writing files.

File IO overview

Like many Pepper APIs, such as the File Loader API, the File IO API includes a set of methods that execute asynchronously and that invoke callback functions in your Native Client module. The high-level flow for the file_io example is described below. Note that methods in the namespace pp::FileIO are part of the Pepper File IO API, while functions in the namespace GetURLHandler are part of the code in the Native Client module (specifically in the file examples/geturl/geturl_handler.cc).

The following image shows the flow of the file_io code used to create and write to a file:

url loader

Following are the high-level steps involved in creating and writing to a file:

  1. The file system is opened.
  2. Open() is called with the PP_FILEOPEN_FLAG_CREATE flag to create a file. When Open() completes, it invokes a callback function in the Native Client module (in this case, SaveOpenCallback()).
  3. Write() is called to write the contents. When Write() completes, it invokes a callback (in this case, SaveWriteCallback)
  4. Flush() is called if we're done writing, otherwise Write() is called again.

The following image shows the flow of the file_io code used to open a file and read from that file:

url loader

Following are the high-level steps involved in opening and reading a file:

  1. Open() is called to open the file. When Open() completes, it invokes a callback function (in this case, LoadOpenCallback()).
  2. Query() is called to query information about the file. When Query() completes, it invokes a callback function (in this case, LoadQueryCallback()).
  3. Read() is called to read the contents. When Read() completes, it invokes a callback function (in this case, ReadCallback()).

file_io deep dive

The file_io example displays a user interface with a couple of fields and several buttons. Following is a screenshot of the file_io example:

url loader

To use the example:

  1. Type some text into the larger field.
  2. Type a file name in the File Name field.
  3. Press the Save button to create a file.
  4. Press the Load button to load the file.
  5. Press the Delete button to delete the file.

This is the order that the functions are discussed in this document.

Opening a file system and preparing for file IO

Init() is called when an instance of a module is created. This method uses a FileSystem object defining the type of file system associated with the file (such as local persistent or local temporary). The file system is then opened.

	
virtual bool Init(uint32_t /*argc*/, const char* /*argn*/[], const char* /*argv*/[]) {
	pp::CompletionCallback callback = callback_factory_.NewCallback(
	    &FileIoInstance::FileSystemOpenCallback);
	int32_t rv = file_system_.Open(1024*1024, callback);
	if (rv != PP_OK_COMPLETIONPENDING) {
		callback.Run(rv);
	    return false;
	}
	return true;
}

The Native Client module sits and awaits user input after Init() completes. The user can type some text in a field (to represent the contents of a file), a file name in another field, and then press one of three buttons (Save, Load, or Delete) to perform that action on the file.

HandleMessage() receives any messages coming from the browser. In this case, HandleMessage() receives a message containing the action (instruction), the file name length, the file name, and the text of the file. The instruction determines what action the native client module will perform on the file (ld for "load", sv for "save" and de for "delete").

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]
	...
	
	// Dispatch the instruction
    if (instruction.compare(kLoadPrefix) == 0) {
      Load(file_name);
      return;
    }
    
    if (instruction.compare(kSavePrefix) == 0) {
      // Read the rest of the message as the file text
      reader.ignore(1);  // Eat the delimiter
      std::string file_text = message.substr(reader.tellg());
      Save(file_name, file_text);
      return;
    }

    if (instruction.compare(kDeletePrefix) == 0) {
      Delete(file_name);
      return;
    }
  }

Saving a file

Save() is called when the Save button is pressed by the user. Save() creates a file reference using the FileSystem objected created in Init(). Save() then calls Open() with PP_FILEOPEN_CREATE flag (to create the new file if it doesn't already exist) and the PP_FILEOPENFLAG_TRUNCATE flag (to request that the file be truncated to length 0 if it exists and is a regular file).

bool Save(const std::string& file_name, const std::string& file_contents) {
   if (!file_system_ready_) {
      ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
      return false;
    }

    FileIoInstance::Request* request = new FileIoInstance::Request;
    request->ref = pp::FileRef(file_system_, file_name.c_str());
    request->file = pp::FileIO(this);
    request->file_contents = file_contents;

    pp::CompletionCallback callback = callback_factory_.NewCallback(
          &FileIoInstance::SaveOpenCallback, request);
    int32_t rv = request->file.Open(request->ref,
        PP_FILEOPENFLAG_WRITE|PP_FILEOPENFLAG_CREATE|PP_FILEOPENFLAG_TRUNCATE,
        callback);

    // Handle cleanup in the callback if error
    if (rv != PP_OK_COMPLETIONPENDING) {
      callback.Run(rv);
      return false;
    }
    return true;
  }

SaveOpenCallback() is called when Open() returns. SaveOpenCallback() ensures that the size of the file is not 0 (empty). If the file is empty, SaveOpenCallback() calls Flush() to flush the cached data for the file. If the file contains content, Write() is called to write the file.

	
 void SaveOpenCallback(int32_t result, FileIoInstance::Request* request) {
    if (result != PP_OK) {
      ShowErrorMessage("File open for write failed", result);
      delete request;
      return;
    }

    // It is an error to write 0 bytes to the file, however,
    // upon opening we have truncated the file to length 0
    if (request->file_contents.length() == 0) {
      pp::CompletionCallback callback = callback_factory_.NewCallback(
          &FileIoInstance::SaveFlushCallback, request);
      int32_t rv = request->file.Flush(callback);

      // Handle cleanup in the callback if error
      if (rv != PP_OK_COMPLETIONPENDING) {
        callback.Run(rv);
        return;
      }
    } else if (request->file_contents.length() <= INT32_MAX) {
      request->offset = 0;
      pp::CompletionCallback callback = callback_factory_.NewCallback(
          &FileIoInstance::SaveWriteCallback, request);

      int32_t rv = request->file.Write(request->offset,
          request->file_contents.c_str(),
          request->file_contents.length(), callback);

      // Handle cleanup in the callback if error
      if (rv != PP_OK_COMPLETIONPENDING) {
        callback.Run(rv);
        return;
      }
    } else {
      ShowErrorMessage("File too big", PP_ERROR_FILETOOBIG);
      delete request;
      return;
    }
  }	
	

SaveWriteCallback() is called after Write() completes.

  • If the Write() fails, the callback deletes the pending write requests and returns.
  • If the Write() succeeds, the callback advances the buffer pointer and calls Write() again if more bytes remain to be written.When all bytes have been written, the callback calls Flush().

void SaveWriteCallback(int32_t bytes_written,
    FileIoInstance::Request* request) {
  // bytes_written is the error code if < 0
  if (bytes_written < 0) {
    ShowErrorMessage("File write failed", bytes_written);
    delete request;
    return;
  }

  // Ensure the content length is something write() can handle
  assert(request->file_contents.length() <= INT32_MAX);

  request->offset += bytes_written;

  if (request->offset == request->file_contents.length() ||
      bytes_written == 0) {
    // All bytes have been written, flush the write buffer to complete
    pp::CompletionCallback callback = callback_factory_.NewCallback(
        &FileIoInstance::SaveFlushCallback, request);
    int32_t rv = request->file.Flush(callback);

    // Handle cleanup in the callback if error
    if (rv != PP_OK_COMPLETIONPENDING) {
      callback.Run(rv);
      return;
    }
  } else {
    // If all the bytes haven't been written call write again with remainder
    pp::CompletionCallback callback = callback_factory_.NewCallback(
        &FileIoInstance::SaveWriteCallback, request);
    int32_t rv = request->file.Write(request->offset,
        request->file_contents.c_str() + request->offset,
        request->file_contents.length() - request->offset, callback);

    // Handle cleanup in the callback if error
    if (rv != PP_OK_COMPLETIONPENDING) {
      callback.Run(rv);
      return;
    }
  }
}

Opening a file

Load() is called when the Load button is pressed by the user. Load() creates a file reference using the FileSystem objected created in Init(). Load() calls Open() to open the file using the PP_FILEOPENFLAG_READ flag.

	
 bool Load(const std::string& file_name) {
    if (!file_system_ready_) {
      ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
      return false;
    }

    FileIoInstance::Request* request = new FileIoInstance::Request;
    request->ref = pp::FileRef(file_system_, file_name.c_str());
    request->file = pp::FileIO(this);

    pp::CompletionCallback callback = callback_factory_.NewCallback(
          &FileIoInstance::LoadOpenCallback, request);
    int32_t rv = request->file.Open(request->ref, PP_FILEOPENFLAG_READ,
        callback);

    // Handle cleanup in the callback if error
    if (rv != PP_OK_COMPLETIONPENDING) {
      callback.Run(rv);
      return false;
    }
    return true;
  }	 

LoadOpenCallback() is executed once Open() is complete. LoadOpenCallback() ensures the file is found and is opened. If the file is not found or cannot be opened, the request is deleted and control is given back to the user. If the file is found and read, Query() is called to query information about the file.

	
 void LoadOpenCallback(int32_t result, FileIoInstance::Request* request) {
    if (result == PP_ERROR_FILENOTFOUND) {
      ShowStatusMessage("File not found");
      delete request;
      return;
    } else if (result != PP_OK) {
      ShowErrorMessage("File open for read failed", result);
      delete request;
      return;
    }

    pp::CompletionCallback callback = callback_factory_.NewCallback(
          &FileIoInstance::LoadQueryCallback, request);
    int32_t rv = request->file.Query(&request->info, callback);

    // Handle cleanup in the callback if error
    if (rv != PP_OK_COMPLETIONPENDING) {
      callback.Run(rv);
      return;
    }
  }

LoadQueryCallback() is called when Query() completes to ensure the query was a success. If the query isn't a success, the request is deleted and control is given back to the user. If the query is successful, Read() is called to read the contents of the file.


void LoadQueryCallback(int32_t result, FileIoInstance::Request* request) {
    if (result != PP_OK) {
      ShowErrorMessage("File query failed", result);
      delete request;
      return;
    }

    // FileIO.Read() can only handle int32 sizes
    if (request->info.size > INT32_MAX) {
      ShowErrorMessage("File too big", PP_ERROR_FILETOOBIG);
      delete request;
      return;
    }

    // Allocate a buffer to read the file into
    // Here we must allocate on the heap so FileIO::Read will write to this
    // one and only copy of file_contents
    request->file_contents.resize(request->info.size, '\0');
    request->offset = 0;

    pp::CompletionCallback callback = callback_factory_.NewCallback(
        &FileIoInstance::LoadReadCallback, request);
    int32_t rv = request->file.Read(request->offset,
        &request->file_contents[request->offset],
        request->file_contents.length(), callback);

    // Handle cleanup in the callback if error
    if (rv != PP_OK_COMPLETIONPENDING) {
      callback.Run(rv);
      return;
    }
  }

LoadReadCallback() is called when Read() completes to ensure the query was a success. If the bytes read are < 0, the request is deleted and control is given back to the user, otherwise LoadReadCallback() either:

  • Calls PostMessage() to display the contents of the file on the web page (if all bytes have been read)
  • Calls Read() again to read the rest of the bytes.

Deleting a file

Delete() is called when the Delete button is pressed by the user. Delete() creates a file reference using the FileSystem objected created in Init(). Delete() calls Delete() to delete the file.


   bool Delete(const std::string& file_name) {
    if (!file_system_ready_) {
      ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
      return false;
    }

    FileIoInstance::Request* request = new FileIoInstance::Request;
    request->ref = pp::FileRef(file_system_, file_name.c_str());

    pp::CompletionCallback callback = callback_factory_.NewCallback(
        &FileIoInstance::DeleteCallback, request);
    int32_t rv = request->ref.Delete(callback);

    // Handle cleanup in the callback if error
    if (rv != PP_OK_COMPLETIONPENDING) {
      callback.Run(rv);
      return false;
    }
    return true;
  }

DeleteCallback() is executed once Delete() is complete. DeleteCallback() ensures the file is found and is deleted. If the file is not found or the deletion fails, the request is deleted and control is given back to the user. If the file is found and deleted, a "File deleted" status message appears and control is given back to the user.

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.