From 7ae87e89f7f4a1c74c26ce9cc74a04fc6060df33 Mon Sep 17 00:00:00 2001 From: Ivailo Monev Date: Tue, 16 Mar 2021 23:00:33 +0200 Subject: [PATCH] karchivemanager: import Signed-off-by: Ivailo Monev --- karchivemanager/CMakeLists.txt | 27 + karchivemanager/karchiveapp.cpp | 186 ++++ karchivemanager/karchiveapp.hpp | 52 + karchivemanager/karchiveapp.ui | 127 +++ karchivemanager/karchivemanager.cpp | 1361 +++++++++++++++++++++++ karchivemanager/karchivemanager.desktop | 10 + karchivemanager/karchivemanager.hpp | 226 ++++ karchivemanager/main.cpp | 56 + karchivemanager/strmode.c | 143 +++ 9 files changed, 2188 insertions(+) create mode 100644 karchivemanager/CMakeLists.txt create mode 100644 karchivemanager/karchiveapp.cpp create mode 100644 karchivemanager/karchiveapp.hpp create mode 100644 karchivemanager/karchiveapp.ui create mode 100644 karchivemanager/karchivemanager.cpp create mode 100644 karchivemanager/karchivemanager.desktop create mode 100644 karchivemanager/karchivemanager.hpp create mode 100644 karchivemanager/main.cpp create mode 100644 karchivemanager/strmode.c diff --git a/karchivemanager/CMakeLists.txt b/karchivemanager/CMakeLists.txt new file mode 100644 index 00000000..2027591f --- /dev/null +++ b/karchivemanager/CMakeLists.txt @@ -0,0 +1,27 @@ +project(karchivemanager) + +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) + find_package(KDE4 4.19.0 REQUIRED) + include(KDE4Defaults) + include_directories(${KDE4_INCLUDES}) + add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) +endif() + +set(karchivemanager_sources + strmode.c + karchiveapp.cpp + karchivemanager.cpp + main.cpp +) + +add_executable(karchivemanager ${karchivemanager_sources}) +target_link_libraries(karchivemanager + ${KDE4_KDEUI_LIBS} + ${KDE4_KFILE_LIBS} + z + bz2 + archive +) + +install(TARGETS karchivemanager DESTINATION ${KDE4_BIN_INSTALL_DIR}) +install(PROGRAMS karchivemanager.desktop DESTINATION ${KDE4_XDG_APPS_INSTALL_DIR}) diff --git a/karchivemanager/karchiveapp.cpp b/karchivemanager/karchiveapp.cpp new file mode 100644 index 00000000..d04bd5f8 --- /dev/null +++ b/karchivemanager/karchiveapp.cpp @@ -0,0 +1,186 @@ +/* This file is part of KArchiveManager + Copyright (C) 2018 Ivailo Monev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "karchivemanager.hpp" +#include "karchiveapp.hpp" +#include "ui_karchiveapp.h" + +class KArchiveAppPrivate { + + public: + Ui_KArchiveAppWindow ui; + + KArchiveModel m_model; + KArchiveManager *m_archive; +}; + +KArchiveApp::KArchiveApp() + : d(new KArchiveAppPrivate()) { + d->ui.setupUi(this); + show(); + + d->ui.archiveView->setModel(&d->m_model); + + connect(d->ui.actionOpen, SIGNAL(triggered()), this, SLOT(slotOpenAction())); + d->ui.actionOpen->setShortcut(QKeySequence::Open); + connect(d->ui.actionQuit, SIGNAL(triggered()), this, SLOT(slotQuitAction())); + d->ui.actionQuit->setShortcut(QKeySequence::Quit); + + connect(d->ui.actionAdd, SIGNAL(triggered()), this, SLOT(slotAddAction())); + connect(d->ui.actionRemove, SIGNAL(triggered()), this, SLOT(slotRemoveAction())); + connect(d->ui.actionExtract, SIGNAL(triggered()), this, SLOT(slotExtractAction())); + + connect(d->ui.archiveView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SLOT(slotSelectionChanged(const QItemSelection&, const QItemSelection&))); + connect(&d->m_model, SIGNAL(loadStarted()), this, SLOT(slotLoadStarted())); + connect(&d->m_model, SIGNAL(loadFinished()), this, SLOT(slotLoadFinished())); +} + +KArchiveApp::~KArchiveApp() { + if (d->m_archive) { + delete d->m_archive; + } + delete d; +} + +void KArchiveApp::changePath(const QString path) { + if (d->m_archive) { + delete d->m_archive; + } + + d->m_archive = new KArchiveManager(path); + d->m_model.loadArchive(d->m_archive); + + statusBar()->showMessage(path); + + const bool iswritable = d->m_archive->writable(); + d->ui.actionAdd->setEnabled(iswritable); + d->ui.actionRemove->setEnabled(iswritable); + d->ui.actionExtract->setEnabled(iswritable); +} + +void KArchiveApp::slotOpenAction() { + // TODO: MIMEs + const QString path = KFileDialog::getOpenFileName(KUrl(), QString(), this, i18n("Archive path")); + if (!path.isEmpty()) { + changePath(path); + } +} + +void KArchiveApp::slotQuitAction() { + qApp->quit(); +} + +void KArchiveApp::slotAddAction() { + QFileDialog opendialog(this, windowTitle()); + opendialog.setFileMode(QFileDialog::ExistingFiles); + opendialog.exec(); + const QStringList selected = opendialog.selectedFiles(); + if (!opendialog.result() || selected.isEmpty()) { + return; + } + + kDebug() << "adding" << selected; + const QByteArray stripdir = opendialog.directory().path().toUtf8(); + QString destdir; + foreach (const QModelIndex &item, d->ui.archiveView->selectionModel()->selectedIndexes()) { + destdir = d->m_model.dir(item); + } + d->m_archive->add(selected, stripdir + "/", destdir.toUtf8() + "/"); + + kDebug() << "reloading archive"; + d->m_model.loadArchive(d->m_archive); +} + +void KArchiveApp::slotRemoveAction() { + QStringList selected; + foreach (const QModelIndex &item, d->ui.archiveView->selectionModel()->selectedIndexes()) { + selected += d->m_model.paths(item); + } + + QMessageBox::StandardButton answer = QMessageBox::question(this, windowTitle(), + QApplication::tr("Are you sure you want to delete:\n\n%1?").arg(selected.join("\n")), + QMessageBox::No | QMessageBox::Yes); + if (answer != QMessageBox::Yes) { + return; + } + + kDebug() << "removing" << selected; + d->m_archive->remove(selected); + + kDebug() << "reloading archive"; + d->m_model.loadArchive(d->m_archive); +} + +void KArchiveApp::slotExtractAction() { + QStringList selected; + foreach (const QModelIndex &item, d->ui.archiveView->selectionModel()->selectedIndexes()) { + selected += d->m_model.paths(item); + } + + const QString destination = QFileDialog::getExistingDirectory(this, windowTitle()); + if (destination.isEmpty()) { + return; + } + + kDebug() << "extracting" << selected << "to" << destination; + d->m_archive->extract(selected, destination, true); +} + +void KArchiveApp::slotSelectionChanged(const QItemSelection ¤t, const QItemSelection &previous) { + Q_UNUSED(previous); + d->ui.menuAction->setEnabled(true); + + if (current.indexes().isEmpty()) { + d->ui.menuAction->setEnabled(false); + } +} + +void KArchiveApp::slotLoadStarted() { + d->ui.menuAction->setEnabled(false); + d->ui.archiveView->setEnabled(false); + d->ui.progressBar->setRange(0, 0); + d->ui.progressBar->setVisible(true); + + QHeaderView* header = d->ui.archiveView->header(); + if (header) { + header->setVisible(false); + } +} + +void KArchiveApp::slotLoadFinished() { + d->ui.archiveView->setEnabled(true); + d->ui.progressBar->setRange(0, 1); + d->ui.progressBar->setVisible(false); + + QHeaderView* header = d->ui.archiveView->header(); + if (header && header->count() > 0) { + header->setVisible(true); + header->setResizeMode(0, QHeaderView::Stretch); + } + + d->m_model.sort(1, Qt::AscendingOrder); +} diff --git a/karchivemanager/karchiveapp.hpp b/karchivemanager/karchiveapp.hpp new file mode 100644 index 00000000..b99c7b8b --- /dev/null +++ b/karchivemanager/karchiveapp.hpp @@ -0,0 +1,52 @@ +/* This file is part of KArchiveManager + Copyright (C) 2018 Ivailo Monev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KARCHIVEAPP_H +#define KARCHIVEAPP_H + +#include +#include + +class KArchiveAppPrivate; + +class KArchiveApp : public QMainWindow { + Q_OBJECT + + public: + KArchiveApp(); + ~KArchiveApp(); + + void changePath(const QString path); + + public Q_SLOTS: + void slotOpenAction(); + void slotQuitAction(); + void slotAddAction(); + void slotRemoveAction(); + void slotExtractAction(); + + void slotSelectionChanged(const QItemSelection ¤t, const QItemSelection &previous); + + void slotLoadStarted(); + void slotLoadFinished(); + + private: + KArchiveAppPrivate *d; +}; + +#endif // KARCHIVEAPP_H diff --git a/karchivemanager/karchiveapp.ui b/karchivemanager/karchiveapp.ui new file mode 100644 index 00000000..19c01a41 --- /dev/null +++ b/karchivemanager/karchiveapp.ui @@ -0,0 +1,127 @@ + + + KArchiveAppWindow + + + + 0 + 0 + 499 + 426 + + + + Archive Manager + + + + .. + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectItems + + + true + + + false + + + + + + + 24 + + + + + + + + + 0 + 0 + 499 + 22 + + + + + File + + + + + + + + Action + + + + + + + + + + + + + .. + + + Open + + + + + + .. + + + Quit + + + + + + .. + + + Add + + + + + + .. + + + Remove + + + + + + .. + + + Extract + + + + + + diff --git a/karchivemanager/karchivemanager.cpp b/karchivemanager/karchivemanager.cpp new file mode 100644 index 00000000..2e59c12c --- /dev/null +++ b/karchivemanager/karchivemanager.cpp @@ -0,0 +1,1361 @@ +/* This file is part of KArchiveManager + Copyright (C) 2018 Ivailo Monev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "karchivemanager.hpp" + +#include +#include +#include +#include +#include +#include + +extern "C" { void strmode(mode_t mode, char *str); }; + +#define KARCHIVEMANAGER_BUFFSIZE 10240 + +static const QStringList s_readwrite = QStringList() +#if ARCHIVE_VERSION_NUMBER > 3000004 + << "application/x-lzop" +#endif +#if ARCHIVE_VERSION_NUMBER > 3001002 + << "application/x-lz4" +#endif + << "application/x-tar" + << "application/x-compressed-tar" + << "application/x-bzip" + << "application/x-gzip" + << "application/x-bzip-compressed-tar" + << "application/x-gzip-compressed-tar" + << "application/x-tarz" + << "application/x-xz" + << "application/x-xz-compressed-tar" + << "application/x-lzma-compressed-tar" + << "application/x-java-archive" + << "application/zip" + << "application/x-7z-compressed" + << "application/x-iso9660-image" + << "application/x-apple-diskimage" + << "application/x-cd-image" + << "application/x-raw-disk-image"; + +KArchiveInfo::KArchiveInfo() + : encrypted(false), + size(0), + gid(-1), + uid(-1), + mode(0), + type(KArchiveInfo::None) { +} + +KArchiveInfo::KArchiveInfo(const KArchiveInfo &info) + : encrypted(info.encrypted), + size(info.size), + gid(info.gid), + uid(info.uid), + mode(info.mode), + atime(info.atime), + ctime(info.ctime), + mtime(info.mtime), + hardlink(info.hardlink), + symlink(info.symlink), + pathname(info.pathname), + mimetype(info.mimetype), + type(info.type) { +} + +QString KArchiveInfo::fancyEncrypted() const { + if (encrypted) { + return QString("yes"); + } + return QString("No"); +} + +// it does not make very accurate estimations but is good enough +QString KArchiveInfo::fancySize() const { + QString result; + + const int64_t bsize = 1024; + const int64_t kbsize = 1024 * 1024; + const int64_t mbsize = 1024 * 1024 * 1024; + + if (size < bsize) { + result = QString::number(size) + " bytes"; + } else if (size < kbsize) { + result = QString::number(size / bsize) + " Kb"; + } else if (size < mbsize) { + result = QString::number(size / kbsize) + " Mb"; + } else { + result = QString::number(size / mbsize) + " Gb"; + } + + return result; +} + +QString KArchiveInfo::fancyMode() const { + if (mode == 0) { + return QString("---------"); + } + + char buffer[11]; + ::strmode(mode, buffer); + + return QString::fromLatin1(buffer); +} + +QString KArchiveInfo::fancyType() const { + switch (type) { + case KArchiveType::None: { + return "None"; + } + case KArchiveType::File: { + return "File"; + } + case KArchiveType::Directory: { + return "Directory"; + } + case KArchiveType::Link: { + return "Link"; + } + case KArchiveType::Character: { + return "Character"; + } + case KArchiveType::Block: { + return "Block"; + } + case KArchiveType::Fifo: { + return "Fifo"; + } + case KArchiveType::Socket: { + return "Socket"; + } + } + + Q_UNREACHABLE(); + return QString(); +} + +QIcon KArchiveInfo::fancyIcon() const { + switch (type) { + case KArchiveType::None: { + return QIcon::fromTheme("unknown"); + } + case KArchiveType::File: { + const KMimeType::Ptr mime = KMimeType::mimeType(mimetype); + if (mime) { + return QIcon::fromTheme(mime->iconName()); + } + return QIcon::fromTheme("unknown"); + } + case KArchiveType::Directory: { + return QIcon::fromTheme("folder"); + } + case KArchiveType::Link: { + return QIcon::fromTheme("emblem-symbolic-link"); + } + case KArchiveType::Character: + case KArchiveType::Block: { + return QIcon::fromTheme("drive-harddisk"); + } + case KArchiveType::Fifo: + case KArchiveType::Socket: { + return QIcon::fromTheme("media-memory"); + } + } + + Q_UNREACHABLE(); + return QIcon(); +} + +bool KArchiveInfo::isNull() const { + if (type == KArchiveType::None) { + return true; + } + return false; +} + +bool KArchiveInfo::operator==(const KArchiveInfo &info) const { + return pathname == info.pathname; +} + +KArchiveInfo& KArchiveInfo::operator=(const KArchiveInfo &info) { + encrypted = info.encrypted; + size = info.size; + gid = info.gid; + uid = info.uid; + mode = info.mode; + atime = info.atime; + ctime = info.ctime; + mtime = info.mtime; + hardlink = info.hardlink; + symlink = info.symlink; + pathname = info.pathname; + mimetype = info.mimetype; + type = info.type; + return *this; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const KArchiveInfo &info) +{ + d << "KArchiveInfo( encrypted:" << info.fancyEncrypted() + << ", size:" << info.fancySize() + << ", gid:" << info.gid + << ", uid:" << info.uid + << ", mode:" << info.fancyMode() + << ", atime:" << info.atime + << ", ctime:" << info.ctime + << ", mtime:" << info.mtime + << ", hardlink:" << info.hardlink + << ", symlink:" << info.symlink + << ", pathname:" << info.pathname + << ", mimetype:" << info.mimetype + << ", type:" << info.fancyType() + << ")"; + return d; +} +#endif + +const QDBusArgument &operator<<(QDBusArgument &argument, const KArchiveInfo &info) { + argument.beginStructure(); + argument << info.encrypted; + argument << qlonglong(info.size); + argument << qlonglong(info.gid); + argument << qlonglong(info.uid); + argument << info.mode; + argument << uint(info.atime); + argument << uint(info.ctime); + argument << uint(info.mtime); + argument << info.hardlink; + argument << info.symlink; + argument << info.pathname; + argument << info.mimetype; + argument << int(info.type); + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, KArchiveInfo &info) { + int typebuf; + uint atimebuff; + uint ctimebuff; + uint mtimebuff; + qlonglong sizebuff; + qlonglong gidbuff; + qlonglong uidbuff; + argument.beginStructure(); + argument >> info.encrypted; + argument >> sizebuff; + info.size = int64_t(sizebuff); + argument >> gidbuff; + info.gid = int64_t(gidbuff); + argument >> uidbuff; + info.uid = int64_t(uidbuff); + argument >> info.mode; + argument >> atimebuff; + info.atime = time_t(atimebuff); + argument >> ctimebuff; + info.ctime = time_t(ctimebuff); + argument >> mtimebuff; + info.mtime = time_t(mtimebuff); + argument >> info.hardlink; + argument >> info.symlink; + argument >> info.pathname; + argument >> info.mimetype; + argument >> typebuf; + info.type = KArchiveInfo::KArchiveType(typebuf); + argument.endStructure(); + + return argument; +} + +class KArchiveManagerPrivate { + + public: + QString m_path; + bool m_writable; + + static struct archive* openRead(const QByteArray &path); + static struct archive* openWrite(const QByteArray &path); + static struct archive* openDisk(const bool preserve); + static bool closeRead(struct archive*); + static bool closeWrite(struct archive*); + + static bool copyData(struct archive* aread, struct archive* awrite); + static bool copyData(struct archive* aread, QByteArray &awrite); + + static QByteArray getMime(struct archive* aread); + static KArchiveInfo::KArchiveType getType(const mode_t mode); +}; + +struct archive* KArchiveManagerPrivate::openRead(const QByteArray &path) { + struct archive* m_archive = archive_read_new(); + + if (m_archive) { + archive_read_support_filter_all(m_archive); + archive_read_support_format_all(m_archive); + + if (archive_read_open_filename(m_archive, path, KARCHIVEMANAGER_BUFFSIZE) != ARCHIVE_OK) { + kWarning() << "archive_read_open_filename" << archive_error_string(m_archive); + } + } + + return m_archive; +} + +struct archive* KArchiveManagerPrivate::openWrite(const QByteArray &path) { + struct archive* m_archive = archive_write_new(); + + if (m_archive) { + if (archive_write_set_format_filter_by_ext(m_archive, path) != ARCHIVE_OK) { + kWarning() << "archive_write_set_format_filter_by_ext" << archive_error_string(m_archive); + archive_write_add_filter_none(m_archive); + } + + archive_write_set_format_pax_restricted(m_archive); + + if (archive_write_open_filename(m_archive, path) != ARCHIVE_OK) { + kWarning() << "archive_write_open_filename" << archive_error_string(m_archive); + } + } + + return m_archive; +} + +struct archive* KArchiveManagerPrivate::openDisk(const bool preserve) { + struct archive* m_archive = archive_write_disk_new(); + + if (m_archive) { + int extractFlags = ARCHIVE_EXTRACT_TIME; + extractFlags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS | ARCHIVE_EXTRACT_SECURE_NODOTDOT; + if (preserve) { + extractFlags |= ARCHIVE_EXTRACT_PERM; + extractFlags |= ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_XATTR; + extractFlags |= ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_MAC_METADATA; + } + + archive_write_disk_set_options(m_archive, extractFlags); + archive_write_disk_set_standard_lookup(m_archive); + } + + return m_archive; +} + +bool KArchiveManagerPrivate::closeRead(struct archive* m_archive) { + if (m_archive) { + if (archive_read_close(m_archive) != ARCHIVE_OK) { + kWarning() << "archive_read_close" << archive_error_string(m_archive); + return false; + } + + if (archive_read_free(m_archive) != ARCHIVE_OK) { + kWarning() << "archive_read_free" << archive_error_string(m_archive); + return false; + } + } + + return true; +} + +bool KArchiveManagerPrivate::closeWrite(struct archive* m_archive) { + if (m_archive) { + if (archive_write_close(m_archive) != ARCHIVE_OK) { + kWarning() << "archive_write_close" << archive_error_string(m_archive); + return false; + } + + if (archive_write_free(m_archive) != ARCHIVE_OK) { + kWarning() << "archive_write_free" << archive_error_string(m_archive); + return false; + } + } + + return true; +} + +bool KArchiveManagerPrivate::copyData(struct archive* aread, struct archive* awrite) { + char buffer[KARCHIVEMANAGER_BUFFSIZE]; + ssize_t readsize = archive_read_data(aread, buffer, sizeof(buffer)); + while (readsize > 0) { + const int result = archive_errno(aread); + if (result != ARCHIVE_OK) { + kWarning() << "archive_read_data" << archive_error_string(aread); + return false; + } + + if (archive_write_data(awrite, buffer, readsize) != readsize) { + kWarning() << "archive_write_data" << archive_error_string(awrite); + return false; + } + + readsize = archive_read_data(aread, buffer, sizeof(buffer)); + } + + return true; +} + + +bool KArchiveManagerPrivate::copyData(struct archive* aread, QByteArray &awrite) { + char buffer[KARCHIVEMANAGER_BUFFSIZE]; + ssize_t readsize = archive_read_data(aread, buffer, sizeof(buffer)); + while (readsize > 0) { + const int result = archive_errno(aread); + if (result != ARCHIVE_OK) { + kWarning() << "archive_read_data" << archive_error_string(aread); + return false; + } + + awrite += buffer; + + readsize = archive_read_data(aread, buffer, sizeof(buffer)); + } + + return true; +} + +QByteArray KArchiveManagerPrivate::getMime(struct archive* aread) { + QByteArray buffer; + copyData(aread, buffer); + const KMimeType::Ptr mime = KMimeType::findByContent(buffer); + if (mime) { + // FIXME: first parent mime? + mime->defaultMimeType(); + } + return QByteArray("application/octet-stream"); +} + +KArchiveInfo::KArchiveType KArchiveManagerPrivate::getType(const mode_t mode) { + if (S_ISREG(mode)) { + return KArchiveInfo::KArchiveType::File; + } else if (S_ISDIR(mode)) { + return KArchiveInfo::KArchiveType::Directory; + } else if (S_ISLNK(mode)) { + return KArchiveInfo::KArchiveType::Link; + } else if (S_ISCHR(mode)) { + return KArchiveInfo::KArchiveType::Character; + } else if (S_ISBLK(mode)) { + return KArchiveInfo::KArchiveType::Block; + } else if (S_ISFIFO(mode)) { + return KArchiveInfo::KArchiveType::Fifo; + } else if (S_ISSOCK(mode)) { + return KArchiveInfo::KArchiveType::Socket; + } else { + return KArchiveInfo::KArchiveType::None; + } +} + +KArchiveManager::KArchiveManager(const QString &path) + : d(new KArchiveManagerPrivate()) { + qRegisterMetaType(); + qDBusRegisterMetaType(); + + d->m_path = path; + + if (path.isEmpty()) { + kWarning() << "empty path"; + return; + } + + if (QFile::exists(path) && !supported(path)) { + kWarning() << "unsupported" << path; + d->m_writable = false; + return; + } + + const KMimeType::Ptr mime = KMimeType::findByPath(path); + if (mime) { + foreach (const QString &parentmime, mime->allParentMimeTypes()) { + if (s_readwrite.contains(parentmime)) { + d->m_writable = true; + break; + } + } + } else { + d->m_writable = false; + } +} + +KArchiveManager::~KArchiveManager() { + if (d) { + delete d; + } +} + +bool KArchiveManager::add(const QStringList &paths, const QByteArray &strip, const QByteArray &destination) const { + bool result = false; + + if (d->m_path.isEmpty()) { + kWarning() << "no path is set"; + return result; + } else if (!d->m_writable) { + kWarning() << "path is not writable"; + return result; + } + + QFileInfo fileinfo(d->m_path); + QTemporaryFile tmpfile("XXXXXX." + fileinfo.completeSuffix()); + if (!tmpfile.open()) { + kWarning() << "could not open temporary file"; + return result; + } + + const QByteArray tmppath = tmpfile.fileName().toUtf8(); + struct archive* m_write = d->openWrite(tmppath); + if (!m_write) { + kWarning() << "could not open temporary archive" << tmppath; + return result; + } + + QStringList recursivepaths; + foreach (const QString &path, paths) { + if (QDir(path).exists()) { + QDirIterator iterator(path, QDir::AllEntries | QDir::System, QDirIterator::Subdirectories); + while (iterator.hasNext()) { + recursivepaths << QDir::cleanPath(iterator.next()); + } + } else { + recursivepaths << QDir::cleanPath(path); + } + } + recursivepaths.removeDuplicates(); + + bool replace = false; + if (QFile::exists(d->m_path)) { + + replace = true; + + struct archive* m_read = d->openRead(d->m_path.toUtf8()); + if (!m_read) { + kWarning() << "could not open archive" << d->m_path; + return result; + } + + struct archive_entry* entry = archive_entry_new(); + int ret = archive_read_next_header(m_read, &entry); + while (ret != ARCHIVE_EOF) { + if (ret < ARCHIVE_OK) { + kWarning() << "archive_read_next_header" << archive_error_string(m_read); + result = false; + break; + } + + const QByteArray pathname = archive_entry_pathname(entry); + if (recursivepaths.contains(strip + pathname)) { + kDebug() << "removing (update)" << pathname; + archive_read_data_skip(m_read); + ret = archive_read_next_header(m_read, &entry); + continue; + } + + if (archive_write_header(m_write, entry) != ARCHIVE_OK) { + kWarning() << "archive_write_header" << archive_error_string(m_write); + result = false; + break; + } + + if (!d->copyData(m_read, m_write)) { + result = false; + break; + } + + if (archive_write_finish_entry(m_write) != ARCHIVE_OK) { + kWarning() << "archive_write_finish_entry" << archive_error_string(m_write); + result = false; + break; + } + + ret = archive_read_next_header(m_read, &entry); + } + + d->closeRead(m_read); + } + + foreach (const QString &path, recursivepaths) { + const QByteArray utfpath = path.toUtf8(); + + struct stat statistic; + if (::lstat(utfpath, &statistic) != 0) { + kWarning() << "stat error" << ::strerror(errno); + result = false; + break; + } + + QByteArray pathname = utfpath; + if (pathname.startsWith(strip)) { + pathname.remove(0, strip.size()); + } + pathname.prepend(destination); + kDebug() << "adding" << path << "as" << pathname; + + // NOTE: archive_entry_copy_stat doesn't work + // http://linux.die.net/man/2/stat + struct archive_entry* newentry = archive_entry_new(); + archive_entry_set_pathname(newentry, pathname); + archive_entry_set_size(newentry, statistic.st_size); + archive_entry_set_gid(newentry, statistic.st_gid); + archive_entry_set_uid(newentry, statistic.st_uid); + archive_entry_set_atime(newentry, statistic.st_atim.tv_sec, statistic.st_atim.tv_nsec); + archive_entry_set_ctime(newentry, statistic.st_ctim.tv_sec, statistic.st_ctim.tv_nsec); + archive_entry_set_mtime(newentry, statistic.st_mtim.tv_sec, statistic.st_mtim.tv_nsec); + + // filetype and mode are supposedly the same + // permissions are set when mode is set, same for filetyp? + archive_entry_set_mode(newentry, statistic.st_mode); + + if (statistic.st_nlink > 1) { + // TODO: archive_entry_set_hardlink(newentry, pathname); + } + + if (S_ISLNK(statistic.st_mode)) { + QByteArray linkbuffer(PATH_MAX + 1, Qt::Uninitialized); + if (::readlink(utfpath, linkbuffer.data(), PATH_MAX) == -1) { + kWarning() << "readlink error" << ::strerror(errno); + result = false; + break; + } + + if (linkbuffer.startsWith(strip)) { + linkbuffer.remove(0, strip.size()); + } + + archive_entry_set_symlink(newentry, linkbuffer.constData()); + } + + if (archive_write_header(m_write, newentry) != ARCHIVE_OK) { + kWarning() << "archive_write_header" << archive_error_string(m_write); + archive_entry_free(newentry); + result = false; + break; + } + archive_entry_free(newentry); + + if (S_ISREG(statistic.st_mode)) { + QFile file(path); + if (!file.open(QFile::ReadOnly)) { + kWarning() << "could not open" << path; + result = false; + break; + } + + const QByteArray data = file.readAll(); + if (data.isEmpty() && statistic.st_size > 0) { + kWarning() << "could not read" << path; + result = false; + break; + } + + if (statistic.st_size > 0 && data.size() != statistic.st_size) { + kWarning() << "read and stat size are different" << path; + result = false; + break; + } + + if (archive_write_data(m_write, data, data.size()) != statistic.st_size) { + kWarning() << "archive_write_data" << archive_error_string(m_write); + result = false; + break; + } + } + + if (archive_write_finish_entry(m_write) != ARCHIVE_OK) { + kWarning() << "archive_write_finish_entry" << archive_error_string(m_write); + result = false; + break; + } + + result = true; + } + + d->closeWrite(m_write); + + if (result && replace) { + result = QFile::remove(d->m_path); + if (!result) { + kWarning() << "could not remove original" << d->m_path; + } + } + + if (result) { + result = QFile::rename(tmppath, d->m_path); + if (!result) { + kWarning() << "could not move" << tmppath << "to" << d->m_path; + } + } + + return result; +} + +bool KArchiveManager::remove(const QStringList &paths) const { + bool result = false; + + if (d->m_path.isEmpty()) { + kWarning() << "no path is set"; + return result; + } else if (!d->m_writable) { + kWarning() << "path is not writable"; + return result; + } else if (!QFile::exists(d->m_path)) { + kWarning() << "path does not exists" << d->m_path; + return result; + } + + struct archive* m_read = d->openRead(d->m_path.toUtf8()); + if (!m_read) { + kWarning() << "could not open archive" << d->m_path; + return result; + } + + QFileInfo fileinfo(d->m_path); + QTemporaryFile tmpfile("XXXXXX." + fileinfo.completeSuffix()); + if (!tmpfile.open()) { + kWarning() << "could not open temporary file"; + return result; + } + + const QByteArray tmppath = tmpfile.fileName().toUtf8(); + struct archive* m_write = d->openWrite(tmppath); + if (!m_write) { + kWarning() << "could not open temporary archive" << tmppath; + return result; + } + + QStringList notfound = paths; + + struct archive_entry* entry = archive_entry_new(); + int ret = archive_read_next_header(m_read, &entry); + while (ret != ARCHIVE_EOF) { + if (ret < ARCHIVE_OK) { + kWarning() << "archive_read_next_header" << archive_error_string(m_read); + result = false; + break; + } + + const QByteArray pathname = archive_entry_pathname(entry); + if (paths.contains(pathname)) { + kDebug() << "removing" << pathname; + notfound.removeAll(pathname); + archive_read_data_skip(m_read); + ret = archive_read_next_header(m_read, &entry); + result = true; + continue; + } + + if (archive_write_header(m_write, entry) != ARCHIVE_OK) { + kWarning() << "archive_write_header" << archive_error_string(m_write); + result = false; + break; + } + + if (!d->copyData(m_read, m_write)) { + result = false; + break; + } + + if (archive_write_finish_entry(m_write) != ARCHIVE_OK) { + kWarning() << "archive_write_finish_entry" << archive_error_string(m_write); + result = false; + break; + } + + ret = archive_read_next_header(m_read, &entry); + } + + d->closeWrite(m_write); + d->closeRead(m_read); + + if (result) { + kDebug() << "replacing" << d->m_path << "with" << tmppath; + + result = QFile::remove(d->m_path); + if (result) { + result = QFile::rename(tmppath, d->m_path); + if (!result) { + kWarning() << "could not move" << tmppath << "to" << d->m_path; + } + } else { + kWarning() << "could not remove original" << d->m_path; + } + } + + if (!notfound.isEmpty()) { + kWarning() << "entries not in archive" << notfound; + } + + return result; +} + +bool KArchiveManager::extract(const QStringList &paths, const QString &destination, bool preserve) const { + bool result = false; + + if (d->m_path.isEmpty()) { + kWarning() << "no path is set"; + return result; + } else if (!QFile::exists(d->m_path)) { + kWarning() << "path does not exists" << d->m_path; + return result; + } + + struct archive* m_read = d->openRead(d->m_path.toUtf8()); + if (!m_read) { + kWarning() << "could not open archive" << d->m_path; + return result; + } + + const QString currendir = QDir::currentPath(); + if (!QDir::setCurrent(destination)) { + kWarning() << "could not change to destination directory" << destination; + return result; + } + + struct archive* m_write = d->openDisk(preserve); + if (!m_write) { + kWarning() << "could not open write archive"; + return result; + } + + QStringList notfound = paths; + + struct archive_entry* entry = archive_entry_new(); + int ret = archive_read_next_header(m_read, &entry); + while (ret != ARCHIVE_EOF) { + if (ret < ARCHIVE_OK) { + kWarning() << "archive_read_next_header" << archive_error_string(m_read); + result = false; + break; + } + + const QByteArray pathname = archive_entry_pathname(entry);; + if (!paths.contains(pathname)) { + archive_read_data_skip(m_read); + ret = archive_read_next_header(m_read, &entry); + continue; + } + notfound.removeAll(pathname); + result = true; + + if (archive_write_header(m_write, entry) != ARCHIVE_OK) { + kWarning() << "archive_write_header" << archive_error_string(m_write); + result = false; + break; + } + + if (archive_read_extract2(m_read, entry, m_write) != ARCHIVE_OK) { + kWarning() << "archive_read_extract2" << archive_error_string(m_write); + result = false; + break; + } + + if (archive_write_finish_entry(m_write) != ARCHIVE_OK) { + kWarning() << "archive_write_finish_entry" << archive_error_string(m_write); + result = false; + break; + } + + ret = archive_read_next_header(m_read, &entry); + } + + d->closeWrite(m_write); + d->closeRead(m_read); + + if (!QDir::setCurrent(currendir)) { + kWarning() << "could not change to orignal directory" << currendir; + } + + if (!notfound.isEmpty()) { + kWarning() << "entries not in archive" << notfound; + } + + return result; +} + +QList KArchiveManager::list() const { + QList result; + + if (d->m_path.isEmpty()) { + kWarning() << "no path is set"; + return result; + } else if (!QFile::exists(d->m_path)) { + kWarning() << "path does not exists" << d->m_path; + return result; + } + + struct archive* m_read = d->openRead(d->m_path.toUtf8()); + if (!m_read) { + kWarning() << "could not open archive" << d->m_path; + return result; + } + + struct archive_entry* entry = archive_entry_new(); + int ret = archive_read_next_header(m_read, &entry); + while (ret != ARCHIVE_EOF) { + if (ret < ARCHIVE_OK) { + kWarning() << "archive_read_next_header" << archive_error_string(m_read); + result.clear(); + break; + } + + KArchiveInfo info; + info.encrypted = bool(archive_entry_is_encrypted(entry)); + info.size = archive_entry_size(entry); + info.gid = archive_entry_gid(entry); + info.uid = archive_entry_uid(entry); + info.mode = archive_entry_mode(entry); + info.atime = archive_entry_atime(entry); + info.ctime = archive_entry_ctime(entry); + info.mtime = archive_entry_mtime(entry); + info.hardlink = QByteArray(archive_entry_hardlink(entry)); + info.symlink = QByteArray(archive_entry_symlink(entry)); + info.pathname = archive_entry_pathname(entry); + info.mimetype = d->getMime(m_read); + info.type = d->getType(info.mode); + + result << info; + + ret = archive_read_next_header(m_read, &entry); + } + + d->closeRead(m_read); + + return result; +} + +KArchiveInfo KArchiveManager::info(const QString &path) const { + KArchiveInfo result; + + if (d->m_path.isEmpty()) { + kWarning() << "no path is set"; + return result; + } + + struct archive* m_read = d->openRead(d->m_path.toUtf8()); + if (!m_read) { + kWarning() << "could not open archive" << d->m_path; + return result; + } + + bool found = false; + struct archive_entry* entry = archive_entry_new(); + int ret = archive_read_next_header(m_read, &entry); + while (ret != ARCHIVE_EOF) { + if (ret < ARCHIVE_OK) { + kWarning() << "archive_read_next_header" << archive_error_string(m_read); + break; + } + + const QByteArray pathname = archive_entry_pathname(entry); + if (pathname == path) { + result.encrypted = bool(archive_entry_is_encrypted(entry)); + result.size = archive_entry_size(entry); + result.gid = archive_entry_gid(entry); + result.uid = archive_entry_uid(entry); + result.mode = archive_entry_mode(entry); + result.atime = archive_entry_atime(entry); + result.ctime = archive_entry_ctime(entry); + result.mtime = archive_entry_mtime(entry); + result.hardlink = QByteArray(archive_entry_hardlink(entry)); + result.symlink = QByteArray(archive_entry_symlink(entry)); + result.pathname = pathname; + result.mimetype = d->getMime(m_read); + result.type = d->getType(result.mode); + + found = true; + break; + } + + ret = archive_read_next_header(m_read, &entry); + } + + d->closeRead(m_read); + + if (!found) { + kWarning() << "entry not in archive" << path; + } + + return result; +} + +bool KArchiveManager::writable() const { + return d->m_writable; +} + +bool KArchiveManager::supported(const QString &path) { + bool result = false; + + struct archive* m_read = KArchiveManagerPrivate::openRead(path.toUtf8()); + if (m_read) { + result = true; + } + + KArchiveManagerPrivate::closeRead(m_read); + + return result; +} + +bool KArchiveManager::gzipRead(const QString &path, QByteArray &buffer) { + bool result = true; + + gzFile gzip = gzopen(path.toUtf8(), "r"); + + if (gzip) { + buffer.clear(); + char gzbuffer[KARCHIVEMANAGER_BUFFSIZE]; + int readbytes = gzread(gzip, gzbuffer, sizeof(gzbuffer)); + while (readbytes > 0) { + buffer += gzbuffer; + + readbytes = gzread(gzip, gzbuffer, sizeof(gzbuffer)); + } + + if (readbytes == -1) { + kWarning() << "gzip read error" << path; + buffer.clear(); + result = false; + } + } else { + kWarning() << "could not open gzip" << path; + result = false; + } + + gzclose(gzip); + + return result; +} + +bool KArchiveManager::gzipWrite(const QString &path, const QByteArray &data) { + bool result = true; + + gzFile gzip = gzopen(path.toUtf8(), "w"); + + if (gzip) { + const int writebytes = gzwrite(gzip, data.constData(), data.size()); + if (writebytes != data.size()) { + kWarning() << "gzip write error" << path; + result = false; + } + } else { + kWarning() << "could not open gzip" << path; + result = false; + } + + gzclose(gzip); + + return result; +} + +bool KArchiveManager::bzipRead(const QString &path, QByteArray &buffer) { + bool result = true; + + BZFILE* bzip = BZ2_bzopen(path.toUtf8(), "r"); + + if (bzip) { + buffer.clear(); + char bzbuffer[KARCHIVEMANAGER_BUFFSIZE]; + int readbytes = BZ2_bzread(bzip, bzbuffer, sizeof(bzbuffer)); + while (readbytes > 0) { + buffer += bzbuffer; + + readbytes = BZ2_bzread(bzip, bzbuffer, sizeof(bzbuffer)); + } + + if (readbytes == -1) { + kWarning() << "bzip read error" << path; + buffer.clear(); + result = false; + } + } else { + kWarning() << "could not open bzip" << path; + result = false; + } + + BZ2_bzclose(bzip); + + return result; +} + +bool KArchiveManager::bzipWrite(const QString &path, QByteArray data) { + bool result = true; + + BZFILE* bzip = BZ2_bzopen(path.toUtf8(), "w"); + + if (bzip) { + const int writebytes = BZ2_bzwrite(bzip, data.data(), data.size()); + if (writebytes != data.size()) { + kWarning() << "bzip write error" << path; + result = false; + } + } else { + kWarning() << "could not open bzip" << path; + result = false; + } + + BZ2_bzclose(bzip); + + return result; +} + +class KArchiveModelPrivate : public QThread { + Q_OBJECT + + public: + KArchiveModelPrivate(QObject *parent = Q_NULLPTR); + + QString joinDir(const QString &dir1, const QString &dir2) const; + QStandardItem* makeRow(const QString &string) const; + + void appendDirectory(const QString &path); + void appendSpecial(const KArchiveInfo &info); + + QStandardItem* m_root; + QMap m_directories; + QList m_list; + const KArchiveManager *m_archive; + bool m_interrupt; + + void requestInterruption(); + + protected: + virtual void run(); +}; + +KArchiveModelPrivate::KArchiveModelPrivate(QObject *parent) + : QThread(parent), + m_interrupt(false) { +} + +// because altering paths is not easy +QString KArchiveModelPrivate::joinDir(const QString &dir1, const QString &dir2) const { + if (dir1.isEmpty()) { + return dir2; + } + return dir1 + "/" + dir2; +} + +QStandardItem* KArchiveModelPrivate::makeRow(const QString &string) const { + QStandardItem* item = new QStandardItem(string); + item->setSelectable(false); + item->setTextAlignment(Qt::AlignRight); + return item; +} + +void KArchiveModelPrivate::appendDirectory(const QString &path) { + QStandardItem* lastitem = m_root; + QString dirsofar; + foreach (const QString &dir, path.split("/")) { + if (dir.isEmpty()) { + continue; + } + + dirsofar = joinDir(dirsofar, dir); + if (m_directories.contains(dirsofar)) { + lastitem = m_directories.value(dirsofar); + continue; + } + + // NOTE: directory entries are not consistent + KArchiveInfo info; + foreach (const KArchiveInfo &listinfo, m_list) { + if (listinfo.pathname == dirsofar || listinfo.pathname == dirsofar + "/") { + info = listinfo; + break; + } + } + if (info.isNull()) { + // fake it for now + info.type = KArchiveInfo::Directory; + } + + QList diritems; + QStandardItem* diritem = new QStandardItem(dir); + // TODO: diritem->setIcon(info.fancyIcon()); + diritem->setWhatsThis("Directory"); + diritem->setStatusTip(dirsofar); + diritems << diritem; + diritems << makeRow(info.fancyType()); + diritems << makeRow(info.fancySize()); + diritems << makeRow(info.fancyMode()); + diritems << makeRow(info.fancyEncrypted()); + + lastitem->appendRow(diritems); + + m_directories.insert(dirsofar, diritem); + lastitem = diritem; + } +} + +void KArchiveModelPrivate::appendSpecial(const KArchiveInfo &info) { + const QFileInfo fileinfo(info.pathname); + const QString infopath = fileinfo.path(); + const QString infoname = fileinfo.fileName(); + + appendDirectory(infopath); + + QList specialitems; + QStandardItem* specialitem = new QStandardItem(infoname); + // TODO: specialitem->setIcon(info.fancyIcon()); + specialitem->setStatusTip(info.pathname); + specialitems << specialitem; + specialitems << makeRow(info.fancyType()); + specialitems << makeRow(info.fancySize()); + specialitems << makeRow(info.fancyMode()); + specialitems << makeRow(info.fancyEncrypted()); + + QStandardItem* diritem = m_directories.value(infopath); + if (diritem) { + diritem->appendRow(specialitems); + } else { + m_root->appendRow(specialitems); + } +} + +void KArchiveModelPrivate::run() { + m_list = m_archive->list(); + m_interrupt = false; + + foreach (const KArchiveInfo &info, m_list) { + if(m_interrupt) { + return; + } + + if (info.type == KArchiveInfo::KArchiveType::Directory) { + appendDirectory(info.pathname); + } else { + appendSpecial(info); + } + } +} + +void KArchiveModelPrivate::requestInterruption() { + m_interrupt = true; +} + +KArchiveModel::KArchiveModel(QObject *parent) + : QStandardItemModel(parent), + d(new KArchiveModelPrivate(this)) { + + connect(d, SIGNAL(finished()), this, SLOT(slotLoadFinished())); +} + +KArchiveModel::~KArchiveModel() { + if (d) { + delete d; + } +} + +bool KArchiveModel::loadArchive(const KArchiveManager *archive) { + bool result = false; + + if (!archive) { + return result; + } + + result = true; + + if (d->isRunning()) { + d->requestInterruption(); + d->wait(); + } + QCoreApplication::processEvents(); + clear(); + + d->m_archive = archive; + d->m_root = invisibleRootItem(); + d->m_directories.clear(); + + emit loadStarted(); + d->start(); + + return result; +} + +QString KArchiveModel::path(const QModelIndex &index) const { + QString result; + + if (!index.isValid()) { + return result; + } + + const QStandardItem *item = itemFromIndex(index); + result = item->statusTip(); + + if (item->whatsThis() == ("Directory")) { + result += "/"; + } + + return result; +} + +QStringList KArchiveModel::paths(const QModelIndex &index) const { + QStringList result; + + const QByteArray indexpath = path(index).toUtf8(); + if (!indexpath.isEmpty() && index.isValid()) { + const QStandardItem *item = itemFromIndex(index); + if (item->whatsThis() == ("Directory")) { + foreach (const KArchiveInfo &info, d->m_list) { + if (info.pathname.startsWith(indexpath)) { + result << info.pathname; + } + } + } else { + result << indexpath; + } + } + + return result; +} + +QString KArchiveModel::dir(const QModelIndex &index) const { + QString result; + + if (!index.isValid()) { + return result; + } + + const QStandardItem *item = itemFromIndex(index); + if (item->whatsThis() == ("Directory")) { + result = item->statusTip(); + } else { + QFileInfo iteminfo(item->statusTip()); + result = iteminfo.path(); + } + + return result; +} + +void KArchiveModel::slotLoadFinished() { + setHeaderData(0, Qt::Horizontal, QVariant("Name")); + setHeaderData(1, Qt::Horizontal, QVariant("Type")); + setHeaderData(2, Qt::Horizontal, QVariant("Size")); + setHeaderData(3, Qt::Horizontal, QVariant("Mode")); + setHeaderData(4, Qt::Horizontal, QVariant("Encrypted")); + + emit loadFinished(); +} + +#include "karchivemanager.moc" diff --git a/karchivemanager/karchivemanager.desktop b/karchivemanager/karchivemanager.desktop new file mode 100644 index 00000000..ef01a0c5 --- /dev/null +++ b/karchivemanager/karchivemanager.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Icon=package-x-generic +Name=KArchiveManager +GenericName=Archive Manager +Comment=Simple archive manager for KDE +Exec=karchivemanager --icon '%i' --caption '%c' %U +Terminal=false +Type=Application +Categories=Qt;KDE;System; +MimeType=application/x-archive;application/x-deb;application/x-cd-image;application/x-bcpio;application/x-cpio;application/x-cpio-compressed;application/x-sv4cpio;application/x-sv4crc;application/x-rpm;application/x-source-rpm;application/vnd.ms-cab-compressed;application/x-servicepack;application/x-lzop;application/x-lz4;application/x-tar;application/x-compressed-tar;application/x-bzip;application/x-gzip;application/x-bzip-compressed-tar;application/x-gzip-compressed-tar;application/x-tarz;application/x-xz;application/x-xz-compressed-tar;application/x-lzma-compressed-tar;application/x-java-archive;application/zip;application/x-7z-compressed;application/x-iso9660-image;application/x-raw-disk-image; diff --git a/karchivemanager/karchivemanager.hpp b/karchivemanager/karchivemanager.hpp new file mode 100644 index 00000000..6133c49b --- /dev/null +++ b/karchivemanager/karchivemanager.hpp @@ -0,0 +1,226 @@ +/* This file is part of KArchiveManager + Copyright (C) 2018 Ivailo Monev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KARCHIVEMANAGER_H +#define KARCHIVEMANAGER_H + +#include +#include +#include +#include + +#include + +/*! + Archive entry information holder, valid object is obtained via @p KArchiveManager::info + + @note It is up to the programmer to keep the integrity of the structure + @note D-Bus signature for the type is (bxxxussssi) + @warning The structure is not very portable (size, gid, uid) + @ingroup Types + + @see KArchiveManager + @see https://dbus.freedesktop.org/doc/dbus-specification.html#container-types +*/ +class KArchiveInfo { + + public: + enum KArchiveType { + None = 0, + File = 1, + Directory = 2, + Link = 3, + Character = 4, + Block = 5, + Fifo = 6, + Socket = 7, + }; + + KArchiveInfo(); + KArchiveInfo(const KArchiveInfo &info); + + bool encrypted; + int64_t size; + int64_t gid; + int64_t uid; + mode_t mode; + time_t atime; + time_t ctime; + time_t mtime; + QByteArray hardlink; + QByteArray symlink; + QByteArray pathname; + QByteArray mimetype; + KArchiveType type; + + //! @brief Fancy encrypted for the purpose of widgets + QString fancyEncrypted() const; + //! @brief Fancy size for the purpose of widgets + QString fancySize() const; + //! @brief Fancy mode for the purpose of widgets + QString fancyMode() const; + + //! @brief Fancy type for the purpose of widgets + QString fancyType() const; + //! @brief Fancy icon for the purpose of widgets + QIcon fancyIcon() const; + + //! @brief Returns if the info is valid or not + bool isNull() const; + + bool operator==(const KArchiveInfo &i) const; + KArchiveInfo &operator=(const KArchiveInfo &i); +}; +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug, const KArchiveInfo &i); +#endif +const QDBusArgument &operator<<(QDBusArgument &, const KArchiveInfo &i); +const QDBusArgument &operator>>(const QDBusArgument &, KArchiveInfo &i); + +class KArchiveManagerPrivate; + +/*! + Archive manager with support for many formats, provides plain GZip and BZip2 support aswell + + Example: + \code + KArchiveManager archive("/home/joe/archive.tar.gz"); + kDebug() << archive.list(); + + QDir::mkpath("/tmp/destination"); + archive.extract("dir/in/archive/", "/tmp/destination"); + archive.delete("file/in/archive.txt"); + \endcode + + @note Paths ending with "/" will be considered as directories + @warning The operations are done on temporary file, copy of the orignal, which after + successfull operation (add or remove) replaces the orignal thus if it is interrupted the + source may get corrupted + + @see KArchiveInfo + + @todo encrypted paths handling + @todo make listing consistent by faking dir info? + @todo error reporting +*/ +class KArchiveManager { + + public: + KArchiveManager(const QString &path); + ~KArchiveManager(); + + /*! + @brief Add paths to the archive + @param strip string to remove from the start of every path + @param destination relative path where paths should be added to + */ + bool add(const QStringList &paths, const QByteArray &strip = "/", const QByteArray &destination = "") const; + //! @brief Remove paths from the archive + bool remove(const QStringList &paths) const; + /*! + @brief Extract paths to destination + @param destination existing directory, you can use @p QDir::mkpath(QString) + @param preserve preserve advanced attributes (ACL/ATTR) + */ + bool extract(const QStringList &paths, const QString &destination, bool preserve = true) const; + + /*! + @brief List the content of the archive + @note may return empty list on both failure and success + @note some formats list directories, some do not + @todo report failure somehow + */ + QList list() const; + + //! @brief Get info for archive entry + KArchiveInfo info(const QString &path) const; + + //! @brief Report if path is writable archive + bool writable() const; + //! @brief Report if path is supported archive + static bool supported(const QString &path); + + //! @brief Read gzip compressed path into buffer + static bool gzipRead(const QString &path, QByteArray &buffer); + //! @brief Compress data as gzip and write it to path + static bool gzipWrite(const QString &path, const QByteArray &data); + //! @brief Read bzip2 compressed path into buffer + static bool bzipRead(const QString &path, QByteArray &buffer); + //! @brief Compress data as bzip2 and write it to path + static bool bzipWrite(const QString &path, QByteArray data); + + private: + KArchiveManagerPrivate *d; +}; + +class KArchiveModelPrivate; + +/*! + Custom item model for displaying archives in tree views (@p QTreeView) + + Example: + \code + QTreeView view; + KArchiveModel model; + KArchiveManager archive("/home/joe/archive.tar.gz"); + + view.setModel(model); + model.loadArchive(&archive); + \endcode + + @see KArchiveManager, KArchiveInfo +*/ +class KArchiveModel : public QStandardItemModel { + Q_OBJECT + + public: + KArchiveModel(QObject *parent = Q_NULLPTR); + ~KArchiveModel(); + + //! @brief Load archive into the model + bool loadArchive(const KArchiveManager *archive); + //! @brief Get path for index, propagates to parents to retrieve the full path + QString path(const QModelIndex &index) const; + /*! + @brief Get paths for index, propagates to childs to retrieve all sub-paths. + Usefull for obtaining the selected item paths for add, remove and extract + operations + */ + QStringList paths(const QModelIndex &index) const; + /*! + @brief Get parent directory for index. Usefull for obtaining the current dir + for add operations + */ + QString dir(const QModelIndex &index) const; + + Q_SIGNALS: + //! @brief Signals load was started + void loadStarted(); + //! @brief Signals load was finished + void loadFinished(); + + private Q_SLOTS: + void slotLoadFinished(); + + private: + KArchiveModelPrivate *d; +}; + +Q_DECLARE_METATYPE(KArchiveInfo); + +#endif // KARCHIVEMANAGER_H diff --git a/karchivemanager/main.cpp b/karchivemanager/main.cpp new file mode 100644 index 00000000..c042fc07 --- /dev/null +++ b/karchivemanager/main.cpp @@ -0,0 +1,56 @@ +/* This file is part of KArchiveManager + Copyright (C) 2018 Ivailo Monev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include + +#include "karchiveapp.hpp" + +int main(int argc, char** argv) +{ + KAboutData aboutData("karchivemanager", 0, ki18n("Archive Manager"), + "1.0.0", ki18n("Simple archive manager for KDE."), + KAboutData::License_GPL_V2, + ki18n("(c) 2018 Ivailo Monev"), + KLocalizedString(), + "http://github.com/fluxer/katana" + ); + + aboutData.addAuthor(ki18n("Ivailo Monev"), + ki18n("Maintainer"), + "xakepa10@gmail.com"); + aboutData.setProgramIconName(QLatin1String("package-x-generic")); + + KCmdLineArgs::init(argc, argv, &aboutData); + KCmdLineOptions option; + option.add("+[url]", ki18n("URL to be opened")); + KCmdLineArgs::addCmdLineOptions(option); + + KApplication *karchiveapp = new KApplication(); + KArchiveApp *karchivewin = new KArchiveApp(); + karchivewin->show(); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + for (int pos = 0; pos < args->count(); ++pos) { + karchivewin->changePath(args->url(pos).toLocalFile()); + } + + return karchiveapp->exec(); +} diff --git a/karchivemanager/strmode.c b/karchivemanager/strmode.c new file mode 100644 index 00000000..3b276503 --- /dev/null +++ b/karchivemanager/strmode.c @@ -0,0 +1,143 @@ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +// #include +#include +#include +#include + +void +strmode(mode_t mode, char *p) +{ + /* print type */ + switch (mode & S_IFMT) { + case S_IFDIR: /* directory */ + *p++ = 'd'; + break; + case S_IFCHR: /* character special */ + *p++ = 'c'; + break; + case S_IFBLK: /* block special */ + *p++ = 'b'; + break; + case S_IFREG: /* regular */ + *p++ = '-'; + break; + case S_IFLNK: /* symbolic link */ + *p++ = 'l'; + break; + case S_IFSOCK: /* socket */ + *p++ = 's'; + break; +#ifdef S_IFIFO + case S_IFIFO: /* fifo */ + *p++ = 'p'; + break; +#endif +#ifdef S_IFWHT + case S_IFWHT: /* whiteout */ + *p++ = 'w'; + break; +#endif + default: /* unknown */ + *p++ = '?'; + break; + } + /* usr */ + if (mode & S_IRUSR) + *p++ = 'r'; + else + *p++ = '-'; + if (mode & S_IWUSR) + *p++ = 'w'; + else + *p++ = '-'; + switch (mode & (S_IXUSR | S_ISUID)) { + case 0: + *p++ = '-'; + break; + case S_IXUSR: + *p++ = 'x'; + break; + case S_ISUID: + *p++ = 'S'; + break; + case S_IXUSR | S_ISUID: + *p++ = 's'; + break; + } + /* group */ + if (mode & S_IRGRP) + *p++ = 'r'; + else + *p++ = '-'; + if (mode & S_IWGRP) + *p++ = 'w'; + else + *p++ = '-'; + switch (mode & (S_IXGRP | S_ISGID)) { + case 0: + *p++ = '-'; + break; + case S_IXGRP: + *p++ = 'x'; + break; + case S_ISGID: + *p++ = 'S'; + break; + case S_IXGRP | S_ISGID: + *p++ = 's'; + break; + } + /* other */ + if (mode & S_IROTH) + *p++ = 'r'; + else + *p++ = '-'; + if (mode & S_IWOTH) + *p++ = 'w'; + else + *p++ = '-'; + switch (mode & (S_IXOTH | S_ISVTX)) { + case 0: + *p++ = '-'; + break; + case S_IXOTH: + *p++ = 'x'; + break; + case S_ISVTX: + *p++ = 'T'; + break; + case S_IXOTH | S_ISVTX: + *p++ = 't'; + break; + } + *p++ = ' '; /* will be a '+' if ACL's implemented */ + *p = '\0'; +}