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";
44const 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"
53class 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".
342class 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
355namespace 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.
361Module* CreateModule() { return new FileIoModule(); }
362}  // namespace pp
363