410 lines
14 KiB
C++
410 lines
14 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2021 Andrew Lutsenko, anlutsenko at gmail dot com
|
|
* Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation, either version 3 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef PCM_H_
|
|
#define PCM_H_
|
|
|
|
#include "core/wx_stl_compat.h"
|
|
#include "pcm_data.h"
|
|
#include "widgets/wx_progress_reporters.h"
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <nlohmann/json-schema.hpp>
|
|
#include <thread>
|
|
#include <tuple>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
#include <wx/wx.h>
|
|
|
|
|
|
///< Contains list of all valid directories that get extracted from a package archive
|
|
const std::unordered_set<wxString> PCM_PACKAGE_DIRECTORIES( {
|
|
"plugins",
|
|
"footprints",
|
|
"3dmodels",
|
|
"symbols",
|
|
"resources",
|
|
"colors",
|
|
"templates",
|
|
"scripts"
|
|
} );
|
|
|
|
|
|
///< Package states
|
|
///< Package is available if it is not installed and there is a compatible version
|
|
///< Package is unavailable if it is not installed and there are no compatible versions
|
|
///< Pending states are intermediary when (un)installation is scheduled but not yet performed
|
|
enum PCM_PACKAGE_STATE
|
|
{
|
|
PPS_AVAILABLE = 0,
|
|
PPS_UNAVAILABLE = 1,
|
|
PPS_INSTALLED = 2,
|
|
PPS_PENDING_INSTALL = 3,
|
|
PPS_PENDING_UNINSTALL = 4,
|
|
PPS_UPDATE_AVAILABLE = 5,
|
|
PPS_PENDING_UPDATE = 6,
|
|
};
|
|
|
|
|
|
///< Package actions
|
|
enum PCM_PACKAGE_ACTION
|
|
{
|
|
PPA_INSTALL = 0,
|
|
PPA_UNINSTALL = 1,
|
|
PPA_UPDATE = 2,
|
|
};
|
|
|
|
|
|
typedef std::vector<std::pair<wxString, wxString>> STRING_PAIR_LIST;
|
|
typedef std::vector<std::tuple<wxString, wxString, wxString>> STRING_TUPLE_LIST;
|
|
|
|
|
|
struct BACKGROUND_JOB;
|
|
|
|
|
|
/**
|
|
* @brief Main class of Plugin and Content Manager subsystem
|
|
*
|
|
* This class handles logistics of repository management, caching, json validation,
|
|
* tracking installed packages and provides some utility methods.
|
|
*
|
|
* Repository caching is done in $KICADX_3RD_PARTY/cache directory with each
|
|
* repository storing it's metadata, packages and optionally resource files under
|
|
* it's id subdirectory.
|
|
*
|
|
* Repository id is a prefix of sha256 of it's main url.
|
|
*
|
|
* JSON schema file is expected to be in $KICAD_DATA/schemas directory
|
|
*
|
|
* Installed packages are stored in <user_settings>/installed_packages.json
|
|
* If that file is missing PCM will try to reconstruct it from existing
|
|
* directory structure inside $KICADX_3RD_PARTY but package descriptions
|
|
* and other metadata will be lost.
|
|
*/
|
|
class PLUGIN_CONTENT_MANAGER
|
|
{
|
|
public:
|
|
PLUGIN_CONTENT_MANAGER( std::function<void( int )> aAvailableUpdateCallbac );
|
|
~PLUGIN_CONTENT_MANAGER();
|
|
|
|
/**
|
|
* @brief Saves metadata of installed packages to disk
|
|
*
|
|
* Path is <user settings>/installed_packages.json
|
|
*/
|
|
void SaveInstalledPackages();
|
|
|
|
/**
|
|
* @brief Fetches repository metadata from given url
|
|
*
|
|
* @param aUrl URL of the repository
|
|
* @param aRepository fetched repository metadata
|
|
* @param aReporter progress reporter dialog to use for download
|
|
* @return true if successful
|
|
* @return false if URL could not be downloaded or result could not be parsed
|
|
*/
|
|
bool FetchRepository( const wxString& aUrl, PCM_REPOSITORY& aRepository,
|
|
PROGRESS_REPORTER* aReporter );
|
|
|
|
/**
|
|
* @brief Validates json against a specific definition in the PCM schema
|
|
*
|
|
* @param aJson JSON object to validate
|
|
* @param aUri JSON URI of a definition to validate against, default is root
|
|
* @throws std::invalid_argument on validation failure
|
|
*/
|
|
void ValidateJson( const nlohmann::json& aJson,
|
|
const nlohmann::json_uri& aUri = nlohmann::json_uri( "#" ) ) const;
|
|
|
|
/**
|
|
* @brief Verifies SHA256 hash of a binary stream
|
|
*
|
|
* @param aStream input stream
|
|
* @param aHash sha256 lowercase hex string
|
|
* @return true if hash matches
|
|
*/
|
|
bool VerifyHash( std::istream& aStream, const wxString& aHash ) const;
|
|
|
|
/**
|
|
* @brief Set list of repositories
|
|
*
|
|
* Discards cache of repositories that were previously cached but are not
|
|
* on the new list of repositories.
|
|
*
|
|
* @param aRepositories list of <URL, name> pairs of repositories
|
|
*/
|
|
void SetRepositoryList( const STRING_PAIR_LIST& aRepositories );
|
|
|
|
/**
|
|
* @brief Discard in-memory and on-disk cache of a repository
|
|
*
|
|
* @param aRepositoryId id of the repository
|
|
*/
|
|
void DiscardRepositoryCache( const wxString& aRepositoryId );
|
|
|
|
/**
|
|
* @brief Mark package as installed
|
|
*
|
|
* @param aPackage package metadata
|
|
* @param aVersion installed package version
|
|
* @param aRepositoryId id of the source repository or empty estring if
|
|
* installed from a local file
|
|
*/
|
|
void MarkInstalled( const PCM_PACKAGE& aPackage, const wxString& aVersion,
|
|
const wxString& aRepositoryId );
|
|
|
|
/**
|
|
* @brief Mark package as uninstalled
|
|
*
|
|
* @param aPackage package metadata
|
|
*/
|
|
void MarkUninstalled( const PCM_PACKAGE& aPackage );
|
|
|
|
/**
|
|
* @brief Get list of repositories
|
|
*
|
|
* @return const STRING_TUPLE_LIST& list of repositories in <id, URL, name> tuple format
|
|
*/
|
|
const STRING_TUPLE_LIST& GetRepositoryList() const { return m_repository_list; }
|
|
|
|
/**
|
|
* @brief Cache specified repository packages and other metadata
|
|
*
|
|
* This method fetches latest repository metadata, checks if there is cache on disk,
|
|
* compares it's last update timestamp to repository metadata and redownloads packages
|
|
* list if necessary.
|
|
* Then it checks sha256 if provided and parses the package list.
|
|
* Parsed packages metadata is stored in memory.
|
|
*
|
|
* Process is repeated with resources file except it is just stored on disk and
|
|
* any errors at this stage are ignored.
|
|
*
|
|
* @param aRepositoryId id of the repository to cache
|
|
* @return true if packages metadata was cached successfully
|
|
*/
|
|
bool CacheRepository( const wxString& aRepositoryId );
|
|
|
|
/**
|
|
* @brief Get the packages metadata from a previously cached repository
|
|
*
|
|
* This should only be called after a successful CacheRepository call
|
|
*
|
|
* @param aRepositoryId id of the repository
|
|
* @return list of package metadata objects
|
|
*/
|
|
const std::vector<PCM_PACKAGE>& GetRepositoryPackages( const wxString& aRepositoryId ) const;
|
|
|
|
/**
|
|
* @brief Get list of installed packages
|
|
*
|
|
* @return vector of PCM_INSTALLATION_ENTRY objects
|
|
*/
|
|
const std::vector<PCM_INSTALLATION_ENTRY> GetInstalledPackages() const;
|
|
|
|
/**
|
|
* @brief Get the current version of an installed package
|
|
*
|
|
* @param aPackageId id of the package
|
|
* @return current version
|
|
* @throws std::out_of_range if package with given id is not installed
|
|
*/
|
|
const wxString& GetInstalledPackageVersion( const wxString& aPackageId ) const;
|
|
|
|
///< Returns current 3rd party directory path
|
|
const wxString& Get3rdPartyPath() const { return m_3rdparty_path; };
|
|
|
|
/**
|
|
* @brief Get current state of the package
|
|
*
|
|
* @param aRepositoryId repository id
|
|
* @param aPackageId package id
|
|
* @return PCM_PACKAGE_STATE
|
|
*/
|
|
PCM_PACKAGE_STATE GetPackageState( const wxString& aRepositoryId, const wxString& aPackageId );
|
|
|
|
/**
|
|
* @brief Returns pinned status of a package
|
|
*
|
|
* @param aPackageId package id
|
|
* @return true if package is installed and is pinned
|
|
* @return false if package is not installed or not pinned
|
|
*/
|
|
bool IsPackagePinned( const wxString& aPackageId ) const;
|
|
|
|
/**
|
|
* @brief Set the pinned status of a package
|
|
*
|
|
* no-op for not installed packages
|
|
*
|
|
* @param aPackageId package id
|
|
* @param aPinned pinned status
|
|
*/
|
|
void SetPinned( const wxString& aPackageId, const bool aPinned );
|
|
|
|
/**
|
|
* @brief Get the preferred package update version or empty string if there is none
|
|
*
|
|
* Works only for installed packages and returns highest compatible version greater
|
|
* than currently installed that is at the same or higher (numerically lower)
|
|
* version stability level.
|
|
*
|
|
* @param aPackage package
|
|
* @return package version string
|
|
*/
|
|
const wxString GetPackageUpdateVersion( const PCM_PACKAGE& aPackage );
|
|
|
|
/**
|
|
* @brief Downloads url to an output stream
|
|
*
|
|
* @param aUrl URL to download
|
|
* @param aOutput output stream
|
|
* @param aReporter progress dialog to use
|
|
* @param aSizeLimit maximum download size, 0 for unlimited
|
|
* @return true if download was successful
|
|
* @return false if download failed or was too large
|
|
*/
|
|
bool DownloadToStream( const wxString& aUrl, std::ostream* aOutput,
|
|
PROGRESS_REPORTER* aReporter,
|
|
const size_t aSizeLimit = DEFAULT_DOWNLOAD_MEM_LIMIT );
|
|
|
|
/**
|
|
* @brief Get the approximate measure of how much given package matches the search term
|
|
*
|
|
* @param aPackage package metadata object
|
|
* @param aSearchTerm search term
|
|
* @return int search rank, higher number means better match, 0 means no match
|
|
*/
|
|
int GetPackageSearchRank( const PCM_PACKAGE& aPackage, const wxString& aSearchTerm );
|
|
|
|
/**
|
|
* @brief Get the icon bitmaps for repository packages
|
|
*
|
|
* Repository package icons are taken from repository's resources zip file
|
|
*
|
|
* @param aRepositoryId id of the repository
|
|
* @return map of package id -> bitmap
|
|
*/
|
|
std::unordered_map<wxString, wxBitmap>
|
|
GetRepositoryPackageBitmaps( const wxString& aRepositoryId );
|
|
|
|
/**
|
|
* @brief Get the icon bitmaps for installed packages
|
|
*
|
|
* Icons for installed packages are taken from package extracted files in
|
|
* $KICADX_3RD_PARTY/resources/<packageid> directories
|
|
*
|
|
* @return map of package id -> bitmap
|
|
*/
|
|
std::unordered_map<wxString, wxBitmap> GetInstalledPackageBitmaps();
|
|
|
|
/**
|
|
* @brief Set the Dialog Window
|
|
*
|
|
* PCM can effectively run in "silent" mode with a background thread that
|
|
* reports to kicad manager window status bar. Setting valid window pointer here
|
|
* will switch it to GUI mode with WX_PROGRESS_DIALOG popup for downloads.
|
|
*
|
|
* @param aDialog parent dialog for progress window
|
|
*/
|
|
void SetDialogWindow( wxWindow* aDialog ) { m_dialog = aDialog; };
|
|
|
|
/**
|
|
* @brief Runs a background update thread that checks for new package versions
|
|
*/
|
|
void RunBackgroundUpdate();
|
|
|
|
/**
|
|
* @brief Interrupts and joins() the update thread
|
|
*/
|
|
void StopBackgroundUpdate();
|
|
|
|
/**
|
|
* @brief Stores 3rdparty path from environment variables
|
|
*/
|
|
void ReadEnvVar();
|
|
|
|
private:
|
|
///< Default download limit of 10 Mb to not use too much memory
|
|
static constexpr size_t DEFAULT_DOWNLOAD_MEM_LIMIT = 10 * 1024 * 1024;
|
|
|
|
/**
|
|
* @brief Downloads packages metadata to in memory stream, verifies hash and attempts to parse it
|
|
*
|
|
* @param aUrl URL of the packages metadata
|
|
* @param aHash optional sha256 hash
|
|
* @param aPackages resulting packages metadata list
|
|
* @param aReporter progress dialog to use for download
|
|
* @return true if packages were successfully downloaded, verified and parsed
|
|
*/
|
|
bool fetchPackages( const wxString& aUrl, const std::optional<wxString>& aHash,
|
|
std::vector<PCM_PACKAGE>& aPackages, PROGRESS_REPORTER* aReporter );
|
|
|
|
/**
|
|
* @brief Get the cached repository metadata
|
|
*
|
|
* @param aRepositoryId id of the repository
|
|
* @return const PCM_REPOSITORY&
|
|
*/
|
|
const PCM_REPOSITORY& getCachedRepository( const wxString& aRepositoryId ) const;
|
|
|
|
/**
|
|
* @brief Updates metadata of installed packages from freshly fetched repo
|
|
*
|
|
* This completely replaces all fields including description.
|
|
* Only exception is versions field, if currently installed version is missing
|
|
* from the repo metadata it is manually added back in to correctly display in the
|
|
* installed packages.
|
|
*
|
|
* @param aRepositoryId
|
|
*/
|
|
void updateInstalledPackagesMetadata( const wxString& aRepositoryId );
|
|
|
|
/**
|
|
* @brief Parses version strings and calculates compatibility
|
|
*
|
|
* This should be called after loading package metadata from repository or from
|
|
* installation entries
|
|
*
|
|
* @param aPackage package metadata object
|
|
*/
|
|
static void preparePackage( PCM_PACKAGE& aPackage );
|
|
|
|
///< Returns current UTC timestamp
|
|
time_t getCurrentTimestamp() const;
|
|
|
|
wxWindow* m_dialog;
|
|
nlohmann::json_schema::json_validator m_schema_validator;
|
|
wxString m_3rdparty_path;
|
|
wxString m_cache_path;
|
|
std::unordered_map<wxString, PCM_REPOSITORY> m_repository_cache;
|
|
STRING_TUPLE_LIST m_repository_list; // (id, name, url) tuples
|
|
// Using sorted map to keep order of entries in installed list stable
|
|
std::map<wxString, PCM_INSTALLATION_ENTRY> m_installed;
|
|
const static std::tuple<int, int, int> m_kicad_version;
|
|
std::function<void( int )> m_availableUpdateCallback;
|
|
std::thread m_updateThread;
|
|
|
|
std::shared_ptr<BACKGROUND_JOB> m_updateBackgroundJob;
|
|
};
|
|
|
|
#endif // PCM_H_
|