[Filesystem] FileHandle handling methods implementation. 68/176468/26
authorArkadiusz Pietraszek <a.pietraszek@partner.samsung.com>
Thu, 19 Apr 2018 10:35:16 +0000 (12:35 +0200)
committerPiotr Kosko <p.kosko@samsung.com>
Mon, 14 May 2018 12:26:19 +0000 (14:26 +0200)
ACR:
http://suprem.sec.samsung.net/jira/browse/TWDAPI-121

Change-Id: Ie0bc67b1858ad9638942d9a0c93ee799316c05ee
Signed-off-by: Szymon Jastrzebski <s.jastrzebsk@partner.samsung.com>
Signed-off-by: Arkadiusz Pietraszek <a.pietraszek@partner.samsung.com>
Signed-off-by: Jakub Skowron <j.skowron@samsung.com>
Signed-off-by: Pawel Wasowski <p.wasowski2@partner.samsung.com>
src/filesystem/filesystem_instance.cc
src/filesystem/filesystem_instance.h

index c8faf71..5c0f341 100644 (file)
@@ -38,6 +38,45 @@ namespace {
 // The privileges that required in Filesystem API
 const std::string kPrivilegeFilesystemRead = "http://tizen.org/privilege/filesystem.read";
 const std::string kPrivilegeFilesystemWrite = "http://tizen.org/privilege/filesystem.write";
+
+std::string GetFopenMode(const picojson::value& args) {
+  ScopeLogger();
+  const std::string& open_mode = args.get("openMode").get<std::string>();
+
+  if ("rw" == open_mode) {
+    return "r+";
+  } else if ("rwo" == open_mode) {
+    return "w+";
+  }
+
+  return open_mode;
+}
+
+bool WriteAccessRequested(const picojson::value& args) {
+  ScopeLogger();
+  const std::string& open_mode = args.get("openMode").get<std::string>();
+
+  return std::string::npos != open_mode.find("w") || "a" == open_mode;
+}
+
+bool ReadAccessRequested(const picojson::value& args) {
+  ScopeLogger();
+  const std::string& open_mode = args.get("openMode").get<std::string>();
+
+  return std::string::npos != open_mode.find("r");
+}
+
+bool ShouldMakeParents(const picojson::value& args) {
+  ScopeLogger();
+  const std::string& path = args.get("path").get<std::string>();
+  struct stat buf {};
+  if (FilesystemUtils::CheckIfExists(FilesystemUtils::Dirname(path), &buf)) {
+    return false;
+  }
+  bool make_parents = args.get("makeParents").get<bool>();
+  const std::string& open_mode = args.get("openMode").get<std::string>();
+  return make_parents && ("w" == open_mode || "a" == open_mode || "rwo" == open_mode);
+}
 }
 
 using namespace common;
@@ -157,6 +196,7 @@ FilesystemInstance::FilesystemInstance() {
   REGISTER_SYNC("File_copyTo", CopyTo);
   REGISTER_SYNC("FileSystemManager_getCanonicalPath", FileSystemManagerGetCanonicalPath);
 
+  REGISTER_SYNC("FileSystemManager_openFile", FileSystemManagerOpenFile);
   REGISTER_SYNC("FileSystemManager_createDirectory", FileSystemManagerCreateDirectory);
   REGISTER_SYNC("FileSystemManager_deleteFile", FileSystemManagerDeleteFile);
   REGISTER_SYNC("FileSystemManager_deleteDirectory", FileSystemManagerDeleteDirectory);
@@ -171,6 +211,15 @@ FilesystemInstance::FilesystemInstance() {
   REGISTER_SYNC("FileSystemManager_pathExists", FileSystemManagerPathExists);
   REGISTER_SYNC("FileSystemManager_getLimits", FileSystemManagerGetLimits);
 
+  REGISTER_SYNC("FileHandle_seek", FileHandleSeek);
+  REGISTER_SYNC("FileHandle_readString", FileHandleReadString);
+  REGISTER_SYNC("FileHandle_writeString", FileHandleWriteString);
+  REGISTER_SYNC("FileHandle_readData", FileHandleReadData);
+  REGISTER_SYNC("FileHandle_writeData", FileHandleWriteData);
+  REGISTER_SYNC("FileHandle_flush", FileHandleFlush);
+  REGISTER_SYNC("FileHandle_sync", FileHandleSync);
+  REGISTER_SYNC("FileHandle_close", FileHandleClose);
+
 #undef REGISTER_SYNC
   FilesystemManager::GetInstance().AddListener(this);
 }
@@ -423,6 +472,161 @@ void write_file(const std::uint8_t* data, std::size_t len, std::string path, lon
   write_file(data, len, file);
 }
 
+#define FIRST_BIT_MASK 0x80
+#define SECOND_BIT_MASK 0x40
+#define THIRD_BIT_MASK 0x20
+#define FOURTH_BIT_MASK 0x10
+#define FIFTH_BIT_MASK 0x08
+
+enum class ByteOfUTF8Classification {
+  NOT_VALID,
+  ONE_BYTE_CHAR,
+  FIRST_OF_2_BYTES,
+  FIRST_OF_3_BYTES,
+  FIRST_OF_4_BYTES,
+  EXTENSION_BYTE
+};
+
+ByteOfUTF8Classification is_utf8_byte(uint8_t byte) {
+  if (FIRST_BIT_MASK & byte) {
+    if (SECOND_BIT_MASK & byte) {
+      if (THIRD_BIT_MASK & byte) {
+        if (FOURTH_BIT_MASK & byte) {
+          if (FIFTH_BIT_MASK & byte) {
+            return ByteOfUTF8Classification::NOT_VALID;
+          } else {
+            return ByteOfUTF8Classification::FIRST_OF_4_BYTES;
+          }
+        } else {
+          return ByteOfUTF8Classification::FIRST_OF_3_BYTES;
+        }
+      } else {
+        return ByteOfUTF8Classification::FIRST_OF_2_BYTES;
+      }
+    } else {
+      return ByteOfUTF8Classification::EXTENSION_BYTE;
+    }
+  } else {
+    return ByteOfUTF8Classification::ONE_BYTE_CHAR;
+  }
+}
+
+void skip_partial_character(std::vector<std::uint8_t>& buf) {
+  auto buf_position = buf.begin();
+
+  while (buf.end() != buf_position &&
+         (ByteOfUTF8Classification::EXTENSION_BYTE == is_utf8_byte(*buf_position))) {
+    buf_position = buf.erase(buf_position);
+    LoggerD("Removed UTF-8 Extension Byte from begining of read buffer");
+  }
+}
+
+bool validate_and_check_character_count(std::vector<std::uint8_t>& buf, unsigned long& char_count,
+                                        short& no_of_extensions_expected) {
+  ScopeLogger();
+  char_count = 0;
+  no_of_extensions_expected = 0;
+  auto buf_position = buf.begin();
+
+  skip_partial_character(buf);
+
+  while (buf.end() != buf_position) {
+    switch (is_utf8_byte(*buf_position)) {
+      case ByteOfUTF8Classification::EXTENSION_BYTE:
+        no_of_extensions_expected--;
+        if (0 > no_of_extensions_expected) {
+          return false;
+        } else if (0 == no_of_extensions_expected) {
+          char_count++;
+        }
+        break;
+      case ByteOfUTF8Classification::ONE_BYTE_CHAR:
+        if (0 != no_of_extensions_expected) {
+          return false;
+        }
+        char_count++;
+        break;
+      case ByteOfUTF8Classification::FIRST_OF_2_BYTES:
+        if (0 != no_of_extensions_expected) {
+          return false;
+        }
+        no_of_extensions_expected = 1;
+        break;
+      case ByteOfUTF8Classification::FIRST_OF_3_BYTES:
+        if (0 != no_of_extensions_expected) {
+          return false;
+        }
+        no_of_extensions_expected = 2;
+        break;
+      case ByteOfUTF8Classification::FIRST_OF_4_BYTES:
+        if (0 != no_of_extensions_expected) {
+          return false;
+        }
+        no_of_extensions_expected = 3;
+        break;
+      case ByteOfUTF8Classification::NOT_VALID:
+        return false;
+      default:
+        LoggerE("Abnormal value returned from is_utf8_byte function");
+    }
+    buf_position++;
+  }
+  return true;
+}
+
+/*
+ * add_utf8_chars_to_buffer
+ * Method returns false if byte read is not a valid utf8 char
+ */
+bool add_utf8_chars_to_buffer(FILE* file, std::vector<std::uint8_t>& buf, int chars_to_read,
+                              short& extension_bytes_expected) {
+  int character;
+  LoggerD("chars_to_read: %i", chars_to_read);
+  LoggerD("extension_bytes_expected: %i", extension_bytes_expected);
+  while (chars_to_read) {
+    if (extension_bytes_expected) {
+      character = getc(file);
+      if (EOF == character) {
+        return false;
+      }
+      buf.push_back(character);
+      if (!--extension_bytes_expected) {
+        chars_to_read--;
+      }
+      continue;
+    }
+    character = getc(file);
+    if(EOF == character) {
+      return false;
+    }
+    buf.push_back(character);
+    switch (is_utf8_byte(character)) {
+      case ByteOfUTF8Classification::ONE_BYTE_CHAR:
+        chars_to_read--;
+        break;
+      case ByteOfUTF8Classification::FIRST_OF_2_BYTES:
+        extension_bytes_expected = 1;
+        break;
+      case ByteOfUTF8Classification::FIRST_OF_3_BYTES:
+        extension_bytes_expected = 2;
+        break;
+      case ByteOfUTF8Classification::FIRST_OF_4_BYTES:
+        extension_bytes_expected = 3;
+        break;
+      case ByteOfUTF8Classification::EXTENSION_BYTE:
+        LoggerD("unexpected EXTENSION_BYTE");
+        return false;
+      case ByteOfUTF8Classification::NOT_VALID:
+        LoggerE("unexpected NOT_VALID byte while reading utf-8");
+        return false;
+      default:
+        LoggerE("Abnormal. Last read char: %c: ", character);
+        return false;
+    }
+  }
+  return true;
+}
+
 namespace base64 {
 static const char int_to_char[] =
     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -1018,6 +1222,87 @@ void FilesystemInstance::FileSystemManagerGetCanonicalPath(const picojson::value
   FilesystemManager::GetInstance().GetCanonicalPath(path, onSuccess, onError);
 }
 
+
+namespace {
+
+FILE* OpenFile(const std::string& path, const std::string& fopen_mode) {
+  FILE* file = std::fopen(path.c_str(), fopen_mode.c_str());
+  if (!file) {
+    throw std::system_error{errno, std::generic_category(), "Could not open file"};
+  }
+
+  return file;
+}
+
+FILE* MakeParentsAndOpenFile(const std::string& path, const std::string& fopen_mode) {
+  /*
+   * If fopen fails, created parent directories have to be removed.
+   * Save the path to the first nonexistent parent directory in the file path,
+   * to know where to start recursive removal
+   */
+  std::string first_nonexistent_parent = FilesystemUtils::Dirname(path);
+  struct ::stat buf;
+
+  while (!FilesystemUtils::CheckIfExists(FilesystemUtils::Dirname(first_nonexistent_parent),
+         &buf)) {
+      first_nonexistent_parent = FilesystemUtils::Dirname(first_nonexistent_parent);
+  }
+
+  FilesystemUtils::Mkdir(FilesystemUtils::Dirname(path), true);
+
+  FILE* file = std::fopen(path.c_str(), fopen_mode.c_str());
+  if (file) {
+    return file;
+  }
+
+  std::system_error fopen_error = std::system_error(errno, std::generic_category(), "Could not open file");
+
+  try {
+    FilesystemUtils::RemoveDirectoryRecursively(first_nonexistent_parent);
+  } catch (const std::system_error& error) {
+    LoggerD("Failed to remove created parent directories: %s", error.what());
+  }
+
+  throw fopen_error;
+}
+
+} //namespace
+
+void FilesystemInstance::FileSystemManagerOpenFile(const picojson::value& args,
+                                                   picojson::object& out) {
+  ScopeLogger();
+  const int unique_id = static_cast<int>(args.get("id").get<double>());
+  if (opened_files.find(unique_id) != opened_files.end()) {
+    LogAndReportError(IOException("Internal error (id is already in use)"), out);
+    return;
+  }
+
+  if (WriteAccessRequested(args)) {
+    CHECK_PRIVILEGE_ACCESS(kPrivilegeFilesystemWrite, &out);
+  }
+
+  if (ReadAccessRequested(args)) {
+    CHECK_PRIVILEGE_ACCESS(kPrivilegeFilesystemRead, &out);
+  }
+
+  const std::string& path = args.get("path").get<std::string>();
+  const std::string open_mode = GetFopenMode(args);
+  FILE* file = nullptr;
+  try {
+    if (ShouldMakeParents(args)) {
+      file = MakeParentsAndOpenFile(path, open_mode);
+    } else {
+      file = OpenFile(path, open_mode);
+    }
+  } catch (const std::system_error& error) {
+      FilesystemUtils::TranslateException(error, out);
+      return;
+  }
+
+  opened_files.emplace(std::make_pair(unique_id, std::make_shared<FileHandle>(file)));
+  ReportSuccess(out);
+}
+
 void FilesystemInstance::FileSystemManagerCreateDirectory(const picojson::value& args,
                                                           picojson::object& out) {
   ScopeLogger();
@@ -1502,6 +1787,543 @@ void FilesystemInstance::FileSystemManagerGetLimits(const picojson::value& args,
   ReportSuccess(response, out);
 }
 
+void FilesystemInstance::FileHandleSeek(const picojson::value& args, picojson::object& out) {
+  ScopeLogger();
+  const int fh_id = static_cast<int>(args.get("id").get<double>());
+  const long offset = static_cast<long>(args.get("offset").get<double>());
+  const std::string& _whence = args.get("whence").get<std::string>();
+
+  auto fh = opened_files.find(fh_id);
+
+  int whence = SEEK_SET;
+
+  if ("CURRENT" == _whence) {
+    whence = SEEK_CUR;
+    LoggerD("SEEK_CUR selected");
+  } else if ("END" == _whence) {
+    whence = SEEK_END;
+    LoggerD("SEEK_END selected");
+  }
+
+  if (opened_files.end() == fh) {
+    LogAndReportError(IOException("Invalid FileHandle"), out);
+    return;
+  }
+
+  auto handle = fh->second;
+
+  auto logic = [handle, whence, offset](decltype(out) out) {
+    try {
+      long ret = fseek(handle->file_handle, offset, whence);
+      if (0 != ret) {
+        LoggerE("fseek returned failed");
+        int errsv = errno;
+        std::string error_message =
+            std::string("seek failed, fileHandle may be corrupted, error message: ") +
+            strerror(errsv);
+        LogAndReportError(IOException(error_message.c_str()), out);
+        return;
+      }
+
+      ret = ftell(handle->file_handle);
+      if (-1L == ret) {
+        LoggerE("ftell returned failed");
+        int errsv = errno;
+        std::string error_message =
+            std::string("seek failed, fileHandle may be corrupted, error message: ") +
+            strerror(errsv);
+        LogAndReportError(IOException(error_message.c_str()), out);
+        return;
+      }
+
+      ReportSuccess(picojson::value((double)ret), out);
+    } catch (std::runtime_error& e) {
+      LoggerE("Cannot read, cause: %s", e.what());
+      LogAndReportError(IOException(e.what()), out);
+      return;
+    }
+  };
+
+  bool blocking = args.contains("blocking") ? args.get("blocking").get<bool>() : true;
+
+  if (blocking) {
+    logic(out);
+    return;
+  } else {
+    // Async logic
+    double callback_id = args.get("callbackId").get<double>();
+    this->worker.add_job([this, callback_id, logic] {
+      picojson::value response = picojson::value(picojson::object());
+      picojson::object& async_out = response.get<picojson::object>();
+      async_out["callbackId"] = picojson::value(callback_id);
+      logic(async_out);
+      this->PostMessage(response.serialize().c_str());
+    });
+
+    // Sync return
+    ReportSuccess(out);
+    return;
+  }
+}
+
+void FilesystemInstance::FileHandleReadString(const picojson::value& args, picojson::object& out) {
+  ScopeLogger();
+  CHECK_EXIST(args, "id", out)
+  const int fh_id = static_cast<int>(args.get("id").get<double>());
+  const std::string& encoding =
+      args.contains("encoding") ? args.get("encoding").get<std::string>() : "utf-8";
+
+  auto fh = opened_files.find(fh_id);
+
+  if (opened_files.end() == fh) {
+    LogAndReportError(IOException("Invalid FileHandle"), out);
+    return;
+  }
+
+  size_t count;
+  bool whole_file = false;
+  if (args.contains("count")) {
+    count = static_cast<size_t>(args.get("count").get<double>());
+  } else {
+    try {
+      count = file_size(fh->second->file_handle);
+      whole_file = true;
+    } catch (const std::system_error& e) {
+      LogAndReportError(IOException(e.what()), out);
+      return;
+    }
+  }
+  LoggerD("count: %d", count);
+
+  if (std::string::npos == count) {
+    LogAndReportError(InvalidValuesException("Invalid count was given"), out);
+    return;
+  }
+
+  auto handle = fh->second;
+
+  auto logic = [handle, count, encoding, whole_file](decltype(out) out) {
+    try {
+      std::vector<std::uint8_t> buf = read_file(handle->file_handle, count);
+      if (encoding == "iso-8859-1") {  // for iso-8859-1 1 byte is equal to 1 character
+        out["result"] = picojson::value(picojson::string_type, true);
+        latin1::to_utf8(buf, out["result"].get<std::string>());
+        ReportSuccess(out);
+      } else if (encoding == "utf-8") {
+        unsigned long char_count;
+        short expected_extension_bytes;
+        if (!validate_and_check_character_count(buf, char_count, expected_extension_bytes)) {
+          LogAndReportError(
+              IOException("File doesn't contain UTF-8 encoded string with given length"), out);
+          return;
+        }
+        LoggerD("char_count: %ld", char_count);
+        LoggerD("ftell: %i", ftell(handle->file_handle));
+        if (!(std::feof(
+                handle->file_handle))) {  // read number of characters if not whole file read
+          LoggerD("count parameter given: %d", count);
+          if (!whole_file && !add_utf8_chars_to_buffer(handle->file_handle, buf, count - char_count,
+                                        expected_extension_bytes)) {
+            LogAndReportError(
+                IOException("File doesn't contain UTF-8 encoded string with given length"), out);
+          }
+        }
+        buf.push_back('\0');
+        const char* str = (const char*)buf.data();
+        ReportSuccess(picojson::value{str}, out);
+      } else {
+        LogAndReportError(NotSupportedException("Given encoding is not supported."), out);
+        return;
+      }
+    } catch (std::runtime_error& e) {
+      LoggerE("Cannot read, cause: %s", e.what());
+      LogAndReportError(IOException(e.what()), out);
+      return;
+    }
+  };
+
+  bool blocking = args.contains("blocking") ? args.get("blocking").get<bool>() : true;
+
+  if (blocking) {
+    logic(out);
+    return;
+  } else {
+    // Async logic
+    double callback_id = args.get("callbackId").get<double>();
+    this->worker.add_job([this, callback_id, logic] {
+      picojson::value response = picojson::value(picojson::object());
+      picojson::object& async_out = response.get<picojson::object>();
+      async_out["callbackId"] = picojson::value(callback_id);
+      logic(async_out);
+      this->PostMessage(response.serialize().c_str());
+    });
+
+    // Sync return
+    ReportSuccess(out);
+    return;
+  }
+}
+
+void FilesystemInstance::FileHandleWriteString(const picojson::value& args, picojson::object& out) {
+  ScopeLogger();
+  CHECK_EXIST(args, "id", out)
+  CHECK_EXIST(args, "string", out)
+  const int fh_id = static_cast<int>(args.get("id").get<double>());
+  const std::string& str = args.get("string").get<std::string>();
+  const std::string& encoding =
+      args.contains("encoding") ? args.get("encoding").get<std::string>() : "utf-8";
+
+  auto fh = opened_files.find(fh_id);
+
+  if (opened_files.end() == fh) {
+    LogAndReportError(IOException("Invalid FileHandle"), out);
+    return;
+  }
+
+  auto handle = fh->second;
+
+  auto logic = [str, handle, encoding](decltype(out) out) {
+    try {
+      std::vector<std::uint8_t> data;
+      data.resize(str.size());
+
+      if (encoding == "iso-8859-1") {
+        latin1::from_utf8(str, data);
+      } else {
+        LoggerD("copying string memory to vector");
+        std::memcpy(data.data(), str.data(), str.size());
+      }
+      write_file(data.data(), data.size(), handle->file_handle);
+      ReportSuccess(picojson::value{(double)data.size()}, out);
+    } catch (std::runtime_error& e) {
+      LoggerE("Cannot write, cause: %s", e.what());
+      LogAndReportError(IOException(e.what()), out);
+      return;
+    }
+  };
+
+  bool blocking = args.contains("blocking") ? args.get("blocking").get<bool>() : true;
+
+  if (blocking) {
+    logic(out);
+    return;
+  } else {
+    // Async logic
+    double callback_id = args.get("callbackId").get<double>();
+    this->worker.add_job([this, callback_id, logic] {
+      picojson::value response = picojson::value(picojson::object());
+      picojson::object& async_out = response.get<picojson::object>();
+      async_out["callbackId"] = picojson::value(callback_id);
+      logic(async_out);
+      this->PostMessage(response.serialize().c_str());
+    });
+
+    // Sync return
+    ReportSuccess(out);
+    return;
+  }
+}
+
+void FilesystemInstance::FileHandleReadData(const picojson::value& args, picojson::object& out) {
+  ScopeLogger();
+  CHECK_EXIST(args, "id", out)
+  const int fh_id = static_cast<int>(args.get("id").get<double>());
+  auto fh = opened_files.find(fh_id);
+
+  if (opened_files.end() == fh) {
+    LoggerE("FileHandle with id: %d not found", fh_id);
+    LogAndReportError(IOException("Invalid FileHandle"), out);
+    return;
+  }
+
+  size_t size;
+  if (args.contains("size")) {
+    size = static_cast<size_t>(args.get("size").get<double>());
+  } else {
+    try {
+      size = file_size(fh->second->file_handle);
+    } catch (const std::system_error& e) {
+      LogAndReportError(IOException(e.what()), out);
+      return;
+    }
+  }
+  LoggerD("size: %d", size);
+
+  if (std::string::npos == size) {
+    LogAndReportError(InvalidValuesException("Invalid size was given"), out);
+    return;
+  }
+
+  auto handle = fh->second;
+
+  auto logic = [handle, size](decltype(out) out) {
+    try {
+      std::vector<std::uint8_t> data = read_file(handle->file_handle, size);
+      out["result"] = picojson::value(picojson::string_type, true);
+      encode_binary_in_string(data, out["result"].get<std::string>());
+    } catch (std::runtime_error& e) {
+      LoggerE("Cannot read, cause: %s", e.what());
+      LogAndReportError(IOException(e.what()), out);
+      return;
+    }
+  };
+
+  bool blocking = args.contains("blocking") ? args.get("blocking").get<bool>() : true;
+
+  if (blocking) {
+    logic(out);
+    return;
+  } else {
+    // Async logic
+    double callback_id = args.get("callbackId").get<double>();
+    this->worker.add_job([this, callback_id, logic] {
+      picojson::value response = picojson::value(picojson::object());
+      picojson::object& async_out = response.get<picojson::object>();
+      async_out["callbackId"] = picojson::value(callback_id);
+      logic(async_out);
+      this->PostMessage(response.serialize().c_str());
+    });
+
+    // Sync return
+    ReportSuccess(out);
+    return;
+  }
+}
+
+void FilesystemInstance::FileHandleWriteData(const picojson::value& args, picojson::object& out) {
+  ScopeLogger();
+  CHECK_EXIST(args, "id", out)
+  CHECK_EXIST(args, "data", out)
+  const auto& str = args.get("data").get<std::string>();
+  const int fh_id = static_cast<int>(args.get("id").get<double>());
+
+  auto fh = opened_files.find(fh_id);
+  if (opened_files.end() == fh) {
+    LoggerE("FileHandle with id: %d not found", fh_id);
+    LogAndReportError(IOException("Invalid FileHandle"), out);
+    return;
+  }
+
+  auto handle = fh->second;
+
+  auto logic = [str, handle](decltype(out) out) {
+    try {
+      std::vector<std::uint8_t> bytes;
+      decode_binary_from_string(str, bytes);
+      write_file(bytes.data(), bytes.size(), handle->file_handle);
+    } catch (std::runtime_error& e) {
+      LogAndReportError(IOException(e.what()), out);
+      return;
+    }
+  };
+
+  bool blocking = args.contains("blocking") ? args.get("blocking").get<bool>() : true;
+
+  if (blocking) {
+    logic(out);
+    return;
+  } else {
+    // Async logic
+    double callback_id = args.get("callbackId").get<double>();
+    this->worker.add_job([this, callback_id, logic] {
+      picojson::value response = picojson::value(picojson::object());
+      picojson::object& async_out = response.get<picojson::object>();
+      async_out["callbackId"] = picojson::value(callback_id);
+      logic(async_out);
+      this->PostMessage(response.serialize().c_str());
+    });
+
+    // Sync return
+    ReportSuccess(out);
+    return;
+  }
+}
+
+void FilesystemInstance::FileHandleFlush(const picojson::value& args, picojson::object& out) {
+  ScopeLogger();
+  const int fh_id = static_cast<int>(args.get("id").get<double>());
+
+  auto fh = opened_files.find(fh_id);
+  if (opened_files.end() == fh) {
+    LogAndReportError(IOException("Invalid FileHandle"), out);
+    return;
+  }
+
+  auto handle = fh->second;
+
+  auto logic = [handle](decltype(out) out) {
+    try {
+      int ret = fflush(handle->file_handle);
+      if (ret) {
+        int errsv = errno;
+        std::string error_message =
+            std::string("flush failed, error message: ") + strerror(errsv);
+        LogAndReportError(IOException(error_message.c_str()), out);
+        return;
+      }
+    } catch (std::runtime_error& e) {
+      LogAndReportError(IOException(e.what()), out);
+      return;
+    }
+  };
+
+  bool blocking = args.contains("blocking") ? args.get("blocking").get<bool>() : true;
+
+  if (blocking) {
+    logic(out);
+    return;
+  } else {
+    // Async logic
+    double callback_id = args.get("callbackId").get<double>();
+    this->worker.add_job([this, callback_id, logic] {
+      picojson::value response = picojson::value(picojson::object());
+      picojson::object& async_out = response.get<picojson::object>();
+      async_out["callbackId"] = picojson::value(callback_id);
+      logic(async_out);
+      this->PostMessage(response.serialize().c_str());
+    });
+
+    // Sync return
+    ReportSuccess(out);
+    return;
+  }
+}
+
+void FilesystemInstance::FileHandleSync(const picojson::value& args, picojson::object& out) {
+  ScopeLogger();
+  const int fh_id = static_cast<int>(args.get("id").get<double>());
+
+  auto fh = opened_files.find(fh_id);
+  if (opened_files.end() == fh) {
+    LogAndReportError(IOException("Invalid FileHandle"), out);
+    return;
+  }
+
+  auto handle = fh->second;
+
+  auto logic = [handle](decltype(out) out) {
+    try {
+      int ret = fsync(fileno(handle->file_handle));
+      if (ret) {
+        int errsv = errno;
+        std::string error_message =
+            std::string("sync failed, error message: ") + strerror(errsv);
+        LogAndReportError(IOException(error_message.c_str()), out);
+        return;
+      }
+    } catch (std::runtime_error& e) {
+      LogAndReportError(IOException(e.what()), out);
+      return;
+    }
+  };
+
+  bool blocking = args.contains("blocking") ? args.get("blocking").get<bool>() : true;
+
+  if (blocking) {
+    logic(out);
+    return;
+  } else {
+    // Async logic
+    double callback_id = args.get("callbackId").get<double>();
+    this->worker.add_job([this, callback_id, logic] {
+      picojson::value response = picojson::value(picojson::object());
+      picojson::object& async_out = response.get<picojson::object>();
+      async_out["callbackId"] = picojson::value(callback_id);
+      logic(async_out);
+      this->PostMessage(response.serialize().c_str());
+    });
+
+    // Sync return
+    ReportSuccess(out);
+    return;
+  }
+}
+
+void FilesystemInstance::FileHandleClose(const picojson::value& args, picojson::object& out) {
+  ScopeLogger();
+  const int fh_id = static_cast<int>(args.get("id").get<double>());
+  auto fh = opened_files.find(fh_id);
+  if (opened_files.end() == fh) {
+    LogAndReportError(IOException("Invalid FileHandle"), out);
+    return;
+  }
+
+  std::shared_ptr<FileHandle> handle = fh->second;
+  opened_files.erase(fh);
+
+  auto logic = [handle](decltype(out) out) {
+    try {
+      if (!handle->file_handle) {
+        LogAndReportError(IOException("File handle already closed."), out);
+        return;
+      }
+      int ret = fclose(handle->file_handle);
+      handle->file_handle = nullptr;
+      if (ret) {
+        int errsv = errno;
+        std::string error_message =
+            std::string("close failed, error message: ") + strerror(errsv);
+        LogAndReportError(IOException(error_message.c_str()), out);
+        return;
+      }
+    } catch (std::runtime_error& e) {
+      LogAndReportError(IOException(e.what()), out);
+      return;
+    }
+  };
+
+  bool blocking = args.contains("blocking") ? args.get("blocking").get<bool>() : true;
+
+  if (blocking) {
+    bool ready = false;
+    bool done = false;
+    std::mutex mutex;
+    std::condition_variable conditional_variable;
+    // adding empty job to worker's queue, in order to wait for all jobs to be done before closing
+    // FILE*
+    this->worker.add_job([] {},
+                         [&conditional_variable, &mutex, &ready, &done, logic, &out] {
+                           {
+                             // wait for close
+                             std::unique_lock<std::mutex> lock(mutex);
+                             conditional_variable.wait(lock, [&ready] { return ready; });
+
+                             logic(out);
+                             done = true;
+                           }
+                           conditional_variable.notify_one();
+                         });
+
+    {
+      // let know that close is ready
+      std::unique_lock<std::mutex> lock(mutex);
+      ready = true;
+    }
+    conditional_variable.notify_one();
+
+    {
+      // wait for worker
+      std::unique_lock<std::mutex> lock(mutex);
+      conditional_variable.wait(lock, [&done] { return done; });
+    }
+    handle->file_handle = nullptr;
+  } else {
+    // Async logic
+    double callback_id = args.get("callbackId").get<double>();
+    this->worker.add_job([this, callback_id, logic] {
+      picojson::value response = picojson::value(picojson::object());
+      picojson::object& async_out = response.get<picojson::object>();
+      async_out["callbackId"] = picojson::value(callback_id);
+      logic(async_out);
+      this->PostMessage(response.serialize().c_str());
+    });
+
+    // Sync return
+    ReportSuccess(out);
+    return;
+  }
+}
+
 #undef CHECK_EXIST
 
 }  // namespace filesystem
index dbe9ee7..0fdb833 100644 (file)
@@ -50,7 +50,6 @@ class FileHandle {
   FileHandle& operator=(const FileHandle&) = delete;
   FileHandle& operator=(FileHandle&&) = delete;
 
- private:
   FILE* file_handle;
 };
 
@@ -65,7 +64,7 @@ class FilesystemInstance : public common::ParsedInstance, FilesystemStateChangeL
   /**
    * @brief Implements single worker executing in new thread
    *
-   * Jobs are done in order. If this worker is destroyed all pending jobs are cancelled,
+   * Jobs are done in FIFO order. If this worker is destroyed all pending jobs are cancelled,
    * and all remaining 'finally' functions are called.
    */
   class Worker {
@@ -130,6 +129,7 @@ class FilesystemInstance : public common::ParsedInstance, FilesystemStateChangeL
   void PrepareError(const FilesystemError& error, picojson::object& out);
   void FileSystemManagerGetCanonicalPath(const picojson::value& args, picojson::object& out);
 
+  void FileSystemManagerOpenFile(const picojson::value& args, picojson::object& out);
   void FileSystemManagerCreateDirectory(const picojson::value& args, picojson::object& out);
   void FileSystemManagerDeleteFile(const picojson::value& args, picojson::object& out);
   void FileSystemManagerDeleteDirectory(const picojson::value& args, picojson::object& out);
@@ -144,6 +144,54 @@ class FilesystemInstance : public common::ParsedInstance, FilesystemStateChangeL
   void FileSystemManagerPathExists(const picojson::value& args, picojson::object& out);
   void FileSystemManagerToURI(const picojson::value& args, picojson::object& out);
   void FileSystemManagerGetLimits(const picojson::value& args, picojson::object& out);
+
+  /**
+   * @brief wrapper for std::fseek function.
+   *        Sets the file position indicator for the file stream stream.
+   * @parameter out has always set status, either success or error.
+   */
+  void FileHandleSeek(const picojson::value& args, picojson::object& out);
+
+  /**
+   * @brief Reads file contents as string.
+   * @parameter out has always set status, either success or error.
+   *            In case of success, string value is passed.
+   */
+  void FileHandleReadString(const picojson::value& args, picojson::object& out);
+
+  /**
+   * @brief Writes string to file.
+   * @parameter out has always set status, either success or error.
+   */
+  void FileHandleWriteString(const picojson::value& args, picojson::object& out);
+
+  /**
+   * @brief Reads file contents as binary data, can use worker and not block GUI.
+   * @parameter out has always set status, either success or error.
+   *            In case of success, encoded uint8_t array is passed.
+   */
+  void FileHandleReadData(const picojson::value& args, picojson::object& out);
+
+  /**
+   * @brief Writes binary data to file, can use worker and not block GUI.
+   * @parameter out has always set status, either success or error.
+   */
+  void FileHandleWriteData(const picojson::value& args, picojson::object& out);
+
+  /**
+   * @brief wrapper for std::fflush function.
+   *        Writes any unwritten data from the stream's buffer to the associated output device.
+   * @parameter out has always set status, either success or error.
+   */
+  void FileHandleFlush(const picojson::value& args, picojson::object& out);
+  void FileHandleSync(const picojson::value& args, picojson::object& out);
+
+  /**
+   * @brief wrapper for std::fclose function.
+   *        Closes the given file stream.
+   * @parameter out has always set status, either success or error.
+   */
+  void FileHandleClose(const picojson::value& args, picojson::object& out);
 };
 
 }  // namespace filesystem