File indexing completed on 2024-05-05 05:50:39
0001 /* 0002 SPDX-FileCopyrightText: 2007 Henrique Pinto <henrique.pinto@kdemail.net> 0003 SPDX-FileCopyrightText: 2008 Harald Hvaal <haraldhv@stud.ntnu.no> 0004 SPDX-FileCopyrightText: 2009-2011 Raphael Kubo da Costa <rakuco@FreeBSD.org> 0005 SPDX-FileCopyrightText: 2016 Vladyslav Batyrenko <mvlabat@gmail.com> 0006 0007 SPDX-License-Identifier: BSD-2-Clause 0008 */ 0009 0010 #include "archive_kerfuffle.h" 0011 #include "archiveinterface.h" 0012 #include "ark_debug.h" 0013 #include "jobs.h" 0014 #include "mimetypes.h" 0015 #include "pluginmanager.h" 0016 0017 #include <KPluginFactory> 0018 0019 #include <QMimeDatabase> 0020 #include <QRegularExpression> 0021 0022 namespace Kerfuffle 0023 { 0024 Archive *Archive::create(const QString &fileName, QObject *parent) 0025 { 0026 return create(fileName, QString(), parent); 0027 } 0028 0029 Archive *Archive::create(const QString &fileName, const QString &fixedMimeType, QObject *parent) 0030 { 0031 qCDebug(ARK) << "Going to create archive" << fileName; 0032 0033 PluginManager pluginManager; 0034 const QMimeType mimeType = fixedMimeType.isEmpty() ? determineMimeType(fileName) : QMimeDatabase().mimeTypeForName(fixedMimeType); 0035 0036 qCDebug(ARK) << "Looking for plugins that handle the mimetype: " << mimeType.name(); 0037 QVector<Plugin *> offers = pluginManager.preferredPluginsFor(mimeType); 0038 if (offers.isEmpty()) { 0039 if (fixedMimeType.isEmpty()) { 0040 const QMimeType extensionMimeType = determineMimeType(fileName, PreferExtensionMime); 0041 offers = pluginManager.preferredPluginsFor(extensionMimeType); 0042 } 0043 if (offers.isEmpty()) { 0044 qCCritical(ARK) << "Could not find a plugin to handle" << fileName; 0045 return new Archive(NoPlugin, parent); 0046 } 0047 } 0048 0049 Archive *archive = nullptr; 0050 for (Plugin *plugin : std::as_const(offers)) { 0051 archive = create(fileName, plugin, parent); 0052 // Use the first valid plugin, according to the priority sorting. 0053 if (archive->isValid()) { 0054 return archive; 0055 } 0056 } 0057 0058 qCCritical(ARK) << "Failed to find a usable plugin for" << fileName; 0059 return archive; 0060 } 0061 0062 Archive *Archive::create(const QString &fileName, Plugin *plugin, QObject *parent) 0063 { 0064 Q_ASSERT(plugin); 0065 0066 qCDebug(ARK) << "Checking plugin" << plugin->metaData().pluginId(); 0067 0068 const QVariantList args = {QVariant(QFileInfo(fileName).absoluteFilePath()), QVariant().fromValue(plugin->metaData())}; 0069 ReadOnlyArchiveInterface *iface = KPluginFactory::instantiatePlugin<ReadOnlyArchiveInterface>(plugin->metaData(), nullptr, args).plugin; 0070 if (!iface) { 0071 qCWarning(ARK) << "Could not create plugin instance" << plugin->metaData().pluginId(); 0072 return new Archive(FailedPlugin, parent); 0073 } 0074 0075 if (!plugin->isValid()) { 0076 qCDebug(ARK) << "Cannot use plugin" << plugin->metaData().pluginId() << "- check whether" << plugin->readOnlyExecutables() << "are installed."; 0077 return new Archive(FailedPlugin, parent); 0078 } 0079 0080 qCDebug(ARK) << "Successfully loaded plugin" << plugin->metaData().pluginId(); 0081 return new Archive(iface, !plugin->isReadWrite(), parent); 0082 } 0083 0084 BatchExtractJob *Archive::batchExtract(const QString &fileName, const QString &destination, bool autoSubfolder, bool preservePaths, QObject *parent) 0085 { 0086 auto loadJob = load(fileName, parent); 0087 auto batchJob = new BatchExtractJob(loadJob, destination, autoSubfolder, preservePaths); 0088 0089 return batchJob; 0090 } 0091 0092 CreateJob * 0093 Archive::create(const QString &fileName, const QString &mimeType, const QVector<Archive::Entry *> &entries, const CompressionOptions &options, QObject *parent) 0094 { 0095 auto archive = create(fileName, mimeType, parent); 0096 auto createJob = new CreateJob(archive, entries, options); 0097 0098 return createJob; 0099 } 0100 0101 Archive *Archive::createEmpty(const QString &fileName, const QString &mimeType, QObject *parent) 0102 { 0103 auto archive = create(fileName, mimeType, parent); 0104 Q_ASSERT(archive->isEmpty()); 0105 0106 return archive; 0107 } 0108 0109 LoadJob *Archive::load(const QString &fileName, QObject *parent) 0110 { 0111 return load(fileName, QString(), parent); 0112 } 0113 0114 LoadJob *Archive::load(const QString &fileName, const QString &mimeType, QObject *parent) 0115 { 0116 auto archive = create(fileName, mimeType, parent); 0117 auto loadJob = new LoadJob(archive); 0118 0119 return loadJob; 0120 } 0121 0122 LoadJob *Archive::load(const QString &fileName, Plugin *plugin, QObject *parent) 0123 { 0124 auto archive = create(fileName, plugin, parent); 0125 auto loadJob = new LoadJob(archive); 0126 0127 return loadJob; 0128 } 0129 0130 Archive::Archive(ArchiveError errorCode, QObject *parent) 0131 : QObject(parent) 0132 , m_iface(nullptr) 0133 , m_error(errorCode) 0134 { 0135 qCDebug(ARK) << "Created archive instance with error"; 0136 } 0137 0138 Archive::Archive(ReadOnlyArchiveInterface *archiveInterface, bool isReadOnly, QObject *parent) 0139 : QObject(parent) 0140 , m_iface(archiveInterface) 0141 , m_isReadOnly(isReadOnly) 0142 , m_isSingleFolder(false) 0143 , m_isMultiVolume(false) 0144 , m_extractedFilesSize(0) 0145 , m_error(NoError) 0146 , m_encryptionType(Unencrypted) 0147 { 0148 qCDebug(ARK) << "Created archive instance"; 0149 0150 Q_ASSERT(m_iface); 0151 m_iface->setParent(this); 0152 0153 connect(m_iface, &ReadOnlyArchiveInterface::compressionMethodFound, this, &Archive::onCompressionMethodFound); 0154 connect(m_iface, &ReadOnlyArchiveInterface::encryptionMethodFound, this, &Archive::onEncryptionMethodFound); 0155 0156 m_userMetaData = std::make_optional<MetadataBackup>(fileName()); 0157 } 0158 0159 void Archive::onCompressionMethodFound(const QString &method) 0160 { 0161 QStringList methods = property("compressionMethods").toStringList(); 0162 0163 if (!methods.contains(method) && method != QLatin1String("Store")) { 0164 methods.append(method); 0165 } 0166 methods.sort(); 0167 0168 setProperty("compressionMethods", methods); 0169 } 0170 0171 void Archive::onEncryptionMethodFound(const QString &method) 0172 { 0173 QStringList methods = property("encryptionMethods").toStringList(); 0174 0175 if (!methods.contains(method)) { 0176 methods.append(method); 0177 } 0178 methods.sort(); 0179 0180 setProperty("encryptionMethods", methods); 0181 } 0182 0183 Archive::~Archive() 0184 { 0185 } 0186 0187 QString Archive::completeBaseName() const 0188 { 0189 const QString suffix = QFileInfo(fileName()).suffix(); 0190 QString base = QFileInfo(fileName()).completeBaseName(); 0191 0192 // Special case for compressed tar archives. 0193 if (base.right(4).toUpper() == QLatin1String(".TAR")) { 0194 base.chop(4); 0195 0196 // Multi-volume 7z's are named name.7z.001. 0197 } else if (base.right(3).toUpper() == QLatin1String(".7Z")) { 0198 base.chop(3); 0199 0200 // Multi-volume zip's are named name.zip.001. 0201 } else if (base.right(4).toUpper() == QLatin1String(".ZIP")) { 0202 base.chop(4); 0203 0204 // For multivolume rar's we want to remove the ".partNNN" suffix. 0205 } else if (suffix.toUpper() == QLatin1String("RAR")) { 0206 base.remove(QRegularExpression(QStringLiteral("\\.part[0-9]{1,3}$"))); 0207 } 0208 0209 return base; 0210 } 0211 0212 QString Archive::fileName() const 0213 { 0214 return isValid() ? m_iface->filename() : QString(); 0215 } 0216 0217 QString Archive::comment() const 0218 { 0219 return isValid() ? m_iface->comment() : QString(); 0220 } 0221 0222 CommentJob *Archive::addComment(const QString &comment) 0223 { 0224 if (!isValid()) { 0225 return nullptr; 0226 } 0227 0228 qCDebug(ARK) << "Going to add comment:" << comment; 0229 Q_ASSERT(!isReadOnly()); 0230 CommentJob *job = new CommentJob(comment, static_cast<ReadWriteArchiveInterface *>(m_iface)); 0231 return job; 0232 } 0233 0234 TestJob *Archive::testArchive() 0235 { 0236 if (!isValid()) { 0237 return nullptr; 0238 } 0239 0240 qCDebug(ARK) << "Going to test archive"; 0241 0242 TestJob *job = new TestJob(m_iface); 0243 return job; 0244 } 0245 0246 QMimeType Archive::mimeType() 0247 { 0248 if (!isValid()) { 0249 return QMimeType(); 0250 } 0251 0252 if (!m_mimeType.isValid()) { 0253 m_mimeType = determineMimeType(fileName()); 0254 } 0255 0256 return m_mimeType; 0257 } 0258 0259 bool Archive::isEmpty() const 0260 { 0261 return (numberOfEntries() == 0); 0262 } 0263 0264 bool Archive::isReadOnly() const 0265 { 0266 return isValid() ? (m_iface->isReadOnly() || m_isReadOnly || (isMultiVolume() && (numberOfEntries() > 0))) : false; 0267 } 0268 0269 bool Archive::isSingleFile() const 0270 { 0271 // If the only entry is a folder, isSingleFolder() is true. 0272 return numberOfEntries() == 1 && !isSingleFolder(); 0273 } 0274 0275 bool Archive::isSingleFolder() const 0276 { 0277 if (!isValid()) { 0278 return false; 0279 } 0280 0281 return m_isSingleFolder; 0282 } 0283 0284 bool Archive::hasComment() const 0285 { 0286 return isValid() ? !comment().isEmpty() : false; 0287 } 0288 0289 bool Archive::isMultiVolume() const 0290 { 0291 if (!isValid()) { 0292 return false; 0293 } 0294 0295 return m_iface->isMultiVolume(); 0296 } 0297 0298 void Archive::setMultiVolume(bool value) 0299 { 0300 m_iface->setMultiVolume(value); 0301 } 0302 0303 int Archive::numberOfVolumes() const 0304 { 0305 return m_iface->numberOfVolumes(); 0306 } 0307 0308 Archive::EncryptionType Archive::encryptionType() const 0309 { 0310 if (!isValid()) { 0311 return Unencrypted; 0312 } 0313 0314 return m_encryptionType; 0315 } 0316 0317 QString Archive::password() const 0318 { 0319 return m_iface->password(); 0320 } 0321 0322 uint Archive::numberOfEntries() const 0323 { 0324 if (!isValid()) { 0325 return 0; 0326 } 0327 0328 return m_iface->numberOfEntries(); 0329 } 0330 0331 qulonglong Archive::unpackedSize() const 0332 { 0333 if (!isValid()) { 0334 return 0; 0335 } 0336 0337 return m_extractedFilesSize; 0338 } 0339 0340 qulonglong Archive::packedSize() const 0341 { 0342 return isValid() ? static_cast<qulonglong>(QFileInfo(fileName()).size()) : 0; 0343 } 0344 0345 QString Archive::subfolderName() const 0346 { 0347 if (!isValid()) { 0348 return QString(); 0349 } 0350 0351 return m_subfolderName; 0352 } 0353 0354 bool Archive::isValid() const 0355 { 0356 return m_iface && (m_error == NoError); 0357 } 0358 0359 ArchiveError Archive::error() const 0360 { 0361 return m_error; 0362 } 0363 0364 DeleteJob *Archive::deleteFiles(QVector<Archive::Entry *> &entries) 0365 { 0366 if (!isValid()) { 0367 return nullptr; 0368 } 0369 0370 qCDebug(ARK) << "Going to delete" << entries.size() << "entries"; 0371 0372 if (m_iface->isReadOnly()) { 0373 return nullptr; 0374 } 0375 0376 DeleteJob *newJob = new DeleteJob(entries, static_cast<ReadWriteArchiveInterface *>(m_iface)); 0377 connect(newJob, &DeleteJob::result, this, [this]() { 0378 restoreUserMetadata(); 0379 }); 0380 0381 return newJob; 0382 } 0383 0384 AddJob *Archive::addFiles(const QVector<Archive::Entry *> &files, const Archive::Entry *destination, const CompressionOptions &options) 0385 { 0386 if (!isValid()) { 0387 return nullptr; 0388 } 0389 0390 CompressionOptions newOptions = options; 0391 if (encryptionType() != Unencrypted) { 0392 newOptions.setEncryptedArchiveHint(true); 0393 } 0394 0395 qCDebug(ARK) << "Going to add files" << files << "with options" << newOptions; 0396 Q_ASSERT(!m_iface->isReadOnly()); 0397 0398 AddJob *newJob = new AddJob(files, destination, newOptions, static_cast<ReadWriteArchiveInterface *>(m_iface)); 0399 connect(newJob, &AddJob::result, this, &Archive::onAddFinished); 0400 return newJob; 0401 } 0402 0403 MoveJob *Archive::moveFiles(const QVector<Archive::Entry *> &files, Archive::Entry *destination, const CompressionOptions &options) 0404 { 0405 if (!isValid()) { 0406 return nullptr; 0407 } 0408 0409 CompressionOptions newOptions = options; 0410 if (encryptionType() != Unencrypted) { 0411 newOptions.setEncryptedArchiveHint(true); 0412 } 0413 0414 qCDebug(ARK) << "Going to move files" << files << "to destination" << destination << "with options" << newOptions; 0415 Q_ASSERT(!m_iface->isReadOnly()); 0416 0417 MoveJob *newJob = new MoveJob(files, destination, newOptions, static_cast<ReadWriteArchiveInterface *>(m_iface)); 0418 connect(newJob, &MoveJob::result, this, [this]() { 0419 restoreUserMetadata(); 0420 }); 0421 0422 return newJob; 0423 } 0424 0425 CopyJob *Archive::copyFiles(const QVector<Archive::Entry *> &files, Archive::Entry *destination, const CompressionOptions &options) 0426 { 0427 if (!isValid()) { 0428 return nullptr; 0429 } 0430 0431 CompressionOptions newOptions = options; 0432 if (encryptionType() != Unencrypted) { 0433 newOptions.setEncryptedArchiveHint(true); 0434 } 0435 0436 qCDebug(ARK) << "Going to copy files" << files << "with options" << newOptions; 0437 Q_ASSERT(!m_iface->isReadOnly()); 0438 0439 CopyJob *newJob = new CopyJob(files, destination, newOptions, static_cast<ReadWriteArchiveInterface *>(m_iface)); 0440 connect(newJob, &CopyJob::result, this, [this]() { 0441 restoreUserMetadata(); 0442 }); 0443 0444 return newJob; 0445 } 0446 0447 ExtractJob *Archive::extractFiles(const QVector<Archive::Entry *> &files, const QString &destinationDir, ExtractionOptions options) 0448 { 0449 if (!isValid()) { 0450 return nullptr; 0451 } 0452 0453 ExtractionOptions newOptions = options; 0454 if (encryptionType() != Unencrypted) { 0455 newOptions.setEncryptedArchiveHint(true); 0456 } 0457 0458 ExtractJob *newJob = new ExtractJob(files, destinationDir, newOptions, m_iface); 0459 return newJob; 0460 } 0461 0462 PreviewJob *Archive::preview(Archive::Entry *entry) 0463 { 0464 if (!isValid()) { 0465 return nullptr; 0466 } 0467 0468 PreviewJob *job = new PreviewJob(entry, (encryptionType() != Unencrypted), m_iface); 0469 return job; 0470 } 0471 0472 OpenJob *Archive::open(Archive::Entry *entry) 0473 { 0474 if (!isValid()) { 0475 return nullptr; 0476 } 0477 0478 OpenJob *job = new OpenJob(entry, (encryptionType() != Unencrypted), m_iface); 0479 return job; 0480 } 0481 0482 OpenWithJob *Archive::openWith(Archive::Entry *entry) 0483 { 0484 if (!isValid()) { 0485 return nullptr; 0486 } 0487 0488 OpenWithJob *job = new OpenWithJob(entry, (encryptionType() != Unencrypted), m_iface); 0489 return job; 0490 } 0491 0492 void Archive::encrypt(const QString &password, bool encryptHeader) 0493 { 0494 if (!isValid()) { 0495 return; 0496 } 0497 0498 m_iface->setPassword(password); 0499 m_iface->setHeaderEncryptionEnabled(encryptHeader); 0500 m_encryptionType = encryptHeader ? HeaderEncrypted : Encrypted; 0501 0502 restoreUserMetadata(); 0503 } 0504 0505 void Archive::onAddFinished(KJob *job) 0506 { 0507 // if the archive was previously a single folder archive and an add job 0508 // has successfully finished, then it is no longer a single folder 0509 // archive (for the current implementation, which does not allow adding 0510 // folders/files other places than the root. 0511 // TODO: handle the case of creating a new file and singlefolderarchive 0512 // then. 0513 if (m_isSingleFolder && !job->error()) { 0514 m_isSingleFolder = false; 0515 } 0516 0517 restoreUserMetadata(); 0518 } 0519 0520 void Archive::onUserQuery(Query *query) 0521 { 0522 query->execute(); 0523 } 0524 0525 QString Archive::multiVolumeName() const 0526 { 0527 return m_iface->multiVolumeName(); 0528 } 0529 0530 ReadOnlyArchiveInterface *Archive::interface() 0531 { 0532 return m_iface; 0533 } 0534 0535 bool Archive::hasMultipleTopLevelEntries() const 0536 { 0537 return !isSingleFile() && !isSingleFolder(); 0538 } 0539 0540 void Archive::restoreUserMetadata() 0541 { 0542 if (!m_userMetaData.has_value()) { 0543 return; 0544 } 0545 0546 m_userMetaData->restore(fileName()); 0547 } 0548 0549 } // namespace Kerfuffle 0550 0551 #include "moc_archive_kerfuffle.cpp"