diff --git a/krusader/Splitter/combiner.cpp b/krusader/Splitter/combiner.cpp index 0dd0c499..59ceaf12 100644 --- a/krusader/Splitter/combiner.cpp +++ b/krusader/Splitter/combiner.cpp @@ -1,343 +1,348 @@ /***************************************************************************** * Copyright (C) 2003 Csaba Karai * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "combiner.h" #include "../FileSystem/filesystem.h" // QtCore #include #include #include #include #include #include +#include #include //TODO: delete destination file on error //TODO: cache more than one byte array of data Combiner::Combiner(QWidget* parent, QUrl baseURLIn, QUrl destinationURLIn, bool unixNamingIn) : QProgressDialog(parent, nullptr), baseURL(std::move(baseURLIn)), destinationURL(std::move(destinationURLIn)), hasValidSplitFile(false), fileCounter(0), permissions(-1), receivedSize(0), statJob(nullptr), combineReadJob(nullptr), combineWriteJob(nullptr), unixNaming(unixNamingIn) { crcContext = new CRC32(); splitFile = ""; setMaximum(100); setAutoClose(false); /* don't close or reset the dialog automatically */ setAutoReset(false); setLabelText("Krusader::Combiner"); setWindowModality(Qt::WindowModal); firstFileIs000 = true; //start with this assumption, will set it to false as soon as .000 isn't found } Combiner::~Combiner() { combineAbortJobs(); delete crcContext; } void Combiner::combine() { setWindowTitle(i18n("Krusader::Combining...")); setLabelText(i18n("Combining the file %1...", baseURL.toDisplayString(QUrl::PreferLocalFile))); /* check whether the .crc file exists */ splURL = baseURL.adjusted(QUrl::RemoveFilename); splURL.setPath(splURL.path() + baseURL.fileName() + ".crc"); KFileItem file(splURL); //FIXME: works only for local files - use KIO::stat() instead file.refresh(); if (!file.isReadable()) { int ret = KMessageBox::questionYesNo(nullptr, i18n("The CRC information file (%1) is missing.\n" "Validity checking is impossible without it. Continue combining?", splURL.toDisplayString(QUrl::PreferLocalFile))); if (ret == KMessageBox::No) { emit reject(); return; } statDest(); } else { permissions = file.permissions() | QFile::WriteUser; combineReadJob = KIO::get(splURL, KIO::NoReload, KIO::HideProgressInfo); connect(combineReadJob, &KIO::TransferJob::data, this, &Combiner::combineSplitFileDataReceived); connect(combineReadJob, &KIO::TransferJob::result, this, &Combiner::combineSplitFileFinished); } exec(); } void Combiner::combineSplitFileDataReceived(KIO::Job *, const QByteArray &byteArray) { splitFile += QString(byteArray); } void Combiner::combineSplitFileFinished(KJob *job) { combineReadJob = nullptr; QString error; if (job->error()) error = i18n("Error at reading the CRC file (%1).", splURL.toDisplayString(QUrl::PreferLocalFile)); else { splitFile.remove('\r'); // Windows compatibility QStringList splitFileContent = splitFile.split('\n'); bool hasFileName = false, hasSize = false, hasCrc = false; for (int i = 0; i != splitFileContent.count(); i++) { int ndx = splitFileContent[i].indexOf('='); if (ndx == -1) continue; QString token = splitFileContent[i].left(ndx).trimmed(); QString value = splitFileContent[i].mid(ndx + 1); if (token == "filename") { expectedFileName = value; hasFileName = true; } else if (token == "size") { //FIXME - don't use c functions !!! sscanf(value.trimmed().toLocal8Bit(), "%llu", &expectedSize); hasSize = true; } if (token == "crc32") { expectedCrcSum = value.trimmed().rightJustified(8, '0'); hasCrc = true; } } if (!hasFileName || !hasSize || !hasCrc) error = i18n("Not a valid CRC file."); else hasValidSplitFile = true; } if (!error.isEmpty()) { int ret = KMessageBox::questionYesNo(nullptr, error + i18n("\nValidity checking is impossible without a good CRC file. Continue combining?")); if (ret == KMessageBox::No) { emit reject(); return; } } statDest(); } void Combiner::statDest() { if (writeURL.isEmpty()) { writeURL = FileSystem::ensureTrailingSlash(destinationURL); if (hasValidSplitFile) writeURL.setPath(writeURL.path() + expectedFileName); else if (unixNaming) writeURL.setPath(writeURL.path() + baseURL.fileName() + ".out"); else writeURL.setPath(writeURL.path() + baseURL.fileName()); } +#if KIO_VERSION >= QT_VERSION_CHECK(5, 69, 0) + statJob = KIO::statDetails(writeURL, KIO::StatJob::DestinationSide, KIO::StatNoDetails, KIO::HideProgressInfo); +#else statJob = KIO::stat(writeURL, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); +#endif connect(statJob, &KIO::StatJob::result, this, &Combiner::statDestResult); } void Combiner::statDestResult(KJob* job) { statJob = nullptr; if (job->error()) { if (job->error() == KIO::ERR_DOES_NOT_EXIST) { openNextFile(); } else { dynamic_cast(job)->uiDelegate()->showErrorMessage(); emit reject(); } } else { // destination already exists KIO::RenameDialog_Options mode = dynamic_cast(job)->statResult().isDir() ? KIO::RenameDialog_IsDirectory : KIO::RenameDialog_Overwrite; KIO::RenameDialog dlg(this, i18n("File Already Exists"), QUrl(), writeURL, mode); switch (dlg.exec()) { case KIO::Result_Overwrite: openNextFile(); break; case KIO::Result_Rename: { writeURL = dlg.newDestUrl(); statDest(); break; } default: emit reject(); } } } void Combiner::openNextFile() { if (unixNaming) { if (readURL.isEmpty()) readURL = baseURL; else { QString name = readURL.fileName(); int pos = name.length() - 1; QChar ch; do { ch = name.at(pos).toLatin1() + 1; if (ch == QChar('Z' + 1)) ch = 'A'; if (ch == QChar('z' + 1)) ch = 'a'; name[ pos ] = ch; pos--; } while (pos >= 0 && ch.toUpper() == QChar('A')); readURL = readURL.adjusted(QUrl::RemoveFilename); readURL.setPath(readURL.path() + name); } } else { QString index("%1"); /* determining the filename */ index = index.arg(fileCounter++).rightJustified(3, '0'); readURL = baseURL.adjusted(QUrl::RemoveFilename); readURL.setPath(readURL.path() + baseURL.fileName() + '.' + index); } /* creating a read job */ combineReadJob = KIO::get(readURL, KIO::NoReload, KIO::HideProgressInfo); connect(combineReadJob, &KIO::TransferJob::data, this, &Combiner::combineDataReceived); connect(combineReadJob, &KIO::TransferJob::result, this, &Combiner::combineReceiveFinished); if (hasValidSplitFile) connect(combineReadJob, SIGNAL(percent(KJob*,ulong)), this, SLOT(combineWritePercent(KJob*,ulong))); } void Combiner::combineDataReceived(KIO::Job *, const QByteArray &byteArray) { if (byteArray.size() == 0) return; crcContext->update((unsigned char *)byteArray.data(), byteArray.size()); transferArray = QByteArray(byteArray.data(), byteArray.length()); receivedSize += byteArray.size(); if (combineWriteJob == nullptr) { combineWriteJob = KIO::put(writeURL, permissions, KIO::HideProgressInfo | KIO::Overwrite); connect(combineWriteJob, &KIO::TransferJob::dataReq, this, &Combiner::combineDataSend); connect(combineWriteJob, &KIO::TransferJob::result, this, &Combiner::combineSendFinished); } // continue writing and suspend read job until received data is handed over to the write job combineReadJob->suspend(); combineWriteJob->resume(); } void Combiner::combineReceiveFinished(KJob *job) { combineReadJob = nullptr; /* KIO automatically deletes the object after Finished signal */ if (job->error()) { if (job->error() == KIO::ERR_DOES_NOT_EXIST) { if (fileCounter == 1) { // .000 file doesn't exist but .001 is still a valid first file firstFileIs000 = false; openNextFile(); } else if (!firstFileIs000 && fileCounter == 2) { // neither .000 nor .001 exist combineAbortJobs(); KMessageBox::error(nullptr, i18n("Cannot open the first split file of %1.", baseURL.toDisplayString(QUrl::PreferLocalFile))); emit reject(); } else { // we've received the last file // write out the remaining part of the file combineWriteJob->resume(); if (hasValidSplitFile) { QString crcResult = QString("%1").arg(crcContext->result(), 0, 16).toUpper().trimmed() .rightJustified(8, '0'); if (receivedSize != expectedSize) error = i18n("Incorrect filesize, the file might have been corrupted."); else if (crcResult != expectedCrcSum.toUpper().trimmed()) error = i18n("Incorrect CRC checksum, the file might have been corrupted."); } } } else { combineAbortJobs(); dynamic_cast(job)->uiDelegate()->showErrorMessage(); emit reject(); } } else openNextFile(); } void Combiner::combineDataSend(KIO::Job *, QByteArray &byteArray) { byteArray = transferArray; transferArray = QByteArray(); if (combineReadJob) { // continue reading and suspend write job until data is available combineReadJob->resume(); combineWriteJob->suspend(); } } void Combiner::combineSendFinished(KJob *job) { combineWriteJob = nullptr; /* KIO automatically deletes the object after Finished signal */ if (job->error()) { /* any error occurred? */ combineAbortJobs(); dynamic_cast(job)->uiDelegate()->showErrorMessage(); emit reject(); } else if (!error.isEmpty()) { /* was any error message at reading ? */ combineAbortJobs(); /* we cannot write out it in combineReceiveFinished */ KMessageBox::error(nullptr, error); /* because emit accept closes it in this function */ emit reject(); } else emit accept(); } void Combiner::combineAbortJobs() { if (statJob) statJob->kill(KJob::Quietly); if (combineReadJob) combineReadJob->kill(KJob::Quietly); if (combineWriteJob) combineWriteJob->kill(KJob::Quietly); statJob = combineReadJob = combineWriteJob = nullptr; } void Combiner::combineWritePercent(KJob *, unsigned long) { auto percent = (int)((((double)receivedSize / expectedSize) * 100.) + 0.5); setValue(percent); } diff --git a/krusader/Splitter/splitter.cpp b/krusader/Splitter/splitter.cpp index b0f76ddc..222bd9b3 100644 --- a/krusader/Splitter/splitter.cpp +++ b/krusader/Splitter/splitter.cpp @@ -1,296 +1,301 @@ /***************************************************************************** * Copyright (C) 2003 Csaba Karai * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "splitter.h" #include "../FileSystem/filesystem.h" // QtCore #include // QtWidgets #include #include #include #include #include #include +#include #include Splitter::Splitter(QWidget* parent, QUrl fileNameIn, QUrl destinationDirIn, bool overWriteIn) : QProgressDialog(parent, nullptr), fileName(std::move(fileNameIn)), destinationDir(std::move(destinationDirIn)), splitSize(0), permissions(0), overwrite(overWriteIn), fileNumber(0), outputFileRemaining(0), receivedSize(0), crcContext(new CRC32()), statJob(nullptr), splitReadJob(nullptr), splitWriteJob(nullptr) { setMaximum(100); setAutoClose(false); /* don't close or reset the dialog automatically */ setAutoReset(false); setLabelText("Krusader::Splitter"); setWindowModality(Qt::WindowModal); } Splitter::~Splitter() { splitAbortJobs(); delete crcContext; } void Splitter::split(KIO::filesize_t splitSizeIn) { Q_ASSERT(!splitReadJob); Q_ASSERT(!splitWriteJob); Q_ASSERT(!fileNumber); Q_ASSERT(!receivedSize); Q_ASSERT(!outputFileRemaining); splitReadJob = splitWriteJob = nullptr; fileNumber = receivedSize = outputFileRemaining = 0; splitSize = splitSizeIn; KFileItem file(fileName); file.refresh(); //FIXME: works only for local files - use KIO::stat() instead permissions = file.permissions() | QFile::WriteUser; setWindowTitle(i18n("Krusader::Splitting...")); setLabelText(i18n("Splitting the file %1...", fileName.toDisplayString(QUrl::PreferLocalFile))); if (file.isDir()) { KMessageBox::error(nullptr, i18n("Cannot split a folder.")); return; } splitReadJob = KIO::get(fileName, KIO::NoReload, KIO::HideProgressInfo); connect(splitReadJob, &KIO::TransferJob::data, this, &Splitter::splitDataReceived); connect(splitReadJob, &KIO::TransferJob::result, this, &Splitter::splitReceiveFinished); connect(splitReadJob, SIGNAL(percent(KJob*,ulong)), this, SLOT(splitReceivePercent(KJob*,ulong))); exec(); } void Splitter::splitDataReceived(KIO::Job *, const QByteArray &byteArray) { Q_ASSERT(!transferArray.length()); // transfer buffer must be empty if (byteArray.size() == 0) return; crcContext->update((unsigned char *)byteArray.data(), byteArray.size()); receivedSize += byteArray.size(); if (!splitWriteJob) nextOutputFile(); transferArray = QByteArray(byteArray.data(), byteArray.length()); // suspend read job until transfer buffer is handed to the write job splitReadJob->suspend(); if (splitWriteJob) splitWriteJob->resume(); } void Splitter::splitReceiveFinished(KJob *job) { splitReadJob = nullptr; /* KIO automatically deletes the object after Finished signal */ if (splitWriteJob) splitWriteJob->resume(); // finish writing the output if (job->error()) { /* any error occurred? */ splitAbortJobs(); KMessageBox::error(nullptr, i18n("Error reading file %1: %2", fileName.toDisplayString(QUrl::PreferLocalFile), job->errorString())); emit reject(); return; } QString crcResult = QString("%1").arg(crcContext->result(), 0, 16).toUpper().trimmed() .rightJustified(8, '0'); splitInfoFileContent = QString("filename=%1\n").arg(fileName.fileName()) + QString("size=%1\n") .arg(KIO::number(receivedSize)) + QString("crc32=%1\n") .arg(crcResult); } void Splitter::splitReceivePercent(KJob *, unsigned long percent) { setValue(percent); } void Splitter::nextOutputFile() { Q_ASSERT(!outputFileRemaining); fileNumber++; outputFileRemaining = splitSize; QString index("%1"); /* making the split filename */ index = index.arg(fileNumber).rightJustified(3, '0'); QString outFileName = fileName.fileName() + '.' + index; writeURL = destinationDir; writeURL = writeURL.adjusted(QUrl::StripTrailingSlash); writeURL.setPath(writeURL.path() + '/' + (outFileName)); if (overwrite) openOutputFile(); else { +#if KIO_VERSION >= QT_VERSION_CHECK(5, 69, 0) + statJob = KIO::statDetails(writeURL, KIO::StatJob::DestinationSide, KIO::StatNoDetails, KIO::HideProgressInfo); +#else statJob = KIO::stat(writeURL, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); +#endif connect(statJob, &KIO::Job::result, this, &Splitter::statOutputFileResult); } } void Splitter::statOutputFileResult(KJob* job) { statJob = nullptr; if (job->error()) { if (job->error() == KIO::ERR_DOES_NOT_EXIST) openOutputFile(); else { dynamic_cast(job)->uiDelegate()->showErrorMessage(); emit reject(); } } else { // destination already exists KIO::RenameDialog dlg(this, i18n("File Already Exists"), QUrl(), writeURL, static_cast(KIO::M_MULTI | KIO::M_OVERWRITE | KIO::M_NORENAME)); switch (dlg.exec()) { case KIO::Result_Overwrite: openOutputFile(); break; case KIO::Result_OverwriteAll: overwrite = true; openOutputFile(); break; default: emit reject(); } } } void Splitter::openOutputFile() { // create write job splitWriteJob = KIO::put(writeURL, permissions, KIO::HideProgressInfo | KIO::Overwrite); connect(splitWriteJob, &KIO::TransferJob::dataReq, this, &Splitter::splitDataSend); connect(splitWriteJob, &KIO::TransferJob::result, this, &Splitter::splitSendFinished); } void Splitter::splitDataSend(KIO::Job *, QByteArray &byteArray) { KIO::filesize_t bufferLen = transferArray.size(); if (!outputFileRemaining) { // current output file needs to be closed ? byteArray = QByteArray(); // giving empty buffer which indicates closing } else if (bufferLen > outputFileRemaining) { // maximum length reached ? byteArray = QByteArray(transferArray.data(), outputFileRemaining); transferArray = QByteArray(transferArray.data() + outputFileRemaining, bufferLen - outputFileRemaining); outputFileRemaining = 0; } else { outputFileRemaining -= bufferLen; // write the whole buffer to the output file byteArray = transferArray; transferArray = QByteArray(); if (splitReadJob) { // suspend write job until transfer buffer is filled or the read job is finished splitWriteJob->suspend(); splitReadJob->resume(); } // else: write job continues until transfer buffer is empty } } void Splitter::splitSendFinished(KJob *job) { splitWriteJob = nullptr; /* KIO automatically deletes the object after Finished signal */ if (job->error()) { /* any error occurred? */ splitAbortJobs(); KMessageBox::error(nullptr, i18n("Error writing file %1: %2", writeURL.toDisplayString(QUrl::PreferLocalFile), job->errorString())); emit reject(); return; } if (transferArray.size()) /* any data remained in the transfer buffer? */ nextOutputFile(); else if (splitReadJob) splitReadJob->resume(); else { // read job is finished and transfer buffer is empty -> splitting is finished /* writing the split information file out */ writeURL = destinationDir; writeURL = writeURL.adjusted(QUrl::StripTrailingSlash); writeURL.setPath(writeURL.path() + '/' + (fileName.fileName() + ".crc")); splitWriteJob = KIO::put(writeURL, permissions, KIO::HideProgressInfo | KIO::Overwrite); connect(splitWriteJob, &KIO::TransferJob::dataReq, this, &Splitter::splitFileSend); connect(splitWriteJob, &KIO::TransferJob::result, this, &Splitter::splitFileFinished); } } void Splitter::splitAbortJobs() { if (statJob) statJob->kill(KJob::Quietly); if (splitReadJob) splitReadJob->kill(KJob::Quietly); if (splitWriteJob) splitWriteJob->kill(KJob::Quietly); splitReadJob = splitWriteJob = nullptr; } void Splitter::splitFileSend(KIO::Job *, QByteArray &byteArray) { byteArray = splitInfoFileContent.toLocal8Bit(); splitInfoFileContent = ""; } void Splitter::splitFileFinished(KJob *job) { splitWriteJob = nullptr; /* KIO automatically deletes the object after Finished signal */ if (job->error()) { /* any error occurred? */ KMessageBox::error(nullptr, i18n("Error writing file %1: %2", writeURL.toDisplayString(QUrl::PreferLocalFile), job->errorString())); emit reject(); return; } emit accept(); }