From c0757da47b5e3e99401a8d28c572a33bb808e6b9 Mon Sep 17 00:00:00 2001 From: Quentin Rouland Date: Fri, 17 Jan 2025 10:40:54 +0100 Subject: [PATCH] Refactor cloning feature and ui --- plugins/Git/CMakeLists.txt | 3 +- plugins/Git/git.cpp | 92 ++++++------- plugins/Git/git.h | 97 +++++++++++-- plugins/Git/jobs/clonejob.cpp | 79 +++++++++++ plugins/Git/jobs/clonejob.h | 111 +++++++++++++++ plugins/Git/jobs/gitjob.cpp | 53 ++++++++ plugins/Git/jobs/gitjob.h | 84 ++++++++++++ plugins/Git/libgit.cpp | 89 ------------ plugins/Git/libgit.h | 46 ------- plugins/Git/utils.h | 8 ++ plugins/Pass/gpg.cpp | 1 + plugins/Pass/gpg.h | 3 +- plugins/Pass/pass.cpp | 4 + plugins/Pass/pass.h | 15 +- plugins/Pass/passkeymodel.h | 3 +- plugins/Pass/passphraseprovider.h | 4 +- plugins/Utils/utils.cpp | 6 + plugins/Utils/utils.h | 53 ++++++++ po/utpass.qrouland.pot | 90 ++++++------ qml/pages/Info.qml | 3 +- qml/pages/settings/ImportGitClone.qml | 128 ------------------ qml/pages/settings/Settings.qml | 2 +- qml/pages/settings/git/GitCloneHttp.qml | 38 ++++++ qml/pages/settings/git/GitCloneHttpAuth.qml | 56 ++++++++ .../settings/git/GitModeOptionSelector.qml | 31 +++++ qml/pages/settings/git/ImportGitClone.qml | 91 +++++++++++++ 26 files changed, 805 insertions(+), 385 deletions(-) create mode 100644 plugins/Git/jobs/clonejob.cpp create mode 100644 plugins/Git/jobs/clonejob.h create mode 100644 plugins/Git/jobs/gitjob.cpp create mode 100644 plugins/Git/jobs/gitjob.h delete mode 100644 plugins/Git/libgit.cpp delete mode 100644 plugins/Git/libgit.h delete mode 100644 qml/pages/settings/ImportGitClone.qml create mode 100644 qml/pages/settings/git/GitCloneHttp.qml create mode 100644 qml/pages/settings/git/GitCloneHttpAuth.qml create mode 100644 qml/pages/settings/git/GitModeOptionSelector.qml create mode 100644 qml/pages/settings/git/ImportGitClone.qml diff --git a/plugins/Git/CMakeLists.txt b/plugins/Git/CMakeLists.txt index 9fd37cd..3d6d059 100644 --- a/plugins/Git/CMakeLists.txt +++ b/plugins/Git/CMakeLists.txt @@ -4,9 +4,10 @@ set(PLUGIN "Git") set( SRC plugin.cpp - libgit.cpp git.cpp utils.h + jobs/clonejob.cpp + jobs/gitjob.cpp ) set(CMAKE_AUTOMOC ON) diff --git a/plugins/Git/git.cpp b/plugins/Git/git.cpp index ee069cc..9a27be3 100644 --- a/plugins/Git/git.cpp +++ b/plugins/Git/git.cpp @@ -2,76 +2,68 @@ #include #include #include - +extern "C" { +#include +} #include "git.h" -#include "libgit.h" #include "utils.h" +#include "jobs/clonejob.h" +#include "jobs/gitjob.h" - -QDir Git::cloneSetup() +Git::Git(): + m_sem(std::unique_ptr(new QSemaphore(1))) { - QDir tmp_dir(QStandardPaths::writableLocation( QStandardPaths::CacheLocation).append("/clone")); + git_libgit2_init(); +} - tmp_dir.removeRecursively(); - qDebug() << "Temp dir path is " << tmp_dir.absolutePath(); - - return tmp_dir; +Git::~Git() +{ + git_libgit2_shutdown(); } -bool Git::cloneTearDown(QDir tmp_dir) +bool Git::clone(QString url, QString path, cred_type mode) { - return tmp_dir.removeRecursively(); -} - -bool Git::moveToDestination(QString path, QDir tmp_dir) -{ - qDebug() << "Removing password_store " << path; - QDir destination_dir(path); - destination_dir.removeRecursively(); - - qDebug() << "Moving cloned content to destination dir"; - QDir dir; - qDebug() << tmp_dir.absolutePath() << " to " << destination_dir.absolutePath(); - return dir.rename(tmp_dir.absolutePath(), destination_dir.absolutePath()); // TODO Better error handling -} - -bool Git::clone(QString url, QString path, mode_type mode) //, GitPlugin::RepoType type, QString pass) -{ - auto v = overload { - [](const Unset & x) { return "Unset"; }, - [](const HTTP & x) { return "HTTP"; }, - [](const HTTPAuth & x) { return "HTTPAuth"; }, - [](const SSHAuth & x) { return "SSHAuth"; }, - [](const SSHKey & x) { return "SSHKey"; }, - }; - qInfo() << "Cloning " << url << " to destination " << path << " using " << std::visit(v, mode); - - LibGit::instance()->setMode(mode); - auto tmp_dir = this->cloneSetup(); - - qDebug() << "Cloning " << url << " to tmp dir " << tmp_dir.absolutePath(); - auto ret = LibGit::instance()->clone(url, tmp_dir.absolutePath()); // TODO Better error handling - - if (ret) { - this->moveToDestination(path, tmp_dir); + if (!this->m_sem->tryAcquire(1, 500)) { + qWarning() << "Can acquire git semaphore a command is already running "; + return false; } - - this->cloneTearDown(tmp_dir); - LibGit::instance()->setMode(Unset()); - - return ret ; + auto v = overload { + [](const HTTP & x) { return "HTTP"; }, + [](const HTTPUserPass & x) { return "HTTPAuth"; }, + [](const SSHPass & x) { return "SSHAuth"; }, + [](const SSHKey & x) { return "SSHKey"; }, + }; + qDebug() << "Creating clone Job " << url << " " << path << " " << std::visit(v, mode); + CloneJob *clone_job = new CloneJob(url, path, mode); + connect(clone_job, &CloneJob::resultReady, this, &Git::cloneResult); + connect(clone_job, &CloneJob::finished, clone_job, &QObject::deleteLater); + clone_job->start(); + return true; } bool Git::cloneHttp(QString url, QString path) { + qInfo() << "Call clone command Http " << url << " " << path; HTTP mode = {}; return this->clone(url, path, mode); } bool Git::cloneHttpPass(QString url, QString path, QString pass) { - HTTPAuth mode = { pass }; + qInfo() << "Call clone command HttpPass " << url << " " << path; + HTTPUserPass mode = { pass }; return this->clone(url, path, mode); } + +void Git::cloneResult(const bool err) +{ + + if (err) { + emit cloneFailed(); // TODO error message + } else { + emit cloneSucceed(); + } + this->m_sem->release(); +} diff --git a/plugins/Git/git.h b/plugins/Git/git.h index 61f8370..d86b1d5 100644 --- a/plugins/Git/git.h +++ b/plugins/Git/git.h @@ -1,35 +1,112 @@ #ifndef GIT_H #define GIT_H +#include "jobs/gitjob.h" #include #include +#include #include -#include "libgit.h" - /** - * @brief The Git class is class that provide Git functionnly to clone and update a repo. + * @class Git + * @brief A class that provides Git functionality for cloning and updating repositories. + * + * The `Git` class provides a set of methods to do Git operation on remote URLs. */ class Git : public QObject { Q_OBJECT -private: - QDir cloneSetup(); - bool moveToDestination(QString path, QDir tmp_dir); - bool cloneTearDown(QDir tmp_dir); - bool clone(QString url, QString path, mode_type mode); +private slots: + /** + * @brief Slot that handles the result of a cloning operation. + * + * This slot is connected to the result of the cloning operation and is triggered when the cloning + * process finishes. It emits the appropriate signal based on whether the clone operation succeeded + * or failed. + * + * @param err A boolean indicating whether an error occurred during cloning. `true` if the clone failed, + * `false` if it succeeded. + */ + void cloneResult(const bool err); +signals: + /** + * @brief Signal emitted when the cloning operation succeeds. + * + * This signal is emitted when the Git repository is successfully cloned. + */ + void cloneSucceed(); + + /** + * @brief Signal emitted when the cloning operation fails. + * + * This signal is emitted when an error occurs during the cloning operation. + */ + void cloneFailed(); + +private: + std::unique_ptr m_sem; /**< Semaphore for managing concurrent operations. */ + + /** + * @brief Clones a repository from a specified URL. + * + * This private method initiates the cloning job. It sets up the repository cloning process based on + * the specified URL, destination path, and cloning mode (HTTP or SSH). + * + * @param url The URL of the Git repository to clone. + * @param path The destination path for the cloned repository. + * @param mode The cloning mode, such as HTTP or SSH (represented as `cred_type`). + * @return `true` if the cloning process was successful, `false` otherwise. + */ + bool clone(QString url, QString path, cred_type mode); public: - Git() = default; - ~Git() override = default; + /** + * @brief Constructor for the Git class. + * + * Initializes the `Git` class, setting up necessary resources such as the semaphore for concurrent operation management. + */ + Git(); + /** + * @brief Destructor for the Git class. + * + * Cleans up any resources used by the `Git` class, ensuring that no ongoing operations or resources are left hanging. + */ + ~Git() override; + + /** + * @brief Clones a repository over HTTP. + * + * This method clones a Git repository from the specified HTTP URL and saves it to the given destination path. + * It is a wrapper around the private `clone()` method, specifying the HTTP cloning mode. + * + * @param url The HTTP URL of the Git repository to clone. + * @param path The destination path for the cloned repository. + * @return `true` if the clone operation was successful, `false` otherwise. + */ Q_INVOKABLE bool cloneHttp(QString url, QString path); + + /** + * @brief Clones a repository over HTTP with a password for authentication. + * + * This method clones a Git repository from the specified HTTP URL using the provided password for authentication, + * and saves it to the given destination path. + * + * @param url The HTTP URL of the Git repository to clone. + * @param path The destination path for the cloned repository. + * @param pass The password used for HTTP authentication. + * @return `true` if the clone operation was successful, `false` otherwise. + */ Q_INVOKABLE bool cloneHttpPass(QString url, QString path, QString pass); + + // Future SSH support methods: // Q_INVOKABLE bool clone_ssh_pass(QString url, QString path, QString pass); // Q_INVOKABLE bool clone_ssh_key(QString url, QString path, QString pub_key, QString priv_key, QString passphrase); + // Q_INVOKABLE bool update(QUrl url, QString path); + // .... }; #endif diff --git a/plugins/Git/jobs/clonejob.cpp b/plugins/Git/jobs/clonejob.cpp new file mode 100644 index 0000000..f1b46b6 --- /dev/null +++ b/plugins/Git/jobs/clonejob.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +extern "C" { +#include +} + +#include "clonejob.h" + +CloneJob::CloneJob(QString url, QString path, cred_type cred): + GitJob(cred), + m_url(url), + m_path(path) +{ + this->setObjectName("CloneJob"); +} + + +void CloneJob::run() { + auto tmp_dir = this->cloneSetup(); + auto err = this->clone(this->m_url, tmp_dir.absolutePath(), this->m_cred, this->credentialsCB); + if (err) { + this->moveToDestination(tmp_dir, this->m_path); + } + this->cloneTearDown(tmp_dir); + + emit resultReady(err); // TODO Clean error handling to return specifics errors for the ui +} + + +QDir CloneJob::cloneSetup() +{ + QDir tmp_dir(QStandardPaths::writableLocation( QStandardPaths::CacheLocation).append("/clone")); + + tmp_dir.removeRecursively(); + qDebug() << "Temp dir path is " << tmp_dir.absolutePath(); + + return tmp_dir; +} + + +bool CloneJob::cloneTearDown(QDir tmp_dir) +{ + return tmp_dir.removeRecursively(); +} + +bool CloneJob::moveToDestination(QDir tmp_dir, QString path) +{ + qDebug() << "Removing password_store " << path; + QDir destination_dir(path); + destination_dir.removeRecursively(); + + qDebug() << "Moving cloned content to destination dir"; + QDir dir; + qDebug() << tmp_dir.absolutePath() << " to " << destination_dir.absolutePath(); + return dir.rename(tmp_dir.absolutePath(), destination_dir.absolutePath()); // TODO Better error handling +} + +bool CloneJob::clone(QString url, QString path, cred_type cred, git_cred_acquire_cb cb) +{ + git_repository *repo = NULL; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + + opts.fetch_opts.callbacks.credentials = cb; + opts.fetch_opts.callbacks.payload = &cred; + + int ret = git_clone(&repo, url.toLocal8Bit().data(), path.toLocal8Bit().data(), &opts); + if (ret != 0) { + qDebug() << git_error_last()->message; + } + if (repo) { + git_repository_free(repo); + }// TODO Better error handling + return ret != 0; +} + diff --git a/plugins/Git/jobs/clonejob.h b/plugins/Git/jobs/clonejob.h new file mode 100644 index 0000000..afc9ff9 --- /dev/null +++ b/plugins/Git/jobs/clonejob.h @@ -0,0 +1,111 @@ +#ifndef CLONEJOB_H +#define CLONEJOB_H + +#include +extern "C" { +#include +} +#include "gitjob.h" + +/** + * @class CloneJob + * @brief A class to handle cloning Git repositories in a separate thread. + * + * The CloneJob runs a cloning process in a separate thread and emits + * signals to indicate the success or failure of the operation. + */ +class CloneJob : public GitJob +{ + Q_OBJECT + + /** + * @brief The main function that performs the cloning operation. + * + * This function overrides the `run()` method from the `QThread` class. It is + * executed when the thread is started, and it handles the process of cloning + * a repository from the specified URL to the target path. + */ + void run() override; + +signals: + /** + * @brief Signal emitted when the cloning operation is complete. + * + * This signal is emitted once the cloning operation finishes. It notifies + * the caller whether an error occurred during the cloning process. + * + * @param err A boolean indicating whether an error occurred during cloning. + * `true` if an error occurred, `false` if the clone was successful. + */ + void resultReady(const bool err); + +private: + QString m_url; ///< The URL of the Git repository to clone. + QString m_path; ///< The destination path for the cloned repository. + + /** + * @brief Prepares the temporary directory for cloning. + * + * This method sets up the required directory structure for cloning a repository. + * It is called before the cloning process begins to ensure that a suitable + * location exists for the repository to be cloned into. + * + * @return A `QDir` object representing the prepared temporary directory. + */ + static QDir cloneSetup(); + + /** + * @brief Moves the cloned repository to the specified destination. + * + * After the repository has been cloned into a temporary directory, this method + * moves it to the final destination specified by the user. + * + * @param path The destination path to move the repository to. + * @param tmp_dir The temporary directory where the repository was cloned. + * @return `true` if the move was successful, `false` otherwise. + */ + static bool moveToDestination(QDir tmp_dir, QString path); + + /** + * @brief Tears down the temporary directory after cloning. + * + * This method is called to clean up the temporary directory after the repository + * has been cloned and moved to the final destination. It removes the temporary + * directory and all its contents. + * + * @param tmp_dir The temporary directory to tear down. + * @return `true` if the teardown was successful, `false` otherwise. + */ + static bool cloneTearDown(QDir tmp_dir); + + /** + * @brief Clones a repository from a specified URL. + * + * This method handles the actual cloning process by setting up a temporary + * directory, cloning the repository into it, and then moving it to the final + * destination. It uses the provided credentials to authenticate the cloning + * operation. + * + * @param url The URL of the Git repository to clone. + * @param path The destination path for the cloned repository. + * @param cred The credentials to use for the cloning operation. + * @param cb The callback function for acquiring credentials during cloning. + * @return `true` if the cloning process was successful, `false` otherwise. + */ + static bool clone(QString url, QString path, cred_type cred, git_cred_acquire_cb cb); + +public: + /** + * @brief Constructor for the CloneJob class. + * + * Initializes the CloneJob with the specified repository URL, destination path, + * and credentials. + * + * @param url The URL of the Git repository to clone. + * @param path The destination path where the repository will be cloned. + * @param cred The credentials to be used for the cloning process. + */ + CloneJob(QString url, QString path, cred_type cred); +}; + +#endif // CLONEJOB_H diff --git a/plugins/Git/jobs/gitjob.cpp b/plugins/Git/jobs/gitjob.cpp new file mode 100644 index 0000000..7b6bd4b --- /dev/null +++ b/plugins/Git/jobs/gitjob.cpp @@ -0,0 +1,53 @@ +#include + +#include "gitjob.h" +#include "../utils.h" + +extern "C" { +#include +} + +GitJob::GitJob(cred_type cred) : + m_cred(cred) +{ + git_libgit2_init(); +} + +GitJob::~GitJob() +{ + git_libgit2_shutdown(); +} + +int GitJob::credentialsCB(git_cred **out, const char *url, const char *username_from_url, + unsigned int allowed_types, void *payload) +{ + cred_type *cred = (cred_type *)payload; + auto v = overload { + [](const HTTP & x) + { + qDebug() << "credentialsCB : HTTP "; + qWarning() << "credentialsCB : callback should never be call for HTTP "; + return (int) GIT_EUSER; + }, + [&out, &username_from_url](const HTTPUserPass & x) + { + qDebug() << "credentialsCB : HTTPUserPass "; + if (!username_from_url) { + qWarning() << "credentials_cb : no username provided "; + return (int) GIT_EUSER; + } + return git_cred_userpass_plaintext_new(out, username_from_url, x.pass.toLocal8Bit().constData()); + }, + [](const SSHPass & x) + { + qWarning() << "credentials_cb : SSHAuth to be implemented "; + return (int) GIT_EUSER; + }, // TODO + [](const SSHKey & x) + { + qWarning() << "credentials_cb : SSHKey to be implemented "; + return (int) GIT_EUSER; + } // TODO + }; + return std::visit(v, *cred); +} diff --git a/plugins/Git/jobs/gitjob.h b/plugins/Git/jobs/gitjob.h new file mode 100644 index 0000000..c33a407 --- /dev/null +++ b/plugins/Git/jobs/gitjob.h @@ -0,0 +1,84 @@ +#ifndef GITJOB_H +#define GITJOB_H + +#include "qthread.h" +extern "C" { +#include +} +#include + +// Forward declarations for the different credential types. +struct HTTP { }; +struct HTTPUserPass { + QString pass; ///< Password for HTTP user authentication. +}; +struct SSHPass { }; +struct SSHKey { }; + +/** + * @brief Variant type to represent various types of credentials. + * + * This type is used to store one of the following credential types: + * - HTTP + * - HTTPUserPass + * - SSHPass + * - SSHKey + */ +typedef std::variant cred_type; + +/** + * @class GitJob + * @brief A class that manages Git-related tasks using libgit2. + * + * The GitJob class is used to perform Git operations, such as cloning repositories, + * in a separate thread. It utilizes libgit2 for interacting with Git repositories. + * The class handles credentials for repository access and allows the user to specify + * the type of credentials to use. + */ +class GitJob : public QThread +{ + Q_OBJECT + +protected: + cred_type m_cred; ///< The credentials used for Git operations. + + /** + * @brief Callback function for handling Git credentials during cloning. + * + * This function is called by libgit2 to handle credentials for cloning a repository. + * The callback is invoked when Git needs to authenticate with a remote repository. + * + * @param out Pointer to the credentials object that will be populated by the callback. + * @param url The URL of the repository being cloned. + * @param username_from_url The username extracted from the URL (if applicable). + * @param allowed_types A bitmask of the allowed types of credentials that can be used. + * @param payload User-defined data passed to the callback. This is typically the instance + * of the class providing the callback, or other user-defined data. + * + * @return A status code indicating success (0) or failure (non-zero). + * @see git_cred + * @see git_cred_type + */ + static int credentialsCB(git_cred **out, const char *url, const char *username_from_url, + unsigned int allowed_types, void *payload); + +public: + /** + * @brief Constructor for the GitJob class. + * + * Initializes the GitJob instance with the given credentials. + * + * @param cred The credentials to be used for the Git operation. This can be one of + * the following types: HTTP, HTTPUserPass, SSHPass, or SSHKey. + */ + GitJob(cred_type cred); + + /** + * @brief Destructor for the GitJob class. + * + * Cleans up any resources used by the GitJob. + */ + ~GitJob(); +}; + +#endif // GITJOB_H diff --git a/plugins/Git/libgit.cpp b/plugins/Git/libgit.cpp deleted file mode 100644 index 2af3bc0..0000000 --- a/plugins/Git/libgit.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include -#include - -#include -extern "C" { -#include -} - -#include "libgit.h" -#include "utils.h" - - - -LibGit::LibGit() -{ - git_libgit2_init(); -} - -LibGit::~LibGit() -{ - git_libgit2_shutdown(); -} - -void LibGit::setMode(mode_type type) -{ - this->mode = type; -} - -int LibGit::credentialsCB(git_cred **out, const char *url, const char *username_from_url, - unsigned int allowed_types, void *payload) -{ - // TODO : More precise Error Handling for UI - auto instance = LibGit::instance(); - auto v = overload { - [](const Unset & x) - { - qDebug() << "credentials_cb : Unset "; - qWarning() << "credentials_cb : callback should never be call for Unset "; - return (int) GIT_EUSER; - }, - [](const HTTP & x) - { - qDebug() << "credentials_cb : HTTP "; - qWarning() << "credentials_cb : callback should never be call for HTTP "; - return (int) GIT_EUSER; - }, - [&out, &username_from_url](const HTTPAuth & x) - { - qDebug() << "credentials_cb : HTTPAuth "; - if (!username_from_url) { - qWarning() << "credentials_cb : no username provided "; - return (int) GIT_EUSER; - } - return git_cred_userpass_plaintext_new(out, username_from_url, x.pass.toLocal8Bit().constData()); - }, - [&](const SSHAuth & x) - { - qWarning() << "credentials_cb : SSHAuth to be implemented "; - return (int) GIT_EUSER; - }, // TODO - [&](const SSHKey & x) - { - qWarning() << "credentials_cb : SSHKey to be implemented "; - return (int) GIT_EUSER; - } // TODO - }; - return std::visit(v, instance->mode); -} - - -bool LibGit::clone(QString url, QString path) -{ - git_repository *repo = NULL; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - - opts.fetch_opts.callbacks.credentials = *credentialsCB; - - int ret = git_clone(&repo, url.toLocal8Bit().data(), path.toLocal8Bit().data(), &opts); - if (ret != 0) { - qDebug() << git_error_last()->message; - } - if (repo) { - git_repository_free(repo); - } - return ret == 0; // TODO Clean error handling to return specifics errors for the ui - - return ret; -} - diff --git a/plugins/Git/libgit.h b/plugins/Git/libgit.h deleted file mode 100644 index d004ce0..0000000 --- a/plugins/Git/libgit.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef LIBGIT_H -#define LIBGIT_H - -#include -#include -#include -extern "C" { -#include -} -#include -#include - -struct Unset { }; -struct HTTP { }; -struct HTTPAuth { - QString pass; -}; -struct SSHAuth { }; -struct SSHKey { }; -typedef std::variant mode_type; - -class LibGit -{ -private: - LibGit(); - - mode_type mode; - - static int credentialsCB(git_cred **out, const char *url, const char *username_from_url, - unsigned int allowed_types, void *payload); - -public: - ~LibGit(); - static std::shared_ptr instance() - { - static std::shared_ptr s{new LibGit}; - return s; - } - LibGit(LibGit const &) = delete; - void operator=(LibGit const &) = delete; - - bool clone(QString url, QString path); - void setMode(mode_type type); -}; - -#endif diff --git a/plugins/Git/utils.h b/plugins/Git/utils.h index 29a3365..4e7f9cb 100644 --- a/plugins/Git/utils.h +++ b/plugins/Git/utils.h @@ -1,10 +1,18 @@ #ifndef UTILS_H #define UTILS_H +/** + * @brief A utility structure for enabling function overloading with template-based classes. + * see : https://stackoverflow.com/a/64018031 + */ template struct overload : Ts... { using Ts::operator()...; }; +/** + * @brief Deduction guide for the `overload` template. + * see : https://stackoverflow.com/a/64018031 +*/ template overload(Ts...) -> overload; diff --git a/plugins/Pass/gpg.cpp b/plugins/Pass/gpg.cpp index c68c537..e721e0d 100644 --- a/plugins/Pass/gpg.cpp +++ b/plugins/Pass/gpg.cpp @@ -170,6 +170,7 @@ void Gpg::decryptResultSlot(const GpgME::DecryptionResult &result, const QByteAr qDebug() << "Code Error : " << result.error().code(); qDebug() << "Error str : " << result.error().asString(); } + qDebug() << "Cancelled : " << result.error().isCanceled(); emit decryptResult(result.error(), QString::fromUtf8(plainText)); } diff --git a/plugins/Pass/gpg.h b/plugins/Pass/gpg.h index b7eb32f..83b9f73 100644 --- a/plugins/Pass/gpg.h +++ b/plugins/Pass/gpg.h @@ -28,8 +28,7 @@ using namespace QGpgME; * * This class integrates with the GPGME (GnuPG Made Easy) library to provide functionalities * for interacting with GPG keys, including decrypting messages, importing keys from files, - * listing keys, and deleting keys. The class also provides slots for handling results - * of asynchronous GPG operations and communicates with the user via signals. + * listing keys, and deleting keys. */ class Gpg: public QObject { diff --git a/plugins/Pass/pass.cpp b/plugins/Pass/pass.cpp index 0897020..29b5968 100644 --- a/plugins/Pass/pass.cpp +++ b/plugins/Pass/pass.cpp @@ -55,6 +55,10 @@ void Pass::showResult(Error err, QString plain_text) if (err) { qInfo() << "Decrypt Failed"; emit showFailed(err.asString()); + + } else if (err.isCanceled()){ + qInfo() << "Decrypt Cancelled"; + emit showCancelled(); } else { qInfo() << "Decrypt OK"; emit showSucceed(this->m_show_filename, plain_text); diff --git a/plugins/Pass/pass.h b/plugins/Pass/pass.h index fedf368..0aecc68 100644 --- a/plugins/Pass/pass.h +++ b/plugins/Pass/pass.h @@ -16,9 +16,6 @@ using namespace GpgME; * * This class provides functionalities for interacting with password storage, including * storing, showing, importing, and deleting passwords securely using GPG encryption. - * It communicates asynchronously with GPG operations, using signals and slots to propagate results. - * The class interacts with the `Gpg` class to perform GPG key operations and provides an interface - * for the user to manage the passwords. */ class Pass : public QObject { @@ -82,6 +79,8 @@ signals: */ void getAllGPGKeysSucceed(QVariant keys_info); + + /** * @brief Emitted when retrieving GPG keys fails. * @param message The error message describing the failure. @@ -109,6 +108,11 @@ signals: */ void showFailed(QString message); + /** + * @brief Emitted hen showing a password cancelled. + */ + void showCancelled(); + private: QString m_password_store; /**< The path to the password store. */ std::unique_ptr m_gpg; /**< The GPG instance used for encryption/decryption. */ @@ -121,11 +125,6 @@ public: */ Pass(); - /** - * @brief Destructor for cleaning up resources used by the Pass object. - */ - ~Pass() override = default; - /** * @brief Gets the path to the password store. * @return The path to the password store. diff --git a/plugins/Pass/passkeymodel.h b/plugins/Pass/passkeymodel.h index 57c13b7..a1a7b1f 100644 --- a/plugins/Pass/passkeymodel.h +++ b/plugins/Pass/passkeymodel.h @@ -11,8 +11,7 @@ using namespace GpgME; * @brief A model representing a user ID associated with a GPG key. * * This class encapsulates the user ID information (UID) for a GPG key, providing access - * to the UID's identifier, name, and email. It is used as a model for user IDs in the - * `PassKeyModel` class. + * to the UID's identifier, name, and email. */ class UserIdModel : public QObject { diff --git a/plugins/Pass/passphraseprovider.h b/plugins/Pass/passphraseprovider.h index 4b00b35..c63b93a 100644 --- a/plugins/Pass/passphraseprovider.h +++ b/plugins/Pass/passphraseprovider.h @@ -15,9 +15,7 @@ * @brief A passphrase provider for GPG operations that interacts with a QML dialog. * * This class implements the `PassphraseProvider` interface from GPGME and is responsible for - * obtaining passphrases for GPG operations. It does so by triggering a QML dialog to prompt the user - * for their passphrase. The response is then handled asynchronously, and the passphrase is returned to - * the calling GPGME function. + * obtaining passphrases for GPG operations. */ class UTPassphraseProvider : public QObject, public PassphraseProvider { diff --git a/plugins/Utils/utils.cpp b/plugins/Utils/utils.cpp index b3c9493..c92bef5 100644 --- a/plugins/Utils/utils.cpp +++ b/plugins/Utils/utils.cpp @@ -58,3 +58,9 @@ bool Utils::rmDir(QUrl dir_url) QDir dir(dir_url.toLocalFile()); return dir.removeRecursively(); } + +QString Utils::manifestPath(){ + auto path = QDir(QDir::currentPath()).filePath("manifest_.json"); + qDebug() << "Manifest path : " << path; + return path; +} diff --git a/plugins/Utils/utils.h b/plugins/Utils/utils.h index bebbe03..9fb781d 100644 --- a/plugins/Utils/utils.h +++ b/plugins/Utils/utils.h @@ -5,17 +5,70 @@ #include #include +/** + * @class Utils + * @brief A utility class that provides helper functions for file and directory operations. + * + * The `Utils` class contains various helper methods such as for managing files and directories, including unzipping files + * and removing files or directories. + */ class Utils : public QObject { Q_OBJECT public: + /** + * @brief Default constructor for the Utils class. + */ Utils() = default; + + /** + * @brief Default destructor for the Utils class. + */ ~Utils() override = default; + /** + * @brief Unzips a ZIP file to the specified output directory. + * + * This method extracts the contents of a ZIP file from the specified URL and saves them to the provided + * output directory path. + * + * @param zip_url The URL of the ZIP file to unzip. + * @param dir_out The output directory where the contents of the ZIP file should be extracted. + * @return `true` if the unzipping operation was successful, `false` otherwise. + */ Q_INVOKABLE bool unzip(QUrl zip_url, QString dir_out); + + /** + * @brief Removes a file at the specified URL. + * + * This method deletes a file at the specified URL. + * + * @param file_url The URL of the file to delete. + * @return `true` if the file was successfully removed, `false` otherwise. + */ Q_INVOKABLE bool rmFile(QUrl file_url); + + /** + * @brief Removes a directory at the specified URL. + * + * This method deletes a directory at the specified URL, along with its contents. + * + * @param dir_url The URL of the directory to remove. + * @return `true` if the directory was successfully removed, `false` otherwise. + */ Q_INVOKABLE bool rmDir(QUrl dir_url); + + + /** + * @brief Retrieves the path to the manifest data. + * + * This function returns the full path to the manifest file used by the application. + * + * @return A QString containing the manifest file path. + */ + Q_INVOKABLE QString manifestPath(); + }; #endif diff --git a/po/utpass.qrouland.pot b/po/utpass.qrouland.pot index 2bb9c95..24e0421 100644 --- a/po/utpass.qrouland.pot +++ b/po/utpass.qrouland.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: utpass.qrouland\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-15 23:39+0100\n" +"POT-Creation-Date: 2025-01-17 10:40+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -55,27 +55,27 @@ msgstr "" msgid "OK" msgstr "" -#: ../qml/pages/Info.qml:61 +#: ../qml/pages/Info.qml:62 msgid "Version" msgstr "" -#: ../qml/pages/Info.qml:82 +#: ../qml/pages/Info.qml:83 msgid "Maintainer" msgstr "" -#: ../qml/pages/Info.qml:109 +#: ../qml/pages/Info.qml:110 msgid "Suggest improvement(s) or report a bug(s)" msgstr "" -#: ../qml/pages/Info.qml:114 +#: ../qml/pages/Info.qml:115 msgid "Access to the source code" msgstr "" -#: ../qml/pages/Info.qml:122 +#: ../qml/pages/Info.qml:123 msgid "Released under the terms of the GNU GPL v3" msgstr "" -#: ../qml/pages/Info.qml:131 ../qml/pages/headers/MainHeader.qml:33 +#: ../qml/pages/Info.qml:132 ../qml/pages/headers/MainHeader.qml:33 msgid "Info" msgstr "" @@ -106,43 +106,6 @@ msgstr "" msgid "Search" msgstr "" -#: ../qml/pages/settings/ImportGitClone.qml:36 -msgid "Repo Url" -msgstr "" - -#: ../qml/pages/settings/ImportGitClone.qml:53 -msgid "Password" -msgstr "" - -#: ../qml/pages/settings/ImportGitClone.qml:70 -msgid "Clone" -msgstr "" - -#: ../qml/pages/settings/ImportGitClone.qml:90 -msgid "" -"Importing a git repo will delete
any existing password store!" -"
Continue ?" -msgstr "" - -#: ../qml/pages/settings/ImportGitClone.qml:91 -#: ../qml/pages/settings/ImportZip.qml:62 -#: ../qml/pages/settings/InfoKeys.qml:131 -msgid "Yes" -msgstr "" - -#: ../qml/pages/settings/ImportGitClone.qml:104 -msgid "An error occured during git clone !" -msgstr "" - -#: ../qml/pages/settings/ImportGitClone.qml:113 -#: ../qml/pages/settings/ImportZip.qml:84 -msgid "Password store sucessfully imported !" -msgstr "" - -#: ../qml/pages/settings/ImportGitClone.qml:125 -msgid "Git Clone Import" -msgstr "" - #: ../qml/pages/settings/ImportKeyFile.qml:57 msgid "Key import failed !" msgstr "" @@ -160,10 +123,21 @@ msgid "" "Importing a new zip will delete
any existing password store!
Continue ?" msgstr "" +#: ../qml/pages/settings/ImportZip.qml:62 +#: ../qml/pages/settings/InfoKeys.qml:131 +#: ../qml/pages/settings/git/ImportGitClone.qml:54 +msgid "Yes" +msgstr "" + #: ../qml/pages/settings/ImportZip.qml:75 msgid "Password store import failed !" msgstr "" +#: ../qml/pages/settings/ImportZip.qml:84 +#: ../qml/pages/settings/git/ImportGitClone.qml:76 +msgid "Password store sucessfully imported !" +msgstr "" + #: ../qml/pages/settings/ImportZip.qml:96 msgid "Zip Password Store Import" msgstr "" @@ -223,3 +197,31 @@ msgstr "" #: ../qml/pages/settings/Settings.qml:62 msgid "Warning: importing delete any exiting Password Store" msgstr "" + +#: ../qml/pages/settings/git/GitCloneHttp.qml:16 +#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:16 +msgid "Repo Url" +msgstr "" + +#: ../qml/pages/settings/git/GitCloneHttp.qml:32 +#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:50 +msgid "Clone" +msgstr "" + +#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:33 +msgid "Password" +msgstr "" + +#: ../qml/pages/settings/git/ImportGitClone.qml:53 +msgid "" +"Importing a git repo will delete
any existing password store!" +"
Continue ?" +msgstr "" + +#: ../qml/pages/settings/git/ImportGitClone.qml:67 +msgid "An error occured during git clone !" +msgstr "" + +#: ../qml/pages/settings/git/ImportGitClone.qml:88 +msgid "Git Clone Import" +msgstr "" diff --git a/qml/pages/Info.qml b/qml/pages/Info.qml index 35dd756..6b8c5db 100644 --- a/qml/pages/Info.qml +++ b/qml/pages/Info.qml @@ -2,13 +2,14 @@ import "../components" import Lomiri.Components 1.3 import QtQuick 2.4 import "headers" +import Utils 1.0 Page { id: infoPage Component.onCompleted: { var xhr = new XMLHttpRequest(); - xhr.open("GET", "../../manifest_.json", false); + xhr.open("GET", Utils.manifestPath(), false); xhr.send(); var mJson = JSON.parse(xhr.responseText); manifestTitle.text = "" + mJson.title + ""; diff --git a/qml/pages/settings/ImportGitClone.qml b/qml/pages/settings/ImportGitClone.qml deleted file mode 100644 index 9194b11..0000000 --- a/qml/pages/settings/ImportGitClone.qml +++ /dev/null @@ -1,128 +0,0 @@ -import "../../components" -import "../../dialogs" -import "../headers" -import Git 1.0 -import Lomiri.Components 1.3 -import Lomiri.Components.Popups 1.3 -import Pass 1.0 -import QtQuick 2.4 - -Page { - id: importGitClonePage - - Component.onCompleted: { - PopupUtils.open(importGitCloneValidation, importGitClonePage); - } - - Flow { - anchors.top: importGitCloneHeader.bottom - anchors.right: parent.right - anchors.left: parent.left - anchors.leftMargin: units.gu(2) - anchors.rightMargin: units.gu(2) - spacing: units.gu(1) - - Rectangle { - width: parent.width - height: units.gu(1) - } - - Text { - id: repoUrlLabel - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: parent.width - text: i18n.tr('Repo Url') - } - - TextField { - id: repoUrlInput - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: parent.width - } - - Text { - id: repoPasswordLabel - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: parent.width - text: i18n.tr('Password') - } - - TextField { - id: repoPasswordInput - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: parent.width - echoMode: TextInput.Password - } - - Button { - id: buttonAdd - - width: parent.width - color: theme.palette.normal.positive - text: i18n.tr('Clone') - onClicked: { - var ret = false; - if (repoPasswordInput.text === "") - ret = Git.clone_http(repoUrlInput.text, Pass.password_store); - else - ret = Git.clone_http_pass(repoUrlInput.text, Pass.password_store, repoPasswordInput.text); - if (ret) - PopupUtils.open(dialogImportGitCloneSuccess); - else - PopupUtils.open(importGitCloneError, importGitClonePage); - } - } - - } - - Component { - id: importGitCloneValidation - - SimpleValidationDialog { - text: i18n.tr("Importing a git repo will delete
any existing password store!
Continue ?") - continueText: i18n.tr("Yes") - continueColor: theme.palette.normal.negative - onCanceled: { - pageStack.pop(); - } - } - - } - - Component { - id: importGitCloneError - - ErrorDialog { - textError: i18n.tr("An error occured during git clone !") - } - - } - - Component { - id: dialogImportGitCloneSuccess - - SuccessDialog { - textSuccess: i18n.tr("Password store sucessfully imported !") - onDialogClosed: { - pageStack.pop(); - pageStack.pop(); - } - } - - } - - header: StackHeader { - id: importGitCloneHeader - - title: i18n.tr('Git Clone Import') - } - -} diff --git a/qml/pages/settings/Settings.qml b/qml/pages/settings/Settings.qml index 5cb62fa..d479f69 100644 --- a/qml/pages/settings/Settings.qml +++ b/qml/pages/settings/Settings.qml @@ -44,7 +44,7 @@ Page { } PageStackLink { - page: Qt.resolvedUrl("ImportGitClone.qml") + page: Qt.resolvedUrl("git/ImportGitClone.qml") text: i18n.tr('Import a Password Store using Git') } diff --git a/qml/pages/settings/git/GitCloneHttp.qml b/qml/pages/settings/git/GitCloneHttp.qml new file mode 100644 index 0000000..e72841d --- /dev/null +++ b/qml/pages/settings/git/GitCloneHttp.qml @@ -0,0 +1,38 @@ +import QtQuick 2.4 +import Git 1.0 +import Lomiri.Components 1.3 +import Pass 1.0 + +Column { + width: parent.width + spacing: units.gu(1) + + Text { + id: repoUrlLabel + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width + text: i18n.tr('Repo Url') + } + + TextField { + id: repoUrlInput + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width + } + + Button { + id: buttonClone + + width: parent.width + color: theme.palette.normal.positive + text: i18n.tr('Clone') + onClicked: { + Git.cloneHttp(repoUrlInput.text, Pass.password_store); + } + } + +} diff --git a/qml/pages/settings/git/GitCloneHttpAuth.qml b/qml/pages/settings/git/GitCloneHttpAuth.qml new file mode 100644 index 0000000..3b0fd93 --- /dev/null +++ b/qml/pages/settings/git/GitCloneHttpAuth.qml @@ -0,0 +1,56 @@ +import QtQuick 2.4 +import Git 1.0 +import Lomiri.Components 1.3 +import Pass 1.0 + +Column { + anchors.top: parent.fill + spacing: units.gu(1) + + Text { + id: repoUrlLabel + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width + text: i18n.tr('Repo Url') + } + + TextField { + id: repoUrlInput + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width + } + + Text { + id: repoPasswordLabel + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width + text: i18n.tr('Password') + } + + TextField { + id: repoPasswordInput + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width + echoMode: TextInput.Password + } + + Button { + id: buttonClone + + width: parent.width + color: theme.palette.normal.positive + text: i18n.tr('Clone') + onClicked: { + Git.cloneHttpPass(repoUrlInput.text, Pass.password_store, repoPasswordInput.text); + } + } + +} diff --git a/qml/pages/settings/git/GitModeOptionSelector.qml b/qml/pages/settings/git/GitModeOptionSelector.qml new file mode 100644 index 0000000..f0aa3b3 --- /dev/null +++ b/qml/pages/settings/git/GitModeOptionSelector.qml @@ -0,0 +1,31 @@ +import QtQuick 2.4 +import Git 1.0 +import Lomiri.Components 1.3 +import Lomiri.Components.Pickers 1.3 + +OptionSelector { + id: combo + width : parent.width + model: ["HTTP", "HTTP AUTH"] + onDelegateClicked: function(i) { + if(i===0) { + timer.setTimeout(function(){importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttp.qml") }, 500); + } else if (i===1) { + timer.setTimeout( function(){importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttpAuth.qml") }, 500); + } + } + + Timer { + id: timer + function setTimeout(cb, delayTime) { + timer.interval = delayTime; + timer.repeat = false; + timer.triggered.connect(cb); + timer.triggered.connect(function release () { + timer.triggered.disconnect(cb); // This is important + timer.triggered.disconnect(release); // This is important as well + }); + timer.start(); + } + } +} diff --git a/qml/pages/settings/git/ImportGitClone.qml b/qml/pages/settings/git/ImportGitClone.qml new file mode 100644 index 0000000..198a0f5 --- /dev/null +++ b/qml/pages/settings/git/ImportGitClone.qml @@ -0,0 +1,91 @@ +import "../../../components" +import "../../../dialogs" +import "../../headers" +import Git 1.0 +import Lomiri.Components 1.3 +import Lomiri.Components.Popups 1.3 +import Pass 1.0 +import QtQuick 2.4 + +Page { + id: importGitClonePage + + Component.onCompleted: { + Git.onCloneSucceed.connect(function() { + PopupUtils.open(dialogGitCloneSuccess); + }); + Git.onCloneFailed.connect(function() { + PopupUtils.open(dialogGitCloneError); + }); + PopupUtils.open(importGitCloneValidation, importGitClonePage); + importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttp.qml") + } + + Column { + anchors.top: importGitCloneHeader.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.leftMargin: units.gu(2) + anchors.rightMargin: units.gu(2) + spacing: units.gu(1) + + Rectangle { + width: parent.width + height: units.gu(1) + } + + GitModeOptionSelector { + id: combo + } + + Loader { + id: importGitCloneForm + + width: parent.width + } + } + + Component { + id: importGitCloneValidation + + SimpleValidationDialog { + text: i18n.tr("Importing a git repo will delete
any existing password store!
Continue ?") + continueText: i18n.tr("Yes") + continueColor: theme.palette.normal.negative + onCanceled: { + pageStack.pop(); + } + } + + } + + Component { + id: dialogGitCloneError + + ErrorDialog { + textError: i18n.tr("An error occured during git clone !") + } + + } + + Component { + id: dialogGitCloneSuccess + + SuccessDialog { + textSuccess: i18n.tr("Password store sucessfully imported !") + onDialogClosed: { + pageStack.pop(); + pageStack.pop(); + } + } + + } + + header: StackHeader { + id: importGitCloneHeader + + title: i18n.tr('Git Clone Import') + } + +}