diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..75c107bcc90038bcdd0d643b20eb4d33a13b4245 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pro.user diff --git a/Makefile b/Makefile deleted file mode 100644 index 9665ad052a0e094cef3f3e0ee6fec3c4b509e5e5..0000000000000000000000000000000000000000 --- a/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -SHELL = /bin/sh - -TARGET = xdgurl -srcdir = ./src - -DESTDIR = -prefix = /usr/local -exec_prefix = $(prefix) -bindir = $(exec_prefix)/bin -datadir = $(prefix)/share - -INSTALL = install -INSTALL_PROGRAM = $(INSTALL) -D -m 755 -INSTALL_DATA = $(INSTALL) -D -m 644 -RM = rm -f - -.PHONY: all rebuild build clean install uninstall - -all: rebuild ; - -rebuild: clean build ; - -build: $(TARGET) ; - -clean: - $(RM) ./$(TARGET) - -install: build - $(INSTALL_PROGRAM) ./$(TARGET) $(DESTDIR)$(bindir)/$(TARGET) - $(INSTALL_DATA) $(srcdir)/desktop/$(TARGET).desktop $(DESTDIR)$(datadir)/applications/$(TARGET).desktop - $(INSTALL_DATA) $(srcdir)/desktop/$(TARGET).svg $(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/$(TARGET).svg - -uninstall: - $(RM) $(DESTDIR)$(bindir)/$(TARGET) - $(RM) $(DESTDIR)$(datadir)/applications/$(TARGET).desktop - $(RM) $(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/$(TARGET).svg - -$(TARGET): - # Just copy for now - install -m 755 $(srcdir)/$(TARGET).py ./$(TARGET) diff --git a/deployment.pri b/deployment.pri new file mode 100644 index 0000000000000000000000000000000000000000..30f39b844554120225fee3e8c25b9c9c2d8ef43d --- /dev/null +++ b/deployment.pri @@ -0,0 +1,21 @@ +unix:!android { + isEmpty(PREFIX) { + PREFIX = /usr/local + } + + SRCDIR = src + BINDIR = $${PREFIX}/bin + DATADIR = $${PREFIX}/share + + target.path = $${BINDIR} + + desktop.files = $${SRCDIR}/desktop/$${TARGET}.desktop + desktop.path = $${DATADIR}/applications + + icon.files = $${SRCDIR}/desktop/$${TARGET}.svg + icon.path = $${DATADIR}/icons/hicolor/scalable/apps + + INSTALLS += target desktop icon +} + +export(INSTALLS) diff --git a/pkg/arch/PKGBUILD b/pkg/arch/PKGBUILD index 64cd7ff4467d045026c3930c9b661dc6a3d8b806..03bce6c9071c40d0f474dea634407d4a6234ff58 100644 --- a/pkg/arch/PKGBUILD +++ b/pkg/arch/PKGBUILD @@ -7,7 +7,7 @@ pkgdesc="An install helper program for desktop stuff." arch=('i686' 'x86_64') url="https://github.com/xdgurl/xdgurl" license=('GPL3') -depends=('tk') +depends=('qt5-base>=5.3.0' 'qt5-declarative>=5.3.0' 'qt5-svg>=5.3.0' 'qt5-quickcontrols>=5.3.0') #source=("https://github.com/xdgurl/xdgurl/archive/release-$pkgver.tar.gz") source=("$pkgname.tar.gz") md5sums=() #autofill using updpkgsums @@ -15,11 +15,12 @@ md5sums=() #autofill using updpkgsums build() { #cd "$pkgname-release-$pkgver" cd $pkgname + qmake PREFIX="/usr" make } package() { #cd "$pkgname-release-$pkgver" cd $pkgname - make DESTDIR="$pkgdir" prefix="/usr" install + make INSTALL_ROOT="$pkgdir" install } diff --git a/pkg/build.sh b/pkg/build.sh index 6e6782b838c6da56fc7f0042c3df495ac4f2d32c..97f5d699ef099aa9c6212e5f5795580e2a898a39 100644 --- a/pkg/build.sh +++ b/pkg/build.sh @@ -5,7 +5,8 @@ cd `dirname $0` build_ubuntu() { mkdir ./build cp -r ../src ./build/ - cp ../Makefile ./build/ + cp ../*.pro ./build/ + cp ../*.pri ./build/ cp -r ./ubuntu/debian ./build/ cd ./build debuild -uc -us -b diff --git a/pkg/fedora/xdgurl.spec b/pkg/fedora/xdgurl.spec index 6efd32c9c1fb05cbc943dc5502527347024b8062..f986afd12f7e196ee233aa2144af85369dc9c57f 100644 --- a/pkg/fedora/xdgurl.spec +++ b/pkg/fedora/xdgurl.spec @@ -9,7 +9,7 @@ URL: https://github.com/xdgurl/xdgurl #Source0: https://github.com/xdgurl/xdgurl/archive/release-%{version}.tar.gz Source0: %{name}.tar.gz -Requires: tkinter, python3-tkinter +Requires: qt5-qtbase >= 5.3.0, qt5-qtsvg >= 5.3.0, qt5-qtdeclarative >= 5.3.0, qt5-qtquickcontrols >= 5.3.0 %description An install helper program for desktop stuff. @@ -20,10 +20,11 @@ An install helper program for desktop stuff. %build %define debug_package %{nil} +qmake PREFIX="/usr" make %install -make DESTDIR="%{buildroot}" prefix="/usr" install +make INSTALL_ROOT="%{buildroot}" install %files %defattr(-,root,root) diff --git a/pkg/ubuntu/debian/control b/pkg/ubuntu/debian/control index 68a1d227a8200f93b03a4cdabd1a5422b5dc9bf1..e98ffa95b6a78afe5040a2c4428d91e019de3a99 100644 --- a/pkg/ubuntu/debian/control +++ b/pkg/ubuntu/debian/control @@ -2,11 +2,11 @@ Source: xdgurl Section: web Priority: optional Maintainer: Akira Ohgaki <akiraohgaki@gmail.com> -Build-Depends: build-essential (>= 11), devscripts (>= 2.14), debhelper (>= 9), fakeroot (>= 1.20) +Build-Depends: build-essential (>= 11), qt5-default (>= 5.3.0), qtdeclarative5-dev (>= 5.3.0), libqt5svg5-dev (>= 5.3.0), devscripts (>= 2.14), debhelper (>= 9), fakeroot (>= 1.20) Standards-Version: 3.9.4 Package: xdgurl Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, python-tk (>= 2.7.0), python3-tk (>= 3.2.0) +Depends: ${shlibs:Depends}, ${misc:Depends}, libqt5svg5 (>= 5.3.0), qtdeclarative5-qtquick2-plugin (>= 5.3.0), qtdeclarative5-window-plugin (>= 5.3.0), qtdeclarative5-controls-plugin (>= 5.3.0), qtdeclarative5-dialogs-plugin (>= 5.3.0) Description: xdgurl An install helper program for desktop stuff. diff --git a/pkg/ubuntu/debian/rules b/pkg/ubuntu/debian/rules index 999ca813027742773a158ff28651d583379299ac..c974694ac22c9622a4a93945ffd7400c36f0bae3 100755 --- a/pkg/ubuntu/debian/rules +++ b/pkg/ubuntu/debian/rules @@ -3,8 +3,8 @@ %: dh $@ -override_dh_auto_install: - make DESTDIR="$(CURDIR)/debian/tmp" prefix="/usr" install +override_dh_auto_configure: + qmake PREFIX="/usr" -override_dh_shlibdeps: - # ignore +override_dh_auto_install: + make INSTALL_ROOT="$(CURDIR)/debian/tmp" install diff --git a/rpath.pri b/rpath.pri new file mode 100644 index 0000000000000000000000000000000000000000..b3cd65e83d8e1df1c1d7bc94254dc152a2a91226 --- /dev/null +++ b/rpath.pri @@ -0,0 +1,5 @@ +unix:!android { + QMAKE_LFLAGS += -Wl,-rpath=\\\$\$ORIGIN + QMAKE_LFLAGS += -Wl,-rpath=/usr/lib/$${TARGET} + QMAKE_RPATH = +} diff --git a/src/configs.qrc b/src/configs.qrc new file mode 100644 index 0000000000000000000000000000000000000000..4d5da4c70cb63857859e5a7b17e299989b48d182 --- /dev/null +++ b/src/configs.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/"> + <file>configs/application.json</file> + <file>configs/destinations.json</file> + <file>configs/destinations_alias.json</file> + </qresource> +</RCC> diff --git a/src/configs/application.json b/src/configs/application.json new file mode 100644 index 0000000000000000000000000000000000000000..0b5e4aaf3f1d9553527921bd8d40353f65db3140 --- /dev/null +++ b/src/configs/application.json @@ -0,0 +1,13 @@ +{ + "id": "xdgurl", + "name": "xdgurl", + "version": "1.1.0", + "organization": "xdgurl", + "domain": "com.xdgurl.xdgurl", + "icon": ":/desktop/xdgurl.svg", + "description": "An install helper program for desktop stuff.", + "license": "GPL-3+", + "author": "Akira Ohgaki", + "contact": "akiraohgaki@gmail.com", + "homepage": "https://github.com/xdgurl/xdgurl" +} diff --git a/src/configs/destinations.json b/src/configs/destinations.json new file mode 100644 index 0000000000000000000000000000000000000000..65e2c3e3e2b22ed99a268c112699100da37e4ccc --- /dev/null +++ b/src/configs/destinations.json @@ -0,0 +1,37 @@ +{ + "downloads": "$HOME/Downloads", + "documents": "$HOME/Documents", + "pictures": "$HOME/Pictures", + "music": "$HOME/Music", + "videos": "$HOME/Videos", + "wallpapers": "$XDG_DATA_HOME/wallpapers", + "fonts": "$HOME/.fonts", + "cursors": "$HOME/.icons", + "icons": "$XDG_DATA_HOME/icons", + "emoticons": "$XDG_DATA_HOME/emoticons", + "themes": "$HOME/.themes", + "emerald_themes": "$HOME/.emerald/themes", + "enlightenment_themes": "$HOME/.e/e/themes", + "enlightenment_backgrounds": "$HOME/.e/e/backgrounds", + "fluxbox_styles": "$HOME/.fluxbox/styles", + "pekwm_themes": "$HOME/.pekwm/themes", + "icewm_themes": "$HOME/.icewm/themes", + "plasma_plasmoids": "$XDG_DATA_HOME/plasma/plasmoids", + "plasma_look_and_feel": "$XDG_DATA_HOME/plasma/look-and-feel", + "plasma_desktopthemes": "$XDG_DATA_HOME/plasma/desktoptheme", + "kwin_effects": "$XDG_DATA_HOME/kwin/effects", + "kwin_scripts": "$XDG_DATA_HOME/kwin/scripts", + "kwin_tabbox": "$XDG_DATA_HOME/kwin/tabbox", + "aurorae_themes": "$XDG_DATA_HOME/aurorae/themes", + "dekorator_themes": "$XDG_DATA_HOME/deKorator/themes", + "qtcurve": "$XDG_DATA_HOME/QtCurve", + "color_schemes": "$XDG_DATA_HOME/color-schemes", + "gnome_shell_extensions": "$XDG_DATA_HOME/gnome-shell/extensions", + "cinnamon_applets": "$XDG_DATA_HOME/cinnamon/applets", + "cinnamon_desklets": "$XDG_DATA_HOME/cinnamon/desklets", + "cinnamon_extensions": "$XDG_DATA_HOME/cinnamon/extensions", + "nautilus_scripts": "$XDG_DATA_HOME/nautilus/scripts", + "amarok_scripts": "$KDEHOME/share/apps/amarok/scripts", + "yakuake_skins": "$KDEHOME/share/apps/yakuake/skins", + "cairo_clock_themes": "$HOME/.cairo-clock/themes" +} diff --git a/src/configs/destinations_alias.json b/src/configs/destinations_alias.json new file mode 100644 index 0000000000000000000000000000000000000000..438fb4dc92cf31716b7741064141b8a10a83f20d --- /dev/null +++ b/src/configs/destinations_alias.json @@ -0,0 +1,17 @@ +{ + "gnome_shell_themes": "themes", + "cinnamon_themes": "themes", + "gtk2_themes": "themes", + "gtk3_themes": "themes", + "metacity_themes": "themes", + "xfwm4_themes": "themes", + "openbox_themes": "themes", + "kvantum_themes": "themes", + "compiz_themes": "emerald_themes", + "beryl_themes": "emerald_themes", + "plasma4_plasmoids": "plasma_plasmoids", + "plasma5_plasmoids": "plasma_plasmoids", + "plasma5_look_and_feel": "plasma_look_and_feel", + "plasma5_desktopthemes": "plasma_desktopthemes", + "plasma_color_schemes": "color_schemes" +} diff --git a/src/core/config.cpp b/src/core/config.cpp new file mode 100644 index 0000000000000000000000000000000000000000..49b3b99a7e918765e7957aebcd2307577bbbcb68 --- /dev/null +++ b/src/core/config.cpp @@ -0,0 +1,39 @@ +#include "../utility/file.h" +#include "../utility/json.h" + +#include "config.h" + +namespace Core { + +Config::Config(const QString &configsDir, QObject *parent) : + QObject(parent), _configsDir(configsDir) +{} + +QJsonObject Config::get(const QString &name) +{ + QString configFile = _configsDir + "/" + name + ".json"; + + if (!_cacheData.contains(name)) { + QString json = Utility::File::readText(configFile); + if (json.isEmpty()) { + json = "{}"; // Blank JSON data as default + } + _cacheData[name] = Utility::Json::convertStrToObj(json); + } + return _cacheData[name].toObject(); +} + +bool Config::set(const QString &name, const QJsonObject &jsonObj) +{ + QString configFile = _configsDir + "/" + name + ".json"; + QString json = Utility::Json::convertObjToStr(jsonObj); + + Utility::File::makeDir(_configsDir); + if (Utility::File::writeText(configFile, json)) { + _cacheData[name] = jsonObj; + return true; + } + return false; +} + +} // namespace Core diff --git a/src/core/config.h b/src/core/config.h new file mode 100644 index 0000000000000000000000000000000000000000..1a4ae9a70ef43f566421155f2eceb80d4a34fe89 --- /dev/null +++ b/src/core/config.h @@ -0,0 +1,26 @@ +#ifndef CORE_CONFIG_H +#define CORE_CONFIG_H + +#include <QObject> +#include <QJsonObject> + +namespace Core { + +class Config : public QObject +{ + Q_OBJECT + +private: + QString _configsDir; + QJsonObject _cacheData; + +public: + explicit Config(const QString &configsDir, QObject *parent = 0); + + QJsonObject get(const QString &name); + bool set(const QString &name, const QJsonObject &jsonObj); +}; + +} // namespace Core + +#endif // CORE_CONFIG_H diff --git a/src/core/network.cpp b/src/core/network.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6d040d2939477f7020befaa23b6ae50a58d19b28 --- /dev/null +++ b/src/core/network.cpp @@ -0,0 +1,65 @@ +#include <QEventLoop> +#include <QNetworkAccessManager> +#include <QNetworkRequest> +#include <QNetworkReply> + +#include "network.h" + +namespace Core { + +Network::Network(const bool &async, QObject *parent) : + QObject(parent), _async(async) +{ + _manager = new QNetworkAccessManager(); + connect(_manager, &QNetworkAccessManager::finished, + this, &Network::_finished); + if (!_async) { + _eventLoop = new QEventLoop(); + connect(_manager, &QNetworkAccessManager::finished, + _eventLoop, &QEventLoop::quit); + } +} + +Network::~Network() +{ + _manager->deleteLater(); + if (!_async) { + delete _eventLoop; + } +} + +QNetworkReply *Network::head(const QUrl &uri) +{ + QNetworkReply *reply = _manager->head(QNetworkRequest(uri)); + if (!_async) { + _eventLoop->exec(); + } + return reply; +} + +QNetworkReply *Network::get(const QUrl &uri) +{ + QNetworkReply *reply = _manager->get(QNetworkRequest(uri)); + connect(reply, &QNetworkReply::downloadProgress, + this, &Network::_downloadProgress); + if (!_async) { + _eventLoop->exec(); + } + return reply; +} + +/** + * Private slots + */ + +void Network::_finished(QNetworkReply *reply) +{ + emit finished(reply); +} + +void Network::_downloadProgress(const qint64 &received, const qint64 &total) +{ + emit downloadProgress(received, total); +} + +} // namespace Core diff --git a/src/core/network.h b/src/core/network.h new file mode 100644 index 0000000000000000000000000000000000000000..374310c5ec00eea27e3d0e06c81a8a94eaae132e --- /dev/null +++ b/src/core/network.h @@ -0,0 +1,39 @@ +#ifndef CORE_NETWORK_H +#define CORE_NETWORK_H + +#include <QObject> + +class QEventLoop; +class QNetworkAccessManager; +class QNetworkReply; + +namespace Core { + +class Network : public QObject +{ + Q_OBJECT + +private: + bool _async; + QNetworkAccessManager *_manager; + QEventLoop *_eventLoop; + +public: + explicit Network(const bool &async = true, QObject *parent = 0); + ~Network(); + + QNetworkReply *head(const QUrl &uri); + QNetworkReply *get(const QUrl &uri); + +private slots: + void _finished(QNetworkReply *reply); + void _downloadProgress(const qint64 &received, const qint64 &total); + +signals: + void finished(QNetworkReply *reply); + void downloadProgress(const qint64 &received, const qint64 &total); +}; + +} // namespace Core + +#endif // CORE_NETWORK_H diff --git a/src/desktop.qrc b/src/desktop.qrc new file mode 100644 index 0000000000000000000000000000000000000000..929b2f8ae623c418161f652dceecb0c340ca2a2c --- /dev/null +++ b/src/desktop.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>desktop/xdgurl.svg</file> + </qresource> +</RCC> diff --git a/src/handlers/xdgurl.cpp b/src/handlers/xdgurl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8391558febb0c7229035317fc256caf78f539a38 --- /dev/null +++ b/src/handlers/xdgurl.cpp @@ -0,0 +1,294 @@ +#include <QUrl> +#include <QUrlQuery> +#include <QTemporaryFile> +#include <QMimeDatabase> +#include <QNetworkReply> + +#include "../core/config.h" +#include "../core/network.h" +#include "../utility/file.h" +#include "../utility/json.h" +#include "../utility/package.h" + +#include "xdgurl.h" + +namespace Handlers { + +XdgUrl::XdgUrl(const QString &xdgUrl, Core::Config *config, Core::Network *network, QObject *parent) : + QObject(parent), _xdgUrl(xdgUrl), _config(config), _network(network) +{ + _parse(); + _loadDestinations(); + + connect(_network, &Core::Network::finished, this, &XdgUrl::_downloaded); + connect(_network, &Core::Network::downloadProgress, this, &XdgUrl::downloadProgress); +} + +void XdgUrl::_parse() +{ + QUrl url(_xdgUrl); + QUrlQuery query(url); + + _metadata["scheme"] = QString("xdg"); + _metadata["command"] = QString("download"); + _metadata["url"] = QString(""); + _metadata["type"] = QString("downloads"); + _metadata["filename"] = QString(""); + + if (!url.scheme().isEmpty()) { + _metadata["scheme"] = url.scheme(); + } + + if (!url.host().isEmpty()) { + _metadata["command"] = url.host(); + } + + if (query.hasQueryItem("url") && !query.queryItemValue("url").isEmpty()) { + _metadata["url"] = query.queryItemValue("url", QUrl::FullyDecoded); + } + + if (query.hasQueryItem("type") && !query.queryItemValue("type").isEmpty()) { + _metadata["type"] = query.queryItemValue("type", QUrl::FullyDecoded); + } + + if (query.hasQueryItem("filename") && !query.queryItemValue("filename").isEmpty()) { + _metadata["filename"] = QUrl(query.queryItemValue("filename", QUrl::FullyDecoded)).fileName(); + } + + if (!_metadata["url"].toString().isEmpty() && _metadata["filename"].toString().isEmpty()) { + _metadata["filename"] = QUrl(_metadata["url"].toString()).fileName(); + } +} + +void XdgUrl::_loadDestinations() +{ + QJsonObject configDestinations = _config->get("destinations"); + QJsonObject configDestinationsAlias = _config->get("destinations_alias"); + + foreach (const QString key, configDestinations.keys()) { + _destinations[key] = _convertPathString(configDestinations[key].toString()); + } + + foreach (const QString key, configDestinationsAlias.keys()) { + QString value = configDestinationsAlias[key].toString(); + if (_destinations.contains(value)) { + _destinations[key] = _destinations.value(value); + } + } +} + +QString XdgUrl::_convertPathString(const QString &path) +{ + QString newPath = path; + + if (newPath.contains("$HOME")) { + newPath.replace("$HOME", Utility::File::homePath()); + } + else if (newPath.contains("$XDG_DATA_HOME")) { + newPath.replace("$XDG_DATA_HOME", Utility::File::xdgDataHomePath()); + } + else if (newPath.contains("$KDEHOME")) { + newPath.replace("$KDEHOME", Utility::File::kdehomePath()); + } + + return newPath; +} + +void XdgUrl::_saveDownloadedFile(QNetworkReply *reply) +{ + QJsonObject result; + + QTemporaryFile temporaryFile; + + if (!temporaryFile.open() || temporaryFile.write(reply->readAll()) == -1) { + result["status"] = QString("error_save"); + result["message"] = temporaryFile.errorString(); + emit error(Utility::Json::convertObjToStr(result)); + return; + } + + QMimeDatabase mimeDb; + QString mimeType = mimeDb.mimeTypeForFile(temporaryFile.fileName()).name(); + + if (mimeType == "text/html" || mimeType == "application/xhtml+xml") { + result["status"] = QString("error_filetype"); + result["message"] = QString("The file is unsupported file type " + mimeType); + emit error(Utility::Json::convertObjToStr(result)); + return; + } + + QString type = _metadata["type"].toString(); + QString destination = _destinations[type].toString(); + QString path = destination + "/" + _metadata["filename"].toString(); + + Utility::File::makeDir(destination); + Utility::File::remove(path); // Remove previous downloaded file + + if (!temporaryFile.copy(path)) { + result["status"] = QString("error_save"); + result["message"] = temporaryFile.errorString(); + emit error(Utility::Json::convertObjToStr(result)); + return; + } + + result["status"] = QString("success_download"); + result["message"] = QString("The file has been stored into " + destination); + emit finished(Utility::Json::convertObjToStr(result)); +} + +void XdgUrl::_installDownloadedFile(QNetworkReply *reply) +{ + QJsonObject result; + + QTemporaryFile temporaryFile; + + if (!temporaryFile.open() || temporaryFile.write(reply->readAll()) == -1) { + result["status"] = QString("error_save"); + result["message"] = temporaryFile.errorString(); + emit error(Utility::Json::convertObjToStr(result)); + return; + } + + QMimeDatabase mimeDb; + QString mimeType = mimeDb.mimeTypeForFile(temporaryFile.fileName()).name(); + + if (mimeType == "text/html" || mimeType == "application/xhtml+xml") { + result["status"] = QString("error_filetype"); + result["message"] = QString("The file is unsupported file type " + mimeType); + emit error(Utility::Json::convertObjToStr(result)); + return; + } + + QString type = _metadata["type"].toString(); + QString destination = _destinations[type].toString(); + QString path = destination + "/" + _metadata["filename"].toString(); + + Utility::File::makeDir(destination); + Utility::File::remove(path); // Remove previous downloaded file + + if ((type == "plasma_plasmoids" || type == "plasma4_plasmoids" || type == "plasma5_plasmoids") + && Utility::Package::installPlasmapkg(temporaryFile.fileName(), "plasmoid")) { + result["message"] = QString("The plasmoid has been installed"); + } + else if ((type == "plasma_look_and_feel" || type == "plasma5_look_and_feel") + && Utility::Package::installPlasmapkg(temporaryFile.fileName(), "lookandfeel")) { + result["message"] = QString("The plasma look and feel has been installed"); + } + else if ((type == "plasma_desktopthemes" || type == "plasma5_desktopthemes") + && Utility::Package::installPlasmapkg(temporaryFile.fileName(), "theme")) { + result["message"] = QString("The plasma desktop theme has been installed"); + } + else if (type == "kwin_effects" + && Utility::Package::installPlasmapkg(temporaryFile.fileName(), "kwineffect")) { + result["message"] = QString("The KWin effect has been installed"); + } + else if (type == "kwin_scripts" + && Utility::Package::installPlasmapkg(temporaryFile.fileName(), "kwinscript")) { + result["message"] = QString("The KWin script has been installed"); + } + else if (type == "kwin_tabbox" + && Utility::Package::installPlasmapkg(temporaryFile.fileName(), "windowswitcher")) { + result["message"] = QString("The KWin window switcher has been installed"); + } + else if (Utility::Package::uncompressArchive(temporaryFile.fileName(), destination)) { + result["message"] = QString("The archive file has been uncompressed into " + destination); + } + else if (temporaryFile.copy(path)) { + result["message"] = QString("The file has been stored into " + destination); + } + else { + result["status"] = QString("error_install"); + result["message"] = temporaryFile.errorString(); + emit error(Utility::Json::convertObjToStr(result)); + return; + } + + result["status"] = QString("success_install"); + emit finished(Utility::Json::convertObjToStr(result)); +} + +/** + * Private slots + */ + +void XdgUrl::_downloaded(QNetworkReply *reply) +{ + if (reply->error() != QNetworkReply::NoError) { + QJsonObject result; + result["status"] = QString("error_network"); + result["message"] = reply->errorString(); + emit error(Utility::Json::convertObjToStr(result)); + return; + } + + // If the network reply has a refresh header, retry download + if (reply->hasRawHeader("Refresh")) { + QString refreshUrl = QString(reply->rawHeader("Refresh")).split("url=").last(); + if (refreshUrl.startsWith("/")) { + refreshUrl = reply->url().authority() + refreshUrl; + } + _network->get(QUrl(refreshUrl)); + return; + } + + if (_metadata["command"].toString() == "download") { + _saveDownloadedFile(reply); + } + else if (_metadata["command"].toString() == "install") { + _installDownloadedFile(reply); + } +} + +/** + * Public slots + */ + +QString XdgUrl::getXdgUrl() +{ + return _xdgUrl; +} + +QString XdgUrl::getMetadata() +{ + return Utility::Json::convertObjToStr(_metadata); +} + +bool XdgUrl::isValid() +{ + QString scheme = _metadata["scheme"].toString(); + QString command = _metadata["command"].toString(); + QString url = _metadata["url"].toString(); + QString type = _metadata["type"].toString(); + QString filename = _metadata["filename"].toString(); + + if ((scheme == "xdg" || scheme == "xdgs") + && (command == "download" || command == "install") + && QUrl(url).isValid() + && _destinations.contains(type) + && !filename.isEmpty()) { + return true; + } + + return false; +} + +void XdgUrl::process() +{ + /** + * xdgs scheme is a reserved name, so the process of xdgs + * is the same process of the xdg scheme currently. + */ + + if (!isValid()) { + QJsonObject result; + result["status"] = QString("error_validation"); + result["message"] = QString("Invalid XDG-URL " + _xdgUrl); + emit error(Utility::Json::convertObjToStr(result)); + return; + } + + _network->get(QUrl(_metadata["url"].toString())); + emit started(); +} + +} // namespace Handlers diff --git a/src/handlers/xdgurl.h b/src/handlers/xdgurl.h new file mode 100644 index 0000000000000000000000000000000000000000..becb66906f6a9c0a0a93cda79390ac5db35b67a9 --- /dev/null +++ b/src/handlers/xdgurl.h @@ -0,0 +1,56 @@ +#ifndef HANDLERS_XDGURL_H +#define HANDLERS_XDGURL_H + +#include <QObject> +#include <QJsonObject> + +class QNetworkReply; + +namespace Core { +class Config; +class Network; +} + +namespace Handlers { + +class XdgUrl : public QObject +{ + Q_OBJECT + +private: + QString _xdgUrl; + Core::Config *_config; + Core::Network *_network; + + QJsonObject _metadata; + QJsonObject _destinations; + +public: + explicit XdgUrl(const QString &xdgUrl, Core::Config *config, Core::Network *network, QObject *parent = 0); + +private: + void _parse(); + void _loadDestinations(); + QString _convertPathString(const QString &path); + void _saveDownloadedFile(QNetworkReply *reply); + void _installDownloadedFile(QNetworkReply *reply); + +private slots: + void _downloaded(QNetworkReply *reply); + +public slots: + QString getXdgUrl(); + QString getMetadata(); + bool isValid(); + void process(); + +signals: + void started(); + void finished(const QString &result); + void error(const QString &result); + void downloadProgress(const qint64 &received, const qint64 &total); +}; + +} // namespace Handlers + +#endif // HANDLERS_XDGURL_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..64f2d6ef3d2e56c3916c60fc0087f9c0b31ca791 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,55 @@ +#include <QString> +#include <QStringList> +#include <QUrl> +#include <QJsonObject> +#include <QCommandLineParser> +#include <QCoreApplication> +#include <QGuiApplication> +#include <QIcon> +#include <QQmlApplicationEngine> +#include <QQmlContext> + +#include "core/config.h" +#include "core/network.h" +#include "handlers/xdgurl.h" + +int main(int argc, char *argv[]) +{ + // Init + //QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // Qt 5.6 or higher + QGuiApplication app(argc, argv); + Core::Config *config = new Core::Config(":/configs"); + Core::Network *network = new Core::Network(true); + + QJsonObject configApplication = config->get("application"); + + app.setApplicationName(configApplication["name"].toString()); + app.setApplicationVersion(configApplication["version"].toString()); + app.setOrganizationName(configApplication["organization"].toString()); + app.setOrganizationDomain(configApplication["domain"].toString()); + app.setWindowIcon(QIcon::fromTheme(configApplication["id"].toString(), QIcon(configApplication["icon"].toString()))); + + // Setup CLI + QCommandLineParser clParser; + clParser.setApplicationDescription(configApplication["description"].toString()); + clParser.addHelpOption(); + clParser.addVersionOption(); + clParser.addPositionalArgument("xdgurl", "XDG-URL"); + clParser.process(app); + + QStringList args = clParser.positionalArguments(); + + if (args.size() != 1) { + clParser.showHelp(1); + } + + QString xdgUrl = args.at(0); + + // Setup QML + QQmlApplicationEngine qmlAppEngine; + QQmlContext *qmlContext = qmlAppEngine.rootContext(); + qmlContext->setContextProperty("xdgUrlHandler", new Handlers::XdgUrl(xdgUrl, config, network)); + qmlAppEngine.load(QUrl("qrc:/qml/main.qml")); + + return app.exec(); +} diff --git a/src/qml.qrc b/src/qml.qrc new file mode 100644 index 0000000000000000000000000000000000000000..69145a822f1f6a3a133b38da279dab003922da12 --- /dev/null +++ b/src/qml.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>qml/main.qml</file> + </qresource> +</RCC> diff --git a/src/qml/main.qml b/src/qml/main.qml new file mode 100644 index 0000000000000000000000000000000000000000..6327dacb43d8b44a63aa64d52a6f62e631860370 --- /dev/null +++ b/src/qml/main.qml @@ -0,0 +1,194 @@ +import QtQuick 2.0 +import QtQuick.Window 2.0 +import QtQuick.Controls 1.1 +import QtQuick.Dialogs 1.2 + +Window { + id: root + title: Qt.application.name + width: 400 + height: 200 + minimumWidth: 400 + minimumHeight: 200 + maximumWidth: 800 + maximumHeight: 400 + + MessageDialog { + id: confirmDialog + title: root.title + icon: StandardIcon.Question + text: '' + informativeText: '' + detailedText: '' + standardButtons: StandardButton.Ok | StandardButton.Cancel + onAccepted: xdgUrlHandler.process() + onRejected: Qt.quit() + } + + MessageDialog { + id: infoDialog + title: root.title + icon: StandardIcon.Information + text: '' + informativeText: '' + detailedText: '' + standardButtons: StandardButton.Ok + onAccepted: Qt.quit() + } + + MessageDialog { + id: errorDialog + title: root.title + icon: StandardIcon.Warning + text: '' + informativeText: '' + detailedText: '' + standardButtons: StandardButton.Ok + onAccepted: Qt.quit() + } + + Dialog { + id: progressDialog + title: root.title + contentItem: Item { + implicitWidth: 400 + implicitHeight: 150 + Column { + anchors.fill: parent + anchors.margins: 12 + spacing: 8 + Label { + id: primaryLabel + text: '' + font.bold: true + } + Label { + id: informativeLabel + text: '' + } + ProgressBar { + id: progressBar + maximumValue: 1 + minimumValue: 0 + value: 0 + anchors.left: parent.left + anchors.right: parent.right + } + Label { + id: progressLabel + text: '' + anchors.right: parent.right + } + Button { + id: cancelButton + text: 'Cancel' + anchors.right: parent.right + onClicked: Qt.quit() + } + } + } + property alias primaryLabel: primaryLabel + property alias informativeLabel: informativeLabel + property alias progressBar: progressBar + property alias progressLabel: progressLabel + } + + Component.onCompleted: { + var metadata = JSON.parse(xdgUrlHandler.getMetadata()); + var primaryMessages = { + 'success_download': 'Download successfull', + 'success_install': 'Installation successfull', + 'error_validation': 'Validation error', + 'error_network': 'Network error', + 'error_filetype': 'File type error', + 'error_save': 'Saving file failed', + 'error_install': 'Installation failed' + }; + + xdgUrlHandler.started.connect(function() { + progressDialog.open(); + }); + + xdgUrlHandler.finished.connect(function(result) { + progressDialog.close(); + result = JSON.parse(result); + infoDialog.text = primaryMessages[result.status]; + infoDialog.informativeText = metadata.filename; + infoDialog.detailedText = result.message; + infoDialog.open(); + }); + + xdgUrlHandler.error.connect(function(result) { + progressDialog.close(); + result = JSON.parse(result); + errorDialog.text = primaryMessages[result.status]; + errorDialog.informativeText = metadata.filename; + errorDialog.detailedText = result.message; + errorDialog.open(); + }); + + xdgUrlHandler.downloadProgress.connect(function(received, total) { + progressDialog.primaryLabel.text = 'Downloading... '; + progressDialog.informativeLabel.text = metadata.filename; + progressDialog.progressBar.value = received / total; + progressDialog.progressLabel.text = convertByteToHumanReadable(received) + + ' / ' + convertByteToHumanReadable(total) + }); + + if (xdgUrlHandler.isValid()) { + confirmDialog.text = 'Do you want to ' + metadata.command + '?'; + confirmDialog.informativeText = metadata.filename; + confirmDialog.detailedText = 'URL: ' + metadata.url + '\n\n' + + 'File: ' + metadata.filename + '\n\n' + + 'Type: ' + metadata.type; + confirmDialog.open(); + } + else { + errorDialog.text = 'Validation error'; + errorDialog.detailedText = 'Invalid XDG-URL ' + xdgUrlHandler.getXdgUrl(); + errorDialog.open(); + } + } + + function convertByteToHumanReadable(bytes) { + bytes = parseFloat(bytes); + var kb = 1024; + var mb = 1024 * kb; + var gb = 1024 * mb; + var tb = 1024 * gb; + var pb = 1024 * tb; + var eb = 1024 * pb; + var zb = 1024 * eb; + var yb = 1024 * zb; + + var text = ''; + if (bytes < kb) { + text = bytes.toFixed(0) + ' B'; + } + else if (bytes < mb) { + text = (bytes / kb).toFixed(2) + ' KB'; + } + else if (bytes < gb) { + text = (bytes / mb).toFixed(2) + ' MB'; + } + else if (bytes < tb) { + text = (bytes / gb).toFixed(2) + ' GB'; + } + else if (bytes < pb) { + text = (bytes / tb).toFixed(2) + ' TB'; + } + else if (bytes < eb) { + text = (bytes / pb).toFixed(2) + ' PB'; + } + else if (bytes < zb) { + text = (bytes / eb).toFixed(2) + ' EB'; + } + else if (bytes < yb) { + text = (bytes / zb).toFixed(2) + ' ZB'; + } + else if (bytes >= yb) { + text = (bytes / yb).toFixed(2) + ' YB'; + } + return text; + } +} diff --git a/src/utility/file.cpp b/src/utility/file.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2330ba180e2bffe3779b1a2912631364fdf91eb4 --- /dev/null +++ b/src/utility/file.cpp @@ -0,0 +1,208 @@ +#include <QIODevice> +#include <QDir> +#include <QFile> +#include <QFileInfo> +#include <QTextStream> + +#include "file.h" + +namespace Utility { + +File::File(QObject *parent) : QObject(parent) +{} + +QString File::rootPath() +{ + return QDir::rootPath(); +} + +QString File::tempPath() +{ + return QDir::tempPath(); +} + +QString File::homePath() +{ + return QDir::homePath(); +} + +/** + * XDG Base Directory Specification + * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + */ + +QString File::xdgDataHomePath() +{ + QString path = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME").constData()); + if (path.isEmpty()) { + path = homePath() + "/.local/share"; + } + return path; +} + +QString File::xdgConfigHomePath() +{ + QString path = QString::fromLocal8Bit(qgetenv("XDG_CONFIG_HOME").constData()); + if (path.isEmpty()) { + path = homePath() + "/.config"; + } + return path; +} + +QString File::xdgCacheHomePath() +{ + QString path = QString::fromLocal8Bit(qgetenv("XDG_CACHE_HOME").constData()); + if (path.isEmpty()) { + path = homePath() + "/.cache"; + } + return path; +} + +/** + * KDE System Administration/Environment Variables + * https://userbase.kde.org/KDE_System_Administration/Environment_Variables + */ + +QString File::kdehomePath() +{ + // KDE 4 maybe uses $KDEHOME + QString path = QString::fromLocal8Bit(qgetenv("KDEHOME").constData()); + if (path.isEmpty()) { + path = homePath() + "/.kde"; + } + return path; +} + +QFileInfoList File::readDir(const QString &path) +{ + QDir dir(path); + dir.setFilter(QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot); + //dir.setSorting(QDir::DirsFirst | QDir::Name); + return dir.entryInfoList(); +} + +bool File::makeDir(const QString &path) +{ + // This function will create all parent directories + QDir dir(path); + if (!dir.exists() && dir.mkpath(path)) { + return true; + } + return false; +} + +QString File::readText(const QString &path) +{ + QString data; + QFile file(path); + if (file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream in(&file); + in.setCodec("UTF-8"); + data = in.readAll(); + file.close(); + } + return data; +} + +bool File::writeText(const QString &path, const QString &data) +{ + QFile file(path); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&file); + out.setCodec("UTF-8"); + out << data; + file.close(); + return true; + } + return false; +} + +QByteArray File::readBinary(const QString &path) +{ + QByteArray data; + QFile file(path); + if (file.exists() && file.open(QIODevice::ReadOnly)) { + data = file.readAll(); + file.close(); + } + return data; +} + +bool File::writeBinary(const QString &path, const QByteArray &data) +{ + QFile file(path); + if (file.open(QIODevice::WriteOnly)) { + file.write(data); + file.close(); + return true; + } + return false; +} + +bool File::copy(const QString &path, const QString &targetPath) +{ + // This function will copy files recursively + QFileInfo fileInfo(path); + if (fileInfo.isFile()) { + QFile file(path); + if (file.copy(targetPath)) { + return true; + } + } + else if (fileInfo.isDir()) { + QDir targetDir(targetPath); + QString targetDirName = targetDir.dirName(); + targetDir.cdUp(); + if (targetDir.mkdir(targetDirName)) { + QDir dir(path); + dir.setFilter(QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot); + QStringList entries = dir.entryList(); + foreach (const QString &entry, entries) { + if (!copy(path + "/" + entry, targetPath + "/" + entry)) { + return false; + } + } + return true; + } + } + return false; +} + +bool File::move(const QString &path, const QString &targetPath) +{ + QFileInfo fileInfo(path); + if (fileInfo.isFile()) { + QFile file(path); + if (file.rename(targetPath)) { + return true; + } + } + else if (fileInfo.isDir()) { + QDir dir(path); + if (dir.rename(path, targetPath)) { + return true; + } + } + return false; +} + +bool File::remove(const QString &path) +{ + // This function will remove files recursively + QFileInfo fileInfo(path); + if (fileInfo.isFile()) { + QFile file(path); + if (file.remove()) { + return true; + } + } + else if (fileInfo.isDir()) { + QDir dir(path); + if (dir.removeRecursively()) { + return true; + } + } + return false; +} + +} // namespace Utility diff --git a/src/utility/file.h b/src/utility/file.h new file mode 100644 index 0000000000000000000000000000000000000000..8700250686283ff77ccc33fbb4001295c6c0ea1e --- /dev/null +++ b/src/utility/file.h @@ -0,0 +1,38 @@ +#ifndef UTILITY_FILE_H +#define UTILITY_FILE_H + +#include <QObject> + +class QFileInfo; +typedef QList<QFileInfo> QFileInfoList; + +namespace Utility { + +class File : public QObject +{ + Q_OBJECT + +public: + explicit File(QObject *parent = 0); + + static QString rootPath(); + static QString tempPath(); + static QString homePath(); + static QString xdgDataHomePath(); + static QString xdgConfigHomePath(); + static QString xdgCacheHomePath(); + static QString kdehomePath(); + static QFileInfoList readDir(const QString &path); + static bool makeDir(const QString &path); + static QString readText(const QString &path); + static bool writeText(const QString &path, const QString &data); + static QByteArray readBinary(const QString &path); + static bool writeBinary(const QString &path, const QByteArray &data); + static bool copy(const QString &path, const QString &targetPath); + static bool move(const QString &path, const QString &targetPath); + static bool remove(const QString &path); +}; + +} // namespace Utility + +#endif // UTILITY_FILE_H diff --git a/src/utility/json.cpp b/src/utility/json.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cbce058ca4917f8c073f5976657984f4ed71b0c0 --- /dev/null +++ b/src/utility/json.cpp @@ -0,0 +1,39 @@ +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonParseError> + +#include "json.h" + +namespace Utility { + +Json::Json(QObject *parent) : QObject(parent) +{} + +bool Json::isValid(const QString &json) +{ + QJsonParseError jsonError; + QJsonDocument::fromJson(json.toUtf8(), &jsonError); + if (jsonError.error == QJsonParseError::NoError) { + return true; + } + return false; +} + +QJsonObject Json::convertStrToObj(const QString &json) +{ + QJsonObject jsonObj; + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(json.toUtf8(), &jsonError); + if (jsonError.error == QJsonParseError::NoError && jsonDoc.isObject()) { + jsonObj = jsonDoc.object(); + } + return jsonObj; +} + +QString Json::convertObjToStr(const QJsonObject &jsonObj) +{ + QJsonDocument jsonDoc(jsonObj); + return QString::fromUtf8(jsonDoc.toJson()); +} + +} // namespace Utility diff --git a/src/utility/json.h b/src/utility/json.h new file mode 100644 index 0000000000000000000000000000000000000000..68f100b58f25af7e48fc63999445976665e4c1d8 --- /dev/null +++ b/src/utility/json.h @@ -0,0 +1,22 @@ +#ifndef UTILITY_JSON_H +#define UTILITY_JSON_H + +#include <QObject> + +namespace Utility { + +class Json : public QObject +{ + Q_OBJECT + +public: + explicit Json(QObject *parent = 0); + + static bool isValid(const QString &json); + static QJsonObject convertStrToObj(const QString &json); + static QString convertObjToStr(const QJsonObject &jsonObj); +}; + +} // namespace Utility + +#endif // UTILITY_JSON_H diff --git a/src/utility/package.cpp b/src/utility/package.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b3c5c75d9b6239e24160845fb3f75de4a4cd8192 --- /dev/null +++ b/src/utility/package.cpp @@ -0,0 +1,104 @@ +#include <QJsonObject> +#include <QMimeDatabase> +#include <QProcess> + +#include "package.h" + +namespace Utility { + +Package::Package(QObject *parent) : QObject(parent) +{} + +bool Package::uncompressArchive(const QString &path, const QString &targetDir) +{ + QJsonObject archiveTypes; + archiveTypes["application/x-tar"] = QString("tar"); + archiveTypes["application/x-gzip"] = QString("tar"); + archiveTypes["application/gzip"] = QString("tar"); + archiveTypes["application/x-bzip"] = QString("tar"); + archiveTypes["application/x-bzip2"] = QString("tar"); + archiveTypes["application/x-xz"] = QString("tar"); + archiveTypes["application/x-lzma"] = QString("tar"); + archiveTypes["application/x-lzip"] = QString("tar"); + archiveTypes["application/x-compressed-tar"] = QString("tar"); + archiveTypes["application/x-bzip-compressed-tar"] = QString("tar"); + archiveTypes["application/x-bzip2-compressed-tar"] = QString("tar"); + archiveTypes["application/x-xz-compressed-tar"] = QString("tar"); + archiveTypes["application/x-lzma-compressed-tar"] = QString("tar"); + archiveTypes["application/x-lzip-compressed-tar"] = QString("tar"); + archiveTypes["application/zip"] = QString("zip"); + archiveTypes["application/x-7z-compressed"] = QString("7z"); + archiveTypes["application/x-rar"] = QString("rar"); + archiveTypes["application/x-rar-compressed"] = QString("rar"); + + QMimeDatabase mimeDb; + QString mimeType = mimeDb.mimeTypeForFile(path).name(); + + if (archiveTypes.contains(mimeType)) { + QString archiveType = archiveTypes[mimeType].toString(); + + QProcess process; + QString program; + QStringList arguments; + + if (archiveType == "tar") { + program = "tar"; + arguments << "-xf" << path << "-C" << targetDir; + } + else if (archiveType == "zip") { + program = "unzip"; + arguments << "-o" << path << "-d" << targetDir; + } + else if (archiveType == "7z") { + program = "7z"; + arguments << "x" << path << "-o" + targetDir; // No space between -o and directory + } + else if (archiveType == "rar") { + program = "unrar"; + arguments << "e" << path << targetDir; + } + + process.start(program, arguments); + + if (process.waitForFinished()) { + process.waitForReadyRead(); + return true; + } + } + + return false; +} + +bool Package::installPlasmapkg(const QString &path, const QString &type) +{ + QProcess process; + QString program = "plasmapkg2"; // Use plasmapkg2 for now + QStringList arguments; + arguments << "-t" << type << "-i" << path; + + process.start(program, arguments); + + if (process.waitForFinished()) { + return true; + } + + return false; +} + +bool Package::uninstallPlasmapkg(const QString &path, const QString &type) +{ + QProcess process; + QString program = "plasmapkg2"; // Use plasmapkg2 for now + QStringList arguments; + arguments << "-t" << type << "-r" << path; + + process.start(program, arguments); + + if (process.waitForFinished()) { + return true; + } + + return false; +} + +} // namespace Utility diff --git a/src/utility/package.h b/src/utility/package.h new file mode 100644 index 0000000000000000000000000000000000000000..89bf0375dc5b1e02298a467a27f5cb7e857b0f12 --- /dev/null +++ b/src/utility/package.h @@ -0,0 +1,22 @@ +#ifndef UTILITY_PACKAGE_H +#define UTILITY_PACKAGE_H + +#include <QObject> + +namespace Utility { + +class Package : public QObject +{ + Q_OBJECT + +public: + explicit Package(QObject *parent = 0); + + static bool uncompressArchive(const QString &path, const QString &targetDir); + static bool installPlasmapkg(const QString &path, const QString &type = "plasmoid"); + static bool uninstallPlasmapkg(const QString &path, const QString &type = "plasmoid"); +}; + +} // namespace Utility + +#endif // UTILITY_PACKAGE_H diff --git a/src/xdgurl.py b/src/xdgurl.py deleted file mode 100644 index 11ae5085dca88a767b79fce38c2f09fea4fd872c..0000000000000000000000000000000000000000 --- a/src/xdgurl.py +++ /dev/null @@ -1,377 +0,0 @@ -#!/usr/bin/env python - -"""xdgurl -An install helper program for desktop stuff. - -Copyright: 2016, Akira Ohgaki -License: GPL-3+ - -https://github.com/xdgurl/xdgurl -""" - -import sys -import os -import shutil -import json -import tempfile -import mimetypes -import subprocess -import argparse - -if sys.version_info.major >= 3: - import urllib.request - import urllib.error - import urllib.parse - import tkinter - import tkinter.messagebox -else: - import urllib - import urlparse - import Tkinter - import tkMessageBox - -class XdgUrl: - - def __init__(self, xdg_url=''): - self.xdg_url = xdg_url - self.meta = self.parse() - - self.temp_dir = tempfile.gettempdir() - self.home_dir = os.path.expanduser('~') - #self.config_dir = os.path.join(self.home_dir, '.config', 'xdgurl') - self.data_dir = os.path.join(self.home_dir, '.local', 'share') - self.kde_data_dir = os.path.join(self.home_dir, '.kde', 'share') - - #self.config = {} - - self.destinations = { - 'downloads': os.path.join(self.home_dir, 'Downloads'), - 'documents': os.path.join(self.home_dir, 'Documents'), - 'pictures': os.path.join(self.home_dir, 'Pictures'), - 'music': os.path.join(self.home_dir, 'Music'), - 'videos': os.path.join(self.home_dir, 'Videos'), - 'wallpapers': os.path.join(self.data_dir, 'wallpapers'), - 'fonts': os.path.join(self.home_dir, '.fonts'), - 'cursors': os.path.join(self.home_dir, '.icons'), - 'icons': os.path.join(self.data_dir, 'icons'), - 'emoticons': os.path.join(self.data_dir, 'emoticons'), - 'themes': os.path.join(self.home_dir, '.themes'), - 'emerald_themes': os.path.join(self.home_dir, '.emerald', 'themes'), - 'enlightenment_themes': os.path.join(self.home_dir, '.e', 'e', 'themes'), - 'enlightenment_backgrounds': os.path.join(self.home_dir, '.e', 'e', 'backgrounds'), - 'fluxbox_styles': os.path.join(self.home_dir, '.fluxbox', 'styles'), - 'pekwm_themes': os.path.join(self.home_dir, '.pekwm', 'themes'), - 'icewm_themes': os.path.join(self.home_dir, '.icewm', 'themes'), - 'plasma_plasmoids': os.path.join(self.data_dir, 'plasma', 'plasmoids'), - 'plasma_look_and_feel': os.path.join(self.data_dir, 'plasma', 'look-and-feel'), - 'plasma_desktopthemes': os.path.join(self.data_dir, 'plasma', 'desktoptheme'), - 'kwin_effects': os.path.join(self.data_dir, 'kwin', 'effects'), - 'kwin_scripts': os.path.join(self.data_dir, 'kwin', 'scripts'), - 'kwin_tabbox': os.path.join(self.data_dir, 'kwin', 'tabbox'), - 'aurorae_themes': os.path.join(self.data_dir, 'aurorae', 'themes'), - 'dekorator_themes': os.path.join(self.data_dir, 'deKorator', 'themes'), - 'qtcurve': os.path.join(self.data_dir, 'QtCurve'), - 'color_schemes': os.path.join(self.data_dir, 'color-schemes'), - 'gnome_shell_extensions': os.path.join(self.data_dir, 'gnome-shell', 'extensions'), - 'cinnamon_applets': os.path.join(self.data_dir, 'cinnamon', 'applets'), - 'cinnamon_desklets': os.path.join(self.data_dir, 'cinnamon', 'desklets'), - 'cinnamon_extensions': os.path.join(self.data_dir, 'cinnamon', 'extensions'), - 'nautilus_scripts': os.path.join(self.data_dir, 'nautilus', 'scripts'), - 'amarok_scripts': os.path.join(self.kde_data_dir, 'apps', 'amarok', 'scripts'), - 'yakuake_skins': os.path.join(self.kde_data_dir, 'apps', 'yakuake', 'skins'), - 'cairo_clock_themes': os.path.join(self.home_dir, '.cairo-clock', 'themes') - } - self.destinations_alias = { - 'gnome_shell_themes': self.destinations['themes'], - 'cinnamon_themes': self.destinations['themes'], - 'gtk2_themes': self.destinations['themes'], - 'gtk3_themes': self.destinations['themes'], - 'metacity_themes': self.destinations['themes'], - 'xfwm4_themes': self.destinations['themes'], - 'openbox_themes': self.destinations['themes'], - 'kvantum_themes': self.destinations['themes'], - 'compiz_themes': self.destinations['emerald_themes'], - 'beryl_themes': self.destinations['emerald_themes'], - 'plasma4_plasmoids': self.destinations['plasma_plasmoids'], - 'plasma5_plasmoids': self.destinations['plasma_plasmoids'], - 'plasma5_look_and_feel': self.destinations['plasma_look_and_feel'], - 'plasma5_desktopthemes': self.destinations['plasma_desktopthemes'], - 'plasma_color_schemes': self.destinations['color_schemes'] - } - self.destinations.update(self.destinations_alias) - - self.archive_types = { - 'tar': [ - 'application/x-tar', - 'application/x-gzip', - 'application/gzip', - 'application/x-bzip', - 'application/x-bzip2', - 'application/x-xz', - 'application/x-lzma', - 'application/x-lzip', - 'application/x-compressed-tar', - 'application/x-bzip-compressed-tar', - 'application/x-bzip2-compressed-tar', - 'application/x-xz-compressed-tar', - 'application/x-lzma-compressed-tar', - 'application/x-lzip-compressed-tar' - ], - 'zip': ['application/zip'], - '7z': ['application/x-7z-compressed'], - 'rar': [ - 'application/x-rar', - 'application/x-rar-compressed' - ] - } - - #self.config.update(self.read_config('config')) - #self.destinations.update(self.read_config('destinations')) - - def read_config(self, name): - path = os.path.join(self.config_dir, name + '.json') - data = {} - if os.path.isfile(path): - f = open(path, 'r') - data = json.load(f) - f.close() - return data - - def write_config(self, name, data): - if not os.path.isdir(self.config_dir): - os.makedirs(self.config_dir) - path = os.path.join(self.config_dir, name + '.json') - f = open(path, 'w') - json.dump(data, f) - f.close() - - def parse(self): - meta = { - 'command': 'download', - 'url': '', - 'type': 'downloads', - 'filename': '' - } - - if sys.version_info.major >= 3: - parse_result = urllib.parse.urlparse(self.xdg_url) - query = urllib.parse.parse_qs(parse_result.query) - else: - parse_result = urlparse.urlparse(self.xdg_url) - query = urlparse.parse_qs(parse_result.query) - - if parse_result.netloc: - meta['command'] = parse_result.netloc - - if 'url' in query and query['url'][0]: - if sys.version_info.major >= 3: - meta['url'] = urllib.parse.unquote(query['url'][0]) - else: - meta['url'] = urllib.unquote(query['url'][0]) - - if 'type' in query and query['type'][0]: - if sys.version_info.major >= 3: - meta['type'] = urllib.parse.unquote(query['type'][0]) - else: - meta['type'] = urllib.unquote(query['type'][0]) - - if 'filename' in query and query['filename'][0]: - if sys.version_info.major >= 3: - meta['filename'] = urllib.parse.unquote(query['filename'][0]).split('?')[0] - else: - meta['filename'] = urllib.unquote(query['filename'][0]).split('?')[0] - - if meta['url'] and not meta['filename']: - meta['filename'] = os.path.basename(meta['url']).split('?')[0] - - return meta - - """ - def detect_desktop_environment(self): - desktop_environment = 'unknown' - if os.environ.get('KDE_FULL_SESSION') == 'true': - desktop_environment = 'kde' - elif os.environ.get('GNOME_DESKTOP_SESSION_ID'): - desktop_environment = 'gnome' - return desktop_environment - """ - - def install_plasmapkg(self, path, type='plasmoid'): - status = subprocess.call(['plasmapkg2', '-t', type, '-i', path]) - if status == 0: - return True - return False - - def uncompress_archive(self, path, target_dir): - (mimetype, encoding) = mimetypes.guess_type(path) - status = None - - if mimetype in self.archive_types['tar']: - status = subprocess.call(['tar', '-xf', path, '-C', target_dir]) - elif mimetype in self.archive_types['zip']: - status = subprocess.call(['unzip', '-o', path, '-d', target_dir]) - elif mimetype in self.archive_types['7z']: - # No space between -o and directory - status = subprocess.call(['7z', 'x', path, '-o' + target_dir]) - elif mimetype in self.archive_types['rar']: - status = subprocess.call(['unrar', 'e', path, target_dir]) - - if status == 0: - return True - return False - - def download(self): - url = self.meta['url'] - type = self.meta['type'] - filename = self.meta['filename'] - destination = self.destinations[type] - - temp_path = os.path.join(self.temp_dir, filename) - path = os.path.join(destination, filename) - - print('Retrieving a file from ' + url) - if sys.version_info.major >= 3: - urllib.request.urlretrieve(url, temp_path) - else: - urllib.urlretrieve(url, temp_path) - - print('Creating a directory ' + destination) - if not os.path.isdir(destination): - os.makedirs(destination) - - print('Saving a file to ' + path) - shutil.move(temp_path, path) - - print('Done') - - def install(self): - url = self.meta['url'] - type = self.meta['type'] - filename = self.meta['filename'] - destination = self.destinations[type] - - temp_path = os.path.join(self.temp_dir, filename) - path = os.path.join(destination, filename) - - print('Retrieving a file from ' + url) - if sys.version_info.major >= 3: - urllib.request.urlretrieve(url, temp_path) - else: - urllib.urlretrieve(url, temp_path) - - print('Creating a directory ' + destination) - if not os.path.isdir(destination): - os.makedirs(destination) - - print('Installing') - if (type in ['plasma_plasmoids', 'plasma4_plasmoids', 'plasma5_plasmoids'] - and self.install_plasmapkg(temp_path, 'plasmoid')): - print('The plasmoid has been installed') - os.remove(temp_path) - elif (type in ['plasma_look_and_feel', 'plasma5_look_and_feel'] - and self.install_plasmapkg(temp_path, 'lookandfeel')): - print('The plasma look and feel has been installed') - os.remove(temp_path) - elif (type in ['plasma_desktopthemes', 'plasma5_desktopthemes'] - and self.install_plasmapkg(temp_path, 'theme')): - print('The plasma desktop theme has been installed') - os.remove(temp_path) - elif (type == 'kwin_effects' - and self.install_plasmapkg(temp_path, 'kwineffect')): - print('The KWin effect has been installed') - os.remove(temp_path) - elif (type == 'kwin_scripts' - and self.install_plasmapkg(temp_path, 'kwinscript')): - print('The KWin script has been installed') - os.remove(temp_path) - elif (type == 'kwin_tabbox' - and self.install_plasmapkg(temp_path, 'windowswitcher')): - print('The KWin window switcher has been installed') - os.remove(temp_path) - elif self.uncompress_archive(temp_path, destination): - print('The archive file has been uncompressed into ' + destination) - os.remove(temp_path) - else: - print('Saving a file to ' + path) - shutil.move(temp_path, path) - - print('Done') - - def execute(self): - if (self.meta['command'] in ['download', 'install'] - and self.meta['url'] - and self.meta['type'] in self.destinations - and self.meta['filename']): - if self.meta['command'] == 'download': - self.download() - elif self.meta['command'] == 'install': - self.install() - else: - raise Exception('Incorrect XDG-URL ' + self.xdg_url) - -def main(): - program = 'xdgurl' - version = '1.0.1' - - parser = argparse.ArgumentParser( - prog=program, - description='An install helper program for desktop stuff', - epilog='Check more information on https://github.com/xdgurl/xdgurl' - ) - parser.add_argument( - '-v', '--version', - action='version', - version='%(prog)s ' + version - ) - parser.add_argument('xdg_url', help='XDG-URL') - args = parser.parse_args() - - if args.xdg_url: - core = XdgUrl(args.xdg_url) - if sys.version_info.major >= 3: - window = tkinter.Tk() - else: - window = Tkinter.Tk() - window.withdraw() - - info_text = 'Download: ' + core.meta['filename'] + '\nFrom: ' + core.meta['url'] - if core.meta['command'] == 'install': - info_text = 'Install: ' + core.meta['filename'] + '\nFrom: ' + core.meta['url'] - - message = 'Do you want to continue?' - - print(info_text) - if sys.version_info.major >= 3: - confirm = tkinter.messagebox.askyesno(program, info_text + '\n\n' + message) - else: - confirm = tkMessageBox.askyesno(program, info_text + '\n\n' + message) - - if confirm: - try: - core.execute(); - except Exception as e: - message = 'Download failed' - if core.meta['command'] == 'install': - message = 'Installation failed' - - print(message) - if sys.version_info.major >= 3: - tkinter.messagebox.showerror(program, info_text + '\n\n' + message + '\n' + str(e)) - else: - tkMessageBox.showerror(program, info_text + '\n\n' + message + '\n' + str(e)) - - return str(e) # stderr and exit code 1 - else: - message = 'Download successfull' - if core.meta['command'] == 'install': - message = 'Installation successfull' - - print(message) - if sys.version_info.major >= 3: - tkinter.messagebox.showinfo(program, message) - else: - tkMessageBox.showinfo(program, message) - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/xdgurl.pro b/xdgurl.pro new file mode 100644 index 0000000000000000000000000000000000000000..096220bc3c93904c291df132aa0093bee7260030 --- /dev/null +++ b/xdgurl.pro @@ -0,0 +1,46 @@ +TARGET = xdgurl + +TEMPLATE = app + +QT += \ + core \ + gui \ + widgets \ + qml \ + quick \ + svg \ + network + +CONFIG += \ + c++11 + +SOURCES += \ + src/main.cpp \ + src/core/config.cpp \ + src/core/network.cpp \ + src/handlers/xdgurl.cpp \ + src/utility/file.cpp \ + src/utility/json.cpp \ + src/utility/package.cpp + +HEADERS += \ + src/core/config.h \ + src/core/network.h \ + src/handlers/xdgurl.h \ + src/utility/file.h \ + src/utility/json.h \ + src/utility/package.h + +RESOURCES += \ + src/configs.qrc \ + src/qml.qrc \ + src/desktop.qrc + +DISTFILES += \ + README.md + +# Additional RPATH +#include(rpath.pri) + +# Deployment rules +include(deployment.pri)