Add Tizen Filesystem API.
authorLeandro Pereira <leandro.pereira@intel.com>
Mon, 26 Aug 2013 19:37:10 +0000 (16:37 -0300)
committerLeandro Pereira <leandro.pereira@intel.com>
Tue, 24 Sep 2013 19:15:13 +0000 (16:15 -0300)
This commit implements most of the Tizen Filesystem API.

Some things were intentionally left unimplemented, such as listing and
obtaining stoarage information. These will be implemented as soon as the
need to pass all TCT tests arise.

File I/O works, as evidenced by the new filesystem.html example (a crude
text editor) -- although this example doesn't showcase the whole API.

examples/filesystem.html [new file with mode: 0644]
examples/index.html
filesystem/filesystem.gyp [new file with mode: 0644]
filesystem/filesystem_api.js [new file with mode: 0644]
filesystem/filesystem_context.cc [new file with mode: 0644]
filesystem/filesystem_context.h [new file with mode: 0644]
tizen-wrt.gyp

diff --git a/examples/filesystem.html b/examples/filesystem.html
new file mode 100644 (file)
index 0000000..1e55aec
--- /dev/null
@@ -0,0 +1,108 @@
+<h2>Tizen-WRT Text Editor</h2>
+
+<body>
+
+<input type="text" size=50 id="path">
+<button id="save" onclick="load()">Load</button>
+<button id="save" onclick="save()">Save</button>
+<button id="save" onclick="del()">Delete</button>
+<br>
+
+<h1 id="filename">Untitled</h1>
+<h2 id="lastmodified">Last modified: now</h2>
+<h2 id="size">Size: unknown</h2>
+
+<hr>
+
+<textarea cols=50 rows=20 id="editor"></textarea>
+
+<script>
+
+var editor = document.getElementById('editor');
+
+function updateStatusInfo(file) {
+   document.title = document.getElementById("filename").innerText = file.fullPath;
+   document.getElementById("lastmodified").innerText = 'Last modified: ' + file.modified;
+   document.getElementById("size").innerText = 'Size: ' + file.fileSize + 'bytes';
+}
+
+function load() {
+   var path = document.getElementById("path").value;
+   if (path == '') {
+      console.log('Type a file path and try again');
+      return;
+   }
+   try {
+      var file = new tizen.filesystem.File(path);
+      file.readAsText(function(contents) {
+         editor.value = contents;
+
+         updateStatusInfo(this);
+      }.bind(file),
+      function(error) {
+         console.log(error);
+      });
+   } catch (e) {
+      console.log(e);
+   }
+}
+
+function save() {
+   var path = document.getElementById("path").value;
+   if (path == '') {
+      console.log('Type a file path and try again');
+      return;
+   }
+   try {
+      var file = new tizen.filesystem.File(path);
+      file.openStream('w',
+         function(stream) {
+            stream.write(editor.value);
+            stream.close();
+
+            updateStatusInfo(this);
+         }.bind(file),
+         function(error) {
+            if (this.readOnly) {
+               console.log('File is read-only');
+               return;
+            }
+            if (this.isDirectory) {
+               console.log('Path is a directory');
+               return;
+            }
+            if (!this.isFile) {
+               console.log('Path is not a regular file');
+               return;
+            }
+            console.log('Error while opening file for writing: ' +
+               error);
+         }.bind(file));
+   } catch (e) {
+      console.log(e);
+   }
+}
+
+function del() {
+   var path = document.getElementById("path").value;
+   if (path == '') {
+      console.log('Type a file path and try again');
+      return;
+   }
+   try {
+      var file = new tizen.filesystem.File(path);
+      file.deleteFile('',
+       function() {
+          console.log('Deleted ' + this + ' with success');
+       }.bind(this.filePath),
+       function(error) {
+          console.log('Error while deleting: ' + JSON.strinfigy(error));
+       });
+   } catch (e) {
+      console.log(e);
+   }
+
+}
+
+</script>
+
index bb0cef6..fad5833 100644 (file)
@@ -28,5 +28,6 @@ div.block {
 <a href="power.html"><div class="block">power</div></a>
 <a href="bluetooth.html"><div class="block">bluetooth</div></a>
 <a href="download.html"><div class="block">download</div></a>
+<a href="filesystem.html"><div class="block">filesystem</div></a>
 </body>
 </html>
diff --git a/filesystem/filesystem.gyp b/filesystem/filesystem.gyp
new file mode 100644 (file)
index 0000000..49b28ed
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  'includes':[
+    '../common/common.gypi',
+  ],
+  'targets': [
+    {
+      'target_name': 'tizen_filesystem',
+      'type': 'loadable_module',
+      'sources': [
+        'filesystem_api.js',
+        'filesystem_context.cc',
+        'filesystem_context.h',
+      ],
+    },
+  ],
+}
diff --git a/filesystem/filesystem_api.js b/filesystem/filesystem_api.js
new file mode 100644 (file)
index 0000000..6be74c3
--- /dev/null
@@ -0,0 +1,486 @@
+// Copyright (c) 2013 Intel Corporation. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var _callbacks = {};
+var _next_reply_id = 0;
+
+var getNextReplyId = function() {
+  return _next_reply_id++;
+};
+
+var postMessage = function(msg, callback) {
+  var reply_id = getNextReplyId();
+  _callbacks[reply_id] = callback;
+  msg.reply_id = reply_id;
+  extension.postMessage(JSON.stringify(msg));
+};
+
+extension.setMessageListener(function(json) {
+  var msg = JSON.parse(json);
+  var reply_id = msg.reply_id;
+  var callback = _callbacks[reply_id];
+  if (typeof(callback) === 'function') {
+    callback(msg);
+    delete msg.reply_id;
+    delete _callbacks[reply_id];
+  } else {
+    console.log('Invalid reply_id from Tizen Filesystem: ' + reply_id);
+  }
+});
+
+var sendSyncMessage = function(msg, args) {
+  args = args || {};
+  args.cmd = msg;
+  return JSON.parse(extension.internal.sendSyncMessage(JSON.stringify(args)));
+};
+
+var FileSystemStorage = function(label, type, state) {
+  Object.defineProperties(this, {
+    'label': { writable: false, value: label, enumerable: true },
+    'type': { writable: false, value: type, enumerable: true },
+    'state': { writable: false, value: state, enumerable: true }
+  });
+};
+
+function FileSystemManager() {
+  Object.defineProperty(this, 'maxPathLength', {
+    get: function() {
+      var message = sendSyncMessage('FileSystemManagerGetMaxPathLength');
+      if (message.isError)
+        return 4096;
+      return message.value;
+    },
+    enumerable: true
+  });
+}
+
+FileSystemManager.prototype.resolve = function(location, onsuccess,
+    onerror, mode) {
+  postMessage({
+    cmd: 'FileSystemManagerResolve',
+    location: location,
+    mode: mode
+  }, function(result) {
+    if (result.isError && typeof(onerror) === 'function')
+      onerror(JSON.stringify(result));
+    else if (typeof(onsuccess) === 'function')
+      onsuccess(new File(result.realPath));
+  });
+};
+
+FileSystemManager.prototype.getStorage = function(label, onsuccess, onerror) {
+  postMessage({
+    cmd: 'FileSystemManagerGetStorage',
+    location: location,
+    mode: mode
+  }, function(result) {
+    if (result.error != 0) {
+      if (onerror)
+        onerror(result);
+      else if (onsuccess)
+        onsuccess(new FileSystemStorage(result.label,
+            result.type, result.state));
+    }
+  });
+};
+
+FileSystemManager.prototype.listStorages = function(onsuccess, onerror) {
+  postMessage({
+    cmd: 'FileSystemManagerListStorages',
+    location: location,
+    mode: mode
+  }, function(result) {
+    if (result.error != 0) {
+      if (onerror) {
+        onerror(result);
+      } else if (onsuccess) {
+        var storages = [];
+
+        for (var i = 0; i < result.storages.length; i++) {
+          var storage = results.storages[i];
+          storages.push(new FileSystemStorage(storage.label,
+              storage.type, storage.state));
+        }
+
+        onsuccess(storages);
+      }
+    }
+  });
+};
+
+FileSystemManager.prototype.addStorageStateChangeListener = function(onsuccess, onerror) {
+  /* FIXME(leandro): Implement this. */
+  onsuccess(0);
+};
+
+FileSystemManager.prototype.removeStorageStateChangeListener = function(watchId) {
+  /* FIXME(leandro): Implement this. */
+};
+
+function FileFilter(name, startModified, endModified, startCreated, endCreated) {
+  var self = {
+    toString: function() {
+      return JSON.stringify(this);
+    }
+  };
+  Object.defineProperties(self, {
+    'name': { writable: false, value: name, enumerable: true },
+    'startModified': { writable: false, value: startModified, enumerable: true },
+    'endModified': { writable: false, value: endModified, enumerable: true },
+    'startCreated': { writable: false, value: startCreated, enumerable: true },
+    'endCreated': { writable: false, value: endCreated, enumerable: true }
+  });
+  return self;
+}
+
+function FileStream(fileDescriptor) {
+  this.fileDescriptor = fileDescriptor;
+}
+
+FileStream.prototype.close = function() {
+  sendSyncMessage('FileStreamClose', {
+    fileDescriptor: this.fileDescriptor
+  });
+  this.fileDescriptor = -1;
+};
+
+FileStream.prototype.read = function(charCount) {
+  var result = sendSyncMessage('FileStreamRead', {
+    fileDescriptor: this.fileDescriptor,
+    charCount: charCount
+  });
+  if (result.isError)
+    return '';
+  return result.value;
+};
+
+FileStream.prototype.readBytes = function(byteCount) {
+  return sendSyncMessage('FileStreamReadBytes', {
+    fileDescriptor: this.fileDescriptor,
+    byteCount: byteCount
+  });
+};
+
+FileStream.prototype.readBase64 = function(byteCount) {
+  return sendSyncMessage('FileStreamReadBase64', {
+    fileDescriptor: this.fileDescriptor,
+    byteCount: byteCount
+  });
+};
+
+FileStream.prototype.write = function(stringData) {
+  return sendSyncMessage('FileStreamWrite', {
+    fileDescriptor: this.fileDescriptor,
+    stringData: stringData
+  });
+};
+
+FileStream.prototype.writeBytes = function(byteData) {
+  return sendSyncMessage('FileStreamWriteBytes', {
+    fileDescriptor: this.fileDescriptor,
+    byteData: byteData
+  });
+};
+
+FileStream.prototype.writeBase64 = function(base64Data) {
+  return sendSyncMessage('FileStreamWriteBase64', {
+    fileDescriptor: this.fileDescriptor,
+    base64Data: base64Data
+  });
+};
+
+function File(path, parent) {
+  this.path = path;
+  this.parent = parent;
+
+  var stat_cached = undefined;
+  var stat_last_time = undefined;
+
+  function getPathAndParent() {
+    var _path = path.lastIndexOf('/');
+    if (_path < 0) {
+      return {
+        path: _path,
+        parent: parent ? parent.path : ''
+      };
+    }
+
+    return {
+      path: path.substr(_path + 1),
+      parent: parent ? parent.path : path.substr(0, _path)
+    };
+  }
+
+  function stat() {
+    var now = Date.now();
+    if (stat_cached === undefined || (now - stat_last_time) > 5) {
+      var args = getPathAndParent();
+      var result = sendSyncMessage('FileStat', args);
+      if (result.isError)
+        return result;
+
+      stat_cached = result;
+      stat_last_time = now;
+      result.value.isError = result.isError;
+      return result.value;
+    }
+    return stat_cached.value;
+  }
+
+  var getParent = function() {
+    return parent;
+  };
+  var getReadOnly = function() {
+    var status = stat();
+    if (status.isError)
+      return true;
+    return status.readOnly;
+  };
+  var getIsFile = function() {
+    var status = stat();
+    if (status.isError)
+      return false;
+    return status.isFile;
+  };
+  var getIsDirectory = function() {
+    var status = stat();
+    if (status.isError)
+      return false;
+    return status.isDirectory;
+  };
+  var getCreatedDate = function() {
+    var status = stat();
+    if (status.isError)
+      return null;
+    return new Date(status.created * 1000);
+  };
+  var getModifiedDate = function() {
+    var status = stat();
+    if (status.isError)
+      return null;
+    return new Date(status.modified * 1000);
+  };
+  var getPath = function() {
+    return path;
+  };
+  var getName = function() {
+    var fullPath = getFullPath();
+    var lastSlashIndex = fullPath.lastIndexOf('/');
+    if (lastSlashIndex < 0)
+      return fullPath;
+    return fullPath.substr(lastSlashIndex + 1);
+  };
+  var getFullPath = function() {
+    if (path[0] == '/')
+      return path;
+    var status = sendSyncMessage('FileGetFullPath', { path: path });
+    if (status.isError)
+      return path;
+    return status.value;
+  };
+  var getFileSize = function() {
+    var status = stat();
+    if (status.isError)
+      return 0;
+    if (status.isDirectory)
+      return 0;
+    return status.size;
+  };
+  var getLength = function() {
+    if (getIsFile())
+      return 1;
+    return files.length;
+  };
+
+  Object.defineProperties(this, {
+    'parent': { get: getParent, enumerable: true },
+    'readOnly': { get: getReadOnly, enumerable: true },
+    'isFile': { get: getIsFile, enumerable: true },
+    'isDirectory': { get: getIsDirectory, enumerable: true },
+    'created': { get: getCreatedDate, enumerable: true },
+    'modified': { get: getModifiedDate, enumerable: true },
+    'path': { get: getPath, enumerable: true },
+    'name': { get: getName, enumerable: true },
+    'fullPath': { get: getFullPath, enumerable: true },
+    'fileSize': { get: getFileSize, enumerable: true },
+    'length': { get: getLength, enumerable: true }
+  });
+}
+
+File.prototype.toURI = function() {
+  var realPathStatus = sendSyncMessage('FileGetFullPath', {
+    path: this.path
+  });
+  if (!realPathStatus.isError)
+    return 'file://' + realPathStatus.fullPath;
+  return null;
+};
+
+File.prototype.listFiles = function(onsuccess, onerror, filter) {
+  if (!(onsuccess instanceof Function))
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  if (typeof(filter) !== 'undefined' && !(filter instanceof FileFilter))
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  postMessage({
+    cmd: 'FileListFiles',
+    path: this.path,
+    filter: filter ? filter.toString() : ''
+  }, function(result) {
+    if (result.isError) {
+      if (!onerror || !(onerror instanceof Function))
+        return;
+      onerror(result);
+    } else {
+      var file_list = [];
+
+      for (var i = 0; i < result.value.length; i++)
+        file_list.push(new File(result.value[i], this));
+
+      onsuccess(file_list);
+    }
+  }.bind(this));
+};
+
+File.prototype.openStream = function(mode, onsuccess, onerror, encoding) {
+  postMessage({
+    cmd: 'FileOpenStream',
+    filePath: this.path,
+    mode: mode,
+    encoding: encoding
+  }, function(result) {
+    if (result.isError) {
+      if (onerror)
+        onerror(result);
+    } else if (onsuccess) {
+      onsuccess(new FileStream(result.fileDescriptor));
+    }
+  });
+};
+
+File.prototype.readAsText = function(onsuccess, onerror, encoding) {
+  var streamOpened = function(stream) {
+    onsuccess(stream.read());
+    stream.close();
+  };
+  var streamError = function(error) {
+    if (onerror)
+      onerror(error);
+  };
+
+  this.openStream('r', streamOpened, streamError, encoding);
+};
+
+File.prototype.copyTo = function(originFilePath, destinationFilePath,
+    overwrite, onsuccess, onerror) {
+  var status = sendSyncMessage('FileCopyTo', {
+    originFilePath: originFilePath,
+    destinationFilePath: destinationFilePath,
+    overwrite: overwrite
+  });
+
+  if (status.isError) {
+    if (onerror)
+      onerror(status);
+  } else {
+    onsuccess(status);
+  }
+};
+
+File.prototype.moveTo = function(originFilePath, destinationFilePath,
+    overwrite, onsuccess, onerror) {
+  var status = sendSyncMessage('FileMoveTo', {
+    originFilePath: originFilePath,
+    destinationFilePath: destinationFilePath,
+    overwrite: overwrite
+  });
+
+  if (status.isError) {
+    if (onerror)
+      onerror(status);
+  } else {
+    onsuccess(status);
+  }
+};
+
+File.prototype.createDirectory = function(relative) {
+  var status = sendSyncMessage('FileCreateDirectory', {
+    path: this.path,
+    relative: relative
+  });
+
+  if (status.isError) {
+    throw new tizen.WebAPIException(status.errorCode);
+  } else {
+    return new File(status.path);
+  }
+};
+
+File.prototype.createFile = function(relative) {
+  var status = sendSyncMessage('FileCreateFile', {
+    path: this.path,
+    relative: relative
+  });
+  if (status.isError) {
+    throw new tizen.WebAPIException(status.errorCode);
+  } else {
+    return new File(status.value, this);
+  }
+};
+
+File.prototype.resolve = function(relative) {
+  var status = sendSyncMessage('FileResolve', {
+    path: this.path,
+    relative: relative
+  });
+
+  if (status.isError)
+    throw new tizen.WebAPIException(status.errorCode);
+
+  return new File(status.value);
+};
+
+File.prototype.deleteDirectory = function(directoryPath, recursive, onsuccess, onerror) {
+  postMessage({
+    cmd: 'FileDeleteDirectory',
+    directoryPath: directoryPath,
+    path: this.path,
+    recursive: !!recursive
+  }, function(result) {
+    if (result.isError) {
+      if (onerror) {
+        var error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
+        onerror(error);
+      }
+    } else if (onsuccess) {
+      onsuccess();
+    }
+  });
+};
+
+File.prototype.deleteFile = function(filePath, onsuccess, onerror) {
+  postMessage({
+    cmd: 'FileDeleteFile',
+    path: this.path,
+    filePath: filePath
+  }, function(result) {
+    if (result.isError) {
+      if (onerror)
+        onerror(result);
+    } else if (onsuccess) {
+      onsuccess();
+    }
+  });
+};
+
+
+(function() {
+  var manager = new FileSystemManager();
+  exports.resolve = manager.resolve;
+  exports.getStorage = manager.getStorage;
+  exports.listStorages = manager.listStorages;
+  exports.addStorageStateChangeListener = manager.addStorageStateChangeListener;
+  exports.removeStorageStateChangeListener = manager.removeStorageStateChangeListener;
+  exports.maxPathLength = manager.maxPathLength;
+})();
diff --git a/filesystem/filesystem_context.cc b/filesystem/filesystem_context.cc
new file mode 100644 (file)
index 0000000..673cd83
--- /dev/null
@@ -0,0 +1,995 @@
+// Copyright (c) 2013 Intel Corporation. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "filesystem/filesystem_context.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+DEFINE_XWALK_EXTENSION(FilesystemContext)
+
+namespace {
+const unsigned kDefaultFileMode = 0644;
+const std::string kDefaultPath = "/opt/usr/media";
+
+bool IsWritable(const struct stat& st) {
+  if (st.st_mode & S_IWOTH)
+    return true;
+  if ((st.st_mode & S_IWUSR) && geteuid() == st.st_uid)
+    return true;
+  if ((st.st_mode & S_IWGRP) && getegid() == st.st_gid)
+    return true;
+  return false;
+}
+
+bool IsValidPathComponent(const std::string& path) {
+  return path.find('/') == std::string::npos;
+}
+
+std::string JoinPath(const std::string& one, const std::string& another) {
+  if (!IsValidPathComponent(another))
+    return std::string();
+  return one + "/" + another;
+}
+
+};  // namespace
+
+FilesystemContext::FilesystemContext(ContextAPI* api)
+  : api_(api) {}
+
+FilesystemContext::~FilesystemContext() {
+  std::set<int>::iterator it;
+
+  for (it = known_file_descriptors_.begin();
+        it != known_file_descriptors_.end(); it++)
+    close(*it);
+}
+
+const char FilesystemContext::name[] = "tizen.filesystem";
+
+extern const char kSource_filesystem_api[];
+
+const char* FilesystemContext::GetJavaScript() {
+  return kSource_filesystem_api;
+}
+
+void FilesystemContext::HandleMessage(const char* message) {
+  picojson::value v;
+
+  std::string err;
+  picojson::parse(v, message, message + strlen(message), &err);
+  if (!err.empty()) {
+    std::cout << "Ignoring message.\n";
+    return;
+  }
+
+  std::string cmd = v.get("cmd").to_str();
+  if (cmd == "FileSystemManagerResolve")
+    HandleFileSystemManagerResolve(v);
+  else if (cmd == "FileSystemManagerGetStorage")
+    HandleFileSystemManagerGetStorage(v);
+  else if (cmd == "FileSystemManagerListStorages")
+    HandleFileSystemManagerListStorages(v);
+  else if (cmd == "FileOpenStream")
+    HandleFileOpenStream(v);
+  else if (cmd == "FileDeleteDirectory")
+    HandleFileDeleteDirectory(v);
+  else if (cmd == "FileDeleteFile")
+    HandleFileDeleteFile(v);
+  else if (cmd == "FileListFiles")
+    HandleFileListFiles(v);
+  else if (cmd == "FileCopyTo")
+    HandleFileCopyTo(v);
+  else if (cmd == "FileMoveTo")
+    HandleFileMoveTo(v);
+  else
+    std::cout << "Ignoring unknown command: " << cmd;
+}
+
+void FilesystemContext::PostAsyncErrorReply(const picojson::value& msg,
+      WebApiAPIErrors error_code) {
+  picojson::value::object o;
+  o["isError"] = picojson::value(true);
+  o["errorCode"] = picojson::value(static_cast<double>(error_code));
+  o["reply_id"] = picojson::value(msg.get("reply_id").get<double>());
+
+  picojson::value v(o);
+  api_->PostMessage(v.serialize().c_str());
+}
+
+void FilesystemContext::PostAsyncSuccessReply(const picojson::value& msg,
+      picojson::value::object& reply) {
+  reply["isError"] = picojson::value(false);
+  reply["reply_id"] = picojson::value(msg.get("reply_id").get<double>());
+
+  picojson::value v(reply);
+  api_->PostMessage(v.serialize().c_str());
+}
+
+void FilesystemContext::PostAsyncSuccessReply(const picojson::value& msg) {
+  picojson::value::object reply;
+  PostAsyncSuccessReply(msg, reply);
+}
+
+void FilesystemContext::PostAsyncSuccessReply(const picojson::value& msg,
+      picojson::value& value) {
+  picojson::value::object reply;
+  reply["value"] = value;
+  PostAsyncSuccessReply(msg, reply);
+}
+
+void FilesystemContext::HandleFileSystemManagerResolve(
+      const picojson::value& msg) {
+  if (!msg.contains("location")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+  std::string mode;
+  if (!msg.contains("mode"))
+    mode = "rw";
+  else
+    mode = msg.get("mode").to_str();
+  std::string location = msg.get("location").to_str();
+  std::string path;
+  bool check_if_inside_default = true;
+  if (location.find("documents") == 0) {
+    path = JoinPath(kDefaultPath, "Documents");
+  } else if (location.find("images") == 0) {
+    path = JoinPath(kDefaultPath, "Images");
+  } else if (location.find("music") == 0) {
+    path = JoinPath(kDefaultPath, "Sounds");
+  } else if (location.find("videos") == 0) {
+    path = JoinPath(kDefaultPath, "Videos");
+  } else if (location.find("downloads") == 0) {
+    path = JoinPath(kDefaultPath, "Downloads");
+  } else if (location.find("ringtones") == 0) {
+    path = JoinPath(kDefaultPath, "Sounds");
+  } else if (location.find("wgt-package") == 0) {
+    if (mode == "w" || mode == "rw") {
+      PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+      return;
+    }
+    path = "/tmp";  // FIXME
+  } else if (location.find("wgt-private") == 0) {
+    path = "/tmp";  // FIXME
+  } else if (location.find("wgt-private-tmp") == 0) {
+    path = "/tmp";  // FIXME
+  } else if (location.find("file://") == 0) {
+    path = location.substr(sizeof("file://") - 1);
+    check_if_inside_default = false;
+  } else {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  char* real_path_cstr = realpath(path.c_str(), NULL);
+  if (!real_path_cstr) {
+    PostAsyncErrorReply(msg, IO_ERR);
+    return;
+  }
+  std::string real_path = std::string(real_path_cstr);
+  free(real_path_cstr);
+
+  if (check_if_inside_default && real_path.find(kDefaultPath) != 0) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  struct stat st;
+  if (stat(path.c_str(), &st) < 0) {
+    if (errno == ENOENT || errno == ENOTDIR)
+      PostAsyncErrorReply(msg, NOT_FOUND_ERR);
+    else
+      PostAsyncErrorReply(msg, IO_ERR);
+    return;
+  }
+
+  if (!IsWritable(st) && (mode == "w" || mode == "rw")) {
+    PostAsyncErrorReply(msg, IO_ERR);
+    return;
+  }
+
+  picojson::value::object o;
+  o["realPath"] = picojson::value(real_path);
+  PostAsyncSuccessReply(msg, o);
+}
+
+void FilesystemContext::HandleFileSystemManagerGetStorage(
+      const picojson::value& msg) {
+  // FIXME(leandro): This requires specific Tizen support.
+  PostAsyncErrorReply(msg, NOT_SUPPORTED_ERR);
+}
+
+void FilesystemContext::HandleFileSystemManagerListStorages(
+      const picojson::value& msg) {
+  // FIXME(leandro): This requires specific Tizen support.
+  PostAsyncErrorReply(msg, NOT_SUPPORTED_ERR);
+}
+
+void FilesystemContext::HandleFileOpenStream(const picojson::value& msg) {
+  if (!msg.contains("mode")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  std::string mode = msg.get("mode").to_str();
+  int mode_for_open = 0;
+  if (mode == "a") {
+    mode_for_open = O_APPEND;
+  } else if (mode == "w") {
+    mode_for_open = O_TRUNC | O_WRONLY | O_CREAT;
+  } else if (mode == "rw") {
+    mode_for_open = O_RDWR | O_CREAT;
+  } else if (mode == "r") {
+    mode_for_open = O_RDONLY;
+  } else {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  std::string encoding;
+  if (msg.contains("encoding"))
+    encoding = msg.get("encoding").to_str();
+  if (!encoding.empty() && encoding != "UTF-8") {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  if (!msg.contains("filePath")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  std::string path = msg.get("filePath").to_str();
+  int fd = open(path.c_str(), mode_for_open, kDefaultFileMode);
+  if (fd < 0) {
+    PostAsyncErrorReply(msg, IO_ERR);
+  } else {
+    known_file_descriptors_.insert(fd);
+
+    picojson::value::object o;
+    o["fileDescriptor"] = picojson::value(static_cast<double>(fd));
+    PostAsyncSuccessReply(msg, o);
+  }
+}
+
+static bool RecursiveDeleteDirectory(const std::string& path) {
+  DIR* dir = opendir(path.c_str());
+  if (!dir)
+    return false;
+  struct dirent entry, *buffer;
+  int fd = dirfd(dir);
+  if (fd < 0)
+    goto error;
+
+  while (!readdir_r(dir, &entry, &buffer)) {
+    struct stat st;
+
+    if (!buffer)
+      break;
+    if (!strcmp(entry.d_name, ".") || !strcmp(entry.d_name, ".."))
+      continue;
+    if (fstatat(fd, entry.d_name, &st, 0) < 0)
+      continue;
+
+    if (S_ISDIR(st.st_mode)) {
+      const std::string next_path = path + "/" + entry.d_name;
+      if (!RecursiveDeleteDirectory(next_path))
+        goto error;
+    } else if (unlinkat(fd, entry.d_name, 0) < 0) {
+      goto error;
+    }
+  }
+
+  closedir(dir);
+  return rmdir(path.c_str()) >= 0;
+
+ error:
+  closedir(dir);
+  return false;
+}
+
+void FilesystemContext::HandleFileDeleteDirectory(const picojson::value& msg) {
+  bool recursive = msg.get("recursive").evaluate_as_boolean();
+
+  if (!msg.contains("path")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+  std::string path = msg.get("path").to_str();
+  if (!IsValidPathComponent(path))  {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  if (recursive) {
+    if (!RecursiveDeleteDirectory(path)) {
+      PostAsyncErrorReply(msg, IO_ERR);
+      return;
+    }
+  } else if (rmdir(path.c_str()) < 0) {
+    PostAsyncErrorReply(msg, IO_ERR);
+    return;
+  }
+
+  PostAsyncSuccessReply(msg);
+}
+
+void FilesystemContext::HandleFileDeleteFile(const picojson::value& msg) {
+  if (!msg.contains("path") || !msg.contains("filePath")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  std::string root_path = msg.get("path").to_str();
+  std::string relative_path = msg.get("filePath").to_str();
+  std::string full_path = JoinPath(root_path, relative_path);
+  if (full_path.empty()) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  if (unlink(full_path.c_str()) < 0) {
+    switch (errno) {
+    case EACCES:
+    case EBUSY:
+    case EIO:
+    case EPERM:
+      PostAsyncErrorReply(msg, IO_ERR);
+      break;
+    case ENOENT:
+      PostAsyncErrorReply(msg, NOT_FOUND_ERR);
+      break;
+    default:
+      PostAsyncErrorReply(msg, UNKNOWN_ERR);
+    }
+  } else {
+    PostAsyncSuccessReply(msg);
+  }
+}
+
+void FilesystemContext::HandleFileListFiles(const picojson::value& msg) {
+  if (!msg.contains("path")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+  std::string path = msg.get("path").to_str();
+  if (path.empty()) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  DIR* directory = opendir(path.c_str());
+  if (!directory) {
+    PostAsyncErrorReply(msg, IO_ERR);
+    return;
+  }
+
+  picojson::value::array a;
+
+  struct dirent entry, *buffer;
+  while (!readdir_r(directory, &entry, &buffer)) {
+    if (!buffer)
+      break;
+    if (!strcmp(entry.d_name, ".") || !strcmp(entry.d_name, ".."))
+      continue;
+
+    a.push_back(picojson::value(entry.d_name));
+  }
+
+  closedir(directory);
+
+  picojson::value v(a);
+  PostAsyncSuccessReply(msg, v);
+}
+
+
+bool FilesystemContext::CopyAndRenameSanityChecks(const picojson::value& msg,
+      const std::string& from, const std::string& to, bool overwrite) {
+  struct stat destination_st;
+  bool destination_exists = true;
+  if (stat(to.c_str(), &destination_st) < 0) {
+    if (errno == ENOENT) {
+      destination_exists = false;
+    } else {
+      PostAsyncErrorReply(msg, IO_ERR);
+      return false;
+    }
+  }
+
+  if (overwrite && !IsWritable(destination_st)) {
+    PostAsyncErrorReply(msg, IO_ERR);
+    return false;
+  }
+  if (!overwrite && destination_exists) {
+    PostAsyncErrorReply(msg, IO_ERR);
+    return false;
+  }
+
+  if (access(from.c_str(), F_OK)) {
+    PostAsyncErrorReply(msg, NOT_FOUND_ERR);
+    return false;
+  }
+
+  return true;
+}
+
+namespace {
+
+class PosixFile {
+ private:
+  int fd_;
+  int mode_;
+  std::string path_;
+  bool unlink_when_done_;
+ public:
+  PosixFile(const std::string& path, int mode)
+      : fd_(open(path.c_str(), mode, kDefaultFileMode))
+      , mode_(mode)
+      , path_(path)
+      , unlink_when_done_(mode & O_CREAT) {}
+  ~PosixFile();
+
+  bool is_valid() { return fd_ >= 0; }
+
+  void UnlinkWhenDone(bool setting) { unlink_when_done_ = setting; }
+
+  ssize_t Read(char* buffer, size_t count);
+  ssize_t Write(char* buffer, size_t count);
+};
+
+PosixFile::~PosixFile() {
+  if (fd_ < 0)
+    return;
+
+  close(fd_);
+  if (unlink_when_done_)
+    unlink(path_.c_str());
+}
+
+ssize_t PosixFile::Read(char* buffer, size_t count) {
+  if (fd_ < 0)
+    return -1;
+
+  while (true) {
+    ssize_t read_bytes = read(fd_, buffer, count);
+    if (read_bytes < 0) {
+      if (errno == EINTR)
+        continue;
+      return -1;
+    }
+    return read_bytes;
+  }
+}
+
+ssize_t PosixFile::Write(char* buffer, size_t count) {
+  if (fd_ < 0)
+    return -1;
+
+  while (true) {
+    ssize_t written_bytes = write(fd_, buffer, count);
+    if (written_bytes < 0) {
+      if (errno == EINTR)
+        continue;
+      return -1;
+    }
+    return written_bytes;
+  }
+}
+
+}  // namespace
+
+void FilesystemContext::HandleFileCopyTo(const picojson::value& msg) {
+  if (!msg.contains("originFilePath")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+  if (!msg.contains("destinationFilePath")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  std::string origin_path = msg.get("originFilePath").to_str();
+  std::string destination_path = msg.get("destinationFilePath").to_str();
+  bool overwrite = msg.get("overwrite").evaluate_as_boolean();
+
+  if (!CopyAndRenameSanityChecks(msg, origin_path, destination_path, overwrite))
+    return;
+
+  PosixFile origin(origin_path, O_RDONLY);
+  if (!origin.is_valid()) {
+    PostAsyncErrorReply(msg, IO_ERR);
+    return;
+  }
+
+  PosixFile destination(destination_path, O_WRONLY | O_CREAT | O_TRUNC);
+  if (!destination.is_valid()) {
+    PostAsyncErrorReply(msg, IO_ERR);
+    return;
+  }
+
+  while (true) {
+    char buffer[512];
+    ssize_t read_bytes = origin.Read(buffer, 512);
+    if (!read_bytes)
+      break;
+    if (read_bytes < 0) {
+      PostAsyncErrorReply(msg, IO_ERR);
+      return;
+    }
+
+    if (destination.Write(buffer, read_bytes) < 0) {
+      PostAsyncErrorReply(msg, IO_ERR);
+      return;
+    }
+  }
+
+  destination.UnlinkWhenDone(false);
+  PostAsyncSuccessReply(msg);
+}
+
+void FilesystemContext::HandleFileMoveTo(const picojson::value& msg) {
+  if (!msg.contains("originFilePath")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+  if (!msg.contains("destinationFilePath")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+  std::string origin_path = msg.get("originFilePath").to_str();
+  std::string destination_path = msg.get("destinationFilePath").to_str();
+  bool overwrite = msg.get("overwrite").evaluate_as_boolean();
+
+  if (!CopyAndRenameSanityChecks(msg, origin_path, destination_path, overwrite))
+    return;
+
+  if (rename(origin_path.c_str(), destination_path.c_str()) < 0) {
+    PostAsyncErrorReply(msg, IO_ERR);
+    return;
+  }
+
+  PostAsyncSuccessReply(msg);
+}
+
+void FilesystemContext::HandleSyncMessage(const char* message) {
+  picojson::value v;
+
+  std::string err;
+  picojson::parse(v, message, message + strlen(message), &err);
+  if (!err.empty()) {
+    std::cout << "Ignoring sync message.\n";
+    return;
+  }
+
+  std::string cmd = v.get("cmd").to_str();
+  std::string reply;
+  if (cmd == "FileSystemManagerGetMaxPathLength")
+    HandleFileSystemManagerGetMaxPathLength(v, reply);
+  else if (cmd == "FileStreamClose")
+    HandleFileStreamClose(v, reply);
+  else if (cmd == "FileStreamRead")
+    HandleFileStreamRead(v, reply);
+  else if (cmd == "FileStreamReadBytes")
+    HandleFileStreamReadBytes(v, reply);
+  else if (cmd == "FileStreamReadBase64")
+    HandleFileStreamReadBase64(v, reply);
+  else if (cmd == "FileStreamWrite")
+    HandleFileStreamWrite(v, reply);
+  else if (cmd == "FileStreamWriteBytes")
+    HandleFileStreamWriteBytes(v, reply);
+  else if (cmd == "FileStreamWriteBase64")
+    HandleFileStreamWriteBase64(v, reply);
+  else if (cmd == "FileCreateDirectory")
+    HandleFileCreateDirectory(v, reply);
+  else if (cmd == "FileCreateFile")
+    HandleFileCreateFile(v, reply);
+  else if (cmd == "FileResolve")
+    HandleFileResolve(v, reply);
+  else if (cmd == "FileStat")
+    HandleFileStat(v, reply);
+  else if (cmd == "FileGetFullPath")
+    HandleFileGetFullPath(v, reply);
+  else
+    std::cout << "Ignoring unknown command: " << cmd;
+
+  if (!reply.empty())
+    api_->SetSyncReply(reply.c_str());
+}
+
+static std::string to_string(int value) {
+  char buffer[sizeof(value) * 3];
+  if (snprintf(buffer, sizeof(buffer), "%d", value) < 0)
+    return std::string();
+  return std::string(buffer);
+}
+
+void FilesystemContext::HandleFileSystemManagerGetMaxPathLength(
+      const picojson::value& msg, std::string& reply) {
+  int max_path = pathconf("/", _PC_PATH_MAX);
+  if (max_path < 0)
+    max_path = PATH_MAX;
+
+  std::string max_path_len_str = to_string(max_path);
+  SetSyncSuccess(reply, max_path_len_str);
+}
+
+bool FilesystemContext::IsKnownFileDescriptor(int fd) {
+  return known_file_descriptors_.find(fd) != known_file_descriptors_.end();
+}
+
+void FilesystemContext::SetSyncError(std::string& output,
+      WebApiAPIErrors error_type) {
+  picojson::value::object o;
+
+  o["isError"] = picojson::value(true);
+  o["errorCode"] = picojson::value(static_cast<double>(error_type));
+  picojson::value v(o);
+  output = v.serialize();
+}
+
+void FilesystemContext::SetSyncSuccess(std::string& reply,
+      std::string& output) {
+  picojson::value::object o;
+
+  o["isError"] = picojson::value(false);
+  o["value"] = picojson::value(output);
+
+  picojson::value v(o);
+  reply = v.serialize();
+}
+
+void FilesystemContext::SetSyncSuccess(std::string& reply) {
+  picojson::value::object o;
+
+  o["isError"] = picojson::value(false);
+
+  picojson::value v(o);
+  reply = v.serialize();
+}
+
+void FilesystemContext::SetSyncSuccess(std::string& reply,
+      picojson::value& output) {
+  picojson::value::object o;
+
+  o["isError"] = picojson::value(false);
+  o["value"] = output;
+
+  picojson::value v(o);
+  reply = v.serialize();
+}
+
+void FilesystemContext::HandleFileStreamClose(const picojson::value& msg,
+      std::string& reply) {
+  if (!msg.contains("fileDescriptor")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  int fd = msg.get("fileDescriptor").get<double>();
+
+  if (IsKnownFileDescriptor(fd)) {
+    close(fd);
+    known_file_descriptors_.erase(fd);
+  }
+
+  SetSyncSuccess(reply);
+}
+
+void FilesystemContext::HandleFileStreamRead(const picojson::value& msg,
+      std::string& reply) {
+  if (!msg.contains("fileDescriptor")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  int fd = msg.get("fileDescriptor").get<double>();
+
+  if (!IsKnownFileDescriptor(fd)) {
+    SetSyncError(reply, IO_ERR);
+    return;
+  }
+
+  unsigned char_count;
+  const unsigned kMaxSize = 64 * 1024;
+  if (!msg.contains("charCount")) {
+    char_count = kMaxSize;
+  } else {
+    char_count = msg.get("charCount").get<double>();
+    if (char_count > kMaxSize) {
+      SetSyncError(reply, IO_ERR);
+      return;
+    }
+  }
+
+  char buffer[kMaxSize];
+  size_t read_bytes = read(fd, buffer, char_count);
+  if (read_bytes < 0) {
+    SetSyncError(reply, IO_ERR);
+    return;
+  }
+
+  std::string buffer_as_string = std::string(buffer, read_bytes);
+  SetSyncSuccess(reply, buffer_as_string);
+}
+
+void FilesystemContext::HandleFileStreamReadBytes(const picojson::value& msg,
+      std::string& reply) {
+  HandleFileStreamRead(msg, reply);
+}
+
+namespace {
+namespace base64 {
+
+static const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn" \
+      "opqrstuvwxyz0123456789+/";
+
+std::string ConvertTo(std::string input) {
+  std::string encoded;
+  size_t input_len = input.length();
+
+  for (size_t i = 0; i < input_len;) {
+    unsigned triple = input[i];
+    i++;
+
+    triple <<= 8;
+    if (i < input_len)
+      triple |= input[i];
+    i++;
+
+    triple <<= 8;
+    if (i < input_len)
+      triple |= input[i];
+    i++;
+
+    encoded.push_back(chars[(triple & 0xfc0000) >> 18]);
+    encoded.push_back(chars[(triple & 0x3f000) >> 12]);
+    encoded.push_back((i > input_len + 1) ? '=' : chars[(triple & 0xfc0) >> 6]);
+    encoded.push_back((i > input_len) ? '=' : chars[triple & 0x3f]);
+  }
+
+  return encoded;
+}
+
+int DecodeOne(char c) {
+  if (c < '0') {
+    if (c == '+')
+      return 62;
+    if (c == '/')
+      return 63;
+    return -1;
+  }
+  if (c <= '9')
+    return c - '0' + 52;
+  if (c >= 'A' && c <= 'Z')
+    return c - 'A';
+  if (c >= 'a' && c <= 'z')
+    return c - 'a' + 26;
+  return -1;
+}
+
+std::string ConvertFrom(std::string input) {
+  std::string decoded;
+  size_t input_len = input.length();
+
+  if (input_len % 4)
+    return input;
+
+  for (size_t i = 0; i < input_len;) {
+    int c0 = DecodeOne(input[i++]);
+    int c1 = DecodeOne(input[i++]);
+
+    if (c0 < 0 || c1 < 0)
+      return input;
+
+    int c2 = DecodeOne(input[i++]);
+    int c3 = DecodeOne(input[i++]);
+
+    if (c2 < 0 && c3 < 0)
+      return input;
+
+    decoded.push_back(c0 << 2 | c1 >> 4);
+    if (c2 < 0)
+      decoded.push_back(((c1 & 15) << 4) | (c2 >> 2));
+    if (c3 < 0)
+      decoded.push_back(((c2 & 3) << 6) | c3);
+    i += 3;
+  }
+
+  return decoded;
+}
+
+}  // namespace base64
+}  // namespace
+
+void FilesystemContext::HandleFileStreamReadBase64(const picojson::value& msg,
+      std::string& reply) {
+  HandleFileStreamRead(msg, reply);
+  std::string base64_contents = base64::ConvertTo(reply);
+  SetSyncSuccess(reply, base64_contents);
+}
+
+void FilesystemContext::HandleFileStreamWrite(const picojson::value& msg,
+      std::string& reply) {
+  if (!msg.contains("fileDescriptor")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  int fd = msg.get("fileDescriptor").get<double>();
+
+  if (!IsKnownFileDescriptor(fd)) {
+    SetSyncError(reply, IO_ERR);
+    return;
+  }
+
+  if (!msg.contains("stringData")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  std::string buffer = msg.get("stringData").to_str();
+  if (write(fd, buffer.c_str(), buffer.length()) < 0) {
+    SetSyncError(reply, IO_ERR);
+    return;
+  }
+
+  SetSyncSuccess(reply);
+}
+
+void FilesystemContext::HandleFileStreamWriteBytes(const picojson::value& msg,
+      std::string& reply) {
+  HandleFileStreamWrite(msg, reply);
+}
+
+void FilesystemContext::HandleFileStreamWriteBase64(const picojson::value& msg,
+      std::string& reply) {
+  if (!msg.contains("base64Data")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  std::string base64_data = msg.get("base64Data").to_str();
+  std::string raw_data = base64::ConvertFrom(base64_data);
+  HandleFileStreamWrite(msg, raw_data);
+}
+
+void FilesystemContext::HandleFileCreateDirectory(const picojson::value& msg,
+      std::string& reply) {
+  if (!msg.contains("path")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  if (!msg.contains("relative")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  std::string relative_path = msg.get("relative").to_str();
+  std::string root_path = msg.get("path").to_str();
+  std::string real_path = JoinPath(root_path, relative_path);
+  if (real_path.empty()) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+
+  if (mkdir(real_path.c_str(), kDefaultFileMode) < 0) {
+    SetSyncError(reply, IO_ERR);
+    return;
+  }
+
+  SetSyncSuccess(reply, relative_path);
+}
+
+void FilesystemContext::HandleFileCreateFile(const picojson::value& msg,
+      std::string& reply) {
+  if (!msg.contains("path")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  if (!msg.contains("relative")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  std::string parent = msg.get("path").to_str();
+  std::string relative = msg.get("relative").to_str();
+  std::string file_path = JoinPath(parent, relative);
+  if (file_path.empty()) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+
+  int result = open(file_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC,
+        kDefaultFileMode);
+  if (result < 0) {
+    SetSyncError(reply, IO_ERR);
+    return;
+  }
+
+  close(result);
+  SetSyncSuccess(reply, file_path);
+}
+
+void FilesystemContext::HandleFileResolve(const picojson::value& msg,
+      std::string& reply) {
+  if (!msg.contains("path")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  if (!msg.contains("relative")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  std::string path = msg.get("path").to_str();
+  std::string relative = msg.get("relative").to_str();
+  std::string joined_path = JoinPath(path, relative);
+  if (joined_path.empty()) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+
+  char* full_path = realpath(joined_path.c_str(), NULL);
+  if (!full_path) {
+    SetSyncError(reply, IO_ERR);
+    return;
+  }
+
+  std::string path_as_str = std::string(full_path);
+  free(full_path);
+
+  SetSyncSuccess(reply, path_as_str);
+}
+
+void FilesystemContext::HandleFileStat(const picojson::value& msg,
+      std::string& reply) {
+  if (!msg.contains("path")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  if (!msg.contains("parent")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  std::string path = msg.get("path").to_str();
+  std::string parent = msg.get("parent").to_str();
+  std::string full_path = JoinPath(parent, path);
+  if (full_path.empty()) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+
+  struct stat st;
+  if (stat(full_path.c_str(), &st) < 0) {
+    SetSyncError(reply, IO_ERR);
+    return;
+  }
+
+  picojson::value::object o;
+  o["size"] = picojson::value(static_cast<double>(st.st_size));
+  o["modified"] = picojson::value(static_cast<double>(st.st_mtime));
+  o["created"] = picojson::value(static_cast<double>(st.st_ctime));  // ?
+  o["readOnly"] = picojson::value(!IsWritable(st));
+  o["isFile"] = picojson::value(!!S_ISREG(st.st_mode));
+  o["isDirectory"] = picojson::value(!!S_ISDIR(st.st_mode));
+
+  picojson::value v(o);
+  SetSyncSuccess(reply, v);
+}
+
+void FilesystemContext::HandleFileGetFullPath(const picojson::value& msg,
+      std::string& reply) {
+  if (!msg.contains("path")) {
+    SetSyncError(reply, INVALID_VALUES_ERR);
+    return;
+  }
+  std::string path = msg.get("path").to_str();
+  char* full_path = realpath(path.c_str(), NULL);
+  if (!full_path) {
+    SetSyncError(reply, IO_ERR);
+    return;
+  }
+
+  std::string full_path_as_str = std::string(full_path);
+  free(full_path);
+
+  SetSyncSuccess(reply, full_path_as_str);
+}
diff --git a/filesystem/filesystem_context.h b/filesystem/filesystem_context.h
new file mode 100644 (file)
index 0000000..ac03f41
--- /dev/null
@@ -0,0 +1,79 @@
+// Copyright (c) 2013 Intel Corporation. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FILESYSTEM_FILESYSTEM_CONTEXT_H_
+#define FILESYSTEM_FILESYSTEM_CONTEXT_H_
+
+#include <set>
+#include <string>
+
+#include "common/extension_adapter.h"
+#include "common/picojson.h"
+#include "tizen/tizen.h"
+
+class FilesystemContext {
+ public:
+  explicit FilesystemContext(ContextAPI* api);
+  ~FilesystemContext();
+
+  /* ExtensionAdapter implementation */
+  static const char name[];
+  static const char* GetJavaScript();
+  void HandleMessage(const char* message);
+  void HandleSyncMessage(const char* message);
+
+ private:
+  /* Asynchronous messages */
+  void HandleFileSystemManagerResolve(const picojson::value& msg);
+  void HandleFileSystemManagerGetStorage(const picojson::value& msg);
+  void HandleFileSystemManagerListStorages(const picojson::value& msg);
+  void HandleFileOpenStream(const picojson::value& msg);
+  void HandleFileDeleteDirectory(const picojson::value& msg);
+  void HandleFileDeleteFile(const picojson::value& msg);
+  void HandleFileListFiles(const picojson::value& msg);
+  void HandleFileCopyTo(const picojson::value& msg);
+  void HandleFileMoveTo(const picojson::value& msg);
+
+  /* Asynchronous message helpers */
+  void PostAsyncErrorReply(const picojson::value&, WebApiAPIErrors);
+  void PostAsyncSuccessReply(const picojson::value&, picojson::value::object&);
+  void PostAsyncSuccessReply(const picojson::value&, picojson::value&);
+  void PostAsyncSuccessReply(const picojson::value&, WebApiAPIErrors);
+  void PostAsyncSuccessReply(const picojson::value&);
+
+  /* Sync messages */
+  void HandleFileSystemManagerGetMaxPathLength(const picojson::value& msg,
+        std::string& reply);
+  void HandleFileStreamClose(const picojson::value& msg, std::string& reply);
+  void HandleFileStreamRead(const picojson::value& msg, std::string& reply);
+  void HandleFileStreamReadBytes(const picojson::value& msg,
+        std::string& reply);
+  void HandleFileStreamReadBase64(const picojson::value& msg,
+        std::string& reply);
+  void HandleFileStreamWrite(const picojson::value& msg, std::string& reply);
+  void HandleFileStreamWriteBytes(const picojson::value& msg,
+        std::string& reply);
+  void HandleFileStreamWriteBase64(const picojson::value& msg,
+        std::string& reply);
+  void HandleFileCreateDirectory(const picojson::value& msg,
+        std::string& reply);
+  void HandleFileCreateFile(const picojson::value& msg, std::string& reply);
+  void HandleFileResolve(const picojson::value& msg, std::string& reply);
+  void HandleFileStat(const picojson::value& msg, std::string& reply);
+  void HandleFileGetFullPath(const picojson::value& msg, std::string& reply);
+
+  /* Sync message helpers */
+  bool IsKnownFileDescriptor(int fd);
+  bool CopyAndRenameSanityChecks(const picojson::value& msg,
+        const std::string& from, const std::string& to, bool overwrite);
+  void SetSyncError(std::string& output, WebApiAPIErrors error_type);
+  void SetSyncSuccess(std::string& reply);
+  void SetSyncSuccess(std::string& reply, std::string& output);
+  void SetSyncSuccess(std::string& reply, picojson::value& output);
+
+  ContextAPI* api_;
+  std::set<int> known_file_descriptors_;
+};
+
+#endif  // FILESYSTEM_FILESYSTEM_CONTEXT_H_
index 5bc5b33..8271fe8 100644 (file)
@@ -9,6 +9,7 @@
       'type': 'none',
       'dependencies': [
         'bluetooth/bluetooth.gyp:*',
+        'filesystem/filesystem.gyp:*',
         'network_bearer_selection/network_bearer_selection.gyp:*',
         'notification/notification.gyp:*',
         'power/power.gyp:*',