File indexing completed on 2025-09-14 05:22:01

0001 /*
0002     SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "potdbackend.h"
0008 
0009 #include <QDBusConnection>
0010 #include <QFileDialog>
0011 #include <QNetworkInformation>
0012 #include <QStandardPaths> // For "Pictures" folder
0013 
0014 #include <KIO/CopyJob> // For "Save Image"
0015 #include <KLocalizedString>
0016 
0017 namespace
0018 {
0019 constinit PotdEngine *s_engine = nullptr;
0020 constinit int s_instanceCount = 0;
0021 }
0022 
0023 PotdBackend::PotdBackend(QObject *parent)
0024     : QObject(parent)
0025     , m_networkInfomationAvailable(!QNetworkInformation::availableBackends().empty())
0026 {
0027     if (!s_engine) {
0028         Q_ASSERT(s_instanceCount == 0);
0029         s_engine = new PotdEngine();
0030     }
0031     s_instanceCount++;
0032 }
0033 
0034 PotdBackend::~PotdBackend()
0035 {
0036     s_engine->unregisterClient(m_identifier, m_args);
0037     s_instanceCount--;
0038 
0039     if (!s_instanceCount) {
0040         delete s_engine;
0041         s_engine = nullptr;
0042     }
0043 }
0044 
0045 void PotdBackend::classBegin()
0046 {
0047 }
0048 
0049 void PotdBackend::componentComplete()
0050 {
0051     // don't bother loading single image until all properties have settled
0052     m_ready = true;
0053 
0054     // Register the identifier in the data engine
0055     registerClient();
0056 }
0057 
0058 QString PotdBackend::identifier() const
0059 {
0060     return m_identifier;
0061 }
0062 
0063 void PotdBackend::setIdentifier(const QString &identifier)
0064 {
0065     if (m_identifier == identifier) {
0066         return;
0067     }
0068 
0069     if (m_ready) {
0070         s_engine->unregisterClient(m_identifier, m_args);
0071     }
0072     m_identifier = identifier;
0073     registerClient();
0074 
0075     Q_EMIT identifierChanged();
0076 }
0077 
0078 QVariantList PotdBackend::arguments() const
0079 {
0080     return m_args;
0081 }
0082 
0083 void PotdBackend::setArguments(const QVariantList &args)
0084 {
0085     if (m_args == args) {
0086         return;
0087     }
0088 
0089     if (m_ready) {
0090         s_engine->unregisterClient(m_identifier, m_args);
0091     }
0092     m_args = args;
0093     registerClient();
0094 
0095     Q_EMIT argumentsChanged();
0096 }
0097 
0098 bool PotdBackend::loading() const
0099 {
0100     if (!m_client) {
0101         return false;
0102     }
0103 
0104     return m_client->m_loading;
0105 }
0106 
0107 QString PotdBackend::localUrl() const
0108 {
0109     if (!m_client) {
0110         return {};
0111     }
0112 
0113     return m_client->m_localPath;
0114 }
0115 
0116 QUrl PotdBackend::infoUrl() const
0117 {
0118     if (!m_client) {
0119         return {};
0120     }
0121 
0122     return m_client->m_infoUrl;
0123 }
0124 
0125 QUrl PotdBackend::remoteUrl() const
0126 {
0127     if (!m_client) {
0128         return {};
0129     }
0130 
0131     return m_client->m_remoteUrl;
0132 }
0133 
0134 QString PotdBackend::title() const
0135 {
0136     if (!m_client) {
0137         return {};
0138     }
0139 
0140     return m_client->m_title;
0141 }
0142 
0143 QString PotdBackend::author() const
0144 {
0145     if (!m_client) {
0146         return {};
0147     }
0148 
0149     return m_client->m_author;
0150 }
0151 
0152 int PotdBackend::doesUpdateOverMeteredConnection() const
0153 {
0154     return m_doesUpdateOverMeteredConnection;
0155 }
0156 
0157 void PotdBackend::setUpdateOverMeteredConnection(int value)
0158 {
0159     value = std::clamp(value, 0, 2);
0160     const bool changed = m_doesUpdateOverMeteredConnection != value;
0161     if (changed) {
0162         m_doesUpdateOverMeteredConnection = value;
0163         Q_EMIT updateOverMeteredConnectionChanged();
0164     }
0165 
0166     if (m_ready && m_client) {
0167         m_client->setUpdateOverMeteredConnection(m_doesUpdateOverMeteredConnection);
0168     }
0169 }
0170 
0171 void PotdBackend::saveImage()
0172 {
0173     if (m_client->m_localPath.isEmpty()) {
0174         return;
0175     }
0176 
0177     auto sanitizeFileName = [](const QString &name) {
0178         if (name.isEmpty()) {
0179             return name;
0180         }
0181 
0182         const char notAllowedChars[] = ",^@={}[]~!?:&*\"|#%<>$\"'();`'/\\";
0183         QString sanitizedName(name);
0184 
0185         for (const char *c = notAllowedChars; *c; c++) {
0186             sanitizedName.replace(QLatin1Char(*c), QLatin1Char('-'));
0187         }
0188 
0189         return sanitizedName;
0190     };
0191 
0192     const QStringList &locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
0193     const QString path = locations.isEmpty() ? QStandardPaths::standardLocations(QStandardPaths::HomeLocation).at(0) : locations.at(0);
0194 
0195     QString defaultFileName = m_client->m_metadata.name().trimmed();
0196 
0197     if (!m_client->m_title.isEmpty()) {
0198         defaultFileName += QLatin1Char('-') + m_client->m_title.trimmed();
0199         if (!m_client->m_author.isEmpty()) {
0200             defaultFileName += QLatin1Char('-') + m_client->m_author.trimmed();
0201         }
0202     } else {
0203         // Use current date
0204         if (!defaultFileName.isEmpty()) {
0205             defaultFileName += QLatin1Char('-');
0206         }
0207         defaultFileName += QDate::currentDate().toString();
0208     }
0209 
0210     m_savedUrl = QUrl::fromLocalFile( //
0211         QFileDialog::getSaveFileName( //
0212             nullptr, //
0213             i18ndc("plasma_wallpaper_org.kde.potd", "@title:window", "Save Today's Picture"), //
0214             path + "/" + sanitizeFileName(defaultFileName) + ".jpg", //
0215             i18ndc("plasma_wallpaper_org.kde.potd", "@label:listbox Template for file dialog", "JPEG image (*.jpeg *.jpg *.jpe)"), //
0216             nullptr, //
0217             QFileDialog::DontConfirmOverwrite // KIO::CopyJob will show the confirmation dialog.
0218             ) //
0219     );
0220 
0221     if (m_savedUrl.isEmpty() || !m_savedUrl.isValid()) {
0222         return;
0223     }
0224 
0225     m_savedFolder = QUrl::fromLocalFile(QFileInfo(m_savedUrl.toLocalFile()).absolutePath());
0226 
0227     KIO::CopyJob *copyJob = KIO::copy(QUrl::fromLocalFile(m_client->m_localPath), m_savedUrl, KIO::HideProgressInfo);
0228     connect(copyJob, &KJob::finished, this, [this](KJob *job) {
0229         if (job->error()) {
0230             m_saveStatusMessage = job->errorText();
0231             if (m_saveStatusMessage.isEmpty()) {
0232                 m_saveStatusMessage = i18ndc("plasma_wallpaper_org.kde.potd", "@info:status after a save action", "The image was not saved.");
0233             }
0234             m_saveStatus = FileOperationStatus::Failed;
0235             Q_EMIT saveStatusChanged();
0236         } else {
0237             m_saveStatusMessage = i18ndc("plasma_wallpaper_org.kde.potd",
0238                                          "@info:status after a save action %1 file path %2 basename",
0239                                          "The image was saved as <a href=\"%1\">%2</a>",
0240                                          m_savedUrl.toString(),
0241                                          m_savedUrl.fileName());
0242             m_saveStatus = FileOperationStatus::Successful;
0243             Q_EMIT saveStatusChanged();
0244         }
0245     });
0246     copyJob->start();
0247 }
0248 
0249 void PotdBackend::registerClient()
0250 {
0251     if (!m_ready) {
0252         return;
0253     }
0254 
0255     m_client = s_engine->registerClient(m_identifier, m_args);
0256 
0257     if (!m_client) {
0258         // Invalid identifier
0259         return;
0260     }
0261 
0262     connect(m_client, &PotdClient::loadingChanged, this, &PotdBackend::loadingChanged);
0263     connect(m_client, &PotdClient::localUrlChanged, this, &PotdBackend::localUrlChanged);
0264     connect(m_client, &PotdClient::infoUrlChanged, this, &PotdBackend::infoUrlChanged);
0265     connect(m_client, &PotdClient::remoteUrlChanged, this, &PotdBackend::remoteUrlChanged);
0266     connect(m_client, &PotdClient::titleChanged, this, &PotdBackend::titleChanged);
0267     connect(m_client, &PotdClient::authorChanged, this, &PotdBackend::authorChanged);
0268     connect(m_client, &PotdClient::done, this, [this](PotdClient *, bool success) {
0269         if (success) {
0270             Q_EMIT imageChanged();
0271         }
0272     });
0273 
0274     // Refresh the desktop wallpaper and the information in config dialog
0275     Q_EMIT loadingChanged();
0276     Q_EMIT localUrlChanged();
0277     Q_EMIT infoUrlChanged();
0278     Q_EMIT remoteUrlChanged();
0279     Q_EMIT titleChanged();
0280     Q_EMIT authorChanged();
0281 
0282     // For updateSource()
0283     setUpdateOverMeteredConnection(m_doesUpdateOverMeteredConnection);
0284 }