File indexing completed on 2024-05-05 17:57:23
0001 /* 0002 SPDX-FileCopyrightText: 2003 Csaba Karai <krusader@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "combiner.h" 0009 #include "../FileSystem/filesystem.h" 0010 0011 // QtCore 0012 #include <QFileInfo> 0013 0014 #include <KI18n/KLocalizedString> 0015 #include <KIO/Job> 0016 #include <KIO/JobUiDelegate> 0017 #include <KIOCore/KFileItem> 0018 #include <KWidgetsAddons/KMessageBox> 0019 #include <kio_version.h> 0020 #include <utility> 0021 0022 // TODO: delete destination file on error 0023 // TODO: cache more than one byte array of data 0024 0025 Combiner::Combiner(QWidget *parent, QUrl baseURLIn, QUrl destinationURLIn, bool unixNamingIn) 0026 : QProgressDialog(parent, Qt::WindowFlags()) 0027 , baseURL(std::move(baseURLIn)) 0028 , destinationURL(std::move(destinationURLIn)) 0029 , hasValidSplitFile(false) 0030 , fileCounter(0) 0031 , permissions(-1) 0032 , receivedSize(0) 0033 , statJob(nullptr) 0034 , combineReadJob(nullptr) 0035 , combineWriteJob(nullptr) 0036 , unixNaming(unixNamingIn) 0037 { 0038 crcContext = new CRC32(); 0039 0040 splitFile = ""; 0041 0042 setMaximum(100); 0043 setAutoClose(false); /* don't close or reset the dialog automatically */ 0044 setAutoReset(false); 0045 setLabelText("Krusader::Combiner"); 0046 setWindowModality(Qt::WindowModal); 0047 0048 firstFileIs000 = true; // start with this assumption, will set it to false as soon as .000 isn't found 0049 } 0050 0051 Combiner::~Combiner() 0052 { 0053 combineAbortJobs(); 0054 delete crcContext; 0055 } 0056 0057 void Combiner::combine() 0058 { 0059 setWindowTitle(i18n("Krusader::Combining...")); 0060 setLabelText(i18n("Combining the file %1...", baseURL.toDisplayString(QUrl::PreferLocalFile))); 0061 0062 /* check whether the .crc file exists */ 0063 splURL = baseURL.adjusted(QUrl::RemoveFilename); 0064 splURL.setPath(splURL.path() + baseURL.fileName() + ".crc"); 0065 KFileItem file(splURL); 0066 // FIXME: works only for local files - use KIO::stat() instead 0067 file.refresh(); 0068 0069 if (!file.isReadable()) { 0070 int ret = KMessageBox::questionYesNo(nullptr, 0071 i18n("The CRC information file (%1) is missing.\n" 0072 "Validity checking is impossible without it. Continue combining?", 0073 splURL.toDisplayString(QUrl::PreferLocalFile)), 0074 {}, 0075 KStandardGuiItem::cont(), 0076 KStandardGuiItem::cancel()); 0077 0078 if (ret == KMessageBox::No) { 0079 reject(); 0080 return; 0081 } 0082 0083 statDest(); 0084 } else { 0085 permissions = file.permissions() | QFile::WriteUser; 0086 0087 combineReadJob = KIO::get(splURL, KIO::NoReload, KIO::HideProgressInfo); 0088 0089 connect(combineReadJob, &KIO::TransferJob::data, this, &Combiner::combineSplitFileDataReceived); 0090 connect(combineReadJob, &KIO::TransferJob::result, this, &Combiner::combineSplitFileFinished); 0091 } 0092 0093 exec(); 0094 } 0095 0096 void Combiner::combineSplitFileDataReceived(KIO::Job *, const QByteArray &byteArray) 0097 { 0098 splitFile += QString(byteArray); 0099 } 0100 0101 void Combiner::combineSplitFileFinished(KJob *job) 0102 { 0103 combineReadJob = nullptr; 0104 QString error; 0105 0106 if (job->error()) 0107 error = i18n("Error at reading the CRC file (%1).", splURL.toDisplayString(QUrl::PreferLocalFile)); 0108 else { 0109 splitFile.remove('\r'); // Windows compatibility 0110 QStringList splitFileContent = splitFile.split('\n'); 0111 0112 bool hasFileName = false, hasSize = false, hasCrc = false; 0113 0114 for (int i = 0; i != splitFileContent.count(); i++) { 0115 int ndx = splitFileContent[i].indexOf('='); 0116 if (ndx == -1) 0117 continue; 0118 QString token = splitFileContent[i].left(ndx).trimmed(); 0119 QString value = splitFileContent[i].mid(ndx + 1); 0120 0121 if (token == "filename") { 0122 expectedFileName = value; 0123 hasFileName = true; 0124 } else if (token == "size") { 0125 // FIXME - don't use c functions !!! 0126 sscanf(value.trimmed().toLocal8Bit(), "%llu", &expectedSize); 0127 hasSize = true; 0128 } 0129 if (token == "crc32") { 0130 expectedCrcSum = value.trimmed().rightJustified(8, '0'); 0131 hasCrc = true; 0132 } 0133 } 0134 0135 if (!hasFileName || !hasSize || !hasCrc) 0136 error = i18n("Not a valid CRC file."); 0137 else 0138 hasValidSplitFile = true; 0139 } 0140 0141 if (!error.isEmpty()) { 0142 int ret = KMessageBox::questionYesNo(nullptr, 0143 error + i18n("\nValidity checking is impossible without a good CRC file. Continue combining?"), 0144 {}, 0145 KStandardGuiItem::cont(), 0146 KStandardGuiItem::cancel()); 0147 if (ret == KMessageBox::No) { 0148 reject(); 0149 return; 0150 } 0151 } 0152 0153 statDest(); 0154 } 0155 0156 void Combiner::statDest() 0157 { 0158 if (writeURL.isEmpty()) { 0159 writeURL = FileSystem::ensureTrailingSlash(destinationURL); 0160 if (hasValidSplitFile) 0161 writeURL.setPath(writeURL.path() + expectedFileName); 0162 else if (unixNaming) 0163 writeURL.setPath(writeURL.path() + baseURL.fileName() + ".out"); 0164 else 0165 writeURL.setPath(writeURL.path() + baseURL.fileName()); 0166 } 0167 0168 #if KIO_VERSION >= QT_VERSION_CHECK(5, 69, 0) 0169 statJob = KIO::statDetails(writeURL, KIO::StatJob::DestinationSide, KIO::StatNoDetails, KIO::HideProgressInfo); 0170 #else 0171 statJob = KIO::stat(writeURL, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); 0172 #endif 0173 connect(statJob, &KIO::StatJob::result, this, &Combiner::statDestResult); 0174 } 0175 0176 void Combiner::statDestResult(KJob *job) 0177 { 0178 statJob = nullptr; 0179 0180 if (job->error()) { 0181 if (job->error() == KIO::ERR_DOES_NOT_EXIST) { 0182 openNextFile(); 0183 } else { 0184 dynamic_cast<KIO::Job *>(job)->uiDelegate()->showErrorMessage(); 0185 reject(); 0186 } 0187 } else { // destination already exists 0188 KIO::RenameDialog_Options mode = dynamic_cast<KIO::StatJob *>(job)->statResult().isDir() ? KIO::RenameDialog_IsDirectory : KIO::RenameDialog_Overwrite; 0189 KIO::RenameDialog dlg(this, i18n("File Already Exists"), QUrl(), writeURL, mode); 0190 switch (dlg.exec()) { 0191 case KIO::Result_Overwrite: 0192 openNextFile(); 0193 break; 0194 case KIO::Result_Rename: { 0195 writeURL = dlg.newDestUrl(); 0196 statDest(); 0197 break; 0198 } 0199 default: 0200 reject(); 0201 } 0202 } 0203 } 0204 0205 void Combiner::openNextFile() 0206 { 0207 if (unixNaming) { 0208 if (readURL.isEmpty()) 0209 readURL = baseURL; 0210 else { 0211 QString name = readURL.fileName(); 0212 int pos = name.length() - 1; 0213 QChar ch; 0214 0215 do { 0216 ch = name.at(pos).toLatin1() + 1; 0217 if (ch == QChar('Z' + 1)) 0218 ch = 'A'; 0219 if (ch == QChar('z' + 1)) 0220 ch = 'a'; 0221 name[pos] = ch; 0222 pos--; 0223 } while (pos >= 0 && ch.toUpper() == QChar('A')); 0224 readURL = readURL.adjusted(QUrl::RemoveFilename); 0225 readURL.setPath(readURL.path() + name); 0226 } 0227 } else { 0228 QString index("%1"); /* determining the filename */ 0229 index = index.arg(fileCounter++).rightJustified(3, '0'); 0230 readURL = baseURL.adjusted(QUrl::RemoveFilename); 0231 readURL.setPath(readURL.path() + baseURL.fileName() + '.' + index); 0232 } 0233 0234 /* creating a read job */ 0235 combineReadJob = KIO::get(readURL, KIO::NoReload, KIO::HideProgressInfo); 0236 0237 connect(combineReadJob, &KIO::TransferJob::data, this, &Combiner::combineDataReceived); 0238 connect(combineReadJob, &KIO::TransferJob::result, this, &Combiner::combineReceiveFinished); 0239 if (hasValidSplitFile) 0240 connect(combineReadJob, SIGNAL(percent(KJob *, ulong)), this, SLOT(combineWritePercent(KJob *, ulong))); 0241 } 0242 0243 void Combiner::combineDataReceived(KIO::Job *, const QByteArray &byteArray) 0244 { 0245 if (byteArray.size() == 0) 0246 return; 0247 0248 crcContext->update(reinterpret_cast<unsigned char *>(const_cast<char *>(byteArray.data())), byteArray.size()); 0249 transferArray = QByteArray(byteArray.data(), byteArray.length()); 0250 0251 receivedSize += byteArray.size(); 0252 0253 if (combineWriteJob == nullptr) { 0254 combineWriteJob = KIO::put(writeURL, permissions, KIO::HideProgressInfo | KIO::Overwrite); 0255 0256 connect(combineWriteJob, &KIO::TransferJob::dataReq, this, &Combiner::combineDataSend); 0257 connect(combineWriteJob, &KIO::TransferJob::result, this, &Combiner::combineSendFinished); 0258 } 0259 0260 // continue writing and suspend read job until received data is handed over to the write job 0261 combineReadJob->suspend(); 0262 combineWriteJob->resume(); 0263 } 0264 0265 void Combiner::combineReceiveFinished(KJob *job) 0266 { 0267 combineReadJob = nullptr; /* KIO automatically deletes the object after Finished signal */ 0268 if (job->error()) { 0269 if (job->error() == KIO::ERR_DOES_NOT_EXIST) { 0270 if (fileCounter == 1) { // .000 file doesn't exist but .001 is still a valid first file 0271 firstFileIs000 = false; 0272 openNextFile(); 0273 } else if (!firstFileIs000 && fileCounter == 2) { // neither .000 nor .001 exist 0274 combineAbortJobs(); 0275 KMessageBox::error(nullptr, i18n("Cannot open the first split file of %1.", baseURL.toDisplayString(QUrl::PreferLocalFile))); 0276 reject(); 0277 } else { // we've received the last file 0278 // write out the remaining part of the file 0279 combineWriteJob->resume(); 0280 0281 if (hasValidSplitFile) { 0282 QString crcResult = QString("%1").arg(crcContext->result(), 0, 16).toUpper().trimmed().rightJustified(8, '0'); 0283 0284 if (receivedSize != expectedSize) 0285 error = i18n("Incorrect filesize, the file might have been corrupted."); 0286 else if (crcResult != expectedCrcSum.toUpper().trimmed()) 0287 error = i18n("Incorrect CRC checksum, the file might have been corrupted."); 0288 } 0289 } 0290 } else { 0291 combineAbortJobs(); 0292 dynamic_cast<KIO::Job *>(job)->uiDelegate()->showErrorMessage(); 0293 reject(); 0294 } 0295 } else 0296 openNextFile(); 0297 } 0298 0299 void Combiner::combineDataSend(KIO::Job *, QByteArray &byteArray) 0300 { 0301 byteArray = transferArray; 0302 transferArray = QByteArray(); 0303 0304 if (combineReadJob) { 0305 // continue reading and suspend write job until data is available 0306 combineReadJob->resume(); 0307 combineWriteJob->suspend(); 0308 } 0309 } 0310 0311 void Combiner::combineSendFinished(KJob *job) 0312 { 0313 combineWriteJob = nullptr; /* KIO automatically deletes the object after Finished signal */ 0314 0315 if (job->error()) { /* any error occurred? */ 0316 combineAbortJobs(); 0317 dynamic_cast<KIO::Job *>(job)->uiDelegate()->showErrorMessage(); 0318 reject(); 0319 } else if (!error.isEmpty()) { /* was any error message at reading ? */ 0320 combineAbortJobs(); /* we cannot write out it in combineReceiveFinished */ 0321 KMessageBox::error(nullptr, error); /* because emit accept closes it in this function */ 0322 reject(); 0323 } else 0324 accept(); 0325 } 0326 0327 void Combiner::combineAbortJobs() 0328 { 0329 if (statJob) 0330 statJob->kill(KJob::Quietly); 0331 if (combineReadJob) 0332 combineReadJob->kill(KJob::Quietly); 0333 if (combineWriteJob) 0334 combineWriteJob->kill(KJob::Quietly); 0335 0336 statJob = combineReadJob = combineWriteJob = nullptr; 0337 } 0338 0339 void Combiner::combineWritePercent(KJob *, unsigned long) 0340 { 0341 auto percent = static_cast<int>(((static_cast<long double>(receivedSize) / expectedSize) * 100.) + 0.5); 0342 setValue(percent); 0343 }