35 #include <archive_entry.h>
43 #include <QDirIterator>
46 #include <QStringList>
48 struct LibArchiveInterface::ArchiveReadCustomDeleter
50 static inline void cleanup(
struct archive *a)
53 archive_read_finish(a);
58 struct LibArchiveInterface::ArchiveWriteCustomDeleter
60 static inline void cleanup(
struct archive *a)
63 archive_write_finish(a);
70 , m_cachedArchiveEntryCount(0)
71 , m_emitNoEntries(false)
72 , m_extractedFilesSize(0)
73 , m_workDir(QDir::current())
74 , m_archiveReadDisk(archive_read_disk_new())
76 archive_read_disk_set_standard_lookup(m_archiveReadDisk.data());
87 ArchiveRead arch_reader(archive_read_new());
89 if (!(arch_reader.data())) {
93 if (archive_read_support_compression_all(arch_reader.data()) != ARCHIVE_OK) {
97 if (archive_read_support_format_all(arch_reader.data()) != ARCHIVE_OK) {
101 if (archive_read_open_filename(arch_reader.data(), QFile::encodeName(
filename()), 10240) != ARCHIVE_OK) {
102 emit
error(i18nc(
"@info",
"Could not open the archive <filename>%1</filename>, libarchive cannot handle it.",
107 m_cachedArchiveEntryCount = 0;
108 m_extractedFilesSize = 0;
110 struct archive_entry *aentry;
113 while ((result = archive_read_next_header(arch_reader.data(), &aentry)) == ARCHIVE_OK) {
114 if (!m_emitNoEntries) {
115 emitEntryFromArchiveEntry(aentry);
118 m_extractedFilesSize += (qlonglong)archive_entry_size(aentry);
120 m_cachedArchiveEntryCount++;
121 archive_read_data_skip(arch_reader.data());
124 if (result != ARCHIVE_EOF) {
125 emit
error(i18nc(
"@info",
"The archive reading failed with the following error: <message>%1</message>",
126 QLatin1String( archive_error_string(arch_reader.data()))));
130 return archive_read_close(arch_reader.data()) == ARCHIVE_OK;
135 kDebug() <<
"Changing current directory to " << destinationDirectory;
136 QDir::setCurrent(destinationDirectory);
138 const bool extractAll = files.isEmpty();
139 const bool preservePaths = options.value(QLatin1String(
"PreservePaths" )).toBool();
141 QString rootNode = options.value(QLatin1String(
"RootNode"), QVariant()).toString();
142 if ((!rootNode.isEmpty()) && (!rootNode.endsWith(QLatin1Char(
'/')))) {
143 rootNode.append(QLatin1Char(
'/'));
146 ArchiveRead arch(archive_read_new());
148 if (!(arch.data())) {
152 if (archive_read_support_compression_all(arch.data()) != ARCHIVE_OK) {
156 if (archive_read_support_format_all(arch.data()) != ARCHIVE_OK) {
160 if (archive_read_open_filename(arch.data(), QFile::encodeName(
filename()), 10240) != ARCHIVE_OK) {
161 emit
error(i18nc(
"@info",
"Could not open the archive <filename>%1</filename>, libarchive cannot handle it.",
166 ArchiveWrite writer(archive_write_disk_new());
167 if (!(writer.data())) {
171 archive_write_disk_set_options(writer.data(), extractionFlags());
177 if (!m_cachedArchiveEntryCount) {
181 kDebug() <<
"For getting progress information, the archive will be listed once";
182 m_emitNoEntries =
true;
184 m_emitNoEntries =
false;
186 totalCount = m_cachedArchiveEntryCount;
188 totalCount = files.size();
191 m_currentExtractedFilesSize = 0;
193 bool overwriteAll =
false;
194 bool skipAll =
false;
195 struct archive_entry *
entry;
197 QString fileBeingRenamed;
199 while (archive_read_next_header(arch.data(), &
entry) == ARCHIVE_OK) {
200 fileBeingRenamed.clear();
205 const bool entryIsDir = S_ISDIR(archive_entry_mode(entry));
208 if (!preservePaths && entryIsDir) {
209 archive_read_data_skip(arch.data());
214 QString entryName = QDir::fromNativeSeparators(QFile::decodeName(archive_entry_pathname(entry)));
216 if (entryName.startsWith(QLatin1Char(
'/' ))) {
219 emit
error(i18n(
"This archive contains archive entries with absolute paths, which are not yet supported by ark."));
224 if (files.contains(entryName) || entryName == fileBeingRenamed || extractAll) {
227 QFileInfo entryFI(entryName);
230 const QString fileWithoutPath(entryFI.fileName());
234 if (!preservePaths) {
237 Q_ASSERT(!fileWithoutPath.isEmpty());
239 archive_entry_copy_pathname(entry, QFile::encodeName(fileWithoutPath).constData());
240 entryFI = QFileInfo(fileWithoutPath);
244 }
else if (!rootNode.isEmpty()) {
245 kDebug() <<
"Removing" << rootNode <<
"from" << entryName;
247 const QString truncatedFilename(entryName.remove(0, rootNode.size()));
248 archive_entry_copy_pathname(entry, QFile::encodeName(truncatedFilename).constData());
250 entryFI = QFileInfo(truncatedFilename);
254 if (!entryIsDir && entryFI.exists()) {
256 archive_read_data_skip(arch.data());
257 archive_entry_clear(entry);
259 }
else if (!overwriteAll && !skipAll) {
265 archive_read_data_skip(arch.data());
266 archive_entry_clear(entry);
269 archive_read_data_skip(arch.data());
270 archive_entry_clear(entry);
273 archive_read_data_skip(arch.data());
274 archive_entry_clear(entry);
279 fileBeingRenamed = newName;
280 archive_entry_copy_pathname(entry, QFile::encodeName(newName).constData());
289 if (entryIsDir && entryFI.exists()) {
290 if (entryFI.isWritable()) {
291 kDebug(1601) <<
"Warning, existing, but writable dir";
293 kDebug(1601) <<
"Warning, existing, but non-writable dir. skipping";
294 archive_entry_clear(entry);
295 archive_read_data_skip(arch.data());
301 kDebug() <<
"Writing " << fileWithoutPath <<
" to " << archive_entry_pathname(entry);
302 if ((header_response = archive_write_header(writer.data(),
entry)) == ARCHIVE_OK) {
305 copyData(arch.data(), writer.data(), (extractAll && m_extractedFilesSize));
306 }
else if (header_response == ARCHIVE_WARN) {
307 kDebug() <<
"Warning while writing " << entryName;
309 kDebug() <<
"Writing header failed with error code " << header_response
310 <<
"While attempting to write " << entryName;
316 if (!extractAll && m_cachedArchiveEntryCount) {
318 emit
progress(
float(entryNr) / totalCount);
320 archive_entry_clear(entry);
322 archive_read_data_skip(arch.data());
326 return archive_read_close(arch.data()) == ARCHIVE_OK;
331 const bool creatingNewFile = !QFileInfo(
filename()).exists();
332 const QString tempFilename =
filename() + QLatin1String(
".arkWriting" );
333 const QString globalWorkDir = options.value(QLatin1String(
"GlobalWorkDir" )).toString();
335 if (!globalWorkDir.isEmpty()) {
336 kDebug() <<
"GlobalWorkDir is set, changing dir to " << globalWorkDir;
337 m_workDir.setPath(globalWorkDir);
338 QDir::setCurrent(globalWorkDir);
341 m_writtenFiles.clear();
343 ArchiveRead arch_reader;
344 if (!creatingNewFile) {
345 arch_reader.reset(archive_read_new());
346 if (!(arch_reader.data())) {
347 emit
error(i18n(
"The archive reader could not be initialized."));
351 if (archive_read_support_compression_all(arch_reader.data()) != ARCHIVE_OK) {
355 if (archive_read_support_format_all(arch_reader.data()) != ARCHIVE_OK) {
359 if (archive_read_open_filename(arch_reader.data(), QFile::encodeName(
filename()), 10240) != ARCHIVE_OK) {
360 emit
error(i18n(
"The source file could not be read."));
365 ArchiveWrite arch_writer(archive_write_new());
366 if (!(arch_writer.data())) {
367 emit
error(i18n(
"The archive writer could not be initialized."));
372 archive_write_set_format_pax_restricted(arch_writer.data());
375 if (creatingNewFile) {
376 if (
filename().right(2).toUpper() == QLatin1String(
"GZ" )) {
377 kDebug() <<
"Detected gzip compression for new file";
378 ret = archive_write_set_compression_gzip(arch_writer.data());
379 }
else if (
filename().right(3).toUpper() == QLatin1String(
"BZ2" )) {
380 kDebug() <<
"Detected bzip2 compression for new file";
381 ret = archive_write_set_compression_bzip2(arch_writer.data());
382 #ifdef HAVE_LIBARCHIVE_XZ_SUPPORT
383 }
else if (
filename().right(2).toUpper() == QLatin1String(
"XZ" )) {
384 kDebug() <<
"Detected xz compression for new file";
385 ret = archive_write_set_compression_xz(arch_writer.data());
387 #ifdef HAVE_LIBARCHIVE_LZMA_SUPPORT
388 }
else if (
filename().right(4).toUpper() == QLatin1String(
"LZMA" )) {
389 kDebug() <<
"Detected lzma compression for new file";
390 ret = archive_write_set_compression_lzma(arch_writer.data());
392 }
else if (
filename().right(3).toUpper() == QLatin1String(
"TAR" )) {
393 kDebug() <<
"Detected no compression for new file (pure tar)";
394 ret = archive_write_set_compression_none(arch_writer.data());
396 kDebug() <<
"Falling back to gzip";
397 ret = archive_write_set_compression_gzip(arch_writer.data());
400 if (ret != ARCHIVE_OK) {
401 emit
error(i18nc(
"@info",
"Setting the compression method failed with the following error: <message>%1</message>",
402 QLatin1String(archive_error_string(arch_writer.data()))));
407 switch (archive_compression(arch_reader.data())) {
408 case ARCHIVE_COMPRESSION_GZIP:
409 ret = archive_write_set_compression_gzip(arch_writer.data());
411 case ARCHIVE_COMPRESSION_BZIP2:
412 ret = archive_write_set_compression_bzip2(arch_writer.data());
414 #ifdef HAVE_LIBARCHIVE_XZ_SUPPORT
415 case ARCHIVE_COMPRESSION_XZ:
416 ret = archive_write_set_compression_xz(arch_writer.data());
419 #ifdef HAVE_LIBARCHIVE_LZMA_SUPPORT
420 case ARCHIVE_COMPRESSION_LZMA:
421 ret = archive_write_set_compression_lzma(arch_writer.data());
424 case ARCHIVE_COMPRESSION_NONE:
425 ret = archive_write_set_compression_none(arch_writer.data());
428 emit
error(i18n(
"The compression type '%1' is not supported by Ark.", QLatin1String(archive_compression_name(arch_reader.data()))));
432 if (ret != ARCHIVE_OK) {
433 emit
error(i18nc(
"@info",
"Setting the compression method failed with the following error: <message>%1</message>", QLatin1String(archive_error_string(arch_writer.data()))));
438 ret = archive_write_open_filename(arch_writer.data(), QFile::encodeName(tempFilename));
439 if (ret != ARCHIVE_OK) {
440 emit
error(i18nc(
"@info",
"Opening the archive for writing failed with the following error: <message>%1</message>", QLatin1String(archive_error_string(arch_writer.data()))));
445 foreach(
const QString& selectedFile, files) {
448 success = writeFile(selectedFile, arch_writer.data());
451 QFile::remove(tempFilename);
455 if (QFileInfo(selectedFile).isDir()) {
456 QDirIterator it(selectedFile,
457 QDir::AllEntries | QDir::Readable |
458 QDir::Hidden | QDir::NoDotAndDotDot,
459 QDirIterator::Subdirectories);
461 while (it.hasNext()) {
462 const QString path = it.next();
464 if ((it.fileName() == QLatin1String(
"..")) ||
465 (it.fileName() == QLatin1String(
"."))) {
469 success = writeFile(path +
470 (it.fileInfo().isDir() ? QLatin1String(
"/" ) : QLatin1String(
"" )),
474 QFile::remove(tempFilename);
481 struct archive_entry *
entry;
484 if (!creatingNewFile) {
486 while (archive_read_next_header(arch_reader.data(), &
entry) == ARCHIVE_OK) {
487 if (m_writtenFiles.contains(QFile::decodeName(archive_entry_pathname(entry)))) {
488 archive_read_data_skip(arch_reader.data());
489 kDebug() <<
"Entry already existing, will be refresh: ===> " << archive_entry_pathname(entry);
495 if ((header_response = archive_write_header(arch_writer.data(),
entry)) == ARCHIVE_OK) {
498 copyData(arch_reader.data(), arch_writer.data(),
false);
500 kDebug() <<
"Writing header failed with error code " << header_response;
501 QFile::remove(tempFilename);
505 archive_entry_clear(entry);
514 QFile::rename(tempFilename,
filename());
521 const QString tempFilename =
filename() + QLatin1String(
".arkWriting" );
523 ArchiveRead arch_reader(archive_read_new());
524 if (!(arch_reader.data())) {
525 emit
error(i18n(
"The archive reader could not be initialized."));
529 if (archive_read_support_compression_all(arch_reader.data()) != ARCHIVE_OK) {
533 if (archive_read_support_format_all(arch_reader.data()) != ARCHIVE_OK) {
537 if (archive_read_open_filename(arch_reader.data(), QFile::encodeName(
filename()), 10240) != ARCHIVE_OK) {
538 emit
error(i18n(
"The source file could not be read."));
542 ArchiveWrite arch_writer(archive_write_new());
543 if (!(arch_writer.data())) {
544 emit
error(i18n(
"The archive writer could not be initialized."));
549 archive_write_set_format_pax_restricted(arch_writer.data());
552 switch (archive_compression(arch_reader.data())) {
553 case ARCHIVE_COMPRESSION_GZIP:
554 ret = archive_write_set_compression_gzip(arch_writer.data());
556 case ARCHIVE_COMPRESSION_BZIP2:
557 ret = archive_write_set_compression_bzip2(arch_writer.data());
559 #ifdef HAVE_LIBARCHIVE_XZ_SUPPORT
560 case ARCHIVE_COMPRESSION_XZ:
561 ret = archive_write_set_compression_xz(arch_writer.data());
564 #ifdef HAVE_LIBARCHIVE_LZMA_SUPPORT
565 case ARCHIVE_COMPRESSION_LZMA:
566 ret = archive_write_set_compression_lzma(arch_writer.data());
569 case ARCHIVE_COMPRESSION_NONE:
570 ret = archive_write_set_compression_none(arch_writer.data());
573 emit
error(i18n(
"The compression type '%1' is not supported by Ark.", QLatin1String(archive_compression_name(arch_reader.data()))));
577 if (ret != ARCHIVE_OK) {
578 emit
error(i18nc(
"@info",
"Setting the compression method failed with the following error: <message>%1</message>", QLatin1String(archive_error_string(arch_writer.data()))));
582 ret = archive_write_open_filename(arch_writer.data(), QFile::encodeName(tempFilename));
583 if (ret != ARCHIVE_OK) {
584 emit
error(i18nc(
"@info",
"Opening the archive for writing failed with the following error: <message>%1</message>", QLatin1String(archive_error_string(arch_writer.data()))));
588 struct archive_entry *
entry;
591 while (archive_read_next_header(arch_reader.data(), &
entry) == ARCHIVE_OK) {
592 if (files.contains(QFile::decodeName(archive_entry_pathname(entry)))) {
593 archive_read_data_skip(arch_reader.data());
594 kDebug() <<
"Entry to be deleted, skipping"
595 << archive_entry_pathname(entry);
596 emit
entryRemoved(QFile::decodeName(archive_entry_pathname(entry)));
602 if ((header_response = archive_write_header(arch_writer.data(),
entry)) == ARCHIVE_OK) {
605 copyData(arch_reader.data(), arch_writer.data(),
false);
607 kDebug() <<
"Writing header failed with error code " << header_response;
616 QFile::rename(tempFilename,
filename());
621 void LibArchiveInterface::emitEntryFromArchiveEntry(
struct archive_entry *aentry)
626 e[
FileName] = QDir::fromNativeSeparators(QString::fromUtf16((ushort*)archive_entry_pathname_w(aentry)));
628 e[
FileName] = QDir::fromNativeSeparators(QString::fromWCharArray(archive_entry_pathname_w(aentry)));
632 const QString owner = QString::fromAscii(archive_entry_uname(aentry));
633 if (!owner.isEmpty()) {
637 const QString group = QString::fromAscii(archive_entry_gname(aentry));
638 if (!group.isEmpty()) {
642 e[
Size] = (qlonglong)archive_entry_size(aentry);
643 e[
IsDirectory] = S_ISDIR(archive_entry_mode(aentry));
645 if (archive_entry_symlink(aentry)) {
646 e[
Link] = QLatin1String( archive_entry_symlink(aentry) );
649 e[
Timestamp] = QDateTime::fromTime_t(archive_entry_mtime(aentry));
654 int LibArchiveInterface::extractionFlags()
const
656 int result = ARCHIVE_EXTRACT_TIME;
657 result |= ARCHIVE_EXTRACT_SECURE_NODOTDOT;
673 void LibArchiveInterface::copyData(
const QString& filename,
struct archive *dest,
bool partialprogress)
677 QFile file(filename);
679 if (!file.open(QIODevice::ReadOnly)) {
683 readBytes = file.read(buff,
sizeof(buff));
684 while (readBytes > 0) {
686 archive_write_data(dest, buff, readBytes);
687 if (archive_errno(dest) != ARCHIVE_OK) {
688 kDebug() <<
"Error while writing..." << archive_error_string(dest) <<
"(error nb =" << archive_errno(dest) <<
')';
692 if (partialprogress) {
693 m_currentExtractedFilesSize += readBytes;
694 emit
progress(
float(m_currentExtractedFilesSize) / m_extractedFilesSize);
697 readBytes = file.read(buff,
sizeof(buff));
703 void LibArchiveInterface::copyData(
struct archive *source,
struct archive *dest,
bool partialprogress)
708 readBytes = archive_read_data(source, buff,
sizeof(buff));
709 while (readBytes > 0) {
711 archive_write_data(dest, buff, readBytes);
712 if (archive_errno(dest) != ARCHIVE_OK) {
713 kDebug() <<
"Error while extracting..." << archive_error_string(dest) <<
"(error nb =" << archive_errno(dest) <<
')';
717 if (partialprogress) {
718 m_currentExtractedFilesSize += readBytes;
719 emit
progress(
float(m_currentExtractedFilesSize) / m_extractedFilesSize);
722 readBytes = archive_read_data(source, buff,
sizeof(buff));
728 bool LibArchiveInterface::writeFile(
const QString& fileName,
struct archive* arch_writer)
732 const bool trailingSlash = fileName.endsWith(QLatin1Char(
'/' ));
738 const QString relativeName = m_workDir.relativeFilePath(fileName) + (trailingSlash ? QLatin1String(
"/" ) : QLatin1String(
"" ));
746 KDE_lstat(QFile::encodeName(fileName).constData(), &st);
748 struct archive_entry *
entry = archive_entry_new();
749 archive_entry_set_pathname(entry, QFile::encodeName(relativeName).constData());
750 archive_entry_copy_sourcepath(entry, QFile::encodeName(fileName).constData());
751 archive_read_disk_entry_from_file(m_archiveReadDisk.data(),
entry, -1, &st);
753 kDebug() <<
"Writing new entry " << archive_entry_pathname(entry);
754 if ((header_response = archive_write_header(arch_writer, entry)) == ARCHIVE_OK) {
757 copyData(fileName, arch_writer,
false);
759 kDebug() <<
"Writing header failed with error code " << header_response;
760 kDebug() <<
"Error while writing..." << archive_error_string(arch_writer) <<
"(error nb =" << archive_errno(arch_writer) <<
')';
762 emit
error(i18nc(
"@info Error in a message box",
763 "Ark could not compress <filename>%1</filename>:<nl/>%2",
765 QLatin1String(archive_error_string(arch_writer))));
767 archive_entry_free(entry);
772 m_writtenFiles.push_back(relativeName);
774 emitEntryFromArchiveEntry(entry);
776 archive_entry_free(entry);
783 #include "libarchivehandler.moc"
The user the entry belongs to.
void entryRemoved(const QString &path)
The entry is a directory.
bool list()
List archive contents.
bool responseOverwriteAll()
void userQuery(Query *query)
QString filename() const
Returns the filename of the archive currently being handled.
bool deleteFiles(const QVariantList &files)
The entry is a symbolic link.
void waitForResponse()
Will block until the response have been set.
void entry(const ArchiveEntry &archiveEntry)
QHash< int, QVariant > ArchiveEntry
bool addFiles(const QStringList &files, const CompressionOptions &options)
QHash< QString, QVariant > CompressionOptions
These are the extra options for doing the compression.
The timestamp for the current entry.
LibArchiveInterface(QObject *parent, const QVariantList &args)
bool copyFiles(const QVariantList &files, const QString &destinationDirectory, ExtractionOptions options)
QHash< QString, QVariant > ExtractionOptions
void error(const QString &message, const QString &details=QString())
The entry's original size.
void progress(double progress)
#define KERFUFFLE_EXPORT_PLUGIN(p)
The user group the entry belongs to.
The entry's ID for Ark's internal manipulation.