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 }