File indexing completed on 2024-04-14 03:40:36
0001 /* 0002 This file is part of Kiten, a KDE Japanese Reference Tool... 0003 SPDX-FileCopyrightText: 2011 Daniel E. Moctezuma <democtezuma@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "dictionaryupdatemanager.h" 0009 0010 #include "kiten.h" 0011 #include "kitenconfig.h" 0012 #include "kitenmacros.h" 0013 0014 #include <KActionCollection> 0015 #include <KCompressionDevice> 0016 #include <KIO/StoredTransferJob> 0017 #include <KLocalizedString> 0018 #include <KMessageBox> 0019 #include <QAction> 0020 #include <QDir> 0021 #include <QUrl> 0022 0023 #include <QStandardPaths> 0024 #include <QStringDecoder> 0025 #include <QTemporaryFile> 0026 #include <QTextStream> 0027 0028 // URL to the information file. 0029 #define INFO_URL QStringLiteral("http://ftp.edrdg.org/pub/Nihongo/edicthdr.txt") 0030 // URL to the EDICT dictionary. 0031 #define EDICT_URL QStringLiteral("http://ftp.edrdg.org/pub/Nihongo/edict.gz") 0032 // URL to the KANJIDIC dictionary. 0033 #define KANJIDIC_URL QStringLiteral("http://ftp.edrdg.org/pub/Nihongo/kanjidic.gz") 0034 0035 using namespace Qt::StringLiterals; 0036 0037 DictionaryUpdateManager::DictionaryUpdateManager(Kiten *parent) 0038 : QObject(parent) 0039 , _parent(parent) 0040 , _config(parent->getConfig()) 0041 , _succeeded(QStringList()) 0042 , _failed(QStringList()) 0043 , _counter(0) 0044 { 0045 _actionUpdate = _parent->actionCollection()->add<QAction>(QStringLiteral("update_dictionaries")); 0046 _actionUpdate->setText(i18n("Check for dictionary &updates")); 0047 _parent->actionCollection()->setDefaultShortcut(_actionUpdate, Qt::CTRL | Qt::Key_U); 0048 0049 connect(_actionUpdate, &QAction::triggered, this, &DictionaryUpdateManager::checkForUpdates); 0050 } 0051 0052 void DictionaryUpdateManager::checkForUpdates() 0053 { 0054 qDebug() << "Checking for EDICT & KANJIDIC updates."; 0055 // Download the information file we need to check 0056 // whether or not an update to our dictionaries is necessary. 0057 KIO::StoredTransferJob *job = KIO::storedGet(QUrl(INFO_URL)); 0058 connect(job, &KIO::StoredTransferJob::result, this, &DictionaryUpdateManager::checkInfoFile); 0059 } 0060 0061 void DictionaryUpdateManager::checkIfUpdateFinished() 0062 { 0063 // Emit the updateFinished signal once we finished 0064 // to update our installed dictionaries. 0065 if (_counter == _config->dictionary_list().size()) { 0066 // Make sure to reset this variable to 0. 0067 _counter = 0; 0068 Q_EMIT updateFinished(); 0069 } 0070 } 0071 0072 void DictionaryUpdateManager::checkInfoFile(KJob *job) 0073 { 0074 KIO::StoredTransferJob *storedJob = static_cast<KIO::StoredTransferJob *>(job); 0075 QByteArray data(storedJob->data()); 0076 // Check if we have valid data to work with. 0077 if (data.isNull() || data.isEmpty()) { 0078 KMessageBox::error(nullptr, i18n("Update canceled.\nCould not read file.")); 0079 job->deleteLater(); 0080 return; 0081 } 0082 0083 // We don't need to store this information file in our Hard Disk Drive, 0084 // a temporary file is enough. 0085 QTemporaryFile tempFile; 0086 if (!tempFile.open()) { 0087 KMessageBox::error(nullptr, i18n("Update canceled.\nCould not open file.")); 0088 qDebug() << "Could not open tempFile."; 0089 tempFile.deleteLater(); 0090 job->deleteLater(); 0091 return; 0092 } 0093 tempFile.write(data); 0094 tempFile.close(); 0095 0096 // Get the latest creation date for the EDICT dictionary on the server. 0097 QDate webFileDate = getFileDate(tempFile); 0098 if (!webFileDate.isValid()) { 0099 // The program should not get to this point. 0100 // Maybe the format/content of (one or both) the files changed? 0101 // or maybe one or both of the files could not be opened in the 0102 // getFileDate function that would return an invalid empty date. 0103 KMessageBox::error(nullptr, i18n("Update canceled.\nThe update information file has an invalid date.")); 0104 tempFile.deleteLater(); 0105 job->deleteLater(); 0106 return; 0107 } 0108 0109 // At this point we have a valid creation date from the server. 0110 // Now we can check our dictionaries. 0111 0112 // Connect to this signal to know when the update process is finished. 0113 connect(this, &DictionaryUpdateManager::updateFinished, this, &DictionaryUpdateManager::showUpdateResults); 0114 0115 // This variable help us to know if we need to download any file. 0116 bool updates = false; 0117 // Iterate on each of our installed dictionaries. 0118 for (const QString &dict : _config->dictionary_list()) { 0119 QString filePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kiten/") + dict.toLower()); 0120 QFile file(filePath); 0121 qDebug() << "Local dictionary path:" << file.fileName(); 0122 0123 // Get the creation date for this dictionary. 0124 QDate localFileDate = getFileDate(file); 0125 0126 if (!localFileDate.isValid()) { 0127 // Add it to our 'failed to udate' list. 0128 _failed.append(dict.toUpper()); 0129 qDebug() << "Failed (invalid date):" << dict.toUpper(); 0130 continue; 0131 } else if (localFileDate == webFileDate) { 0132 // Add it to our 'up to date' list. 0133 _succeeded.append(dict.toUpper()); 0134 qDebug() << "Success (up to date):" << dict.toUpper(); 0135 continue; 0136 } else { 0137 // Indicate we need to download something. 0138 updates = true; 0139 0140 if (dict.toLower() == EDICT) { 0141 downloadDictionary(EDICT_URL); 0142 } else { 0143 downloadDictionary(KANJIDIC_URL); 0144 } 0145 } 0146 } 0147 0148 // Let Kiten know we finished and don't need to download anything else. 0149 if (!updates) { 0150 Q_EMIT updateFinished(); 0151 } 0152 0153 tempFile.deleteLater(); 0154 job->deleteLater(); 0155 } 0156 0157 void DictionaryUpdateManager::downloadDictionary(const QString &url) 0158 { 0159 qDebug() << "Download started!"; 0160 // Download dictionary. 0161 KIO::StoredTransferJob *dictionaryJob = KIO::storedGet(QUrl(url)); 0162 connect(dictionaryJob, &KIO::StoredTransferJob::result, this, &DictionaryUpdateManager::installDictionary); 0163 } 0164 0165 QDate DictionaryUpdateManager::getFileDate(QFile &file) 0166 { 0167 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 0168 qDebug() << "Could not open " << file.fileName(); 0169 return QDate(); 0170 } 0171 0172 QStringDecoder decoder("EUC-JP"); 0173 const QString decoded = decoder(file.readAll()); 0174 0175 QTextStream fileStream(decoded.toUtf8()); 0176 0177 // The first line of the file is in the following form: 0178 // ??? / 0179 // EDICT, EDICT_SUB(P), EDICT2 Japanese-English Electronic Dictionary Files/ 0180 // Copyright Electronic Dictionary Research & Development Group - year/ 0181 // Created: year-month-day/ 0182 // ###### entries 0183 0184 // NOTE: the above last line only appears in the web status file for EDICT. 0185 // That file has only 2 lines, one is the '?' symbols with the name of the 0186 // dictionary, copyright and creation date, the other one is the number 0187 // of entries in the EDICT dictionary. 0188 // (see INFO_URL macro). 0189 0190 // We take the 4th section which is the last one 0191 // separated by the '/' character and the 0192 // 10 rightmost characters for that section. 0193 QString dateSection = fileStream.readLine().section('/'_L1, -1, -1, QString::SectionSkipEmpty).right(10); 0194 0195 // dateSection has the following value: year-month-day 0196 // Finally we take the numbers separated by the '-' character. 0197 int year = dateSection.section('-'_L1, 0, 0).toInt(); 0198 int month = dateSection.section('-'_L1, 1, 1).toInt(); 0199 int day = dateSection.section('-'_L1, 2, 2).toInt(); 0200 0201 file.close(); 0202 0203 qDebug() << "Date found:" << dateSection << "(" << file.fileName() << ")"; 0204 0205 return QDate(year, month, day); 0206 } 0207 0208 void DictionaryUpdateManager::installDictionary(KJob *job) 0209 { 0210 // Increase the number of dictionaries we try to install. 0211 // This way we can know when we finished with our installed dictionaries. 0212 _counter++; 0213 0214 KIO::StoredTransferJob *storedJob = static_cast<KIO::StoredTransferJob *>(job); 0215 QByteArray data(storedJob->data()); 0216 QString url(storedJob->url().toDisplayString()); 0217 0218 // What we actually downloaded was a GZIP file that we need to extract. 0219 // As there is no need to keep this file, we make it a temporary file. 0220 QTemporaryFile compressedFile; 0221 if (!compressedFile.open()) { 0222 qDebug() << "Could not create the downloaded .gz file."; 0223 _failed.append(url.contains(EDICT) ? EDICT : KANJIDIC); 0224 job->deleteLater(); 0225 checkIfUpdateFinished(); 0226 return; 0227 } 0228 // Create the GZIP file from the downloaded data. 0229 compressedFile.write(data); 0230 compressedFile.close(); 0231 0232 qDebug() << "Dictionary download finished!"; 0233 qDebug() << "Extracting dictionary..."; 0234 0235 // Extract the GZIP file. 0236 // QIODevice *device = KFilterDev::deviceForFile( compressedFile.fileName(), "application/x-gzip" ); 0237 KCompressionDevice *device = new KCompressionDevice(compressedFile.fileName(), KCompressionDevice::GZip); 0238 if (!device->open(QIODevice::ReadOnly)) { 0239 qDebug() << "Could not extract the dictionary file."; 0240 _failed.append(url.contains(EDICT) ? EDICT : KANJIDIC); 0241 delete device; 0242 job->deleteLater(); 0243 checkIfUpdateFinished(); 0244 return; 0245 } 0246 0247 // Check the first line's first character of the extracted file. 0248 // EDICT starts with a ' ', KANJIDIC starts with a '#'. 0249 QString fileName = device->readLine().startsWith('#') ? QStringLiteral("kanjidic") : QStringLiteral("edict"); 0250 // Reset the position where we are going to start reading the content. 0251 device->reset(); 0252 // Thanks to the above lines we can get the path to the correct file to be updated. 0253 QString dictPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kiten/"); 0254 QDir dir(dictPath); 0255 dir.mkpath(dictPath); 0256 0257 QFile dictionary(dictPath + fileName); 0258 if (!dictionary.open(QIODevice::WriteOnly)) { 0259 qDebug() << "Could not create the new dictionary file."; 0260 _failed.append(fileName.toUpper()); 0261 device->close(); 0262 delete device; 0263 job->deleteLater(); 0264 checkIfUpdateFinished(); 0265 return; 0266 } 0267 0268 // Write the new dictionary file to disk. 0269 dictionary.write(device->readAll()); 0270 dictionary.close(); 0271 device->close(); 0272 0273 delete device; 0274 job->deleteLater(); 0275 0276 // Check if we finished updating. 0277 checkIfUpdateFinished(); 0278 qDebug() << "Successfully installed at:" << dictionary.fileName(); 0279 } 0280 0281 void DictionaryUpdateManager::showUpdateResults() 0282 { 0283 // Avoid multiple calls to this slot. 0284 disconnect(this, &DictionaryUpdateManager::updateFinished, this, &DictionaryUpdateManager::showUpdateResults); 0285 0286 if (!_succeeded.isEmpty() && _failed.isEmpty()) { 0287 KMessageBox::information(nullptr, i18n("You already have the latest updates.")); 0288 } else if (_succeeded.isEmpty() && _failed.isEmpty()) { 0289 KMessageBox::information(nullptr, i18n("Successfully updated your dictionaries.")); 0290 } else if (!_succeeded.isEmpty() && !_failed.isEmpty()) { 0291 KMessageBox::information(nullptr, i18n("Successfully updated:\n%1\n\nFailed to update:\n%2", _succeeded.join("\n"_L1), _failed.join("\n"_L1))); 0292 } else if (_succeeded.isEmpty() && !_failed.isEmpty()) { 0293 KMessageBox::error(nullptr, i18n("Failed to update:\n%1", _failed.join("\n"_L1))); 0294 } 0295 0296 // Avoid repetitions in our lists. 0297 _succeeded.clear(); 0298 _failed.clear(); 0299 } 0300 0301 #include "moc_dictionaryupdatemanager.cpp"