mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-25 14:36:01 +03:00
Merge pull request #3425 from nextcloud/bugfix/vfs-wipe-moved-folder-when-conflict
VFS wipe moved folder when conflict
This commit is contained in:
commit
ce54a98f7d
10 changed files with 121 additions and 4 deletions
|
@ -105,6 +105,8 @@ Folder::Folder(const FolderDefinition &definition,
|
|||
this, &Folder::slotLogPropagationStart);
|
||||
connect(_engine.data(), &SyncEngine::syncError, this, &Folder::slotSyncError);
|
||||
|
||||
connect(_engine.data(), &SyncEngine::addErrorToGui, this, &Folder::slotAddErrorToGui);
|
||||
|
||||
_scheduleSelfTimer.setSingleShot(true);
|
||||
_scheduleSelfTimer.setInterval(SyncEngine::minimumFileAgeForUpload);
|
||||
connect(&_scheduleSelfTimer, &QTimer::timeout,
|
||||
|
@ -936,6 +938,11 @@ void Folder::slotSyncError(const QString &message, ErrorCategory category)
|
|||
emit ProgressDispatcher::instance()->syncError(alias(), message, category);
|
||||
}
|
||||
|
||||
void Folder::slotAddErrorToGui(SyncFileItem::Status status, const QString &errorMessage, const QString &subject)
|
||||
{
|
||||
emit ProgressDispatcher::instance()->addErrorToGui(alias(), status, errorMessage, subject);
|
||||
}
|
||||
|
||||
void Folder::slotSyncStarted()
|
||||
{
|
||||
qCInfo(lcFolder) << "#### Propagation start ####################################################";
|
||||
|
|
|
@ -370,6 +370,8 @@ private slots:
|
|||
*/
|
||||
void slotSyncError(const QString &message, ErrorCategory category = ErrorCategory::Normal);
|
||||
|
||||
void slotAddErrorToGui(SyncFileItem::Status status, const QString &errorMessage, const QString &subject = {});
|
||||
|
||||
void slotTransmissionProgress(const ProgressInfo &pi);
|
||||
void slotItemCompleted(const SyncFileItemPtr &);
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ public:
|
|||
QString _file;
|
||||
QUrl _link;
|
||||
QDateTime _dateTime;
|
||||
qint64 _expireAtMsecs = -1;
|
||||
QString _accName;
|
||||
QString _icon;
|
||||
QString _iconData;
|
||||
|
|
|
@ -24,6 +24,11 @@
|
|||
// refreshes of the notifications
|
||||
#define NOTIFICATION_REQUEST_FREE_PERIOD 15000
|
||||
|
||||
namespace {
|
||||
constexpr qint64 expiredActivitiesCheckIntervalMsecs = 1000 * 60;
|
||||
constexpr qint64 activityDefaultExpirationTimeMsecs = 1000 * 60 * 10;
|
||||
}
|
||||
|
||||
namespace OCC {
|
||||
|
||||
User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
|
||||
|
@ -39,10 +44,15 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
|
|||
this, &User::slotItemCompleted);
|
||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError,
|
||||
this, &User::slotAddError);
|
||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::addErrorToGui,
|
||||
this, &User::slotAddErrorToGui);
|
||||
|
||||
connect(&_notificationCheckTimer, &QTimer::timeout,
|
||||
this, &User::slotRefresh);
|
||||
|
||||
connect(&_expiredActivitiesCheckTimer, &QTimer::timeout,
|
||||
this, &User::slotCheckExpiredActivities);
|
||||
|
||||
connect(_account.data(), &AccountState::stateChanged,
|
||||
[=]() { if (isConnected()) {slotRefreshImmediately();} });
|
||||
connect(_account.data(), &AccountState::stateChanged, this, &User::accountStateChanged);
|
||||
|
@ -145,6 +155,19 @@ void User::slotReceivedPushActivity(Account *account)
|
|||
}
|
||||
}
|
||||
|
||||
void User::slotCheckExpiredActivities()
|
||||
{
|
||||
for (const Activity &activity : _activityModel->errorsList()) {
|
||||
if (activity._expireAtMsecs > 0 && QDateTime::currentDateTime().toMSecsSinceEpoch() >= activity._expireAtMsecs) {
|
||||
_activityModel->removeActivityFromActivityList(activity);
|
||||
}
|
||||
}
|
||||
|
||||
if (_activityModel->errorsList().size() == 0) {
|
||||
_expiredActivitiesCheckTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void User::connectPushNotifications() const
|
||||
{
|
||||
connect(_account->account().data(), &Account::pushNotificationsDisabled, this, &User::slotDisconnectPushNotifications, Qt::UniqueConnection);
|
||||
|
@ -328,6 +351,10 @@ void User::slotProgressInfo(const QString &folder, const ProgressInfo &progress)
|
|||
const auto &engine = f->syncEngine();
|
||||
const auto style = engine.lastLocalDiscoveryStyle();
|
||||
foreach (Activity activity, _activityModel->errorsList()) {
|
||||
if (activity._expireAtMsecs != -1) {
|
||||
// we process expired activities in a different slot
|
||||
continue;
|
||||
}
|
||||
if (activity._folder != folder) {
|
||||
continue;
|
||||
}
|
||||
|
@ -417,6 +444,39 @@ void User::slotAddError(const QString &folderAlias, const QString &message, Erro
|
|||
}
|
||||
}
|
||||
|
||||
void User::slotAddErrorToGui(const QString &folderAlias, SyncFileItem::Status status, const QString &errorMessage, const QString &subject)
|
||||
{
|
||||
const auto folderInstance = FolderMan::instance()->folder(folderAlias);
|
||||
if (!folderInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (folderInstance->accountState() == _account.data()) {
|
||||
qCWarning(lcActivity) << "Item " << folderInstance->shortGuiLocalPath() << " retrieved resulted in " << errorMessage;
|
||||
|
||||
Activity activity;
|
||||
activity._type = Activity::SyncFileItemType;
|
||||
activity._status = status;
|
||||
const auto currentDateTime = QDateTime::currentDateTime();
|
||||
activity._dateTime = QDateTime::fromString(currentDateTime.toString(), Qt::ISODate);
|
||||
activity._expireAtMsecs = currentDateTime.addMSecs(activityDefaultExpirationTimeMsecs).toMSecsSinceEpoch();
|
||||
activity._subject = !subject.isEmpty() ? subject : folderInstance->shortGuiLocalPath();
|
||||
activity._message = errorMessage;
|
||||
activity._link = folderInstance->shortGuiLocalPath();
|
||||
activity._accName = folderInstance->accountState()->account()->displayName();
|
||||
activity._folder = folderAlias;
|
||||
|
||||
// add 'other errors' to activity list
|
||||
_activityModel->addErrorToActivityList(activity);
|
||||
|
||||
showDesktopNotification(activity._subject, activity._message);
|
||||
|
||||
if (!_expiredActivitiesCheckTimer.isActive()) {
|
||||
_expiredActivitiesCheckTimer.start(expiredActivitiesCheckIntervalMsecs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool User::isActivityOfCurrentAccount(const Folder *folder) const
|
||||
{
|
||||
return folder->accountState() == _account.data();
|
||||
|
|
|
@ -75,6 +75,7 @@ public slots:
|
|||
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
|
||||
void slotProgressInfo(const QString &folder, const ProgressInfo &progress);
|
||||
void slotAddError(const QString &folderAlias, const QString &message, ErrorCategory category);
|
||||
void slotAddErrorToGui(const QString &folderAlias, SyncFileItem::Status status, const QString &errorMessage, const QString &subject = {});
|
||||
void slotNotificationRequestFinished(int statusCode);
|
||||
void slotNotifyNetworkError(QNetworkReply *reply);
|
||||
void slotEndNotificationRequest(int replyCode);
|
||||
|
@ -94,6 +95,7 @@ private:
|
|||
void slotDisconnectPushNotifications();
|
||||
void slotReceivedPushNotification(Account *account);
|
||||
void slotReceivedPushActivity(Account *account);
|
||||
void slotCheckExpiredActivities();
|
||||
|
||||
void connectPushNotifications() const;
|
||||
bool checkPushNotificationsAreReady() const;
|
||||
|
@ -109,6 +111,7 @@ private:
|
|||
ActivityListModel *_activityModel;
|
||||
ActivityList _blacklistedNotifications;
|
||||
|
||||
QTimer _expiredActivitiesCheckTimer;
|
||||
QTimer _notificationCheckTimer;
|
||||
QHash<AccountState *, QElapsedTimer> _timeSinceLastCheck;
|
||||
|
||||
|
|
|
@ -894,13 +894,43 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
|
|||
_childModified = true;
|
||||
|
||||
auto postProcessLocalNew = [item, localEntry, path, this]() {
|
||||
if (localEntry.isVirtualFile) {
|
||||
const bool isPlaceHolder = _discoveryData->_syncOptions._vfs->isDehydratedPlaceholder(_discoveryData->_localDir + path._local);
|
||||
if (isPlaceHolder) {
|
||||
qCWarning(lcDisco) << "Wiping virtual file without db entry for" << path._local;
|
||||
// TODO: We may want to execute the same logic for non-VFS mode, as, moving/renaming the same folder by 2 or more clients at the same time is not possible in Web UI.
|
||||
// Keeping it like this (for VFS files and folders only) just to fix a user issue.
|
||||
const auto isVfsEnabled = _discoveryData->_syncOptions._vfs && _discoveryData->_syncOptions._vfs->mode() != Vfs::Off;
|
||||
if (localEntry.isVirtualFile || (localEntry.isDirectory && isVfsEnabled)) {
|
||||
// must be a dehydrated placeholder
|
||||
const bool isFilePlaceHolder = !localEntry.isDirectory && _discoveryData->_syncOptions._vfs->isDehydratedPlaceholder(_discoveryData->_localDir + path._local);
|
||||
|
||||
// a folder must be online-only (no files should be hydrated)
|
||||
const bool isFolderPlaceholder = localEntry.isDirectory && *_discoveryData->_syncOptions._vfs->availability(path._local) == VfsItemAvailability::OnlineOnly;
|
||||
|
||||
Q_ASSERT(item->_instruction == CSYNC_INSTRUCTION_NEW);
|
||||
if (item->_instruction != CSYNC_INSTRUCTION_NEW) {
|
||||
qCWarning(lcDisco) << "Wiping virtual file without db entry for" << path._local << ". But, item->_instruction is" << item->_instruction;
|
||||
}
|
||||
|
||||
// must be a file placeholder or an online-only folder placeholder
|
||||
if (isFilePlaceHolder || isFolderPlaceholder) {
|
||||
if (isFolderPlaceholder) {
|
||||
qCInfo(lcDisco) << "Wiping virtual folder without db entry for" << path._local;
|
||||
} else {
|
||||
qCInfo(lcDisco) << "Wiping virtual file without db entry for" << path._local;
|
||||
}
|
||||
item->_instruction = CSYNC_INSTRUCTION_REMOVE;
|
||||
item->_direction = SyncFileItem::Down;
|
||||
// this flag needs to be unset, otherwise a folder would get marked as new in the processSubJobs
|
||||
_childModified = false;
|
||||
if (isFolderPlaceholder && _discoveryData) {
|
||||
emit _discoveryData->addErrorToGui(SyncFileItem::SoftError, tr("Conflict when uploading a folder. It's going to get cleared!"), path._local);
|
||||
}
|
||||
} else {
|
||||
if (localEntry.isDirectory && !isFolderPlaceholder) {
|
||||
qCInfo(lcDisco) << "Virtual directory without db entry for" << path._local << "but it contains hydrated file(s), so let's keep it and reupload.";
|
||||
if (_discoveryData) {
|
||||
emit _discoveryData->addErrorToGui(SyncFileItem::SoftError, tr("Conflict when uploading some files to a folder. Those, conflicted, are going to get cleared!"), path._local);
|
||||
}
|
||||
return;
|
||||
}
|
||||
qCWarning(lcDisco) << "Virtual file without db entry for" << path._local
|
||||
<< "but looks odd, keeping";
|
||||
item->_instruction = CSYNC_INSTRUCTION_IGNORE;
|
||||
|
|
|
@ -279,6 +279,8 @@ signals:
|
|||
* The path is relative to the sync folder, similar to item->_file
|
||||
*/
|
||||
void silentlyExcluded(const QString &folderPath);
|
||||
|
||||
void addErrorToGui(SyncFileItem::Status status, const QString &errorMessage, const QString &subject);
|
||||
};
|
||||
|
||||
/// Implementation of DiscoveryPhase::adjustRenamedPath
|
||||
|
|
|
@ -290,6 +290,15 @@ signals:
|
|||
*/
|
||||
void syncError(const QString &folder, const QString &message, ErrorCategory category);
|
||||
|
||||
/**
|
||||
* @brief Emitted when an error needs to be added into GUI
|
||||
* @param[out] folder The folder which is being processed
|
||||
* @param[out] status of the error
|
||||
* @param[out] full error message
|
||||
* @param[out] subject (optional)
|
||||
*/
|
||||
void addErrorToGui(const QString &folder, SyncFileItem::Status status, const QString &errorMessage, const QString &subject);
|
||||
|
||||
/**
|
||||
* @brief Emitted for a folder when a sync is done, listing all pending conflicts
|
||||
*/
|
||||
|
|
|
@ -592,6 +592,7 @@ void SyncEngine::startSync()
|
|||
_discoveryPhase.data(), PinState::AlwaysLocal, _journal->keyValueStoreGetInt("last_sync", 0), _discoveryPhase.data());
|
||||
_discoveryPhase->startJob(discoveryJob);
|
||||
connect(discoveryJob, &ProcessDirectoryJob::etag, this, &SyncEngine::slotRootEtagReceived);
|
||||
connect(_discoveryPhase.data(), &DiscoveryPhase::addErrorToGui, this, &SyncEngine::addErrorToGui);
|
||||
}
|
||||
|
||||
void SyncEngine::slotFolderDiscovered(bool local, const QString &folder)
|
||||
|
|
|
@ -151,6 +151,8 @@ signals:
|
|||
/// We've produced a new sync error of a type.
|
||||
void syncError(const QString &message, ErrorCategory category = ErrorCategory::Normal);
|
||||
|
||||
void addErrorToGui(SyncFileItem::Status status, const QString &errorMessage, const QString &subject);
|
||||
|
||||
void finished(bool success);
|
||||
void started();
|
||||
|
||||
|
|
Loading…
Reference in a new issue