Details
-
Suggestion
-
Resolution: Unresolved
-
P3: Somewhat important
-
None
-
None
-
None
Description
QFile or a another class should integrate xattr.
http://man7.org/linux/man-pages/man7/xattr.7.html
"Extended attributes are name:value pairs associated permanently with
files and directories, similar to the environment strings associated
with a process."
I am going to keep it light on details in case it is outright rejected. What I would like to see though is the ability to read and write key value pairs to extended attributes on QFile, that would be mindful of of the filesystem capabilities. A structure similar to QMap would make sense to me:
QFile f_Foobar("~/foobar.txt"); f_Foobar.insert("foo" , "bar"); // returns enum or ssize_t error f_Foobar.insert("user.foo", "bar"); // returns 0 for no error f_Foobar["user.foo"]; // returns "bar" f_Foobar.keys(); // returns QStringList() << "foo";
The benefit of this is this provides a simple and accessible way to maintain state between sessions.
The downside I see to this, is limited portability. However, we do have functionality already in Qt that is limited to certain platforms, so maybe this is fine.
edit:
I went ahead and created my own functional class.
The long and short of it, is that we should create a class for handling xattr, "QXattr", and make it a member of QFile. I am guessing a pointer who's default value is "nullptr", but in the constructor, will initiate it if it detects a filesystem with xattr capabilities.
Hence:
QFile f("/home/akiva/foobar.txt"); f->xattr.insert("user.foo", "foo"); f->xattr.insert("user.bar", "bar"); qDebug() << f.xattr.value<QByteArray>("user.foo");
Here is the header:
#ifndef QXATTR_H #define QXATTR_H #include <QObject> #include <QFile> #include <sys/xattr.h> class QXattr : public QObject { Q_OBJECT public: explicit QXattr(QFile &parent); template<typename T> bool insert ( const QString &name, T value ); // Returns true if properly serialized template<typename T> T value ( const QString &name ); // Gets value of name. Returns Null pointer if none found. template<typename T> QString name ( T value ); // Returns first name of given value template<typename T> QStringList names ( T value ); // Returns all names of a given value QStringList names (); // Returns all names bool contains( const QString &name ); // Returns true if name exists. bool remove ( const QString &name ); // Returns true if something was removed. QString errorString(); // Last registered errno protected: inline const char *fileName(); void setErrorString(const QString &error); void getNames(); // Should trigger on a move action in case the filesystem changed signals: void namesChanged(); void errorStringChanged( QString error ); // More signals will be placed here private: QStringList m_Names; QFile &m_File; QString m_ErrorString; }; #endif // QXATTR_H
Instead of "Key:Value" pairs, I used the wording "Name:Value" pairs, as that is what is used in xattr's documentation, and key implies that different types other than a string can be used.
In the source, I included my own library qconsoletoolkit. It accounts for the `ct_Check( ... )` lines, and can be easily changed. Anyway, here is the source:
#include "qxattr.h" #include "qconsoletoolkit.h" QXattr::QXattr(QFile &parent) : QObject(&parent) , m_File(parent) {} /* Insertion Templates */ template<typename T> bool QXattr::insert(const QString &name, T value) { Q_UNUSED(name) Q_UNUSED(value) return false; } template<> bool QXattr::insert<QByteArray> (const QString &name, QByteArray value) { int error = setxattr(fileName(), name.toLocal8Bit().data(), value.data(), size_t(value.size()), 0 ); ct_Check(error == -1, strerror(errno)); this->getNames(); return true; } template<> bool QXattr::insert<QString> (const QString &name, QString value) { return this->insert<QByteArray>(name, value.toLocal8Bit()); } template<> bool QXattr::insert<const char*>(const QString &name, const char* value) { return this->insert<QByteArray>(name, QByteArray(value)); } /* Value Templates */ template<typename T> T QXattr::value(const QString &name) { Q_UNUSED(name) } template<> QByteArray QXattr::value<QByteArray>(const QString &name) { ssize_t valueLength; valueLength = getxattr(fileName(), name.toLocal8Bit().data(), nullptr, 0); ct_Check(valueLength == -1, strerror(errno)); if (!valueLength) { return QByteArray(); } QByteArray val(int(valueLength), ' '); valueLength = getxattr(fileName(), name.toLocal8Bit().data(), val.data(), size_t(valueLength)); ct_Check(valueLength == -1, strerror(errno)); return val; } /* Name Templates */ template<typename T> QString QXattr::name(T value) { Q_UNUSED(value) return QString(); } template<> QString QXattr::name<const char *>(const char* value) { getNames(); QByteArray ba(value); for ( int i = 0; i < m_Names.length(); i++ ) { if (ba == this->value<QByteArray>(m_Names.at(i))) { return m_Names.at(i); } } return QString(); } /* Names Templates */ template<class T> QStringList QXattr::names(T value) { Q_UNUSED(value) return QStringList(); } template<> QStringList QXattr::names<const char *>(const char* value) { getNames(); QByteArray ba(value); QStringList names; for ( int i = 0; i < m_Names.length(); i++ ) { if (ba == this->value<QByteArray>(m_Names.at(i))) { names << m_Names.at(i); } } return names; } QStringList QXattr::names() { getNames(); return m_Names; } /* Regular Functions */ bool QXattr::contains(const QString &name) { getNames(); return m_Names.contains(name); } bool QXattr::remove(const QString &name) { // Returns false if error. // Error can mean, that the name simply did not exist. if ( removexattr(fileName(), name.toLocal8Bit().data()) == -1 ) { setErrorString(strerror(errno)); return false; } return true; } /* Convenience Functions */ const char* QXattr::fileName() { return m_File.fileName().toLocal8Bit().data(); } void QXattr::setErrorString(const QString &error) { m_ErrorString = error; emit errorStringChanged( error ); } void QXattr::getNames() { m_Names.clear(); ssize_t bufferLength(0); ssize_t nameLength; char *name; /* Determine the length of the buffer needed. */ bufferLength = listxattr(fileName(), nullptr, 0); ct_Check(bufferLength == -1, strerror(errno)); if (!bufferLength) { return; } /* Allocate the buffer. */ QByteArray buffer(int(bufferLength), ' '); /* Copy the list of attribute keys to the buffer. */ bufferLength = listxattr(fileName(), buffer.data(), size_t(bufferLength)); ct_Check(bufferLength == -1, strerror(errno)); /* Loop over the list of zero terminated strings with the attribute keys. * Use the remaining buffer length to determine the end of the list. */ name = buffer.data(); QStringList names; while (bufferLength > 0) { m_Names << name; /* Forward to next attribute name. */ nameLength = ssize_t(strlen(name) + 1); bufferLength -= nameLength; name += nameLength ; } }
No issues thus far.