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 <philippe.mavridis@yandex.com>
feat/extended-attributes
Philippe Mavridis 3 weeks ago
parent 2e76346c68
commit a6ea4bf998
No known key found for this signature in database
GPG Key ID: BB7EB07B27B3C64B

@ -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;
}

@ -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
}
@ -2861,6 +2864,745 @@ void KBindingPropsPlugin::applyChanges()
config.sync();
}
/* ----------------------------------------------------
*
* 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

@ -715,6 +715,52 @@ 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 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

@ -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

@ -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:

@ -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.
};

@ -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 ); }

@ -127,6 +127,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
*

@ -1241,6 +1241,79 @@ 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<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()

@ -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);

@ -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;

@ -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;

@ -461,6 +461,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

@ -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;

@ -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;

@ -94,6 +94,7 @@
#include <klargefile.h>
#include <tdeglobal.h>
#include <kmimetype.h>
#include <tdexattr.h>
using namespace TDEIO;
@ -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 )
{

@ -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:

@ -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

Loading…
Cancel
Save