Skip to content

Commit

Permalink
Introduce metadata tagging functions for files and directories
Browse files Browse the repository at this point in the history
  • Loading branch information
erikjv committed Nov 15, 2023
1 parent 83fbdeb commit 77e92f0
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 5 deletions.
102 changes: 99 additions & 3 deletions src/libsync/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,22 @@

#include "filesystem.h"

#include "common/asserts.h"
#include "common/utility.h"
#include <QFile>
#include <QFileInfo>
#include <QCoreApplication>
#include <QDir>
#include <QDirIterator>
#include <QCoreApplication>
#include <QFile>
#include <QFileInfo>

#include "csync.h"
#include "vio/csync_vio_local.h"
#include "std/c_time.h"

#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
#include <sys/xattr.h>
#endif

#ifdef Q_OS_WIN32
#include "common/utility_win.h"
#include <winsock2.h>
Expand Down Expand Up @@ -198,5 +203,96 @@ bool FileSystem::getInode(const QString &filename, quint64 *inode)
return false;
}

namespace {

#ifdef Q_OS_LINUX
Q_ALWAYS_INLINE ssize_t getxattr(const char *path, const char *name, void *value, size_t size, u_int32_t, int)
{
return ::getxattr(path, name, value, size);
}

Q_ALWAYS_INLINE int setxattr(const char *path, const char *name, const void *value, size_t size, u_int32_t, int)
{
return ::setxattr(path, name, value, size, 0);
}

Q_ALWAYS_INLINE int removexattr(const char *path, const char *name, int)
{
return ::removexattr(path, name);
}
#endif // Q_OS_LINUX

} // anonymous namespace

static constexpr unsigned MaxValueSize = 1023; // This is without a terminating NUL character

std::optional<QByteArray> FileSystem::Tags::get(const QString &path, const QString &key)
{
#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
QString platformKey = key;
if (Utility::isLinux()) {
platformKey = QStringLiteral("user.") + platformKey;
}

QByteArray value(MaxValueSize + 1, '\0'); // Add a NUL character to terminate a string
auto size = getxattr(path.toUtf8().constData(), platformKey.toUtf8().constData(), value.data(), MaxValueSize, 0, 0);
if (size != -1) {
value.truncate(size);
return value;
}
#elif defined(Q_OS_WIN)
QFile file(QStringLiteral("%1:%2").arg(path, key));
if (file.open(QIODevice::ReadOnly)) {
return file.readAll();
}
#endif // Q_OS_MAC || Q_OS_LINUX

return {};
}

bool FileSystem::Tags::set(const QString &path, const QString &key, const QString &value)
{
OC_ASSERT(value.size() < MaxValueSize)

#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
QString platformKey = key;
if (Utility::isLinux()) {
platformKey = QStringLiteral("user.") + platformKey;
}

const QByteArray utf8Value = value.toUtf8();
auto result = setxattr(path.toUtf8().constData(), platformKey.toUtf8().constData(), utf8Value.constData(), utf8Value.size(), 0, 0);

return result == 0;
#elif defined(Q_OS_WIN)
QFile file(QStringLiteral("%1:%2").arg(path, key));
if (!file.open(QIODevice::WriteOnly)) {
return {};
}
const QByteArray utf8Value = value.toUtf8();
return file.write(utf8Value) == utf8Value.size();
#else
return false;
#endif // Q_OS_MAC || Q_OS_LINUX
}

bool FileSystem::Tags::remove(const QString &path, const QString &key)
{
#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
QString platformKey = key;
if (Utility::isLinux()) {
platformKey = QStringLiteral("user.") + platformKey;
}

auto result = removexattr(path.toUtf8().constData(), platformKey.toUtf8().constData(), 0);

return result == 0;
#elif defined(Q_OS_WIN)
return QFile::remove(QStringLiteral("%1:%2").arg(path, key));
#else
return false;
#endif // Q_OS_MAC || Q_OS_LINUX
}


} // namespace OCC
6 changes: 6 additions & 0 deletions src/libsync/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ namespace FileSystem {
RemoveEntryList *success,
RemoveEntryList *locked,
RemoveErrorList *errors);

namespace Tags {
std::optional<QByteArray> OWNCLOUDSYNC_EXPORT get(const QString &path, const QString &key);
bool OWNCLOUDSYNC_EXPORT set(const QString &path, const QString &key, const QString &value);
bool OWNCLOUDSYNC_EXPORT remove(const QString &path, const QString &key);
}
}

/** @} */
Expand Down
46 changes: 44 additions & 2 deletions test/testutility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
#include <QtTest>
#include <QTemporaryDir>

#include "filesystem.h"
#include "testutils/testutils.h"

#include "common/filesystembase.h"
#include "common/utility.h"

#include "libsync/theme.h"

using namespace OCC::Utility;

namespace OCC {
Expand Down Expand Up @@ -261,6 +262,47 @@ private slots:
CHECK_NORMALIZE_ETAG("\"foo\"-gzip", "foo");
CHECK_NORMALIZE_ETAG("\"foo-gzip\"", "foo");
}

void testFileMetaData()
{
using namespace OCC::TestUtils;
using namespace OCC::FileSystem;

QTemporaryDir temp = createTempDir();
QFile tempFile(temp.filePath(QStringLiteral("testfile")));
QVERIFY(tempFile.open(QIODevice::WriteOnly));
QByteArray data(64, 'X');
QCOMPARE(tempFile.write(data), data.size());
tempFile.close();

const auto fn = tempFile.fileName();
const QString testKey = QStringLiteral("testKey");
const QString testValue = QStringLiteral("testValue");

QVERIFY(!Tags::get(fn, testKey).has_value());
QVERIFY(Tags::set(fn, testKey, testValue));
QCOMPARE(Tags::get(fn, testKey).value(), testValue.toUtf8());
QVERIFY(Tags::remove(fn, testKey));
QVERIFY(!Tags::get(fn, testKey).has_value());
}

void testDirMetaData()
{
using namespace OCC::TestUtils;
using namespace OCC::FileSystem;

QTemporaryDir tempDir = createTempDir();

const auto fn = tempDir.path();
const QString testKey = QStringLiteral("testKey");
const QString testValue = QStringLiteral("testValue");

QVERIFY(!Tags::get(fn, testKey).has_value());
QVERIFY(Tags::set(fn, testKey, testValue));
QCOMPARE(Tags::get(fn, testKey).value(), testValue.toUtf8());
QVERIFY(Tags::remove(fn, testKey));
QVERIFY(!Tags::get(fn, testKey).has_value());
}
};

QTEST_GUILESS_MAIN(TestUtility)
Expand Down

0 comments on commit 77e92f0

Please sign in to comment.