File indexing completed on 2024-04-28 05:47:34
0001 /* 0002 SPDX-FileCopyrightText: 2008 Harald Hvaal <haraldhv@stud.ntnu.no> 0003 SPDX-FileCopyrightText: 2009-2010 Raphael Kubo da Costa <rakuco@FreeBSD.org> 0004 0005 SPDX-License-Identifier: BSD-2-Clause 0006 */ 0007 0008 #include "batchextract.h" 0009 #include "ark_debug.h" 0010 #include "extractiondialog.h" 0011 #include "jobs.h" 0012 #include "queries.h" 0013 0014 #include <KIO/JobTracker> 0015 #include <KIO/JobUiDelegate> 0016 #include <KIO/JobUiDelegateFactory> 0017 #include <KIO/OpenUrlJob> 0018 #include <KLocalizedString> 0019 #include <KMessageBox> 0020 #include <KWidgetJobTracker> 0021 0022 #include <QDir> 0023 #include <QFileInfo> 0024 #include <QPointer> 0025 #include <QTimer> 0026 0027 BatchExtract::BatchExtract(QObject *parent) 0028 : KCompositeJob(parent) 0029 , m_autoSubfolder(false) 0030 , m_preservePaths(true) 0031 , m_openDestinationAfterExtraction(false) 0032 { 0033 setCapabilities(KJob::Killable); 0034 0035 connect(this, &KJob::result, this, &BatchExtract::showFailedFiles); 0036 } 0037 0038 BatchExtract::~BatchExtract() 0039 { 0040 } 0041 0042 void BatchExtract::addExtraction(const QUrl &url) 0043 { 0044 QString destination = destinationFolder(); 0045 0046 auto job = Kerfuffle::Archive::batchExtract(url.toLocalFile(), destination, autoSubfolder(), preservePaths()); 0047 0048 qCDebug(ARK) << QString(QStringLiteral("Registering job from archive %1, to %2, preservePaths %3")) 0049 .arg(url.toLocalFile(), destination, QString::number(preservePaths())); 0050 0051 addSubjob(job); 0052 0053 m_fileNames[job] = qMakePair(url.toLocalFile(), destination); 0054 0055 connect(job, &KJob::percentChanged, this, &BatchExtract::forwardProgress); 0056 0057 connect(job, &Kerfuffle::BatchExtractJob::userQuery, this, &BatchExtract::slotUserQuery); 0058 } 0059 0060 bool BatchExtract::doKill() 0061 { 0062 if (subjobs().isEmpty()) { 0063 return false; 0064 } 0065 0066 return subjobs().first()->kill(); 0067 } 0068 0069 void BatchExtract::slotUserQuery(Kerfuffle::Query *query) 0070 { 0071 query->execute(); 0072 } 0073 0074 bool BatchExtract::autoSubfolder() const 0075 { 0076 return m_autoSubfolder; 0077 } 0078 0079 void BatchExtract::setAutoSubfolder(bool value) 0080 { 0081 m_autoSubfolder = value; 0082 } 0083 0084 void BatchExtract::start() 0085 { 0086 QTimer::singleShot(0, this, &BatchExtract::slotStartJob); 0087 } 0088 0089 void BatchExtract::slotStartJob() 0090 { 0091 if (m_inputs.isEmpty()) { 0092 emitResult(); 0093 return; 0094 } 0095 0096 for (const auto &url : std::as_const(m_inputs)) { 0097 addExtraction(url); 0098 } 0099 0100 KIO::getJobTracker()->registerJob(this); 0101 0102 Q_EMIT description(this, 0103 i18n("Extracting Files"), 0104 qMakePair(i18n("Source archive"), m_fileNames.value(subjobs().at(0)).first), 0105 qMakePair(i18n("Destination"), m_fileNames.value(subjobs().at(0)).second)); 0106 0107 m_initialJobCount = subjobs().size(); 0108 0109 qCDebug(ARK) << "Starting first job"; 0110 0111 subjobs().at(0)->start(); 0112 } 0113 0114 void BatchExtract::showFailedFiles() 0115 { 0116 if (!m_failedFiles.isEmpty()) { 0117 KMessageBox::informationList(nullptr, i18n("The following files could not be extracted:"), m_failedFiles); 0118 } 0119 } 0120 0121 void BatchExtract::slotResult(KJob *job) 0122 { 0123 if (job->error()) { 0124 qCDebug(ARK) << "There was en error:" << job->error() << ", errorText:" << job->errorString(); 0125 0126 setErrorText(job->errorString()); 0127 setError(job->error()); 0128 0129 removeSubjob(job); 0130 0131 if (job->error() != KJob::KilledJobError) { 0132 const QString filename = m_fileNames.value(job).first; 0133 if (hasSubjobs()) { 0134 KMessageBox::error( 0135 nullptr, 0136 job->errorString().isEmpty() 0137 ? xi18nc("@info", "There was an error while extracting <filename>%1</filename>. Any further archive will not be extracted.", filename) 0138 : xi18nc("@info", 0139 "There was an error while extracting <filename>%1</filename>:<nl/><message>%2</message><nl/>Any further archive will not be " 0140 "extracted.", 0141 filename, 0142 job->errorString())); 0143 } else { 0144 KMessageBox::error(nullptr, 0145 job->errorString().isEmpty() 0146 ? xi18nc("@info", "There was an error while extracting <filename>%1</filename>.", filename) 0147 : xi18nc("@info", 0148 "There was an error while extracting <filename>%1</filename>:<nl/><message>%2</message>", 0149 filename, 0150 job->errorString())); 0151 } 0152 } 0153 0154 emitResult(); 0155 return; 0156 } 0157 0158 removeSubjob(job); 0159 0160 if (!hasSubjobs()) { 0161 if (openDestinationAfterExtraction()) { 0162 const QString path = QDir::cleanPath(destinationFolder()); 0163 const QUrl destination(QUrl::fromLocalFile(path)); 0164 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(destination, QStringLiteral("inode/directory")); 0165 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr)); 0166 job->start(); 0167 } 0168 0169 qCDebug(ARK) << "Finished, emitting the result"; 0170 emitResult(); 0171 } else { 0172 qCDebug(ARK) << "Starting the next job"; 0173 Q_EMIT description(this, 0174 i18n("Extracting Files"), 0175 qMakePair(i18n("Source archive"), m_fileNames.value(subjobs().at(0)).first), 0176 qMakePair(i18n("Destination"), m_fileNames.value(subjobs().at(0)).second)); 0177 subjobs().at(0)->start(); 0178 } 0179 } 0180 0181 void BatchExtract::forwardProgress(KJob *job, unsigned long percent) 0182 { 0183 Q_UNUSED(job) 0184 auto jobPart = static_cast<ulong>(100 / m_initialJobCount); 0185 auto remainingJobs = static_cast<ulong>(m_initialJobCount - subjobs().size()); 0186 setPercent(jobPart * remainingJobs + percent / static_cast<ulong>(m_initialJobCount)); 0187 } 0188 0189 void BatchExtract::addInput(const QUrl &url) 0190 { 0191 qCDebug(ARK) << "Adding archive" << url.toLocalFile(); 0192 0193 if (!QFileInfo::exists(url.toLocalFile())) { 0194 m_failedFiles.append(url.fileName()); 0195 return; 0196 } 0197 0198 m_inputs.append(url); 0199 } 0200 0201 bool BatchExtract::openDestinationAfterExtraction() const 0202 { 0203 return m_openDestinationAfterExtraction; 0204 } 0205 0206 bool BatchExtract::preservePaths() const 0207 { 0208 return m_preservePaths; 0209 } 0210 0211 QString BatchExtract::destinationFolder() const 0212 { 0213 if (m_destinationFolder.isEmpty()) { 0214 return QDir::currentPath(); 0215 } else { 0216 return m_destinationFolder; 0217 } 0218 } 0219 0220 void BatchExtract::setDestinationFolder(const QString &folder) 0221 { 0222 if (QFileInfo(folder).isDir()) { 0223 m_destinationFolder = folder; 0224 // Magic property that tells the job tracker the job's destination 0225 setProperty("destUrl", QUrl::fromLocalFile(folder).toString()); 0226 } 0227 } 0228 0229 void BatchExtract::setOpenDestinationAfterExtraction(bool value) 0230 { 0231 m_openDestinationAfterExtraction = value; 0232 } 0233 0234 void BatchExtract::setPreservePaths(bool value) 0235 { 0236 m_preservePaths = value; 0237 } 0238 0239 bool BatchExtract::showExtractDialog() 0240 { 0241 QPointer<Kerfuffle::ExtractionDialog> dialog = new Kerfuffle::ExtractionDialog; 0242 0243 if (m_inputs.size() > 1) { 0244 dialog.data()->batchModeOption(); 0245 } 0246 0247 dialog.data()->setModal(true); 0248 dialog.data()->setAutoSubfolder(autoSubfolder()); 0249 dialog.data()->setCurrentUrl(QUrl::fromUserInput(destinationFolder(), QDir::currentPath(), QUrl::AssumeLocalFile)); 0250 dialog.data()->setPreservePaths(preservePaths()); 0251 0252 // Only one archive, we need a LoadJob to get the single-folder and subfolder properties. 0253 // TODO: find a better way (e.g. let the dialog handle everything), otherwise we list 0254 // the archive twice (once here and once in the following BatchExtractJob). 0255 Kerfuffle::LoadJob *loadJob = nullptr; 0256 if (m_inputs.size() == 1) { 0257 loadJob = Kerfuffle::Archive::load(m_inputs.at(0).toLocalFile(), this); 0258 // We need to access the job after result has been emitted, if the user rejects the dialog. 0259 loadJob->setAutoDelete(false); 0260 0261 connect(loadJob, &KJob::result, this, [=](KJob *job) { 0262 if (job->error()) { 0263 return; 0264 } 0265 0266 auto archive = qobject_cast<Kerfuffle::LoadJob *>(job)->archive(); 0267 dialog->setExtractToSubfolder(archive->hasMultipleTopLevelEntries()); 0268 dialog->setSubfolder(archive->subfolderName()); 0269 }); 0270 0271 connect(loadJob, &KJob::result, dialog.data(), &Kerfuffle::ExtractionDialog::setReadyGui); 0272 dialog->setBusyGui(); 0273 // NOTE: we exploit the dialog->exec() below to run this job. 0274 loadJob->start(); 0275 } 0276 0277 QUrl destinationDirectory; 0278 if (dialog.data()->exec()) { 0279 destinationDirectory = dialog.data()->destinationDirectory(); 0280 if (destinationDirectory.isLocalFile()) { 0281 setAutoSubfolder(false && dialog.data()->autoSubfolders()); 0282 setDestinationFolder(destinationDirectory.toLocalFile()); 0283 setOpenDestinationAfterExtraction(dialog.data()->openDestinationAfterExtraction()); 0284 setPreservePaths(dialog.data()->preservePaths()); 0285 0286 delete dialog.data(); 0287 return true; 0288 } 0289 } 0290 0291 // we get here if no directory was chosen, or the chosen directory is not local 0292 if (loadJob) { 0293 loadJob->kill(); 0294 loadJob->deleteLater(); 0295 } 0296 0297 if (!destinationDirectory.isEmpty() && !destinationDirectory.isLocalFile()) { 0298 KMessageBox::error(nullptr, 0299 xi18nc("@info", 0300 "The archive could not be extracted to <filename>%1</filename> because Ark can only extract to local destinations.", 0301 destinationDirectory.toDisplayString())); 0302 } 0303 0304 delete dialog.data(); 0305 0306 return false; 0307 } 0308 0309 #include "moc_batchextract.cpp"