Skip to content
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ add_feature_info(AppImageUpdate WITH_APPIMAGEUPDATER "Built-in libappimageupdate
option(WITH_EXTERNAL_BRANDING "A URL to an external branding repo" "")

# specify additional vfs plugins
set(VIRTUAL_FILE_SYSTEM_PLUGINS off cfapi CACHE STRING "Name of internal plugin in src/libsync/vfs or the locations of virtual file plugins")
set(VIRTUAL_FILE_SYSTEM_PLUGINS off cfapi xattr CACHE STRING "Name of internal plugin in src/libsync/vfs or the locations of virtual file plugins")

if(APPLE)
set( SOCKETAPI_TEAM_IDENTIFIER_PREFIX "" CACHE STRING "SocketApi prefix (including a following dot) that must match the codesign key's TeamIdentifier/Organizational Unit" )
Expand Down
5 changes: 5 additions & 0 deletions src/gui/folderwizard/folderwizard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,12 @@ const AccountStatePtr &FolderWizardPrivate::accountState()

bool FolderWizardPrivate::useVirtualFiles() const
{
#ifdef Q_OS_WIN
return VfsPluginManager::instance().bestAvailableVfsMode() == Vfs::WindowsCfApi;
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
return VfsPluginManager::instance().bestAvailableVfsMode() == Vfs::XAttr;
#endif
return false;
}

FolderWizard::FolderWizard(const AccountStatePtr &account, QWidget *parent)
Expand Down
6 changes: 3 additions & 3 deletions src/gui/guiutility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ void Utility::markDirectoryAsSyncRoot(const QString &path, const QUuid &accountU
Q_ASSERT(getDirectorySyncRootMarkings(path).first.isEmpty());
Q_ASSERT(getDirectorySyncRootMarkings(path).second.isNull());

auto result1 = FileSystem::Tags::set(path, dirTag(), Theme::instance()->orgDomainName().toUtf8());
auto result1 = FileSystem::Tags::set(path, dirTag(), Theme::instance()->orgDomainName());
if (!result1) {
qCWarning(lcGuiUtility) << QStringLiteral("Failed to set tag on »%1«: %2").arg(path, result1.error())
#ifdef Q_OS_WIN
Expand All @@ -110,7 +110,7 @@ void Utility::markDirectoryAsSyncRoot(const QString &path, const QUuid &accountU
return;
}

auto result2 = FileSystem::Tags::set(path, uuidTag(), accountUuid.toString().toUtf8());
auto result2 = FileSystem::Tags::set(path, uuidTag(), accountUuid.toString());
if (!result2) {
qCWarning(lcGuiUtility) << QStringLiteral("Failed to set tag on »%1«: %2").arg(path, result2.error())
#ifdef Q_OS_WIN
Expand All @@ -127,7 +127,7 @@ std::pair<QString, QUuid> Utility::getDirectorySyncRootMarkings(const QString &p
auto existingUuidTag = FileSystem::Tags::get(path, uuidTag());

if (existingDirTag.has_value() && existingUuidTag.has_value()) {
return {QString::fromUtf8(existingDirTag.value()), QUuid::fromString(QString::fromUtf8(existingUuidTag.value()))};
return {existingDirTag.value(), QUuid::fromString(existingUuidTag.value())};
}

return {};
Expand Down
49 changes: 37 additions & 12 deletions src/gui/socketapi/socketapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "syncengine.h"
#include "syncfileitem.h"
#include "theme.h"
#include "vfs/hydrationjob.h"

#include <QApplication>
#include <QDir>
Expand Down Expand Up @@ -427,6 +428,24 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList
listener->registerMonitoredDirectory(qHash(directory));

statusString = fileData.syncFileStatus().toSocketAPIString();

#ifdef Q_OS_LINUX
// append vfs status in case...
QString vfsStatus;
const auto fileType = fileData.journalRecord().type();
if (fileType == ItemTypeVirtualFileDownload || fileType == ItemTypeVirtualFile) {
vfsStatus = QStringLiteral("+VIRT");
}
const auto pState = fileData.folder->vfs().pinState(argument);
if (pState) {
if (*pState == PinState::AlwaysLocal) {
vfsStatus += QStringLiteral("+AL");
} else if (*pState == PinState::OnlineOnly) {
vfsStatus += QStringLiteral("+OO");
}
}
statusString.append(vfsStatus);
#endif
}

const QString message = QStringLiteral("STATUS:") % statusString % QLatin1Char(':') % QDir::toNativeSeparators(argument);
Expand Down Expand Up @@ -476,7 +495,9 @@ void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener

void SocketApi::command_VERSION(const QString &, SocketListener *listener)
{
listener->sendMessage(QStringLiteral("VERSION:%1:%2").arg(OCC::Version::versionWithBuildNumber().toString(), QStringLiteral(MIRALL_SOCKET_API_VERSION)));
listener->sendMessage(QStringLiteral("VERSION:%1:%2:%3").arg(OCC::Version::versionWithBuildNumber().toString(),
QStringLiteral(MIRALL_SOCKET_API_VERSION),
QString::number(qApp->applicationPid())));
}

void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener)
Expand Down Expand Up @@ -665,23 +686,27 @@ void SocketApi::command_V2_HYDRATE_FILE(const QSharedPointer<SocketApiJobV2> &jo
{
const auto &arguments = job->arguments();

const QString targetPath = arguments[QStringLiteral("file")].toString();
const QByteArray fileId = arguments[QStringLiteral("fileId")].toString().toUtf8();
const QString targetPath = arguments[QStringLiteral("file")].toString();

auto fileData = FileData::get(targetPath);

if (fileData.folder) {
auto watcher = new QFutureWatcher<Result<void, QString>>();
connect(watcher, &QFutureWatcher<Result<void, QString>>::finished, this, [job, watcher] {
const auto resut = watcher->result<Result<void, QString>>();
watcher->deleteLater();
if (!resut) {
job->success({{QStringLiteral("status"), QStringLiteral("ERROR")}, {QStringLiteral("error"), resut.error()}});
} else {
HydrationJob *hydJob = fileData.folder->vfs().hydrateFile(fileId, targetPath);

if (hydJob) {
connect(hydJob, &HydrationJob::finished, this, [job, hydJob] {
job->success({{QStringLiteral("status"), QStringLiteral("OK")}});
}
});
watcher->setFuture(fileData.folder->vfs().hydrateFile(fileId, targetPath));
hydJob->deleteLater();
});
connect(hydJob, &HydrationJob::error, this, [job, hydJob](const QString& err) {
job->success({{QStringLiteral("status"), QStringLiteral("ERROR")}, {QStringLiteral("error"), err}});
hydJob->deleteLater();
});
hydJob->start();
} else {
qCDebug(lcSocketApi) << "Hydration job for" << fileId << "already running";
}
} else {
job->failure(QStringLiteral("cannot hydrate unknown file"));
}
Expand Down
16 changes: 8 additions & 8 deletions src/libsync/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ set_package_properties(LibreGraphAPI PROPERTIES

configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)

set(libsync_SRCS

add_library(libsync SHARED
account.cpp
bandwidthmanager.cpp
capabilities.cpp
Expand Down Expand Up @@ -45,8 +46,9 @@ set(libsync_SRCS
syncoptions.cpp
theme.cpp

creds/credentialmanager.cpp
creds/abstractcredentials.cpp
creds/credentialmanager.cpp
creds/httpcredentials.cpp
creds/oauth.cpp
creds/jwt.cpp
creds/webfinger.cpp
Expand All @@ -65,17 +67,16 @@ set(libsync_SRCS
)

if(WIN32)
list(APPEND libsync_SRCS platform_win.cpp)
target_sources(libsync PRIVATE platform_win.cpp)
elseif(UNIX)
target_sources(libsync PRIVATE xattr.cpp)
if (APPLE)
list(APPEND libsync_SRCS platform_mac.mm)
target_sources(libsync PRIVATE platform_mac.mm)
else()
list(APPEND libsync_SRCS platform_unix.cpp)
target_sources(libsync PRIVATE platform_unix.cpp)
endif()
endif()

set(libsync_SRCS ${libsync_SRCS} creds/httpcredentials.cpp)

# These headers are installed for libopencloudsync to be used by 3rd party apps
INSTALL(
FILES
Expand All @@ -85,7 +86,6 @@ INSTALL(
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/${APPLICATION_SHORTNAME}/libsync
)

add_library(libsync SHARED ${libsync_SRCS})
set_target_properties(libsync PROPERTIES EXPORT_NAME SyncCore)

target_link_libraries(libsync
Expand Down
80 changes: 16 additions & 64 deletions src/libsync/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "common/asserts.h"
#include "common/utility.h"
#include "libsync/discoveryinfo.h"
#include "libsync/xattr.h"

#include <QCoreApplication>
#include <QDirIterator>
Expand All @@ -28,10 +29,6 @@

#include <sys/stat.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 @@ -250,52 +247,25 @@ std::optional<uint64_t> FileSystem::getInode(const std::filesystem::path &filena
return info.inode();
}

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

std::optional<QByteArray> FileSystem::Tags::get(const QString &path, const QString &key)
std::optional<QString> 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;
}
return Xattr::getxattr(toFilesystemPath(path), platformKey);
#elif defined(Q_OS_WIN)
QFile file(QStringLiteral("%1:%2").arg(path, key));
if (file.open(QIODevice::ReadOnly)) {
return file.readAll();
return QString::fromUtf8(file.readAll());
}
#endif // Q_OS_MAC || Q_OS_LINUX

return {};
}

OCC::Result<void, QString> FileSystem::Tags::set(const QString &path, const QString &key, const QByteArray &value)
OCC::Result<void, QString> FileSystem::Tags::set(const QString &path, const QString &key, const QString &value)
{
OC_ASSERT(value.size() < MaxValueSize)

Expand All @@ -304,62 +274,44 @@ OCC::Result<void, QString> FileSystem::Tags::set(const QString &path, const QStr
if (Utility::isLinux()) {
platformKey = QStringLiteral("user.") + platformKey;
}

auto result = setxattr(path.toUtf8().constData(), platformKey.toUtf8().constData(), value.constData(), value.size(), 0, 0);
if (result != 0) {
return QString::fromUtf8(strerror(errno));
}

return {};
return Xattr::setxattr(toFilesystemPath(path), platformKey, value);
#elif defined(Q_OS_WIN)
QFile file(QStringLiteral("%1:%2").arg(path, key));
if (!file.open(QIODevice::WriteOnly)) {
return file.errorString();
}
auto bytesWritten = file.write(value);
if (bytesWritten != value.size()) {
return QStringLiteral("wrote %1 out of %2 bytes").arg(QString::number(bytesWritten), QString::number(value.size()));
const auto data = value.toUtf8();
auto bytesWritten = file.write(data);
if (bytesWritten != data.size()) {
return QStringLiteral("wrote %1 out of %2 bytes").arg(QString::number(bytesWritten), QString::number(data.size()));
}

return {};
#else
return QStringLiteral("function not implemented");
return u"Not implemented"_s;
#endif // Q_OS_MAC || Q_OS_LINUX
}

bool FileSystem::Tags::remove(const QString &path, const QString &key)
OCC::Result<void, QString> 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.%1").arg(platformKey);
}

auto result = removexattr(path.toUtf8().constData(), platformKey.toUtf8().constData(), 0);
if (result == 0) {
return true;
}
#ifdef Q_OS_MAC
if (errno == ENOATTR) {
#else
if (errno == ENODATA) {
#endif
qCWarning(lcFileSystem) << u"Failed to remove tag" << key << u"from" << path << u"tag doesn't exist";
return true;
}
qCWarning(lcFileSystem) << u"Failed to remove tag" << key << u"from" << path << u":" << strerror(errno);
return false;
return Xattr::removexattr(toFilesystemPath(path), platformKey);
#elif defined(Q_OS_WIN)
const auto fsPath = toFilesystemPath(u"%1:%2"_s.arg(path, key));
std::error_code fileError;
std::filesystem::remove(fsPath, fileError);
if (fileError) {
qCWarning(lcFileSystem) << u"Failed to remove tag" << key << u"from" << path << u":" << fsPath << u"error:" << fileError.message();
return false;
return QString::fromStdString(fileError.message());
}
return true;
return {};
#else
return false;
return u"Not implemented"_s;
#endif // Q_OS_MAC || Q_OS_LINUX
}
} // namespace OCC
6 changes: 3 additions & 3 deletions src/libsync/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ namespace FileSystem {
bool OPENCLOUD_SYNC_EXPORT removeRecursively(const QString &path, RemoveEntryList *success, RemoveEntryList *locked, RemoveErrorList *errors);

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

Expand Down
10 changes: 10 additions & 0 deletions src/libsync/vfs/hydrationjob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ HydrationJob::HydrationJob(Vfs *vfs, const QByteArray &fileId, std::unique_ptr<Q
{
}

void HydrationJob::setTargetFile(const QString& fileName)
{
_fileName = fileName;
}

QString HydrationJob::targetFileName() const
{
return _fileName;
}

void HydrationJob::start()
{
_vfs->params().journal->getFileRecordsByFileId(_fileId, [this](const SyncJournalFileRecord &record) {
Expand Down
7 changes: 7 additions & 0 deletions src/libsync/vfs/hydrationjob.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ class OPENCLOUD_SYNC_EXPORT HydrationJob : public QObject
void start();
void abort();

// In case the device to write to is a file, it can be passed here to the result slots
void setTargetFile(const QString& fileName);
QString targetFileName() const;

Vfs *vfs() const;

SyncJournalFileRecord record() const;

QByteArray fileId() const { return _fileId; }

Q_SIGNALS:
void finished();
void error(const QString &error);
Expand All @@ -34,6 +40,7 @@ class OPENCLOUD_SYNC_EXPORT HydrationJob : public QObject
Vfs *_vfs;
QByteArray _fileId;
std::unique_ptr<QIODevice> _device;
QString _fileName;
SyncJournalFileRecord _record;
GETFileJob *_job = nullptr;
};
Expand Down
Loading