Commit 04d56afa authored by Ragnar Thomsen's avatar Ragnar Thomsen

Add support for editing/adding archive comments

Support was added for editing comments in supported archive types
(currently only RAR). A new bool was added to plugin json files
("SupportsWriteComment") to indicate support. A new action was added and
is found in Archive menu. The editing of comment is done in the same
QPlainTextEdit used to display comments before. When user modifies
comment, a KMessageWidget pops up with a "Save" button. Actual saving of
comment to archive is achieved by a new job type: CommentJob.

FEATURE: 357594
FIXED-IN: 16.08.0
GUI:
Differential Revision: D1493
parent a2a17102
......@@ -103,4 +103,8 @@ bool JSONArchiveInterface::deleteFiles(const QList<QVariant>& files)
return true;
}
bool JSONArchiveInterface::addComment(const QString& comment)
{
Q_UNUSED(comment)
return true;
}
......@@ -58,6 +58,7 @@ public:
virtual bool addFiles(const QStringList& files, const Kerfuffle::CompressionOptions& options) Q_DECL_OVERRIDE;
virtual bool copyFiles(const QList<QVariant>& files, const QString& destinationDirectory, const Kerfuffle::ExtractionOptions& options) Q_DECL_OVERRIDE;
virtual bool deleteFiles(const QList<QVariant>& files) Q_DECL_OVERRIDE;
virtual bool addComment(const QString& comment) Q_DECL_OVERRIDE;
private:
JSONParser::JSONArchive m_archive;
......
......@@ -169,6 +169,18 @@ QString Archive::comment() const
return isValid() ? m_iface->comment() : QString();
}
CommentJob* Archive::addComment(const QString &comment)
{
if (!isValid()) {
return Q_NULLPTR;
}
qCDebug(ARK) << "Going to add comment:" << comment;
Q_ASSERT(!isReadOnly());
CommentJob *job = new CommentJob(comment, static_cast<ReadWriteArchiveInterface*>(m_iface), this);
return job;
}
QMimeType Archive::mimeType() const
{
return isValid() ? determineMimeType(fileName()) : QMimeType();
......
......@@ -45,6 +45,7 @@ class ListJob;
class ExtractJob;
class DeleteJob;
class AddJob;
class CommentJob;
class Plugin;
class Query;
class ReadOnlyArchiveInterface;
......@@ -198,6 +199,7 @@ public:
ListJob* list();
DeleteJob* deleteFiles(const QList<QVariant> & files);
CommentJob* addComment(const QString &comment);
/**
* Compression options that should be handled by all interfaces:
......
......@@ -37,12 +37,14 @@ ArchiveFormat::ArchiveFormat(const QMimeType& mimeType,
Archive::EncryptionType encryptionType,
int minCompLevel,
int maxCompLevel,
int defaultCompLevel) :
int defaultCompLevel,
bool supportsWriteComment) :
m_mimeType(mimeType),
m_encryptionType(encryptionType),
m_minCompressionLevel(minCompLevel),
m_maxCompressionLevel(maxCompLevel),
m_defaultCompressionLevel(defaultCompLevel)
m_defaultCompressionLevel(defaultCompLevel),
m_supportsWriteComment(supportsWriteComment)
{
}
......@@ -60,6 +62,8 @@ ArchiveFormat ArchiveFormat::fromMetadata(const QMimeType& mimeType, const KPlug
int maxCompLevel = formatProps[QStringLiteral("CompressionLevelMax")].toInt();
int defaultCompLevel = formatProps[QStringLiteral("CompressionLevelDefault")].toInt();
bool supportsWriteComment = formatProps[QStringLiteral("SupportsWriteComment")].toBool();
Archive::EncryptionType encType = Archive::Unencrypted;
if (formatProps[QStringLiteral("HeaderEncryption")].toBool()) {
encType = Archive::HeaderEncrypted;
......@@ -67,7 +71,7 @@ ArchiveFormat ArchiveFormat::fromMetadata(const QMimeType& mimeType, const KPlug
encType = Archive::Encrypted;
}
return ArchiveFormat(mimeType, encType, minCompLevel, maxCompLevel, defaultCompLevel);
return ArchiveFormat(mimeType, encType, minCompLevel, maxCompLevel, defaultCompLevel, supportsWriteComment);
}
return ArchiveFormat();
......@@ -98,4 +102,9 @@ int ArchiveFormat::defaultCompressionLevel() const
return m_defaultCompressionLevel;
}
bool ArchiveFormat::supportsWriteComment() const
{
return m_supportsWriteComment;
}
}
......@@ -41,7 +41,8 @@ public:
Kerfuffle::Archive::EncryptionType encryptionType,
int minCompLevel,
int maxCompLevel,
int defaultCompLevel);
int defaultCompLevel,
bool supportsWriteComment);
/**
* @return The archive format of the given @p mimeType, according to the given @p metadata.
......@@ -61,6 +62,7 @@ public:
int minCompressionLevel() const;
int maxCompressionLevel() const;
int defaultCompressionLevel() const;
bool supportsWriteComment() const;
private:
QMimeType m_mimeType;
......@@ -68,6 +70,7 @@ private:
int m_minCompressionLevel;
int m_maxCompressionLevel;
int m_defaultCompressionLevel;
bool m_supportsWriteComment;
};
}
......
......@@ -148,6 +148,7 @@ public:
//contain
virtual bool addFiles(const QStringList & files, const CompressionOptions& options) = 0;
virtual bool deleteFiles(const QList<QVariant> & files) = 0;
virtual bool addComment(const QString &comment) = 0;
};
} // namespace Kerfuffle
......
......@@ -50,6 +50,7 @@
#include <QRegularExpression>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <QThread>
#include <QTimer>
#include <QUrl>
......@@ -61,7 +62,8 @@ CliInterface::CliInterface(QObject *parent, const QVariantList & args)
m_process(0),
m_listEmptyLines(false),
m_abortingOperation(false),
m_extractTempDir(Q_NULLPTR)
m_extractTempDir(Q_NULLPTR),
m_commentTempFile(Q_NULLPTR)
{
//because this interface uses the event loop
setWaitForFinishedSignal(true);
......@@ -84,6 +86,7 @@ void CliInterface::cacheParameterList()
CliInterface::~CliInterface()
{
Q_ASSERT(!m_process);
delete m_commentTempFile;
}
bool CliInterface::isCliBased() const
......@@ -676,6 +679,36 @@ QStringList CliInterface::substituteAddVariables(const QStringList &addArgs, con
return args;
}
QStringList CliInterface::substituteCommentVariables(const QStringList &commentArgs, const QString &commentFile)
{
// Required if we call this function from unit tests.
cacheParameterList();
QStringList args;
foreach (const QString& arg, commentArgs) {
qCDebug(ARK) << "Processing argument " << arg;
if (arg == QLatin1String("$Archive")) {
args << filename();
continue;
}
if (arg == QLatin1String("$CommentSwitch")) {
QString commentSwitch = m_param.value(CommentSwitch).toString();
commentSwitch.replace(QStringLiteral("$CommentFile"), commentFile);
args << commentSwitch;
continue;
}
args << arg;
}
// Remove empty strings, if any.
args.removeAll(QString());
return args;
}
QString CliInterface::preservePathSwitch(bool preservePaths) const
{
Q_ASSERT(m_param.contains(PreservePathSwitch));
......@@ -1138,6 +1171,33 @@ void CliInterface::writeToProcess(const QByteArray& data)
#endif
}
}
bool CliInterface::addComment(const QString &comment)
{
cacheParameterList();
m_operationMode = Comment;
m_commentTempFile = new QTemporaryFile;
if (!m_commentTempFile->open()) {
qCWarning(ARK) << "Failed to create temporary file for comment";
failOperation();
emit finished(false);
return false;
}
QTextStream stream(m_commentTempFile);
stream << comment << endl;
m_commentTempFile->close();
const auto args = substituteCommentVariables(m_param.value(CommentArgs).toStringList(),
m_commentTempFile->fileName());
if (!runProcess(m_param.value(AddProgram).toStringList(), args)) {
failOperation();
return false;
}
m_comment = comment;
return true;
}
}
......@@ -39,6 +39,7 @@ class KPtyProcess;
class QDir;
class QTemporaryDir;
class QTemporaryFile;
namespace Kerfuffle
{
......@@ -255,6 +256,22 @@ enum CliInterfaceParameters {
* Example (rar plugin): ("-hp$Password")
*/
PasswordHeaderSwitch,
///////////////[ COMMENT ]/////////////
/**
* QStringList
* The arguments that are passed to AddProgram when adding
* a comment.
*/
CommentArgs,
/**
* QString
* The variable $CommentFile will be substituted for the file
* containing the comment.
* Example (rar plugin): -z$CommentFile
*/
CommentSwitch
};
typedef QHash<int, QVariant> ParameterList;
......@@ -265,7 +282,7 @@ class KERFUFFLE_EXPORT CliInterface : public ReadWriteArchiveInterface
public:
enum OperationMode {
List, Copy, Add, Delete
List, Copy, Add, Delete, Comment
};
OperationMode m_operationMode;
......@@ -276,6 +293,7 @@ public:
virtual bool copyFiles(const QList<QVariant>& files, const QString& destinationDirectory, const ExtractionOptions& options) Q_DECL_OVERRIDE;
virtual bool addFiles(const QStringList & files, const CompressionOptions& options) Q_DECL_OVERRIDE;
virtual bool deleteFiles(const QList<QVariant> & files) Q_DECL_OVERRIDE;
virtual bool addComment(const QString &comment) Q_DECL_OVERRIDE;
virtual void resetParsing() = 0;
virtual ParameterList parameterList() const = 0;
......@@ -318,6 +336,7 @@ public:
QStringList substituteListVariables(const QStringList &listArgs, const QString &password);
QStringList substituteCopyVariables(const QStringList &extractArgs, const QVariantList &files, bool preservePaths, const QString &password, const QString &rootNode);
QStringList substituteAddVariables(const QStringList &addArgs, const QStringList &files, const QString &password, bool encryptHeader, int compLevel);
QStringList substituteCommentVariables(const QStringList &commentArgs, const QString &commentFile);
/**
* @return The preserve path switch, according to the @p preservePaths extraction option.
......@@ -442,6 +461,7 @@ private:
QString m_oldWorkingDir;
QString m_extractDestDir;
QTemporaryDir *m_extractTempDir;
QTemporaryFile *m_commentTempFile;
QVariantList m_copiedFiles;
private slots:
......
......@@ -417,6 +417,29 @@ void DeleteJob::doWork()
}
}
CommentJob::CommentJob(const QString& comment, ReadWriteArchiveInterface *interface, QObject *parent)
: Job(interface, parent)
, m_comment(comment)
{
}
void CommentJob::doWork()
{
emit description(this, i18n("Adding comment"));
ReadWriteArchiveInterface *m_writeInterface =
qobject_cast<ReadWriteArchiveInterface*>(archiveInterface());
Q_ASSERT(m_writeInterface);
connectToArchiveInterfaceSignals();
bool ret = m_writeInterface->addComment(m_comment);
if (!archiveInterface()->waitForFinishedSignal()) {
onFinished(ret);
}
}
} // namespace Kerfuffle
......@@ -173,6 +173,22 @@ private:
QVariantList m_files;
};
class KERFUFFLE_EXPORT CommentJob : public Job
{
Q_OBJECT
public:
CommentJob(const QString& comment, ReadWriteArchiveInterface *interface, QObject *parent = 0);
public slots:
virtual void doWork() Q_DECL_OVERRIDE;
private:
QString m_comment;
};
} // namespace Kerfuffle
#endif // JOBS_H
<!DOCTYPE kpartgui>
<kpartgui name="ark_part" version="12">
<kpartgui name="ark_part" version="13">
<MenuBar>
<Menu name="archive">
<text>&amp;Archive</text>
<Action name="ark_file_save_as" group="file_save"/>
<Action name="add" group="archive_edit"/>
<Action name="add-dir" group="archive_edit"/>
<Action name="extract_all" group="archive_extract"/>
<Action name="edit_comment" group="archive_edit"/>
<Action name="extract_all" group="archive_extract"/>
<Action name="properties" group="archive_props"/>
</Menu>
<Menu name="ark_file">
......
......@@ -23,6 +23,7 @@
#include "part.h"
#include "ark_debug.h"
#include "archiveformat.h"
#include "archivemodel.h"
#include "archiveview.h"
#include "arkviewer.h"
......@@ -36,6 +37,7 @@
#include "kerfuffle/settings.h"
#include "kerfuffle/previewsettingspage.h"
#include "kerfuffle/propertiesdialog.h"
#include "pluginmanager.h"
#include <KAboutData>
#include <KActionCollection>
......@@ -114,6 +116,20 @@ Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList& args)
vbox->addWidget(m_commentView);
m_commentBox->setLayout(vbox);
m_commentMsgWidget = new KMessageWidget();
m_commentMsgWidget->setText(i18n("Comment has been modified."));
m_commentMsgWidget->setMessageType(KMessageWidget::Information);
m_commentMsgWidget->setCloseButtonVisible(false);
m_commentMsgWidget->hide();
QAction *saveAction = new QAction(i18n("Save"), m_commentMsgWidget);
m_commentMsgWidget->addAction(saveAction);
connect(saveAction, &QAction::triggered, this, &Part::slotAddComment);
m_commentBox->layout()->addWidget(m_commentMsgWidget);
connect(m_commentView, &QPlainTextEdit::textChanged, this, &Part::slotCommentChanged);
setWidget(mainWidget);
mainWidget->setLayout(m_vlayout);
......@@ -179,6 +195,15 @@ Part::~Part()
m_extractAction->menu()->deleteLater();
}
void Part::slotCommentChanged()
{
if (m_commentMsgWidget->isHidden() && m_commentView->toPlainText() != m_model->archive()->comment()) {
m_commentMsgWidget->animatedShow();
} else if (m_commentMsgWidget->isVisible() && m_commentView->toPlainText() == m_model->archive()->comment()) {
m_commentMsgWidget->hide();
}
}
KAboutData *Part::createAboutData()
{
return new KAboutData(QStringLiteral("ark"),
......@@ -359,6 +384,13 @@ void Part::setupActions()
connect(m_propertiesAction, &QAction::triggered,
this, &Part::slotShowProperties);
m_editCommentAction = actionCollection()->addAction(QStringLiteral("edit_comment"));
m_editCommentAction->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
m_editCommentAction->setText(i18nc("@action:inmenu", "&Edit Comment"));
actionCollection()->setDefaultShortcut(m_editCommentAction, Qt::ALT + Qt::Key_C);
m_editCommentAction->setToolTip(i18nc("@info:tooltip", "Click to add or edit comment"));
connect(m_editCommentAction, &QAction::triggered, this, &Part::slotShowComment);
connect(m_signalMapper, SIGNAL(mapped(int)), this, SLOT(slotOpenEntry(int)));
updateActions();
......@@ -405,6 +437,44 @@ void Part::updateActions()
(selectedEntriesCount == 1));
m_propertiesAction->setEnabled(!isBusy() &&
m_model->archive());
m_commentView->setEnabled(!isBusy());
m_commentMsgWidget->setEnabled(!isBusy());
if (m_model->archive()) {
const KPluginMetaData metadata = PluginManager().preferredPluginFor(m_model->archive()->mimeType())->metaData();
bool supportsWriteComment = ArchiveFormat::fromMetadata(m_model->archive()->mimeType(), metadata).supportsWriteComment();
m_editCommentAction->setEnabled(!isBusy() &&
supportsWriteComment);
m_commentView->setReadOnly(!supportsWriteComment);
m_model->archive()->comment().isEmpty() ? m_editCommentAction->setText(i18nc("@action:inmenu", "Add &Comment")) : m_editCommentAction->setText(i18nc("@action:inmenu", "Edit &Comment"));
} else {
m_editCommentAction->setEnabled(false);
m_commentView->setReadOnly(true);
}
}
void Part::slotShowComment()
{
if (!m_commentBox->isVisible()) {
m_commentBox->show();
m_commentSplitter->setSizes(QList<int>() << m_view->height() * 0.6 << 1);
}
m_commentView->setFocus();
}
void Part::slotAddComment()
{
CommentJob *job = m_model->archive()->addComment(m_commentView->toPlainText());
if (!job) {
return;
}
registerJob(job);
job->start();
m_commentMsgWidget->hide();
if (m_commentView->toPlainText().isEmpty()) {
m_commentBox->hide();
}
}
void Part::updateQuickExtractMenu(QAction *extractAction)
......@@ -675,8 +745,7 @@ void Part::slotLoadingFinished(KJob *job)
if (!m_model->archive()->comment().isEmpty()) {
m_commentView->setPlainText(m_model->archive()->comment());
m_commentBox->show();
m_commentSplitter->setSizes(QList<int>() << m_view->height() * 0.6 << 1);
slotShowComment();
} else {
m_commentView->clear();
m_commentBox->hide();
......
......@@ -121,6 +121,9 @@ private slots:
void setReadyGui();
void setFileNameFromArchive();
void slotWatchedFileModified(const QString& file);
void slotShowComment();
void slotAddComment();
void slotCommentChanged();
signals:
void busy();
......@@ -150,6 +153,7 @@ private:
QAction *m_deleteFilesAction;
QAction *m_saveAsAction;
QAction *m_propertiesAction;
QAction *m_editCommentAction;
KToggleAction *m_showInfoPanelAction;
InfoPanel *m_infoPanel;
QSplitter *m_splitter;
......@@ -165,6 +169,7 @@ private:
QSplitter *m_commentSplitter;
QGroupBox *m_commentBox;
QPlainTextEdit *m_commentView;
KMessageWidget *m_commentMsgWidget;
};
} // namespace Ark
......
......@@ -122,6 +122,10 @@ ParameterList CliPlugin::parameterList() const
p[CorruptArchivePatterns] = QStringList() << QStringLiteral("Unexpected end of archive")
<< QStringLiteral("the file header is corrupt");
p[DiskFullPatterns] = QStringList() << QStringLiteral("No space left on device");
p[CommentArgs] = QStringList() << QStringLiteral("c")
<< QStringLiteral("$CommentSwitch")
<< QStringLiteral("$Archive");
p[CommentSwitch] = QStringLiteral("-z$CommentFile");
}
return p;
......
......@@ -62,7 +62,8 @@
"application/x-rar": {
"CompressionLevelDefault": 3,
"CompressionLevelMax": 5,
"CompressionLevelMin": 0,
"CompressionLevelMin": 0,
"SupportsWriteComment": true,
"HeaderEncryption": true
}
}
\ No newline at end of file
}
......@@ -129,6 +129,12 @@ bool LibarchivePlugin::deleteFiles(const QList<QVariant> &files)
return false;
}
bool LibarchivePlugin::addComment(const QString& comment)
{
Q_UNUSED(comment)
return false;
}
bool LibarchivePlugin::doKill()
{
m_abortOperation = true;
......
......@@ -49,6 +49,7 @@ public:
virtual bool addFiles(const QStringList& files, const CompressionOptions& options) Q_DECL_OVERRIDE;
virtual bool deleteFiles(const QList<QVariant>& files) Q_DECL_OVERRIDE;
virtual bool addComment(const QString& comment) Q_DECL_OVERRIDE;
protected:
void emitEntryFromArchiveEntry(struct archive_entry *entry);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment