Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
b1-lender authored Nov 22, 2022
2 parents 54aa9e8 + 8fd4519 commit 0fbbd66
Show file tree
Hide file tree
Showing 18 changed files with 280 additions and 177 deletions.
16 changes: 10 additions & 6 deletions src/common/syncjournaldb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ 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, " \
" lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap " \
" lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe" \
" FROM metadata" \
" LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id"

Expand All @@ -75,7 +75,8 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que
rec._lockstate._lockTime = query.int64Value(17);
rec._lockstate._lockTimeout = query.int64Value(18);
rec._isShared = query.intValue(19) > 0;
rec._lastShareStateFetchedTimestmap = query.int64Value(20);
rec._lastShareStateFetchedTimestamp = query.int64Value(20);
rec._sharedByMe = query.intValue(21) > 0;
}

static QByteArray defaultJournalMode(const QString &dbPath)
Expand Down Expand Up @@ -731,6 +732,7 @@ bool SyncJournalDb::updateMetadataTableStructure()
addColumn(QStringLiteral("isE2eEncrypted"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("isShared"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("lastShareStateFetchedTimestmap"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("sharedByMe"), QStringLiteral("INTEGER"));

auto uploadInfoColumns = tableColumns("uploadinfo");
if (uploadInfoColumns.isEmpty())
Expand Down Expand Up @@ -894,8 +896,9 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
<< "lock owner:" << record._lockstate._lockOwnerDisplayName
<< "lock owner id:" << record._lockstate._lockOwnerId
<< "lock editor:" << record._lockstate._lockEditorApp
<< "sharedByMe:" << record._sharedByMe
<< "isShared:" << record._isShared
<< "lastShareStateFetchedTimestmap:" << record._lastShareStateFetchedTimestmap;
<< "lastShareStateFetchedTimestamp:" << record._lastShareStateFetchedTimestamp;

const qint64 phash = getPHash(record._path);
if (!checkConnect()) {
Expand All @@ -921,8 +924,8 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
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, lock, lockType, lockOwnerDisplayName, lockOwnerId, "
"lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap) "
"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, ?26, ?27);"),
"lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe) "
"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, ?26, ?27, ?28);"),
_db);
if (!query) {
return query->error();
Expand Down Expand Up @@ -954,7 +957,8 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
query->bindValue(24, record._lockstate._lockTime);
query->bindValue(25, record._lockstate._lockTimeout);
query->bindValue(26, record._isShared);
query->bindValue(27, record._lastShareStateFetchedTimestmap);
query->bindValue(27, record._lastShareStateFetchedTimestamp);
query->bindValue(28, record._sharedByMe);

if (!query->exec()) {
return query->error();
Expand Down
3 changes: 2 additions & 1 deletion src/common/syncjournalfilerecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ class OCSYNC_EXPORT SyncJournalFileRecord
bool _isE2eEncrypted = false;
SyncJournalFileLockInfo _lockstate;
bool _isShared = false;
qint64 _lastShareStateFetchedTimestmap = 0;
qint64 _lastShareStateFetchedTimestamp = 0;
bool _sharedByMe = false;
};

bool OCSYNC_EXPORT
Expand Down
15 changes: 15 additions & 0 deletions src/gui/folderman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1451,6 +1451,21 @@ void FolderMan::setDirtyNetworkLimits()
}
}

void FolderMan::leaveShare(const QString &localFile)
{
if (const auto folder = FolderMan::instance()->folderForPath(localFile)) {
const auto filePathRelative = QString(localFile).remove(folder->path());

const auto leaveShareJob = new SimpleApiJob(folder->accountState()->account(), folder->accountState()->account()->davPath() + filePathRelative);
leaveShareJob->setVerb(SimpleApiJob::Verb::Delete);
connect(leaveShareJob, &SimpleApiJob::resultReceived, this, [this, folder](int statusCode) {
Q_UNUSED(statusCode)
scheduleFolder(folder);
});
leaveShareJob->start();
}
}

void FolderMan::trayOverallStatus(const QList<Folder *> &folders,
SyncResult::Status *status, bool *unresolvedConflicts)
{
Expand Down
3 changes: 3 additions & 0 deletions src/gui/folderman.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ class FolderMan : public QObject
void setDirtyProxy();
void setDirtyNetworkLimits();

/** removes current user from the share **/
void leaveShare(const QString &localFile);

signals:
/**
* signal to indicate a folder has changed its sync state.
Expand Down
227 changes: 85 additions & 142 deletions src/gui/shellextensionsserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
#include <libsync/vfs/cfapi/shellext/configvfscfapishellext.h>
#include "folder.h"
#include "folderman.h"
#include "ocssharejob.h"
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
Expand All @@ -28,7 +27,6 @@

namespace {
constexpr auto isSharedInvalidationInterval = 2 * 60 * 1000; // 2 minutes, so we don't make fetch sharees requests too often
constexpr auto folderAliasPropertyKey = "folderAlias";
}

namespace OCC {
Expand Down Expand Up @@ -102,6 +100,7 @@ void ShellExtensionsServer::processCustomStateRequest(QLocalSocket *socket, cons
sendEmptyDataAndCloseSession(socket);
return;
}

const auto filePathRelative = QString(customStateRequestInfo.path).remove(folder->path());

SyncJournalFileRecord record;
Expand All @@ -123,43 +122,14 @@ void ShellExtensionsServer::processCustomStateRequest(QLocalSocket *socket, cons
QVariantMap{{VfsShellExtensions::Protocol::CustomStateStatesKey, states}}}};
};

if (QDateTime::currentMSecsSinceEpoch() - record._lastShareStateFetchedTimestmap < _isSharedInvalidationInterval) {
qCInfo(lcShellExtServer) << record.path() << " record._lastShareStateFetchedTimestmap has less than " << _isSharedInvalidationInterval << " ms difference with QDateTime::currentMSecsSinceEpoch(). Returning data from SyncJournal.";
if (QDateTime::currentMSecsSinceEpoch() - record._lastShareStateFetchedTimestamp < _isSharedInvalidationInterval) {
qCInfo(lcShellExtServer) << record.path() << " record._lastShareStateFetchedTimestamp has less than " << _isSharedInvalidationInterval << " ms difference with QDateTime::currentMSecsSinceEpoch(). Returning data from SyncJournal.";
sendJsonMessageWithVersion(socket, composeMessageReplyFromRecord(record));
closeSession(socket);
return;
}

const auto job = new OcsShareJob(folder->accountState()->account());
job->setProperty(folderAliasPropertyKey, customStateRequestInfo.folderAlias);
connect(job, &OcsShareJob::shareJobFinished, this, &ShellExtensionsServer::slotSharesFetched);
connect(job, &OcsJob::ocsError, this, &ShellExtensionsServer::slotSharesFetchError);

{
_customStateSocketConnections.insert(socket->socketDescriptor(), QObject::connect(this, &ShellExtensionsServer::fetchSharesJobFinished, [this, socket, filePathRelative, composeMessageReplyFromRecord](const QString &folderAlias) {
{
const auto connection = _customStateSocketConnections[socket->socketDescriptor()];
if (connection) {
QObject::disconnect(connection);
}
_customStateSocketConnections.remove(socket->socketDescriptor());
}

const auto folder = FolderMan::instance()->folder(folderAlias);
SyncJournalFileRecord record;
if (!folder || !folder->journalDb()->getFileRecord(filePathRelative, &record) || !record.isValid()) {
qCWarning(lcShellExtServer) << "Record not found in SyncJournal for: " << filePathRelative;
sendEmptyDataAndCloseSession(socket);
return;
}

qCInfo(lcShellExtServer) << "Sending reply from OcsShareJob for socket: " << socket->socketDescriptor() << " and record: " << record.path();
sendJsonMessageWithVersion(socket, composeMessageReplyFromRecord(record));
closeSession(socket);
}));
}

const auto sharesPath = [&record, folder, &filePathRelative]() {
const auto lsColJobPath = [folder, &filePathRelative]() {
const auto filePathRelativeRemote = QDir(folder->remotePath()).filePath(filePathRelative);
// either get parent's path, or, return '/' if we are in the root folder
auto recordPathSplit = filePathRelativeRemote.split(QLatin1Char('/'), Qt::SkipEmptyParts);
Expand All @@ -170,13 +140,88 @@ void ShellExtensionsServer::processCustomStateRequest(QLocalSocket *socket, cons
return QStringLiteral("/");
}();

if (!_runningFetchShareJobsForPaths.contains(sharesPath)) {
_runningFetchShareJobsForPaths.push_back(sharesPath);
qCInfo(lcShellExtServer) << "Started OcsShareJob for path: " << sharesPath;
job->getShares(sharesPath, {{QStringLiteral("subfiles"), QStringLiteral("true")}});
} else {
qCInfo(lcShellExtServer) << "OcsShareJob is already running for path: " << sharesPath;
if (_runningLsColJobsForPaths.contains(lsColJobPath)) {
qCInfo(lcShellExtServer) << "LsColJob is already running for path: " << lsColJobPath;
sendJsonMessageWithVersion(socket, composeMessageReplyFromRecord(record));
closeSession(socket);
return;
}

_customStateSocketConnections.insert(socket->socketDescriptor(), QObject::connect(this, &ShellExtensionsServer::directoryListingIterationFinished, [this, socket, filePathRelative, composeMessageReplyFromRecord](const QString &folderAlias) {
{
const auto connection = _customStateSocketConnections[socket->socketDescriptor()];
if (connection) {
QObject::disconnect(connection);
}
_customStateSocketConnections.remove(socket->socketDescriptor());
}

const auto folder = FolderMan::instance()->folder(folderAlias);
SyncJournalFileRecord record;
if (!folder || !folder->journalDb()->getFileRecord(filePathRelative, &record) || !record.isValid()) {
qCWarning(lcShellExtServer) << "Record not found in SyncJournal for: " << filePathRelative;
sendEmptyDataAndCloseSession(socket);
return;
}
qCInfo(lcShellExtServer) << "Sending reply from LsColJob for socket: " << socket->socketDescriptor() << " and record: " << record.path();
sendJsonMessageWithVersion(socket, composeMessageReplyFromRecord(record));
closeSession(socket);
}));

auto *const lsColJob = new LsColJob(folder->accountState()->account(), QDir::cleanPath(folder->remotePath() + lsColJobPath), this);
lsColJob->setProperties({QByteArrayLiteral("http://owncloud.org/ns:share-types"), QByteArrayLiteral("http://owncloud.org/ns:permissions")});

const auto folderAlias = customStateRequestInfo.folderAlias;

QObject::connect(lsColJob, &LsColJob::directoryListingIterated, this, [this, folderAlias, lsColJobPath](const QString &name, const QMap<QString, QString> &properties) {
const auto folder = FolderMan::instance()->folder(folderAlias);

if (!folder) {
qCWarning(lcShellExtServer) << "No folder found for folderAlias: " << folderAlias;
return;
}

SyncJournalFileRecord record;
const auto filePathWithoutDavPath = QString(name).remove(folder->accountState()->account()->davPathRoot());
const auto filePathAdjusted = (filePathWithoutDavPath.size() > 1 && filePathWithoutDavPath.startsWith(QLatin1Char('/'))) ? filePathWithoutDavPath.mid(1) : filePathWithoutDavPath;
if (filePathAdjusted.isEmpty() || filePathAdjusted == lsColJobPath) {
// we are skipping the first item as it is the current path, but we are interested in nested items
return;
}
if (!folder || !folder->journalDb()->getFileRecord(filePathAdjusted, &record) || !record.isValid()) {
return;
}

const auto isIncomingShare = properties.contains(QStringLiteral("permissions")) && RemotePermissions::fromServerString(properties.value(QStringLiteral("permissions"))).hasPermission(OCC::RemotePermissions::IsShared);

const auto sharedByMe = !properties.value(QStringLiteral("share-types")).isEmpty();

record._sharedByMe = sharedByMe;

record._isShared = isIncomingShare || sharedByMe;
record._lastShareStateFetchedTimestamp = QDateTime::currentMSecsSinceEpoch();

if (!folder->journalDb()->setFileRecord(record)) {
qCWarning(lcShellExtServer) << "Could not set file record for path: " << record._path;
emit directoryListingIterationFinished(folderAlias);
return;
}
});

QObject::connect(lsColJob, &LsColJob::finishedWithError, this, [this, folderAlias, lsColJobPath](QNetworkReply *reply) {
_runningLsColJobsForPaths.removeOne(lsColJobPath);
const auto httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qCWarning(lcShellExtServer) << "LSCOL job error" << reply->errorString() << httpCode << reply->error();
emit directoryListingIterationFinished(folderAlias);
});

QObject::connect(lsColJob, &LsColJob::finishedWithoutError, this, [this, folderAlias, lsColJobPath]() {
_runningLsColJobsForPaths.removeOne(lsColJobPath);
emit directoryListingIterationFinished(folderAlias);
});

_runningLsColJobsForPaths.push_back(lsColJobPath);
lsColJob->start();
}

void ShellExtensionsServer::processThumbnailRequest(QLocalSocket *socket, const ThumbnailRequestInfo &thumbnailRequestInfo)
Expand Down Expand Up @@ -252,108 +297,6 @@ void ShellExtensionsServer::slotNewConnection()
return;
}

void ShellExtensionsServer::slotSharesFetched(const QJsonDocument &reply)
{
const auto job = qobject_cast<OcsShareJob *>(sender());

Q_ASSERT(job);
if (!job) {
qCWarning(lcShellExtServer) << "ShellExtensionsServer::slotSharesFetched is not called by OcsShareJob's signal!";
return;
}

const auto sharesPath = job->getParamValue(QStringLiteral("path"));

_runningFetchShareJobsForPaths.removeAll(sharesPath);

const auto folderAlias = job->property(folderAliasPropertyKey).toString();

Q_ASSERT(!folderAlias.isEmpty());
if (folderAlias.isEmpty()) {
qCWarning(lcShellExtServer) << "No 'folderAlias' set for OcsShareJob's instance!";
return;
}

const auto folder = FolderMan::instance()->folder(folderAlias);

Q_ASSERT(folder);
if (!folder) {
qCWarning(lcShellExtServer) << "folder not found for folderAlias: " << folderAlias;
return;
}

const auto timeStamp = QDateTime::currentMSecsSinceEpoch();
QStringList recortPathsToResetIsSharedFlag;
const QByteArray pathOfSharesToResetIsSharedFlag = sharesPath == QStringLiteral("/") ? QByteArrayLiteral("") : sharesPath.toUtf8();
if (folder->journalDb()->listFilesInPath(pathOfSharesToResetIsSharedFlag, [&](const SyncJournalFileRecord &rec) {
recortPathsToResetIsSharedFlag.push_back(rec.path());
})) {
for (const auto &recordPath : recortPathsToResetIsSharedFlag) {
SyncJournalFileRecord record;
if (!folder->journalDb()->getFileRecord(recordPath, &record) || !record.isValid()) {
continue;
}
record._isShared = false;
record._lastShareStateFetchedTimestmap = timeStamp;
if (!folder->journalDb()->setFileRecord(record)) {
qCWarning(lcShellExtServer) << "Could not set file record for path: " << record._path;
}
}
}

const auto sharesFetched = reply.object().value(QStringLiteral("ocs")).toObject().value(QStringLiteral("data")).toArray();

for (const auto &share : sharesFetched) {
const auto shareData = share.toObject();

const auto sharePath = [&shareData, folder]() {
const auto sharePathRemote = shareData.value(QStringLiteral("path")).toString();

const auto folderPath = folder->remotePath();
if (folderPath != QLatin1Char('/') && sharePathRemote.startsWith(folderPath)) {
// shares are ruturned with absolute remote path, so, if we have our remote root set to subfolder, we need to adjust share's remote path to relative local path
const auto sharePathLocalRelative = sharePathRemote.midRef(folder->remotePathTrailingSlash().length());
return sharePathLocalRelative.toString();
}
return sharePathRemote.size() > 1 && sharePathRemote.startsWith(QLatin1Char('/'))
? QString(sharePathRemote).remove(0, 1)
: sharePathRemote;
}();

SyncJournalFileRecord record;
if (!folder || !folder->journalDb()->getFileRecord(sharePath, &record) || !record.isValid()) {
continue;
}
record._isShared = true;
record._lastShareStateFetchedTimestmap = timeStamp;

if (!folder->journalDb()->setFileRecord(record)) {
qCWarning(lcShellExtServer) << "Could not set file record for path: " << record._path;
}
}

qCInfo(lcShellExtServer) << "Succeeded OcsShareJob for path: " << sharesPath;
emit fetchSharesJobFinished(folderAlias);
}

void ShellExtensionsServer::slotSharesFetchError(int statusCode, const QString &message)
{
const auto job = qobject_cast<OcsShareJob *>(sender());

Q_ASSERT(job);
if (!job) {
qCWarning(lcShellExtServer) << "ShellExtensionsServer::slotSharesFetched is not called by OcsShareJob's signal!";
return;
}

const auto sharesPath = job->getParamValue(QStringLiteral("path"));

_runningFetchShareJobsForPaths.removeAll(sharesPath);

emit fetchSharesJobFinished(sharesPath);
qCWarning(lcShellExtServer) << "Failed OcsShareJob for path: " << sharesPath;
}

void ShellExtensionsServer::parseCustomStateRequest(QLocalSocket *socket, const QVariantMap &message)
{
const auto customStateRequestMessage = message.value(VfsShellExtensions::Protocol::CustomStateProviderRequestKey).toMap();
Expand Down
Loading

0 comments on commit 0fbbd66

Please sign in to comment.