mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-26 15:06:08 +03:00
Merge pull request #5226 from nextcloud/feature/lock-file-edit-locally
Lock file when editing locally
This commit is contained in:
commit
120476e93b
3 changed files with 123 additions and 9 deletions
|
@ -136,7 +136,6 @@ void EditLocallyJob::proceedWithSetup()
|
||||||
}
|
}
|
||||||
|
|
||||||
_fileName = relPathSplit.last();
|
_fileName = relPathSplit.last();
|
||||||
|
|
||||||
_folderForFile = findFolderForFile(_relPath, _userId);
|
_folderForFile = findFolderForFile(_relPath, _userId);
|
||||||
|
|
||||||
if (!_folderForFile) {
|
if (!_folderForFile) {
|
||||||
|
@ -253,7 +252,7 @@ void EditLocallyJob::startSyncBeforeOpening()
|
||||||
{
|
{
|
||||||
eraseBlacklistRecordForItem();
|
eraseBlacklistRecordForItem();
|
||||||
if (!checkIfFileParentSyncIsNeeded()) {
|
if (!checkIfFileParentSyncIsNeeded()) {
|
||||||
openFile();
|
lockFile();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,7 +465,7 @@ void EditLocallyJob::slotItemCompleted(const OCC::SyncFileItemPtr &item)
|
||||||
if (item->_file == _relativePathToRemoteRoot) {
|
if (item->_file == _relativePathToRemoteRoot) {
|
||||||
disconnect(&_folderForFile->syncEngine(), &SyncEngine::itemCompleted, this, &EditLocallyJob::slotItemCompleted);
|
disconnect(&_folderForFile->syncEngine(), &SyncEngine::itemCompleted, this, &EditLocallyJob::slotItemCompleted);
|
||||||
disconnect(&_folderForFile->syncEngine(), &SyncEngine::itemDiscovered, this, &EditLocallyJob::slotItemDiscovered);
|
disconnect(&_folderForFile->syncEngine(), &SyncEngine::itemDiscovered, this, &EditLocallyJob::slotItemDiscovered);
|
||||||
openFile();
|
lockFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,16 +541,118 @@ void EditLocallyJob::openFile()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto localFilePath = _localFilePath;
|
const auto localFilePathUrl = QUrl::fromLocalFile(_localFilePath);
|
||||||
// In case the VFS mode is enabled and a file is not yet hydrated, we must call QDesktopServices::openUrl
|
// In case the VFS mode is enabled and a file is not yet hydrated, we must call QDesktopServices::openUrl
|
||||||
// from a separate thread, or, there will be a freeze. To avoid searching for a specific folder and checking
|
// from a separate thread, or, there will be a freeze. To avoid searching for a specific folder and checking
|
||||||
// if the VFS is enabled - we just always call it from a separate thread.
|
// if the VFS is enabled - we just always call it from a separate thread.
|
||||||
QtConcurrent::run([localFilePath]() {
|
QtConcurrent::run([localFilePathUrl, this]() {
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(localFilePath));
|
if (QDesktopServices::openUrl(localFilePathUrl)) {
|
||||||
|
showError(tr("Could not open %1").arg(_fileName), tr("Please try again."));
|
||||||
|
}
|
||||||
|
|
||||||
Systray::instance()->destroyEditFileLocallyLoadingDialog();
|
Systray::instance()->destroyEditFileLocallyLoadingDialog();
|
||||||
|
emit finished();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Q_EMIT fileOpened();
|
void EditLocallyJob::lockFile()
|
||||||
|
{
|
||||||
|
Q_ASSERT(_accountState);
|
||||||
|
Q_ASSERT(_accountState->account());
|
||||||
|
Q_ASSERT(_folderForFile);
|
||||||
|
|
||||||
|
if (_accountState->account()->fileLockStatus(_folderForFile->journalDb(), _relativePathToRemoteRoot) == SyncFileItem::LockStatus::LockedItem) {
|
||||||
|
fileAlreadyLocked();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto syncEngineFileSlot = [this](const SyncFileItemPtr &item) {
|
||||||
|
if (item->_file == _relativePathToRemoteRoot && item->_locked == SyncFileItem::LockStatus::LockedItem) {
|
||||||
|
fileLockSuccess(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto runSingleFileDiscovery = [this] {
|
||||||
|
const SyncEngine::SingleItemDiscoveryOptions singleItemDiscoveryOptions = {(_relPathParent == QStringLiteral("/") ? QString{} : _relPathParent),
|
||||||
|
_relativePathToRemoteRoot,
|
||||||
|
_fileParentItem};
|
||||||
|
_folderForFile->syncEngine().setSingleItemDiscoveryOptions(singleItemDiscoveryOptions);
|
||||||
|
FolderMan::instance()->forceSyncForFolder(_folderForFile);
|
||||||
|
};
|
||||||
|
|
||||||
|
_folderConnections.append(connect(&_folderForFile->syncEngine(), &SyncEngine::itemCompleted,
|
||||||
|
this, syncEngineFileSlot));
|
||||||
|
_folderConnections.append(connect(&_folderForFile->syncEngine(), &SyncEngine::itemDiscovered,
|
||||||
|
this, syncEngineFileSlot));
|
||||||
|
_folderConnections.append(connect(_accountState->account().data(), &Account::lockFileSuccess,
|
||||||
|
this, runSingleFileDiscovery));
|
||||||
|
_folderConnections.append(connect(_accountState->account().data(), &Account::lockFileError,
|
||||||
|
this, &EditLocallyJob::fileLockError));
|
||||||
|
|
||||||
|
_folderForFile->accountState()->account()->setLockFileState(_relPath,
|
||||||
|
_folderForFile->journalDb(),
|
||||||
|
SyncFileItem::LockStatus::LockedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditLocallyJob::disconnectFolderSignals()
|
||||||
|
{
|
||||||
|
for (const auto &connection : qAsConst(_folderConnections)) {
|
||||||
|
disconnect(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditLocallyJob::fileAlreadyLocked()
|
||||||
|
{
|
||||||
|
SyncJournalFileRecord rec;
|
||||||
|
Q_ASSERT(_folderForFile->journalDb()->getFileRecord(_relativePathToRemoteRoot, &rec));
|
||||||
|
Q_ASSERT(rec.isValid());
|
||||||
|
Q_ASSERT(rec._lockstate._locked);
|
||||||
|
|
||||||
|
const auto remainingTimeInMinutes = fileLockTimeRemainingMinutes(rec._lockstate._lockTime, rec._lockstate._lockTimeout);
|
||||||
|
fileLockProcedureComplete(tr("File %1 already locked.").arg(_fileName),
|
||||||
|
tr("Lock will last for %1 minutes. "
|
||||||
|
"You can also unlock this file manually once you are finished editing.").arg(remainingTimeInMinutes),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditLocallyJob::fileLockSuccess(const SyncFileItemPtr &item)
|
||||||
|
{
|
||||||
|
qCDebug(lcEditLocallyJob()) << "File lock succeeded, showing notification" << _relPath;
|
||||||
|
|
||||||
|
const auto remainingTimeInMinutes = fileLockTimeRemainingMinutes(item->_lockTime, item->_lockTimeout);
|
||||||
|
fileLockProcedureComplete(tr("File %1 now locked.").arg(_fileName),
|
||||||
|
tr("Lock will last for %1 minutes. "
|
||||||
|
"You can also unlock this file manually once you are finished editing.").arg(remainingTimeInMinutes),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditLocallyJob::fileLockError(const QString &errorMessage)
|
||||||
|
{
|
||||||
|
qCWarning(lcEditLocallyJob()) << "File lock failed, showing notification" << _relPath << errorMessage;
|
||||||
|
fileLockProcedureComplete(tr("File %1 could not be locked."), errorMessage, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditLocallyJob::fileLockProcedureComplete(const QString ¬ificationTitle,
|
||||||
|
const QString ¬ificationMessage,
|
||||||
|
const bool success)
|
||||||
|
{
|
||||||
|
Systray::instance()->showMessage(notificationTitle,
|
||||||
|
notificationMessage,
|
||||||
|
success ? QSystemTrayIcon::Information : QSystemTrayIcon::Warning);
|
||||||
|
|
||||||
|
disconnectFolderSignals();
|
||||||
|
openFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
int EditLocallyJob::fileLockTimeRemainingMinutes(const qint64 lockTime, const qint64 lockTimeOut)
|
||||||
|
{
|
||||||
|
const auto lockExpirationTime = lockTime + lockTimeOut;
|
||||||
|
const auto remainingTime = QDateTime::currentDateTime().secsTo(QDateTime::fromSecsSinceEpoch(lockExpirationTime));
|
||||||
|
|
||||||
|
static constexpr auto SECONDS_PER_MINUTE = 60;
|
||||||
|
const auto remainingTimeInMinutes = static_cast<int>(remainingTime > 0 ? remainingTime / SECONDS_PER_MINUTE : 0);
|
||||||
|
|
||||||
|
return remainingTimeInMinutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ public:
|
||||||
signals:
|
signals:
|
||||||
void setupFinished();
|
void setupFinished();
|
||||||
void error(const QString &message, const QString &informativeText);
|
void error(const QString &message, const QString &informativeText);
|
||||||
void fileOpened();
|
void finished();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void startSetup();
|
void startSetup();
|
||||||
|
@ -72,12 +72,23 @@ private slots:
|
||||||
void slotDirectoryListingIterated(const QString &name, const QMap<QString, QString> &properties);
|
void slotDirectoryListingIterated(const QString &name, const QMap<QString, QString> &properties);
|
||||||
|
|
||||||
void openFile();
|
void openFile();
|
||||||
|
void lockFile();
|
||||||
|
|
||||||
|
void fileAlreadyLocked();
|
||||||
|
void fileLockSuccess(const SyncFileItemPtr &item);
|
||||||
|
void fileLockError(const QString &errorMessage);
|
||||||
|
void fileLockProcedureComplete(const QString ¬ificationTitle,
|
||||||
|
const QString ¬ificationMessage,
|
||||||
|
const bool success);
|
||||||
|
void disconnectFolderSignals();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] bool checkIfFileParentSyncIsNeeded(); // returns true if sync will be needed, false otherwise
|
[[nodiscard]] bool checkIfFileParentSyncIsNeeded(); // returns true if sync will be needed, false otherwise
|
||||||
[[nodiscard]] const QString getRelativePathToRemoteRootForFile() const; // returns either '/' or a (relative path - Folder::remotePath()) for folders pointing to a non-root remote path e.g. '/subfolder' instead of '/'
|
[[nodiscard]] const QString getRelativePathToRemoteRootForFile() const; // returns either '/' or a (relative path - Folder::remotePath()) for folders pointing to a non-root remote path e.g. '/subfolder' instead of '/'
|
||||||
[[nodiscard]] const QString getRelativePathParent() const;
|
[[nodiscard]] const QString getRelativePathParent() const;
|
||||||
|
|
||||||
|
[[nodiscard]] static int fileLockTimeRemainingMinutes(const qint64 lockTime, const qint64 lockTimeOut);
|
||||||
|
|
||||||
bool _tokenVerified = false;
|
bool _tokenVerified = false;
|
||||||
|
|
||||||
AccountStatePtr _accountState;
|
AccountStatePtr _accountState;
|
||||||
|
@ -90,9 +101,11 @@ private:
|
||||||
|
|
||||||
QString _fileName;
|
QString _fileName;
|
||||||
QString _localFilePath;
|
QString _localFilePath;
|
||||||
|
QString _folderRelativePath;
|
||||||
Folder *_folderForFile = nullptr;
|
Folder *_folderForFile = nullptr;
|
||||||
std::unique_ptr<SimpleApiJob> _checkTokenJob;
|
std::unique_ptr<SimpleApiJob> _checkTokenJob;
|
||||||
QMetaObject::Connection _syncTerminatedConnection = {};
|
QMetaObject::Connection _syncTerminatedConnection = {};
|
||||||
|
QVector<QMetaObject::Connection> _folderConnections;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ void EditLocallyManager::createJob(const QString &userId,
|
||||||
|
|
||||||
connect(job.data(), &EditLocallyJob::error,
|
connect(job.data(), &EditLocallyJob::error,
|
||||||
this, removeJob);
|
this, removeJob);
|
||||||
connect(job.data(), &EditLocallyJob::fileOpened,
|
connect(job.data(), &EditLocallyJob::finished,
|
||||||
this, removeJob);
|
this, removeJob);
|
||||||
connect(job.data(), &EditLocallyJob::setupFinished,
|
connect(job.data(), &EditLocallyJob::setupFinished,
|
||||||
job.data(), setupJob);
|
job.data(), setupJob);
|
||||||
|
|
Loading…
Reference in a new issue