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