summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilippe Mavridis <philippe.mavridis@yandex.com>2025-06-24 16:52:05 +0300
committerPhilippe Mavridis <philippe.mavridis@yandex.com>2025-07-11 21:27:18 +0300
commita6ea4bf99838d3386fcbc33f8bb5cd7826657f48 (patch)
treee5c0274d3baca16dd971e7246dc7562915c5bb21
parent2e76346c68e5c1db6b6058014c024bb70fec5e34 (diff)
downloadtdelibs-feat/extended-attributes.tar.gz
tdelibs-feat/extended-attributes.zip
Add basic extended attributes supportfeat/extended-attributes
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 <philippe.mavridis@yandex.com>
-rw-r--r--tdecore/kprotocolinfo_tdecore.cpp19
-rw-r--r--tdeio/tdefile/kpropertiesdialog.cpp788
-rw-r--r--tdeio/tdefile/kpropertiesdialog.h88
-rw-r--r--tdeio/tdeio/CMakeLists.txt4
-rw-r--r--tdeio/tdeio/global.cpp23
-rw-r--r--tdeio/tdeio/global.h10
-rw-r--r--tdeio/tdeio/job.cpp117
-rw-r--r--tdeio/tdeio/job.h60
-rw-r--r--tdeio/tdeio/jobclasses.h73
-rw-r--r--tdeio/tdeio/kprotocolinfo.cpp18
-rw-r--r--tdeio/tdeio/kprotocolinfo.h26
-rw-r--r--tdeio/tdeio/slavebase.cpp38
-rw-r--r--tdeio/tdeio/slavebase.h39
-rw-r--r--tdeio/tdeio/tdexattr.cpp379
-rw-r--r--tdeio/tdeio/tdexattr.h105
-rw-r--r--tdeioslave/file/file.cpp90
-rw-r--r--tdeioslave/file/file.h11
-rw-r--r--tdeioslave/file/file.protocol2
18 files changed, 1819 insertions, 71 deletions
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 <tdestandarddirs.h>
#include <tdeglobal.h>
#include <tdeapplication.h>
-#include <kdebug.h>
#include <ksimpleconfig.h>
#include <tdeconfig.h>
#include <kstringhandler.h>
@@ -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..8026f0767 100644
--- a/tdeio/tdefile/kpropertiesdialog.cpp
+++ b/tdeio/tdefile/kpropertiesdialog.cpp
@@ -124,6 +124,9 @@ extern "C" {
#include <krun.h>
#include <tdelistview.h>
#include <kacl.h>
+#include <kprotocolinfo.h>
+#include <ktabwidget.h>
+#include <tdeaccel.h>
#include "tdefilesharedlg.h"
#include "kpropertiesdesktopbase.h"
@@ -139,6 +142,17 @@ extern "C" {
# include <win32_utils.h>
#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 +401,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 +501,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 +1345,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 +1400,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 +1419,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 +2567,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 +2579,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 +2588,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
}
@@ -2863,6 +2866,745 @@ void KBindingPropsPlugin::applyChanges()
/* ----------------------------------------------------
*
+ * TDEAttrPropsPlugin
+ *
+ * -------------------------------------------------- */
+class TDEAttrPropsPlugin::TDEAttrEntry : public TQHBox
+{
+ friend class TDEAttrPropsPlugin;
+ public:
+ TDEAttrEntry(TQWidget *parent)
+ : TQHBox(parent, "tdeattrentry")
+ {
+ 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->setFocusPolicy(TQWidget::NoFocus);
+ 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::TDEAttrNamespaceTab : public TQWidget
+{
+ friend class TDEAttrPropsPlugin;
+ public:
+ TDEAttrNamespaceTab(TQWidget *parent, TQCString ns)
+ : TQWidget(parent), m_namespace(ns), m_queryPending(true)
+ {
+ m_layout = new TQVBoxLayout(this);
+ m_entriesLayout = new TQVBoxLayout;
+ m_label = new TQLabel(i18n("Querying attributes..."), this);
+
+ m_layout->setSpacing(KDialog::spacingHint());
+ m_layout->setMargin(KDialog::marginHint());
+
+ m_layout->addWidget(m_label);
+ m_layout->addLayout(m_entriesLayout);
+ m_layout->addStretch();
+
+ // Read saved attributes
+ TDEConfig *config = new TDEConfig("kdeglobals");
+ config->setGroup("TDE Extended Attributes");
+ TQString key = "SavedAttributes_" + ns;
+ m_savedAttrs = config->readListEntry(key);
+
+ m_entryWidgets.setAutoDelete(true);
+ }
+
+ private:
+ bool m_queryPending;
+ TQPtrList<TDEAttrEntry> m_entryWidgets, m_pendingWidgets;
+ TQCString m_namespace;
+ TQVBoxLayout *m_layout, *m_entriesLayout;
+ TQMap<TQCString, TQCString> m_entries;
+ TQLabel *m_label;
+ TQStringList m_savedAttrs;
+};
+
+class TDEAttrPropsPlugin::TDEAttrPropsPluginPrivate
+{
+ public:
+ TDEAttrPropsPluginPrivate() {}
+ ~TDEAttrPropsPluginPrivate() {}
+
+ TQFrame *m_frame, *m_tab;
+ KTabWidget *m_tabWidget;
+ TQMap<TQCString, TDEAttrNamespaceTab*> m_tabs;
+ TQHBox *m_buttonsBox;
+ TQPushButton *m_newEntry, *m_editSavedAttrs, *m_preferences;
+ bool m_showSystemNs;
+ TDEAccel *m_accel;
+};
+
+TDEAttrPropsPlugin::TDEAttrPropsPlugin(KPropertiesDialog *_props)
+ : KPropsDlgPlugin(_props)
+{
+ // Initialise tabs
+ d = new TDEAttrPropsPluginPrivate;
+
+ d->m_frame = properties->addPage(i18n("A&ttributes"));
+
+ d->m_tabWidget = new KTabWidget(d->m_frame);
+ d->m_tabWidget->setTabShape(TQTabWidget::Triangular);
+
+ TDEConfig *config = new TDEConfig("kdeglobals");
+ config->setGroup("TDE Extended Attributes");
+ d->m_showSystemNs = config->readBoolEntry("ShowSystemNs");
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotUpdateTabs()));
+
+ // Initialise button box
+ d->m_buttonsBox = new TQHBox(d->m_frame);
+ d->m_buttonsBox->setSpacing(KDialog::spacingHint());
+ d->m_buttonsBox->setMargin(KDialog::marginHint());
+
+ d->m_newEntry = new TQPushButton(
+ SmallIcon("add"),
+ i18n("Add attribute"),
+ d->m_buttonsBox
+ );
+
+ if (!KProtocolInfo::supportsWritingAttrs(properties->kurl()))
+ {
+ d->m_newEntry->setEnabled(false);
+ }
+
+ d->m_editSavedAttrs = new TQPushButton(
+ SmallIcon("document-save"),
+ i18n("Common attributes..."),
+ d->m_buttonsBox
+ );
+
+ d->m_preferences = new TQPushButton(
+ SmallIcon("configure"),
+ i18n("Preferences..."),
+ d->m_buttonsBox
+ );
+
+ // Initialise shortcuts
+ d->m_accel = new TDEAccel(d->m_frame);
+ d->m_accel->insert(
+ "add", i18n("Add attribute"),
+ i18n("Add a new attribute entry to the current namespace"),
+ TDEShortcut("Insert"), this, TQ_SLOT(slotAddEntry())
+ );
+ d->m_accel->insert(
+ "del", i18n("Remove attribute"),
+ i18n("Remove currently edited attribute entry"),
+ TDEShortcut("Alt+Delete"), this, TQ_SLOT(slotDelEntry())
+ );
+
+ // Create module layout
+ TQVBoxLayout *layout = new TQVBoxLayout(d->m_frame);
+ layout->addWidget(d->m_tabWidget);
+ layout->addWidget(d->m_buttonsBox);
+
+ // Connect signals
+ connect(d->m_newEntry, TQ_SIGNAL(clicked()), TQ_SLOT(slotAddEntry()));
+ connect(d->m_editSavedAttrs, TQ_SIGNAL(clicked()), TQ_SLOT(slotEditSavedAttrs()));
+ connect(d->m_preferences, TQ_SIGNAL(clicked()), TQ_SLOT(slotPreferences()));
+ connect(this, TQ_SIGNAL(changed()), TQ_SLOT(slotCheckNoAttrs()));
+
+ connect(d->m_tabWidget, TQ_SIGNAL(currentChanged(TQWidget*)),
+ TQ_SLOT(slotTabChanged()));
+}
+
+TDEAttrPropsPlugin::~TDEAttrPropsPlugin()
+{
+ delete d;
+}
+
+void
+TDEAttrPropsPlugin::slotUpdateTabs()
+{
+ TQString tip;
+ tip = i18n(
+ "<qt><b>User attributes</b> may be assigned to files and directories "
+ "for storing arbitrary additional information.</qt>"
+ );
+ updateTab("user", i18n("User"), tip);
+
+#if defined(Q_OS_LINUX)
+ tip = i18n(
+ "<qt><p><b>System attributes</b> are used by the kernel to store "
+ "system objects such as Access Control Lists."
+ "<p>Read and write access permissions to system attributes depend on "
+ "the policy implemented for each system attribute implemented by "
+ "filesystems in the kernel.</qt>"
+ );
+ updateTab("system", i18n("System"), tip);
+
+ tip = i18n(
+ "<qt><p><b>Security attributes</b> are used by kernel security modules "
+ "such as SELinux, and also to implement file capabilities."
+ "<p>Read and write access permissions to security attributes depend on "
+ "the policy implemented for each security attribute by the security "
+ "module. When no security module is loaded, all processes have read "
+ "access to extended security attributes, and write access is limited "
+ "to processes that have the CAP_SYS_ADMIN capability.</qt>"
+ );
+ updateTab("security", i18n("Security"), tip);
+
+ tip = i18n(
+ "<qt><p><b>Trusted attributes</b> are visible and accessible only to "
+ "processes that have the CAP_SYS_ADMIN capability. Attributes in this "
+ "class are used to implement mechanisms in user space (i.e., outside "
+ "the kernel) which keep information in extended attributes to which "
+ "ordinary processes should not have access.</qt>"
+ );
+ updateTab("trusted", i18n("Trusted"), tip);
+#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
+ // From extattr(9) (FIXME: possibly find a more informative source?)
+ tip = i18n(
+ "<qt><p><b>System attributes</b> are protected such that appropriate "
+ "privilege is required to directly access or manipulate them.</qt>"
+ );
+ updateTab("system", i18n("System"), tip);
+#endif
+
+ d->m_tabWidget->setTabBarHidden(d->m_tabWidget->count() == 1);
+ if (d->m_tabWidget->count() == 1) slotTabChanged();
+}
+
+void
+TDEAttrPropsPlugin::updateTab(TQCString ns, TQString label, TQString tip)
+{
+ if (d->m_tabs.contains(ns))
+ {
+ if (ns != "user" && !d->m_showSystemNs)
+ {
+ d->m_tabWidget->removePage(d->m_tabs[ns]);
+ d->m_tabs.remove(ns);
+ }
+ return;
+ }
+ else
+ {
+ if (ns != "user" && !d->m_showSystemNs)
+ {
+ return;
+ }
+
+ TDEAttrNamespaceTab *tab = new TDEAttrNamespaceTab(d->m_tabWidget, ns);
+ d->m_tabWidget->addTab(tab, label);
+ d->m_tabWidget->setTabToolTip(tab, tip);
+ TQWhatsThis::add(tab, tip);
+ d->m_tabs[ns] = tab;
+ }
+}
+
+TDEAttrPropsPlugin::TDEAttrNamespaceTab*
+TDEAttrPropsPlugin::currentNamespaceTab()
+{
+ return static_cast<TDEAttrNamespaceTab*>(d->m_tabWidget->currentPage());
+}
+
+void TDEAttrPropsPlugin::slotTabChanged()
+{
+ TDEAttrNamespaceTab *tab = currentNamespaceTab();
+ if (!tab->m_queryPending)
+ {
+ return;
+ }
+
+ slotCheckNoAttrs();
+
+ // Query attributes
+ KURL url = properties->kurl();
+ TDEIO::AttributeJob *listJob = TDEIO::listAttr(url, tab->m_namespace, true);
+ connect(listJob, TQ_SIGNAL(result(TDEIO::Job*)),
+ TQ_SLOT(slotListJobResult(TDEIO::Job*)));
+}
+
+TDEAttrPropsPlugin::TDEAttrEntry *TDEAttrPropsPlugin::addEntry(TDEAttrNamespaceTab *tab)
+{
+ if (!tab) tab = currentNamespaceTab();
+ Q_ASSERT(tab);
+
+ TDEAttrEntry *entryWidget = new TDEAttrEntry(tab);
+ tab->m_entriesLayout->addWidget(entryWidget);
+
+ tab->m_entryWidgets.append(entryWidget);
+
+ entryWidget->m_combo->insertStringList(tab->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(slotDelButtonPressed()));
+}
+
+void TDEAttrPropsPlugin::slotAddEntry()
+{
+ if (!KProtocolInfo::supportsWritingAttrs(properties->kurl()))
+ {
+ return;
+ }
+ TDEAttrEntry *entryWidget = addEntry();
+ connectEntry(entryWidget);
+ entryWidget->m_combo->lineEdit()->setFocus();
+ emit changed();
+}
+
+void TDEAttrPropsPlugin::slotDelEntry()
+{
+ TDEAttrNamespaceTab *tab = currentNamespaceTab();
+ if (tab->focusWidget() && tab->focusWidget()->parentWidget())
+ {
+ // From value lineedit
+ TQWidget *w = tab->focusWidget()->parentWidget();
+ if (qstrcmp(w->name(), "tdeattrentry") == 0)
+ {
+ TDEAttrEntry *entry = static_cast<TDEAttrEntry*>(w);
+ delEntry(entry);
+ }
+
+ // From combobox lineedit
+ else if (qstrcmp(w->parentWidget()->name(), "tdeattrentry") == 0)
+ {
+ TDEAttrEntry *entry = static_cast<TDEAttrEntry*>(w->parentWidget());
+ delEntry(entry);
+ }
+ }
+}
+
+void TDEAttrPropsPlugin::slotEditSavedAttrs()
+{
+ TDEAttrNamespaceTab *tab = currentNamespaceTab();
+ TQString caption = i18n("Common attributes [namespace: %1]")
+ .arg(tab->m_namespace);
+
+ KDialogBase *dlg = new KDialogBase(
+ properties,
+ "TDE Saved Attributes",
+ true,
+ caption,
+ KDialogBase::Ok | KDialogBase::Cancel,
+ KDialogBase::Ok
+ );
+
+ KEditListBox *elb = new KEditListBox(dlg);
+ elb->setTitle(caption);
+ elb->insertStringList(currentNamespaceTab()->m_savedAttrs);
+ connect(dlg, TQ_SIGNAL(okClicked()), this, TQ_SLOT(slotApplySavedAttrs()));
+ dlg->setMainWidget(elb);
+ dlg->exec();
+}
+
+void TDEAttrPropsPlugin::slotApplySavedAttrs()
+{
+ KDialogBase *dlg = static_cast<KDialogBase*>(const_cast<TQObject*>(TQObject::sender()));
+ Q_ASSERT(dlg);
+ KEditListBox *elb = static_cast<KEditListBox*>(dlg->mainWidget());
+ Q_ASSERT(elb);
+
+ TDEAttrNamespaceTab *tab = currentNamespaceTab();
+
+ tab->m_savedAttrs = elb->items();
+ TDEConfig *config = new TDEConfig("kdeglobals");
+ config->setGroup("TDE Extended Attributes");
+ TQString key = "SavedAttributes_" + tab->m_namespace;
+ config->writeEntry(key, tab->m_savedAttrs);
+ config->sync();
+ dlg->deleteLater();
+
+ // Update comboboxes immediately
+ TDEAttrEntry *entry;
+ for (entry = tab->m_entryWidgets.first(); entry;
+ entry = tab->m_entryWidgets.next())
+ {
+ TQString currentText = entry->m_combo->currentText();
+ entry->m_combo->clear();
+ entry->m_combo->insertStringList(tab->m_savedAttrs);
+ entry->m_combo->setCurrentText(currentText);
+ }
+}
+
+void
+TDEAttrPropsPlugin::delEntry(TDEAttrPropsPlugin::TDEAttrEntry *entry)
+{
+ if (!KProtocolInfo::supportsWritingAttrs(properties->kurl()))
+ {
+ return;
+ }
+
+ TQCString attr = entry->key();
+ bool del = false;
+
+ if (attr.isEmpty() && entry->value().isEmpty())
+ {
+ del = true;
+ }
+ else
+ {
+ TQString msg;
+ if (!attr.isEmpty())
+ {
+ msg = i18n("<qt>Are you sure you want to delete attribute <b>%1</b>?</qt>")
+ .arg(TQString::fromLocal8Bit(attr));
+ }
+ else
+ {
+ msg = i18n("<qt>Are you sure you want to delete this unnamed attribute?</qt>");
+ }
+
+ int result = KMessageBox::warningYesNo(
+ properties, msg, i18n("Remove attribute?"),
+ KStdGuiItem::yes(), KStdGuiItem::no(), "ConfirmRemoveAttribute");
+ del = (result == KMessageBox::Yes);
+ }
+
+ if (del)
+ {
+ TDEAttrNamespaceTab *tab = static_cast<TDEAttrNamespaceTab*>(entry->parentWidget());
+ Q_ASSERT(tab);
+
+ tab->m_entryWidgets.remove(entry);
+ if (tab->m_entryWidgets.count() > 0)
+ {
+ tab->m_entryWidgets.last()->m_combo->setFocus();
+ }
+ else
+ {
+ tab->setFocus();
+ }
+ emit changed();
+ }
+}
+
+void TDEAttrPropsPlugin::slotDelButtonPressed()
+{
+ const TQObject *sender = TQObject::sender();
+ TQWidget *button = static_cast<TQWidget*>(const_cast<TQObject*>(sender));
+ Q_ASSERT(button);
+ TDEAttrEntry *entry = static_cast<TDEAttrEntry*>(button->parentWidget());
+ Q_ASSERT(entry);
+
+ delEntry(entry);
+}
+
+void TDEAttrPropsPlugin::slotCheckNoAttrs()
+{
+
+ TDEAttrNamespaceTab *tab = currentNamespaceTab();
+ if (tab->m_entryWidgets.count() == 0)
+ {
+ tab->m_label->show();
+ tab->m_label->setText(i18n("No attributes or insufficient permissions."));
+ }
+ else tab->m_label->hide();
+}
+
+void TDEAttrPropsPlugin::slotListJobResult(TDEIO::Job *job)
+{
+ TDEIO::AttributeJob *listJob = static_cast<TDEIO::AttributeJob*>(job);
+ Q_ASSERT(listJob);
+
+ const TQCString& ns = listJob->attributeNs();
+
+ TDEAttrNamespaceTab *tab = d->m_tabs[ns];
+ Q_ASSERT(tab);
+
+ if (job->error())
+ {
+ job->showErrorDialog();
+ tab->m_label->setText(i18n("Error reading attributes information!"));
+ }
+ else
+ {
+ TQValueList<TQCString> attrs = listJob->attributes();
+ TQValueList<TQCString>::iterator it;
+ for (it = attrs.begin(); it != attrs.end(); ++it)
+ {
+ TQCString attr(*it);
+#if defined(Q_OS_LINUX)
+ if (attr.left(ns.length() + 1) == ns + ".")
+ {
+ attr = attr.mid(ns.length() + 1);
+ }
+#endif
+
+ TDEAttrEntry *entryWidget = addEntry(tab);
+ entryWidget->setKey(attr);
+ tab->m_pendingWidgets.append(entryWidget);
+
+ TDEIO::AttributeJob *readJob = TDEIO::readAttr(properties->kurl(),
+ ns, attr, false);
+ connect(readJob, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotReadJobResult(TDEIO::Job*)));
+ }
+
+ slotCheckNoAttrs();
+ }
+}
+
+void TDEAttrPropsPlugin::slotReadJobResult(TDEIO::Job *job)
+{
+ TDEIO::AttributeJob *readJob = static_cast<TDEIO::AttributeJob*>(job);
+ Q_ASSERT(readJob);
+
+ if (job->error())
+ {
+ job->showErrorDialog();
+ }
+ else
+ {
+ const TQCString& ns = readJob->attributeNs();
+
+ TDEAttrNamespaceTab *tab = d->m_tabs[ns];
+ Q_ASSERT(tab);
+
+ tab->m_queryPending = false;
+
+ const TQCString& attr = readJob->attribute(), val = readJob->value();
+ TDEAttrEntry *entryWidget;
+ for (entryWidget = tab->m_pendingWidgets.first(); entryWidget;
+ entryWidget = tab->m_pendingWidgets.next())
+ {
+ if (entryWidget->key() == attr)
+ {
+ entryWidget->setValue(val);
+ tab->m_pendingWidgets.remove(entryWidget);
+ tab->m_entries[attr] = val;
+ if (KProtocolInfo::supportsWritingAttrs(properties->kurl()))
+ {
+ entryWidget->setReadOnly(false);
+ connectEntry(entryWidget);
+ }
+ tab->m_pendingWidgets.remove(entryWidget);
+ tab->m_entries[attr] = val;
+ return;
+ }
+ }
+
+ kdWarning() << "attribute widget not found: " << ns << "." << attr << endl;
+ }
+}
+
+void TDEAttrPropsPlugin::slotWriteJobResult(TDEIO::Job *job)
+{
+ TDEIO::AttributeJob *writeJob = static_cast<TDEIO::AttributeJob*>(job);
+ Q_ASSERT(writeJob);
+
+ if (job->error())
+ {
+ job->showErrorDialog();
+ }
+ else
+ {
+ TDEAttrNamespaceTab *tab = d->m_tabs[writeJob->attributeNs()];
+ Q_ASSERT(tab);
+ tab->m_entries[writeJob->attribute()] = writeJob->value();
+ }
+ JOB_DONE;
+}
+
+void TDEAttrPropsPlugin::slotRemoveJobResult(TDEIO::Job *job)
+{
+ TDEIO::AttributeJob *removeJob = static_cast<TDEIO::AttributeJob*>(job);
+ Q_ASSERT(removeJob);
+
+ if (job->error())
+ {
+ job->showErrorDialog();
+ }
+ else
+ {
+ TDEAttrNamespaceTab *tab = d->m_tabs[removeJob->attributeNs()];
+ Q_ASSERT(tab);
+ tab->m_entries.remove(removeJob->attribute());
+ }
+ JOB_DONE;
+}
+
+void TDEAttrPropsPlugin::applyChanges()
+{
+ if (!KProtocolInfo::supportsWritingAttrs(properties->kurl()))
+ {
+ return;
+ }
+ TDEIO::AttributeJob *writeJob;
+
+ TQMap<TQCString, TDEAttrNamespaceTab*>::Iterator it;
+ for (it = d->m_tabs.begin(); it != d->m_tabs.end(); ++it)
+ {
+ TDEAttrNamespaceTab *tab = (*it);
+
+ // First do a validation
+ TQStringList existingKeys;
+ TDEAttrEntry *entry;
+ for (entry = tab->m_entryWidgets.first(); entry;
+ entry = tab->m_entryWidgets.next())
+ {
+ if (entry->key().isEmpty() && !entry->value().isEmpty())
+ {
+ KMessageBox::sorry(properties,
+ i18n("There is an attribute without a name."));
+ properties->abortApplying();
+ return;
+ }
+
+ else 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 = tab->m_entryWidgets.first(); entry;
+ entry = tab->m_entryWidgets.next())
+ {
+ // Ignore empty fields
+ if (entry->key().isEmpty()) continue;
+
+ // Ignore if the attribute value is unchanged
+ if (tab->m_entries.contains(entry->key()) &&
+ tab->m_entries[entry->key()] == entry->value())
+ {
+ continue;
+ }
+
+ writeJob = TDEIO::writeAttr(properties->kurl(), tab->m_namespace,
+ entry->key(), entry->value(), true);
+ connect(writeJob, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotWriteJobResult(TDEIO::Job*)));
+ WAIT_FOR_JOB;
+ }
+
+ TQMap<TQCString, TQCString> entries(tab->m_entries);
+ TQMap<TQCString, TQCString>::Iterator it;
+ for (it = entries.begin(); it != entries.end(); ++it)
+ {
+ if (!existingKeys.contains(it.key()))
+ {
+ TDEIO::AttributeJob *rmJob = TDEIO::removeAttr(properties->kurl(),
+ tab->m_namespace,
+ it.key(), true);
+ connect(rmJob, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotRemoveJobResult(TDEIO::Job*)));
+ WAIT_FOR_JOB;
+ }
+ }
+ }
+}
+
+void TDEAttrPropsPlugin::slotPreferences()
+{
+ KDialogBase *dlg = new KDialogBase(
+ properties,
+ "TDE Attribute Properties Plugin Preferences",
+ true,
+ i18n("Preferences"),
+ KDialogBase::Ok | KDialogBase::Cancel,
+ KDialogBase::Ok
+ );
+
+ connect(dlg, TQ_SIGNAL(okClicked()), this, TQ_SLOT(slotApplyPreferences()));
+ TQVBox *vbox = dlg->makeVBoxMainWidget();
+
+ TQCheckBox *showSystemNs = new TQCheckBox(
+ i18n("Show &system attributes"),
+ vbox,
+ "showSystemNs"
+ );
+ showSystemNs->setChecked(d->m_showSystemNs);
+
+ dlg->exec();
+}
+
+void TDEAttrPropsPlugin::slotApplyPreferences()
+{
+ KDialogBase *dlg = static_cast<KDialogBase*>(const_cast<TQObject*>(TQObject::sender()));
+ Q_ASSERT(dlg);
+ TQCheckBox *showSystemNs = static_cast<TQCheckBox*>(dlg->child("showSystemNs", "TQCheckBox"));
+ Q_ASSERT(showSystemNs);
+
+ d->m_showSystemNs = showSystemNs->isChecked();
+
+ TDEConfig *config = new TDEConfig("kdeglobals");
+ config->setGroup("TDE Extended Attributes");
+ config->writeEntry("ShowSystemNs", d->m_showSystemNs);
+ config->sync();
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotUpdateTabs()));
+}
+
+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..f30ba1494 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().
@@ -716,6 +716,52 @@ private:
};
/**
+ * 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 slotDelEntry();
+ void slotDelButtonPressed();
+ void slotEditSavedAttrs();
+ void slotApplySavedAttrs();
+ void slotCheckNoAttrs();
+ void slotTabChanged();
+ void slotPreferences();
+ void slotApplyPreferences();
+ void slotUpdateTabs();
+
+ private:
+ class TDEAttrEntry;
+ class TDEAttrNamespaceTab;
+ class TDEAttrPropsPluginPrivate;
+ TDEAttrPropsPluginPrivate *d;
+
+ protected:
+ void updateTab(TQCString ns, TQString label, TQString tip);
+ TDEAttrNamespaceTab* currentNamespaceTab();
+ void updateComboBoxes();
+ TDEAttrEntry *addEntry(TDEAttrNamespaceTab *tab = nullptr);
+ void delEntry(TDEAttrPropsPlugin::TDEAttrEntry *entry);
+ 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..a52dbe7ec 100644
--- a/tdeio/tdeio/job.cpp
+++ b/tdeio/tdeio/job.cpp
@@ -837,6 +837,120 @@ 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<TQCString> m_attributes;
+
+ // The value of an attribute, result of the READATTR command or the value
+ // passed to WRITEATTR command
+ TQCString m_value;
+
+ // The current namespace
+ TQCString m_namespace;
+
+ // 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;
+
+ stream >> d->m_namespace;
+
+ 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<TQCString> AttributeJob::attributes() { return d->m_attributes; }
+TQCString AttributeJob::attributeNs() { return d->m_namespace; }
+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, const TQCString& ns,
+ bool showProgressInfo)
+{
+ TDEIO_ARGS << url << ns;
+ return new AttributeJob(url, CMD_LISTATTR, packedArgs, showProgressInfo);
+}
+
+AttributeJob *TDEIO::readAttr(const KURL& url, const TQCString& ns,
+ const TQCString& attr, bool showProgressInfo)
+{
+ TDEIO_ARGS << url << ns << attr;
+ return new AttributeJob(url, CMD_READATTR, packedArgs, showProgressInfo);
+}
+
+AttributeJob *TDEIO::writeAttr(const KURL& url, const TQCString& ns,
+ const TQCString& attr, const TQCString& val,
+ bool showProgressInfo)
+{
+ TDEIO_ARGS << url << ns << attr << val;
+ return new AttributeJob(url, CMD_WRITEATTR, packedArgs, showProgressInfo);
+}
+
+AttributeJob *TDEIO::removeAttr(const KURL& url, const TQCString& ns,
+ const TQCString& attr, bool showProgressInfo)
+{
+ TDEIO_ARGS << url << ns << attr;
+ return new AttributeJob(url, CMD_REMOVEATTR, packedArgs, showProgressInfo);
+}
+
//////////
LocalURLJob::LocalURLJob( const KURL& url, int command,
const TQByteArray &packedArgs, bool showProgressInfo )
@@ -4825,6 +4939,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..29b2758b4 100644
--- a/tdeio/tdeio/job.h
+++ b/tdeio/tdeio/job.h
@@ -128,6 +128,66 @@ 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, const TQCString &ns,
+ 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 &ns,
+ 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 &ns,
+ 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 &ns,
+ const TQCString& attr,
+ bool showProgressInfo = true );
+
+ /**
* Retrieve local URL if available
*
* @param remoteURL the remote URL to get the local URL for
diff --git a/tdeio/tdeio/jobclasses.h b/tdeio/tdeio/jobclasses.h
index 5d04b7f5d..c2d494b6b 100644
--- a/tdeio/tdeio/jobclasses.h
+++ b/tdeio/tdeio/jobclasses.h
@@ -1242,6 +1242,79 @@ namespace TDEIO {
};
/**
+ * 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<TQCString> attributes();
+
+ /** This function stores the namespace that this job is related to.
+ * Useful if you request multiple attribute values but have one handler
+ * slot.
+ * @return namespace as passed to listAttr(), readAttr(), writeAttr() or removeAttr()
+ */
+ TQCString attributeNs();
+
+ /** 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()
* @see TDEIO::file_move()
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
@@ -344,6 +344,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
* filesystem using the file-protocol and pass the data on to the destination
@@ -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..137e154c0 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 &, TQCString const &)
+{ error( ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_LISTATTR)); }
+void SlaveBase::readAttr(KURL const &, TQCString const &, TQCString const &)
+{ error( ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_READATTR)); }
+void SlaveBase::writeAttr(KURL const &, TQCString const &, TQCString const &, TQCString const &)
+{ error( ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, CMD_WRITEATTR)); }
+void SlaveBase::removeAttr(KURL const &, TQCString 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,34 @@ void SlaveBase::dispatch( int command, const TQByteArray &data )
stream >> url >> i;
chmod( url, i);
break;
+ case CMD_LISTATTR:
+ {
+ TQCString ns;
+ stream >> url >> ns;
+ listAttr(url, ns);
+ break;
+ }
+ case CMD_READATTR:
+ {
+ TQCString ns, attr;
+ stream >> url >> ns >> attr;
+ readAttr(url, ns, attr);
+ break;
+ }
+ case CMD_WRITEATTR:
+ {
+ TQCString ns, attr, val;
+ stream >> url >> ns >> attr >> val;
+ writeAttr(url, ns, attr, val);
+ break;
+ }
+ case CMD_REMOVEATTR:
+ {
+ TQCString ns, attr;
+ stream >> url >> ns >> attr;
+ removeAttr(url, ns, attr);
+ break;
+ }
case CMD_SPECIAL:
special( data );
break;
diff --git a/tdeio/tdeio/slavebase.h b/tdeio/tdeio/slavebase.h
index 05d3cbbed..0add0362b 100644
--- a/tdeio/tdeio/slavebase.h
+++ b/tdeio/tdeio/slavebase.h
@@ -462,6 +462,45 @@ public:
virtual void chmod( const KURL& url, int permissions );
/**
+ * List extended attributes of @p path
+ * @param url file URL
+ * @param ns attribute namespace (usually "user")
+ * @since R14.1.5
+ */
+ virtual void listAttr(const KURL& url, const TQCString& ns);
+
+ /**
+ * Reads extended attribute @p attr of @p path
+ * @param url file URL
+ * @param ns attribute namespace (usually "user")
+ * @param attr attribute name
+ * @since R14.1.5
+ */
+ virtual void readAttr(const KURL& url, const TQCString& ns,
+ const TQCString& attr);
+
+ /**
+ * Sets the value of extended attribute @p attr of @p path to @p val
+ * @param url file URL
+ * @param ns attribute namespace (usually "user")
+ * @param attr attribute name
+ * @param val attribute value
+ * @since R14.1.5
+ */
+ virtual void writeAttr(const KURL& url, const TQCString& ns,
+ const TQCString& attr, const TQCString& val);
+
+ /**
+ * Removes extended attribute @p attr from @p path
+ * @param url file URL
+ * @param ns attribute namespace (usually "user")
+ * @param attr attribute name
+ * @since R14.1.5
+ */
+ virtual void removeAttr(const KURL& url, const TQCString& ns,
+ const TQCString& attr);
+
+ /**
* Copy @p src into @p dest.
* If the slave returns an error ERR_UNSUPPORTED_ACTION, the job will
* ask for get + put instead.
diff --git a/tdeio/tdeio/tdexattr.cpp b/tdeio/tdeio/tdexattr.cpp
new file mode 100644
index 000000000..6fd715f38
--- /dev/null
+++ b/tdeio/tdeio/tdexattr.cpp
@@ -0,0 +1,379 @@
+/*******************************************************************************
+ Extended attributes support for TDE
+ Copyright © 2025 Philippe Mavridis <philippe.mavridis@yandex.com>
+
+ Based on xattr_p.h from KFileMetaData
+ Copyright © 2014 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+
+ 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 <sys/types.h>
+#include <errno.h>
+
+#if defined(Q_OS_LINUX) || defined(__GLIBC__)
+# include <sys/xattr.h>
+#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
+# include <sys/extattr.h>
+#endif
+
+#include <tqfile.h>
+
+#include <kdebug.h>
+
+#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) // FIXME untested
+static TQValueList<TQCString>
+splitLengthValue(TQByteArray data)
+{
+ uint pos = 0;
+ TQValueList<TQCString> 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;
+}
+
+#define GET_EXTATTR_NS(ns) \
+ int extattrNS = \
+ ns == "user" ? EXTATTR_NAMESPACE_USER : \
+ ns == "system" ? EXTATTR_NAMESPACE_SYSTEM : -1; \
+ if (extattrNS == -1) return TDEIO::ERR_UNSUPPORTED_ACTION
+
+#else
+static TQValueList<TQCString>
+splitZeroSeparator(TQByteArray data)
+{
+ uint pos = 0;
+ TQValueList<TQCString> 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, const TQCString &ns, TQValueList<TQCString> *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)
+ GET_EXTATTR_NS(ns);
+ const ssize_t size = extattr_list_file(encodedPath, extattrNS, nullptr, 0);
+#endif
+
+ if (size == 0)
+ {
+ return 0;
+ }
+ else if (size == -1)
+ {
+ kdWarning() << "(" << errno << ") error while listing attributes of "
+ << url << endl;
+ 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)
+ GET_EXTATTR_NS(ns);
+ const ssize_t r = extattr_list_file(encodedPath, extattrNS, data.data(), data.size());
+#endif
+
+ if (r == 0) {
+ list->clear();
+ return 0;
+ }
+
+ if (r == -1 && errno != ERANGE)
+ {
+ kdWarning() << "(" << errno << ") error while listing attributes of "
+ << url << endl;
+ 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<TQCString> entries = splitZeroSeparator(data);
+ TQCString nsPrefix = ns + ".";
+#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
+ const TQValueList<TQCString> entries = splitLengthValue(data);
+#endif
+
+ list->clear();
+
+ TQValueList<TQCString>::const_iterator it;
+ for (it = entries.begin(); it != entries.end(); ++it)
+ {
+ TQCString attribute(*it);
+
+#if defined(Q_OS_LINUX)
+ if (attribute.left(nsPrefix.length()) != nsPrefix)
+ {
+ continue;
+ }
+#endif
+
+ *list << attribute;
+ }
+ return 0;
+ }
+}
+
+uint
+TDEXAttr::read(const KURL& url, const TQCString &ns, const TQCString& attribute, TQCString *value)
+{
+ CHECK_URL(url);
+ GET_ENCODED_PATH(url);
+
+#if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_getxattr))
+ const ssize_t size = getxattr(encodedPath, ns + "." + attribute, nullptr, 0);
+#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
+ GET_EXTATTR_NS(ns);
+ const ssize_t size = extattr_get_file(encodedPath, extattrNS, attribute, NULL, 0);
+#endif
+
+ if (size == -1)
+ {
+ kdWarning() << "(" << errno << ") error while reading attribute "
+ << ns << "." << attribute << " of " << url << endl;
+ 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, ns + "." + attribute,
+ data.data(), data.size());
+#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
+ GET_EXTATTR_NS(ns);
+ const ssize_t r = extattr_get_file(encodedPath, extattrNS, attribute,
+ data.data(), data.size());
+#endif
+
+ if (r == -1 && errno != ERANGE)
+ {
+ if (errno != ENODATA)
+ {
+ kdWarning() << "(" << errno << ") error while getting value of attribute "
+ << ns << "." << attribute << " of " << url << endl;
+ }
+ *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 &ns, const TQCString& attribute, const TQCString& value)
+{
+ CHECK_URL(url);
+ GET_ENCODED_PATH(url);
+
+ int r;
+#if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_setxattr))
+ r = setxattr(encodedPath, ns + "." + attribute, value.data(), value.size(), 0);
+#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
+ GET_EXTATTR_NS(ns);
+ r = extattr_set_file(encodedPath, extattrNS, attribute, value.data(), value.size());
+#endif
+
+ if (r == -1)
+ {
+ kdWarning() << "(" << errno << ") error while writing attribute "
+ << ns << "." << attribute << " of " << url << endl;
+ }
+ return r == -1 ? parseError(errno, TDEIO::CMD_WRITEATTR) : 0;
+}
+
+uint
+TDEXAttr::remove(const KURL& url, const TQCString &ns, const TQCString& attribute)
+{
+ CHECK_URL(url);
+ GET_ENCODED_PATH(url);
+
+ int r;
+#if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_removexattr))
+ r = removexattr(encodedPath, ns + "." + attribute);
+#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
+ GET_EXTATTR_NS(ns);
+ r = extattr_delete_file (encodedPath, extattrNS, attribute);
+#else
+ return TDEIO::ERR_UNSUPPORTED_ACTION;
+#endif
+
+ if (r == -1)
+ {
+ kdWarning() << "(" << errno << ") error while removing attribute "
+ << ns << "." << attribute << " of " << url << endl;
+ }
+
+ return r == -1 ? parseError(errno, TDEIO::CMD_REMOVEATTR) : 0;
+}
+
+bool
+TDEXAttr::readSupported(const KURL& url, const TQCString &ns)
+{
+ return read(url, ns, "x-tde-test", nullptr) != TDEIO::ERR_UNSUPPORTED_ACTION;
+}
+
+bool
+TDEXAttr::writeSupported(const KURL& url, const TQCString &ns)
+{
+ return write(url, ns, "x-tde-test", nullptr) != TDEIO::ERR_UNSUPPORTED_ACTION
+ && remove(url, ns, "x-tde-test") != TDEIO::ERR_UNSUPPORTED_ACTION;
+}
+
+#else // stubs for unsupported platforms
+
+uint
+TDEXAttr::list(const KURL& url, const TQCString &ns, TQValueList<TQCString> *list)
+{
+ return TDEIO::ERR_UNSUPPORTED_ACTION;
+}
+
+uint
+TDEXAttr::read(const KURL& url, const TQCString &ns, const TQCString& attr, TQCString *value)
+{
+ return TDEIO::ERR_UNSUPPORTED_ACTION;
+}
+
+uint
+TDEXAttr::write(const KURL& url, const TQCString &ns, const TQCString& attr, const TQCString& value)
+{
+ return TDEIO::ERR_UNSUPPORTED_ACTION;
+}
+
+TDEIO::Error
+TDEXAttr::remove(const KURL& url, const TQCString &ns, 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;
+ }
+ }
+}
+
+// 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..a4f1a3458
--- /dev/null
+++ b/tdeio/tdeio/tdexattr.h
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ Extended attributes support for TDE
+ Copyright © 2025 Philippe Mavridis <philippe.mavridis@yandex.com>
+
+ Based on xattr_p.h from KFileMetaData
+ Copyright © 2014 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+
+ 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 <kurl.h>
+#include <tdeio/global.h>
+#include <tqvaluelist.h>
+
+/**
+ * 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 <philippe.mavridis@yandex.com>
+ */
+
+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, const TQCString &ns, TQValueList<TQCString> *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 &ns, 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 &ns, 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 &ns, const TQCString& attribute);
+
+ /**
+ * Check if reading extended attributes is supported for the given URL
+ * and namespace.
+ *
+ * @param url the URL of the file
+ * @return true if extended attributes are supported, false otherwise
+ */
+ static bool readSupported(const KURL& url, const TQCString &ns);
+
+ /**
+ * Check if writing extended attributes is supported for the given URL
+ * and namespace.
+ *
+ * @param url the URL of the file
+ * @return true if extended attributes are supported, false otherwise
+ */
+ static bool writeSupported(const KURL& url, const TQCString &ns);
+
+ 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..a801d71ac 100644
--- a/tdeioslave/file/file.cpp
+++ b/tdeioslave/file/file.cpp
@@ -94,6 +94,7 @@
#include <klargefile.h>
#include <tdeglobal.h>
#include <kmimetype.h>
+#include <tdexattr.h>
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,87 @@ void FileProtocol::del( const KURL& url, bool isfile)
finished();
}
+void FileProtocol::listAttr( const KURL& url, const TQCString &ns )
+{
+ if (!TDEXAttr::readSupported(url, ns))
+ {
+ error(TDEIO::ERR_ACCESS_DENIED, url.path());
+ return;
+ }
+
+ TQValueList<TQCString> attrs;
+ int result = TDEXAttr::list(url, ns, &attrs);
+ if (result != 0)
+ {
+ error(result, url.path());
+ return;
+ }
+ TQValueList<TQCString>::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 &ns, const TQCString& attr )
+{
+ if (!TDEXAttr::readSupported(url, ns))
+ {
+ error(TDEIO::ERR_ACCESS_DENIED, url.path());
+ return;
+ }
+
+ TQCString value;
+ int result = TDEXAttr::read(url, ns, attr, &value);
+ if (result != 0)
+ {
+ error(result, url.path());
+ }
+ else
+ {
+ TQByteArray d;
+ TQDataStream s(d, IO_WriteOnly);
+ s << value;
+ data(d);
+ finished();
+ }
+}
+
+void FileProtocol::writeAttr( const KURL& url, const TQCString &ns, const TQCString& attr, const TQCString& value )
+{
+ if (!TDEXAttr::writeSupported(url, ns))
+ {
+ error(TDEIO::ERR_WRITE_ACCESS_DENIED, url.path());
+ return;
+ }
+
+ int result = TDEXAttr::write(url, ns, attr, value);
+ if (result != 0)
+ {
+ error(result, url.path());
+ }
+ else finished();
+}
+
+void FileProtocol::removeAttr( const KURL& url, const TQCString &ns, const TQCString& attr )
+{
+ if (!TDEXAttr::writeSupported(url, ns))
+ {
+ error(TDEIO::ERR_WRITE_ACCESS_DENIED, url.path());
+ return;
+ }
+
+ int result = TDEXAttr::remove(url, ns, attr);
+ if (result != 0)
+ {
+ error(result, url.path());
+ }
+ finished();
+}
TQString FileProtocol::getUserName( uid_t uid )
{
@@ -990,8 +1072,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..962941ba9 100644
--- a/tdeioslave/file/file.h
+++ b/tdeioslave/file/file.h
@@ -62,6 +62,13 @@ 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, const TQCString &ns );
+ virtual void readAttr( const KURL& url, const TQCString &ns,
+ const TQCString& attr );
+ virtual void writeAttr( const KURL& url, const TQCString &ns,
+ const TQCString& attr, const TQCString& value );
+ virtual void removeAttr( const KURL& url, const TQCString &ns,
+ const TQCString& attr );
/**
* Special commands supported by this slave:
@@ -81,10 +88,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