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