Skip to content

Commit

Permalink
fetch and store in sync database information abot lock state of files
Browse files Browse the repository at this point in the history
fetch lock properties from server

decode them and store them in sync database

test to ensure we do properly handle those properties

Signed-off-by: Matthieu Gallien <[email protected]>
  • Loading branch information
mgallien committed Apr 20, 2022
1 parent 064b6ef commit 0ca193a
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 53 deletions.
100 changes: 48 additions & 52 deletions src/common/syncjournaldb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ Q_LOGGING_CATEGORY(lcDb, "nextcloud.sync.database", QtInfoMsg)

#define GET_FILE_RECORD_QUERY \
"SELECT path, inode, modtime, type, md5, fileid, remotePerm, filesize," \
" ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted " \
" ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, " \
" lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime " \
" FROM metadata" \
" LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id"

Expand All @@ -66,6 +67,12 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que
rec._checksumHeader = query.baValue(9);
rec._e2eMangledName = query.baValue(10);
rec._isE2eEncrypted = query.intValue(11) > 0;
rec._locked = query.intValue(12) > 0;
rec._lockOwnerDisplayName = query.stringValue(13);
rec._lockOwnerId = query.stringValue(14);
rec._lockOwnerType = query.int64Value(15);
rec._lockEditorApp = query.stringValue(16);
rec._lockTime = query.int64Value(17);
}

static QByteArray defaultJournalMode(const QString &dbPath)
Expand Down Expand Up @@ -658,6 +665,20 @@ bool SyncJournalDb::updateMetadataTableStructure()
return false;
}

const auto addColumn = [this, &columns, &re] (const QString &columnName, const QString &dataType) {
const auto latin1ColumnName = columnName.toLatin1();
if (columns.indexOf(latin1ColumnName) == -1) {
SqlQuery query(_db);
const auto request = QStringLiteral("ALTER TABLE metadata ADD COLUMN %1 %2;").arg(columnName).arg(dataType);
query.prepare(request.toLatin1());
if (!query.exec()) {
sqlFail(QStringLiteral("updateMetadataTableStructure: add %1 column").arg(columnName), query);
re = false;
}
commitInternal(QStringLiteral("update database structure: add %1 column").arg(columnName));
}
};

if (columns.indexOf("fileid") == -1) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN fileid VARCHAR(128);");
Expand Down Expand Up @@ -722,54 +743,11 @@ bool SyncJournalDb::updateMetadataTableStructure()
commitInternal(QStringLiteral("update database structure: add parent index"));
}

if (columns.indexOf("ignoredChildrenRemote") == -1) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN ignoredChildrenRemote INT;");
if (!query.exec()) {
sqlFail(QStringLiteral("updateMetadataTableStructure: add ignoredChildrenRemote column"), query);
re = false;
}
commitInternal(QStringLiteral("update database structure: add ignoredChildrenRemote col"));
}

if (columns.indexOf("contentChecksum") == -1) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN contentChecksum TEXT;");
if (!query.exec()) {
sqlFail(QStringLiteral("updateMetadataTableStructure: add contentChecksum column"), query);
re = false;
}
commitInternal(QStringLiteral("update database structure: add contentChecksum col"));
}
if (columns.indexOf("contentChecksumTypeId") == -1) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN contentChecksumTypeId INTEGER;");
if (!query.exec()) {
sqlFail(QStringLiteral("updateMetadataTableStructure: add contentChecksumTypeId column"), query);
re = false;
}
commitInternal(QStringLiteral("update database structure: add contentChecksumTypeId col"));
}

if (!columns.contains("e2eMangledName")) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN e2eMangledName TEXT;");
if (!query.exec()) {
sqlFail(QStringLiteral("updateMetadataTableStructure: add e2eMangledName column"), query);
re = false;
}
commitInternal(QStringLiteral("update database structure: add e2eMangledName col"));
}

if (!columns.contains("isE2eEncrypted")) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN isE2eEncrypted INTEGER;");
if (!query.exec()) {
sqlFail(QStringLiteral("updateMetadataTableStructure: add isE2eEncrypted column"), query);
re = false;
}
commitInternal(QStringLiteral("update database structure: add isE2eEncrypted col"));
}
addColumn(QStringLiteral("ignoredChildrenRemote"), QStringLiteral("INT"));
addColumn(QStringLiteral("contentChecksum"), QStringLiteral("TEXT"));
addColumn(QStringLiteral("contentChecksumTypeId"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("e2eMangledName"), QStringLiteral("TEXT"));
addColumn(QStringLiteral("isE2eEncrypted"), QStringLiteral("INTEGER"));

auto uploadInfoColumns = tableColumns("uploadinfo");
if (uploadInfoColumns.isEmpty())
Expand Down Expand Up @@ -806,6 +784,13 @@ bool SyncJournalDb::updateMetadataTableStructure()
commitInternal(QStringLiteral("update database structure: add e2eMangledName index"));
}

addColumn(QStringLiteral("lock"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("lockType"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("lockOwnerDisplayName"), QStringLiteral("TEXT"));
addColumn(QStringLiteral("lockOwnerId"), QStringLiteral("TEXT"));
addColumn(QStringLiteral("lockOwnerEditor"), QStringLiteral("TEXT"));
addColumn(QStringLiteral("lockTime"), QStringLiteral("INTEGER"));

return re;
}

Expand Down Expand Up @@ -919,7 +904,10 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
<< "modtime:" << record._modtime << "type:" << record._type
<< "etag:" << record._etag << "fileId:" << record._fileId << "remotePerm:" << record._remotePerm.toString()
<< "fileSize:" << record._fileSize << "checksum:" << record._checksumHeader
<< "e2eMangledName:" << record.e2eMangledName() << "isE2eEncrypted:" << record._isE2eEncrypted;
<< "e2eMangledName:" << record.e2eMangledName() << "isE2eEncrypted:" << record._isE2eEncrypted
<< "lock:" << (record._locked ? "true" : "false") << "lock owner type:" << record._lockOwnerType
<< "lock owner:" << record._lockOwnerDisplayName << "lock owner id:" << record._lockOwnerId
<< "lock editor:" << record._lockEditorApp;

const qint64 phash = getPHash(record._path);
if (checkConnect()) {
Expand All @@ -937,8 +925,10 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
int contentChecksumTypeId = mapChecksumType(checksumType);

const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO metadata "
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted) "
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18);"),
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, "
"contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted, lock, lockType, lockOwnerDisplayName, lockOwnerId, "
"lockOwnerEditor, lockTime) "
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24);"),
_db);
if (!query) {
return query->error();
Expand All @@ -962,6 +952,12 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
query->bindValue(16, contentChecksumTypeId);
query->bindValue(17, record._e2eMangledName);
query->bindValue(18, record._isE2eEncrypted);
query->bindValue(19, record._locked ? 1 : 0);
query->bindValue(20, record._lockOwnerType);
query->bindValue(21, record._lockOwnerDisplayName);
query->bindValue(22, record._lockOwnerId);
query->bindValue(23, record._lockEditorApp);
query->bindValue(24, record._lockTime);

if (!query->exec()) {
return query->error();
Expand Down
6 changes: 6 additions & 0 deletions src/common/syncjournalfilerecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ class OCSYNC_EXPORT SyncJournalFileRecord
QByteArray _checksumHeader;
QByteArray _e2eMangledName;
bool _isE2eEncrypted = false;
bool _locked = false;
QString _lockOwnerDisplayName;
QString _lockOwnerId;
qint64 _lockOwnerType = 0;
QString _lockEditorApp;
qint64 _lockTime = 0;
};

bool OCSYNC_EXPORT
Expand Down
10 changes: 9 additions & 1 deletion src/libsync/discovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,8 @@ void ProcessDirectoryJob::processFile(PathTuple path,
<< " | inode: " << dbEntry._inode << "/" << localEntry.inode << "/"
<< " | type: " << dbEntry._type << "/" << localEntry.type << "/" << (serverEntry.isDirectory ? ItemTypeDirectory : ItemTypeFile)
<< " | e2ee: " << dbEntry._isE2eEncrypted << "/" << serverEntry.isE2eEncrypted
<< " | e2eeMangledName: " << dbEntry.e2eMangledName() << "/" << serverEntry.e2eMangledName;
<< " | e2eeMangledName: " << dbEntry.e2eMangledName() << "/" << serverEntry.e2eMangledName
<< " | file lock: " << (serverEntry.isValid() ? (serverEntry.locked == SyncFileItem::LockStatus::LockedItem ? "locked" : "not locked") : (dbEntry._locked ? "locked" : "not locked"));

if (localEntry.isValid()
&& !serverEntry.isValid()
Expand Down Expand Up @@ -483,6 +484,13 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
Q_ASSERT(serverEntry.e2eMangledName.startsWith(rootPath));
return serverEntry.e2eMangledName.mid(rootPath.length());
}();
item->_locked = serverEntry.locked;
item->_lockOwnerDisplayName = serverEntry.lockOwnerDisplayName;
item->_lockOwnerId = serverEntry.lockOwnerId;
item->_lockOwnerType = serverEntry.lockOwnerType;
item->_lockEditorApp = serverEntry.lockEditorApp;
item->_lockTime = serverEntry.lockTime;
qCInfo(lcDisco()) << item->_locked << item->_lockOwnerDisplayName << item->_lockOwnerId << item->_lockOwnerType << item->_lockEditorApp << item->_lockTime;

// Check for missing server data
{
Expand Down
37 changes: 37 additions & 0 deletions src/libsync/discoveryphase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,14 @@ void DiscoverySingleDirectoryJob::start()
if (_account->capabilities().clientSideEncryptionAvailable()) {
props << "http://nextcloud.org/ns:is-encrypted";
}
if (_account->capabilities().filesLockAvailable()) {
props << "http://nextcloud.org/ns:lock"
<< "http://nextcloud.org/ns:lock-owner-displayname"
<< "http://nextcloud.org/ns:lock-owner"
<< "http://nextcloud.org/ns:lock-owner-type"
<< "http://nextcloud.org/ns:lock-owner-editor"
<< "http://nextcloud.org/ns:lock-time";
}

lsColJob->setProperties(props);

Expand Down Expand Up @@ -445,6 +453,35 @@ static void propertyMapToRemoteInfo(const QMap<QString, QString> &map, RemoteInf
}
} else if (property == "is-encrypted" && value == QStringLiteral("1")) {
result.isE2eEncrypted = true;
} else if (property == "lock") {
result.locked = (value == QStringLiteral("1") ? SyncFileItem::LockStatus::LockedItem : SyncFileItem::LockStatus::UnlockedItem);
}
if (property == "lock-owner-displayname") {
result.lockOwnerDisplayName = value;
}
if (property == "lock-owner") {
result.lockOwnerId = value;
}
if (property == "lock-owner-type") {
auto ok = false;
const auto intConvertedValue = value.toULongLong(&ok);
if (ok) {
result.lockOwnerType = static_cast<SyncFileItem::LockOwnerType>(intConvertedValue);
} else {
result.lockOwnerType = SyncFileItem::LockOwnerType::UserLock;
}
}
if (property == "lock-owner-editor") {
result.lockEditorApp = value;
}
if (property == "lock-time") {
auto ok = false;
const auto intConvertedValue = value.toULongLong(&ok);
if (ok) {
result.lockTime = intConvertedValue;
} else {
result.lockTime = 0;
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/libsync/discoveryphase.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ struct RemoteInfo

QString directDownloadUrl;
QString directDownloadCookies;

SyncFileItem::LockStatus locked = SyncFileItem::LockStatus::UnlockedItem;
QString lockOwnerDisplayName;
QString lockOwnerId;
SyncFileItem::LockOwnerType lockOwnerType = SyncFileItem::LockOwnerType::UserLock;
QString lockEditorApp;
qint64 lockTime = 0;
};

struct LocalInfo
Expand Down
6 changes: 6 additions & 0 deletions src/libsync/propagatedownload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,12 @@ void PropagateDownloadFile::downloadFinished()
return;
}

qCInfo(lcPropagateDownload()) << propagator()->account()->davUser() << propagator()->account()->davDisplayName() << propagator()->account()->displayName();
if (_item->_locked == SyncFileItem::LockStatus::LockedItem && (_item->_lockOwnerType != SyncFileItem::LockOwnerType::UserLock || _item->_lockOwnerId != propagator()->account()->davUser())) {
qCInfo(lcPropagateDownload()) << "file is locked: making it read only";
FileSystem::setFileReadOnly(fn, true);
}

FileSystem::setFileHidden(fn, false);

// Maybe we downloaded a newer version of the file than we thought we would...
Expand Down
12 changes: 12 additions & 0 deletions src/libsync/syncfileitem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri
rec._checksumHeader = _checksumHeader;
rec._e2eMangledName = _encryptedFileName.toUtf8();
rec._isE2eEncrypted = _isEncrypted;
rec._locked = _locked == LockStatus::LockedItem;
rec._lockOwnerDisplayName = _lockOwnerDisplayName;
rec._lockOwnerId = _lockOwnerId;
rec._lockOwnerType = static_cast<qint64>(_lockOwnerType);
rec._lockEditorApp = _lockEditorApp;
rec._lockTime = _lockTime;

// Update the inode if possible
rec._inode = _inode;
Expand Down Expand Up @@ -75,6 +81,12 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec
item->_checksumHeader = rec._checksumHeader;
item->_encryptedFileName = rec.e2eMangledName();
item->_isEncrypted = rec._isE2eEncrypted;
item->_locked = rec._locked ? LockStatus::LockedItem : LockStatus::UnlockedItem;
item->_lockOwnerDisplayName = rec._lockOwnerDisplayName;
item->_lockOwnerId = rec._lockOwnerId;
item->_lockOwnerType = static_cast<LockOwnerType>(rec._lockOwnerType);
item->_lockEditorApp = rec._lockEditorApp;
item->_lockTime = rec._lockTime;
return item;
}

Expand Down
22 changes: 22 additions & 0 deletions src/libsync/syncfileitem.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,21 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem
};
Q_ENUM(Status)

enum class LockStatus {
LockedItem,
UnlockedItem,
};

Q_ENUM(LockStatus)

enum class LockOwnerType : int{
UserLock = 0,
AppLock = 1,
TokenLock = 2,
};

Q_ENUM(LockOwnerType)

SyncJournalFileRecord toSyncJournalFileRecordWithInode(const QString &localFileName) const;

/** Creates a basic SyncFileItem from a DB record
Expand Down Expand Up @@ -278,6 +293,13 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem

QString _directDownloadUrl;
QString _directDownloadCookies;

LockStatus _locked = LockStatus::UnlockedItem;
QString _lockOwnerId;
QString _lockOwnerDisplayName;
LockOwnerType _lockOwnerType = LockOwnerType::UserLock;
QString _lockEditorApp;
qint64 _lockTime = 0;
};

inline bool operator<(const SyncFileItemPtr &item1, const SyncFileItemPtr &item2)
Expand Down
2 changes: 2 additions & 0 deletions test/syncenginetestutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,11 +298,13 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces
// Don't care about the request and just return a full propfind
const QString davUri { QStringLiteral("DAV:") };
const QString ocUri { QStringLiteral("http://owncloud.org/ns") };
const QString ncUri { QStringLiteral("http://nextcloud.org/ns") };
QBuffer buffer { &payload };
buffer.open(QIODevice::WriteOnly);
QXmlStreamWriter xml(&buffer);
xml.writeNamespace(davUri, QStringLiteral("d"));
xml.writeNamespace(ocUri, QStringLiteral("oc"));
xml.writeNamespace(ncUri, QStringLiteral("nc"));
xml.writeStartDocument();
xml.writeStartElement(davUri, QStringLiteral("multistatus"));
auto writeFileResponse = [&](const FileInfo &fileInfo) {
Expand Down
Loading

0 comments on commit 0ca193a

Please sign in to comment.