File indexing completed on 2024-05-05 17:57:24

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 "splitter.h"
0009 #include "../FileSystem/filesystem.h"
0010 
0011 // QtCore
0012 #include <QFileInfo>
0013 // QtWidgets
0014 #include <QLayout>
0015 
0016 #include <KI18n/KLocalizedString>
0017 #include <KIO/Job>
0018 #include <KIO/JobUiDelegate>
0019 #include <KIOCore/KFileItem>
0020 #include <KWidgetsAddons/KMessageBox>
0021 #include <kio_version.h>
0022 #include <utility>
0023 
0024 Splitter::Splitter(QWidget *parent, QUrl fileNameIn, QUrl destinationDirIn, bool overWriteIn)
0025     : QProgressDialog(parent, Qt::WindowFlags())
0026     , fileName(std::move(fileNameIn))
0027     , destinationDir(std::move(destinationDirIn))
0028     , splitSize(0)
0029     , permissions(0)
0030     , overwrite(overWriteIn)
0031     , fileNumber(0)
0032     , outputFileRemaining(0)
0033     , receivedSize(0)
0034     , crcContext(new CRC32())
0035     , statJob(nullptr)
0036     , splitReadJob(nullptr)
0037     , splitWriteJob(nullptr)
0038 
0039 {
0040     setMaximum(100);
0041     setAutoClose(false); /* don't close or reset the dialog automatically */
0042     setAutoReset(false);
0043     setLabelText("Krusader::Splitter");
0044     setWindowModality(Qt::WindowModal);
0045 }
0046 
0047 Splitter::~Splitter()
0048 {
0049     splitAbortJobs();
0050     delete crcContext;
0051 }
0052 
0053 void Splitter::split(KIO::filesize_t splitSizeIn)
0054 {
0055     Q_ASSERT(!splitReadJob);
0056     Q_ASSERT(!splitWriteJob);
0057     Q_ASSERT(!fileNumber);
0058     Q_ASSERT(!receivedSize);
0059     Q_ASSERT(!outputFileRemaining);
0060 
0061     splitReadJob = splitWriteJob = nullptr;
0062     fileNumber = 0;
0063     receivedSize = outputFileRemaining = 0;
0064 
0065     splitSize = splitSizeIn;
0066 
0067     KFileItem file(fileName);
0068     file.refresh(); // FIXME: works only for local files - use KIO::stat() instead
0069 
0070     permissions = file.permissions() | QFile::WriteUser;
0071 
0072     setWindowTitle(i18n("Krusader::Splitting..."));
0073     setLabelText(i18n("Splitting the file %1...", fileName.toDisplayString(QUrl::PreferLocalFile)));
0074 
0075     if (file.isDir()) {
0076         KMessageBox::error(nullptr, i18n("Cannot split a folder."));
0077         return;
0078     }
0079 
0080     splitReadJob = KIO::get(fileName, KIO::NoReload, KIO::HideProgressInfo);
0081 
0082     connect(splitReadJob, &KIO::TransferJob::data, this, &Splitter::splitDataReceived);
0083     connect(splitReadJob, &KIO::TransferJob::result, this, &Splitter::splitReceiveFinished);
0084     connect(splitReadJob, SIGNAL(percent(KJob *, ulong)), this, SLOT(splitReceivePercent(KJob *, ulong)));
0085 
0086     exec();
0087 }
0088 
0089 void Splitter::splitDataReceived(KIO::Job *, const QByteArray &byteArray)
0090 {
0091     Q_ASSERT(!transferArray.length()); // transfer buffer must be empty
0092 
0093     if (byteArray.size() == 0)
0094         return;
0095 
0096     crcContext->update(reinterpret_cast<unsigned char *>(const_cast<char *>(byteArray.data())), byteArray.size());
0097     receivedSize += byteArray.size();
0098 
0099     if (!splitWriteJob)
0100         nextOutputFile();
0101 
0102     transferArray = QByteArray(byteArray.data(), byteArray.length());
0103 
0104     // suspend read job until transfer buffer is handed to the write job
0105     splitReadJob->suspend();
0106 
0107     if (splitWriteJob)
0108         splitWriteJob->resume();
0109 }
0110 
0111 void Splitter::splitReceiveFinished(KJob *job)
0112 {
0113     splitReadJob = nullptr; /* KIO automatically deletes the object after Finished signal */
0114 
0115     if (splitWriteJob)
0116         splitWriteJob->resume(); // finish writing the output
0117 
0118     if (job->error()) { /* any error occurred? */
0119         splitAbortJobs();
0120         KMessageBox::error(nullptr, i18n("Error reading file %1: %2", fileName.toDisplayString(QUrl::PreferLocalFile), job->errorString()));
0121         reject();
0122         return;
0123     }
0124 
0125     QString crcResult = QString("%1").arg(crcContext->result(), 0, 16).toUpper().trimmed().rightJustified(8, '0');
0126 
0127     splitInfoFileContent =
0128         QString("filename=%1\n").arg(fileName.fileName()) + QString("size=%1\n").arg(KIO::number(receivedSize)) + QString("crc32=%1\n").arg(crcResult);
0129 }
0130 
0131 void Splitter::splitReceivePercent(KJob *, unsigned long percent)
0132 {
0133     setValue(static_cast<int>(percent));
0134 }
0135 
0136 void Splitter::nextOutputFile()
0137 {
0138     Q_ASSERT(!outputFileRemaining);
0139 
0140     fileNumber++;
0141 
0142     outputFileRemaining = splitSize;
0143 
0144     QString index("%1"); /* making the split filename */
0145     index = index.arg(fileNumber).rightJustified(3, '0');
0146     QString outFileName = fileName.fileName() + '.' + index;
0147 
0148     writeURL = destinationDir;
0149     writeURL = writeURL.adjusted(QUrl::StripTrailingSlash);
0150     writeURL.setPath(writeURL.path() + '/' + (outFileName));
0151 
0152     if (overwrite)
0153         openOutputFile();
0154     else {
0155 #if KIO_VERSION >= QT_VERSION_CHECK(5, 69, 0)
0156         statJob = KIO::statDetails(writeURL, KIO::StatJob::DestinationSide, KIO::StatNoDetails, KIO::HideProgressInfo);
0157 #else
0158         statJob = KIO::stat(writeURL, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo);
0159 #endif
0160         connect(statJob, &KIO::Job::result, this, &Splitter::statOutputFileResult);
0161     }
0162 }
0163 
0164 void Splitter::statOutputFileResult(KJob *job)
0165 {
0166     statJob = nullptr;
0167 
0168     if (job->error()) {
0169         if (job->error() == KIO::ERR_DOES_NOT_EXIST)
0170             openOutputFile();
0171         else {
0172             dynamic_cast<KIO::Job *>(job)->uiDelegate()->showErrorMessage();
0173             reject();
0174         }
0175     } else { // destination already exists
0176         KIO::RenameDialog dlg(this,
0177                               i18n("File Already Exists"),
0178                               QUrl(),
0179                               writeURL,
0180                               static_cast<KIO::RenameDialog_Options>(KIO::M_MULTI | KIO::M_OVERWRITE | KIO::M_NORENAME));
0181         switch (dlg.exec()) {
0182         case KIO::Result_Overwrite:
0183             openOutputFile();
0184             break;
0185         case KIO::Result_OverwriteAll:
0186             overwrite = true;
0187             openOutputFile();
0188             break;
0189         default:
0190             reject();
0191         }
0192     }
0193 }
0194 
0195 void Splitter::openOutputFile()
0196 {
0197     // create write job
0198     splitWriteJob = KIO::put(writeURL, permissions, KIO::HideProgressInfo | KIO::Overwrite);
0199     connect(splitWriteJob, &KIO::TransferJob::dataReq, this, &Splitter::splitDataSend);
0200     connect(splitWriteJob, &KIO::TransferJob::result, this, &Splitter::splitSendFinished);
0201 }
0202 
0203 void Splitter::splitDataSend(KIO::Job *, QByteArray &byteArray)
0204 {
0205     KIO::filesize_t bufferLen = transferArray.size();
0206 
0207     if (!outputFileRemaining) { // current output file needs to be closed ?
0208         byteArray = QByteArray(); // giving empty buffer which indicates closing
0209     } else if (bufferLen > outputFileRemaining) { // maximum length reached ?
0210         byteArray = QByteArray(transferArray.data(), static_cast<int>(outputFileRemaining));
0211         transferArray = QByteArray(transferArray.data() + outputFileRemaining, static_cast<int>(bufferLen - outputFileRemaining));
0212         outputFileRemaining = 0;
0213     } else {
0214         outputFileRemaining -= bufferLen; // write the whole buffer to the output file
0215 
0216         byteArray = transferArray;
0217         transferArray = QByteArray();
0218 
0219         if (splitReadJob) {
0220             // suspend write job until transfer buffer is filled or the read job is finished
0221             splitWriteJob->suspend();
0222             splitReadJob->resume();
0223         } // else: write job continues until transfer buffer is empty
0224     }
0225 }
0226 
0227 void Splitter::splitSendFinished(KJob *job)
0228 {
0229     splitWriteJob = nullptr; /* KIO automatically deletes the object after Finished signal */
0230 
0231     if (job->error()) { /* any error occurred? */
0232         splitAbortJobs();
0233         KMessageBox::error(nullptr, i18n("Error writing file %1: %2", writeURL.toDisplayString(QUrl::PreferLocalFile), job->errorString()));
0234         reject();
0235         return;
0236     }
0237 
0238     if (transferArray.size()) /* any data remained in the transfer buffer? */
0239         nextOutputFile();
0240     else if (splitReadJob)
0241         splitReadJob->resume();
0242     else { // read job is finished and transfer buffer is empty -> splitting is finished
0243         /* writing the split information file out */
0244         writeURL = destinationDir;
0245         writeURL = writeURL.adjusted(QUrl::StripTrailingSlash);
0246         writeURL.setPath(writeURL.path() + '/' + (fileName.fileName() + ".crc"));
0247         splitWriteJob = KIO::put(writeURL, permissions, KIO::HideProgressInfo | KIO::Overwrite);
0248         connect(splitWriteJob, &KIO::TransferJob::dataReq, this, &Splitter::splitFileSend);
0249         connect(splitWriteJob, &KIO::TransferJob::result, this, &Splitter::splitFileFinished);
0250     }
0251 }
0252 
0253 void Splitter::splitAbortJobs()
0254 {
0255     if (statJob)
0256         statJob->kill(KJob::Quietly);
0257     if (splitReadJob)
0258         splitReadJob->kill(KJob::Quietly);
0259     if (splitWriteJob)
0260         splitWriteJob->kill(KJob::Quietly);
0261 
0262     splitReadJob = splitWriteJob = nullptr;
0263 }
0264 
0265 void Splitter::splitFileSend(KIO::Job *, QByteArray &byteArray)
0266 {
0267     byteArray = splitInfoFileContent.toLocal8Bit();
0268     splitInfoFileContent = "";
0269 }
0270 
0271 void Splitter::splitFileFinished(KJob *job)
0272 {
0273     splitWriteJob = nullptr; /* KIO automatically deletes the object after Finished signal */
0274 
0275     if (job->error()) { /* any error occurred? */
0276         KMessageBox::error(nullptr, i18n("Error writing file %1: %2", writeURL.toDisplayString(QUrl::PreferLocalFile), job->errorString()));
0277         reject();
0278         return;
0279     }
0280 
0281     accept();
0282 }