#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"
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)
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())
<< "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()) {
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();
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();
bool _isE2eEncrypted = false;
SyncJournalFileLockInfo _lockstate;
bool _isShared = false;
- qint64 _lastShareStateFetchedTimestmap = 0;
+ qint64 _lastShareStateFetchedTimestamp = 0;
+ bool _sharedByMe = false;
};
bool OCSYNC_EXPORT
}
}
+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)
{
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.
#include <libsync/vfs/cfapi/shellext/configvfscfapishellext.h>
#include "folder.h"
#include "folderman.h"
-#include "ocssharejob.h"
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
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 {
sendEmptyDataAndCloseSession(socket);
return;
}
+
const auto filePathRelative = QString(customStateRequestInfo.path).remove(folder->path());
SyncJournalFileRecord record;
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);
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)
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();
class QJsonDocument;
class QLocalSocket;
+class QNetworkReply;
namespace OCC {
class ShellExtensionsServer : public QObject
void setIsSharedInvalidationInterval(qint64 interval);
signals:
- void fetchSharesJobFinished(const QString &folderAlias);
+ void directoryListingIterationFinished(const QString &folderAlias);
private:
void sendJsonMessageWithVersion(QLocalSocket *socket, const QVariantMap &message);
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;
};
#include "config.h"
#include "configfile.h"
+#include "deletejob.h"
#include "folderman.h"
#include "folder.h"
#include "theme.h"
}
}
+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());
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);
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)) {
#ifndef SOCKETAPI_H
#define SOCKETAPI_H
-#include "syncfileitem.h"
#include "common/syncfilestatus.h"
#include "common/syncjournalfilerecord.h"
+#include "syncfileitem.h"
#include "config.h"
class QStringList;
class QFileInfo;
-namespace OCC {
-
+namespace OCC
+{
class SyncFileStatus;
class Folder;
class SocketListener;
// 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);
// 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);
// 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)
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)
*/
[[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;
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.
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;
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;
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) {
// 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;
bool isDirectory = false;
bool isE2eEncrypted = false;
QString e2eMangledName;
+ bool sharedByMe = false;
[[nodiscard]] bool isValid() const { return !name.isNull(); }
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();
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();
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;
}
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)
#include "folderman.h"
#include "account.h"
#include "accountstate.h"
+#include <accountmanager.h>
#include "configfile.h"
+#include "syncenginetestutils.h"
#include "testhelper.h"
using namespace OCC;
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
}
};
-QTEST_APPLESS_MAIN(TestFolderMan)
+QTEST_GUILESS_MAIN(TestFolderMan)
#include "testfolderman.moc"