File indexing completed on 2024-05-12 16:59:41

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