mirror of
https://github.com/nextcloud/desktop.git
synced 2024-10-23 12:55:44 +03:00
Implement 'Leave this share' context menu entry. Fix incorrect sharing state for incoming and my shares in custom state icons and local database.
Signed-off-by: allexzander <blackslayer4@gmail.com>
This commit is contained in:
parent
ab8f0dbbbb
commit
6affc6e6ab
18 changed files with 280 additions and 177 deletions
|
@ -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"
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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()) {
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
|
|
@ -82,7 +82,8 @@ public:
|
|||
bool _isE2eEncrypted = false;
|
||||
SyncJournalFileLockInfo _lockstate;
|
||||
bool _isShared = false;
|
||||
qint64 _lastShareStateFetchedTimestmap = 0;
|
||||
qint64 _lastShareStateFetchedTimestamp = 0;
|
||||
bool _sharedByMe = false;
|
||||
};
|
||||
|
||||
bool OCSYNC_EXPORT
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -215,6 +215,9 @@ public:
|
|||
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.
|
||||
|
|
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -102,6 +100,7 @@ void ShellExtensionsServer::processCustomStateRequest(QLocalSocket *socket, cons
|
|||
sendEmptyDataAndCloseSession(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto filePathRelative = QString(customStateRequestInfo.path).remove(folder->path());
|
||||
|
||||
SyncJournalFileRecord record;
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
class QJsonDocument;
|
||||
class QLocalSocket;
|
||||
class QNetworkReply;
|
||||
|
||||
namespace OCC {
|
||||
class ShellExtensionsServer : public QObject
|
||||
|
@ -53,7 +54,7 @@ public:
|
|||
void setIsSharedInvalidationInterval(qint64 interval);
|
||||
|
||||
signals:
|
||||
void fetchSharesJobFinished(const QString &folderAlias);
|
||||
void directoryListingIterationFinished(const QString &folderAlias);
|
||||
|
||||
private:
|
||||
void sendJsonMessageWithVersion(QLocalSocket *socket, const QVariantMap &message);
|
||||
|
@ -67,12 +68,10 @@ private:
|
|||
|
||||
private slots:
|
||||
void slotNewConnection();
|
||||
void slotSharesFetched(const QJsonDocument &reply);
|
||||
void slotSharesFetchError(int statusCode, const QString &message);
|
||||
|
||||
private:
|
||||
QLocalServer _localServer;
|
||||
QStringList _runningFetchShareJobsForPaths;
|
||||
QStringList _runningLsColJobsForPaths;
|
||||
QMap<qintptr, QMetaObject::Connection> _customStateSocketConnections;
|
||||
qint64 _isSharedInvalidationInterval = 0;
|
||||
};
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
#include "config.h"
|
||||
#include "configfile.h"
|
||||
#include "deletejob.h"
|
||||
#include "folderman.h"
|
||||
#include "folder.h"
|
||||
#include "theme.h"
|
||||
|
@ -541,6 +542,12 @@ void SocketApi::processShareRequest(const QString &localFile, SocketListener *li
|
|||
}
|
||||
}
|
||||
|
||||
void SocketApi::processLeaveShareRequest(const QString &localFile, SocketListener *listener)
|
||||
{
|
||||
Q_UNUSED(listener)
|
||||
FolderMan::instance()->leaveShare(QDir::fromNativeSeparators(localFile));
|
||||
}
|
||||
|
||||
void SocketApi::broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus)
|
||||
{
|
||||
QString msg = buildMessage(QLatin1String("STATUS"), systemPath, fileStatus.toSocketAPIString());
|
||||
|
@ -584,6 +591,11 @@ void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener
|
|||
processShareRequest(localFile, listener);
|
||||
}
|
||||
|
||||
void SocketApi::command_LEAVESHARE(const QString &localFile, SocketListener *listener)
|
||||
{
|
||||
processLeaveShareRequest(localFile, listener);
|
||||
}
|
||||
|
||||
void SocketApi::command_ACTIVITY(const QString &localFile, SocketListener *listener)
|
||||
{
|
||||
Q_UNUSED(listener);
|
||||
|
@ -1047,6 +1059,10 @@ void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketLi
|
|||
if (!capabilities.shareAPI() || !(theme->userGroupSharing() || (theme->linkSharing() && capabilities.sharePublicLink())))
|
||||
return;
|
||||
|
||||
if (record._isShared && !record._sharedByMe) {
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:LEAVESHARE") + flagString + tr("Leave this share"));
|
||||
}
|
||||
|
||||
// If sharing is globally disabled, do not show any sharing entries.
|
||||
// If there is no permission to share for this file, add a disabled entry saying so
|
||||
if (isOnTheServer && !record._remotePerm.isNull() && !record._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
#ifndef SOCKETAPI_H
|
||||
#define SOCKETAPI_H
|
||||
|
||||
#include "syncfileitem.h"
|
||||
#include "common/syncfilestatus.h"
|
||||
#include "common/syncjournalfilerecord.h"
|
||||
#include "syncfileitem.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
@ -28,8 +28,8 @@ class QLocalSocket;
|
|||
class QStringList;
|
||||
class QFileInfo;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
namespace OCC
|
||||
{
|
||||
class SyncFileStatus;
|
||||
class Folder;
|
||||
class SocketListener;
|
||||
|
@ -102,6 +102,7 @@ private:
|
|||
|
||||
// opens share dialog, sends reply
|
||||
void processShareRequest(const QString &localFile, SocketListener *listener);
|
||||
void processLeaveShareRequest(const QString &localFile, SocketListener *listener);
|
||||
void processFileActivityRequest(const QString &localFile);
|
||||
|
||||
Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, OCC::SocketListener *listener);
|
||||
|
@ -114,6 +115,7 @@ private:
|
|||
// The context menu actions
|
||||
Q_INVOKABLE void command_ACTIVITY(const QString &localFile, OCC::SocketListener *listener);
|
||||
Q_INVOKABLE void command_SHARE(const QString &localFile, OCC::SocketListener *listener);
|
||||
Q_INVOKABLE void command_LEAVESHARE(const QString &localFile, SocketListener *listener);
|
||||
Q_INVOKABLE void command_MANAGE_PUBLIC_LINKS(const QString &localFile, OCC::SocketListener *listener);
|
||||
Q_INVOKABLE void command_COPY_PUBLIC_LINK(const QString &localFile, OCC::SocketListener *listener);
|
||||
Q_INVOKABLE void command_COPY_PRIVATE_LINK(const QString &localFile, OCC::SocketListener *listener);
|
||||
|
@ -149,15 +151,13 @@ private:
|
|||
// Sends the context menu options relating to sharing to listener
|
||||
void sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener, bool enabled);
|
||||
|
||||
void sendLockFileCommandMenuEntries(const QFileInfo &fileInfo,
|
||||
Folder * const syncFolder,
|
||||
const FileData &fileData,
|
||||
const SocketListener * const listener) const;
|
||||
void
|
||||
sendLockFileCommandMenuEntries(const QFileInfo &fileInfo, Folder *const syncFolder, const FileData &fileData, const SocketListener *const listener) const;
|
||||
|
||||
void sendLockFileInfoMenuEntries(const QFileInfo &fileInfo,
|
||||
Folder * const syncFolder,
|
||||
Folder* const syncFolder,
|
||||
const FileData &fileData,
|
||||
const SocketListener * const listener,
|
||||
const SocketListener* const listener,
|
||||
const SyncJournalFileRecord &record) const;
|
||||
|
||||
/** Send the list of menu item. (added in version 1.1)
|
||||
|
|
|
@ -96,7 +96,12 @@ Account::~Account() = default;
|
|||
|
||||
QString Account::davPath() const
|
||||
{
|
||||
return davPathBase() + QLatin1Char('/') + davUser() + QLatin1Char('/');
|
||||
return davPathRoot() + QLatin1Char('/');
|
||||
}
|
||||
|
||||
QString Account::davPathRoot() const
|
||||
{
|
||||
return davPathBase() + QLatin1Char('/') + davUser();
|
||||
}
|
||||
|
||||
void Account::setSharedThis(AccountPtr sharedThis)
|
||||
|
|
|
@ -141,6 +141,13 @@ public:
|
|||
*/
|
||||
[[nodiscard]] QString davPath() const;
|
||||
|
||||
/**
|
||||
* @brief The possibly themed dav path root for the account. It has
|
||||
* no trailing slash.
|
||||
* @returns the (themeable) dav path for the account.
|
||||
*/
|
||||
[[nodiscard]] QString davPathRoot() const;
|
||||
|
||||
/** Returns webdav entry URL, based on url() */
|
||||
[[nodiscard]] QUrl davUrl() const;
|
||||
|
||||
|
|
|
@ -393,8 +393,8 @@ void BulkPropagatorJob::slotPutFinishedOneFile(const BulkUploadItem &singleFile,
|
|||
singleFile._item->_etag = etag;
|
||||
singleFile._item->_fileId = getHeaderFromJsonReply(fileReply, "fileid");
|
||||
singleFile._item->_remotePerm = RemotePermissions::fromServerString(getHeaderFromJsonReply(fileReply, "permissions"));
|
||||
singleFile._item->_isShared = singleFile._item->_remotePerm.hasPermission(RemotePermissions::IsShared);
|
||||
singleFile._item->_lastShareStateFetchedTimestmap = QDateTime::currentMSecsSinceEpoch();
|
||||
singleFile._item->_isShared = singleFile._item->_remotePerm.hasPermission(RemotePermissions::IsShared) || singleFile._item->_sharedByMe;
|
||||
singleFile._item->_lastShareStateFetchedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
if (getHeaderFromJsonReply(fileReply, "X-OC-MTime") != "accepted") {
|
||||
// X-OC-MTime is supported since owncloud 5.0. But not when chunking.
|
||||
|
|
|
@ -475,8 +475,9 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
|
|||
item->_checksumHeader = serverEntry.checksumHeader;
|
||||
item->_fileId = serverEntry.fileId;
|
||||
item->_remotePerm = serverEntry.remotePerm;
|
||||
item->_isShared = serverEntry.remotePerm.hasPermission(RemotePermissions::IsShared);
|
||||
item->_lastShareStateFetchedTimestmap = QDateTime::currentMSecsSinceEpoch();
|
||||
item->_isShared = serverEntry.remotePerm.hasPermission(RemotePermissions::IsShared) || serverEntry.sharedByMe;
|
||||
item->_sharedByMe = serverEntry.sharedByMe;
|
||||
item->_lastShareStateFetchedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
item->_type = serverEntry.isDirectory ? ItemTypeDirectory : ItemTypeFile;
|
||||
item->_etag = serverEntry.etag;
|
||||
item->_directDownloadUrl = serverEntry.directDownloadUrl;
|
||||
|
@ -1280,7 +1281,8 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
|
|||
item->_fileId = base._fileId;
|
||||
item->_remotePerm = base._remotePerm;
|
||||
item->_isShared = base._isShared;
|
||||
item->_lastShareStateFetchedTimestmap = base._lastShareStateFetchedTimestmap;
|
||||
item->_sharedByMe = base._sharedByMe;
|
||||
item->_lastShareStateFetchedTimestamp = base._lastShareStateFetchedTimestamp;
|
||||
item->_etag = base._etag;
|
||||
item->_type = base._type;
|
||||
|
||||
|
@ -1406,8 +1408,9 @@ void ProcessDirectoryJob::processFileConflict(const SyncFileItemPtr &item, Proce
|
|||
rec._type = item->_type;
|
||||
rec._fileSize = serverEntry.size;
|
||||
rec._remotePerm = serverEntry.remotePerm;
|
||||
rec._isShared = serverEntry.remotePerm.hasPermission(RemotePermissions::IsShared);
|
||||
rec._lastShareStateFetchedTimestmap = QDateTime::currentMSecsSinceEpoch();
|
||||
rec._isShared = serverEntry.remotePerm.hasPermission(RemotePermissions::IsShared) || serverEntry.sharedByMe;
|
||||
rec._sharedByMe = serverEntry.sharedByMe;
|
||||
rec._lastShareStateFetchedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
rec._checksumHeader = serverEntry.checksumHeader;
|
||||
const auto result = _discoveryData->_statedb->setFileRecord(rec);
|
||||
if (!result) {
|
||||
|
|
|
@ -451,6 +451,7 @@ static void propertyMapToRemoteInfo(const QMap<QString, QString> &map, RemoteInf
|
|||
// if we are the owner or not.
|
||||
// Piggy back on the persmission field
|
||||
result.remotePerm.setPermission(RemotePermissions::IsShared);
|
||||
result.sharedByMe = true;
|
||||
}
|
||||
} else if (property == "is-encrypted" && value == QStringLiteral("1")) {
|
||||
result.isE2eEncrypted = true;
|
||||
|
|
|
@ -68,6 +68,7 @@ struct RemoteInfo
|
|||
bool isDirectory = false;
|
||||
bool isE2eEncrypted = false;
|
||||
QString e2eMangledName;
|
||||
bool sharedByMe = false;
|
||||
|
||||
[[nodiscard]] bool isValid() const { return !name.isNull(); }
|
||||
|
||||
|
|
|
@ -140,12 +140,13 @@ void PropagateRemoteMkdir::finalizeMkColJob(QNetworkReply::NetworkError err, con
|
|||
|
||||
propagator()->_activeJobList.append(this);
|
||||
auto propfindJob = new PropfindJob(propagator()->account(), jobPath, this);
|
||||
propfindJob->setProperties({"http://owncloud.org/ns:permissions"});
|
||||
propfindJob->setProperties({QByteArrayLiteral("http://owncloud.org/ns:share-types"), QByteArrayLiteral("http://owncloud.org/ns:permissions")});
|
||||
connect(propfindJob, &PropfindJob::result, this, [this, jobPath](const QVariantMap &result){
|
||||
propagator()->_activeJobList.removeOne(this);
|
||||
_item->_remotePerm = RemotePermissions::fromServerString(result.value(QStringLiteral("permissions")).toString());
|
||||
_item->_isShared = _item->_remotePerm.hasPermission(RemotePermissions::IsShared);
|
||||
_item->_lastShareStateFetchedTimestmap = QDateTime::currentMSecsSinceEpoch();
|
||||
_item->_sharedByMe = !result.value(QStringLiteral("share-types")).toString().isEmpty();
|
||||
_item->_isShared = _item->_remotePerm.hasPermission(RemotePermissions::IsShared) || _item->_sharedByMe;
|
||||
_item->_lastShareStateFetchedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
if (!_uploadEncryptedHelper && !_item->_isEncrypted) {
|
||||
success();
|
||||
|
|
|
@ -42,7 +42,8 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri
|
|||
rec._fileSize = _size;
|
||||
rec._remotePerm = _remotePerm;
|
||||
rec._isShared = _isShared;
|
||||
rec._lastShareStateFetchedTimestmap = _lastShareStateFetchedTimestmap;
|
||||
rec._sharedByMe = _sharedByMe;
|
||||
rec._lastShareStateFetchedTimestamp = _lastShareStateFetchedTimestamp;
|
||||
rec._serverHasIgnoredFiles = _serverHasIgnoredFiles;
|
||||
rec._checksumHeader = _checksumHeader;
|
||||
rec._e2eMangledName = _encryptedFileName.toUtf8();
|
||||
|
@ -91,8 +92,9 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec
|
|||
item->_lockEditorApp = rec._lockstate._lockEditorApp;
|
||||
item->_lockTime = rec._lockstate._lockTime;
|
||||
item->_lockTimeout = rec._lockstate._lockTimeout;
|
||||
item->_sharedByMe = rec._sharedByMe;
|
||||
item->_isShared = rec._isShared;
|
||||
item->_lastShareStateFetchedTimestmap = rec._lastShareStateFetchedTimestmap;
|
||||
item->_lastShareStateFetchedTimestamp = rec._lastShareStateFetchedTimestamp;
|
||||
return item;
|
||||
}
|
||||
|
||||
|
|
|
@ -310,7 +310,9 @@ public:
|
|||
qint64 _lockTimeout = 0;
|
||||
|
||||
bool _isShared = false;
|
||||
time_t _lastShareStateFetchedTimestmap = 0;
|
||||
time_t _lastShareStateFetchedTimestamp = 0;
|
||||
|
||||
bool _sharedByMe = false;
|
||||
};
|
||||
|
||||
inline bool operator<(const SyncFileItemPtr &item1, const SyncFileItemPtr &item2)
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
#include "folderman.h"
|
||||
#include "account.h"
|
||||
#include "accountstate.h"
|
||||
#include <accountmanager.h>
|
||||
#include "configfile.h"
|
||||
#include "syncenginetestutils.h"
|
||||
#include "testhelper.h"
|
||||
|
||||
using namespace OCC;
|
||||
|
@ -24,7 +26,105 @@ class TestFolderMan: public QObject
|
|||
|
||||
FolderMan _fm;
|
||||
|
||||
signals:
|
||||
void incomingShareDeleted();
|
||||
|
||||
private slots:
|
||||
void testLeaveShare()
|
||||
{
|
||||
constexpr auto firstSharePath = "A/sharedwithme_A.txt";
|
||||
constexpr auto secondSharePath = "A/B/sharedwithme_B.data";
|
||||
|
||||
QScopedPointer<FakeQNAM> fakeQnam(new FakeQNAM({}));
|
||||
OCC::AccountPtr account = OCC::Account::create();
|
||||
account->setCredentials(new FakeCredentials{fakeQnam.data()});
|
||||
account->setUrl(QUrl(("http://example.de")));
|
||||
OCC::AccountManager::instance()->addAccount(account);
|
||||
|
||||
FakeFolder fakeFolder{FileInfo{}};
|
||||
fakeFolder.remoteModifier().mkdir("A");
|
||||
|
||||
fakeFolder.remoteModifier().insert(firstSharePath, 100);
|
||||
const auto firstShare = fakeFolder.remoteModifier().find(firstSharePath);
|
||||
QVERIFY(firstShare);
|
||||
firstShare->permissions.setPermission(OCC::RemotePermissions::IsShared);
|
||||
|
||||
fakeFolder.remoteModifier().mkdir("A/B");
|
||||
|
||||
fakeFolder.remoteModifier().insert(secondSharePath, 100);
|
||||
const auto secondShare = fakeFolder.remoteModifier().find(secondSharePath);
|
||||
QVERIFY(secondShare);
|
||||
secondShare->permissions.setPermission(OCC::RemotePermissions::IsShared);
|
||||
|
||||
FolderMan *folderman = FolderMan::instance();
|
||||
QCOMPARE(folderman, &_fm);
|
||||
OCC::AccountState *accountState = OCC::AccountManager::instance()->accounts().first().data();
|
||||
const auto folder = folderman->addFolder(accountState, folderDefinition(fakeFolder.localPath()));
|
||||
QVERIFY(folder);
|
||||
|
||||
auto realFolder = FolderMan::instance()->folderForPath(fakeFolder.localPath());
|
||||
QVERIFY(realFolder);
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
fakeQnam->setOverride([this, accountState, &fakeFolder](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
|
||||
Q_UNUSED(device);
|
||||
QNetworkReply *reply = nullptr;
|
||||
|
||||
if (op != QNetworkAccessManager::DeleteOperation) {
|
||||
reply = new FakeErrorReply(op, req, this, 405);
|
||||
return reply;
|
||||
}
|
||||
|
||||
if (req.url().path().isEmpty()) {
|
||||
reply = new FakeErrorReply(op, req, this, 404);
|
||||
return reply;
|
||||
}
|
||||
|
||||
const auto filePathRelative = req.url().path().remove(accountState->account()->davPath());
|
||||
|
||||
const auto foundFileInRemoteFolder = fakeFolder.remoteModifier().find(filePathRelative);
|
||||
|
||||
if (filePathRelative.isEmpty() || !foundFileInRemoteFolder) {
|
||||
reply = new FakeErrorReply(op, req, this, 404);
|
||||
return reply;
|
||||
}
|
||||
|
||||
fakeFolder.remoteModifier().remove(filePathRelative);
|
||||
reply = new FakePayloadReply(op, req, {}, nullptr);
|
||||
|
||||
emit incomingShareDeleted();
|
||||
|
||||
return reply;
|
||||
});
|
||||
|
||||
QSignalSpy incomingShareDeletedSignal(this, &TestFolderMan::incomingShareDeleted);
|
||||
|
||||
// verify first share gets deleted
|
||||
folderman->leaveShare(fakeFolder.localPath() + firstSharePath);
|
||||
QCOMPARE(incomingShareDeletedSignal.count(), 1);
|
||||
QVERIFY(!fakeFolder.remoteModifier().find(firstSharePath));
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
// verify no share gets deleted
|
||||
folderman->leaveShare(fakeFolder.localPath() + "A/B/notsharedwithme_B.data");
|
||||
QCOMPARE(incomingShareDeletedSignal.count(), 1);
|
||||
QVERIFY(fakeFolder.remoteModifier().find("A/B/sharedwithme_B.data"));
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
// verify second share gets deleted
|
||||
folderman->leaveShare(fakeFolder.localPath() + secondSharePath);
|
||||
QCOMPARE(incomingShareDeletedSignal.count(), 2);
|
||||
QVERIFY(!fakeFolder.remoteModifier().find(secondSharePath));
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
OCC::AccountManager::instance()->deleteAccount(accountState);
|
||||
}
|
||||
|
||||
void testCheckPathValidityForNewFolder()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -210,5 +310,5 @@ private slots:
|
|||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(TestFolderMan)
|
||||
QTEST_GUILESS_MAIN(TestFolderMan)
|
||||
#include "testfolderman.moc"
|
||||
|
|
Loading…
Reference in a new issue