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 29, 2022
1 parent 7c48b31 commit 68583be
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 84 deletions.
143 changes: 60 additions & 83 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, lockTimeout " \
" FROM metadata" \
" LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id"

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

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

if (columns.indexOf("fileid") == -1) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN fileid VARCHAR(128);");
if (!query.exec()) {
sqlFail(QStringLiteral("updateMetadataTableStructure: Add column fileid"), query);
re = false;
}
const auto addColumn = [this, &columns, &re] (const QString &columnName, const QString &dataType, const bool withIndex = false) {
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;
}

query.prepare("CREATE INDEX metadata_file_id ON metadata(fileid);");
if (!query.exec()) {
sqlFail(QStringLiteral("updateMetadataTableStructure: create index fileid"), query);
re = false;
}
commitInternal(QStringLiteral("update database structure: add fileid col"));
}
if (columns.indexOf("remotePerm") == -1) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN remotePerm VARCHAR(128);");
if (!query.exec()) {
sqlFail(QStringLiteral("updateMetadataTableStructure: add column remotePerm"), query);
re = false;
}
commitInternal(QStringLiteral("update database structure (remotePerm)"));
}
if (columns.indexOf("filesize") == -1) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN filesize BIGINT;");
if (!query.exec()) {
sqlFail(QStringLiteral("updateDatabaseStructure: add column filesize"), query);
re = false;
if (withIndex) {
query.prepare(QStringLiteral("CREATE INDEX metadata_%1 ON metadata(%1);").arg(columnName).toLatin1());
if (!query.exec()) {
sqlFail(QStringLiteral("updateMetadataTableStructure: create index %1").arg(columnName), query);
re = false;
}
}
commitInternal(QStringLiteral("update database structure: add %1 column").arg(columnName));
}
commitInternal(QStringLiteral("update database structure: add filesize col"));
}
};

addColumn(QStringLiteral("fileid"), QStringLiteral("VARCHAR(128)"), true);
addColumn(QStringLiteral("remotePerm"), QStringLiteral("VARCHAR(128)"));
addColumn(QStringLiteral("filesize"), QStringLiteral("BIGINT"));

if (true) {
SqlQuery query(_db);
Expand Down Expand Up @@ -722,54 +722,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 +763,14 @@ 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"));
addColumn(QStringLiteral("lockTimeout"), QStringLiteral("INTEGER"));

return re;
}

Expand Down Expand Up @@ -919,7 +884,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._lockstate._locked ? "true" : "false") << "lock owner type:" << record._lockstate._lockOwnerType
<< "lock owner:" << record._lockstate._lockOwnerDisplayName << "lock owner id:" << record._lockstate._lockOwnerId
<< "lock editor:" << record._lockstate._lockEditorApp;

const qint64 phash = getPHash(record._path);
if (!checkConnect()) {
Expand All @@ -943,8 +911,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, lockTimeout) "
"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, ?25);"),
_db);
if (!query) {
return query->error();
Expand All @@ -968,6 +938,13 @@ 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._lockstate._locked ? 1 : 0);
query->bindValue(20, record._lockstate._lockOwnerType);
query->bindValue(21, record._lockstate._lockOwnerDisplayName);
query->bindValue(22, record._lockstate._lockOwnerId);
query->bindValue(23, record._lockstate._lockEditorApp);
query->bindValue(24, record._lockstate._lockTime);
query->bindValue(25, record._lockstate._lockTimeout);

if (!query->exec()) {
return query->error();
Expand Down
11 changes: 11 additions & 0 deletions src/common/syncjournalfilerecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ namespace OCC {

class SyncFileItem;

struct SyncJournalFileLockInfo {
bool _locked = false;
QString _lockOwnerDisplayName;
QString _lockOwnerId;
qint64 _lockOwnerType = 0;
QString _lockEditorApp;
qint64 _lockTime = 0;
qint64 _lockTimeout = 0;
};

/**
* @brief The SyncJournalFileRecord class
* @ingroup libsync
Expand Down Expand Up @@ -70,6 +80,7 @@ class OCSYNC_EXPORT SyncJournalFileRecord
QByteArray _checksumHeader;
QByteArray _e2eMangledName;
bool _isE2eEncrypted = false;
SyncJournalFileLockInfo _lockstate;
};

bool OCSYNC_EXPORT
Expand Down
13 changes: 12 additions & 1 deletion src/libsync/discovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ void ProcessDirectoryJob::processFile(PathTuple path,
{
const char *hasServer = serverEntry.isValid() ? "true" : _queryServer == ParentNotChanged ? "db" : "false";
const char *hasLocal = localEntry.isValid() ? "true" : _queryLocal == ParentNotChanged ? "db" : "false";
const auto serverFileIsLocked = serverEntry.locked == SyncFileItem::LockStatus::LockedItem ? "locked" : "not locked";
const auto localFileIsLocked = dbEntry._lockstate._locked ? "locked" : "not locked";
qCInfo(lcDisco).nospace() << "Processing " << path._original
<< " | valid: " << dbEntry.isValid() << "/" << hasLocal << "/" << hasServer
<< " | mtime: " << dbEntry._modtime << "/" << localEntry.modtime << "/" << serverEntry.modtime
Expand All @@ -374,7 +376,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: " << localFileIsLocked << "//" << serverFileIsLocked;

if (localEntry.isValid()
&& !serverEntry.isValid()
Expand Down Expand Up @@ -483,6 +486,14 @@ 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;
item->_lockTimeout = serverEntry.lockTimeout;
qCInfo(lcDisco()) << item->_locked << item->_lockOwnerDisplayName << item->_lockOwnerId << item->_lockOwnerType << item->_lockEditorApp << item->_lockTime << item->_lockTimeout;

// Check for missing server data
{
Expand Down
48 changes: 48 additions & 0 deletions src/libsync/discoveryphase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,15 @@ 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"
<< "http://nextcloud.org/ns:lock-timeout";
}

lsColJob->setProperties(props);

Expand Down Expand Up @@ -445,7 +454,46 @@ 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;
}
}
if (property == "lock-timeout") {
auto ok = false;
const auto intConvertedValue = value.toULongLong(&ok);
if (ok) {
result.lockTimeout = intConvertedValue;
} else {
result.lockTimeout = 0;
}
}

}

if (result.isDirectory && map.contains("size")) {
Expand Down
8 changes: 8 additions & 0 deletions src/libsync/discoveryphase.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ 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;
qint64 lockTimeout = 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
14 changes: 14 additions & 0 deletions src/libsync/syncfileitem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri
rec._checksumHeader = _checksumHeader;
rec._e2eMangledName = _encryptedFileName.toUtf8();
rec._isE2eEncrypted = _isEncrypted;
rec._lockstate._locked = _locked == LockStatus::LockedItem;
rec._lockstate._lockOwnerDisplayName = _lockOwnerDisplayName;
rec._lockstate._lockOwnerId = _lockOwnerId;
rec._lockstate._lockOwnerType = static_cast<qint64>(_lockOwnerType);
rec._lockstate._lockEditorApp = _lockEditorApp;
rec._lockstate._lockTime = _lockTime;
rec._lockstate._lockTimeout = _lockTimeout;

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

Expand Down
Loading

0 comments on commit 68583be

Please sign in to comment.