diff options
author | Saturn&Eric <[email protected]> | 2022-05-13 19:31:40 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2022-05-13 19:31:40 +0000 |
commit | 49090d1d511a4a0fbcfac253656a40a42716c82e (patch) | |
tree | 2553bd9a1d14c099a0a990576fb7c874f72b3859 | |
parent | Merge pull request #60 from saturneric/develop-2.0.7 (diff) | |
parent | fix(core): solve memory access issues (diff) | |
download | GpgFrontend-49090d1d511a4a0fbcfac253656a40a42716c82e.tar.gz GpgFrontend-49090d1d511a4a0fbcfac253656a40a42716c82e.zip |
Merge pull request #62 from saturneric/develop-2.0.8v2.0.8
Develop 2.0.8
32 files changed, 1096 insertions, 359 deletions
diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml index b8468ba7..9ee690be 100644 --- a/.github/workflows/debug.yml +++ b/.github/workflows/debug.yml @@ -124,7 +124,7 @@ jobs: pacman --noconfirm -S --needed make texinfo mingw-w64-x86_64-libconfig mingw-w64-x86_64-boost automake pacman --noconfirm -S --needed mingw-w64-x86_64-qt5 libintl msys2-runtime-devel gettext-devel pacman --noconfirm -S --needed mingw-w64-x86_64-ninja mingw-w64-x86_64-gnupg mingw-w64-x86_64-gpgme - pacman --noconfirm -S --needed mingw-w64-x86_64-libarchive + pacman --noconfirm -S --needed mingw-w64-x86_64-libarchive mingw-w64-x86_64-icu mingw-w64-x86_64-icu-debug-libs if: matrix.os == 'windows-latest' - name: Build GpgFrontend (Linux) @@ -137,7 +137,7 @@ jobs: - name: Build GpgFrontend (macOS) # Build your GpgFrontend with the given configuration run: | - cmake -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DGPGFRONTEND_BUILD_TYPE_TEST_UI=ON -DOPENSSL_ROOT_DIR=/usr/local/opt/[email protected] + cmake -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DGPGFRONTEND_BUILD_TYPE_TEST_UI=ON cmake --build ${{github.workspace}}/build --config {{$env.BUILD_TYPE}} -- -v if: matrix.os == 'macos-10.15' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 32577559..5696c692 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -131,6 +131,7 @@ jobs: pacman --noconfirm -S --needed make texinfo mingw-w64-x86_64-libconfig mingw-w64-x86_64-boost automake pacman --noconfirm -S --needed mingw-w64-x86_64-qt5 libintl msys2-runtime-devel gettext-devel mingw-w64-x86_64-gpgme pacman --noconfirm -S --needed mingw-w64-x86_64-ninja mingw-w64-x86_64-gnupg mingw-w64-x86_64-libarchive + pacman --noconfirm -S --needed mingw-w64-x86_64-icu mingw-w64-x86_64-icu-debug-libs if: matrix.os == 'windows-2019' - name: Build GpgFrontend (Linux) @@ -143,7 +144,7 @@ jobs: - name: Build GpgFrontend (macOS) # Build your GpgFrontend with the given configuration run: | - cmake -B ${{github.workspace}}/build -G Ninja -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DOPENSSL_ROOT_DIR=/usr/local/opt/[email protected] + cmake -B ${{github.workspace}}/build -G Ninja -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} cmake --build ${{github.workspace}}/build --config {{$env.BUILD_TYPE}} -- -v if: matrix.os == 'macos-10.15' @@ -3,7 +3,7 @@ src/GpgFrontend.h src/GpgFrontendBuildInfo.h src/GpgFrontendBuildInstallInfo.h src/core/GpgFrontendCoreExport.h -src/core/GpgFrontendUIExport.h +src/ui/GpgFrontendUIExport.h docs/ # gettext diff --git a/CMakeLists.txt b/CMakeLists.txt index 167f23d5..e93de668 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ cmake_minimum_required(VERSION 3.16) # define project -project(GpgFrontend VERSION 2.0.7 LANGUAGES CXX) +project(GpgFrontend VERSION 2.0.8 LANGUAGES CXX) # show cmake version message(STATUS "GpgFrontend Build Configuration Started CMAKE Version ${CMAKE_VERSION}") @@ -422,6 +422,9 @@ SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC) find_package(Boost COMPONENTS date_time system REQUIRED) # Introduce OpenSSL +if(APPLE) + set(OPENSSL_ROOT_DIR /usr/local/opt/[email protected]) +endif() find_package(OpenSSL REQUIRED) # Introduce Qt diff --git a/manual/_sidebar.md b/manual/_sidebar.md index 399909b7..26a39c9f 100644 --- a/manual/_sidebar.md +++ b/manual/_sidebar.md @@ -13,8 +13,6 @@ - [View Key Pair Details](manual/view-keypair-info.md) - [Import & Export Key Pair](manual/import-export-key-pair.md) - [Key Server Operations](manual/key-server-operations.md) - - [Email Operations](manual/email-operations.md) - - [Advanced Key Operation](manual/advance-key-opera.md) - Features Guides - [Introduce](features/introduce.md) - [Short Cipher-text](features/short-ciphertext.md) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f11accd4..ee0e39a1 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -44,9 +44,9 @@ set(UTILS_DIR ${CMAKE_SOURCE_DIR}/utils) set(GPGME_LIB_DIR ${UTILS_DIR}/gpgme/lib) # link third-party libraries -target_link_libraries(gpgfrontend_core config++) +target_link_libraries(gpgfrontend_core PUBLIC config++) if (NOT LINUX) - target_link_libraries(gpgfrontend_core config++ intl) + target_link_libraries(gpgfrontend_core PUBLIC config++ intl) endif () # easyloggingpp @@ -55,28 +55,40 @@ target_include_directories(gpgfrontend_core PUBLIC target_sources(gpgfrontend_core PUBLIC ${CMAKE_SOURCE_DIR}/third_party/easyloggingpp/src/easylogging++.cc) # qt-aes -target_sources(gpgfrontend_core PUBLIC +target_sources(gpgfrontend_core PRIVATE ${CMAKE_SOURCE_DIR}/third_party/qt-aes/qaesencryption.cpp) # encoding detect library aux_source_directory(${CMAKE_SOURCE_DIR}/third_party/encoding-detect ENCODING_DETECT_SOURCE_CODE) target_sources(gpgfrontend_core PUBLIC ${ENCODING_DETECT_SOURCE_CODE}) +# icu +if(APPLE) + target_include_directories(gpgfrontend_core PRIVATE /usr/local/opt/icu4c/include) + target_link_directories(gpgfrontend_core PRIVATE /usr/local/opt/icu4c/lib) + target_link_libraries(gpgfrontend_core PRIVATE icui18n icuuc icudata) +else () + find_package(ICU 60.0 REQUIRED COMPONENTS i18n uc data) + message("ICU version: ${ICU_VERSION}") + message("ICU libraries: ${ICU_LIBRARIES}") + target_link_libraries(gpgfrontend_core PRIVATE ${ICU_LIBRARIES}) +endif () + # link gnupg libraries -target_link_libraries(gpgfrontend_core gpgme assuan gpg-error) +target_link_libraries(gpgfrontend_core PUBLIC gpgme assuan gpg-error) # link openssl -target_link_libraries(gpgfrontend_core OpenSSL::SSL OpenSSL::Crypto) +target_link_libraries(gpgfrontend_core PUBLIC OpenSSL::SSL OpenSSL::Crypto) # link boost libraries -target_link_libraries(gpgfrontend_core ${Boost_LIBRARIES}) +target_link_libraries(gpgfrontend_core PUBLIC ${Boost_LIBRARIES}) # link libarchive -target_link_libraries(gpgfrontend_core archive) +target_link_libraries(gpgfrontend_core PRIVATE archive) # link json target_link_libraries(gpgfrontend_core - nlohmann_json::nlohmann_json) + PUBLIC nlohmann_json::nlohmann_json) # link Qt core -target_link_libraries(gpgfrontend_core Qt5::Core) +target_link_libraries(gpgfrontend_core PUBLIC Qt5::Core) # set up pch target_precompile_headers(gpgfrontend_core @@ -89,10 +101,10 @@ target_compile_features(gpgfrontend_core PUBLIC cxx_std_17) # link for different platforms if (MINGW) message(STATUS "Link GPG Static Library For MINGW") - target_link_libraries(gpgfrontend_core wsock32) + target_link_libraries(gpgfrontend_core PUBLIC wsock32) elseif (APPLE) message(STATUS "Link GPG Static Library For macOS") - target_link_libraries(gpgfrontend_core dl) + target_link_libraries(gpgfrontend_core PUBLIC dl) if (XCODE_BUILD) set_target_properties(gpgfrontend_core PROPERTIES @@ -105,5 +117,5 @@ elseif (APPLE) else () # linux message(STATUS "Link GPG Static Library For Unix") - target_link_libraries(gpgfrontend_core pthread dl) + target_link_libraries(gpgfrontend_core PUBLIC pthread dl) endif () diff --git a/src/core/GpgConstants.cpp b/src/core/GpgConstants.cpp index 284022a8..88068f37 100644 --- a/src/core/GpgConstants.cpp +++ b/src/core/GpgConstants.cpp @@ -33,6 +33,8 @@ #include <boost/algorithm/string/predicate.hpp> #include <string> +#include "function/FileOperator.h" + const char* GpgFrontend::GpgConstants::PGP_CRYPT_BEGIN = "-----BEGIN PGP MESSAGE-----"; ///< const char* GpgFrontend::GpgConstants::PGP_CRYPT_END = @@ -115,37 +117,14 @@ static inline std::string trim(std::string& s) { } std::string GpgFrontend::read_all_data_in_file(const std::string& utf8_path) { - using namespace std::filesystem; - class path file_info(utf8_path.c_str()); - if (!exists(file_info) || !is_regular_file(file_info)) return {}; - std::ifstream in_file; -#ifndef WINDOWS - in_file.open(file_info.u8string(), std::ios::in); -#else - in_file.open(file_info.wstring().c_str(), std::ios::in); -#endif - if (!in_file.good()) return {}; - std::istreambuf_iterator<char> begin(in_file); - std::istreambuf_iterator<char> end; - std::string in_buffer(begin, end); - in_file.close(); - return in_buffer; + std::string data; + FileOperator::ReadFileStd(utf8_path, data); + return data; } bool GpgFrontend::write_buffer_to_file(const std::string& utf8_path, const std::string& out_buffer) { - using namespace std::filesystem; - class path file_info(utf8_path.c_str()); -#ifndef WINDOWS - std::ofstream out_file(file_info.u8string(), std::ios::out | std::ios::trunc); -#else - std::ofstream out_file(file_info.wstring().c_str(), - std::ios::out | std::ios::trunc); -#endif - if (!out_file.good()) return false; - out_file.write(out_buffer.c_str(), out_buffer.size()); - out_file.close(); - return true; + return FileOperator::WriteFileStd(utf8_path, out_buffer); } std::string GpgFrontend::get_file_extension(const std::string& path) { diff --git a/src/core/function/CharsetOperator.cpp b/src/core/function/CharsetOperator.cpp new file mode 100644 index 00000000..81c23388 --- /dev/null +++ b/src/core/function/CharsetOperator.cpp @@ -0,0 +1,136 @@ +/** + * Copyright (C) 2021 Saturneric + * + * This file is part of GpgFrontend. + * + * GpgFrontend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * All the source code of GpgFrontend was modified and released by + * Saturneric<[email protected]> starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "core/function/CharsetOperator.h" + +#include <unicode/ucnv.h> +#include <unicode/ucsdet.h> +#include <unicode/ustring.h> +#include <unicode/utypes.h> + +#include <cstddef> +#include <memory> +#include <string> + +#include "easylogging++.h" + +GpgFrontend::CharsetOperator::CharsetInfo GpgFrontend::CharsetOperator::Detect( + const std::string &buffer) { + const UCharsetMatch *ucm; + UErrorCode status = U_ZERO_ERROR; + UCharsetDetector *csd = ucsdet_open(&status); + + status = U_ZERO_ERROR; + if (U_FAILURE(status)) { + LOG(ERROR) << "Failed to open charset detector: " << u_errorName(status); + return {"unknown", "unknown", 0}; + } + + LOG(INFO) << "Detecting charset buffer:" << buffer.size() << "bytes"; + + status = U_ZERO_ERROR; + ucsdet_setText(csd, buffer.data(), buffer.size(), &status); + if (U_FAILURE(status)) { + LOG(ERROR) << "Failed to set text to charset detector: " + << u_errorName(status); + return {"unknown", "unknown", 0}; + } + + status = U_ZERO_ERROR; + ucm = ucsdet_detect(csd, &status); + + if (U_FAILURE(status)) return {"unknown", "unknown", 0}; + + status = U_ZERO_ERROR; + const char *name = ucsdet_getName(ucm, &status); + if (U_FAILURE(status)) return {"unknown", "unknown", 0}; + + status = U_ZERO_ERROR; + int confidence = ucsdet_getConfidence(ucm, &status); + if (U_FAILURE(status)) return {name, "unknown", 0}; + + status = U_ZERO_ERROR; + const char *language = ucsdet_getLanguage(ucm, &status); + if (U_FAILURE(status)) return {name, "unknown", confidence}; + + LOG(INFO) << "Detected charset: " << name << language << confidence; + return {name, language, confidence}; +} + +bool GpgFrontend::CharsetOperator::Convert2Utf8(const std::string &buffer, + std::string &out_buffer, + std::string from_charset_name) { + UErrorCode status = U_ZERO_ERROR; + const auto from_encode = std::string("utf-8"); + const auto to_encode = from_charset_name; + + LOG(INFO) << "Converting buffer:" << buffer.size(); + + // test if the charset is supported + UConverter *conv = ucnv_open(from_encode.c_str(), &status); + ucnv_close(conv); + if (U_FAILURE(status)) { + LOG(ERROR) << "Failed to open converter: " << u_errorName(status) << ":" + << from_encode; + return false; + } + + // test if the charset is supported + conv = ucnv_open(to_encode.c_str(), &status); + ucnv_close(conv); + if (U_FAILURE(status)) { + LOG(ERROR) << "Failed to open converter: " << u_errorName(status) << ":" + << to_encode; + return false; + } + + status = U_ZERO_ERROR; + int32_t target_limit = 0, target_capacity = 0; + + target_capacity = + ucnv_convert(from_encode.c_str(), to_encode.c_str(), nullptr, + target_limit, buffer.data(), buffer.size(), &status); + + if (status == U_BUFFER_OVERFLOW_ERROR) { + status = U_ZERO_ERROR; + target_limit = target_capacity + 1; + out_buffer.clear(); + out_buffer.resize(target_capacity); + target_capacity = + ucnv_convert(from_encode.c_str(), to_encode.c_str(), out_buffer.data(), + out_buffer.size(), buffer.data(), buffer.size(), &status); + } + + if (U_FAILURE(status)) { + LOG(ERROR) << "Failed to convert to utf-8: " << u_errorName(status); + return false; + } + + LOG(INFO) << "Converted buffer:" << out_buffer.size() << "bytes"; + return true; +}
\ No newline at end of file diff --git a/src/core/function/CharsetOperator.h b/src/core/function/CharsetOperator.h new file mode 100644 index 00000000..c04430f2 --- /dev/null +++ b/src/core/function/CharsetOperator.h @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2021 Saturneric + * + * This file is part of GpgFrontend. + * + * GpgFrontend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * All the source code of GpgFrontend was modified and released by + * Saturneric<[email protected]> starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef GPGFRONTEND_CHARSETDETECTOR_H +#define GPGFRONTEND_CHARSETDETECTOR_H + +#include <string> + +#include "core/GpgFrontendCore.h" + +namespace GpgFrontend { + +class GPGFRONTEND_CORE_EXPORT CharsetOperator { + public: + using CharsetInfo = std::tuple<std::string, std::string, int>; + + static CharsetInfo Detect(const std::string &buffer); + + static bool Convert2Utf8(const std::string &buffer, std::string &out_buffer, + std::string from_charset_name); +}; +} // namespace GpgFrontend + +#endif // GPGFRONTEND_CHARSETDETECTOR_H diff --git a/src/core/function/gpg/GpgKeyGetter.cpp b/src/core/function/gpg/GpgKeyGetter.cpp index 9a7b505c..ff848e0e 100644 --- a/src/core/function/gpg/GpgKeyGetter.cpp +++ b/src/core/function/gpg/GpgKeyGetter.cpp @@ -44,16 +44,14 @@ GpgFrontend::GpgKeyGetter::GpgKeyGetter(int channel) << "channel:" << channel; } -GpgFrontend::GpgKey GpgFrontend::GpgKeyGetter::GetKey(const std::string& fpr) { +GpgFrontend::GpgKey GpgFrontend::GpgKeyGetter::GetKey(const std::string& fpr, + bool use_cache) { LOG(INFO) << "called"; // find in cache first - { - std::lock_guard<std::mutex> lock(keys_cache_mutex_); - if (keys_cache_.find(fpr) != keys_cache_.end()) { - std::lock_guard<std::mutex> lock(ctx_mutex_); - return keys_cache_[fpr].Copy(); - } + if (use_cache) { + auto key = get_key_in_cache(fpr); + if (key.IsGood()) return key; } gpgme_key_t _p_key = nullptr; @@ -66,15 +64,12 @@ GpgFrontend::GpgKey GpgFrontend::GpgKeyGetter::GetKey(const std::string& fpr) { } } -GpgFrontend::GpgKey GpgFrontend::GpgKeyGetter::GetPubkey( - const std::string& fpr) { +GpgFrontend::GpgKey GpgFrontend::GpgKeyGetter::GetPubkey(const std::string& fpr, + bool use_cache) { // find in cache first - { - std::lock_guard<std::mutex> lock(keys_cache_mutex_); - if (keys_cache_.find(fpr) != keys_cache_.end()) { - std::lock_guard<std::mutex> lock(ctx_mutex_); - return keys_cache_[fpr].Copy(); - } + if (use_cache) { + auto key = get_key_in_cache(fpr); + if (key.IsGood()) return key; } gpgme_key_t _p_key = nullptr; @@ -124,8 +119,18 @@ void GpgFrontend::GpgKeyGetter::FlushKeyCache() { std::lock_guard<std::mutex> lock(keys_cache_mutex_); gpgme_key_t key; while ((err = gpgme_op_keylist_next(ctx_, &key)) == GPG_ERR_NO_ERROR) { - LOG(INFO) << "LoadKey Fpr:" << key->fpr << "Id:" << key->subkeys->keyid; - keys_cache_.insert({key->subkeys->keyid, GpgKey(std::move(key))}); + auto gpg_key = GpgKey(std::move(key)); + + // detect if the key is in a smartcard + // if so, try to get full information using gpgme_get_key() + // this maybe a bug in gpgme + if (gpg_key.IsHasCardKey()) { + gpg_key = GetKey(gpg_key.GetId(), false); + } + + LOG(INFO) << "LoadKey Fpr:" << gpg_key.GetFingerprint() + << "Id:" << gpg_key.GetId(); + keys_cache_.insert({gpg_key.GetId(), std::move(gpg_key)}); } } @@ -143,7 +148,7 @@ void GpgFrontend::GpgKeyGetter::FlushKeyCache() { GpgFrontend::KeyListPtr GpgFrontend::GpgKeyGetter::GetKeys( const KeyIdArgsListPtr& ids) { auto keys = std::make_unique<KeyArgsList>(); - for (const auto& id : *ids) keys->push_back(GetKey(id)); + for (const auto& id : *ids) keys->emplace_back(GetKey(id)); return keys; } @@ -152,7 +157,7 @@ GpgFrontend::KeyLinkListPtr GpgFrontend::GpgKeyGetter::GetKeysCopy( // get the lock std::lock_guard<std::mutex> lock(ctx_mutex_); auto keys_copy = std::make_unique<GpgKeyLinkList>(); - for (const auto& key : *keys) keys_copy->push_back(key.Copy()); + for (const auto& key : *keys) keys_copy->emplace_back(key.Copy()); return keys_copy; } @@ -161,6 +166,18 @@ GpgFrontend::KeyListPtr GpgFrontend::GpgKeyGetter::GetKeysCopy( // get the lock std::lock_guard<std::mutex> lock(ctx_mutex_); auto keys_copy = std::make_unique<KeyArgsList>(); - for (const auto& key : *keys) keys_copy->push_back(key.Copy()); + for (const auto& key : *keys) keys_copy->emplace_back(key.Copy()); return keys_copy; } + +GpgFrontend::GpgKey GpgFrontend::GpgKeyGetter::get_key_in_cache( + const std::string& id) { + std::lock_guard<std::mutex> lock(keys_cache_mutex_); + if (keys_cache_.find(id) != keys_cache_.end()) { + std::lock_guard<std::mutex> lock(ctx_mutex_); + // return a copy of the key in cache + return keys_cache_[id].Copy(); + } + // return a bad key + return GpgKey(); +} diff --git a/src/core/function/gpg/GpgKeyGetter.h b/src/core/function/gpg/GpgKeyGetter.h index 72cd777c..c96dbea7 100644 --- a/src/core/function/gpg/GpgKeyGetter.h +++ b/src/core/function/gpg/GpgKeyGetter.h @@ -59,7 +59,7 @@ class GPGFRONTEND_CORE_EXPORT GpgKeyGetter * @param fpr * @return GpgKey */ - GpgKey GetKey(const std::string& id); + GpgKey GetKey(const std::string& id, bool use_cache = true); /** * @brief Get the Keys object @@ -75,7 +75,7 @@ class GPGFRONTEND_CORE_EXPORT GpgKeyGetter * @param fpr * @return GpgKey */ - GpgKey GetPubkey(const std::string& id); + GpgKey GetPubkey(const std::string& id, bool use_cache = true); /** * @brief Get all the keys by receiving a linked list @@ -108,7 +108,7 @@ class GPGFRONTEND_CORE_EXPORT GpgKeyGetter private: /** - * @brief + * @brief Get the gpgme context object * */ GpgContext& ctx_ = @@ -121,7 +121,7 @@ class GPGFRONTEND_CORE_EXPORT GpgKeyGetter mutable std::mutex ctx_mutex_; /** - * @brief cache the keys with key fpr + * @brief cache the keys with key id * */ std::map<std::string, GpgKey> keys_cache_; @@ -131,6 +131,14 @@ class GPGFRONTEND_CORE_EXPORT GpgKeyGetter * */ mutable std::mutex keys_cache_mutex_; + + /** + * @brief Get the Key object + * + * @param id + * @return GpgKey + */ + GpgKey get_key_in_cache(const std::string& id); }; } // namespace GpgFrontend diff --git a/src/core/thread/CtxCheckThread.cpp b/src/core/thread/CtxCheckTask.cpp index edec8855..ee170fbc 100644 --- a/src/core/thread/CtxCheckThread.cpp +++ b/src/core/thread/CtxCheckTask.cpp @@ -24,20 +24,20 @@ * */ -#include "core/thread/CtxCheckThread.h" +#include "core/thread/CtxCheckTask.h" #include "core/GpgContext.h" #include "core/GpgCoreInit.h" #include "core/common/CoreCommonUtil.h" #include "core/function/gpg/GpgKeyGetter.h" -GpgFrontend::CtxCheckThread::CtxCheckThread() : QThread(nullptr) { - connect(this, &CtxCheckThread::SignalGnupgNotInstall, +GpgFrontend::Thread::CtxCheckTask::CtxCheckTask() { + connect(this, &CtxCheckTask::SignalGnupgNotInstall, CoreCommonUtil::GetInstance(), &CoreCommonUtil::SignalGnupgNotInstall); } -void GpgFrontend::CtxCheckThread::run() { +void GpgFrontend::Thread::CtxCheckTask::Run() { // init logging init_logging(); diff --git a/src/core/thread/CtxCheckThread.h b/src/core/thread/CtxCheckTask.h index c597141f..06ddfd82 100644 --- a/src/core/thread/CtxCheckThread.h +++ b/src/core/thread/CtxCheckTask.h @@ -28,20 +28,21 @@ #define GPGFRONTEND_CTXCHECKTRHEAD_H #include "core/GpgFrontendCore.h" +#include "core/thread/Task.h" -namespace GpgFrontend { +namespace GpgFrontend::Thread { /** * @brief * */ -class GPGFRONTEND_CORE_EXPORT CtxCheckThread : public QThread { +class GPGFRONTEND_CORE_EXPORT CtxCheckTask : public Task { Q_OBJECT public: /** * @brief Construct a new Ctx Check Thread object * */ - CtxCheckThread(); + CtxCheckTask(); signals: /** @@ -55,8 +56,8 @@ class GPGFRONTEND_CORE_EXPORT CtxCheckThread : public QThread { * @brief * */ - void run() override; + void Run() override; }; -} // namespace GpgFrontend +} // namespace GpgFrontend::Thread #endif // GPGFRONTEND_CTXCHECKTRHEAD_H diff --git a/src/core/thread/FileReadTask.cpp b/src/core/thread/FileReadTask.cpp new file mode 100644 index 00000000..3a235390 --- /dev/null +++ b/src/core/thread/FileReadTask.cpp @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2021 Saturneric + * + * This file is part of GpgFrontend. + * + * GpgFrontend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * The source code version of this software was modified and released + * by Saturneric<[email protected]><[email protected]> starting on May 12, 2021. + * + */ + +#include "core/thread/FileReadTask.h" + +#include <utility> + +namespace GpgFrontend::UI { + +FileReadTask::FileReadTask(std::string path) { + connect(this, &FileReadTask::SignalFileBytesReadNext, this, + &FileReadTask::read_bytes); + +#ifdef WINDOWS + std::filesystem::path read_file_path( + QString::fromStdString(path).toStdU16String()); +#else + std::filesystem::path read_file_path( + QString::fromStdString(path).toStdString()); +#endif + read_file_path_ = read_file_path; +} + +void FileReadTask::Run() { + SetFinishAfterRun(false); + + if (is_regular_file(read_file_path_)) { + LOG(INFO) << "read open file" << read_file_path_; + + target_file_.setFileName( + QString::fromStdString(read_file_path_.u8string())); + target_file_.open(QIODevice::ReadOnly); + + if (!(target_file_.isOpen() && target_file_.isReadable())) { + LOG(ERROR) << "file not open or not readable"; + if (target_file_.isOpen()) target_file_.close(); + return; + } + LOG(INFO) << "started reading" << read_file_path_; + read_bytes(); + } else { + emit SignalFileBytesReadEnd(); + } +} + +void FileReadTask::read_bytes() { + QByteArray read_buffer; + if (!target_file_.atEnd() && + (read_buffer = target_file_.read(buffer_size_)).size() > 0) { + LOG(INFO) << "read bytes" << read_buffer.size(); + emit SignalFileBytesRead(std::move(read_buffer)); + } else { + LOG(INFO) << "read bytes end"; + emit SignalFileBytesReadEnd(); + // finish task + emit SignalTaskFinished(); + } +} + +FileReadTask::~FileReadTask() { + LOG(INFO) << "close file" << read_file_path_; + if (target_file_.isOpen()) target_file_.close(); +} + +} // namespace GpgFrontend::UI diff --git a/src/ui/thread/FileReadThread.h b/src/core/thread/FileReadTask.h index e7573af8..d4e61cbe 100644 --- a/src/ui/thread/FileReadThread.h +++ b/src/core/thread/FileReadTask.h @@ -27,7 +27,8 @@ #ifndef GPGFRONTEND_FILEREADTHREAD_H #define GPGFRONTEND_FILEREADTHREAD_H -#include "ui/GpgFrontendUI.h" +#include "core/GpgFrontendCore.h" +#include "core/thread/Task.h" namespace GpgFrontend::UI { @@ -35,41 +36,28 @@ namespace GpgFrontend::UI { * @brief * */ -class FileReadThread : public QThread { +class GPGFRONTEND_CORE_EXPORT FileReadTask : public GpgFrontend::Thread::Task { Q_OBJECT - public: - /** - * @brief Construct a new File Read Thread object - * - * @param path - */ - explicit FileReadThread(std::string path); - - signals: + explicit FileReadTask(std::string path); - /** - * @brief - * - * @param block - */ - void SignalSendReadBlock(const std::string& block); + virtual ~FileReadTask() override; - /** - * @brief - * - */ - void SignalReadDone(); + void Run() override; - protected: - /** - * @brief - * - */ - void run() override; + signals: + void SignalFileBytesRead(QByteArray bytes); + void SignalFileBytesReadEnd(); + void SignalFileBytesReadNext(); private: - std::string path_; ///< + std::filesystem::path read_file_path_; + QFile target_file_; + const size_t buffer_size_ = 4096; + QEventLoop looper; + + private slots: + void read_bytes(); }; } // namespace GpgFrontend::UI diff --git a/src/core/thread/Task.cpp b/src/core/thread/Task.cpp new file mode 100644 index 00000000..9626ba69 --- /dev/null +++ b/src/core/thread/Task.cpp @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2021 Saturneric + * + * This file is part of GpgFrontend. + * + * GpgFrontend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * The source code version of this software was modified and released + * by Saturneric<[email protected]><[email protected]> starting on May 12, 2021. + * + */ + +#include "core/thread/Task.h" + +#include <functional> + +#include "core/thread/TaskRunner.h" + +GpgFrontend::Thread::Task::Task() { init(); } + +GpgFrontend::Thread::Task::Task(TaskCallback callback) + : callback_(std::move(callback)) { + init(); +} + +GpgFrontend::Thread::Task::Task(TaskRunnable runnable, TaskCallback callback) + : runnable_(runnable), callback_(std::move(callback)) { + init(); +} + +GpgFrontend::Thread::Task::~Task() = default; + +void GpgFrontend::Thread::Task::SetFinishAfterRun(bool finish_after_run) { + this->finish_after_run_ = finish_after_run; +} + +void GpgFrontend::Thread::Task::SetRTN(int rtn) { this->rtn_ = rtn; } + +void GpgFrontend::Thread::Task::init() { + LOG(INFO) << "called"; + connect(this, &Task::SignalTaskFinished, this, &Task::before_finish_task); + connect(this, &Task::SignalTaskFinished, this, &Task::deleteLater); +} + +void GpgFrontend::Thread::Task::before_finish_task() { + LOG(INFO) << "called"; + if (callback_) callback_(rtn_); +} + +void GpgFrontend::Thread::Task::run() { + LOG(INFO) << "called"; + Run(); + if (finish_after_run_) emit SignalTaskFinished(); +} + +void GpgFrontend::Thread::Task::Run() { + if (runnable_) { + rtn_ = runnable_(); + } +} diff --git a/src/core/thread/Task.h b/src/core/thread/Task.h new file mode 100644 index 00000000..4b536176 --- /dev/null +++ b/src/core/thread/Task.h @@ -0,0 +1,113 @@ +/** + * Copyright (C) 2021 Saturneric + * + * This file is part of GpgFrontend. + * + * GpgFrontend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * The source code version of this software was modified and released + * by Saturneric<[email protected]><[email protected]> starting on May 12, 2021. + * + */ + +#ifndef GPGFRONTEND_TASK_H +#define GPGFRONTEND_TASK_H + +#include <functional> + +#include "core/GpgFrontendCore.h" + +namespace GpgFrontend::Thread { + +class TaskRunner; + +class GPGFRONTEND_CORE_EXPORT Task : public QObject, public QRunnable { + Q_OBJECT + public: + using TaskRunnable = std::function<int()>; ///< + using TaskCallback = std::function<void(int)>; ///< + friend class TaskRunner; + + /** + * @brief Construct a new Task object + * + */ + Task(); + + /** + * @brief Construct a new Task object + * + * @param callback The callback function to be executed. + * callback must not be nullptr, and not tp opreate UI object. + */ + Task(TaskCallback callback); + + /** + * @brief Construct a new Task object + * + * @param runnable + */ + Task( + TaskRunnable runnable, TaskCallback callback = [](int) {}); + + /** + * @brief Destroy the Task object + * + */ + virtual ~Task() override; + + /** + * @brief Run - run the task + * + */ + virtual void Run(); + + signals: + void SignalTaskFinished(); + + protected: + void SetFinishAfterRun(bool finish_after_run); + + void SetRTN(int rtn); + + private: + TaskCallback callback_; ///< + TaskRunnable runnable_; ///< + bool finish_after_run_ = true; ///< + int rtn_ = 0; ///< + + /** + * @brief + * + */ + void before_finish_task(); + + /** + * @brief + * + */ + void init(); + + /** + * @brief + * + */ + virtual void run() override; +}; +} // namespace GpgFrontend::Thread + +#endif // GPGFRONTEND_TASK_H
\ No newline at end of file diff --git a/src/core/thread/TaskRunner.cpp b/src/core/thread/TaskRunner.cpp new file mode 100644 index 00000000..2223bdda --- /dev/null +++ b/src/core/thread/TaskRunner.cpp @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2021 Saturneric + * + * This file is part of GpgFrontend. + * + * GpgFrontend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * The source code version of this software was modified and released + * by Saturneric<[email protected]><[email protected]> starting on May 12, 2021. + * + */ + +#include "core/thread/TaskRunner.h" + +#include "core/thread/Task.h" +#include "easylogging++.h" + +GpgFrontend::Thread::TaskRunner::TaskRunner() = default; + +GpgFrontend::Thread::TaskRunner::~TaskRunner() = default; + +void GpgFrontend::Thread::TaskRunner::PostTask(Task* task) { + LOG(INFO) << "called"; + if (task == nullptr) return; + task->setParent(nullptr); + task->moveToThread(this); + { + std::lock_guard<std::mutex> lock(tasks_mutex_); + tasks.push(task); + } + quit(); +} + +void GpgFrontend::Thread::TaskRunner::run() { + LOG(INFO) << "called"; + while (true) { + if (tasks.empty()) { + LOG(INFO) << "TaskRunner: No tasks to run"; + exec(); + } else { + LOG(INFO) << "TaskRunner: Running task, queue size:" << tasks.size(); + + Task* task = nullptr; + { + std::lock_guard<std::mutex> lock(tasks_mutex_); + task = std::move(tasks.front()); + tasks.pop(); + } + if (task != nullptr) task->run(); + } + } +} diff --git a/src/core/thread/TaskRunner.h b/src/core/thread/TaskRunner.h new file mode 100644 index 00000000..14eaeae7 --- /dev/null +++ b/src/core/thread/TaskRunner.h @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2021 Saturneric + * + * This file is part of GpgFrontend. + * + * GpgFrontend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * The source code version of this software was modified and released + * by Saturneric<[email protected]><[email protected]> starting on May 12, 2021. + * + */ + +#ifndef GPGFRONTEND_TASKRUNNER_H +#define GPGFRONTEND_TASKRUNNER_H + +#include <mutex> +#include <queue> + +#include "core/GpgFrontendCore.h" + +namespace GpgFrontend::Thread { + +class Task; + +class GPGFRONTEND_CORE_EXPORT TaskRunner : public QThread { + Q_OBJECT + public: + /** + * @brief Construct a new Task Runner object + * + */ + TaskRunner(); + + /** + * @brief Destroy the Task Runner object + * + */ + virtual ~TaskRunner() override; + + /** + * @brief + * + */ + void run() override; + + public slots: + + /** + * @brief + * + * @param task + */ + void PostTask(Task* task); + + private: + std::queue<Task*> tasks; ///< The task queue + std::mutex tasks_mutex_; ///< The task queue mutex +}; +} // namespace GpgFrontend::Thread + +#endif // GPGFRONTEND_TASKRUNNER_H
\ No newline at end of file diff --git a/src/core/thread/TaskRunnerGetter.cpp b/src/core/thread/TaskRunnerGetter.cpp new file mode 100644 index 00000000..186483ec --- /dev/null +++ b/src/core/thread/TaskRunnerGetter.cpp @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2021 Saturneric + * + * This file is part of GpgFrontend. + * + * GpgFrontend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * The source code version of this software was modified and released + * by Saturneric<[email protected]><[email protected]> starting on May 12, 2021. + * + */ + +#include "core/thread/TaskRunnerGetter.h" + +GpgFrontend::Thread::TaskRunnerGetter::TaskRunnerGetter(int channel) + : SingletonFunctionObject<TaskRunnerGetter>(channel) {} + +GpgFrontend::Thread::TaskRunner* +GpgFrontend::Thread::TaskRunnerGetter::GetTaskRunner( + TaskRunnerType runner_type) { + while (true) { + auto it = task_runners_.find(runner_type); + if (it != task_runners_.end()) { + return it->second; + } else { + auto runner = new TaskRunner(); + task_runners_[runner_type] = runner; + runner->start(); + continue; + } + } +} diff --git a/src/core/thread/TaskRunnerGetter.h b/src/core/thread/TaskRunnerGetter.h new file mode 100644 index 00000000..722484b5 --- /dev/null +++ b/src/core/thread/TaskRunnerGetter.h @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2021 Saturneric + * + * This file is part of GpgFrontend. + * + * GpgFrontend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * The source code version of this software was modified and released + * by Saturneric<[email protected]><[email protected]> starting on May 12, 2021. + * + */ + +#ifndef GPGFRONTEND_TASKRUNNERGETTER_H +#define GPGFRONTEND_TASKRUNNERGETTER_H + +#include "core/GpgFrontendCore.h" +#include "core/GpgFunctionObject.h" +#include "core/thread/TaskRunner.h" + +namespace GpgFrontend::Thread { + +class GPGFRONTEND_CORE_EXPORT TaskRunnerGetter + : public GpgFrontend::SingletonFunctionObject<TaskRunnerGetter> { + public: + enum TaskRunnerType { + kTaskRunnerType_Default, + kTaskRunnerType_GPG, + kTaskRunnerType_IO, + }; + + TaskRunnerGetter(int channel = SingletonFunctionObject::GetDefaultChannel()); + + TaskRunner *GetTaskRunner( + TaskRunnerType runner_type = kTaskRunnerType_Default); + + private: + std::map<TaskRunnerType, TaskRunner *> task_runners_; +}; + +} // namespace GpgFrontend::Thread + +#endif // GPGFRONTEND_TASKRUNNERGETTER_H
\ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 24f2b7fa..fd20a664 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,7 +36,6 @@ #include "GpgFrontendBuildInfo.h" #include "core/GpgFunctionObject.h" -#include "core/thread/CtxCheckThread.h" #include "ui/GpgFrontendUIInit.h" #include "ui/main_window/MainWindow.h" diff --git a/src/ui/GpgFrontendUIInit.cpp b/src/ui/GpgFrontendUIInit.cpp index 2a8ac8b4..e7751ee6 100644 --- a/src/ui/GpgFrontendUIInit.cpp +++ b/src/ui/GpgFrontendUIInit.cpp @@ -29,7 +29,8 @@ #include "GpgFrontendUIInit.h" #include "core/function/GlobalSettingStation.h" -#include "core/thread/CtxCheckThread.h" +#include "core/thread/CtxCheckTask.h" +#include "core/thread/TaskRunnerGetter.h" #include "ui/SignalStation.h" #include "ui/UserInterfaceUtils.h" #include "ui/main_window/MainWindow.h" @@ -49,9 +50,7 @@ void InitGpgFrontendUI() { CommonUtils::GetInstance(); // create the thread to load the gpg context - auto* init_ctx_thread = new GpgFrontend::CtxCheckThread(); - QApplication::connect(init_ctx_thread, &QThread::finished, init_ctx_thread, - &QThread::deleteLater); + auto* init_ctx_task = new Thread::CtxCheckTask(); // create and show loading window before starting the main window auto* waiting_dialog = new QProgressDialog(); @@ -66,27 +65,37 @@ void InitGpgFrontendUI() { waiting_dialog_label->setWordWrap(true); waiting_dialog->setLabel(waiting_dialog_label); waiting_dialog->resize(420, 120); - QApplication::connect(init_ctx_thread, &QThread::finished, [=]() { - waiting_dialog->finished(0); - waiting_dialog->deleteLater(); - }); + QApplication::connect(init_ctx_task, + &Thread::CtxCheckTask::SignalTaskFinished, + waiting_dialog, [=]() { + LOG(INFO) << "Gpg context loaded"; + waiting_dialog->finished(0); + waiting_dialog->deleteLater(); + }); + QApplication::connect(waiting_dialog, &QProgressDialog::canceled, [=]() { LOG(INFO) << "cancel clicked"; - if (init_ctx_thread->isRunning()) init_ctx_thread->terminate(); QCoreApplication::quit(); exit(0); }); // show the loading window + waiting_dialog->setModal(true); waiting_dialog->show(); waiting_dialog->setFocus(); - // start the thread to load the gpg context - init_ctx_thread->start(); - QEventLoop loop; - QApplication::connect(init_ctx_thread, &QThread::finished, &loop, + // new local event looper + QEventLoop looper; + QApplication::connect(init_ctx_task, + &Thread::CtxCheckTask::SignalTaskFinished, &looper, &QEventLoop::quit); - loop.exec(); + + // start the thread to load the gpg context + Thread::TaskRunnerGetter::GetInstance().GetTaskRunner()->PostTask( + init_ctx_task); + + // block the main thread until the gpg context is loaded + looper.exec(); } int RunGpgFrontendUI() { diff --git a/src/ui/UserInterfaceUtils.cpp b/src/ui/UserInterfaceUtils.cpp index 724a467e..65c55c8f 100644 --- a/src/ui/UserInterfaceUtils.cpp +++ b/src/ui/UserInterfaceUtils.cpp @@ -35,9 +35,13 @@ #include "core/function/FileOperator.h" #include "core/function/GlobalSettingStation.h" #include "core/function/gpg/GpgKeyGetter.h" +#include "core/thread/Task.h" +#include "core/thread/TaskRunner.h" +#include "core/thread/TaskRunnerGetter.h" #include "easylogging++.h" #include "ui/SignalStation.h" #include "ui/dialog/WaitingDialog.h" +#include "ui/struct/SettingsObject.h" #include "ui/widgets/TextEdit.h" namespace GpgFrontend::UI { @@ -111,17 +115,21 @@ void process_result_analyse(TextEdit *edit, InfoBoardWidget *info_board, void process_operation(QWidget *parent, const std::string &waiting_title, const std::function<void()> &func) { + auto *dialog = + new WaitingDialog(QString::fromStdString(waiting_title), parent); + auto thread = QThread::create(func); QApplication::connect(thread, &QThread::finished, thread, &QThread::deleteLater); - thread->start(); + QApplication::connect(thread, &QThread::finished, dialog, &QDialog::close); + QApplication::connect(thread, &QThread::finished, dialog, + &QDialog::deleteLater); - auto *dialog = - new WaitingDialog(QString::fromStdString(waiting_title), parent); - while (thread->isRunning()) { - QApplication::processEvents(); - } - dialog->close(); + QEventLoop looper; + QApplication::connect(dialog, &QDialog::finished, &looper, &QEventLoop::quit); + + thread->start(); + looper.exec(); } CommonUtils *CommonUtils::GetInstance() { @@ -245,11 +253,23 @@ void CommonUtils::SlotExecuteGpgCommand( void CommonUtils::SlotImportKeyFromKeyServer( const KeyIdArgsList &key_ids, const ImportCallbackFunctiopn &callback) { std::string target_keyserver; + if (target_keyserver.empty()) { try { auto &settings = GlobalSettingStation::GetInstance().GetUISettings(); + SettingsObject key_server_json("key_server"); + + // get key servers from settings + const auto key_server_list = + key_server_json.Check("server_list", nlohmann::json::array()); + if (key_server_list.empty()) { + throw std::runtime_error("No key server configured"); + } - target_keyserver = settings.lookup("keyserver.default_server").c_str(); + const int target_key_server_index = + key_server_json.Check("default_server", 0); + target_keyserver = + key_server_list[target_key_server_index].get<std::string>(); LOG(INFO) << _("Set target Key Server to default Key Server") << target_keyserver; @@ -323,39 +343,26 @@ void CommonUtils::SlotImportKeyFromKeyServer( current_index++; } }); - connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); } void CommonUtils::slot_update_key_status() { LOG(INFO) << "called"; - auto *thread = QThread::create([this]() { - std::vector<QThread *> threads; + auto refresh_task = new Thread::Task([]() -> int { // flush key cache for all GpgKeyGetter Intances. for (const auto &channel_id : GpgKeyGetter::GetAllChannelId()) { - // multi threading - auto *refresh_thread = QThread::create([channel_id]() { - LOG(INFO) << "FlushKeyCache thread start" - << "channel:" << channel_id; - GpgKeyGetter::GetInstance(channel_id).FlushKeyCache(); - }); - refresh_thread->start(); - threads.push_back(refresh_thread); + GpgKeyGetter::GetInstance(channel_id).FlushKeyCache(); } - - for (auto *thread : threads) { - thread->wait(); - thread->deleteLater(); - } - - emit SignalKeyDatabaseRefreshDone(); - LOG(INFO) << "finished"; + return 0; }); - connect(thread, &QThread::finished, thread, &QThread::deleteLater); - LOG(INFO) << "start thread"; - thread->start(); + connect(refresh_task, &Thread::Task::SignalTaskFinished, this, + &CommonUtils::SignalKeyDatabaseRefreshDone); + + // post the task to the default task runner + Thread::TaskRunnerGetter::GetInstance().GetTaskRunner()->PostTask( + refresh_task); } } // namespace GpgFrontend::UI
\ No newline at end of file diff --git a/src/ui/keypair_details/KeyPairOperaTab.cpp b/src/ui/keypair_details/KeyPairOperaTab.cpp index 988ce527..4f7cd66f 100644 --- a/src/ui/keypair_details/KeyPairOperaTab.cpp +++ b/src/ui/keypair_details/KeyPairOperaTab.cpp @@ -145,8 +145,12 @@ void KeyPairOperaTab::slot_export_public_key() { _("An error occurred during the export operation.")); return; } - auto file_string = m_key_.GetName() + " " + m_key_.GetEmail() + "(" + + + // generate a file name + auto file_string = m_key_.GetName() + "<" + m_key_.GetEmail() + ">(" + m_key_.GetId() + ")_pub.asc"; + std::replace(file_string.begin(), file_string.end(), ' ', '_'); + auto file_name = QFileDialog::getSaveFileName( this, _("Export Key To File"), QString::fromStdString(file_string), @@ -188,8 +192,11 @@ void KeyPairOperaTab::slot_export_short_private_key() { _("An error occurred during the export operation.")); return; } - auto file_string = m_key_.GetName() + " " + m_key_.GetEmail() + "(" + + + auto file_string = m_key_.GetName() + "<" + m_key_.GetEmail() + ">(" + m_key_.GetId() + ")_short_secret.asc"; + std::replace(file_string.begin(), file_string.end(), ' ', '_'); + auto file_name = QFileDialog::getSaveFileName( this, _("Export Key To File"), QString::fromStdString(file_string), @@ -228,8 +235,10 @@ void KeyPairOperaTab::slot_export_private_key() { _("An error occurred during the export operation.")); return; } - auto file_string = m_key_.GetName() + " " + m_key_.GetEmail() + "(" + + auto file_string = m_key_.GetName() + "<" + m_key_.GetEmail() + ">(" + m_key_.GetId() + ")_full_secret.asc"; + std::replace(file_string.begin(), file_string.end(), ' ', '_'); + auto file_name = QFileDialog::getSaveFileName( this, _("Export Key To File"), QString::fromStdString(file_string), diff --git a/src/ui/settings/SettingsKeyServer.cpp b/src/ui/settings/SettingsKeyServer.cpp index f2eaf9a9..e80e4598 100644 --- a/src/ui/settings/SettingsKeyServer.cpp +++ b/src/ui/settings/SettingsKeyServer.cpp @@ -225,7 +225,7 @@ void KeyserverTab::slot_refresh_table() { void KeyserverTab::slot_test_listed_key_server() { auto timeout = QInputDialog::getInt(this, _("Set TCP Timeout"), tr("timeout(ms): "), - QLineEdit::Normal, 500, 2000); + QLineEdit::Normal, 800, 3000); QStringList urls; const auto row_size = ui_->keyServerListTable->rowCount(); diff --git a/src/ui/thread/FileReadThread.cpp b/src/ui/thread/FileReadThread.cpp deleted file mode 100644 index 83731c8a..00000000 --- a/src/ui/thread/FileReadThread.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright (C) 2021 Saturneric - * - * This file is part of GpgFrontend. - * - * GpgFrontend is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GpgFrontend is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. - * - * The initial version of the source code is inherited from - * the gpg4usb project, which is under GPL-3.0-or-later. - * - * The source code version of this software was modified and released - * by Saturneric<[email protected]><[email protected]> starting on May 12, 2021. - * - */ - -#include "FileReadThread.h" - - -#include <utility> - -namespace GpgFrontend::UI { - -FileReadThread::FileReadThread(std::string path) : path_(std::move(path)) { - qRegisterMetaType<std::string>("std::string"); -} - -void FileReadThread::run() { - LOG(INFO) << "started reading" << path_; - -#ifdef WINDOWS - std::filesystem::path read_file_path(QString::fromStdString(path_).toStdU16String()); -#else - std::filesystem::path read_file_path(QString::fromStdString(path_).toStdString()); -#endif - - if (is_regular_file(read_file_path)) { - LOG(INFO) << "read open" << read_file_path; - - QFile target_file; - target_file.setFileName(QString::fromStdString(read_file_path.u8string())); - target_file.open(QIODevice::ReadOnly); - QByteArray read_buffer; - LOG(INFO) << "thread start reading"; - - const size_t buffer_size = 4096; - if(!(target_file.isOpen() && target_file.isReadable())) { - LOG(ERROR) << "file not open or not readable"; - if(target_file.isOpen()) - target_file.close(); - return; - } - - while (!target_file.atEnd() && (read_buffer = target_file.read(buffer_size)).size() > 0) { - // Check isInterruptionRequested - if (QThread::currentThread()->isInterruptionRequested()) { - LOG(INFO) << "thread is interruption requested "; - target_file.close(); - return; - } - LOG(INFO) << "block size " << read_buffer.size(); - std::string buffer_str(read_buffer.toStdString()); - - emit SignalSendReadBlock(buffer_str); -#ifdef RELEASE - QThread::msleep(32); -#else - QThread::msleep(128); -#endif - } - target_file.close(); - emit SignalReadDone(); - LOG(INFO) << "thread end reading"; - } -} - -} // namespace GpgFrontend::UI diff --git a/src/ui/widgets/FilePage.cpp b/src/ui/widgets/FilePage.cpp index 1047be75..fe188e93 100644 --- a/src/ui/widgets/FilePage.cpp +++ b/src/ui/widgets/FilePage.cpp @@ -118,29 +118,30 @@ void FilePage::slot_file_tree_view_item_clicked(const QModelIndex& index) { QFileInfo info(QString::fromStdString(selected_path_.u8string())); if ((info.isDir() || info.isFile()) && - (info.suffix() != "gpg" && info.suffix() != "sig" && - info.suffix() != "asc")) { + (info.suffix() != "gpg" && info.suffix() != "pgp" && + info.suffix() != "sig" && info.suffix() != "asc")) { operation_type |= MainWindow::CryptoMenu::Encrypt; } if ((info.isDir() || info.isFile()) && - (info.suffix() != "gpg" && info.suffix() != "sig" && - info.suffix() != "asc")) { + (info.suffix() != "gpg" && info.suffix() != "pgp" && + info.suffix() != "sig" && info.suffix() != "asc")) { operation_type |= MainWindow::CryptoMenu::EncryptAndSign; } - if (info.isFile() && (info.suffix() == "gpg" || info.suffix() == "asc")) { + if (info.isFile() && (info.suffix() == "gpg" || info.suffix() == "pgp" || + info.suffix() == "asc")) { operation_type |= MainWindow::CryptoMenu::Decrypt; operation_type |= MainWindow::CryptoMenu::DecryptAndVerify; } - if (info.isFile() && (info.suffix() != "gpg" && info.suffix() != "sig" && - info.suffix() != "asc")) { + if (info.isFile() && (info.suffix() != "gpg" && info.suffix() != "pgp" && + info.suffix() != "sig" && info.suffix() != "asc")) { operation_type |= MainWindow::CryptoMenu::Sign; } if (info.isFile() && (info.suffix() == "sig" || info.suffix() == "gpg" || - info.suffix() == "asc")) { + info.suffix() == "pgp" || info.suffix() == "asc")) { operation_type |= MainWindow::CryptoMenu::Verify; } } @@ -155,8 +156,10 @@ void FilePage::slot_up_level() { auto str_path = dir_model_->fileInfo(currentRoot).absoluteFilePath().toStdU16String(); #else - auto str_path = - dir_model_->fileInfo(currentRoot).absoluteFilePath().toUtf8().toStdString(); + auto str_path = dir_model_->fileInfo(currentRoot) + .absoluteFilePath() + .toUtf8() + .toStdString(); #endif std::filesystem::path path_obj(str_path); @@ -291,9 +294,11 @@ void FilePage::onCustomContextMenu(const QPoint& point) { LOG(INFO) << "right click" << selected_path_.u8string(); #ifdef WINDOWS - auto index_dir_str = dir_model_->fileInfo(index).absoluteFilePath().toStdU16String(); + auto index_dir_str = + dir_model_->fileInfo(index).absoluteFilePath().toStdU16String(); #else - auto index_dir_str = dir_model_->fileInfo(index).absoluteFilePath().toStdString(); + auto index_dir_str = + dir_model_->fileInfo(index).absoluteFilePath().toStdString(); #endif selected_path_ = std::filesystem::path(index_dir_str); @@ -350,9 +355,9 @@ void FilePage::slot_rename_item() { new_name_path = new_name_path.remove_filename(); bool ok; - auto text = - QInputDialog::getText(this, _("Rename"), _("New Filename"), - QLineEdit::Normal, QString::fromStdString(old_name.u8string()), &ok); + auto text = QInputDialog::getText( + this, _("Rename"), _("New Filename"), QLineEdit::Normal, + QString::fromStdString(old_name.u8string()), &ok); if (ok && !text.isEmpty()) { try { #ifdef WINDOWS diff --git a/src/ui/widgets/KeyList.cpp b/src/ui/widgets/KeyList.cpp index 237576ad..0bd65f25 100644 --- a/src/ui/widgets/KeyList.cpp +++ b/src/ui/widgets/KeyList.cpp @@ -66,8 +66,11 @@ void KeyList::init() { connect(SignalStation::GetInstance(), &SignalStation::SignalKeyDatabaseRefreshDone, this, &KeyList::SlotRefresh); + + // register key database sync signal for refresh button connect(ui_->refreshKeyListButton, &QPushButton::clicked, this, - &KeyList::SlotRefresh); + &KeyList::SignalRefreshDatabase); + connect(ui_->uncheckButton, &QPushButton::clicked, this, &KeyList::uncheck_all); connect(ui_->checkALLButton, &QPushButton::clicked, this, @@ -161,7 +164,6 @@ void KeyList::SlotRefresh() { ui_->syncButton->setDisabled(true); emit SignalRefreshStatusBar(_("Refreshing Key List..."), 3000); - this->buffered_keys_list_ = GpgKeyGetter::GetInstance().FetchKey(); this->slot_refresh_ui(); } @@ -460,7 +462,7 @@ void KeyList::slot_sync_with_key_server() { ui_->syncButton->setDisabled(false); ui_->refreshKeyListButton->setDisabled(false); emit SignalRefreshStatusBar(_("Key List Sync Done."), 3000); - emit SignalRefreshDatabase(); + emit this->SignalRefreshDatabase(); } }); } @@ -498,8 +500,9 @@ KeyIdArgsListPtr& KeyTable::GetChecked() { if (checked_key_ids_ == nullptr) checked_key_ids_ = std::make_unique<KeyIdArgsList>(); auto& ret = checked_key_ids_; - for (int i = 0; i < key_list_->rowCount(); i++) { + for (int i = 0; i < buffered_keys_.size(); i++) { auto key_id = buffered_keys_[i].GetId(); + LOG(INFO) << "i: " << i << " key_id: " << key_id; if (key_list_->item(i, 0)->checkState() == Qt::Checked && std::find(ret->begin(), ret->end(), key_id) == ret->end()) { ret->push_back(key_id); @@ -514,7 +517,7 @@ void KeyTable::SetChecked(KeyIdArgsListPtr key_ids) { } void KeyTable::Refresh(KeyLinkListPtr m_keys) { - LOG(INFO) << "Called"; + LOG(INFO) << "called"; auto& checked_key_list = GetChecked(); // while filling the table, sort enabled causes errors @@ -529,16 +532,19 @@ void KeyTable::Refresh(KeyLinkListPtr m_keys) { else keys = std::move(m_keys); + LOG(INFO) << "keys size: " << keys->size(); auto it = keys->begin(); int row_count = 0; while (it != keys->end()) { + LOG(INFO) << "filtering key id: " << it->GetId(); if (filter_ != nullptr) { if (!filter_(*it)) { it = keys->erase(it); continue; } } + LOG(INFO) << "adding key id: " << it->GetId(); if (select_type_ == KeyListRow::ONLY_SECRET_KEY && !it->IsPrivateKey()) { it = keys->erase(it); continue; @@ -547,18 +553,15 @@ void KeyTable::Refresh(KeyLinkListPtr m_keys) { it++; } + LOG(INFO) << "row_count: " << row_count; key_list_->setRowCount(row_count); int row_index = 0; it = keys->begin(); - auto& table_buffered_keys = buffered_keys_; - - table_buffered_keys.clear(); + buffered_keys_.clear(); while (it != keys->end()) { - table_buffered_keys.push_back(it->Copy()); - auto* tmp0 = new QTableWidgetItem(QString::number(row_index)); tmp0->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); @@ -624,6 +627,12 @@ void KeyTable::Refresh(KeyLinkListPtr m_keys) { tmp2->setFont(strike); tmp3->setFont(strike); } + + LOG(INFO) << "key id: " << it->GetId() << "added into key_list_:" << this; + + // move to buffered keys + buffered_keys_.emplace_back(std::move(*it)); + it++; ++row_index; } diff --git a/src/ui/widgets/PlainTextEditorPage.cpp b/src/ui/widgets/PlainTextEditorPage.cpp index c70991b9..7eb682cc 100644 --- a/src/ui/widgets/PlainTextEditorPage.cpp +++ b/src/ui/widgets/PlainTextEditorPage.cpp @@ -24,14 +24,16 @@ * */ -#include "ui/widgets/PlainTextEditorPage.h" - -#include <encoding-detect/TextEncodingDetect.h> +#include "PlainTextEditorPage.h" #include <boost/format.hpp> +#include <string> #include <utility> -#include "ui/thread/FileReadThread.h" +#include "core/function/CharsetOperator.h" +#include "core/thread/FileReadTask.h" +#include "core/thread/Task.h" +#include "core/thread/TaskRunnerGetter.h" #include "ui_PlainTextEditor.h" namespace GpgFrontend::UI { @@ -42,8 +44,6 @@ PlainTextEditorPage::PlainTextEditorPage(QString file_path, QWidget *parent) full_file_path_(std::move(file_path)) { ui_->setupUi(this); - if (full_file_path_.isEmpty()) read_done_ = true; - ui_->textPage->setFocus(); ui_->loadingLabel->setHidden(true); @@ -52,21 +52,26 @@ PlainTextEditorPage::PlainTextEditorPage(QString file_path, QWidget *parent) this->setAttribute(Qt::WA_DeleteOnClose); this->ui_->characterLabel->setText(_("0 character")); - this->ui_->lfLabel->setText(_("None")); - this->ui_->encodingLabel->setText(_("Binary")); + this->ui_->lfLabel->setText(_("lf")); + this->ui_->encodingLabel->setText(_("utf-8")); connect(ui_->textPage, &QPlainTextEdit::textChanged, this, [=]() { + // if file is loading if (!read_done_) return; auto text = ui_->textPage->document()->toPlainText(); auto str = boost::format(_("%1% character(s)")) % text.size(); this->ui_->characterLabel->setText(str.str().c_str()); - - detect_cr_lf(text); - detect_encoding(text.toStdString()); }); - ui_->loadingLabel->setText(_("Loading...")); + if (full_file_path_.isEmpty()) { + read_done_ = true; + ui_->loadingLabel->setHidden(true); + } else { + read_done_ = false; + ui_->loadingLabel->setText(_("Loading...")); + ui_->loadingLabel->setHidden(false); + } } const QString &PlainTextEditorPage::GetFilePath() const { @@ -75,6 +80,26 @@ const QString &PlainTextEditorPage::GetFilePath() const { QPlainTextEdit *PlainTextEditorPage::GetTextPage() { return ui_->textPage; } +bool PlainTextEditorPage::WillCharsetChange() const { + // detect if the line-ending will change + if (is_crlf_) return true; + + // detect if the charset of the file will change + if (charset_name_ != "UTF-8" && charset_name_ != "ISO-8859-1") + return true; + else + return false; +} + +void PlainTextEditorPage::NotifyFileSaved() { + this->is_crlf_ = false; + this->charset_confidence_ = 100; + this->charset_name_ = "UTF-8"; + + this->ui_->lfLabel->setText(_("lf")); + this->ui_->encodingLabel->setText(_("UTF-8")); +} + void PlainTextEditorPage::SetFilePath(const QString &filePath) { full_file_path_ = filePath; } @@ -140,37 +165,34 @@ void PlainTextEditorPage::ReadFile() { auto text_page = this->GetTextPage(); text_page->setReadOnly(true); - auto thread = new FileReadThread(this->full_file_path_.toStdString()); - - connect(thread, &FileReadThread::SignalSendReadBlock, this, - &PlainTextEditorPage::slot_insert_text); - - connect(thread, &FileReadThread::SignalReadDone, this, [=]() { - LOG(INFO) << "thread read done"; - if (!binary_mode_) { - text_page->setReadOnly(false); - } - }); - connect(thread, &FileReadThread::finished, this, [=]() { - LOG(INFO) << "thread finished"; - thread->deleteLater(); - read_done_ = true; - read_thread_ = nullptr; - ui_->textPage->setEnabled(true); + const auto target_path = this->full_file_path_.toStdString(); + + auto *task_runner = + GpgFrontend::Thread::TaskRunnerGetter::GetInstance().GetTaskRunner(); + + auto *read_task = new FileReadTask(target_path); + connect(read_task, &FileReadTask::SignalFileBytesRead, this, + &PlainTextEditorPage::slot_insert_text, Qt::QueuedConnection); + connect(this, &PlainTextEditorPage::SignalUIBytesDisplayed, read_task, + &FileReadTask::SignalFileBytesReadNext, Qt::QueuedConnection); + + connect(read_task, &FileReadTask::SignalTaskFinished, this, + []() { LOG(INFO) << "read thread closed"; }); + connect(this, &PlainTextEditorPage::close, read_task, + &FileReadTask::SignalTaskFinished); + connect(read_task, &FileReadTask::SignalFileBytesReadEnd, this, [=]() { + // set the UI + if (!binary_mode_) text_page->setReadOnly(false); + this->read_done_ = true; + this->ui_->textPage->setEnabled(true); text_page->document()->setModified(false); - ui_->textPage->blockSignals(false); - ui_->textPage->document()->blockSignals(false); - ui_->loadingLabel->setHidden(true); + this->ui_->textPage->blockSignals(false); + this->ui_->textPage->document()->blockSignals(false); + this->ui_->loadingLabel->setHidden(true); }); - connect(this, &PlainTextEditorPage::destroyed, [=]() { - LOG(INFO) << "request interruption for read thread"; - if (read_thread_ && thread->isRunning()) thread->requestInterruption(); - read_thread_ = nullptr; - }); - this->read_thread_ = thread; - thread->start(); + task_runner->PostTask(read_task); } std::string binary_to_string(const std::string &source) { @@ -181,17 +203,19 @@ std::string binary_to_string(const std::string &source) { return ss.str(); } -void PlainTextEditorPage::slot_insert_text(const std::string &data) { +void PlainTextEditorPage::slot_insert_text(QByteArray bytes_data) { + std::string data = bytes_data.toStdString(); LOG(INFO) << "data size" << data.size(); read_bytes_ += data.size(); - // If binary format is detected, the entire file is converted to binary format - // for display + // If binary format is detected, the entire file is converted to binary + // format for display. bool if_last_binary_mode = binary_mode_; - if (!binary_mode_) { + if (!binary_mode_ && !read_done_) { detect_encoding(data); } if (binary_mode_) { + // change formery displayed text to binary format if (if_last_binary_mode != binary_mode_) { auto text_buffer = ui_->textPage->document()->toRawText().toLocal8Bit().toStdString(); @@ -200,60 +224,75 @@ void PlainTextEditorPage::slot_insert_text(const std::string &data) { binary_to_string(text_buffer).c_str()); this->ui_->lfLabel->setText("None"); } + + // insert new data this->GetTextPage()->insertPlainText(binary_to_string(data).c_str()); + // update the size of the file auto str = boost::format(_("%1% byte(s)")) % read_bytes_; this->ui_->characterLabel->setText(str.str().c_str()); } else { - this->GetTextPage()->insertPlainText(data.c_str()); + // detect crlf/lf line ending + detect_cr_lf(data); + + // when reding from a text file + // try convert the any of thetext to utf8 + std::string utf8_data; + if (!read_done_ && charset_confidence_ > 25) { + CharsetOperator::Convert2Utf8(data, utf8_data, charset_name_); + } else { + // when editing a text file, do nothing. + utf8_data = data; + } + + // insert the text to the text page + this->GetTextPage()->insertPlainText(utf8_data.c_str()); auto text = this->GetTextPage()->toPlainText(); auto str = boost::format(_("%1% character(s)")) % text.size(); this->ui_->characterLabel->setText(str.str().c_str()); - detect_cr_lf(text); - } -} - -void PlainTextEditorPage::PrepareToDestroy() { - if (read_thread_) { - read_thread_->requestInterruption(); - read_thread_ = nullptr; } + QTimer::singleShot(25, this, &PlainTextEditorPage::SignalUIBytesDisplayed); + LOG(INFO) << "end"; } void PlainTextEditorPage::detect_encoding(const std::string &data) { - AutoIt::Common::TextEncodingDetect text_detect; - AutoIt::Common::TextEncodingDetect::Encoding encoding = - text_detect.DetectEncoding((unsigned char *)(data.data()), data.size()); + // skip the binary data to avoid the false detection of the encoding + if (binary_mode_) return; + + // detect the encoding + auto charset = CharsetOperator::Detect(data); + this->charset_name_ = std::get<0>(charset).c_str(); + this->language_name_ = std::get<1>(charset).c_str(); + this->charset_confidence_ = std::get<2>(charset); - if (encoding == AutoIt::Common::TextEncodingDetect::None) { + // probably there is no need to detect the encoding again + if (this->charset_confidence_ < 10) { binary_mode_ = true; - ui_->encodingLabel->setText(_("Binary")); - } else if (encoding == AutoIt::Common::TextEncodingDetect::ASCII) { - ui_->encodingLabel->setText(_("ASCII(7 bits)")); - } else if (encoding == AutoIt::Common::TextEncodingDetect::ANSI) { - ui_->encodingLabel->setText(_("ASCII(8 bits)")); - } else if (encoding == AutoIt::Common::TextEncodingDetect::UTF8_BOM || - encoding == AutoIt::Common::TextEncodingDetect::UTF8_NOBOM) { - ui_->encodingLabel->setText(_("UTF-8")); - } else if (encoding == AutoIt::Common::TextEncodingDetect::UTF16_LE_BOM || - encoding == AutoIt::Common::TextEncodingDetect::UTF16_LE_NOBOM) { - ui_->encodingLabel->setText(_("UTF-16")); - } else if (encoding == AutoIt::Common::TextEncodingDetect::UTF16_BE_BOM || - encoding == AutoIt::Common::TextEncodingDetect::UTF16_BE_NOBOM) { - ui_->encodingLabel->setText(_("UTF-16(BE)")); + } + + if (binary_mode_) { + // hide the line ending label, when the file is binary + this->ui_->lfLabel->setHidden(true); + this->ui_->encodingLabel->setText(_("binary")); + } else { + ui_->encodingLabel->setText(this->charset_name_.c_str()); } } -void PlainTextEditorPage::detect_cr_lf(const QString &data) { +void PlainTextEditorPage::detect_cr_lf(const std::string &data) { if (binary_mode_) { - this->ui_->lfLabel->setText("None"); return; } - if (data.contains("\r\n")) { - this->ui_->lfLabel->setText("CRLF"); + + // if contain crlf, set the label to crlf + if (is_crlf_) return; + + if (data.find("\r\n") != std::string::npos) { + this->ui_->lfLabel->setText("crlf"); + is_crlf_ = true; } else { - this->ui_->lfLabel->setText("LF"); + this->ui_->lfLabel->setText("lf"); } } diff --git a/src/ui/widgets/PlainTextEditorPage.h b/src/ui/widgets/PlainTextEditorPage.h index e76c11e3..e5c1c89d 100644 --- a/src/ui/widgets/PlainTextEditorPage.h +++ b/src/ui/widgets/PlainTextEditorPage.h @@ -29,6 +29,8 @@ #ifndef __EDITORPAGE_H__ #define __EDITORPAGE_H__ +#include <string> + #include "core/GpgConstants.h" #include "ui/GpgFrontendUI.h" @@ -49,7 +51,7 @@ class PlainTextEditorPage : public QWidget { * @param file_path Path of the file handled in this tab * @param parent Pointer to the parent widget */ - explicit PlainTextEditorPage(QString file_path = "", + explicit PlainTextEditorPage(QString file_path = {}, QWidget* parent = nullptr); /** @@ -99,19 +101,36 @@ class PlainTextEditorPage : public QWidget { [[nodiscard]] bool ReadDone() const { return this->read_done_; } /** - * @brief + * @brief detect if the charset of the file will change + * + */ + bool WillCharsetChange() const; + + /** + * @brief notify the user that the file has been saved. + * + */ + void NotifyFileSaved(); + + signals: + + /** + * @brief this signal is emitted when the bytes has been append in texteditor. * */ - void PrepareToDestroy(); + void SignalUIBytesDisplayed(); private: std::shared_ptr<Ui_PlainTextEditor> ui_; ///< QString full_file_path_; ///< The path to the file handled in the tab bool sign_marked_{}; ///< true, if the signed header is marked, false if not - bool read_done_ = false; ///< - QThread* read_thread_ = nullptr; ///< - bool binary_mode_ = false; ///< - size_t read_bytes_ = 0; ///< + bool read_done_ = false; ///< + bool binary_mode_ = false; ///< + size_t read_bytes_ = 0; ///< + std::string charset_name_; ///< + std::string language_name_; ///< + int32_t charset_confidence_; ///< + bool is_crlf_ = false; ///< /** * @brief @@ -125,7 +144,7 @@ class PlainTextEditorPage : public QWidget { * * @param data */ - void detect_cr_lf(const QString& data); + void detect_cr_lf(const std::string& data); private slots: @@ -139,7 +158,7 @@ class PlainTextEditorPage : public QWidget { * * @param data */ - void slot_insert_text(const std::string& data); + void slot_insert_text(QByteArray bytes_data); }; } // namespace GpgFrontend::UI diff --git a/src/ui/widgets/TextEdit.cpp b/src/ui/widgets/TextEdit.cpp index ecf1a4bd..713dbb80 100644 --- a/src/ui/widgets/TextEdit.cpp +++ b/src/ui/widgets/TextEdit.cpp @@ -166,11 +166,30 @@ bool TextEdit::save_file(const QString& fileName) { return false; } + PlainTextEditorPage* page = SlotCurPageTextEdit(); + if (page == nullptr) return false; + + if (page->WillCharsetChange()) { + auto result = QMessageBox::warning( + this, _("Save"), + QString("<p>") + + _("After saving, the encoding of the current file will be " + "converted to UTF-8 and the line endings will be changed to " + "LF. ") + + "</p>" + "<p>" + + _("If this is not the result you expect, please use \"save " + "as\".") + + "</p>", + QMessageBox::Save | QMessageBox::Cancel, QMessageBox::Cancel); + + if (result == QMessageBox::Cancel) { + return false; + } + } + QFile file(fileName); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { - PlainTextEditorPage* page = SlotCurPageTextEdit(); - QTextStream outputStream(&file); QApplication::setOverrideCursor(Qt::WaitCursor); outputStream << page->GetTextPage()->toPlainText(); @@ -182,7 +201,8 @@ bool TextEdit::save_file(const QString& fileName) { int curIndex = tab_widget_->currentIndex(); tab_widget_->setTabText(curIndex, stripped_name(fileName)); page->SetFilePath(fileName); - // statusBar()->showMessage(_("File saved"), 2000); + page->NotifyFileSaved(); + file.close(); return true; } else { @@ -295,9 +315,7 @@ bool TextEdit::maybe_save_current_tab(bool askToSave) { return false; } } - - // destroy - page->PrepareToDestroy(); + page->deleteLater(); return true; } |