File indexing completed on 2025-01-05 04:29:53
0001 /** 0002 * SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "gpodder.h" 0008 0009 #include <QDateTime> 0010 #include <QJsonArray> 0011 #include <QJsonDocument> 0012 #include <QJsonObject> 0013 #include <QNetworkReply> 0014 #include <QNetworkRequest> 0015 #include <QSqlQuery> 0016 #include <QUrl> 0017 0018 #include "database.h" 0019 #include "fetcher.h" 0020 #include "sync/gpodder/devicerequest.h" 0021 #include "sync/gpodder/episodeactionrequest.h" 0022 #include "sync/gpodder/loginrequest.h" 0023 #include "sync/gpodder/logoutrequest.h" 0024 #include "sync/gpodder/subscriptionrequest.h" 0025 #include "sync/gpodder/syncrequest.h" 0026 #include "sync/gpodder/updatedevicerequest.h" 0027 #include "sync/gpodder/updatesyncrequest.h" 0028 #include "sync/gpodder/uploadepisodeactionrequest.h" 0029 #include "sync/gpodder/uploadsubscriptionrequest.h" 0030 #include "sync/sync.h" 0031 #include "sync/syncutils.h" 0032 #include "synclogging.h" 0033 0034 GPodder::GPodder(const QString &username, const QString &password, QObject *parent) 0035 : QObject(parent) 0036 , m_username(username) 0037 , m_password(password) 0038 { 0039 } 0040 0041 GPodder::GPodder(const QString &username, const QString &password, const QString &hostname, const SyncUtils::Provider provider, QObject *parent) 0042 : QObject(parent) 0043 , m_username(username) 0044 , m_password(password) 0045 , m_hostname(hostname) 0046 , m_provider(provider) 0047 { 0048 } 0049 0050 LoginRequest *GPodder::login() 0051 { 0052 QString url = QStringLiteral("%1/api/2/auth/%2/login.json").arg(baseUrl()).arg(m_username); 0053 QNetworkRequest request((QUrl(url))); 0054 request.setTransferTimeout(); 0055 addAuthentication(request); 0056 0057 QNetworkReply *reply = Fetcher::instance().post(request, QByteArray()); 0058 LoginRequest *loginRequest = new LoginRequest(m_provider, reply, this); 0059 return loginRequest; 0060 } 0061 0062 LogoutRequest *GPodder::logout() 0063 { 0064 QString url = QStringLiteral("%1/api/2/auth/%2/logout.json").arg(baseUrl()).arg(m_username); 0065 QNetworkRequest request((QUrl(url))); 0066 request.setTransferTimeout(); 0067 addAuthentication(request); 0068 0069 QNetworkReply *reply = Fetcher::instance().post(request, QByteArray()); 0070 LogoutRequest *logoutRequest = new LogoutRequest(m_provider, reply, this); 0071 return logoutRequest; 0072 } 0073 0074 DeviceRequest *GPodder::getDevices() 0075 { 0076 QString url = QStringLiteral("%1/api/2/devices/%2.json").arg(baseUrl()).arg(m_username); 0077 QNetworkRequest request((QUrl(url))); 0078 request.setTransferTimeout(); 0079 addAuthentication(request); 0080 0081 QNetworkReply *reply = Fetcher::instance().get(request); 0082 DeviceRequest *deviceRequest = new DeviceRequest(m_provider, reply, this); 0083 return deviceRequest; 0084 } 0085 0086 UpdateDeviceRequest *GPodder::updateDevice(const QString &id, const QString &caption, const QString &type) 0087 { 0088 QString url = QStringLiteral("%1/api/2/devices/%2/%3.json").arg(baseUrl()).arg(m_username).arg(id); 0089 QNetworkRequest request((QUrl(url))); 0090 request.setTransferTimeout(); 0091 addAuthentication(request); 0092 0093 QJsonObject deviceObject; 0094 deviceObject.insert(QStringLiteral("caption"), caption); 0095 deviceObject.insert(QStringLiteral("type"), type); 0096 QJsonDocument json(deviceObject); 0097 0098 QByteArray data = json.toJson(QJsonDocument::Compact); 0099 0100 QNetworkReply *reply = Fetcher::instance().post(request, data); 0101 0102 UpdateDeviceRequest *updateDeviceRequest = new UpdateDeviceRequest(m_provider, reply, this); 0103 return updateDeviceRequest; 0104 } 0105 SyncRequest *GPodder::getSyncStatus() 0106 { 0107 QString url = QStringLiteral("%1/api/2/sync-devices/%2.json").arg(baseUrl()).arg(m_username); 0108 QNetworkRequest request((QUrl(url))); 0109 request.setTransferTimeout(); 0110 addAuthentication(request); 0111 0112 QNetworkReply *reply = Fetcher::instance().get(request); 0113 SyncRequest *syncRequest = new SyncRequest(m_provider, reply, this); 0114 return syncRequest; 0115 } 0116 0117 UpdateSyncRequest *GPodder::updateSyncStatus(const QVector<QStringList> &syncedDevices, const QStringList &unsyncedDevices) 0118 { 0119 QString url = QStringLiteral("%1/api/2/sync-devices/%2.json").arg(baseUrl()).arg(m_username); 0120 QNetworkRequest request((QUrl(url))); 0121 request.setTransferTimeout(); 0122 addAuthentication(request); 0123 0124 QJsonArray syncGroupArray; 0125 for (QStringList syncGroup : syncedDevices) { 0126 syncGroupArray.push_back(QJsonArray::fromStringList(syncGroup)); 0127 } 0128 QJsonArray unsyncArray = QJsonArray::fromStringList(unsyncedDevices); 0129 0130 QJsonObject uploadObject; 0131 uploadObject.insert(QStringLiteral("synchronize"), syncGroupArray); 0132 uploadObject.insert(QStringLiteral("stop-synchronize"), unsyncArray); 0133 QJsonDocument json(uploadObject); 0134 0135 QByteArray data = json.toJson(QJsonDocument::Compact); 0136 0137 QNetworkReply *reply = Fetcher::instance().post(request, data); 0138 0139 UpdateSyncRequest *updateSyncRequest = new UpdateSyncRequest(m_provider, reply, this); 0140 return updateSyncRequest; 0141 } 0142 0143 SubscriptionRequest *GPodder::getSubscriptionChanges(const qulonglong &oldtimestamp, const QString &device) 0144 { 0145 QString url; 0146 if (m_provider == SyncUtils::Provider::GPodderNextcloud) { 0147 url = QStringLiteral("%1/index.php/apps/gpoddersync/subscriptions?since=%2").arg(baseUrl()).arg(QString::number(oldtimestamp)); 0148 } else { 0149 url = QStringLiteral("%1/api/2/subscriptions/%2/%3.json?since=%4").arg(baseUrl()).arg(m_username).arg(device).arg(QString::number(oldtimestamp)); 0150 } 0151 QNetworkRequest request((QUrl(url))); 0152 request.setTransferTimeout(); 0153 addAuthentication(request); 0154 0155 QNetworkReply *reply = Fetcher::instance().get(request); 0156 0157 SubscriptionRequest *subscriptionRequest = new SubscriptionRequest(m_provider, reply, this); 0158 return subscriptionRequest; 0159 } 0160 0161 UploadSubscriptionRequest *GPodder::uploadSubscriptionChanges(const QStringList &add, const QStringList &remove, const QString &device) 0162 { 0163 QString url; 0164 if (m_provider == SyncUtils::Provider::GPodderNextcloud) { 0165 url = QStringLiteral("%1/index.php/apps/gpoddersync/subscription_change/create").arg(baseUrl()); 0166 } else { 0167 url = QStringLiteral("%1/api/2/subscriptions/%2/%3.json").arg(baseUrl()).arg(m_username).arg(device); 0168 } 0169 QNetworkRequest request((QUrl(url))); 0170 request.setTransferTimeout(); 0171 addAuthentication(request); 0172 0173 QJsonArray addArray = QJsonArray::fromStringList(add); 0174 QJsonArray removeArray = QJsonArray::fromStringList(remove); 0175 0176 QJsonObject uploadObject; 0177 uploadObject.insert(QStringLiteral("add"), addArray); 0178 uploadObject.insert(QStringLiteral("remove"), removeArray); 0179 QJsonDocument json(uploadObject); 0180 0181 QByteArray data = json.toJson(QJsonDocument::Compact); 0182 0183 QNetworkReply *reply = Fetcher::instance().post(request, data); 0184 0185 UploadSubscriptionRequest *uploadSubscriptionRequest = new UploadSubscriptionRequest(m_provider, reply, this); 0186 return uploadSubscriptionRequest; 0187 } 0188 0189 EpisodeActionRequest *GPodder::getEpisodeActions(const qulonglong ×tamp, bool aggregated) 0190 { 0191 QString url; 0192 if (m_provider == SyncUtils::Provider::GPodderNextcloud) { 0193 url = QStringLiteral("%1/index.php/apps/gpoddersync/episode_action?since=%2").arg(baseUrl()).arg(QString::number(timestamp)); 0194 } else { 0195 url = QStringLiteral("%1/api/2/episodes/%2.json?since=%3&aggregated=%4") 0196 .arg(baseUrl()) 0197 .arg(m_username) 0198 .arg(QString::number(timestamp)) 0199 .arg(aggregated ? QStringLiteral("true") : QStringLiteral("false")); 0200 } 0201 QNetworkRequest request((QUrl(url))); 0202 request.setTransferTimeout(); 0203 addAuthentication(request); 0204 0205 QNetworkReply *reply = Fetcher::instance().get(request); 0206 0207 EpisodeActionRequest *episodeActionRequest = new EpisodeActionRequest(m_provider, reply, this); 0208 return episodeActionRequest; 0209 } 0210 0211 UploadEpisodeActionRequest *GPodder::uploadEpisodeActions(const QVector<SyncUtils::EpisodeAction> &episodeActions) 0212 { 0213 QString url; 0214 if (m_provider == SyncUtils::Provider::GPodderNextcloud) { 0215 url = QStringLiteral("%1/index.php/apps/gpoddersync/episode_action/create").arg(baseUrl()); 0216 } else { 0217 url = QStringLiteral("%1/api/2/episodes/%2.json").arg(baseUrl()).arg(m_username); 0218 } 0219 QNetworkRequest request((QUrl(url))); 0220 request.setTransferTimeout(); 0221 addAuthentication(request); 0222 0223 QJsonArray actionArray; 0224 for (SyncUtils::EpisodeAction episodeAction : episodeActions) { 0225 QJsonObject actionObject; 0226 actionObject.insert(QStringLiteral("podcast"), episodeAction.podcast); 0227 if (!episodeAction.url.isEmpty()) { 0228 actionObject.insert(QStringLiteral("episode"), episodeAction.url); 0229 } else if (!episodeAction.id.isEmpty()) { 0230 QSqlQuery query; 0231 query.prepare(QStringLiteral("SELECT url FROM Enclosures WHERE id=:id;")); 0232 query.bindValue(QStringLiteral(":id"), episodeAction.id); 0233 Database::instance().execute(query); 0234 if (!query.next()) { 0235 qCDebug(kastsSync) << "cannot find episode with id:" << episodeAction.id; 0236 continue; 0237 } else { 0238 actionObject.insert(QStringLiteral("episode"), query.value(QStringLiteral("url")).toString()); 0239 } 0240 } 0241 actionObject.insert(QStringLiteral("guid"), episodeAction.id); 0242 actionObject.insert(QStringLiteral("device"), episodeAction.device); 0243 actionObject.insert(QStringLiteral("action"), episodeAction.action); 0244 0245 QString dateTime = QDateTime::fromSecsSinceEpoch(episodeAction.timestamp).toUTC().toString(Qt::ISODate); 0246 // Qt::ISODate adds "Z" to the end of the string; cut it off since 0247 // GPodderNextcloud cannot handle it 0248 dateTime.chop(1); 0249 actionObject.insert(QStringLiteral("timestamp"), dateTime); 0250 if (episodeAction.action == QStringLiteral("play")) { 0251 actionObject.insert(QStringLiteral("started"), static_cast<qint64>(episodeAction.started)); 0252 actionObject.insert(QStringLiteral("position"), static_cast<qint64>(episodeAction.position)); 0253 actionObject.insert(QStringLiteral("total"), static_cast<qint64>(episodeAction.total)); 0254 } 0255 actionArray.push_back(actionObject); 0256 } 0257 QJsonDocument json(actionArray); 0258 0259 QByteArray data = json.toJson(QJsonDocument::Compact); 0260 0261 QNetworkReply *reply = Fetcher::instance().post(request, data); 0262 0263 UploadEpisodeActionRequest *uploadEpisodeActionRequest = new UploadEpisodeActionRequest(m_provider, reply, this); 0264 return uploadEpisodeActionRequest; 0265 } 0266 0267 QString GPodder::baseUrl() 0268 { 0269 return m_hostname; 0270 } 0271 0272 void GPodder::addAuthentication(QNetworkRequest &request) 0273 { 0274 QByteArray headerData = "Basic " + QString(m_username + QStringLiteral(":") + m_password).toLocal8Bit().toBase64(); 0275 request.setRawHeader("Authorization", headerData); 0276 }