diff --git a/.travis.yml b/.travis.yml
index 85675aaf75266f4652c0830df86e27a4ffe97f73..5eb62c7afd53e26d20750f5a6c4229f0d241f497 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,7 +8,7 @@ before_install:
 
 install:
   - sudo apt-get -y install build-essential fuse zsync desktop-file-utils
-  - sudo apt-get -y install qt57base qt57svg qt57quickcontrols qt57quickcontrols2
+  - sudo apt-get -y install qt57base qt57svg qt57declarative qt57quickcontrols
   # Replace linuxdeployqt download URL to official download URL when the stable version released
   - curl -L -o linuxdeployqt https://dl.dropboxusercontent.com/u/150776/temp/linuxdeployqt-799f704-x86-64.appimage
   - sudo install -m 755 -p linuxdeployqt /usr/local/bin/linuxdeployqt
diff --git a/README.md b/README.md
index 1f85dd2431f344db0c31f6a4a75801133bcca413..f58d1072ac3a2989ff769f335491e8db18182a34 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 An install helper program for desktop stuff.
 
-Copyright: 2016, Akira Ohgaki
+Copyright: 2016-2017, Akira Ohgaki
 
 License: GPL-3+
 
diff --git a/pkg/arch/PKGBUILD b/pkg/arch/PKGBUILD
index 4c13ad724b0804e7e215e4751bcaf068b2552c60..fbb56ae6f9b6ff0bc02d917c7f5631dbb206289b 100644
--- a/pkg/arch/PKGBUILD
+++ b/pkg/arch/PKGBUILD
@@ -1,7 +1,7 @@
 # Maintainer: Akira Ohgaki <akiraohgaki@gmail.com>
 
 pkgname='xdgurl'
-pkgver='2.0.2'
+pkgver='2.0.3'
 pkgrel='1'
 pkgdesc='An install helper program for desktop stuff.'
 arch=('i686' 'x86_64')
diff --git a/pkg/build.sh b/pkg/build.sh
index 0408083c09839c5a07b21256abd87ced9ef347af..f848e2625191f03526aa93aa939dee882531f364 100644
--- a/pkg/build.sh
+++ b/pkg/build.sh
@@ -71,7 +71,7 @@ build_appimage() {
     #sudo apt update
 
     #sudo apt install build-essential fuse zsync desktop-file-utils
-    #sudo apt install qt57base qt57svg qt57quickcontrols qt57quickcontrols2
+    #sudo apt install qt57base qt57svg qt57declarative qt57quickcontrols
     # Replace linuxdeployqt download URL to official download URL when the stable version released
     #curl -L -o linuxdeployqt https://dl.dropboxusercontent.com/u/150776/temp/linuxdeployqt-799f704-x86-64.appimage
     #sudo install -m 755 -p linuxdeployqt /usr/local/bin/linuxdeployqt
diff --git a/pkg/fedora/xdgurl.spec b/pkg/fedora/xdgurl.spec
index 9aa3770687b6f7f6fdc85b180c48353026318fa6..544b375641def9da9cafa928739d2a6d9984d757 100644
--- a/pkg/fedora/xdgurl.spec
+++ b/pkg/fedora/xdgurl.spec
@@ -1,6 +1,6 @@
 Summary: An install helper program for desktop stuff
 Name: xdgurl
-Version: 2.0.2
+Version: 2.0.3
 Release: 1%{?dist}
 License: GPLv3+
 Group: Applications/Internet
@@ -37,6 +37,11 @@ make INSTALL_ROOT="%{buildroot}" install
 rm -rf %{buildroot}
 
 %changelog
+* Wed Jan 25 2017 Akira Ohgaki <akiraohgaki@gmail.com> - 2.0.3-1
+- Change installation destination of type bin
+- Update qtlib
+- Fix for dialog
+
 * Thu Nov 17 2016 Akira Ohgaki <akiraohgaki@gmail.com> - 2.0.2-1
 - Update qtlibs
 - Small fix
diff --git a/pkg/ubuntu/debian/changelog b/pkg/ubuntu/debian/changelog
index 4c855927dc3d040069f1cf721d003dec9e8cb50c..45b73174bb88c0812be7f177f6d19cec2b7339bf 100644
--- a/pkg/ubuntu/debian/changelog
+++ b/pkg/ubuntu/debian/changelog
@@ -1,3 +1,11 @@
+xdgurl (2.0.3-0ubuntu1) xenial; urgency=low
+
+  * Change installation destination of type bin
+  * Update qtlib
+  * Fix for dialog
+
+ -- Akira Ohgaki <akiraohgaki@gmail.com>  Wed, 25 Jan 2017 19:58:44 +0000
+
 xdgurl (2.0.2-0ubuntu1) xenial; urgency=low
 
   * Update qtlibs
diff --git a/pkg/ubuntu/debian/copyright b/pkg/ubuntu/debian/copyright
index 5e4c2739d8fd9bf4e1d75d27646f8441c8757f7b..68078a2329aef059899c05e080d16e312bf11afc 100644
--- a/pkg/ubuntu/debian/copyright
+++ b/pkg/ubuntu/debian/copyright
@@ -4,7 +4,7 @@ Upstream-Contact: Akira Ohgaki <akiraohgaki@gmail.com>
 Source: https://github.com/xdgurl/xdgurl
 
 Files: *
-Copyright: 2016, Akira Ohgaki
+Copyright: 2016-2017, Akira Ohgaki
 License: GPL-3+
  On Debian systems, the full text of the GNU General Public License version 3
  can be found in the `/usr/share/common-licenses/GPL-3' file.
diff --git a/src/app/app.pri b/src/app/app.pri
index f192467c311e56612551a1aee77a8498e2b54bf0..b37beda3dc2a93b64009db66f8235a7ca4393592 100644
--- a/src/app/app.pri
+++ b/src/app/app.pri
@@ -6,11 +6,11 @@ QT += \
     svg
 
 HEADERS += \
-    $${PWD}/handlers/xdgurl.h
+    $${PWD}/handlers/xdgurlhandler.h
 
 SOURCES += \
     $${PWD}/main.cpp \
-    $${PWD}/handlers/xdgurl.cpp
+    $${PWD}/handlers/xdgurlhandler.cpp
 
 RESOURCES += \
     $${PWD}/configs/configs.qrc \
diff --git a/src/app/configs/application.json b/src/app/configs/application.json
index 6beb7f2875311727de5349524654697c4ea2707b..1419f6b09cc338abb33e834352bffe476a7c3bdd 100644
--- a/src/app/configs/application.json
+++ b/src/app/configs/application.json
@@ -1,7 +1,7 @@
 {
     "id": "xdgurl",
     "name": "xdgurl",
-    "version": "2.0.2",
+    "version": "2.0.3",
     "organization": "xdgurl",
     "domain": "com.xdgurl.xdgurl",
     "icon": ":/desktop/xdgurl.svg",
diff --git a/src/app/configs/destinations.json b/src/app/configs/destinations.json
index 1ea1bbf332a39ffa2f62bc8633089035ee5755ea..ed85557bb2cb708a88b695f623c97843c3315764 100644
--- a/src/app/configs/destinations.json
+++ b/src/app/configs/destinations.json
@@ -1,5 +1,5 @@
 {
-    "bin": "$HOME/.bin",
+    "bin": "$HOME/.local/bin",
     "downloads": "$HOME/Downloads",
     "documents": "$HOME/Documents",
     "pictures": "$HOME/Pictures",
diff --git a/src/app/handlers/xdgurl.h b/src/app/handlers/xdgurl.h
deleted file mode 100644
index 87ab472486719f2052f7502cab4ab1af67f4fb24..0000000000000000000000000000000000000000
--- a/src/app/handlers/xdgurl.h
+++ /dev/null
@@ -1,52 +0,0 @@
-#pragma once
-
-#include <QObject>
-#include <QJsonObject>
-
-#include "qtlibs/config.h"
-
-namespace qtlibs {
-class NetworkResource;
-}
-
-namespace handlers {
-
-class XdgUrl : public QObject
-{
-    Q_OBJECT
-
-public:
-    explicit XdgUrl(const QString &xdgUrl, const qtlibs::Config &config, QObject *parent = 0);
-
-signals:
-    void started();
-    void finishedWithSuccess(const QJsonObject &result);
-    void finishedWithError(const QJsonObject &result);
-    void downloadProgress(const qint64 &bytesReceived, const qint64 &bytesTotal);
-
-public slots:
-    QString xdgUrl() const;
-    QJsonObject metadata() const;
-
-    void process();
-    bool isValid();
-    void openDestination();
-
-private slots:
-    void networkResourceFinished(qtlibs::NetworkResource *resource);
-
-private:
-    void parse();
-    void loadDestinations();
-    QString convertPathString(const QString &path);
-    void saveDownloadedFile(qtlibs::NetworkResource *resource);
-    void installDownloadedFile(qtlibs::NetworkResource *resource);
-
-    QString xdgUrl_;
-    qtlibs::Config config_;
-    QJsonObject metadata_;
-    QJsonObject destinations_;
-    QString destination_;
-};
-
-} // namespace handlers
diff --git a/src/app/handlers/xdgurl.cpp b/src/app/handlers/xdgurlhandler.cpp
similarity index 69%
rename from src/app/handlers/xdgurl.cpp
rename to src/app/handlers/xdgurlhandler.cpp
index 0a7e442ce16fd1f3faae8868d8aa33791b83b116..91533ca0b3dadda8e573f8c66e9ad61191dee0b8 100644
--- a/src/app/handlers/xdgurl.cpp
+++ b/src/app/handlers/xdgurlhandler.cpp
@@ -1,38 +1,34 @@
-#include "xdgurl.h"
+#include "xdgurlhandler.h"
 
 #include <QUrlQuery>
 #include <QDesktopServices>
 
-#include "qtlibs/file.h"
-#include "qtlibs/dir.h"
-#include "qtlibs/networkresource.h"
-#include "qtlibs/package.h"
+#include "qtlib_file.h"
+#include "qtlib_dir.h"
+#include "qtlib_networkresource.h"
+#include "qtlib_package.h"
 
-namespace handlers {
-
-XdgUrl::XdgUrl(const QString &xdgUrl, const qtlibs::Config &config, QObject *parent)
+XdgUrlHandler::XdgUrlHandler(const QString &xdgUrl, const qtlib::Config &config, QObject *parent)
     : QObject(parent), xdgUrl_(xdgUrl), config_(config)
 {
     parse();
     loadDestinations();
 }
 
-QString XdgUrl::xdgUrl() const
+QString XdgUrlHandler::xdgUrl() const
 {
     return xdgUrl_;
 }
 
-QJsonObject XdgUrl::metadata() const
+QJsonObject XdgUrlHandler::metadata() const
 {
     return metadata_;
 }
 
-void XdgUrl::process()
+void XdgUrlHandler::process()
 {
-    /**
-     * xdgs scheme is a reserved name, so the process of xdgs
-     * is the same process of the xdg scheme currently.
-     */
+    // xdgs scheme is a reserved name, so the process of xdgs
+    // is the same process of the xdg scheme currently.
 
     if (!isValid()) {
         QJsonObject result;
@@ -43,14 +39,14 @@ void XdgUrl::process()
     }
 
     QString url = metadata_["url"].toString();
-    qtlibs::NetworkResource *resource = new qtlibs::NetworkResource(url, QUrl(url), true, this);
-    connect(resource, &qtlibs::NetworkResource::downloadProgress, this, &XdgUrl::downloadProgress);
-    connect(resource, &qtlibs::NetworkResource::finished, this, &XdgUrl::networkResourceFinished);
+    qtlib::NetworkResource *resource = new qtlib::NetworkResource(url, QUrl(url), true, this);
+    connect(resource, &qtlib::NetworkResource::downloadProgress, this, &XdgUrlHandler::downloadProgress);
+    connect(resource, &qtlib::NetworkResource::finished, this, &XdgUrlHandler::networkResourceFinished);
     resource->get();
     emit started();
 }
 
-bool XdgUrl::isValid()
+bool XdgUrlHandler::isValid()
 {
     QString scheme = metadata_["scheme"].toString();
     QString command = metadata_["command"].toString();
@@ -68,20 +64,20 @@ bool XdgUrl::isValid()
     return false;
 }
 
-void XdgUrl::openDestination()
+void XdgUrlHandler::openDestination()
 {
-    if (!destination_.isEmpty()) {
-        QDesktopServices::openUrl(QUrl("file://" + destination_));
-    }
+    QString type = metadata_["type"].toString();
+    QDesktopServices::openUrl(QUrl("file://" + destinations_[type].toString()));
 }
 
-void XdgUrl::networkResourceFinished(qtlibs::NetworkResource *resource)
+void XdgUrlHandler::networkResourceFinished(qtlib::NetworkResource *resource)
 {
-    if (resource->reply()->error() != QNetworkReply::NoError) {
+    if (!resource->isFinishedWithNoError()) {
         QJsonObject result;
         result["status"] = QString("error_network");
         result["message"] = resource->reply()->errorString();
         emit finishedWithError(result);
+        resource->deleteLater();
         return;
     }
 
@@ -93,7 +89,7 @@ void XdgUrl::networkResourceFinished(qtlibs::NetworkResource *resource)
     }
 }
 
-void XdgUrl::parse()
+void XdgUrlHandler::parse()
 {
     QUrl url(xdgUrl_);
     QUrlQuery query(url);
@@ -129,7 +125,7 @@ void XdgUrl::parse()
     }
 }
 
-void XdgUrl::loadDestinations()
+void XdgUrlHandler::loadDestinations()
 {
     QJsonObject configDestinations = config_.get("destinations");
     QJsonObject configDestinationsAlias = config_.get("destinations_alias");
@@ -146,72 +142,70 @@ void XdgUrl::loadDestinations()
     }
 }
 
-QString XdgUrl::convertPathString(const QString &path)
+QString XdgUrlHandler::convertPathString(const QString &path)
 {
     QString newPath = path;
 
     if (newPath.contains("$HOME")) {
-        newPath.replace("$HOME", qtlibs::Dir::homePath());
+        newPath.replace("$HOME", qtlib::Dir::homePath());
     }
     else if (newPath.contains("$XDG_DATA_HOME")) {
-        newPath.replace("$XDG_DATA_HOME", qtlibs::Dir::genericDataPath());
+        newPath.replace("$XDG_DATA_HOME", qtlib::Dir::genericDataPath());
     }
     else if (newPath.contains("$KDEHOME")) {
-        newPath.replace("$KDEHOME", qtlibs::Dir::kdehomePath());
+        newPath.replace("$KDEHOME", qtlib::Dir::kdehomePath());
     }
 
     return newPath;
 }
 
-void XdgUrl::saveDownloadedFile(qtlibs::NetworkResource *resource)
+void XdgUrlHandler::saveDownloadedFile(qtlib::NetworkResource *resource)
 {
     QJsonObject result;
 
     QString type = metadata_["type"].toString();
-    QString destination = destinations_[type].toString();
-    QString path = destination + "/" + metadata_["filename"].toString();
-
-    qtlibs::Dir(destination).make();
+    qtlib::Dir destDir(destinations_[type].toString());
+    destDir.make();
+    qtlib::File destFile(destDir.path() + "/" + metadata_["filename"].toString());
 
-    if (!resource->saveData(path)) {
+    if (!resource->saveData(destFile.path())) {
         result["status"] = QString("error_save");
-        result["message"] = QString("Failed to save data as " + path);
+        result["message"] = QString("Failed to save data as " + destFile.path());
         emit finishedWithError(result);
+        resource->deleteLater();
         return;
     }
 
-    destination_ = destination;
-
     result["status"] = QString("success_download");
-    result["message"] = QString("The file has been stored into " + destination);
+    result["message"] = QString("The file has been stored into " + destDir.path());
     emit finishedWithSuccess(result);
+
+    resource->deleteLater();
 }
 
-void XdgUrl::installDownloadedFile(qtlibs::NetworkResource *resource)
+void XdgUrlHandler::installDownloadedFile(qtlib::NetworkResource *resource)
 {
     QJsonObject result;
 
-    QString tempPath = qtlibs::Dir::tempPath() + "/" + metadata_["filename"].toString();
+    qtlib::File tempFile(qtlib::Dir::tempPath() + "/" + metadata_["filename"].toString());
 
-    if (!resource->saveData(tempPath)) {
+    if (!resource->saveData(tempFile.path())) {
         result["status"] = QString("error_save");
-        result["message"] = QString("Failed to save data as " + tempPath);
+        result["message"] = QString("Failed to save data as " + tempFile.path());
         emit finishedWithError(result);
+        resource->deleteLater();
         return;
     }
 
-    qtlibs::Package package(tempPath);
-    qtlibs::File tempFile(tempPath);
-
+    qtlib::Package package(tempFile.path());
     QString type = metadata_["type"].toString();
-    QString destination = destinations_[type].toString();
-    QString path = destination + "/" + metadata_["filename"].toString();
-
-    qtlibs::Dir(destination).make();
+    qtlib::Dir destDir(destinations_[type].toString());
+    destDir.make();
+    qtlib::File destFile(destDir.path() + "/" + metadata_["filename"].toString());
 
     if (type == "bin"
-            && package.installAsProgram(path)) {
-        result["message"] = QString("The file has been installed into " + destination);
+            && package.installAsProgram(destFile.path())) {
+        result["message"] = QString("The file has been installed into " + destDir.path());
     }
     else if ((type == "plasma_plasmoids" || type == "plasma4_plasmoids" || type == "plasma5_plasmoids")
              && package.installAsPlasmapkg("plasmoid")) {
@@ -237,26 +231,23 @@ void XdgUrl::installDownloadedFile(qtlibs::NetworkResource *resource)
              && package.installAsPlasmapkg("windowswitcher")) {
         result["message"] = QString("The KWin window switcher has been installed");
     }
-    else if (package.installAsArchive(destination)) {
-        result["message"] = QString("The archive file has been extracted into " + destination);
+    else if (package.installAsArchive(destDir.path())) {
+        result["message"] = QString("The archive file has been extracted into " + destDir.path());
     }
-    else if (package.installAsFile(path)) {
-        result["message"] = QString("The file has been installed into " + destination);
+    else if (package.installAsFile(destFile.path())) {
+        result["message"] = QString("The file has been installed into " + destDir.path());
     }
     else {
-        tempFile.remove();
         result["status"] = QString("error_install");
         result["message"] = QString("Failed to installation");
         emit finishedWithError(result);
+        tempFile.remove();
         return;
     }
 
-    tempFile.remove();
-
-    destination_ = destination;
-
     result["status"] = QString("success_install");
     emit finishedWithSuccess(result);
-}
 
-} // namespace handlers
+    tempFile.remove();
+    resource->deleteLater();
+}
diff --git a/src/app/handlers/xdgurlhandler.h b/src/app/handlers/xdgurlhandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..6abc30b79b760f7093da5e438946f0a98a2f0373
--- /dev/null
+++ b/src/app/handlers/xdgurlhandler.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <QObject>
+#include <QJsonObject>
+
+#include "qtlib_config.h"
+
+namespace qtlib {
+class NetworkResource;
+}
+
+class XdgUrlHandler : public QObject
+{
+    Q_OBJECT
+
+public:
+    explicit XdgUrlHandler(const QString &xdgUrl, const qtlib::Config &config, QObject *parent = 0);
+
+signals:
+    void started();
+    void finishedWithSuccess(QJsonObject result);
+    void finishedWithError(QJsonObject result);
+    void downloadProgress(QString id, qint64 bytesReceived, qint64 bytesTotal);
+
+public slots:
+    QString xdgUrl() const;
+    QJsonObject metadata() const;
+
+    void process();
+    bool isValid();
+    void openDestination();
+
+private slots:
+    void networkResourceFinished(qtlib::NetworkResource *resource);
+
+private:
+    void parse();
+    void loadDestinations();
+    QString convertPathString(const QString &path);
+    void saveDownloadedFile(qtlib::NetworkResource *resource);
+    void installDownloadedFile(qtlib::NetworkResource *resource);
+
+    QString xdgUrl_;
+    qtlib::Config config_;
+    QJsonObject metadata_;
+    QJsonObject destinations_;
+};
diff --git a/src/app/main.cpp b/src/app/main.cpp
index 6ed32e68340e71f0f326ef0986cce24eb6a7655c..7f3aebc33194bd527678e9cb7b86bfed802bcdec 100644
--- a/src/app/main.cpp
+++ b/src/app/main.cpp
@@ -1,28 +1,28 @@
-#include <QtGlobal>
+//#include <QtGlobal>
 #include <QString>
 #include <QStringList>
 #include <QUrl>
 #include <QJsonObject>
 #include <QCommandLineParser>
-#include <QCoreApplication>
+//#include <QCoreApplication>
 #include <QGuiApplication>
 #include <QIcon>
 #include <QQmlApplicationEngine>
 #include <QQmlContext>
 
-#include "qtlibs/config.h"
+#include "qtlib_config.h"
 
-#include "handlers/xdgurl.h"
+#include "handlers/xdgurlhandler.h"
 
 int main(int argc, char *argv[])
 {
     // Init
-#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
-    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
-#endif
+//#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
+//    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+//#endif
     QGuiApplication app(argc, argv);
 
-    qtlibs::Config config(":/configs");
+    qtlib::Config config(":/configs");
     QJsonObject configApplication = config.get("application");
 
     app.setApplicationName(configApplication["name"].toString());
@@ -50,7 +50,7 @@ int main(int argc, char *argv[])
     // Setup QML
     QQmlApplicationEngine qmlAppEngine;
     QQmlContext *qmlContext = qmlAppEngine.rootContext();
-    qmlContext->setContextProperty("xdgUrlHandler", new handlers::XdgUrl(xdgUrl, config, &qmlAppEngine));
+    qmlContext->setContextProperty("xdgUrlHandler", new XdgUrlHandler(xdgUrl, config, &qmlAppEngine));
     qmlAppEngine.load(QUrl("qrc:/qml/main.qml"));
 
     return app.exec();
diff --git a/src/app/qml/main.qml b/src/app/qml/main.qml
index 1d68730b9a8f4c36b95288dde8fcc3ea26201870..4ad2414fd1ed3c2961f56a78154eda587e9a907f 100644
--- a/src/app/qml/main.qml
+++ b/src/app/qml/main.qml
@@ -3,10 +3,10 @@ import QtQuick.Window 2.0
 import QtQuick.Controls 1.2
 import QtQuick.Dialogs 1.2
 
-import 'scripts/Utility.js' as Utility
+import "scripts/Utility.js" as Utility
 
 Window {
-    id: root
+    id: app
     title: Qt.application.name
     width: 400
     height: 200
@@ -17,11 +17,11 @@ Window {
 
     MessageDialog {
         id: confirmDialog
-        title: root.title
+        title: app.title
         icon: StandardIcon.Question
-        text: ''
-        informativeText: ''
-        detailedText: ''
+        text: ""
+        informativeText: ""
+        detailedText: ""
         standardButtons: StandardButton.Ok | StandardButton.Cancel
         onAccepted: xdgUrlHandler.process()
         onRejected: Qt.quit()
@@ -29,11 +29,11 @@ Window {
 
     MessageDialog {
         id: infoDialog
-        title: root.title
+        title: app.title
         icon: StandardIcon.Information
-        text: ''
-        informativeText: ''
-        detailedText: ''
+        text: ""
+        informativeText: ""
+        detailedText: ""
         standardButtons: StandardButton.Open | StandardButton.Close
         onAccepted: {
             xdgUrlHandler.openDestination();
@@ -44,18 +44,22 @@ Window {
 
     MessageDialog {
         id: errorDialog
-        title: root.title
+        title: app.title
         icon: StandardIcon.Warning
-        text: ''
-        informativeText: ''
-        detailedText: ''
+        text: ""
+        informativeText: ""
+        detailedText: ""
         standardButtons: StandardButton.Close
         onRejected: Qt.quit()
     }
 
     Dialog {
         id: progressDialog
-        title: root.title
+        title: app.title
+        property alias primaryLabel: primaryLabel
+        property alias informativeLabel: informativeLabel
+        property alias progressBar: progressBar
+        property alias progressLabel: progressLabel
         contentItem: Item {
             implicitWidth: 400
             implicitHeight: 150
@@ -65,12 +69,12 @@ Window {
                 spacing: 8
                 Label {
                     id: primaryLabel
-                    text: ''
+                    text: " "
                     font.bold: true
                 }
                 Label {
                     id: informativeLabel
-                    text: ''
+                    text: " "
                 }
                 ProgressBar {
                     id: progressBar
@@ -82,32 +86,28 @@ Window {
                 }
                 Label {
                     id: progressLabel
-                    text: ''
+                    text: " "
                     anchors.right: parent.right
                 }
                 Button {
                     id: cancelButton
-                    text: 'Cancel'
+                    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 = xdgUrlHandler.metadata();
         var primaryMessages = {
-            'success_download': 'Download successfull',
-            'success_install': 'Installation successfull',
-            'error_validation': 'Validation error',
-            'error_network': 'Network error',
-            'error_save': 'Saving file failed',
-            'error_install': 'Installation failed'
+            "success_download": "Download successfull",
+            "success_install": "Installation successfull",
+            "error_validation": "Validation error",
+            "error_network": "Network error",
+            "error_save": "Saving file failed",
+            "error_install": "Installation failed"
         };
 
         xdgUrlHandler.started.connect(function() {
@@ -130,25 +130,25 @@ Window {
             errorDialog.open();
         });
 
-        xdgUrlHandler.downloadProgress.connect(function(bytesReceived, bytesTotal) {
-            progressDialog.primaryLabel.text = 'Downloading... ';
+        xdgUrlHandler.downloadProgress.connect(function(id, bytesReceived, bytesTotal) {
+            progressDialog.primaryLabel.text = "Downloading... ";
             progressDialog.informativeLabel.text = metadata.filename;
             progressDialog.progressBar.value = bytesReceived / bytesTotal;
             progressDialog.progressLabel.text = Utility.convertByteToHumanReadable(bytesReceived)
-                    + ' / ' + Utility.convertByteToHumanReadable(bytesTotal)
+                    + " / " + Utility.convertByteToHumanReadable(bytesTotal)
         });
 
         if (xdgUrlHandler.isValid()) {
-            confirmDialog.text = 'Do you want to ' + metadata.command + '?';
+            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.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.xdgUrl();
+            errorDialog.text = "Validation error";
+            errorDialog.detailedText = "Invalid XDG-URL " + xdgUrlHandler.xdgUrl();
             errorDialog.open();
         }
     }
diff --git a/src/desktop/appimage-desktopintegration b/src/desktop/appimage-desktopintegration
old mode 100644
new mode 100755
diff --git a/src/lib/qtlib/.gitignore b/src/lib/qtlib/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..75c107bcc90038bcdd0d643b20eb4d33a13b4245
--- /dev/null
+++ b/src/lib/qtlib/.gitignore
@@ -0,0 +1 @@
+*.pro.user
diff --git a/src/lib/qtlib/README.md b/src/lib/qtlib/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..c042d940a2901ad45b5eb37249e495be9f60fd16
--- /dev/null
+++ b/src/lib/qtlib/README.md
@@ -0,0 +1,7 @@
+# qtlib
+
+A library for Qt app.
+
+Copyright: Akira Ohgaki
+
+License: LGPL-3+
diff --git a/src/lib/qtlib/qtlib-test.pro b/src/lib/qtlib/qtlib-test.pro
new file mode 100644
index 0000000000000000000000000000000000000000..7769787444482c0c01b5367587f20104631aa8bf
--- /dev/null
+++ b/src/lib/qtlib/qtlib-test.pro
@@ -0,0 +1,13 @@
+include(qtlib.pri)
+
+TARGET = qtlib-test
+
+TEMPLATE = app
+
+CONFIG += c++11
+
+QT += core
+
+SOURCES += test/main.cpp
+
+DISTFILES += README.md
diff --git a/src/lib/qtlib/qtlib.pri b/src/lib/qtlib/qtlib.pri
new file mode 100644
index 0000000000000000000000000000000000000000..8d8e38182c47b2e2e8a5e28c514e65bac347562f
--- /dev/null
+++ b/src/lib/qtlib/qtlib.pri
@@ -0,0 +1,31 @@
+QT += \
+    core \
+    network
+
+HEADERS += \
+    $${PWD}/src/qtlib_file.h \
+    $${PWD}/src/qtlib_dir.h \
+    $${PWD}/src/qtlib_json.h \
+    $${PWD}/src/qtlib_config.h \
+    $${PWD}/src/qtlib_networkresource.h \
+    $${PWD}/src/qtlib_ocsapi.h \
+    $${PWD}/src/qtlib_package.h
+
+SOURCES += \
+    $${PWD}/src/qtlib_file.cpp \
+    $${PWD}/src/qtlib_dir.cpp \
+    $${PWD}/src/qtlib_json.cpp \
+    $${PWD}/src/qtlib_config.cpp \
+    $${PWD}/src/qtlib_networkresource.cpp \
+    $${PWD}/src/qtlib_ocsapi.cpp \
+    $${PWD}/src/qtlib_package.cpp
+
+INCLUDEPATH += $${PWD}/src
+
+unix:!ios:!android {
+    DEFINES += QTLIB_UNIX
+}
+
+android {
+    QT += androidextras
+}
diff --git a/src/libs/qtlibs/config.cpp b/src/lib/qtlib/src/qtlib_config.cpp
similarity index 59%
rename from src/libs/qtlibs/config.cpp
rename to src/lib/qtlib/src/qtlib_config.cpp
index 11e0de575c9050a752bdb5fc9266fbccfc602fc6..0c7b3961c1c5bd1b3ab30ae39831f16b4509fef9 100644
--- a/src/libs/qtlibs/config.cpp
+++ b/src/lib/qtlib/src/qtlib_config.cpp
@@ -1,21 +1,19 @@
 /**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
+ * qtlib
  *
  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
  * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
  */
 
-#include "config.h"
+#include "qtlib_config.h"
 
-#include "file.h"
-#include "dir.h"
-#include "json.h"
+#include "qtlib_file.h"
+#include "qtlib_dir.h"
+#include "qtlib_json.h"
 
-namespace qtlibs {
+namespace qtlib {
 
 Config::Config(const QString &configDirPath, QObject *parent)
     : QObject(parent), configDirPath_(configDirPath)
@@ -46,22 +44,19 @@ void Config::setConfigDirPath(const QString &configDirPath)
 QJsonObject Config::get(const QString &name)
 {
     QString configFilePath = configDirPath() + "/" + name + ".json";
-    QByteArray json = qtlibs::File(configFilePath).readData();
+    QByteArray json = qtlib::File(configFilePath).readData();
     if (json.isEmpty()) {
         json = QString("{}").toUtf8(); // Blank JSON data as default
     }
-    return qtlibs::Json(json).toObject();
+    return qtlib::Json(json).toObject();
 }
 
 bool Config::set(const QString &name, const QJsonObject &object)
 {
     QString configFilePath = configDirPath() + "/" + name + ".json";
-    QByteArray json = qtlibs::Json(object).toJson();
-    qtlibs::Dir(configDirPath()).make();
-    if (qtlibs::File(configFilePath).writeData(json)) {
-        return true;
-    }
-    return false;
+    QByteArray json = qtlib::Json(object).toJson();
+    qtlib::Dir(configDirPath()).make();
+    return qtlib::File(configFilePath).writeData(json);
 }
 
-} // namespace qtlibs
+} // namespace qtlib
diff --git a/src/libs/qtlibs/config.h b/src/lib/qtlib/src/qtlib_config.h
similarity index 67%
rename from src/libs/qtlibs/config.h
rename to src/lib/qtlib/src/qtlib_config.h
index 190c2c7d41d6fe2fa1e1b72e0f42a9d313dc3eeb..456a9b66b006d2f36ad00b3525472490f804725d 100644
--- a/src/libs/qtlibs/config.h
+++ b/src/lib/qtlib/src/qtlib_config.h
@@ -1,12 +1,10 @@
 /**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
+ * qtlib
  *
  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
  * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
  */
 
 #pragma once
@@ -14,7 +12,7 @@
 #include <QObject>
 #include <QJsonObject>
 
-namespace qtlibs {
+namespace qtlib {
 
 class Config : public QObject
 {
@@ -36,4 +34,4 @@ private:
     QString configDirPath_;
 };
 
-} // namespace qtlibs
+} // namespace qtlib
diff --git a/src/libs/qtlibs/dir.cpp b/src/lib/qtlib/src/qtlib_dir.cpp
similarity index 82%
rename from src/libs/qtlibs/dir.cpp
rename to src/lib/qtlib/src/qtlib_dir.cpp
index c444ae6c02651e72d2ba6056417de56bec151e94..94512f234c894f92b502a7ab5814cb504a18fe7c 100644
--- a/src/libs/qtlibs/dir.cpp
+++ b/src/lib/qtlib/src/qtlib_dir.cpp
@@ -1,22 +1,20 @@
 /**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
+ * qtlib
  *
  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
  * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
  */
 
-#include "dir.h"
+#include "qtlib_dir.h"
 
 #include <QDir>
 #include <QFile>
 #include <QFileInfo>
 #include <QStandardPaths>
 
-namespace qtlibs {
+namespace qtlib {
 
 Dir::Dir(const QString &path, QObject *parent)
     : QObject(parent), path_(path)
@@ -46,8 +44,7 @@ void Dir::setPath(const QString &path)
 
 bool Dir::exists()
 {
-    QDir dir(path());
-    return dir.exists();
+    return QDir(path()).exists();
 }
 
 QFileInfoList Dir::list()
@@ -76,15 +73,13 @@ bool Dir::copy(const QString &newPath)
 
 bool Dir::move(const QString &newPath)
 {
-    QDir dir(path());
-    return dir.rename(path(), newPath);
+    return QDir(path()).rename(path(), newPath);
 }
 
 bool Dir::remove()
 {
     // This function will remove files recursively
-    QDir dir(path());
-    return dir.removeRecursively();
+    return QDir(path()).removeRecursively();
 }
 
 QString Dir::rootPath()
@@ -133,11 +128,12 @@ QString Dir::kdehomePath()
 bool Dir::copyRecursively(const QString &srcPath, const QString &newPath)
 {
     QFileInfo fileInfo(srcPath);
-    if (fileInfo.isFile()) {
-        QFile file(srcPath);
-        if (file.copy(newPath)) {
-            return true;
-        }
+    if (fileInfo.isSymLink() && !fileInfo.exists()) {
+        // Ignore broken symlink
+        return true;
+    }
+    else if (fileInfo.isFile()) {
+        return QFile(srcPath).copy(newPath);
     }
     else if (fileInfo.isDir()) {
         QDir newDir(newPath);
@@ -158,4 +154,4 @@ bool Dir::copyRecursively(const QString &srcPath, const QString &newPath)
     return false;
 }
 
-} // namespace qtlibs
+} // namespace qtlib
diff --git a/src/libs/qtlibs/dir.h b/src/lib/qtlib/src/qtlib_dir.h
similarity index 76%
rename from src/libs/qtlibs/dir.h
rename to src/lib/qtlib/src/qtlib_dir.h
index aa3dfe15369a49bf5bfd49e87a92ddaaa54a6554..a8b5a5e4b1c9b0af56b80b7f43dfe88d04cdd30a 100644
--- a/src/libs/qtlibs/dir.h
+++ b/src/lib/qtlib/src/qtlib_dir.h
@@ -1,12 +1,10 @@
 /**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
+ * qtlib
  *
  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
  * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
  */
 
 #pragma once
@@ -14,7 +12,7 @@
 #include <QObject>
 #include <QFileInfoList>
 
-namespace qtlibs {
+namespace qtlib {
 
 class Dir : public QObject
 {
@@ -50,4 +48,4 @@ private:
     QString path_;
 };
 
-} // namespace qtlibs
+} // namespace qtlib
diff --git a/src/libs/qtlibs/file.cpp b/src/lib/qtlib/src/qtlib_file.cpp
similarity index 76%
rename from src/libs/qtlibs/file.cpp
rename to src/lib/qtlib/src/qtlib_file.cpp
index 39d9542826fb3f92a6ed853e23dfe3326f20f169..ac4f740f01ae1a302b9ea7b1cfc017b2cfa41db2 100644
--- a/src/libs/qtlibs/file.cpp
+++ b/src/lib/qtlib/src/qtlib_file.cpp
@@ -1,21 +1,19 @@
 /**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
+ * qtlib
  *
  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
  * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
  */
 
-#include "file.h"
+#include "qtlib_file.h"
 
 #include <QIODevice>
 #include <QTextStream>
 #include <QFile>
 
-namespace qtlibs {
+namespace qtlib {
 
 File::File(const QString &path, QObject *parent)
     : QObject(parent), path_(path)
@@ -45,8 +43,7 @@ void File::setPath(const QString &path)
 
 bool File::exists()
 {
-    QFile file(path());
-    return file.exists();
+    return QFile(path()).exists();
 }
 
 QByteArray File::readData()
@@ -99,20 +96,17 @@ bool File::writeText(const QString &data)
 
 bool File::copy(const QString &newPath)
 {
-    QFile file(path());
-    return file.copy(newPath);
+    return QFile(path()).copy(newPath);
 }
 
 bool File::move(const QString &newPath)
 {
-    QFile file(path());
-    return file.rename(newPath);
+    return QFile(path()).rename(newPath);
 }
 
 bool File::remove()
 {
-    QFile file(path());
-    return file.remove();
+    return QFile(path()).remove();
 }
 
-} // namespace qtlibs
+} // namespace qtlib
diff --git a/src/libs/qtlibs/file.h b/src/lib/qtlib/src/qtlib_file.h
similarity index 70%
rename from src/libs/qtlibs/file.h
rename to src/lib/qtlib/src/qtlib_file.h
index d9a77830189e8d51fc4595eb0ee07450e31ff2d0..5804b19b36e85caa5a66ef2f257ef54ee633957d 100644
--- a/src/libs/qtlibs/file.h
+++ b/src/lib/qtlib/src/qtlib_file.h
@@ -1,19 +1,17 @@
 /**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
+ * qtlib
  *
  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
  * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
  */
 
 #pragma once
 
 #include <QObject>
 
-namespace qtlibs {
+namespace qtlib {
 
 class File : public QObject
 {
@@ -41,4 +39,4 @@ private:
     QString path_;
 };
 
-} // namespace qtlibs
+} // namespace qtlib
diff --git a/src/libs/qtlibs/json.cpp b/src/lib/qtlib/src/qtlib_json.cpp
similarity index 59%
rename from src/libs/qtlibs/json.cpp
rename to src/lib/qtlib/src/qtlib_json.cpp
index 09d992231a8aa3dc577b0304fd6b787493f9bfb1..49faa10d6c8a6ad352a1b3ae79982763c9ecd060 100644
--- a/src/libs/qtlibs/json.cpp
+++ b/src/lib/qtlib/src/qtlib_json.cpp
@@ -1,20 +1,18 @@
 /**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
+ * qtlib
  *
  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
  * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
  */
 
-#include "json.h"
+#include "qtlib_json.h"
 
 #include <QJsonDocument>
 #include <QJsonParseError>
 
-namespace qtlibs {
+namespace qtlib {
 
 Json::Json(const QByteArray &json, QObject *parent)
     : QObject(parent), json_(json)
@@ -56,32 +54,27 @@ void Json::setJson(const QByteArray &json)
 
 void Json::fromObject(const QJsonObject &object)
 {
-    QJsonDocument doc(object);
-    setJson(doc.toJson());
+    setJson(QJsonDocument(object).toJson());
 }
 
 void Json::fromArray(const QJsonArray &array)
 {
-    QJsonDocument doc(array);
-    setJson(doc.toJson());
+    setJson(QJsonDocument(array).toJson());
 }
 
 QByteArray Json::toJson()
 {
-    QJsonDocument doc = QJsonDocument::fromJson(json());
-    return doc.toJson();
+    return QJsonDocument::fromJson(json()).toJson();
 }
 
 QJsonObject Json::toObject()
 {
-    QJsonDocument doc = QJsonDocument::fromJson(json());
-    return doc.object();
+    return QJsonDocument::fromJson(json()).object();
 }
 
 QJsonArray Json::toArray()
 {
-    QJsonDocument doc = QJsonDocument::fromJson(json());
-    return doc.array();
+    return QJsonDocument::fromJson(json()).array();
 }
 
 bool Json::isValid()
@@ -96,14 +89,12 @@ bool Json::isValid()
 
 bool Json::isObject()
 {
-    QJsonDocument doc = QJsonDocument::fromJson(json());
-    return doc.isObject();
+    return QJsonDocument::fromJson(json()).isObject();
 }
 
 bool Json::isArray()
 {
-    QJsonDocument doc = QJsonDocument::fromJson(json());
-    return doc.isArray();
+    return QJsonDocument::fromJson(json()).isArray();
 }
 
-} // namespace qtlibs
+} // namespace qtlib
diff --git a/src/libs/qtlibs/json.h b/src/lib/qtlib/src/qtlib_json.h
similarity index 74%
rename from src/libs/qtlibs/json.h
rename to src/lib/qtlib/src/qtlib_json.h
index 1c60a145c6c75f831670f0e274d1e97b6e33d6c0..5cb9a60a43939a6880492b7c6fa13a3106562036 100644
--- a/src/libs/qtlibs/json.h
+++ b/src/lib/qtlib/src/qtlib_json.h
@@ -1,12 +1,10 @@
 /**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
+ * qtlib
  *
  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
  * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
  */
 
 #pragma once
@@ -15,7 +13,7 @@
 #include <QJsonObject>
 #include <QJsonArray>
 
-namespace qtlibs {
+namespace qtlib {
 
 class Json : public QObject
 {
@@ -45,4 +43,4 @@ private:
     QByteArray json_;
 };
 
-} // namespace qtlibs
+} // namespace qtlib
diff --git a/src/lib/qtlib/src/qtlib_networkresource.cpp b/src/lib/qtlib/src/qtlib_networkresource.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e7d415eb683cea798ff25c418af5e384deb70234
--- /dev/null
+++ b/src/lib/qtlib/src/qtlib_networkresource.cpp
@@ -0,0 +1,289 @@
+/**
+ * qtlib
+ *
+ * @author      Akira Ohgaki <akiraohgaki@gmail.com>
+ * @copyright   Akira Ohgaki
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
+ */
+
+#include "qtlib_networkresource.h"
+
+#include <QEventLoop>
+
+#include "qtlib_file.h"
+
+namespace qtlib {
+
+NetworkResource::NetworkResource(const QString &id, const QUrl &url, bool async, QObject *parent)
+    : QObject(parent), id_(id), url_(url), async_(async)
+{
+    setManager(new QNetworkAccessManager(this));
+}
+
+NetworkResource::~NetworkResource()
+{
+    manager()->deleteLater();
+}
+
+NetworkResource::NetworkResource(const NetworkResource &other, QObject *parent)
+    : QObject(parent)
+{
+    setId(other.id());
+    setUrl(other.url());
+    setAsync(other.async());
+    setRequest(other.request());
+    setManager(new QNetworkAccessManager(this));
+}
+
+NetworkResource &NetworkResource::operator =(const NetworkResource &other)
+{
+    setId(other.id());
+    setUrl(other.url());
+    setAsync(other.async());
+    setRequest(other.request());
+    return *this;
+}
+
+QString NetworkResource::id() const
+{
+    return id_;
+}
+
+void NetworkResource::setId(const QString &id)
+{
+    id_ = id;
+}
+
+QUrl NetworkResource::url() const
+{
+    return url_;
+}
+
+void NetworkResource::setUrl(const QUrl &url)
+{
+    url_ = url;
+}
+
+bool NetworkResource::async() const
+{
+    return async_;
+}
+
+void NetworkResource::setAsync(bool async)
+{
+    async_ = async;
+}
+
+QNetworkRequest NetworkResource::request() const
+{
+    return request_;
+}
+
+void NetworkResource::setRequest(const QNetworkRequest &request)
+{
+    request_ = request;
+}
+
+QNetworkAccessManager *NetworkResource::manager() const
+{
+    return manager_;
+}
+
+QNetworkReply *NetworkResource::reply() const
+{
+    return reply_;
+}
+
+QString NetworkResource::method() const
+{
+    return method_;
+}
+
+QString NetworkResource::contentType() const
+{
+    return contentType_;
+}
+
+QByteArray NetworkResource::contentData() const
+{
+    return contentData_;
+}
+
+NetworkResource *NetworkResource::head()
+{
+    setMethod("HEAD");
+    return send(url(), async());
+}
+
+NetworkResource *NetworkResource::get()
+{
+    setMethod("GET");
+    return send(url(), async());
+}
+
+NetworkResource *NetworkResource::post(const QByteArray &contentData, const QString &contentType)
+{
+    setMethod("POST");
+    setContentType(contentType);
+    setContentData(contentData);
+    return send(url(), async());
+}
+
+NetworkResource *NetworkResource::post(const QUrlQuery &contentData)
+{
+    setMethod("POST");
+    setContentType("application/x-www-form-urlencoded");
+    setContentData(contentData.toString(QUrl::FullyEncoded).toUtf8());
+    return send(url(), async());
+}
+
+NetworkResource *NetworkResource::put(const QByteArray &contentData, const QString &contentType)
+{
+    setMethod("PUT");
+    setContentType(contentType);
+    setContentData(contentData);
+    return send(url(), async());
+}
+
+NetworkResource *NetworkResource::put(const QUrlQuery &contentData)
+{
+    setMethod("PUT");
+    setContentType("application/x-www-form-urlencoded");
+    setContentData(contentData.toString(QUrl::FullyEncoded).toUtf8());
+    return send(url(), async());
+}
+
+NetworkResource *NetworkResource::deleteResource()
+{
+    setMethod("DELETE");
+    return send(url(), async());
+}
+
+bool NetworkResource::isFinishedWithNoError()
+{
+    if (reply()->isFinished() && reply()->error() == QNetworkReply::NoError) {
+        return true;
+    }
+    return false;
+}
+
+QByteArray NetworkResource::readData()
+{
+    QByteArray data;
+    if (isFinishedWithNoError()) {
+        data = reply()->readAll();
+    }
+    return data;
+}
+
+bool NetworkResource::saveData(const QString &path)
+{
+    if (isFinishedWithNoError()) {
+        return qtlib::File(path).writeData(reply()->readAll());
+    }
+    return false;
+}
+
+void NetworkResource::abort()
+{
+    if (reply()->isRunning()) {
+        reply()->abort();
+    }
+}
+
+void NetworkResource::replyFinished()
+{
+    if (isFinishedWithNoError()) {
+        // Check if redirection
+        // Note: An auto redirection option is available since Qt 5.6
+        QUrl redirectUrl;
+        if (reply()->hasRawHeader("Location")) {
+            redirectUrl.setUrl(QString(reply()->rawHeader("Location")));
+        }
+        else if (reply()->hasRawHeader("Refresh")) {
+            redirectUrl.setUrl(QString(reply()->rawHeader("Refresh")).split("url=").last());
+        }
+        if (!redirectUrl.isEmpty()) {
+            if (redirectUrl.isRelative()) {
+                redirectUrl = reply()->url().resolved(redirectUrl);
+            }
+            reply()->deleteLater();
+            send(redirectUrl, true);
+            return;
+        }
+    }
+    emit finished(this);
+}
+
+void NetworkResource::replyDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
+{
+    emit downloadProgress(id(), bytesReceived, bytesTotal);
+}
+
+void NetworkResource::replyUploadProgress(qint64 bytesSent, qint64 bytesTotal)
+{
+    emit uploadProgress(id(), bytesSent, bytesTotal);
+}
+
+void NetworkResource::setManager(QNetworkAccessManager *manager)
+{
+    manager_ = manager;
+}
+
+void NetworkResource::setReply(QNetworkReply *reply)
+{
+    reply_ = reply;
+}
+
+void NetworkResource::setMethod(const QString &method)
+{
+    method_ = method;
+}
+
+void NetworkResource::setContentType(const QString &contentType)
+{
+    contentType_ = contentType;
+}
+
+void NetworkResource::setContentData(const QByteArray &contentData)
+{
+    contentData_ = contentData;
+}
+
+NetworkResource *NetworkResource::send(const QUrl &url, bool async)
+{
+    QNetworkRequest networkRequest = request();
+    networkRequest.setUrl(url);
+    if (method() == "HEAD") {
+        setReply(manager()->head(networkRequest));
+    }
+    else if (method() == "GET") {
+        setReply(manager()->get(networkRequest));
+    }
+    else if (method() == "POST") {
+        networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(contentType()));
+        setReply(manager()->post(networkRequest, contentData()));
+    }
+    else if (method() == "PUT") {
+        networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(contentType()));
+        setReply(manager()->put(networkRequest, contentData()));
+    }
+    else if (method() == "DELETE") {
+        setReply(manager()->deleteResource(networkRequest));
+    }
+    else {
+        Q_ASSERT(false);
+    }
+    connect(reply(), &QNetworkReply::finished, this, &NetworkResource::replyFinished);
+    connect(reply(), &QNetworkReply::downloadProgress, this, &NetworkResource::replyDownloadProgress);
+    connect(reply(), &QNetworkReply::uploadProgress, this, &NetworkResource::replyUploadProgress);
+    if (!async) {
+        QEventLoop eventLoop;
+        connect(this, &NetworkResource::finished, &eventLoop, &QEventLoop::quit);
+        eventLoop.exec();
+    }
+    return this;
+}
+
+} // namespace qtlib
diff --git a/src/libs/qtlibs/networkresource.h b/src/lib/qtlib/src/qtlib_networkresource.h
similarity index 50%
rename from src/libs/qtlibs/networkresource.h
rename to src/lib/qtlib/src/qtlib_networkresource.h
index 1a4c73bc58ccb8caf4c197bcdced28570c0b4bc1..beae8fc20949f57e58a87a9049e3e4553e65950f 100644
--- a/src/libs/qtlibs/networkresource.h
+++ b/src/lib/qtlib/src/qtlib_networkresource.h
@@ -1,37 +1,36 @@
 /**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
+ * qtlib
  *
  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
  * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
  */
 
 #pragma once
 
 #include <QObject>
 #include <QUrl>
+#include <QUrlQuery>
 #include <QNetworkRequest>
 #include <QNetworkAccessManager>
 #include <QNetworkReply>
 
-namespace qtlibs {
+namespace qtlib {
 
 class NetworkResource : public QObject
 {
     Q_OBJECT
 
 public:
-    explicit NetworkResource(const QString &name = "", const QUrl &url = QUrl(), bool async = true, QObject *parent = 0);
+    explicit NetworkResource(const QString &id = "", const QUrl &url = QUrl(), bool async = true, QObject *parent = 0);
     ~NetworkResource();
 
     NetworkResource(const NetworkResource &other, QObject *parent = 0);
     NetworkResource &operator =(const NetworkResource &other);
 
-    QString name() const;
-    void setName(const QString &name);
+    QString id() const;
+    void setId(const QString &id);
     QUrl url() const;
     void setUrl(const QUrl &url);
     bool async() const;
@@ -41,36 +40,51 @@ public:
     QNetworkAccessManager *manager() const;
     QNetworkReply *reply() const;
     QString method() const;
+    QString contentType() const;
+    QByteArray contentData() const;
 
     NetworkResource *head();
     NetworkResource *get();
+    NetworkResource *post(const QByteArray &contentData, const QString &contentType);
+    NetworkResource *post(const QUrlQuery &contentData);
+    NetworkResource *put(const QByteArray &contentData, const QString &contentType);
+    NetworkResource *put(const QUrlQuery &contentData);
+    NetworkResource *deleteResource();
+    bool isFinishedWithNoError();
     QByteArray readData();
     bool saveData(const QString &path);
 
 signals:
     void finished(NetworkResource *resource);
-    void downloadProgress(const qint64 &bytesReceived, const qint64 &bytesTotal);
+    void downloadProgress(QString id, qint64 bytesReceived, qint64 bytesTotal);
+    void uploadProgress(QString id, qint64 bytesSent, qint64 bytesTotal);
 
 public slots:
     void abort();
 
 private slots:
     void replyFinished();
+    void replyDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+    void replyUploadProgress(qint64 bytesSent, qint64 bytesTotal);
 
 private:
     void setManager(QNetworkAccessManager *manager);
     void setReply(QNetworkReply *reply);
     void setMethod(const QString &method);
+    void setContentType(const QString &contentType);
+    void setContentData(const QByteArray &contentData);
 
-    NetworkResource *send(bool async, const QNetworkRequest &request);
+    NetworkResource *send(const QUrl &url, bool async);
 
-    QString name_;
+    QString id_;
     QUrl url_;
     bool async_;
     QNetworkRequest request_;
     QNetworkAccessManager *manager_;
     QNetworkReply *reply_;
     QString method_;
+    QString contentType_;
+    QByteArray contentData_;
 };
 
-} // namespace qtlibs
+} // namespace qtlib
diff --git a/src/lib/qtlib/src/qtlib_ocsapi.cpp b/src/lib/qtlib/src/qtlib_ocsapi.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f9f345d962a101c3e76bf6199d4a3687d5ba1bdc
--- /dev/null
+++ b/src/lib/qtlib/src/qtlib_ocsapi.cpp
@@ -0,0 +1,197 @@
+/**
+ * qtlib
+ *
+ * @author      Akira Ohgaki <akiraohgaki@gmail.com>
+ * @copyright   Akira Ohgaki
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
+ */
+
+#include "qtlib_ocsapi.h"
+
+#include <QXmlStreamReader>
+
+#include "qtlib_json.h"
+#include "qtlib_networkresource.h"
+
+namespace qtlib {
+
+// OCS-API Specification
+// https://www.freedesktop.org/wiki/Specifications/open-collaboration-services/
+
+OcsApi::OcsApi(const QString &id, const QUrl &baseUrl, const QString &userName, const QString &password, QObject *parent)
+    : QObject(parent), id_(id), baseUrl_(baseUrl), userName_(userName), password_(password)
+{}
+
+OcsApi::OcsApi(const OcsApi &other, QObject *parent)
+    : QObject(parent)
+{
+    setId(other.id());
+    setBaseUrl(other.baseUrl());
+    setUserName(other.userName());
+    setPassword(other.password());
+}
+
+OcsApi &OcsApi::operator =(const OcsApi &other)
+{
+    setId(other.id());
+    setBaseUrl(other.baseUrl());
+    setUserName(other.userName());
+    setPassword(other.password());
+    return *this;
+}
+
+QString OcsApi::id() const
+{
+    return id_;
+}
+
+void OcsApi::setId(const QString &id)
+{
+    id_ = id;
+}
+
+QUrl OcsApi::baseUrl() const
+{
+    return baseUrl_;
+}
+
+void OcsApi::setBaseUrl(const QUrl &baseUrl)
+{
+    baseUrl_ = baseUrl;
+}
+
+QString OcsApi::userName() const
+{
+    return userName_;
+}
+
+void OcsApi::setUserName(const QString &userName)
+{
+    userName_ = userName;
+}
+
+QString OcsApi::password() const
+{
+    return password_;
+}
+
+void OcsApi::setPassword(const QString &password)
+{
+    password_ = password;
+}
+
+QJsonObject OcsApi::getConfig()
+{
+    QUrl url = baseUrl().resolved(QUrl("config"));
+    url.setQuery("format=json");
+    qtlib::NetworkResource resource(url.toString(), url, false);
+    return qtlib::Json(resource.get()->readData()).toObject();
+}
+
+QJsonObject OcsApi::checkPerson()
+{
+    QUrl url = baseUrl().resolved(QUrl("person/check"));
+    QUrlQuery formData;
+    formData.addQueryItem("login", userName());
+    formData.addQueryItem("password", password());
+    formData.addQueryItem("format", "json");
+    qtlib::NetworkResource resource(url.toString(), url, false);
+    return qtlib::Json(resource.post(formData)->readData()).toObject();
+}
+
+QJsonObject OcsApi::getPersonDataSet(const QUrlQuery &query)
+{
+    QUrl url = baseUrl().resolved(QUrl("person/data"));
+    url.setUserName(userName());
+    url.setPassword(password());
+    QUrlQuery newQuery(query);
+    newQuery.removeQueryItem("format");
+    newQuery.addQueryItem("format", "json");
+    url.setQuery(newQuery);
+    qtlib::NetworkResource resource(url.toString(), url, false);
+    return qtlib::Json(resource.get()->readData()).toObject();
+}
+
+QJsonObject OcsApi::getPersonData(const QString &personId)
+{
+    QUrl url = baseUrl().resolved(QUrl("person/data/" + personId));
+    url.setUserName(userName());
+    url.setPassword(password());
+    url.setQuery("format=json");
+    qtlib::NetworkResource resource(url.toString(), url, false);
+    return qtlib::Json(resource.get()->readData()).toObject();
+}
+
+QJsonObject OcsApi::getPersonSelf()
+{
+    QUrl url = baseUrl().resolved(QUrl("person/self"));
+    url.setUserName(userName());
+    url.setPassword(password());
+    url.setQuery("format=json");
+    qtlib::NetworkResource resource(url.toString(), url, false);
+    return qtlib::Json(resource.get()->readData()).toObject();
+}
+
+QJsonObject OcsApi::getContentCategories()
+{
+    QUrl url = baseUrl().resolved(QUrl("content/categories"));
+    url.setQuery("format=json");
+    qtlib::NetworkResource resource(url.toString(), url, false);
+    return qtlib::Json(resource.get()->readData()).toObject();
+}
+
+QJsonObject OcsApi::getContentDataSet(const QUrlQuery &query)
+{
+    QUrl url = baseUrl().resolved(QUrl("content/data"));
+    QUrlQuery newQuery(query);
+    newQuery.removeQueryItem("format");
+    newQuery.addQueryItem("format", "json");
+    url.setQuery(newQuery);
+    qtlib::NetworkResource resource(url.toString(), url, false);
+    return qtlib::Json(resource.get()->readData()).toObject();
+}
+
+QJsonObject OcsApi::getContentData(const QString &contentId)
+{
+    QUrl url = baseUrl().resolved(QUrl("content/data/" + contentId));
+    url.setQuery("format=json");
+    qtlib::NetworkResource resource(url.toString(), url, false);
+    return qtlib::Json(resource.get()->readData()).toObject();
+}
+
+QJsonObject OcsApi::getContentDownload(const QString &contentId, const QString &itemId)
+{
+    QUrl url = baseUrl().resolved(QUrl("content/download/" + contentId + "/" + itemId));
+    url.setQuery("format=json");
+    qtlib::NetworkResource resource(url.toString(), url, false);
+    return qtlib::Json(resource.get()->readData()).toObject();
+}
+
+QJsonArray OcsApi::getProviderFile(const QUrl &url)
+{
+    QJsonArray providers;
+    qtlib::NetworkResource resource(url.toString(), url, false);
+    QXmlStreamReader reader(resource.get()->readData());
+    QStringList whitelist;
+    whitelist << "id" << "location" << "name" << "icon" << "termsofuse" << "register";
+    while (!reader.atEnd() && !reader.hasError()) {
+        reader.readNext();
+        if (reader.isStartElement() && reader.name() == "provider") {
+            QJsonObject provider;
+            provider["_providerfile"] = url.toString();
+            providers.append(provider);
+            continue;
+        }
+        QString elementName = reader.name().toString();
+        if (!providers.isEmpty() && whitelist.contains(elementName)) {
+            int i(providers.size() - 1);
+            QJsonObject provider = providers[i].toObject();
+            provider[elementName] = reader.readElementText();
+            providers[i] = provider;
+        }
+    }
+    return providers;
+}
+
+} // namespace qtlib
diff --git a/src/lib/qtlib/src/qtlib_ocsapi.h b/src/lib/qtlib/src/qtlib_ocsapi.h
new file mode 100644
index 0000000000000000000000000000000000000000..93346d8efa77d0785cc277f3d741646901608cfb
--- /dev/null
+++ b/src/lib/qtlib/src/qtlib_ocsapi.h
@@ -0,0 +1,58 @@
+/**
+ * qtlib
+ *
+ * @author      Akira Ohgaki <akiraohgaki@gmail.com>
+ * @copyright   Akira Ohgaki
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
+ */
+
+#pragma once
+
+#include <QObject>
+#include <QUrl>
+#include <QUrlQuery>
+#include <QJsonObject>
+#include <QJsonArray>
+
+namespace qtlib {
+
+class OcsApi : public QObject
+{
+    Q_OBJECT
+
+public:
+    explicit OcsApi(const QString &id = "", const QUrl &baseUrl = QUrl(), const QString &userName = "", const QString &password = "", QObject *parent = 0);
+
+    OcsApi(const OcsApi &other, QObject *parent = 0);
+    OcsApi &operator =(const OcsApi &other);
+
+    QString id() const;
+    void setId(const QString &id);
+    QUrl baseUrl() const;
+    void setBaseUrl(const QUrl &baseUrl);
+    QString userName() const;
+    void setUserName(const QString &userName);
+    QString password() const;
+    void setPassword(const QString &password);
+
+    QJsonObject getConfig();
+    QJsonObject checkPerson();
+    QJsonObject getPersonDataSet(const QUrlQuery &query = QUrlQuery());
+    QJsonObject getPersonData(const QString &personId);
+    QJsonObject getPersonSelf();
+    QJsonObject getContentCategories();
+    QJsonObject getContentDataSet(const QUrlQuery &query = QUrlQuery());
+    QJsonObject getContentData(const QString &contentId);
+    QJsonObject getContentDownload(const QString &contentId, const QString &itemId);
+
+    static QJsonArray getProviderFile(const QUrl &url);
+
+private:
+    QString id_;
+    QUrl baseUrl_;
+    QString userName_;
+    QString password_;
+};
+
+} // namespace qtlib
diff --git a/src/libs/qtlibs/package.cpp b/src/lib/qtlib/src/qtlib_package.cpp
similarity index 77%
rename from src/libs/qtlibs/package.cpp
rename to src/lib/qtlib/src/qtlib_package.cpp
index 9db1ae9ab9c872e4733834780e5e9b656b5edd92..fde761253b9023598af258feb3d1d8da3241458f 100644
--- a/src/libs/qtlibs/package.cpp
+++ b/src/lib/qtlib/src/qtlib_package.cpp
@@ -1,17 +1,15 @@
 /**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
+ * qtlib
  *
  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
  * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
  */
 
-#include "package.h"
+#include "qtlib_package.h"
 
-#ifdef QTLIBS_UNIX
+#ifdef QTLIB_UNIX
 #include <QJsonObject>
 #include <QMimeDatabase>
 #include <QProcess>
@@ -21,7 +19,7 @@
 #include <QAndroidJniObject>
 #endif
 
-namespace qtlibs {
+namespace qtlib {
 
 Package::Package(const QString &path, QObject *parent)
     : QObject(parent), path_(path)
@@ -49,7 +47,7 @@ void Package::setPath(const QString &path)
     path_ = path;
 }
 
-#ifdef QTLIBS_UNIX
+#ifdef QTLIB_UNIX
 bool Package::installAsProgram(const QString &newPath)
 {
     QStringList arguments;
@@ -86,8 +84,7 @@ bool Package::installAsArchive(const QString &destinationDirPath)
     archiveTypes["application/x-rar"] = QString("rar");
     archiveTypes["application/x-rar-compressed"] = QString("rar");
 
-    QMimeDatabase mimeDb;
-    QString mimeType = mimeDb.mimeTypeForFile(path()).name();
+    QString mimeType = QMimeDatabase().mimeTypeForFile(path()).name();
 
     if (archiveTypes.contains(mimeType)) {
         QString archiveType = archiveTypes[mimeType].toString();
@@ -134,21 +131,31 @@ bool Package::installAsApk()
 {
     QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;");
     if (activity.isValid()) {
-        QAndroidJniObject fileUri = QAndroidJniObject::fromString(path());
-        QAndroidJniObject parsedUri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", fileUri.object());
+        QString filePath = path();
+        if (filePath.startsWith("file://", Qt::CaseInsensitive)) {
+            filePath.replace("file://localhost", "", Qt::CaseInsensitive);
+            filePath.replace("file://", "", Qt::CaseInsensitive);
+        }
+
+        QAndroidJniObject fileUri = QAndroidJniObject::fromString("file://" + filePath);
+        QAndroidJniObject uri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", fileUri.object());
         QAndroidJniObject mimeType = QAndroidJniObject::fromString("application/vnd.android.package-archive");
-        QAndroidJniObject activityKind = QAndroidJniObject::fromString("android.intent.action.VIEW");
-        QAndroidJniObject intent("android/content/Intent", "(Ljava/lang/String;)V", activityKind.object());
-        intent = intent.callObjectMethod("setDataAndType", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/Intent;", parsedUri.object(), mimeType.object());
-        intent = intent.callObjectMethod("setFlags", "(I)Landroid/content/Intent;", 0x10000000); // 0x10000000 = FLAG_ACTIVITY_NEW_TASK
-        activity.callObjectMethod("startActivity", "(Landroid/content/Intent;)V", intent.object());
+
+        QAndroidJniObject ACTION_VIEW = QAndroidJniObject::getStaticObjectField("android/content/Intent", "ACTION_VIEW", "Ljava/lang/String");
+        QAndroidJniObject FLAG_ACTIVITY_NEW_TASK = QAndroidJniObject::getStaticObjectField("android/content/Intent", "FLAG_ACTIVITY_NEW_TASK", "Ljava/lang/Integer");
+
+        QAndroidJniObject intent("android/content/Intent", "(Ljava/lang/String;)V", ACTION_VIEW.object());
+        intent = intent.callObjectMethod("setDataAndType", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/Intent;", uri.object(), mimeType.object());
+        intent = intent.callObjectMethod("setFlags", "(I)Landroid/content/Intent;", FLAG_ACTIVITY_NEW_TASK.object());
+
+        activity.callMethod<void>("startActivity", "(Landroid/content/Intent;)V", intent.object());
         return true;
     }
     return false;
 }
 #endif
 
-#ifdef QTLIBS_UNIX
+#ifdef QTLIB_UNIX
 bool Package::execute(const QString &program, const QStringList &arguments)
 {
     QProcess process;
@@ -161,4 +168,4 @@ bool Package::execute(const QString &program, const QStringList &arguments)
 }
 #endif
 
-} // namespace qtlibs
+} // namespace qtlib
diff --git a/src/libs/qtlibs/package.h b/src/lib/qtlib/src/qtlib_package.h
similarity index 72%
rename from src/libs/qtlibs/package.h
rename to src/lib/qtlib/src/qtlib_package.h
index 077a8cdddbba17acd4e4f40bc83526bcc4ab7969..e5bd1125023ff12a6dd3b9c61b4c2dae621c4c6f 100644
--- a/src/libs/qtlibs/package.h
+++ b/src/lib/qtlib/src/qtlib_package.h
@@ -1,19 +1,17 @@
 /**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
+ * qtlib
  *
  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
  * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
+ * @license     https://opensource.org/licenses/LGPL-3.0
+ * @link        https://github.com/akiraohgaki/qtlib
  */
 
 #pragma once
 
 #include <QObject>
 
-namespace qtlibs {
+namespace qtlib {
 
 class Package : public QObject
 {
@@ -28,7 +26,7 @@ public:
     QString path() const;
     void setPath(const QString &path);
 
-#ifdef QTLIBS_UNIX
+#ifdef QTLIB_UNIX
     bool installAsProgram(const QString &newPath);
     bool installAsFile(const QString &newPath);
     bool installAsArchive(const QString &destinationDirPath);
@@ -41,11 +39,11 @@ public:
 #endif
 
 private:
-#ifdef QTLIBS_UNIX
+#ifdef QTLIB_UNIX
     bool execute(const QString &program, const QStringList &arguments);
 #endif
 
     QString path_;
 };
 
-} // namespace qtlibs
+} // namespace qtlib
diff --git a/src/lib/qtlib/test/main.cpp b/src/lib/qtlib/test/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9c2ab9447cdfc88ef2d0d1bb073afeed420107dd
--- /dev/null
+++ b/src/lib/qtlib/test/main.cpp
@@ -0,0 +1,67 @@
+#include <QObject>
+#include <QCoreApplication>
+#include <QDebug>
+
+#include "qtlib_file.h"
+#include "qtlib_dir.h"
+#include "qtlib_json.h"
+#include "qtlib_config.h"
+#include "qtlib_networkresource.h"
+#include "qtlib_ocsapi.h"
+#include "qtlib_package.h"
+
+class Test : public QObject
+{
+public:
+    Test() {}
+    virtual ~Test() {}
+
+    void start()
+    {
+        qDebug() << "Start";
+
+        qtlib::NetworkResource *resource = new qtlib::NetworkResource(
+                    "LGPL-3.0",
+                    QUrl("https://api.opensource.org/license/LGPL-3.0"),
+                    false,
+                    this);
+        connect(resource, &qtlib::NetworkResource::downloadProgress, this, &Test::downloadProgress);
+        QJsonObject result = qtlib::Json(resource->get()->readData()).toObject();
+
+        qDebug() << resource->id() << ":" << result["name"].toString();
+
+        connect(resource, &qtlib::NetworkResource::finished, this, &Test::finished);
+        resource->setId(result["name"].toString());
+        resource->setUrl(QUrl(result["text"].toArray()[0].toObject()["url"].toString()));
+        resource->setAsync(true);
+        resource->get();
+    }
+
+public slots:
+    void finished(qtlib::NetworkResource *resource)
+    {
+        QString path = qtlib::Dir::tempPath() + "/" + resource->url().fileName();
+        resource->saveData(path);
+
+        qDebug() << "Downloaded" << resource->id() << ":" << path;
+        qDebug() << "Finished";
+
+        resource->deleteLater();
+        QCoreApplication::exit();
+    }
+
+    void downloadProgress(QString id, qint64 bytesReceived, qint64 bytesTotal)
+    {
+        qDebug() << "Progress" << id << ":" << bytesReceived << "/" << bytesTotal;
+    }
+};
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication app(argc, argv);
+
+    Test test;
+    test.start();
+
+    return app.exec();
+}
diff --git a/src/libs/qtlibs/networkresource.cpp b/src/libs/qtlibs/networkresource.cpp
deleted file mode 100644
index ddf879c4fbf3ac5bc4a532d54e60f46bd6c7c355..0000000000000000000000000000000000000000
--- a/src/libs/qtlibs/networkresource.cpp
+++ /dev/null
@@ -1,203 +0,0 @@
-/**
- * A library for Qt app
- *
- * LICENSE: The GNU Lesser General Public License, version 3.0
- *
- * @author      Akira Ohgaki <akiraohgaki@gmail.com>
- * @copyright   Akira Ohgaki
- * @license     https://opensource.org/licenses/LGPL-3.0  The GNU Lesser General Public License, version 3.0
- * @link        https://github.com/akiraohgaki/qtlibs
- */
-
-#include "networkresource.h"
-
-#include <QEventLoop>
-
-#include "file.h"
-
-namespace qtlibs {
-
-NetworkResource::NetworkResource(const QString &name, const QUrl &url, bool async, QObject *parent)
-    : QObject(parent), name_(name), url_(url), async_(async)
-{
-    setManager(new QNetworkAccessManager(this));
-}
-
-NetworkResource::~NetworkResource()
-{
-    manager()->deleteLater();
-}
-
-NetworkResource::NetworkResource(const NetworkResource &other, QObject *parent)
-    : QObject(parent)
-{
-    setName(other.name());
-    setUrl(other.url());
-    setAsync(other.async());
-    setRequest(other.request());
-    setManager(new QNetworkAccessManager(this));
-}
-
-NetworkResource &NetworkResource::operator =(const NetworkResource &other)
-{
-    setName(other.name());
-    setUrl(other.url());
-    setAsync(other.async());
-    setRequest(other.request());
-    return *this;
-}
-
-QString NetworkResource::name() const
-{
-    return name_;
-}
-
-void NetworkResource::setName(const QString &name)
-{
-    name_ = name;
-}
-
-QUrl NetworkResource::url() const
-{
-    return url_;
-}
-
-void NetworkResource::setUrl(const QUrl &url)
-{
-    url_ = url;
-}
-
-bool NetworkResource::async() const
-{
-    return async_;
-}
-
-void NetworkResource::setAsync(bool async)
-{
-    async_ = async;
-}
-
-QNetworkRequest NetworkResource::request() const
-{
-    return request_;
-}
-
-void NetworkResource::setRequest(const QNetworkRequest &request)
-{
-    request_ = request;
-}
-
-QNetworkAccessManager *NetworkResource::manager() const
-{
-    return manager_;
-}
-
-QNetworkReply *NetworkResource::reply() const
-{
-    return reply_;
-}
-
-QString NetworkResource::method() const
-{
-    return method_;
-}
-
-NetworkResource *NetworkResource::head()
-{
-    setMethod("HEAD");
-    QNetworkRequest networkRequest = request();
-    networkRequest.setUrl(url());
-    return send(async(), networkRequest);
-}
-
-NetworkResource *NetworkResource::get()
-{
-    setMethod("GET");
-    QNetworkRequest networkRequest = request();
-    networkRequest.setUrl(url());
-    return send(async(), networkRequest);
-}
-
-QByteArray NetworkResource::readData()
-{
-    QByteArray data;
-    if (reply()->isFinished()) {
-        data = reply()->readAll();
-    }
-    return data;
-}
-
-bool NetworkResource::saveData(const QString &path)
-{
-    if (reply()->isFinished()) {
-        return qtlibs::File(path).writeData(readData());
-    }
-    return false;
-}
-
-void NetworkResource::abort()
-{
-    if (reply()->isRunning()) {
-        reply()->abort();
-    }
-}
-
-void NetworkResource::replyFinished()
-{
-    if (reply()->error() == QNetworkReply::NoError) {
-        // Check if redirection
-        // Note: An auto redirection option is available since Qt 5.6
-        QString newUrl;
-        if (reply()->hasRawHeader("Location")) {
-            newUrl = QString(reply()->rawHeader("Location"));
-        }
-        else if (reply()->hasRawHeader("Refresh")) {
-            newUrl = QString(reply()->rawHeader("Refresh")).split("url=").last();
-        }
-        if (!newUrl.isEmpty()) {
-            if (newUrl.startsWith("/")) {
-                newUrl = reply()->url().authority() + newUrl;
-            }
-            QNetworkRequest networkRequest = request();
-            networkRequest.setUrl(QUrl(newUrl));
-            send(true, networkRequest);
-            return;
-        }
-    }
-    emit finished(this);
-}
-
-void NetworkResource::setManager(QNetworkAccessManager *manager)
-{
-    manager_ = manager;
-}
-
-void NetworkResource::setReply(QNetworkReply *reply)
-{
-    reply_ = reply;
-}
-
-void NetworkResource::setMethod(const QString &method)
-{
-    method_ = method;
-}
-
-NetworkResource *NetworkResource::send(bool async, const QNetworkRequest &request)
-{
-    if (method() == "HEAD") {
-        setReply(manager()->head(request));
-    }
-    else if (method() == "GET") {
-        setReply(manager()->get(request));
-        connect(reply(), &QNetworkReply::downloadProgress, this, &NetworkResource::downloadProgress);
-    }
-    connect(reply(), &QNetworkReply::finished, this, &NetworkResource::replyFinished);
-    if (!async) {
-        QEventLoop eventLoop;
-        connect(this, &NetworkResource::finished, &eventLoop, &QEventLoop::quit);
-        eventLoop.exec();
-    }
-    return this;
-}
-
-} // namespace qtlibs
diff --git a/src/libs/qtlibs/qtlibs.pri b/src/libs/qtlibs/qtlibs.pri
deleted file mode 100644
index 1050283dd6f985bfada5b46a89b1b7ecf6792d0e..0000000000000000000000000000000000000000
--- a/src/libs/qtlibs/qtlibs.pri
+++ /dev/null
@@ -1,27 +0,0 @@
-QT += \
-    core \
-    network
-
-HEADERS += \
-    $${PWD}/file.h \
-    $${PWD}/dir.h \
-    $${PWD}/json.h \
-    $${PWD}/config.h \
-    $${PWD}/networkresource.h \
-    $${PWD}/package.h
-
-SOURCES += \
-    $${PWD}/file.cpp \
-    $${PWD}/dir.cpp \
-    $${PWD}/json.cpp \
-    $${PWD}/config.cpp \
-    $${PWD}/networkresource.cpp \
-    $${PWD}/package.cpp
-
-unix:!ios:!android {
-    DEFINES += QTLIBS_UNIX
-}
-
-android {
-    QT += androidextras
-}
diff --git a/xdgurl.pro b/xdgurl.pro
index ef0d7d99b58cff15dad44833239417660a004dcd..7e008d5a1016896ae32b8bc08ef6bb82109323e9 100644
--- a/xdgurl.pro
+++ b/xdgurl.pro
@@ -1,13 +1,13 @@
+include(src/lib/qtlib/qtlib.pri)
+
+include(src/app/app.pri)
+
 TARGET = xdgurl
 
 TEMPLATE = app
 
 CONFIG += c++11
 
-INCLUDEPATH += \
-    src/app \
-    src/libs
-
 RESOURCES += src/desktop/desktop.qrc
 
 DISTFILES += \
@@ -24,8 +24,4 @@ DISTFILES += \
     pkg/fedora/xdgurl.spec \
     pkg/arch/PKGBUILD
 
-include(src/libs/qtlibs/qtlibs.pri)
-
-include(src/app/app.pri)
-
 include(deployment.pri)