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 alpm/packagefinder.h
) )
set(SRC_FILES set(SRC_FILES
main.cpp
alpm/manager.cpp alpm/manager.cpp
alpm/package.cpp alpm/package.cpp
alpm/utilities.cpp alpm/utilities.cpp
@ -44,6 +43,21 @@ set(SRC_FILES
network/userrepository.cpp network/userrepository.cpp
network/networkaccessmanager.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 set(WEB_FILES
web/3rdparty/bootstrap/css/bootstrap-theme.min.css web/3rdparty/bootstrap/css/bootstrap-theme.min.css
web/3rdparty/bootstrap/css/bootstrap.min.css web/3rdparty/bootstrap/css/bootstrap.min.css
@ -71,13 +85,13 @@ set(WEB_FILES
# meta data # meta data
set(META_PROJECT_NAME repoindex) 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_AUTHOR "Martchus")
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}") 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_MAJOR 0)
set(META_VERSION_MINOR 2) set(META_VERSION_MINOR 2)
set(META_VERSION_PATCH 1) set(META_VERSION_PATCH 2)
# stringification of meta data # stringification of meta data
set(META_PROJECT_NAME_STR "\"${META_PROJECT_NAME}\"") set(META_PROJECT_NAME_STR "\"${META_PROJECT_NAME}\"")
@ -119,6 +133,9 @@ if(MINGW)
enable_language(RC) enable_language(RC)
endif(MINGW) endif(MINGW)
# read cached variables
set(WEBVIEW_PROVIDER "auto" CACHE STRING "specifies the webview provider: auto, webkit or webengine")
# check required Qt 5 modules # check required Qt 5 modules
find_package(Qt5Core REQUIRED) find_package(Qt5Core REQUIRED)
find_package(Qt5Concurrent REQUIRED) find_package(Qt5Concurrent REQUIRED)
@ -126,6 +143,36 @@ find_package(Qt5Network REQUIRED)
find_package(Qt5WebSockets REQUIRED) find_package(Qt5WebSockets REQUIRED)
find_package(KF5Archive 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 # enable moc
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
@ -134,18 +181,33 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_definitions( add_definitions(
-D_GLIBCXX_USE_CXX11_ABI=0 -D_GLIBCXX_USE_CXX11_ABI=0
-DCMAKE_BUILD -DCMAKE_BUILD
${WEBVIEW_DEFINITION}
) )
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_definitions(-DDEBUG_BUILD) add_definitions(-DDEBUG_BUILD)
message(STATUS "Debug build enabled.") message(STATUS "Debug build enabled.")
endif() endif()
# executable and linking # executable and linking
add_executable(${META_PROJECT_NAME} ${HEADER_FILES} ${SRC_FILES} ${WEB_FILES} ${RES_FILES}) add_library(${META_PROJECT_NAME}-lib SHARED ${HEADER_FILES} ${SRC_FILES} ${WEB_FILES})
target_link_libraries(${META_PROJECT_NAME} c++utilities Qt5::Core Qt5::Concurrent Qt5::Network Qt5::WebSockets KF5::Archive) 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 set_target_properties(${META_PROJECT_NAME} PROPERTIES
CXX_STANDARD 11 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 # add install target for web files / minimizing
# -> don't minimize debug builds # -> don't minimize debug builds
@ -211,6 +273,11 @@ foreach(WEB_FILE ${WEB_FILES})
) )
endif() endif()
endforeach() 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 # add target for minimizing
if(HTML_MIN_FILES) if(HTML_MIN_FILES)
add_custom_target(htmlmin ALL DEPENDS ${HTML_MIN_FILES}) add_custom_target(htmlmin ALL DEPENDS ${HTML_MIN_FILES})
@ -220,10 +287,32 @@ if(JS_MIN_FILES)
endif() endif()
# add install target # 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} install(TARGETS ${META_PROJECT_NAME}
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
COMPONENT binary 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 install(FILES resources/systemd/${META_PROJECT_NAME}.service
DESTINATION lib/systemd/system DESTINATION lib/systemd/system
COMPONENT service COMPONENT service
@ -232,18 +321,45 @@ install(FILES resources/settings/${META_PROJECT_NAME}.conf.js
DESTINATION share/${META_PROJECT_NAME}/skel DESTINATION share/${META_PROJECT_NAME}/skel
COMPONENT config 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) if(NOT TARGET install-binary)
add_custom_target(install-binary add_custom_target(install-binary
DEPENDS ${META_PROJECT_NAME} DEPENDS ${META_PROJECT_NAME}
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=binary -P "${CMAKE_BINARY_DIR}/cmake_install.cmake" COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=binary -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
) )
endif() 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) if(NOT TARGET install-service)
add_custom_target(install-service add_custom_target(install-service
DEPENDS ${META_PROJECT_NAME} DEPENDS ${META_PROJECT_NAME}
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=service -P "${CMAKE_BINARY_DIR}/cmake_install.cmake" COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=service -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
) )
endif() 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) if(NOT TARGET install-config)
add_custom_target(install-config add_custom_target(install-config
DEPENDS ${META_PROJECT_NAME} DEPENDS ${META_PROJECT_NAME}
@ -258,7 +374,7 @@ if(NOT TARGET install-web)
endif() endif()
if(NOT TARGET install-mingw-w64) if(NOT TARGET install-mingw-w64)
add_custom_target(install-mingw-w64 add_custom_target(install-mingw-w64
DEPENDS install-binary DEPENDS install-binary ${GUI_INSTALL_TARGET} install-header
) )
endif() endif()
if(NOT TARGET install-binary-strip) if(NOT TARGET install-binary-strip)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -229,6 +229,7 @@ public:
PackageOrigin origin() const; PackageOrigin origin() const;
Repository *repository() const; Repository *repository() const;
ChronoUtilities::DateTime timeStamp() const; ChronoUtilities::DateTime timeStamp() const;
void setTimeStamp(ChronoUtilities::DateTime timeStamp);
bool hasGeneralInfo() const; bool hasGeneralInfo() const;
bool hasAllGeneralInfo() const; bool hasAllGeneralInfo() const;
const QString &name() const; const QString &name() const;
@ -427,6 +428,14 @@ inline ChronoUtilities::DateTime Package::timeStamp() const
return m_timeStamp; 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. * \brief Returns whether general information is available for the package.
*/ */

View File

@ -73,6 +73,12 @@ const QStringList Repository::packageNames() const
return names; 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() void Repository::updateGroups()
{ {
m_groups.clear(); m_groups.clear();
@ -101,9 +107,12 @@ PackageLoader *Repository::init()
{ {
addBusyFlag(); addBusyFlag();
QWriteLocker locker(lock()); QWriteLocker locker(lock());
// wipe current packages
wipePackages();
if(PackageLoader *loader = internalInit()) { if(PackageLoader *loader = internalInit()) {
if(loader->future().isRunning()) { if(loader->future().isRunning()) {
auto watcher = new QFutureWatcher<void>; 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::removeBusyFlag);
connect(watcher, &QFutureWatcher<void>::finished, this, &Repository::initialized); connect(watcher, &QFutureWatcher<void>::finished, this, &Repository::initialized);
connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater); connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater);
@ -111,20 +120,30 @@ PackageLoader *Repository::init()
} }
return loader; return loader;
} else { } else {
updateGroups();
removeBusyFlag();
return nullptr; return nullptr;
} }
} }
void Repository::initAsSoonAsPossible() 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()) { if(isBusy()) {
auto connection = make_shared<QMetaObject::Connection>(); auto connection = make_shared<QMetaObject::Connection>();
*connection = connect(this, &Repository::available, [connection, this] { *connection = connect(this, &Repository::available, [connection, operation] {
disconnect(*connection); disconnect(*connection);
init(); operation();
}); });
} else { } else {
init(); operation();
} }
} }
@ -283,30 +302,6 @@ QList<Package *> Repository::packageByFilter(std::function<bool (const Package *
* \cond * \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 class ComputeRequired
{ {
public: public:
@ -555,7 +550,7 @@ void Repository::restoreFromCacheStream(QDataStream &in, bool skipOutdated)
quint32 packageCount; quint32 packageCount;
in >> packageCount; in >> packageCount;
bool good = true; 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) { for(quint32 i = 0; i < packageCount && good && in.status() == QDataStream::Ok; ++i) {
if(auto package = emptyPackage()) { if(auto package = emptyPackage()) {
package->restoreFromCacheStream(in); package->restoreFromCacheStream(in);
@ -632,7 +627,7 @@ void Repository::cleanOutdatedPackages()
if(maxPackageAge().isInfinity()) { if(maxPackageAge().isInfinity()) {
return; return;
} }
auto now = DateTime::now(); auto now = DateTime::gmtNow();
for(auto i = m_packages.begin(); i != m_packages.end(); ) { for(auto i = m_packages.begin(); i != m_packages.end(); ) {
const Package &pkg = *i->second; const Package &pkg = *i->second;
if((now - pkg.timeStamp()) > maxPackageAge()) { if((now - pkg.timeStamp()) > maxPackageAge()) {
@ -652,10 +647,9 @@ bool Repository::hasOutdatedPackages()
if(maxPackageAge().isInfinity()) { if(maxPackageAge().isInfinity()) {
return false; return false;
} }
auto now = DateTime::now(); auto now = DateTime::gmtNow();
for(auto i = m_packages.begin(); i != m_packages.end(); ) { for(const auto &pkgEntry : m_packages) {
const Package &pkg = *i->second; if((now - pkgEntry.second->timeStamp()) > maxPackageAge()) {
if((now - pkg.timeStamp()) > maxPackageAge()) {
return true; 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 * \returns Returns the added/updated packages. In the case of a split package more then
* one package is returned. * one package is returned.
*/ */
QList<Package *> Repository::addPackagesFromSrcInfo(const QByteArray &srcInfo) QList<Package *> Repository::addPackagesFromSrcInfo(const QByteArray &srcInfo, ChronoUtilities::DateTime timeStamp)
{ {
// define states // define states
enum { enum {
@ -965,6 +959,7 @@ QList<Package *> Repository::addPackagesFromSrcInfo(const QByteArray &srcInfo)
pkg = emptyPackage(); pkg = emptyPackage();
} }
currentPackage = pkg.get(); currentPackage = pkg.get();
currentPackage->setTimeStamp(timeStamp);
packageInfo.clear(); packageInfo.clear();
} }
// add field to ... // 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. * - 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. * \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 // parse fields
QList<QPair<QString, QStringList> > fields; QList<QPair<QString, QStringList> > fields;
@ -1021,6 +1016,7 @@ Package *Repository::addPackageFromDescription(QString name, const QList<QByteAr
// find/create package for description // find/create package for description
auto pkg = emptyPackage(); auto pkg = emptyPackage();
Package *pkgRawPtr = pkg.get(); Package *pkgRawPtr = pkg.get();
pkgRawPtr->setTimeStamp(timeStamp);
pkgRawPtr->putDescription(name, fields, origin); pkgRawPtr->putDescription(name, fields, origin);
{ {
QWriteLocker locker(&m_lock); QWriteLocker locker(&m_lock);

View File

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

View File

@ -15,8 +15,6 @@
#include <iostream> #include <iostream>
#include <algorithm> #include <algorithm>
#include <QReadWriteLock>
using namespace std; using namespace std;
using namespace ApplicationUtilities; using namespace ApplicationUtilities;
using namespace RepoIndex; 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 what = obj.value(QStringLiteral("w")).toString();
const auto id = obj.value(QStringLiteral("id")); const auto id = obj.value(QStringLiteral("id"));
if(what == QLatin1String("stop")) { 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; cerr << shchar << "Info: Server stopped via web interface." << endl;
QCoreApplication::quit(); QCoreApplication::quit();
} else { } else {

View File

@ -55,6 +55,7 @@ void AurPackageReply::processData(QNetworkReply *reply)
QJsonParseError error; QJsonParseError error;
const auto doc = QJsonDocument::fromJson(reply->readAll(), &error); const auto doc = QJsonDocument::fromJson(reply->readAll(), &error);
if(error.error == QJsonParseError::NoError) { if(error.error == QJsonParseError::NoError) {
auto now = DateTime::gmtNow();
QWriteLocker locker(m_repo->lock()); QWriteLocker locker(m_repo->lock());
auto &packages = m_repo->packages(); auto &packages = m_repo->packages();
for(const auto &result : doc.object().value(QStringLiteral("results")).toArray()) { for(const auto &result : doc.object().value(QStringLiteral("results")).toArray()) {
@ -67,6 +68,7 @@ void AurPackageReply::processData(QNetworkReply *reply)
} else { } else {
package = make_unique<AurPackage>(obj, static_cast<UserRepository *>(m_userRepo)); package = make_unique<AurPackage>(obj, static_cast<UserRepository *>(m_userRepo));
} }
package->setTimeStamp(now);
} }
} }
} else { } else {
@ -113,7 +115,7 @@ void AurFullPackageReply::processData(QNetworkReply *reply)
if(srcInfoEntry && srcInfoEntry->isFile()) { if(srcInfoEntry && srcInfoEntry->isFile()) {
const auto srcInfo = static_cast<const KArchiveFile *>(srcInfoEntry)->data(); const auto srcInfo = static_cast<const KArchiveFile *>(srcInfoEntry)->data();
QWriteLocker locker(m_userRepo->lock()); QWriteLocker locker(m_userRepo->lock());
const auto packages = m_userRepo->addPackagesFromSrcInfo(srcInfo); const auto packages = m_userRepo->addPackagesFromSrcInfo(srcInfo, DateTime::gmtNow());
// TODO: error handling // TODO: error handling
for(const auto &entryName : baseDir->entries()) { for(const auto &entryName : baseDir->entries()) {
if(entryName != QLatin1String(".SRCINFO")) { 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": { "repos": {
"localEnabled": true,
"fromPacmanConfig": true, "fromPacmanConfig": true,
"add": [ "//add": [
{"name": "examplerepo", {"name": "examplerepo",
"maxAge": 3600
"dataBaseFile": "path/to/database/file", "dataBaseFile": "path/to/database/file",
"sourcesDir": "path/to/local/source/dir", "sourcesDir": "path/to/local/source/dir",
"packagesDir": "path/to/local/pkg/dir", "packagesDir": "path/to/local/pkg/dir",

View File

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

View File

@ -229,6 +229,7 @@
<h4 class="modal-title">About</h4> <h4 class="modal-title">About</h4>
</div> </div>
<div class="modal-body" style="text-align: center;"> <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> <h2>@META_APP_NAME@</h2>
<p>@META_APP_VERSION@</p> <p>@META_APP_VERSION@</p>
<p style="margin: 25px 0px;"> <p style="margin: 25px 0px;">

View File

@ -92,7 +92,7 @@
}; };
this.socket.onerror = function(event) { 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); repoindex.pageManager.setConnectionStatus(repoindex.ConnectionStatus.Disconnected);
}; };
}; };
@ -514,11 +514,14 @@
}; };
// create a global client used within the entire document // 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)) { if(!repoindex.isLoopback(document.domain)) {
// hide stop button if server is not on loopback interface // hide stop button if server is not on loopback interface
document.getElementById("nav_server").style.display = "none"; document.getElementById("nav_server").style.display = "none";
} }
if(window.location.protocol === "file:") {
document.getElementById("nav_connect").style.display = "none";
}
return repoindex; return repoindex;

View File

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

View File

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