From c17df85bda8938ac47b7dfbffe2c6ee531de7fad Mon Sep 17 00:00:00 2001 From: Philippe Mavridis Date: Tue, 24 Jun 2025 16:52:05 +0300 Subject: [PATCH] Add basic extended attributes support This commit adds extended attributes support to TDEIO, tdeio_file and a read-write plugin for the file properties dialog. Signed-off-by: Philippe Mavridis --- tdecore/kprotocolinfo_tdecore.cpp | 19 +- tdeio/tdefile/kpropertiesdialog.cpp | 491 ++++++++++++++++++++++++++-- tdeio/tdefile/kpropertiesdialog.h | 79 +++-- tdeio/tdeio/CMakeLists.txt | 4 +- tdeio/tdeio/global.cpp | 23 +- tdeio/tdeio/global.h | 10 +- tdeio/tdeio/job.cpp | 106 ++++++ tdeio/tdeio/job.h | 52 +++ tdeio/tdeio/jobclasses.h | 66 ++++ tdeio/tdeio/kprotocolinfo.cpp | 18 + tdeio/tdeio/kprotocolinfo.h | 26 ++ tdeio/tdeio/slavebase.cpp | 37 ++- tdeio/tdeio/slavebase.h | 32 ++ tdeio/tdeio/tdexattr.cpp | 355 ++++++++++++++++++++ tdeio/tdeio/tdexattr.h | 96 ++++++ tdeioslave/file/file.cpp | 62 +++- tdeioslave/file/file.h | 8 +- tdeioslave/file/file.protocol | 2 + 18 files changed, 1415 insertions(+), 71 deletions(-) create mode 100644 tdeio/tdeio/tdexattr.cpp create mode 100644 tdeio/tdeio/tdexattr.h diff --git a/tdecore/kprotocolinfo_tdecore.cpp b/tdecore/kprotocolinfo_tdecore.cpp index 3d08cccd5..1ce9f4e3a 100644 --- a/tdecore/kprotocolinfo_tdecore.cpp +++ b/tdecore/kprotocolinfo_tdecore.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -71,6 +70,8 @@ KProtocolInfo::KProtocolInfo(const TQString &path) m_supportsDeleting = config.readBoolEntry( "deleting", false ); m_supportsLinking = config.readBoolEntry( "linking", false ); m_supportsMoving = config.readBoolEntry( "moving", false ); + m_supportsReadingAttrs = config.readBoolEntry( "readattr", false ); + m_supportsWritingAttrs = config.readBoolEntry( "writeattr", false ); m_canCopyFromFile = config.readBoolEntry( "copyFromFile", false ); m_canCopyToFile = config.readBoolEntry( "copyToFile", false ); d->canRenameFromFile = config.readBoolEntry( "renameFromFile", false ); @@ -159,7 +160,8 @@ KProtocolInfo::load( TQDataStream& _str) i_supportsMoving, i_determineMimetypeFromExtension, i_canCopyFromFile, i_canCopyToFile, i_showPreviews, i_uriMode, i_canRenameFromFile, i_canRenameToFile, - i_canDeleteRecursive, i_fileNameUsedForCopying; + i_canDeleteRecursive, i_fileNameUsedForCopying, + i_supportsReadingAttrs, i_supportsWritingAttrs; _str >> m_name >> m_exec >> m_listing >> m_defaultMimetype >> i_determineMimetypeFromExtension @@ -175,7 +177,8 @@ KProtocolInfo::load( TQDataStream& _str) >> d->extraFields >> i_showPreviews >> i_uriMode >> d->capabilities >> d->proxyProtocol >> i_canRenameFromFile >> i_canRenameToFile - >> i_canDeleteRecursive >> i_fileNameUsedForCopying; + >> i_canDeleteRecursive >> i_fileNameUsedForCopying + >> i_supportsReadingAttrs >> i_supportsWritingAttrs; m_inputType = (Type) i_inputType; m_outputType = (Type) i_outputType; @@ -188,6 +191,8 @@ KProtocolInfo::load( TQDataStream& _str) m_supportsDeleting = (i_supportsDeleting != 0); m_supportsLinking = (i_supportsLinking != 0); m_supportsMoving = (i_supportsMoving != 0); + m_supportsReadingAttrs = (i_supportsReadingAttrs != 0); + m_supportsWritingAttrs = (i_supportsWritingAttrs != 0); m_canCopyFromFile = (i_canCopyFromFile != 0); m_canCopyToFile = (i_canCopyToFile != 0); d->canRenameFromFile = (i_canRenameFromFile != 0); @@ -214,7 +219,8 @@ KProtocolInfo::save( TQDataStream& _str) i_supportsMoving, i_determineMimetypeFromExtension, i_canCopyFromFile, i_canCopyToFile, i_showPreviews, i_uriMode, i_canRenameFromFile, i_canRenameToFile, - i_canDeleteRecursive, i_fileNameUsedForCopying; + i_canDeleteRecursive, i_fileNameUsedForCopying, + i_supportsReadingAttrs, i_supportsWritingAttrs; i_inputType = (TQ_INT32) m_inputType; i_outputType = (TQ_INT32) m_outputType; @@ -227,6 +233,8 @@ KProtocolInfo::save( TQDataStream& _str) i_supportsDeleting = m_supportsDeleting ? 1 : 0; i_supportsLinking = m_supportsLinking ? 1 : 0; i_supportsMoving = m_supportsMoving ? 1 : 0; + i_supportsReadingAttrs = m_supportsReadingAttrs ? 1 : 0; + i_supportsWritingAttrs = m_supportsWritingAttrs ? 1 : 0; i_canCopyFromFile = m_canCopyFromFile ? 1 : 0; i_canCopyToFile = m_canCopyToFile ? 1 : 0; i_canRenameFromFile = d->canRenameFromFile ? 1 : 0; @@ -251,7 +259,8 @@ KProtocolInfo::save( TQDataStream& _str) << d->extraFields << i_showPreviews << i_uriMode << d->capabilities << d->proxyProtocol << i_canRenameFromFile << i_canRenameToFile - << i_canDeleteRecursive << i_fileNameUsedForCopying; + << i_canDeleteRecursive << i_fileNameUsedForCopying + << i_supportsReadingAttrs << i_supportsWritingAttrs; } diff --git a/tdeio/tdefile/kpropertiesdialog.cpp b/tdeio/tdefile/kpropertiesdialog.cpp index c8e324a2d..720579346 100644 --- a/tdeio/tdefile/kpropertiesdialog.cpp +++ b/tdeio/tdefile/kpropertiesdialog.cpp @@ -124,6 +124,7 @@ extern "C" { #include #include #include +#include #include "tdefilesharedlg.h" #include "kpropertiesdesktopbase.h" @@ -139,6 +140,17 @@ extern "C" { # include #endif +// This KDE3-era HACK ensures that TDEIO jobs are properly terminated in the +// applyChanges() slots. +void tqt_enter_modal( TQWidget *widget ); +void tqt_leave_modal( TQWidget *widget ); +#define WAIT_FOR_JOB \ + TQWidget dummy(0,0,(WFlags)(WType_Dialog|WShowModal)); \ + tqt_enter_modal(&dummy); \ + tqApp->enter_loop(); \ + tqt_leave_modal(&dummy); +#define JOB_DONE tqApp->exit_loop(); + static TQString nameFromFileName(TQString nameStr) { if ( nameStr.endsWith(".desktop") ) @@ -387,6 +399,7 @@ bool KPropertiesDialog::canDisplay( KFileItemList _items ) KBindingPropsPlugin::supports( _items ) || KURLPropsPlugin::supports( _items ) || KDevicePropsPlugin::supports( _items ) || + TDEAttrPropsPlugin::supports(_items) || KFileMetaPropsPlugin::supports( _items ) || KPreviewPropsPlugin::supports( _items ); } @@ -486,6 +499,12 @@ void KPropertiesDialog::insertPages() insertPlugin (p); } + if (TDEAttrPropsPlugin::supports(m_items)) + { + TDEAttrPropsPlugin *p = new TDEAttrPropsPlugin(this); + insertPlugin(p); + } + if ( KFileMetaPropsPlugin::supports( m_items ) ) { KPropsDlgPlugin *p = new KFileMetaPropsPlugin( this ); @@ -1324,10 +1343,6 @@ bool KFilePropsPlugin::supports( KFileItemList /*_items*/ ) return true; } -// Don't do this at home -void tqt_enter_modal( TQWidget *widget ); -void tqt_leave_modal( TQWidget *widget ); - void KFilePropsPlugin::applyChanges() { if ( d->dirSizeJob ) { @@ -1383,11 +1398,7 @@ void KFilePropsPlugin::applyChanges() TQ_SLOT( slotCopyFinished( TDEIO::Job * ) ) ); connect( job, TQ_SIGNAL( renamed( TDEIO::Job *, const KURL &, const KURL & ) ), TQ_SLOT( slotFileRenamed( TDEIO::Job *, const KURL &, const KURL & ) ) ); - // wait for job - TQWidget dummy(0,0,(WFlags)(WType_Dialog|WShowModal)); - tqt_enter_modal(&dummy); - tqApp->enter_loop(); - tqt_leave_modal(&dummy); + WAIT_FOR_JOB; return; } properties->updateUrl(properties->kurl()); @@ -1406,8 +1417,7 @@ void KFilePropsPlugin::slotCopyFinished( TDEIO::Job * job ) kdDebug(250) << "KFilePropsPlugin::slotCopyFinished" << endl; if (job) { - // allow apply() to return - tqApp->exit_loop(); + JOB_DONE; if ( job->error() ) { job->showErrorDialog( d->m_frame ); @@ -2555,11 +2565,7 @@ void KFilePermissionsPropsPlugin::applyChanges() connect( job, TQ_SIGNAL( result( TDEIO::Job * ) ), TQ_SLOT( slotChmodResult( TDEIO::Job * ) ) ); - // Wait for job - TQWidget dummy(0,0,(WFlags)(WType_Dialog|WShowModal)); - tqt_enter_modal(&dummy); - tqApp->enter_loop(); - tqt_leave_modal(&dummy); + WAIT_FOR_JOB; } if (dirs.count() > 0) { job = TDEIO::chmod( dirs, orDirPermissions, ~andDirPermissions, @@ -2571,11 +2577,7 @@ void KFilePermissionsPropsPlugin::applyChanges() connect( job, TQ_SIGNAL( result( TDEIO::Job * ) ), TQ_SLOT( slotChmodResult( TDEIO::Job * ) ) ); - // Wait for job - TQWidget dummy(0,0,(WFlags)(WType_Dialog|WShowModal)); - tqt_enter_modal(&dummy); - tqApp->enter_loop(); - tqt_leave_modal(&dummy); + WAIT_FOR_JOB; } } @@ -2584,8 +2586,7 @@ void KFilePermissionsPropsPlugin::slotChmodResult( TDEIO::Job * job ) kdDebug(250) << "KFilePermissionsPropsPlugin::slotChmodResult" << endl; if (job->error()) job->showErrorDialog( d->m_frame ); - // allow apply() to return - tqApp->exit_loop(); + JOB_DONE } @@ -2861,6 +2862,450 @@ void KBindingPropsPlugin::applyChanges() config.sync(); } +/* ---------------------------------------------------- + * + * TDEAttrPropsPlugin + * + * -------------------------------------------------- */ + +class TDEAttrPropsPlugin::TDEAttrEntry : public TQHBox +{ + friend class TDEAttrPropsPlugin; + public: + TDEAttrEntry(TQWidget *parent) + : TQHBox(parent) + { + m_combo = new TQComboBox(this); + m_combo->setEditable(true); + m_combo->setDuplicatesEnabled(false); + m_combo->lineEdit()->setMaxLength(255); + m_combo->setSizePolicy(TQSizePolicy::MinimumExpanding, TQSizePolicy::Fixed); + + m_value = new TQLineEdit(this); + m_value->setMaxLength(255); + m_value->setSizePolicy(TQSizePolicy::MinimumExpanding, TQSizePolicy::Fixed); + + m_delete = new TQPushButton(this); + m_delete->setPixmap(SmallIcon("edittrash")); + m_delete->setSizePolicy(TQSizePolicy::Maximum, TQSizePolicy::Expanding); + + setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Fixed); + } + + TQCString key() { return m_combo->currentText().local8Bit(); } + void setKey(const TQCString& key) + { + m_combo->setCurrentText(TQString::fromLocal8Bit(key)); + } + + TQCString value() { return m_value->text().local8Bit(); } + void setValue(const TQCString& value) + { + m_value->setText(TQString::fromLocal8Bit(value)); + } + + void setReadOnly(bool ro) + { + m_combo->setDisabled(ro); + m_value->setReadOnly(ro); + m_delete->setDisabled(ro); + } + + ~TDEAttrEntry() {} + + private: + TQComboBox *m_combo; + TQLineEdit *m_value; + TQPushButton *m_delete; +}; + +class TDEAttrPropsPlugin::TDEAttrPropsPluginPrivate +{ + public: + TDEAttrPropsPluginPrivate() { m_entryWidgets.setAutoDelete(true); } + ~TDEAttrPropsPluginPrivate() {} + + TQStringList m_savedAttrs; + + TQFrame *m_frame; + TQVBox *m_entriesBox; + TQHBox *m_buttonsBox; + TQLabel *m_label; + TQPushButton *m_newEntry, *m_editSavedAttrs; + + TQMap m_entries; + TQPtrList m_entryWidgets, m_pendingWidgets; +}; + +TDEAttrPropsPlugin::TDEAttrPropsPlugin(KPropertiesDialog *_props) + : KPropsDlgPlugin(_props) +{ + d = new TDEAttrPropsPluginPrivate; + + d->m_frame = properties->addPage(i18n("A&ttributes")); + d->m_entriesBox = new TQVBox(d->m_frame); + d->m_buttonsBox = new TQHBox(d->m_frame); + + d->m_label = new TQLabel(d->m_frame); + d->m_label->setText(i18n("Loading attributes information...")); + + d->m_newEntry = new TQPushButton( + SmallIcon("add"), + i18n("Add attribute"), + d->m_buttonsBox + ); + connect(d->m_newEntry, TQ_SIGNAL(clicked()), + this, TQ_SLOT(slotAddEntry())); + + if (!KProtocolInfo::supportsWritingAttrs(properties->kurl())) + { + d->m_newEntry->setEnabled(false); + } + + d->m_editSavedAttrs = new TQPushButton( + i18n("Saved attributes..."), + d->m_buttonsBox + ); + connect(d->m_editSavedAttrs, TQ_SIGNAL(clicked()), + this, TQ_SLOT(slotEditSavedAttrs())); + + TQVBoxLayout *layout = new TQVBoxLayout(d->m_frame); + layout->addWidget(d->m_entriesBox); + layout->addWidget(d->m_label); + layout->addStretch(); + layout->addWidget(d->m_buttonsBox); + + TDEConfig *config = new TDEConfig("kdeglobals"); + config->setGroup("TDE Extended Attributes"); + d->m_savedAttrs = config->readListEntry("SavedAttributes"); + + TDEIO::AttributeJob *listJob = TDEIO::listAttr(_props->kurl(), true); + connect(listJob, TQ_SIGNAL(result(TDEIO::Job*)), + this, TQ_SLOT(slotListJobResult(TDEIO::Job*))); + + connect(this, TQ_SIGNAL(changed()), this, TQ_SLOT(slotCheckNoAttrs())); +} + +TDEAttrPropsPlugin::~TDEAttrPropsPlugin() +{ + delete d; +} + +TDEAttrPropsPlugin::TDEAttrEntry *TDEAttrPropsPlugin::addEntry() +{ + TDEAttrEntry *entryWidget = new TDEAttrEntry(d->m_entriesBox); + d->m_entryWidgets.append(entryWidget); + entryWidget->m_combo->insertStringList(d->m_savedAttrs); + entryWidget->m_combo->setCurrentText(TQString::null); + if (!KProtocolInfo::supportsWritingAttrs(properties->kurl())) + { + entryWidget->setReadOnly(true); + } + entryWidget->show(); + return entryWidget; +} + +void TDEAttrPropsPlugin::connectEntry(TDEAttrEntry *entry) +{ + connect(entry->m_combo, TQ_SIGNAL(textChanged(const TQString&)), + this, TQ_SIGNAL(changed())); + connect(entry->m_value, TQ_SIGNAL(textChanged(const TQString&)), + this, TQ_SIGNAL(changed())); + connect(entry->m_delete, TQ_SIGNAL(clicked()), + this, TQ_SLOT(slotConfirmDelete())); +} + +void TDEAttrPropsPlugin::slotAddEntry() +{ + if (!KProtocolInfo::supportsWritingAttrs(properties->kurl())) + { + return; + } + TDEAttrEntry *entryWidget = addEntry(); + connectEntry(entryWidget); + entryWidget->m_combo->lineEdit()->setFocus(); + emit changed(); +} + +void TDEAttrPropsPlugin::slotEditSavedAttrs() +{ + KDialogBase *dlg = new KDialogBase( + properties, + "TDE Saved Attributes", + true, + i18n("Saved attributes"), + KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Ok + ); + + KEditListBox *elb = new KEditListBox(dlg); + elb->setTitle(i18n("Edit saved attributes")); + elb->upButton()->hide(); + elb->downButton()->hide(); + elb->insertStringList(d->m_savedAttrs); + connect(dlg, TQ_SIGNAL(okClicked()), this, TQ_SLOT(slotApplySavedAttrs())); + dlg->setMainWidget(elb); + dlg->exec(); +} + +void TDEAttrPropsPlugin::slotApplySavedAttrs() +{ + KDialogBase *dlg = static_cast(const_cast(TQObject::sender())); + Q_ASSERT(dlg); + KEditListBox *elb = static_cast(dlg->mainWidget()); + Q_ASSERT(elb); + + d->m_savedAttrs = elb->items(); + TDEConfig *config = new TDEConfig("kdeglobals"); + config->setGroup("TDE Extended Attributes"); + config->writeEntry("SavedAttributes", d->m_savedAttrs); + config->sync(); + dlg->deleteLater(); + + // Update comboboxes immediately + TDEAttrEntry *entry; + for (entry = d->m_entryWidgets.first(); entry; + entry = d->m_entryWidgets.next()) + { + TQString currentText = entry->m_combo->currentText(); + entry->m_combo->clear(); + entry->m_combo->insertStringList(d->m_savedAttrs); + entry->m_combo->setCurrentText(currentText); + } +} + +void TDEAttrPropsPlugin::slotConfirmDelete() +{ + if (!KProtocolInfo::supportsWritingAttrs(properties->kurl())) + { + return; + } + const TQObject *sender = TQObject::sender(); + TQWidget *button = static_cast(const_cast(sender)); + Q_ASSERT(button); + TDEAttrEntry *entry = static_cast(button->parentWidget()); + Q_ASSERT(entry); + + TQCString attr = entry->key(); + bool remove = false; + + if (attr.isEmpty() && entry->value().isEmpty()) + { + remove = true; + } + else + { + TQString msg; + if (!attr.isEmpty()) + { + msg = i18n("Are you sure you want to delete attribute %1?
" + "Note that this won't actually take effect until you press \"Ok\".
" + ).arg(TQString::fromLocal8Bit(attr)); + } + else + { + msg = i18n("Are you sure you want to delete this unnamed attribute?"); + } + + int result = KMessageBox::warningYesNo(properties, msg, i18n("Remove attribute?")); + remove = (result == KMessageBox::Yes); + } + + if (remove) + { + d->m_entryWidgets.remove(entry); + emit changed(); + } +} + +void TDEAttrPropsPlugin::slotCheckNoAttrs() +{ + if (d->m_entryWidgets.count() == 0) + { + d->m_label->show(); + d->m_label->setText(i18n("This file has no attributes.")); + } + else d->m_label->hide(); +} + +void TDEAttrPropsPlugin::slotListJobResult(TDEIO::Job *job) +{ + if (job->error()) + { + job->showErrorDialog(); + d->m_label->setText(i18n("Error reading attributes information!")); + } + else + { + TDEIO::AttributeJob *listJob = static_cast(job); + Q_ASSERT(listJob); + + TQValueList attrs = listJob->attributes(); + TQValueList::iterator it; + for (it = attrs.begin(); it != attrs.end(); ++it) + { + TDEAttrEntry *entryWidget = addEntry(); + entryWidget->setKey(*it); + d->m_pendingWidgets.append(entryWidget); + + TDEIO::AttributeJob *readJob = TDEIO::readAttr(properties->kurl(), + (*it), false); + connect(readJob, TQ_SIGNAL(result(TDEIO::Job*)), + this, TQ_SLOT(slotReadJobResult(TDEIO::Job*))); + } + + slotCheckNoAttrs(); + } +} + +void TDEAttrPropsPlugin::slotReadJobResult(TDEIO::Job *job) +{ + if (job->error()) + { + job->showErrorDialog(); + } + else + { + TDEIO::AttributeJob *readJob = static_cast(job); + Q_ASSERT(readJob); + + TQCString attr = readJob->attribute(), + val = readJob->value(); + TDEAttrEntry *entryWidget; + for (entryWidget = d->m_pendingWidgets.first(); entryWidget; + entryWidget = d->m_pendingWidgets.next()) + { + if (entryWidget->key() == attr) + { + entryWidget->setValue(val); + d->m_pendingWidgets.remove(entryWidget); + d->m_entries[attr] = val; + if (KProtocolInfo::supportsWritingAttrs(properties->kurl())) + { + entryWidget->setReadOnly(false); + connectEntry(entryWidget); + } + d->m_pendingWidgets.remove(entryWidget); + d->m_entries[attr] = val; + return; + } + } + + kdWarning() << "attribute widget not found: " << attr << endl; + } +} + +void TDEAttrPropsPlugin::slotWriteJobResult(TDEIO::Job *job) +{ + if (job->error()) + { + job->showErrorDialog(); + } + else + { + TDEIO::AttributeJob *writeJob = static_cast(job); + Q_ASSERT(writeJob); + d->m_entries[writeJob->attribute()] = writeJob->value(); + } + JOB_DONE; +} + +void TDEAttrPropsPlugin::slotRemoveJobResult(TDEIO::Job *job) +{ + if (job->error()) + { + job->showErrorDialog(); + } + else + { + TDEIO::AttributeJob *removeJob = static_cast(job); + Q_ASSERT(removeJob); + d->m_entries.remove(removeJob->attribute()); + } + JOB_DONE; +} + +void TDEAttrPropsPlugin::applyChanges() +{ + if (!KProtocolInfo::supportsWritingAttrs(properties->kurl())) + { + return; + } + TQStringList existingKeys; + TDEIO::AttributeJob *writeJob; + + // First do a validation + TDEAttrEntry *entry; + for (entry = d->m_entryWidgets.first(); entry; + entry = d->m_entryWidgets.next()) + { + if (entry->key().isEmpty()) + { + KMessageBox::sorry(properties, + i18n("There is an attribute without a name.")); + properties->abortApplying(); + return; + } + + if (existingKeys.contains(entry->key())) + { + KMessageBox::sorry(properties, + i18n("Duplicate attribute '%1'.") + .arg(TQString::fromLocal8Bit(entry->key()))); + properties->abortApplying(); + return; + } + + existingKeys << entry->key(); + } + + // Then actually apply the settings + for (entry = d->m_entryWidgets.first(); entry; + entry = d->m_entryWidgets.next()) + { + // Ignore if the attribute value is unchanged + if (d->m_entries.contains(entry->key()) && + d->m_entries[entry->key()] == entry->value()) + { + continue; + } + + writeJob = TDEIO::writeAttr(properties->kurl(), entry->key(), entry->value(), true); + connect(writeJob, TQ_SIGNAL(result(TDEIO::Job*)), + this, TQ_SLOT(slotWriteJobResult(TDEIO::Job*))); + WAIT_FOR_JOB; + } + + TQMap entries(d->m_entries); + TQMap::Iterator it; + for (it = entries.begin(); it != entries.end(); ++it) + { + if (!existingKeys.contains(it.key())) + { + TDEIO::AttributeJob *rmJob = TDEIO::removeAttr(properties->kurl(), it.key(), true); + connect(rmJob, TQ_SIGNAL(result(TDEIO::Job*)), + this, TQ_SLOT(slotRemoveJobResult(TDEIO::Job*))); + WAIT_FOR_JOB; + } + } +} + +bool TDEAttrPropsPlugin::supports(KFileItemList _items) +{ + if (_items.count() != 1) + { + return false; + } + + KFileItem *item = _items.first(); + if (item->isDir() || !KProtocolInfo::supportsReadingAttrs(item->url())) + { + return false; + } + + return true; +} + /* ---------------------------------------------------- * * KDevicePropsPlugin diff --git a/tdeio/tdefile/kpropertiesdialog.h b/tdeio/tdefile/kpropertiesdialog.h index 8fa1a5880..689824e12 100644 --- a/tdeio/tdefile/kpropertiesdialog.h +++ b/tdeio/tdefile/kpropertiesdialog.h @@ -63,7 +63,7 @@ namespace TDEIO { class Job; } * * This class must be created with (void)new KPropertiesDialog(...) * It will take care of deleting itself. - * + * * If you are looking for more flexibility, see KFileMetaInfo and * KFileMetaInfoWidget. */ @@ -82,9 +82,9 @@ public: static bool canDisplay( KFileItemList _items ); /** - * Brings up a Properties dialog, as shown above. + * Brings up a Properties dialog, as shown above. * This is the normal constructor for - * file-manager type applications, where you have a KFileItem instance + * file-manager type applications, where you have a KFileItem instance * to work with. Normally you will use this * method rather than the one below. * @@ -192,40 +192,40 @@ public: virtual ~KPropertiesDialog(); /** - * Immediately displays a Properties dialog using constructor with - * the same parameters. - * On MS Windows, if @p item points to a local file, native (non modal) property + * Immediately displays a Properties dialog using constructor with + * the same parameters. + * On MS Windows, if @p item points to a local file, native (non modal) property * dialog is displayed (@p parent and @p modal are ignored in this case). - * + * * @return true on succesfull dialog displaying (can be false on win32). * @since 3.4 */ - static bool showDialog(KFileItem* item, TQWidget* parent = 0, + static bool showDialog(KFileItem* item, TQWidget* parent = 0, const char* name = 0, bool modal = false); /** - * Immediately displays a Properties dialog using constructor with - * the same parameters. - * On MS Windows, if @p _url points to a local file, native (non modal) property + * Immediately displays a Properties dialog using constructor with + * the same parameters. + * On MS Windows, if @p _url points to a local file, native (non modal) property * dialog is displayed (@p parent and @p modal are ignored in this case). - * + * * @return true on succesfull dialog displaying (can be false on win32). * @since 3.4 */ - static bool showDialog(const KURL& _url, TQWidget* parent = 0, + static bool showDialog(const KURL& _url, TQWidget* parent = 0, const char* name = 0, bool modal = false); /** - * Immediately displays a Properties dialog using constructor with - * the same parameters. - * On MS Windows, if @p _items has one element and this element points - * to a local file, native (non modal) property dialog is displayed + * Immediately displays a Properties dialog using constructor with + * the same parameters. + * On MS Windows, if @p _items has one element and this element points + * to a local file, native (non modal) property dialog is displayed * (@p parent and @p modal are ignored in this case). - * + * * @return true on succesfull dialog displaying (can be false on win32). * @since 3.4 */ - static bool showDialog(const KFileItemList& _items, TQWidget* parent = 0, + static bool showDialog(const KFileItemList& _items, TQWidget* parent = 0, const char* name = 0, bool modal = false); /** @@ -244,7 +244,7 @@ public: void insertPlugin (KPropsDlgPlugin *plugin); /** - * The URL of the file that has its properties being displayed. + * The URL of the file that has its properties being displayed. * This is only valid if the KPropertiesDialog was created/shown * for one file or URL. * @@ -323,7 +323,7 @@ public: * @since 3.1 */ void showFileSharingPage(); - + /** * Sets the file sharing page. * This page is shown when calling showFileSharingPage(). @@ -715,6 +715,43 @@ private: KBindingPropsPluginPrivate *d; }; +/** + * Properties plugin for extended attributes + * @internal + */ +class TDEIO_EXPORT TDEAttrPropsPlugin : public KPropsDlgPlugin +{ + TQ_OBJECT + + public: + TDEAttrPropsPlugin(KPropertiesDialog *_props); + virtual ~TDEAttrPropsPlugin(); + + virtual void applyChanges(); + static bool supports(KFileItemList _items); + + private slots: + void slotListJobResult(TDEIO::Job *); + void slotReadJobResult(TDEIO::Job *); + void slotWriteJobResult(TDEIO::Job *); + void slotRemoveJobResult(TDEIO::Job *); + void slotAddEntry(); + void slotEditSavedAttrs(); + void slotApplySavedAttrs(); + void slotConfirmDelete(); + void slotCheckNoAttrs(); + + private: + class TDEAttrEntry; + class TDEAttrPropsPluginPrivate; + TDEAttrPropsPluginPrivate *d; + + protected: + void updateComboBoxes(); + TDEAttrEntry *addEntry(); + void connectEntry(TDEAttrEntry *entry); +}; + /** * Properties plugin for device .desktop files * @internal diff --git a/tdeio/tdeio/CMakeLists.txt b/tdeio/tdeio/CMakeLists.txt index 91828320a..6a895af94 100644 --- a/tdeio/tdeio/CMakeLists.txt +++ b/tdeio/tdeio/CMakeLists.txt @@ -46,7 +46,7 @@ install( FILES kfilterbase.h kfilterdev.h tdeemailsettings.h kscan.h kdatatool.h karchive.h tdefilefilter.h tdefilemetainfo.h renamedlgplugin.h kmimetyperesolver.h kdcopservicestarter.h - kremoteencoding.h kmimetypechooser.h kacl.h + kremoteencoding.h kmimetypechooser.h kacl.h tdexattr.h DESTINATION ${INCLUDE_INSTALL_DIR} ) install( FILES @@ -77,7 +77,7 @@ set( ${target}_SRCS kdirnotify.cpp kdirnotify.skel kdirnotify_stub.cpp observer.cpp ../misc/uiserver.stub observer.skel tdeemailsettings.cpp kprotocolinfo.cpp renamedlg.cpp skipdlg.cpp kremoteencoding.cpp - kmimetypechooser.cpp + kmimetypechooser.cpp tdexattr.cpp ) tde_add_library( ${target} STATIC_PIC AUTOMOC diff --git a/tdeio/tdeio/global.cpp b/tdeio/tdeio/global.cpp index f026b3044..33051a4b8 100644 --- a/tdeio/tdeio/global.cpp +++ b/tdeio/tdeio/global.cpp @@ -426,7 +426,7 @@ TDEIO_EXPORT TQString TDEIO::buildErrorString(int errorCode, const TQString &err result = i18n( "Access to restricted port in POST denied."); break; case TDEIO::ERR_OFFLINE_MODE: - result = i18n( "Could not access %1.\nOffline mode active.").arg( errorText ); + result = i18n( "Could not access %1.\nOffline mode active.").arg( errorText ); break; default: result = i18n( "Unknown error code %1\n%2\nPlease send a full bug report at http://bugs.trinitydesktop.org." ).arg( errorCode ).arg( errorText ); @@ -466,6 +466,11 @@ TDEIO_EXPORT TQString TDEIO::unsupportedActionErrorString(const TQString &protoc return i18n("Creating folders is not supported with protocol %1.").arg(protocol); case CMD_CHMOD: return i18n("Changing the attributes of files is not supported with protocol %1.").arg(protocol); + case CMD_LISTATTR: + case CMD_READATTR: + case CMD_WRITEATTR: + case CMD_REMOVEATTR: + return i18n("Extended attributes are not supported with protocol %1.").arg(protocol); case CMD_SUBURL: return i18n("Using sub-URLs with %1 is not supported.").arg(protocol); case CMD_MULTI_GET: @@ -1361,10 +1366,10 @@ extern "C" void endvfsent( ); # endif #endif -#ifdef __CYGWIN__ -#define hasmntopt(var,opt) (0) -#endif - +#ifdef __CYGWIN__ +#define hasmntopt(var,opt) (0) +#endif + // There are (at least) four kind of APIs: // setmntent + getmntent + struct mntent (linux...) // getmntent + struct mnttab @@ -1949,7 +1954,7 @@ TQString TDEIO::findPathMountPoint(const TQString& filename) return get_mount_info(filename, isautofs, isslow, ismanual, fstype); #else //!Q_OS_UNIX return TQString::null; -#endif +#endif } bool TDEIO::manually_mounted(const TQString& filename) @@ -1961,7 +1966,7 @@ bool TDEIO::manually_mounted(const TQString& filename) return !mountPoint.isNull() && (ismanual == Right); #else //!Q_OS_UNIX return false; -#endif +#endif } bool TDEIO::probably_slow_mounted(const TQString& filename) @@ -1973,7 +1978,7 @@ bool TDEIO::probably_slow_mounted(const TQString& filename) return !mountPoint.isNull() && (isslow == Right); #else //!Q_OS_UNIX return false; -#endif +#endif } bool TDEIO::testFileSystemFlag(const TQString& filename, FileSystemFlag flag) @@ -1995,7 +2000,7 @@ bool TDEIO::testFileSystemFlag(const TQString& filename, FileSystemFlag flag) case CaseInsensitive: return isMsDos; } -#endif +#endif return false; } diff --git a/tdeio/tdeio/global.h b/tdeio/tdeio/global.h index 77e2da77b..c7ec9c911 100644 --- a/tdeio/tdeio/global.h +++ b/tdeio/tdeio/global.h @@ -164,7 +164,11 @@ namespace TDEIO CMD_RESUMEANSWER = 'T', // 84 CMD_CONFIG = 'U', // 85 CMD_MULTI_GET = 'V', // 86 - CMD_LOCALURL = 'W' // 87 + CMD_LOCALURL = 'W', // 87 + CMD_LISTATTR = 'X', // 88 + CMD_READATTR = 'Y', // 89 + CMD_WRITEATTR = 'Z', // 90 + CMD_REMOVEATTR = '[' // 91 // Add new ones here once a release is done, to avoid breaking binary compatibility. // Note that protocol-specific commands shouldn't be added here, but should use special. }; @@ -247,7 +251,7 @@ namespace TDEIO // the server in order to continue. ERR_POST_DENIED = 65, // Issued when trying to POST data to a certain Ports // see job.cpp - ERR_OFFLINE_MODE = 66 // Used when an app is in offline mode and a + ERR_OFFLINE_MODE = 66 // Used when an app is in offline mode and a // requested document is unavailable. }; @@ -350,7 +354,7 @@ namespace TDEIO /// @since 3.5 UDS_DEFAULT_ACL_STRING = 104 | UDS_STRING, - // available: 112, 120 + // available: 112, 120 /// Access permissions (part of the mode returned by stat) UDS_ACCESS = 128 | UDS_LONG, diff --git a/tdeio/tdeio/job.cpp b/tdeio/tdeio/job.cpp index 5bdea2fd8..cccf77522 100644 --- a/tdeio/tdeio/job.cpp +++ b/tdeio/tdeio/job.cpp @@ -837,6 +837,109 @@ SimpleJob *TDEIO::unmount( const TQString& point, bool showProgressInfo ) return job; } +//////////// +class AttributeJob::AttributeJobPrivate +{ + public: + AttributeJobPrivate() {} + + // The extended attributes of a file, result of the LISTATTR command + TQValueList m_attributes; + + // The value of an attribute, result of the READATTR command or the value + // passed to WRITEATTR command + TQCString m_value; + + // The current attribute + TQCString m_attribute; +}; + +AttributeJob::AttributeJob(const KURL& url, int command, const TQByteArray &packedArgs, bool showProgressInfo) + : TransferJob(url, command, packedArgs, TQByteArray(), showProgressInfo) +{ + d = new AttributeJobPrivate; + + // Store a copy of some arguments so that they can be retrieved later from + // a job result slot. + TQByteArray args(packedArgs); + args.detach(); + + TQDataStream stream(packedArgs, IO_ReadOnly); + + KURL _url; + stream >> _url; + + if (command != CMD_LISTATTR) + { + stream >> d->m_attribute; + + if (command == CMD_WRITEATTR) + { + stream >> d->m_value; + } + } +} + +AttributeJob::~AttributeJob() +{ + delete d; +} + +void AttributeJob::start(Slave *slave) +{ + TransferJob::start(slave); +} + +void AttributeJob::slotData(const TQByteArray &data) +{ + if (command() == CMD_LISTATTR) + { + TQCString attr; + TQDataStream stream(data, IO_ReadOnly); + stream >> attr; + d->m_attributes << attr; + } + else if (command() == CMD_READATTR) + { + TQDataStream stream(data, IO_ReadOnly); + stream >> d->m_value; + } +} + +const TQValueList AttributeJob::attributes() { return d->m_attributes; } +TQCString AttributeJob::attribute() { return d->m_attribute; } +const TQCString AttributeJob::value() { return d->m_value; } + +void AttributeJob::slotFinished() +{ + // Return slave to the scheduler + TransferJob::slotFinished(); +} + +AttributeJob *TDEIO::listAttr(const KURL& url, bool showProgressInfo) +{ + TDEIO_ARGS << url; + return new AttributeJob(url, CMD_LISTATTR, packedArgs, showProgressInfo); +} + +AttributeJob *TDEIO::readAttr(const KURL& url, const TQCString& attr, bool showProgressInfo) +{ + TDEIO_ARGS << url << attr; + return new AttributeJob(url, CMD_READATTR, packedArgs, showProgressInfo); +} + +AttributeJob *TDEIO::writeAttr(const KURL& url, const TQCString& attr, const TQCString& val, bool showProgressInfo) +{ + TDEIO_ARGS << url << attr << val; + return new AttributeJob(url, CMD_WRITEATTR, packedArgs, showProgressInfo); +} + +AttributeJob *TDEIO::removeAttr(const KURL& url, const TQCString& attr, bool showProgressInfo) +{ + TDEIO_ARGS << url << attr; + return new AttributeJob(url, CMD_REMOVEATTR, packedArgs, showProgressInfo); +} + ////////// LocalURLJob::LocalURLJob( const KURL& url, int command, const TQByteArray &packedArgs, bool showProgressInfo ) @@ -4825,6 +4928,9 @@ void MultiGetJob::virtual_hook( int id, void* data ) void MimetypeJob::virtual_hook( int id, void* data ) { TransferJob::virtual_hook( id, data ); } +void AttributeJob::virtual_hook( int id, void* data ) +{ TransferJob::virtual_hook( id, data ); } + void FileCopyJob::virtual_hook( int id, void* data ) { Job::virtual_hook( id, data ); } diff --git a/tdeio/tdeio/job.h b/tdeio/tdeio/job.h index 5484712bb..776c6cfd7 100644 --- a/tdeio/tdeio/job.h +++ b/tdeio/tdeio/job.h @@ -127,6 +127,58 @@ namespace TDEIO { */ TDEIO_EXPORT SimpleJob *unmount( const TQString & point, bool showProgressInfo = true ); + /** + * List extended attributes. + * + * Currently only used by @p tdeio_file. + * + * @param url file URL + * @param showProgressInfo true to show progress information + * @return the job handling the operation. + * @since R14.1.5 + */ + TDEIO_EXPORT AttributeJob *listAttr( const KURL& url, bool showProgressInfo = true ); + + /** + * Read an extended attribute. + * + * Currently only used by @p tdeio_file. + * + * @param url file URL + * @param attr attribute name + * @param showProgressInfo true to show progress information + * @return the job handling the operation. + * @since R14.1.5 + */ + TDEIO_EXPORT AttributeJob *readAttr( const KURL& url, const TQCString& attr, bool showProgressInfo = true ); + + /** + * Write an extended attribute. + * + * Currently only used by @p tdeio_file. + * + * @param url file URL + * @param attr attribute name + * @param val attribute value + * @param showProgressInfo true to show progress information + * @return the job handling the operation. + * @since R14.1.5 + */ + TDEIO_EXPORT AttributeJob *writeAttr( const KURL& url, const TQCString& attr, const TQCString& val, bool showProgressInfo = true ); + + /** + * Remove an extended attribute. + * + * Currently only used by @p tdeio_file. + * + * @param url file URL + * @param attr attribute name + * @param showProgressInfo true to show progress information + * @return the job handling the operation. + * @since R14.1.5 + */ + TDEIO_EXPORT AttributeJob *removeAttr( const KURL& url, const TQCString& attr, bool showProgressInfo = true ); + /** * Retrieve local URL if available * diff --git a/tdeio/tdeio/jobclasses.h b/tdeio/tdeio/jobclasses.h index 5d04b7f5d..4d6961114 100644 --- a/tdeio/tdeio/jobclasses.h +++ b/tdeio/tdeio/jobclasses.h @@ -1241,6 +1241,72 @@ namespace TDEIO { class MimetypeJobPrivate* d; }; + /** + * An AttributeJob is a TransferJob that allows you to query and modify + * the extended attributes of an URL. Don't create directly, but use + * TDEIO::listAttr(), TDEIO::readAttr(), TDEIO::writeAttr() or + * TDEIO::removeAttr() instead. + * @see TDEIO::listAttr(), TDEIO::readAttr(), TDEIO::writeAttr(), TDEIO::removeAttr() + */ + class TDEIO_EXPORT AttributeJob : public TransferJob { + TQ_OBJECT + + public: + /** + * Do not create an AttributeJob directly. Use TDEIO::listAttr(), + * TDEIO::readAttr(), TDEIO::writeAttr() or TDEIO::removeAttr() instead. + * @param url the url to get + * @param command the command to issue + * @param packedArgs the arguments + * @param showProgressInfo true to show progress information to the user + */ + AttributeJob(const KURL& url, int command, const TQByteArray &packedArgs, bool showProgressInfo); + ~AttributeJob(); + + /** + * This function contains the result of a successful listAttr() operation. + * If an error occured, an empty list is returned instead. + * @return list of extended attributes of the file + */ + const TQValueList attributes(); + + /** This function stores the attribute name that this job is related to + * unless the current operation is listAttr(). Useful if you request + * multiple attribute values but have one handler slot. + * @return attribute name as passed to readAttr(), writeAttr() or removeAttr() + */ + TQCString attribute(); + + /** + * This function contains the result of a successful readAttr() operation. + * If an error occured, an empty TQCString is returned instead. + * + * Alternatively, in case of a writeAttr() operation, the function always + * returns the value that was passed to the writeAttr() function. + * + * @return result of successful readAttr() operation or value passed to writeAttr() operation + */ + const TQCString value(); + + /** + * @internal + * Called by the scheduler when a slave gets to work on this job. + * @param slave the slave that works on the job + */ + virtual void start( Slave *slave ); + + protected slots: + virtual void slotData(const TQByteArray &data); + virtual void slotFinished(); + + protected: + virtual void virtual_hook(int id, void* data); + + private: + class AttributeJobPrivate; + AttributeJobPrivate *d; + }; + /** * The FileCopyJob copies data from one place to another. * @see TDEIO::file_copy() diff --git a/tdeio/tdeio/kprotocolinfo.cpp b/tdeio/tdeio/kprotocolinfo.cpp index 4332fc23b..4261e5f62 100644 --- a/tdeio/tdeio/kprotocolinfo.cpp +++ b/tdeio/tdeio/kprotocolinfo.cpp @@ -190,6 +190,24 @@ bool KProtocolInfo::supportsMoving( const KURL &url ) return prot->m_supportsMoving; } +bool KProtocolInfo::supportsReadingAttrs( const KURL &url ) +{ + KProtocolInfo::Ptr prot = findProtocol(url); + if ( !prot ) + return false; + + return prot->m_supportsReadingAttrs; +} + +bool KProtocolInfo::supportsWritingAttrs( const KURL &url ) +{ + KProtocolInfo::Ptr prot = findProtocol(url); + if ( !prot ) + return false; + + return prot->m_supportsWritingAttrs; +} + bool KProtocolInfo::canCopyFromFile( const KURL &url ) { KProtocolInfo::Ptr prot = findProtocol(url); diff --git a/tdeio/tdeio/kprotocolinfo.h b/tdeio/tdeio/kprotocolinfo.h index 000a88323..a6fea6954 100644 --- a/tdeio/tdeio/kprotocolinfo.h +++ b/tdeio/tdeio/kprotocolinfo.h @@ -343,6 +343,30 @@ public: */ static bool supportsMoving( const KURL &url ); + /** + * Returns whether the protocol supports reading attributes. + * + * This corresponds to the "readattr=" field in the protocol description file. + * Valid values for this field are "true" or "false" (default). + * + * @param url the url to check + * @return true if the protocol supports reading attributes + * @since R14.1.5 + */ + static bool supportsReadingAttrs( const KURL &url ); + + /** + * Returns whether the protocol supports reading attributes. + * + * This corresponds to the "writeattr=" field in the protocol description file. + * Valid values for this field are "true" or "false" (default). + * + * @param url the url to check + * @return true if the protocol supports reading attributes + * @since R14.1.5 + */ + static bool supportsWritingAttrs( const KURL &url ); + /** * Returns whether the protocol can copy files/objects directly from the * filesystem itself. If not, the application will read files from the @@ -661,6 +685,8 @@ protected: bool m_supportsDeleting; bool m_supportsLinking; bool m_supportsMoving; + bool m_supportsReadingAttrs; + bool m_supportsWritingAttrs; TQString m_defaultMimetype; bool m_determineMimetypeFromExtension; TQString m_icon; diff --git a/tdeio/tdeio/slavebase.cpp b/tdeio/tdeio/slavebase.cpp index b9e9a04a6..cbab662fa 100644 --- a/tdeio/tdeio/slavebase.cpp +++ b/tdeio/tdeio/slavebase.cpp @@ -90,7 +90,7 @@ return false; } KEntryMap internalEntryMap() const { return KEntryMap(); } - void putData(const KEntryKey &_key, const KEntry&_data, bool _checkGroup) + void putData(const KEntryKey &_key, const KEntry&_data, bool _checkGroup) { Q_UNUSED(_key); Q_UNUSED(_data); Q_UNUSED(_checkGroup); } KEntry lookupData(const KEntryKey &_key) const @@ -800,6 +800,14 @@ void SlaveBase::mkdir(KURL const &, int) { error( ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_MKDIR)); } void SlaveBase::chmod(KURL const &, int) { error( ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_CHMOD)); } +void SlaveBase::listAttr(KURL const &) +{ error( ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_LISTATTR)); } +void SlaveBase::readAttr(KURL const &, TQCString const &) +{ error( ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_READATTR)); } +void SlaveBase::writeAttr(KURL const &, TQCString const &, TQCString const &) +{ error( ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_WRITEATTR)); } +void SlaveBase::removeAttr(KURL const &, TQCString const &) +{ error( ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_REMOVEATTR)); } void SlaveBase::setSubURL(KURL const &) { error( ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_SUBURL)); } void SlaveBase::multiGet(const TQByteArray &) @@ -1134,6 +1142,33 @@ void SlaveBase::dispatch( int command, const TQByteArray &data ) stream >> url >> i; chmod( url, i); break; + case CMD_LISTATTR: + { + stream >> url; + listAttr(url); + break; + } + case CMD_READATTR: + { + TQCString attr; + stream >> url >> attr; + readAttr(url, attr); + break; + } + case CMD_WRITEATTR: + { + TQCString attr, val; + stream >> url >> attr >> val; + writeAttr(url, attr, val); + break; + } + case CMD_REMOVEATTR: + { + TQCString attr; + stream >> url >> attr; + removeAttr(url, attr); + break; + } case CMD_SPECIAL: special( data ); break; diff --git a/tdeio/tdeio/slavebase.h b/tdeio/tdeio/slavebase.h index 05d3cbbed..3a73f4ab3 100644 --- a/tdeio/tdeio/slavebase.h +++ b/tdeio/tdeio/slavebase.h @@ -461,6 +461,38 @@ public: */ virtual void chmod( const KURL& url, int permissions ); + /** + * List extended attributes of @p path + * @param url file URL + * @since R14.1.5 + */ + virtual void listAttr(const KURL& url); + + /** + * Reads extended attribute @p attr of @p path + * @param url file URL + * @param attr attribute name + * @since R14.1.5 + */ + virtual void readAttr(const KURL& url, const TQCString& attr); + + /** + * Sets the value of extended attribute @p attr of @p path to @p val + * @param url file URL + * @param attr attribute name + * @param val attribute value + * @since R14.1.5 + */ + virtual void writeAttr(const KURL& url, const TQCString& attr, const TQCString& val); + + /** + * Removes extended attribute @p attr from @p path + * @param url file URL + * @param attr attribute name + * @since R14.1.5 + */ + virtual void removeAttr(const KURL& url, const TQCString& attr); + /** * Copy @p src into @p dest. * If the slave returns an error ERR_UNSUPPORTED_ACTION, the job will diff --git a/tdeio/tdeio/tdexattr.cpp b/tdeio/tdeio/tdexattr.cpp new file mode 100644 index 000000000..d45345019 --- /dev/null +++ b/tdeio/tdeio/tdexattr.cpp @@ -0,0 +1,355 @@ +/******************************************************************************* + Extended attributes support for TDE + Copyright © 2025 Philippe Mavridis + + Based on xattr_p.h from KFileMetaData + Copyright © 2014 Raphael Kubo da Costa + + This program or library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + 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 Lesser General Public License for more + details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*******************************************************************************/ + +#include +#include + +#if defined(Q_OS_LINUX) || defined(__GLIBC__) +# include +#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) +# include +#endif + +#include + +#include "tdexattr.h" + +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) + +# define CHECK_URL(url) \ + if (!url.isValid() || url.isEmpty()) return TDEIO::ERR_MALFORMED_URL; \ + if (!url.isLocalFile()) return 0; // silently ignore non-local urls + +# define GET_ENCODED_PATH(url) \ + const TQByteArray p = TQFile::encodeName(url.path()); \ + const char* encodedPath = p.data(); + +#if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) +static TQValueList // FIXME untested +splitLengthValue(TQByteArray data) +{ + uint pos = 0; + TQValueList entries; + char *s; + while (pos < data.size()) + { + uchar len = data[pos]; + + if (pos + 1 + len <= data.size()) + { + strncat(s, &data[pos + 1], len); + entries.append(TQCString(s, len)); + } + + pos += 1 + len; + } + return entries; +} +#else +static TQValueList +splitZeroSeparator(TQByteArray data) +{ + uint pos = 0; + TQValueList entries; + while (pos < data.size()) + { + const char *c = &data[pos]; + size_t len = strlen(c); + entries.append(TQCString(c)); + pos += len + 1; + } + + return entries; +} +#endif + +uint +TDEXAttr::list(const KURL& url, TQValueList *list) +{ + CHECK_URL(url); + GET_ENCODED_PATH(url); + +#if defined(Q_OS_LINUX) + const ssize_t size = listxattr(encodedPath, nullptr, 0); +#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) + const ssize_t size = extattr_list_file(encodedPath, EXTATTR_NAMESPACE_USER, nullptr, 0); +#endif + + if (size == 0) + { + return 0; + } + else if (size == -1) + { + return parseError(errno, TDEIO::CMD_LISTATTR); + } + else + { + TQByteArray data(size); + + for (;;) + { +#if defined(Q_OS_LINUX) + const ssize_t r = listxattr(encodedPath, data.data(), data.size()); +#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) + const ssize_t r = extattr_list_file(encodedPath, EXTATTR_NAMESPACE_USER, data.data(), data.size()); +#endif + + if (r == 0) { + list->clear(); + return 0; + } + + if (r == -1 && errno != ERANGE) + { + list->clear(); + return parseError(errno, TDEIO::CMD_LISTATTR); + } + + if (r > 0) + { + data.resize(r); + break; + } + else // ERANGE + { + data.resize(data.size() * 2); + } + } + +#if defined(Q_OS_LINUX) + const TQValueList entries = splitZeroSeparator(data); +#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) + const TQValueList entries = splitLengthValue(data); +#endif + + list->clear(); + + TQValueList::const_iterator it; + for (it = entries.begin(); it != entries.end(); ++it) + { + TQCString attribute(*it); + +#if defined(Q_OS_LINUX) + if (attribute.left(5) != "user.") + { + continue; + } +#endif + + *list << getVisibleAttributeName(attribute); + } + return 0; + } +} + +uint +TDEXAttr::read(const KURL& url, const TQCString& attribute, TQCString *value) +{ + CHECK_URL(url); + GET_ENCODED_PATH(url); + TQCString attr = getLocalAttributeName(attribute); + +#if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_getxattr)) + const ssize_t size = getxattr(encodedPath, attr, nullptr, 0); +#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) + const ssize_t size = extattr_get_file(encodedPath, EXTATTR_NAMESPACE_USER, attr, NULL, 0); +#endif + + if (size == -1) + { + return parseError(errno, TDEIO::CMD_READATTR); + } + else if (size == 0) + { + *value = ""; + return 0; + } + else + { + TQByteArray data(size); + + for (;;) + { + +#if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_getxattr)) + const ssize_t r = getxattr(encodedPath, attr, data.data(), data.size()); +#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) + const ssize_t r = extattr_get_file(encodedPath, EXTATTR_NAMESPACE_USER, attr, data.data(), data.size()); +#endif + + if (r == -1 && errno != ERANGE) + { + *value = ""; + return parseError(errno, TDEIO::CMD_READATTR); + } + else if (r == 0) + { + *value = ""; + return 0; + } + else if (r >= 0) + { + *value = TQCString(data, r + 1); + return 0; + } + else // ERANGE + { + data.resize(data.size() * 2); + } + } + } +} + +uint +TDEXAttr::write(const KURL& url, const TQCString& attribute, const TQCString& value) +{ + CHECK_URL(url); + GET_ENCODED_PATH(url); + TQCString attr = getLocalAttributeName(attribute); + + int r; +#if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_setxattr)) + r = setxattr(encodedPath, attr, value.data(), value.size(), 0); +#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) + r = extattr_set_file(encodedPath, EXTATTR_NAMESPACE_USER, attr, value.data(), value.size()); +#endif + + return r == -1 ? parseError(errno, TDEIO::CMD_WRITEATTR) : 0; +} + +uint +TDEXAttr::remove(const KURL& url, const TQCString& attribute) +{ + CHECK_URL(url); + GET_ENCODED_PATH(url); + TQCString attr = getLocalAttributeName(attribute); + + int r; +#if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_removexattr)) + r = removexattr(encodedPath, attr); +#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) + r = extattr_delete_file (encodedPath, EXTATTR_NAMESPACE_USER, attr); +#else + return TDEIO::ERR_UNSUPPORTED_ACTION; +#endif + + return r == -1 ? parseError(errno, TDEIO::CMD_REMOVEATTR) : 0; +} + +bool +TDEXAttr::supported(const KURL& url) +{ + return read(url, "test", nullptr) != TDEIO::ERR_UNSUPPORTED_ACTION; +} + +#else // stubs for unsupported platforms + +uint +TDEXAttr::list(const KURL& url, TQValueList *list) +{ + return TDEIO::ERR_UNSUPPORTED_ACTION; +} + +uint +TDEXAttr::read(const KURL& url, const TQCString& attr, TQCString *value) +{ + return TDEIO::ERR_UNSUPPORTED_ACTION; +} + +uint +TDEXAttr::write(const KURL& url, const TQCString& attr, const TQCString& value) +{ + return TDEIO::ERR_UNSUPPORTED_ACTION; +} + +TDEIO::Error +TDEXAttr::remove(const KURL& url, const TQCString& attr) +{ + return TDEIO::ERR_UNSUPPORTED_ACTION; +} + +bool +TDEXAttr::supported(const KURL& url) +{ + return TDEIO::ERR_UNSUPPORTED_ACTION; +} +#endif + +uint +TDEXAttr::parseError(int _errno, int command) +{ + switch (_errno) + { + case E2BIG: + case ENODATA: + { + return command == TDEIO::CMD_READATTR ? + TDEIO::ERR_COULD_NOT_READ : + TDEIO::ERR_COULD_NOT_WRITE; + } + case ENOTSUP: + { + return TDEIO::ERR_UNSUPPORTED_ACTION; + } + case ERANGE: + { + return TDEIO::ERR_OUT_OF_MEMORY; + } + case EDQUOT: + case ENOSPC: + { + return TDEIO::ERR_DISK_FULL; + } + case EPERM: + { + return TDEIO::ERR_WRITE_ACCESS_DENIED; + } + default: + { + return TDEIO::ERR_INTERNAL; + } + } +} + +TQCString +TDEXAttr::getLocalAttributeName(const TQCString& attr) +{ + TQCString a(attr); +#if defined(Q_OS_LINUX) + a = a.prepend("user."); +#endif + return a; +} + +TQCString +TDEXAttr::getVisibleAttributeName(const TQCString& attr) +{ + TQCString a(attr); +#if defined(Q_OS_LINUX) + a = a.remove(0, 5); +#endif + return a; +} + +// kate: replace-tabs true; tab-width 4; \ No newline at end of file diff --git a/tdeio/tdeio/tdexattr.h b/tdeio/tdeio/tdexattr.h new file mode 100644 index 000000000..f544c3585 --- /dev/null +++ b/tdeio/tdeio/tdexattr.h @@ -0,0 +1,96 @@ +/******************************************************************************* + Extended attributes support for TDE + Copyright © 2025 Philippe Mavridis + + Based on xattr_p.h from KFileMetaData + Copyright © 2014 Raphael Kubo da Costa + + This program or library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + 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 Lesser General Public License for more + details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*******************************************************************************/ + +#ifndef __TDEXATTR_H +#define __TDEXATTR_H + +#include +#include +#include + +/** + * Extended attributes support class for TDE. + * + * This class implements extended attributes support for TDE. Static methods are + * provided which can list, read and write user-defined extended attributes of + * local files. + * + * @author Philippe Mavridis + */ + +class TDEIO_EXPORT TDEXAttr +{ + public: + /** + * List all the extended attributes of a file. + * @param url the URL of the file + * @param list pointer to a TQCString list where the list of attributes will be stored + * @return the status of the operation (0 for success, enum TDEIO::Error otherwise) + */ + static uint list(const KURL& url, TQValueList *list); + + /** + * Read the value of an extended attribute. + * @param url the URL of the file + * @param attr attribute name + * @param value pointer to TQCString where the value of the attribute @p attr will be stored + * @return the status of the operation (0 for success, enum TDEIO::Error otherwise) + */ + static uint read(const KURL& url, const TQCString& attribute, TQCString *value); + + /** + * Modify the value of an extended attribute. + * + * @param url the URL of the file + * @param attr attribute name + * @param value new value of the attribute @p attr + * @return the status of the operation (0 for success, enum TDEIO::Error otherwise) + */ + static uint write(const KURL& url, const TQCString& attribute, const TQCString& value); + + /** + * Remove an extended attribute. + * + * @param url the URL of the file + * @param attr attribute name + * @return the status of the operation (0 for success, enum TDEIO::Error otherwise) + */ + static uint remove(const KURL& url, const TQCString& attribute); + + + /** + * Check if extended attributes are supported for the given URL. + * + * @param url the URL of the file + * @return true if extended attributes are supported, false otherwise + */ + static bool supported(const KURL& url); + + private: + static TQCString getLocalAttributeName(const TQCString& attr); + static TQCString getVisibleAttributeName(const TQCString& attr); + static uint parseError(int _errno, int command); +}; + +#endif // __TDEXATTR_H +// kate: replace-tabs true; tab-width 2; \ No newline at end of file diff --git a/tdeioslave/file/file.cpp b/tdeioslave/file/file.cpp index 162f8a848..1ceba1874 100644 --- a/tdeioslave/file/file.cpp +++ b/tdeioslave/file/file.cpp @@ -94,6 +94,7 @@ #include #include #include +#include using namespace TDEIO; @@ -360,7 +361,7 @@ write_all(int fd, const char *buf, size_t len) return 0; } -static bool +static bool same_inode(const KDE_struct_stat &src, const KDE_struct_stat &dest) { if (src.st_ino == dest.st_ino && @@ -626,7 +627,7 @@ void FileProtocol::copy( const KURL &src, const KURL &dest, return; } - if ( same_inode( buff_dest, buff_src) ) + if ( same_inode( buff_dest, buff_src) ) { error( TDEIO::ERR_IDENTICAL_FILES, dest.path() ); return; @@ -839,7 +840,7 @@ void FileProtocol::rename( const KURL &src, const KURL &dest, return; } - if ( same_inode( buff_dest, buff_src) ) + if ( same_inode( buff_dest, buff_src) ) { error( TDEIO::ERR_IDENTICAL_FILES, dest.path() ); return; @@ -955,6 +956,59 @@ void FileProtocol::del( const KURL& url, bool isfile) finished(); } +void FileProtocol::listAttr( const KURL& url ) +{ + TQValueList attrs; + int result = TDEXAttr::list(url, &attrs); + if (result != 0) + { + error(result, url.path()); + } + TQValueList::iterator it; + for (it = attrs.begin(); it != attrs.end(); ++it) + { + TQByteArray d; + TQDataStream s(d, IO_WriteOnly); + s << (*it); + data(d); + } + finished(); +} + +void FileProtocol::readAttr( const KURL& url, const TQCString& attr ) +{ + TQCString value; + int result = TDEXAttr::read(url, attr, &value); + if (result != 0) + { + error(result, url.path()); + } + TQByteArray d; + TQDataStream s(d, IO_WriteOnly); + s << value; + data(d); + finished(); +} + +void FileProtocol::writeAttr( const KURL& url, const TQCString& attr, const TQCString& value ) +{ + int result = TDEXAttr::write(url, attr, value); + if (result != 0) + { + error(result, url.path()); + } + finished(); +} + +void FileProtocol::removeAttr( const KURL& url, const TQCString& attr ) +{ + int result = TDEXAttr::remove(url, attr); + if (result != 0) + { + error(result, url.path()); + } + finished(); +} TQString FileProtocol::getUserName( uid_t uid ) { @@ -990,8 +1044,6 @@ TQString FileProtocol::getGroupName( gid_t gid ) return *temp; } - - bool FileProtocol::createUDSEntry( const TQString & filename, const TQCString & path, UDSEntry & entry, short int details, bool withACL ) { diff --git a/tdeioslave/file/file.h b/tdeioslave/file/file.h index 04a6ed225..0ceffe264 100644 --- a/tdeioslave/file/file.h +++ b/tdeioslave/file/file.h @@ -62,6 +62,10 @@ public: virtual void mkdir( const KURL& url, int permissions ); virtual void chmod( const KURL& url, int permissions ); virtual void del( const KURL& url, bool isfile); + virtual void listAttr( const KURL& url ); + virtual void readAttr( const KURL& url, const TQCString& attr ); + virtual void writeAttr( const KURL& url, const TQCString& attr, const TQCString& value ); + virtual void removeAttr( const KURL& url, const TQCString& attr ); /** * Special commands supported by this slave: @@ -81,10 +85,10 @@ protected slots: protected: - bool createUDSEntry( const TQString & filename, const TQCString & path, TDEIO::UDSEntry & entry, + bool createUDSEntry( const TQString & filename, const TQCString & path, TDEIO::UDSEntry & entry, short int details, bool withACL ); int setACL( const char *path, mode_t perm, bool _directoryDefault ); - + TQString getUserName( uid_t uid ); TQString getGroupName( gid_t gid ); diff --git a/tdeioslave/file/file.protocol b/tdeioslave/file/file.protocol index 14f17e033..26ed30a6e 100644 --- a/tdeioslave/file/file.protocol +++ b/tdeioslave/file/file.protocol @@ -10,6 +10,8 @@ makedir=true deleting=true linking=true moving=true +readattr=true +writeattr=true maxInstances=4 X-DocPath=tdeioslave/file/index.html Class=:local