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
15 #include "ppapi/c/pp_stdint.h"
16 #include "ppapi/c/ppb_file_io.h"
17 #include "ppapi/cpp/directory_entry.h"
18 #include "ppapi/cpp/file_io.h"
19 #include "ppapi/cpp/file_ref.h"
20 #include "ppapi/cpp/file_system.h"
21 #include "ppapi/cpp/instance.h"
22 #include "ppapi/cpp/message_loop.h"
23 #include "ppapi/cpp/module.h"
24 #include "ppapi/cpp/var.h"
25 #include "ppapi/cpp/var_array.h"
26 #include "ppapi/utility/completion_callback_factory.h"
27 #include "ppapi/utility/threading/simple_thread.h"
30 #define INT32_MAX (0x7FFFFFFF)
38 // Allow 'this' in initializer list
39 #pragma warning(disable : 4355)
43 typedef std::vector
<std::string
> StringVector
;
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
50 /// type="application/x-nacl"
52 class FileIoInstance
: public pp::Instance
{
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),
63 virtual ~FileIoInstance() { file_thread_
.Join(); }
65 virtual bool Init(uint32_t /*argc*/,
66 const char * /*argn*/ [],
67 const char * /*argv*/ []) {
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
));
79 pp::CompletionCallbackFactory
<FileIoInstance
> callback_factory_
;
80 pp::FileSystem file_system_
;
82 // Indicates whether file_system_ was opened successfully. We only read/write
83 // this on the file_thread_.
84 bool file_system_ready_
;
86 // We do all our file operations on the file_thread_.
87 pp::SimpleThread file_thread_
;
89 void PostArrayMessage(const char* command
, const StringVector
& strings
) {
91 message
.Set(0, command
);
92 for (size_t i
= 0; i
< strings
.size(); ++i
) {
93 message
.Set(i
+ 1, strings
[i
]);
99 void PostArrayMessage(const char* command
) {
100 PostArrayMessage(command
, StringVector());
103 void PostArrayMessage(const char* command
, std::string s
) {
106 PostArrayMessage(command
, sv
);
109 /// Handler for messages coming in from the browser via postMessage(). The
110 /// @a var_message can contain anything: a JSON string; a string that encodes
111 /// method names and arguments; etc.
113 /// Here we use messages to communicate with the user interface
115 /// @param[in] var_message The message posted by the browser.
116 virtual void HandleMessage(const pp::Var
& var_message
) {
117 if (!var_message
.is_array())
120 // Message should be an array with the following elements:
121 // [command, path, extra args]
122 pp::VarArray
message(var_message
);
123 std::string command
= message
.Get(0).AsString();
124 std::string file_name
= message
.Get(1).AsString();
126 if (file_name
.length() == 0 || file_name
[0] != '/') {
127 ShowStatusMessage("File name must begin with /");
131 printf("command: %s file_name: %s\n", command
.c_str(), file_name
.c_str());
133 if (command
== "load") {
134 file_thread_
.message_loop().PostWork(
135 callback_factory_
.NewCallback(&FileIoInstance::Load
, file_name
));
136 } else if (command
== "save") {
137 std::string file_text
= message
.Get(2).AsString();
138 file_thread_
.message_loop().PostWork(callback_factory_
.NewCallback(
139 &FileIoInstance::Save
, file_name
, file_text
));
140 } else if (command
== "delete") {
141 file_thread_
.message_loop().PostWork(
142 callback_factory_
.NewCallback(&FileIoInstance::Delete
, file_name
));
143 } else if (command
== "list") {
144 const std::string
& dir_name
= file_name
;
145 file_thread_
.message_loop().PostWork(
146 callback_factory_
.NewCallback(&FileIoInstance::List
, dir_name
));
147 } else if (command
== "makedir") {
148 const std::string
& dir_name
= file_name
;
149 file_thread_
.message_loop().PostWork(
150 callback_factory_
.NewCallback(&FileIoInstance::MakeDir
, dir_name
));
151 } else if (command
== "rename") {
152 const std::string new_name
= message
.Get(2).AsString();
153 file_thread_
.message_loop().PostWork(callback_factory_
.NewCallback(
154 &FileIoInstance::Rename
, file_name
, new_name
));
158 void OpenFileSystem(int32_t /* result */) {
159 int32_t rv
= file_system_
.Open(1024 * 1024, pp::BlockUntilComplete());
161 file_system_ready_
= true;
162 // Notify the user interface that we're ready
163 PostArrayMessage("READY");
165 ShowErrorMessage("Failed to open file system", rv
);
169 void Save(int32_t /* result */,
170 const std::string
& file_name
,
171 const std::string
& file_contents
) {
172 if (!file_system_ready_
) {
173 ShowErrorMessage("File system is not open", PP_ERROR_FAILED
);
176 pp::FileRef
ref(file_system_
, file_name
.c_str());
177 pp::FileIO
file(this);
179 int32_t open_result
=
181 PP_FILEOPENFLAG_WRITE
| PP_FILEOPENFLAG_CREATE
|
182 PP_FILEOPENFLAG_TRUNCATE
,
183 pp::BlockUntilComplete());
184 if (open_result
!= PP_OK
) {
185 ShowErrorMessage("File open for write failed", open_result
);
189 // We have truncated the file to 0 bytes. So we need only write if
190 // file_contents is non-empty.
191 if (!file_contents
.empty()) {
192 if (file_contents
.length() > INT32_MAX
) {
193 ShowErrorMessage("File too big", PP_ERROR_FILETOOBIG
);
197 int32_t bytes_written
= 0;
199 bytes_written
= file
.Write(offset
,
200 file_contents
.data() + offset
,
201 file_contents
.length(),
202 pp::BlockUntilComplete());
203 if (bytes_written
> 0) {
204 offset
+= bytes_written
;
206 ShowErrorMessage("File write failed", bytes_written
);
209 } while (bytes_written
< static_cast<int64_t>(file_contents
.length()));
211 // All bytes have been written, flush the write buffer to complete
212 int32_t flush_result
= file
.Flush(pp::BlockUntilComplete());
213 if (flush_result
!= PP_OK
) {
214 ShowErrorMessage("File fail to flush", flush_result
);
217 ShowStatusMessage("Save success");
220 void Load(int32_t /* result */, const std::string
& file_name
) {
221 if (!file_system_ready_
) {
222 ShowErrorMessage("File system is not open", PP_ERROR_FAILED
);
225 pp::FileRef
ref(file_system_
, file_name
.c_str());
226 pp::FileIO
file(this);
228 int32_t open_result
=
229 file
.Open(ref
, PP_FILEOPENFLAG_READ
, pp::BlockUntilComplete());
230 if (open_result
== PP_ERROR_FILENOTFOUND
) {
231 ShowErrorMessage("File not found", open_result
);
233 } else if (open_result
!= PP_OK
) {
234 ShowErrorMessage("File open for read failed", open_result
);
238 int32_t query_result
= file
.Query(&info
, pp::BlockUntilComplete());
239 if (query_result
!= PP_OK
) {
240 ShowErrorMessage("File query failed", query_result
);
243 // FileIO.Read() can only handle int32 sizes
244 if (info
.size
> INT32_MAX
) {
245 ShowErrorMessage("File too big", PP_ERROR_FILETOOBIG
);
249 std::vector
<char> data(info
.size
);
251 int32_t bytes_read
= 0;
252 int32_t bytes_to_read
= info
.size
;
253 while (bytes_to_read
> 0) {
254 bytes_read
= file
.Read(offset
,
256 data
.size() - offset
,
257 pp::BlockUntilComplete());
258 if (bytes_read
> 0) {
259 offset
+= bytes_read
;
260 bytes_to_read
-= bytes_read
;
261 } else if (bytes_read
< 0) {
262 // If bytes_read < PP_OK then it indicates the error code.
263 ShowErrorMessage("File read failed", bytes_read
);
267 // Done reading, send content to the user interface
268 std::string
string_data(data
.begin(), data
.end());
269 PostArrayMessage("DISP", string_data
);
270 ShowStatusMessage("Load success");
273 void Delete(int32_t /* result */, const std::string
& file_name
) {
274 if (!file_system_ready_
) {
275 ShowErrorMessage("File system is not open", PP_ERROR_FAILED
);
278 pp::FileRef
ref(file_system_
, file_name
.c_str());
280 int32_t result
= ref
.Delete(pp::BlockUntilComplete());
281 if (result
== PP_ERROR_FILENOTFOUND
) {
282 ShowStatusMessage("File/Directory not found");
284 } else if (result
!= PP_OK
) {
285 ShowErrorMessage("Deletion failed", result
);
288 ShowStatusMessage("Delete success");
291 void List(int32_t /* result */, const std::string
& dir_name
) {
292 if (!file_system_ready_
) {
293 ShowErrorMessage("File system is not open", PP_ERROR_FAILED
);
297 pp::FileRef
ref(file_system_
, dir_name
.c_str());
299 // Pass ref along to keep it alive.
300 ref
.ReadDirectoryEntries(callback_factory_
.NewCallbackWithOutput(
301 &FileIoInstance::ListCallback
, ref
));
304 void ListCallback(int32_t result
,
305 const std::vector
<pp::DirectoryEntry
>& entries
,
306 pp::FileRef
/* unused_ref */) {
307 if (result
!= PP_OK
) {
308 ShowErrorMessage("List failed", result
);
313 for (size_t i
= 0; i
< entries
.size(); ++i
) {
314 pp::Var name
= entries
[i
].file_ref().GetName();
315 if (name
.is_string()) {
316 sv
.push_back(name
.AsString());
319 PostArrayMessage("LIST", sv
);
320 ShowStatusMessage("List success");
323 void MakeDir(int32_t /* result */, const std::string
& dir_name
) {
324 if (!file_system_ready_
) {
325 ShowErrorMessage("File system is not open", PP_ERROR_FAILED
);
328 pp::FileRef
ref(file_system_
, dir_name
.c_str());
330 int32_t result
= ref
.MakeDirectory(
331 PP_MAKEDIRECTORYFLAG_NONE
, pp::BlockUntilComplete());
332 if (result
!= PP_OK
) {
333 ShowErrorMessage("Make directory failed", result
);
336 ShowStatusMessage("Make directory success");
339 void Rename(int32_t /* result */,
340 const std::string
& old_name
,
341 const std::string
& new_name
) {
342 if (!file_system_ready_
) {
343 ShowErrorMessage("File system is not open", PP_ERROR_FAILED
);
347 pp::FileRef
ref_old(file_system_
, old_name
.c_str());
348 pp::FileRef
ref_new(file_system_
, new_name
.c_str());
350 int32_t result
= ref_old
.Rename(ref_new
, pp::BlockUntilComplete());
351 if (result
!= PP_OK
) {
352 ShowErrorMessage("Rename failed", result
);
355 ShowStatusMessage("Rename success");
358 /// Encapsulates our simple javascript communication protocol
359 void ShowErrorMessage(const std::string
& message
, int32_t result
) {
360 std::stringstream ss
;
361 ss
<< message
<< " -- Error #: " << result
;
362 PostArrayMessage("ERR", ss
.str());
365 void ShowStatusMessage(const std::string
& message
) {
366 PostArrayMessage("STAT", message
);
370 /// The Module class. The browser calls the CreateInstance() method to create
371 /// an instance of your NaCl module on the web page. The browser creates a new
372 /// instance for each <embed> tag with type="application/x-nacl".
373 class FileIoModule
: public pp::Module
{
375 FileIoModule() : pp::Module() {}
376 virtual ~FileIoModule() {}
378 /// Create and return a FileIoInstance object.
379 /// @param[in] instance The browser-side instance.
380 /// @return the plugin-side instance.
381 virtual pp::Instance
* CreateInstance(PP_Instance instance
) {
382 return new FileIoInstance(instance
);
387 /// Factory function called by the browser when the module is first loaded.
388 /// The browser keeps a singleton of this module. It calls the
389 /// CreateInstance() method on the object you return to make instances. There
390 /// is one instance per <embed> tag on the page. This is the main binding
391 /// point for your NaCl module with the browser.
392 Module
* CreateModule() { return new FileIoModule(); }