diff --git a/.github/workflows/artifcats.yaml b/.github/workflows/artifcats.yaml index 24fbb7b..c36e5c2 100644 --- a/.github/workflows/artifcats.yaml +++ b/.github/workflows/artifcats.yaml @@ -13,7 +13,7 @@ defaults: jobs: release: name: Make artifacts - runs-on: windows-2019 + runs-on: windows-2022 steps: - name: Set Short Sha run: | @@ -33,7 +33,7 @@ jobs: echo "pbom_build_number=${{github.run_number}} (${{env.git_sha}})" >> $env:GITHUB_ENV echo "pbom_installer_version=0.0.${{github.run_number}}" >> $env:GITHUB_ENV - - name: Install MSVC 2019 + - name: Install MSVC 2022 uses: ilammy/msvc-dev-cmd@v1 with: arch: x64 @@ -41,11 +41,12 @@ jobs: - name: Install Qt6 uses: jurplel/install-qt-action@v2 with: - version: '6.1.1' + version: '6.2.1' host: windows target: desktop arch: win64_msvc2019_64 dir: ${{github.workspace}} + tools: tools_openssl_x64,1.1.1-10,qt.tools.openssl.win_x64 - name: Checkout uses: actions/checkout@v2 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 329d51e..7cd665f 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -8,7 +8,7 @@ defaults: jobs: release: name: Run tests - runs-on: windows-2019 + runs-on: windows-2022 steps: - name: Set Short Sha run: | @@ -26,7 +26,7 @@ jobs: echo "pbom_version=$('${{github.ref}}' -replace 'refs/heads/','' -replace '[^\w\d]','_')" >> $env:GITHUB_ENV echo "pbom_build_number=${{github.run_number}} (${{env.git_sha}})" >> $env:GITHUB_ENV - - name: Install MSVC 2019 + - name: Install MSVC 2022 uses: ilammy/msvc-dev-cmd@v1 with: arch: x64 @@ -34,7 +34,7 @@ jobs: - name: Install Qt6 uses: jurplel/install-qt-action@v2 with: - version: '6.1.1' + version: '6.2.1' host: windows target: desktop arch: win64_msvc2019_64 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ced5d5..fbfb469 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # PBO Manager change log +## Version 1.1.0 + +- [Enhancement] UI no longer freezes when opening a PBO +- [Enhancement] UI no longer freezes when pasting files into a PBO from the file system +- [Enhancement] Progress operations are displayed in the Windows taskbar +- [Enhancement] The application now can check for a new version. See "Help->Check for updates" +- [Fix] If make any changes in a PBO while saving, the PBO got corrupted +- [Fix] If "Cancel" while saving a PBO had a chance to corrupt +- [Fix] The "pack" console command was interpreting "-o" option incorrectly +- [Fix] It was possible to rename a file inside a PBO, if there was a file with the same name in a different casing +- [Fix] The context menu "Extract to " had a superfluous file extension + ## Version 1.0.0 - Support of normal PBOs - Support of Mikero's mangled PBOs -- Windows explorer integration \ No newline at end of file +- Windows explorer integration diff --git a/CMakeLists.txt b/CMakeLists.txt index 984dc9a..28dbdfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,12 +3,13 @@ cmake_minimum_required(VERSION 3.5) project(pboman3 LANGUAGES CXX) set(PBOM_BUILD_NUMBER "build \(ver\)" CACHE STRING "App build number") -set(PBOM_VERSION "version" CACHE STRING "App release version") +set(PBOM_VERSION "0.0.1" CACHE STRING "App release version") add_definitions(-DPBOM_PROJECT_NAME="PBO Manager") add_definitions(-DPBOM_BUILD_NUMBER="${PBOM_BUILD_NUMBER}") add_definitions(-DPBOM_VERSION="${PBOM_VERSION}") add_definitions(-DPBOM_PROJECT_SITE="https://github.com/winseros/pboman3") +add_definitions(-DPBOM_API_SITE="https://api.github.com/repos/winseros/pboman3") set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/Folder.DotSettings b/Folder.DotSettings index 8d51946..937585e 100644 --- a/Folder.DotSettings +++ b/Folder.DotSettings @@ -63,4 +63,5 @@ True True True + True True diff --git a/README.md b/README.md index 23ca48d..49b1116 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,32 @@ A tool to open, pack and unpack ArmA PBO files. ## Building from source -``` -# powershell -git clone --recurse-submodules git@github.com:winseros/pboman3.git -cmake -S -B -cmake --build -``` +1. Set the env variables: + + | Variable | Description | Example | + |----------|-------------------------------------------------------------------|---------------------------------| + | QT_DIR | Where QT is located. Needed for CMAKE to build. | G:\Qt\6.1.1\msvc2019_64 | + + +2. Run the script: + + ``` + # powershell + git clone --recurse-submodules git@github.com:winseros/pboman3.git + cmake -S -B + cmake --build + ``` Also, see [how CI builds](.github/workflows/artifcats.yaml). + +## Open in IDE + +1. Set the env variabls: + + | Variable | Description | Example | + |----------|-------------------------------------------------------------------|---------------------------------| + | QT_DIR | Where QT is located. Needed for CMAKE to build. | G:\Qt\6.1.1\msvc2019_64 | + | PATH | Where QT binaries are located. Needed for the IDE to run/debug. | G:\Qt\6.1.1\msvc2019_64\bin | + | PATH | Where OpenSSL binaries are located. Needed for IDE to run/debug. | G:\Qt\Tools\OpenSSL\Win_x64\bin | + +2. Open the the root folder in IDE diff --git a/installer/PBOManager.wxs b/installer/PBOManager.wxs index 9f64e8f..19bfbc4 100644 --- a/installer/PBOManager.wxs +++ b/installer/PBOManager.wxs @@ -61,6 +61,7 @@ + @@ -69,9 +70,13 @@ + + + + + + + + + + + + + + + + @@ -176,6 +196,14 @@ Source="$(var.ArtifactsFolder)Styles\qwindowsvistastyle.dll" /> + + + + + + @@ -226,6 +254,7 @@ PbomInstallPer = "machine" PbomInstallPer = "user" PbomInstallPer = "machine" + 1 1 +#include "domain/documentheader.h" +#include "domain/validationexception.h" + +namespace pboman3::domain::test { + TEST(DocumentHeaderTest, Ctor_Initializes_Object) { + const DocumentHeader header1("h1", "v1"); + ASSERT_EQ(header1.name(), "h1"); + ASSERT_EQ(header1.value(), "v1"); + + const DocumentHeader header2("h2", ""); + ASSERT_EQ(header2.name(), "h2"); + ASSERT_EQ(header2.value(), ""); + } + + TEST(DocumentHeaderTest, Ctor_Throws_If_Name_Empty) { + ASSERT_THROW(DocumentHeader header("", "v1"), ValidationException); + } + + TEST(DocumentHeaderTest, Repository_Does_Not_Throw) { + const DocumentHeader header1(DocumentHeader::InternalData{ "h1", "v1" }); + ASSERT_EQ(header1.name(), "h1"); + ASSERT_EQ(header1.value(), "v1"); + + const DocumentHeader header2(DocumentHeader::InternalData{ "", "" }); + ASSERT_EQ(header2.name(), ""); + ASSERT_EQ(header2.value(), ""); + } + + TEST(DocumentHeaderTest, Equality_Operator_Returns_True) { + const DocumentHeader header1(DocumentHeader::InternalData{ "h1", "v1" }); + const DocumentHeader header2(DocumentHeader::InternalData{ "h1", "v1" }); + + ASSERT_EQ(header1, header2); + } + + TEST(DocumentHeaderTest, Equality_Operator_Returns_False) { + const DocumentHeader header1(DocumentHeader::InternalData{ "h1", "v2" }); + const DocumentHeader header2(DocumentHeader::InternalData{ "h1", "v1" }); + + ASSERT_NE(header1, header2); + + const DocumentHeader header3(DocumentHeader::InternalData{ "h2", "v1" }); + const DocumentHeader header4(DocumentHeader::InternalData{ "h1", "v1" }); + + ASSERT_NE(header3, header4); + } +} diff --git a/pbom/domain/__test__/documentheaders_test.cpp b/pbom/domain/__test__/documentheaders_test.cpp new file mode 100644 index 0000000..3af81d7 --- /dev/null +++ b/pbom/domain/__test__/documentheaders_test.cpp @@ -0,0 +1,96 @@ +#include +#include "domain/documentheaders.h" +#include "domain/documentheaderstransaction.h" + +namespace pboman3::domain::test { + TEST(DocumentHeadersTest, Count_Returns_Headers_Count) { + const DocumentHeaders headers(QList{ + QSharedPointer(new DocumentHeader("h1", "v1")), + QSharedPointer(new DocumentHeader("h2", "v2")), + }); + ASSERT_EQ(headers.count(), 2); + } + + TEST(DocumentHeadersTest, At_Returns_Header) { + const DocumentHeaders headers(QList{ + QSharedPointer(new DocumentHeader("h1", "v1")), + QSharedPointer(new DocumentHeader("h2", "v2")), + }); + + ASSERT_EQ(headers.at(0)->name(), "h1"); + ASSERT_EQ(headers.at(0)->value(), "v1"); + + ASSERT_EQ(headers.at(1)->name(), "h2"); + ASSERT_EQ(headers.at(1)->value(), "v2"); + } + + TEST(DocumentHeadersTest, Begin_End_Work) { + DocumentHeaders headers(QList{ + QSharedPointer(new DocumentHeader("h1", "v1")), + QSharedPointer(new DocumentHeader("h2", "v2")), + }); + + int count = 0; + for (const DocumentHeader* header : headers) { + ASSERT_FALSE(header->name().isEmpty()); + count++; + } + + ASSERT_EQ(count, 2); + } + + TEST(DocumentHeadersTest, HeadersChanged_Fires_If_Count_Of_Headers_Changed) { + DocumentHeaders headers(QList{ + QSharedPointer(new DocumentHeader("h1", "v1")), + QSharedPointer(new DocumentHeader("h2", "v2")), + }); + + int count = 0; + QObject::connect(&headers, &DocumentHeaders::headersChanged, [&count]() { count++; }); + + QSharedPointer tran = headers.beginTransaction(); + tran->add("h3", "v3"); + tran->commit(); + tran.clear(); + + ASSERT_EQ(count, 1); + } + + TEST(DocumentHeadersTest, HeadersChanged_Fires_If_Header_Changed) { + DocumentHeaders headers(QList{ + QSharedPointer(new DocumentHeader("h1", "v1")), + QSharedPointer(new DocumentHeader("h2", "v2")), + }); + + int count = 0; + QObject::connect(&headers, &DocumentHeaders::headersChanged, [&count]() { count++; }); + + QSharedPointer tran = headers.beginTransaction(); + tran->clear(); + tran->add("h2", "v2"); + tran->add("h1", "v1"); + tran->commit(); + tran.clear(); + + ASSERT_EQ(count, 1); + } + + TEST(DocumentHeadersTest, HeadersChanged_Not_Fires_If_Header_Replaced) { + DocumentHeaders headers(QList{ + QSharedPointer(new DocumentHeader("h1", "v1")), + QSharedPointer(new DocumentHeader("h2", "v2")), + }); + + int count = 0; + QObject::connect(&headers, &DocumentHeaders::headersChanged, [&count]() { count++; }); + + QSharedPointer tran = headers.beginTransaction(); + tran->clear(); + tran->add("h1", "v1"); + tran->add("h2", "v2"); + tran->commit(); + tran.clear(); + + ASSERT_EQ(count, 0); + } +} diff --git a/pbom/domain/__test__/documentheaderstransaction_test.cpp b/pbom/domain/__test__/documentheaderstransaction_test.cpp new file mode 100644 index 0000000..88bee6a --- /dev/null +++ b/pbom/domain/__test__/documentheaderstransaction_test.cpp @@ -0,0 +1,126 @@ +#include +#include "domain/documentheaders.h" +#include "domain/documentheaderstransaction.h" +#include "exception.h" + +namespace pboman3::domain::test { + TEST(DocumentHeadersTransactionTest, Count_Returns_Headers_Count) { + DocumentHeaders headers; + + const QSharedPointer tran = headers.beginTransaction(); + ASSERT_EQ(tran->count(), 0); + + tran->add("h1", "v1"); + ASSERT_EQ(tran->count(), 1); + + tran->add("h2", "v2"); + ASSERT_EQ(tran->count(), 2); + + tran->clear(); + ASSERT_EQ(tran->count(), 0); + } + + TEST(DocumentHeadersTransactionTest, At_Returns_Header) { + DocumentHeaders headers; + + const QSharedPointer tran = headers.beginTransaction(); + tran->add("h1", "v1"); + tran->add("h2", "v2"); + + ASSERT_EQ(tran->at(0)->name(), "h1"); + ASSERT_EQ(tran->at(0)->value(), "v1"); + + ASSERT_EQ(tran->at(1)->name(), "h2"); + ASSERT_EQ(tran->at(1)->value(), "v2"); + } + + TEST(DocumentHeadersTransactionTest, Add_Throws_If_Committed) { + DocumentHeaders headers; + + const QSharedPointer tran = headers.beginTransaction(); + tran->commit(); + + try { + tran->add("h1", "v1"); + FAIL() << "Should have not reached this line"; + } catch (InvalidOperationException& ex) { + ASSERT_EQ(ex.message(), "You must not modify a committed transaction."); + } + } + + + TEST(DocumentHeadersTransactionTest, Clear_Throws_If_Committed) { + DocumentHeaders headers; + + const QSharedPointer tran = headers.beginTransaction(); + tran->commit(); + + try { + tran->clear(); + FAIL() << "Should have not reached this line"; + } catch (InvalidOperationException& ex) { + ASSERT_EQ(ex.message(), "You must not modify a committed transaction."); + } + } + + TEST(DocumentHeadersTransactionTest, Begin_End_Work) { + DocumentHeaders headers; + + const QSharedPointer tran = headers.beginTransaction(); + tran->add("h1", "v1"); + tran->add("h2", "v2"); + + int count = 0; + for (const DocumentHeader* header : *tran) { + ASSERT_FALSE(header->name().isEmpty()); + count++; + } + + ASSERT_EQ(count, 2); + } + + TEST(DocumentHeadersTransactionTest, Transaction_Commits) { + DocumentHeaders headers; + + QSharedPointer tran = headers.beginTransaction(); + tran->add("h1", "v1"); + tran->add("h2", "v2"); + + tran->commit(); + tran.clear();//explicitly clear + + ASSERT_EQ(headers.count(), 2); + ASSERT_EQ(headers.at(0)->name(), "h1"); + ASSERT_EQ(headers.at(1)->name(), "h2"); + } + + TEST(DocumentHeadersTransactionTest, Transaction_RollsBack) { + DocumentHeaders headers; + + QSharedPointer tran = headers.beginTransaction(); + tran->add("h1", "v1"); + tran->add("h2", "v2"); + + //tran->commit();//no commit call! + //tran.clear();//explicitly clear + + ASSERT_EQ(headers.count(), 0); + } + + TEST(DocumentHeadersTransactionTest, Transaction_Does_Not_Change_Source_Data_On_Rollback) { + DocumentHeaders headers(QList{ + QSharedPointer(new DocumentHeader("h1", "v1")), + QSharedPointer(new DocumentHeader("h2", "v2")), + }); + + QSharedPointer tran = headers.beginTransaction(); + tran->add("h3", "v3"); + + //tran->commit();//no commit call! + tran.clear();//explicitly clear + + ASSERT_EQ(headers.count(), 2); + ASSERT_EQ(headers.at(0)->name(), "h1"); + ASSERT_EQ(headers.at(1)->name(), "h2"); + } +} diff --git a/pbom/domain/__test__/func_test.cpp b/pbom/domain/__test__/func_test.cpp new file mode 100644 index 0000000..6ee0031 --- /dev/null +++ b/pbom/domain/__test__/func_test.cpp @@ -0,0 +1,79 @@ +#include +#include "domain/func.h" +#include "domain/pbonode.h" + +namespace pboman3::domain::test { + TEST(FuncTest, CountFilesInTree_Counts_Files) { + PboNode node("file.pbo", PboNodeType::Container, nullptr); + node.createHierarchy(PboPath("f1/e1.txt")); + node.createHierarchy(PboPath("f1/e2.txt")); + node.createHierarchy(PboPath("f1/f2/e2.txt")); + node.createHierarchy(PboPath("e2.txt")); + + int count = 0; + CountFilesInTree(node, count); + + ASSERT_EQ(count, 4); + } + + TEST(FuncTest, FindDirectChild_Finds_Child_Case_Insensitively_Const) { + PboNode node("file.pbo", PboNodeType::Container, nullptr); + node.createHierarchy(PboPath("e1.txt")); + node.createHierarchy(PboPath("e2.txt")); + PboNode* e3 = node.createHierarchy(PboPath("e3.txt")); + + const PboNode* found = FindDirectChild(const_cast(&node), "E3.TxT"); + + ASSERT_EQ(e3, found); + } + + TEST(FuncTest, FindDirectChild_Returns_Null_Const) { + PboNode node("file.pbo", PboNodeType::Container, nullptr); + node.createHierarchy(PboPath("e1.txt")); + node.createHierarchy(PboPath("e2.txt")); + node.createHierarchy(PboPath("e3.txt")); + + const PboNode* found = FindDirectChild(const_cast(&node), "e4.txt"); + + ASSERT_EQ(found, nullptr); + } + + TEST(FuncTest, FindDirectChild_Finds_Child_Case_Insensitively_NonConst) { + PboNode node("file.pbo", PboNodeType::Container, nullptr); + node.createHierarchy(PboPath("e1.txt")); + node.createHierarchy(PboPath("e2.txt")); + PboNode* e3 = node.createHierarchy(PboPath("e3.txt")); + + const PboNode* found = FindDirectChild(&node, "E3.TxT"); + + ASSERT_EQ(e3, found); + } + + TEST(FuncTest, FindDirectChild_Returns_Null_NonConst) { + PboNode node("file.pbo", PboNodeType::Container, nullptr); + node.createHierarchy(PboPath("e1.txt")); + node.createHierarchy(PboPath("e2.txt")); + node.createHierarchy(PboPath("e3.txt")); + + const PboNode* found = FindDirectChild(&node, "e4.txt"); + + ASSERT_EQ(found, nullptr); + } + + TEST(FuncTest, IsPathConflict_Functional) { + PboNode root("file-name", PboNodeType::Container, nullptr); + ASSERT_EQ(root.depth(), 0); + + root.createHierarchy(PboPath("e1.txt")); + root.createHierarchy(PboPath("f2/e2.txt")); + + ASSERT_TRUE(IsPathConflict(&root, PboPath("e1.txT"))); + ASSERT_TRUE(IsPathConflict(&root, PboPath("f2"))); + ASSERT_TRUE(IsPathConflict(&root, PboPath("F2/e2.Txt"))); + ASSERT_TRUE(IsPathConflict(&root, PboPath("f2/E2.txt/e4.txt"))); + + ASSERT_FALSE(IsPathConflict(&root, PboPath("e2.txt"))); + ASSERT_FALSE(IsPathConflict(&root, PboPath("f2/e3.txt"))); + ASSERT_FALSE(IsPathConflict(&root, PboPath("f3/e4.txt"))); + } +} diff --git a/pbom/domain/__test__/pbodocument_test.cpp b/pbom/domain/__test__/pbodocument_test.cpp new file mode 100644 index 0000000..c6b4bcc --- /dev/null +++ b/pbom/domain/__test__/pbodocument_test.cpp @@ -0,0 +1,104 @@ +#include "domain/pbodocument.h" +#include +#include "domain/documentheaderstransaction.h" +#include "domain/pbonodetransaction.h" + +namespace pboman3::domain::test { + TEST(PboDocumentTest, Public_Ctor_Changed_Fires_When_Hierarchy_Changes) { + const PboDocument document("file.pbo"); + + int count = 0; + QObject::connect(&document, &PboDocument::changed, [&count]() { + count++; + }); + + document.root()->createHierarchy(PboPath("f1.txt"), ConflictResolution::Unset); + + ASSERT_EQ(count, 1); + } + + TEST(PboDocumentTest, Repository_Ctor_Changed_Fires_When_Hierarchy_Changes) { + const PboDocument document("file.pbo", QList>{}, QByteArray{}); + + int count = 0; + QObject::connect(&document, &PboDocument::changed, [&count]() { + count++; + }); + + document.root()->createHierarchy(PboPath("f1.txt"), ConflictResolution::Unset); + + ASSERT_EQ(count, 1); + } + + + TEST(PboDocumentTest, Public_Ctor_Changed_Fires_When_Headers_Change) { + const PboDocument document("file.pbo"); + + int count = 0; + QObject::connect(&document, &PboDocument::changed, [&count]() { + count++; + }); + + QSharedPointer tran = document.headers()->beginTransaction(); + tran->add("h1", "v1"); + tran->commit(); + tran.clear(); + + ASSERT_EQ(count, 1); + } + + TEST(PboDocumentTest, Repository_Ctor_Changed_Fires_When_Header_Change) { + const PboDocument document("file.pbo", QList>{}, QByteArray{}); + + int count = 0; + QObject::connect(&document, &PboDocument::changed, [&count]() { + count++; + }); + + QSharedPointer tran = document.headers()->beginTransaction(); + tran->add("h1", "v1"); + tran->commit(); + tran.clear(); + + ASSERT_EQ(count, 1); + } + + + TEST(PboDocumentTest, Public_Ctor_TitleChanged_Fires) { + const PboDocument document("file.pbo"); + + int count = 0; + QString title; + QObject::connect(&document, &PboDocument::titleChanged, [&count, &title](const QString& t) { + count++; + title = t; + }); + + QSharedPointer tran = document.root()->beginTransaction(); + tran->setTitle("new.pbo"); + tran->commit(); + tran.clear(); + + ASSERT_EQ(count, 1); + ASSERT_EQ(title, "new.pbo"); + } + + TEST(PboDocumentTest, Repository_Ctor_TitleChanged_Fires) { + const PboDocument document("file.pbo", QList>{}, QByteArray{}); + + int count = 0; + QString title; + QObject::connect(&document, &PboDocument::titleChanged, [&count, &title](const QString& t) { + count++; + title = t; + }); + + QSharedPointer tran = document.root()->beginTransaction(); + tran->setTitle("new.pbo"); + tran->commit(); + tran.clear(); + + ASSERT_EQ(count, 1); + ASSERT_EQ(title, "new.pbo"); + } +} diff --git a/pbom/model/__test__/pbonode_test.cpp b/pbom/domain/__test__/pbonode_test.cpp similarity index 75% rename from pbom/model/__test__/pbonode_test.cpp rename to pbom/domain/__test__/pbonode_test.cpp index 624a08e..6196093 100644 --- a/pbom/model/__test__/pbonode_test.cpp +++ b/pbom/domain/__test__/pbonode_test.cpp @@ -1,11 +1,13 @@ -#include "model/pbonode.h" +#include "domain/pbonode.h" #include #include #include + +#include "domain/pbonodetransaction.h" #include "gmock/gmock.h" -#include "util/exception.h" +#include "exception.h" -namespace pboman3::test { +namespace pboman3::domain::test { TEST(PboNodeTest, Ctor_Initializes_Node) { PboNode nodeA("a-node", PboNodeType::Folder, nullptr); ASSERT_EQ(nodeA.nodeType(), PboNodeType::Folder); @@ -202,67 +204,6 @@ namespace pboman3::test { ASSERT_EQ(node, &root); } - TEST(PboNodeTest, SetTitle_Throws_If_Can_Not_Set_Title) { - PboNode root("file-name", PboNodeType::Container, nullptr); - PboNode* e1 = root.createHierarchy(PboPath("e1")); - root.createHierarchy(PboPath("e2")); - - ASSERT_THROW(e1->setTitle("e2"), InvalidOperationException); - } - - TEST(PboNodeTest, SetTitle_Emits_If_Set_Title) { - PboNode root("file-name", PboNodeType::Container, nullptr); - PboNode* e1 = root.createHierarchy(PboPath("e1")); - - //callback variables - int count = 0; - - //connect and wait for the callback - QObject::connect(e1, &PboNode::titleChanged, [&count](const QString& title) { - ASSERT_EQ(title, "e2"); - count++; - }); - - e1->setTitle("e2"); - ASSERT_EQ(count, 1); - } - - TEST(PboNodeTest, SetTitle_Does_Not_Emit_If_Did_Not_Set_Title) { - PboNode root("file-name", PboNodeType::Container, nullptr); - PboNode* e1 = root.createHierarchy(PboPath("e1")); - - //callback variables - int callbackCount = 0; - - //connect and wait for the callback - QObject::connect(e1, &PboNode::titleChanged, [&callbackCount]() { - callbackCount++; - }); - - e1->setTitle("e1"); //the same name - not changing - - ASSERT_EQ(callbackCount, 0); - } - - TEST(PboNodeTest, SetTitle_Emits_HierarchyChanged_On_Root) { - PboNode root("file-name", PboNodeType::Container, nullptr); - PboNode* e2 = root.createHierarchy(PboPath("f1/e2")); - - int count = 0; - auto hierarchyChanged1 = []() { FAIL() << "Should not have been called"; }; - auto hierarchyChanged2 = [&count]() {count++; }; - - QObject::connect(root.at(0), &PboNode::hierarchyChanged, hierarchyChanged1); - QObject::connect(&root, &PboNode::hierarchyChanged, hierarchyChanged2); - - //only the root fires the callback - e2->setTitle("e1"); - ASSERT_EQ(count, 1); - - e2->setTitle("e1");//does not emit if no actual change - ASSERT_EQ(count, 1); - } - struct VerifyTitleTestParam { QString input; QString expectedOutput; @@ -271,20 +212,6 @@ namespace pboman3::test { class VerifyTitleTest : public testing::TestWithParam { }; - TEST_P(VerifyTitleTest, VerifyTitle_Is_Functional) { - PboNode root("file-name", PboNodeType::Container, nullptr); - root.createHierarchy(PboPath("e1")); - const PboNode* e2 = root.createHierarchy(PboPath("e2")); - - ASSERT_EQ(e2->verifyTitle(GetParam().input), GetParam().expectedOutput); - } - - INSTANTIATE_TEST_SUITE_P(PboNodeTest, VerifyTitleTest, testing::Values( - VerifyTitleTestParam {nullptr, "The value can not be empty"}, - VerifyTitleTestParam {"", "The value can not be empty"}, - VerifyTitleTestParam {"e1", "The item with this name already exists"}, - VerifyTitleTestParam {"e2", ""})); - TEST(PboNodeTest, RemoveFromHierarchy_Removes) { PboNode root("file-name", PboNodeType::Container, nullptr); PboNode* e1 = root.createHierarchy(PboPath("e1")); @@ -373,20 +300,71 @@ namespace pboman3::test { ASSERT_EQ(count, 1); } - TEST(PboNodeTest, IsPathConflict_Functional) { + TEST(PboNodeTest, SetTitle_Wont_Emit_If_Title_Not_Changed) { PboNode root("file-name", PboNodeType::Container, nullptr); - ASSERT_EQ(root.depth(), 0); - root.createHierarchy(PboPath("e1.txt")); - root.createHierarchy(PboPath("f2/e2.txt")); + int count = 0; + auto changed = [&count]() {count++; }; + QObject::connect(&root, &PboNode::titleChanged, changed); + QObject::connect(&root, &PboNode::hierarchyChanged, changed); + + QSharedPointer tran = root.beginTransaction(); + tran->commit(); + tran.clear(); + + ASSERT_EQ(count, 0); + } - ASSERT_TRUE(root.isPathConflict(PboPath("e1.txt"))); - ASSERT_TRUE(root.isPathConflict(PboPath("f2"))); - ASSERT_TRUE(root.isPathConflict(PboPath("f2/e2.txt"))); - ASSERT_TRUE(root.isPathConflict(PboPath("f2/e2.txt/e4.txt"))); + TEST(PboNodeTest, SetTitle_Emits_If_Title_Changed) { + PboNode root("file-name", PboNodeType::Container, nullptr); - ASSERT_FALSE(root.isPathConflict(PboPath("e2.txt"))); - ASSERT_FALSE(root.isPathConflict(PboPath("f2/e3.txt"))); - ASSERT_FALSE(root.isPathConflict(PboPath("f3/e4.txt"))); + int count = 0; + QObject::connect(&root, &PboNode::titleChanged, [&count](const QString& title) { + count++; + ASSERT_EQ(title, "new-title"); + }); + QObject::connect(&root, &PboNode::hierarchyChanged, [&count]() {count++; }); + + QSharedPointer tran = root.beginTransaction(); + tran->setTitle("new-title"); + tran->commit(); + tran.clear(); + + ASSERT_EQ(count, 2); + } + + TEST(PboNodeTest, SetTitle_Emits_ChildMoved) { + PboNode root("node.pbo", PboNodeType::Container, nullptr); + root.createHierarchy(PboPath("f1.txt")); + PboNode* f2 = root.createHierarchy(PboPath("f2.txt")); + + int count = 0; + QObject::connect(&root, &PboNode::childMoved, [&count](qsizetype prevIndex, qsizetype newIndex) { + count++; + ASSERT_EQ(prevIndex, 1); + ASSERT_EQ(newIndex, 0); + }); + + QSharedPointer tran = f2->beginTransaction(); + tran->setTitle("f0.txt"); + tran->commit(); + tran.clear(); + + ASSERT_EQ(count, 1); + } + + TEST(PboNodeTest, SetTitle_Emits_Changed_On_Root) { + PboNode root("node.pbo", PboNodeType::Container, nullptr); + PboNode* f1 = root.createHierarchy(PboPath("f1.txt")); + + int count = 0; + QObject::connect(&root, &PboNode::hierarchyChanged, [&count]() {count++; }); + + QSharedPointer tran = f1->beginTransaction(); + tran->setTitle("f0.txt"); + tran->commit(); + tran.clear(); + + ASSERT_EQ(count, 1); } } diff --git a/pbom/domain/__test__/pbonodetransaction_test.cpp b/pbom/domain/__test__/pbonodetransaction_test.cpp new file mode 100644 index 0000000..66cb582 --- /dev/null +++ b/pbom/domain/__test__/pbonodetransaction_test.cpp @@ -0,0 +1,61 @@ +#include +#include "domain/pbonodetransaction.h" +#include "domain/validationexception.h" +#include "exception.h" + +namespace pboman3::domain::test { + TEST(PboNodeTransactionTest, Title_Returns_Title) { + PboNode node("node.pbo", PboNodeType::Container, nullptr); + + const QSharedPointer tran = node.beginTransaction(); + ASSERT_EQ(tran->title(), "node.pbo"); + } + + TEST(PboNodeTransactionTest, SetTitle_Throws_If_Committed) { + PboNode node("node.pbo", PboNodeType::Container, nullptr); + + const QSharedPointer tran = node.beginTransaction(); + tran->commit(); + ASSERT_THROW(tran->setTitle("new title"), InvalidOperationException); + } + + TEST(PboNodeTransactionTest, SetTitle_Throws_If_Title_Emmpty) { + PboNode node("node.pbo", PboNodeType::Container, nullptr); + + const QSharedPointer tran = node.beginTransaction(); + try { + tran->setTitle(""); + FAIL() << "Should have not reached this line"; + } catch (ValidationException& ex) { + ASSERT_EQ(ex.message(), "The value can not be empty"); + } + } + + TEST(PboNodeTransactionTest, SetTitle_Throws_If_Node_With_This_Name_Exists) { + PboNode node("node.pbo", PboNodeType::Container, nullptr); + node.createHierarchy(PboPath("f1.txt")); + PboNode* f2 = node.createHierarchy(PboPath("f2.txt")); + + const QSharedPointer tran = f2->beginTransaction(); + try { + tran->setTitle("f1.txt"); + FAIL() << "Should have not reached this line"; + } + catch (ValidationException& ex) { + ASSERT_EQ(ex.message(), "The item with this name already exists"); + } + } + + TEST(PboNodeTransactionTest, SetTitle_Renames_Node) { + PboNode node("node.pbo", PboNodeType::Container, nullptr); + node.createHierarchy(PboPath("f1.txt")); + PboNode* f2 = node.createHierarchy(PboPath("f2.txt")); + + QSharedPointer tran = f2->beginTransaction(); + tran->setTitle("f3.txt"); + tran->commit(); + tran.clear(); + + ASSERT_EQ(f2->title(), "f3.txt"); + } +} diff --git a/pbom/model/__test__/pbopath_test.cpp b/pbom/domain/__test__/pbopath_test.cpp similarity index 91% rename from pbom/model/__test__/pbopath_test.cpp rename to pbom/domain/__test__/pbopath_test.cpp index 43de867..b8045f9 100644 --- a/pbom/model/__test__/pbopath_test.cpp +++ b/pbom/domain/__test__/pbopath_test.cpp @@ -1,7 +1,7 @@ -#include "model/pbopath.h" +#include "domain/pbopath.h" #include -namespace pboman3::test { +namespace pboman3::domain::test { TEST(PboPath, Ctor_Initializes_Empty_Path) { const PboPath p; ASSERT_EQ(p.length(), 0); diff --git a/pbom/domain/abstractnode.h b/pbom/domain/abstractnode.h new file mode 100644 index 0000000..510eb60 --- /dev/null +++ b/pbom/domain/abstractnode.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include "util/qpointerlistiterator.h" + +namespace pboman3::domain { + template + class AbstractNode : public QObject { + + public: + AbstractNode(T* parentNode) + : parentNode_(parentNode) { + } + + T* parentNode() const { + return parentNode_; + } + + T* at(qsizetype index) const { + return children_.at(index).get(); + } + + int depth() const { + int result = 0; + T* parentNode = parentNode_; + while (parentNode) { + result++; + parentNode = parentNode->parentNode_; + } + return result; + } + + int count() const { + return static_cast(children_.count()); + } + + QPointerListIterator begin() { + return QPointerListIterator(children_.data()); + } + + QPointerListIterator end() { + return QPointerListIterator(children_.data() + children_.count()); + } + + QPointerListConstIterator begin() const { + return QPointerListConstIterator(children_.data()); + } + + QPointerListConstIterator end() const { + return QPointerListConstIterator(children_.data() + count()); + } + + protected: + T* parentNode_; + QList> children_; + }; +} diff --git a/pbom/domain/binarysource.cpp b/pbom/domain/binarysource.cpp new file mode 100644 index 0000000..2aefc11 --- /dev/null +++ b/pbom/domain/binarysource.cpp @@ -0,0 +1 @@ +#include "binarysource.h" diff --git a/pbom/io/bs/binarysource.h b/pbom/domain/binarysource.h similarity index 56% rename from pbom/io/bs/binarysource.h rename to pbom/domain/binarysource.h index 356f69b..76aa1e8 100644 --- a/pbom/io/bs/binarysource.h +++ b/pbom/domain/binarysource.h @@ -4,24 +4,26 @@ #include #include "util/util.h" -namespace pboman3 { +namespace pboman3::domain { + using namespace util; + class BinarySource { - public: - BinarySource(QString path); - virtual ~BinarySource(); + protected: + ~BinarySource() = default; + public: virtual void writeToPbo(QFileDevice* targetFile, const Cancel& cancel) = 0; virtual void writeToFs(QFileDevice* targetFile, const Cancel& cancel) = 0; - void open() const; + virtual void open() const = 0; - void close() const; + virtual void close() const = 0; - bool isOpen() const; + virtual bool isOpen() const = 0; - const QString& path() const; + virtual const QString& path() const = 0; virtual qint32 readOriginalSize() const = 0; @@ -30,13 +32,7 @@ namespace pboman3 { virtual bool isCompressed() const = 0; friend QDebug operator <<(QDebug debug, const BinarySource& bs) { - return debug << "BinarySource(Compressed=" << bs.isCompressed() << ", Path=" << bs.path_ << ")"; + return debug << "BinarySource()"; } - - protected: - QFileDevice* file_; - - private: - QString path_; }; } diff --git a/pbom/model/conflictresolution.h b/pbom/domain/conflictresolution.h similarity index 78% rename from pbom/model/conflictresolution.h rename to pbom/domain/conflictresolution.h index a025ec6..f2eeed3 100644 --- a/pbom/model/conflictresolution.h +++ b/pbom/domain/conflictresolution.h @@ -1,6 +1,6 @@ #pragma once -namespace pboman3 { +namespace pboman3::domain { enum class ConflictResolution { Unset = -1, Skip = 0, diff --git a/pbom/domain/documentheader.cpp b/pbom/domain/documentheader.cpp new file mode 100644 index 0000000..f223a96 --- /dev/null +++ b/pbom/domain/documentheader.cpp @@ -0,0 +1,47 @@ +#include "documentheader.h" +#include "validationexception.h" + +namespace pboman3::domain { + DocumentHeader::DocumentHeader(QString name, QString value) + : name_(std::move(name)), + value_(std::move(value)) { + validateName(name_); + } + + DocumentHeader::DocumentHeader(const InternalData& data) + : name_(data.name), + value_(data.value) { + //this is a repository constructor + //it does no validation but must be used only from the persistence layer + } + + const QString& DocumentHeader::name() const { + return name_; + } + + void DocumentHeader::setName(const QString& name) { + validateName(name); + name_ = name; + } + + const QString& DocumentHeader::value() const { + return value_; + } + + void DocumentHeader::setValue(const QString& value) { + value_ = value; + } + + void DocumentHeader::validateName(const QString& name) { + if (name.isEmpty()) + throw ValidationException("The name can not be empty"); + } + + bool operator==(const DocumentHeader& h1, const DocumentHeader& h2) { + return h1.name_ == h2.name_ && h1.value_ == h2.value_; + } + + bool operator!=(const DocumentHeader& h1, const DocumentHeader& h2) { + return !(h1 == h2); + } +} diff --git a/pbom/domain/documentheader.h b/pbom/domain/documentheader.h new file mode 100644 index 0000000..513c981 --- /dev/null +++ b/pbom/domain/documentheader.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace pboman3::domain { + class DocumentHeader { + public: + DocumentHeader(QString name, QString value); + + struct InternalData { + QString name; + QString value; + }; + + explicit DocumentHeader(const InternalData& data);//Repository Ctor + + const QString& name() const; + + void setName(const QString& name); + + const QString& value() const; + + void setValue(const QString& value); + + friend bool operator==(const DocumentHeader& h1, const DocumentHeader& h2); + + friend bool operator!=(const DocumentHeader& h1, const DocumentHeader& h2); + + private: + QString name_; + QString value_; + + void validateName(const QString& name); + }; +} diff --git a/pbom/domain/documentheaders.cpp b/pbom/domain/documentheaders.cpp new file mode 100644 index 0000000..2a98f04 --- /dev/null +++ b/pbom/domain/documentheaders.cpp @@ -0,0 +1,70 @@ +#include "documentheaders.h" +#include +#include "documentheaderstransaction.h" + +namespace pboman3::domain { + DocumentHeaders::DocumentHeaders() = default; + + DocumentHeaders::DocumentHeaders(QList> headers) + : headers_(std::move(headers)) { + //this is a repository constructor + //it does no validation but must be used only from the persistence layer + } + + qsizetype DocumentHeaders::count() const { + return headers_.count(); + } + + const DocumentHeader* DocumentHeaders::at(qsizetype index) const { + return headers_.at(index).get(); + } + + QPointerListIterator DocumentHeaders::begin() { + return QPointerListIterator(headers_.begin()); + } + + QPointerListIterator DocumentHeaders::end() { + return QPointerListIterator(headers_.end()); + } + + QPointerListConstIterator DocumentHeaders::begin() const { + return QPointerListConstIterator(headers_.constBegin()); + } + + QPointerListConstIterator DocumentHeaders::end() const { + return QPointerListConstIterator(headers_.constEnd()); + } + + QSharedPointer DocumentHeaders::beginTransaction() { + return QSharedPointer(new DocumentHeadersTransaction(headers_, this)); + } + + void DocumentHeaders::setHeaders(QList> headers) { + if (areDifferent(headers_, headers)) { + headers_ = std::move(headers); + emit headersChanged(); + } + } + + bool DocumentHeaders::areDifferent(const QList>& list1, const QList>& list2) { + if (list1.count() != list2.count()) + return true; + + auto it1 = list1.begin(); + auto it2 = list2.begin(); + + while (it1 != list1.end()) { + if (**it1 != **it2) + return true; + + ++it1; + ++it2; + } + + return false; + } + + QDebug& operator<<(QDebug& debug, const DocumentHeaders& headers) { + return debug << "DocumentHeaders(Count=" << headers.count() << ")"; + } +} diff --git a/pbom/domain/documentheaders.h b/pbom/domain/documentheaders.h new file mode 100644 index 0000000..ece1a7d --- /dev/null +++ b/pbom/domain/documentheaders.h @@ -0,0 +1,48 @@ +#pragma once + +#include "documentheader.h" +#include "util/qpointerlistiterator.h" + +namespace pboman3::domain { + using namespace util; + + class DocumentHeadersTransaction; + + class DocumentHeaders : public QObject { + Q_OBJECT + + public: + DocumentHeaders(); + + explicit DocumentHeaders(QList> headers); //Repository Ctor + + qsizetype count() const; + + const DocumentHeader* at(qsizetype index) const; + + QPointerListIterator begin(); + + QPointerListIterator end(); + + QPointerListConstIterator begin() const; + + QPointerListConstIterator end() const; + + QSharedPointer beginTransaction(); + + friend DocumentHeadersTransaction; + + friend QDebug& operator<<(QDebug& debug, const DocumentHeaders& headers); + + signals: + void headersChanged(); + + private: + QList> headers_; + + void setHeaders(QList> headers); + + static bool areDifferent(const QList>& list1, + const QList>& list2); + }; +} diff --git a/pbom/domain/documentheaderstransaction.cpp b/pbom/domain/documentheaderstransaction.cpp new file mode 100644 index 0000000..775be08 --- /dev/null +++ b/pbom/domain/documentheaderstransaction.cpp @@ -0,0 +1,52 @@ +#include "documentheaders.h" +#include "documentheaderstransaction.h" +#include "exception.h" + +namespace pboman3::domain { + DocumentHeadersTransaction::DocumentHeadersTransaction(QList> headers, DocumentHeaders* parent) + : committed_(false), + headers_(std::move(headers)), + parent_(parent) { + } + + DocumentHeadersTransaction::~DocumentHeadersTransaction() { + if (committed_) { + parent_->setHeaders(std::move(headers_)); + } + } + + void DocumentHeadersTransaction::commit() { + committed_ = true; + } + + qsizetype DocumentHeadersTransaction::count() const { + return headers_.count(); + } + + const DocumentHeader* DocumentHeadersTransaction::at(qsizetype index) const { + return headers_.at(index).data(); + } + + void DocumentHeadersTransaction::add(const QString& name, const QString& value) { + throwIfCommitted(); + const QSharedPointer header(new DocumentHeader(name, value)); + headers_.append(header); + } + + void DocumentHeadersTransaction::clear() { + throwIfCommitted(); + headers_.clear(); + } + + QPointerListIterator DocumentHeadersTransaction::begin() { + return QPointerListIterator(headers_.begin()); + } + + QPointerListIterator DocumentHeadersTransaction::end() { + return QPointerListIterator(headers_.end()); + } + + void DocumentHeadersTransaction::throwIfCommitted() const { + if (committed_) throw InvalidOperationException("You must not modify a committed transaction."); + } +} diff --git a/pbom/domain/documentheaderstransaction.h b/pbom/domain/documentheaderstransaction.h new file mode 100644 index 0000000..972ed5a --- /dev/null +++ b/pbom/domain/documentheaderstransaction.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include "domain/documentheader.h" +#include "domain/documentheaders.h" + +namespace pboman3::domain { + using namespace util; + + class DocumentHeadersTransaction : public QObject { + Q_OBJECT + + public: + explicit DocumentHeadersTransaction(QList> headers, DocumentHeaders* parent); + + ~DocumentHeadersTransaction() override; + + qsizetype count() const; + + const DocumentHeader* at(qsizetype index) const; + + void add(const QString& name, const QString& value); + + void clear(); + + QPointerListIterator begin(); + + QPointerListIterator end(); + + void commit(); + + private: + bool committed_; + QList> headers_; + DocumentHeaders* parent_; + + void throwIfCommitted() const; + }; +} diff --git a/pbom/domain/func.cpp b/pbom/domain/func.cpp new file mode 100644 index 0000000..16475c5 --- /dev/null +++ b/pbom/domain/func.cpp @@ -0,0 +1,45 @@ +#include "func.h" + +namespace pboman3::domain { + void CountFilesInTree(const PboNode& node, qint32& result) { + if (node.nodeType() == PboNodeType::File) { + result++; + } else { + for (const PboNode* child : node) + CountFilesInTree(*child, result); + } + } + + bool IsPathConflict(const PboNode* node, const PboPath& path) { + const PboNode* n = node; + for (qsizetype i = 0; i < path.length() - 1; i++) { + const PboNode* folder = FindDirectChild(n, path.at(i)); + if (folder) { + if (folder->nodeType() == PboNodeType::File) + return true; + n = folder; + } else { + return false; + } + } + + const PboNode* file = FindDirectChild(n, path.last()); + return file; + } + + const PboNode* FindDirectChild(const PboNode* parent, const QString& title) { + for (const PboNode* child : *parent) { + if (child->title().compare(title, Qt::CaseInsensitive) == 0) + return child; + } + return nullptr; + } + + PboNode* FindDirectChild(PboNode* parent, const QString& title) { + for (PboNode* child : *parent) { + if (child->title().compare(title, Qt::CaseInsensitive) == 0) + return child; + } + return nullptr; + } +} diff --git a/pbom/domain/func.h b/pbom/domain/func.h new file mode 100644 index 0000000..4b3277b --- /dev/null +++ b/pbom/domain/func.h @@ -0,0 +1,13 @@ +#pragma once + +#include "pbonode.h" + +namespace pboman3::domain { + void CountFilesInTree(const PboNode& node, qint32& result); + + bool IsPathConflict(const PboNode* node, const PboPath& path); + + const PboNode* FindDirectChild(const PboNode* parent, const QString& title); + + PboNode* FindDirectChild(PboNode* parent, const QString& title); +} diff --git a/pbom/domain/pbodocument.cpp b/pbom/domain/pbodocument.cpp new file mode 100644 index 0000000..09aa2e6 --- /dev/null +++ b/pbom/domain/pbodocument.cpp @@ -0,0 +1,56 @@ +#include "pbodocument.h" +#include "validationexception.h" + +namespace pboman3::domain { + PboDocument::PboDocument(QString pboName) { + headers_ = QSharedPointer(new DocumentHeaders); + root_ = QSharedPointer(new PboNode(std::move(pboName), PboNodeType::Container, nullptr)); + setupConnections(); + } + + PboDocument::PboDocument(QString pboName, QList> headers, QByteArray signature) + : signature_(std::move(signature)) { + root_ = QSharedPointer(new PboNode(std::move(pboName), PboNodeType::Container, nullptr)); + headers_ = QSharedPointer(new DocumentHeaders(std::move(headers))); + setupConnections(); + //this is a repository constructor + //it does no validation but must be used only from the persistence layer + } + + DocumentHeaders* PboDocument::headers() const { + return headers_.get(); + } + + PboNode* PboDocument::root() const { + return root_.get(); + } + + const QByteArray& PboDocument::signature() const { + return signature_; + } + + void PboDocument::setSignature(QByteArray signature) { + if (signature.length() != 20) + throw ValidationException("The signature must be 20 bytes long"); + signature_ = std::move(signature); + } + + void PboDocument::setupConnections() { + connect(root_.get(), &PboNode::hierarchyChanged, [this] { + emit changed(); + }); + + connect(root_.get(), &PboNode::titleChanged, [this](const QString& title) { + emit titleChanged(title); + }); + + connect(headers_.get(), &DocumentHeaders::headersChanged, [this]() { + emit changed(); + }); + } + + QDebug& operator<<(QDebug& debug, const PboDocument& document) { + return debug << "PboDocument(Headers=" << *document.headers_ << ", Root=" << *document.root_ << ", Signature=" + << document.signature_.length() << "bytes)"; + } +} diff --git a/pbom/domain/pbodocument.h b/pbom/domain/pbodocument.h new file mode 100644 index 0000000..5bedf33 --- /dev/null +++ b/pbom/domain/pbodocument.h @@ -0,0 +1,37 @@ +#pragma once + +#include "documentheaders.h" +#include "pbonode.h" + +namespace pboman3::domain { + class PboDocument : public QObject { + Q_OBJECT + + public: + PboDocument(QString pboName); + + PboDocument(QString pboName, QList> headers, QByteArray signature);//Repository Ctor + + DocumentHeaders* headers() const; + + PboNode* root() const; + + const QByteArray& signature() const; + + void setSignature(QByteArray signature); + + signals: + void changed(); + + void titleChanged(const QString& title); + + friend QDebug& operator<<(QDebug& debug, const PboDocument& document); + + private: + QSharedPointer headers_; + QSharedPointer root_; + QByteArray signature_; + + void setupConnections(); + }; +} diff --git a/pbom/model/pbonode.cpp b/pbom/domain/pbonode.cpp similarity index 73% rename from pbom/model/pbonode.cpp rename to pbom/domain/pbonode.cpp index 2bf7032..4dbf919 100644 --- a/pbom/model/pbonode.cpp +++ b/pbom/domain/pbonode.cpp @@ -1,14 +1,17 @@ #include "pbonode.h" #include -#include "pbonodeevents.h" -#include "util/exception.h" +#include "exception.h" +#include "validationexception.h" +#include "pbonodetransaction.h" +#include "func.h" -namespace pboman3 { +namespace pboman3::domain { PboNode::PboNode(QString title, PboNodeType nodeType, PboNode* parentNode) - : QObject(), + : AbstractNode(parentNode), nodeType_(nodeType), - title_(std::move(title)), - parentNode_(parentNode) { + title_(std::move(title)) { + if (title_.isEmpty()) + throw ValidationException("Title must not be empty"); } PboNode* PboNode::createHierarchy(const PboPath& entryPath) { @@ -40,70 +43,19 @@ namespace pboman3 { p->emitHierarchyChanged(); } - bool PboNode::isPathConflict(const PboPath& path) const { - const PboNode* node = this; - for (qsizetype i = 0; i < path.length() - 1; i++) { - const PboNode* folder = node->findChild(path.at(i)); - if (folder) { - if (folder->nodeType_ == PboNodeType::File) - return true; - node = folder; - } else { - return false; - } - } - - const PboNode* file = node->findChild(path.last()); - return file; - } - const QString& PboNode::title() const { return title_; } - void PboNode::setTitle(QString title) { - if (title != title_) { - if (const TitleError err = verifyTitle(title); err != nullptr) - throw InvalidOperationException(err); - - title_ = std::move(title); - emit titleChanged(title_); - - if (parentNode_) { - const qsizetype prevIndex = parentNode_->children_.indexOf(this); - const qsizetype newIndex = parentNode_->getChildListIndex(this); - if (prevIndex != newIndex) { - parentNode_->children_.move(prevIndex, newIndex); - emit parentNode_->childMoved(prevIndex, newIndex); - } - - } - emitHierarchyChanged(); - } - } - - TitleError PboNode::verifyTitle(const QString& title) const { - if (title == nullptr || title.isEmpty()) - return "The value can not be empty"; - if (!parentNode_) - return ""; - PboNode* existing = parentNode_->findChild(title); - return existing && existing != this ? "The item with this name already exists" : ""; - } - PboNodeType PboNode::nodeType() const { return nodeType_; } - PboNode* PboNode::parentNode() const { - return parentNode_; - } - PboNode* PboNode::get(const PboPath& path) { PboNode* result = this; auto it = path.begin(); while (it != path.end()) { - result = result->findChild(*it); + result = FindDirectChild(result, *it); if (!result) return nullptr; ++it; @@ -111,24 +63,6 @@ namespace pboman3 { return result; } - PboNode* PboNode::at(qsizetype index) const { - return children_.at(index).get(); - } - - int PboNode::depth() const { - int result = 0; - PboNode* parentNode = parentNode_; - while (parentNode) { - result++; - parentNode = parentNode->parentNode_; - } - return result; - } - - int PboNode::count() const { - return static_cast(children_.count()); - } - PboPath PboNode::makePath() const { PboPath path; path.reserve(depth()); @@ -141,20 +75,8 @@ namespace pboman3 { return path; } - QPointerListIterator PboNode::begin() { - return QPointerListIterator(children_.data()); - } - - QPointerListIterator PboNode::end() { - return QPointerListIterator(children_.data() + children_.count()); - } - - QPointerListConstIterator PboNode::begin() const { - return QPointerListConstIterator(children_.data()); - } - - QPointerListConstIterator PboNode::end() const { - return QPointerListConstIterator(children_.data() + count()); + QSharedPointer PboNode::beginTransaction() { + return QSharedPointer(new PboNodeTransaction(this)); } bool PboNode::operator<(const PboNode& node) const { @@ -179,7 +101,7 @@ namespace pboman3 { bool emitEvents) { PboNode* node = this; for (qsizetype i = 0; i < entryPath.length() - 1; i++) { - PboNode* folder = node->findChild(entryPath.at(i)); + PboNode* folder = FindDirectChild(node, entryPath.at(i)); if (!folder) { folder = node->createChild(entryPath.at(i), PboNodeType::Folder); } else if (folder->nodeType_ == PboNodeType::File) { @@ -203,7 +125,7 @@ namespace pboman3 { node = folder; } - PboNode* file = node->findChild(entryPath.last()); + PboNode* file = FindDirectChild(node, entryPath.last()); if (!file) { file = node->createChild(entryPath.last(), PboNodeType::File); if (emitEvents) @@ -232,14 +154,6 @@ namespace pboman3 { return file; } - PboNode* PboNode::findChild(const QString& title) const { - for (const QSharedPointer& child : children_) { - if (child->title_ == title) - return child.get(); - } - return nullptr; - } - QString PboNode::pickFolderTitle(const PboNode* parent, const QString& expectedTitle) const { int index = 1; QString attemptTitle = formatFolderTitleCopy(expectedTitle, index); @@ -312,7 +226,29 @@ namespace pboman3 { emit parent->hierarchyChanged(); } + void PboNode::setTitle(QString title) { + if (title != title_) { + + title_ = std::move(title); + emit titleChanged(title_); + + if (parentNode_) { + const qsizetype prevIndex = parentNode_->children_.indexOf(this); + const qsizetype newIndex = parentNode_->getChildListIndex(this); + if (prevIndex != newIndex) { + parentNode_->children_.move(prevIndex, newIndex); + emit parentNode_->childMoved(prevIndex, newIndex); + } + + } + emitHierarchyChanged(); + } + } + QDebug operator<<(QDebug debug, const PboNode& node) { - return debug << "PboNode(" << node.makePath() << ")"; + return node.parentNode_ + ? debug << "PboNode(Path=" << node.makePath() << ")" + : debug << "PboNode(RootTitle=" << node.title_ << ")"; + } } diff --git a/pbom/model/pbonode.h b/pbom/domain/pbonode.h similarity index 67% rename from pbom/model/pbonode.h rename to pbom/domain/pbonode.h index 30d44db..b505384 100644 --- a/pbom/model/pbonode.h +++ b/pbom/domain/pbonode.h @@ -4,13 +4,13 @@ #include "pbonodetype.h" #include "pbopath.h" #include "conflictresolution.h" -#include "io/bs/binarysource.h" -#include "util/qpointerlistiterator.h" +#include "binarysource.h" +#include "abstractnode.h" -namespace pboman3 { - typedef QString TitleError; +namespace pboman3::domain { + class PboNodeTransaction; - class PboNode final : public QObject { + class PboNode final : public AbstractNode { Q_OBJECT public: @@ -24,35 +24,15 @@ namespace pboman3 { void removeFromHierarchy(); - bool isPathConflict(const PboPath& path) const; - const QString& title() const; - void setTitle(QString title); - - TitleError verifyTitle(const QString& title) const; - PboNodeType nodeType() const; - PboNode* parentNode() const; - PboNode* get(const PboPath& path); - PboNode* at(qsizetype index) const; - - int depth() const; - - int count() const; - PboPath makePath() const; - QPointerListIterator begin(); - - QPointerListIterator end(); - - QPointerListConstIterator begin() const; - - QPointerListConstIterator end() const; + QSharedPointer beginTransaction(); bool operator <(const PboNode& node) const; @@ -72,13 +52,9 @@ namespace pboman3 { private: PboNodeType nodeType_; QString title_; - PboNode* parentNode_; - QList> children_; PboNode* createHierarchy(const PboPath& entryPath, const ConflictResolution& onConflict, bool emitEvents); - PboNode* findChild(const QString& title) const; - QString pickFolderTitle(const PboNode* parent, const QString& expectedTitle) const; QString pickFileTitle(const PboNode* parent, const QString& expectedTitle) const; @@ -92,5 +68,9 @@ namespace pboman3 { qsizetype getChildListIndex(const PboNode* node) const; void emitHierarchyChanged(); + + void setTitle(QString title); + + friend PboNodeTransaction; }; } diff --git a/pbom/domain/pbonodetransaction.cpp b/pbom/domain/pbonodetransaction.cpp new file mode 100644 index 0000000..15ef83a --- /dev/null +++ b/pbom/domain/pbonodetransaction.cpp @@ -0,0 +1,46 @@ +#include "pbonodetransaction.h" +#include "validationexception.h" +#include "exception.h" +#include "domain/func.h" + +namespace pboman3::domain { + PboNodeTransaction::PboNodeTransaction(PboNode* node) + : committed_(false), + node_(node), + title_(node->title()) { + } + + PboNodeTransaction::~PboNodeTransaction() { + if (committed_) + node_->setTitle(std::move(title_)); + } + + const QString& PboNodeTransaction::title() const { + return title_; + } + + void PboNodeTransaction::setTitle(QString title) { + if (committed_) + throw InvalidOperationException("The transaction has already committed"); + + if (title != title_) { + if (const QString err = verifyTitle(title); !err.isEmpty()) + throw ValidationException(err); + } + + title_ = std::move(title); + } + + void PboNodeTransaction::commit() { + committed_ = true; + } + + QString PboNodeTransaction::verifyTitle(const QString& title) const { + if (title.isEmpty()) + return "The value can not be empty"; + if (!node_->parentNode_) + return ""; + PboNode* existing = FindDirectChild(node_->parentNode_, title); + return existing && existing != node_ ? "The item with this name already exists" : ""; + } +} diff --git a/pbom/domain/pbonodetransaction.h b/pbom/domain/pbonodetransaction.h new file mode 100644 index 0000000..9aee712 --- /dev/null +++ b/pbom/domain/pbonodetransaction.h @@ -0,0 +1,24 @@ +#pragma once + +#include "pbonode.h" + +namespace pboman3::domain { + class PboNodeTransaction { + public: + PboNodeTransaction(PboNode* node); + + ~PboNodeTransaction(); + + const QString& title() const; + + void setTitle(QString title); + + void commit(); + private: + bool committed_; + PboNode* node_; + QString title_; + + QString verifyTitle(const QString& title) const; + }; +} diff --git a/pbom/model/pbonodetype.h b/pbom/domain/pbonodetype.h similarity index 72% rename from pbom/model/pbonodetype.h rename to pbom/domain/pbonodetype.h index 4fd15e5..c16b1a5 100644 --- a/pbom/model/pbonodetype.h +++ b/pbom/domain/pbonodetype.h @@ -1,6 +1,6 @@ #pragma once -namespace pboman3 { +namespace pboman3::domain { enum class PboNodeType { File, Folder, diff --git a/pbom/model/pbopath.cpp b/pbom/domain/pbopath.cpp similarity index 94% rename from pbom/model/pbopath.cpp rename to pbom/domain/pbopath.cpp index 36889cd..bca2d83 100644 --- a/pbom/model/pbopath.cpp +++ b/pbom/domain/pbopath.cpp @@ -1,7 +1,7 @@ #include "pbopath.h" #include -namespace pboman3 { +namespace pboman3::domain { PboPath::PboPath() : QList() { } diff --git a/pbom/model/pbopath.h b/pbom/domain/pbopath.h similarity index 94% rename from pbom/model/pbopath.h rename to pbom/domain/pbopath.h index fbf861b..8da678d 100644 --- a/pbom/model/pbopath.h +++ b/pbom/domain/pbopath.h @@ -2,7 +2,7 @@ #include -namespace pboman3 { +namespace pboman3::domain { class PboPath : public QList { public: PboPath(); diff --git a/pbom/domain/validationexception.cpp b/pbom/domain/validationexception.cpp new file mode 100644 index 0000000..b6e1521 --- /dev/null +++ b/pbom/domain/validationexception.cpp @@ -0,0 +1,19 @@ +#include "validationexception.h" + +namespace pboman3::domain { + ValidationException::ValidationException(QString message) + : AppException(std::move(message)) { + } + + QDebug operator<<(QDebug debug, const ValidationException& ex) { + return debug << "ValidationException(" << ex.message_ << ")"; + } + + void ValidationException::raise() const { + throw* this; + } + + QException* ValidationException::clone() const { + return new ValidationException(*this); + } +} diff --git a/pbom/domain/validationexception.h b/pbom/domain/validationexception.h new file mode 100644 index 0000000..755f609 --- /dev/null +++ b/pbom/domain/validationexception.h @@ -0,0 +1,17 @@ +#pragma once + +#include "exception.h" +#include + +namespace pboman3::domain { + class ValidationException : public AppException { + public: + ValidationException(QString message); + + friend QDebug operator<<(QDebug debug, const ValidationException& ex); + + void raise() const override; + + QException* clone() const override; + }; +} diff --git a/pbom/util/exception.cpp b/pbom/exception.cpp similarity index 100% rename from pbom/util/exception.cpp rename to pbom/exception.cpp diff --git a/pbom/util/exception.h b/pbom/exception.h similarity index 100% rename from pbom/util/exception.h rename to pbom/exception.h diff --git a/pbom/io/CMakeLists.txt b/pbom/io/CMakeLists.txt new file mode 100644 index 0000000..a20f292 --- /dev/null +++ b/pbom/io/CMakeLists.txt @@ -0,0 +1,51 @@ +list(APPEND PROJECT_SOURCES + "io/bb/binarybackend.cpp" + "io/bb/execbackend.cpp" + "io/bb/nodefilesystem.cpp" + "io/bb/sanitizedstring.cpp" + "io/bb/tempbackend.cpp" + "io/bb/unpackbackend.cpp" + "io/bb/unpacktaskbackend.cpp" + "io/bs/abstractbinarysource.cpp" + "io/bs/fslzhbinarysource.cpp" + "io/bs/fsrawbinarysource.cpp" + "io/bs/pbobinarysource.cpp" + "io/lzh/compressionbuffer.cpp" + "io/lzh/compressionchunk.cpp" + "io/lzh/decompressioncontext.cpp" + "io/lzh/lzh.cpp" + "io/lzh/lzhdecompressionexception.cpp" + "io/diskaccessexception.cpp" + "io/documentreader.cpp" + "io/documentwriter.cpp" + "io/pbodatastream.cpp" + "io/pbofile.cpp" + "io/pbofileformatexception.cpp" + "io/pboheaderentity.cpp" + "io/pboheaderio.cpp" + "io/pboheaderreader.cpp" + "io/pbonodeentity.cpp") + +set(PROJECT_SOURCES ${PROJECT_SOURCES} PARENT_SCOPE) + +list(APPEND TEST_SOURCES + "io/bb/__test__/execbackend_test.cpp" + "io/bb/__test__/nodefilesystem_test.cpp" + "io/bb/__test__/sanitizedstring_test.cpp" + "io/bb/__test__/tempbackend_test.cpp" + "io/bb/__test__/unpackbackend_test.cpp" + "io/bs/__test__/fslzhbinarysource_test.cpp" + "io/bs/__test__/fsrawbinarysource_test.cpp" + "io/bs/__test__/pbobinarysource_test.cpp" + "io/lzh/__test__/compressionbuffer_test.cpp" + "io/lzh/__test__/compressionchunk_test.cpp" + "io/lzh/__test__/lzh_test.cpp" + "io/__test__/documentreader_test.cpp" + "io/__test__/documentwriter_test.cpp" + "io/__test__/pbofile_test.cpp" + "io/__test__/pboheaderentity_test.cpp" + "io/__test__/pboheaderio_test.cpp" + "io/__test__/pboheaderreader_test.cpp" + "io/__test__/pbonodeentity_test.cpp") + +set(TEST_SOURCES ${TEST_SOURCES} PARENT_SCOPE) diff --git a/pbom/io/__test__/documentreader_test.cpp b/pbom/io/__test__/documentreader_test.cpp new file mode 100644 index 0000000..1ebbcaf --- /dev/null +++ b/pbom/io/__test__/documentreader_test.cpp @@ -0,0 +1,82 @@ +#include +#include "io/documentreader.h" +#include +#include "io/pbofile.h" +#include "io/pboheaderio.h" +#include +#include "io/bs/pbobinarysource.h" + +namespace pboman3::io::test { + TEST(PboReaderTest, Read_Reads_File) { + //build a mock pbo file + QTemporaryFile t; + t.open(); + + PboFile p(t.fileName()); + p.open(QIODeviceBase::WriteOnly); + const PboHeaderIO io(&p); + + const PboNodeEntity e0 = PboNodeEntity::makeSignature(); + const PboNodeEntity e1("f1", PboPackingMethod::Packed, 0x01010101, 0x02020202, + 0x03030303, 5); + const PboNodeEntity e2("f2", PboPackingMethod::Uncompressed, 0x05050505, 0x06060606, + 0x07070707, 10); + const PboNodeEntity e3 = PboNodeEntity::makeBoundary(); + + const PboHeaderEntity h1("p1", "v1"); + const PboHeaderEntity h2("p2", "v2"); + const PboHeaderEntity h3 = PboHeaderEntity::makeBoundary(); + + const QByteArray signature(20, 5); + + io.writeEntry(e0); + io.writeHeader(h1); + io.writeHeader(h2); + io.writeHeader(h3); + io.writeEntry(e1); + io.writeEntry(e2); + io.writeEntry(e3); + p.write(QByteArray(e1.dataSize(), 1)); + p.write(QByteArray(e2.dataSize(), 2)); + p.write(QByteArray(1, 0)); //zero byte between data and signature + p.write(signature); + + p.close(); + t.close(); + + //call the method + const DocumentReader reader(t.fileName()); + const QSharedPointer document = reader.read(); + + //verify the results + ASSERT_TRUE(document); + + ASSERT_EQ(document->headers()->count(), 2); + ASSERT_EQ(document->headers()->at(0)->name(), "p1"); + ASSERT_EQ(document->headers()->at(0)->value(), "v1"); + ASSERT_EQ(document->headers()->at(1)->name(), "p2"); + ASSERT_EQ(document->headers()->at(1)->value(), "v2"); + + QFileInfo fi(t.fileName()); + ASSERT_TRUE(document->root()); + ASSERT_EQ(document->root()->count(), 2); + ASSERT_EQ(document->root()->title(), fi.fileName()); + ASSERT_EQ(document->root()->nodeType(), PboNodeType::Container); + ASSERT_FALSE(document->root()->parentNode()); + + ASSERT_EQ(document->root()->at(0)->title(), "f1"); + ASSERT_EQ(document->root()->at(0)->nodeType(), PboNodeType::File); + const auto bs1 = dynamic_cast(document->root()->at(0)->binarySource.get()); + ASSERT_EQ(bs1->getInfo().compressed, true); + ASSERT_EQ(bs1->getInfo().originalSize, 0x01010101); + ASSERT_EQ(bs1->getInfo().timestamp, 0x03030303); + ASSERT_EQ(bs1->getInfo().dataSize, 5); + ASSERT_EQ(bs1->getInfo().dataOffset, 101);//where the actual data begins + const auto bs2 = dynamic_cast(document->root()->at(1)->binarySource.get()); + ASSERT_EQ(bs2->getInfo().compressed, false); + ASSERT_EQ(bs2->getInfo().originalSize, 0x05050505); + ASSERT_EQ(bs2->getInfo().timestamp, 0x07070707); + ASSERT_EQ(bs2->getInfo().dataSize, 10); + ASSERT_EQ(bs2->getInfo().dataOffset, 106);//bs1.dataOffset + bs1.dataSize + } +} diff --git a/pbom/io/__test__/documentwriter_test.cpp b/pbom/io/__test__/documentwriter_test.cpp new file mode 100644 index 0000000..6493c62 --- /dev/null +++ b/pbom/io/__test__/documentwriter_test.cpp @@ -0,0 +1,284 @@ +#include "io/documentwriter.h" +#include +#include +#include +#include "domain/pbodocument.h" +#include "domain/documentheaderstransaction.h" +#include "io/pboheaderreader.h" +#include "io/bs/fsrawbinarysource.h" + +namespace pboman3::io::test { + using namespace domain; + TEST(DocumentWriterTest, Write_Writes_File_WithHeaders) { + //mock files contents + const QByteArray mockContent1(15, 1); + QTemporaryFile e1; + e1.open(); + e1.write(mockContent1); + e1.close(); + + const QByteArray mockContent2(10, 2); + QTemporaryFile e2; + e2.open(); + e2.write(mockContent2); + e2.close(); + + //pbo document + PboDocument document("file.pbo"); + + //pbo headers + QSharedPointer tran = document.headers()->beginTransaction(); + tran->add("h1", "v1"); + tran->add("h2", "v2"); + tran->commit(); + tran.clear(); + + //pbo entries with content + PboNode* n1 = document.root()->createHierarchy(PboPath("e1.txt")); + n1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); + n1->binarySource->open(); + PboNode* n2 = document.root()->createHierarchy(PboPath("f2/e2.txt")); + n2->binarySource = QSharedPointer(new FsRawBinarySource(e2.fileName())); + n2->binarySource->open(); + + //write the file + const QTemporaryDir temp; + const QString filePath = temp.filePath("file.pbo"); + + DocumentWriter writer(filePath); + writer.write(&document, []() { return false; }); + + //assert the result + PboFile pbo(filePath); + pbo.open(QIODeviceBase::ReadOnly); + const PboFileHeader header = PboHeaderReader::readFileHeader(&pbo); + + //pbo header + ASSERT_EQ(header.headers.count(), 2); + ASSERT_EQ(header.headers.at(0)->name, "h1"); + ASSERT_EQ(header.headers.at(0)->value, "v1"); + ASSERT_EQ(header.headers.at(1)->name, "h2"); + ASSERT_EQ(header.headers.at(1)->value, "v2"); + + ASSERT_EQ(header.entries.count(), 2); + ASSERT_EQ(header.entries.at(0)->fileName(), "f2/e2.txt"); + ASSERT_EQ(header.entries.at(1)->fileName(), "e1.txt"); + + //pbo contents + pbo.seek(header.dataBlockStart); + + QByteArray contents; + contents.resize(mockContent2.size()); + pbo.read(contents.data(), contents.size()); + ASSERT_EQ(contents, mockContent2); + + contents.resize(mockContent1.size()); + pbo.read(contents.data(), contents.size()); + ASSERT_EQ(contents, mockContent1); + + //zero byte + QByteArray zero; + zero.resize(1); + pbo.read(zero.data(), zero.size()); + ASSERT_EQ(zero.at(0), 0); + + //signature + ASSERT_EQ(document.signature().size(), 20);//sha1 is 20 bytes + contents.resize(document.signature().size()); + pbo.read(contents.data(), contents.size()); + ASSERT_EQ(contents.size(), document.signature().size()); + + //file has ended + ASSERT_TRUE(pbo.atEnd()); + } + + class DocumentWriterTest : public testing::TestWithParam {}; + + TEST_P(DocumentWriterTest, Write_Cleans_On_Cancel_When_Writing_New_File) { + //mock files contents + const QByteArray mockContent1(15, 1); + QTemporaryFile e1; + e1.open(); + e1.write(mockContent1); + e1.close(); + + //pbo document with content + PboDocument document("file.pbo"); + PboNode* n1 = document.root()->createHierarchy(PboPath("e1.txt")); + n1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); + n1->binarySource->open(); + + //pbo file + const QTemporaryDir temp; + const QString filePath = temp.filePath("file.pbo"); + + //call the method + int count = 0; + int expectedHitCount = GetParam();//experimental way + DocumentWriter writer(filePath); + writer.write(&document, [&count, expectedHitCount]() { count++; return count > expectedHitCount - 1; }); + + ASSERT_FALSE(QFileInfo(filePath).exists()); + ASSERT_FALSE(QFileInfo(filePath + ".b").exists()); + } + + INSTANTIATE_TEST_SUITE_P(Write_Cleans_On_Cancel_When_Writing_New_File, DocumentWriterTest, testing::Range(1, 16)); + + TEST_P(DocumentWriterTest, Write_Cleans_On_Cancel_When_Rewriting_Existing_File) { + //mock files contents + const QByteArray mockContent1(15, 1); + QTemporaryFile e1; + e1.open(); + e1.write(mockContent1); + e1.close(); + + //existing pbo file + constexpr int origPboSize = 100; + const QByteArray origPboContent(origPboSize, '1'); + const QTemporaryDir temp; + const QString filePath = temp.filePath("file.pbo"); + QFile origPbo(filePath); + origPbo.open(QIODeviceBase::ReadWrite); + origPbo.write(origPboContent); + origPbo.close(); + + //pbo document with content + PboDocument document("file.pbo"); + PboNode* n1 = document.root()->createHierarchy(PboPath("e1.txt")); + n1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); + n1->binarySource->open(); + + //call the method + int count = 0; + int expectedHitCount = GetParam();//experimental way + DocumentWriter writer(filePath); + writer.write(&document, [&count, expectedHitCount]() { count++; return count > expectedHitCount - 1; }); + + ASSERT_FALSE(QFileInfo(filePath + ".b").exists());//no temp file + + QFile prevPbo(filePath); + ASSERT_TRUE(prevPbo.exists());//the original file is in place and has its content + prevPbo.open(QIODeviceBase::ReadWrite); + const QByteArray prevPboContent = prevPbo.read(origPboSize); + prevPbo.close(); + ASSERT_EQ(prevPboContent, origPboContent); + } + + INSTANTIATE_TEST_SUITE_P(Write_Cleans_On_Cancel_When_Rewriting_Existing_File, DocumentWriterTest, testing::Range(1, 16)); + + TEST(DocumentWriterTest, Write_Cleans_Temporary_Files_On_Write) { + //mock files contents + const QByteArray mockContent1(15, 1); + QTemporaryFile e1; + e1.open(); + e1.write(mockContent1); + e1.close(); + + //pbo file + const QTemporaryDir temp; + const QString filePath = temp.filePath("file.pbo"); + + //pbo content structure + PboDocument document("file.pbo"); + PboNode* n1 = document.root()->createHierarchy(PboPath("e1.txt")); + n1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); + n1->binarySource->open(); + + //call the method + DocumentWriter writer(filePath); + writer.write(&document, []() { return false; }); + + //ensure no junk left + ASSERT_TRUE(QFileInfo(filePath).exists()); + ASSERT_FALSE(QFileInfo(filePath + ".b").exists()); + } + + TEST_P(DocumentWriterTest, Write_Leaves_Binary_Sources_Open_If_Cancelled) { + //mock files contents + const QByteArray mockContent1(15, 1); + QTemporaryFile e1; + e1.open(); + e1.write(mockContent1); + e1.close(); + + //pbo file + const QTemporaryDir temp; + const QString filePath = temp.filePath("file.pbo"); + + //pbo content structure + PboDocument document("file.pbo"); + PboNode* n1 = document.root()->createHierarchy(PboPath("e1.txt")); + n1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); + n1->binarySource->open(); + + //call the method + int count = 0; + int expectedHitCount = GetParam();//experimental way + DocumentWriter writer(filePath); + writer.write(&document, [&count, expectedHitCount]() { count++; return count > expectedHitCount - 1; }); + + //ensure bs left open + ASSERT_TRUE(n1->binarySource->isOpen()); + } + + INSTANTIATE_TEST_SUITE_P(Write_Leaves_Binary_Sources_Open_If_Cancelled, DocumentWriterTest, testing::Range(1, 16)); + + TEST(DocumentWriterTest, Write_Opens_Binary_Sources_After_Write) { + //mock files contents + const QByteArray mockContent1(15, 1); + QTemporaryFile e1; + e1.open(); + e1.write(mockContent1); + e1.close(); + + //pbo file + const QTemporaryDir temp; + const QString filePath = temp.filePath("file.pbo"); + + //pbo content structure + PboDocument document("file.pbo"); + PboNode* n1 = document.root()->createHierarchy(PboPath("e1.txt")); + n1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); + n1->binarySource->open(); + + //call the method + DocumentWriter writer(filePath); + writer.write(&document, []() { return false; }); + + //ensure no junk left + ASSERT_TRUE(n1->binarySource->isOpen()); + } + + TEST(DocumentWriterTest, Write_Replaces_Existing_File) { + //mock files contents + const QByteArray mockContent1(15, 1); + QTemporaryFile e1; + e1.open(); + e1.write(mockContent1); + e1.close(); + + //pbo file + const QTemporaryDir temp; + QFile existingFile(temp.filePath("file.pbo")); + existingFile.open(QIODeviceBase::WriteOnly); + existingFile.write(QByteArray("some content")); + existingFile.close(); + + //pbo content structure + PboDocument document("file.pbo"); + PboNode* n1 = document.root()->createHierarchy(PboPath("e1.txt")); + n1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); + n1->binarySource->open(); + + //call the method + DocumentWriter writer(existingFile.fileName()); + writer.write(&document, []() { return false; }); + + //ensure files in place + ASSERT_TRUE(QFileInfo(existingFile.fileName()).exists()); + ASSERT_EQ(QFileInfo(existingFile.fileName()).size(), 106);//pbo written + ASSERT_TRUE(QFileInfo(existingFile.fileName() + ".bak").exists()); + ASSERT_EQ(QFileInfo(existingFile.fileName() + ".bak").size(), 12);//the original file + } + +} diff --git a/pbom/io/__test__/pbofile_test.cpp b/pbom/io/__test__/pbofile_test.cpp index 6031721..f7a43de 100644 --- a/pbom/io/__test__/pbofile_test.cpp +++ b/pbom/io/__test__/pbofile_test.cpp @@ -2,7 +2,7 @@ #include #include -namespace pboman3::test { +namespace pboman3::io::test { TEST(PboFileTest, ReadCString_Reads_Zero_Terminated_String) { static const char* mockString1 = "some string value 11"; static const char* mockString2 = "some string value 22"; diff --git a/pbom/io/__test__/pboheaderentity_test.cpp b/pbom/io/__test__/pboheaderentity_test.cpp new file mode 100644 index 0000000..cc593ba --- /dev/null +++ b/pbom/io/__test__/pboheaderentity_test.cpp @@ -0,0 +1,17 @@ +#include +#include "io/pboheaderentity.h" + +namespace pboman3::io::test { + TEST(PboHeaderEntityTest, Ctor_Functional) { + const PboHeaderEntity header("name1", "value1"); + ASSERT_EQ(header.name, "name1"); + ASSERT_EQ(header.value, "value1"); + + ASSERT_FALSE(header.isBoundary()); + } + + TEST(PboHeaderEntityTest, IsBoundary_Functional) { + const PboHeaderEntity header = PboHeaderEntity::makeBoundary(); + ASSERT_TRUE(header.isBoundary()); + } +} diff --git a/pbom/io/__test__/pboheaderio_test.cpp b/pbom/io/__test__/pboheaderio_test.cpp index 5a3cfab..e221a5b 100644 --- a/pbom/io/__test__/pboheaderio_test.cpp +++ b/pbom/io/__test__/pboheaderio_test.cpp @@ -4,7 +4,7 @@ #include #include -namespace pboman3::test { +namespace pboman3::io::test { // ReSharper disable once CppInconsistentNaming class PboHeaderIOTest_ReadNextEntry : public testing::TestWithParam { }; @@ -25,7 +25,7 @@ namespace pboman3::test { const PboHeaderIO io(&f); - const QSharedPointer e = io.readNextEntry(); + const QSharedPointer e = io.readNextEntry(); ASSERT_FALSE(e); } @@ -45,7 +45,7 @@ namespace pboman3::test { const PboHeaderIO io(&f); - const QSharedPointer e = io.readNextEntry(); + const QSharedPointer e = io.readNextEntry(); ASSERT_TRUE(e); ASSERT_TRUE(e->isBoundary()); } @@ -70,15 +70,15 @@ namespace pboman3::test { const PboHeaderIO io(&f); - const QSharedPointer e = io.readNextHeader(); + const QSharedPointer e = io.readNextHeader(); ASSERT_FALSE(e); } INSTANTIATE_TEST_SUITE_P(ReadNextHeader, PboHeaderIOTest_ReadNextHeader, testing::Values(0)); TEST(PboHeaderIOTest, WriteEntry_Writes) { - const PboEntry e1("some-file1", PboPackingMethod::Packed, 0x01010101, 0x02020202, 0x03030303, 0x04040404); - const PboEntry e2("some-file2", PboPackingMethod::Uncompressed, 0x05050505, 0x06060606, 0x07070707, 0x08080808); + const PboNodeEntity e1("some-file1", PboPackingMethod::Packed, 0x01010101, 0x02020202, 0x03030303, 0x04040404); + const PboNodeEntity e2("some-file2", PboPackingMethod::Uncompressed, 0x05050505, 0x06060606, 0x07070707, 0x08080808); QTemporaryFile t; ASSERT_TRUE(t.open()); @@ -122,9 +122,9 @@ namespace pboman3::test { } TEST(PboHeaderIOTest, WriteHeader_Writes) { - const PboHeader h1("h1", "v1"); - const PboHeader h2("h2", "v2"); - const PboHeader h3 = PboHeader::makeBoundary(); + const PboHeaderEntity h1("h1", "v1"); + const PboHeaderEntity h2("h2", "v2"); + const PboHeaderEntity h3 = PboHeaderEntity::makeBoundary(); QTemporaryFile t; ASSERT_TRUE(t.open()); diff --git a/pbom/io/__test__/pboheaderreader_test.cpp b/pbom/io/__test__/pboheaderreader_test.cpp index fee0d4f..0802950 100644 --- a/pbom/io/__test__/pboheaderreader_test.cpp +++ b/pbom/io/__test__/pboheaderreader_test.cpp @@ -3,9 +3,9 @@ #include #include "io/pbofile.h" #include "io/pboheaderio.h" -#include "io/pboioexception.h" +#include "io/pbofileformatexception.h" -namespace pboman3::test { +namespace pboman3::io::test { TEST(PboHeaderReaderTest, ReadFileHeader_Reads_File_Without_Headers_Without_Signature) { //build a mock pbo file QTemporaryFile t; @@ -14,9 +14,9 @@ namespace pboman3::test { PboFile p(t.fileName()); p.open(QIODeviceBase::WriteOnly); const PboHeaderIO io(&p); - const PboEntry e1("f1", PboPackingMethod::Packed, 0x01010101, 0x02020202, + const PboNodeEntity e1("f1", PboPackingMethod::Packed, 0x01010101, 0x02020202, 0x03030303, 0x04040404); - const PboEntry e2 = PboEntry::makeBoundary(); + const PboNodeEntity e2 = PboNodeEntity::makeBoundary(); io.writeEntry(e1); io.writeEntry(e2); @@ -53,9 +53,9 @@ namespace pboman3::test { PboFile p(t.fileName()); p.open(QIODeviceBase::WriteOnly); const PboHeaderIO io(&p); - const PboEntry e1("f1", PboPackingMethod::Packed, 0x01010101, 0x02020202, + const PboNodeEntity e1("f1", PboPackingMethod::Packed, 0x01010101, 0x02020202, 0x03030303, 10); - const PboEntry e2 = PboEntry::makeBoundary(); + const PboNodeEntity e2 = PboNodeEntity::makeBoundary(); const QByteArray signature(20, 5); io.writeEntry(e1); @@ -97,16 +97,16 @@ namespace pboman3::test { p.open(QIODeviceBase::WriteOnly); const PboHeaderIO io(&p); - const PboEntry e0 = PboEntry::makeSignature(); - const PboEntry e1("f1", PboPackingMethod::Packed, 0x01010101, 0x02020202, + const PboNodeEntity e0 = PboNodeEntity::makeSignature(); + const PboNodeEntity e1("f1", PboPackingMethod::Packed, 0x01010101, 0x02020202, 0x03030303, 5); - const PboEntry e2("f2", PboPackingMethod::Uncompressed, 0x05050505, 0x06060606, + const PboNodeEntity e2("f2", PboPackingMethod::Uncompressed, 0x05050505, 0x06060606, 0x07070707, 10); - const PboEntry e3 = PboEntry::makeBoundary(); + const PboNodeEntity e3 = PboNodeEntity::makeBoundary(); - const PboHeader h1("p1", "v1"); - const PboHeader h2("p2", "v2"); - const PboHeader h3 = PboHeader::makeBoundary(); + const PboHeaderEntity h1("p1", "v1"); + const PboHeaderEntity h2("p2", "v2"); + const PboHeaderEntity h3 = PboHeaderEntity::makeBoundary(); const QByteArray signature(20, 5); @@ -165,9 +165,9 @@ namespace pboman3::test { PboFile p(t.fileName()); p.open(QIODeviceBase::WriteOnly); const PboHeaderIO io(&p); - const PboEntry e1("f1", PboPackingMethod::Packed, 0x01010101, 0x02020202, + const PboNodeEntity e1("f1", PboPackingMethod::Packed, 0x01010101, 0x02020202, 0x03030303, 10); - const PboEntry e2 = PboEntry::makeBoundary(); + const PboNodeEntity e2 = PboNodeEntity::makeBoundary(); io.writeEntry(e1); io.writeEntry(e2); @@ -201,7 +201,7 @@ namespace pboman3::test { //call the method PboFile p(t.fileName()); p.open(QIODeviceBase::ReadOnly); - ASSERT_THROW(PboHeaderReader::readFileHeader(&p), PboIoException); + ASSERT_THROW(PboHeaderReader::readFileHeader(&p), PboFileFormatException); p.close(); } @@ -215,7 +215,7 @@ namespace pboman3::test { //call the method PboFile p(t.fileName()); p.open(QIODeviceBase::ReadOnly); - ASSERT_THROW(PboHeaderReader::readFileHeader(&p), PboIoException); + ASSERT_THROW(PboHeaderReader::readFileHeader(&p), PboFileFormatException); p.close(); } @@ -227,15 +227,15 @@ namespace pboman3::test { PboFile p(t.fileName()); p.open(QIODeviceBase::WriteOnly); const PboHeaderIO io(&p); - io.writeEntry(PboEntry::makeSignature()); - io.writeHeader(PboHeader("p1", "v1")); + io.writeEntry(PboNodeEntity::makeSignature()); + io.writeHeader(PboHeaderEntity("p1", "v1")); p.close(); t.close(); //call the method PboFile r(t.fileName()); r.open(QIODeviceBase::ReadOnly); - ASSERT_THROW(PboHeaderReader::readFileHeader(&r), PboIoException); + ASSERT_THROW(PboHeaderReader::readFileHeader(&r), PboFileFormatException); r.close(); } @@ -248,12 +248,12 @@ namespace pboman3::test { p.open(QIODeviceBase::WriteOnly); const PboHeaderIO io(&p); - const PboEntry e0 = PboEntry::makeSignature(); - const PboEntry e1("f1", PboPackingMethod::Packed, 0x01010101, 0x02020202, + const PboNodeEntity e0 = PboNodeEntity::makeSignature(); + const PboNodeEntity e1("f1", PboPackingMethod::Packed, 0x01010101, 0x02020202, 0x03030303, 0x04040404); - const PboHeader h1("p1", "v1"); - const PboHeader h2 = PboHeader::makeBoundary(); + const PboHeaderEntity h1("p1", "v1"); + const PboHeaderEntity h2 = PboHeaderEntity::makeBoundary(); io.writeEntry(e0); io.writeHeader(h1); @@ -266,7 +266,7 @@ namespace pboman3::test { //call the method PboFile r(t.fileName()); r.open(QIODeviceBase::ReadOnly); - ASSERT_THROW(PboHeaderReader::readFileHeader(&r), PboIoException); + ASSERT_THROW(PboHeaderReader::readFileHeader(&r), PboFileFormatException); r.close(); } } diff --git a/pbom/io/__test__/pbonodeentity_test.cpp b/pbom/io/__test__/pbonodeentity_test.cpp new file mode 100644 index 0000000..084db50 --- /dev/null +++ b/pbom/io/__test__/pbonodeentity_test.cpp @@ -0,0 +1,52 @@ +#include +#include "io/pbonodeentity.h" + +namespace pboman3::io::test { + TEST(PboNodeEntityTest, Ctor_Functional) { + const PboNodeEntity entry("some-file", PboPackingMethod::Packed, 1, 2, 3, 4); + ASSERT_EQ(entry.fileName(), "some-file"); + ASSERT_EQ(entry.packingMethod(), PboPackingMethod::Packed); + ASSERT_EQ(entry.originalSize(), 1); + ASSERT_EQ(entry.reserved(), 2); + ASSERT_EQ(entry.timestamp(), 3); + ASSERT_EQ(entry.dataSize(), 4); + } + + TEST(PboNodeEntityTest, IsBoundary_Functional) { + const PboNodeEntity entry = PboNodeEntity::makeBoundary(); + ASSERT_TRUE(entry.isBoundary()); + ASSERT_FALSE(entry.isContent()); + } + + TEST(PboNodeEntityTest, IsSignature_Functional) { + const PboNodeEntity entry = PboNodeEntity::makeSignature(); + ASSERT_TRUE(entry.isSignature()); + ASSERT_FALSE(entry.isContent()); + } + + // ReSharper disable once CppInconsistentNaming + class PboNodeEntityTest_IsContent : public testing::TestWithParam { + }; + + TEST_P(PboNodeEntityTest_IsContent, Functional) { + const PboNodeEntity entry("some-file", GetParam(), 100, 0, 0, 100); + ASSERT_FALSE(entry.isSignature()); + ASSERT_FALSE(entry.isBoundary()); + ASSERT_TRUE(entry.isContent()); + } + + INSTANTIATE_TEST_SUITE_P(IsContent, PboNodeEntityTest_IsContent, + testing::Values(PboPackingMethod::Packed, PboPackingMethod::Uncompressed)); + + TEST(PboNodeEntityTest, Size_Functional) { + const PboNodeEntity entry("some-file", PboPackingMethod::Packed, 1, 2, 3, 4); + ASSERT_EQ(entry.size(), entry.fileName().size() + 21); + } + + TEST(PboNodeEntityTest, IsCompressed_Functional) { + const PboNodeEntity entry1("some-file", PboPackingMethod::Packed, 1, 2, 3, 4); + ASSERT_TRUE(entry1.isCompressed()); + const PboNodeEntity entry2("some-file", PboPackingMethod::Uncompressed, 1, 2, 3, 1); + ASSERT_FALSE(entry2.isCompressed()); + } +} diff --git a/pbom/io/__test__/pbowriter_test.cpp b/pbom/io/__test__/pbowriter_test.cpp deleted file mode 100644 index c828788..0000000 --- a/pbom/io/__test__/pbowriter_test.cpp +++ /dev/null @@ -1,242 +0,0 @@ -#include "io/pbowriter.h" -#include -#include -#include "io/bs/fsrawbinarysource.h" -#include -#include -#include "io/pboheaderreader.h" - -namespace pboman3::test { - TEST(PboWriterTest, Write_Writes_File_With_Headers) { - //mock files contents - const QByteArray mockContent1(15, 1); - QTemporaryFile e1; - e1.open(); - e1.write(mockContent1); - e1.close(); - - const QByteArray mockContent2(10, 2); - QTemporaryFile e2; - e2.open(); - e2.write(mockContent2); - e2.close(); - - //pbo file - const QTemporaryDir temp; - const QString filePath = temp.filePath("file.pbo"); - - //pbo content structure - PboNode root("file.pbo", PboNodeType::Container, nullptr); - PboNode* n1 = root.createHierarchy(PboPath("e1.txt")); - n1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); - n1->binarySource->open(); - PboNode* n2 = root.createHierarchy(PboPath("f2/e2.txt")); - n2->binarySource = QSharedPointer(new FsRawBinarySource(e2.fileName())); - n2->binarySource->open(); - - //pbo headers - HeadersModel headers; - headers.setData(QList({ - QSharedPointer(new PboHeader("h1", "v1")), - QSharedPointer(new PboHeader("h2", "v2")) - })); - - QByteArray signature; - - //write the file - PboWriter writer; - writer.usePath(filePath) - .useRoot(&root) - .useHeaders(&headers) - .copySignatureTo(&signature); - - writer.write([]() { return false; }); - - //assert the result - PboFile pbo(filePath); - pbo.open(QIODeviceBase::ReadOnly); - const PboFileHeader header = PboHeaderReader::readFileHeader(&pbo); - - //pbo header - ASSERT_EQ(header.headers.count(), 2); - ASSERT_EQ(header.headers.at(0)->name, "h1"); - ASSERT_EQ(header.headers.at(0)->value, "v1"); - ASSERT_EQ(header.headers.at(1)->name, "h2"); - ASSERT_EQ(header.headers.at(1)->value, "v2"); - - ASSERT_EQ(header.entries.count(), 2); - ASSERT_EQ(header.entries.at(0)->fileName(), "f2/e2.txt"); - ASSERT_EQ(header.entries.at(1)->fileName(), "e1.txt"); - - //pbo contents - pbo.seek(header.dataBlockStart); - - QByteArray contents; - contents.resize(mockContent2.size()); - pbo.read(contents.data(), contents.size()); - ASSERT_EQ(contents, mockContent2); - - contents.resize(mockContent1.size()); - pbo.read(contents.data(), contents.size()); - ASSERT_EQ(contents, mockContent1); - - //zero byte - QByteArray zero; - zero.resize(1); - pbo.read(zero.data(), zero.size()); - ASSERT_EQ(zero.at(0), 0); - - //signature - ASSERT_EQ(signature.size(), 20);//sha1 is 20 bytes - contents.resize(signature.size()); - pbo.read(contents.data(), contents.size()); - ASSERT_EQ(contents.size(), signature.size()); - - //file has ended - ASSERT_TRUE(pbo.atEnd()); - } - - class PboWriterTest: public testing::TestWithParam{}; - - TEST_P(PboWriterTest, Cleans_Files_On_Cancel) { - //mock files contents - const QByteArray mockContent1(15, 1); - QTemporaryFile e1; - e1.open(); - e1.write(mockContent1); - e1.close(); - - //pbo file - const QTemporaryDir temp; - const QString filePath = temp.filePath("file.pbo"); - - //pbo content structure - PboNode root("file.pbo", PboNodeType::Container, nullptr); - PboNode* n1 = root.createHierarchy(PboPath("e1.txt")); - n1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); - n1->binarySource->open(); - - //pbo headers - HeadersModel headers; - - //write the file - PboWriter writer; - writer.usePath(filePath) - .useRoot(&root) - .useHeaders(&headers); - - int count = 0; - int expectedHitCount = GetParam();//experimental way - writer.write([&count, expectedHitCount]() { count++; return count > expectedHitCount - 1; }); - - ASSERT_FALSE(QFileInfo(filePath).exists()); - ASSERT_FALSE(QFileInfo(filePath + ".b").exists()); - } - - INSTANTIATE_TEST_SUITE_P(Write_Cleans_On_Cancel, PboWriterTest, testing::Range(1, 13)); - - TEST(PboWriterTest, Cleans_Temporary_Files_On_Write) { - //mock files contents - const QByteArray mockContent1(15, 1); - QTemporaryFile e1; - e1.open(); - e1.write(mockContent1); - e1.close(); - - //pbo file - const QTemporaryDir temp; - const QString filePath = temp.filePath("file.pbo"); - - //pbo content structure - PboNode root("file.pbo", PboNodeType::Container, nullptr); - PboNode* n1 = root.createHierarchy(PboPath("e1.txt")); - n1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); - n1->binarySource->open(); - - //pbo headers - HeadersModel headers; - - //write the file - PboWriter writer; - writer.usePath(filePath) - .useRoot(&root) - .useHeaders(&headers); - - writer.write([]() { return false; }); - - ASSERT_TRUE(QFileInfo(filePath).exists()); - ASSERT_FALSE(QFileInfo(filePath + ".b").exists()); - } - - TEST(PboWriterTest, Write_Does_Not_Throw_If_No_Signature_Copy_Target_Set) { - //mock files contents - const QByteArray mockContent1(15, 1); - QTemporaryFile e1; - e1.open(); - e1.write(mockContent1); - e1.close(); - - //pbo file - const QTemporaryDir temp; - const QString filePath = temp.filePath("file.pbo"); - - //pbo content structure - PboNode root("file.pbo", PboNodeType::Container, nullptr); - PboNode* n1 = root.createHierarchy(PboPath("e1.txt")); - n1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); - n1->binarySource->open(); - - //pbo headers - HeadersModel headers; - - //write the file - PboWriter writer; - writer.usePath(filePath) - .useRoot(&root) - .useHeaders(&headers);//don't specify signature target - - ASSERT_NO_THROW(writer.write([]() { return false; })); - } - - TEST(PboWriterTest, SuspendBinarySources_Closes_Binary_Sources_And_ResumeBinarySources_Opens_Them) { - // mock files contents - const QByteArray mockContent1(15, 1); - QTemporaryFile e1; - e1.open(); - e1.write(mockContent1); - e1.close(); - - const QByteArray mockContent2(10, 1); - QTemporaryFile e2; - e2.open(); - e2.write(mockContent2); - e2.close(); - - //pbo content structure - PboNode root("file.pbo", PboNodeType::Container, nullptr); - PboNode* node1 = root.createHierarchy(PboPath("f1/e1.txt")); - node1->binarySource = QSharedPointer(new FsRawBinarySource(e1.fileName())); - node1->binarySource->open(); - PboNode* node2 = root.createHierarchy(PboPath("f1/e2.txt")); - node2->binarySource = QSharedPointer(new FsRawBinarySource(e2.fileName())); - node2->binarySource->open(); - - PboWriter writer; - - //Suspend the sources - writer.useRoot(&root) - .suspendBinarySources(); - - //Ensure sources closed - ASSERT_FALSE(node1->binarySource->isOpen()); - ASSERT_FALSE(node2->binarySource->isOpen()); - - //Resume the sources - writer.useRoot(&root) - .resumeBinarySources(); - - //Ensure sources opened - ASSERT_TRUE(node1->binarySource->isOpen()); - ASSERT_TRUE(node2->binarySource->isOpen()); - } -} diff --git a/pbom/io/bb/__test__/execbackend_test.cpp b/pbom/io/bb/__test__/execbackend_test.cpp index 208fdf6..9dda9bc 100644 --- a/pbom/io/bb/__test__/execbackend_test.cpp +++ b/pbom/io/bb/__test__/execbackend_test.cpp @@ -9,7 +9,7 @@ #include #include -namespace pboman3::test { +namespace pboman3::io::test { TEST(ExecBackendTest, ExecSync_Extracts_New_File) { //dummy files QTemporaryFile f1; diff --git a/pbom/io/bb/__test__/nodefilesystem_test.cpp b/pbom/io/bb/__test__/nodefilesystem_test.cpp index c0eef50..2dc0c69 100644 --- a/pbom/io/bb/__test__/nodefilesystem_test.cpp +++ b/pbom/io/bb/__test__/nodefilesystem_test.cpp @@ -1,9 +1,9 @@ #include "io/bb/nodefilesystem.h" -#include "io/pboioexception.h" +#include "exception.h" #include #include -namespace pboman3::test { +namespace pboman3::io::test { TEST(NodeFileSystemTest, AllocatePath_Creates_And_Sanitizes_Path_For_Node) { const QTemporaryDir dir; const NodeFileSystem fs(QDir(dir.path())); diff --git a/pbom/io/bb/__test__/sanitizedstring_test.cpp b/pbom/io/bb/__test__/sanitizedstring_test.cpp index 3240021..a8f7786 100644 --- a/pbom/io/bb/__test__/sanitizedstring_test.cpp +++ b/pbom/io/bb/__test__/sanitizedstring_test.cpp @@ -2,7 +2,7 @@ #include #include -namespace pboman3::test { +namespace pboman3::io::test { struct CtorParam { const QString sourceText; const QString expectedText; diff --git a/pbom/io/bb/__test__/tempbackend_test.cpp b/pbom/io/bb/__test__/tempbackend_test.cpp index 1874f92..ef03a4a 100644 --- a/pbom/io/bb/__test__/tempbackend_test.cpp +++ b/pbom/io/bb/__test__/tempbackend_test.cpp @@ -7,7 +7,7 @@ #include #include "io/bs/fsrawbinarysource.h" -namespace pboman3::test { +namespace pboman3::io::test { TEST(TempBackendTest, HddSync_Creates_Files_On_Disk) { //dummy files QTemporaryFile f1; diff --git a/pbom/io/bb/__test__/unpackbackend_test.cpp b/pbom/io/bb/__test__/unpackbackend_test.cpp index 0fc476e..69e313e 100644 --- a/pbom/io/bb/__test__/unpackbackend_test.cpp +++ b/pbom/io/bb/__test__/unpackbackend_test.cpp @@ -5,9 +5,9 @@ #include #include "io/bs/fsrawbinarysource.h" #include "io/bs/pbobinarysource.h" -#include "util/exception.h" +#include "exception.h" -namespace pboman3::test { +namespace pboman3::io::test { TEST(UnpackBackendTest, UnpackSync_Extracts_Nodes_To_File_System) { const QTemporaryDir dir; diff --git a/pbom/io/bb/binarybackend.cpp b/pbom/io/bb/binarybackend.cpp index 1f24ab1..9b2b2a7 100644 --- a/pbom/io/bb/binarybackend.cpp +++ b/pbom/io/bb/binarybackend.cpp @@ -1,11 +1,12 @@ #include "binarybackend.h" #include "unpackbackend.h" -#include "io/pboioexception.h" +#include "io/diskaccessexception.h" #include "util/log.h" #define LOG(...) LOGGER("io/bb/BinaryBackend", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::io { + using namespace domain; #define TEMP_PBOMAN "pboman3" BinaryBackend::BinaryBackend(const QString& name) { @@ -20,9 +21,9 @@ namespace pboman3 { LOG(info, "Binary backend exec:", exec) if (!QDir::temp().mkpath(treePath)) - throw PboIoException("Could not create the folder.", tree.path()); + throw DiskAccessException("Could not create the folder.", tree.path()); if (!QDir::temp().mkpath(execPath)) - throw PboIoException("Could not create the folder.", exec.path()); + throw DiskAccessException("Could not create the folder.", exec.path()); tempBackend_ = QSharedPointer(new TempBackend(tree)); execBackend_ = QSharedPointer(new ExecBackend(exec)); @@ -45,5 +46,7 @@ namespace pboman3 { } void BinaryBackend::clear(const PboNode* node) const { + tempBackend_->clear(node); + execBackend_->clear(node); } } diff --git a/pbom/io/bb/binarybackend.h b/pbom/io/bb/binarybackend.h index 15e50ce..bf3f144 100644 --- a/pbom/io/bb/binarybackend.h +++ b/pbom/io/bb/binarybackend.h @@ -2,9 +2,11 @@ #include "execbackend.h" #include "tempbackend.h" -#include "model/pbonode.h" +#include "domain/pbonode.h" + +namespace pboman3::io { + using namespace domain; -namespace pboman3 { class BinaryBackend { public: BinaryBackend(const QString& name); diff --git a/pbom/io/bb/execbackend.cpp b/pbom/io/bb/execbackend.cpp index 0cff71e..46bdb8b 100644 --- a/pbom/io/bb/execbackend.cpp +++ b/pbom/io/bb/execbackend.cpp @@ -2,9 +2,9 @@ #include #include #include -#include "io/pboioexception.h" +#include "io/diskaccessexception.h" -namespace pboman3 { +namespace pboman3::io { ExecBackend::ExecBackend(const QDir& folder) : folder_(folder) { if (!folder.exists()) @@ -85,11 +85,11 @@ namespace pboman3 { const QFileInfo fi(execPath); if (!folder_.mkpath(folder_.relativeFilePath(fi.dir().path()))) - throw PboIoException("Could not create the folder.", fi.dir().path()); + throw DiskAccessException("Could not create the folder.", fi.dir().path()); QFile file(execPath); if (!file.open(QIODeviceBase::ReadWrite | QIODeviceBase::NewOnly)) - throw PboIoException( + throw DiskAccessException( "Could not open file. Check you have enough permissions and the file is not locked by another process.", execPath); diff --git a/pbom/io/bb/execbackend.h b/pbom/io/bb/execbackend.h index e341555..8c57301 100644 --- a/pbom/io/bb/execbackend.h +++ b/pbom/io/bb/execbackend.h @@ -1,9 +1,9 @@ #pragma once #include "nodefilesystem.h" -#include "model/pbonode.h" +#include "domain/pbonode.h" -namespace pboman3 { +namespace pboman3::io { class ExecBackend { public: explicit ExecBackend(const QDir& folder); diff --git a/pbom/io/bb/nodefilesystem.cpp b/pbom/io/bb/nodefilesystem.cpp index 3dec634..a278a19 100644 --- a/pbom/io/bb/nodefilesystem.cpp +++ b/pbom/io/bb/nodefilesystem.cpp @@ -1,11 +1,13 @@ #include "nodefilesystem.h" #include "sanitizedstring.h" -#include "io/pboioexception.h" +#include "io/diskaccessexception.h" #include "util/log.h" #define LOG(...) LOGGER("io/bb/NodeFileSystem", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::io { + using namespace domain; + NodeFileSystem::NodeFileSystem(const QDir& folder) : QObject(), folder_(folder) { @@ -69,7 +71,7 @@ namespace pboman3 { for (const PboNode* par : parents) { SanitizedString title(par->title()); if (!QDir(local.filePath(title)).exists() && !local.mkdir(title)) - throw PboIoException("Could not create the folder.", local.filePath(title)); + throw DiskAccessException("Could not create the folder.", local.filePath(title)); local.cd(title); } diff --git a/pbom/io/bb/nodefilesystem.h b/pbom/io/bb/nodefilesystem.h index 0417b19..4e1b6a0 100644 --- a/pbom/io/bb/nodefilesystem.h +++ b/pbom/io/bb/nodefilesystem.h @@ -1,9 +1,11 @@ #pragma once #include -#include "model/pbonode.h" +#include "domain/pbonode.h" + +namespace pboman3::io { + using namespace domain; -namespace pboman3 { class NodeFileSystem : public QObject { Q_OBJECT public: diff --git a/pbom/io/bb/sanitizedstring.cpp b/pbom/io/bb/sanitizedstring.cpp index 2857a6e..422175f 100644 --- a/pbom/io/bb/sanitizedstring.cpp +++ b/pbom/io/bb/sanitizedstring.cpp @@ -1,6 +1,6 @@ #include "sanitizedstring.h" -namespace pboman3 { +namespace pboman3::io { SanitizedString::SanitizedString(const QString& text) : originalText_(nullptr) { diff --git a/pbom/io/bb/sanitizedstring.h b/pbom/io/bb/sanitizedstring.h index 31edb84..7636220 100644 --- a/pbom/io/bb/sanitizedstring.h +++ b/pbom/io/bb/sanitizedstring.h @@ -2,7 +2,7 @@ #include -namespace pboman3 { +namespace pboman3::io { class SanitizedString { public: SanitizedString(const QString& text); diff --git a/pbom/io/bb/tempbackend.cpp b/pbom/io/bb/tempbackend.cpp index bc19176..5d8dcaf 100644 --- a/pbom/io/bb/tempbackend.cpp +++ b/pbom/io/bb/tempbackend.cpp @@ -1,8 +1,10 @@ #include "tempbackend.h" #include -#include "io/pboioexception.h" +#include "io/diskaccessexception.h" + +namespace pboman3::io { + using namespace domain; -namespace pboman3 { TempBackend::TempBackend(const QDir& folder) : folder_(folder) { if (!folder.exists()) @@ -32,7 +34,7 @@ namespace pboman3 { const QString path = nodeFileSystem_->composeAbsolutePath(node); if (QFileInfo(path).exists()) { if (!QFile::remove(path)) { - throw PboIoException( + throw DiskAccessException( "Could not remove the file. Check you have enough permissions and the file is not locked by another process.", path); } @@ -45,7 +47,7 @@ namespace pboman3 { if (const QFileInfo fi(fsPath); !fi.exists()) { QFile file(fsPath); if (!file.open(QIODeviceBase::ReadWrite | QIODeviceBase::NewOnly)) - throw PboIoException( + throw DiskAccessException( "Could not open the file. Check you have enough permissions and the file is not locked by another process.", fsPath); diff --git a/pbom/io/bb/tempbackend.h b/pbom/io/bb/tempbackend.h index 00d140f..b341192 100644 --- a/pbom/io/bb/tempbackend.h +++ b/pbom/io/bb/tempbackend.h @@ -1,10 +1,12 @@ #pragma once #include "nodefilesystem.h" -#include "model/pbonode.h" +#include "domain/pbonode.h" #include "util/util.h" -namespace pboman3 { +namespace pboman3::io { + using namespace domain; + class TempBackend { public: TempBackend(const QDir& folder); diff --git a/pbom/io/bb/unpackbackend.cpp b/pbom/io/bb/unpackbackend.cpp index 9833eca..3586452 100644 --- a/pbom/io/bb/unpackbackend.cpp +++ b/pbom/io/bb/unpackbackend.cpp @@ -1,13 +1,13 @@ #include "unpackbackend.h" #include -#include "io/pboioexception.h" -#include "util/exception.h" +#include "io/diskaccessexception.h" +#include "exception.h" #include "util/log.h" #define LOG(...) LOGGER("io/bb/UnpackBackend", __VA_ARGS__) -namespace pboman3 { - UnpackBackend::UnpackBackend(const QDir& folder) { +namespace pboman3::io { + io::UnpackBackend::UnpackBackend(const QDir& folder) { if (!folder.exists()) throw InvalidOperationException("The folder provided must exist"); nodeFileSystem_ = QSharedPointer(new NodeFileSystem(folder)); @@ -59,7 +59,7 @@ namespace pboman3 { QFile file(filePath); //WriteOnly won't work for LZH unpacking if (!file.open(QIODeviceBase::ReadWrite)) { LOG(critical, "Can not access the file:", file.fileName()) - throw PboIoException( + throw DiskAccessException( "Can not open the file. Check you have enough permissions and the file is not locked by another process.", file.fileName()); } diff --git a/pbom/io/bb/unpackbackend.h b/pbom/io/bb/unpackbackend.h index c91b89e..5051630 100644 --- a/pbom/io/bb/unpackbackend.h +++ b/pbom/io/bb/unpackbackend.h @@ -1,9 +1,9 @@ #pragma once #include "nodefilesystem.h" -#include "model/pbonode.h" +#include "domain/pbonode.h" -namespace pboman3 { +namespace pboman3::io { class UnpackBackend { public: explicit UnpackBackend(const QDir& folder); diff --git a/pbom/io/bb/unpacktaskbackend.cpp b/pbom/io/bb/unpacktaskbackend.cpp index 961efbd..966021d 100644 --- a/pbom/io/bb/unpacktaskbackend.cpp +++ b/pbom/io/bb/unpacktaskbackend.cpp @@ -1,10 +1,10 @@ #include "unpacktaskbackend.h" -#include "io/pboioexception.h" +#include "io/diskaccessexception.h" #include "util/log.h" #define LOG(...) LOGGER("io/bb/UnpackTaskBackend", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::io { UnpackTaskBackend::UnpackTaskBackend(const QDir& folder) : UnpackBackend(folder), onError_(nullptr), @@ -26,7 +26,7 @@ namespace pboman3 { QString filePath; try { filePath = nodeFileSystem_->allocatePath(rootNode, childNode); - } catch (const PboIoException& ex) { + } catch (const DiskAccessException& ex) { LOG(warning, ex) //remove the "." symbol from the end error(ex.message().left(ex.message().length() - 1) + " | " + ex.file()); diff --git a/pbom/io/bb/unpacktaskbackend.h b/pbom/io/bb/unpacktaskbackend.h index 0749cfc..9d8bef5 100644 --- a/pbom/io/bb/unpacktaskbackend.h +++ b/pbom/io/bb/unpacktaskbackend.h @@ -2,7 +2,7 @@ #include "unpackbackend.h" -namespace pboman3 { +namespace pboman3::io { class UnpackTaskBackend : public UnpackBackend { public: diff --git a/pbom/io/bs/__test__/fslzhbinarysource_test.cpp b/pbom/io/bs/__test__/fslzhbinarysource_test.cpp index 2994884..3168da4 100644 --- a/pbom/io/bs/__test__/fslzhbinarysource_test.cpp +++ b/pbom/io/bs/__test__/fslzhbinarysource_test.cpp @@ -3,7 +3,7 @@ #include #include -namespace pboman3::test { +namespace pboman3::io::test { TEST(FsLzhBinarySourceTest, WriteToPbo_Compresses_Data) { //create a binary source QTemporaryFile sourceFile; diff --git a/pbom/io/bs/__test__/fsrawbinarysource_test.cpp b/pbom/io/bs/__test__/fsrawbinarysource_test.cpp index 3029d42..f74c694 100644 --- a/pbom/io/bs/__test__/fsrawbinarysource_test.cpp +++ b/pbom/io/bs/__test__/fsrawbinarysource_test.cpp @@ -4,7 +4,7 @@ #include #include -namespace pboman3::test { +namespace pboman3::io::test { TEST(FsRawBinarySource, WriteToPbo_Writes_When_Buffer_Size_Less_Than_Data_Size) { //create a binary source QTemporaryFile sourceFile; diff --git a/pbom/io/bs/__test__/pbobinarysource_test.cpp b/pbom/io/bs/__test__/pbobinarysource_test.cpp index 1a0f676..9b523c5 100644 --- a/pbom/io/bs/__test__/pbobinarysource_test.cpp +++ b/pbom/io/bs/__test__/pbobinarysource_test.cpp @@ -3,7 +3,7 @@ #include #include -namespace pboman3::test { +namespace pboman3::io::test { TEST(PboBinarySource, WriteToPbo_Writes_When_Buffer_Size_Less_Than_Data_Size) { //create a binary source QTemporaryFile sourceFile; diff --git a/pbom/io/bs/abstractbinarysource.cpp b/pbom/io/bs/abstractbinarysource.cpp new file mode 100644 index 0000000..6318092 --- /dev/null +++ b/pbom/io/bs/abstractbinarysource.cpp @@ -0,0 +1,31 @@ +#include "abstractbinarysource.h" +#include +#include "io/diskaccessexception.h" + +namespace pboman3::io { + AbstractBinarySource::AbstractBinarySource(QString path) + : path_(std::move(path)) { + file_ = new QFile(path_); + } + + AbstractBinarySource::~AbstractBinarySource() { + delete file_; + } + + void AbstractBinarySource::open() const { + if (!file_->open(QIODeviceBase::ReadOnly)) + throw DiskAccessException("Can not open the file. Check you have enough permissions and the file is not locked by another process.", path_); + } + + void AbstractBinarySource::close() const { + file_->close(); + } + + bool AbstractBinarySource::isOpen() const { + return file_->isOpen(); + } + + const QString& AbstractBinarySource::path() const { + return path_; + } +} diff --git a/pbom/io/bs/abstractbinarysource.h b/pbom/io/bs/abstractbinarysource.h new file mode 100644 index 0000000..a977f8d --- /dev/null +++ b/pbom/io/bs/abstractbinarysource.h @@ -0,0 +1,42 @@ +#pragma once + +#include "domain/binarysource.h" + +namespace pboman3::io { + using namespace domain; + + class AbstractBinarySource: public BinarySource { + public: + AbstractBinarySource(QString path); + + virtual ~AbstractBinarySource(); + + virtual void writeToPbo(QFileDevice* targetFile, const Cancel& cancel) = 0; + + virtual void writeToFs(QFileDevice* targetFile, const Cancel& cancel) = 0; + + void open() const override; + + void close() const override; + + bool isOpen() const override; + + const QString& path() const override; + + qint32 readOriginalSize() const override = 0; + + qint32 readTimestamp() const override = 0; + + bool isCompressed() const override = 0; + + friend QDebug operator <<(QDebug debug, const AbstractBinarySource& bs) { + return debug << "AbstractBinarySource(Compressed=" << bs.isCompressed() << ", Path=" << bs.path_ << ")"; + } + + protected: + QFileDevice* file_; + + private: + QString path_; + }; +} diff --git a/pbom/io/bs/binarysource.cpp b/pbom/io/bs/binarysource.cpp deleted file mode 100644 index 947fad6..0000000 --- a/pbom/io/bs/binarysource.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "binarysource.h" -#include -#include "io/pboioexception.h" - -namespace pboman3 { - BinarySource::BinarySource(QString path) - : path_(std::move(path)) { - file_ = new QFile(path_); - } - - BinarySource::~BinarySource() { - delete file_; - } - - void BinarySource::open() const { - if (!file_->open(QIODeviceBase::ReadOnly)) - throw PboIoException("Can not open the file. Check you have enough permissions and the file is not locked by another process.", path_); - } - - void BinarySource::close() const { - file_->close(); - } - - bool BinarySource::isOpen() const { - return file_->isOpen(); - } - - const QString& BinarySource::path() const { - return path_; - } -} diff --git a/pbom/io/bs/fslzhbinarysource.cpp b/pbom/io/bs/fslzhbinarysource.cpp index 3d77ac5..ddf73f6 100644 --- a/pbom/io/bs/fslzhbinarysource.cpp +++ b/pbom/io/bs/fslzhbinarysource.cpp @@ -1,7 +1,7 @@ #include "fslzhbinarysource.h" #include "io/lzh/lzh.h" -namespace pboman3 { +namespace pboman3::io { FsLzhBinarySource::FsLzhBinarySource(QString path, qsizetype bufferSize) : FsRawBinarySource(std::move(path), bufferSize){ } diff --git a/pbom/io/bs/fslzhbinarysource.h b/pbom/io/bs/fslzhbinarysource.h index 96c2e56..669a91c 100644 --- a/pbom/io/bs/fslzhbinarysource.h +++ b/pbom/io/bs/fslzhbinarysource.h @@ -2,7 +2,7 @@ #include "fsrawbinarysource.h" -namespace pboman3 { +namespace pboman3::io { class FsLzhBinarySource: public FsRawBinarySource { public: FsLzhBinarySource(QString path, qsizetype bufferSize = 1024 * 1024); diff --git a/pbom/io/bs/fsrawbinarysource.cpp b/pbom/io/bs/fsrawbinarysource.cpp index b01749d..467eb40 100644 --- a/pbom/io/bs/fsrawbinarysource.cpp +++ b/pbom/io/bs/fsrawbinarysource.cpp @@ -1,9 +1,9 @@ #include "fsrawbinarysource.h" #include -namespace pboman3 { +namespace pboman3::io { FsRawBinarySource::FsRawBinarySource(QString path, qsizetype bufferSize) - : BinarySource(std::move(path)), + : AbstractBinarySource(std::move(path)), bufferSize_(bufferSize) { } diff --git a/pbom/io/bs/fsrawbinarysource.h b/pbom/io/bs/fsrawbinarysource.h index 4841381..e3f20b1 100644 --- a/pbom/io/bs/fsrawbinarysource.h +++ b/pbom/io/bs/fsrawbinarysource.h @@ -1,9 +1,9 @@ #pragma once -#include "binarysource.h" +#include "abstractbinarysource.h" -namespace pboman3 { - class FsRawBinarySource : public BinarySource { +namespace pboman3::io { + class FsRawBinarySource : public AbstractBinarySource { public: FsRawBinarySource(QString path, qsizetype bufferSize = 1024 * 1024); diff --git a/pbom/io/bs/pbobinarysource.cpp b/pbom/io/bs/pbobinarysource.cpp index 92568c7..eb0cf48 100644 --- a/pbom/io/bs/pbobinarysource.cpp +++ b/pbom/io/bs/pbobinarysource.cpp @@ -1,11 +1,11 @@ #include "pbobinarysource.h" -#include "io/pboioexception.h" +#include "io/diskaccessexception.h" #include "io/lzh/lzh.h" #include "io/lzh/lzhdecompressionexception.h" -namespace pboman3 { +namespace pboman3::io { PboBinarySource::PboBinarySource(const QString& path, const PboDataInfo& dataInfo, qsizetype bufferSize) - : BinarySource(path), + : AbstractBinarySource(path), dataInfo_(dataInfo), bufferSize_(bufferSize) { } @@ -36,7 +36,7 @@ namespace pboman3 { const qsizetype willRead = remaining > buf.size() ? buf.size() : remaining; const qint64 hasRead = file_->read(buf.data(), willRead); if (hasRead <= 0) - throw PboIoException("For some reason could not read from the file.", file_->fileName()); + throw DiskAccessException("For some reason could not read from the file.", file_->fileName()); targetFile->write(buf.data(), hasRead); remaining -= hasRead; } diff --git a/pbom/io/bs/pbobinarysource.h b/pbom/io/bs/pbobinarysource.h index 4629edc..a922504 100644 --- a/pbom/io/bs/pbobinarysource.h +++ b/pbom/io/bs/pbobinarysource.h @@ -1,8 +1,8 @@ #pragma once -#include "binarysource.h" +#include "abstractbinarysource.h" -namespace pboman3 { +namespace pboman3::io { struct PboDataInfo { qint32 originalSize; qint32 dataSize; @@ -11,7 +11,7 @@ namespace pboman3 { qint32 compressed; }; - class PboBinarySource : public BinarySource { + class PboBinarySource : public AbstractBinarySource { public: PboBinarySource(const QString& path, const PboDataInfo& dataInfo, qsizetype bufferSize = 1024 * 1024); diff --git a/pbom/model/diskaccessexception.cpp b/pbom/io/diskaccessexception.cpp similarity index 72% rename from pbom/model/diskaccessexception.cpp rename to pbom/io/diskaccessexception.cpp index d16f9e3..5fe1320 100644 --- a/pbom/model/diskaccessexception.cpp +++ b/pbom/io/diskaccessexception.cpp @@ -1,17 +1,12 @@ #include "diskaccessexception.h" #include -namespace pboman3 { +namespace pboman3::io { DiskAccessException::DiskAccessException(QString message, QString file) : AppException(std::move(message)), file_(std::move(file)) { } - DiskAccessException::DiskAccessException(const PboIoException& ex): - AppException(ex.message()), - file_(ex.file()) { - } - void DiskAccessException::raise() const { throw *this; } @@ -23,4 +18,8 @@ namespace pboman3 { const QString& DiskAccessException::file() const { return file_; } + + QDebug operator<<(QDebug debug, const DiskAccessException& ex) { + return debug << "DiskAccessException(" << ex.message_ << ")"; + } } diff --git a/pbom/model/diskaccessexception.h b/pbom/io/diskaccessexception.h similarity index 65% rename from pbom/model/diskaccessexception.h rename to pbom/io/diskaccessexception.h index 55190a0..1dd7b82 100644 --- a/pbom/model/diskaccessexception.h +++ b/pbom/io/diskaccessexception.h @@ -1,21 +1,20 @@ #pragma once -#include "io/pboioexception.h" -#include "util/exception.h" +#include "exception.h" -namespace pboman3 { +namespace pboman3::io { class DiskAccessException : public AppException { public: DiskAccessException(QString message, QString file); - explicit DiskAccessException(const PboIoException& ex); - void raise() const override; QException* clone() const override; const QString& file() const; + friend QDebug operator<<(QDebug debug, const DiskAccessException& ex); + private: QString file_; }; diff --git a/pbom/io/documentreader.cpp b/pbom/io/documentreader.cpp new file mode 100644 index 0000000..10feb56 --- /dev/null +++ b/pbom/io/documentreader.cpp @@ -0,0 +1,44 @@ +#include "documentreader.h" +#include +#include "diskaccessexception.h" +#include "pboheaderreader.h" +#include "bs/pbobinarysource.h" + +namespace pboman3::io { + DocumentReader::DocumentReader(QString path) + : path_(std::move(path)) { + } + + QSharedPointer DocumentReader::read() const { + PboFile pbo(path_); + if (!pbo.open(QIODeviceBase::ReadOnly)) { + throw DiskAccessException("Can not access the file. Check if it is used by other processes.", path_); + } + PboFileHeader header = PboHeaderReader::readFileHeader(&pbo); + + QList> headers; + headers.reserve(header.headers.count()); + for (const QSharedPointer& h : header.headers) + headers.append(QSharedPointer(new DocumentHeader(DocumentHeader::InternalData{ h->name, h->value }))); + + const QFileInfo fi(path_); + QSharedPointer document(new PboDocument(fi.fileName(), std::move(headers), std::move(header.signature))); + + qsizetype entryDataOffset = header.dataBlockStart; + for (const QSharedPointer& e : header.entries) { + PboNode* node = document->root()->createHierarchy(e->makePath()); + PboDataInfo dataInfo{0, 0, 0, 0, 0}; + dataInfo.originalSize = e->originalSize(); + dataInfo.dataSize = e->dataSize(); + dataInfo.dataOffset = entryDataOffset; + dataInfo.timestamp = e->timestamp(); + dataInfo.compressed = e->packingMethod() == PboPackingMethod::Packed; + entryDataOffset += dataInfo.dataSize; + node->binarySource = QSharedPointer( + new PboBinarySource(path_, dataInfo)); + node->binarySource->open(); + } + + return document; + } +} diff --git a/pbom/io/documentreader.h b/pbom/io/documentreader.h new file mode 100644 index 0000000..8ebc709 --- /dev/null +++ b/pbom/io/documentreader.h @@ -0,0 +1,17 @@ +#pragma once + +#include "domain/pbodocument.h" + +namespace pboman3::io { + using namespace domain; + + class DocumentReader { + public: + DocumentReader(QString path); + + QSharedPointer read() const; + + private: + QString path_; + }; +} diff --git a/pbom/io/documentwriter.cpp b/pbom/io/documentwriter.cpp new file mode 100644 index 0000000..225723a --- /dev/null +++ b/pbom/io/documentwriter.cpp @@ -0,0 +1,317 @@ +#include "documentwriter.h" +#include +#include +#include "diskaccessexception.h" +#include "pboheaderentity.h" +#include "pboheaderio.h" +#include "util/log.h" + +#define LOG(...) LOGGER("io/documentwriter", __VA_ARGS__) + +namespace pboman3::io { + DocumentWriter::DocumentWriter(QString path) + : path_(std::move(path)) { + assert(!path_.isEmpty() && "Path must not be empty"); + } + + void DocumentWriter::write(PboDocument* document, const Cancel& cancel) { + assert(document && "Document must not be null"); + + LOG(info, "Writing the document to:", path_) + const bool shouldBackup = QFile::exists(path_); + const QString filePath = shouldBackup ? path_ + ".t" : path_; + + writeInternal(document, filePath, cancel); + + if (cancel()) { + if (!shouldBackup) + QFile::remove(filePath); //don't assert as not critical + LOG(info, "Cancel - clean temp files and return") + return; + } + + LOG(info, "Suspending binary sources") + suspendBinarySources(document->root()); + + bool backupMade = false; + const QString backupPath = path_ + ".bak"; + if (shouldBackup && !cancel()) { + LOG(info, "Back up the original file as: ", backupPath) + if (QFile::exists(backupPath) && !QFile::remove(backupPath)) { + LOG(warning, "Could not remove the prev backup file - throwing;", backupPath) + resumeBinarySources(document->root()); + throw DiskAccessException( + "Could not remove the file. Check you have enough permissions and the file is not locked by another process.", + backupPath); + } + if (!QFile::rename(path_, backupPath)) { + LOG(info, "Could not replace the prev PBO file with a write copy - throwing:", path_) + resumeBinarySources(document->root()); + throw DiskAccessException( + "Could not write to the file. Check you have enough permissions and the file is not locked by another process.", + path_); + } + backupMade = QFile::rename(filePath, path_); + if (!backupMade) { + LOG(warning, "Could not rename file 1 to file 2 - throwing:", filePath, "|", path_) + throw DiskAccessException("Could not rename the file. Normally this must not happen.", filePath); + } + } + + if (cancel()) { + LOG(info, "Cancel - removing the written files") + if (backupMade) { + if (!QFile::remove(path_)) { + LOG(warning, "Could not remove the file - throwing", path_) + throw DiskAccessException("Could not remove the file. Normally this must not happen.", path_); + } + LOG(info, "The original file has been already renamed - renaming back") + if (!QFile::rename(backupPath, path_)) { + LOG(warning, "Could not rename file 1 to file 2 - throwing:", backupPath, "|", path_) + throw DiskAccessException("Could not renames the file. Normally this must not happen.", backupPath); + } + } else { + if (!shouldBackup && !QFile::remove(path_)) { + LOG(warning, "Could not remove the file - throwing", path_) + throw DiskAccessException("Could not remove the file. Normally this must not happen.", path_); + } + } + LOG(info, "Resuming binary sources") + resumeBinarySources(document->root()); + } else { + LOG(info, "Assigining binary sources") + assignBinarySources(document->root()); + } + } + + void DocumentWriter::writeInternal(PboDocument* document, const QString& path, const Cancel& cancel) { + LOG(info, "Writing to the file:", path) + + QTemporaryFile body; + body.setFileName(path + ".b"); + if (!body.open()) { + LOG(warning, "Could not open the body temp file - throwing:", body.fileName()) + throw DiskAccessException("Could not create the file.", body.fileName()); + } + + LOG(info, "Writing nodes") + QList> entries; + writeNode(&body, document->root(), entries, cancel); + + if (cancel()) { + LOG(info, "Cancel - return") + return; + } + + PboFile pbo(path); + if (!pbo.open(QIODeviceBase::ReadWrite)) { + LOG(warning, "Could not open the file - throwing:", path) + throw DiskAccessException("Could not create the file.", path); + } + + LOG(info, "Writing headers") + writeHeader(&pbo, document->headers(), entries, cancel); + + if (cancel()) { + LOG(info, "Cancel - clean temp files and return") + pbo.close(); + pbo.remove(); + return; + } + + const bool seek = body.seek(0); + assert(seek); + + LOG(info, "Copy body bytes") + copyBody(&pbo, &body, cancel); + + LOG(info, "Calc signature") + writeSignature(&pbo, document, cancel); + + if (cancel()) { + LOG(info, "Cancel - clean temp files") + pbo.close(); + pbo.remove(); + } + } + + void DocumentWriter::writeNode(QFileDevice* file, PboNode* node, QList>& entries, + const Cancel& cancel) { + for (PboNode* child : *node) { + if (cancel()) { + LOG(info, "Cancel - return") + return; + } + + if (child->nodeType() == PboNodeType::File) { + const qint64 before = file->pos(); + child->binarySource->writeToPbo(file, cancel); + const qint64 after = file->pos(); + + const qint32 originalSize = child->binarySource->readOriginalSize(); + const auto dataSize = static_cast(after - before); + + QSharedPointer entry(new PboNodeEntity( + child->makePath().toString(), + child->binarySource->isCompressed() ? PboPackingMethod::Packed : PboPackingMethod::Uncompressed, + child->binarySource->readOriginalSize(), + 0, + child->binarySource->readTimestamp(), + dataSize)); + entries.append(entry); + + PboDataInfo data{0, 0, 0, 0, 0}; + data.originalSize = originalSize; + data.dataSize = dataSize; + data.dataOffset = before; + data.timestamp = child->binarySource->readTimestamp(); + data.compressed = child->binarySource->isCompressed(); + + binarySources_.insert(child, data); + + emitWriteEntry(); + } else { + writeNode(file, child, entries, cancel); + } + } + } + + void DocumentWriter::writeHeader(PboFile* file, const DocumentHeaders* headers, + const QList>& entries, const Cancel& cancel) { + const PboHeaderIO io(file); + io.writeEntry(PboNodeEntity::makeSignature()); + + for (const DocumentHeader* header : *headers) { + if (cancel()) { + break; + } + io.writeHeader(PboHeaderEntity(header->name(), header->value())); + } + + if (cancel()) { + LOG(info, "Cancel - return") + return; + } + + io.writeHeader(PboHeaderEntity::makeBoundary()); + + for (const QSharedPointer& entry : entries) { + if (cancel()) { + LOG(info, "Cancel - break") + break; + } + io.writeEntry(*entry); + } + + if (cancel()) { + LOG(info, "Cancel - return") + return; + } + + io.writeEntry(PboNodeEntity::makeBoundary()); + + for (PboNode* key : binarySources_.keys()) { + PboDataInfo& existing = binarySources_[key]; + existing.dataOffset += file->pos(); + } + } + + void DocumentWriter::copyBody(QFileDevice* pbo, QFileDevice* body, const Cancel& cancel) { + QByteArray data; + data.resize(1024 * 1024); + + qsizetype copiedBytes = 0; + const qsizetype totalBytes = body->size(); + + qint64 read = body->read(data.data(), data.size()); + while (read > 0) { + pbo->write(data.data(), read); + + copiedBytes += read; + emitCopyBytes(copiedBytes, totalBytes); + + if (cancel()) { + LOG(info, "Cancel - return") + return; + } + read = body->read(data.data(), data.size()); + } + } + + void DocumentWriter::writeSignature(QFileDevice* pbo, PboDocument* document, const Cancel& cancel) { + const bool seek = pbo->seek(0); + assert(seek); + + QCryptographicHash sha1(QCryptographicHash::Sha1); + + qsizetype processed = 0; + const qsizetype total = pbo->size(); + + constexpr qint64 bufferSize = 1024; + char buffer[bufferSize]; + qint64 read; + + while (!cancel() && (read = pbo->read(buffer, bufferSize)) > 0) { + sha1.addData(buffer, read); + processed += read; + emitCalcHash(processed, total); + } + + if (cancel()) { + LOG(info, "Cancel - return") + return; + } + + document->setSignature(sha1.result()); + + pbo->write(QByteArray(1, 0)); + pbo->write(document->signature(), document->signature().count()); + } + + void DocumentWriter::suspendBinarySources(PboNode* node) const { + for (PboNode* child : *node) { + if (child->nodeType() == PboNodeType::File) { + child->binarySource->close(); + } else { + suspendBinarySources(child); + } + } + } + + void DocumentWriter::resumeBinarySources(PboNode* node) const { + for (PboNode* child : *node) { + if (child->nodeType() == PboNodeType::File) { + child->binarySource->open(); + } else { + resumeBinarySources(child); + } + } + } + + void DocumentWriter::assignBinarySources(PboNode* node) { + for (PboNode* child : *node) { + if (child->nodeType() == PboNodeType::File) { + const PboDataInfo& existing = binarySources_.take(child); + child->binarySource = QSharedPointer(new PboBinarySource(path_, existing)); + child->binarySource->open(); + } else { + assignBinarySources(child); + } + } + } + + void DocumentWriter::emitWriteEntry() { + const WriteEntryEvent evt; + emit progress(&evt); + } + + void DocumentWriter::emitCopyBytes(qsizetype copied, qsizetype total) { + const CopyBytesEvent evt(copied, total); + emit progress(&evt); + } + + void DocumentWriter::emitCalcHash(qsizetype processed, qsizetype total) { + const CalcHashEvent evt(processed, total); + emit progress(&evt); + } +} diff --git a/pbom/io/pbowriter.h b/pbom/io/documentwriter.h similarity index 52% rename from pbom/io/pbowriter.h rename to pbom/io/documentwriter.h index 2d427e5..d9c15b6 100644 --- a/pbom/io/pbowriter.h +++ b/pbom/io/documentwriter.h @@ -1,60 +1,48 @@ #pragma once #include +#include +#include "pbonodeentity.h" #include "pbofile.h" #include "bs/pbobinarysource.h" -#include "model/headersmodel.h" -#include "model/pboentry.h" -#include "model/pbonode.h" +#include "domain/pbodocument.h" +#include "util/util.h" -namespace pboman3 { - class PboWriter : public QObject { +namespace pboman3::io { + using namespace domain; + + class DocumentWriter: public QObject { Q_OBJECT public: - struct ProgressEvent; - - PboWriter(); - - PboWriter& usePath(QString path); - - PboWriter& useHeaders(HeadersModel* headers); - - PboWriter& useRoot(PboNode* root); + DocumentWriter(QString path); - PboWriter& copySignatureTo(QByteArray* signature); + void write(PboDocument* document, const Cancel& cancel); - void write(const Cancel& cancel); - - void suspendBinarySources() const; - - void resumeBinarySources() const; - - void assignBinarySources(const QString& path); + struct ProgressEvent; signals: void progress(const ProgressEvent* evt); private: QString path_; - PboNode* root_; - HeadersModel* headers_; - QByteArray* signature_; QHash binarySources_; - void writeNode(QFileDevice* file, PboNode* node, QList& entries, const Cancel& cancel); + void writeInternal(PboDocument* document, const QString& path, const Cancel& cancel); + + void writeNode(QFileDevice* file, PboNode* node, QList>& entries, const Cancel& cancel); - void writeHeader(PboFile* file, const QList& entries, const Cancel& cancel); + void writeHeader(PboFile* file, const DocumentHeaders* headers, const QList>& entries, const Cancel& cancel); void copyBody(QFileDevice* pbo, QFileDevice* body, const Cancel& cancel); - void writeSignature(QFileDevice* pbo, const Cancel& cancel); + void writeSignature(QFileDevice* pbo, PboDocument* document, const Cancel& cancel); void suspendBinarySources(PboNode* node) const; void resumeBinarySources(PboNode* node) const; - void assignBinarySources(PboNode* node, const QString& path); + void assignBinarySources(PboNode* node); void emitWriteEntry(); @@ -71,7 +59,7 @@ namespace pboman3 { }; struct CopyBytesEvent : ProgressEvent { - CopyBytesEvent(qsizetype c, qsizetype t): + CopyBytesEvent(qsizetype c, qsizetype t) : copied(c), total(t) { } diff --git a/pbom/io/execstore.cpp b/pbom/io/execstore.cpp index 893cc41..13e8c83 100644 --- a/pbom/io/execstore.cpp +++ b/pbom/io/execstore.cpp @@ -2,8 +2,9 @@ #include #include #include "pboioexception.h" +#include "io/diskaccessexception.h" -namespace pboman3 { +namespace pboman3::io { ExecStore::ExecStore(QString fileSystemPath) : fileSystemPath_(std::move(fileSystemPath)) { } @@ -93,11 +94,11 @@ namespace pboman3 { QString tempDirPath = fi.dir().absolutePath(); if (!QDir::temp().mkpath(QDir::temp().relativeFilePath(tempDirPath))) - throw PboIoException("Could not create the folder.", std::move(tempDirPath)); + throw DiskAccessException("Could not create the folder.", std::move(tempDirPath)); QFile file(execPath); if (!file.open(QIODeviceBase::ReadWrite | QIODeviceBase::NewOnly)) - throw PboIoException("Could not open file. Check you have enough permissions and the file is not locked by another process.", execPath); + throw DiskAccessException("Could not open file. Check you have enough permissions and the file is not locked by another process.", execPath); node->binarySource->writeToFs(&file, cancel); diff --git a/pbom/io/execstore.h b/pbom/io/execstore.h index 6f0fd99..e995217 100644 --- a/pbom/io/execstore.h +++ b/pbom/io/execstore.h @@ -1,10 +1,10 @@ #pragma once #include -#include "model/pbonode.h" +#include "domain/pbonode.h" #include "util/util.h" -namespace pboman3 { +namespace pboman3::io { class ExecStore { public: explicit ExecStore(QString fileSystemPath); diff --git a/pbom/io/lzh/__test__/compressionbuffer_test.cpp b/pbom/io/lzh/__test__/compressionbuffer_test.cpp index 5e7e401..dc39e14 100644 --- a/pbom/io/lzh/__test__/compressionbuffer_test.cpp +++ b/pbom/io/lzh/__test__/compressionbuffer_test.cpp @@ -3,7 +3,7 @@ #include #include -namespace pboman3::test { +namespace pboman3::io::test { TEST(CompressionBufferTest, Add1_Adds_Buffer_Longer_Than_Space_Remaining) { QTemporaryFile t1; t1.open(); diff --git a/pbom/io/lzh/__test__/compressionchunk_test.cpp b/pbom/io/lzh/__test__/compressionchunk_test.cpp index c696acc..a11a160 100644 --- a/pbom/io/lzh/__test__/compressionchunk_test.cpp +++ b/pbom/io/lzh/__test__/compressionchunk_test.cpp @@ -2,7 +2,7 @@ #include #include -namespace pboman3::test { +namespace pboman3::io::test { TEST(CompressionChunkTest, Compose_Fulfills_Packet) { QByteArray dummy; dummy.resize(100); diff --git a/pbom/io/lzh/__test__/lzh_test.cpp b/pbom/io/lzh/__test__/lzh_test.cpp index 428c034..fe3364e 100644 --- a/pbom/io/lzh/__test__/lzh_test.cpp +++ b/pbom/io/lzh/__test__/lzh_test.cpp @@ -2,7 +2,7 @@ #include #include -namespace pboman3::test { +namespace pboman3::io::test { struct LzhTestParam { QString original; QString source; diff --git a/pbom/io/lzh/compressionbuffer.cpp b/pbom/io/lzh/compressionbuffer.cpp index 70196c0..a7e49dc 100644 --- a/pbom/io/lzh/compressionbuffer.cpp +++ b/pbom/io/lzh/compressionbuffer.cpp @@ -1,7 +1,7 @@ #include "compressionbuffer.h" #include -namespace pboman3 { +namespace pboman3::io { CompressionBuffer::CompressionBuffer(qint64 size) : size_(size), fullfillment_(0) { diff --git a/pbom/io/lzh/compressionbuffer.h b/pbom/io/lzh/compressionbuffer.h index 7647cdf..40876fc 100644 --- a/pbom/io/lzh/compressionbuffer.h +++ b/pbom/io/lzh/compressionbuffer.h @@ -2,7 +2,7 @@ #include -namespace pboman3 { +namespace pboman3::io { struct BufferIntersection { inline static qint64 posNo = -1; qint64 position; diff --git a/pbom/io/lzh/compressionchunk.cpp b/pbom/io/lzh/compressionchunk.cpp index c7ed913..4851f5a 100644 --- a/pbom/io/lzh/compressionchunk.cpp +++ b/pbom/io/lzh/compressionchunk.cpp @@ -1,6 +1,6 @@ #include "compressionchunk.h" -namespace pboman3 { +namespace pboman3::io { CompressionChunk::CompressionChunk() : format_(0b00000000), length_(0) { diff --git a/pbom/io/lzh/compressionchunk.h b/pbom/io/lzh/compressionchunk.h index 573fe83..100d07f 100644 --- a/pbom/io/lzh/compressionchunk.h +++ b/pbom/io/lzh/compressionchunk.h @@ -2,7 +2,7 @@ #include "compressionbuffer.h" -namespace pboman3 { +namespace pboman3::io { class CompressionChunk { public: CompressionChunk(); diff --git a/pbom/io/lzh/decompressioncontext.cpp b/pbom/io/lzh/decompressioncontext.cpp index 43d2e9e..0b0adde 100644 --- a/pbom/io/lzh/decompressioncontext.cpp +++ b/pbom/io/lzh/decompressioncontext.cpp @@ -1,6 +1,6 @@ #include "decompressioncontext.h" -namespace pboman3 { +namespace pboman3::io { DecompressionContext::DecompressionContext(QFileDevice* pSource, QFileDevice* pTarget) : format(0), crc(0), diff --git a/pbom/io/lzh/decompressioncontext.h b/pbom/io/lzh/decompressioncontext.h index b3103ed..48b8e19 100644 --- a/pbom/io/lzh/decompressioncontext.h +++ b/pbom/io/lzh/decompressioncontext.h @@ -2,7 +2,7 @@ #include -namespace pboman3 { +namespace pboman3::io { class DecompressionContext { public: int format; diff --git a/pbom/io/lzh/lzh.cpp b/pbom/io/lzh/lzh.cpp index d996723..86d2e4a 100644 --- a/pbom/io/lzh/lzh.cpp +++ b/pbom/io/lzh/lzh.cpp @@ -5,7 +5,7 @@ #define LOG(...) LOGGER("io/lzh/Lzh", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::io { void Lzh::decompress(QFileDevice* source, QFileDevice* target, int outputLength, const Cancel& cancel) { DecompressionContext ctx(source, target); const qint64 maxTargetOffset = target->pos() + outputLength; diff --git a/pbom/io/lzh/lzh.h b/pbom/io/lzh/lzh.h index de534f2..a4dfeed 100644 --- a/pbom/io/lzh/lzh.h +++ b/pbom/io/lzh/lzh.h @@ -3,7 +3,9 @@ #include "decompressioncontext.h" #include "util/util.h" -namespace pboman3 { +namespace pboman3::io { + using namespace util; + class Lzh { public: static void decompress(QFileDevice* source, QFileDevice* target, int outputLength, const Cancel& cancel); diff --git a/pbom/io/lzh/lzhdecompressionexception.cpp b/pbom/io/lzh/lzhdecompressionexception.cpp index 9a6381d..f078abc 100644 --- a/pbom/io/lzh/lzhdecompressionexception.cpp +++ b/pbom/io/lzh/lzhdecompressionexception.cpp @@ -1,7 +1,7 @@ #include "lzhdecompressionexception.h" #include -namespace pboman3 { +namespace pboman3::io { LzhDecompressionException::LzhDecompressionException(QString message) : AppException(std::move(message)) { } diff --git a/pbom/io/lzh/lzhdecompressionexception.h b/pbom/io/lzh/lzhdecompressionexception.h index 1a3d4f9..27f6470 100644 --- a/pbom/io/lzh/lzhdecompressionexception.h +++ b/pbom/io/lzh/lzhdecompressionexception.h @@ -1,8 +1,8 @@ #pragma once -#include "util/exception.h" +#include "exception.h" -namespace pboman3 { +namespace pboman3::io { class LzhDecompressionException : public AppException { public: LzhDecompressionException(QString message); diff --git a/pbom/io/pbodatastream.cpp b/pbom/io/pbodatastream.cpp index c7cd9ea..fca1e27 100644 --- a/pbom/io/pbodatastream.cpp +++ b/pbom/io/pbodatastream.cpp @@ -1,6 +1,6 @@ #include "pbodatastream.h" -namespace pboman3 { +namespace pboman3::io { PboDataStream::PboDataStream(PboFile* file) : QDataStream(file), file_(file) { diff --git a/pbom/io/pbodatastream.h b/pbom/io/pbodatastream.h index 293754a..492364f 100644 --- a/pbom/io/pbodatastream.h +++ b/pbom/io/pbodatastream.h @@ -3,7 +3,7 @@ #include "pbofile.h" #include -namespace pboman3 { +namespace pboman3::io { class PboEofException: public QException { }; diff --git a/pbom/io/pbofile.cpp b/pbom/io/pbofile.cpp index 6cd30a7..d607533 100644 --- a/pbom/io/pbofile.cpp +++ b/pbom/io/pbofile.cpp @@ -1,6 +1,6 @@ #include "pbofile.h" -namespace pboman3 { +namespace pboman3::io { PboFile::PboFile(const QString& name) : QFile(name) { } diff --git a/pbom/io/pbofile.h b/pbom/io/pbofile.h index 1e0aabf..314f86a 100644 --- a/pbom/io/pbofile.h +++ b/pbom/io/pbofile.h @@ -2,7 +2,7 @@ #include -namespace pboman3 { +namespace pboman3::io { class PboFile : public QFile { Q_OBJECT public: diff --git a/pbom/model/pbofileformatexception.cpp b/pbom/io/pbofileformatexception.cpp similarity index 78% rename from pbom/model/pbofileformatexception.cpp rename to pbom/io/pbofileformatexception.cpp index f768d36..04b85e0 100644 --- a/pbom/model/pbofileformatexception.cpp +++ b/pbom/io/pbofileformatexception.cpp @@ -1,16 +1,20 @@ #include "pbofileformatexception.h" #include -namespace pboman3 { +namespace pboman3::io { PboFileFormatException::PboFileFormatException(QString message) : AppException(std::move(message)) { } - QDebug operator<<(QDebug debug, const PboFileFormatException& ex) { - return debug << "PboFileFormatException(" << ex.message_ << ")"; + void PboFileFormatException::raise() const { + throw *this; } QException* PboFileFormatException::clone() const { return new PboFileFormatException(*this); } + + QDebug operator<<(QDebug debug, const PboFileFormatException& ex) { + return debug << "PboFileFormatException(" << ex.message_ << ")"; + } } diff --git a/pbom/model/pbofileformatexception.h b/pbom/io/pbofileformatexception.h similarity index 73% rename from pbom/model/pbofileformatexception.h rename to pbom/io/pbofileformatexception.h index 2c8ab06..20795d5 100644 --- a/pbom/model/pbofileformatexception.h +++ b/pbom/io/pbofileformatexception.h @@ -1,14 +1,16 @@ #pragma once -#include "util/exception.h" +#include "exception.h" -namespace pboman3 { +namespace pboman3::io { class PboFileFormatException : public AppException { public: explicit PboFileFormatException(QString message); - friend QDebug operator<<(QDebug debug, const PboFileFormatException& ex); + void raise() const override; QException* clone() const override; + + friend QDebug operator<<(QDebug debug, const PboFileFormatException& ex); }; } diff --git a/pbom/io/pboheaderentity.cpp b/pbom/io/pboheaderentity.cpp new file mode 100644 index 0000000..d201a6f --- /dev/null +++ b/pbom/io/pboheaderentity.cpp @@ -0,0 +1,17 @@ +#include "pboheaderentity.h" + +namespace pboman3::io { + PboHeaderEntity PboHeaderEntity::makeBoundary() { + return PboHeaderEntity(QString(), QString()); + } + + PboHeaderEntity::PboHeaderEntity(QString name, QString value) + : name(std::move(name)), + value(std::move(value)) { + } + + bool PboHeaderEntity::isBoundary() const { + return name.isEmpty() && value.isEmpty(); + } + +} diff --git a/pbom/io/pboheaderentity.h b/pbom/io/pboheaderentity.h new file mode 100644 index 0000000..3be2bbe --- /dev/null +++ b/pbom/io/pboheaderentity.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace pboman3::io { + struct PboHeaderEntity { + static PboHeaderEntity makeBoundary(); + + const QString name; + const QString value; + + PboHeaderEntity(QString name, QString value); + + bool isBoundary() const; + }; +} diff --git a/pbom/io/pboheaderio.cpp b/pbom/io/pboheaderio.cpp index 62208e7..d785d03 100644 --- a/pbom/io/pboheaderio.cpp +++ b/pbom/io/pboheaderio.cpp @@ -1,14 +1,14 @@ #include "pboheaderio.h" #include "pbodatastream.h" -namespace pboman3 { +namespace pboman3::io { using namespace std; PboHeaderIO::PboHeaderIO(PboFile* file) : file_(file) { } - QSharedPointer PboHeaderIO::readNextEntry() const { + QSharedPointer PboHeaderIO::readNextEntry() const { PboDataStream data(file_); try { @@ -30,13 +30,13 @@ namespace pboman3 { qint32 dataSize; data >> dataSize; - return QSharedPointer(new PboEntry(fileName, packingMethod, originalSize, reserved, timeStamp, dataSize)); + return QSharedPointer(new PboNodeEntity(fileName, packingMethod, originalSize, reserved, timeStamp, dataSize)); } catch (PboEofException&) { return nullptr; } } - QSharedPointer PboHeaderIO::readNextHeader() const { + QSharedPointer PboHeaderIO::readNextHeader() const { PboDataStream data(file_); try { @@ -44,18 +44,18 @@ namespace pboman3 { data >> name; if (name.isEmpty()) - return QSharedPointer(new PboHeader("", "")); + return QSharedPointer(new PboHeaderEntity("", "")); QString value; data >> value; - return QSharedPointer(new PboHeader(name, value)); + return QSharedPointer(new PboHeaderEntity(name, value)); } catch (PboEofException&) { return nullptr; } } - void PboHeaderIO::writeEntry(const PboEntry& entry) const { + void PboHeaderIO::writeEntry(const PboNodeEntity& entry) const { PboDataStream data(file_); data << entry.fileName(); @@ -66,7 +66,7 @@ namespace pboman3 { data << entry.dataSize(); } - void PboHeaderIO::writeHeader(const PboHeader& header) const { + void PboHeaderIO::writeHeader(const PboHeaderEntity& header) const { PboDataStream data(file_); if (header.isBoundary()) { diff --git a/pbom/io/pboheaderio.h b/pbom/io/pboheaderio.h index 5dc25eb..30a5239 100644 --- a/pbom/io/pboheaderio.h +++ b/pbom/io/pboheaderio.h @@ -1,23 +1,24 @@ #pragma once #include "pbofile.h" -#include "model/pboentry.h" -#include "model/pboheader.h" +#include "io/pbonodeentity.h" +#include "io/pboheaderentity.h" +#include -namespace pboman3 { +namespace pboman3::io { using namespace std; class PboHeaderIO { public: explicit PboHeaderIO(PboFile* file); - QSharedPointer readNextEntry() const; + QSharedPointer readNextEntry() const; - QSharedPointer readNextHeader() const; + QSharedPointer readNextHeader() const; - void writeEntry(const PboEntry& entry) const; + void writeEntry(const PboNodeEntity& entry) const; - void writeHeader(const PboHeader& header) const; + void writeHeader(const PboHeaderEntity& header) const; private: PboFile* file_; diff --git a/pbom/io/pboheaderreader.cpp b/pbom/io/pboheaderreader.cpp index 3d8c1b0..a5f8cea 100644 --- a/pbom/io/pboheaderreader.cpp +++ b/pbom/io/pboheaderreader.cpp @@ -1,35 +1,35 @@ #include "pboheaderreader.h" #include +#include "pbofileformatexception.h" #include "pboheaderio.h" -#include "pboioexception.h" -namespace pboman3 { +namespace pboman3::io { PboFileHeader PboHeaderReader::readFileHeader(PboFile* file) { - QList> headers; - QList> entries; + QList> headers; + QList> entries; const PboHeaderIO reader(file); - QSharedPointer entry = reader.readNextEntry(); + QSharedPointer entry = reader.readNextEntry(); if (!entry) { - throw PboIoException("The file is not a valid PBO.", file->fileName()); + throw PboFileFormatException("The file is not a valid PBO."); } qsizetype dataBlockEnd = 0; if (entry->isSignature()) { - QSharedPointer header = reader.readNextHeader(); + QSharedPointer header = reader.readNextHeader(); while (header && !header->isBoundary()) { headers.append(header); header = reader.readNextHeader(); } if (!header) { - throw PboIoException("The file headers are corrupted.", file->fileName()); + throw PboFileFormatException("The file headers are corrupted."); } } else if (entry->isContent()) { entries.append(entry); dataBlockEnd += entry->dataSize(); } else { - throw PboIoException("The file first entry is corrupted.", file->fileName()); + throw PboFileFormatException("The file first entry is corrupted."); } entry = reader.readNextEntry(); @@ -39,10 +39,9 @@ namespace pboman3 { entry = reader.readNextEntry(); } if (!entry || !entry->isBoundary()) { - throw PboIoException("The file entries list is corrupted.", file->fileName()); + throw PboFileFormatException("The file entries list is corrupted."); } - const qsizetype dataBlockStart = file->pos(); dataBlockEnd += dataBlockStart; diff --git a/pbom/io/pboheaderreader.h b/pbom/io/pboheaderreader.h index 136dddb..a1aac3f 100644 --- a/pbom/io/pboheaderreader.h +++ b/pbom/io/pboheaderreader.h @@ -1,13 +1,14 @@ #pragma once #include "pbofile.h" -#include "model/pboentry.h" -#include "model/pboheader.h" +#include "io/pbonodeentity.h" +#include "io/pboheaderentity.h" +#include -namespace pboman3 { +namespace pboman3::io { struct PboFileHeader { - QList> headers; - QList> entries; + QList> headers; + QList> entries; qsizetype dataBlockStart; QByteArray signature; diff --git a/pbom/io/pboioexception.cpp b/pbom/io/pboioexception.cpp deleted file mode 100644 index ce28325..0000000 --- a/pbom/io/pboioexception.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "pboioexception.h" - -namespace pboman3 { - PboIoException::PboIoException(QString message, QString file) - : AppException(std::move(message)), - file_(std::move(file)){ - } - - QDebug operator<<(QDebug debug, const PboIoException& ex) { - return debug << "PboIoException(Message=" << ex.message_ << "; File=" << ex.file_ << ")"; - } - - void PboIoException::raise() const { - throw* this; - } - - QException* PboIoException::clone() const { - return new PboIoException(*this); - } - - QString PboIoException::file() const { - return file_; - } -} diff --git a/pbom/io/pboioexception.h b/pbom/io/pboioexception.h deleted file mode 100644 index fcc7d79..0000000 --- a/pbom/io/pboioexception.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include "util/exception.h" - -namespace pboman3 { - class PboIoException : public AppException { - public: - PboIoException(QString message, QString file); - - friend QDebug operator<<(QDebug debug, const PboIoException& ex); - - void raise() const override; - - QException* clone() const override; - - QString file() const; - - private: - QString file_; - }; -} diff --git a/pbom/model/pboentry.cpp b/pbom/io/pbonodeentity.cpp similarity index 52% rename from pbom/model/pboentry.cpp rename to pbom/io/pbonodeentity.cpp index cdbe16a..d6dd330 100644 --- a/pbom/model/pboentry.cpp +++ b/pbom/io/pbonodeentity.cpp @@ -1,15 +1,16 @@ -#include "pboentry.h" +#include "pbonodeentity.h" +#include -namespace pboman3 { - PboEntry PboEntry::makeSignature() { - return PboEntry("", PboPackingMethod::Product, 0, 0, 0, 0); +namespace pboman3::io { + PboNodeEntity PboNodeEntity::makeSignature() { + return PboNodeEntity("", PboPackingMethod::Product, 0, 0, 0, 0); } - PboEntry PboEntry::makeBoundary() { - return PboEntry("", PboPackingMethod::Uncompressed, 0, 0, 0, 0); + PboNodeEntity PboNodeEntity::makeBoundary() { + return PboNodeEntity("", PboPackingMethod::Uncompressed, 0, 0, 0, 0); } - PboEntry::PboEntry(QString fileName, PboPackingMethod packingMethod, + PboNodeEntity::PboNodeEntity(QString fileName, PboPackingMethod packingMethod, qint32 originalSize, qint32 reserved, qint32 timestamp, qint32 dataSize) : fileName_(std::move(fileName)), @@ -20,60 +21,60 @@ namespace pboman3 { dataSize_(dataSize) { } - bool PboEntry::isBoundary() const { + bool PboNodeEntity::isBoundary() const { return fileName_.isEmpty(); } - bool PboEntry::isSignature() const { + bool PboNodeEntity::isSignature() const { return packingMethod_ == PboPackingMethod::Product; } - bool PboEntry::isCompressed() const { + bool PboNodeEntity::isCompressed() const { return packingMethod_ == PboPackingMethod::Packed && originalSize_ != dataSize_; } - bool PboEntry::isContent() const { + bool PboNodeEntity::isContent() const { return !isBoundary() && packingMethod_ == PboPackingMethod::Uncompressed || packingMethod_ == PboPackingMethod::Packed; } - int PboEntry::size() const { + int PboNodeEntity::size() const { return static_cast(fileName_.length()) + sizeOfFields; } - PboPath PboEntry::makePath() const { + PboPath PboNodeEntity::makePath() const { return PboPath(fileName_); } //each header entry consists of 5x4 bytes of fields + filename.length + 1 byte zero string terminator - constexpr int PboEntry::sizeOfFields = 21; + constexpr int PboNodeEntity::sizeOfFields = 21; - const QString& PboEntry::fileName() const { + const QString& PboNodeEntity::fileName() const { return fileName_; } - PboPackingMethod PboEntry::packingMethod() const { + PboPackingMethod PboNodeEntity::packingMethod() const { return packingMethod_; } - qint32 PboEntry::originalSize() const { + qint32 PboNodeEntity::originalSize() const { return originalSize_; } - qint32 PboEntry::reserved() const { + qint32 PboNodeEntity::reserved() const { return reserved_; } - qint32 PboEntry::timestamp() const { + qint32 PboNodeEntity::timestamp() const { return timestamp_; } - qint32 PboEntry::dataSize() const { + qint32 PboNodeEntity::dataSize() const { return dataSize_; } - QDebug operator<<(QDebug debug, const PboEntry& entry) { - return debug << "PboEntry(FileName=" << entry.fileName_ << ", PackingMethod=" << + QDebug operator<<(QDebug debug, const PboNodeEntity& entry) { + return debug << "PboNodeEntity(FileName=" << entry.fileName_ << ", PackingMethod=" << static_cast(entry.packingMethod_) << ", OriginalSize=" << entry.originalSize_ << ", TimeStamp=" << entry.timestamp_ << ", DataSize=" << entry.dataSize_ << ")"; } diff --git a/pbom/model/pboentry.h b/pbom/io/pbonodeentity.h similarity index 68% rename from pbom/model/pboentry.h rename to pbom/io/pbonodeentity.h index 2c9e1c3..b5c1234 100644 --- a/pbom/model/pboentry.h +++ b/pbom/io/pbonodeentity.h @@ -1,10 +1,10 @@ #pragma once -#include "pbopath.h" -#include "io/bs/binarysource.h" +#include "domain/pbopath.h" -namespace pboman3 { +namespace pboman3::io { using namespace std; + using namespace domain; enum class PboPackingMethod { Uncompressed = 0x00000000, @@ -12,17 +12,17 @@ namespace pboman3 { Product = 0x56657273 }; - class PboEntry { + class PboNodeEntity { public: - static PboEntry makeSignature(); + static PboNodeEntity makeSignature(); - static PboEntry makeBoundary(); + static PboNodeEntity makeBoundary(); - PboEntry(QString fileName, PboPackingMethod packingMethod, + PboNodeEntity(QString fileName, PboPackingMethod packingMethod, qint32 originalSize, qint32 reserved, qint32 timestamp, qint32 dataSize); - virtual ~PboEntry() = default; + virtual ~PboNodeEntity() = default; bool isBoundary() const; @@ -48,7 +48,7 @@ namespace pboman3 { qint32 dataSize() const; - friend QDebug operator <<(QDebug debug, const PboEntry& entry); + friend QDebug operator <<(QDebug debug, const PboNodeEntity& entry); private: static const int sizeOfFields; diff --git a/pbom/io/pbowriter.cpp b/pbom/io/pbowriter.cpp deleted file mode 100644 index d1f05fa..0000000 --- a/pbom/io/pbowriter.cpp +++ /dev/null @@ -1,262 +0,0 @@ -#include "pbowriter.h" -#include -#include -#include "pbofile.h" -#include "pboheaderio.h" -#include "pboioexception.h" -#include "bs/pbobinarysource.h" - -namespace pboman3 { - PboWriter::PboWriter() - : root_(nullptr), - headers_(nullptr), - signature_(nullptr) { - } - - PboWriter& PboWriter::usePath(QString path) { - path_ = std::move(path); - return *this; - } - - PboWriter& PboWriter::useHeaders(HeadersModel* headers) { - headers_ = headers; - return *this; - } - - PboWriter& PboWriter::useRoot(PboNode* root) { - root_ = root; - return *this; - } - - PboWriter& PboWriter::copySignatureTo(QByteArray* signature) { - signature_ = signature; - return *this; - } - - void PboWriter::write(const Cancel& cancel) { - assert(!path_.isEmpty() && "Path must not be empty"); - assert(root_ && "Root must not be null"); - - QTemporaryFile body; - body.setFileName(path_ + ".b"); - if (!body.open()) - throw PboIoException("Could not create the file.", body.fileName()); - - QList entries; - writeNode(&body, root_, entries, cancel); - - if (cancel()) - return; - - PboFile pbo(path_); - if (!pbo.open(QIODeviceBase::ReadWrite)) - throw PboIoException("Could not create the file.", path_); - - writeHeader(&pbo, entries, cancel); - - if (cancel()) { - pbo.close(); - pbo.remove(); - return; - } - - const bool seek = body.seek(0); - assert(seek); - - copyBody(&pbo, &body, cancel); - - writeSignature(&pbo, cancel); - - body.close(); - pbo.close(); - - if (cancel()) { - pbo.remove(); - } - } - - void PboWriter::suspendBinarySources() const { - suspendBinarySources(root_); - } - - void PboWriter::resumeBinarySources() const { - resumeBinarySources(root_); - } - - void PboWriter::assignBinarySources(const QString& path) { - assignBinarySources(root_, path); - } - - void PboWriter::writeNode(QFileDevice* file, PboNode* node, QList& entries, const Cancel& cancel) { - for (PboNode* child : *node) { - if (cancel()) - return; - - if (child->nodeType() == PboNodeType::File) { - const qint64 before = file->pos(); - child->binarySource->writeToPbo(file, cancel); - const qint64 after = file->pos(); - - const qint32 originalSize = child->binarySource->readOriginalSize(); - const auto dataSize = static_cast(after - before); - - PboEntry entry( - child->makePath().toString(), - child->binarySource->isCompressed() ? PboPackingMethod::Packed : PboPackingMethod::Uncompressed, - child->binarySource->readOriginalSize(), - 0, - child->binarySource->readTimestamp(), - dataSize); - entries.append(entry); - - PboDataInfo data{0, 0, 0, 0, 0}; - data.originalSize = originalSize; - data.dataSize = dataSize; - data.dataOffset = before; - data.timestamp = child->binarySource->readTimestamp(); - data.compressed = child->binarySource->isCompressed(); - - binarySources_.insert(child, data); - - emitWriteEntry(); - } else { - writeNode(file, child, entries, cancel); - } - } - } - - void PboWriter::writeHeader(PboFile* file, const QList& entries, const Cancel& cancel) { - const PboHeaderIO io(file); - io.writeEntry(PboEntry::makeSignature()); - - for (const QSharedPointer& header : *headers_) { - if (cancel()) { - break; - } - io.writeHeader(*header); - } - - if (cancel()) { - return; - } - - io.writeHeader(PboHeader::makeBoundary()); - - for (const PboEntry& entry : entries) { - if (cancel()) { - break; - } - io.writeEntry(entry); - } - - if (cancel()) { - return; - } - - io.writeEntry(PboEntry::makeBoundary()); - - for (PboNode* key : binarySources_.keys()) { - PboDataInfo& existing = binarySources_[key]; - existing.dataOffset += file->pos(); - } - } - - void PboWriter::copyBody(QFileDevice* pbo, QFileDevice* body, const Cancel& cancel) { - QByteArray data; - data.resize(1024 * 1024); - - qsizetype copiedBytes = 0; - const qsizetype totalBytes = body->size(); - - qint64 read = body->read(data.data(), data.size()); - while (read > 0) { - pbo->write(data.data(), read); - - copiedBytes += read; - emitCopyBytes(copiedBytes, totalBytes); - - if (cancel()) - return; - read = body->read(data.data(), data.size()); - } - } - - void PboWriter::writeSignature(QFileDevice* pbo, const Cancel& cancel) { - const bool seek = pbo->seek(0); - assert(seek); - - QCryptographicHash sha1(QCryptographicHash::Sha1); - - qsizetype processed = 0; - const qsizetype total = pbo->size(); - - constexpr qint64 bufferSize = 1024; - char buffer[bufferSize]; - qint64 read; - - while (!cancel() && (read = pbo->read(buffer, bufferSize)) > 0) { - sha1.addData(buffer, read); - processed += read; - emitCalcHash(processed, total); - } - - if (cancel()) - return; - - const QByteArray sha1Bytes = sha1.result(); - - pbo->write(QByteArray(1, 0)); - pbo->write(sha1Bytes, sha1Bytes.count()); - - if (signature_) { - signature_->append(sha1Bytes); - } - } - - void PboWriter::suspendBinarySources(PboNode* node) const { - for (PboNode* child : *node) { - if (child->nodeType() == PboNodeType::File) { - child->binarySource->close(); - } else { - suspendBinarySources(child); - } - } - } - - void PboWriter::resumeBinarySources(PboNode* node) const { - for (PboNode* child : *node) { - if (child->nodeType() == PboNodeType::File) { - child->binarySource->open(); - } else { - resumeBinarySources(child); - } - } - } - - void PboWriter::assignBinarySources(PboNode* node, const QString& path) { - for (PboNode* child : *node) { - if (child->nodeType() == PboNodeType::File) { - const PboDataInfo& existing = binarySources_.take(child); - child->binarySource = QSharedPointer(new PboBinarySource(path, existing)); - child->binarySource->open(); - } else { - assignBinarySources(child, path); - } - } - } - - void PboWriter::emitWriteEntry() { - const WriteEntryEvent evt; - emit progress(&evt); - } - - void PboWriter::emitCopyBytes(qsizetype copied, qsizetype total) { - const CopyBytesEvent evt(copied, total); - emit progress(&evt); - } - - void PboWriter::emitCalcHash(qsizetype processed, qsizetype total) { - const CalcHashEvent evt(processed, total); - emit progress(&evt); - } -} diff --git a/pbom/main.cpp b/pbom/main.cpp index a85c10a..f23e074 100644 --- a/pbom/main.cpp +++ b/pbom/main.cpp @@ -9,7 +9,7 @@ #include "ui/mainwindow.h" #include "ui/packwindow.h" #include "ui/unpackwindow.h" -#include "util/exception.h" +#include "exception.h" #include "util/log.h" #define LOG(...) LOGGER("Main", __VA_ARGS__) @@ -60,8 +60,8 @@ namespace pboman3 { ActivateCom(app); LOG(info, "Display the main window") - const auto model = QScopedPointer(new PboModel()); - MainWindow w(nullptr, model.get()); + const auto model = QScopedPointer(new model::PboModel()); + ui::MainWindow w(nullptr, model.get()); w.show(); if (!pboFile.isEmpty()) { @@ -86,7 +86,7 @@ namespace pboman3 { ActivateCom(app); int exitCode; - PackWindow w(nullptr); + ui::PackWindow w(nullptr); if (outputDir.isEmpty()) { exitCode = w.tryPackFoldersWithPrompt(folders) ? QApplication::exec() : 0; } else { @@ -109,7 +109,7 @@ namespace pboman3 { ActivateCom(app); int exitCode; - UnpackWindow w(nullptr); + ui::UnpackWindow w(nullptr); if (outputDir.isEmpty()) { exitCode = w.tryUnpackFilesWithPrompt(files) ? QApplication::exec() : 0; } else { diff --git a/pbom/model/CMakeLists.txt b/pbom/model/CMakeLists.txt new file mode 100644 index 0000000..471dad9 --- /dev/null +++ b/pbom/model/CMakeLists.txt @@ -0,0 +1,18 @@ +list(APPEND PROJECT_SOURCES + "model/task/packtask.cpp" + "model/task/packwindowmodel.cpp" + "model/task/task.h" + "model/task/taskwindowmodel.cpp" + "model/task/unpacktask.cpp" + "model/task/unpackwindowmodel.cpp" + "model/conflictsparcel.cpp" + "model/interactionparcel.cpp" + "model/pbomodel.cpp") + +set(PROJECT_SOURCES ${PROJECT_SOURCES} PARENT_SCOPE) + +list(APPEND TEST_SOURCES + "model/__test__/conflictsparcel_test.cpp" + "model/__test__/interactionparcel_test.cpp") + +set(TEST_SOURCES ${TEST_SOURCES} PARENT_SCOPE) diff --git a/pbom/model/__test__/conflictsparcel_test.cpp b/pbom/model/__test__/conflictsparcel_test.cpp index 070a51f..f69ae1f 100644 --- a/pbom/model/__test__/conflictsparcel_test.cpp +++ b/pbom/model/__test__/conflictsparcel_test.cpp @@ -1,7 +1,7 @@ #include #include "model/conflictsparcel.h" -namespace pboman3::test { +namespace pboman3::model::test { TEST(ConflictsParcelTest, GetResolution_Returns_Unset_If_Resolution_Was_Not_Set) { const ConflictsParcel conflicts; const ConflictResolution res = conflicts.getResolution(NodeDescriptor(nullptr, PboPath("some-path"))); diff --git a/pbom/model/__test__/headersmodel_test.cpp b/pbom/model/__test__/headersmodel_test.cpp deleted file mode 100644 index 9ada894..0000000 --- a/pbom/model/__test__/headersmodel_test.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "model/headersmodel.h" -#include - -namespace pboman3::test { - TEST(HeadersModelTest, SetData_Sets_Data) { - const QSharedPointer p1(new PboHeader("p1", "v1")); - const QSharedPointer p2(new PboHeader("p2", "v2")); - - HeadersModel model; - model.setData(QList({p1, p2})); - - auto it = model.begin(); - ASSERT_EQ(*it, p1); - ++it; - ASSERT_EQ(*it, p2); - ++it; - ASSERT_EQ(it, model.end()); - } - - TEST(HeadersModelTest, SetData_Replaces_Data) { - const QSharedPointer p1(new PboHeader("p1", "v1")); - const QSharedPointer p2(new PboHeader("p2", "v2")); - const QSharedPointer p3(new PboHeader("p3", "v3")); - const QSharedPointer p4(new PboHeader("p4", "v4")); - - HeadersModel model; - model.setData(QList({p1, p2})); - model.setData(QList({p3, p4})); - - auto it = model.begin(); - ASSERT_EQ(*it, p3); - ++it; - ASSERT_EQ(*it, p4); - ++it; - ASSERT_EQ(it, model.end()); - } - - TEST(HeadersModelTest, SetData_Triggers_If_Data_Different) { - const QSharedPointer p1(new PboHeader("p1", "v1")); - const QSharedPointer p2(new PboHeader("p2", "v2")); - const QSharedPointer p3(new PboHeader("p3", "v3")); - const QSharedPointer p4(new PboHeader("p4", "v4")); - - HeadersModel model; - model.setData(QList({p1, p2})); - - int count = 0; - auto callback = [&count]() { count++; }; - QObject::connect(&model, &HeadersModel::changed, callback); - - model.setData(QList({p3, p4})); - - ASSERT_EQ(count, 1); - } - - TEST(HeadersModelTest, SetData_Triggers_If_Data_Same) { - const QSharedPointer p1(new PboHeader("p1", "v1")); - const QSharedPointer p2(new PboHeader("p2", "v2")); - const QSharedPointer p3(new PboHeader("p1", "v1")); - const QSharedPointer p4(new PboHeader("p2", "v2")); - - HeadersModel model; - model.setData(QList({p1, p2})); - - int count = 0; - auto callback = [&count]() { count++; }; - QObject::connect(&model, &HeadersModel::changed, callback); - - model.setData(QList({p3, p4})); - - ASSERT_EQ(count, 0); - } -} diff --git a/pbom/model/__test__/interactionparcel_test.cpp b/pbom/model/__test__/interactionparcel_test.cpp index 3bff468..9c8199b 100644 --- a/pbom/model/__test__/interactionparcel_test.cpp +++ b/pbom/model/__test__/interactionparcel_test.cpp @@ -3,10 +3,10 @@ #include #include "io/bs/fsrawbinarysource.h" #include "io/bs/pbobinarysource.h" -#include "model/pbonode.h" -#include "model/pbopath.h" +#include "domain/pbonode.h" +#include "domain/pbopath.h" -namespace pboman3::test { +namespace pboman3::model::test { TEST(NodeDescriptorTest, Ctor_Initializes_Fields) { QTemporaryFile t; t.open(); diff --git a/pbom/model/__test__/pboentry_test.cpp b/pbom/model/__test__/pboentry_test.cpp deleted file mode 100644 index a8c841a..0000000 --- a/pbom/model/__test__/pboentry_test.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include "model/pboentry.h" - -namespace pboman3::test { - TEST(PboEntryTest, Ctor_Functional) { - const PboEntry entry("some-file", PboPackingMethod::Packed, 1, 2, 3, 4); - ASSERT_EQ(entry.fileName(), "some-file"); - ASSERT_EQ(entry.packingMethod(), PboPackingMethod::Packed); - ASSERT_EQ(entry.originalSize(), 1); - ASSERT_EQ(entry.reserved(), 2); - ASSERT_EQ(entry.timestamp(), 3); - ASSERT_EQ(entry.dataSize(), 4); - } - - TEST(PboEntryTest, IsBoundary_Functional) { - const PboEntry entry = PboEntry::makeBoundary(); - ASSERT_TRUE(entry.isBoundary()); - ASSERT_FALSE(entry.isContent()); - } - - TEST(PboEntryTest, IsSignature_Functional) { - const PboEntry entry = PboEntry::makeSignature(); - ASSERT_TRUE(entry.isSignature()); - ASSERT_FALSE(entry.isContent()); - } - - // ReSharper disable once CppInconsistentNaming - class PboEntryTest_IsContent : public testing::TestWithParam { - }; - - TEST_P(PboEntryTest_IsContent, Functional) { - const PboEntry entry("some-file", GetParam(), 100, 0, 0, 100); - ASSERT_FALSE(entry.isSignature()); - ASSERT_FALSE(entry.isBoundary()); - ASSERT_TRUE(entry.isContent()); - } - - INSTANTIATE_TEST_SUITE_P(IsContent, PboEntryTest_IsContent, - testing::Values(PboPackingMethod::Packed, PboPackingMethod::Uncompressed)); - - TEST(PboEntryTest, Size_Functional) { - const PboEntry entry("some-file", PboPackingMethod::Packed, 1, 2, 3, 4); - ASSERT_EQ(entry.size(), entry.fileName().size() + 21); - } - - TEST(PboEntryTest, IsCompressed_Functional) { - const PboEntry entry1("some-file", PboPackingMethod::Packed, 1, 2, 3, 4); - ASSERT_TRUE(entry1.isCompressed()); - const PboEntry entry2("some-file", PboPackingMethod::Uncompressed, 1, 2, 3, 1); - ASSERT_FALSE(entry2.isCompressed()); - } -} diff --git a/pbom/model/__test__/pboheader_test.cpp b/pbom/model/__test__/pboheader_test.cpp deleted file mode 100644 index 738ac11..0000000 --- a/pbom/model/__test__/pboheader_test.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include "model/pboheader.h" - -namespace pboman3::test { - TEST(PboHeaderTest, Ctor_Functional) { - const PboHeader header("name1", "value1"); - ASSERT_EQ(header.name, "name1"); - ASSERT_EQ(header.value, "value1"); - - ASSERT_FALSE(header.isBoundary()); - } - - TEST(PboHeaderTest, IsBoundary_Functional) { - const PboHeader header = PboHeader::makeBoundary(); - ASSERT_TRUE(header.isBoundary()); - } -} diff --git a/pbom/model/__test__/rootreader_test.cpp b/pbom/model/__test__/rootreader_test.cpp deleted file mode 100644 index f53c70f..0000000 --- a/pbom/model/__test__/rootreader_test.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "model/rootreader.h" -#include -#include -#include "io/pboheaderreader.h" -#include "io/bs/pbobinarysource.h" - -namespace pboman3::test { - TEST(RootReaderTest, InflateRoot_Functional) { - QTemporaryFile file; - file.open(); - file.close(); - - const PboFileHeader header{ - QList>(0), - QList{ - QSharedPointer(new PboEntry("f1", PboPackingMethod::Packed, 1, 2, 3, 4)), - QSharedPointer(new PboEntry("f2", PboPackingMethod::Uncompressed, 5, 6, 7, 8)), - }, - 100, - QByteArray() - }; - - PboNode root("root", PboNodeType::Container, nullptr); - RootReader(&header, file.fileName()).inflateRoot(&root); - - ASSERT_EQ(root.count(), 2); - - ASSERT_EQ(root.at(0)->title(), "f1"); - const auto source1 = dynamic_cast(root.at(0)->binarySource.get()); - ASSERT_TRUE(source1); - ASSERT_EQ(source1->getInfo().originalSize, header.entries.at(0)->originalSize()); - ASSERT_EQ(source1->getInfo().dataSize, header.entries.at(0)->dataSize()); - ASSERT_EQ(source1->getInfo().dataOffset, header.dataBlockStart); - ASSERT_EQ(source1->getInfo().timestamp, header.entries.at(0)->timestamp()); - ASSERT_TRUE(source1->getInfo().compressed); - - ASSERT_EQ(root.at(1)->title(), "f2"); - const auto source2 = dynamic_cast(root.at(1)->binarySource.get()); - ASSERT_TRUE(source2); - ASSERT_EQ(source2->getInfo().originalSize, header.entries.at(1)->originalSize()); - ASSERT_EQ(source2->getInfo().dataSize, header.entries.at(1)->dataSize()); - ASSERT_EQ(source2->getInfo().dataOffset, header.dataBlockStart + source1->getInfo().dataSize); - ASSERT_EQ(source2->getInfo().timestamp, header.entries.at(1)->timestamp()); - ASSERT_FALSE(source2->getInfo().compressed); - } -} diff --git a/pbom/model/__test__/signaturemodel_test.cpp b/pbom/model/__test__/signaturemodel_test.cpp deleted file mode 100644 index 8e50e72..0000000 --- a/pbom/model/__test__/signaturemodel_test.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "model/signaturemodel.h" - -namespace pboman3::test { - TEST(SignatureModelTest, SignatureBytes_Returns_Empty_By_Default) { - const SignatureModel m; - ASSERT_TRUE(m.signatureString().isNull()); - } - - TEST(SignatureModelTest, SetSignatureBytes_Sets_Bytes) { - SignatureModel m; - - m.setSignatureBytes(QByteArray(10, 5)); - ASSERT_EQ(m.signatureString(), "05 05 05 05 05 05 05 05 05 05"); - - m.setSignatureBytes(QByteArray()); - ASSERT_TRUE(m.signatureString().isNull()); - } -} diff --git a/pbom/model/conflictsparcel.cpp b/pbom/model/conflictsparcel.cpp index 3908b5f..b0f4634 100644 --- a/pbom/model/conflictsparcel.cpp +++ b/pbom/model/conflictsparcel.cpp @@ -1,6 +1,6 @@ #include "conflictsparcel.h" -namespace pboman3 { +namespace pboman3::model { ConflictResolution ConflictsParcel::getResolution(const NodeDescriptor& descriptor) const { return conflicts_.contains(descriptor.path()) ? conflicts_[descriptor.path()] : ConflictResolution::Unset; } diff --git a/pbom/model/conflictsparcel.h b/pbom/model/conflictsparcel.h index b16dfd0..a0fc3dd 100644 --- a/pbom/model/conflictsparcel.h +++ b/pbom/model/conflictsparcel.h @@ -1,10 +1,11 @@ #pragma once -#include "conflictresolution.h" +#include "domain/conflictresolution.h" +#include "domain/pbopath.h" #include "interactionparcel.h" #include -namespace pboman3 { +namespace pboman3::model { class ConflictsParcel { public: ConflictResolution getResolution(const NodeDescriptor& descriptor) const; diff --git a/pbom/model/headersmodel.cpp b/pbom/model/headersmodel.cpp deleted file mode 100644 index 3edcf6b..0000000 --- a/pbom/model/headersmodel.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "headersmodel.h" - -namespace pboman3 { - QList>::iterator HeadersModel::begin() { - return data_.begin(); - } - - QList>::iterator HeadersModel::end() { - return data_.end(); - } - - void HeadersModel::setData(QList> data) { - if (hasChanges(data)) { - data_ = std::move(data); - emit changed(); - } - } - - bool HeadersModel::hasChanges(const QList>& data) const { - if (data.count() != data_.count()) { - return true; - } - - auto d1 = data.begin(); - auto d2 = data_.begin(); - - while (d1 != data.end()) { - if (areDifferent(**d1, **d2)) { - return true; - } - ++d1; - ++d2; - } - - return false; - } - - bool HeadersModel::areDifferent(const PboHeader& h1, const PboHeader& h2) const { - return h1.name != h2.name || h1.value != h2.value; - } -} diff --git a/pbom/model/headersmodel.h b/pbom/model/headersmodel.h deleted file mode 100644 index 5fe3122..0000000 --- a/pbom/model/headersmodel.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include "pboheader.h" -#include "util/qpointerlistiterator.h" - -namespace pboman3 { - class HeadersModel : public QObject { - Q_OBJECT - public: - QList>::iterator begin(); - - QList>::iterator end(); - - void setData(QList> data); - - signals: - void changed(); - - private: - QList> data_; - - bool hasChanges(const QList>& data) const; - - bool areDifferent(const PboHeader& h1, const PboHeader& h2) const; - }; -} diff --git a/pbom/model/interactionparcel.cpp b/pbom/model/interactionparcel.cpp index 2b46367..2c9a438 100644 --- a/pbom/model/interactionparcel.cpp +++ b/pbom/model/interactionparcel.cpp @@ -1,9 +1,9 @@ #include "interactionparcel.h" #include #include -#include "util/exception.h" +#include "exception.h" -namespace pboman3 { +namespace pboman3::model { const QSharedPointer& NodeDescriptor::binarySource() const { return binarySource_; } @@ -148,7 +148,7 @@ namespace pboman3 { return QSharedPointer(new FsRawBinarySource(fsPath)); } - NodeDescriptors NodeDescriptors::packNodes(const QList& nodes) { + NodeDescriptors NodeDescriptors::packNodes(const QList& nodes) { NodeDescriptors descriptors; descriptors.reserve(nodes.length() * 2); diff --git a/pbom/model/interactionparcel.h b/pbom/model/interactionparcel.h index 643343e..6975f1c 100644 --- a/pbom/model/interactionparcel.h +++ b/pbom/model/interactionparcel.h @@ -1,14 +1,17 @@ #pragma once -#include "pbonode.h" -#include "pbopath.h" -#include "io/bs/binarysource.h" +#include "domain/pbopath.h" +#include "domain/binarysource.h" #include "io/bs/fslzhbinarysource.h" #include "io/bs/fsrawbinarysource.h" #include "io/bs/pbobinarysource.h" #include +#include "domain/pbonode.h" + +namespace pboman3::model { + using namespace domain; + using namespace io; -namespace pboman3 { enum class BinarySourceType { Pbo = 0, FsLzh = 1, @@ -46,7 +49,7 @@ namespace pboman3 { static NodeDescriptors deserialize(const QByteArray& data); - static NodeDescriptors packNodes(const QList& nodes); + static NodeDescriptors packNodes(const QList& nodes); private: static void writeNodeInfo(QDataStream& stream, const NodeDescriptor& nodeInfo); diff --git a/pbom/model/pboheader.cpp b/pbom/model/pboheader.cpp deleted file mode 100644 index d436ee2..0000000 --- a/pbom/model/pboheader.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "pboheader.h" - -namespace pboman3 { - PboHeader PboHeader::makeBoundary() { - return PboHeader(QString(), QString()); - } - - PboHeader::PboHeader(QString name, QString value) - : name(std::move(name)), - value(std::move(value)) { - } - - bool PboHeader::isBoundary() const { - return name.isEmpty() && value.isEmpty(); - } - -} diff --git a/pbom/model/pboheader.h b/pbom/model/pboheader.h deleted file mode 100644 index dcf898a..0000000 --- a/pbom/model/pboheader.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -namespace pboman3 { - struct PboHeader { - static PboHeader makeBoundary(); - - const QString name; - const QString value; - - PboHeader(QString name, QString value); - - bool isBoundary() const; - }; -} diff --git a/pbom/model/pbomodel.cpp b/pbom/model/pbomodel.cpp index 02c2ed7..2393f07 100644 --- a/pbom/model/pbomodel.cpp +++ b/pbom/model/pbomodel.cpp @@ -1,18 +1,19 @@ #include "pbomodel.h" +#include "domain/pbonode.h" +#include "domain/func.h" #include #include #include -#include "diskaccessexception.h" -#include "pbofileformatexception.h" -#include "rootreader.h" -#include "io/pboheaderreader.h" -#include "io/pboioexception.h" -#include "io/pbowriter.h" +#include "io/documentreader.h" +#include "io/documentwriter.h" +#include "exception.h" #include "util/log.h" #define LOG(...) LOGGER("model/PboModel", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::model { + using namespace io; + void PboModel::loadFile(const QString& path) { LOG(info, "Loading the file:", path) @@ -23,120 +24,60 @@ namespace pboman3 { setLoadedPath(path); - PboFile file(loadedPath_); - if (!file.open(QIODeviceBase::OpenModeFlag::ReadWrite)) - throw DiskAccessException("Can not access the file. Check if it is used by other processes.", path); - - QString title = QFileInfo(path).fileName(); - LOG(info, "The file title is:", title) - - rootEntry_ = QSharedPointer(new PboNode(std::move(title), PboNodeType::Container, nullptr)); - connect(rootEntry_.get(), &PboNode::hierarchyChanged, this, &PboModel::modelChanged); - connect(rootEntry_.get(), &PboNode::titleChanged, this, &PboModel::rootTitleChanged); - - PboFileHeader header; + const DocumentReader reader(path); try { - header = PboHeaderReader::readFileHeader(&file); - LOG(info, "The file header:", header) - } catch (const PboIoException& ex) { - LOG(warning, "Got error while reading the file header:", ex) - throw PboFileFormatException("Can not open the file. It is not a valid PBO."); + document_ = reader.read(); + LOG(info, "Read the document:", *document_); + } catch (const AppException& ex) { + LOG(info, "Could not load the file:", ex) + setLoadedPath(nullptr); + throw; } - headers_ = QSharedPointer(new HeadersModel); - headers_->setData(std::move(header.headers)); - connect(headers_.get(), &HeadersModel::changed, this, &PboModel::modelChanged); - - signature_ = QSharedPointer(new SignatureModel); - signature_->setSignatureBytes(header.signature); - - RootReader(&header, path).inflateRoot(rootEntry_.get()); + connect(document_.get(), &PboDocument::changed, this, &PboModel::modelChanged); + connect(document_.get(), &PboDocument::titleChanged, this, &PboModel::titleChanged); LOG(info, "Creating the binary backend") binaryBackend_ = QSharedPointer( new BinaryBackend(QUuid::createUuid().toString(QUuid::WithoutBraces))); + + emit loadedStatusChanged(true); } void PboModel::saveFile(const Cancel& cancel, const QString& filePath) { LOG(info, "Saving the model to:", filePath) const QString savePath = filePath.isNull() ? loadedPath_ : filePath; - const QString tempPath(savePath + ".t"); - LOG(info, "The savePath was set as:", savePath) - LOG(info, "The tempPath was set as:", tempPath) - QByteArray signature; + DocumentWriter writer(savePath); + writer.write(document_.get(), cancel); - PboWriter writer; - writer.usePath(savePath == loadedPath_ ? tempPath : savePath) - .useHeaders(headers_.get()) - .useRoot(rootEntry_.get()) - .copySignatureTo(&signature); - - try { - LOG(info, "Writing the file") - writer.write(cancel); - } catch (const PboIoException& ex) { - LOG(warning, "Got error while writing:", ex) - throw DiskAccessException(ex); - } - - if (cancel()) { - LOG(info, "The write process was canceled - exit") - return; - } - - LOG(info, "Clean up the previous binary sources") - writer.suspendBinarySources(); - - LOG(info, "Update the model signature") - signature_->setSignatureBytes(signature); - - if (savePath == loadedPath_) { - const QString backupPath = loadedPath_ + ".bak"; - LOG(info, "Cleaning up the temporary files") - if (QFile::exists(backupPath) && !QFile::remove(backupPath)) { - LOG(info, "Could not remove the prev backup file - throwing;", backupPath) - writer.resumeBinarySources(); - throw DiskAccessException( - "Could not remove the file. Check you have enough permissions and the file is not locked by another process.", - backupPath); - } - if (!QFile::rename(loadedPath_, backupPath)) { - LOG(info, "Could not replace the prev PBO file with a write copy - throwing;", loadedPath_) - writer.resumeBinarySources(); - throw DiskAccessException( - "Could not write to the file. Check you have enough permissions and the file is not locked by another process.", - loadedPath_); - } - const bool renamed = QFile::rename(tempPath, loadedPath_); - assert(renamed); + if (!cancel()) { + LOG(info, "Write process complete") + setLoadedPath(savePath); + } else { + LOG(info, "Write process cancelled") } - - LOG(info, "Assign binary sources back") - writer.assignBinarySources(savePath); - - setLoadedPath(savePath); } void PboModel::unloadFile() { - if (!rootEntry_) + if (!document_) throw InvalidOperationException("The model is not initialized"); LOG(info, "Unloading the current file") setLoadedPath(nullptr); - signature_.clear(); - headers_.clear(); - rootEntry_.clear(); + emit loadedStatusChanged(false); + + document_.clear(); binaryBackend_.clear(); } void PboModel::createNodeSet(PboNode* parent, const QList& descriptors, - const ConflictsParcel& conflicts) const { - if (!rootEntry_) + const ConflictsParcel& conflicts) const { + if (!document_) throw InvalidOperationException("The model is not initialized"); LOG(info, "Creating the set of nodes, parent:", *parent) @@ -158,14 +99,8 @@ namespace pboman3 { InteractionParcel PboModel::interactionPrepare(const QList& nodes, const Cancel& cancel) const { LOG(info, "Preparing the interaction for", nodes.count(), "nodes") - QList files; - try { - files = binaryBackend_->hddSync(nodes, cancel); - LOG(info, "Got files:", files) - } catch (const PboIoException& ex) { - LOG(warning, "Got error while syncing:", ex) - throw DiskAccessException(ex); - } + QList files = binaryBackend_->hddSync(nodes, cancel); + LOG(info, "Got files:", files) NodeDescriptors descriptors = NodeDescriptors::packNodes(nodes); LOG(info, "Got descriptors:", descriptors) @@ -176,27 +111,22 @@ namespace pboman3 { QString PboModel::execPrepare(const PboNode* node, const Cancel& cancel) const { LOG(info, "Preparing the execution of the node", *node) - QString file; - try { - file = binaryBackend_->execSync(node, cancel); - LOG(info, "The node contents was stored to:", file) - } catch (const PboIoException& ex) { - LOG(warning, "Got error while syncing:", ex) - throw DiskAccessException(ex); - } + QString file = binaryBackend_->execSync(node, cancel); + LOG(info, "The node contents was stored to:", file) return file; } - ConflictsParcel PboModel::checkConflicts(const PboNode* parent, const QList& descriptors) const { - if (!rootEntry_) + ConflictsParcel PboModel::checkConflicts(const PboNode* parent, + const QList& descriptors) const { + if (!document_) throw InvalidOperationException("The model is not initialized"); LOG(info, "Check conflicts for the set of descriptors") ConflictsParcel conflicts; for (const NodeDescriptor& descriptor : descriptors) { - if (parent->isPathConflict(PboPath(descriptor.path()))) { + if (IsPathConflict(parent, PboPath(descriptor.path()))) { LOG(info, "The descriptor is in conflict:", descriptor) conflicts.setResolution(descriptor, ConflictResolution::Copy); } @@ -205,33 +135,25 @@ namespace pboman3 { return conflicts; } - void PboModel::unpackNodesTo(const QDir& dest, const PboNode* rootNode, const QList& childNodes, - const Cancel& cancel) const { - try { - LOG(info, "Unpack", childNodes.count(), "nodes to", dest) - binaryBackend_->unpackSync(dest, rootNode, childNodes, cancel); - } catch (const PboIoException& ex) { - LOG(warning, "Got error while unpacking:", ex) - throw DiskAccessException(ex); - } - } - - PboNode* PboModel::rootEntry() const { - return rootEntry_.get(); + void PboModel::unpackNodesTo(const QDir& dest, const PboNode* rootNode, + const QList& childNodes, + const Cancel& cancel) const { + LOG(info, "Unpack", childNodes.count(), "nodes to", dest) + binaryBackend_->unpackSync(dest, rootNode, childNodes, cancel); } - HeadersModel* PboModel::headers() const { - return headers_.get(); - } - - SignatureModel* PboModel::signature() const { - return signature_.get(); + PboDocument* PboModel::document() const { + return document_.get(); } const QString& PboModel::loadedPath() const { return loadedPath_; } + bool PboModel::isLoaded() const { + return !loadedPath_.isNull(); + } + void PboModel::setLoadedPath(const QString& loadedFile) { if (loadedPath_ != loadedFile) { LOG(info, "Set the loadedPath to:", loadedFile) @@ -240,10 +162,10 @@ namespace pboman3 { } } - void PboModel::rootTitleChanged() { - LOG(info, "The root title was changed") + void PboModel::titleChanged() { + LOG(info, "The document title was changed") QFileInfo fi(loadedPath_); - fi.setFile(fi.dir(), rootEntry_->title()); + fi.setFile(fi.dir(), document_->root()->title()); setLoadedPath(fi.absoluteFilePath()); } } diff --git a/pbom/model/pbomodel.h b/pbom/model/pbomodel.h index e0d5fd3..8e90d58 100644 --- a/pbom/model/pbomodel.h +++ b/pbom/model/pbomodel.h @@ -1,17 +1,17 @@ #pragma once +#include "domain/pbodocument.h" #include #include #include "conflictsparcel.h" -#include "headersmodel.h" #include "interactionparcel.h" -#include "pbonode.h" -#include "signaturemodel.h" #include "io/bb/binarybackend.h" -namespace pboman3 { +namespace pboman3::model { + using namespace domain; + class PboModel : public QObject { - Q_OBJECT + Q_OBJECT public: void loadFile(const QString& path); @@ -29,28 +29,26 @@ namespace pboman3 { void unpackNodesTo(const QDir& dest, const PboNode* rootNode, const QList& childNodes, const Cancel& cancel) const; - PboNode* rootEntry() const; - - HeadersModel* headers() const; - - SignatureModel* signature() const; + PboDocument* document() const; const QString& loadedPath() const; + bool isLoaded() const; + signals: void modelChanged(); void loadedPathChanged(); + void loadedStatusChanged(bool loaded); + private: QString loadedPath_; - QSharedPointer rootEntry_; - QSharedPointer headers_; - QSharedPointer signature_; + QSharedPointer document_; QSharedPointer binaryBackend_; void setLoadedPath(const QString& loadedFile); - void rootTitleChanged(); + void titleChanged(); }; } diff --git a/pbom/model/pbonodeevents.cpp b/pbom/model/pbonodeevents.cpp deleted file mode 100644 index 868fe11..0000000 --- a/pbom/model/pbonodeevents.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "pbonodeevents.h" - -namespace pboman3 { - PboNodeCreatedEvent::PboNodeCreatedEvent(const PboPath* pNodePath, PboNodeType pNodeType) - : PboNodeEvent(), - nodePath(pNodePath), - nodeType(pNodeType) { - } - - PboNodeRenamedEvent::PboNodeRenamedEvent(const PboPath* pNodePath, QString pNewNodeTitle) - : PboNodeEvent(), - nodePath(pNodePath), - newNodeTitle(std::move(pNewNodeTitle)) { - } - - PboNodeRemovedEvent::PboNodeRemovedEvent(const PboPath* pNodePath) - : PboNodeEvent(), - nodePath(pNodePath) { - } -} diff --git a/pbom/model/pbonodeevents.h b/pbom/model/pbonodeevents.h deleted file mode 100644 index d3a37f6..0000000 --- a/pbom/model/pbonodeevents.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "pbonodetype.h" -#include "pbopath.h" - -namespace pboman3 { - class PboNodeEvent { - }; - - class PboNodeCreatedEvent final : public PboNodeEvent { - public: - PboNodeCreatedEvent(const PboPath* pNodePath, PboNodeType pNodeType); - const PboPath* nodePath; - const PboNodeType nodeType; - }; - - class PboNodeRenamedEvent final : public PboNodeEvent { - public: - PboNodeRenamedEvent(const PboPath* pNodePath, QString pNewNodeTitle); - const PboPath* nodePath; - const QString newNodeTitle; - }; - - class PboNodeRemovedEvent final : public PboNodeEvent { - public: - PboNodeRemovedEvent(const PboPath* pNodePath); - const PboPath* nodePath; - }; -} diff --git a/pbom/model/rootreader.cpp b/pbom/model/rootreader.cpp deleted file mode 100644 index 2daedfb..0000000 --- a/pbom/model/rootreader.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "rootreader.h" -#include "io/bs/pbobinarysource.h" -#include "util/log.h" - -#define LOG(...) LOGGER("model/RootReader", __VA_ARGS__) - -namespace pboman3 { - RootReader::RootReader(const PboFileHeader* header, const QString& path) - : header_(header), - path_(path) { - } - - void RootReader::inflateRoot(PboNode* root) const { - LOG(info, "Inflating the nodes hierarchy") - qsizetype entryDataOffset = header_->dataBlockStart; - for (const QSharedPointer& entry : header_->entries) { - LOG(debug, "Processing the entry:", *entry) - PboNode* node = root->createHierarchy(entry->makePath()); - PboDataInfo dataInfo{0, 0, 0, 0, 0}; - dataInfo.originalSize = entry->originalSize(); - dataInfo.dataSize = entry->dataSize(); - dataInfo.dataOffset = entryDataOffset; - dataInfo.timestamp = entry->timestamp(); - dataInfo.compressed = entry->packingMethod() == PboPackingMethod::Packed; - entryDataOffset += dataInfo.dataSize; - node->binarySource = QSharedPointer( - new PboBinarySource(path_, dataInfo)); - node->binarySource->open(); - } - - } -} diff --git a/pbom/model/rootreader.h b/pbom/model/rootreader.h deleted file mode 100644 index dd3ecff..0000000 --- a/pbom/model/rootreader.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "pbonode.h" -#include "io/pboheaderreader.h" - -namespace pboman3 { - class RootReader { - public: - RootReader(const PboFileHeader* header, const QString& path); - - void inflateRoot(PboNode* root) const; - - private: - const PboFileHeader* header_; - const QString& path_; - }; -} diff --git a/pbom/model/signaturemodel.cpp b/pbom/model/signaturemodel.cpp deleted file mode 100644 index cdd2a1a..0000000 --- a/pbom/model/signaturemodel.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "signaturemodel.h" - -namespace pboman3 { - void SignatureModel::setSignatureBytes(const QByteArray& bytes) { - if (bytes.count() == 0) - signatureString_.clear(); - else - signatureString_ = bytes.toHex(' '); - } - - const QString& SignatureModel::signatureString() const { - return signatureString_; - } -} diff --git a/pbom/model/signaturemodel.h b/pbom/model/signaturemodel.h deleted file mode 100644 index f12a016..0000000 --- a/pbom/model/signaturemodel.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -namespace pboman3 { - class SignatureModel { - public: - void setSignatureBytes(const QByteArray& bytes); - - const QString& signatureString() const; - - private: - QString signatureString_; - }; -} diff --git a/pbom/model/task/packtask.cpp b/pbom/model/task/packtask.cpp index 89fa4c0..570284c 100644 --- a/pbom/model/task/packtask.cpp +++ b/pbom/model/task/packtask.cpp @@ -1,11 +1,13 @@ #include "packtask.h" -#include "io/pboioexception.h" -#include "io/pbowriter.h" +#include "io/diskaccessexception.h" +#include "io/documentwriter.h" #include "util/log.h" #define LOG(...) LOGGER("model/task/PackTask", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::model { + using namespace io; + PackTask::PackTask(QString folder, QString outputDir) : folder_(std::move(folder)), outputDir_(std::move(outputDir)) { @@ -15,9 +17,8 @@ namespace pboman3 { LOG(info, "Folder file: ", folder_) LOG(info, "Output dir: ", outputDir_) - const QFileInfo fi(folder_); - - const QString pboFile = QDir(outputDir_).filePath(fi.fileName()).append(".pbo"); + const QDir folder(folder_); + const QString pboFile = QDir(outputDir_).filePath(folder.dirName()).append(".pbo"); LOG(info, "The pbo file name:", pboFile) if (QFileInfo(pboFile).exists()) { LOG(info, "The pbo file already exists") @@ -25,25 +26,21 @@ namespace pboman3 { return; } - emit taskThinking(fi.absoluteFilePath()); + emit taskThinking(folder.absolutePath()); - PboNode root("root", PboNodeType::Container, nullptr); - const qint32 filesCount = collectDir(fi, fi.dir(), root, cancel); + PboDocument document("root"); + const qint32 filesCount = collectDir(folder, folder, *document.root(), cancel); if (cancel()) return; if (filesCount == 0) { LOG(info, "The Folder was empty") - emit taskMessage("Failure | The folder is empty | " + fi.absolutePath()); + emit taskMessage("Failure | The folder is empty | " + folder.absolutePath()); return; } - HeadersModel headers; - PboWriter writer; - writer.usePath(pboFile) - .useRoot(&root) - .useHeaders(&headers); + DocumentWriter writer(pboFile); //it is tricky to display real PBO pack progress as the process consists of four independent steps. //1. Scan the source folder and grab files. It might take time we can't estimate at all. So just show "indeterminate" progress indicator. @@ -60,18 +57,18 @@ namespace pboman3 { #define WT_BODY 2 #define WT_SIGNATURE 7 - emit taskInitialized(fi.absoluteFilePath(), 0, filesCount * (WT_ENTRIES + WT_BODY + WT_SIGNATURE)); + emit taskInitialized(folder.absolutePath(), 0, filesCount * (WT_ENTRIES + WT_BODY + WT_SIGNATURE)); qint32 progress = 0; - connect(&writer, &PboWriter::progress, [this, &progress, filesCount](const PboWriter::ProgressEvent* evt) { - if (dynamic_cast(evt)) { + connect(&writer, &DocumentWriter::progress, [this, &progress, filesCount](const DocumentWriter::ProgressEvent* evt) { + if (dynamic_cast(evt)) { //2nd step - just increment progress by 1 for each processed file progress++; - } else if (const auto evt1 = dynamic_cast(evt)) { + } else if (const auto evt1 = dynamic_cast(evt)) { //3rd step - see how many bytes copied and how it corresponds to the overall progress progress = filesCount * WT_ENTRIES + static_cast(1.0 * filesCount * WT_BODY / static_cast(evt1->total) * static_cast(evt1->copied)); - } else if (const auto evt2 = dynamic_cast(evt)) { + } else if (const auto evt2 = dynamic_cast(evt)) { //4th step - see how many bytes were processed for signature and update the overall progress //once all bytes processed - report 100% progress explicitly progress = evt2->processed == evt2->total @@ -84,11 +81,11 @@ namespace pboman3 { }); try { - writer.write(cancel); + writer.write(&document, cancel); LOG(info, "Unpack complete") - } catch (const PboIoException& ex) { + } catch (const DiskAccessException& ex) { LOG(warning, "Task failed with exception:", ex) - emit taskMessage("Failure | " + ex.message() + " | " + fi.absolutePath()); + emit taskMessage("Failure | " + ex.message() + " | " + folder.absolutePath()); } } @@ -96,23 +93,21 @@ namespace pboman3 { return debug << "PackTask(Folder=" << task.folder_ << ", OutputDir=" << task.outputDir_ << ")"; } - qint32 PackTask::collectDir(const QFileInfo& dirEntry, const QDir& rootDir, PboNode& rootNode, + qint32 PackTask::collectDir(const QDir& dirEntry, const QDir& rootDir, PboNode& rootNode, const Cancel& cancel) const { LOG(debug, "Collecting the dir:", dirEntry) qint32 count = 0; - const QDir d(dirEntry.filePath() + QDir::separator()); - const QFileInfoList entries = d.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot); + const QFileInfoList entries = dirEntry.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot); for (const QFileInfo& entry : entries) { if (cancel()) return 0; - if (!entry.isSymLink()) { if (entry.isFile()) count += collectFile(entry, rootDir, rootNode); else if (entry.isDir()) - count += collectDir(entry, rootDir, rootNode, cancel); + count += collectDir(QDir(entry.filePath()), rootDir, rootNode, cancel); } } diff --git a/pbom/model/task/packtask.h b/pbom/model/task/packtask.h index 2544a59..6e99063 100644 --- a/pbom/model/task/packtask.h +++ b/pbom/model/task/packtask.h @@ -4,7 +4,9 @@ #include "task.h" #include "model/interactionparcel.h" -namespace pboman3 { +namespace pboman3::model { + using namespace domain; + class PackTask : public Task { public: PackTask(QString folder, QString outputDir); @@ -17,7 +19,7 @@ namespace pboman3 { const QString folder_; const QString outputDir_; - qint32 collectDir(const QFileInfo& dirEntry, const QDir& rootDir, PboNode& rootNode, const Cancel& cancel) const; + qint32 collectDir(const QDir& dirEntry, const QDir& rootDir, PboNode& rootNode, const Cancel& cancel) const; qint32 collectFile(const QFileInfo& fileEntry, const QDir& rootDir, PboNode& rootNode) const; }; diff --git a/pbom/model/task/packwindowmodel.cpp b/pbom/model/task/packwindowmodel.cpp index c033c13..3ebd040 100644 --- a/pbom/model/task/packwindowmodel.cpp +++ b/pbom/model/task/packwindowmodel.cpp @@ -1,7 +1,7 @@ #include "packwindowmodel.h" #include "packtask.h" -namespace pboman3 { +namespace pboman3::model { PackWindowModel::PackWindowModel(const QStringList& folders, const QString& outputDir) { for (const QString& folder : folders) { QSharedPointer task(new PackTask(folder, outputDir)); diff --git a/pbom/model/task/packwindowmodel.h b/pbom/model/task/packwindowmodel.h index ab14f20..f9d1dbf 100644 --- a/pbom/model/task/packwindowmodel.h +++ b/pbom/model/task/packwindowmodel.h @@ -2,7 +2,7 @@ #include "taskwindowmodel.h" -namespace pboman3 { +namespace pboman3::model { class PackWindowModel: public TaskWindowModel{ public: PackWindowModel(const QStringList& folders, const QString& outputDir); diff --git a/pbom/model/task/task.h b/pbom/model/task/task.h index 34a3214..91f03d8 100644 --- a/pbom/model/task/task.h +++ b/pbom/model/task/task.h @@ -3,7 +3,9 @@ #include #include "util/util.h" -namespace pboman3 { +namespace pboman3::model { + using namespace util; + class Task : public QObject { Q_OBJECT diff --git a/pbom/model/task/taskwindowmodel.cpp b/pbom/model/task/taskwindowmodel.cpp index 272426f..be680af 100644 --- a/pbom/model/task/taskwindowmodel.cpp +++ b/pbom/model/task/taskwindowmodel.cpp @@ -2,12 +2,12 @@ #include #include #include -#include "util/exception.h" +#include "exception.h" #include "util/log.h" #define LOG(...) LOGGER("model/task/TaskWindowModel", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::model { void TaskWindowModel::start() { const int numThreads = std::min(QThread::idealThreadCount(), static_cast(tasks_.count())); for (int i = 0; i < numThreads; i++) { diff --git a/pbom/model/task/taskwindowmodel.h b/pbom/model/task/taskwindowmodel.h index 0195cf4..bf4f3f2 100644 --- a/pbom/model/task/taskwindowmodel.h +++ b/pbom/model/task/taskwindowmodel.h @@ -6,7 +6,7 @@ #include #include "task.h" -namespace pboman3 { +namespace pboman3::model { typedef qint32 ThreadId; class TaskWindowModel : public QObject { diff --git a/pbom/model/task/unpacktask.cpp b/pbom/model/task/unpacktask.cpp index d042fd7..b90fbe2 100644 --- a/pbom/model/task/unpacktask.cpp +++ b/pbom/model/task/unpacktask.cpp @@ -1,17 +1,21 @@ #include "unpacktask.h" #include #include -#include "io/pboheaderreader.h" -#include "io/pboioexception.h" #include "io/bb/unpacktaskbackend.h" #include "io/bs/pbobinarysource.h" -#include "model/pboentry.h" -#include "model/rootreader.h" +#include "io/pbonodeentity.h" +#include "domain/pbonode.h" +#include "domain/func.h" +#include "io/diskaccessexception.h" +#include "io/documentreader.h" +#include "io/pbofileformatexception.h" #include "util/log.h" #define LOG(...) LOGGER("model/task/UnpackTask", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::model { + using namespace io; + UnpackTask::UnpackTask(QString pboPath, const QString& outputDir) : pboPath_(std::move(pboPath)), outputDir_(outputDir) { @@ -21,15 +25,17 @@ namespace pboman3 { LOG(info, "PBO file: ", pboPath_) LOG(info, "Output dir: ", outputDir_.absolutePath()) - PboFileHeader header; - if (!tryReadPboHeader(&header)) + QSharedPointer document; + if (!tryReadPboHeader(&document)) return; QDir pboDir; if (!tryCreatePboDir(&pboDir)) return; constexpr qsizetype startProgress = 0; - emit taskInitialized(pboPath_, startProgress, static_cast(header.entries.count())); + qint32 endProgress = 0; + CountFilesInTree(*document->root(), endProgress); + emit taskInitialized(pboPath_, startProgress, endProgress); std::function onError = [this](const QString& error) { emit taskMessage(error); @@ -41,18 +47,15 @@ namespace pboman3 { emit taskProgress(progress); }; - PboNode root("root", PboNodeType::Container, nullptr); - RootReader(&header, pboPath_).inflateRoot(&root); - UnpackTaskBackend be(pboDir); be.setOnError(&onError); be.setOnProgress(&onProgress); QList childNodes; - childNodes.reserve(root.count()); - for (PboNode* node: root) + childNodes.reserve(document->root()->count()); + for (PboNode* node : *document->root()) childNodes.append(node); - be.unpackSync(&root, childNodes, cancel); + be.unpackSync(document->root(), childNodes, cancel); LOG(info, "Unpack complete") } @@ -61,15 +64,18 @@ namespace pboman3 { return debug << "UnpackTask(PboPath=" << task.pboPath_ << ", OutputDir=" << task.outputDir_ << ")"; } - bool UnpackTask::tryReadPboHeader(PboFileHeader* header) { + bool UnpackTask::tryReadPboHeader(QSharedPointer* document) { try { - PboFile file(pboPath_); - file.open(QIODeviceBase::OpenModeFlag::ReadOnly); - *header = PboHeaderReader::readFileHeader(&file); - LOG(info, "The file header:", *header) + const DocumentReader reader(pboPath_); + *document = reader.read(); + LOG(info, "The document:", *document) return true; - } catch (const PboIoException& ex) { - LOG(warning, "Got error while reading the file header:", ex) + } catch (const DiskAccessException& ex) { + LOG(warning, "Got error while opening the file:", ex) + emit taskMessage("Can not read the file | " + pboPath_); + return false; + } catch (const PboFileFormatException& ex) { + LOG(warning, "Got error while reading the file document:", ex) emit taskMessage("The file is not a PBO | " + pboPath_); return false; } @@ -87,9 +93,9 @@ namespace pboman3 { return true; } - bool UnpackTask::tryCreateEntryDir(const QDir& pboDir, const QSharedPointer& entry) { + bool UnpackTask::tryCreateEntryDir(const QDir& pboDir, const QSharedPointer& entry) { QDir local(pboDir); - PboPath path(entry->fileName()); + PboPath path = entry->makePath(); auto it = path.begin(); const auto last = path.end() - 1; diff --git a/pbom/model/task/unpacktask.h b/pbom/model/task/unpacktask.h index 61031ef..544e48d 100644 --- a/pbom/model/task/unpacktask.h +++ b/pbom/model/task/unpacktask.h @@ -2,9 +2,11 @@ #include #include "task.h" -#include "io/pboheaderreader.h" +#include "domain/pbodocument.h" + +namespace pboman3::model { + using namespace domain; -namespace pboman3 { class UnpackTask : public Task { public: UnpackTask(QString pboPath, const QString& outputDir); @@ -17,10 +19,10 @@ namespace pboman3 { const QString pboPath_; const QDir outputDir_; - bool tryReadPboHeader(PboFileHeader* header); + bool tryReadPboHeader(QSharedPointer* document); bool tryCreatePboDir(QDir* dir); - bool tryCreateEntryDir(const QDir& pboDir, const QSharedPointer& entry); + bool tryCreateEntryDir(const QDir& pboDir, const QSharedPointer& entry); }; } diff --git a/pbom/model/task/unpackwindowmodel.cpp b/pbom/model/task/unpackwindowmodel.cpp index 3150876..b0e37b6 100644 --- a/pbom/model/task/unpackwindowmodel.cpp +++ b/pbom/model/task/unpackwindowmodel.cpp @@ -1,7 +1,7 @@ #include "unpackwindowmodel.h" #include "unpacktask.h" -namespace pboman3 { +namespace pboman3::model { UnpackWindowModel::UnpackWindowModel(const QStringList& pboFiles, const QString& outputDir) { for (const QString& pboFile : pboFiles) { QSharedPointer task(new UnpackTask(pboFile, outputDir)); diff --git a/pbom/model/task/unpackwindowmodel.h b/pbom/model/task/unpackwindowmodel.h index 533e278..4f98477 100644 --- a/pbom/model/task/unpackwindowmodel.h +++ b/pbom/model/task/unpackwindowmodel.h @@ -2,7 +2,7 @@ #include "taskwindowmodel.h" -namespace pboman3 { +namespace pboman3::model { class UnpackWindowModel: public TaskWindowModel{ public: UnpackWindowModel(const QStringList& pboFiles, const QString& outputDir); diff --git a/pbom/ui/CMakeLists.txt b/pbom/ui/CMakeLists.txt new file mode 100644 index 0000000..fff2ac2 --- /dev/null +++ b/pbom/ui/CMakeLists.txt @@ -0,0 +1,46 @@ +list(APPEND PROJECT_SOURCES + "ui/progresswidget/progresswidget.cpp" + "ui/treewidget/deleteop.cpp" + "ui/treewidget/treewidget.cpp" + "ui/treewidget/treewidgetbase.cpp" + "ui/treewidget/treewidgetitem.cpp" + "ui/win32/win32iconmgr.cpp" + "ui/win32/win32fileviewer.cpp" + "ui/win32/win32taskbarindicator.cpp" + "ui/aboutdialog.cpp" + "ui/aboutdialog.ui" + "ui/closedialog.cpp" + "ui/closedialog.ui" + "ui/compresslist.cpp" + "ui/conflictslist.cpp" + "ui/errordialog.ui" + "ui/errordialog.cpp" + "ui/fscollector.cpp" + "ui/headersdialog.cpp" + "ui/headersdialog.ui" + "ui/insertdialog.cpp" + "ui/insertdialog.ui" + "ui/insertdialogbuttons.cpp" + "ui/renamedialog.cpp" + "ui/renamedialog.ui" + "ui/mainwindow.cpp" + "ui/mainwindow.ui" + "ui/packwindow.cpp" + "ui/signaturedialog.cpp" + "ui/signaturedialog.ui" + "ui/statusbar.cpp" + "ui/statusbar.cpp" + "ui/taskwindow.cpp" + "ui/taskwindow.ui" + "ui/unpackwindow.cpp" + "ui/updatesdialog.ui" + "ui/updatesdialog.cpp") + +set(PROJECT_SOURCES ${PROJECT_SOURCES} PARENT_SCOPE) + +list(APPEND TEST_SOURCES + "ui/__test__/fscollector__test.cpp" + "ui/__test__/updatesdialog__test.cpp" + "ui/treewidget/__test__/treewidgetbase__test.cpp") + +set(TEST_SOURCES ${TEST_SOURCES} PARENT_SCOPE) diff --git a/pbom/ui/__test__/fscollector__test.cpp b/pbom/ui/__test__/fscollector__test.cpp index dba87fc..6a90e57 100644 --- a/pbom/ui/__test__/fscollector__test.cpp +++ b/pbom/ui/__test__/fscollector__test.cpp @@ -3,7 +3,7 @@ #include #include "ui/fscollector.h" -namespace pboman3::test { +namespace pboman3::ui::test { TEST(FsCollectorTest, CollectFiles_Finds_Files_In_All_Folders) { const QString d1 = "f1"; const QString d11 = d1 + QDir::separator() + "f11"; @@ -37,29 +37,29 @@ namespace pboman3::test { f5.open(QIODeviceBase::ReadWrite); f5.close(); - const NodeDescriptors files = FsCollector::collectFiles(QList{ - QUrl::fromLocalFile(tempDir.filePath(d2)), - QUrl::fromLocalFile(f1.fileName()), - QUrl::fromLocalFile(tempDir.filePath(d1)) - }); + const QSharedPointer files = FsCollector::collectFiles(QList{ + QUrl::fromLocalFile(tempDir.filePath(d2)), + QUrl::fromLocalFile(f1.fileName()), + QUrl::fromLocalFile(tempDir.filePath(d1)) + }, []() { return false; }); - ASSERT_EQ(files.length(), 5); + ASSERT_EQ(files->length(), 5); - ASSERT_EQ(files[0].path(), PboPath("f1/f11/f3.txt")); - ASSERT_EQ(files[0].binarySource()->path(), f3.fileName()); - ASSERT_TRUE(files[0].binarySource()->isOpen()); + ASSERT_EQ(files->at(0).path(), PboPath("f1/f11/f3.txt")); + ASSERT_EQ(files->at(0).binarySource()->path(), f3.fileName()); + ASSERT_TRUE(files->at(0).binarySource()->isOpen()); - ASSERT_EQ(files[1].path(), PboPath("f1/f11/f4.txt")); - ASSERT_EQ(files[1].binarySource()->path(), f4.fileName()); + ASSERT_EQ(files->at(1).path(), PboPath("f1/f11/f4.txt")); + ASSERT_EQ(files->at(1).binarySource()->path(), f4.fileName()); - ASSERT_EQ(files[2].path(), PboPath("f1/f2.txt")); - ASSERT_EQ(files[2].binarySource()->path(), f2.fileName()); + ASSERT_EQ(files->at(2).path(), PboPath("f1/f2.txt")); + ASSERT_EQ(files->at(2).binarySource()->path(), f2.fileName()); - ASSERT_EQ(files[3].path(), PboPath("f1.txt")); - ASSERT_EQ(files[3].binarySource()->path(), f1.fileName()); + ASSERT_EQ(files->at(3).path(), PboPath("f1.txt")); + ASSERT_EQ(files->at(3).binarySource()->path(), f1.fileName()); - ASSERT_EQ(files[4].path(), PboPath("f2/f5.txt")); - ASSERT_EQ(files[4].binarySource()->path(), f5.fileName()); + ASSERT_EQ(files->at(4).path(), PboPath("f2/f5.txt")); + ASSERT_EQ(files->at(4).binarySource()->path(), f5.fileName()); } TEST(FsCollectorTest, CollectFiles_Wont_Find_Symlinks) { @@ -86,12 +86,12 @@ namespace pboman3::test { ASSERT_TRUE(QFile::link(tempDir.filePath(d1), tempDir.filePath("d1.lnk"))); //run the code - const NodeDescriptors files = FsCollector::collectFiles(QList{ - QUrl::fromLocalFile(tempDir.absolutePath()) - }); + const QSharedPointer files = FsCollector::collectFiles(QList{ + QUrl::fromLocalFile(tempDir.absolutePath()) + }, []() { return false; }); //ensure symlinks were not collected - ASSERT_EQ(files.count(), 1); - ASSERT_EQ(files[0].path(), PboPath({ tempDir.dirName(), "d1", "f1.txt" })); + ASSERT_EQ(files->count(), 1); + ASSERT_EQ(files->at(0).path(), PboPath({ tempDir.dirName(), "d1", "f1.txt" })); } } diff --git a/pbom/ui/__test__/updatesdialog__test.cpp b/pbom/ui/__test__/updatesdialog__test.cpp new file mode 100644 index 0000000..756a927 --- /dev/null +++ b/pbom/ui/__test__/updatesdialog__test.cpp @@ -0,0 +1,96 @@ +#include +#include "ui/updatesdialog.h" +#include + +namespace pboman3::ui::test { + TEST(SemanticVersionTest, Ctor_Initializes_Without_Params) { + const SemanticVersion ver; + ASSERT_FALSE(ver.isValid()); + ASSERT_EQ("", ver.raw()); + } + + class CtorWithParamTest1 : public testing::TestWithParam { + }; + + TEST_P(CtorWithParamTest1, Ctor_Initializes_Valid_Version) { + const SemanticVersion ver(GetParam()); + ASSERT_TRUE(ver.isValid()); + ASSERT_EQ(GetParam(), ver.raw()); + } + + INSTANTIATE_TEST_SUITE_P(SemanticVersionTest, CtorWithParamTest1, testing::Values( + QString("1.0.0"), + QString("1.1.0"), + QString("1.1.1"), + QString("1.1.1-alpha"), + QString("0.0.1-alpha") + )); + + class CtorWithParamTest2 : public testing::TestWithParam { + }; + + TEST_P(CtorWithParamTest2, Ctor_Initializes_Valid_Version) { + const SemanticVersion ver(GetParam()); + ASSERT_FALSE(ver.isValid()); + ASSERT_EQ(GetParam(), ver.raw()); + } + + INSTANTIATE_TEST_SUITE_P(SemanticVersionTest, CtorWithParamTest2, testing::Values( + QString("0.0.0"), + QString("1.1.a"), + QString("some text") + )); + + struct OperatorTestParam { + QString ver1; + QString ver2; + bool expectedResult; + }; + + class OperatorLessTest : public testing::TestWithParam { + }; + + TEST_P(OperatorLessTest, Operator_Less_Functional) { + SemanticVersion ver1(GetParam().ver1); + SemanticVersion ver2(GetParam().ver2); + ASSERT_EQ(GetParam().expectedResult, ver1 < ver2); + } + + INSTANTIATE_TEST_SUITE_P(SemanticVersionTest, OperatorLessTest, testing::Values( + OperatorTestParam{ "0.0.1", "0.0.2", true }, + OperatorTestParam{ "0.1.0", "0.2.0", true }, + OperatorTestParam{ "1.0.0", "2.0.0", true }, + OperatorTestParam{ "1.0.0", "1.1.0", true }, + OperatorTestParam{ "1.0.0", "1.0.1", true }, + OperatorTestParam{ "0.1.0", "0.1.1", true }, + + OperatorTestParam{ "0.0.2", "0.0.1", false }, + OperatorTestParam{ "0.2.0", "0.1.0", false }, + OperatorTestParam{ "2.0.0", "1.0.0", false }, + OperatorTestParam{ "1.1.0", "1.0.0", false }, + OperatorTestParam{ "1.0.1", "1.0.0", false }, + OperatorTestParam{ "0.1.1", "0.1.0", false } + )); + + TEST(GithubLatestVersionTest, Returns_Valid_Version) { + GithubLatestVersion githubVersion; + + int argc = 0; + char argv[1]; + QApplication app(argc, reinterpret_cast(argv)); + QEventLoop loop; + + bool successCalled = false; + QObject::connect(&githubVersion, &GithubLatestVersion::success, [&successCalled](const SemanticVersion& ver) { + successCalled = true; + ASSERT_TRUE(ver.isValid()); + }); + + QObject::connect(&githubVersion, &GithubLatestVersion::success, &loop, &QEventLoop::quit); + QObject::connect(&githubVersion, &GithubLatestVersion::error, &loop, &QEventLoop::quit); + githubVersion.check(); + loop.exec(); + + ASSERT_TRUE(successCalled); + } +} diff --git a/pbom/ui/aboutdialog.cpp b/pbom/ui/aboutdialog.cpp index 5e2f50a..aa12ab5 100644 --- a/pbom/ui/aboutdialog.cpp +++ b/pbom/ui/aboutdialog.cpp @@ -3,7 +3,7 @@ #include #include "ui_aboutdialog.h" -namespace pboman3 { +namespace pboman3::ui { AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui_(new Ui::AboutDialog) { diff --git a/pbom/ui/aboutdialog.h b/pbom/ui/aboutdialog.h index c568c92..f6ba14b 100644 --- a/pbom/ui/aboutdialog.h +++ b/pbom/ui/aboutdialog.h @@ -6,7 +6,7 @@ namespace Ui { class AboutDialog; } -namespace pboman3 { +namespace pboman3::ui { class AboutDialog : public QDialog { public: AboutDialog(QWidget* parent = nullptr); diff --git a/pbom/ui/closedialog.cpp b/pbom/ui/closedialog.cpp index 3bcad4f..f88e421 100644 --- a/pbom/ui/closedialog.cpp +++ b/pbom/ui/closedialog.cpp @@ -1,7 +1,6 @@ #include "closedialog.h" -namespace pboman3 { - +namespace pboman3::ui { CloseDialog::CloseDialog(const QFileInfo& file, QWidget* parent) : QDialog(parent), ui_(new Ui::CloseDialog) { diff --git a/pbom/ui/closedialog.h b/pbom/ui/closedialog.h index a9eb5d5..041aed0 100644 --- a/pbom/ui/closedialog.h +++ b/pbom/ui/closedialog.h @@ -8,7 +8,7 @@ namespace Ui { class CloseDialog; } -namespace pboman3 { +namespace pboman3::ui { class CloseDialog : public QDialog { Q_OBJECT public: diff --git a/pbom/ui/compresslist.cpp b/pbom/ui/compresslist.cpp index 242101e..9349f82 100644 --- a/pbom/ui/compresslist.cpp +++ b/pbom/ui/compresslist.cpp @@ -3,7 +3,7 @@ #include #include -namespace pboman3 { +namespace pboman3::ui { constexpr int colTitleIndex = 0; constexpr int colExtensionIndex = 1; constexpr int colCompressIndex = 2; diff --git a/pbom/ui/compresslist.h b/pbom/ui/compresslist.h index 77be89e..adc36df 100644 --- a/pbom/ui/compresslist.h +++ b/pbom/ui/compresslist.h @@ -3,11 +3,15 @@ #include #include "model/interactionparcel.h" -namespace pboman3 { +namespace pboman3::ui { + using namespace model; + class CompressListItem : public QTreeWidgetItem { public: - CompressListItem(int id) : QTreeWidgetItem() { - id_ = id; + CompressListItem(int id) + : QTreeWidgetItem(), + id_(id) { + } int id() const { return id_; } @@ -16,7 +20,7 @@ namespace pboman3 { int id_; }; - class CompressList: public QTreeWidget { + class CompressList : public QTreeWidget { public: CompressList(QWidget* parent = nullptr); diff --git a/pbom/ui/conflictslist.cpp b/pbom/ui/conflictslist.cpp index b37cec7..ee6f38a 100644 --- a/pbom/ui/conflictslist.cpp +++ b/pbom/ui/conflictslist.cpp @@ -3,7 +3,7 @@ #include #include -namespace pboman3 { +namespace pboman3::ui { constexpr int colTitleIndex = 0; constexpr int colExtensionIndex = 1; constexpr int colResolutionIndex = 2; diff --git a/pbom/ui/conflictslist.h b/pbom/ui/conflictslist.h index 9832d5d..253473d 100644 --- a/pbom/ui/conflictslist.h +++ b/pbom/ui/conflictslist.h @@ -3,11 +3,15 @@ #include "QTreeWidget" #include "model/conflictsparcel.h" -namespace pboman3 { +namespace pboman3::ui { + using namespace model; + class ConflictsListItem : public QTreeWidgetItem { public: - ConflictsListItem(int id): QTreeWidgetItem() { - id_ = id; + ConflictsListItem(int id): + QTreeWidgetItem(), + id_(id) { + } int id() const { return id_; } diff --git a/pbom/ui/errordialog.cpp b/pbom/ui/errordialog.cpp index 4b90076..25f622b 100644 --- a/pbom/ui/errordialog.cpp +++ b/pbom/ui/errordialog.cpp @@ -1,6 +1,6 @@ #include "errordialog.h" -namespace pboman3 { +namespace pboman3::ui { ErrorDialog::ErrorDialog(const DiskAccessException& ex, QWidget* parent) : QDialog(parent), ui_(new Ui::ErrorDialog) { @@ -8,6 +8,13 @@ namespace pboman3 { ui_->label->setText(ex.message() + "
" + ex.file() + ""); } + ErrorDialog::ErrorDialog(const PboFileFormatException& ex, QWidget* parent) + : QDialog(parent), + ui_(new Ui::ErrorDialog) { + ui_->setupUi(this); + ui_->label->setText("Can not open the file. It is not a valid PBO."); + } + ErrorDialog::ErrorDialog(const AppException& ex, QWidget* parent) : QDialog(parent), ui_(new Ui::ErrorDialog) { diff --git a/pbom/ui/errordialog.h b/pbom/ui/errordialog.h index 5322114..8533a17 100644 --- a/pbom/ui/errordialog.h +++ b/pbom/ui/errordialog.h @@ -1,18 +1,23 @@ #pragma once #include "ui_errordialog.h" -#include "model/diskaccessexception.h" -#include "util/exception.h" +#include "io/diskaccessexception.h" +#include "io/pbofileformatexception.h" +#include "exception.h" namespace Ui { class ErrorDialog; } -namespace pboman3 { +namespace pboman3::ui { + using namespace io; + class ErrorDialog : public QDialog { public: ErrorDialog(const DiskAccessException& ex, QWidget* parent = nullptr); + ErrorDialog(const PboFileFormatException& ex, QWidget* parent = nullptr); + ErrorDialog(const AppException& ex, QWidget* parent = nullptr); ErrorDialog(const QString& text, QWidget* parent = nullptr); diff --git a/pbom/ui/fileviewer.h b/pbom/ui/fileviewer.h index e77ea01..b29eb15 100644 --- a/pbom/ui/fileviewer.h +++ b/pbom/ui/fileviewer.h @@ -2,7 +2,7 @@ #include -namespace pboman3 { +namespace pboman3::ui { class FileViewer { public: virtual void previewFile(const QString& path) = 0; diff --git a/pbom/ui/fscollector.cpp b/pbom/ui/fscollector.cpp index 7ce5aa5..4535b95 100644 --- a/pbom/ui/fscollector.cpp +++ b/pbom/ui/fscollector.cpp @@ -4,46 +4,52 @@ #define LOG(...) LOGGER("ui/FsCollector", __VA_ARGS__) -namespace pboman3 { - NodeDescriptors FsCollector::collectFiles(const QList& urls) { +namespace pboman3::ui { + QSharedPointer FsCollector::collectFiles(const QList& urls, const Cancel& cancel) { LOG(info, "Collecting the files at:", urls) - NodeDescriptors result; - result.reserve(100); + QSharedPointer result(new NodeDescriptors); + result->reserve(100); for (const QUrl& url : urls) { + if (cancel()) + return nullptr; QFileInfo fi(url.toLocalFile()); - if (fi.isFile()) { - collectFile(fi, fi.dir(), result); - } else if (fi.isDir()) { - collectDir(fi, fi.dir(), result); + if (!fi.isSymLink()) { + if (fi.isFile()) { + collectFile(fi, fi.dir(), result.get()); + } else if (fi.isDir()) { + collectDir(fi, fi.dir(), result.get(), cancel); + } } } - std::sort(result.begin(), result.end(), [](const NodeDescriptor& f1, const NodeDescriptor& f2) { + std::sort(result->begin(), result->end(), [](const NodeDescriptor& f1, const NodeDescriptor& f2) { return f1.path() < f2.path(); }); - LOG(info, "Collected files:", result.size()) + LOG(info, "Collected files:", result->size()) return result; } - void FsCollector::collectDir(const QFileInfo& fi, const QDir& parent, NodeDescriptors& descriptors) { + void FsCollector::collectDir(const QFileInfo& fi, const QDir& parent, NodeDescriptors* descriptors, const Cancel& cancel) { LOG(debug, "Collecting the dir:", fi) const QDir d(fi.filePath() + QDir::separator()); const QFileInfoList entries = d.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot); for (const QFileInfo& entry : entries) { + if (cancel()) + return; if (!entry.isSymLink()) { if (entry.isFile()) collectFile(entry, parent, descriptors); else if (entry.isDir()) - collectDir(entry, parent, descriptors); + collectDir(entry, parent, descriptors, cancel); } } } - void FsCollector::collectFile(const QFileInfo& fi, const QDir& parent, NodeDescriptors& descriptors) { + void FsCollector::collectFile(const QFileInfo& fi, const QDir& parent, NodeDescriptors* descriptors) { LOG(debug, "Collecting the file:", fi) if (!fi.isShortcut() && !fi.isSymbolicLink()) { @@ -51,10 +57,10 @@ namespace pboman3 { const QString pboPath = parent.relativeFilePath(fi.canonicalFilePath()); - const auto bs = QSharedPointer(new FsRawBinarySource(std::move(fsPath))); + const auto bs = QSharedPointer(new FsRawBinarySource(std::move(fsPath))); bs->open(); - - descriptors.append(NodeDescriptor(bs, PboPath(pboPath))); + + descriptors->append(NodeDescriptor(bs, PboPath(pboPath))); LOG(debug, "File collected") } diff --git a/pbom/ui/fscollector.h b/pbom/ui/fscollector.h index 90e622d..e886dc1 100644 --- a/pbom/ui/fscollector.h +++ b/pbom/ui/fscollector.h @@ -3,14 +3,16 @@ #include #include "model/interactionparcel.h" -namespace pboman3 { +namespace pboman3::ui { + using namespace model; + class FsCollector { public: - static NodeDescriptors collectFiles(const QList& urls); + static QSharedPointer collectFiles(const QList& urls, const Cancel& cancel); private: - static void collectDir(const QFileInfo& fi, const QDir& parent, NodeDescriptors& descriptors); + static void collectDir(const QFileInfo& fi, const QDir& parent, NodeDescriptors* descriptors, const Cancel& cancel); - static void collectFile(const QFileInfo& fi, const QDir& parent, NodeDescriptors& descriptors); + static void collectFile(const QFileInfo& fi, const QDir& parent, NodeDescriptors* descriptors); }; } diff --git a/pbom/ui/headersdialog.cpp b/pbom/ui/headersdialog.cpp index 9b672a7..5d8199f 100644 --- a/pbom/ui/headersdialog.cpp +++ b/pbom/ui/headersdialog.cpp @@ -2,21 +2,24 @@ #include #include #include +#include "domain/documentheaderstransaction.h" +#include "domain/validationexception.h" #include "util/log.h" #define LOG(...) LOGGER("ui/HeadersDialog", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::ui { constexpr int colName = 0; constexpr int colValue = 1; constexpr Qt::ItemFlags itemFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; - HeadersDialog::HeadersDialog(HeadersModel* model, QWidget* parent) + HeadersDialog::HeadersDialog(DocumentHeaders* headers, QWidget* parent) : QDialog(parent), - ui_(new Ui::HeadersDialog), - model_(model) { + ui_(new Ui::HeadersDialog) { ui_->setupUi(this); + tran_ = headers->beginTransaction(); + renderHeaderItems(); setupConnections(); } @@ -28,21 +31,20 @@ namespace pboman3 { void HeadersDialog::accept() { LOG(info, "User clicked the Accept button") - QList> headers; - headers.reserve(ui_->treeWidget->topLevelItemCount()); - + tran_->clear(); for (int i = 0; i < ui_->treeWidget->topLevelItemCount(); i++) { const QTreeWidgetItem* item = ui_->treeWidget->topLevelItem(i); - if (isValidHeader(item->text(colName), item->text(colValue))) { + try { LOG(debug, "Append header, name=", item->text(colName), "value=", item->text(colValue)) - headers.append(QSharedPointer(new PboHeader(item->text(colName), item->text(colValue)))); + tran_->add(item->text(colName), item->text(colValue)); + } catch (const ValidationException& ex) { + LOG(debug, "Header failed validation:", ex.message()) } } - model_->setData(std::move(headers)); - LOG(info, "Accepting the dialog") + tran_->commit(); QDialog::accept(); } @@ -55,12 +57,12 @@ namespace pboman3 { LOG(info, "Rendering headers") ui_->treeWidget->setHeaderLabels(QList({"Name", "Value"})); - for (const QSharedPointer& header : *model_) { - LOG(debug, "Render header, name=", header->name, "value=", header->value) + for (const DocumentHeader* header : *tran_) { + LOG(debug, "Render header, name=", header->name(), "value=", header->value()) const auto item = new QTreeWidgetItem(); - item->setText(colName, header->name); - item->setText(colValue, header->value); + item->setText(colName, header->name()); + item->setText(colValue, header->value()); item->setFlags(itemFlags); ui_->treeWidget->addTopLevelItem(item); } @@ -98,10 +100,6 @@ namespace pboman3 { menu.exec(ui_->treeWidget->mapToGlobal(pos)); } - bool HeadersDialog::isValidHeader(const QString& name, const QString& value) const { - return !name.isEmpty() && !value.isEmpty(); - } - void HeadersDialog::onInsertClick(int index) const { LOG(info, "User clicked the Insert button") diff --git a/pbom/ui/headersdialog.h b/pbom/ui/headersdialog.h index 3c3d9c4..87fde35 100644 --- a/pbom/ui/headersdialog.h +++ b/pbom/ui/headersdialog.h @@ -1,7 +1,7 @@ #pragma once #include "ui_headersdialog.h" -#include "model/headersmodel.h" +#include "domain/documentheaders.h" #include namespace Ui { @@ -9,10 +9,12 @@ namespace Ui { } -namespace pboman3 { +namespace pboman3::ui { + using namespace domain; + class HeadersDialog : public QDialog { public: - HeadersDialog(HeadersModel* model, QWidget* parent = nullptr); + HeadersDialog(DocumentHeaders* headers, QWidget* parent = nullptr); ~HeadersDialog() override; @@ -23,7 +25,7 @@ namespace pboman3 { private: Ui::HeadersDialog* ui_; - HeadersModel* model_; + QSharedPointer tran_; void renderHeaderItems() const; @@ -31,8 +33,6 @@ namespace pboman3 { void contextMenuRequested(const QPoint& pos) const; - bool isValidHeader(const QString& name, const QString& value) const; - void onInsertClick(int index) const; void onMoveClick(int index) const; diff --git a/pbom/ui/iconmgr.h b/pbom/ui/iconmgr.h index f180c84..bd5d74c 100644 --- a/pbom/ui/iconmgr.h +++ b/pbom/ui/iconmgr.h @@ -3,7 +3,7 @@ // ReSharper disable once CppUnusedIncludeDirective #include -namespace pboman3 { +namespace pboman3::ui { class IconMgr { public: virtual ~IconMgr() = default; diff --git a/pbom/ui/insertdialog.cpp b/pbom/ui/insertdialog.cpp index 2184109..41e4266 100644 --- a/pbom/ui/insertdialog.cpp +++ b/pbom/ui/insertdialog.cpp @@ -3,7 +3,7 @@ #define LOG(...) LOGGER("ui/InsertDialog", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::ui { InsertDialog::InsertDialog(QWidget* parent, Mode dialogMode, NodeDescriptors* descriptors, ConflictsParcel* conflicts) : QDialog(parent), diff --git a/pbom/ui/insertdialog.h b/pbom/ui/insertdialog.h index 4c02601..58cacde 100644 --- a/pbom/ui/insertdialog.h +++ b/pbom/ui/insertdialog.h @@ -8,7 +8,7 @@ namespace Ui { class InsertDialog; } -namespace pboman3 { +namespace pboman3::ui { class InsertDialog : public QDialog { Q_OBJECT diff --git a/pbom/ui/insertdialog.ui b/pbom/ui/insertdialog.ui index 6996339..9f3794b 100644 --- a/pbom/ui/insertdialog.ui +++ b/pbom/ui/insertdialog.ui @@ -40,7 +40,7 @@ - + Qt::CustomContextMenu @@ -74,7 +74,7 @@ - + QFrame::Box @@ -99,7 +99,7 @@ - + QDialogButtonBox::Cancel|QDialogButtonBox::Ok @@ -109,17 +109,17 @@ - pboman3::CompressList + pboman3::ui::CompressList QTreeView
ui/compresslist.h
- pboman3::ConflictsList + pboman3::ui::ConflictsList QTreeWidget
ui/conflictslist.h
- pboman3::InsertDialogButtons + pboman3::ui::InsertDialogButtons QDialogButtonBox
ui/insertdialogbuttons.h
diff --git a/pbom/ui/insertdialogbuttons.cpp b/pbom/ui/insertdialogbuttons.cpp index 8cc4a9c..6c28005 100644 --- a/pbom/ui/insertdialogbuttons.cpp +++ b/pbom/ui/insertdialogbuttons.cpp @@ -4,7 +4,7 @@ #define LOG(...) LOGGER("ui/InsertDialogButtons", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::ui { InsertDialogButtons::InsertDialogButtons(QWidget* parent) : QDialogButtonBox(StandardButtons(Ok | Cancel), parent), btnNext_(nullptr), diff --git a/pbom/ui/insertdialogbuttons.h b/pbom/ui/insertdialogbuttons.h index 036e894..af38160 100644 --- a/pbom/ui/insertdialogbuttons.h +++ b/pbom/ui/insertdialogbuttons.h @@ -2,7 +2,7 @@ #include -namespace pboman3 { +namespace pboman3::ui { class InsertDialogButtons : public QDialogButtonBox { Q_OBJECT diff --git a/pbom/ui/mainwindow.cpp b/pbom/ui/mainwindow.cpp index e181ba9..bde3c9b 100644 --- a/pbom/ui/mainwindow.cpp +++ b/pbom/ui/mainwindow.cpp @@ -5,22 +5,23 @@ #include #include #include - #include "aboutdialog.h" #include "closedialog.h" #include "errordialog.h" #include "headersdialog.h" #include "signaturedialog.h" +#include "updatesdialog.h" #include "ui_mainwindow.h" -#include "model/diskaccessexception.h" -#include "model/pbofileformatexception.h" +#include "io/diskaccessexception.h" +#include "io/pbofileformatexception.h" +#include "model/pbomodel.h" #include "treewidget/treewidget.h" #include "util/log.h" #define LOG(...) LOGGER("ui/MainWindow", __VA_ARGS__) -namespace pboman3 { - MainWindow::MainWindow(QWidget* parent, PboModel* model) +namespace pboman3::ui { + MainWindow::MainWindow(QWidget* parent, model::PboModel* model) : QMainWindow(parent), ui_(new Ui::MainWindow), model_(model), @@ -40,24 +41,45 @@ namespace pboman3 { } void MainWindow::loadFile(const QString& fileName) { - LOG(info, "Loading the file:", fileName) - try { + if (model_->isLoaded()) + unloadFile(); + + const QFuture future = QtConcurrent::run([this, &fileName](QPromise& promise) { + LOG(info, "Loading the file:", fileName) model_->loadFile(fileName); - setLoaded(true); + promise.addResult(0); + }); + + setIsLoading(static_cast>(future), false); + loadWatcher_.setFuture(future); + } + + void MainWindow::loadComplete() { + resetIsLoading(); + + QFuture future = loadWatcher_.future(); + + if (!future.isValid()) { + LOG(info, "File loading was cancelled - exiting") + return; + } + + try { + future.takeResult(); //to get exceptions rethrown + LOG(info, "File loading is complete") + setHasChanges(false); } catch (const PboFileFormatException& ex) { LOG(info, "Error when loading file - show error modal:", ex) UI_HANDLE_ERROR(ex) - unloadFile(); } catch (const DiskAccessException& ex) { LOG(info, "Error when loading file - show error modal:", ex) UI_HANDLE_ERROR(ex) - unloadFile(); } } void MainWindow::unloadFile() { + LOG(info, "Unloading the current file") setHasChanges(false); - setLoaded(false); model_->unloadFile(); } @@ -70,11 +92,12 @@ namespace pboman3 { } void MainWindow::setupConnections() { + connect(&loadWatcher_, &QFutureWatcher::finished, this, &MainWindow::loadComplete); connect(&saveWatcher_, &QFutureWatcher::finished, this, &MainWindow::saveComplete); connect(ui_->treeWidget, &TreeWidget::backgroundOpStarted, this, [this](QFuture f) { ui_->treeWidget->setEnabled(false); - ui_->statusBar->progressShow(f); + ui_->statusBar->progressShow(std::move(f), true); }); connect(ui_->treeWidget, &TreeWidget::backgroundOpStopped, this, [this]() { ui_->treeWidget->setEnabled(true); @@ -108,9 +131,11 @@ namespace pboman3 { connect(ui_->actionSelectionDelete, &QAction::triggered, ui_->treeWidget, &TreeWidget::selectionRemove); connect(ui_->actionHelpAbout, &QAction::triggered, [this]() { AboutDialog(this).exec(); }); + connect(ui_->actionCheckUpdates, &QAction::triggered, [this]() { UpdatesDialog(this).exec(); }); connect(model_, &PboModel::modelChanged, this, [this]() { setHasChanges(true); }); connect(model_, &PboModel::loadedPathChanged, this, &MainWindow::updateWindowTitle); + connect(model_, &PboModel::loadedStatusChanged, this, &MainWindow::updateLoadedStatus); } void MainWindow::onFileOpenClick() { @@ -154,12 +179,12 @@ namespace pboman3 { void MainWindow::onViewHeadersClick() { LOG(info, "User clicked the ViewHeaders button") - HeadersDialog(model_->headers(), this).exec(); + HeadersDialog(model_->document()->headers(), this).exec(); } void MainWindow::onViewSignatureClick() { LOG(info, "User clicked the ViewSignature button") - SignatureDialog(model_->signature(), this).exec(); + SignatureDialog(&model_->document()->signature(), this).exec(); } void MainWindow::selectionExtractToClick() { @@ -214,7 +239,7 @@ namespace pboman3 { void MainWindow::selectionExtractContainerClick() const { LOG(info, "User clicked the ExtractToContainer button") const QDir dir = QFileInfo(model_->loadedPath()).dir(); - const QString folderName = GetFileNameWithoutExtension(model_->rootEntry()->title()); + const QString folderName = GetFileNameWithoutExtension(model_->document()->root()->title()); const QString folderPath = dir.filePath(folderName); if (!QDir(dir.filePath(folderName)).exists() && !dir.mkdir(folderName)) { LOG(critical, "Could not create the dir:", folderPath) @@ -223,7 +248,7 @@ namespace pboman3 { } LOG(info, "Extracting to:", folderPath) - ui_->treeWidget->selectionExtract(folderPath, model_->rootEntry()); + ui_->treeWidget->selectionExtract(folderPath, model_->document()->root()); } bool MainWindow::queryCloseUnsaved() { @@ -314,25 +339,23 @@ namespace pboman3 { ui_->actionSelectionExtractContainer->setEnabled(false); ui_->actionSelectionExtractContainer->setVisible(false); } - - } void MainWindow::saveFile(const QString& fileName) { LOG(info, "Saving the file") - const QFuture future = QtConcurrent::run([this, fileName](QPromise& promise) { + const QFuture future = QtConcurrent::run([this, &fileName](QPromise& promise) { model_->saveFile([&promise]() { return promise.isCanceled(); }, fileName); promise.addResult(0); }); - ui_->statusBar->progressShow(static_cast>(future)); + setIsLoading(static_cast>(future), true); saveWatcher_.setFuture(future); } void MainWindow::saveComplete() { - ui_->statusBar->progressHide(); + resetIsLoading(); QFuture future = saveWatcher_.future(); @@ -358,15 +381,15 @@ namespace pboman3 { updateWindowTitle(); } - void MainWindow::setLoaded(bool loaded) const { + void MainWindow::updateLoadedStatus(bool loaded) const { LOG(info, "The Loaded status set to:", loaded) if (loaded) { - ui_->treeWidget->setRoot(model_->rootEntry()); + ui_->treeWidget->setRoot(model_->document()->root()); ui_->treeWidget->setDragDropMode(QAbstractItemView::DragDrop); - ui_->actionSelectionExtractContainer->setText(makeExtractToTitle(model_->rootEntry())); - connect(model_->rootEntry(), &PboNode::titleChanged, [this]() { - ui_->actionSelectionExtractContainer->setText(makeExtractToTitle(model_->rootEntry())); + ui_->actionSelectionExtractContainer->setText(makeExtractToTitle(model_->document()->root())); + connect(model_->document()->root(), &PboNode::titleChanged, [this]() { + ui_->actionSelectionExtractContainer->setText(makeExtractToTitle(model_->document()->root())); }); } else { ui_->treeWidget->resetRoot(); @@ -388,11 +411,7 @@ namespace pboman3 { } void MainWindow::updateWindowTitle() { - - if (model_->loadedPath().isNull()) { - LOG(info, "There is no loaded file - reset window title to the default") - setWindowTitle(PBOM_PROJECT_NAME); - } else { + if (model_->isLoaded()) { const QFileInfo fi(model_->loadedPath()); const QString title = hasChanges_ ? "*" + fi.fileName() + " - " + PBOM_PROJECT_NAME @@ -400,11 +419,28 @@ namespace pboman3 { LOG(info, "Set window title to:", title) setWindowTitle(title); + } else { + LOG(info, "There is no loaded file - reset window title to the default") + setWindowTitle(PBOM_PROJECT_NAME); } } + void MainWindow::setIsLoading(QFuture future, bool supportsCancellation) const { + ui_->menubar->setEnabled(false); + ui_->treeWidget->setEnabled(false); + ui_->statusBar->progressShow(std::move(future), supportsCancellation); + } + + void MainWindow::resetIsLoading() const { + ui_->menubar->setEnabled(true); + ui_->treeWidget->setEnabled(true); + ui_->statusBar->progressHide(); + } + QString MainWindow::makeExtractToTitle(const PboNode* node) const { - return "Extract to ./" + node->title() + return "Extract to ./" + (node->nodeType() == PboNodeType::Container + ? GetFileNameWithoutExtension(node->title()) + : node->title()) + (node->nodeType() == PboNodeType::File ? "" : "/"); } } diff --git a/pbom/ui/mainwindow.h b/pbom/ui/mainwindow.h index d70b173..8b059d5 100644 --- a/pbom/ui/mainwindow.h +++ b/pbom/ui/mainwindow.h @@ -10,12 +10,14 @@ namespace Ui { class MainWindow; } -namespace pboman3 { +namespace pboman3::ui { + using namespace model; + class MainWindow : public QMainWindow { Q_OBJECT public: - explicit MainWindow(QWidget* parent, PboModel* model); + explicit MainWindow(QWidget* parent, model::PboModel* model); ~MainWindow() override; @@ -28,8 +30,11 @@ namespace pboman3 { Ui::MainWindow* ui_; PboModel* model_; QFutureWatcher saveWatcher_; + QFutureWatcher loadWatcher_; bool hasChanges_; + void loadComplete(); + void unloadFile(); void setupConnections(); @@ -64,10 +69,14 @@ namespace pboman3 { void setHasChanges(bool hasChanges); - void setLoaded(bool loaded) const; + void updateLoadedStatus(bool loaded) const; void updateWindowTitle(); + void setIsLoading(QFuture future, bool supportsCancellation) const; + + void resetIsLoading() const; + QString makeExtractToTitle(const PboNode* node) const; }; } diff --git a/pbom/ui/mainwindow.ui b/pbom/ui/mainwindow.ui index d4ca6b7..ce7111c 100644 --- a/pbom/ui/mainwindow.ui +++ b/pbom/ui/mainwindow.ui @@ -50,7 +50,7 @@ 3 - + true @@ -121,6 +121,7 @@ Help + @@ -152,7 +153,7 @@ - + Open.. @@ -316,15 +317,20 @@ false + + + Check for updates + + - pboman3::TreeWidget + pboman3::ui::TreeWidget QTreeWidget
treewidget/treewidget.h
- pboman3::StatusBar + pboman3::ui::StatusBar QStatusBar
statusbar.h
diff --git a/pbom/ui/packwindow.cpp b/pbom/ui/packwindow.cpp index 868fade..1ed1bab 100644 --- a/pbom/ui/packwindow.cpp +++ b/pbom/ui/packwindow.cpp @@ -2,7 +2,9 @@ #include #include "model/task/packwindowmodel.h" -namespace pboman3 { +namespace pboman3::ui { + using namespace model; + PackWindow::PackWindow(QWidget* parent) : TaskWindow(parent) { QString title = "Pack PBO(s) - "; diff --git a/pbom/ui/packwindow.h b/pbom/ui/packwindow.h index b8cf4b2..fbf42cf 100644 --- a/pbom/ui/packwindow.h +++ b/pbom/ui/packwindow.h @@ -2,7 +2,7 @@ #include "taskwindow.h" -namespace pboman3 { +namespace pboman3::ui { class PackWindow : public TaskWindow { public: PackWindow(QWidget* parent); diff --git a/pbom/ui/progresswidget/progresswidget.cpp b/pbom/ui/progresswidget/progresswidget.cpp index 89b0a6d..d84997d 100644 --- a/pbom/ui/progresswidget/progresswidget.cpp +++ b/pbom/ui/progresswidget/progresswidget.cpp @@ -2,7 +2,7 @@ #include #include -namespace pboman3 { +namespace pboman3::ui { ProgressWidget::ProgressWidget(QWidget* parent) : QWidget(parent) { progress_ = new QProgressBar(this); diff --git a/pbom/ui/progresswidget/progresswidget.h b/pbom/ui/progresswidget/progresswidget.h index 71e655e..7bdfc56 100644 --- a/pbom/ui/progresswidget/progresswidget.h +++ b/pbom/ui/progresswidget/progresswidget.h @@ -4,7 +4,7 @@ #include #include -namespace pboman3 { +namespace pboman3::ui { class ProgressWidget : public QWidget { public: ProgressWidget(QWidget* parent = nullptr); diff --git a/pbom/ui/renamedialog.cpp b/pbom/ui/renamedialog.cpp index 947c8ef..46db797 100644 --- a/pbom/ui/renamedialog.cpp +++ b/pbom/ui/renamedialog.cpp @@ -1,21 +1,22 @@ #include "renamedialog.h" #include +#include "domain/pbonodetransaction.h" +#include "domain/validationexception.h" #include "util/log.h" #define LOG(...) LOGGER("ui/RenameDialog", __VA_ARGS__) -namespace pboman3 { - RenameDialog::RenameDialog(QWidget* parent, - PboNode* node) +namespace pboman3::ui { + RenameDialog::RenameDialog(QWidget* parent, PboNode* node) : QDialog(parent), ui_(new Ui::RenameDialog), - node_(node), + initialTitle_(node->title()), isDirty_(false) { ui_->setupUi(this); - disableAccept(setErrorState("")); - - setTextAndSelect(); + transaction_ = node->beginTransaction(); + setErrorState(""); + setTextAndSelect(node->title()); } RenameDialog::~RenameDialog() { @@ -23,47 +24,46 @@ namespace pboman3 { } void RenameDialog::onTextEdited(const QString& title) const { - if (title == node_->title()) { + if (title == initialTitle_) { LOG(info, "Title was set to the initial value") - disableAccept(setErrorState("")); + setErrorState(""); } else { LOG(info, "Title was set to the value:", title) - if (isDirty_) { - LOG(info, "The input is dirty - validating") - disableAccept(setErrorState(node_->verifyTitle(title))); + try { + transaction_->setTitle(title); + setErrorState(""); + } catch (const ValidationException& ex) { + if (isDirty_) { + LOG(info, "The input is dirty - validating") + setErrorState(ex.message()); + } } } } void RenameDialog::accept() { const QString title = ui_->input->text(); - if (title == node_->title()) { + if (title == initialTitle_) { LOG(info, "The user clicked Accept having not changed the title") QDialog::reject(); } else { - if (isDirty_) { + try { LOG(info, "Updating the node title to:", title) - node_->setTitle(title); - QDialog::accept(); - } else { - LOG(info, "The user clicked Accept the 1st time - running validations") isDirty_ = true; - if (setErrorState(node_->verifyTitle(title))) { - LOG(info, "The title contained errors:", title) - disableAccept(true); - } else { - LOG(info, "The title contained no errors:", title) - node_->setTitle(title); - QDialog::accept(); - } + transaction_->setTitle(title); + transaction_->commit(); + QDialog::accept(); + } catch (const ValidationException& ex) { + LOG(info, "The title contained errors:", ex.message()) + setErrorState(ex.message()); } } } - bool RenameDialog::setErrorState(const TitleError& err) const { + void RenameDialog::setErrorState(const QString& err) const { LOG(info, "Set validation error to:", err) ui_->error->setText(err); - return !err.isEmpty(); + disableAccept(!err.isEmpty()); } void RenameDialog::disableAccept(bool disable) const { @@ -71,8 +71,7 @@ namespace pboman3 { ui_->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(disable); } - void RenameDialog::setTextAndSelect() const { - const QString title = node_->title(); + void RenameDialog::setTextAndSelect(const QString& title) const { qsizetype index = title.lastIndexOf("."); if (index < 1) index = title.length(); diff --git a/pbom/ui/renamedialog.h b/pbom/ui/renamedialog.h index aff0e4f..1becd17 100644 --- a/pbom/ui/renamedialog.h +++ b/pbom/ui/renamedialog.h @@ -1,10 +1,12 @@ #pragma once #include -#include "model/pbonode.h" +#include "domain/pbonode.h" #include "ui_renamedialog.h" -namespace pboman3 { +namespace pboman3::ui { + using namespace domain; + class RenameDialog : public QDialog { Q_OBJECT @@ -20,13 +22,14 @@ namespace pboman3 { private: Ui::RenameDialog* ui_; - PboNode* node_; + QSharedPointer transaction_; + QString initialTitle_; bool isDirty_; - bool setErrorState(const TitleError& err) const; + void setErrorState(const QString& err) const; void disableAccept(bool disable) const; - void setTextAndSelect() const; + void setTextAndSelect(const QString& title) const; }; } diff --git a/pbom/ui/signaturedialog.cpp b/pbom/ui/signaturedialog.cpp index 5a7f6e5..4a843c4 100644 --- a/pbom/ui/signaturedialog.cpp +++ b/pbom/ui/signaturedialog.cpp @@ -3,14 +3,14 @@ #define LOG(...) LOGGER("ui/SignatureDialog", __VA_ARGS__) -namespace pboman3 { - SignatureDialog::SignatureDialog(const SignatureModel* model, QWidget* parent) +namespace pboman3::ui { + SignatureDialog::SignatureDialog(const QByteArray* signature, QWidget* parent) : QDialog(parent), ui_(new Ui::SignatureDialog) { ui_->setupUi(this); - QString sig = model->signatureString(); - if (sig.isNull()) { + QString sig = signature->toHex(' '); + if (sig.isEmpty()) { sig = "/*The file has no signature*/"; } LOG(info, "Show signature as:", sig) diff --git a/pbom/ui/signaturedialog.h b/pbom/ui/signaturedialog.h index bff7f60..c240c5d 100644 --- a/pbom/ui/signaturedialog.h +++ b/pbom/ui/signaturedialog.h @@ -2,17 +2,16 @@ #include #include "ui_signaturedialog.h" -#include "model/signaturemodel.h" namespace Ui { class SignatureDialog; } -namespace pboman3 { +namespace pboman3::ui { class SignatureDialog : public QDialog { Q_OBJECT public: - SignatureDialog(const SignatureModel* model, QWidget* parent); + SignatureDialog(const QByteArray* signature, QWidget* parent); ~SignatureDialog() override; diff --git a/pbom/ui/statusbar.cpp b/pbom/ui/statusbar.cpp index 87c132a..f7b6fd9 100644 --- a/pbom/ui/statusbar.cpp +++ b/pbom/ui/statusbar.cpp @@ -1,12 +1,14 @@ #include "statusbar.h" -#include "util/exception.h" +#include "exception.h" #include "util/log.h" +#include "ui/win32/win32taskbarindicator.h" -namespace pboman3 { +namespace pboman3::ui { StatusBar::StatusBar(QWidget* parent) - : QStatusBar(parent) { + : QStatusBar(parent), + progress_(new QProgressBar(this)), + button_(new QPushButton(this)) { - progress_ = new QProgressBar(this); progress_->setTextVisible(false); progress_->setMinimum(0); progress_->setMaximum(0); @@ -14,7 +16,6 @@ namespace pboman3 { progress_->setVisible(false); addWidget(progress_, 1); //ownership transferred! - button_ = new QPushButton(this); button_->setText("&Cancel"); button_->setStyleSheet("padding: 0 5; alignment: center;"); button_->setVisible(false); @@ -22,17 +23,25 @@ namespace pboman3 { addWidget(button_); //ownership transferred! } - void StatusBar::progressShow(QFuture future) { + void StatusBar::progressShow(QFuture future, bool supportsCancellation) { if (progress_->isVisible()) throw InvalidOperationException("A new background operation must not begin while the previous is running"); future_ = std::move(future); progress_->setVisible(true); - button_->setVisible(true); + if (supportsCancellation) + button_->setVisible(true); + + taskbar_ = QSharedPointer(new Win32TaskbarIndicator(effectiveWinId())); + taskbar_->setIndeterminate(); } - void StatusBar::progressHide() const { + void StatusBar::progressHide() { + if (!progress_->isVisible()) + throw InvalidOperationException("There is no background operation in progress"); + progress_->setVisible(false); button_->setVisible(false); + taskbar_.clear(); } } diff --git a/pbom/ui/statusbar.h b/pbom/ui/statusbar.h index 053055e..65ef163 100644 --- a/pbom/ui/statusbar.h +++ b/pbom/ui/statusbar.h @@ -5,17 +5,18 @@ #include #include #include +#include "ui/taskbarindicator.h" -namespace pboman3 { +namespace pboman3::ui { class StatusBar : public QStatusBar { Q_OBJECT public: StatusBar(QWidget* parent = nullptr); public slots: - void progressShow(QFuture future); + void progressShow(QFuture future, bool supportsCancellation = true); - void progressHide() const; + void progressHide(); signals: void cancelRequested(); @@ -24,5 +25,6 @@ namespace pboman3 { QFuture future_; QProgressBar* progress_; QPushButton* button_; + QSharedPointer taskbar_; }; } diff --git a/pbom/ui/taskbarindicator.h b/pbom/ui/taskbarindicator.h new file mode 100644 index 0000000..7cd3256 --- /dev/null +++ b/pbom/ui/taskbarindicator.h @@ -0,0 +1,14 @@ +#pragma once + +namespace pboman3::ui { + class TaskbarIndicator { + public: + virtual ~TaskbarIndicator() = default; + + virtual void setProgressValue(qint64 value, qint64 maxValue) = 0; + + virtual void setIndeterminate() = 0; + + virtual void setError() = 0; + }; +} diff --git a/pbom/ui/taskwindow.cpp b/pbom/ui/taskwindow.cpp index 1d346ce..5367b8d 100644 --- a/pbom/ui/taskwindow.cpp +++ b/pbom/ui/taskwindow.cpp @@ -4,9 +4,10 @@ #include #include "ui_taskwindow.h" #include "model/task/taskwindowmodel.h" -#include "util/exception.h" +#include "exception.h" +#include "win32/win32taskbarindicator.h" -namespace pboman3 { +namespace pboman3::ui { TaskWindow::TaskWindow(QWidget* parent) : QMainWindow(parent), ui_(new Ui::TaskWindow), @@ -14,6 +15,7 @@ namespace pboman3 { log_(nullptr), doneText_("Done") { ui_->setupUi(this); + taskbar_ = QSharedPointer(new TaskbarIndicator(winId())); } TaskWindow::~TaskWindow() { @@ -49,6 +51,8 @@ namespace pboman3 { } void TaskWindow::threadThinking(ThreadId threadId, const QString& text) const { + taskbar_->threadThinking(); + const ProgressWidget* progress = progressBars_.value(threadId); progress->setIndeterminate(true); progress->setText(text); @@ -56,6 +60,8 @@ namespace pboman3 { void TaskWindow::threadInitialized(ThreadId threadId, const QString& text, qint32 minProgress, qint32 maxProgress) const { + taskbar_->threadInitialized(maxProgress); + const ProgressWidget* progress = progressBars_.value(threadId); progress->setMinimum(minProgress); progress->setMaximum(maxProgress); @@ -64,6 +70,8 @@ namespace pboman3 { } void TaskWindow::threadProgress(ThreadId threadId, qint32 progress) const { + taskbar_->threadProgress(threadId, progress); + const ProgressWidget* progressBar = progressBars_.value(threadId); progressBar->setValue(progress); } @@ -75,6 +83,8 @@ namespace pboman3 { activeThreadCount_--; if (activeThreadCount_ == 0) { + taskbar_.clear(); + ui_->buttonBox->setEnabled(true); ui_->buttonBox->setStandardButtons(QDialogButtonBox::StandardButton::Close); } @@ -102,4 +112,28 @@ namespace pboman3 { close(); } } + + TaskWindow::TaskbarIndicator::TaskbarIndicator(WId windowId) + : maxValue_(0), + currentValue_(0) { + taskbar_ = QSharedPointer(new Win32TaskbarIndicator(windowId)); + } + + void TaskWindow::TaskbarIndicator::threadThinking() const { + if (currentValue_ == 0) + taskbar_->setIndeterminate(); + } + + void TaskWindow::TaskbarIndicator::threadInitialized(qint64 maxProgress) { + maxValue_ += maxProgress; + taskbar_->setProgressValue(currentValue_, maxValue_); + } + + void TaskWindow::TaskbarIndicator::threadProgress(ThreadId threadId, qint32 progress) { + const qint32 threadValue = threadValues_.value(threadId, 0); + const qint32 increment = progress - threadValue; + threadValues_.insert(threadId, progress); + currentValue_ += increment; + taskbar_->setProgressValue(currentValue_, maxValue_); + } } diff --git a/pbom/ui/taskwindow.h b/pbom/ui/taskwindow.h index 21e8f4f..a417902 100644 --- a/pbom/ui/taskwindow.h +++ b/pbom/ui/taskwindow.h @@ -3,6 +3,7 @@ #include #include #include +#include "taskbarindicator.h" #include "model/task/taskwindowmodel.h" #include "progresswidget/progresswidget.h" @@ -10,7 +11,9 @@ namespace Ui { class TaskWindow; } -namespace pboman3 { +namespace pboman3::ui { + using namespace model; + class TaskWindow : public QMainWindow { Q_OBJECT @@ -23,12 +26,15 @@ namespace pboman3 { void start(const QSharedPointer& model); private: + class TaskbarIndicator; + Ui::TaskWindow* ui_; QSharedPointer model_; int activeThreadCount_; QHash progressBars_; QPlainTextEdit* log_; QString doneText_; + QSharedPointer taskbar_; void threadStarted(ThreadId threadId); @@ -43,5 +49,22 @@ namespace pboman3 { void threadMessage(ThreadId threadId, const QString& message); void buttonClicked(QAbstractButton* button); + + class TaskbarIndicator { + public: + TaskbarIndicator(WId windowId); + + void threadThinking() const; + + void threadInitialized(qint64 maxProgress); + + void threadProgress(ThreadId threadId, qint32 progress); + + private: + qint64 maxValue_; + qint64 currentValue_; + QHash threadValues_; + QSharedPointer taskbar_; + }; }; } diff --git a/pbom/ui/treewidget/__test__/treewidgetbase__test.cpp b/pbom/ui/treewidget/__test__/treewidgetbase__test.cpp index f4d1056..52cac72 100644 --- a/pbom/ui/treewidget/__test__/treewidgetbase__test.cpp +++ b/pbom/ui/treewidget/__test__/treewidgetbase__test.cpp @@ -3,7 +3,7 @@ #include #include "ui/treewidget/treewidgetbase.h" -namespace pboman3::test { +namespace pboman3::ui::test { namespace treewidgetbase_test { class MockTreeWidgetBase : public TreeWidgetBase { public: diff --git a/pbom/ui/treewidget/__test__/treewidgetbase_test.cpp b/pbom/ui/treewidget/__test__/treewidgetbase_test.cpp index a63a74b..d38ea64 100644 --- a/pbom/ui/treewidget/__test__/treewidgetbase_test.cpp +++ b/pbom/ui/treewidget/__test__/treewidgetbase_test.cpp @@ -3,7 +3,7 @@ #include #include "ui/treewidget/treewidgetbase.h" -namespace pboman3::test { +namespace pboman3::ui::test { namespace treewidgetbase__test { class MockTreeWidgetBase : public TreeWidgetBase { protected: diff --git a/pbom/ui/treewidget/deleteop.cpp b/pbom/ui/treewidget/deleteop.cpp index d8d5182..6b6aa66 100644 --- a/pbom/ui/treewidget/deleteop.cpp +++ b/pbom/ui/treewidget/deleteop.cpp @@ -3,7 +3,7 @@ #define LOG(...) LOGGER("ui/treewidget/DeleteOp", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::ui { void DeleteOp::schedule(QList nodes) { LOG(info, "Schedule delete for", nodes.count(), "nodes") nodes_ = std::move(nodes); diff --git a/pbom/ui/treewidget/deleteop.h b/pbom/ui/treewidget/deleteop.h index 9a06d5b..6092cf3 100644 --- a/pbom/ui/treewidget/deleteop.h +++ b/pbom/ui/treewidget/deleteop.h @@ -1,8 +1,10 @@ #pragma once -#include "model/pbomodel.h" +#include "domain/pbonode.h" + +namespace pboman3::ui { + using namespace domain; -namespace pboman3 { class DeleteOp { public: void schedule(QList nodes); diff --git a/pbom/ui/treewidget/treewidget.cpp b/pbom/ui/treewidget/treewidget.cpp index 1190876..9aa821d 100644 --- a/pbom/ui/treewidget/treewidget.cpp +++ b/pbom/ui/treewidget/treewidget.cpp @@ -5,16 +5,16 @@ #include #include "ui/fscollector.h" #include "ui/insertdialog.h" -#include "util/exception.h" +#include "exception.h" #include -#include "model/diskaccessexception.h" +#include "io/diskaccessexception.h" #include "ui/errordialog.h" #include "ui/win32/win32fileviewer.h" #include "util/log.h" #define LOG(...) LOGGER("ui/treewidget/TreeWidget", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::ui { #define MIME_TYPE_PBOMAN "application/pboman3" TreeWidget::TreeWidget(QWidget* parent) @@ -26,6 +26,8 @@ namespace pboman3 { connect(&cutCopyWatcher_, &QFutureWatcher::finished, this, &TreeWidget::copyOrCutExecute); connect(&openWatcher_, &QFutureWatcher::finished, this, &TreeWidget::openExecute); connect(&extractWatcher_, &QFutureWatcher::finished, this, &TreeWidget::extractExecute); + connect(&fsOpWatcher_, &QFutureWatcher::finished, this, + &TreeWidget::addFilesFromFileSystemExecute); connect(this, &TreeWidget::itemSelectionChanged, this, &TreeWidget::onSelectionChanged); connect(this, &TreeWidget::doubleClicked, this, &TreeWidget::onDoubleClicked); } @@ -375,11 +377,33 @@ namespace pboman3 { void TreeWidget::addFilesFromFilesystem(const QList& urls) { LOG(info, "Add files from the file system:", urls) - NodeDescriptors files; + const QFuture> future = QtConcurrent::run([&urls](QPromise>& promise) { + QSharedPointer files = FsCollector::collectFiles(urls, [&promise]() { return promise.isCanceled(); }); + promise.addResult(files); + }); + + emit backgroundOpStarted(static_cast>(future)); + + fsOpWatcher_.setFuture(future); + } + + void TreeWidget::addFilesFromFileSystemExecute() { + LOG(info, "File collecting completed") + + emit backgroundOpStopped(); + + const QFuture> future = fsOpWatcher_.future(); + + if (!future.isValid()) { + LOG(info, "The operation was cancelled - exitig") + return; + } + + QSharedPointer files; try { - files = FsCollector::collectFiles(urls); - LOG(debug, "Collected descriptors:", files) - } catch (const PboIoException& ex) { + files = future.result(); + LOG(debug, "Collected descriptors:", *files) + } catch (const DiskAccessException& ex) { LOG(info, "Error when collecting - show error modal:", ex) UI_HANDLE_ERROR_RET(ex) } @@ -387,13 +411,13 @@ namespace pboman3 { PboNode* item = getCurrentFolder(); LOG(info, "Selected node is:", *item) - ConflictsParcel conflicts = model_->checkConflicts(item, files); + ConflictsParcel conflicts = model_->checkConflicts(item, *files); LOG(debug, "The result of conflicts check:", conflicts) - InsertDialog dialog(this, InsertDialog::Mode::ExternalFiles, &files, &conflicts); + InsertDialog dialog(this, InsertDialog::Mode::ExternalFiles, files.get(), &conflicts); if (dialog.exec() == QDialog::DialogCode::Accepted) { LOG(info, "The user accepted the file insert dialog") - model_->createNodeSet(item, files, conflicts); + model_->createNodeSet(item, *files, conflicts); } } } diff --git a/pbom/ui/treewidget/treewidget.h b/pbom/ui/treewidget/treewidget.h index 6129640..8e4f3c4 100644 --- a/pbom/ui/treewidget/treewidget.h +++ b/pbom/ui/treewidget/treewidget.h @@ -5,7 +5,9 @@ #include "model/pbomodel.h" #include -namespace pboman3 { +namespace pboman3::ui { + using namespace model; + class TreeWidget : public TreeWidgetBase { Q_OBJECT public: @@ -63,6 +65,7 @@ namespace pboman3 { QFutureWatcher cutCopyWatcher_; QFutureWatcher openWatcher_; QFutureWatcher extractWatcher_; + QFutureWatcher> fsOpWatcher_; ActionState actionState_; void onDoubleClicked(); @@ -82,5 +85,7 @@ namespace pboman3 { void addFilesFromPbo(PboNode* target, const QMimeData* mimeData); void addFilesFromFilesystem(const QList& urls); + + void addFilesFromFileSystemExecute(); }; } diff --git a/pbom/ui/treewidget/treewidgetbase.cpp b/pbom/ui/treewidget/treewidgetbase.cpp index d017af5..e1abca8 100644 --- a/pbom/ui/treewidget/treewidgetbase.cpp +++ b/pbom/ui/treewidget/treewidgetbase.cpp @@ -2,12 +2,12 @@ #include #include #include -#include "util/exception.h" +#include "exception.h" #include "util/log.h" #define LOG(...) LOGGER("ui/treewidget/TreeWidgetBase", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::ui { TreeWidgetBase::TreeWidgetBase(QWidget* parent) : QTreeWidget(parent), root_(nullptr), diff --git a/pbom/ui/treewidget/treewidgetbase.h b/pbom/ui/treewidget/treewidgetbase.h index 460c4f8..aabb7c4 100644 --- a/pbom/ui/treewidget/treewidgetbase.h +++ b/pbom/ui/treewidget/treewidgetbase.h @@ -2,9 +2,11 @@ #include #include "treewidgetitem.h" -#include "model/pbonode.h" +#include "domain/pbonode.h" + +namespace pboman3::ui { + using namespace domain; -namespace pboman3 { class TreeWidgetBase : public QTreeWidget { Q_OBJECT public: diff --git a/pbom/ui/treewidget/treewidgetitem.cpp b/pbom/ui/treewidget/treewidgetitem.cpp index 9b4c677..4c4bf8b 100644 --- a/pbom/ui/treewidget/treewidgetitem.cpp +++ b/pbom/ui/treewidget/treewidgetitem.cpp @@ -3,7 +3,7 @@ #include "ui/renamedialog.h" #include "ui/win32/win32iconmgr.h" -namespace pboman3 { +namespace pboman3::ui { TreeWidgetItem::TreeWidgetItem(PboNode* node) : TreeWidgetItem(node, QSharedPointer(new Win32IconMgr)) { } diff --git a/pbom/ui/treewidget/treewidgetitem.h b/pbom/ui/treewidget/treewidgetitem.h index 46388d8..11f1ad8 100644 --- a/pbom/ui/treewidget/treewidgetitem.h +++ b/pbom/ui/treewidget/treewidgetitem.h @@ -1,10 +1,12 @@ #pragma once #include -#include "model/pbonode.h" +#include "domain/pbonode.h" #include "ui/iconmgr.h" -namespace pboman3 { +namespace pboman3::ui { + using namespace domain; + class TreeWidgetItem : public QTreeWidgetItem, public QObject { public: TreeWidgetItem(PboNode* node); diff --git a/pbom/ui/unpackwindow.cpp b/pbom/ui/unpackwindow.cpp index fe63515..320d8c8 100644 --- a/pbom/ui/unpackwindow.cpp +++ b/pbom/ui/unpackwindow.cpp @@ -2,7 +2,7 @@ #include #include "model/task/unpackwindowmodel.h" -namespace pboman3 { +namespace pboman3::ui { UnpackWindow::UnpackWindow(QWidget* parent) : TaskWindow(parent) { QString title = "Unpack PBO(s) - "; diff --git a/pbom/ui/unpackwindow.h b/pbom/ui/unpackwindow.h index b01d10d..315e631 100644 --- a/pbom/ui/unpackwindow.h +++ b/pbom/ui/unpackwindow.h @@ -2,7 +2,7 @@ #include "taskwindow.h" -namespace pboman3 { +namespace pboman3::ui { class UnpackWindow : public TaskWindow { public: UnpackWindow(QWidget* parent); diff --git a/pbom/ui/updatesdialog.cpp b/pbom/ui/updatesdialog.cpp new file mode 100644 index 0000000..c5e92a9 --- /dev/null +++ b/pbom/ui/updatesdialog.cpp @@ -0,0 +1,154 @@ +#include "updatesdialog.h" +#include "ui_updatesdialog.h" +#include +#include +#include "util/log.h" + +#define LOG(...) LOGGER("ui/UpdatesDialog", __VA_ARGS__) + +namespace pboman3::ui { + SemanticVersion::SemanticVersion(QString rawVersion) + : raw_(std::move(rawVersion)) { + const QRegularExpression reg("v?(\\d+)\\.(\\d+)\\.(\\d+)"); + const QRegularExpressionMatch match = reg.match(raw_); + if (match.hasMatch()) { + major_ = match.captured(1).toShort(); + minor_ = match.captured(2).toShort(); + patch_ = match.captured(3).toShort(); + } else { + major_ = -1; + minor_ = -1; + patch_ = -1; + } + } + + SemanticVersion::SemanticVersion() + : major_(-1), + minor_(-1), + patch_(-1) { + } + + bool SemanticVersion::isValid() const { + return major_ > 0 || minor_ > 0 || patch_ > 0; + } + + bool operator>(const SemanticVersion& v1, const SemanticVersion& v2) { + return v2 < v1; + } + + bool operator<(const SemanticVersion& v1, const SemanticVersion& v2) { + if (v1.major_ == v2.major_) { + if (v1.minor_ == v2.minor_) { + if (v1.patch_ == v2.patch_) { + return false; + } + return v1.patch_ < v2.patch_; + } + return v1.minor_ < v2.minor_; + } + return v1.major_ < v2.major_; + } + + const QString& SemanticVersion::raw() const { + return raw_; + } + + + void GithubLatestVersion::check() { + QNetworkRequest request(QUrl(PBOM_API_SITE"/releases?per-page=1")); + request.setRawHeader("Accept", "application/vnd.github.v3+json"); + reply_.reset(network_.get(request)); + connect(reply_.get(), &QNetworkReply::finished, this, &GithubLatestVersion::replyReceived); + } + + void GithubLatestVersion::abort() const { + reply_->abort(); + } + +#define MSG_UNEXPECTED_RESPONSE "The server returned unexpected response." +#define F_TAG_NAME "tag_name" + + void GithubLatestVersion::replyReceived() { + if (reply_->error() == QNetworkReply::NoError) { + const QByteArray response = reply_->readAll(); + QJsonParseError jsonParseErr; + const QJsonDocument json = QJsonDocument::fromJson(response, &jsonParseErr); + if (json.isNull()) { + LOG(warning, "Not a JSON response. rror:", jsonParseErr.error, ". Offset:", jsonParseErr.offset, + ". Message:", jsonParseErr.errorString()) + emit error(MSG_UNEXPECTED_RESPONSE); + } else { + if (json.isArray() && json[0].isObject() && json[0][F_TAG_NAME].isString()) { + const QString rawVersion = json[0][F_TAG_NAME].toString(); + const SemanticVersion version(rawVersion); + if (version.isValid()) { + emit success(version); + } else { + LOG(warning, "The JSON version information malformed:", rawVersion) + emit error(MSG_UNEXPECTED_RESPONSE); + } + } else { + LOG(warning, "Could not get the version from the JSON:", json) + emit error(MSG_UNEXPECTED_RESPONSE); + } + } + } else { + LOG(warning, "Network failure. Code:", reply_->error(), ". Message:", reply_->errorString()) + emit error(reply_->errorString()); + } + } + + + UpdatesDialog::UpdatesDialog(QWidget* parent) + : QDialog(parent), + ui_(new Ui::UpdatesDialog) { + ui_->setupUi(this); + + uiSetLoading(); + + connect(&github_, &GithubLatestVersion::success, this, &UpdatesDialog::versionReceiveSuccess); + connect(&github_, &GithubLatestVersion::error, this, &UpdatesDialog::versionReceiveError); + github_.check(); + } + + UpdatesDialog::~UpdatesDialog() { + delete ui_; + } + + void UpdatesDialog::closeEvent(QCloseEvent* evt) { + QDialog::closeEvent(evt); + github_.abort(); + } + + void UpdatesDialog::versionReceiveSuccess(const SemanticVersion& version) const { + const SemanticVersion currentVersion(PBOM_VERSION); + assert(currentVersion.isValid()); + + if (version > currentVersion) { + ui_->label1->setTextFormat(Qt::MarkdownText); + ui_->label1->setText("New version available: **" + version.raw() + "**"); + ui_->label2->setTextFormat(Qt::MarkdownText); + ui_->label2->setText("[Download](" PBOM_PROJECT_SITE"/releases) from GitHub"); + ui_->label2->setTextInteractionFlags(Qt::TextBrowserInteraction); + ui_->label2->setOpenExternalLinks(true); + ui_->label2->show(); + } else { + ui_->label1->setText("No updates available."); + } + + ui_->progressBar->hide(); + } + + void UpdatesDialog::versionReceiveError(const QString& error) const { + ui_->progressBar->hide(); + + ui_->label1->setText("Could not check for updates:"); + + ui_->label2->show(); + ui_->label2->setText(error); + } + + void UpdatesDialog::uiSetLoading() const { + ui_->label2->hide(); + } +} diff --git a/pbom/ui/updatesdialog.h b/pbom/ui/updatesdialog.h new file mode 100644 index 0000000..3324619 --- /dev/null +++ b/pbom/ui/updatesdialog.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include + +namespace Ui { + class UpdatesDialog; +} + +namespace pboman3::ui { + class SemanticVersion { + public: + explicit SemanticVersion(QString rawVersion); + + SemanticVersion(); + + bool isValid() const; + + friend bool operator >(const SemanticVersion& v1, const SemanticVersion& v2); + + friend bool operator <(const SemanticVersion& v1, const SemanticVersion& v2); + + const QString& raw() const; + + private: + qint16 major_; + qint16 minor_; + qint16 patch_; + + QString raw_; + }; + + class GithubLatestVersion: public QObject { + Q_OBJECT + + public: + GithubLatestVersion() = default; + + void check(); + + void abort() const; + + signals: + void success(const SemanticVersion& version); + + void error(const QString& message); + + private: + QNetworkAccessManager network_; + QScopedPointer reply_; + + void replyReceived(); + }; + + class UpdatesDialog: public QDialog { + public: + UpdatesDialog(QWidget* parent); + + ~UpdatesDialog() override; + + protected: + void closeEvent(QCloseEvent*) override; + + private: + Ui::UpdatesDialog* ui_; + GithubLatestVersion github_; + + void versionReceiveSuccess(const SemanticVersion& version) const; + + void versionReceiveError(const QString& error) const; + + void uiSetLoading() const; + }; +} diff --git a/pbom/ui/updatesdialog.ui b/pbom/ui/updatesdialog.ui new file mode 100644 index 0000000..7d75e39 --- /dev/null +++ b/pbom/ui/updatesdialog.ui @@ -0,0 +1,120 @@ + + + UpdatesDialog + + + + 0 + 0 + 400 + 120 + + + + + 400 + 120 + + + + Check for updates + + + + :app.ico:app.ico + + + true + + + true + + + + + + Checking for updates.. + + + + + + + TextLabel + + + + + + + 0 + + + -1 + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + UpdatesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + UpdatesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/pbom/ui/win32/win32fileviewer.cpp b/pbom/ui/win32/win32fileviewer.cpp index 447d352..5c32481 100644 --- a/pbom/ui/win32/win32fileviewer.cpp +++ b/pbom/ui/win32/win32fileviewer.cpp @@ -6,7 +6,7 @@ #define LOG(...) LOGGER("ui/win32/Win32FileViewer", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::ui { Win32FileViewerException::Win32FileViewerException(QString message) : AppException(std::move(message)) { } diff --git a/pbom/ui/win32/win32fileviewer.h b/pbom/ui/win32/win32fileviewer.h index de0a733..2fccd4b 100644 --- a/pbom/ui/win32/win32fileviewer.h +++ b/pbom/ui/win32/win32fileviewer.h @@ -1,9 +1,9 @@ #pragma once #include "ui/fileviewer.h" -#include "util/exception.h" +#include "exception.h" -namespace pboman3 { +namespace pboman3::ui { class Win32FileViewerException : public AppException { public: Win32FileViewerException(QString message); diff --git a/pbom/ui/win32/win32iconmgr.cpp b/pbom/ui/win32/win32iconmgr.cpp index c83700a..5d3a6c0 100644 --- a/pbom/ui/win32/win32iconmgr.cpp +++ b/pbom/ui/win32/win32iconmgr.cpp @@ -1,14 +1,14 @@ #include "win32iconmgr.h" #include #include -#include "util/exception.h" +#include "exception.h" #include #include #include "util/log.h" #define LOG(...) LOGGER("ui/win32/Win32IconMgr", __VA_ARGS__) -namespace pboman3 { +namespace pboman3::ui { Win32IconMgr::Win32IconMgr() { cache_[""] = QIcon(":ifile.png"); cache_[":folder-closed:"] = QIcon(":ifolderclosed.png"); @@ -19,13 +19,12 @@ namespace pboman3 { if (extension.startsWith(".")) throw AppException("The extension must not start with a \".\" symbol"); - LOG(debug, "Get icon for extension:", extension) - if (cache_.contains(extension)) { - LOG(debug, "Retrieve from cache") return cache_[extension]; } + LOG(info, "Get icon for extension:", extension) + SHFILEINFOW info; const QString fn = "file." + extension; const DWORD_PTR hr = SHGetFileInfoW( @@ -35,7 +34,7 @@ namespace pboman3 { sizeof info, SHGFI_ICON | SHGFI_USEFILEATTRIBUTES); - LOG(debug, "Retrieve from the OS:", hr) + LOG(info, "Retrieve from the OS:", hr) if (SUCCEEDED(hr)) { cache_[extension] = QIcon(QPixmap::fromImage(QImage::fromHICON(info.hIcon))); @@ -43,7 +42,7 @@ namespace pboman3 { return cache_[extension]; } - LOG(debug, "Retrieve failed - fall back to the default", hr) + LOG(warning, "Retrieve failed - fall back to the default", hr) return cache_[""]; } diff --git a/pbom/ui/win32/win32iconmgr.h b/pbom/ui/win32/win32iconmgr.h index a853e00..58c599f 100644 --- a/pbom/ui/win32/win32iconmgr.h +++ b/pbom/ui/win32/win32iconmgr.h @@ -3,7 +3,7 @@ #include "ui/iconmgr.h" #include -namespace pboman3 { +namespace pboman3::ui { class Win32IconMgr: public IconMgr { public: Win32IconMgr(); diff --git a/pbom/ui/win32/win32taskbarindicator.cpp b/pbom/ui/win32/win32taskbarindicator.cpp new file mode 100644 index 0000000..1a3df27 --- /dev/null +++ b/pbom/ui/win32/win32taskbarindicator.cpp @@ -0,0 +1,57 @@ +#include "win32taskbarindicator.h" +#include + +namespace pboman3::ui { + Win32TaskbarIndicator::Win32TaskbarIndicator(WId windowId) + : window_(reinterpret_cast(windowId)), // NOLINT(performance-no-int-to-ptr) + isErr_(false) { + const HRESULT hr = CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, + reinterpret_cast(&progress_)); + if (!SUCCEEDED(hr)) + progress_ = 0; + } + + Win32TaskbarIndicator::~Win32TaskbarIndicator() { + if (progress_) { + progress_->SetProgressState(window_, TBPF_NOPROGRESS); + progress_->Release(); + if (!isErr_ && !windowHasFocus()) + flashWindow(); + } + } + + void Win32TaskbarIndicator::setProgressValue(qint64 value, qint64 maxValue) { + if (!progress_) + return; + isErr_ = false; + progress_->SetProgressState(window_, TBPF_NORMAL); + progress_->SetProgressValue(window_, value, maxValue); + } + + void Win32TaskbarIndicator::setIndeterminate() { + if (progress_) { + isErr_ = false; + progress_->SetProgressState(window_, TBPF_INDETERMINATE); + } + } + + void Win32TaskbarIndicator::setError() { + if (progress_) { + isErr_ = true; + progress_->SetProgressState(window_, TBPF_ERROR); + } + } + + bool Win32TaskbarIndicator::windowHasFocus() const { + return GetActiveWindow() == window_; + } + + void Win32TaskbarIndicator::flashWindow() const { + FLASHWINFO fi; + fi.cbSize = sizeof fi; + fi.hwnd = window_; + fi.dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG; + fi.dwTimeout = 0; + FlashWindowEx(&fi); + } +} diff --git a/pbom/ui/win32/win32taskbarindicator.h b/pbom/ui/win32/win32taskbarindicator.h new file mode 100644 index 0000000..98f3c68 --- /dev/null +++ b/pbom/ui/win32/win32taskbarindicator.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include "ui/taskbarindicator.h" + +namespace pboman3::ui { + class Win32TaskbarIndicator : public TaskbarIndicator { + public: + Win32TaskbarIndicator(WId windowId); + + ~Win32TaskbarIndicator() override; + + void setProgressValue(qint64 value, qint64 maxValue) override; + + void setIndeterminate() override; + + void setError() override; + + private: + ITaskbarList3* progress_; + HWND window_; + bool isErr_; + + bool windowHasFocus() const; + + void flashWindow() const; + }; +} diff --git a/pbom/util/CMakeLists.txt b/pbom/util/CMakeLists.txt new file mode 100644 index 0000000..1dac322 --- /dev/null +++ b/pbom/util/CMakeLists.txt @@ -0,0 +1,11 @@ +list(APPEND PROJECT_SOURCES + "util/log.cpp" + "util/util.cpp") + +set(PROJECT_SOURCES ${PROJECT_SOURCES} PARENT_SCOPE) + +list(APPEND TEST_SOURCES + "util/__test__/qpointerlistiterator_test.cpp" + "util/__test__/util_test.cpp") + +set(TEST_SOURCES ${TEST_SOURCES} PARENT_SCOPE) diff --git a/pbom/util/__test__/qpointerlistiterator_test.cpp b/pbom/util/__test__/qpointerlistiterator_test.cpp index 006a2b9..bb21179 100644 --- a/pbom/util/__test__/qpointerlistiterator_test.cpp +++ b/pbom/util/__test__/qpointerlistiterator_test.cpp @@ -1,13 +1,14 @@ #include #include "util/qpointerlistiterator.h" -#include "model/pboheader.h" +#include "io/pboheaderentity.h" -namespace pboman3::test { +namespace pboman3::util::test { + using namespace io; TEST(QPointerListIteratorTest, Ietrator_Works) { - const QSharedPointer h1(new PboHeader("n1", "v1")); - const QSharedPointer h2(new PboHeader("n2", "v2")); + const QSharedPointer h1(new PboHeaderEntity("n1", "v1")); + const QSharedPointer h2(new PboHeaderEntity("n2", "v2")); - QList> headers; + QList> headers; headers.append(h1); headers.append(h2); @@ -77,14 +78,14 @@ namespace pboman3::test { ASSERT_EQ(it11, it22); ASSERT_EQ(it1, it2); - ASSERT_EQ(static_cast(it1), static_cast(it2)); + ASSERT_EQ(static_cast(it1), static_cast(it2)); } TEST(QPointerListConstIteratorTest, Ietrator_Works) { - const QSharedPointer h1(new PboHeader("n1", "v1")); - const QSharedPointer h2(new PboHeader("n2", "v2")); + const QSharedPointer h1(new PboHeaderEntity("n1", "v1")); + const QSharedPointer h2(new PboHeaderEntity("n2", "v2")); - QList> headers; + QList> headers; headers.append(h1); headers.append(h2); @@ -154,6 +155,6 @@ namespace pboman3::test { ASSERT_EQ(it11, it22); ASSERT_EQ(it1, it2); - ASSERT_EQ(static_cast(it1), static_cast(it2)); + ASSERT_EQ(static_cast(it1), static_cast(it2)); } } diff --git a/pbom/util/__test__/util_test.cpp b/pbom/util/__test__/util_test.cpp index 826099f..5ba1a88 100644 --- a/pbom/util/__test__/util_test.cpp +++ b/pbom/util/__test__/util_test.cpp @@ -3,7 +3,7 @@ #include "util/util.h" #include -namespace pboman3::test { +namespace pboman3::util::test { struct GetFileExtensionParam { QString fileName; QString expectedResult; diff --git a/pbom/util/log.cpp b/pbom/util/log.cpp index 91eabf1..795be09 100644 --- a/pbom/util/log.cpp +++ b/pbom/util/log.cpp @@ -1,6 +1,6 @@ #include "log.h" -namespace pboman3 { +namespace pboman3::util { LogWorker::LogWorker(QtMessageHandler implementation) : implementation_(implementation) { } diff --git a/pbom/util/log.h b/pbom/util/log.h index 7323421..dfbbfe8 100644 --- a/pbom/util/log.h +++ b/pbom/util/log.h @@ -32,9 +32,9 @@ USE THIS CODE FOR MACRO DEBUGGING #include #include -#define ACTIVATE_ASYNC_LOG_SINK auto logging = QScopedPointer(new LoggingInfrastructure); logging->run(); +#define ACTIVATE_ASYNC_LOG_SINK auto logging = QScopedPointer(new util::LoggingInfrastructure); logging->run(); -namespace pboman3 { +namespace pboman3::util { /*These two guys make standard QT logs be output on a non UI-thread*/ /*as UI-thread output has too big UI responsiveness penalty*/ class LogWorker : public QObject { diff --git a/pbom/util/qpointerlistiterator.h b/pbom/util/qpointerlistiterator.h index bbf21ca..8ac6eb4 100644 --- a/pbom/util/qpointerlistiterator.h +++ b/pbom/util/qpointerlistiterator.h @@ -2,7 +2,7 @@ #include -namespace pboman3 { +namespace pboman3::util { template class QPointerListIterator { public: diff --git a/pbom/util/util.cpp b/pbom/util/util.cpp index e655aba..14fbf86 100644 --- a/pbom/util/util.cpp +++ b/pbom/util/util.cpp @@ -1,6 +1,6 @@ #include "util.h" -namespace pboman3 { +namespace pboman3::util { QString GetFileExtension(const QString& fileName) { const qsizetype extPos = fileName.lastIndexOf("."); QString ext = extPos >= 0 ? fileName.right(fileName.length() - extPos - 1) : ""; diff --git a/pbom/util/util.h b/pbom/util/util.h index 41abe6c..3d6c3d5 100644 --- a/pbom/util/util.h +++ b/pbom/util/util.h @@ -3,7 +3,7 @@ #include #include -namespace pboman3 { +namespace pboman3::util { typedef std::function Cancel; QString GetFileExtension(const QString& fileName);