diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 76c58be8d4..803710e0f2 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -2,7 +2,9 @@ abi ACCESSDENIED ACTIONDATA ACTIONSTART +addfile addmanifest +addportablefile addstore admins alloc @@ -88,6 +90,7 @@ cpprestsdk cppwinrt CPRWL createnew +createportabletable createtables cref csproj @@ -179,6 +182,7 @@ FILESINUSE FILESUBTYPE filesystem FILETYPE +filetype FILEVERSION FLUSHEACHLINE forcerestart @@ -242,6 +246,7 @@ interop INVALIDARG iomanip iostream +IPortable ISAPPROVEDFOROUTPUT isspace istream @@ -315,6 +320,7 @@ Nelon netcoreapp netstandard newid +NOCASE NOCLOSEPROCESS nodiscard noexcept @@ -376,6 +382,7 @@ pipssource PKCS placeholders png +portableindex posix powershell pplx @@ -416,7 +423,9 @@ REFCLSID REFCOUNT regex regexp +removefile removemanifest +removeportablefile repolibtest requeue rescap @@ -524,6 +533,7 @@ SWIPECONTROL SYMED symlink symlinks +symlinktarget Sys sz TARG @@ -538,6 +548,7 @@ tempdb terabyte testcontainer testmoniker +TESTPORTABLEFILE Testrun testsettingname TEXTFORMAT @@ -588,7 +599,9 @@ uninstalling Unregister Unregisters untimes +updatefile updatemanifest +updateportablefile UPLEVEL upvote uregex diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index bd31a99920..5fe317e436 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -213,6 +213,7 @@ + diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 9c50142299..e4f7c48cfd 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -143,6 +143,9 @@ Source Files\Repository + + Source Files\Repository + Source Files\Repository diff --git a/src/AppInstallerCLITests/PortableIndex.cpp b/src/AppInstallerCLITests/PortableIndex.cpp new file mode 100644 index 0000000000..aadc1bf5b9 --- /dev/null +++ b/src/AppInstallerCLITests/PortableIndex.cpp @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace TestCommon; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Repository::SQLite; +using namespace AppInstaller::Repository::Microsoft::Schema; + +void CreateFakePortableFile(IPortableIndex::PortableFile& file) +{ + file.SetFilePath("testPortableFile.exe"); + file.FileType = IPortableIndex::PortableFileType::File; + file.SHA256 = "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"; + file.SymlinkTarget = "testSymlinkTarget.exe"; +} + +TEST_CASE("PortableIndexCreateLatestAndReopen", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Schema::Version versionCreated; + + // Create the index + { + PortableIndex index = PortableIndex::CreateNew(tempFile, Schema::Version::Latest()); + versionCreated = index.GetVersion(); + } + + // Reopen the index for read only + { + INFO("Trying with Read"); + PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); + Schema::Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for read/write + { + INFO("Trying with ReadWrite"); + PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + Schema::Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for immutable read + { + INFO("Trying with Immutable"); + PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable); + Schema::Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } +} + +TEST_CASE("PortableIndexAddEntryToTable", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + IPortableIndex::PortableFile portableFile; + CreateFakePortableFile(portableFile); + + { + PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPortableFile(portableFile); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(!Schema::Portable_V1_0::PortableTable::IsEmpty(connection)); + } + + { + PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.RemovePortableFile(portableFile); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Schema::Portable_V1_0::PortableTable::IsEmpty(connection)); + } +} + +TEST_CASE("PortableIndex_AddUpdateRemove", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + IPortableIndex::PortableFile portableFile; + CreateFakePortableFile(portableFile); + + PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPortableFile(portableFile); + + // Apply changes to portable file + std::string updatedHash = "2db8ae7657c6622b04700137740002c51c36588e566651c9f67b4b096c8ad18b"; + portableFile.FileType = IPortableIndex::PortableFileType::Symlink; + portableFile.SHA256 = updatedHash; + portableFile.SymlinkTarget = "fakeSymlinkTarget.exe"; + + REQUIRE(index.UpdatePortableFile(portableFile)); + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + auto fileFromIndex = Schema::Portable_V1_0::PortableTable::GetPortableFileById(connection, 1); + REQUIRE(fileFromIndex.has_value()); + REQUIRE(fileFromIndex->GetFilePath() == "testPortableFile.exe"); + REQUIRE(fileFromIndex->FileType == IPortableIndex::PortableFileType::Symlink); + REQUIRE(fileFromIndex->SHA256 == updatedHash); + REQUIRE(fileFromIndex->SymlinkTarget == "fakeSymlinkTarget.exe"); + } + + { + PortableIndex index2 = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index2.RemovePortableFile(portableFile); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Schema::Portable_V1_0::PortableTable::IsEmpty(connection)); + } +} + +TEST_CASE("PortableIndex_UpdateFile_CaseInsensitive", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + IPortableIndex::PortableFile portableFile; + CreateFakePortableFile(portableFile); + + PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPortableFile(portableFile); + + // By default, portable file path is set to "testPortableFile.exe" + // Change file path to all upper case should still successfully update. + portableFile.SetFilePath("TESTPORTABLEFILE.exe"); + std::string updatedHash = "2db8ae7657c6622b04700137740002c51c36588e566651c9f67b4b096c8ad18b"; + portableFile.FileType = IPortableIndex::PortableFileType::Symlink; + portableFile.SHA256 = updatedHash; + portableFile.SymlinkTarget = "fakeSymlinkTarget.exe"; + + REQUIRE(index.UpdatePortableFile(portableFile)); + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + auto fileFromIndex = Schema::Portable_V1_0::PortableTable::GetPortableFileById(connection, 1); + REQUIRE(fileFromIndex.has_value()); + REQUIRE(fileFromIndex->GetFilePath() == "TESTPORTABLEFILE.exe"); + REQUIRE(fileFromIndex->FileType == IPortableIndex::PortableFileType::Symlink); + REQUIRE(fileFromIndex->SHA256 == updatedHash); + REQUIRE(fileFromIndex->SymlinkTarget == "fakeSymlinkTarget.exe"); + } +} + +TEST_CASE("PortableIndex_AddDuplicateFile", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + IPortableIndex::PortableFile portableFile; + CreateFakePortableFile(portableFile); + + PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPortableFile(portableFile); + + // Change file path to all upper case. Adding duplicate file should fail. + portableFile.SetFilePath("TESTPORTABLEFILE.exe"); + REQUIRE_THROWS(index.AddPortableFile(portableFile), ERROR_ALREADY_EXISTS); +} + +TEST_CASE("PortableIndex_RemoveWithId", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + IPortableIndex::PortableFile portableFile; + CreateFakePortableFile(portableFile); + + PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPortableFile(portableFile); + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Portable_V1_0::PortableTable::ExistsById(connection, 1)); + Portable_V1_0::PortableTable::DeleteById(connection, 1); + REQUIRE_FALSE(Portable_V1_0::PortableTable::ExistsById(connection, 1)); + } +} \ No newline at end of file diff --git a/src/AppInstallerCLITests/SQLiteIndex.cpp b/src/AppInstallerCLITests/SQLiteIndex.cpp index 6010a659c2..9884fbe33f 100644 --- a/src/AppInstallerCLITests/SQLiteIndex.cpp +++ b/src/AppInstallerCLITests/SQLiteIndex.cpp @@ -368,7 +368,7 @@ TEST_CASE("SQLiteIndexCreateLatestAndReopen", "[sqliteindex]") // Reopen the index for read only { INFO("Trying with Read"); - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::Read); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); Schema::Version versionRead = index.GetVersion(); REQUIRE(versionRead == versionCreated); } @@ -376,7 +376,7 @@ TEST_CASE("SQLiteIndexCreateLatestAndReopen", "[sqliteindex]") // Reopen the index for read/write { INFO("Trying with ReadWrite"); - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); Schema::Version versionRead = index.GetVersion(); REQUIRE(versionRead == versionCreated); } @@ -384,7 +384,7 @@ TEST_CASE("SQLiteIndexCreateLatestAndReopen", "[sqliteindex]") // Reopen the index for immutable read { INFO("Trying with Immutable"); - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::Immutable); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable); Schema::Version versionRead = index.GetVersion(); REQUIRE(versionRead == versionCreated); } @@ -611,7 +611,7 @@ TEST_CASE("SQLiteIndex_DependenciesTable_CheckConsistency", "[sqliteindex][V1_4] } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); REQUIRE(!index.CheckConsistency(true)); } @@ -729,7 +729,7 @@ TEST_CASE("SQLiteIndex_RemoveManifest", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); // Now remove manifest2 index.RemoveManifest(manifest2, manifest2Path); @@ -1113,7 +1113,7 @@ TEST_CASE("SQLiteIndex_UpdateManifest", "[sqliteindex][V1_4]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); // Update with no updates should return false REQUIRE(!index.UpdateManifest(manifest, manifestPath)); @@ -1149,7 +1149,7 @@ TEST_CASE("SQLiteIndex_UpdateManifest", "[sqliteindex][V1_4]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); // Now remove manifest2 index.RemoveManifest(manifest, manifestPath); @@ -1267,7 +1267,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangePath", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); manifestPath = "test/newid/test.newid-1.0.0.yaml"; @@ -1291,7 +1291,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangePath", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); // Now remove manifest, with unknown path index.RemoveManifest(manifest, ""); @@ -1348,7 +1348,7 @@ TEST_CASE("SQLiteIndex_UpdateManifest_Pathless", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); // Update with no updates should return false REQUIRE(!index.UpdateManifest(manifest)); @@ -1384,7 +1384,7 @@ TEST_CASE("SQLiteIndex_UpdateManifest_Pathless", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); // Now remove manifest2 index.RemoveManifest(manifest); @@ -1426,7 +1426,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); manifest.Id = "Test.Id"; @@ -1435,7 +1435,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); manifest.Version = "1.0.0-Test"; @@ -1444,7 +1444,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); manifest.Channel = "Test"; @@ -1453,7 +1453,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); manifest.DefaultLocalization.Add("test name"); @@ -1462,7 +1462,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); // Now remove manifest, with unknown path index.RemoveManifest(manifest, ""); @@ -1513,7 +1513,7 @@ TEST_CASE("SQLiteIndex_IdCaseInsensitivity", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); index.AddManifest(manifest2, manifest2Path); @@ -1523,7 +1523,7 @@ TEST_CASE("SQLiteIndex_IdCaseInsensitivity", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); manifest1.Id = "TEST.ID"; @@ -1535,7 +1535,7 @@ TEST_CASE("SQLiteIndex_IdCaseInsensitivity", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); index.RemoveManifest(manifest1, manifest1Path); @@ -1545,7 +1545,7 @@ TEST_CASE("SQLiteIndex_IdCaseInsensitivity", "[sqliteindex][V1_0]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); index.RemoveManifest(manifest2, manifest2Path); @@ -2648,7 +2648,7 @@ TEST_CASE("SQLiteIndex_CheckConsistency_Failure", "[sqliteindex][V1_1]") } { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); REQUIRE(!index.CheckConsistency(true)); } diff --git a/src/AppInstallerCLITests/SQLiteWrapper.cpp b/src/AppInstallerCLITests/SQLiteWrapper.cpp index ab1212db99..129fa9f174 100644 --- a/src/AppInstallerCLITests/SQLiteWrapper.cpp +++ b/src/AppInstallerCLITests/SQLiteWrapper.cpp @@ -451,6 +451,40 @@ TEST_CASE("SQLBuilder_Update", "[sqlbuilder]") SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); } +TEST_CASE("SQLBuilder_CaseInsensitive", "[sqlbuilder") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + Builder::StatementBuilder createTable; + createTable.CreateTable(s_tableName).Columns({ + Builder::ColumnBuilder(s_firstColumn, Builder::Type::Text).CollateNoCase() + }); + + createTable.Execute(connection); + + std::string upperCaseVal = "TEST"; + std::string lowerCaseVal = "test"; + + { + INFO("Insert initial value"); + Builder::StatementBuilder builder; + builder.InsertInto(s_tableName) + .Columns({ s_firstColumn }) + .Values(upperCaseVal); + + builder.Execute(connection); + } + + { + INFO("Retrieve using case-insensitive value"); + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn }).From(s_tableName).Where(s_firstColumn).Equals(lowerCaseVal); + + auto statement = builder.Prepare(connection); + REQUIRE(statement.Step()); + } +} + TEST_CASE("SQLBuilder_CreateTable", "[sqlbuilder]") { Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index 80103826bd..51988e0dce 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -237,6 +237,7 @@ + @@ -272,11 +273,15 @@ + + + + @@ -329,6 +334,7 @@ + @@ -350,9 +356,12 @@ + + + diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters index 25b7164366..904db25747 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -67,6 +67,9 @@ {84a55def-9fb8-4c90-8d5a-2cedc171940b} + + {edef5ff7-9bfe-48f8-a179-e343d1a8b57f} + @@ -300,6 +303,21 @@ Microsoft\Schema\1_6 + + Microsoft\Schema\Portable_1_0 + + + Microsoft\Schema\Portable_1_0 + + + Microsoft + + + Microsoft\Schema + + + Microsoft + @@ -470,6 +488,18 @@ Microsoft\Schema\1_6 + + Microsoft + + + Microsoft\Schema\Portable_1_0 + + + Microsoft\Schema\Portable_1_0 + + + Microsoft + diff --git a/src/AppInstallerRepositoryCore/Microsoft/PortableIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PortableIndex.cpp new file mode 100644 index 0000000000..3b09ee00a0 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/PortableIndex.cpp @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PortableIndex.h" +#include "SQLiteStorageBase.h" +#include "Schema/Portable_1_0/PortableIndexInterface.h" + +namespace AppInstaller::Repository::Microsoft +{ + PortableIndex PortableIndex::CreateNew(const std::string& filePath, Schema::Version version) + { + AICLI_LOG(Repo, Info, << "Creating new Portable Index [" << version << "] at '" << filePath << "'"); + PortableIndex result{ filePath, version }; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "portableindex_createnew"); + + // Use calculated version, as incoming version could be 'latest' + result.m_version.SetSchemaVersion(result.m_dbconn); + + result.m_interface->CreateTable(result.m_dbconn); + + result.SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + PortableIndex::IdType PortableIndex::AddPortableFile(const Schema::IPortableIndex::PortableFile& file) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Adding portable file for [" << file.GetFilePath() << "]"); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "portableindex_addfile"); + + IdType result = m_interface->AddPortableFile(m_dbconn, file); + + SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + void PortableIndex::RemovePortableFile(const Schema::IPortableIndex::PortableFile& file) + { + AICLI_LOG(Repo, Verbose, << "Removing portable file [" << file.GetFilePath() << "]"); + std::lock_guard lockInterface{ *m_interfaceLock }; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "portableindex_removefile"); + + m_interface->RemovePortableFile(m_dbconn, file); + + SetLastWriteTime(); + + savepoint.Commit(); + } + + bool PortableIndex::UpdatePortableFile(const Schema::IPortableIndex::PortableFile& file) + { + AICLI_LOG(Repo, Verbose, << "Updating portable file [" << file.GetFilePath() << "]"); + std::lock_guard lockInterface{ *m_interfaceLock }; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "portableindex_updatefile"); + + bool result = m_interface->UpdatePortableFile(m_dbconn, file).first; + + if (result) + { + SetLastWriteTime(); + savepoint.Commit(); + } + + return result; + } + + std::unique_ptr PortableIndex::CreateIPortableIndex() const + { + if (m_version == Schema::Version{ 1, 0 } || + m_version.MajorVersion == 1 || + m_version.IsLatest()) + { + return std::make_unique(); + } + + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + + PortableIndex::PortableIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile) : + SQLiteStorageBase(target, disposition, std::move(indexFile)) + { + AICLI_LOG(Repo, Info, << "Opened Portable Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); + m_interface = CreateIPortableIndex(); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, disposition == SQLiteStorageBase::OpenDisposition::ReadWrite && m_version != m_interface->GetVersion()); + } + + PortableIndex::PortableIndex(const std::string& target, Schema::Version version) : SQLiteStorageBase(target, version) + { + m_interface = CreateIPortableIndex(); + m_version = m_interface->GetVersion(); + } +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/PortableIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PortableIndex.h new file mode 100644 index 0000000000..2becefd82c --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/PortableIndex.h @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "SQLiteWrapper.h" +#include "Microsoft/Schema/IPortableIndex.h" +#include "Microsoft/Schema/Portable_1_0/PortableTable.h" +#include "Microsoft/SQLiteStorageBase.h" +#include + +namespace AppInstaller::Repository::Microsoft +{ + struct PortableIndex : SQLiteStorageBase + { + // An id that refers to a specific portable file. + using IdType = SQLite::rowid_t; + + PortableIndex(const PortableIndex&) = delete; + PortableIndex& operator=(const PortableIndex&) = delete; + + PortableIndex(PortableIndex&&) = default; + PortableIndex& operator=(PortableIndex&&) = default; + + // Creates a new PortableIndex database of the given version. + static PortableIndex CreateNew(const std::string& filePath, Schema::Version version = Schema::Version::Latest()); + + // Opens an existing PortableIndex database. + static PortableIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}) + { + return { filePath, disposition, std::move(indexFile) }; + } + + IdType AddPortableFile(const Schema::IPortableIndex::PortableFile& file); + + void RemovePortableFile(const Schema::IPortableIndex::PortableFile& file); + + bool UpdatePortableFile(const Schema::IPortableIndex::PortableFile& file); + + private: + // Constructor used to open an existing index. + PortableIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); + + // Constructor used to create a new index. + PortableIndex(const std::string& target, Schema::Version version); + + // Creates the IPortableIndex interface object for this version. + std::unique_ptr CreateIPortableIndex() const; + + std::unique_ptr m_interface; + }; +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp index 906ce77744..24df0fa6e2 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp @@ -2,30 +2,20 @@ // Licensed under the MIT License. #include "pch.h" #include "SQLiteIndex.h" -#include "Schema/MetadataTable.h" +#include "SQLiteStorageBase.h" #include "ArpVersionValidation.h" #include +#include "Schema/1_0/Interface.h" +#include "Schema/1_1/Interface.h" +#include "Schema/1_2/Interface.h" +#include "Schema/1_3/Interface.h" +#include "Schema/1_4/Interface.h" +#include "Schema/1_5/Interface.h" +#include "Schema/1_6/Interface.h" + namespace AppInstaller::Repository::Microsoft { - namespace - { - char const* const GetOpenDispositionString(SQLiteIndex::OpenDisposition disposition) - { - switch (disposition) - { - case AppInstaller::Repository::Microsoft::SQLiteIndex::OpenDisposition::Read: - return "Read"; - case AppInstaller::Repository::Microsoft::SQLiteIndex::OpenDisposition::ReadWrite: - return "ReadWrite"; - case AppInstaller::Repository::Microsoft::SQLiteIndex::OpenDisposition::Immutable: - return "ImmutableRead"; - default: - return "Unknown"; - } - } - } - SQLiteIndex SQLiteIndex::CreateNew(const std::string& filePath, Schema::Version version, CreateOptions options) { AICLI_LOG(Repo, Info, << "Creating new SQLite Index [" << version << "] at '" << filePath << "'"); @@ -33,7 +23,6 @@ namespace AppInstaller::Repository::Microsoft SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "sqliteindex_createnew"); - Schema::MetadataTable::Create(result.m_dbconn); // Use calculated version, as incoming version could be 'latest' result.m_version.SetSchemaVersion(result.m_dbconn); @@ -46,89 +35,66 @@ namespace AppInstaller::Repository::Microsoft return result; } - SQLiteIndex SQLiteIndex::Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile) + std::unique_ptr SQLiteIndex::CreateISQLiteIndex() const { - AICLI_LOG(Repo, Info, << "Opening SQLite Index for " << GetOpenDispositionString(disposition) << " at '" << filePath << "'"); - switch (disposition) + using namespace Schema; + + if (m_version == Version{ 1, 0 }) + { + return std::make_unique(); + } + else if (m_version == Version{ 1, 1 }) + { + return std::make_unique(); + } + else if (m_version == Version{ 1, 2 }) + { + return std::make_unique(); + } + else if (m_version == Version{ 1, 3 }) + { + return std::make_unique(); + } + else if (m_version == Version{ 1, 4 }) { - case AppInstaller::Repository::Microsoft::SQLiteIndex::OpenDisposition::Read: - return { filePath, SQLite::Connection::OpenDisposition::ReadOnly, SQLite::Connection::OpenFlags::None, std::move(indexFile) }; - case AppInstaller::Repository::Microsoft::SQLiteIndex::OpenDisposition::ReadWrite: - return { filePath, SQLite::Connection::OpenDisposition::ReadWrite, SQLite::Connection::OpenFlags::None, std::move(indexFile) }; - case AppInstaller::Repository::Microsoft::SQLiteIndex::OpenDisposition::Immutable: + return std::make_unique(); + } + else if (m_version == Version{ 1, 5 }) { - // Following the algorithm set forth at https://sqlite.org/uri.html [3.1] to convert to a URI path - // The execution order builds out the string so that it shouldn't require any moves (other than growing) - std::string target; - // Add an 'arbitrary' growth size to prevent the majority of needing to grow (adding 'file:/' and '?immutable=1') - target.reserve(filePath.size() + 20); - - target += "file:"; - - bool wasLastCharSlash = false; - - if (filePath.size() >= 2 && filePath[1] == ':' && - ((filePath[0] >= 'a' && filePath[0] <= 'z') || - (filePath[0] >= 'A' && filePath[0] <= 'Z'))) - { - target += '/'; - wasLastCharSlash = true; - } - - for (char c : filePath) - { - bool wasThisCharSlash = false; - switch (c) - { - case '?': target += "%3f"; break; - case '#': target += "%23"; break; - case '\\': - case '/': - { - wasThisCharSlash = true; - if (!wasLastCharSlash) - { - target += '/'; - } - break; - } - default: target += c; break; - } - - wasLastCharSlash = wasThisCharSlash; - } - - target += "?immutable=1"; - - return { target, SQLite::Connection::OpenDisposition::ReadOnly, SQLite::Connection::OpenFlags::Uri, std::move(indexFile) }; + return std::make_unique(); } - default: - THROW_HR(E_UNEXPECTED); + else if (m_version == Version{ 1, 6 } || + m_version.MajorVersion == 1 || + m_version.IsLatest()) + { + return std::make_unique(); } + + // We do not have the capacity to operate on this schema version + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } - SQLiteIndex::SQLiteIndex(const std::string& target, SQLite::Connection::OpenDisposition disposition, SQLite::Connection::OpenFlags flags, Utility::ManagedFile&& indexFile) : - m_dbconn(SQLite::Connection::Create(target, disposition, flags)), m_indexFile(std::move(indexFile)) + SQLiteIndex::SQLiteIndex(const std::string& target, Schema::Version version) : SQLiteStorageBase(target, version) { m_dbconn.EnableICU(); - m_version = Schema::Version::GetSchemaVersion(m_dbconn); - AICLI_LOG(Repo, Info, << "Opened SQLite Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); - m_interface = m_version.CreateISQLiteIndex(); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, disposition == SQLite::Connection::OpenDisposition::ReadWrite && m_version != m_interface->GetVersion()); + m_interface = CreateISQLiteIndex(); + m_version = m_interface->GetVersion(); } - SQLiteIndex::SQLiteIndex(const std::string& target, Schema::Version version) : - m_dbconn(SQLite::Connection::Create(target, SQLite::Connection::OpenDisposition::Create)) + SQLiteIndex::SQLiteIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile) : + SQLiteStorageBase(target, disposition, std::move(indexFile)) { m_dbconn.EnableICU(); - m_interface = version.CreateISQLiteIndex(); - m_version = m_interface->GetVersion(); + AICLI_LOG(Repo, Info, << "Opened SQLite Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); + m_interface = CreateISQLiteIndex(); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, disposition == SQLiteStorageBase::OpenDisposition::ReadWrite && m_version != m_interface->GetVersion()); } #ifndef AICLI_DISABLE_TEST_HOOKS void SQLiteIndex::ForceVersion(const Schema::Version& version) { - m_interface = version.CreateISQLiteIndex(); + m_version = version; + m_interface = CreateISQLiteIndex(); } #endif @@ -324,17 +290,4 @@ namespace AppInstaller::Repository::Microsoft { return m_interface->GetDependentsById(m_dbconn, packageId); } - - // Recording last write time based on MSDN documentation stating that time returns a POSIX epoch time and thus - // should be consistent across systems. - void SQLiteIndex::SetLastWriteTime() - { - Schema::MetadataTable::SetNamedValue(m_dbconn, Schema::s_MetadataValueName_LastWriteTime, Utility::GetCurrentUnixEpoch()); - } - - std::chrono::system_clock::time_point SQLiteIndex::GetLastWriteTime() - { - int64_t lastWriteTime = Schema::MetadataTable::GetNamedValue(m_dbconn, Schema::s_MetadataValueName_LastWriteTime); - return Utility::ConvertUnixEpochToSystemClock(lastWriteTime); - } } diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h index 05fe0e613d..f8201adc7e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h @@ -4,6 +4,7 @@ #include "SQLiteWrapper.h" #include "Microsoft/Schema/ISQLiteIndex.h" #include "Microsoft/Schema/Version.h" +#include "Microsoft/SQLiteStorageBase.h" #include "ISource.h" #include #include @@ -11,7 +12,6 @@ #include #include -#include #include #include #include @@ -23,7 +23,7 @@ namespace AppInstaller::Repository::Microsoft { // Holds the connection to the database, as well as the appropriate functionality to interface with it. - struct SQLiteIndex + struct SQLiteIndex : SQLiteStorageBase { // An id that refers to a specific application. using IdType = SQLite::rowid_t; @@ -46,22 +46,11 @@ namespace AppInstaller::Repository::Microsoft // Creates a new index database of the given version. static SQLiteIndex CreateNew(const std::string& filePath, Schema::Version version = Schema::Version::Latest(), CreateOptions options = CreateOptions::None); - // The disposition for opening the index. - enum class OpenDisposition + // Opens an existing SQLiteIndex database. + static SQLiteIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}) { - // Open for read only. - Read, - // Open for read and write. - ReadWrite, - // The database will not change while in use; open for immutable read. - Immutable, - }; - - // Opens an existing index database. - static SQLiteIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}); - - // Gets the schema version of the index. - Schema::Version GetVersion() const { return m_version; } + return { filePath, disposition, std::move(indexFile) }; + } #ifndef AICLI_DISABLE_TEST_HOOKS // Changes the version of the interface being used to operate on the database. @@ -69,9 +58,6 @@ namespace AppInstaller::Repository::Microsoft void ForceVersion(const Schema::Version& version); #endif - // Gets the last write time for the index. - std::chrono::system_clock::time_point GetLastWriteTime(); - // Adds the manifest at the repository relative path to the index. // If the function succeeds, the manifest has been added. // Returns the manifest id. @@ -151,23 +137,19 @@ namespace AppInstaller::Repository::Microsoft std::set> GetDependenciesByManifestRowId(SQLite::rowid_t manifestRowId) const; std::vector> GetDependentsById(AppInstaller::Manifest::string_t packageId) const; private: - // Constructor used to open an existing index. - SQLiteIndex(const std::string& target, SQLite::Connection::OpenDisposition disposition, SQLite::Connection::OpenFlags flags, Utility::ManagedFile&& indexFile); - // Constructor used to create a new index. SQLiteIndex(const std::string& target, Schema::Version version); + // Constructor used to open an existing index. + SQLiteIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); + // Internal functions to normalize on the relativePath being present. IdType AddManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath); bool UpdateManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath); - // Sets the last write time metadata value in the index. - void SetLastWriteTime(); + // Creates the ISQLiteIndex interface object for this version. + std::unique_ptr CreateISQLiteIndex() const; - Utility::ManagedFile m_indexFile; - SQLite::Connection m_dbconn; - Schema::Version m_version; std::unique_ptr m_interface; - std::unique_ptr m_interfaceLock = std::make_unique(); }; -} +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteStorageBase.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteStorageBase.cpp new file mode 100644 index 0000000000..e2e3fc7804 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteStorageBase.cpp @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SQLiteStorageBase.h" +#include "Schema/MetadataTable.h" + +namespace AppInstaller::Repository::Microsoft +{ + namespace + { + static char const* const GetOpenDispositionString(SQLiteStorageBase::OpenDisposition disposition) + { + switch (disposition) + { + case AppInstaller::Repository::Microsoft::SQLiteStorageBase::OpenDisposition::Read: + return "Read"; + case AppInstaller::Repository::Microsoft::SQLiteStorageBase::OpenDisposition::ReadWrite: + return "ReadWrite"; + case AppInstaller::Repository::Microsoft::SQLiteStorageBase::OpenDisposition::Immutable: + return "ImmutableRead"; + default: + return "Unknown"; + } + } + } + + // One method for converting open disposition to proper open disposition + // another method for obtaining the right flags + void SQLiteStorageBase::SetLastWriteTime() + { + Schema::MetadataTable::SetNamedValue(m_dbconn, Schema::s_MetadataValueName_LastWriteTime, Utility::GetCurrentUnixEpoch()); + } + + // Recording last write time based on MSDN documentation stating that time returns a POSIX epoch time and thus + // should be consistent across systems. + std::chrono::system_clock::time_point SQLiteStorageBase::GetLastWriteTime() + { + int64_t lastWriteTime = Schema::MetadataTable::GetNamedValue(m_dbconn, Schema::s_MetadataValueName_LastWriteTime); + return Utility::ConvertUnixEpochToSystemClock(lastWriteTime); + } + + SQLiteStorageBase::SQLiteStorageBase(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile) : + m_indexFile(std::move(indexFile)) + { + AICLI_LOG(Repo, Info, << "Opening SQLite Index for " << GetOpenDispositionString(disposition) << " at '" << filePath << "'"); + switch (disposition) + { + case OpenDisposition::Read: + m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadOnly, SQLite::Connection::OpenFlags::None); + break; + case OpenDisposition::ReadWrite: + m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadWrite, SQLite::Connection::OpenFlags::None); + break; + case OpenDisposition::Immutable: + { + // Following the algorithm set forth at https://sqlite.org/uri.html [3.1] to convert to a URI path + // The execution order builds out the string so that it shouldn't require any moves (other than growing) + std::string target; + // Add an 'arbitrary' growth size to prevent the majority of needing to grow (adding 'file:/' and '?immutable=1') + target.reserve(filePath.size() + 20); + + target += "file:"; + + bool wasLastCharSlash = false; + + if (filePath.size() >= 2 && filePath[1] == ':' && + ((filePath[0] >= 'a' && filePath[0] <= 'z') || + (filePath[0] >= 'A' && filePath[0] <= 'Z'))) + { + target += '/'; + wasLastCharSlash = true; + } + + for (char c : filePath) + { + bool wasThisCharSlash = false; + switch (c) + { + case '?': target += "%3f"; break; + case '#': target += "%23"; break; + case '\\': + case '/': + { + wasThisCharSlash = true; + if (!wasLastCharSlash) + { + target += '/'; + } + break; + } + default: target += c; break; + } + + wasLastCharSlash = wasThisCharSlash; + } + + target += "?immutable=1"; + m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadOnly, SQLite::Connection::OpenFlags::Uri); + break; + } + default: + THROW_HR(E_UNEXPECTED); + } + + m_version = Schema::Version::GetSchemaVersion(m_dbconn); + } + + SQLiteStorageBase::SQLiteStorageBase(const std::string& target, Schema::Version version) : + m_dbconn(SQLite::Connection::Create(target, SQLite::Connection::OpenDisposition::Create)) + { + m_version = version; + Schema::MetadataTable::Create(m_dbconn); + } +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteStorageBase.h b/src/AppInstallerRepositoryCore/Microsoft/SQLiteStorageBase.h new file mode 100644 index 0000000000..26d86c5f40 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteStorageBase.h @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "SQLiteWrapper.h" +#include "Microsoft/Schema/Version.h" +#include +#include + +#include + +namespace AppInstaller::Repository::Microsoft +{ + struct SQLiteStorageBase + { + // The disposition for opening the index. + enum class OpenDisposition + { + // Open for read only. + Read, + // Open for read and write. + ReadWrite, + // The database will not change while in use; open for immutable read. + Immutable, + }; + + // Gets the last write time for the index. + std::chrono::system_clock::time_point GetLastWriteTime(); + + // Gets the schema version of the index. + Schema::Version GetVersion() const { return m_version; } + + protected: + SQLiteStorageBase(const std::string& target, Schema::Version version); + + SQLiteStorageBase(const std::string& filePath, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); + + // Sets the last write time metadata value in the index. + void SetLastWriteTime(); + + // Gets the corresponding OpenFlags based on the disposition. + SQLite::Connection::OpenFlags GetOpenFlags(SQLiteStorageBase::OpenDisposition disposition); + + Utility::ManagedFile m_indexFile; + SQLite::Connection m_dbconn; + Schema::Version m_version; + std::unique_ptr m_interfaceLock = std::make_unique(); + }; +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPortableIndex.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPortableIndex.h new file mode 100644 index 0000000000..2ac19f9543 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPortableIndex.h @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "SQLiteWrapper.h" +#include "Microsoft/Schema/Version.h" +#include + +namespace AppInstaller::Repository::Microsoft::Schema +{ + struct IPortableIndex + { + // File type enum of the portable file + enum class PortableFileType + { + Unknown, + File, + Directory, + Symlink + }; + + // Metadata representation of a portable file placed down during installation + struct PortableFile + { + // Version 1.0 + PortableFileType FileType = PortableFileType::Unknown; + std::string SHA256; + std::string SymlinkTarget; + + void SetFilePath(const std::filesystem::path& path) { m_filePath = std::filesystem::weakly_canonical(path); }; + + std::filesystem::path GetFilePath() const { return m_filePath; }; + + private: + std::filesystem::path m_filePath; + }; + + virtual ~IPortableIndex() = default; + + // Gets the schema version that this index interface is built for. + virtual Schema::Version GetVersion() const = 0; + + // Creates all of the version dependent tables within the database. + virtual void CreateTable(SQLite::Connection& connection) = 0; + + // Version 1.0 + // Adds a portable file to the index. + virtual SQLite::rowid_t AddPortableFile(SQLite::Connection& connection, const PortableFile& file) = 0; + + // Removes a portable file from the index. + virtual SQLite::rowid_t RemovePortableFile(SQLite::Connection& connection, const PortableFile& file) = 0; + + // Updates the file with matching FilePath in the index. + // The return value indicates whether the index was modified by the function. + virtual std::pair UpdatePortableFile(SQLite::Connection& connection, const PortableFile& file) = 0; + }; +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableIndexInterface.h new file mode 100644 index 0000000000..b87f07d5a6 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableIndexInterface.h @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/IPortableIndex.h" +#include "Microsoft/Schema/Portable_1_0/PortableTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 +{ + struct PortableIndexInterface : public IPortableIndex + { + // Version 1.0 + Schema::Version GetVersion() const override; + void CreateTable(SQLite::Connection& connection) override; + + private: + SQLite::rowid_t AddPortableFile(SQLite::Connection& connection, const PortableFile& file) override; + SQLite::rowid_t RemovePortableFile(SQLite::Connection& connection, const PortableFile& file) override; + std::pair UpdatePortableFile(SQLite::Connection& connection, const PortableFile& file) override; + }; +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableIndexInterface_1_0.cpp new file mode 100644 index 0000000000..d7e49588ac --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableIndexInterface_1_0.cpp @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/Portable_1_0/PortableIndexInterface.h" +#include "Microsoft/Schema/Portable_1_0/PortableTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 +{ + namespace + { + std::optional GetExistingPortableFileId(const SQLite::Connection& connection, const IPortableIndex::PortableFile& file) + { + auto result = PortableTable::SelectByFilePath(connection, file.GetFilePath()); + + if (!result) + { + AICLI_LOG(Repo, Verbose, << "Did not find a portable file with the path { " << file.GetFilePath() << " }"); + } + + return result; + } + } + + Schema::Version PortableIndexInterface::GetVersion() const + { + return { 1, 0 }; + } + + void PortableIndexInterface::CreateTable(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createportabletable_v1_0"); + Portable_V1_0::PortableTable::Create(connection); + savepoint.Commit(); + } + + SQLite::rowid_t PortableIndexInterface::AddPortableFile(SQLite::Connection& connection, const PortableFile& file) + { + auto portableEntryResult = GetExistingPortableFileId(connection, file); + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), portableEntryResult.has_value()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addportablefile_v1_0"); + SQLite::rowid_t portableFileId = PortableTable::AddPortableFile(connection, file); + + savepoint.Commit(); + return portableFileId; + } + + SQLite::rowid_t PortableIndexInterface::RemovePortableFile(SQLite::Connection& connection, const PortableFile& file) + { + auto portableEntryResult = GetExistingPortableFileId(connection, file); + + // If the portable file doesn't actually exist, fail the remove. + THROW_HR_IF(E_NOT_SET, !portableEntryResult); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removeportablefile_v1_0"); + PortableTable::RemovePortableFileById(connection, portableEntryResult.value()); + + savepoint.Commit(); + return portableEntryResult.value(); + } + + std::pair PortableIndexInterface::UpdatePortableFile(SQLite::Connection& connection, const PortableFile& file) + { + auto portableEntryResult = GetExistingPortableFileId(connection, file); + + // If the portable file doesn't actually exist, fail the update. + THROW_HR_IF(E_NOT_SET, !portableEntryResult); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updateportablefile_v1_0"); + bool status = PortableTable::UpdatePortableFileById(connection, portableEntryResult.value(), file); + + savepoint.Commit(); + return { status, portableEntryResult.value() }; + } +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp new file mode 100644 index 0000000000..b6c0c830d1 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PortableTable.h" +#include "SQLiteStatementBuilder.h" +#include "Microsoft/Schema/IPortableIndex.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 +{ + using namespace std::string_view_literals; + static constexpr std::string_view s_PortableTable_Table_Name = "portable"sv; + static constexpr std::string_view s_PortableTable_FilePath_Column = "filepath"sv; + static constexpr std::string_view s_PortableTable_FileType_Column = "filetype"sv; + static constexpr std::string_view s_PortableTable_SHA256_Column = "sha256"sv; + static constexpr std::string_view s_PortableTable_SymlinkTarget_Column = "symlinktarget"sv; + + std::string_view PortableTable::TableName() + { + return s_PortableTable_Table_Name; + } + + void PortableTable::Create(SQLite::Connection& connection) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPortableTable_v1_0"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_PortableTable_Table_Name).BeginColumns(); + + createTableBuilder.Column(ColumnBuilder(s_PortableTable_FilePath_Column, Type::Text).NotNull().Unique().CollateNoCase()); + createTableBuilder.Column(ColumnBuilder(s_PortableTable_FileType_Column, Type::Int64).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PortableTable_SHA256_Column, Type::Blob)); + createTableBuilder.Column(ColumnBuilder(s_PortableTable_SymlinkTarget_Column, Type::Text)); + + createTableBuilder.EndColumns(); + createTableBuilder.Execute(connection); + + savepoint.Commit(); + } + + std::optional PortableTable::SelectByFilePath(const SQLite::Connection& connection, const std::filesystem::path& path) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::RowIDName).From(s_PortableTable_Table_Name).Where(s_PortableTable_FilePath_Column); + builder.Equals(path.u8string()); + + SQLite::Statement select = builder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + void PortableTable::RemovePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + builder.Execute(connection); + } + + SQLite::rowid_t PortableTable::AddPortableFile(SQLite::Connection& connection, const IPortableIndex::PortableFile& file) + { + SQLite::Builder::StatementBuilder builder; + builder.InsertInto(s_PortableTable_Table_Name) + .Columns({ s_PortableTable_FilePath_Column, + s_PortableTable_FileType_Column, + s_PortableTable_SHA256_Column, + s_PortableTable_SymlinkTarget_Column }) + .Values(file.GetFilePath().u8string(), file.FileType, file.SHA256, file.SymlinkTarget); + + builder.Execute(connection); + return connection.GetLastInsertRowID(); + } + + bool PortableTable::UpdatePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id, const IPortableIndex::PortableFile& file) + { + SQLite::Builder::StatementBuilder builder; + builder.Update(s_PortableTable_Table_Name).Set() + .Column(s_PortableTable_FilePath_Column).Equals(file.GetFilePath().u8string()) + .Column(s_PortableTable_FileType_Column).Equals(file.FileType) + .Column(s_PortableTable_SHA256_Column).Equals(file.SHA256) + .Column(s_PortableTable_SymlinkTarget_Column).Equals(file.SymlinkTarget) + .Where(SQLite::RowIDName).Equals(id); + + builder.Execute(connection); + return connection.GetChanges() != 0; + } + + std::optional PortableTable::GetPortableFileById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ s_PortableTable_FilePath_Column, + s_PortableTable_FileType_Column, + s_PortableTable_SHA256_Column, + s_PortableTable_SymlinkTarget_Column}) + .From(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement select = builder.Prepare(connection); + + IPortableIndex::PortableFile portableFile; + if (select.Step()) + { + auto [filePath, fileType, sha256, symlinkTarget] = select.GetRow(); + portableFile.SetFilePath(std::move(filePath)); + portableFile.FileType = fileType; + portableFile.SHA256 = std::move(sha256); + portableFile.SymlinkTarget = std::move(symlinkTarget); + return portableFile; + } + else + { + return {}; + } + } + + bool PortableTable::ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return (countStatement.GetColumn(0) != 0); + } + + void PortableTable::DeleteById(SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + builder.Execute(connection); + } + + bool PortableTable::IsEmpty(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_PortableTable_Table_Name); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return (countStatement.GetColumn(0) == 0); + } +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.h new file mode 100644 index 0000000000..513e100010 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.h @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "SQLiteWrapper.h" +#include "SQLiteStatementBuilder.h" +#include "Microsoft/Schema/IPortableIndex.h" +#include + +namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 +{ + // A table the represents a single portable file + struct PortableTable + { + // Get the table name. + static std::string_view TableName(); + + // Creates the table with named indices. + static void Create(SQLite::Connection& connection); + + // Gets a value indicating whether the portable file with rowid id exists. + static bool ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id); + + // Deletes the portable file row with the given rowid + static void DeleteById(SQLite::Connection& connection, SQLite::rowid_t id); + + // Gets a value indicating whether the table is empty. + static bool IsEmpty(SQLite::Connection& connection); + + // Selects the portable file by filepath from the table, returning the rowid if it exists. + static std::optional SelectByFilePath(const SQLite::Connection& connection, const std::filesystem::path& path); + + // Selects the portable file by rowid from the table, returning the portable file object if it exists. + static std::optional GetPortableFileById(const SQLite::Connection& connection, SQLite::rowid_t id); + + // Adds the portable file into the table. + static SQLite::rowid_t AddPortableFile(SQLite::Connection& connection, const IPortableIndex::PortableFile& file); + + // Removes the portable file from the table by id. + static void RemovePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id); + + // Updates the portable file in the table by id. + static bool UpdatePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id, const IPortableIndex::PortableFile& file); + }; +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Version.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Version.cpp index 0c6cc20f0f..f5a7429596 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Version.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Version.cpp @@ -4,14 +4,6 @@ #include "Version.h" #include "MetadataTable.h" -#include "1_0/Interface.h" -#include "1_1/Interface.h" -#include "1_2/Interface.h" -#include "1_3/Interface.h" -#include "1_4/Interface.h" -#include "1_5/Interface.h" -#include "1_6/Interface.h" - namespace AppInstaller::Repository::Microsoft::Schema { Version Version::GetSchemaVersion(SQLite::Connection& connection) @@ -32,44 +24,6 @@ namespace AppInstaller::Repository::Microsoft::Schema savepoint.Commit(); } - // Creates the interface object for this version. - std::unique_ptr Version::CreateISQLiteIndex() const - { - if (*this == Version{ 1, 0 }) - { - return std::make_unique(); - } - else if (*this == Version{ 1, 1 }) - { - return std::make_unique(); - } - else if (*this == Version{ 1, 2 }) - { - return std::make_unique(); - } - else if (*this == Version{ 1, 3 }) - { - return std::make_unique(); - } - else if (*this == Version{ 1, 4 }) - { - return std::make_unique(); - } - else if (*this == Version{ 1, 5 }) - { - return std::make_unique(); - } - else if (*this == Version{ 1, 6 } || - this->MajorVersion == 1 || - this->IsLatest()) - { - return std::make_unique(); - } - - // We do not have the capacity to operate on this schema version - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - std::ostream& operator<<(std::ostream& out, const Version& version) { return (out << version.MajorVersion << '.' << version.MinorVersion); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Version.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Version.h index a898517e82..f435c08b62 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Version.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Version.h @@ -2,16 +2,11 @@ // Licensed under the MIT License. #pragma once #include "SQLiteWrapper.h" -#include "Microsoft/Schema/ISQLiteIndex.h" - #include #include namespace AppInstaller::Repository::Microsoft::Schema { - // Forward declarations - struct ISQLiteIndex; - // Represents the schema version of the index. struct Version { @@ -56,9 +51,6 @@ namespace AppInstaller::Repository::Microsoft::Schema // Writes the current version to the given index. void SetSchemaVersion(SQLite::Connection& connection); - - // Creates the interface object for this version. - std::unique_ptr CreateISQLiteIndex() const; }; // Output the version diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/IRestClient.h b/src/AppInstallerRepositoryCore/Rest/Schema/IRestClient.h index 1930ca95f3..2ec600e7eb 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/IRestClient.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/IRestClient.h @@ -2,6 +2,8 @@ // Licensed under the MIT License. #pragma once #include "Microsoft/Schema/Version.h" +#include +#include #include #include diff --git a/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.cpp b/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.cpp index e431375fb5..216acaac33 100644 --- a/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.cpp +++ b/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.cpp @@ -174,6 +174,15 @@ namespace AppInstaller::Repository::SQLite::Builder return *this; } + ColumnBuilder& ColumnBuilder::CollateNoCase(bool isTrue) + { + if (isTrue) + { + m_stream << " COLLATE NOCASE"; + } + return *this; + } + ColumnBuilder& ColumnBuilder::Default(int64_t value) { m_stream << " DEFAULT " << value; diff --git a/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.h b/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.h index 3bcb632d6e..193728b636 100644 --- a/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.h +++ b/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.h @@ -151,6 +151,10 @@ namespace AppInstaller::Repository::SQLite::Builder // Allow for data driven construction with input value. ColumnBuilder& NotNull(bool isTrue = true); + // Indicate that the column is case-insensitive. + // Allow for data driven construction with input value. + ColumnBuilder& CollateNoCase(bool isTrue = true); + // Indicate the default value for the column. // Note that a default value is not considered constant if it is bound, // so this function directly places the incoming value into the SQL statement.