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.
6 /// This example demonstrates the use of persistent file I/O
8 #define __STDC_LIMIT_MACROS
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"
26 #define INT32_MAX (0x7FFFFFFF)
34 // Allow 'this' in initializer list
35 #pragma warning(disable : 4355)
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";
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
51 /// type="application/x-nacl"
53 class FileIoInstance
: public pp::Instance
{
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),
64 virtual ~FileIoInstance() { file_thread_
.Join(); }
66 virtual bool Init(uint32_t /*argc*/,
67 const char * /*argn*/ [],
68 const char * /*argv*/ []) {
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
));
80 pp::CompletionCallbackFactory
<FileIoInstance
> callback_factory_
;
81 pp::FileSystem file_system_
;
83 // Indicates whether file_system_ was opened successfully. We only read/write
84 // this on the file_thread_.
85 bool file_system_ready_
;
87 // We do all our file operations on the file_thread_.
88 pp::SimpleThread file_thread_
;
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.
94 /// Here we use messages to communicate with the user interface
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())
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
;
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
);
113 if (file_name
.length() == 0 || file_name
[0] != '/') {
114 ShowStatusMessage("File name must begin with /");
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
));
142 void OpenFileSystem(int32_t /* result */) {
143 int32_t rv
= file_system_
.Open(1024 * 1024, pp::BlockUntilComplete());
145 file_system_ready_
= true;
146 // Notify the user interface that we're ready
147 PostMessage("READY|");
149 ShowErrorMessage("Failed to open file system", rv
);
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
);
160 pp::FileRef
ref(file_system_
, file_name
.c_str());
161 pp::FileIO
file(this);
163 int32_t open_result
=
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
);
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
);
181 int32_t bytes_written
= 0;
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
;
190 ShowErrorMessage("File write failed", bytes_written
);
193 } while (bytes_written
< static_cast<int64_t>(file_contents
.length()));
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
);
201 ShowStatusMessage("Save success");
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
);
209 pp::FileRef
ref(file_system_
, file_name
.c_str());
210 pp::FileIO
file(this);
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
);
217 } else if (open_result
!= PP_OK
) {
218 ShowErrorMessage("File open for read failed", open_result
);
222 int32_t query_result
= file
.Query(&info
, pp::BlockUntilComplete());
223 if (query_result
!= PP_OK
) {
224 ShowErrorMessage("File query failed", query_result
);
227 // FileIO.Read() can only handle int32 sizes
228 if (info
.size
> INT32_MAX
) {
229 ShowErrorMessage("File too big", PP_ERROR_FILETOOBIG
);
233 std::vector
<char> data(info
.size
);
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
,
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
);
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");
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
);
262 pp::FileRef
ref(file_system_
, file_name
.c_str());
264 int32_t result
= ref
.Delete(pp::BlockUntilComplete());
265 if (result
== PP_ERROR_FILENOTFOUND
) {
266 ShowStatusMessage("File/Directory not found");
268 } else if (result
!= PP_OK
) {
269 ShowErrorMessage("Deletion failed", result
);
272 ShowStatusMessage("Delete success");
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
);
281 pp::FileRef
ref(file_system_
, dir_name
.c_str());
283 // Pass ref along to keep it alive.
284 ref
.ReadDirectoryEntries(callback_factory_
.NewCallbackWithOutput(
285 &FileIoInstance::ListCallback
, ref
));
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
);
296 std::stringstream ss
;
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();
304 PostMessage(ss
.str());
305 ShowStatusMessage("List success");
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
);
313 pp::FileRef
ref(file_system_
, dir_name
.c_str());
315 int32_t result
= ref
.MakeDirectory(
316 PP_MAKEDIRECTORYFLAG_NONE
, pp::BlockUntilComplete());
317 if (result
!= PP_OK
) {
318 ShowErrorMessage("Make directory failed", result
);
321 ShowStatusMessage("Make directory success");
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());
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());
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
{
344 FileIoModule() : pp::Module() {}
345 virtual ~FileIoModule() {}
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
);
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(); }