[Filesystem] Fix and complete the API
authorRomuald Texier-Marcadé <romuald.texier-marcade@open.eurogiciel.org>
Fri, 6 Jun 2014 13:02:21 +0000 (15:02 +0200)
committerRomuald Texier-Marcadé <romuald.texier-marcade@open.eurogiciel.org>
Wed, 2 Jul 2014 09:28:25 +0000 (11:28 +0200)
Implement the spec.
Fix and pass the TCT tests.
Fix and complete the example.

BUG=XWALK-1071
BUG=XWALK-1827

examples/filesystem.html
filesystem/filesystem.gyp
filesystem/filesystem_api.src.js [moved from filesystem/filesystem_api.js with 90% similarity]
filesystem/filesystem_instance.cc
filesystem/filesystem_instance.h
filesystem/tools/inject_encodings.py [new file with mode: 0644]

index 1e55aec..5d875a3 100644 (file)
 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';
+  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 loadContents(file){
+  file.readAsText(function(contents) {
+    editor.value = contents;
+    updateStatusInfo(this);
+  }.bind(file),
+  function(error) {
+    console.log(error.name);
+  });
 }
 
 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;
+  var path = document.getElementById("path").value;
+  if (path == '') {
+    console.log('Type a file path and try again');
+    return;
+  }
+  try {
+    tizen.filesystem.resolve(path, loadContents, function(error) {
+      console.log(error.name);
+    });
+  } catch (e) {
+    console.log(e);
+  }
+}
 
-         updateStatusInfo(this);
+function saveContents(file){
+  file.openStream('w',
+      function(stream) {
+        stream.write(editor.value);
+        stream.close();
+        updateStatusInfo(this);
       }.bind(file),
       function(error) {
-         console.log(error);
-      });
-   } catch (e) {
-      console.log(e);
-   }
+        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.name);
+      }.bind(file));
 }
 
 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));
+  var path = document.getElementById("path").value;
+  if (path == '') {
+    console.log('Type a file path and try again');
+    return;
+  }
+  try {
+    tizen.filesystem.resolve(path, saveContents, function(error) {
+      // file not found
+      var seppos = path.lastIndexOf('/');
+      var dirpath = path.substring(0, seppos);
+      var filepath = path.substring(seppos + 1);
+      tizen.filesystem.resolve(dirpath, function (dir) {
+        try {
+          file = dir.createFile(filepath);
+          saveContents(file);
+        } catch(e) {
+          console.log(e.name);
+        }
+      }, function(error) {console.log(e.name);});
+    });
    } catch (e) {
-      console.log(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) {
+  var path = document.getElementById("path").value;
+  if (path == '') {
+    console.log('Type a file path and try again');
+    return;
+  }
+  var dirpath = path.substring(0, path.lastIndexOf('/'));
+  tizen.filesystem.resolve(dirpath, function(dir) {
+    try {
+      dir.deleteFile(path,
+          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);
-   }
-
+    }
+  }, function(error){console.log(e.name);});
 }
 
 </script>
index 0f711a3..e33f8fe 100644 (file)
@@ -13,7 +13,8 @@
         ],
       },
       'sources': [
-        'filesystem_api.js',
+        # filesystem_api.js is generated by inject_encodings action below
+        '<(INTERMEDIATE_DIR)/filesystem_api.js',
         'filesystem_extension.cc',
         'filesystem_extension.h',
         'filesystem_instance.cc',
       'includes': [
         '../common/pkg-config.gypi',
       ],
+      'actions': [
+        {
+          'action_name': 'inject_encodings',
+          'inputs': [
+            'tools/inject_encodings.py',
+            'filesystem_api.src.js'
+          ],
+          'outputs': [
+            '<(INTERMEDIATE_DIR)/filesystem_api.js'
+          ],
+          'action' : ['python', 'tools/inject_encodings.py', 
+                      'filesystem_api.src.js', '<(INTERMEDIATE_DIR)/filesystem_api.js']
+        }
+      ]
     },
   ],
 }
similarity index 90%
rename from filesystem/filesystem_api.js
rename to filesystem/filesystem_api.src.js
index c8a7c60..57f744c 100644 (file)
@@ -5,20 +5,14 @@
 var _callbacks = {};
 var _next_reply_id = 0;
 
-var _listeners = {};
+var _listeners = [];
 var _next_listener_id = 0;
 
 var getNextReplyId = function() {
   return _next_reply_id++;
 };
 
-function defineReadOnlyProperty(object, key, value) {
-  Object.defineProperty(object, key, {
-    configurable: false,
-    writable: false,
-    value: value
-  });
-}
+var encodings = {'UTF-8' : 1, 'ISO8859-1' : 1}; // gyp injection here
 
 var postMessage = function(msg, callback) {
   var reply_id = getNextReplyId();
@@ -69,7 +63,7 @@ var getFileParent = function(childPath) {
 function is_string(value) { return typeof(value) === 'string' || value instanceof String; }
 function is_integer(value) { return isFinite(value) && !isNaN(parseInt(value)); }
 function get_valid_mode(mode) {
-  if (mode == null)
+  if (mode === null)
     return 'rw';
   else if (mode === 'a' || mode === 'w' || mode === 'r' || mode === 'rw')
     return mode;
@@ -96,7 +90,8 @@ FileSystemManager.prototype.resolve = function(location, onsuccess,
   if (onerror !== null && !(onerror instanceof Function) &&
       arguments.length > 2)
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
-
+  if (arguments.length < 4)
+    mode = null;
   mode = get_valid_mode(mode);
 
   postMessage({
@@ -171,35 +166,18 @@ FileSystemManager.prototype.addStorageStateChangeListener = function(onsuccess,
 };
 
 FileSystemManager.prototype.removeStorageStateChangeListener = function(watchId) {
-  if (!(typeof(watchId) !== 'number'))
+  if (watchId !== undefined && typeof(watchId) !== 'number')
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
 
-  var index = _listeners.indexOf(watchId);
-  if (~index)
-    _listeners.slice(index, 1);
+
+  if (_listeners[watchId])
+    _listeners[watchId] = null;
   else
     throw new tizen.WebAPIException(tizen.WebAPIException.NOT_FOUND_ERR);
 };
 
-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(streamID, encoding) {
+function FileStream(streamID) {
   this.streamID = streamID;
-  this.encoding = encoding || 'UTF-8';
 
   function fs_stat(streamID) {
     var result = sendSyncMessage('FileStreamStat', { streamID: streamID });
@@ -211,9 +189,7 @@ function FileStream(streamID, encoding) {
   var getStreamID = function() {
     return streamID;
   };
-  var getEncoding = function() {
-    return encoding;
-  };
+
   var isEof = function() {
     var status = fs_stat(streamID);
     if (status.isError)
@@ -243,13 +219,11 @@ function FileStream(streamID, encoding) {
     return status.bytesAvailable;
   };
 
-  defineReadOnlyProperty(this, 'eof', false);
-  defineReadOnlyProperty(this, 'bytesAvailable', 0);
-
   Object.defineProperties(this, {
     'streamID': { get: getStreamID, enumerable: false },
-    'encoding': { get: getEncoding, enumerable: false },
-    'position': { get: getPosition, set: setPosition, enumerable: true }
+    'position': { get: getPosition, set: setPosition, enumerable: true },
+    'eof': { get: isEof, enumerable: true },
+    'bytesAvailable': { get: getBytesAvailable, enumerable: true }
   });
 }
 
@@ -260,12 +234,11 @@ FileStream.prototype.close = function() {
 };
 
 FileStream.prototype.read = function(charCount) {
-  if (arguments.length == 1 && !(is_integer(charCount)))
+  if (!(is_integer(charCount)))
     throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR);
 
   var result = sendSyncMessage('FileStreamRead', {
     streamID: this.streamID,
-    encoding: this.encoding,
     type: 'Default',
     count: charCount
   });
@@ -276,12 +249,11 @@ FileStream.prototype.read = function(charCount) {
 };
 
 FileStream.prototype.readBytes = function(byteCount) {
-  if (arguments.length == 1 && !(is_integer(byteCount)))
+  if (!(is_integer(byteCount)))
     throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR);
 
   var result = sendSyncMessage('FileStreamRead', {
     streamID: this.streamID,
-    encoding: this.encoding,
     type: 'Bytes',
     count: byteCount
   });
@@ -292,12 +264,11 @@ FileStream.prototype.readBytes = function(byteCount) {
 };
 
 FileStream.prototype.readBase64 = function(byteCount) {
-  if (arguments.length == 1 && !(is_integer(byteCount)))
+  if (!(is_integer(byteCount)))
     throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR);
 
   var result = sendSyncMessage('FileStreamRead', {
     streamID: this.streamID,
-    encoding: this.encoding,
     type: 'Base64',
     count: byteCount
   });
@@ -313,7 +284,6 @@ FileStream.prototype.write = function(stringData) {
 
   var result = sendSyncMessage('FileStreamWrite', {
     streamID: this.streamID,
-    encoding: this.encoding,
     type: 'Default',
     data: stringData
   });
@@ -327,7 +297,6 @@ FileStream.prototype.writeBytes = function(byteData) {
 
   var result = sendSyncMessage('FileStreamWrite', {
     streamID: this.streamID,
-    encoding: this.encoding,
     type: 'Bytes',
     data: byteData
   });
@@ -341,7 +310,6 @@ FileStream.prototype.writeBase64 = function(base64Data) {
 
   var result = sendSyncMessage('FileStreamWrite', {
     streamID: this.streamID,
-    encoding: this.encoding,
     type: 'Base64',
     data: base64Data
   });
@@ -465,14 +433,13 @@ File.prototype.listFiles = function(onsuccess, onerror, filter) {
   if (onerror !== null && !(onerror instanceof Function) &&
       arguments.length > 1)
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
-  if (filter !== null && !(filter instanceof FileFilter) &&
-      arguments.length > 2)
+  if (filter !== null && typeof(filter) !== 'object' && arguments.length > 2)
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
 
   postMessage({
     cmd: 'FileListFiles',
     fullPath: this.fullPath,
-    filter: filter ? filter.toString() : ''
+    filter: filter ? JSON.stringify(filter) : ''
   }, function(result) {
     if (result.isError) {
       if (onerror)
@@ -494,11 +461,11 @@ File.prototype.openStream = function(mode, onsuccess, onerror, encoding) {
   if (onerror !== null && !(onerror instanceof Function) &&
       arguments.length > 2)
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
-
+  if (mode == null)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   mode = get_valid_mode(mode);
-
-  if ((arguments.length > 3 && is_string(encoding)) &&
-      (encoding != 'UTF-8' && encoding != 'ISO-8859-1'))
+  encoding = encoding || 'UTF-8';
+  if (!is_string(encoding) || !(encoding.toUpperCase() in encodings))
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
 
   postMessage({
@@ -511,7 +478,7 @@ File.prototype.openStream = function(mode, onsuccess, onerror, encoding) {
       if (onerror)
         onerror(new tizen.WebAPIError(result.errorCode));
     } else if (onsuccess) {
-      onsuccess(new FileStream(result.streamID, result.encoding));
+      onsuccess(new FileStream(result.streamID));
     }
   });
 };
@@ -523,12 +490,12 @@ File.prototype.readAsText = function(onsuccess, onerror, encoding) {
       arguments.length > 1)
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
 
-  if ((arguments.length > 2 && is_string(encoding)) &&
-      (encoding != 'UTF-8' && encoding != 'ISO-8859-1'))
+  if ((arguments.length > 2 && encoding !== null && !is_string(encoding)))
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
 
   var streamOpened = function(stream) {
-    onsuccess(stream.read());
+    // number of characters is less than or equal to number of bytes
+    onsuccess(stream.read(stream.bytesAvailable));
     stream.close();
   };
   var streamError = function(error) {
@@ -546,6 +513,8 @@ File.prototype.readAsText = function(onsuccess, onerror, encoding) {
 
 File.prototype.copyTo = function(originFilePath, destinationFilePath,
     overwrite, onsuccess, onerror) {
+  if (!this.isDirectory)
+    onerror(new tizen.WebAPIException(tizen.WebAPIException.IO_ERR));
   // originFilePath, destinationFilePath - full virtual file path
   if (onsuccess !== null && !(onsuccess instanceof Function) &&
       arguments.length > 3)
@@ -587,6 +556,8 @@ File.prototype.copyTo = function(originFilePath, destinationFilePath,
 
 File.prototype.moveTo = function(originFilePath, destinationFilePath,
     overwrite, onsuccess, onerror) {
+  if (!this.isDirectory)
+    onerror(new tizen.WebAPIException(tizen.WebAPIException.IO_ERR));
   // originFilePath, destinationFilePath - full virtual file path
   if (onsuccess !== null && !(onsuccess instanceof Function) &&
       arguments.length > 3)
@@ -627,6 +598,8 @@ File.prototype.moveTo = function(originFilePath, destinationFilePath,
 };
 
 File.prototype.createDirectory = function(relativeDirPath) {
+  if (!this.isDirectory)
+    onerror(new tizen.WebAPIException(tizen.WebAPIException.IO_ERR));
   if (relativeDirPath.indexOf('./') >= 0)
     throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR);
 
@@ -642,6 +615,8 @@ File.prototype.createDirectory = function(relativeDirPath) {
 };
 
 File.prototype.createFile = function(relativeFilePath) {
+  if (!this.isDirectory)
+    onerror(new tizen.WebAPIException(tizen.WebAPIException.IO_ERR));
   if (relativeFilePath.indexOf('./') >= 0)
     throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR);
 
@@ -657,6 +632,8 @@ File.prototype.createFile = function(relativeFilePath) {
 };
 
 File.prototype.resolve = function(relativeFilePath) {
+  if (!this.isDirectory)
+    onerror(new tizen.WebAPIException(tizen.WebAPIException.IO_ERR));
   var status = sendSyncMessage('FileResolve', {
     fullPath: this.fullPath,
     relativeFilePath: relativeFilePath
@@ -669,6 +646,8 @@ File.prototype.resolve = function(relativeFilePath) {
 };
 
 File.prototype.deleteDirectory = function(directoryPath, recursive, onsuccess, onerror) {
+  if (!this.isDirectory)
+    onerror(new tizen.WebAPIException(tizen.WebAPIException.IO_ERR));
   // directoryPath - full virtual directory path
   if (onsuccess !== null && !(onsuccess instanceof Function) &&
       arguments.length > 2)
@@ -697,6 +676,8 @@ File.prototype.deleteDirectory = function(directoryPath, recursive, onsuccess, o
 };
 
 File.prototype.deleteFile = function(filePath, onsuccess, onerror) {
+  if (!this.isDirectory)
+    onerror(new tizen.WebAPIException(tizen.WebAPIException.IO_ERR));
   // filePath - full virtual file path
   if (onsuccess !== null && !(onsuccess instanceof Function) &&
       arguments.length > 1)
index 236c83b..fd70c88 100644 (file)
@@ -41,10 +41,11 @@ const char kStorageStateMounted[] = "MOUNTED";
 const char kStorageStateRemoved[] = "REMOVED";
 const char kStorageStateUnmountable[] = "UNMOUNTABLE";
 
+const char kPlatformEncoding[] = "UTF-8";
+const size_t kBufferSize = 1024 * 4;
+
 unsigned int lastStreamId = 0;
-// FIXME(ricardotk): This needs another approach, kMaxSize is a palliative
-// solution.
-const unsigned kMaxSize = 64 * 1024;
+
 
 bool IsWritable(const struct stat& st) {
   if (st.st_mode & S_IWOTH)
@@ -125,7 +126,7 @@ int get_dir_entry_count(const char* path) {
 }
 
 std::string GetAppId(const std::string& package_id) {
-  char *appid = NULL;
+  charappid = NULL;
   pkgmgrinfo_pkginfo_h pkginfo_handle;
   int ret = pkgmgrinfo_pkginfo_get_pkginfo(package_id.c_str(), &pkginfo_handle);
   if (ret != PMINFO_R_OK)
@@ -209,7 +210,7 @@ FilesystemInstance::~FilesystemInstance() {
   FStreamMap::iterator it;
 
   for (it = fstream_map_.begin(); it != fstream_map_.end(); it++) {
-    std::fstream* fs = it->second.second;
+    std::fstream* fs = std::get<1>(it->second);
     fs->close();
     delete(fs);
   }
@@ -323,7 +324,10 @@ void FilesystemInstance::HandleFileSystemManagerResolve(
 
   char* real_path_cstr = realpath(real_path.c_str(), NULL);
   if (!real_path_cstr) {
-    PostAsyncErrorReply(msg, IO_ERR);
+    if (errno == ENOENT)
+      PostAsyncErrorReply(msg, NOT_FOUND_ERR);
+    else
+      PostAsyncErrorReply(msg, IO_ERR);
     return;
   }
   std::string real_path_ack = std::string(real_path_cstr);
@@ -331,17 +335,17 @@ void FilesystemInstance::HandleFileSystemManagerResolve(
 
   if (check_if_inside_default &&
       real_path_ack.find(
-          tzplatform_getenv(TZ_USER_CONTENT)) != std::string::npos) {
+          tzplatform_getenv(TZ_USER_CONTENT)) == std::string::npos) {
     PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
     return;
   }
-
   struct stat st;
   if (stat(real_path_ack.c_str(), &st) < 0) {
-    if (errno == ENOENT || errno == ENOTDIR)
+    if (errno == ENOENT || errno == ENOTDIR) {
       PostAsyncErrorReply(msg, NOT_FOUND_ERR);
-    else
+    } else {
       PostAsyncErrorReply(msg, IO_ERR);
+    }
     return;
   }
 
@@ -349,7 +353,6 @@ void FilesystemInstance::HandleFileSystemManagerResolve(
     PostAsyncErrorReply(msg, IO_ERR);
     return;
   }
-
   picojson::value::object o;
   o["fullPath"] = picojson::value(location);
   PostAsyncSuccessReply(msg, o);
@@ -408,11 +411,17 @@ void FilesystemInstance::HandleFileOpenStream(const picojson::value& msg) {
   std::string encoding = "";
   if (msg.contains("encoding"))
     encoding = msg.get("encoding").to_str();
-  if (!encoding.empty() && (encoding != "UTF-8" && encoding != "ISO-8859-1")) {
-    PostAsyncErrorReply(msg, TYPE_MISMATCH_ERR);
+  if (encoding.empty()) {
+    encoding = kPlatformEncoding;
+  }
+  // is the encoding supported by iconv?
+  iconv_t cd = iconv_open("UTF-8", encoding.c_str());
+  if (cd == reinterpret_cast<iconv_t>(-1)) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    iconv_close(cd);
     return;
   }
-
+  iconv_close(cd);
   if (!msg.contains("fullPath")) {
     PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
     return;
@@ -438,22 +447,20 @@ void FilesystemInstance::HandleFileOpenStream(const picojson::value& msg) {
     PostAsyncErrorReply(msg, IO_ERR);
     return;
   }
-
   std::fstream* fs = new std::fstream(real_path_cstr, open_mode);
   if (!(*fs) || !fs->is_open()) {
     free(real_path_cstr);
+    delete fs;
     PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
     return;
   }
   free(real_path_cstr);
 
-  fstream_map_[lastStreamId] = FStream(open_mode, fs);
+  fstream_map_[lastStreamId] = FStream(open_mode, fs, encoding);
 
   picojson::value::object o;
   o["streamID"] = picojson::value(static_cast<double>(lastStreamId));
-  o["encoding"] = picojson::value(encoding);
   lastStreamId++;
-
   PostAsyncSuccessReply(msg, o);
 }
 
@@ -542,6 +549,9 @@ void FilesystemInstance::HandleFileDeleteFile(const picojson::value& msg) {
     case ENOENT:
       PostAsyncErrorReply(msg, NOT_FOUND_ERR);
       break;
+    case EISDIR:
+      PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+      break;
     default:
       PostAsyncErrorReply(msg, UNKNOWN_ERR);
     }
@@ -587,37 +597,81 @@ void FilesystemInstance::HandleFileListFiles(const picojson::value& msg) {
   PostAsyncSuccessReply(msg, v);
 }
 
+std::string FilesystemInstance::ResolveImplicitDestination(
+    const std::string& from, const std::string& to) {
+  // Resolve implicit destination paths
+  // Sanity checks are done later, in CopyAndRenameSanityChecks
+  if (to.empty())
+    return "";
+
+  std::string explicit_to = to;
+  if (*to.rbegin() == '/' || *to.rbegin() == '\\') {
+    // 1. hinted paths
+    std::string::size_type found = from.find_last_of("/\\");
+    explicit_to.append(from.substr(found + 1));
+  } else {
+    // 2. no hint, apply heuristics on path types
+    // i.e. if we copy a file to a directory, we copy it into that directory
+    // as a file with the same name
+    struct stat to_st;
+    struct stat from_st;
+    if (stat(from.c_str(), &from_st) == 0 &&
+        stat(to.c_str(), &to_st) == 0 &&
+        S_ISREG(from_st.st_mode) &&
+        S_ISDIR(to_st.st_mode)) {
+      std::string::size_type found = from.find_last_of("/\\");
+      explicit_to.append(from.substr(found));  // including '/' to join
+    }
+  }
+  return explicit_to;
+}
 
 bool FilesystemInstance::CopyAndRenameSanityChecks(const picojson::value& msg,
-      const std::string& from, const std::string& to, bool overwrite) {
+    const std::string& from, const std::string& to, bool overwrite) {
+  if (from.empty() || to.empty()) {
+    PostAsyncErrorReply(msg, NOT_FOUND_ERR);
+    return false;
+  }
   bool destination_file_exists = true;
   if (access(to.c_str(), F_OK) < 0) {
     if (errno == ENOENT) {
       destination_file_exists = false;
     } else {
       PostAsyncErrorReply(msg, IO_ERR);
+      std::cerr << "destination unreachable\n";
       return false;
     }
   }
 
-  unsigned found = to.find_last_of("/\\");
+  std::string::size_type found = to.find_last_of("/\\");
   struct stat destination_parent_st;
   if (stat(to.substr(0, found).c_str(), &destination_parent_st) < 0) {
     PostAsyncErrorReply(msg, IO_ERR);
+    std::cerr << "parent of destination does not exist\n";
     return false;
   }
 
   if (overwrite && !IsWritable(destination_parent_st)) {
     PostAsyncErrorReply(msg, IO_ERR);
+    std::cerr << "parent of destination is not writable (overwrite is true)\n";
     return false;
   }
   if (!overwrite && destination_file_exists) {
     PostAsyncErrorReply(msg, IO_ERR);
+    std::cerr << "destination exists and overwrite is false\n";
     return false;
   }
 
   if (access(from.c_str(), F_OK)) {
     PostAsyncErrorReply(msg, NOT_FOUND_ERR);
+    std::cerr << "origin does not exist\n";
+    return false;
+  }
+
+  // don't copy or move into itself
+  if (to.length() >= from.length() && to.compare(0, from.length(), from) == 0) {
+    PostAsyncErrorReply(msg, IO_ERR);
+    std::cerr << "won't copy/move into itself\n";
     return false;
   }
 
@@ -687,6 +741,67 @@ ssize_t PosixFile::Write(char* buffer, size_t count) {
   }
 }
 
+bool CopyElement(const std::string &from, const std::string &to) {
+  struct stat from_st;
+  // element is a file
+  if (stat(from.c_str(), &from_st) == 0 && S_ISREG(from_st.st_mode)) {
+    PosixFile origin(from, O_RDONLY);
+    if (!origin.is_valid()) {
+      std::cerr << "from: " << from << " is invalid\n";
+      return false;
+    }
+
+    PosixFile destination(to, O_WRONLY | O_CREAT | O_TRUNC);
+    if (!destination.is_valid()) {
+      std::cerr << "to: " << to << " is invalid\n";
+      return false;
+    }
+
+    while (true) {
+      char buffer[kBufferSize];
+      ssize_t read_bytes = origin.Read(buffer, kBufferSize);
+      if (!read_bytes)
+        break;
+      if (read_bytes < 0) {
+        std::cerr << "read error\n";
+        return false;
+      }
+
+      if (destination.Write(buffer, read_bytes) < 0) {
+        std::cerr << "write error\n";
+        return false;
+      }
+    }
+
+    destination.UnlinkWhenDone(false);
+    return true;
+  }  // end file case
+
+  // element is a directory, create if not exists
+  int status = mkdir(to.c_str(), kDefaultFileMode);
+  if (status != 0 && errno != EEXIST) {
+    std::cerr << "failed to create destination dir: " << to << std::endl;
+    return false;
+  }
+  // recursively copy content
+  DIR* dir;
+  dir = opendir(from.c_str());
+  dirent* elt;
+  while ((elt = readdir(dir)) != NULL) {
+    if (!strcmp(elt->d_name, ".") || !strcmp(elt->d_name, ".."))
+      continue;
+    const std::string filename = elt->d_name;
+    const std::string full_origin = from + "/" + filename;
+    const std::string full_destination = to + "/" + filename;
+    if (!CopyElement(full_origin, full_destination)) {
+      closedir(dir);
+      return false;
+    }
+  }
+  closedir(dir);
+  return true;
+}
+
 }  // namespace
 
 void FilesystemInstance::HandleFileCopyTo(const picojson::value& msg) {
@@ -701,51 +816,18 @@ void FilesystemInstance::HandleFileCopyTo(const picojson::value& msg) {
 
   bool overwrite = msg.get("overwrite").evaluate_as_boolean();
   std::string real_origin_path =
-     GetRealPath(msg.get("originFilePath").to_str());
+      GetRealPath(msg.get("originFilePath").to_str());
   std::string real_destination_path =
-     GetRealPath(msg.get("destinationFilePath").to_str());
-
-  if (*real_destination_path.rbegin() == '/' ||
-      *real_destination_path.rbegin() == '\\') {
-    unsigned found = real_origin_path.find_last_of("/\\");
-    real_destination_path.append(real_origin_path.substr(found + 1));
-  }
+      ResolveImplicitDestination(real_origin_path,
+      GetRealPath(msg.get("destinationFilePath").to_str()));
 
   if (!CopyAndRenameSanityChecks(msg, real_origin_path, real_destination_path,
                                  overwrite))
     return;
-
-  PosixFile origin(real_origin_path, O_RDONLY);
-  if (!origin.is_valid()) {
-    PostAsyncErrorReply(msg, IO_ERR);
-    return;
-  }
-
-  PosixFile destination(real_destination_path, O_WRONLY | O_CREAT |
-                                               (overwrite ? O_TRUNC : O_EXCL));
-  if (!destination.is_valid()) {
+  if (CopyElement(real_origin_path, real_destination_path))
+    PostAsyncSuccessReply(msg);
+  else
     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 FilesystemInstance::HandleFileMoveTo(const picojson::value& msg) {
@@ -760,9 +842,10 @@ void FilesystemInstance::HandleFileMoveTo(const picojson::value& msg) {
 
   bool overwrite = msg.get("overwrite").evaluate_as_boolean();
   std::string real_origin_path =
-     GetRealPath(msg.get("originFilePath").to_str());
+      GetRealPath(msg.get("originFilePath").to_str());
   std::string real_destination_path =
-     GetRealPath(msg.get("destinationFilePath").to_str());
+      ResolveImplicitDestination(real_origin_path,
+      GetRealPath(msg.get("destinationFilePath").to_str()));
 
   if (!CopyAndRenameSanityChecks(msg, real_origin_path, real_destination_path,
                                  overwrite))
@@ -811,8 +894,7 @@ void FilesystemInstance::HandleSyncMessage(const char* message) {
   else if (cmd == "FileStreamSetPosition")
     HandleFileStreamSetPosition(v, reply);
   else
-    std::cout << "Ignoring unknown command: " << cmd;
-
+    std::cout << "Ignoring unknown command: " << cmd << std::endl;
   if (!reply.empty())
     SendSyncReply(reply.c_str());
 }
@@ -839,23 +921,30 @@ std::fstream* FilesystemInstance::GetFileStream(unsigned int key) {
   FStreamMap::iterator it = fstream_map_.find(key);
   if (it == fstream_map_.end())
     return NULL;
-  std::fstream* fs = it->second.second;
+  std::fstream* fs = std::get<1>(it->second);
 
   if (fs->is_open())
     return fs;
   return NULL;
 }
 
+std::string FilesystemInstance::GetFileEncoding(unsigned int key) const {
+  FStreamMap::const_iterator it = fstream_map_.find(key);
+  if (it == fstream_map_.end())
+    return kPlatformEncoding;
+  return std::get<2>(it->second);
+}
+
 std::fstream* FilesystemInstance::GetFileStream(unsigned int key,
     std::ios_base::openmode mode) {
   FStreamMap::iterator it = fstream_map_.find(key);
   if (it == fstream_map_.end())
     return NULL;
 
-  if ((it->second.first & mode) != mode)
+  if ((std::get<0>(it->second) & mode) != mode)
     return NULL;
 
-  std::fstream* fs = it->second.second;
+  std::fstream* fs = std::get<1>(it->second);
 
   if (fs->is_open())
     return fs;
@@ -913,7 +1002,7 @@ void FilesystemInstance::HandleFileStreamClose(const picojson::value& msg,
 
   FStreamMap::iterator it = fstream_map_.find(key);
   if (it != fstream_map_.end()) {
-    std::fstream* fs = it->second.second;
+    std::fstream* fs = std::get<1>(it->second);
     if (fs->is_open())
       fs->close();
     delete fs;
@@ -1024,29 +1113,6 @@ std::string ConvertFrom(std::string input) {
 
 }  // namespace base64
 
-std::string ConvertCharacterEncoding(const char* from_encoding,
-                                     const char* to_encoding, char* buffer,
-                                     size_t buffer_len) {
-  iconv_t cd = iconv_open(from_encoding, to_encoding);
-
-  char converted[kMaxSize];
-  char *converted_buffer = converted;
-  size_t converted_len = sizeof(converted) - 1;
-
-  do {
-    if (iconv(cd, &buffer, &buffer_len, &converted_buffer, &converted_len)
-        == (size_t) -1) {
-      iconv_close(cd);
-      return "";
-    }
-  } while (buffer_len > 0 && converted_len > 0);
-  *converted_buffer = 0;
-
-  iconv_close(cd);
-
-  return std::string(converted, converted_len);
-}
-
 }  // namespace
 
 void FilesystemInstance::HandleFileStreamRead(const picojson::value& msg,
@@ -1058,34 +1124,45 @@ void FilesystemInstance::HandleFileStreamRead(const picojson::value& msg,
   unsigned int key = msg.get("streamID").get<double>();
 
   std::streamsize count;
-  if (msg.contains("count"))
+  if (msg.contains("count")) {
     count = msg.get("count").get<double>();
-  else
-    count = kMaxSize;
-
+  } else {
+    // count is not optional
+    SetSyncError(reply, IO_ERR);
+    return;
+  }
   std::fstream* fs = GetFileStream(key, std::ios_base::in);
   if (!fs) {
     SetSyncError(reply, IO_ERR);
     return;
   }
 
-  std::streampos initial_pos = fs->tellg();
-  char buffer[kMaxSize] = { 0 };
-  fs->read(buffer, count);
+  if (msg.get("type").to_str() == "Default") {
+    // we want decoded text data
+    // depending on encoding, a character (a.k.a. a glyph) may take
+    // one or several bytes in input and in output as well.
+    std::string encoding = GetFileEncoding(key);
+    ReadText(fs, count, encoding.c_str(), reply);
+    return;
+  }
+  // we want binary data
+  std::string buffer;
+  buffer.resize(count);
+  fs->read(&buffer[0], count);
+  std::streamsize bytes_read = fs->gcount();
   fs->clear();
-  std::streampos bytes_read = fs->tellg() - initial_pos;
-
   if (fs->bad()) {
     fs->clear();
     SetSyncError(reply, IO_ERR);
     return;
   }
+  buffer.resize(bytes_read);
 
   if (msg.get("type").to_str() == "Bytes") {
+    // return binary data as numeric array
     picojson::value::array a;
 
     for (int i = 0; i < bytes_read; i++) {
-      if (+buffer[i] != 0)
         a.push_back(picojson::value(static_cast<double>(buffer[i])));
     }
 
@@ -1094,20 +1171,14 @@ void FilesystemInstance::HandleFileStreamRead(const picojson::value& msg,
     return;
   }
 
-  std::string buffer_as_string;
-  if (msg.get("encoding").to_str() == "ISO-8859-1")
-    buffer_as_string = ConvertCharacterEncoding("ISO_8859-1", "UTF-8", buffer,
-                                                bytes_read);
-  else
-    buffer_as_string = std::string(buffer, bytes_read);
-
   if (msg.get("type").to_str() == "Base64") {
-    std::string base64_buffer = base64::ConvertTo(buffer_as_string);
+    // return binary data as Base64 encoded string
+    std::string base64_buffer = base64::ConvertTo(buffer);
     SetSyncSuccess(reply, base64_buffer);
     return;
   }
 
-  SetSyncSuccess(reply, buffer_as_string);
+  SetSyncSuccess(reply, buffer);
 }
 
 void FilesystemInstance::HandleFileStreamWrite(const picojson::value& msg,
@@ -1137,21 +1208,47 @@ void FilesystemInstance::HandleFileStreamWrite(const picojson::value& msg,
   } else if (msg.get("type").to_str() == "Base64") {
     buffer = base64::ConvertFrom(msg.get("data").to_str());
   } else {
-    buffer = msg.get("data").to_str();
+    // text mode
+    std::string text = msg.get("data").to_str();
+    std::string encoding = GetFileEncoding(key);
+    if (encoding != "UTF-8" && encoding != "utf-8") {
+      // transcode
+      iconv_t cd = iconv_open(encoding.c_str(), "UTF-8");
+      char encode_buf[kBufferSize];
+      // ugly cast for inconsistent iconv prototype
+      char* in_p = const_cast<char*>(text.data());
+      size_t in_bytes_left = text.length();
+      while (in_bytes_left > 0) {
+        char* out_p = encode_buf;
+        size_t out_bytes_free = kBufferSize;
+        size_t icnv = iconv(cd, &in_p, &in_bytes_left, &out_p, &out_bytes_free);
+        if (icnv == static_cast<size_t>(-1)) {
+          switch (errno) {
+            case E2BIG:
+              // expected case if encode_buf is full
+              break;
+            case EINVAL:
+            case EILSEQ:
+            default:
+              iconv_close(cd);
+              SetSyncError(reply, IO_ERR);
+              return;
+          }
+        }
+        buffer.append(encode_buf, kBufferSize-out_bytes_free);
+      }
+      iconv_close(cd);
+    } else {
+      buffer = text;
+    }
   }
 
-  // FIXME(ricardotk): get default platform encoding mode and compare.
-  if (msg.get("encoding").to_str() == "ISO-8859-1")
-    buffer = ConvertCharacterEncoding("ISO_8859-1", "UTF-8",
-                                                &buffer[0], buffer.length());
-
   if (!((*fs) << buffer)) {
     fs->clear();
     SetSyncError(reply, IO_ERR);
     return;
   }
   fs->flush();
-
   SetSyncSuccess(reply);
 }
 
@@ -1179,7 +1276,7 @@ void FilesystemInstance::HandleFileCreateDirectory(const picojson::value& msg,
     return;
   }
 
-  if (mkdir(real_path.c_str(), kDefaultFileMode) < 0) {
+  if (!makePath(real_path)) {
     SetSyncError(reply, IO_ERR);
     return;
   }
@@ -1333,12 +1430,11 @@ void FilesystemInstance::HandleFileStreamStat(const picojson::value& msg,
     return;
   }
 
-  std::streampos bytes_read = -1;
+  std::streampos fsize = 0;
   if (!fs->eof()) {
     std::streampos initial_pos = fs->tellg();
-    char buffer[kMaxSize] = { 0 };
-    fs->read(buffer, kMaxSize);
-    bytes_read = fs->tellg() - initial_pos;
+    fs->seekg(0, std::ios::end);
+    fsize = fs->tellg() - initial_pos;
     if (fs->bad()) {
       fs->clear();
       SetSyncError(reply, IO_ERR);
@@ -1353,11 +1449,10 @@ void FilesystemInstance::HandleFileStreamStat(const picojson::value& msg,
       return;
     }
   }
-
   picojson::value::object o;
   o["position"] = picojson::value(static_cast<double>(fs->tellg()));
   o["eof"] = picojson::value(fs->eof());
-  o["bytesAvailable"] = picojson::value(static_cast<double>(bytes_read));
+  o["bytesAvailable"] = picojson::value(static_cast<double>(fsize));
 
   picojson::value v(o);
   SetSyncSuccess(reply, v);
@@ -1388,10 +1483,165 @@ void FilesystemInstance::HandleFileStreamSetPosition(const picojson::value& msg,
     SetSyncError(reply, IO_ERR);
     return;
   }
-
   SetSyncSuccess(reply);
 }
 
+namespace {
+
+/**
+ * Request req_char_num characters (glyphs) from buffer s of length slen.
+ * Set actual_char_num to the number of actually available characters
+ * (actual_char_num <= req_char_num)
+ * Set bytes_num to the number of corresponding bytes in the buffer.
+ * Beware! the buffer must contain complete and valid UTF-8 character sequences.
+ */
+bool GetCharsFromBytes(char* s, int slen, int req_char_num,
+    size_t* actual_char_num, size_t* bytes_num) {
+  int i = 0;
+  int j = 0;
+  while (i < slen && j < req_char_num) {
+    if (s[i] > 0) {
+      i++;
+    } else if ((s[i] & 0xE0) == 0xC0) {
+      i += 2;
+    } else if ((s[i] & 0xF0) == 0xE0) {
+      i += 3;
+    } else if ((s[i] & 0xF8) == 0xF0) {
+      i += 4;
+    // these should never happen (restriction of unicode under 0x10FFFF)
+    // but belong to the UTF-8 standard yet.
+    } else if ((s[i] & 0xFC) == 0xF8) {
+      i += 5;
+    } else if ((s[i] & 0xFE) == 0xFC) {
+      i += 6;
+    } else {
+      std::cerr << "Invalid UTF-8!" << std::endl;
+      return false;
+    }
+    j++;
+  }
+  (*actual_char_num) = j;
+  (*bytes_num) = i;
+  return true;
+}
+
+}  // namespace
+
+void FilesystemInstance::ReadText(std::fstream* file, size_t num_chars,
+    const char* encoding, std::string& reply) {
+  iconv_t cd = iconv_open("UTF-8", encoding);
+
+  char inbuffer[kBufferSize];
+  char utf8buffer[kBufferSize];
+  std::string out;
+  int strlength = 0;
+
+  bool out_of_space = false;
+  bool partial_remains = false;
+  // number of bytes already at start of buffer
+  size_t offset = 0;
+  // As we can't predict how much data to read, we may convert more
+  // data than needed. Keep track of excess (converted) bytes in utf8buffer.
+  size_t excess_offset = 0;
+  size_t excess_len = 0;
+  std::streampos original_pos = file->tellg();
+
+  while (strlength < num_chars && !file->eof()) {
+    file->read(inbuffer + offset, kBufferSize - offset);
+    size_t src_bytes_left = file->gcount()+offset;
+
+    char* in_p = inbuffer;
+    do {
+      char* utf8_p = utf8buffer;
+      size_t utf8_bytes_free = kBufferSize;
+      out_of_space = false;
+      partial_remains = false;
+
+      size_t icnv = iconv(cd, &in_p, &src_bytes_left, &utf8_p,
+          &utf8_bytes_free);
+
+      if (icnv == static_cast<size_t>(-1)) {
+        switch (errno) {
+          case E2BIG:
+            out_of_space = true;
+            break;
+          case EINVAL:
+            partial_remains = true;
+            break;
+          case EILSEQ:
+          default:
+            iconv_close(cd);
+            // restore filepos
+            file->clear();
+            file->seekg(original_pos);
+            SetSyncError(reply, IO_ERR);
+            return;
+        }
+      }
+      int missing = num_chars - strlength;
+      size_t available = 0;
+      size_t datalen;
+      size_t utf8load = kBufferSize - utf8_bytes_free;
+      if (!GetCharsFromBytes(utf8buffer, utf8load,
+          missing, &available, &datalen)) {
+        iconv_close(cd);
+        // restore filepos
+        file->clear();
+        file->seekg(original_pos);
+        SetSyncError(reply, IO_ERR);
+        return;
+      }
+      out.append(utf8buffer, datalen);
+      strlength += available;
+      if (datalen < utf8load) {
+        excess_offset = datalen;
+        excess_len = utf8load - datalen;
+        out_of_space = false;
+        partial_remains = (src_bytes_left > 0);
+      }
+    } while (out_of_space);
+
+    if (partial_remains) {
+      // some bytes remains at the end of the buffer that were not converted
+      // move them to the beginning of the inbuffer before completing with data
+      // from disk
+      if (strlength < num_chars)
+        memmove(inbuffer, inbuffer + kBufferSize - src_bytes_left,
+            src_bytes_left);
+      offset = src_bytes_left;
+    } else {
+      offset = 0;
+    }
+  }
+
+  iconv_close(cd);
+  std::streampos back_jump = 0;
+  if (offset > 0) {
+    back_jump = offset;
+  }
+  if (excess_len > 0) {
+    // we've read too much, so reposition the file
+    // first, convert back(!) to compute input data size
+    cd = iconv_open(encoding, "UTF-8");
+    char* in_p = utf8buffer + excess_offset;
+    char* out_p = inbuffer;
+    size_t free_bytes = kBufferSize;
+    iconv(cd, &in_p, &excess_len, &out_p, &free_bytes);
+    if (!strcasecmp(encoding, "UTF16") || !strcasecmp(encoding, "UTF-16")) {
+      // don't count the BOM added by iconv
+      free_bytes += 2;
+    }
+    back_jump += (kBufferSize-free_bytes);
+    iconv_close(cd);
+  }
+  if (back_jump > 0) {
+    file->clear();
+    file->seekg(file->tellg()-back_jump);
+  }
+  SetSyncSuccess(reply, out);
+  return;
+}
+
 std::string FilesystemInstance::GetRealPath(const std::string& fullPath) {
   std::size_t pos = fullPath.find_first_of('/');
   std::string virtual_root = fullPath;
@@ -1460,7 +1710,7 @@ void FilesystemInstance::NotifyStorageStateChanged(int id,
 
 bool FilesystemInstance::OnStorageDeviceSupported(
     int id, storage_type_e type, storage_state_e state,
-    const char *path, void* user_data) {
+    const charpath, void* user_data) {
   reinterpret_cast<FilesystemInstance*>(user_data)->AddStorage(
       id, type, state, path);
   return true;
index 9fc4382..19be05d 100644 (file)
@@ -100,6 +100,11 @@ class FilesystemInstance : public common::Instance {
   bool IsKnownFileStream(const picojson::value& msg);
   std::fstream* GetFileStream(unsigned int key);
   std::fstream* GetFileStream(unsigned int key, std::ios_base::openmode mode);
+  std::string GetFileEncoding(unsigned int key) const;
+  void ReadText(std::fstream* file, size_t num_chars, const char* encoding,
+      std::string& reply);
+  std::string ResolveImplicitDestination(const std::string& from,
+      const std::string& to);
   bool CopyAndRenameSanityChecks(const picojson::value& msg,
       const std::string& from, const std::string& to, bool overwrite);
   void SetSyncError(std::string& output, WebApiAPIErrors error_type);
@@ -117,7 +122,8 @@ class FilesystemInstance : public common::Instance {
   static void OnStorageStateChanged(int id, storage_state_e state,
       void *user_data);
 
-  typedef std::pair<std::ios_base::openmode, std::fstream*> FStream;
+  typedef std::tuple<std::ios_base::openmode, std::fstream*,
+      std::string> FStream;
   typedef std::map<unsigned int, FStream> FStreamMap;
   FStreamMap fstream_map_;
   typedef std::map<std::string, Storage> Storages;
diff --git a/filesystem/tools/inject_encodings.py b/filesystem/tools/inject_encodings.py
new file mode 100644 (file)
index 0000000..4435056
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2014 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.
+
+"""
+Supported encodings (iconv), with aliases.
+The spec seems to prefer asynchronous checks (more efficient)
+but the TCT tests expect immediate checking of encoding support,
+so we have to inject iconv supported encodings into the js source.
+"""
+
+
+import subprocess, json, sys
+
+raw_encodings = subprocess.check_output(["iconv", "-l"])
+encodings = dict((line.split('/')[0], 1) for line in raw_encodings.split() if line)
+src = open(sys.argv[1]).readlines()
+dest = open(sys.argv[2], 'w')
+for line in src:
+    if line.startswith("var encodings = "):
+        dest.write("var encodings = %s;\n" % json.dumps(encodings))
+    else:
+        dest.write(line)
+dest.close()
+exit(0)