File indexing completed on 2024-12-08 10:25:53

0001 /*
0002    SPDX-FileCopyrightText: 2018-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "rocketchatcache.h"
0008 #include "avatarmanager.h"
0009 #include "connection.h"
0010 #include "rocketchataccount.h"
0011 #include "ruqola_debug.h"
0012 #include <QDateTime>
0013 #include <QDir>
0014 #include <QSettings>
0015 #include <QUrlQuery>
0016 
0017 RocketChatCache::RocketChatCache(RocketChatAccount *account, QObject *parent)
0018     : QObject(parent)
0019     , mAccount(account)
0020     , mAvatarManager(new AvatarManager(mAccount, this))
0021 {
0022     connect(mAvatarManager, &AvatarManager::insertAvatarUrl, this, &RocketChatCache::insertAvatarUrl);
0023     loadAvatarCache();
0024 }
0025 
0026 RocketChatCache::~RocketChatCache()
0027 {
0028     QSettings settings(ManagerDataPaths::self()->accountAvatarConfigPath(mAccount->accountName()), QSettings::IniFormat);
0029 
0030     settings.beginGroup(QStringLiteral("Avatar"));
0031     QHash<QString, QUrl>::const_iterator i = mAvatarUrl.constBegin();
0032     while (i != mAvatarUrl.constEnd()) {
0033         if (!i.value().toString().isEmpty()) {
0034             settings.setValue(i.key(), i.value().toString());
0035         }
0036         ++i;
0037     }
0038     settings.endGroup();
0039 }
0040 
0041 void RocketChatCache::setRestApiConnection(RocketChatRestApi::Connection *restApi)
0042 {
0043     connect(restApi, &RocketChatRestApi::Connection::downloadFileDone, this, &RocketChatCache::slotDataDownloaded);
0044 }
0045 
0046 bool RocketChatCache::fileInCache(const QUrl &url)
0047 {
0048     const QFileInfo f(fileCachePath(url));
0049     return f.exists();
0050 }
0051 
0052 QString RocketChatCache::fileCachePath(const QUrl &url, ManagerDataPaths::PathType type)
0053 {
0054     QString cachePath = ManagerDataPaths::self()->path(type, mAccount->accountName());
0055     QString urlPath = url.path();
0056     if (type == ManagerDataPaths::PathType::PreviewUrl) {
0057         urlPath = urlPath.replace(QLatin1Char('/'), QLatin1Char('_'));
0058     }
0059     cachePath += QLatin1Char('/') + urlPath;
0060     if (url.hasQuery()) {
0061         const QUrlQuery query(url);
0062         cachePath += query.queryItemValue(QStringLiteral("etag"));
0063     }
0064     return cachePath;
0065 }
0066 
0067 void RocketChatCache::slotDataDownloaded(const QUrl &url, const QUrl &localFileUrl)
0068 {
0069     mFileInDownload.remove(url.path());
0070     Q_EMIT fileDownloaded(url.path(), localFileUrl);
0071 }
0072 
0073 void RocketChatCache::loadAvatarCache()
0074 {
0075     QSettings settings(ManagerDataPaths::self()->accountAvatarConfigPath(mAccount->accountName()), QSettings::IniFormat);
0076     settings.beginGroup(QStringLiteral("Avatar"));
0077     const QStringList keys = settings.childKeys();
0078     for (const QString &key : keys) {
0079         mAvatarUrl[key] = QUrl(settings.value(key).toString());
0080     }
0081     settings.endGroup();
0082 }
0083 
0084 void RocketChatCache::downloadFile(const QString &url, const QUrl &localFile)
0085 {
0086     QFile f(fileCachePath(QUrl(url)));
0087     if (f.exists()) {
0088         if (!f.copy(localFile.toLocalFile())) {
0089             qCWarning(RUQOLA_LOG) << "Impossible to copy" << f.fileName() << "to" << localFile;
0090         }
0091         // emit fileDownloaded?
0092     } else {
0093         // Not in cache. We need to download it (e.g. file attachment).
0094         const QUrl downloadUrl = mAccount->urlForLink(url);
0095         // const QUrl destUrl = storeInCache ? QUrl::fromLocalFile(fileCachePath(downloadUrl)) : localFile;
0096         mAccount->restApi()->downloadFile(downloadUrl, localFile, QByteArray("text/plain"));
0097         // this will call slotDataDownloaded
0098     }
0099 }
0100 
0101 bool RocketChatCache::attachmentIsInLocalCache(const QString &url)
0102 {
0103     const QString cachePath = fileCachePath(QUrl(url));
0104     if (QFileInfo::exists(cachePath)) {
0105         return true;
0106     }
0107     return false;
0108 }
0109 
0110 QUrl RocketChatCache::faviconLogoUrlFromLocalCache(const QString &url)
0111 {
0112     return urlFromLocalCache(url, false);
0113 }
0114 
0115 QUrl RocketChatCache::avatarUrlFromLocalCache(const QString &url)
0116 {
0117     return urlFromLocalCache(url, false);
0118 }
0119 
0120 QUrl RocketChatCache::urlFromLocalCache(const QString &url, bool needAuthentication, ManagerDataPaths::PathType type)
0121 {
0122     const QString cachePath = fileCachePath(QUrl(url), type);
0123     if (QFileInfo::exists(cachePath)) {
0124         // QML wants a QUrl here. The widgets code would be simpler with just a QString path.
0125         return QUrl::fromLocalFile(cachePath);
0126     } else {
0127         downloadFileFromServer(url, needAuthentication, type);
0128     }
0129     return {};
0130 }
0131 
0132 QUrl RocketChatCache::previewUrlFromLocalCache(const QString &url)
0133 {
0134     const QUrl previewUrl = urlFromLocalCache(url, false, ManagerDataPaths::PreviewUrl);
0135     // qDebug() << "previewUrl  " << previewUrl;
0136     return previewUrl;
0137 }
0138 
0139 QUrl RocketChatCache::attachmentUrlFromLocalCache(const QString &url)
0140 {
0141     return urlFromLocalCache(url, true);
0142 }
0143 
0144 void RocketChatCache::downloadAvatarFromServer(const Utils::AvatarInfo &info)
0145 {
0146     mAvatarManager->insertInDownloadQueue(info);
0147 }
0148 
0149 void RocketChatCache::downloadFileFromServer(const QString &filename, bool needAuthentication, ManagerDataPaths::PathType type)
0150 {
0151     if (!mFileInDownload.contains(filename)) {
0152         mFileInDownload.insert(filename);
0153         const QUrl downloadUrl = mAccount->urlForLink(filename);
0154         const QUrl destFileUrl = QUrl::fromLocalFile(fileCachePath(downloadUrl, type));
0155         mAccount->restApi()->downloadFile(mAccount->urlForLink(filename), destFileUrl, "text/plain", needAuthentication);
0156         // this will call slotDataDownloaded
0157     }
0158 }
0159 
0160 QString RocketChatCache::avatarUrlFromCacheOnly(const QString &userId)
0161 {
0162     const QUrl avatarUrl = mAvatarUrl.value(userId);
0163     if (!avatarUrl.isEmpty() && fileInCache(avatarUrl)) {
0164         const QString url = QUrl::fromLocalFile(fileCachePath(avatarUrl)).toString();
0165         qCDebug(RUQOLA_LOG) << " Use image in cache" << url << " userId " << userId << " mUserAvatarUrl.value(userId) " << mAvatarUrl.value(userId);
0166         return url;
0167     }
0168     return {};
0169 }
0170 
0171 void RocketChatCache::removeAvatar(const QString &avatarIdentifier)
0172 {
0173     const QUrl avatarUrl = mAvatarUrl.value(avatarIdentifier);
0174     if (avatarUrl.isEmpty()) {
0175         return;
0176     }
0177     QFile f(fileCachePath(avatarUrl));
0178     if (f.exists()) {
0179         if (!f.remove()) {
0180             qCWarning(RUQOLA_LOG) << "Impossible to remove f" << f.fileName() << " avartarUrl " << avatarUrl << " userIdentifier  " << avatarIdentifier;
0181         }
0182     }
0183 }
0184 
0185 void RocketChatCache::updateAvatar(const Utils::AvatarInfo &info)
0186 {
0187     const QString avatarIdentifier = info.generateAvatarIdentifier();
0188     // qDebug() << " updateAvatar" << info;
0189     removeAvatar(avatarIdentifier);
0190     mAvatarUrl.remove(avatarIdentifier);
0191     insertAvatarUrl(avatarIdentifier, QUrl());
0192     downloadAvatarFromServer(info);
0193 }
0194 
0195 QString RocketChatCache::avatarUrl(const Utils::AvatarInfo &info)
0196 {
0197     if (!info.isValid()) {
0198         return {};
0199     }
0200     // const QString avatarIdentifier = info.identifier;
0201     const QString avatarIdentifier = info.generateAvatarIdentifier();
0202     // qDebug() << " RocketChatCache::avatarUrl " << avatarIdentifier;
0203     // avoid to call this method several time.
0204     if (!mAvatarUrl.contains(avatarIdentifier)) {
0205         insertAvatarUrl(avatarIdentifier, QUrl());
0206         downloadAvatarFromServer(info);
0207         return {};
0208     } else {
0209         const QUrl valueUrl = mAvatarUrl.value(avatarIdentifier);
0210         // qDebug() << " valueUrl " << valueUrl;
0211 #if 0
0212         const QUrlQuery query{valueUrl};
0213         const QString etagValue = query.queryItemValue(QStringLiteral("etag"));
0214         if (info.etag.isEmpty() && !etagValue.isEmpty()) {
0215             insertAvatarUrl(avatarIdentifier, QUrl());
0216             downloadAvatarFromServer(info);
0217             return {};
0218         } else if (!info.etag.isEmpty() && etagValue.isEmpty()) {
0219             insertAvatarUrl(avatarIdentifier, QUrl());
0220             qDebug() << " redownload " << info;
0221             downloadAvatarFromServer(info);
0222             return {};
0223         }
0224 #endif
0225 
0226         if (!valueUrl.isEmpty() && fileInCache(valueUrl)) {
0227             const QString url = QUrl::fromLocalFile(fileCachePath(valueUrl)).toString();
0228             // qDebug() << " Use image in cache" << url << " userId " << userId << " mUserAvatarUrl.value(userId) "<< mUserAvatarUrl.value(userId);
0229             // qDebug() << "Use image in cache  " << url;
0230 
0231             return url;
0232         } else {
0233             downloadAvatarFromServer(info);
0234         }
0235         return {};
0236     }
0237 }
0238 
0239 void RocketChatCache::insertAvatarUrl(const QString &userIdentifier, const QUrl &url)
0240 {
0241     mAvatarUrl.insert(userIdentifier, url);
0242     if (!url.isEmpty() && !fileInCache(url)) {
0243         mAccount->restApi()->downloadFile(url, QUrl::fromLocalFile(fileCachePath(url)), {});
0244         // this will call slotDataDownloaded
0245     }
0246 }
0247 
0248 QString RocketChatCache::recordingVideoPath(const QString &accountName) const
0249 {
0250     const QString path = ManagerDataPaths::self()->path(ManagerDataPaths::Video, accountName);
0251     if (!QDir().mkpath(path)) {
0252         qCWarning(RUQOLA_LOG) << "Unable to create video folder: " << path;
0253         return {};
0254     }
0255     const QString filePath = path + QLatin1Char('/') + QString::number(QDateTime::currentDateTime().toMSecsSinceEpoch()) + QStringLiteral(".mp4");
0256     return filePath;
0257 }
0258 
0259 QString RocketChatCache::recordingImagePath(const QString &accountName) const
0260 {
0261     const QString path = ManagerDataPaths::self()->path(ManagerDataPaths::Picture, accountName);
0262     if (!QDir().mkpath(path)) {
0263         qCWarning(RUQOLA_LOG) << "Unable to create picture folder: " << path;
0264         return {};
0265     }
0266     const QString filePath = path + QLatin1Char('/') + QString::number(QDateTime::currentDateTime().toMSecsSinceEpoch()) + QStringLiteral(".jpg");
0267     return filePath;
0268 }
0269 
0270 #include "moc_rocketchatcache.cpp"