mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-23 18:32:53 +00:00

less space used by the options, automatic sub-folder detection even for single folder archive and handling of dot (".") as the returned sub-folder name by the archive list job (libarchive quirck). tested batch and non-batch extraction, with and without automatic sub-folder detection but with tar.xz and .deb files only Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
982 lines
32 KiB
C++
982 lines
32 KiB
C++
/*
|
|
* ark -- archiver for the KDE project
|
|
*
|
|
* Copyright (C) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
|
|
* Copyright (C) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
|
|
* Copyright (C) 2009-2012 Raphael Kubo da Costa <rakuco@FreeBSD.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "part.h"
|
|
#include "archivemodel.h"
|
|
#include "archiveview.h"
|
|
#include "arkviewer.h"
|
|
#include "dnddbusinterfaceadaptor.h"
|
|
#include "infopanel.h"
|
|
#include "jobtracker.h"
|
|
#include "kerfuffle/archive.h"
|
|
#include "kerfuffle/extractiondialog.h"
|
|
#include "kerfuffle/jobs.h"
|
|
#include "kerfuffle/settings.h"
|
|
|
|
#include <KAboutData>
|
|
#include <KAction>
|
|
#include <KActionCollection>
|
|
#include <KApplication>
|
|
#include <KConfigGroup>
|
|
#include <KDebug>
|
|
#include <KFileDialog>
|
|
#include <KGuiItem>
|
|
#include <KIO/Job>
|
|
#include <KIO/NetAccess>
|
|
#include <KIO/RenameDialog>
|
|
#include <KIcon>
|
|
#include <KInputDialog>
|
|
#include <KMenu>
|
|
#include <KMessageBox>
|
|
#include <KPluginFactory>
|
|
#include <KRun>
|
|
#include <KToolInvocation>
|
|
#include <KSelectAction>
|
|
#include <KStandardDirs>
|
|
#include <KStandardGuiItem>
|
|
#include <KTempDir>
|
|
#include <KToggleAction>
|
|
#include <KXMLGUIFactory>
|
|
|
|
#include <QAction>
|
|
#include <QCursor>
|
|
#include <QHeaderView>
|
|
#include <QMenu>
|
|
#include <QMimeData>
|
|
#include <QtGui/qevent.h>
|
|
#include <QScopedPointer>
|
|
#include <QSplitter>
|
|
#include <QTimer>
|
|
#include <QVBoxLayout>
|
|
#include <QtCore/qsharedpointer.h>
|
|
#include <QtDBus/QtDBus>
|
|
|
|
using namespace Kerfuffle;
|
|
|
|
K_PLUGIN_FACTORY(Factory, registerPlugin<Ark::Part>();)
|
|
K_EXPORT_PLUGIN(Factory("ark"))
|
|
|
|
namespace Ark
|
|
{
|
|
|
|
static quint32 s_instanceCounter = 1;
|
|
|
|
Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList& args)
|
|
: KParts::ReadWritePart(parent),
|
|
m_splitter(0),
|
|
m_busy(false),
|
|
m_jobTracker(0)
|
|
{
|
|
Q_UNUSED(args)
|
|
setObjectName(QString::fromLatin1("ArkPart"));
|
|
setComponentData(Factory::componentData());
|
|
|
|
new DndExtractAdaptor(this);
|
|
|
|
const QString pathName = QString(QLatin1String("/DndExtract/%1")).arg(s_instanceCounter++);
|
|
if (!QDBusConnection::sessionBus().registerObject(pathName, this)) {
|
|
kFatal() << "Could not register a D-Bus object for drag'n'drop";
|
|
}
|
|
|
|
m_model = new ArchiveModel(pathName, this);
|
|
|
|
m_splitter = new QSplitter(Qt::Horizontal, parentWidget);
|
|
setWidget(m_splitter);
|
|
|
|
m_view = new ArchiveView;
|
|
m_infoPanel = new InfoPanel(m_model);
|
|
|
|
m_splitter->addWidget(m_view);
|
|
m_splitter->addWidget(m_infoPanel);
|
|
|
|
QList<int> splitterSizes = ArkSettings::splitterSizes();
|
|
if (splitterSizes.isEmpty()) {
|
|
splitterSizes.append(200);
|
|
splitterSizes.append(100);
|
|
}
|
|
m_splitter->setSizes(splitterSizes);
|
|
|
|
setupView();
|
|
setupActions();
|
|
|
|
connect(m_model, SIGNAL(loadingStarted()),
|
|
this, SLOT(slotLoadingStarted()));
|
|
connect(m_model, SIGNAL(loadingFinished(KJob*)),
|
|
this, SLOT(slotLoadingFinished(KJob*)));
|
|
connect(m_model, SIGNAL(droppedFiles(QStringList,QString)),
|
|
this, SLOT(slotAddFiles(QStringList,QString)));
|
|
connect(m_model, SIGNAL(error(QString,QString)),
|
|
this, SLOT(slotError(QString,QString)));
|
|
|
|
connect(this, SIGNAL(busy()),
|
|
this, SLOT(setBusyGui()));
|
|
connect(this, SIGNAL(ready()),
|
|
this, SLOT(setReadyGui()));
|
|
connect(this, SIGNAL(completed()),
|
|
this, SLOT(setFileNameFromArchive()));
|
|
|
|
setXMLFile(QLatin1String( "ark_part.rc" ));
|
|
}
|
|
|
|
Part::~Part()
|
|
{
|
|
qDeleteAll(m_previewDirList);
|
|
|
|
saveSplitterSizes();
|
|
|
|
m_extractFilesAction->menu()->deleteLater();
|
|
}
|
|
|
|
void Part::registerJob(KJob* job)
|
|
{
|
|
if (!m_jobTracker) {
|
|
m_jobTracker = new JobTracker(widget());
|
|
m_jobTracker->widget(job)->show();
|
|
}
|
|
m_jobTracker->registerJob(job);
|
|
|
|
emit busy();
|
|
connect(job, SIGNAL(result(KJob*)), this, SIGNAL(ready()));
|
|
}
|
|
|
|
// TODO: One should construct a KUrl out of localPath in order to be able to handle
|
|
// non-local destinations (ie. trash:/ or a remote location)
|
|
// See bugs #189322 and #204323.
|
|
void Part::extractSelectedFilesTo(const QString& localPath)
|
|
{
|
|
kDebug() << "Extract to " << localPath;
|
|
if (!m_model) {
|
|
return;
|
|
}
|
|
|
|
if (m_view->selectionModel()->selectedRows().count() != 1) {
|
|
m_view->selectionModel()->setCurrentIndex(m_view->currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
|
}
|
|
if (m_view->selectionModel()->selectedRows().count() != 1) {
|
|
return;
|
|
}
|
|
|
|
QVariant internalRoot;
|
|
kDebug() << "valid " << m_view->currentIndex().parent().isValid();
|
|
if (m_view->currentIndex().parent().isValid()) {
|
|
internalRoot = m_model->entryForIndex(m_view->currentIndex().parent()).value(InternalID);
|
|
}
|
|
|
|
if (internalRoot.isNull()) {
|
|
//we have the special case valid parent, but the parent does not
|
|
//actually correspond to an item in the archive, but an automatically
|
|
//created folder. for now, we will just use the filename of the node
|
|
//instead, but for plugins that rely on a non-filename value as the
|
|
//InternalId, this WILL break things. TODO find a solution
|
|
internalRoot = m_model->entryForIndex(m_view->currentIndex().parent()).value(FileName);
|
|
}
|
|
|
|
QList<QVariant> files = selectedFilesWithChildren();
|
|
if (files.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
kDebug() << "selected files are " << files;
|
|
Kerfuffle::ExtractionOptions options;
|
|
options[QLatin1String( "PreservePaths" )] = true;
|
|
if (!internalRoot.isNull()) {
|
|
options[QLatin1String("RootNode")] = internalRoot;
|
|
}
|
|
|
|
ExtractJob *job = m_model->extractFiles(files, localPath, options);
|
|
registerJob(job);
|
|
|
|
connect(job, SIGNAL(result(KJob*)),
|
|
this, SLOT(slotExtractionDone(KJob*)));
|
|
|
|
job->start();
|
|
}
|
|
|
|
void Part::setupView()
|
|
{
|
|
m_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
m_view->setModel(m_model);
|
|
|
|
m_view->setSortingEnabled(true);
|
|
|
|
connect(m_view->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
|
|
this, SLOT(updateActions()));
|
|
connect(m_view->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
|
|
this, SLOT(selectionChanged()));
|
|
|
|
//TODO: fix an actual eventhandler
|
|
connect(m_view, SIGNAL(activated(QModelIndex)),
|
|
this, SLOT(slotPreviewWithInternalViewer()));
|
|
|
|
connect(m_view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotShowContextMenu()));
|
|
|
|
connect(m_model, SIGNAL(columnsInserted(QModelIndex,int,int)),
|
|
this, SLOT(adjustColumns()));
|
|
}
|
|
|
|
void Part::setupActions()
|
|
{
|
|
KToggleAction *showInfoPanelAction = new KToggleAction(i18nc("@action:inmenu", "Show information panel"), this);
|
|
actionCollection()->addAction(QLatin1String( "show-infopanel" ), showInfoPanelAction);
|
|
showInfoPanelAction->setChecked(m_splitter->sizes().at(1) > 0);
|
|
connect(showInfoPanelAction, SIGNAL(triggered(bool)),
|
|
this, SLOT(slotToggleInfoPanel(bool)));
|
|
|
|
m_saveAsAction = KStandardAction::saveAs(this, SLOT(slotSaveAs()), actionCollection());
|
|
|
|
m_previewChooseAppAction = actionCollection()->addAction(QLatin1String("openwith"));
|
|
m_previewChooseAppAction->setText(i18nc("open a file with external program", "Open &With..."));
|
|
m_previewChooseAppAction->setIcon(KIcon(QLatin1String("document-open")));
|
|
m_previewChooseAppAction->setStatusTip(i18n("Click to open the selected file with an external program"));
|
|
connect(m_previewChooseAppAction, SIGNAL(triggered(bool)), this, SLOT(slotPreviewWithExternalProgram()));
|
|
|
|
m_previewAction = actionCollection()->addAction(QLatin1String( "preview" ));
|
|
m_previewAction->setText(i18nc("to preview a file inside an archive", "Pre&view"));
|
|
m_previewAction->setIcon(KIcon( QLatin1String( "document-preview-archive" )));
|
|
m_previewAction->setStatusTip(i18n("Click to preview the selected file"));
|
|
m_previewAction->setShortcut(QKeySequence(Qt::Key_Return, Qt::Key_Space));
|
|
connect(m_previewAction, SIGNAL(triggered(bool)),
|
|
this, SLOT(slotPreviewWithInternalViewer()));
|
|
|
|
m_extractFilesAction = actionCollection()->addAction(QLatin1String( "extract" ));
|
|
m_extractFilesAction->setText(i18n("E&xtract"));
|
|
m_extractFilesAction->setIcon(KIcon( QLatin1String( "archive-extract" )));
|
|
m_extractFilesAction->setStatusTip(i18n("Click to open an extraction dialog, where you can choose to extract either all files or just the selected ones"));
|
|
m_extractFilesAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_E));
|
|
connect(m_extractFilesAction, SIGNAL(triggered(bool)),
|
|
this, SLOT(slotExtractFiles()));
|
|
|
|
m_addFilesAction = actionCollection()->addAction(QLatin1String( "add" ));
|
|
m_addFilesAction->setIcon(KIcon( QLatin1String( "archive-insert" )));
|
|
m_addFilesAction->setText(i18n("Add &File..."));
|
|
m_addFilesAction->setStatusTip(i18n("Click to add files to the archive"));
|
|
connect(m_addFilesAction, SIGNAL(triggered(bool)),
|
|
this, SLOT(slotAddFiles()));
|
|
|
|
m_addDirAction = actionCollection()->addAction(QLatin1String( "add-dir" ));
|
|
m_addDirAction->setIcon(KIcon( QLatin1String( "archive-insert-directory" )));
|
|
m_addDirAction->setText(i18n("Add Fo&lder..."));
|
|
m_addDirAction->setStatusTip(i18n("Click to add a folder to the archive"));
|
|
connect(m_addDirAction, SIGNAL(triggered(bool)),
|
|
this, SLOT(slotAddDir()));
|
|
|
|
m_deleteFilesAction = actionCollection()->addAction(QLatin1String( "delete" ));
|
|
m_deleteFilesAction->setIcon(KIcon( QLatin1String( "archive-remove" )));
|
|
m_deleteFilesAction->setText(i18n("De&lete"));
|
|
m_deleteFilesAction->setShortcut(Qt::Key_Delete);
|
|
m_deleteFilesAction->setStatusTip(i18n("Click to delete the selected files"));
|
|
connect(m_deleteFilesAction, SIGNAL(triggered(bool)),
|
|
this, SLOT(slotDeleteFiles()));
|
|
|
|
updateActions();
|
|
}
|
|
|
|
void Part::updateActions()
|
|
{
|
|
bool isWritable = m_model->archive() && (!m_model->archive()->isReadOnly());
|
|
|
|
m_previewAction->setEnabled(!isBusy() && (m_view->selectionModel()->selectedRows().count() == 1)
|
|
&& isPreviewable(m_view->selectionModel()->currentIndex()));
|
|
m_extractFilesAction->setEnabled(!isBusy() && (m_model->rowCount() > 0));
|
|
m_addFilesAction->setEnabled(!isBusy() && isWritable);
|
|
m_addDirAction->setEnabled(!isBusy() && isWritable);
|
|
m_deleteFilesAction->setEnabled(!isBusy() && (m_view->selectionModel()->selectedRows().count() > 0)
|
|
&& isWritable);
|
|
m_previewChooseAppAction->setEnabled(!isBusy() && (m_view->selectionModel()->selectedRows().count() > 0)
|
|
&& isWritable);
|
|
|
|
QMenu *menu = m_extractFilesAction->menu();
|
|
if (!menu) {
|
|
menu = new QMenu;
|
|
m_extractFilesAction->setMenu(menu);
|
|
connect(menu, SIGNAL(triggered(QAction*)),
|
|
this, SLOT(slotQuickExtractFiles(QAction*)));
|
|
|
|
// Remember to keep this action's properties as similar to
|
|
// m_extractFilesAction's as possible (except where it does not make
|
|
// sense, such as the text or the shortcut).
|
|
QAction *extractTo = menu->addAction(i18n("Extract To..."));
|
|
extractTo->setIcon(m_extractFilesAction->icon());
|
|
extractTo->setStatusTip(m_extractFilesAction->statusTip());
|
|
connect(extractTo, SIGNAL(triggered(bool)), SLOT(slotExtractFiles()));
|
|
|
|
menu->addSeparator();
|
|
|
|
QAction *header = menu->addAction(i18n("Quick Extract To..."));
|
|
header->setEnabled(false);
|
|
header->setIcon(KIcon( QLatin1String( "archive-extract" )));
|
|
}
|
|
|
|
while (menu->actions().size() > 3) {
|
|
menu->removeAction(menu->actions().last());
|
|
}
|
|
|
|
const KConfigGroup conf(KGlobal::config(), "DirSelect Dialog");
|
|
const QStringList dirHistory = conf.readPathEntry("History Items", QStringList());
|
|
|
|
for (int i = 0; i < qMin(10, dirHistory.size()); ++i) {
|
|
const KUrl dirUrl(dirHistory.at(i));
|
|
QAction *newAction = menu->addAction(dirUrl.pathOrUrl());
|
|
newAction->setData(dirUrl.pathOrUrl());
|
|
}
|
|
}
|
|
|
|
void Part::slotQuickExtractFiles(QAction *triggeredAction)
|
|
{
|
|
// #190507: triggeredAction->data.isNull() means it's the "Extract to..."
|
|
// action, and we do not want it to run here
|
|
if (!triggeredAction->data().isNull()) {
|
|
kDebug() << "Extract to " << triggeredAction->data().toString();
|
|
|
|
const QString userDestination = triggeredAction->data().toString();
|
|
QString finalDestinationDirectory;
|
|
const QString detectedSubfolder = detectSubfolder();
|
|
|
|
if (!isSingleFolderArchive()) {
|
|
finalDestinationDirectory = userDestination +
|
|
QDir::separator() + detectedSubfolder;
|
|
QDir(userDestination).mkdir(detectedSubfolder);
|
|
} else {
|
|
finalDestinationDirectory = userDestination;
|
|
}
|
|
|
|
Kerfuffle::ExtractionOptions options;
|
|
options[QLatin1String( "PreservePaths" )] = true;
|
|
QList<QVariant> files = selectedFiles();
|
|
ExtractJob *job = m_model->extractFiles(files, finalDestinationDirectory, options);
|
|
registerJob(job);
|
|
|
|
connect(job, SIGNAL(result(KJob*)),
|
|
this, SLOT(slotExtractionDone(KJob*)));
|
|
|
|
job->start();
|
|
}
|
|
}
|
|
|
|
bool Part::isPreviewable(const QModelIndex& index) const
|
|
{
|
|
return index.isValid() && (!m_model->entryForIndex(index)[ IsDirectory ].toBool());
|
|
}
|
|
|
|
void Part::selectionChanged()
|
|
{
|
|
m_infoPanel->setIndexes(m_view->selectionModel()->selectedRows());
|
|
}
|
|
|
|
KAboutData* Part::createAboutData()
|
|
{
|
|
return new KAboutData("ark", 0, ki18n("ArkPart"), "3.0");
|
|
}
|
|
|
|
bool Part::openFile()
|
|
{
|
|
const QString localFile = localFilePath();
|
|
const QFileInfo localFileInfo(localFile);
|
|
const bool creatingNewArchive =
|
|
arguments().metaData()[QLatin1String("createNewArchive")] == QLatin1String("true");
|
|
|
|
if (localFileInfo.isDir()) {
|
|
KMessageBox::error(
|
|
NULL,
|
|
i18nc("@info", "<tt>%1</tt> is a directory.", localFile)
|
|
);
|
|
return false;
|
|
}
|
|
|
|
if (creatingNewArchive) {
|
|
if (localFileInfo.exists()) {
|
|
int overwrite = KMessageBox::questionYesNo(
|
|
NULL,
|
|
i18nc("@info", "The archive <tt>%1</tt> already exists. Would you like to open it instead?", localFile),
|
|
i18nc("@title:window", "File Exists"), KGuiItem(i18n("Open File")), KStandardGuiItem::cancel()
|
|
);
|
|
if (overwrite == KMessageBox::No) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
if (!localFileInfo.exists()) {
|
|
KMessageBox::sorry(
|
|
NULL,
|
|
i18nc("@info", "The archive <tt>%1</tt> was not found.", localFile),
|
|
i18nc("@title:window", "Error Opening Archive")
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
QScopedPointer<Kerfuffle::Archive> archive(Kerfuffle::Archive::create(localFile, m_model));
|
|
|
|
if ((!archive) || ((creatingNewArchive) && (archive->isReadOnly()))) {
|
|
QStringList mimeTypeList;
|
|
QHash<QString, QString> mimeTypes;
|
|
|
|
if (creatingNewArchive) {
|
|
mimeTypeList = Kerfuffle::supportedWriteMimeTypes();
|
|
} else {
|
|
mimeTypeList = Kerfuffle::supportedMimeTypes();
|
|
}
|
|
|
|
foreach(const QString& mime, mimeTypeList) {
|
|
KMimeType::Ptr mimePtr(KMimeType::mimeType(mime));
|
|
if (mimePtr) {
|
|
// Key = "application/zip", Value = "Zip Archive"
|
|
mimeTypes[mime] = mimePtr->comment();
|
|
}
|
|
}
|
|
|
|
QStringList mimeComments(mimeTypes.values());
|
|
mimeComments.sort();
|
|
|
|
bool ok = false;
|
|
QString item;
|
|
|
|
if (creatingNewArchive) {
|
|
item = KInputDialog::getItem(
|
|
i18nc("@title:window", "Invalid Archive Type"),
|
|
i18nc("@info", "Ark cannot create archives of the type you have chosen.<br/><br/>Please choose another archive type below."),
|
|
mimeComments, 0, false, &ok
|
|
);
|
|
} else {
|
|
item = KInputDialog::getItem(
|
|
i18nc("@title:window", "Unable to Determine Archive Type"),
|
|
i18nc("@info", "Ark was unable to determine the archive type of the filename.<br/><br/>Please choose the correct archive type below."),
|
|
mimeComments, 0, false, &ok
|
|
);
|
|
}
|
|
|
|
if ((!ok) || (item.isEmpty())) {
|
|
return false;
|
|
}
|
|
|
|
archive.reset(Kerfuffle::Archive::create(localFile, mimeTypes.key(item), m_model));
|
|
}
|
|
|
|
if (!archive) {
|
|
KMessageBox::sorry(
|
|
NULL,
|
|
i18nc("@info", "Ark was not able to open the archive <tt>%1</tt>. No plugin capable of handling the file was found.", localFile),
|
|
i18nc("@title:window", "Error Opening Archive")
|
|
);
|
|
return false;
|
|
}
|
|
|
|
KJob *job = m_model->setArchive(archive.take());
|
|
registerJob(job);
|
|
job->start();
|
|
m_infoPanel->setIndex(QModelIndex());
|
|
|
|
if (arguments().metaData()[QLatin1String( "showExtractDialog" )] == QLatin1String( "true" )) {
|
|
QTimer::singleShot(0, this, SLOT(slotExtractFiles()));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Part::saveFile()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool Part::isBusy() const
|
|
{
|
|
return m_busy;
|
|
}
|
|
|
|
void Part::slotLoadingStarted()
|
|
{
|
|
}
|
|
|
|
void Part::slotLoadingFinished(KJob *job)
|
|
{
|
|
if (job->error() != KJob::NoError) {
|
|
if (arguments().metaData()[QLatin1String( "createNewArchive" )] != QLatin1String( "true" )) {
|
|
KMessageBox::sorry(
|
|
NULL,
|
|
i18nc("@info", "Loading the archive <tt>%1</tt> failed with the following error: <i>%2</i>", localFilePath(), job->errorText()),
|
|
i18nc("@title:window", "Error Opening Archive")
|
|
);
|
|
|
|
// The file failed to open, so reset the open archive, info panel and caption.
|
|
m_model->setArchive(NULL);
|
|
|
|
m_infoPanel->setPrettyFileName(QString());
|
|
m_infoPanel->updateWithDefaults();
|
|
|
|
emit setWindowCaption(QString());
|
|
}
|
|
}
|
|
|
|
m_view->sortByColumn(0, Qt::AscendingOrder);
|
|
m_view->expandToDepth(0);
|
|
|
|
// After loading all files, resize the columns to fit all fields
|
|
m_view->header()->resizeSections(QHeaderView::ResizeToContents);
|
|
|
|
updateActions();
|
|
}
|
|
|
|
void Part::setReadyGui()
|
|
{
|
|
QApplication::restoreOverrideCursor();
|
|
m_busy = false;
|
|
m_view->setEnabled(true);
|
|
updateActions();
|
|
}
|
|
|
|
void Part::setBusyGui()
|
|
{
|
|
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
|
m_busy = true;
|
|
m_view->setEnabled(false);
|
|
updateActions();
|
|
}
|
|
|
|
void Part::setFileNameFromArchive()
|
|
{
|
|
const QString prettyName = url().fileName();
|
|
|
|
m_infoPanel->setPrettyFileName(prettyName);
|
|
m_infoPanel->updateWithDefaults();
|
|
|
|
emit setWindowCaption(prettyName);
|
|
}
|
|
|
|
void Part::slotPreviewWithInternalViewer()
|
|
{
|
|
preview(m_view->selectionModel()->currentIndex(), InternalViewer);
|
|
}
|
|
|
|
void Part::slotPreviewWithExternalProgram()
|
|
{
|
|
preview(m_view->selectionModel()->currentIndex(), ExternalProgram);
|
|
}
|
|
|
|
void Part::preview(const QModelIndex &index, PreviewMode mode)
|
|
{
|
|
if (!isPreviewable(index)) {
|
|
return;
|
|
}
|
|
|
|
const ArchiveEntry& entry = m_model->entryForIndex(index);
|
|
|
|
if (!entry.isEmpty()) {
|
|
Kerfuffle::ExtractionOptions options;
|
|
options[QLatin1String( "PreservePaths" )] = true;
|
|
|
|
m_previewDirList.append(new KTempDir);
|
|
m_previewMode = mode;
|
|
ExtractJob *job = m_model->extractFile(entry[InternalID], m_previewDirList.last()->name(), options);
|
|
|
|
registerJob(job);
|
|
connect(job, SIGNAL(result(KJob*)),
|
|
this, SLOT(slotPreviewExtracted(KJob*)));
|
|
job->start();
|
|
}
|
|
}
|
|
|
|
void Part::slotPreviewExtracted(KJob *job)
|
|
{
|
|
// FIXME: the error checking here isn't really working
|
|
// if there's an error or an overwrite dialog,
|
|
// the preview dialog will be launched anyway
|
|
if (!job->error()) {
|
|
const ArchiveEntry& entry = m_model->entryForIndex(m_view->selectionModel()->currentIndex());
|
|
|
|
ExtractJob *extractJob = qobject_cast<ExtractJob*>(job);
|
|
Q_ASSERT(extractJob);
|
|
QString fullName = extractJob->destinationDirectory() + entry[FileName].toString();
|
|
|
|
// Make sure a maliciously crafted archive with parent folders named ".." do
|
|
// not cause the previewed file path to be located outside the temporary
|
|
// directory, resulting in a directory traversal issue.
|
|
fullName.remove(QLatin1String("../"));
|
|
|
|
// TODO: get rid of m_previewMode by extending ExtractJob with a PreviewJob.
|
|
// This would prevent race conditions if we ever stop disabling
|
|
// the whole UI while extracting a file to preview it.
|
|
switch (m_previewMode) {
|
|
case InternalViewer:
|
|
ArkViewer::view(fullName, widget());
|
|
break;
|
|
case ExternalProgram:
|
|
KUrl::List list;
|
|
list.append(KUrl(fullName));
|
|
KRun::displayOpenWithDialog(list, widget(), true);
|
|
break;
|
|
}
|
|
} else {
|
|
KMessageBox::error(widget(), job->errorString());
|
|
}
|
|
setReadyGui();
|
|
}
|
|
|
|
void Part::slotError(const QString& errorMessage, const QString& details)
|
|
{
|
|
if (details.isEmpty()) {
|
|
KMessageBox::error(widget(), errorMessage);
|
|
} else {
|
|
KMessageBox::detailedError(widget(), errorMessage, details);
|
|
}
|
|
}
|
|
|
|
bool Part::isSingleFolderArchive() const
|
|
{
|
|
return m_model->archive()->isSingleFolderArchive();
|
|
}
|
|
|
|
QString Part::detectSubfolder() const
|
|
{
|
|
if (!m_model) {
|
|
return QString();
|
|
}
|
|
|
|
return m_model->archive()->subfolderName();
|
|
}
|
|
|
|
void Part::slotExtractFiles()
|
|
{
|
|
if (!m_model) {
|
|
return;
|
|
}
|
|
|
|
QVariantList files = selectedFilesWithChildren();
|
|
kDebug() << "Selected" << files;
|
|
|
|
QWeakPointer<Kerfuffle::ExtractionDialog> dialog = new Kerfuffle::ExtractionDialog();
|
|
|
|
dialog.data()->setUrl(QFileInfo(m_model->archive()->fileName()).path());
|
|
if (!files.isEmpty()) {
|
|
dialog.data()->selectionModeOption();
|
|
}
|
|
|
|
if (dialog.data()->exec()) {
|
|
//this is done to update the quick extract menu
|
|
updateActions();
|
|
|
|
Kerfuffle::ExtractionOptions options;
|
|
|
|
if (dialog.data()->preservePaths()) {
|
|
options[QLatin1String("PreservePaths")] = true;
|
|
}
|
|
|
|
options[QLatin1String("FollowExtractionDialogSettings")] = true;
|
|
|
|
QString destinationDirectory = dialog.data()->destinationDirectory();
|
|
if (dialog.data()->autoSubfolders()) {
|
|
QString subFolder = detectSubfolder();
|
|
if (!subFolder.isEmpty()) {
|
|
// do what batch extraction does, create the folder or automatically pick other if it
|
|
// exists
|
|
const QDir destDir(destinationDirectory);
|
|
if (destDir.exists(subFolder)) {
|
|
subFolder = KIO::RenameDialog::suggestName(destinationDirectory, subFolder);
|
|
}
|
|
destDir.mkdir(subFolder);
|
|
|
|
if (!destinationDirectory.endsWith(QDir::separator())) {
|
|
destinationDirectory += QDir::separator();
|
|
}
|
|
destinationDirectory += subFolder;
|
|
}
|
|
}
|
|
kDebug() << "Destination" << destinationDirectory;
|
|
|
|
ExtractJob *job = m_model->extractFiles(files, destinationDirectory, options);
|
|
registerJob(job);
|
|
|
|
connect(job, SIGNAL(result(KJob*)),
|
|
this, SLOT(slotExtractionDone(KJob*)));
|
|
|
|
job->start();
|
|
}
|
|
|
|
delete dialog.data();
|
|
}
|
|
|
|
QList<QVariant> Part::selectedFilesWithChildren()
|
|
{
|
|
Q_ASSERT(m_model);
|
|
|
|
QModelIndexList toIterate = m_view->selectionModel()->selectedRows();
|
|
|
|
for (int i = 0; i < toIterate.size(); ++i) {
|
|
QModelIndex index = toIterate.at(i);
|
|
|
|
for (int j = 0; j < m_model->rowCount(index); ++j) {
|
|
QModelIndex child = m_model->index(j, 0, index);
|
|
if (!toIterate.contains(child)) {
|
|
toIterate << child;
|
|
}
|
|
}
|
|
}
|
|
|
|
QVariantList ret;
|
|
foreach(const QModelIndex & index, toIterate) {
|
|
const ArchiveEntry& entry = m_model->entryForIndex(index);
|
|
if (entry.contains(InternalID)) {
|
|
ret << entry[ InternalID ];
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
QList<QVariant> Part::selectedFiles()
|
|
{
|
|
QStringList toSort;
|
|
|
|
foreach(const QModelIndex & index, m_view->selectionModel()->selectedRows()) {
|
|
const ArchiveEntry& entry = m_model->entryForIndex(index);
|
|
toSort << entry[ InternalID ].toString();
|
|
}
|
|
|
|
toSort.sort();
|
|
QVariantList ret;
|
|
foreach(const QString &i, toSort) {
|
|
ret << i;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void Part::slotExtractionDone(KJob* job)
|
|
{
|
|
if (job->error() != KJob::NoError) {
|
|
KMessageBox::error(widget(), job->errorString());
|
|
} else {
|
|
ExtractJob *extractJob = qobject_cast<ExtractJob*>(job);
|
|
Q_ASSERT(extractJob);
|
|
|
|
const bool followExtractionDialogSettings =
|
|
extractJob->extractionOptions().value(QLatin1String("FollowExtractionDialogSettings"), false).toBool();
|
|
if (!followExtractionDialogSettings) {
|
|
return;
|
|
}
|
|
|
|
if (ArkSettings::openDestinationFolderAfterExtraction()) {
|
|
KToolInvocation::self()->startServiceForUrl(extractJob->destinationDirectory(), widget());
|
|
}
|
|
|
|
if (ArkSettings::closeAfterExtraction()) {
|
|
emit quit();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Part::adjustColumns()
|
|
{
|
|
m_view->header()->setResizeMode(0, QHeaderView::ResizeToContents);
|
|
}
|
|
|
|
void Part::slotAddFiles(const QStringList& filesToAdd, const QString& path)
|
|
{
|
|
if (filesToAdd.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
kDebug() << "Adding " << filesToAdd << " to " << path;
|
|
kDebug() << "Warning, for now the path argument is not implemented";
|
|
|
|
QStringList cleanFilesToAdd(filesToAdd);
|
|
for (int i = 0; i < cleanFilesToAdd.size(); ++i) {
|
|
QString& file = cleanFilesToAdd[i];
|
|
if (QFileInfo(file).isDir()) {
|
|
if (!file.endsWith(QLatin1Char( '/' ))) {
|
|
file += QLatin1Char( '/' );
|
|
}
|
|
}
|
|
}
|
|
|
|
CompressionOptions options;
|
|
|
|
QString firstPath = cleanFilesToAdd.first();
|
|
if (firstPath.right(1) == QLatin1String( "/" )) {
|
|
firstPath.chop(1);
|
|
}
|
|
firstPath = QFileInfo(firstPath).dir().absolutePath();
|
|
|
|
kDebug() << "Detected relative path to be " << firstPath;
|
|
options[QLatin1String( "GlobalWorkDir" )] = firstPath;
|
|
|
|
AddJob *job = m_model->addFiles(cleanFilesToAdd, options);
|
|
if (!job) {
|
|
return;
|
|
}
|
|
|
|
connect(job, SIGNAL(result(KJob*)), this, SLOT(slotAddFilesDone(KJob*)));
|
|
registerJob(job);
|
|
job->start();
|
|
}
|
|
|
|
void Part::slotAddFiles()
|
|
{
|
|
// #264819: passing widget() as the parent will not work as expected.
|
|
// KFileDialog will create a KFileWidget, which runs an internal
|
|
// event loop to stat the given directory. This, in turn, leads to
|
|
// events being delivered to widget(), which is a QSplitter, which
|
|
// in turn reimplements childEvent() and will end up calling
|
|
// QWidget::show() on the KFileDialog (thus showing it in a
|
|
// non-modal state).
|
|
// When KFileDialog::exec() is called, the widget is already shown
|
|
// and nothing happens.
|
|
const QStringList filesToAdd = KFileDialog::getOpenFileNames(
|
|
KUrl("kfiledialog:///ArkAddFiles"),
|
|
QString(), widget()->parentWidget(),
|
|
i18nc("@title:window", "Add Files")
|
|
);
|
|
|
|
slotAddFiles(filesToAdd);
|
|
}
|
|
|
|
void Part::slotAddDir()
|
|
{
|
|
const QString dirToAdd = KFileDialog::getExistingDirectory(
|
|
KUrl("kfiledialog:///ArkAddFiles"),
|
|
widget(),
|
|
i18nc("@title:window", "Add Folder")
|
|
);
|
|
|
|
if (!dirToAdd.isEmpty()) {
|
|
slotAddFiles(QStringList() << dirToAdd);
|
|
}
|
|
}
|
|
|
|
void Part::slotAddFilesDone(KJob* job)
|
|
{
|
|
if (job->error() != KJob::NoError) {
|
|
KMessageBox::error(widget(), job->errorString());
|
|
} else {
|
|
setModified();
|
|
}
|
|
}
|
|
|
|
void Part::slotDeleteFilesDone(KJob* job)
|
|
{
|
|
if (job->error() != KJob::NoError) {
|
|
KMessageBox::error(widget(), job->errorString());
|
|
} else {
|
|
setModified();
|
|
}
|
|
}
|
|
|
|
void Part::slotDeleteFiles()
|
|
{
|
|
const int reallyDelete = KMessageBox::questionYesNo(
|
|
NULL,
|
|
i18n("Deleting these files is not undoable. Are you sure you want to do this?"),
|
|
i18nc("@title:window", "Delete files"),
|
|
KStandardGuiItem::del(),
|
|
KStandardGuiItem::cancel(),
|
|
QString(),
|
|
KMessageBox::Dangerous | KMessageBox::Notify
|
|
);
|
|
|
|
if (reallyDelete == KMessageBox::No) {
|
|
return;
|
|
}
|
|
|
|
DeleteJob *job = m_model->deleteFiles(selectedFilesWithChildren());
|
|
connect(job, SIGNAL(result(KJob*)), this, SLOT(slotDeleteFilesDone(KJob*)));
|
|
registerJob(job);
|
|
job->start();
|
|
}
|
|
|
|
void Part::slotToggleInfoPanel(bool visible)
|
|
{
|
|
QList<int> splitterSizes;
|
|
|
|
if (visible) {
|
|
splitterSizes = ArkSettings::splitterSizesWithBothWidgets();
|
|
} else {
|
|
splitterSizes = m_splitter->sizes();
|
|
ArkSettings::setSplitterSizesWithBothWidgets(splitterSizes);
|
|
splitterSizes[1] = 0;
|
|
}
|
|
|
|
m_splitter->setSizes(splitterSizes);
|
|
saveSplitterSizes();
|
|
}
|
|
|
|
void Part::saveSplitterSizes()
|
|
{
|
|
ArkSettings::setSplitterSizes(m_splitter->sizes());
|
|
ArkSettings::self()->writeConfig();
|
|
}
|
|
|
|
void Part::slotSaveAs()
|
|
{
|
|
KUrl saveUrl = KFileDialog::getSaveUrl(
|
|
KUrl(QLatin1String( "kfiledialog:///ArkSaveAs/" ) + url().fileName()),
|
|
QString(),
|
|
widget()
|
|
);
|
|
|
|
if ((saveUrl.isValid()) && (!saveUrl.isEmpty())) {
|
|
if (KIO::NetAccess::exists(saveUrl, KIO::NetAccess::DestinationSide, widget())) {
|
|
int overwrite = KMessageBox::warningContinueCancel(
|
|
widget(),
|
|
i18nc("@info", "An archive named <tt>%1</tt> already exists. Are you sure you want to overwrite it?", saveUrl.fileName()),
|
|
QString(),
|
|
KStandardGuiItem::overwrite()
|
|
);
|
|
if (overwrite != KMessageBox::Continue) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
KUrl srcUrl = KUrl::fromPath(localFilePath());
|
|
|
|
if (!QFile::exists(localFilePath())) {
|
|
if (url().isLocalFile()) {
|
|
KMessageBox::error(
|
|
widget(),
|
|
i18nc("@info", "The archive <tt>%1</tt> cannot be copied to the specified location. The archive does not exist anymore.", localFilePath())
|
|
);
|
|
return;
|
|
} else {
|
|
srcUrl = url();
|
|
}
|
|
}
|
|
|
|
KIO::Job *copyJob = KIO::file_copy(srcUrl, saveUrl, -1, KIO::Overwrite);
|
|
|
|
if (!KIO::NetAccess::synchronousRun(copyJob, widget())) {
|
|
KMessageBox::error(
|
|
widget(),
|
|
i18nc("@info", "The archive could not be saved as <tt>%1</tt>. Try saving it to another location.", saveUrl.pathOrUrl())
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Part::slotShowContextMenu()
|
|
{
|
|
if (!factory()) {
|
|
return;
|
|
}
|
|
|
|
KMenu *popup = static_cast<KMenu *>(factory()->container(QLatin1String("context_menu"), this));
|
|
popup->popup(QCursor::pos());
|
|
}
|
|
|
|
} // namespace Ark
|