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