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())
75 , m_abortOperation(false)
77 archive_read_disk_set_standard_lookup(m_archiveReadDisk.
data());
90 if (!(arch_reader.
data())) {
94 if (archive_read_support_compression_all(arch_reader.
data()) != ARCHIVE_OK) {
98 if (archive_read_support_format_all(arch_reader.
data()) != ARCHIVE_OK) {
103 emit
error(i18nc(
"@info",
"Could not open the archive <filename>%1</filename>, libarchive cannot handle it.",
108 m_cachedArchiveEntryCount = 0;
109 m_extractedFilesSize = 0;
111 struct archive_entry *aentry;
114 while (!m_abortOperation && (result = archive_read_next_header(arch_reader.
data(), &aentry)) == ARCHIVE_OK) {
115 if (!m_emitNoEntries) {
116 emitEntryFromArchiveEntry(aentry);
119 m_extractedFilesSize += (qlonglong)archive_entry_size(aentry);
121 m_cachedArchiveEntryCount++;
122 archive_read_data_skip(arch_reader.
data());
124 m_abortOperation =
false;
126 if (result != ARCHIVE_EOF) {
127 emit
error(i18nc(
"@info",
"The archive reading failed with the following error: <message>%1</message>",
132 return archive_read_close(arch_reader.
data()) == ARCHIVE_OK;
137 m_abortOperation =
true;
143 kDebug() <<
"Changing current directory to " << destinationDirectory;
146 const bool extractAll = files.isEmpty();
156 if (!(arch.
data())) {
160 if (archive_read_support_compression_all(arch.
data()) != ARCHIVE_OK) {
164 if (archive_read_support_format_all(arch.
data()) != ARCHIVE_OK) {
169 emit
error(i18nc(
"@info",
"Could not open the archive <filename>%1</filename>, libarchive cannot handle it.",
175 if (!(writer.
data())) {
179 archive_write_disk_set_options(writer.
data(), extractionFlags());
185 if (!m_cachedArchiveEntryCount) {
189 kDebug() <<
"For getting progress information, the archive will be listed once";
190 m_emitNoEntries =
true;
192 m_emitNoEntries =
false;
194 totalCount = m_cachedArchiveEntryCount;
196 totalCount = files.size();
199 m_currentExtractedFilesSize = 0;
201 bool overwriteAll =
false;
202 bool skipAll =
false;
203 struct archive_entry *
entry;
207 while (archive_read_next_header(arch.
data(), &
entry) == ARCHIVE_OK) {
208 fileBeingRenamed.
clear();
213 const bool entryIsDir = S_ISDIR(archive_entry_mode(entry));
216 if (!preservePaths && entryIsDir) {
217 archive_read_data_skip(arch.
data());
227 emit
error(i18n(
"This archive contains archive entries with absolute paths, which are not yet supported by ark."));
232 if (files.contains(entryName) || entryName == fileBeingRenamed || extractAll) {
242 if (!preservePaths) {
245 Q_ASSERT(!fileWithoutPath.isEmpty());
247 archive_entry_copy_pathname(entry,
QFile::encodeName(fileWithoutPath).constData());
252 }
else if (!rootNode.
isEmpty()) {
253 kDebug() <<
"Removing" << rootNode <<
"from" << entryName;
255 const QString truncatedFilename(entryName.remove(0, rootNode.
size()));
256 archive_entry_copy_pathname(entry,
QFile::encodeName(truncatedFilename).constData());
262 if (!entryIsDir && entryFI.
exists()) {
264 archive_read_data_skip(arch.
data());
265 archive_entry_clear(entry);
267 }
else if (!overwriteAll && !skipAll) {
273 archive_read_data_skip(arch.
data());
274 archive_entry_clear(entry);
277 archive_read_data_skip(arch.
data());
278 archive_entry_clear(entry);
281 archive_read_data_skip(arch.
data());
282 archive_entry_clear(entry);
287 fileBeingRenamed = newName;
297 if (entryIsDir && entryFI.
exists()) {
299 kDebug(1601) <<
"Warning, existing, but writable dir";
301 kDebug(1601) <<
"Warning, existing, but non-writable dir. skipping";
302 archive_entry_clear(entry);
303 archive_read_data_skip(arch.
data());
309 kDebug() <<
"Writing " << fileWithoutPath <<
" to " << archive_entry_pathname(entry);
310 if ((header_response = archive_write_header(writer.
data(),
entry)) == ARCHIVE_OK) {
313 copyData(arch.
data(), writer.
data(), (extractAll && m_extractedFilesSize));
314 }
else if (header_response == ARCHIVE_WARN) {
315 kDebug() <<
"Warning while writing " << entryName;
317 kDebug() <<
"Writing header failed with error code " << header_response
318 <<
"While attempting to write " << entryName;
324 if (!extractAll && m_cachedArchiveEntryCount) {
326 emit
progress(
float(entryNr) / totalCount);
328 archive_entry_clear(entry);
330 archive_read_data_skip(arch.
data());
334 return archive_read_close(arch.
data()) == ARCHIVE_OK;
343 if (!globalWorkDir.
isEmpty()) {
344 kDebug() <<
"GlobalWorkDir is set, changing dir to " << globalWorkDir;
345 m_workDir.
setPath(globalWorkDir);
349 m_writtenFiles.
clear();
352 if (!creatingNewFile) {
353 arch_reader.
reset(archive_read_new());
354 if (!(arch_reader.
data())) {
355 emit
error(i18n(
"The archive reader could not be initialized."));
359 if (archive_read_support_compression_all(arch_reader.
data()) != ARCHIVE_OK) {
363 if (archive_read_support_format_all(arch_reader.
data()) != ARCHIVE_OK) {
368 emit
error(i18n(
"The source file could not be read."));
374 if (!(arch_writer.
data())) {
375 emit
error(i18n(
"The archive writer could not be initialized."));
380 archive_write_set_format_pax_restricted(arch_writer.
data());
383 if (creatingNewFile) {
385 kDebug() <<
"Detected gzip compression for new file";
386 ret = archive_write_set_compression_gzip(arch_writer.
data());
388 kDebug() <<
"Detected bzip2 compression for new file";
389 ret = archive_write_set_compression_bzip2(arch_writer.
data());
390 #ifdef HAVE_LIBARCHIVE_XZ_SUPPORT
392 kDebug() <<
"Detected xz compression for new file";
393 ret = archive_write_set_compression_xz(arch_writer.
data());
395 #ifdef HAVE_LIBARCHIVE_LZMA_SUPPORT
397 kDebug() <<
"Detected lzma compression for new file";
398 ret = archive_write_set_compression_lzma(arch_writer.
data());
401 kDebug() <<
"Detected no compression for new file (pure tar)";
402 ret = archive_write_set_compression_none(arch_writer.
data());
404 kDebug() <<
"Falling back to gzip";
405 ret = archive_write_set_compression_gzip(arch_writer.
data());
408 if (ret != ARCHIVE_OK) {
409 emit
error(i18nc(
"@info",
"Setting the compression method failed with the following error: <message>%1</message>",
415 switch (archive_compression(arch_reader.
data())) {
416 case ARCHIVE_COMPRESSION_GZIP:
417 ret = archive_write_set_compression_gzip(arch_writer.
data());
419 case ARCHIVE_COMPRESSION_BZIP2:
420 ret = archive_write_set_compression_bzip2(arch_writer.
data());
422 #ifdef HAVE_LIBARCHIVE_XZ_SUPPORT
423 case ARCHIVE_COMPRESSION_XZ:
424 ret = archive_write_set_compression_xz(arch_writer.
data());
427 #ifdef HAVE_LIBARCHIVE_LZMA_SUPPORT
428 case ARCHIVE_COMPRESSION_LZMA:
429 ret = archive_write_set_compression_lzma(arch_writer.
data());
432 case ARCHIVE_COMPRESSION_NONE:
433 ret = archive_write_set_compression_none(arch_writer.
data());
436 emit
error(i18n(
"The compression type '%1' is not supported by Ark.",
QLatin1String(archive_compression_name(arch_reader.
data()))));
440 if (ret != ARCHIVE_OK) {
441 emit
error(i18nc(
"@info",
"Setting the compression method failed with the following error: <message>%1</message>",
QLatin1String(archive_error_string(arch_writer.
data()))));
447 if (ret != ARCHIVE_OK) {
448 emit
error(i18nc(
"@info",
"Opening the archive for writing failed with the following error: <message>%1</message>",
QLatin1String(archive_error_string(arch_writer.
data()))));
453 foreach(
const QString& selectedFile, files) {
456 success = writeFile(selectedFile, arch_writer.
data());
465 QDir::AllEntries | QDir::Readable |
466 QDir::Hidden | QDir::NoDotAndDotDot,
467 QDirIterator::Subdirectories);
477 success = writeFile(path +
489 struct archive_entry *
entry;
492 if (!creatingNewFile) {
494 while (archive_read_next_header(arch_reader.
data(), &
entry) == ARCHIVE_OK) {
496 archive_read_data_skip(arch_reader.
data());
497 kDebug() <<
"Entry already existing, will be refresh: ===> " << archive_entry_pathname(entry);
503 if ((header_response = archive_write_header(arch_writer.
data(),
entry)) == ARCHIVE_OK) {
506 copyData(arch_reader.
data(), arch_writer.
data(),
false);
508 kDebug() <<
"Writing header failed with error code " << header_response;
513 archive_entry_clear(entry);
532 if (!(arch_reader.
data())) {
533 emit
error(i18n(
"The archive reader could not be initialized."));
537 if (archive_read_support_compression_all(arch_reader.
data()) != ARCHIVE_OK) {
541 if (archive_read_support_format_all(arch_reader.
data()) != ARCHIVE_OK) {
546 emit
error(i18n(
"The source file could not be read."));
551 if (!(arch_writer.
data())) {
552 emit
error(i18n(
"The archive writer could not be initialized."));
557 archive_write_set_format_pax_restricted(arch_writer.
data());
560 switch (archive_compression(arch_reader.
data())) {
561 case ARCHIVE_COMPRESSION_GZIP:
562 ret = archive_write_set_compression_gzip(arch_writer.
data());
564 case ARCHIVE_COMPRESSION_BZIP2:
565 ret = archive_write_set_compression_bzip2(arch_writer.
data());
567 #ifdef HAVE_LIBARCHIVE_XZ_SUPPORT
568 case ARCHIVE_COMPRESSION_XZ:
569 ret = archive_write_set_compression_xz(arch_writer.
data());
572 #ifdef HAVE_LIBARCHIVE_LZMA_SUPPORT
573 case ARCHIVE_COMPRESSION_LZMA:
574 ret = archive_write_set_compression_lzma(arch_writer.
data());
577 case ARCHIVE_COMPRESSION_NONE:
578 ret = archive_write_set_compression_none(arch_writer.
data());
581 emit
error(i18n(
"The compression type '%1' is not supported by Ark.",
QLatin1String(archive_compression_name(arch_reader.
data()))));
585 if (ret != ARCHIVE_OK) {
586 emit
error(i18nc(
"@info",
"Setting the compression method failed with the following error: <message>%1</message>",
QLatin1String(archive_error_string(arch_writer.
data()))));
591 if (ret != ARCHIVE_OK) {
592 emit
error(i18nc(
"@info",
"Opening the archive for writing failed with the following error: <message>%1</message>",
QLatin1String(archive_error_string(arch_writer.
data()))));
596 struct archive_entry *
entry;
599 while (archive_read_next_header(arch_reader.
data(), &
entry) == ARCHIVE_OK) {
601 archive_read_data_skip(arch_reader.
data());
602 kDebug() <<
"Entry to be deleted, skipping"
603 << archive_entry_pathname(entry);
610 if ((header_response = archive_write_header(arch_writer.
data(),
entry)) == ARCHIVE_OK) {
613 copyData(arch_reader.
data(), arch_writer.
data(),
false);
615 kDebug() <<
"Writing header failed with error code " << header_response;
629 void LibArchiveInterface::emitEntryFromArchiveEntry(
struct archive_entry *aentry)
650 e[
Size] = (qlonglong)archive_entry_size(aentry);
651 e[
IsDirectory] = S_ISDIR(archive_entry_mode(aentry));
653 if (archive_entry_symlink(aentry)) {
662 int LibArchiveInterface::extractionFlags()
const
664 int result = ARCHIVE_EXTRACT_TIME;
665 result |= ARCHIVE_EXTRACT_SECURE_NODOTDOT;
681 void LibArchiveInterface::copyData(
const QString& filename,
struct archive *dest,
bool partialprogress)
685 QFile file(filename);
687 if (!file.open(QIODevice::ReadOnly)) {
691 readBytes = file.read(buff,
sizeof(buff));
692 while (readBytes > 0) {
694 archive_write_data(dest, buff, readBytes);
695 if (archive_errno(dest) != ARCHIVE_OK) {
696 kDebug() <<
"Error while writing..." << archive_error_string(dest) <<
"(error nb =" << archive_errno(dest) <<
')';
700 if (partialprogress) {
701 m_currentExtractedFilesSize += readBytes;
702 emit
progress(
float(m_currentExtractedFilesSize) / m_extractedFilesSize);
705 readBytes = file.read(buff,
sizeof(buff));
711 void LibArchiveInterface::copyData(
struct archive *source,
struct archive *dest,
bool partialprogress)
716 readBytes = archive_read_data(source, buff,
sizeof(buff));
717 while (readBytes > 0) {
719 archive_write_data(dest, buff, readBytes);
720 if (archive_errno(dest) != ARCHIVE_OK) {
721 kDebug() <<
"Error while extracting..." << archive_error_string(dest) <<
"(error nb =" << archive_errno(dest) <<
')';
725 if (partialprogress) {
726 m_currentExtractedFilesSize += readBytes;
727 emit
progress(
float(m_currentExtractedFilesSize) / m_extractedFilesSize);
730 readBytes = archive_read_data(source, buff,
sizeof(buff));
736 bool LibArchiveInterface::writeFile(
const QString& fileName,
struct archive* arch_writer)
756 struct archive_entry *
entry = archive_entry_new();
759 archive_read_disk_entry_from_file(m_archiveReadDisk.
data(),
entry, -1, &st);
761 kDebug() <<
"Writing new entry " << archive_entry_pathname(entry);
762 if ((header_response = archive_write_header(arch_writer, entry)) == ARCHIVE_OK) {
765 copyData(fileName, arch_writer,
false);
767 kDebug() <<
"Writing header failed with error code " << header_response;
768 kDebug() <<
"Error while writing..." << archive_error_string(arch_writer) <<
"(error nb =" << archive_errno(arch_writer) <<
')';
770 emit
error(i18nc(
"@info Error in a message box",
771 "Ark could not compress <filename>%1</filename>:<nl/>%2",
773 QLatin1String(archive_error_string(arch_writer))));
775 archive_entry_free(entry);
782 emitEntryFromArchiveEntry(entry);
784 archive_entry_free(entry);
791 #include "libarchivehandler.moc"
QString fromWCharArray(const wchar_t *string, int size)
The user the entry belongs to.
QString fromAscii(const char *str, int size)
QString & append(QChar ch)
QString relativeFilePath(const QString &fileName) const
QFileInfo fileInfo() const
void entryRemoved(const QString &path)
QString fromNativeSeparators(const QString &pathName)
The entry is a directory.
void push_back(const T &value)
bool list()
List archive contents.
bool responseOverwriteAll()
bool rename(const QString &newName)
void userQuery(Query *query)
bool contains(const QString &str, Qt::CaseSensitivity cs) const
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)
QDateTime fromTime_t(uint seconds)
QHash< int, QVariant > ArchiveEntry
QString fromUtf16(const ushort *unicode, int size)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
bool addFiles(const QStringList &files, const CompressionOptions &options)
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
bool setCurrent(const QString &path)
The timestamp for the current entry.
QString right(int n) const
const T value(const Key &key) const
LibArchiveInterface(QObject *parent, const QVariantList &args)
bool copyFiles(const QVariantList &files, const QString &destinationDirectory, ExtractionOptions options)
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.
void setPath(const QString &path)
QByteArray encodeName(const QString &fileName)
QString decodeName(const QByteArray &localFileName)