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