diff options
39 files changed, 932 insertions, 75 deletions
diff --git a/doc/images/ifw-repository-categories.png b/doc/images/ifw-repository-categories.png Binary files differnew file mode 100644 index 000000000..afed7c1b9 --- /dev/null +++ b/doc/images/ifw-repository-categories.png diff --git a/doc/includes/installerfw-examples-generating-online.qdocinc b/doc/includes/installerfw-examples-generating-online.qdocinc new file mode 100644 index 000000000..f623b1d22 --- /dev/null +++ b/doc/includes/installerfw-examples-generating-online.qdocinc @@ -0,0 +1,18 @@ + \section1 Generating the Example Installer + + To create the example installer, switch to the example source directory on + the command line and enter the following command: + + \list + \li On Windows: + \code + ..\..\bin\binarycreator.exe --online-only -c config\config.xml -p packages installer.exe + \endcode + \li On Linux or macOS: + \code + ../../bin/binarycreator --online-only -c config/config.xml -p packages installer + \endcode + \endlist + + You should now be able to run the installer and install from the repository. + diff --git a/doc/installerfw.qdoc b/doc/installerfw.qdoc index 22a15983b..bdd99c089 100644 --- a/doc/installerfw.qdoc +++ b/doc/installerfw.qdoc @@ -266,6 +266,10 @@ elements that each contain the \c <Url> child element that specifies the URL to access the repository. For more information, see \l{Configuring Repositories}. \row + \li RepositoryCategories + \li Name of a category that can contain a list of \c <RemoteRepositories> child elements. + For more information, see \l{Configuring Repository Categories}. + \row \li MaintenanceToolName \li Filename of the generated maintenance tool. Defaults to \e maintenancetool. The platform-specific executable file extension is @@ -1114,6 +1118,36 @@ text. Authentication details not set here will be gotten at runtime using a dialog. The user can work around these settings at runtime. + \section1 Configuring Repository Categories + + The \c <RepositoryCategory> element in the installer configuration file + (config.xml) can contain a list of several \c <RemoteRepositories> elements. Each \c <RemoteRepositories> + element within the \c <RepositoryCagetory> element is considered a category, which has a \c <DisplayName> and can + contain several \c <Repository> elements. Repository categories are shown in the component selection page, + on the left side of the component selection widget: + + \image ifw-repository-categories.png "Component selection Page" + + By default, only repositories with no category are shown in the component selection widget. Checking one or + several repositories and pressing \uicontrol Fetch will update the widget to show content also + from the selected categorized repositories. + + Example of creating a repository category: + + \code + <RepositoryCategories> + <RemoteRepositories> + <Displayname>Category 1</Displayname> + <Repository> + <Url>http://www.example.com/packages</Url> + <Enabled>1</Enabled> + <Username>user</Username> + <Password>password</Password> + <DisplayName>Example repository</DisplayName> + </Repository> + </RemoteRepositories> + </RepositoryCategories> + \endcode \section1 Creating Installer Binaries diff --git a/examples/doc/images/qtifw-examples-repository-categories.png b/examples/doc/images/qtifw-examples-repository-categories.png Binary files differnew file mode 100644 index 000000000..afed7c1b9 --- /dev/null +++ b/examples/doc/images/qtifw-examples-repository-categories.png diff --git a/examples/doc/repositorycategories.qdoc b/examples/doc/repositorycategories.qdoc new file mode 100644 index 000000000..dca5ada43 --- /dev/null +++ b/examples/doc/repositorycategories.qdoc @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example repositorycategories + \ingroup qtifwexamples + \title Repository Categories Example + + \brief Using the RepositoryCategories element to set up an + online installer where repositories are grouped. + + \image qtifw-examples-repository-categories.png + + \e{Repository Categories} illustrates how to set up an installer + where repositories are grouped into categories. + + \include installerfw-examples-configuring.qdocinc + + \list + \li The \c <RepositoryCategories> element shows how to group repositories into categories. + \c <RepositoryCategories> can contain one or several \c <RemoteRepositories> + child elements that specify a connection to repositories. For more + information about \c <RemoteRepositories> see + \l{Configuring Repositories}. + \endlist + + \quotefile repositorycategories/config/config.xml + + \include installerfw-examples-packaging.qdocinc + + \list + \li The \c <Default> element is set to \c true to preselect the + component in the installer. + \endlist + + \quotefile online/packages/A/meta/package.xml + + \section1 Generating the Online Repository + + This installer contains four packages that each have two components. The \c Packages directory contains two components + that are not grouped categories. They are always visible in tree view in the component selection page. \c Packages_forcategory1 + and \c packages2_forcategory1 both contain two components, which are visible when \c Category 1 is fetched. \c Packages_forcategory2 + contains two components that are visible only when \c Category 2 is fetched. + + The packages need to be converted to a file structure that the installer can + fetch at runtime. To use the \c repogen tool to convert the packages, switch + to the example source directory on the command line and enter the following + command: + + \list + \li On Windows: + \code + ..\..\bin\repogen.exe -p packages repository + ..\..\bin\repogen.exe -p packages_forcategory1 repository1 + ..\..\bin\repogen.exe -p packages2_forcategory1 repository2 + ..\..\bin\repogen.exe -p packages_forcategory2 repository3 + \endcode + \li On Linux or macOS: + \code + ../../bin/repogen -p packages repository + ../../bin/repogen -p packages_forcategory1 repository1 + ../../bin/repogen -p packages2_forcategory1 repository2 + ../../bin/repogen -p packages_forcategory2 repository3 + \endcode + \endlist + + The generated \c repository, \c repository1, \c repository2 and \c repository3 directories will now + contain a full copy of the package data and some additionally generated metadata, such as SHA + checksums. + + The directories now need to be made available at the URL set in + \c config.xml: \c{http://localhost/repository}, \c{http://localhost/repository1}, \c{http://localhost/repository2} and + \c{http://localhost/repository3}. How this is done depends on + the platform and web server used. If you do not have a running web server + yet, but have Python available, you should be able to start a minimal web + server from the command line. Make sure you are in the example directory, + and then enter: + + \code + python -m SimpleHTTPServer 80 + \endcode + + You should now be able to open and explore \l{http://localhost/repository} + in your web browser. + + \note If you do not have enough permissions to set up a web server locally, + you can also specify an absolute \c{file:///} URL as the value of the \c URL + element in \c config.xml. For example, + \c file:///C:/Qt/QtIFW/examples/repositorycategories/repository would be a valid URL on + Windows if \c repository is located in \c C:\Qt\QtIFW\examples\repositorycategories. + + \include installerfw-examples-generating-online.qdocinc +*/ diff --git a/examples/examples.pro b/examples/examples.pro index 377632b9c..bfe69a5c8 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -11,6 +11,7 @@ SUBDIRS += \ openreadme \ quitinstaller \ registerfileextension \ + repositorycategories \ startmenu \ systeminfo \ stylesheet diff --git a/examples/repositorycategories/README b/examples/repositorycategories/README new file mode 100644 index 000000000..4a1f491c6 --- /dev/null +++ b/examples/repositorycategories/README @@ -0,0 +1,30 @@ +Shows how to set up an online installer and how to use categorized repositories. Categorized repositories are not loaded to the tree view by default, instead you can select +to show categorized repositories in a tree view combobox. By default, repositories without categories are always shown in the tree view. + +The example uses a very simple web server shipped with Python. + +Generate the online repositories with + + repogen -p packages repository + repogen -p packages_forcategory1 repository1 + repogen -p packages2_forcategory1 repository2 + repogen -p packages_forcategory2 repository3 + +Generate the installer with + + binarycreator --online-only -c config/config.xml -p packages installer + +Now launch a minimal web server in the example's directory (admin rights may be needed) + + python -m SimpleHTTPServer 80 + +This should make the content of the local directory available under +http://localhost + +You should be able to now launch the installer. + +To deploy an update, run + + repogen --update-new-components -p packages_update repository + +and launch the maintenance tool in your installation. diff --git a/examples/repositorycategories/config/config.xml b/examples/repositorycategories/config/config.xml new file mode 100644 index 000000000..c61e4893d --- /dev/null +++ b/examples/repositorycategories/config/config.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Installer> + <Name>Repository category Installer Example</Name> + <Version>1.0.0</Version> + <Title>Repository category Installer Example</Title> + <Publisher>The Qt Company</Publisher> + <!-- Directory name is used in component.xml --> + <StartMenuDir>Qt IFW Examples</StartMenuDir> + <TargetDir>@HomeDir@/IfwExamples/repositoryCategories</TargetDir> + <RemoteRepositories> + <Repository> + <Url>http://localhost/repository</Url> + </Repository> + </RemoteRepositories> + <RepositoryCategories> + <RepositoryCategoryDisplayname>Releases</RepositoryCategoryDisplayname> + <RemoteRepositories> + <DisplayName>Category 1</DisplayName> + <Repository> + <Url>http://localhost/repository1</Url> + </Repository> + <Repository> + <Url>http://localhost/repository2</Url> + </Repository> + </RemoteRepositories> + <RemoteRepositories> + <DisplayName>Category 2</DisplayName> + <Repository> + <Url>http://localhost/repository3</Url> + </Repository> + </RemoteRepositories> + </RepositoryCategories> +</Installer> diff --git a/examples/repositorycategories/packages/A/data/A.txt b/examples/repositorycategories/packages/A/data/A.txt new file mode 100644 index 000000000..98114dd6e --- /dev/null +++ b/examples/repositorycategories/packages/A/data/A.txt @@ -0,0 +1,2 @@ +Example content for package A. + diff --git a/examples/repositorycategories/packages/A/meta/package.xml b/examples/repositorycategories/packages/A/meta/package.xml new file mode 100644 index 000000000..08cef59c0 --- /dev/null +++ b/examples/repositorycategories/packages/A/meta/package.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Package> + <DisplayName>A</DisplayName> + <Description>Example component A</Description> + <Version>1.0.2-1</Version> + <ReleaseDate>2015-01-01</ReleaseDate> + <Default>true</Default> +</Package> diff --git a/examples/repositorycategories/packages/B/data/B.txt b/examples/repositorycategories/packages/B/data/B.txt new file mode 100644 index 000000000..1ee864074 --- /dev/null +++ b/examples/repositorycategories/packages/B/data/B.txt @@ -0,0 +1,2 @@ +Example content for package B. + diff --git a/examples/repositorycategories/packages/B/meta/package.xml b/examples/repositorycategories/packages/B/meta/package.xml new file mode 100644 index 000000000..44e90b9a2 --- /dev/null +++ b/examples/repositorycategories/packages/B/meta/package.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Package> + <DisplayName>B</DisplayName> + <Description>Example component B</Description> + <Version>1.0.0-1</Version> + <ReleaseDate>2015-01-01</ReleaseDate> + <Default>true</Default> +</Package> diff --git a/examples/repositorycategories/packages2_forcategory1/A2Cagetory1/data/A2_category1.txt b/examples/repositorycategories/packages2_forcategory1/A2Cagetory1/data/A2_category1.txt new file mode 100644 index 000000000..2b328a750 --- /dev/null +++ b/examples/repositorycategories/packages2_forcategory1/A2Cagetory1/data/A2_category1.txt @@ -0,0 +1,2 @@ +Example content for package A2, using category 1. + diff --git a/examples/repositorycategories/packages2_forcategory1/A2Cagetory1/meta/package.xml b/examples/repositorycategories/packages2_forcategory1/A2Cagetory1/meta/package.xml new file mode 100644 index 000000000..02d175868 --- /dev/null +++ b/examples/repositorycategories/packages2_forcategory1/A2Cagetory1/meta/package.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Package> + <DisplayName>A2 (from category 1)</DisplayName> + <Description>Example component A2</Description> + <Version>1.0.3-1</Version> + <ReleaseDate>2015-01-01</ReleaseDate> + <Default>true</Default> +</Package> diff --git a/examples/repositorycategories/packages2_forcategory1/B2Category1/data/B2_category1.txt b/examples/repositorycategories/packages2_forcategory1/B2Category1/data/B2_category1.txt new file mode 100644 index 000000000..56baa1709 --- /dev/null +++ b/examples/repositorycategories/packages2_forcategory1/B2Category1/data/B2_category1.txt @@ -0,0 +1,2 @@ +Example content for package B2, using category 1. + diff --git a/examples/repositorycategories/packages2_forcategory1/B2Category1/meta/package.xml b/examples/repositorycategories/packages2_forcategory1/B2Category1/meta/package.xml new file mode 100644 index 000000000..0c0b0411d --- /dev/null +++ b/examples/repositorycategories/packages2_forcategory1/B2Category1/meta/package.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Package> + <DisplayName>B2 (from category 1)</DisplayName> + <Description>Example component B</Description> + <Version>1.0.0-1</Version> + <ReleaseDate>2015-01-01</ReleaseDate> + <Default>true</Default> +</Package> diff --git a/examples/repositorycategories/packages_forcategory1/ACagetory1/data/A_category1.txt b/examples/repositorycategories/packages_forcategory1/ACagetory1/data/A_category1.txt new file mode 100644 index 000000000..e899cb202 --- /dev/null +++ b/examples/repositorycategories/packages_forcategory1/ACagetory1/data/A_category1.txt @@ -0,0 +1,2 @@ +Example content for package A, using category 1. + diff --git a/examples/repositorycategories/packages_forcategory1/ACagetory1/meta/package.xml b/examples/repositorycategories/packages_forcategory1/ACagetory1/meta/package.xml new file mode 100644 index 000000000..1f0e795e9 --- /dev/null +++ b/examples/repositorycategories/packages_forcategory1/ACagetory1/meta/package.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Package> + <DisplayName>A (from category 1)</DisplayName> + <Description>Example component A</Description> + <Version>1.0.3-1</Version> + <ReleaseDate>2015-01-01</ReleaseDate> + <Default>true</Default> +</Package> diff --git a/examples/repositorycategories/packages_forcategory1/BCategory1/data/B_category1.txt b/examples/repositorycategories/packages_forcategory1/BCategory1/data/B_category1.txt new file mode 100644 index 000000000..d355cb633 --- /dev/null +++ b/examples/repositorycategories/packages_forcategory1/BCategory1/data/B_category1.txt @@ -0,0 +1,2 @@ +Example content for package B, using category 1. + diff --git a/examples/repositorycategories/packages_forcategory1/BCategory1/meta/package.xml b/examples/repositorycategories/packages_forcategory1/BCategory1/meta/package.xml new file mode 100644 index 000000000..98b9776fd --- /dev/null +++ b/examples/repositorycategories/packages_forcategory1/BCategory1/meta/package.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Package> + <DisplayName>B (from category 1)</DisplayName> + <Description>Example component B</Description> + <Version>1.0.0-1</Version> + <ReleaseDate>2015-01-01</ReleaseDate> + <Default>true</Default> +</Package> diff --git a/examples/repositorycategories/packages_forcategory2/ACategory2/data/A_category2.txt b/examples/repositorycategories/packages_forcategory2/ACategory2/data/A_category2.txt new file mode 100644 index 000000000..0a10aa452 --- /dev/null +++ b/examples/repositorycategories/packages_forcategory2/ACategory2/data/A_category2.txt @@ -0,0 +1,2 @@ +Example content for package A, using category 2. + diff --git a/examples/repositorycategories/packages_forcategory2/ACategory2/meta/package.xml b/examples/repositorycategories/packages_forcategory2/ACategory2/meta/package.xml new file mode 100644 index 000000000..25a25f94e --- /dev/null +++ b/examples/repositorycategories/packages_forcategory2/ACategory2/meta/package.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Package> + <DisplayName>A (from category 2)</DisplayName> + <Description>Example component A</Description> + <Version>1.0.3-1</Version> + <ReleaseDate>2015-01-01</ReleaseDate> + <Default>true</Default> +</Package> diff --git a/examples/repositorycategories/packages_forcategory2/BCategory2/data/B_category2.txt b/examples/repositorycategories/packages_forcategory2/BCategory2/data/B_category2.txt new file mode 100644 index 000000000..ebf02b452 --- /dev/null +++ b/examples/repositorycategories/packages_forcategory2/BCategory2/data/B_category2.txt @@ -0,0 +1,2 @@ +Example content for package B, using category 2. + diff --git a/examples/repositorycategories/packages_forcategory2/BCategory2/meta/package.xml b/examples/repositorycategories/packages_forcategory2/BCategory2/meta/package.xml new file mode 100644 index 000000000..fa21c631b --- /dev/null +++ b/examples/repositorycategories/packages_forcategory2/BCategory2/meta/package.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Package> + <DisplayName>B (from category 2)</DisplayName> + <Description>Example component B</Description> + <Version>1.0.0-1</Version> + <ReleaseDate>2015-01-01</ReleaseDate> + <Default>true</Default> +</Package> diff --git a/examples/repositorycategories/repositorycategories.pro b/examples/repositorycategories/repositorycategories.pro new file mode 100644 index 000000000..37a0144b7 --- /dev/null +++ b/examples/repositorycategories/repositorycategories.pro @@ -0,0 +1,13 @@ +TEMPLATE = aux + +INSTALLER = installer + +INPUT = $$PWD/config/config.xml $$PWD/packages +example.input = INPUT +example.output = $$INSTALLER +example.commands = ../../bin/binarycreator --online-only -c $$PWD/config/config.xml -p $$PWD/packages ${QMAKE_FILE_OUT} +example.CONFIG += target_predeps no_link combine + +QMAKE_EXTRA_COMPILERS += example + +OTHER_FILES = README diff --git a/src/libs/installer/constants.h b/src/libs/installer/constants.h index 0e9646737..26c2a7dfe 100644 --- a/src/libs/installer/constants.h +++ b/src/libs/installer/constants.h @@ -91,6 +91,7 @@ static const QLatin1String scAllUsers("AllUsers"); static const QLatin1String scSupportsModify("SupportsModify"); static const QLatin1String scAllowUnstableComponents("AllowUnstableComponents"); static const QLatin1String scSaveDefaultRepositories("SaveDefaultRepositories"); +static const QLatin1String scRepositoryCategoryDisplayName("RepositoryCategoryDisplayName"); const char scRelocatable[] = "@RELOCATABLE_PATH@"; diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro index f649a1ecb..bdca7e0f3 100644 --- a/src/libs/installer/installer.pro +++ b/src/libs/installer/installer.pro @@ -131,7 +131,8 @@ HEADERS += packagemanagercore.h \ lib7z_guid.h \ lib7z_create.h \ lib7z_extract.h \ - lib7z_list.h + lib7z_list.h \ + repositorycategory.h SOURCES += packagemanagercore.cpp \ packagemanagercore_p.cpp \ @@ -206,7 +207,8 @@ SOURCES += packagemanagercore.cpp \ serverauthenticationdialog.cpp \ keepaliveobject.cpp \ systeminfo.cpp \ - packagesource.cpp + packagesource.cpp \ + repositorycategory.cpp FORMS += proxycredentialsdialog.ui \ serverauthenticationdialog.ui diff --git a/src/libs/installer/metadatajob.cpp b/src/libs/installer/metadatajob.cpp index ae29a2dcb..cb1579756 100644 --- a/src/libs/installer/metadatajob.cpp +++ b/src/libs/installer/metadatajob.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -70,11 +70,33 @@ MetadataJob::~MetadataJob() reset(); } -Repository MetadataJob::repositoryForDirectory(const QString &directory) const +/* + * Parse the metadata of currently selected repositories. We cannot + * return all metadata as that contains metadata also from categorized archived + * repositories which might not be currently selected. + */ + +QList<Metadata> MetadataJob::metadata() const { - return m_metadata.value(directory).repository; + QList<Metadata> metadata = m_metaFromDefaultRepositories.values(); + foreach (RepositoryCategory repositoryCategory, m_core->settings().repositoryCategories()) { + if (m_core->isUpdater() || (repositoryCategory.isEnabled() && m_fetchedArchive.contains(repositoryCategory.displayname()))) { + QList<ArchiveMetadata> archiveMetaList = m_fetchedArchive.values(repositoryCategory.displayname()); + foreach (ArchiveMetadata archiveMeta, archiveMetaList) { + metadata.append(archiveMeta.metaData); + } + } + } + return metadata; } +Repository MetadataJob::repositoryForDirectory(const QString &directory) const +{ + if (m_metaFromDefaultRepositories.contains(directory)) + return m_metaFromDefaultRepositories.value(directory).repository; + else + return m_metaFromArchive.value(directory).repository; +} // -- private slots @@ -86,13 +108,12 @@ void MetadataJob::doStart() } const ProductKeyCheck *const productKeyCheck = ProductKeyCheck::instance(); if (!m_addCompressedPackages) { - reset(); emit infoMessage(this, tr("Preparing meta information download...")); const bool onlineInstaller = m_core->isInstaller() && !m_core->isOfflineOnly(); - if (onlineInstaller || m_core->isMaintainer()) { QList<FileTaskItem> items; - foreach (const Repository &repo, m_core->settings().repositories()) { + QSet<Repository> repositories = getRepositories(); + foreach (const Repository &repo, repositories) { if (repo.isEnabled() && productKeyCheck->isValidRepository(repo)) { QAuthenticator authenticator; @@ -451,7 +472,9 @@ bool MetadataJob::fetchMetaDataPackages() void MetadataJob::reset() { m_packages.clear(); - m_metadata.clear(); + m_metaFromDefaultRepositories.clear(); + m_metaFromArchive.clear(); + m_fetchedArchive.clear(); setError(Job::NoError); setErrorString(QString()); @@ -587,7 +610,17 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re } } } - m_metadata.insert(metadata.directory, metadata); + if (metadata.repository.archivename().isEmpty()) { + m_metaFromDefaultRepositories.insert(metadata.directory, metadata); + } else { + //Hash metadata to help checking if meta for repository is already fetched + ArchiveMetadata archiveMetadata; + archiveMetadata.metaData = metadata; + m_fetchedArchive.insertMulti(metadata.repository.archivename(), archiveMetadata); + // Hash for faster lookups + m_metaFromArchive.insert(metadata.directory, metadata); + } + // search for additional repositories that we might need to check const QDomNode repositoryUpdate = root.firstChildElement(QLatin1String("RepositoryUpdate")); @@ -670,8 +703,31 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re } double taskCount = m_packages.length()/static_cast<double>(m_downloadableChunkSize); m_totalTaskCount = qCeil(taskCount); + m_taskNumber = 0; return XmlDownloadSuccess; } +QSet<Repository> MetadataJob::getRepositories() +{ + QSet<Repository> repositories; + + //In the first run, m_metadata is empty. Get always the default repositories + if (m_metaFromDefaultRepositories.isEmpty()) { + repositories = m_core->settings().repositories(); + } + + // Fetch repositories under archive which are selected in UI. + // If repository is already fetched, do not fetch it again. + // In updater mode, fetch always all archive repositories to get updates + foreach (RepositoryCategory repositoryCategory, m_core->settings().repositoryCategories()) { + if (m_core->isUpdater() || (repositoryCategory.isEnabled() && !m_fetchedArchive.contains(repositoryCategory.displayname()))) { + foreach (Repository repository, repositoryCategory.repositories()) { + repositories.insert(repository); + } + } + } + return repositories; +} + } // namespace QInstaller diff --git a/src/libs/installer/metadatajob.h b/src/libs/installer/metadatajob.h index e3f5aceea..973275de3 100644 --- a/src/libs/installer/metadatajob.h +++ b/src/libs/installer/metadatajob.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -46,6 +46,12 @@ struct Metadata Repository repository; }; +struct ArchiveMetadata +{ + QString archive; + Metadata metaData; +}; + class INSTALLER_EXPORT MetadataJob : public Job { Q_OBJECT @@ -61,7 +67,7 @@ public: explicit MetadataJob(QObject *parent = 0); ~MetadataJob(); - QList<Metadata> metadata() const { return m_metadata.values(); } + QList<Metadata> metadata() const; Repository repositoryForDirectory(const QString &directory) const; void setPackageManagerCore(PackageManagerCore *core) { m_core = core; } void addCompressedPackages(bool addCompressPackage) { m_addCompressedPackages = addCompressPackage;} @@ -85,13 +91,13 @@ private: void reset(); void resetCompressedFetch(); Status parseUpdatesXml(const QList<FileTaskResult> &results); + QSet<Repository> getRepositories(); private: PackageManagerCore *m_core; QList<FileTaskItem> m_packages; TempDirDeleter m_tempDirDeleter; - QHash<QString, Metadata> m_metadata; QFutureWatcher<FileTaskResult> m_xmlTask; QFutureWatcher<FileTaskResult> m_metadataTask; QHash<QFutureWatcher<void> *, QObject*> m_unzipTasks; @@ -103,6 +109,9 @@ private: int m_taskNumber; int m_totalTaskCount; QStringList m_shaMissmatchPackages; + QHash<QString, ArchiveMetadata> m_fetchedArchive; + QHash<QString, Metadata> m_metaFromDefaultRepositories; + QHash<QString, Metadata> m_metaFromArchive; //for faster lookups. }; } // namespace QInstaller diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp index 747960cee..44025de55 100644 --- a/src/libs/installer/packagemanagercore.cpp +++ b/src/libs/installer/packagemanagercore.cpp @@ -1176,7 +1176,6 @@ bool PackageManagerCore::fetchCompressedPackagesTree() return fetchPackagesTree(packages, installedPackages); } - /*! Checks for packages to install. Returns \c true if newer versions exist and they can be installed. diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp index 195c16f30..1e271a0d5 100644 --- a/src/libs/installer/packagemanagercore_p.cpp +++ b/src/libs/installer/packagemanagercore_p.cpp @@ -2210,14 +2210,12 @@ LocalPackagesHash PackageManagerCorePrivate::localInstalledPackages() bool PackageManagerCorePrivate::fetchMetaInformationFromRepositories() { - if (m_repoFetched) - return m_repoFetched; - m_updates = false; m_repoFetched = false; m_updateSourcesAdded = false; try { + m_metadataJob.addCompressedPackages(false); m_metadataJob.start(); m_metadataJob.waitForFinished(); } catch (Error &error) { diff --git a/src/libs/installer/packagemanagergui.cpp b/src/libs/installer/packagemanagergui.cpp index 287bf7d38..90ff11ea3 100644 --- a/src/libs/installer/packagemanagergui.cpp +++ b/src/libs/installer/packagemanagergui.cpp @@ -39,6 +39,7 @@ #include "utils.h" #include "scriptengine.h" #include "productkeycheck.h" +#include "repositorycategory.h" #include "sysinfo.h" @@ -71,6 +72,7 @@ #include <QVBoxLayout> #include <QShowEvent> #include <QFileDialog> +#include <QGroupBox> #ifdef Q_OS_WIN # include <qt_windows.h> @@ -1863,6 +1865,8 @@ public: , m_updaterModel(m_core->updaterComponentModel()) , m_currentModel(m_allModel) , m_compressedButtonVisible(false) + , m_allowCompressedRepositoryInstall(false) + , m_archiveButtonVisible(false) { m_treeView->setObjectName(QLatin1String("ComponentsTreeView")); @@ -1871,32 +1875,42 @@ public: connect(m_updaterModel, SIGNAL(checkStateChanged(QInstaller::ComponentModel::ModelState)), this, SLOT(onModelStateChanged(QInstaller::ComponentModel::ModelState))); - QHBoxLayout *hlayout = new QHBoxLayout; - hlayout->addWidget(m_treeView, 3); + m_descriptionVLayout = new QVBoxLayout; + m_descriptionVLayout->setObjectName(QLatin1String("DescriptionLayout")); m_descriptionLabel = new QLabel(q); m_descriptionLabel->setWordWrap(true); m_descriptionLabel->setObjectName(QLatin1String("ComponentDescriptionLabel")); - - m_vlayout = new QVBoxLayout; - m_vlayout->setObjectName(QLatin1String("VerticalLayout")); - m_vlayout->addWidget(m_descriptionLabel); + m_descriptionVLayout->addWidget(m_descriptionLabel); m_sizeLabel = new QLabel(q); m_sizeLabel->setWordWrap(true); - m_vlayout->addWidget(m_sizeLabel); m_sizeLabel->setObjectName(QLatin1String("ComponentSizeLabel")); - -#ifdef INSTALLCOMPRESSED - allowCompressedRepositoryInstall(); -#endif - m_vlayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::MinimumExpanding, + m_descriptionVLayout->addWidget(m_sizeLabel); + m_descriptionVLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); - hlayout->addLayout(m_vlayout, 2); - QVBoxLayout *layout = new QVBoxLayout(q); - layout->addLayout(hlayout, 1); + m_mainHLayout = new QHBoxLayout; + + m_treeViewVLayout = new QVBoxLayout; + m_treeViewVLayout->setObjectName(QLatin1String("TreeviewLayout")); + + m_bspLabel = new QLabel(); + m_bspLabel->hide(); + m_treeViewVLayout->addWidget(m_bspLabel); + + m_progressBar = new QProgressBar(); + m_progressBar->setRange(0, 0); + m_progressBar->hide(); + m_progressBar->setObjectName(QLatin1String("CompressedInstallProgressBar")); + m_treeViewVLayout->addWidget(m_progressBar); + connect(m_core, SIGNAL(metaJobProgress(int)), this, SLOT(onProgressChanged(int))); + connect(m_core, SIGNAL(metaJobInfoMessage(QString)), this, SLOT(setMessage(QString))); + connect(m_core, &PackageManagerCore::metaJobTotalProgress, this, + &ComponentSelectionPage::Private::setTotalProgress); + + m_buttonHLayout = new QHBoxLayout; m_checkDefault = new QPushButton; connect(m_checkDefault, &QAbstractButton::clicked, this, &ComponentSelectionPage::Private::selectDefault); @@ -1912,62 +1926,93 @@ public: "reset to already installed components"))); m_checkDefault->setText(ComponentSelectionPage::tr("&Reset")); } - hlayout = new QHBoxLayout; - hlayout->addWidget(m_checkDefault); + m_buttonHLayout->addWidget(m_checkDefault); m_checkAll = new QPushButton; - hlayout->addWidget(m_checkAll); connect(m_checkAll, &QAbstractButton::clicked, this, &ComponentSelectionPage::Private::selectAll); m_checkAll->setObjectName(QLatin1String("SelectAllComponentsButton")); m_checkAll->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+S", "select all components"))); m_checkAll->setText(ComponentSelectionPage::tr("&Select All")); + m_buttonHLayout->addWidget(m_checkAll); m_uncheckAll = new QPushButton; - hlayout->addWidget(m_uncheckAll); connect(m_uncheckAll, &QAbstractButton::clicked, this, &ComponentSelectionPage::Private::deselectAll); m_uncheckAll->setObjectName(QLatin1String("DeselectAllComponentsButton")); m_uncheckAll->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+D", "deselect all components"))); m_uncheckAll->setText(ComponentSelectionPage::tr("&Deselect All")); + m_buttonHLayout->addWidget(m_uncheckAll); - hlayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::MinimumExpanding, - QSizePolicy::MinimumExpanding)); - layout->addLayout(hlayout); + m_treeViewVLayout->addLayout(m_buttonHLayout); + m_treeViewVLayout->addWidget(m_treeView, 3); + + m_mainHLayout->addLayout(m_treeViewVLayout, 3); + m_mainHLayout->addLayout(m_descriptionVLayout, 2); + QVBoxLayout *layout = new QVBoxLayout(q); + layout->addLayout(m_mainHLayout, 1); + +#ifdef INSTALLCOMPRESSED + allowCompressedRepositoryInstall(); +#endif } void allowCompressedRepositoryInstall() { - if (m_compressedButtonVisible) { + m_allowCompressedRepositoryInstall = true; + } + + void showCompressedRepositoryButton() + { + if (m_compressedButtonVisible || !m_allowCompressedRepositoryInstall) { return; } - connect(m_core, SIGNAL(metaJobProgress(int)), this, SLOT(onProgressChanged(int))); connect(m_core, SIGNAL(metaJobInfoMessage(QString)), this, SLOT(setMessage(QString))); - m_bspLabel = new QLabel(ComponentSelectionPage::tr("To install new "\ - "compressed repository, browse the repositories from your computer"),q); - m_bspLabel->setWordWrap(true); - m_bspLabel->setObjectName(QLatin1String("CompressedButtonLabel")); - - m_vlayout->addSpacing(50); - m_vlayout->addWidget(m_bspLabel); + QWizard *wizard = qobject_cast<QWizard*>(m_core->guiObject()); + if (wizard) { + wizard->setOption(QWizard::HaveCustomButton2, true); + wizard->setButtonText(QWizard::CustomButton2, + ComponentSelectionPage::tr("&Browse QBSP files")); + connect(wizard, &QWizard::customButtonClicked, + this, &ComponentSelectionPage::Private::selectCompressedPackage); + q->gui()->updateButtonLayout(); + } + m_compressedButtonVisible = true; + } - m_progressBar = new QProgressBar(); - m_progressBar->setRange(0, 0); - m_progressBar->hide(); - m_vlayout->addWidget(m_progressBar); - m_progressBar->setObjectName(QLatin1String("CompressedInstallProgressBar")); + void setupArchiveButton() + { + if (m_archiveButtonVisible) + return; + QVBoxLayout *vLayout = new QVBoxLayout; + m_archiveVLayout = new QVBoxLayout; + m_archiveGroupBox = new QGroupBox(q); + m_archiveGroupBox->setTitle(m_core->settings().repositoryCategoryDisplayName()); + QVBoxLayout *groupboxLayout = new QVBoxLayout(m_archiveGroupBox); + + m_fetchArchiveButton = new QPushButton(tr("Fetch")); + connect(m_fetchArchiveButton, &QPushButton::clicked, this, + &ComponentSelectionPage::Private::fetchRepositoryCategories); + foreach (RepositoryCategory repository, m_core->settings().repositoryCategories()) { + QCheckBox *checkBox = new QCheckBox; + connect(checkBox, &QCheckBox::stateChanged, this, + &ComponentSelectionPage::Private::checkboxStateChanged); + checkBox->setText(repository.displayname()); + groupboxLayout->addWidget(checkBox); + } + m_archiveVLayout->insertWidget(0, m_archiveGroupBox); - m_installCompressButton = new QPushButton; - connect(m_installCompressButton, &QAbstractButton::clicked, - this, &ComponentSelectionPage::Private::selectCompressedPackage); - m_installCompressButton->setObjectName(QLatin1String("InstallCompressedPackageButton")); - m_installCompressButton->setText(ComponentSelectionPage::tr("&Browse QBSP files")); - m_vlayout->addWidget(m_installCompressButton); - m_compressedButtonVisible = true; + m_metadataProgressLabel = new QLabel(q); + m_archiveVLayout->addWidget(m_metadataProgressLabel); + vLayout->addWidget(m_archiveGroupBox); + vLayout->addWidget(m_fetchArchiveButton); + vLayout->addStretch(); + m_mainHLayout->insertLayout(0, vLayout); + m_archiveButtonVisible = true; } void updateTreeView() @@ -2061,6 +2106,77 @@ public slots: m_currentModel->setCheckedState(ComponentModel::AllUnchecked); } + void checkboxStateChanged() + { + QList<QCheckBox*> checkboxes = m_archiveGroupBox->findChildren<QCheckBox *>(); + bool enableFetchButton = false; + foreach (QCheckBox *checkbox, checkboxes) { + if (checkbox->isChecked()) { + enableFetchButton = true; + break; + } + } + } + + void enableArchiveRepos(int index, bool enable) { + RepositoryCategory archiveRepo = m_core->settings().repositoryCategories().toList().at(index); + RepositoryCategory replacement = archiveRepo; + replacement.setEnabled(enable); + QSet<RepositoryCategory> tmpArchiveRepos = m_core->settings().repositoryCategories(); + if (tmpArchiveRepos.contains(archiveRepo)) { + tmpArchiveRepos.remove(archiveRepo); + tmpArchiveRepos.insert(replacement); + m_core->settings().addRepositoryCategories(tmpArchiveRepos); + } + } + + void updateWidgetVisibility(bool show) + { + if (show) { + QSpacerItem *verticalSpacer2 = new QSpacerItem(0, 0, QSizePolicy::Minimum, + QSizePolicy::Expanding); + m_treeViewVLayout->addSpacerItem(verticalSpacer2); + } else { + QSpacerItem *item = m_treeViewVLayout->spacerItem(); + m_treeViewVLayout->removeItem(item); + } + m_fetchArchiveButton->setDisabled(show); + m_progressBar->setVisible(show); + m_bspLabel->setVisible(show); + m_archiveGroupBox->setDisabled(show); + + m_treeView->setVisible(!show); + m_checkDefault->setVisible(!show); + m_checkAll->setVisible(!show); + m_uncheckAll->setVisible(!show); + m_descriptionLabel->setVisible(!show); + QPushButton *const b = qobject_cast<QPushButton *>(q->gui()->button(QWizard::NextButton)); + b->setEnabled(!show); + + if (QAbstractButton *bspButton = q->gui()->button(QWizard::CustomButton2)) + bspButton->setEnabled(!show); + } + + void fetchRepositoryCategories() + { + updateWidgetVisibility(true); + + QCheckBox *checkbox; + QList<QCheckBox*> checkboxes = m_archiveGroupBox->findChildren<QCheckBox *>(); + for (int i = 0; i < checkboxes.count(); i++) { + checkbox = checkboxes.at(i); + enableArchiveRepos(i, checkbox->isChecked()); + } + + if (!m_core->fetchRemotePackagesTree()) { + m_metadataProgressLabel->setText(m_core->error()); + } else { + updateTreeView(); + m_metadataProgressLabel->setText(QLatin1String()); + } + updateWidgetVisibility(false); + } + void selectCompressedPackage() { QString defaultDownloadDirectory = @@ -2076,10 +2192,7 @@ public slots: set.insert(repository); } if (set.count() > 0) { - m_progressBar->show(); - m_installCompressButton->hide(); - QPushButton *const b = qobject_cast<QPushButton *>(q->gui()->button(QWizard::NextButton)); - b->setEnabled(false); + updateWidgetVisibility(true); m_core->settings().addTemporaryRepositories(set, false); if (!m_core->fetchCompressedPackagesTree()) { setMessage(m_core->error()); @@ -2089,11 +2202,8 @@ public slots: setMessage(ComponentSelectionPage::tr("To install new "\ "compressed repository, browse the repositories from your computer")); } - - m_progressBar->hide(); - m_installCompressButton->show(); - b->setEnabled(true); } + updateWidgetVisibility(false); } /*! @@ -2114,6 +2224,12 @@ public slots: m_bspLabel->setText(msg); } + void setTotalProgress(int totalProgress) + { + if (m_progressBar) + m_progressBar->setRange(0, totalProgress); + } + void selectDefault() { m_currentModel->setCheckedState(ComponentModel::DefaultChecked); @@ -2152,10 +2268,19 @@ public: QPushButton *m_uncheckAll; QPushButton *m_checkDefault; QPushButton *m_installCompressButton; + QGroupBox *m_archiveGroupBox; + QPushButton *m_fetchArchiveButton; QLabel *m_bspLabel; + QLabel *m_metadataProgressLabel; QProgressBar *m_progressBar; - QVBoxLayout *m_vlayout; + QVBoxLayout *m_descriptionVLayout; + QHBoxLayout *m_mainHLayout; + QVBoxLayout *m_treeViewVLayout; + QVBoxLayout *m_archiveVLayout; + QHBoxLayout *m_buttonHLayout; bool m_compressedButtonVisible; + bool m_allowCompressedRepositoryInstall; + bool m_archiveButtonVisible; }; @@ -2211,6 +2336,21 @@ void ComponentSelectionPage::entering() d->updateTreeView(); setModified(isComplete()); + if (core->settings().repositoryCategories().count() > 0 && !core->isOfflineOnly() + && !core->isUpdater()) { + d->setupArchiveButton(); + } + d->showCompressedRepositoryButton(); +} + +void ComponentSelectionPage::leaving() +{ + QWizard *wizard = qobject_cast<QWizard*>(d->m_core->guiObject()); + if (wizard && (gui()->options() & QWizard::HaveCustomButton2)) { + wizard->setOption(QWizard::HaveCustomButton2, false); + gui()->updateButtonLayout(); + d->m_compressedButtonVisible = false; + } } /*! diff --git a/src/libs/installer/packagemanagergui.h b/src/libs/installer/packagemanagergui.h index 238e22a62..d67bc9bd9 100644 --- a/src/libs/installer/packagemanagergui.h +++ b/src/libs/installer/packagemanagergui.h @@ -314,6 +314,7 @@ public: protected: void entering(); + void leaving(); void showEvent(QShowEvent *event); private Q_SLOTS: diff --git a/src/libs/installer/repository.cpp b/src/libs/installer/repository.cpp index eb45573a9..249012786 100644 --- a/src/libs/installer/repository.cpp +++ b/src/libs/installer/repository.cpp @@ -57,6 +57,7 @@ Repository::Repository(const Repository &other) , m_password(other.m_password) , m_displayname(other.m_displayname) , m_compressed(other.m_compressed) + , m_archivename(other.m_archivename) { registerMetaType(); } @@ -183,7 +184,7 @@ void Repository::setPassword(const QString &password) } /*! - Returns the Name for the repository to be displayed instead of the URL + Returns the Name for the repository to be displayed instead of the URL. */ QString Repository::displayname() const { @@ -199,6 +200,22 @@ void Repository::setDisplayName(const QString &displayname) } /*! + Returns the archive name if the repository belongs to an archive. +*/ +QString Repository::archivename() const +{ + return m_archivename; +} + +/*! + Sets the archive name to \a archivename if the repository belongs to an archive. +*/ +void Repository::setArchiveName(const QString &archivename) +{ + m_archivename = archivename; +} + +/*! Returns true if repository is compressed */ bool Repository::isCompressed() const @@ -248,6 +265,7 @@ const Repository &Repository::operator=(const Repository &other) m_password = other.m_password; m_displayname = other.m_displayname; m_compressed = other.m_compressed; + m_archivename = other.m_archivename; return *this; } @@ -273,7 +291,7 @@ QDataStream &operator<<(QDataStream &ostream, const Repository &repository) { return ostream << repository.m_url.toEncoded().toBase64() << repository.m_default << repository.m_enabled << repository.m_username.toUtf8().toBase64() << repository.m_password.toUtf8().toBase64() - << repository.m_displayname.toUtf8().toBase64(); + << repository.m_displayname.toUtf8().toBase64() << repository.m_archivename.toUtf8().toBase64(); } } diff --git a/src/libs/installer/repository.h b/src/libs/installer/repository.h index b73e7bd4c..83393ead9 100644 --- a/src/libs/installer/repository.h +++ b/src/libs/installer/repository.h @@ -64,6 +64,9 @@ public: QString displayname() const; void setDisplayName(const QString &displayname); + QString archivename() const; + void setArchiveName(const QString &archivename); + bool isCompressed() const; void setCompressed(bool compressed); bool operator==(const Repository &other) const; @@ -82,6 +85,7 @@ private: QString m_username; QString m_password; QString m_displayname; + QString m_archivename; bool m_compressed; }; diff --git a/src/libs/installer/repositorycategory.cpp b/src/libs/installer/repositorycategory.cpp new file mode 100644 index 000000000..af7f6e818 --- /dev/null +++ b/src/libs/installer/repositorycategory.cpp @@ -0,0 +1,159 @@ +/************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "repositorycategory.h" +#include "filedownloaderfactory.h" + +#include <QDataStream> +#include <QFileInfo> +#include <QStringList> + +namespace QInstaller { + + +template <typename T> +static QSet<T> variantListToSet(const QVariantList &list) +{ + QSet<T> set; + foreach (const QVariant &variant, list) + set.insert(variant.value<T>()); + return set; +} + +/*! + Constructs an uninitialized RepositoryCategory object. +*/ +RepositoryCategory::RepositoryCategory() + : m_enabled(false) +{ + registerMetaType(); +} + +/*! + Constructs a new category by using all fields of the given category \a other. +*/ +RepositoryCategory::RepositoryCategory(const RepositoryCategory &other) + : m_displayname(other.m_displayname), m_data(other.m_data), m_enabled(other.m_enabled) +{ + registerMetaType(); +} + + +void RepositoryCategory::registerMetaType() +{ + qRegisterMetaType<RepositoryCategory>("RepositoryCategory"); + qRegisterMetaTypeStreamOperators<RepositoryCategory>("RepositoryCategory"); +} + +/*! + Returns the Name for the category to be displayed. +*/ +QString RepositoryCategory::displayname() const +{ + return m_displayname; +} + +/*! + Sets the DisplayName of the category to \a displayname. +*/ +void RepositoryCategory::setDisplayName(const QString &displayname) +{ + m_displayname = displayname; +} + +/*! + Returns the list of repositories the category has. +*/ +QSet<Repository> RepositoryCategory::repositories() const +{ + return variantListToSet<Repository>(m_data.values(QLatin1String("Repositories"))); +} + +/*! + Inserts a set of \a repositories to the category. +*/ +void RepositoryCategory::setRepositories(const QSet<Repository> repositories) +{ + foreach (const Repository &repository, repositories) + m_data.insertMulti(QLatin1String("Repositories"), QVariant().fromValue(repository)); +} + +/*! + Inserts \a repository to the category. +*/ +void RepositoryCategory::addRepository(const Repository repository) +{ + m_data.insertMulti(QLatin1String("Repositories"), QVariant().fromValue(repository)); +} + +/*! + Returns whether this category is enabled and used during information retrieval. +*/ +bool RepositoryCategory::isEnabled() const +{ + return m_enabled; +} + +/*! + Sets this category to \a enabled state and and thus determines whether to use this + repository for information retrieval. + +*/ +void RepositoryCategory::setEnabled(bool enabled) +{ + m_enabled = enabled; +} + +/*! + Compares the values of this category to \a other and returns true if they are equal. +*/ +bool RepositoryCategory::operator==(const RepositoryCategory &other) const +{ + return m_displayname == other.m_displayname; +} + +/*! + Returns true if the \a other category is not equal to this repository; otherwise returns false. Two + categories are considered equal if they contain the same displayname. \sa operator==() +*/ +bool RepositoryCategory::operator!=(const RepositoryCategory &other) const +{ + return !(*this == other); +} + +QDataStream &operator>>(QDataStream &istream, RepositoryCategory &repository) +{ + return istream; +} + +QDataStream &operator<<(QDataStream &ostream, const RepositoryCategory &repository) +{ + return ostream << repository.m_displayname.toUtf8().toBase64() << repository.m_data; +} + +} diff --git a/src/libs/installer/repositorycategory.h b/src/libs/installer/repositorycategory.h new file mode 100644 index 000000000..315af761b --- /dev/null +++ b/src/libs/installer/repositorycategory.h @@ -0,0 +1,86 @@ +/************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef ARCHIVEREPOSITORY_H +#define ARCHIVEREPOSITORY_H + +#include "installer_global.h" +#include "repository.h" + +#include <QtCore/QMetaType> +#include <QtCore/QUrl> +#include <QSet> + +namespace QInstaller { + +class INSTALLER_EXPORT RepositoryCategory +{ + +public: + explicit RepositoryCategory(); + RepositoryCategory(const RepositoryCategory &other); + + static void registerMetaType(); + + QString displayname() const; + void setDisplayName(const QString &displayname); + + QSet<Repository> repositories() const; + void setRepositories(const QSet<Repository> repositories); + void addRepository(const Repository repository); + + bool isEnabled() const; + void setEnabled(bool enabled); + + bool operator==(const RepositoryCategory &other) const; + bool operator!=(const RepositoryCategory &other) const; + + uint qHash(const RepositoryCategory &repository); + + friend INSTALLER_EXPORT QDataStream &operator>>(QDataStream &istream, RepositoryCategory &repository); + friend INSTALLER_EXPORT QDataStream &operator<<(QDataStream &ostream, const RepositoryCategory &repository); + +private: + QVariantHash m_data; + QString m_displayname; + bool m_enabled; +}; + +inline uint qHash(const RepositoryCategory &repository) +{ + return qHash(repository.displayname()); +} + +INSTALLER_EXPORT QDataStream &operator>>(QDataStream &istream, RepositoryCategory &repository); +INSTALLER_EXPORT QDataStream &operator<<(QDataStream &ostream, const RepositoryCategory &repository); + +} // namespace QInstaller + +Q_DECLARE_METATYPE(QInstaller::RepositoryCategory) + +#endif // ARCHIVEREPOSITORY_H diff --git a/src/libs/installer/settings.cpp b/src/libs/installer/settings.cpp index 5761387c6..21bbe8b4c 100644 --- a/src/libs/installer/settings.cpp +++ b/src/libs/installer/settings.cpp @@ -30,6 +30,7 @@ #include "errors.h" #include "qinstallerglobal.h" #include "repository.h" +#include "repositorycategory.h" #include <QtCore/QFileInfo> #include <QtCore/QStringList> @@ -55,6 +56,7 @@ static const QLatin1String scUserRepositories("UserRepositories"); static const QLatin1String scTmpRepositories("TemporaryRepositories"); static const QLatin1String scMaintenanceToolIniFile("MaintenanceToolIniFile"); static const QLatin1String scRemoteRepositories("RemoteRepositories"); +static const QLatin1String scRepositoryCategories("RepositoryCategories"); static const QLatin1String scDependsOnLocalInstallerBinary("DependsOnLocalInstallerBinary"); static const QLatin1String scTranslations("Translations"); static const QLatin1String scCreateLocalRepository("CreateLocalRepository"); @@ -133,11 +135,15 @@ static QStringList readArgumentAttributes(QXmlStreamReader &reader, Settings::Pa return arguments; } -static QSet<Repository> readRepositories(QXmlStreamReader &reader, bool isDefault, Settings::ParseMode parseMode) +static QSet<Repository> readRepositories(QXmlStreamReader &reader, bool isDefault, Settings::ParseMode parseMode, QString *displayName = 0) { + qDebug()<<__FUNCTION__; QSet<Repository> set; while (reader.readNextStartElement()) { - if (reader.name() == QLatin1String("Repository")) { + if (reader.name() == QLatin1String("DisplayName")) { + //remote repository can have also displayname. Needed when creating archive repositories + *displayName = reader.readElementText(); + } else if (reader.name() == QLatin1String("Repository")) { Repository repo(QString(), isDefault); while (reader.readNextStartElement()) { if (reader.name() == QLatin1String("Url")) { @@ -160,6 +166,8 @@ static QSet<Repository> readRepositories(QXmlStreamReader &reader, bool isDefaul .arg(reader.name().toString()), parseMode); } } + if (displayName && !displayName->isEmpty()) + repo.setArchiveName(*displayName); set.insert(repo); } else { raiseError(reader, QString::fromLatin1("Unexpected element \"%1\".").arg(reader.name().toString()), @@ -174,6 +182,23 @@ static QSet<Repository> readRepositories(QXmlStreamReader &reader, bool isDefaul return set; } +static QSet<RepositoryCategory> readRepositoryCategories(QXmlStreamReader &reader, bool isDefault, Settings::ParseMode parseMode, + QString *repositoryCategoryName) +{ + QSet<RepositoryCategory> archiveSet; + while (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("RemoteRepositories")) { + RepositoryCategory archiveRepo; + QString displayName; + archiveRepo.setRepositories(readRepositories(reader, isDefault, parseMode, &displayName)); + archiveRepo.setDisplayName(displayName); + archiveSet.insert(archiveRepo); + } else if (reader.name() == QLatin1String("RepositoryCategoryDisplayname")) { + *repositoryCategoryName = reader.readElementText(); + } + } + return archiveSet; +} // -- Settings::Private @@ -257,7 +282,7 @@ Settings Settings::fromFileAndPrefix(const QString &path, const QString &prefix, << scRepositorySettingsPageVisible << scTargetConfigurationFile << scRemoteRepositories << scTranslations << scUrlQueryString << QLatin1String(scControlScript) << scCreateLocalRepository << scInstallActionColumnVisible << scSupportsModify << scAllowUnstableComponents - << scSaveDefaultRepositories; + << scSaveDefaultRepositories << scRepositoryCategories; Settings s; s.d->m_data.insert(scPrefix, prefix); @@ -280,11 +305,16 @@ Settings Settings::fromFileAndPrefix(const QString &path, const QString &prefix, s.setRunProgramArguments(readArgumentAttributes(reader, parseMode, QLatin1String("Argument"))); } else if (name == scRemoteRepositories) { s.addDefaultRepositories(readRepositories(reader, true, parseMode)); + } else if (name == scRepositoryCategories) { + QString repositoryCategoryName; + s.addRepositoryCategories(readRepositoryCategories(reader, true, parseMode, &repositoryCategoryName)); + if (!repositoryCategoryName.isEmpty()) { + s.setRepositoryCategoryDisplayName(repositoryCategoryName); + } } else { s.d->m_data.insert(name, reader.readElementText(QXmlStreamReader::SkipChildElements)); } } - if (reader.error() != QXmlStreamReader::NoError) { throw Error(QString::fromLatin1("Error in %1, line %2, column %3: %4").arg(path).arg(reader .lineNumber()).arg(reader.columnNumber()).arg(reader.errorString())); @@ -549,6 +579,11 @@ QSet<Repository> Settings::defaultRepositories() const return variantListToSet<Repository>(d->m_data.values(scRepositories)); } +QSet<RepositoryCategory> Settings::repositoryCategories() const +{ + return variantListToSet<RepositoryCategory>(d->m_data.values(scRepositoryCategories)); +} + void Settings::setDefaultRepositories(const QSet<Repository> &repositories) { d->m_data.remove(scRepositories); @@ -561,6 +596,12 @@ void Settings::addDefaultRepositories(const QSet<Repository> &repositories) d->m_data.insertMulti(scRepositories, QVariant().fromValue(repository)); } +void Settings::addRepositoryCategories(const QSet<RepositoryCategory> &repositories) +{ + foreach (const RepositoryCategory &repository, repositories) + d->m_data.insertMulti(scRepositoryCategories, QVariant().fromValue(repository)); +} + static bool apply(const RepoHash &updates, QHash<QUrl, Repository> *reposToUpdate) { bool update = false; @@ -767,3 +808,14 @@ void Settings::setSaveDefaultRepositories(bool save) { d->m_data.insert(scSaveDefaultRepositories, save); } + +QString Settings::repositoryCategoryDisplayName() const +{ + QString displayName = d->m_data.value(QLatin1String(scRepositoryCategoryDisplayName)).toString(); + return displayName.isEmpty() ? tr("Package categories") : displayName; +} + +void Settings::setRepositoryCategoryDisplayName(const QString& name) +{ + d->m_data.insert(scRepositoryCategoryDisplayName, name); +} diff --git a/src/libs/installer/settings.h b/src/libs/installer/settings.h index bc2db655b..0ee58639d 100644 --- a/src/libs/installer/settings.h +++ b/src/libs/installer/settings.h @@ -31,6 +31,7 @@ #include "constants.h" #include "installer_global.h" +#include "repositorycategory.h" #include <QtCore/QCoreApplication> #include <QtCore/QSharedDataPointer> @@ -114,8 +115,10 @@ public: QSet<Repository> repositories() const; QSet<Repository> defaultRepositories() const; + QSet<RepositoryCategory> repositoryCategories() const; void setDefaultRepositories(const QSet<Repository> &repositories); void addDefaultRepositories(const QSet<Repository> &repositories); + void addRepositoryCategories(const QSet<RepositoryCategory> &repositories); Settings::Update updateDefaultRepositories(const RepoHash &updates); QSet<Repository> temporaryRepositories() const; @@ -160,6 +163,9 @@ public: bool saveDefaultRepositories() const; void setSaveDefaultRepositories(bool save); + QString repositoryCategoryDisplayName() const; + void setRepositoryCategoryDisplayName(const QString &displayName); + private: class Private; QSharedDataPointer<Private> d; |