some fixed, increased version

This commit is contained in:
Martchus 2016-02-27 21:00:58 +01:00
parent 69a3d6de9d
commit 1cffbcb468
27 changed files with 892 additions and 84 deletions

View File

@ -23,7 +23,6 @@ set(HEADER_FILES
alpm/packagefinder.h
)
set(SRC_FILES
main.cpp
alpm/manager.cpp
alpm/package.cpp
alpm/utilities.cpp
@ -44,6 +43,21 @@ set(SRC_FILES
network/userrepository.cpp
network/networkaccessmanager.cpp
)
set(CLI_HEADER_FILES
)
set(CLI_SRC_FILES
cli/main.cpp
)
set(GUI_HEADER_FILES
gui/mainwindow.h
gui/webpage.h
gui/webviewprovider.h
)
set(GUI_SRC_FILES
gui/main.cpp
gui/mainwindow.cpp
gui/webpage.cpp
)
set(WEB_FILES
web/3rdparty/bootstrap/css/bootstrap-theme.min.css
web/3rdparty/bootstrap/css/bootstrap.min.css
@ -71,13 +85,13 @@ set(WEB_FILES
# meta data
set(META_PROJECT_NAME repoindex)
set(META_APP_NAME "Repository browser")
set(META_APP_NAME "Repository Browser")
set(META_APP_AUTHOR "Martchus")
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
set(META_APP_DESCRIPTION "Arch Linux repository browser")
set(META_APP_DESCRIPTION "Repository browser for Arch Linux")
set(META_VERSION_MAJOR 0)
set(META_VERSION_MINOR 2)
set(META_VERSION_PATCH 1)
set(META_VERSION_PATCH 2)
# stringification of meta data
set(META_PROJECT_NAME_STR "\"${META_PROJECT_NAME}\"")
@ -119,6 +133,9 @@ if(MINGW)
enable_language(RC)
endif(MINGW)
# read cached variables
set(WEBVIEW_PROVIDER "auto" CACHE STRING "specifies the webview provider: auto, webkit or webengine")
# check required Qt 5 modules
find_package(Qt5Core REQUIRED)
find_package(Qt5Concurrent REQUIRED)
@ -126,6 +143,36 @@ find_package(Qt5Network REQUIRED)
find_package(Qt5WebSockets REQUIRED)
find_package(KF5Archive REQUIRED)
# select Qt module providing webview (either Qt WebKit or Qt WebEngine)
if(${WEBVIEW_PROVIDER} STREQUAL "none")
set(WEBVIEW_PROVIDER OFF)
message(STATUS "Webview disabled, not building GUI.")
elseif(${WEBVIEW_PROVIDER} STREQUAL "auto")
find_package(Qt5WebEngineWidgets)
if(Qt5WebEngineWidgets_FOUND)
set(WEBVIEW_PROVIDER Qt5::WebEngineWidgets)
set(WEBVIEW_DEFINITION -DREPOINDEX_USE_WEBENGINE)
message(STATUS "No webview provider explicitely specified, defaulting to Qt WebEngine.")
else()
find_package(Qt5WebKitWidgets REQUIRED)
set(WEBVIEW_PROVIDER Qt5::WebKitWidgets)
message(STATUS "No webview provider explicitely specified, defaulting to Qt WebKit.")
endif()
else()
if(${WEBVIEW_PROVIDER} STREQUAL "webkit")
find_package(Qt5WebKitWidgets REQUIRED)
set(WEBVIEW_PROVIDER Qt5::WebKitWidgets)
message(STATUS "Using Qt WebKit as webview provider.")
elseif(${WEBVIEW_PROVIDER} STREQUAL "webengine")
find_package(Qt5WebEngineWidgets REQUIRED)
set(WEBVIEW_PROVIDER Qt5::WebEngineWidgets)
set(WEBVIEW_DEFINITION -DREPOINDEX_USE_WEBENGINE)
message(STATUS "Using Qt WebEngine as webview provider.")
else()
message(FATAL_ERROR "The specified webview provider '${WEBVIEW_PROVIDER}' is unknown.")
endif()
endif()
# enable moc
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@ -134,18 +181,33 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_definitions(
-D_GLIBCXX_USE_CXX11_ABI=0
-DCMAKE_BUILD
${WEBVIEW_DEFINITION}
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_definitions(-DDEBUG_BUILD)
message(STATUS "Debug build enabled.")
endif()
# executable and linking
add_executable(${META_PROJECT_NAME} ${HEADER_FILES} ${SRC_FILES} ${WEB_FILES} ${RES_FILES})
target_link_libraries(${META_PROJECT_NAME} c++utilities Qt5::Core Qt5::Concurrent Qt5::Network Qt5::WebSockets KF5::Archive)
add_library(${META_PROJECT_NAME}-lib SHARED ${HEADER_FILES} ${SRC_FILES} ${WEB_FILES})
target_link_libraries(${META_PROJECT_NAME}-lib c++utilities Qt5::Core Qt5::Concurrent Qt5::Network Qt5::WebSockets KF5::Archive)
set_target_properties(${META_PROJECT_NAME}-lib PROPERTIES
VERSION ${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH}
SOVERSION ${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH}
CXX_STANDARD 11
OUTPUT_NAME ${META_PROJECT_NAME}
)
add_executable(${META_PROJECT_NAME} ${CLI_HEADER_FILES} ${CLI_SRC_FILES})
target_link_libraries(${META_PROJECT_NAME} c++utilities ${META_PROJECT_NAME}-lib Qt5::Core Qt5::Network Qt5::WebSockets)
set_target_properties(${META_PROJECT_NAME} PROPERTIES
CXX_STANDARD 11
)
if(NOT ${WEBVIEW_PROVIDER} STREQUAL "none")
add_executable(${META_PROJECT_NAME}-gui ${GUI_HEADER_FILES} ${GUI_SRC_FILES})
target_link_libraries(${META_PROJECT_NAME}-gui c++utilities qtutilities ${META_PROJECT_NAME}-lib Qt5::Core Qt5::Network Qt5::WebSockets ${WEBVIEW_PROVIDER})
set_target_properties(${META_PROJECT_NAME}-gui PROPERTIES
CXX_STANDARD 11
)
endif()
# add install target for web files / minimizing
# -> don't minimize debug builds
@ -211,6 +273,11 @@ foreach(WEB_FILE ${WEB_FILES})
)
endif()
endforeach()
install(
FILES resources/icons/hicolor/scalable/apps/${META_PROJECT_NAME}.svg
DESTINATION share/${META_PROJECT_NAME}/web/img
COMPONENT web
)
# add target for minimizing
if(HTML_MIN_FILES)
add_custom_target(htmlmin ALL DEPENDS ${HTML_MIN_FILES})
@ -220,10 +287,32 @@ if(JS_MIN_FILES)
endif()
# add install target
foreach(HEADER_FILE ${HEADER_FILES})
get_filename_component(HEADER_DIR ${HEADER_FILE} DIRECTORY)
install(
FILES ${HEADER_FILE}
DESTINATION include/${META_PROJECT_NAME}/${HEADER_DIR}
COMPONENT header
)
endforeach()
install(TARGETS ${META_PROJECT_NAME}
RUNTIME DESTINATION bin
COMPONENT binary
)
if(NOT ${WEBVIEW_PROVIDER} STREQUAL "none")
install(TARGETS ${META_PROJECT_NAME}-gui
RUNTIME DESTINATION bin
COMPONENT binary-gui
)
endif()
install(TARGETS ${META_PROJECT_NAME}-lib
RUNTIME DESTINATION bin
COMPONENT binary
LIBRARY DESTINATION lib
COMPONENT binary
ARCHIVE DESTINATION lib
COMPONENT binary
)
install(FILES resources/systemd/${META_PROJECT_NAME}.service
DESTINATION lib/systemd/system
COMPONENT service
@ -232,18 +321,45 @@ install(FILES resources/settings/${META_PROJECT_NAME}.conf.js
DESTINATION share/${META_PROJECT_NAME}/skel
COMPONENT config
)
install(FILES resources/icons/hicolor/scalable/apps/${META_PROJECT_NAME}.svg
DESTINATION share/icons/hicolor/scalable/apps
COMPONENT desktop
)
install(FILES resources/desktop/applications/${META_PROJECT_NAME}.desktop
DESTINATION share/applications
COMPONENT desktop
)
if(NOT TARGET install-binary)
add_custom_target(install-binary
DEPENDS ${META_PROJECT_NAME}
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=binary -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
)
endif()
if((NOT ${WEBVIEW_PROVIDER} STREQUAL "none") AND (NOT TARGET install-binary-gui))
set(GUI_INSTALL_TARGET "install-binary-gui")
add_custom_target(install-binary-gui
DEPENDS ${META_PROJECT_NAME}
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=binary-gui -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
)
endif()
if(NOT TARGET install-header)
add_custom_target(install-header
DEPENDS ${META_PROJECT_NAME}
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=header -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
)
endif()
if(NOT TARGET install-service)
add_custom_target(install-service
DEPENDS ${META_PROJECT_NAME}
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=service -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
)
endif()
if(NOT TARGET install-desktop)
add_custom_target(install-desktop
DEPENDS ${META_PROJECT_NAME}
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=desktop -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
)
endif()
if(NOT TARGET install-config)
add_custom_target(install-config
DEPENDS ${META_PROJECT_NAME}
@ -258,7 +374,7 @@ if(NOT TARGET install-web)
endif()
if(NOT TARGET install-mingw-w64)
add_custom_target(install-mingw-w64
DEPENDS install-binary
DEPENDS install-binary ${GUI_INSTALL_TARGET} install-header
)
endif()
if(NOT TARGET install-binary-strip)

View File

@ -22,6 +22,7 @@
#include <iostream>
using namespace std;
using namespace ChronoUtilities;
namespace RepoIndex {
@ -37,25 +38,31 @@ using namespace Utilities;
class LoadPackage
{
public:
LoadPackage(AlpmDatabase *database, PackageOrigin origin) :
LoadPackage(AlpmDatabase *database, PackageOrigin origin, DateTime descriptionsLastModified) :
m_db(database),
m_origin(origin)
m_origin(origin),
m_descriptionsLastModified(descriptionsLastModified)
{}
void operator()(const QPair<QString, QList<QByteArray> > &description)
{
m_db->addPackageFromDescription(description.first, description.second, m_origin);
m_db->addPackageFromDescription(description.first, description.second, m_origin, m_descriptionsLastModified);
}
private:
AlpmDatabase *const m_db;
const PackageOrigin m_origin;
const DateTime m_descriptionsLastModified;
};
DatabaseError AlpmDatabase::loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &descriptions)
DatabaseError AlpmDatabase::loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &descriptions, ChronoUtilities::DateTime *lastModified)
{
QFileInfo pathInfo(databasePath());
if(pathInfo.isDir()) {
if(lastModified) {
// just use current date here since this is usually the local db
*lastModified = DateTime::gmtNow();
}
static const QStringList relevantFiles = QStringList() << QStringLiteral("desc") << QStringLiteral("files");
QDir dbDir(databasePath());
QStringList pkgDirNames = dbDir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
@ -83,6 +90,9 @@ DatabaseError AlpmDatabase::loadDescriptions(QList<QPair<QString, QList<QByteArr
}
}
} else if(pathInfo.isFile()) {
if(lastModified) {
*lastModified = DateTime::fromTimeStampGmt(pathInfo.lastModified().toUTC().toTime_t());
}
KTar tar(databasePath());
const KArchiveDirectory *dbDir;
if(tar.open(QIODevice::ReadOnly) && (dbDir = tar.directory())) {
@ -125,8 +135,8 @@ DatabaseError AlpmDatabase::loadDescriptions(QList<QPair<QString, QList<QByteArr
AlpmPackageLoader::AlpmPackageLoader(AlpmDatabase *repository, PackageOrigin origin) :
m_db(repository)
{
if((m_error = repository->loadDescriptions(m_descriptions)) == DatabaseError::NoError) {
m_future = QtConcurrent::map(m_descriptions, LoadPackage(repository, origin));
if((m_error = repository->loadDescriptions(m_descriptions, &m_descriptionsLastModified)) == DatabaseError::NoError) {
m_future = QtConcurrent::map(m_descriptions, LoadPackage(repository, origin, m_descriptionsLastModified));
}
}
@ -157,9 +167,6 @@ AlpmPackageLoader *AlpmDatabase::internalInit()
origin = PackageOrigin::SyncDb;
}
// wipe current packages
wipePackages();
// initialization of packages is done concurrently via AlpmPackageLoader
return new AlpmPackageLoader(this, origin);
@ -211,17 +218,19 @@ QNetworkRequest AlpmDatabase::filesDatabaseRequest()
* - Does nothing if there is not at least one server URL available.
* - Status messages are printed via cerr.
*/
void AlpmDatabase::downloadDatabase(const QString &targetDir, bool filesDatabase)
bool AlpmDatabase::downloadDatabase(const QString &targetDir, bool filesDatabase)
{
QWriteLocker locker(lock());
if(serverUrls().isEmpty()) {
return; // no server URLs available
return false; // no server URLs available
}
addBusyFlag();
cerr << shchar << "Downloading " << (filesDatabase ? "files" : "regular") << " database for [" << name().toLocal8Bit().data() << "] from mirror " << serverUrls().front().toLocal8Bit().data() << " ..." << endl;
QNetworkReply *reply = networkAccessManager().get(filesDatabase ? filesDatabaseRequest() : regularDatabaseRequest());
reply->setProperty("filesDatabase", filesDatabase);
m_downloadTargetDir = targetDir.isEmpty() ? QString(QChar('.')) : targetDir;
connect(reply, &QNetworkReply::finished, this, &AlpmDatabase::databaseDownloadFinished);
return true;
}
/*!
@ -234,10 +243,8 @@ void AlpmDatabase::downloadDatabase(const QString &targetDir, bool filesDatabase
*/
void AlpmDatabase::refresh(const QString &targetDir)
{
if(serverUrls().isEmpty()) {
if(!downloadDatabase(targetDir, true)) {
init();
} else {
downloadDatabase(targetDir, true);
}
}
@ -252,6 +259,7 @@ std::unique_ptr<Package> AlpmDatabase::emptyPackage()
*/
void AlpmDatabase::databaseDownloadFinished()
{
removeBusyFlag();
auto *reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
bool filesDatabase = reply->property("filesDatabase").toBool();

View File

@ -38,6 +38,7 @@ private:
AlpmDatabase *const m_db;
DatabaseError m_error;
QList<QPair<QString, QList<QByteArray> > > m_descriptions;
ChronoUtilities::DateTime m_descriptionsLastModified;
};
/*!
@ -73,7 +74,7 @@ public:
void setDatabasePath(const QString &dbPath);
// updating/refreshing
void downloadDatabase(const QString &targetDir, bool filesDatabase = true);
bool downloadDatabase(const QString &targetDir, bool filesDatabase = true);
void refresh(const QString &targetDir);
protected:
@ -83,7 +84,7 @@ private slots:
void databaseDownloadFinished();
private:
DatabaseError loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &descriptions);
DatabaseError loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &descriptions, ChronoUtilities::DateTime *lastModified = nullptr);
QNetworkRequest regularDatabaseRequest();
QNetworkRequest filesDatabaseRequest();

View File

@ -7,6 +7,7 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QStandardPaths>
#include <iostream>
@ -40,6 +41,7 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) :
rootdirArg("root-dir", "r", "specifies the root directory (default is /)"),
dbpathArg("db-path", "d", "specifies the pacman database path (default is /var/lib/pacman)"),
pacmanConfArg("pacman-conf", "p", "specifies the path of the pacman config file (default is /etc/pacman.conf"),
reposFromPacmanConfEnabled("repos-from-pacman-conf", string(), "enables repositories from the pacman config file"),
websocketAddrArg("addr", string(), "specifies the listening address for the websocket server, default is 127.0.0.1"),
websocketPortArg("port", string(), "specifies the listening port for the websocket server, default is 1234"),
certFileArg("cert-file", string(), "specifies the SSL certificate"),
@ -84,6 +86,7 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) :
pacmanConfArg.setCombinable(true);
pacmanConfArg.setValueNames(pathValueName);
pacmanConfArg.setRequiredValueCount(1);
reposFromPacmanConfEnabled.setCombinable(true);
websocketAddrArg.setCombinable(true);
websocketAddrArg.setValueNames({"IP address"});
websocketAddrArg.setRequiredValueCount(1);
@ -127,7 +130,7 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) :
shSyntaxArg.setCombinable(true);
repoArg.setRequiredValueCount(1);
repoArg.setValueNames({"repo name"});
serverArg.setSecondaryArguments({&rootdirArg, &dbpathArg, &pacmanConfArg, &certFileArg, &keyFileArg, &websocketAddrArg, &websocketPortArg, &insecureArg, &aurArg, &shSyntaxArg});
serverArg.setSecondaryArguments({&rootdirArg, &dbpathArg, &pacmanConfArg, &reposFromPacmanConfEnabled, &certFileArg, &keyFileArg, &websocketAddrArg, &websocketPortArg, &insecureArg, &aurArg, &shSyntaxArg});
upgradeLookupArg.setSecondaryArguments({&shSyntaxArg});
buildOrderArg.setSecondaryArguments({&aurArg, &addSourceOnlyDepsArg, &requireSourcesArg, &verboseArg, &shSyntaxArg});
mingwBundleArg.setSecondaryArguments({&targetDirArg, &targetNameArg, &targetFormatArg, &iconThemesArg, &defaultIconThemeArg, &extraPackagesArg});
@ -137,7 +140,7 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) :
storageDirArg.setCombinable(true);
storageDirArg.setRequiredValueCount(1);
storageDirArg.setValueNames(pathValueName);
parser.setMainArguments({&buildOrderArg, &upgradeLookupArg, &serverArg, &mingwBundleArg, &repoindexConfArg, &repoindexConfArg, &cacheDirArg, &helpArg});
parser.setMainArguments({&buildOrderArg, &upgradeLookupArg, &serverArg, &mingwBundleArg, &repoindexConfArg, &repoindexConfArg, &cacheDirArg, &storageDirArg, &helpArg});
}
/*!
@ -157,6 +160,7 @@ Config::Config() :
m_websocketServerListeningAddr(QHostAddress::LocalHost),
m_websocketServerListeningPort(1234),
m_serverInsecure(false),
m_serverCloseable(true),
m_localEnabled(true),
m_reposFromPacmanConfEnabled(false),
m_aurEnabled(true),
@ -280,8 +284,8 @@ void Config::loadFromConfigFile(const ConfigArgs &args)
loadFromConfigFile(QString::fromLocal8Bit(args.repoindexConfArg.values().front().data()));
return;
} else {
for(const auto &defaultPath : {QStringLiteral("./repoindex.conf"), QStringLiteral("/etc/repoindex.conf")}) {
if(QFile::exists(defaultPath)) {
for(const auto &defaultPath : {QStringLiteral("./repoindex.conf"), QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + QStringLiteral("/repoindex.conf"), QStringLiteral("/etc/repoindex.conf")}) {
if(!defaultPath.isEmpty() && QFile::exists(defaultPath)) {
loadFromConfigFile(defaultPath);
return;
}
@ -322,6 +326,18 @@ void Config::loadFromArgs(const ConfigArgs &args)
if(args.storageDirArg.isPresent()) {
m_storageDir = QString::fromLocal8Bit(args.storageDirArg.values().front().data());
}
if(args.reposFromPacmanConfEnabled.isPresent()) {
m_reposFromPacmanConfEnabled = true;
}
}
/*!
* \brief Ensures that the server is only accessable from the local machine.
*/
void Config::loadLocalOnlySetup()
{
m_websocketServerListeningAddr = QHostAddress::LocalHost;
m_serverInsecure = true;
}
RepoEntry::RepoEntry() :

View File

@ -33,6 +33,7 @@ public:
ApplicationUtilities::Argument rootdirArg;
ApplicationUtilities::Argument dbpathArg;
ApplicationUtilities::Argument pacmanConfArg;
ApplicationUtilities::Argument reposFromPacmanConfEnabled;
ApplicationUtilities::Argument websocketAddrArg;
ApplicationUtilities::Argument websocketPortArg;
ApplicationUtilities::Argument certFileArg;
@ -143,6 +144,8 @@ public:
const QString &serverCertFile() const;
const QString &serverKeyFile() const;
bool serverInsecure() const;
bool isServerCloseable() const;
void setServerCloseable(bool closeable);
bool isLocalDatabaseEnabled() const;
bool areReposFromPacmanConfEnabled() const;
const QList<RepoEntry> &repoEntries() const;
@ -154,6 +157,7 @@ public:
void loadFromConfigFile(const QString &args);
void loadFromConfigFile(const ConfigArgs &args);
void loadFromArgs(const ConfigArgs &args);
void loadLocalOnlySetup();
private:
QString m_alpmRootDir;
@ -167,6 +171,7 @@ private:
QString m_serverCertFile;
QString m_serverKeyFile;
bool m_serverInsecure;
bool m_serverCloseable;
QList<RepoEntry> m_repoEntries;
bool m_localEnabled;
@ -226,6 +231,16 @@ inline bool Config::serverInsecure() const
return m_serverInsecure;
}
inline bool Config::isServerCloseable() const
{
return m_serverCloseable;
}
inline void Config::setServerCloseable(bool closeable)
{
m_serverCloseable = closeable;
}
inline bool Config::isLocalDatabaseEnabled() const
{
return m_localEnabled;

View File

@ -523,9 +523,6 @@ void Manager::initAlpmDataBases()
delete loader;
}
}
for(auto &syncDbEntry : m_syncDbMap) {
syncDbEntry.second->updateGroups();
}
if(m_config.isVerbose() || m_config.runServer()) {
cerr << "DONE" << endl;
}
@ -547,6 +544,10 @@ void Manager::computeRequiredBy(Repository *repo)
} else {
relevantDbs.reserve(m_syncDbs.size());
for(auto &syncDb : m_syncDbs) {
if(syncDb->isBusy()) {
syncDb->asSoonAsPossible(bind(&Manager::computeRequiredBy, this, repo));
return;
}
relevantDbs << syncDb.get();
}
}
@ -590,6 +591,7 @@ void Manager::writeCache()
// could iterate through all repos and check isCachingUseful() but
// currently its just the AUR which is needed to be cached
if(userRepository()) {
QDir().mkpath(config().cacheDir());
QFile file(config().cacheDir() % QChar('/') % userRepository()->name() % QStringLiteral(".cache"));
if(file.open(QFileDevice::WriteOnly)) {
QDataStream stream(&file);
@ -690,13 +692,17 @@ void Manager::setAutoUpdateEnabled(bool enabled)
void Manager::updateAlpmDatabases()
{
if(localDatabase()) {
QReadLocker locker(localDatabase()->lock());
if(localDatabase()->hasOutdatedPackages()) {
locker.unlock();
localDatabase()->init();
}
}
for(auto &syncDbEntry : m_syncDbMap) {
if(syncDbEntry.second->hasOutdatedPackages()) {
syncDbEntry.second->refresh(m_config.storageDir() + QStringLiteral("/sync"));
for(auto &syncDb : m_syncDbs) {
QReadLocker locker(syncDb->lock());
if(syncDb->hasOutdatedPackages()) {
locker.unlock();
syncDb->refresh(m_config.storageDir() + QStringLiteral("/sync"));
}
}
}

View File

@ -34,7 +34,6 @@ namespace RepoIndex {
Package::Package(const QString &name, Repository *repository) :
m_origin(PackageOrigin::Unknown),
m_repository(repository),
m_timeStamp(DateTime::now()),
m_hasGeneralInfo(false),
m_hasAllGeneralInfo(false),
m_name(name),

View File

@ -229,6 +229,7 @@ public:
PackageOrigin origin() const;
Repository *repository() const;
ChronoUtilities::DateTime timeStamp() const;
void setTimeStamp(ChronoUtilities::DateTime timeStamp);
bool hasGeneralInfo() const;
bool hasAllGeneralInfo() const;
const QString &name() const;
@ -427,6 +428,14 @@ inline ChronoUtilities::DateTime Package::timeStamp() const
return m_timeStamp;
}
/*!
* \brief Sets the package's timestamp.
*/
inline void Package::setTimeStamp(ChronoUtilities::DateTime timeStamp)
{
m_timeStamp = timeStamp;
}
/*!
* \brief Returns whether general information is available for the package.
*/

View File

@ -73,6 +73,12 @@ const QStringList Repository::packageNames() const
return names;
}
/*!
* \brief Updates the groups.
*
* This method is automatically after initialization, so there is usually no need
* to call this method manually.
*/
void Repository::updateGroups()
{
m_groups.clear();
@ -101,9 +107,12 @@ PackageLoader *Repository::init()
{
addBusyFlag();
QWriteLocker locker(lock());
// wipe current packages
wipePackages();
if(PackageLoader *loader = internalInit()) {
if(loader->future().isRunning()) {
auto watcher = new QFutureWatcher<void>;
connect(watcher, &QFutureWatcher<void>::finished, this, &Repository::updateGroups);
connect(watcher, &QFutureWatcher<void>::finished, this, &Repository::removeBusyFlag);
connect(watcher, &QFutureWatcher<void>::finished, this, &Repository::initialized);
connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater);
@ -111,20 +120,30 @@ PackageLoader *Repository::init()
}
return loader;
} else {
updateGroups();
removeBusyFlag();
return nullptr;
}
}
void Repository::initAsSoonAsPossible()
{
asSoonAsPossible(bind(&Repository::init, this));
}
/*!
* \brief Performs the specified \a operation as soon as possible.
*/
void Repository::asSoonAsPossible(std::function<void ()> operation)
{
if(isBusy()) {
auto connection = make_shared<QMetaObject::Connection>();
*connection = connect(this, &Repository::available, [connection, this] {
*connection = connect(this, &Repository::available, [connection, operation] {
disconnect(*connection);
init();
operation();
});
} else {
init();
operation();
}
}
@ -283,30 +302,6 @@ QList<Package *> Repository::packageByFilter(std::function<bool (const Package *
* \cond
*/
class Blocker
{
public:
Blocker(const QList<Repository *> &relevantRepos)
{
m_blockedRepos.reserve(relevantRepos.size());
for(Repository *repo : relevantRepos) {
if(repo->lock()->tryLockForWrite()) {
m_blockedRepos << repo;
}
}
}
~Blocker()
{
for(Repository *repo : m_blockedRepos) {
repo->lock()->unlock();
}
}
private:
QList<Repository *> m_blockedRepos;
};
class ComputeRequired
{
public:
@ -555,7 +550,7 @@ void Repository::restoreFromCacheStream(QDataStream &in, bool skipOutdated)
quint32 packageCount;
in >> packageCount;
bool good = true;
const auto now = DateTime::now();
const auto now = DateTime::gmtNow();
for(quint32 i = 0; i < packageCount && good && in.status() == QDataStream::Ok; ++i) {
if(auto package = emptyPackage()) {
package->restoreFromCacheStream(in);
@ -632,7 +627,7 @@ void Repository::cleanOutdatedPackages()
if(maxPackageAge().isInfinity()) {
return;
}
auto now = DateTime::now();
auto now = DateTime::gmtNow();
for(auto i = m_packages.begin(); i != m_packages.end(); ) {
const Package &pkg = *i->second;
if((now - pkg.timeStamp()) > maxPackageAge()) {
@ -652,10 +647,9 @@ bool Repository::hasOutdatedPackages()
if(maxPackageAge().isInfinity()) {
return false;
}
auto now = DateTime::now();
for(auto i = m_packages.begin(); i != m_packages.end(); ) {
const Package &pkg = *i->second;
if((now - pkg.timeStamp()) > maxPackageAge()) {
auto now = DateTime::gmtNow();
for(const auto &pkgEntry : m_packages) {
if((now - pkgEntry.second->timeStamp()) > maxPackageAge()) {
return true;
}
}
@ -866,7 +860,7 @@ void Repository::parseDescriptions(const QList<QByteArray> &descriptions, QStrin
* \returns Returns the added/updated packages. In the case of a split package more then
* one package is returned.
*/
QList<Package *> Repository::addPackagesFromSrcInfo(const QByteArray &srcInfo)
QList<Package *> Repository::addPackagesFromSrcInfo(const QByteArray &srcInfo, ChronoUtilities::DateTime timeStamp)
{
// define states
enum {
@ -965,6 +959,7 @@ QList<Package *> Repository::addPackagesFromSrcInfo(const QByteArray &srcInfo)
pkg = emptyPackage();
}
currentPackage = pkg.get();
currentPackage->setTimeStamp(timeStamp);
packageInfo.clear();
}
// add field to ...
@ -1007,7 +1002,7 @@ QList<Package *> Repository::addPackagesFromSrcInfo(const QByteArray &srcInfo)
* - If \a name is empty and the description doesn't provide a name either, the package can not be added.
* \returns Returns the added/updated package or nullptr if no package could be added.
*/
Package *Repository::addPackageFromDescription(QString name, const QList<QByteArray> &descriptions, PackageOrigin origin)
Package *Repository::addPackageFromDescription(QString name, const QList<QByteArray> &descriptions, PackageOrigin origin, DateTime timeStamp)
{
// parse fields
QList<QPair<QString, QStringList> > fields;
@ -1021,6 +1016,7 @@ Package *Repository::addPackageFromDescription(QString name, const QList<QByteAr
// find/create package for description
auto pkg = emptyPackage();
Package *pkgRawPtr = pkg.get();
pkgRawPtr->setTimeStamp(timeStamp);
pkgRawPtr->putDescription(name, fields, origin);
{
QWriteLocker locker(&m_lock);

View File

@ -212,16 +212,16 @@ public:
bool isPackageOnly() const;
std::map<QString, QList<Package *> > &groups();
const std::map<QString, QList<Package *> > &groups() const;
void updateGroups();
Q_SLOT void updateGroups();
const QStringList &serverUrls() const;
QStringList &serverUrls();
SignatureLevel sigLevel() const;
void setSigLevel(SignatureLevel sigLevel);
// gathering data
public:
PackageLoader *init();
Q_SLOT PackageLoader *init();
void initAsSoonAsPossible();
void asSoonAsPossible(std::function<void(void)> operation);
virtual PackageLoader *internalInit();
virtual PackageDetailAvailability requestsRequired(PackageDetail packageDetail = PackageDetail::Basics) const;
virtual SuggestionsReply *requestSuggestions(const QString &phrase);
@ -273,8 +273,8 @@ public:
// parsing src/pkg info
static void parsePkgInfo(const QByteArray &pkgInfo, QString &name, QList<QPair<QString, QString> > packageInfo);
static void parseDescriptions(const QList<QByteArray> &descriptions, QString &name, QList<QPair<QString, QStringList> > &fields);
QList<Package *> addPackagesFromSrcInfo(const QByteArray &srcInfo);
Package *addPackageFromDescription(QString name, const QList<QByteArray> &descriptions, PackageOrigin origin);
QList<Package *> addPackagesFromSrcInfo(const QByteArray &srcInfo, ChronoUtilities::DateTime timeStamp);
Package *addPackageFromDescription(QString name, const QList<QByteArray> &descriptions, PackageOrigin origin, ChronoUtilities::DateTime timeStamp);
// thread synchronization
QReadWriteLock *lock() const;
@ -576,6 +576,7 @@ inline void Repository::setMaxPackageAge(ChronoUtilities::TimeSpan maxPackageAge
inline void Repository::wipePackages()
{
m_packages.clear();
m_groups.clear();
}
/*!

View File

@ -15,8 +15,6 @@
#include <iostream>
#include <algorithm>
#include <QReadWriteLock>
using namespace std;
using namespace ApplicationUtilities;
using namespace RepoIndex;

106
gui/main.cpp Normal file
View File

@ -0,0 +1,106 @@
#include "./alpm/manager.h"
#include "./alpm/utilities.h"
#include "./alpm/config.h"
#include "./network/server.h"
#include "./gui/mainwindow.h"
#include "resources/config.h"
#include <qtutilities/resources/qtconfigarguments.h>
#include <qtutilities/resources/resources.h>
#include <c++utilities/application/argumentparser.h>
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/application/failure.h>
#include <QApplication>
#include <iostream>
using namespace std;
using namespace ApplicationUtilities;
using namespace RepoIndex;
int main(int argc, char *argv[])
{
// setup the argument parser
ArgumentParser parser;
SET_APPLICATION_INFO;
SET_QT_APPLICATION_INFO;
QT_CONFIG_ARGUMENTS qtConfigArgs;
ConfigArgs configArgs(parser);
parser.setIgnoreUnknownArguments(false);
Argument webdirArg("web-dir", string(), "specifies the directory of the web files");
webdirArg.setCombinable(true);
webdirArg.setRequiredValueCount(1);
webdirArg.setValueNames({"path"});
for(Argument *arg : initializer_list<Argument *>{&configArgs.rootdirArg, &configArgs.dbpathArg, &configArgs.pacmanConfArg, &configArgs.reposFromPacmanConfEnabled, &configArgs.aurArg}) {
qtConfigArgs.qtWidgetsGuiArg().addSecondaryArgument(arg);
}
parser.setMainArguments({&qtConfigArgs.qtWidgetsGuiArg(), &configArgs.repoindexConfArg, &configArgs.repoindexConfArg, &webdirArg, &configArgs.cacheDirArg, &configArgs.storageDirArg, &configArgs.helpArg});
// parse command line arguments
try {
parser.parseArgs(argc, argv);
} catch (const Failure &e) {
cerr << shchar << "Unable to parse arguments: " << e.what() << endl;
return 2;
}
try {
// load configuration
Config config;
config.loadFromConfigFile(configArgs);
config.loadFromArgs(configArgs);
config.loadLocalOnlySetup();
config.setServerCloseable(false);
if(qtConfigArgs.qtWidgetsGuiArg().isPresent()) {
// configure Qt
qtConfigArgs.applySettings();
// find directory with web files
QString webdir;
if(webdirArg.isPresent()) {
webdir = QString::fromLocal8Bit(webdirArg.values().front().data());
} else {
webdir = QStringLiteral("/usr/share/" PROJECT_NAME "/web");
}
// create app
QApplication application(argc, argv);
MainWindow mainWindow(webdir);
mainWindow.show();
// setup manager
Manager manager(config);
cerr << shchar << "Loading databases ..." << endl;
if(config.areReposFromPacmanConfEnabled()) {
manager.addDataBasesFromPacmanConfig();
}
manager.addDatabasesFromRepoIndexConfig();
manager.initAlpmDataBases();
cerr << shchar << "Restoring cache ... ";
manager.restoreCache();
cerr << shchar << "DONE" << endl;
// setup the server
Server server(manager, manager.config());
manager.setAutoCacheMaintenanceEnabled(true);
manager.setAutoUpdateEnabled(true);
// run Qt event loop
return application.exec();
} else if(!configArgs.helpArg.isPresent()) {
if(useShSyntax) {
cerr << "export REPOINDEX_ERROR='No command line arguments specified. See --help for available commands.'" << endl;
} else {
cerr << "No command line arguments specified. See --help for available commands." << endl;
}
}
} catch (const exception &ex) {
Utilities::printError(ex);
return 1;
}
}

98
gui/mainwindow.cpp Normal file
View File

@ -0,0 +1,98 @@
#include "./mainwindow.h"
#include "./webpage.h"
#include "resources/config.h"
#include <QGuiApplication>
#include <QClipboard>
#include <QSettings>
#include <QStringBuilder>
#include <QAction>
#include <QMenu>
#include <QKeyEvent>
#include <QMouseEvent>
namespace RepoIndex {
/*!
* \brief Constructs a new main window.
*/
MainWindow::MainWindow(const QString &webdir)
{
QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName());
setWindowTitle(QStringLiteral(APP_NAME));
setWindowIcon(QIcon::fromTheme(QStringLiteral(PROJECT_NAME)));
m_webView.setPage(new WebPage(&m_webView));
QUrl url(QStringLiteral("file://") % webdir % QStringLiteral("/index.html"));
url.setFragment(settings.value(QStringLiteral("fragment"), QStringLiteral("packages")).toString());
m_webView.setUrl(url);
m_webView.setContextMenuPolicy(Qt::CustomContextMenu);
connect(&m_webView, &QWidget::customContextMenuRequested, this, &MainWindow::showInfoWebViewContextMenu);
setCentralWidget(&m_webView);
restoreGeometry(settings.value(QStringLiteral("geometry")).toByteArray());
}
bool MainWindow::event(QEvent *event)
{
switch(event->type()) {
case QEvent::KeyRelease: {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
switch(keyEvent->key()) {
case Qt::Key_Left:
case Qt::Key_Back:
m_webView.back();
return true;
case Qt::Key_Right:
case Qt::Key_Forward:
m_webView.forward();
return true;
default:
;
}
} case QEvent::MouseButtonPress: {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
switch(mouseEvent->button()) {
case Qt::BackButton:
m_webView.back();
return true;
case Qt::ForwardButton:
m_webView.forward();
return true;
default:
;
}
break;
} case QEvent::Close: {
// save settings
QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName());
settings.setValue(QStringLiteral("geometry"), saveGeometry());
settings.setValue(QStringLiteral("fragment"), m_webView.url().fragment());
break;
} default:
;
}
return QMainWindow::event(event);
}
/*!
* \brief Shows the context menu for the info web view.
*/
void MainWindow::showInfoWebViewContextMenu(const QPoint &)
{
QAction copyAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy"), nullptr);
copyAction.setDisabled(m_webView.selectedText().isEmpty());
connect(&copyAction, &QAction::triggered, this, &MainWindow::copyInfoWebViewSelection);
QMenu menu;
menu.addAction(&copyAction);
menu.exec(QCursor::pos());
}
/*!
* \brief Copies the current selection of the info web view.
*/
void MainWindow::copyInfoWebViewSelection()
{
QGuiApplication::clipboard()->setText(m_webView.selectedText());
}
}

34
gui/mainwindow.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "./webviewprovider.h"
#include <QMainWindow>
#ifdef REPOINDEX_USE_WEBENGINE
# include <QWebEngineView>
#else
# include <QWebView>
#endif
namespace RepoIndex {
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(const QString &webdir);
protected:
bool event(QEvent *event);
private slots:
void showInfoWebViewContextMenu(const QPoint &);
void copyInfoWebViewSelection();
private:
WEB_VIEW_PROVIDER m_webView;
};
}
#endif // MAINWINDOW_H

51
gui/webpage.cpp Normal file
View File

@ -0,0 +1,51 @@
#include "./webpage.h"
#include "resources/config.h"
#include <QDesktopServices>
#ifdef REPOINDEX_USE_WEBENGINE
# include <QWebEngineSettings>
# include <QWebEngineView>
#else
# include <QWebSettings>
# include <QWebView>
#endif
namespace RepoIndex {
WebPage::WebPage(WEB_VIEW_PROVIDER *view) :
WEB_PAGE_PROVIDER(view),
m_view(view)
{
#ifdef REPOINDEX_USE_WEBENGINE
settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true);
#else
settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true);
#endif
if(!m_view) {
// delegate to external browser if no view is assigned
connect(this, &WebPage::urlChanged, this, &WebPage::delegateToExternalBrowser);
m_view = new WEB_VIEW_PROVIDER;
m_view->setPage(this);
}
}
WEB_PAGE_PROVIDER *WebPage::createWindow(QWebEnginePage::WebWindowType type)
{
return new WebPage;
}
void WebPage::delegateToExternalBrowser(const QUrl &url)
{
openUrlExternal(url);
// this page and the associated view are useless
m_view->deleteLater();
deleteLater();
}
void WebPage::openUrlExternal(const QUrl &url)
{
QDesktopServices::openUrl(url);
}
}

37
gui/webpage.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef WEBPAGE_H
#define WEBPAGE_H
#include "./webviewprovider.h"
#ifdef REPOINDEX_USE_WEBENGINE
# include <QWebEnginePage>
#else
# include <QWebPage>
#endif
QT_FORWARD_DECLARE_CLASS(WEB_VIEW_PROVIDER)
namespace RepoIndex {
class WebPage : public WEB_PAGE_PROVIDER
{
Q_OBJECT
public:
WebPage(WEB_VIEW_PROVIDER *view = nullptr);
public slots:
void openUrlExternal(const QUrl &url);
protected:
WEB_PAGE_PROVIDER *createWindow(WebWindowType type);
private slots:
void delegateToExternalBrowser(const QUrl &url);
private:
WEB_VIEW_PROVIDER *m_view;
};
}
#endif // WEBPAGE_H

9
gui/webviewprovider.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef WEB_VIEW_PROVIDER
#ifdef REPOINDEX_USE_WEBENGINE
# define WEB_VIEW_PROVIDER QWebEngineView
# define WEB_PAGE_PROVIDER QWebEnginePage
#else
# define WEB_VIEW_PROVIDER QWebView
# define WEB_PAGE_PROVIDER QWebPage
#endif
#endif

View File

@ -132,7 +132,7 @@ void Connection::handleCmd(const QJsonObject &obj)
const auto what = obj.value(QStringLiteral("w")).toString();
const auto id = obj.value(QStringLiteral("id"));
if(what == QLatin1String("stop")) {
if(m_socket->peerAddress().isLoopback()) {
if(m_manager.config().isServerCloseable() && m_socket->peerAddress().isLoopback()) {
cerr << shchar << "Info: Server stopped via web interface." << endl;
QCoreApplication::quit();
} else {

View File

@ -55,6 +55,7 @@ void AurPackageReply::processData(QNetworkReply *reply)
QJsonParseError error;
const auto doc = QJsonDocument::fromJson(reply->readAll(), &error);
if(error.error == QJsonParseError::NoError) {
auto now = DateTime::gmtNow();
QWriteLocker locker(m_repo->lock());
auto &packages = m_repo->packages();
for(const auto &result : doc.object().value(QStringLiteral("results")).toArray()) {
@ -67,6 +68,7 @@ void AurPackageReply::processData(QNetworkReply *reply)
} else {
package = make_unique<AurPackage>(obj, static_cast<UserRepository *>(m_userRepo));
}
package->setTimeStamp(now);
}
}
} else {
@ -113,7 +115,7 @@ void AurFullPackageReply::processData(QNetworkReply *reply)
if(srcInfoEntry && srcInfoEntry->isFile()) {
const auto srcInfo = static_cast<const KArchiveFile *>(srcInfoEntry)->data();
QWriteLocker locker(m_userRepo->lock());
const auto packages = m_userRepo->addPackagesFromSrcInfo(srcInfo);
const auto packages = m_userRepo->addPackagesFromSrcInfo(srcInfo, DateTime::gmtNow());
// TODO: error handling
for(const auto &entryName : baseDir->entries()) {
if(entryName != QLatin1String(".SRCINFO")) {

View File

@ -0,0 +1,9 @@
#!/usr/bin/env xdg-open
[Desktop Entry]
Name=Repository Browser
Comment=An Arch Linux repository browser.
Exec=sh -c "repoindex-gui --repos-from-pacman-conf --cache-dir \\"\\$HOME/.cache/Martchus/Repository Browser\\" --storage-dir \\"\\$HOME/.config/Martchus/Repository Browser\\""
Icon=repoindex
Terminal=false
Type=Application
Categories=Utility;

View File

@ -0,0 +1,286 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128.47652mm"
height="128.47652mm"
viewBox="0 0 455.23172 455.23175"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="repoindex.svg">
<defs
id="defs4">
<linearGradient
inkscape:collect="always"
id="linearGradient4156">
<stop
style="stop-color:#0088cc;stop-opacity:1;"
offset="0"
id="stop4158" />
<stop
style="stop-color:#005b88;stop-opacity:1"
offset="1"
id="stop4160" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4342">
<stop
style="stop-color:#ffe247;stop-opacity:1"
offset="0"
id="stop4344" />
<stop
id="stop4350"
offset="0.67710614"
style="stop-color:#ffe113;stop-opacity:1" />
<stop
style="stop-color:#eeff88;stop-opacity:1"
offset="1"
id="stop4346" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="-30.507242 : 472.74589 : 1"
inkscape:vp_y="0 : 590.9609 : 0"
inkscape:vp_z="1084.0486 : 472.74589 : 1"
inkscape:persp3d-origin="526.77064 : 369.09507 : 1"
id="perspective4148" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4342"
id="radialGradient4352"
cx="321.2179"
cy="524.50543"
fx="321.2179"
fy="524.50543"
r="150.79008"
gradientTransform="matrix(1,0,0,1.0631051,-255.83411,84.518479)"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4156"
id="linearGradient4162"
x1="291.74536"
y1="713.38061"
x2="452.79324"
y2="761.86792"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4156"
id="linearGradient4178"
x1="396.44601"
y1="557.31207"
x2="439.7518"
y2="461.3476"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4156"
id="linearGradient4186"
x1="134.08012"
y1="532.63721"
x2="333.1778"
y2="477.0788"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="172.38789"
inkscape:cy="222.01635"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1039"
inkscape:window-x="0"
inkscape:window-y="18"
inkscape:window-maximized="1"
showguides="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-95.085314,-386.88471)">
<path
style="fill:#6ccfff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.954;stroke-linecap:butt;stroke-miterlimit:1.45000005;stroke-dasharray:none;stroke-dashoffset:1.99954295;stroke-opacity:1"
d="m 154.4949,579.62112 167.59343,-58.47155 164.83869,74.78194 -189.49553,65.00691 z"
id="rect4358"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
style="fill:url(#linearGradient4178);fill-opacity:1;stroke:#ffffff;stroke-width:2.95759749;stroke-linecap:butt;stroke-miterlimit:1.45000005;stroke-dasharray:none;stroke-dashoffset:2;stroke-opacity:1"
d="m 319.56719,519.77647 69.28807,-53.41761 160.99173,68.71253 -61.3275,58.59649 z"
id="rect4214-0"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
style="fill:url(#linearGradient4186);fill-opacity:1;stroke:#ffffff;stroke-width:2.95759749;stroke-linecap:butt;stroke-miterlimit:1.45000005;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1"
d="m 96.802346,515.47264 162.667844,-52.81423 65.66299,57.4282 -171.00252,60.24593 z"
id="rect4214"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
style="fill:url(#radialGradient4352);fill-opacity:1;fill-rule:nonzero;stroke:#434343;stroke-width:2.854;stroke-linecap:butt;stroke-miterlimit:1.45000005;stroke-dasharray:none;stroke-dashoffset:2;stroke-opacity:1"
id="path4140"
sodipodi:type="arc"
sodipodi:cx="74.886345"
sodipodi:cy="642.12262"
sodipodi:rx="159.61505"
sodipodi:ry="159.61505"
sodipodi:start="0.55248493"
sodipodi:end="5.7870476"
d="M 210.75436,725.88925 A 159.61505,159.61505 0 0 1 29.193706,795.05773 159.61505,159.61505 0 0 1 -84.665363,637.62628 159.61505,159.61505 0 0 1 37.879134,486.85694 159.61505,159.61505 0 0 1 215.25624,566.14067 L 74.886345,642.12262 Z"
transform="matrix(0.91759584,-0.39751462,0.39751462,0.91759584,0,0)" />
<circle
style="fill:#ffffff;fill-opacity:1;stroke:#434343;stroke-width:2.183;stroke-linecap:butt;stroke-miterlimit:1.45000005;stroke-dasharray:none;stroke-dashoffset:1.99954295;stroke-opacity:1"
id="path4144"
cx="95.899529"
cy="548.30194"
r="21.144077"
transform="matrix(0.91759584,-0.39751462,0.39751462,0.91759584,0,0)" />
<path
inkscape:connector-curvature="0"
id="path4202"
style="fill:url(#linearGradient4162);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.95759749;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.45000005;stroke-dasharray:none;stroke-dashoffset:2;stroke-opacity:1"
d="m 293.22416,660.48706 2.11255,172.15066 195.37368,-65.01376 0,-173.50047 z"
sodipodi:nodetypes="ccccc" />
<path
inkscape:connector-curvature="0"
id="path4202-6"
style="fill:#0088cc;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.95759749;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.45000005;stroke-dasharray:none;stroke-dashoffset:2;stroke-opacity:1"
d="m 298.74404,658.26061 -2.11338,172.15066 -146.8618,-84.02688 4.22513,-167.16276 z"
sodipodi:nodetypes="ccccc" />
<g
id="g4269"
transform="matrix(0.40448316,-0.14264259,0,0.40448316,264.26281,440.48667)">
<g
style="fill:#ffffff;fill-opacity:1"
id="g2809"
transform="matrix(0.55789384,0,0,0.55789384,-34.632645,718.71563)">
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path2284"
d="m 339.96875,309.09375 c -14.47141,-0.0239 -26.4812,2.94367 -31.125,4.5625 l -4.78125,25.8125 c -0.0116,0.0951 23.79543,-6.34855 34.28125,-5.96875 17.36158,0.62381 18.95948,6.63541 18.65625,14.75 0.29595,0.47462 -4.47933,-7.33192 -19.5,-7.59375 -18.94961,-0.32687 -45.69284,6.70947 -45.65625,35.3125 -0.51086,32.17412 24.03361,41.63882 40.75,41.8125 15.02821,-0.27364 22.0777,-5.69136 25.9375,-8.59375 5.07124,-5.30236 10.87308,-10.63447 16.40625,-17.03125 -5.23567,9.51278 -9.77472,16.0898 -14.5,21.125 l 0,4.25 22.84375,-3.84375 0.15625,-62.09375 c -0.23141,-8.78839 5.04123,-42.41827 -43.46875,-42.5 z m -3.28125,54.0625 c 9.46889,0.12995 20.32788,4.79708 20.34375,16.03125 0.049,10.21821 -12.80005,15.71183 -21.15625,15.625 -8.35976,-0.0868 -19.45093,-6.56982 -19.5,-16.53125 0.16016,-8.90444 10.45953,-15.35418 20.3125,-15.125 z"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path2286"
d="m 398.50106,314.83145 -0.15505,102.82693 26.61213,-5.12724 0.0449,-58.30157 c 0.006,-8.68089 12.40554,-18.82451 27.9627,-18.66287 3.30202,-5.97408 9.5087,-21.24219 11.02088,-24.71514 -34.75649,-0.0833 -35.19897,9.98993 -41.24398,14.94517 -0.0631,-9.45285 -0.0213,-15.12741 -0.0213,-15.12741 l -24.2202,4.16213 z"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path2288"
d="m 548.2688,328.33058 c -0.25696,-0.12068 -13.87938,-15.93419 -41.26638,-16.0589 -25.65249,-0.42638 -54.42578,9.51895 -54.88631,52.5328 0.22457,37.81852 27.6402,52.59809 55.0314,52.88627 29.31292,0.30451 40.97654,-18.32947 41.67615,-18.79124 -3.49762,-3.0321 -16.59792,-16.0131 -16.59792,-16.0131 0,0 -8.18236,11.65102 -24.05802,11.79913 -15.87942,0.1512 -29.68245,-12.27325 -29.87805,-29.60905 -0.20349,-17.33595 12.68881,-26.72821 29.99725,-27.48687 14.98466,-0.003 23.6297,9.67334 23.6297,9.67334 l 16.35218,-18.93238 z"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path2290"
d="m 581.8125,278.84375 -25.125,5.90625 0.1875,133.9375 24.75,-4.46875 0.28125,-63.03125 c 0.0529,-6.60927 9.56127,-16.75916 25.4375,-16.4375 15.17973,0.15775 18.57236,10.11767 18.53125,11.375 l 0.4375,72.96875 24.40625,-4.3125 0.0937,-77.375 c 0.1607,-7.44539 -16.30833,-23.16954 -42.78125,-23.28125 -12.58087,0.0202 -19.54815,2.86825 -23.09375,4.96875 -6.06656,4.68565 -12.9998,9.17543 -19.8125,14.90625 6.29809,-8.09099 11.58551,-13.68516 16.75,-17.84375 l -0.0625,-37.3125 z"
inkscape:connector-curvature="0" />
</g>
<g
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="g5326"
transform="matrix(0.82595102,0,0.01168815,0.82595102,1.9788752,356.03908)">
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path2292"
d="m 400.67581,629.79609 7.68167,-1.91575 -0.92851,91.20792 -7.79574,1.32426 1.04258,-90.61643 z"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path2294"
d="m 421.10266,657.01757 6.75064,-2.9867 -0.86808,65.39931 -6.49779,1.33915 0.61523,-63.75176 z m -1.26059,-23.58316 5.47167,-4.41533 4.42261,4.99952 -5.47558,4.53221 -4.4187,-5.1164 z"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path2296"
d="m 440.44273,655.82614 7.67755,-1.56201 -0.1573,13.6722 c -0.007,0.58717 4.4194,-15.27364 24.68502,-14.92094 19.67986,0.10952 22.68401,15.34634 22.5291,18.76237 l -0.43759,48.0783 -6.73044,1.45631 0.63316,-47.489 c 0.0974,-1.38684 -2.88144,-13.11441 -16.78906,-13.15754 -13.90509,-0.0404 -23.68364,10.10048 -23.75821,16.57937 l -0.48127,41.83477 -7.80388,2.0313 0.63292,-65.28513 z"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path2298"
d="m 561.53301,720.20203 -7.6776,1.56186 0.15737,-13.67198 c 0.007,-0.58742 -4.42201,15.27361 -24.68504,14.92086 -19.67983,-0.10944 -22.68399,-15.34626 -22.52908,-18.76229 l 0.43757,-48.07861 8.15674,-1.64226 -0.54644,47.48988 c -0.0149,1.29682 1.36845,13.29979 15.27604,13.3426 13.90511,0.0405 23.76622,-8.37359 24.01453,-21.04416 l 0.43105,-37.46902 7.5978,-1.93195 -0.63294,65.28507 z"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path2300"
d="m 577.45461,655.28678 -5.42715,4.20017 20.19894,26.93328 -22.39092,31.11622 5.63499,4.226 21.04365,-28.8967 20.8779,29.58159 5.32727,-4.20103 -22.37578,-31.62866 18.56963,-25.5775 -5.53193,-4.73429 -16.92109,23.66778 -19.00551,-24.68686 z"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(0.8746356,0,0,0.8746356,-66.545022,716.81646)"
style="font-style:normal;font-weight:normal;font-size:8.44138241px;font-family:'DejaVu Sans Mono';fill:#ffffff;fill-opacity:1;stroke:none"
id="text2634">
<path
style="fill:#ffffff"
id="path7858"
d="m 685.46692,263.83624 0,-5.32944 -1.99082,0 0,-0.71307 4.7895,0 0,0.71307 -1.99906,0 0,5.32944 -0.79962,0"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff"
id="path7860"
d="m 689.0982,263.83624 0,-6.04251 1.20355,0 1.43026,4.2784 c 0.13189,0.39843 0.22806,0.69658 0.28852,0.89442 0.0687,-0.21983 0.17586,-0.5427 0.3215,-0.96862 l 1.44674,-4.2042 1.07578,0 0,6.04251 -0.77077,0 0,-5.05741 -1.75587,5.05741 -0.72131,0 -1.74763,-5.14396 0,5.14396 -0.77077,0"
inkscape:connector-curvature="0" />
</g>
</g>
<g
id="text2638"
style="font-style:normal;font-weight:normal;font-size:8.25130367px;font-family:'DejaVu Sans Mono';fill:#ffffff;fill-opacity:1;stroke:none"
transform="matrix(0.8746356,0,0,0.8746356,136.49564,557.21236)">
<path
inkscape:connector-curvature="0"
d="m 239.84053,313.69965 0,-5.20945 -1.94598,0 0,-0.697 4.68164,0 0,0.697 -1.95404,0 0,5.20945 -0.78162,0"
id="path7853"
style="fill:#ffffff" />
<path
inkscape:connector-curvature="0"
d="m 243.39004,313.69965 0,-5.90645 1.17646,0 1.39805,4.18205 c 0.12892,0.38947 0.22293,0.6809 0.28202,0.87429 0.0671,-0.21488 0.1719,-0.53048 0.31426,-0.94681 l 1.41417,-4.10953 1.05155,0 0,5.90645 -0.75341,0 0,-4.94353 -1.71634,4.94353 -0.70506,0 -1.70828,-5.02814 0,5.02814 -0.75342,0"
id="path7855"
style="fill:#ffffff" />
</g>
<path
style="fill:#0088cc;fill-opacity:1;stroke:#ffffff;stroke-width:2.95759749;stroke-linecap:butt;stroke-miterlimit:1.45000005;stroke-dasharray:none;stroke-dashoffset:1;stroke-opacity:1"
d="m 106.65363,647.21618 46.11221,-67.92278 146.48421,78.50104 -44.86588,74.74063 z"
id="rect4214-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
inkscape:connector-curvature="0"
d="m 397.52991,637.10169 c -4.91286,13.77761 -7.87608,22.70147 -13.34592,36.31744 3.3537,2.37223 7.47021,5.0604 14.15539,7.37845 -7.18724,-0.42289 -12.08981,-1.66325 -15.75358,-3.4524 -7.00035,17.07601 -17.9679,41.75123 -40.22458,89.59085 17.49302,-16.26801 31.05326,-27.27622 43.69078,-34.10863 -0.54266,-2.1426 -0.85118,-4.55844 -0.83023,-7.20003 l 0.0207,-0.56771 c 0.27757,-11.30518 6.10759,-21.97959 13.01382,-23.82991 6.90623,-1.85034 12.27437,5.82171 11.99681,17.1269 -0.0522,2.12726 -0.29008,4.23986 -0.7057,6.26802 12.50031,-1.96301 25.91556,-0.48374 43.17189,3.39314 -3.40263,-5.06448 -6.43974,-9.64038 -9.34007,-13.99569 -4.56852,-1.92979 -9.3337,-4.85783 -19.05376,-6.41897 6.68102,-0.62015 11.46453,-0.30414 15.19319,0.61964 -29.48858,-44.50331 -31.87652,-50.95656 -41.98881,-71.12108 z"
id="path2518-2"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -21,10 +21,12 @@
},
"repos": {
"localEnabled": true,
"fromPacmanConfig": true,
"add": [
"//add": [
{"name": "examplerepo",
"maxAge": 3600
"dataBaseFile": "path/to/database/file",
"sourcesDir": "path/to/local/source/dir",
"packagesDir": "path/to/local/pkg/dir",

View File

@ -30,7 +30,8 @@
"upgradeSources": ["aur"],
"server": [
"https://localhost/repo/arch/$repo/os/$arch"
]
],
"maxAge": 3600
},
{"name": "local", "maxAge": 3600},

View File

@ -229,6 +229,7 @@
<h4 class="modal-title">About</h4>
</div>
<div class="modal-body" style="text-align: center;">
<img src="img/@META_PROJECT_NAME@.svg" style="width: 256px; height: 256px;" />
<h2>@META_APP_NAME@</h2>
<p>@META_APP_VERSION@</p>
<p style="margin: 25px 0px;">

View File

@ -92,7 +92,7 @@
};
this.socket.onerror = function(event) {
repoindex.pageManager.addError("Connecting to server failed: " + event.reason);
repoindex.pageManager.addError("Connecting to server failed.");
repoindex.pageManager.setConnectionStatus(repoindex.ConnectionStatus.Disconnected);
};
};
@ -514,11 +514,14 @@
};
// create a global client used within the entire document
repoindex.client = new Client((window.location.protocol === "https:" ? "wss://" : "ws://") + document.domain + ":1234");
repoindex.client = new Client((window.location.protocol === "https:" ? "wss://" : "ws://") + (window.location.protocol === "file:" ? "localhost" : document.domain) + ":1234");
if(!repoindex.isLoopback(document.domain)) {
// hide stop button if server is not on loopback interface
document.getElementById("nav_server").style.display = "none";
}
if(window.location.protocol === "file:") {
document.getElementById("nav_connect").style.display = "none";
}
return repoindex;

View File

@ -86,6 +86,8 @@
repoArray.push({index: entry.info.index, name: entry.info.name});
}
entriesRequired = true;
} else {
entry.updateTableRow();
}
}, mgr.filteredEntries.length);
var updateTableRows = function() {

View File

@ -37,7 +37,9 @@
return false;
};
this.link.appendChild(document.createTextNode(repoName));
this.link.title = repoInfo.desc;
if(repoInfo.desc) {
this.link.title = repoInfo.desc;
}
// use Bootstrap tooltip
this.link.setAttribute("data-placement", "bottom");