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/ocsapihandler.cpp b/app/src/handlers/ocsapihandler.cpp
index 5d1be986f702e1235f19a884aa7cbe00f47eef16..310ed88a5e7fe20c2a858b7a23de316fe341867d 100644
--- a/app/src/handlers/ocsapihandler.cpp
+++ b/app/src/handlers/ocsapihandler.cpp
@@ -134,8 +134,8 @@ bool OcsApiHandler::updateCategories(const QString &providerKey, bool force)
 }
 
 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)
+                                       const QString &xdgTypes, const QString &packageTypes,
+                                       const QString &search, const QString &sortmode, int pagesize, int page)
 {
     QJsonObject responseSet;
 
diff --git a/app/src/websockets/websocketserver.cpp b/app/src/websockets/websocketserver.cpp
index a5e7398f0aa00a3906f9e4d0f20f40868e1c399c..857c9e9e7496aee7551c855bd63aabad11c3ba5f 100644
--- a/app/src/websockets/websocketserver.cpp
+++ b/app/src/websockets/websocketserver.cpp
@@ -26,6 +26,8 @@ WebSocketServer::WebSocketServer(ConfigHandler *configHandler, const QString &se
     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;
@@ -278,8 +294,8 @@ void WebSocketServer::receiveMessage(const QString &id, const QString &func, con
     }
     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()));
+                                                      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::getContent") {
         resultData.append(ocsApiHandler_->getContent(data.at(0).toString(), data.at(1).toString()));
@@ -288,8 +304,12 @@ void WebSocketServer::receiveMessage(const QString &id, const QString &func, con
     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 77e3837e1d1e1261c766299149ef5de9b1adfb07..6bcebf7c28fb7d6ee4987b04ff3a89ab378f0eff 100644
--- a/app/src/websockets/websocketserver.h
+++ b/app/src/websockets/websocketserver.h
@@ -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);