From c395e5c1c8df3307cdf5da6ca42d12e69d32bed7 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 30 Jun 2024 22:58:18 -0400 Subject: [PATCH 01/12] Add copyDirectory() to Io --- lib/io/include/qx/io/qx-common-io.h | 9 +++--- lib/io/src/qx-common-io.cpp | 50 ++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/lib/io/include/qx/io/qx-common-io.h b/lib/io/include/qx/io/qx-common-io.h index af0fbeb3..e13ea717 100644 --- a/lib/io/include/qx/io/qx-common-io.h +++ b/lib/io/include/qx/io/qx-common-io.h @@ -71,12 +71,13 @@ QX_IO_EXPORT IoOpReport writeStringToFile(QSaveFile& textFile, const QString& te QX_IO_EXPORT IoOpReport deleteTextFromFile(QFile& textFile, TextPos startPos, TextPos endPos); // Directory: -QX_IO_EXPORT bool dirContainsFiles(QDir directory, QDirIterator::IteratorFlags iteratorFlags); -QX_IO_EXPORT IoOpReport dirContainsFiles(bool& returnBuffer, QDir directory, QDirIterator::IteratorFlags iteratorFlags); -QX_IO_EXPORT IoOpReport dirContentInfoList(QFileInfoList& returnBuffer, QDir directory, QStringList nameFilters = QStringList(), +QX_IO_EXPORT bool dirContainsFiles(const QDir& directory, QDirIterator::IteratorFlags iteratorFlags); +QX_IO_EXPORT IoOpReport dirContainsFiles(bool& returnBuffer, const QDir& directory, QDirIterator::IteratorFlags iteratorFlags); +QX_IO_EXPORT IoOpReport dirContentInfoList(QFileInfoList& returnBuffer, const QDir& directory, QStringList nameFilters = QStringList(), QDir::Filters filters = QDir::NoFilter, QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags); -QX_IO_EXPORT IoOpReport dirContentList(QStringList& returnBuffer, QDir directory, QStringList nameFilters = QStringList(), +QX_IO_EXPORT IoOpReport dirContentList(QStringList& returnBuffer, const QDir& directory, QStringList nameFilters = QStringList(), QDir::Filters filters = QDir::NoFilter, QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags, PathType pathType = Absolute); +QX_IO_EXPORT IoOpReport copyDirectory(const QDir& directory, const QDir& destination, bool recursive = true, bool overwrite = false); // Integrity QX_IO_EXPORT IoOpReport calculateFileChecksum(QString& returnBuffer, QFile& file, QCryptographicHash::Algorithm hashAlgorithm); diff --git a/lib/io/src/qx-common-io.cpp b/lib/io/src/qx-common-io.cpp index 9b1fdf87..3c60d9ee 100644 --- a/lib/io/src/qx-common-io.cpp +++ b/lib/io/src/qx-common-io.cpp @@ -1270,7 +1270,7 @@ IoOpReport deleteTextFromFile(QFile& textFile, TextPos startPos, TextPos endPos) * * @warning This also returns false if the directory doesn't exist. */ -bool dirContainsFiles(QDir directory, QDirIterator::IteratorFlags iteratorFlags) +bool dirContainsFiles(const QDir& directory, QDirIterator::IteratorFlags iteratorFlags) { // Construct directory iterator QDirIterator listIterator(directory.path(), QDir::Files | QDir::NoDotAndDotDot, iteratorFlags); @@ -1285,7 +1285,7 @@ bool dirContainsFiles(QDir directory, QDirIterator::IteratorFlags iteratorFlags) * If the directory doesn't exist, @a returnBuffer will be set to false and an operation report noting the directory's absence * is returned. */ -IoOpReport dirContainsFiles(bool& returnBuffer, QDir directory, QDirIterator::IteratorFlags iteratorFlags) +IoOpReport dirContainsFiles(bool& returnBuffer, const QDir& directory, QDirIterator::IteratorFlags iteratorFlags) { // Assume false returnBuffer = false; @@ -1314,7 +1314,7 @@ IoOpReport dirContainsFiles(bool& returnBuffer, QDir directory, QDirIterator::It * * @sa QDir::entryInfoList */ -IoOpReport dirContentInfoList(QFileInfoList& returnBuffer, QDir directory, QStringList nameFilters, +IoOpReport dirContentInfoList(QFileInfoList& returnBuffer, const QDir& directory, QStringList nameFilters, QDir::Filters filters, QDirIterator::IteratorFlags flags) { // Empty buffer @@ -1358,7 +1358,7 @@ IoOpReport dirContentInfoList(QFileInfoList& returnBuffer, QDir directory, QStri * * @sa QDir::entryList */ -IoOpReport dirContentList(QStringList& returnBuffer, QDir directory, QStringList nameFilters, +IoOpReport dirContentList(QStringList& returnBuffer, const QDir& directory, QStringList nameFilters, QDir::Filters filters, QDirIterator::IteratorFlags flags, PathType pathType) { // Empty buffer @@ -1389,6 +1389,48 @@ IoOpReport dirContentList(QStringList& returnBuffer, QDir directory, QStringList return IoOpReport(IO_OP_ENUMERATE, IO_SUCCESS, directory); } +/*! + * Copies @a directory to @a destination, recursively if @a recursive is @c true. Existing files are overwritten if + * @a overwrite is @c true. + */ +IoOpReport copyDirectory(const QDir& directory, const QDir& destination, bool recursive, bool overwrite) +{ + // Ensure destination exists + if(!destination.mkpath(u"."_s)) + return IoOpReport(IO_OP_WRITE, IO_ERR_CANT_CREATE, destination); + + QDirIterator srcItr(directory.path(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs, recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags); + while(srcItr.hasNext()) + { + srcItr.next(); + QFileInfo fi = srcItr.fileInfo(); + + QString subPath = fi.absoluteFilePath().mid(directory.absolutePath().length() + 1); // Drop last '/' + QString absDestPath = destination.absoluteFilePath(subPath); + + if(fi.isDir()) + { + if(!destination.mkpath(subPath)) + return IoOpReport(IO_OP_WRITE, IO_ERR_CANT_CREATE, QDir(absDestPath)); + } + else if(fi.isFile()) + { + if(QFile::exists(absDestPath)) + { + if(!overwrite) + return IoOpReport(IO_OP_WRITE, IO_ERR_EXISTS, QFile(absDestPath)); + else if(!QFile::remove(absDestPath)) + return IoOpReport(IO_OP_WRITE, IO_ERR_REMOVE, QFile(absDestPath)); + } + + if(!QFile::copy(fi.absoluteFilePath(), absDestPath)) + return IoOpReport(IO_OP_WRITE, IO_ERR_COPY, QFile(absDestPath)); + } + } + + return IoOpReport(IO_OP_WRITE, IO_SUCCESS, destination); +} + /*! * Computes a file's checksum. * From 7a9e25920e039abe1056928c469ae4200bb0f503 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Mon, 1 Jul 2024 01:56:57 -0400 Subject: [PATCH 02/12] Add option for DownloadManagers to auto-delete incomplete files --- .../include/qx/network/qx-downloadmanager.h | 6 +++ lib/network/src/qx-downloadmanager.cpp | 53 +++++++++++++++---- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/lib/network/include/qx/network/qx-downloadmanager.h b/lib/network/include/qx/network/qx-downloadmanager.h index 66c7ce1a..29125e5f 100644 --- a/lib/network/include/qx/network/qx-downloadmanager.h +++ b/lib/network/include/qx/network/qx-downloadmanager.h @@ -99,6 +99,8 @@ class QX_NETWORK_EXPORT AsyncDownloadManager: public QObject bool mOverwrite; bool mStopOnError; bool mSkipEnumeration; + bool mDeletePartials; + // TODO: May also want to have option for removing ALL files upon any failure, even complete ones QCryptographicHash::Algorithm mVerificationMethod; // Status @@ -154,6 +156,7 @@ class QX_NETWORK_EXPORT AsyncDownloadManager: public QObject bool isOverwrite() const; bool isStopOnError() const; bool isSkipEnumeration() const; + bool isDeletePartialDownloads() const; QCryptographicHash::Algorithm verificationMethod() const; int taskCount() const; bool hasTasks() const; @@ -166,6 +169,7 @@ class QX_NETWORK_EXPORT AsyncDownloadManager: public QObject void setOverwrite(bool overwrite); void setStopOnError(bool stopOnError); void setSkipEnumeration(bool skipEnumeration); + void setDeletePartialDownloads(bool deletePartialDownloads); void setVerificationMethod(QCryptographicHash::Algorithm method); // Tasks @@ -230,6 +234,7 @@ class QX_NETWORK_EXPORT SyncDownloadManager: public QObject bool isOverwrite() const; bool isStopOnError() const; bool isSkipEnumeration() const; + bool isDeletePartialDownloads() const; QCryptographicHash::Algorithm verificationMethod() const; int taskCount() const; bool hasTasks() const; @@ -242,6 +247,7 @@ class QX_NETWORK_EXPORT SyncDownloadManager: public QObject void setOverwrite(bool overwrite); void setStopOnError(bool stopOnError); void setSkipEnumeration(bool skipEnumeration); + void setDeletePartialDownloads(bool deletePartialDownloads); void setVerificationMethod(QCryptographicHash::Algorithm method); // Tasks diff --git a/lib/network/src/qx-downloadmanager.cpp b/lib/network/src/qx-downloadmanager.cpp index 04083188..96b79c81 100644 --- a/lib/network/src/qx-downloadmanager.cpp +++ b/lib/network/src/qx-downloadmanager.cpp @@ -80,6 +80,7 @@ AsyncDownloadManager::AsyncDownloadManager(QObject* parent) : mOverwrite(false), mStopOnError(false), mSkipEnumeration(false), + mDeletePartials(false), mVerificationMethod(QCryptographicHash::Sha256), mStatus(Status::Initial) { @@ -322,7 +323,7 @@ bool AsyncDownloadManager::isStopOnError() const { return mStopOnError; } /*! * Returns @c true if the manager is configured to query the size of all queued tasks before - * actually initiating any downloads; otherwise returns @c false. + * actually initiating any downloads; otherwise, returns @c false. * * If enumeration is disabled, total download progress reported by the manager will be limited * in scope to only active and finished downloads, as the size of future download tasks cannot @@ -341,6 +342,17 @@ bool AsyncDownloadManager::isStopOnError() const { return mStopOnError; } */ bool AsyncDownloadManager::isSkipEnumeration() const { return mSkipEnumeration; } + +/*! + * Returns @c true if the manager is configured to remove any incomplete downloads after they + * fail or are aborted; otherwise, returns @c false. + * + * The default is @c false. + * + * @sa setDeletePartialDownloads(). + */ +bool AsyncDownloadManager::isDeletePartialDownloads() const { return mDeletePartials; } + /*! * Returns the hash algorithm used to verify downloads for tasks that include a checksum. * @@ -441,6 +453,14 @@ void AsyncDownloadManager::setStopOnError(bool stopOnError) { mStopOnError = sto */ void AsyncDownloadManager::setSkipEnumeration(bool skipEnumeration) { mSkipEnumeration = skipEnumeration; } +/*! + * Configures the manager to automatically remove incomplete files after a download fails or is aborted if + * @a deletePartialDownloads is @c true; otherwise, partial downloads are kept. + * + * @sa isDeletePartialDownloads(). + */ +void AsyncDownloadManager::setDeletePartialDownloads(bool deletePartialDownloads) { mDeletePartials = deletePartialDownloads; } + /*! * Sets the hash algorithm used to verify downloads for tasks that include a checksum. * @@ -539,9 +559,8 @@ void AsyncDownloadManager::readyReadHandler() if(wr.isFailure()) { - // Close and delete file, finished handler will use this info to create correct report + // Close file, finished handler will use this state to create correct report writer->close(); - QFile::remove(writer->path()); if(mStopOnError) stopOnError(); @@ -701,19 +720,23 @@ void AsyncDownloadManager::downloadFinishedHandler(QNetworkReply* reply) recordFinishedDownload(DownloadOpReport::failedDownload(task, reply->errorString())); } - // Followup if needed - if(fail && mStopOnError && mStatus == Status::Downloading) - stopOnError(); - - // Cleanup writer + // Ensure writer is cleaned up writer->close(); - - // Remove from active writers mActiveWriters.remove(reply); // Mark reply for deletion reply->deleteLater(); + // Followup on fail if needed + if(fail) + { + if(mDeletePartials) + QFile::remove(writer->path()); + + if(mStopOnError && mStatus == Status::Downloading) + stopOnError(); + } + // Proceed on next loop iteration QTimer::singleShot(0, this, &AsyncDownloadManager::pushDownloadsUntilFinished); } @@ -974,6 +997,11 @@ bool SyncDownloadManager::isStopOnError() const { return mAsyncDm->isStopOnError */ bool SyncDownloadManager::isSkipEnumeration() const { return mAsyncDm->isSkipEnumeration(); } +/*! + * @copydoc AsyncDownloadManager::isDeletePartialDownloads() + */ +bool SyncDownloadManager::isDeletePartialDownloads() const { return mAsyncDm->isDeletePartialDownloads(); } + /*! * @copydoc AsyncDownloadManager::verificationMethod() */ @@ -1032,6 +1060,11 @@ void SyncDownloadManager::setStopOnError(bool autoAbort) { mAsyncDm->setStopOnEr */ void SyncDownloadManager::setSkipEnumeration(bool skipEnumeration) { mAsyncDm->setSkipEnumeration(skipEnumeration); } +/*! + * @copydoc AsyncDownloadManager::setDeletePartialDownloads(bool skipEnumeration) + */ +void SyncDownloadManager::setDeletePartialDownloads(bool deletePartialDownloads) { mAsyncDm->setDeletePartialDownloads(deletePartialDownloads); } + /*! * @copydoc AsyncDownloadManager::setVerificationMethod(QCryptographicHash::Algorithm method) */ From 8cac10a5b628e111ab0ce4c41261c0678a0c147c Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sat, 6 Jul 2024 14:21:12 -0400 Subject: [PATCH 03/12] Introduce ReplaceMode to Io Handles how to deal with file conflicts during file IO that moves/copies files. So far only applies to copyDirectory(). --- lib/io/include/qx/io/qx-common-io.h | 3 ++- lib/io/src/qx-common-io.cpp | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/io/include/qx/io/qx-common-io.h b/lib/io/include/qx/io/qx-common-io.h index e13ea717..b43db349 100644 --- a/lib/io/include/qx/io/qx-common-io.h +++ b/lib/io/include/qx/io/qx-common-io.h @@ -23,6 +23,7 @@ namespace Qx { //-Namespace Enums----------------------------------------------------------------------------------------------------- +enum ReplaceMode {Replace, Skip, Stop}; enum WriteMode {Insert, Overwrite, Append, Truncate}; enum WriteOption { @@ -77,7 +78,7 @@ QX_IO_EXPORT IoOpReport dirContentInfoList(QFileInfoList& returnBuffer, const QD QDir::Filters filters = QDir::NoFilter, QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags); QX_IO_EXPORT IoOpReport dirContentList(QStringList& returnBuffer, const QDir& directory, QStringList nameFilters = QStringList(), QDir::Filters filters = QDir::NoFilter, QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags, PathType pathType = Absolute); -QX_IO_EXPORT IoOpReport copyDirectory(const QDir& directory, const QDir& destination, bool recursive = true, bool overwrite = false); +QX_IO_EXPORT IoOpReport copyDirectory(const QDir& directory, const QDir& destination, bool recursive = true, ReplaceMode replaceMode = Stop); // Integrity QX_IO_EXPORT IoOpReport calculateFileChecksum(QString& returnBuffer, QFile& file, QCryptographicHash::Algorithm hashAlgorithm); diff --git a/lib/io/src/qx-common-io.cpp b/lib/io/src/qx-common-io.cpp index 3c60d9ee..5f8d45d6 100644 --- a/lib/io/src/qx-common-io.cpp +++ b/lib/io/src/qx-common-io.cpp @@ -27,10 +27,26 @@ namespace Qx { //-Namespace Enums----------------------------------------------------------------------------------------------------- + +/*! + * @enum ReplaceMode + * + * This enum is used to describe how filename conflicts should be handled in operations that move/copy files. + * + * @var ReplaceMode Replace + * Existing files should be replaced with new files. + * + * @var ReplaceMode Skip + * Existing files should be kept. + * + * @var ReplaceMode Stop + * Filename conflicts should be considered an error. + */ + /*! * @enum WriteMode * - * This enum is used to describe mode with which data is written to a file. + * This enum is used to describe the mode with which data is written to a file. * * The exact effects of its values can vary depending on the context in which they are used. */ @@ -1393,7 +1409,7 @@ IoOpReport dirContentList(QStringList& returnBuffer, const QDir& directory, QStr * Copies @a directory to @a destination, recursively if @a recursive is @c true. Existing files are overwritten if * @a overwrite is @c true. */ -IoOpReport copyDirectory(const QDir& directory, const QDir& destination, bool recursive, bool overwrite) +IoOpReport copyDirectory(const QDir& directory, const QDir& destination, bool recursive, ReplaceMode replaceMode) { // Ensure destination exists if(!destination.mkpath(u"."_s)) @@ -1417,7 +1433,9 @@ IoOpReport copyDirectory(const QDir& directory, const QDir& destination, bool re { if(QFile::exists(absDestPath)) { - if(!overwrite) + if(replaceMode == ReplaceMode::Skip) + continue; + else if(replaceMode == ReplaceMode::Stop) return IoOpReport(IO_OP_WRITE, IO_ERR_EXISTS, QFile(absDestPath)); else if(!QFile::remove(absDestPath)) return IoOpReport(IO_OP_WRITE, IO_ERR_REMOVE, QFile(absDestPath)); From 5dc3b3440442408456a867eb901185e5a281d4af Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 7 Jul 2024 00:34:39 -0400 Subject: [PATCH 04/12] Add full details combo function to DownloadManagerReport --- .../qx/network/qx-downloadmanagerreport.h | 1 + lib/network/src/qx-downloadmanagerreport.cpp | 33 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/network/include/qx/network/qx-downloadmanagerreport.h b/lib/network/include/qx/network/qx-downloadmanagerreport.h index 5c7e5172..a2d175de 100644 --- a/lib/network/include/qx/network/qx-downloadmanagerreport.h +++ b/lib/network/include/qx/network/qx-downloadmanagerreport.h @@ -59,6 +59,7 @@ friend AsyncDownloadManager; QString outcomeString() const; QString specificDetails() const; QString generalDetails() const; + QString details() const; bool wasSuccessful() const; QList taskReports() const; diff --git a/lib/network/src/qx-downloadmanagerreport.cpp b/lib/network/src/qx-downloadmanagerreport.cpp index 0a683cbf..a90ba845 100644 --- a/lib/network/src/qx-downloadmanagerreport.cpp +++ b/lib/network/src/qx-downloadmanagerreport.cpp @@ -60,20 +60,7 @@ QString DownloadManagerReport::derivePrimary() const QString DownloadManagerReport::deriveSecondary() const { return mDetailsHeading; }; -QString DownloadManagerReport::deriveDetails() const -{ - if(mDetailsSpecific.isEmpty() && mDetailsGeneral.isEmpty()) - return QString(); - - QString details; - details.reserve(mDetailsSpecific.size() && mDetailsGeneral.size() + 2); - details.append(mDetailsGeneral); - if(!details.isEmpty()) - details.append(u"\n\n"_s); // +2 - details.append(mDetailsSpecific); - - return details; -} +QString DownloadManagerReport::deriveDetails() const { return details(); } //Public: /*! @@ -96,6 +83,24 @@ QString DownloadManagerReport::specificDetails() const { return mDetailsSpecific */ QString DownloadManagerReport::generalDetails() const { return mDetailsGeneral; } +/*! + * Returns all extended error information, if present. + */ +QString DownloadManagerReport::details() const +{ + if(mDetailsSpecific.isEmpty() && mDetailsGeneral.isEmpty()) + return QString(); + + QString details; + details.reserve(mDetailsSpecific.size() && mDetailsGeneral.size() + 2); + details.append(mDetailsGeneral); + if(!details.isEmpty()) + details.append(u"\n\n"_s); // +2 + details.append(mDetailsSpecific); + + return details; +} + /*! * Returns @c true if the download manager that generated this report processed its queue successfully; * otherwise returns @c false. From e54e021d2dc73d9c01f38f2549845d36861acdab Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 7 Jul 2024 00:56:30 -0400 Subject: [PATCH 05/12] Actions: Remove GCC 13 workaround (fixed by GitHub) --- .github/workflows/build-qx-linux.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/build-qx-linux.yml b/.github/workflows/build-qx-linux.yml index d1255855..29d5201c 100644 --- a/.github/workflows/build-qx-linux.yml +++ b/.github/workflows/build-qx-linux.yml @@ -72,13 +72,6 @@ jobs: credentials: ${{ secrets.qt_ffynnon_cred }} - name: Update package index run: sudo apt-get update - - name: WORKAROUND FOR https://github.com/actions/runner-images/issues/8659 - if: matrix.os == 'ubuntu-22.04' - run: | - echo "TEMPORARY WORKAROUND FOR GITHUB RUNNER BUG #8659\n\nRemoving GCC 13 as it breaks Clang14" - sudo rm -f /etc/apt/sources.list.d/ubuntu-toolchain-r-ubuntu-test-jammy.list - sudo apt-get update - sudo apt-get install -y --allow-downgrades libc6=2.35-0ubuntu3.4 libc6-dev=2.35-0ubuntu3.4 libstdc++6=12.3.0-1ubuntu1~22.04 libgcc-s1=12.3.0-1ubuntu1~22.04 - name: Install OpenGL lib run: sudo apt-get install libglu1-mesa-dev - name: Install XCB Related libs From b96d312beada916344498161ce881ab626d9be08 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 7 Jul 2024 03:49:14 -0400 Subject: [PATCH 06/12] Actions: Update GitHub action versions --- .github/workflows/build-qt-windows.yml | 2 +- .github/workflows/build-qx-linux.yml | 4 ++-- .github/workflows/build-qx-windows.yml | 4 ++-- .github/workflows/master-pull-request-merge-reaction.yml | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-qt-windows.yml b/.github/workflows/build-qt-windows.yml index bb157c54..579159ae 100644 --- a/.github/workflows/build-qt-windows.yml +++ b/.github/workflows/build-qt-windows.yml @@ -83,7 +83,7 @@ jobs: - name: Copy docs into actual build run: Copy-Item -Path "${{ env.qt_doc_build_dir }}/doc" -Destination "${{ env.qt_doc_install_dir }}/doc" -Recurse - name: Upload Qt build artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.qt_build_artifact_name }} path: ${{ env.qt_install_dir }} diff --git a/.github/workflows/build-qx-linux.yml b/.github/workflows/build-qx-linux.yml index 29d5201c..818c729a 100644 --- a/.github/workflows/build-qx-linux.yml +++ b/.github/workflows/build-qx-linux.yml @@ -90,7 +90,7 @@ jobs: if: matrix.os == 'ubuntu-22.04' run: sudo apt-get install libegl1-mesa-dev - name: Checkout Qx - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: ${{ env.qx_src_suffix }} fetch-depth: 0 # Required for verbose versioning to work correctly @@ -121,7 +121,7 @@ jobs: if: matrix.lib_linkage == 'static' && matrix.cxx_comp == 'clang++-14' run: echo "doc_artifact_name=${{ env.current_artifact_name }}" >> $GITHUB_OUTPUT - name: Upload Qx build artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.current_artifact_name }} path: ${{ env.qx_install_path }} diff --git a/.github/workflows/build-qx-windows.yml b/.github/workflows/build-qx-windows.yml index e78e1200..824c4dc9 100644 --- a/.github/workflows/build-qx-windows.yml +++ b/.github/workflows/build-qx-windows.yml @@ -52,7 +52,7 @@ jobs: - name: Install Graphviz run: choco install graphviz - name: Checkout PxCrypt - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: ${{ env.qx_src_suffix }} fetch-depth: 0 # Required for verbose versioning to work correctly @@ -80,7 +80,7 @@ jobs: $artifact_name=$((Get-ChildItem -Path "${{ env.qx_package_path }}" -Filter *.zip)[0].BaseName) echo "current_artifact_name=$artifact_name" >> $Env:GITHUB_ENV - name: Upload Qx build artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.current_artifact_name }} path: ${{ env.qx_install_path }} diff --git a/.github/workflows/master-pull-request-merge-reaction.yml b/.github/workflows/master-pull-request-merge-reaction.yml index c2d7a3ce..f8f44884 100644 --- a/.github/workflows/master-pull-request-merge-reaction.yml +++ b/.github/workflows/master-pull-request-merge-reaction.yml @@ -70,12 +70,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Download built doc Qx artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ needs.build_qx_release_linux.outputs.doc_artifact_name }} path: ${{ env.doc_artifact_path }} - name: Setup pages - uses: actions/configure-pages@v3 + uses: actions/configure-pages@v4 - name: Upload pages artifact uses: actions/upload-pages-artifact@v1 with: @@ -91,7 +91,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download built Qx artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: path: ${{ env.artifacts_path }} - name: Zip up release artifacts From a7abaefe0f9fe62ba9b8a677a790e5c3f556ed35 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Mon, 8 Jul 2024 22:40:31 -0400 Subject: [PATCH 07/12] Actions: Simply build matrices Utilize maps for some matrix entries (undocumented feature) to greatly simplify he matrices for builds. Still requires some cruft with the use of "exclude", but overall the setup is much clearer, readable, and less prone to error. --- .github/workflows/build-qx-linux.yml | 48 +++++++++----------------- .github/workflows/build-qx-windows.yml | 11 ++---- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/.github/workflows/build-qx-linux.yml b/.github/workflows/build-qx-linux.yml index 818c729a..b298587b 100644 --- a/.github/workflows/build-qx-linux.yml +++ b/.github/workflows/build-qx-linux.yml @@ -22,33 +22,17 @@ jobs: fail-fast: false matrix: os: [ubuntu-20.04, ubuntu-22.04] - compiler: [gcc, clang] - lib_linkage: [shared, static] - include: - - os: ubuntu-20.04 - compiler: gcc - c_comp: gcc-10 - cxx_comp: g++-10 - qt_comp: clang12 - - os: ubuntu-20.04 - compiler: clang - c_comp: clang-12 - cxx_comp: clang++-12 - qt_comp: clang12 - - os: ubuntu-22.04 - compiler: gcc - c_comp: gcc-12 - cxx_comp: g++-12 - qt_comp: clang14 - - os: ubuntu-22.04 - compiler: clang - c_comp: clang-14 - cxx_comp: clang++-14 - qt_comp: clang14 - - lib_linkage: shared - cmake_bsl: ON - - lib_linkage: static - cmake_bsl: OFF + compiler: [{c: gcc-10, cxx: g++-10, qt: clang12}, {c: gcc-12, cxx: g++-12, qt: clang14}, {c: clang-12, cxx: clang++-12, qt: clang12}, {c: clang-14, cxx: clang++-14, qt: clang14}] + linkage: [{type: shared, cmake_bsl: ON}, {type: static, cmake_bsl: OFF}] + exclude: + - os: ubuntu-20.04 + compiler: {c: gcc-12, cxx: g++-12, qt: clang14} + - os: ubuntu-20.04 + compiler: {c: clang-14, cxx: clang++-14, qt: clang14} + - os: ubuntu-22.04 + compiler: {c: gcc-10, cxx: g++-10, qt: clang12} + - os: ubuntu-22.04 + compiler: {c: clang-12, cxx: clang++-12, qt: clang12} runs-on: ${{ matrix.os }} env: cmake_gen: Ninja Multi-Config @@ -66,8 +50,8 @@ jobs: with: version: 6.5.1 os: linux - compiler: ${{ matrix.qt_comp }} - linkage: ${{ matrix.lib_linkage }} + compiler: ${{ matrix.compiler.qt }} + linkage: ${{ matrix.linkage.type }} path: ${{ env.qt_install_dir }} credentials: ${{ secrets.qt_ffynnon_cred }} - name: Update package index @@ -98,7 +82,7 @@ jobs: working-directory: ${{ env.qx_src_dir }} run: | echo Configuring CMake... - "$qt_cmake" -G "$cmake_gen" -S "$qx_src_dir" -B "$qx_build_dir" -D BUILD_SHARED_LIBS="${{ matrix.cmake_bsl }}" -D QX_DOCS=ON -D QX_TESTS=ON -D CMAKE_CXX_COMPILER="${{ matrix.cxx_comp }}" -D CMAKE_C_COMPILER="${{ matrix.c_comp }}" + "$qt_cmake" -G "$cmake_gen" -S "$qx_src_dir" -B "$qx_build_dir" -D BUILD_SHARED_LIBS="${{ matrix.linkage.cmake_bsl }}" -D QX_DOCS=ON -D QX_TESTS=ON -D CMAKE_CXX_COMPILER="${{ matrix.compiler.cxx }}" -D CMAKE_C_COMPILER="${{ matrix.compiler.c }}" echo Changing to build directory... cd "$qx_build_dir" echo Building Qx Release/Docs... @@ -114,11 +98,11 @@ jobs: id: get_artifact_name run: | cpack_name=$(find "${{ env.qx_package_path }}" -type f -name "*.zip") - artifact_name="$(basename "$cpack_name" .zip) [${{ matrix.cxx_comp }}]" + artifact_name="$(basename "$cpack_name" .zip) [${{ matrix.compiler.cxx }}]" echo "current_artifact_name=$artifact_name" >> $GITHUB_ENV - name: Set doc artifact name id: set_doc_artifact_name - if: matrix.lib_linkage == 'static' && matrix.cxx_comp == 'clang++-14' + if: matrix.linkage.type == 'static' && matrix.compiler.cxx == 'clang++-14' run: echo "doc_artifact_name=${{ env.current_artifact_name }}" >> $GITHUB_OUTPUT - name: Upload Qx build artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/build-qx-windows.yml b/.github/workflows/build-qx-windows.yml index 824c4dc9..4e5874dc 100644 --- a/.github/workflows/build-qx-windows.yml +++ b/.github/workflows/build-qx-windows.yml @@ -17,12 +17,7 @@ jobs: strategy: fail-fast: false matrix: - lib_linkage: [shared, static] - include: - - lib_linkage: shared - cmake_bsl: ON - - lib_linkage: static - cmake_bsl: OFF + linkage: [{type: shared, cmake_bsl: ON}, {type: static, cmake_bsl: OFF}] runs-on: windows-latest env: vs_dir: C:/Program Files/Microsoft Visual Studio/2022/Enterprise @@ -40,7 +35,7 @@ jobs: version: 6.5.1 os: windows compiler: msvc2022 - linkage: ${{ matrix.lib_linkage }} + linkage: ${{ matrix.linkage.type }} path: ${{ env.qt_install_dir }} credentials: ${{ secrets.qt_ffynnon_cred }} - name: Install Doxygen @@ -63,7 +58,7 @@ jobs: echo "Setup C++ Build Environment..." CALL "${{ env.vs_dir }}\Common7\Tools\VsDevCmd.bat" -arch=amd64 echo "Configure CMake using Qt wrapper..." - CALL "${{ env.qt_cmake }}" -G "${{ env.cmake_gen }}" -S "${{ env.qx_src_dir}}" -B "${{ env.qx_build_dir }}" -D QX_DOCS=ON -D QX_TESTS=ON -D BUILD_SHARED_LIBS=${{ matrix.cmake_bsl }} + CALL "${{ env.qt_cmake }}" -G "${{ env.cmake_gen }}" -S "${{ env.qx_src_dir}}" -B "${{ env.qx_build_dir }}" -D QX_DOCS=ON -D QX_TESTS=ON -D BUILD_SHARED_LIBS=${{ matrix.linkage.cmake_bsl }} echo "Changing to build directory..." cd "%qx_build_dir%" echo "Building Qx Relase/Docs ..." From 22c0b01ab15045eff14542d6b44d2920e0c6afab Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Tue, 9 Jul 2024 01:13:17 -0400 Subject: [PATCH 08/12] Update Doxygen theme and tool version --- .github/workflows/build-qx-linux.yml | 2 +- .github/workflows/build-qx-windows.yml | 2 +- .../theme/doxygen-awesome/doxygen-awesome.css | 36 +++++++++++-------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-qx-linux.yml b/.github/workflows/build-qx-linux.yml index b298587b..f1201cbd 100644 --- a/.github/workflows/build-qx-linux.yml +++ b/.github/workflows/build-qx-linux.yml @@ -63,7 +63,7 @@ jobs: - name: Install Doxygen uses: oblivioncth/actions/ubuntu/install-doxygen-from-sourceforge@dev with: - version: 1.9.8 + version: 1.10.0 - name: Install Graphviz run: sudo apt-get install graphviz - name: Install Ninja diff --git a/.github/workflows/build-qx-windows.yml b/.github/workflows/build-qx-windows.yml index 4e5874dc..cadea43f 100644 --- a/.github/workflows/build-qx-windows.yml +++ b/.github/workflows/build-qx-windows.yml @@ -41,7 +41,7 @@ jobs: - name: Install Doxygen uses: oblivioncth/actions/general/cache-and-install-doxygen-from-ffynnon@dev with: - version: 1.9.8 + version: 1.10.0 os: windows credentials: ${{ secrets.qt_ffynnon_cred }} - name: Install Graphviz diff --git a/doc/res/theme/doxygen-awesome/doxygen-awesome.css b/doc/res/theme/doxygen-awesome/doxygen-awesome.css index ac7f0608..a2715e26 100644 --- a/doc/res/theme/doxygen-awesome/doxygen-awesome.css +++ b/doc/res/theme/doxygen-awesome/doxygen-awesome.css @@ -313,7 +313,7 @@ body { body, table, div, p, dl, #nav-tree .label, .title, .sm-dox a, .sm-dox a:hover, .sm-dox a:focus, #projectname, .SelectItem, #MSearchField, .navpath li.navelem a, -.navpath li.navelem a:hover, p.reference, p.definition { +.navpath li.navelem a:hover, p.reference, p.definition, div.toc li, div.toc h3 { font-family: var(--font-family); } @@ -334,6 +334,7 @@ p.reference, p.definition { a:link, a:visited, a:hover, a:focus, a:active { color: var(--primary-color) !important; font-weight: 500; + background: none; } a.anchor { @@ -806,6 +807,10 @@ html.dark-mode iframe#MSearchResults { line-height: var(--tree-item-height); } +#nav-tree .item > a:focus { + outline: none; +} + #nav-sync { bottom: 12px; right: 12px; @@ -843,6 +848,7 @@ html.dark-mode iframe#MSearchResults { #nav-tree .arrow { opacity: var(--side-nav-arrow-opacity); + background: none; } .arrow { @@ -1040,7 +1046,7 @@ blockquote::after { blockquote p { margin: var(--spacing-small) 0 var(--spacing-medium) 0; } -.paramname { +.paramname, .paramname em { font-weight: 600; color: var(--primary-dark-color); } @@ -1090,7 +1096,7 @@ div.contents .toc { border: 0; border-left: 1px solid var(--separator-color); border-radius: 0; - background-color: transparent; + background-color: var(--page-background-color); box-shadow: none; position: sticky; top: var(--toc-sticky-top); @@ -1982,14 +1988,16 @@ hr { } .contents hr { - box-shadow: 100px 0 0 var(--separator-color), - -100px 0 0 var(--separator-color), - 500px 0 0 var(--separator-color), - -500px 0 0 var(--separator-color), - 1500px 0 0 var(--separator-color), - -1500px 0 0 var(--separator-color), - 2000px 0 0 var(--separator-color), - -2000px 0 0 var(--separator-color); + box-shadow: 100px 0 var(--separator-color), + -100px 0 var(--separator-color), + 500px 0 var(--separator-color), + -500px 0 var(--separator-color), + 900px 0 var(--separator-color), + -900px 0 var(--separator-color), + 1400px 0 var(--separator-color), + -1400px 0 var(--separator-color), + 1900px 0 var(--separator-color), + -1900px 0 var(--separator-color); } .contents img, .contents .center, .contents center, .contents div.image object { @@ -2460,17 +2468,17 @@ h2:hover a.anchorlink, h1:hover a.anchorlink, h3:hover a.anchorlink, h4:hover a. Optional tab feature */ -.tabbed ul { +.tabbed > ul { padding-inline-start: 0px; margin: 0; padding: var(--spacing-small) 0; } -.tabbed li { +.tabbed > ul > li { display: none; } -.tabbed li.selected { +.tabbed > ul > li.selected { display: block; } From 57edfdeae1f01cd3680fcdaab3c5ae8d755b4d86 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Tue, 9 Jul 2024 02:54:20 -0400 Subject: [PATCH 09/12] Update OBCMake --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 67217fe5..de9eb807 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ project(Qx # Get helper scripts include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FetchOBCMake.cmake) -fetch_ob_cmake("v0.3.4.1") +fetch_ob_cmake("b54172f823528bde80fc8d79fa9a95eaf38eff63") # Initialize project according to standard rules include(OB/Project) From 02f25217481e6f1fd6928afcc9cd5f5d631c0696 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 14 Jul 2024 16:11:57 -0400 Subject: [PATCH 10/12] Improve .gitignore --- .gitignore | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8b95cebe..8d76c0c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,76 @@ -*.user \ No newline at end of file +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +*.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Build within source +/build + +# Binaries +# -------- +*.dll +*.exe + From 7cb30731600998dc66451a01be87c842c845438b Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 14 Jul 2024 17:12:18 -0400 Subject: [PATCH 11/12] Make Error and AbstractError derivatives implicitly convertible to bool More convenient for checking if the error is set. --- lib/core/include/qx/core/qx-abstracterror.h | 1 + lib/core/include/qx/core/qx-error.h | 1 + lib/core/src/qx-abstracterror.cpp | 7 +++++++ lib/core/src/qx-error.cpp | 8 ++++++++ 4 files changed, 17 insertions(+) diff --git a/lib/core/include/qx/core/qx-abstracterror.h b/lib/core/include/qx/core/qx-abstracterror.h index 86ac1be0..c01c763c 100644 --- a/lib/core/include/qx/core/qx-abstracterror.h +++ b/lib/core/include/qx/core/qx-abstracterror.h @@ -105,6 +105,7 @@ friend class Error; public: bool operator==(const AbstractError& other) const = default; bool operator!=(const AbstractError& other) const = default; + operator bool() const { return deriveValue() > 0; }; }; /* TODO: Get string of the type automatically when it becomes diff --git a/lib/core/include/qx/core/qx-error.h b/lib/core/include/qx/core/qx-error.h index f6c3f5c0..65b97e44 100644 --- a/lib/core/include/qx/core/qx-error.h +++ b/lib/core/include/qx/core/qx-error.h @@ -114,6 +114,7 @@ class QX_CORE_EXPORT Error public: bool operator==(const Error& other) const = default; bool operator!=(const Error& other) const = default; + operator bool() const; //-Friend Functions------------------------------------------------------------------------------------------------ friend QTextStream& ::operator<<(QTextStream& ts, const Error& e); diff --git a/lib/core/src/qx-abstracterror.cpp b/lib/core/src/qx-abstracterror.cpp index 19e99380..fed16479 100644 --- a/lib/core/src/qx-abstracterror.cpp +++ b/lib/core/src/qx-abstracterror.cpp @@ -216,6 +216,13 @@ QString IError::deriveDetails() const { return QString(); } * Returns @c true if this error is not the same as @a other; otherwise, returns false. */ +/*! + * @fn AbstractError::operator bool() const; + * + * A convenience operator to check if the error is valid. Produces @c true if + * the value of the error is greater than @c 0; otherwise, produces @c false. + */ + //-Namespace Concepts-------------------------------------------------------------------------------------------------------- /*! * @concept error_type diff --git a/lib/core/src/qx-error.cpp b/lib/core/src/qx-error.cpp index 86edd222..f4201e6b 100644 --- a/lib/core/src/qx-error.cpp +++ b/lib/core/src/qx-error.cpp @@ -351,6 +351,14 @@ Error Error::withSeverity(Severity sv) * Returns @c true if this error is not the same as @a other; otherwise, returns false. */ +/*! + * A convenience operator to check if the error is valid. Produces @c true if + * the value of the error is greater than @c 0; otherwise, produces @c false. + * + * @sa isValid(). + */ +Error::operator bool() const { return isValid(); } + } // namespace Qx //-Non-member/Related Functions------------------------------------------------------------------------------------ From 2f26dac2cb05cc2343bbbda784f040d2ddc99a6c Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 14 Jul 2024 17:13:57 -0400 Subject: [PATCH 12/12] Bump for release --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de9eb807..8da059e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,14 +8,14 @@ cmake_minimum_required(VERSION 3.23.0...3.26.0) # avoided and only used for hotfixes. DON'T USE TRAILING # ZEROS IN VERSIONS project(Qx - VERSION 0.5.6.1 + VERSION 0.5.7 LANGUAGES CXX DESCRIPTION "Qt Extensions Library" ) # Get helper scripts include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FetchOBCMake.cmake) -fetch_ob_cmake("b54172f823528bde80fc8d79fa9a95eaf38eff63") +fetch_ob_cmake("v0.3.5") # Initialize project according to standard rules include(OB/Project)