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 }