From: Arkadiusz Pietraszek Date: Thu, 19 Apr 2018 10:35:16 +0000 (+0200) Subject: [Filesystem] FileHandle handling methods implementation. X-Git-Tag: accepted/tizen/unified/20180522.055729~5 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Fchanges%2F68%2F176468%2F26;p=platform%2Fcore%2Fapi%2Fwebapi-plugins.git [Filesystem] FileHandle handling methods implementation. ACR: http://suprem.sec.samsung.net/jira/browse/TWDAPI-121 Change-Id: Ie0bc67b1858ad9638942d9a0c93ee799316c05ee Signed-off-by: Szymon Jastrzebski Signed-off-by: Arkadiusz Pietraszek Signed-off-by: Jakub Skowron Signed-off-by: Pawel Wasowski --- diff --git a/src/filesystem/filesystem_instance.cc b/src/filesystem/filesystem_instance.cc index c8faf71..5c0f341 100644 --- a/src/filesystem/filesystem_instance.cc +++ b/src/filesystem/filesystem_instance.cc @@ -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(); + + 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(); + + 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(); + + return std::string::npos != open_mode.find("r"); +} + +bool ShouldMakeParents(const picojson::value& args) { + ScopeLogger(); + const std::string& path = args.get("path").get(); + struct stat buf {}; + if (FilesystemUtils::CheckIfExists(FilesystemUtils::Dirname(path), &buf)) { + return false; + } + bool make_parents = args.get("makeParents").get(); + const std::string& open_mode = args.get("openMode").get(); + 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& 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& 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& 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(args.get("id").get()); + 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(); + 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(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(args.get("id").get()); + const long offset = static_cast(args.get("offset").get()); + const std::string& _whence = args.get("whence").get(); + + 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() : true; + + if (blocking) { + logic(out); + return; + } else { + // Async logic + double callback_id = args.get("callbackId").get(); + this->worker.add_job([this, callback_id, logic] { + picojson::value response = picojson::value(picojson::object()); + picojson::object& async_out = response.get(); + 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(args.get("id").get()); + const std::string& encoding = + args.contains("encoding") ? args.get("encoding").get() : "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(args.get("count").get()); + } 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 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()); + 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() : true; + + if (blocking) { + logic(out); + return; + } else { + // Async logic + double callback_id = args.get("callbackId").get(); + this->worker.add_job([this, callback_id, logic] { + picojson::value response = picojson::value(picojson::object()); + picojson::object& async_out = response.get(); + 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(args.get("id").get()); + const std::string& str = args.get("string").get(); + const std::string& encoding = + args.contains("encoding") ? args.get("encoding").get() : "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 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() : true; + + if (blocking) { + logic(out); + return; + } else { + // Async logic + double callback_id = args.get("callbackId").get(); + this->worker.add_job([this, callback_id, logic] { + picojson::value response = picojson::value(picojson::object()); + picojson::object& async_out = response.get(); + 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(args.get("id").get()); + 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(args.get("size").get()); + } 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 data = read_file(handle->file_handle, size); + out["result"] = picojson::value(picojson::string_type, true); + encode_binary_in_string(data, out["result"].get()); + } 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() : true; + + if (blocking) { + logic(out); + return; + } else { + // Async logic + double callback_id = args.get("callbackId").get(); + this->worker.add_job([this, callback_id, logic] { + picojson::value response = picojson::value(picojson::object()); + picojson::object& async_out = response.get(); + 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(); + const int fh_id = static_cast(args.get("id").get()); + + 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 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() : true; + + if (blocking) { + logic(out); + return; + } else { + // Async logic + double callback_id = args.get("callbackId").get(); + this->worker.add_job([this, callback_id, logic] { + picojson::value response = picojson::value(picojson::object()); + picojson::object& async_out = response.get(); + 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(args.get("id").get()); + + 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() : true; + + if (blocking) { + logic(out); + return; + } else { + // Async logic + double callback_id = args.get("callbackId").get(); + this->worker.add_job([this, callback_id, logic] { + picojson::value response = picojson::value(picojson::object()); + picojson::object& async_out = response.get(); + 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(args.get("id").get()); + + 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() : true; + + if (blocking) { + logic(out); + return; + } else { + // Async logic + double callback_id = args.get("callbackId").get(); + this->worker.add_job([this, callback_id, logic] { + picojson::value response = picojson::value(picojson::object()); + picojson::object& async_out = response.get(); + 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(args.get("id").get()); + auto fh = opened_files.find(fh_id); + if (opened_files.end() == fh) { + LogAndReportError(IOException("Invalid FileHandle"), out); + return; + } + + std::shared_ptr 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() : 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 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 lock(mutex); + ready = true; + } + conditional_variable.notify_one(); + + { + // wait for worker + std::unique_lock lock(mutex); + conditional_variable.wait(lock, [&done] { return done; }); + } + handle->file_handle = nullptr; + } else { + // Async logic + double callback_id = args.get("callbackId").get(); + this->worker.add_job([this, callback_id, logic] { + picojson::value response = picojson::value(picojson::object()); + picojson::object& async_out = response.get(); + 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 diff --git a/src/filesystem/filesystem_instance.h b/src/filesystem/filesystem_instance.h index dbe9ee7..0fdb833 100644 --- a/src/filesystem/filesystem_instance.h +++ b/src/filesystem/filesystem_instance.h @@ -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