Home | History | Annotate | Download | only in file_io
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /// @file file_io.cc
      6 /// This example demonstrates the use of persistent file I/O
      7 
      8 #define __STDC_LIMIT_MACROS
      9 #include <sstream>
     10 #include <string>
     11 
     12 #include "ppapi/c/pp_stdint.h"
     13 #include "ppapi/c/ppb_file_io.h"
     14 #include "ppapi/cpp/directory_entry.h"
     15 #include "ppapi/cpp/file_io.h"
     16 #include "ppapi/cpp/file_ref.h"
     17 #include "ppapi/cpp/file_system.h"
     18 #include "ppapi/cpp/instance.h"
     19 #include "ppapi/cpp/message_loop.h"
     20 #include "ppapi/cpp/module.h"
     21 #include "ppapi/cpp/var.h"
     22 #include "ppapi/utility/completion_callback_factory.h"
     23 #include "ppapi/utility/threading/simple_thread.h"
     24 
     25 #ifndef INT32_MAX
     26 #define INT32_MAX (0x7FFFFFFF)
     27 #endif
     28 
     29 #ifdef WIN32
     30 #undef min
     31 #undef max
     32 #undef PostMessage
     33 
     34 // Allow 'this' in initializer list
     35 #pragma warning(disable : 4355)
     36 #endif
     37 
     38 namespace {
     39 /// Used for our simple protocol to communicate with Javascript
     40 const char* const kLoadPrefix = "ld";
     41 const char* const kSavePrefix = "sv";
     42 const char* const kDeletePrefix = "de";
     43 const char* const kListPrefix = "ls";
     44 const char* const kMakeDirPrefix = "md";
     45 }
     46 
     47 /// The Instance class.  One of these exists for each instance of your NaCl
     48 /// module on the web page.  The browser will ask the Module object to create
     49 /// a new Instance for each occurrence of the <embed> tag that has these
     50 /// attributes:
     51 ///     type="application/x-nacl"
     52 ///     src="file_io.nmf"
     53 class FileIoInstance : public pp::Instance {
     54  public:
     55   /// The constructor creates the plugin-side instance.
     56   /// @param[in] instance the handle to the browser-side plugin instance.
     57   explicit FileIoInstance(PP_Instance instance)
     58       : pp::Instance(instance),
     59         callback_factory_(this),
     60         file_system_(this, PP_FILESYSTEMTYPE_LOCALPERSISTENT),
     61         file_system_ready_(false),
     62         file_thread_(this) {}
     63 
     64   virtual ~FileIoInstance() { file_thread_.Join(); }
     65 
     66   virtual bool Init(uint32_t /*argc*/,
     67                     const char * /*argn*/ [],
     68                     const char * /*argv*/ []) {
     69     file_thread_.Start();
     70     // Open the file system on the file_thread_. Since this is the first
     71     // operation we perform there, and because we do everything on the
     72     // file_thread_ synchronously, this ensures that the FileSystem is open
     73     // before any FileIO operations execute.
     74     file_thread_.message_loop().PostWork(
     75         callback_factory_.NewCallback(&FileIoInstance::OpenFileSystem));
     76     return true;
     77   }
     78 
     79  private:
     80   pp::CompletionCallbackFactory<FileIoInstance> callback_factory_;
     81   pp::FileSystem file_system_;
     82 
     83   // Indicates whether file_system_ was opened successfully. We only read/write
     84   // this on the file_thread_.
     85   bool file_system_ready_;
     86 
     87   // We do all our file operations on the file_thread_.
     88   pp::SimpleThread file_thread_;
     89 
     90   /// Handler for messages coming in from the browser via postMessage().  The
     91   /// @a var_message can contain anything: a JSON string; a string that encodes
     92   /// method names and arguments; etc.
     93   ///
     94   /// Here we use messages to communicate with the user interface
     95   ///
     96   /// @param[in] var_message The message posted by the browser.
     97   virtual void HandleMessage(const pp::Var& var_message) {
     98     if (!var_message.is_string())
     99       return;
    100 
    101     // Parse message into: instruction file_name_length file_name [file_text]
    102     std::string message = var_message.AsString();
    103     std::string instruction;
    104     std::string file_name;
    105     std::stringstream reader(message);
    106     int file_name_length;
    107 
    108     reader >> instruction >> file_name_length;
    109     file_name.resize(file_name_length);
    110     reader.ignore(1);  // Eat the delimiter
    111     reader.read(&file_name[0], file_name_length);
    112 
    113     if (file_name.length() == 0 || file_name[0] != '/') {
    114       ShowStatusMessage("File name must begin with /");
    115       return;
    116     }
    117 
    118     // Dispatch the instruction
    119     if (instruction == kLoadPrefix) {
    120       file_thread_.message_loop().PostWork(
    121           callback_factory_.NewCallback(&FileIoInstance::Load, file_name));
    122     } else if (instruction == kSavePrefix) {
    123       // Read the rest of the message as the file text
    124       reader.ignore(1);  // Eat the delimiter
    125       std::string file_text = message.substr(reader.tellg());
    126       file_thread_.message_loop().PostWork(callback_factory_.NewCallback(
    127           &FileIoInstance::Save, file_name, file_text));
    128     } else if (instruction == kDeletePrefix) {
    129       file_thread_.message_loop().PostWork(
    130           callback_factory_.NewCallback(&FileIoInstance::Delete, file_name));
    131     } else if (instruction == kListPrefix) {
    132       const std::string& dir_name = file_name;
    133       file_thread_.message_loop().PostWork(
    134           callback_factory_.NewCallback(&FileIoInstance::List, dir_name));
    135     } else if (instruction == kMakeDirPrefix) {
    136       const std::string& dir_name = file_name;
    137       file_thread_.message_loop().PostWork(
    138           callback_factory_.NewCallback(&FileIoInstance::MakeDir, dir_name));
    139     }
    140   }
    141 
    142   void OpenFileSystem(int32_t /* result */) {
    143     int32_t rv = file_system_.Open(1024 * 1024, pp::BlockUntilComplete());
    144     if (rv == PP_OK) {
    145       file_system_ready_ = true;
    146       // Notify the user interface that we're ready
    147       PostMessage("READY|");
    148     } else {
    149       ShowErrorMessage("Failed to open file system", rv);
    150     }
    151   }
    152 
    153   void Save(int32_t /* result */,
    154             const std::string& file_name,
    155             const std::string& file_contents) {
    156     if (!file_system_ready_) {
    157       ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
    158       return;
    159     }
    160     pp::FileRef ref(file_system_, file_name.c_str());
    161     pp::FileIO file(this);
    162 
    163     int32_t open_result =
    164         file.Open(ref,
    165                   PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_CREATE |
    166                       PP_FILEOPENFLAG_TRUNCATE,
    167                   pp::BlockUntilComplete());
    168     if (open_result != PP_OK) {
    169       ShowErrorMessage("File open for write failed", open_result);
    170       return;
    171     }
    172 
    173     // We have truncated the file to 0 bytes. So we need only write if
    174     // file_contents is non-empty.
    175     if (!file_contents.empty()) {
    176       if (file_contents.length() > INT32_MAX) {
    177         ShowErrorMessage("File too big", PP_ERROR_FILETOOBIG);
    178         return;
    179       }
    180       int64_t offset = 0;
    181       int32_t bytes_written = 0;
    182       do {
    183         bytes_written = file.Write(offset,
    184                                    file_contents.data() + offset,
    185                                    file_contents.length(),
    186                                    pp::BlockUntilComplete());
    187         if (bytes_written > 0) {
    188           offset += bytes_written;
    189         } else {
    190           ShowErrorMessage("File write failed", bytes_written);
    191           return;
    192         }
    193       } while (bytes_written < static_cast<int64_t>(file_contents.length()));
    194     }
    195     // All bytes have been written, flush the write buffer to complete
    196     int32_t flush_result = file.Flush(pp::BlockUntilComplete());
    197     if (flush_result != PP_OK) {
    198       ShowErrorMessage("File fail to flush", flush_result);
    199       return;
    200     }
    201     ShowStatusMessage("Save success");
    202   }
    203 
    204   void Load(int32_t /* result */, const std::string& file_name) {
    205     if (!file_system_ready_) {
    206       ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
    207       return;
    208     }
    209     pp::FileRef ref(file_system_, file_name.c_str());
    210     pp::FileIO file(this);
    211 
    212     int32_t open_result =
    213         file.Open(ref, PP_FILEOPENFLAG_READ, pp::BlockUntilComplete());
    214     if (open_result == PP_ERROR_FILENOTFOUND) {
    215       ShowErrorMessage("File not found", open_result);
    216       return;
    217     } else if (open_result != PP_OK) {
    218       ShowErrorMessage("File open for read failed", open_result);
    219       return;
    220     }
    221     PP_FileInfo info;
    222     int32_t query_result = file.Query(&info, pp::BlockUntilComplete());
    223     if (query_result != PP_OK) {
    224       ShowErrorMessage("File query failed", query_result);
    225       return;
    226     }
    227     // FileIO.Read() can only handle int32 sizes
    228     if (info.size > INT32_MAX) {
    229       ShowErrorMessage("File too big", PP_ERROR_FILETOOBIG);
    230       return;
    231     }
    232 
    233     std::vector<char> data(info.size);
    234     int64_t offset = 0;
    235     int32_t bytes_read = 0;
    236     int32_t bytes_to_read = info.size;
    237     while (bytes_to_read > 0) {
    238       bytes_read = file.Read(offset,
    239                              &data[offset],
    240                              data.size() - offset,
    241                              pp::BlockUntilComplete());
    242       if (bytes_read > 0) {
    243         offset += bytes_read;
    244         bytes_to_read -= bytes_read;
    245       } else if (bytes_read < 0) {
    246         // If bytes_read < PP_OK then it indicates the error code.
    247         ShowErrorMessage("File read failed", bytes_read);
    248         return;
    249       }
    250     }
    251     // Done reading, send content to the user interface
    252     std::string string_data(data.begin(), data.end());
    253     PostMessage("DISP|" + string_data);
    254     ShowStatusMessage("Load success");
    255   }
    256 
    257   void Delete(int32_t /* result */, const std::string& file_name) {
    258     if (!file_system_ready_) {
    259       ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
    260       return;
    261     }
    262     pp::FileRef ref(file_system_, file_name.c_str());
    263 
    264     int32_t result = ref.Delete(pp::BlockUntilComplete());
    265     if (result == PP_ERROR_FILENOTFOUND) {
    266       ShowStatusMessage("File/Directory not found");
    267       return;
    268     } else if (result != PP_OK) {
    269       ShowErrorMessage("Deletion failed", result);
    270       return;
    271     }
    272     ShowStatusMessage("Delete success");
    273   }
    274 
    275   void List(int32_t /* result */, const std::string& dir_name) {
    276     if (!file_system_ready_) {
    277       ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
    278       return;
    279     }
    280 
    281     pp::FileRef ref(file_system_, dir_name.c_str());
    282 
    283     // Pass ref along to keep it alive.
    284     ref.ReadDirectoryEntries(callback_factory_.NewCallbackWithOutput(
    285         &FileIoInstance::ListCallback, ref));
    286   }
    287 
    288   void ListCallback(int32_t result,
    289                     const std::vector<pp::DirectoryEntry>& entries,
    290                     pp::FileRef /* unused_ref */) {
    291     if (result != PP_OK) {
    292       ShowErrorMessage("List failed", result);
    293       return;
    294     }
    295 
    296     std::stringstream ss;
    297     ss << "LIST";
    298     for (size_t i = 0; i < entries.size(); ++i) {
    299       pp::Var name = entries[i].file_ref().GetName();
    300       if (name.is_string()) {
    301         ss << "|" << name.AsString();
    302       }
    303     }
    304     PostMessage(ss.str());
    305     ShowStatusMessage("List success");
    306   }
    307 
    308   void MakeDir(int32_t /* result */, const std::string& dir_name) {
    309     if (!file_system_ready_) {
    310       ShowErrorMessage("File system is not open", PP_ERROR_FAILED);
    311       return;
    312     }
    313     pp::FileRef ref(file_system_, dir_name.c_str());
    314 
    315     int32_t result = ref.MakeDirectory(
    316         PP_MAKEDIRECTORYFLAG_NONE, pp::BlockUntilComplete());
    317     if (result != PP_OK) {
    318       ShowErrorMessage("Make directory failed", result);
    319       return;
    320     }
    321     ShowStatusMessage("Make directory success");
    322   }
    323 
    324   /// Encapsulates our simple javascript communication protocol
    325   void ShowErrorMessage(const std::string& message, int32_t result) {
    326     std::stringstream ss;
    327     ss << "ERR|" << message << " -- Error #: " << result;
    328     PostMessage(ss.str());
    329   }
    330 
    331   /// Encapsulates our simple javascript communication protocol
    332   void ShowStatusMessage(const std::string& message) {
    333     std::stringstream ss;
    334     ss << "STAT|" << message;
    335     PostMessage(ss.str());
    336   }
    337 };
    338 
    339 /// The Module class.  The browser calls the CreateInstance() method to create
    340 /// an instance of your NaCl module on the web page.  The browser creates a new
    341 /// instance for each <embed> tag with type="application/x-nacl".
    342 class FileIoModule : public pp::Module {
    343  public:
    344   FileIoModule() : pp::Module() {}
    345   virtual ~FileIoModule() {}
    346 
    347   /// Create and return a FileIoInstance object.
    348   /// @param[in] instance The browser-side instance.
    349   /// @return the plugin-side instance.
    350   virtual pp::Instance* CreateInstance(PP_Instance instance) {
    351     return new FileIoInstance(instance);
    352   }
    353 };
    354 
    355 namespace pp {
    356 /// Factory function called by the browser when the module is first loaded.
    357 /// The browser keeps a singleton of this module.  It calls the
    358 /// CreateInstance() method on the object you return to make instances.  There
    359 /// is one instance per <embed> tag on the page.  This is the main binding
    360 /// point for your NaCl module with the browser.
    361 Module* CreateModule() { return new FileIoModule(); }
    362 }  // namespace pp
    363