diff options
| -rw-r--r-- | tdecore/kprotocolinfo_tdecore.cpp | 19 | ||||
| -rw-r--r-- | tdeio/tdefile/kpropertiesdialog.cpp | 782 | ||||
| -rw-r--r-- | tdeio/tdefile/kpropertiesdialog.h | 88 | ||||
| -rw-r--r-- | tdeio/tdeio/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | tdeio/tdeio/global.cpp | 23 | ||||
| -rw-r--r-- | tdeio/tdeio/global.h | 15 | ||||
| -rw-r--r-- | tdeio/tdeio/job.cpp | 117 | ||||
| -rw-r--r-- | tdeio/tdeio/job.h | 60 | ||||
| -rw-r--r-- | tdeio/tdeio/jobclasses.h | 73 | ||||
| -rw-r--r-- | tdeio/tdeio/kprotocolinfo.cpp | 18 | ||||
| -rw-r--r-- | tdeio/tdeio/kprotocolinfo.h | 26 | ||||
| -rw-r--r-- | tdeio/tdeio/slavebase.cpp | 38 | ||||
| -rw-r--r-- | tdeio/tdeio/slavebase.h | 39 | ||||
| -rw-r--r-- | tdeio/tdeio/tdefileitem.cpp | 43 | ||||
| -rw-r--r-- | tdeio/tdeio/tdefileitem.h | 5 | ||||
| -rw-r--r-- | tdeio/tdeio/tdexattr.cpp | 381 | ||||
| -rw-r--r-- | tdeio/tdeio/tdexattr.h | 105 | ||||
| -rw-r--r-- | tdeioslave/file/file.cpp | 146 | ||||
| -rw-r--r-- | tdeioslave/file/file.h | 13 | ||||
| -rw-r--r-- | tdeioslave/file/file.protocol | 2 | 
20 files changed, 1915 insertions, 82 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..1096d871d 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,739 @@ 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); +            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..385403591 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.    }; @@ -303,7 +307,7 @@ namespace TDEIO    TDEIO_EXPORT TQString unsupportedActionErrorString(const TQString &protocol, int cmd);    /** -   * Constants used to specify the type of a KUDSAtom. +   * Constants used to specify the type of a UDSAtom.     */    enum UDSAtomTypes {      /// First let's define the item types @@ -349,8 +353,11 @@ namespace TDEIO      /// Only available for directories.      /// @since 3.5      UDS_DEFAULT_ACL_STRING = 104 | UDS_STRING, +    /// The extended attributes associated with the file, serialized into a +    /// single string of the format "key1=value1;key2=value2" +    UDS_ATTRIBUTES = 112 | UDS_STRING, -    // available: 112, 120  +    // available: 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/tdefileitem.cpp b/tdeio/tdeio/tdefileitem.cpp index aa4d1f873..213e9368f 100644 --- a/tdeio/tdeio/tdefileitem.cpp +++ b/tdeio/tdeio/tdefileitem.cpp @@ -36,6 +36,7 @@  #include <tqmap.h>  #include <tqstylesheet.h>  #include <tqimage.h> +#include <tqregexp.h>  #include <kdebug.h>  #include <tdefilemetainfo.h> @@ -272,6 +273,21 @@ void KFileItem::readUDSEntry( bool _urlIsDirectory )          else            m_hidden = Shown;          break; + +      case TDEIO::UDS_ATTRIBUTES: +      { +        m_attrs.clear(); +        TQString serialized = (*it).m_str; +        TQStringList pairs = TQStringList::split(TQChar((char)30), serialized); +        for (TQStringList::Iterator it = pairs.begin(); it != pairs.end(); ++it) +        { +            TQStringList pair = TQStringList::split(TQChar((char)31), (*it)); +            TQString key = pair[0]; +            TQString value = pair[1]; +            m_attrs[key] = value; +        } +        break; +      }      }    } @@ -291,6 +307,7 @@ void KFileItem::refresh()    m_group = TQString::null;    m_metaInfo = KFileMetaInfo();    m_hidden = Auto; +  m_attrs.clear();    // Basically, we can't trust any information we got while listing.    // Everything could have changed... @@ -597,10 +614,10 @@ TQString KFileItem::mimeComment()  	if (d && (d->commentCached)) return d->comment;  	KMimeType::Ptr mType = determineMimeType(); -	 +  	bool isLocalURL;  	KURL url = mostLocalURL(isLocalURL); -	 +  	TQString comment = mType->comment( url, isLocalURL );  	//kdDebug() << "finding comment for " << url.url() << " : " << m_pMimeType->name() << endl;  	if ( !d ) { @@ -718,7 +735,7 @@ TQPixmap KFileItem::pixmap( int _size, int _state ) const  		kdWarning() << "failed to open file" << url.path() << endl;  		return p;  	} -	 +  	icon_size = _size;  	icon = libr_icon_geticon_bysize(handle, icon_size); @@ -937,6 +954,9 @@ TQString KFileItem::getToolTipText(int maxcount)    TQString tip;    KFileMetaInfo info = metaInfo(); +#define TIP_SEPARATOR "<tr><td colspan=2></td></tr>" +  // TODO find better style (e.g. colored headings) +    // the font tags are a workaround for the fact that the tool tip gets    // screwed if the color scheme uses white as default text color    const char* start = "<tr><td><nobr><font color=\"black\">"; @@ -976,7 +996,7 @@ TQString KFileItem::getToolTipText(int maxcount)    if (info.isValid() && !info.isEmpty() )    { -    tip += "<tr><td colspan=2><center><s>                    </s></center></td></tr>"; +    tip += TIP_SEPARATOR;      TQStringList keys = info.preferredKeys();      // now the rest @@ -1005,8 +1025,22 @@ TQString KFileItem::getToolTipText(int maxcount)        }      }    } + +  if (m_attrs.count() > 0) +  { +    tip += TIP_SEPARATOR; +    tip += "<tr><td colspan=2><nobr><center><b>" + i18n("Attributes") + "</td></tr>"; +    TQMap<TQString, TQString>::Iterator it; +    for (it = m_attrs.begin(); it != m_attrs.end(); ++it) +    { +      tip += start + it.key() + mid + it.data() + end; +    } +  } +    tip += "</table>"; +#undef TIP_SEPARATOR +    //kdDebug() << "making this the tool tip rich text:\n";    //kdDebug() << tip << endl; @@ -1107,6 +1141,7 @@ void KFileItem::setUDSEntry( const TDEIO::UDSEntry& _entry, const KURL& _url,    m_hidden = Auto;    m_guessedMimeType = TQString::null;    m_metaInfo = KFileMetaInfo(); +  m_attrs.clear();    if ( d ) {      d->iconName = TQString::null; diff --git a/tdeio/tdeio/tdefileitem.h b/tdeio/tdeio/tdefileitem.h index 171b611e3..450c952d9 100644 --- a/tdeio/tdeio/tdefileitem.h +++ b/tdeio/tdeio/tdefileitem.h @@ -259,9 +259,9 @@ public:     * Returns the size of the file, if known, and sets @p hasSize to false if not known     * @param @hasSize This is set to true if the size is known, and false if not known     * @return the file size, or 0 if not known -   */  +   */    TDEIO::filesize_t size(bool &hasSize) const; -   +    //FIXME KDE4 deprecate this in favor of time(unsigned int which, bool &hasSize)    /**     * Requests the modification, access or creation time, depending on @p which. @@ -674,6 +674,7 @@ private:    TQString m_guessedMimeType;    mutable TQString m_access;    TQMap<const void*, void*> m_extra; +  TQMap<TQString, TQString> m_attrs;    mutable KFileMetaInfo m_metaInfo;    enum { Modification = 0, Access = 1, Creation = 2, NumFlags = 3 }; diff --git a/tdeio/tdeio/tdexattr.cpp b/tdeio/tdeio/tdexattr.cpp new file mode 100644 index 000000000..649c3cc61 --- /dev/null +++ b/tdeio/tdeio/tdexattr.cpp @@ -0,0 +1,381 @@ +/******************************************************************************* +  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 \ +    if (!url.isValid() || url.isEmpty()) \ +        return TDEIO::ERR_MALFORMED_URL; \ +    if (!url.isLocalFile()) \ +        return 0; // silently ignore non-local urls + +# define GET_ENCODED_PATH \ +    CHECK_URL \ +    const char *encodedPath = TQFile::encodeName(url.path()).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) +{ +    GET_ENCODED_PATH + +# 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.path() << 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.path() << 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; +            } +            attribute = attribute.mid(nsPrefix.length()); + +# endif + +            *list << attribute; +        } +        return 0; +    } +} + +uint +TDEXAttr::read(const KURL& url, const TQCString &ns, const TQCString& attribute, TQCString *value) +{ +    GET_ENCODED_PATH + +# 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) +    { +        if (attribute != "x-tde-test") +        { +            kdWarning() << "(" << errno << ") error while reading attribute " +                        << ns << "." << attribute << " of " << url.path() << 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.path() << 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) +{ +    GET_ENCODED_PATH + +    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 && attribute != "x-tde-test") +    { +        kdWarning() << "(" << errno << ") error while writing attribute " +                    << ns << "." << attribute << " of " << url.path() << endl; +    } +    return r == -1 ? parseError(errno, TDEIO::CMD_WRITEATTR) : 0; +} + +uint +TDEXAttr::remove(const KURL& url, const TQCString &ns, const TQCString& attribute) +{ +    GET_ENCODED_PATH + +    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 && attribute != "x-tde-test") +    { +        kdWarning() << "(" << errno << ") error while removing attribute " +                    << ns << "." << attribute << " of " << url.path() << 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..1a2c5183c 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; @@ -106,6 +107,7 @@ static bool isExtendedACL(  acl_t p_acl );  static void appendACLAtoms( const TQCString & path, UDSEntry& entry,                              mode_t type, bool withACL );  #endif +static void appendAttributeAtom(const TQString& filename, const TQString& path, UDSEntry& entry);  extern "C" { TDE_EXPORT int kdemain(int argc, char **argv); } @@ -360,7 +362,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 +628,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 +841,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 +957,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,10 +1073,8 @@ TQString FileProtocol::getGroupName( gid_t gid )          return *temp;  } - - -bool FileProtocol::createUDSEntry( const TQString & filename, const TQCString & path, UDSEntry & entry, -                                   short int details, bool withACL ) +bool FileProtocol::createUDSEntry( const TQString & filename, const TQCString & path, const TQString & absPath, +                                   UDSEntry & entry, short int details, bool withACL )  {      assert(entry.count() == 0); // by contract :-)      // Note: details = 0 (only "file or directory or symlink or doesn't exist") isn't implemented @@ -1071,6 +1152,8 @@ bool FileProtocol::createUDSEntry( const TQString & filename, const TQCString &      appendACLAtoms( path, entry, type, withACL );  #endif +    appendAttributeAtom(filename, absPath, entry); +   notype:      atom.m_uds = TDEIO::UDS_MODIFICATION_TIME;      atom.m_long = buff.st_mtime; @@ -1120,7 +1203,7 @@ void FileProtocol::stat( const KURL & url )      kdDebug(7101) << "FileProtocol::stat details=" << details << endl;      UDSEntry entry; -    if ( !createUDSEntry( url.fileName(), _path, entry, details, true /*with acls*/ ) ) +    if ( !createUDSEntry( url.fileName(), _path, url.upURL().path(), entry, details, true /*with acls*/ ) )      {          error( TDEIO::ERR_DOES_NOT_EXIST, url.path(-1) );          return; @@ -1256,6 +1339,7 @@ void FileProtocol::listDir( const KURL& url)          entry.clear();          if ( createUDSEntry( TQFile::decodeName(*it),                               *it /* we can use the filename as relative path*/, +                             _path,                               entry, 2, true ) )            listEntry( entry, false);          //else @@ -1887,4 +1971,50 @@ static void appendACLAtoms( const TQCString & path, UDSEntry& entry, mode_t type  }  #endif +static void +appendAttributeAtom(const TQString& filename, const TQString& path, UDSEntry& entry) +{ +    KURL url(path); +    url.addPath(filename); +    url.cleanPath(); + +//     if (!TDEXAttr::readSupported(url, "user")) +//     { +//         kdWarning() << "attributes read not supported" << endl; +//         return; +//     } + +    TQString serialized; + +    TQValueList<TQCString> attrs; +    int result = TDEXAttr::list(url, "user", &attrs); +    if (result != 0) +    { +        kdWarning() << "no attributes" << endl; +        return; +    } + +    TQValueList<TQCString>::iterator it; +    TQChar us((char)31), rs((char)30); +    for (it = attrs.begin(); it != attrs.end(); ++it) +    { +        TQCString key(*it); +        TQCString value; + +        int result = TDEXAttr::read(url, "user", key, &value); +        if (result == 0) +        { +            serialized += TQString::fromLocal8Bit(key).remove(us).remove(rs); +            serialized += us; +            serialized += TQString::fromLocal8Bit(value).remove(us).remove(rs); +            serialized += rs; +        } +    } + +    UDSAtom atom; +    atom.m_uds = TDEIO::UDS_ATTRIBUTES; +    atom.m_str = serialized; +    entry.append(atom); +} +  #include "file.moc" diff --git a/tdeioslave/file/file.h b/tdeioslave/file/file.h index 04a6ed225..5ebde9e7c 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,  -                       short int details, bool withACL ); +  bool createUDSEntry( const TQString & filename, const TQCString & path, const TQString & absPath, +                       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 | 
