diff --git a/app/app.pri b/app/app.pri
index 21388a6a3eab19e9f25988883e0f9348f2d16a11..737e3eba34ea71dcc522e3e8a1f466a72023b313 100644
--- a/app/app.pri
+++ b/app/app.pri
@@ -9,7 +9,7 @@ HEADERS += \
     $${PWD}/src/websockets/websocketserver.h \
     $${PWD}/src/handlers/confighandler.h \
     $${PWD}/src/handlers/systemhandler.h \
-    $${PWD}/src/handlers/ocshandler.h \
+    $${PWD}/src/handlers/ocsapihandler.h \
     $${PWD}/src/handlers/itemhandler.h
 
 SOURCES += \
@@ -17,7 +17,7 @@ SOURCES += \
     $${PWD}/src/websockets/websocketserver.cpp \
     $${PWD}/src/handlers/confighandler.cpp \
     $${PWD}/src/handlers/systemhandler.cpp \
-    $${PWD}/src/handlers/ocshandler.cpp \
+    $${PWD}/src/handlers/ocsapihandler.cpp \
     $${PWD}/src/handlers/itemhandler.cpp
 
 RESOURCES += $${PWD}/configs/configs.qrc
diff --git a/app/src/handlers/itemhandler.cpp b/app/src/handlers/itemhandler.cpp
index eb11a85d3665072ae5e888e13a98fe7da9a7806c..e7e7c8273160ef63cd9963bd2968fe1713c58761 100644
--- a/app/src/handlers/itemhandler.cpp
+++ b/app/src/handlers/itemhandler.cpp
@@ -1,5 +1,6 @@
 #include "itemhandler.h"
 
+#include <QUrlQuery>
 #include <QJsonValue>
 #include <QJsonArray>
 #include <QFileInfo>
@@ -25,15 +26,20 @@ QJsonObject ItemHandler::metadataSet() const
     return metadataSet_;
 }
 
-void ItemHandler::download(const QString &url, const QString &installType, const QString &providerKey, const QString &contentId)
+void ItemHandler::getItem(const QString &command, const QString &url, const QString &installType, const QString &filename,
+                          const QString &providerKey, const QString &contentId)
 {
     // Use URL as unique key for metadata, network resource, and installed item
     QString itemKey = url;
 
     QJsonObject metadata;
+    metadata["command"] = command;
     metadata["url"] = url;
-    metadata["filename"] = QUrl(url).fileName();
     metadata["install_type"] = installType;
+    metadata["filename"] = filename;
+    if (filename.isEmpty()) {
+        metadata["filename"] = QUrl(url).fileName();
+    }
     metadata["provider"] = providerKey;
     metadata["content_id"] = contentId;
     metadata["files"] = QJsonArray();
@@ -64,6 +70,57 @@ void ItemHandler::download(const QString &url, const QString &installType, const
     emit downloadStarted(result);
 }
 
+void ItemHandler::getItemByOcsUrl(const QString &ocsUrl)
+{
+    QUrl ocsUrlObj(ocsUrl);
+    QUrlQuery query(ocsUrlObj);
+
+    QString scheme = "ocs";
+    QString command = "download";
+    QString url = "";
+    QString type = "downloads";
+    QString filename = "";
+
+    if (!ocsUrlObj.scheme().isEmpty()) {
+        scheme = ocsUrlObj.scheme();
+    }
+
+    if (!ocsUrlObj.host().isEmpty()) {
+        command = ocsUrlObj.host();
+    }
+
+    if (query.hasQueryItem("url") && !query.queryItemValue("url").isEmpty()) {
+        url = query.queryItemValue("url", QUrl::FullyDecoded);
+    }
+
+    if (query.hasQueryItem("type") && !query.queryItemValue("type").isEmpty()) {
+        type = query.queryItemValue("type", QUrl::FullyDecoded);
+    }
+
+    if (query.hasQueryItem("filename") && !query.queryItemValue("filename").isEmpty()) {
+        filename = QUrl(query.queryItemValue("filename", QUrl::FullyDecoded)).fileName();
+    }
+
+    if (!url.isEmpty() && filename.isEmpty()) {
+        filename = QUrl(url).fileName();
+    }
+
+    // Still support xdg and xdgs schemes for backward compatibility
+    if ((scheme == "ocs" || scheme == "ocss" || scheme == "xdg" || scheme == "xdgs")
+            && (command == "download" || command == "install")
+            && QUrl(url).isValid()
+            && configHandler_->getAppConfigInstallTypes().contains(type)
+            && !filename.isEmpty()) {
+        getItem(command, url, type, filename);
+    }
+    else {
+        QJsonObject result;
+        result["status"] = QString("error_downloadstart");
+        result["message"] = tr("Invalid OCS-URL");
+        emit downloadStarted(result);
+    }
+}
+
 void ItemHandler::uninstall(const QString &itemKey)
 {
     QJsonObject result;
@@ -165,7 +222,12 @@ void ItemHandler::networkResourceFinished(qtlib::NetworkResource *resource)
     result["message"] = tr("Downloaded");
     emit downloadFinished(result);
 
-    install(resource);
+    if (metadata["command"].toString() == "download") {
+        saveDownloadedFile(resource);
+    }
+    else if (metadata["command"].toString() == "install") {
+        installDownloadedFile(resource);
+    }
 }
 
 void ItemHandler::setMetadataSet(const QJsonObject &metadataSet)
@@ -174,7 +236,45 @@ void ItemHandler::setMetadataSet(const QJsonObject &metadataSet)
     emit metadataSetChanged();
 }
 
-void ItemHandler::install(qtlib::NetworkResource *resource)
+void ItemHandler::saveDownloadedFile(qtlib::NetworkResource *resource)
+{
+    QString itemKey = resource->id();
+
+    QJsonObject itemMetadataSet = metadataSet();
+    QJsonObject metadata = itemMetadataSet[itemKey].toObject();
+
+    itemMetadataSet.remove(itemKey);
+    setMetadataSet(itemMetadataSet);
+
+    QJsonObject result;
+    result["metadata"] = metadata;
+    result["status"] = QString("success_savestart");
+    result["message"] = tr("Saving");
+    emit saveStarted(result);
+
+    QString filename = metadata["filename"].toString();
+    QString installType = metadata["install_type"].toString();
+
+    qtlib::Dir destDir(configHandler_->getAppConfigInstallTypes()[installType].toObject()["destination"].toString());
+    destDir.make();
+    qtlib::File destFile(destDir.path() + "/" + filename);
+
+    if (!resource->saveData(destFile.path())) {
+        result["status"] = QString("error_save");
+        result["message"] = tr("Failed to save data");
+        emit saveFinished(result);
+        resource->deleteLater();
+        return;
+    }
+
+    result["status"] = QString("success_save");
+    result["message"] = tr("Saved");
+    emit saveFinished(result);
+
+    resource->deleteLater();
+}
+
+void ItemHandler::installDownloadedFile(qtlib::NetworkResource *resource)
 {
     // Installation pre-process
     QString itemKey = resource->id();
@@ -187,9 +287,9 @@ void ItemHandler::install(qtlib::NetworkResource *resource)
 
     QJsonObject result;
     result["metadata"] = metadata;
-    result["status"] = QString("success_installstart");
-    result["message"] = tr("Installing");
-    emit installStarted(result);
+    result["status"] = QString("success_savestart");
+    result["message"] = tr("Saving");
+    emit saveStarted(result);
 
     QString filename = metadata["filename"].toString();
     QString installType = metadata["install_type"].toString();
@@ -202,15 +302,23 @@ void ItemHandler::install(qtlib::NetworkResource *resource)
     qtlib::Package package(tempDir.path() + "/" + filename);
 
     if (!resource->saveData(package.path())) {
-        result["status"] = QString("error_install");
+        result["status"] = QString("error_save");
         result["message"] = tr("Failed to save data");
-        emit installFinished(result);
+        emit saveFinished(result);
         tempDir.remove();
         resource->deleteLater();
         return;
     }
 
+    result["status"] = QString("success_save");
+    result["message"] = tr("Saved");
+    emit saveFinished(result);
+
     // Installation main-process
+    result["status"] = QString("success_installstart");
+    result["message"] = tr("Installing");
+    emit installStarted(result);
+
     qtlib::Dir destDir;
 #ifdef QTLIB_UNIX
     destDir.setPath(configHandler_->getAppConfigInstallTypes()[installType].toObject()["destination"].toString());
@@ -295,6 +403,7 @@ void ItemHandler::install(qtlib::NetworkResource *resource)
     }
 
     // Installation post-process
+    metadata.remove("command");
     metadata["files"] = installedFiles;
     metadata["installed_at"] = QDateTime::currentMSecsSinceEpoch();
     configHandler_->setUsrConfigInstalledItemsItem(itemKey, metadata);
diff --git a/app/src/handlers/itemhandler.h b/app/src/handlers/itemhandler.h
index b7bd685309743fe145af708fa0224f7234db682b..5f378c4dbc385704a2a7a5c69890b8b585e23f66 100644
--- a/app/src/handlers/itemhandler.h
+++ b/app/src/handlers/itemhandler.h
@@ -21,6 +21,8 @@ signals:
     void downloadStarted(QJsonObject result);
     void downloadFinished(QJsonObject result);
     void downloadProgress(QString id, qint64 bytesReceived, qint64 bytesTotal);
+    void saveStarted(QJsonObject result);
+    void saveFinished(QJsonObject result);
     void installStarted(QJsonObject result);
     void installFinished(QJsonObject result);
     void uninstallStarted(QJsonObject result);
@@ -29,7 +31,9 @@ signals:
 public slots:
     QJsonObject metadataSet() const;
 
-    void download(const QString &url, const QString &installType, const QString &providerKey = "", const QString &contentId = "");
+    void getItem(const QString &command, const QString &url, const QString &installType, const QString &filename = "",
+                 const QString &providerKey = "", const QString &contentId = "");
+    void getItemByOcsUrl(const QString &ocsUrl);
     void uninstall(const QString &itemKey);
 
 private slots:
@@ -38,7 +42,8 @@ private slots:
 private:
     void setMetadataSet(const QJsonObject &metadataSet);
 
-    void install(qtlib::NetworkResource *resource);
+    void saveDownloadedFile(qtlib::NetworkResource *resource);
+    void installDownloadedFile(qtlib::NetworkResource *resource);
 
     ConfigHandler *configHandler_;
     QJsonObject metadataSet_;
diff --git a/app/src/handlers/ocshandler.cpp b/app/src/handlers/ocsapihandler.cpp
similarity index 89%
rename from app/src/handlers/ocshandler.cpp
rename to app/src/handlers/ocsapihandler.cpp
index d82a900705c51128149039ec1555ecaa65534825..310ed88a5e7fe20c2a858b7a23de316fe341867d 100644
--- a/app/src/handlers/ocshandler.cpp
+++ b/app/src/handlers/ocsapihandler.cpp
@@ -1,4 +1,4 @@
-#include "ocshandler.h"
+#include "ocsapihandler.h"
 
 #include <QJsonValue>
 
@@ -6,11 +6,11 @@
 
 #include "handlers/confighandler.h"
 
-OcsHandler::OcsHandler(ConfigHandler *configHandler, QObject *parent)
+OcsApiHandler::OcsApiHandler(ConfigHandler *configHandler, QObject *parent)
     : QObject(parent), configHandler_(configHandler)
 {}
 
-bool OcsHandler::addProviders(const QString &providerFileUrl)
+bool OcsApiHandler::addProviders(const QString &providerFileUrl)
 {
     QJsonArray providers = qtlib::OcsApi::getProviderFile(QUrl(providerFileUrl));
     if (!providers.isEmpty()) {
@@ -29,7 +29,7 @@ bool OcsHandler::addProviders(const QString &providerFileUrl)
     return false;
 }
 
-bool OcsHandler::removeProvider(const QString &providerKey)
+bool OcsApiHandler::removeProvider(const QString &providerKey)
 {
     if (configHandler_->removeUsrConfigProvidersProvider(providerKey)) {
         configHandler_->removeUsrConfigCategoriesProvider(providerKey);
@@ -38,7 +38,7 @@ bool OcsHandler::removeProvider(const QString &providerKey)
     return false;
 }
 
-bool OcsHandler::updateAllCategories(bool force)
+bool OcsApiHandler::updateAllCategories(bool force)
 {
     QJsonObject providers = configHandler_->getUsrConfigProviders();
     if (!providers.isEmpty()) {
@@ -50,7 +50,7 @@ bool OcsHandler::updateAllCategories(bool force)
     return false;
 }
 
-bool OcsHandler::updateCategories(const QString &providerKey, bool force)
+bool OcsApiHandler::updateCategories(const QString &providerKey, bool force)
 {
     QJsonObject providers = configHandler_->getUsrConfigProviders();
 
@@ -133,9 +133,9 @@ bool OcsHandler::updateCategories(const QString &providerKey, bool force)
     return configHandler_->setUsrConfigCategoriesProvider(providerKey, newProviderCategories);
 }
 
-QJsonObject OcsHandler::getContents(const QString &providerKeys, const QString &categoryKeys,
-                                    const QString &xdgTypes, const QString &packageTypes,
-                                    const QString &search, const QString &sortmode, int pagesize, int page)
+QJsonObject OcsApiHandler::getContents(const QString &providerKeys, const QString &categoryKeys,
+                                       const QString &xdgTypes, const QString &packageTypes,
+                                       const QString &search, const QString &sortmode, int pagesize, int page)
 {
     QJsonObject responseSet;
 
@@ -191,7 +191,7 @@ QJsonObject OcsHandler::getContents(const QString &providerKeys, const QString &
     return responseSet;
 }
 
-QJsonObject OcsHandler::getContent(const QString &providerKey, const QString &contentId)
+QJsonObject OcsApiHandler::getContent(const QString &providerKey, const QString &contentId)
 {
     QJsonObject response;
     QJsonObject providers = configHandler_->getUsrConfigProviders();
diff --git a/app/src/handlers/ocshandler.h b/app/src/handlers/ocsapihandler.h
similarity index 87%
rename from app/src/handlers/ocshandler.h
rename to app/src/handlers/ocsapihandler.h
index 72b4853817297bab6a71309a1384fdebe2b1fbf3..4e4d3095e50f985c7c39f392eaccc85bdc719e5a 100644
--- a/app/src/handlers/ocshandler.h
+++ b/app/src/handlers/ocsapihandler.h
@@ -5,12 +5,12 @@
 
 class ConfigHandler;
 
-class OcsHandler : public QObject
+class OcsApiHandler : public QObject
 {
     Q_OBJECT
 
 public:
-    explicit OcsHandler(ConfigHandler *configHandler, QObject *parent = 0);
+    explicit OcsApiHandler(ConfigHandler *configHandler, QObject *parent = 0);
 
 public slots:
     bool addProviders(const QString &providerFileUrl);
diff --git a/app/src/websockets/websocketserver.cpp b/app/src/websockets/websocketserver.cpp
index 793ac1f8952c43827cde1f67ef388aad838db4be..857c9e9e7496aee7551c855bd63aabad11c3ba5f 100644
--- a/app/src/websockets/websocketserver.cpp
+++ b/app/src/websockets/websocketserver.cpp
@@ -8,7 +8,7 @@
 
 #include "handlers/confighandler.h"
 #include "handlers/systemhandler.h"
-#include "handlers/ocshandler.h"
+#include "handlers/ocsapihandler.h"
 #include "handlers/itemhandler.h"
 
 WebSocketServer::WebSocketServer(ConfigHandler *configHandler, const QString &serverName, quint16 serverPort, QObject *parent)
@@ -20,12 +20,14 @@ WebSocketServer::WebSocketServer(ConfigHandler *configHandler, const QString &se
 
     configHandler_->setParent(this);
     systemHandler_ = new SystemHandler(this);
-    ocsHandler_ = new OcsHandler(configHandler_, this);
+    ocsApiHandler_ = new OcsApiHandler(configHandler_, this);
     itemHandler_ = new ItemHandler(configHandler_, this);
     connect(itemHandler_, &ItemHandler::metadataSetChanged, this, &WebSocketServer::itemMetadataSetChanged);
     connect(itemHandler_, &ItemHandler::downloadStarted, this, &WebSocketServer::itemDownloadStarted);
     connect(itemHandler_, &ItemHandler::downloadFinished, this, &WebSocketServer::itemDownloadFinished);
     connect(itemHandler_, &ItemHandler::downloadProgress, this, &WebSocketServer::itemDownloadProgress);
+    connect(itemHandler_, &ItemHandler::saveStarted, this, &WebSocketServer::itemSaveStarted);
+    connect(itemHandler_, &ItemHandler::saveFinished, this, &WebSocketServer::itemSaveFinished);
     connect(itemHandler_, &ItemHandler::installStarted, this, &WebSocketServer::itemInstallStarted);
     connect(itemHandler_, &ItemHandler::installFinished, this, &WebSocketServer::itemInstallFinished);
     connect(itemHandler_, &ItemHandler::uninstallStarted, this, &WebSocketServer::itemUninstallStarted);
@@ -141,6 +143,20 @@ void WebSocketServer::itemDownloadProgress(QString id, qint64 bytesReceived, qin
     sendMessage("", "ItemHandler::downloadProgress", data);
 }
 
+void WebSocketServer::itemSaveStarted(QJsonObject result)
+{
+    QJsonArray data;
+    data.append(result);
+    sendMessage("", "ItemHandler::saveStarted", data);
+}
+
+void WebSocketServer::itemSaveFinished(QJsonObject result)
+{
+    QJsonArray data;
+    data.append(result);
+    sendMessage("", "ItemHandler::saveFinished", data);
+}
+
 void WebSocketServer::itemInstallStarted(QJsonObject result)
 {
     QJsonArray data;
@@ -263,33 +279,37 @@ void WebSocketServer::receiveMessage(const QString &id, const QString &func, con
         resultData.append(false);
 #endif
     }
-    // OcsHandler
-    else if (func == "OcsHandler::addProviders") {
-        resultData.append(ocsHandler_->addProviders(data.at(0).toString()));
+    // OcsApiHandler
+    else if (func == "OcsApiHandler::addProviders") {
+        resultData.append(ocsApiHandler_->addProviders(data.at(0).toString()));
     }
-    else if (func == "OcsHandler::removeProvider") {
-        resultData.append(ocsHandler_->removeProvider(data.at(0).toString()));
+    else if (func == "OcsApiHandler::removeProvider") {
+        resultData.append(ocsApiHandler_->removeProvider(data.at(0).toString()));
     }
-    else if (func == "OcsHandler::updateAllCategories") {
-        resultData.append(ocsHandler_->updateAllCategories(data.at(0).toBool()));
+    else if (func == "OcsApiHandler::updateAllCategories") {
+        resultData.append(ocsApiHandler_->updateAllCategories(data.at(0).toBool()));
     }
-    else if (func == "OcsHandler::updateCategories") {
-        resultData.append(ocsHandler_->updateCategories(data.at(0).toString(), data.at(1).toBool()));
+    else if (func == "OcsApiHandler::updateCategories") {
+        resultData.append(ocsApiHandler_->updateCategories(data.at(0).toString(), data.at(1).toBool()));
     }
-    else if (func == "OcsHandler::getContents") {
-        resultData.append(ocsHandler_->getContents(data.at(0).toString(), data.at(1).toString(),
-                                                   data.at(2).toString(), data.at(3).toString(),
-                                                   data.at(4).toString(), data.at(5).toString(), data.at(6).toInt(), data.at(7).toInt()));
+    else if (func == "OcsApiHandler::getContents") {
+        resultData.append(ocsApiHandler_->getContents(data.at(0).toString(), data.at(1).toString(),
+                                                      data.at(2).toString(), data.at(3).toString(),
+                                                      data.at(4).toString(), data.at(5).toString(), data.at(6).toInt(), data.at(7).toInt()));
     }
-    else if (func == "OcsHandler::getContent") {
-        resultData.append(ocsHandler_->getContent(data.at(0).toString(), data.at(1).toString()));
+    else if (func == "OcsApiHandler::getContent") {
+        resultData.append(ocsApiHandler_->getContent(data.at(0).toString(), data.at(1).toString()));
     }
     // ItemHandler
     else if (func == "ItemHandler::metadataSet") {
         resultData.append(itemHandler_->metadataSet());
     }
-    else if (func == "ItemHandler::download") {
-        itemHandler_->download(data.at(0).toString(), data.at(1).toString(), data.at(2).toString(), data.at(3).toString());
+    else if (func == "ItemHandler::getItem") {
+        itemHandler_->getItem(data.at(0).toString(), data.at(1).toString(), data.at(2).toString(), data.at(3).toString(),
+                              data.at(4).toString(), data.at(5).toString());
+    }
+    else if (func == "ItemHandler::getItemByOcsUrl") {
+        itemHandler_->getItemByOcsUrl(data.at(0).toString());
     }
     else if (func == "ItemHandler::uninstall") {
         itemHandler_->uninstall(data.at(0).toString());
diff --git a/app/src/websockets/websocketserver.h b/app/src/websockets/websocketserver.h
index 698bd5c2b1fdd2794d35bfe501ff864ff2494ba7..6bcebf7c28fb7d6ee4987b04ff3a89ab378f0eff 100644
--- a/app/src/websockets/websocketserver.h
+++ b/app/src/websockets/websocketserver.h
@@ -10,7 +10,7 @@ class QWebSocket;
 
 class ConfigHandler;
 class SystemHandler;
-class OcsHandler;
+class OcsApiHandler;
 class ItemHandler;
 
 class WebSocketServer : public QObject
@@ -42,6 +42,8 @@ private slots:
     void itemDownloadStarted(QJsonObject result);
     void itemDownloadFinished(QJsonObject result);
     void itemDownloadProgress(QString id, qint64 bytesReceived, qint64 bytesTotal);
+    void itemSaveStarted(QJsonObject result);
+    void itemSaveFinished(QJsonObject result);
     void itemInstallStarted(QJsonObject result);
     void itemInstallFinished(QJsonObject result);
     void itemUninstallStarted(QJsonObject result);
@@ -53,7 +55,7 @@ private:
 
     ConfigHandler *configHandler_;
     SystemHandler *systemHandler_;
-    OcsHandler *ocsHandler_;
+    OcsApiHandler *ocsApiHandler_;
     ItemHandler *itemHandler_;
 
     QString serverName_;