diff --git a/ChangeLog b/ChangeLog index 594c0a82c4..45c776b105 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,15 +1,19 @@ ChangeLog ========= -version 2.5.4 (unreleased) +version 2.5.4 (2019-03-xx) * Crash fix: Infinite recursion for bad paths on Windows (#7041) * Crash fix: SocketApi mustn't send if readyRead happens after disconnected (#7044) +* Fix rare error causing spurious local deletes (#6677) * Disable HTTP2 support due to bugs in Qt 5.12.1 (#7020, QTBUG-73947) * Fix loading of persisted cookies when loading accounts (#7054) * Windows: Fix breaking of unrelated explorer actions (#7004, #7023) * Windows: Forbid syncing of files with bytes 0x00 to 0x1F in filenames (#6987) -* OSX: Opt out of dark mode until problems can be addressed (#7043) -* OSX: Fix folder dehydration requests (#6977) +* macOS: Opt out of dark mode until problems can be addressed (#7043) +* macOS: Fix folder dehydration requests (#6977) +* Linux: Tray: Try to establish tray after 10s if failed initially (#6518) +* Linux: FolderWatcher: Work around missing notifications (#7068) +* Shares: "copy link" action can create shares with expiry (#7061) * Selective sync: Don't collapse folder tree when changing selection (#7055) * Client cert dialog: Avoid incorrect behavior due to multiple signal connections diff --git a/src/common/ownsql.cpp b/src/common/ownsql.cpp index db4d3de5aa..467af67840 100644 --- a/src/common/ownsql.cpp +++ b/src/common/ownsql.cpp @@ -338,10 +338,31 @@ bool SqlQuery::exec() return true; } -bool SqlQuery::next() +auto SqlQuery::next() -> NextResult { - SQLITE_DO(sqlite3_step(_stmt)); - return _errId == SQLITE_ROW; + const bool firstStep = !sqlite3_stmt_busy(_stmt); + + int n = 0; + forever { + _errId = sqlite3_step(_stmt); + if (n < SQLITE_REPEAT_COUNT && firstStep && (_errId == SQLITE_LOCKED || _errId == SQLITE_BUSY)) { + sqlite3_reset(_stmt); // not necessary after sqlite version 3.6.23.1 + n++; + OCC::Utility::usleep(SQLITE_SLEEP_TIME_USEC); + } else { + break; + } + } + + NextResult result; + result.ok = _errId == SQLITE_ROW || _errId == SQLITE_DONE; + result.hasData = _errId == SQLITE_ROW; + if (!result.ok) { + _error = QString::fromUtf8(sqlite3_errmsg(_db)); + qCWarning(lcSql) << "Sqlite step statement error:" << _errId << _error << "in" << _sql; + } + + return result; } void SqlQuery::bindValue(int pos, const QVariant &value) diff --git a/src/common/ownsql.h b/src/common/ownsql.h index a5e53e8d0d..5ca2d7442c 100644 --- a/src/common/ownsql.h +++ b/src/common/ownsql.h @@ -129,7 +129,14 @@ public: bool isSelect(); bool isPragma(); bool exec(); - bool next(); + + struct NextResult + { + bool ok = false; + bool hasData = false; + }; + NextResult next(); + void bindValue(int pos, const QVariant &value); QString lastQuery() const; int numRowsAffected(); diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index 66e3bea473..bf05f60704 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -521,7 +521,7 @@ bool SyncJournalDb::checkConnect() bool forceRemoteDiscovery = false; SqlQuery versionQuery("SELECT major, minor, patch FROM version;", _db); - if (!versionQuery.next()) { + if (!versionQuery.next().hasData) { // If there was no entry in the table, it means we are likely upgrading from 1.5 qCInfo(lcDb) << "possibleUpgradeFromMirall_1_5 detected!"; forceRemoteDiscovery = true; @@ -850,7 +850,7 @@ QVector SyncJournalDb::tableColumns(const QByteArray &table) if (!query.exec()) { return columns; } - while (query.next()) { + while (query.next().hasData) { columns.append(query.baValue(1)); } qCDebug(lcDb) << "Columns in the current journal: " << columns; @@ -1000,15 +1000,15 @@ bool SyncJournalDb::getFileRecord(const QByteArray &filename, SyncJournalFileRec return false; } - if (_getFileRecordQuery.next()) { + auto next = _getFileRecordQuery.next(); + if (!next.ok) { + QString err = _getFileRecordQuery.error(); + qCWarning(lcDb) << "No journal entry found for " << filename << "Error: " << err; + close(); + return false; + } + if (next.hasData) { fillFileRecordFromGetQuery(*rec, _getFileRecordQuery); - } else { - int errId = _getFileRecordQuery.errorId(); - if (errId != SQLITE_DONE) { // only do this if the problem is different from SQLITE_DONE - QString err = _getFileRecordQuery.error(); - qCWarning(lcDb) << "No journal entry found for " << filename << "Error: " << err; - close(); - } } } return true; @@ -1037,7 +1037,10 @@ bool SyncJournalDb::getFileRecordByInode(quint64 inode, SyncJournalFileRecord *r if (!_getFileRecordQueryByInode.exec()) return false; - if (_getFileRecordQueryByInode.next()) + auto next = _getFileRecordQueryByInode.next(); + if (!next.ok) + return false; + if (next.hasData) fillFileRecordFromGetQuery(*rec, _getFileRecordQueryByInode); return true; @@ -1061,7 +1064,13 @@ bool SyncJournalDb::getFileRecordsByFileId(const QByteArray &fileId, const std:: if (!_getFileRecordQueryByFileId.exec()) return false; - while (_getFileRecordQueryByFileId.next()) { + forever { + auto next = _getFileRecordQueryByFileId.next(); + if (!next.ok) + return false; + if (!next.hasData) + break; + SyncJournalFileRecord rec; fillFileRecordFromGetQuery(rec, _getFileRecordQueryByFileId); rowCallback(rec); @@ -1113,7 +1122,13 @@ bool SyncJournalDb::getFilesBelowPath(const QByteArray &path, const std::functio return false; } - while (query->next()) { + forever { + auto next = query->next(); + if (!next.ok) + return false; + if (!next.hasData) + break; + SyncJournalFileRecord rec; fillFileRecordFromGetQuery(rec, *query); rowCallback(rec); @@ -1142,7 +1157,13 @@ bool SyncJournalDb::listFilesInPath(const QByteArray& path, if (!_listFilesInPathQuery.exec()) return false; - while (_listFilesInPathQuery.next()) { + forever { + auto next = _listFilesInPathQuery.next(); + if (!next.ok) + return false; + if (!next.hasData) + break; + SyncJournalFileRecord rec; fillFileRecordFromGetQuery(rec, _listFilesInPathQuery); if (!rec._path.startsWith(path) || rec._path.indexOf("/", path.size() + 1) > 0) { @@ -1166,7 +1187,7 @@ int SyncJournalDb::getFileRecordCount() return -1; } - if (query.next()) { + if (query.next().hasData) { int count = query.intValue(0); return count; } @@ -1277,10 +1298,8 @@ SyncJournalDb::DownloadInfo SyncJournalDb::getDownloadInfo(const QString &file) return res; } - if (_getDownloadInfoQuery.next()) { + if (_getDownloadInfoQuery.next().hasData) { toDownloadInfo(_getDownloadInfoQuery, &res); - } else { - res._valid = false; } } return res; @@ -1334,7 +1353,7 @@ QVector SyncJournalDb::getAndDeleteStaleDownloadInf QStringList superfluousPaths; QVector deleted_entries; - while (query.next()) { + while (query.next().hasData) { const QString file = query.stringValue(3); // path if (!keep.contains(file)) { superfluousPaths.append(file); @@ -1361,7 +1380,7 @@ int SyncJournalDb::downloadInfoCount() if (!query.exec()) { sqlFail("Count number of downloadinfo entries failed", query); } - if (query.next()) { + if (query.next().hasData) { re = query.intValue(0); } } @@ -1386,7 +1405,7 @@ SyncJournalDb::UploadInfo SyncJournalDb::getUploadInfo(const QString &file) return res; } - if (_getUploadInfoQuery.next()) { + if (_getUploadInfoQuery.next().hasData) { bool ok = true; res._chunk = _getUploadInfoQuery.intValue(0); res._transferid = _getUploadInfoQuery.intValue(1); @@ -1455,7 +1474,7 @@ QVector SyncJournalDb::deleteStaleUploadInfos(const QSet &keep) QStringList superfluousPaths; - while (query.next()) { + while (query.next().hasData) { const QString file = query.stringValue(0); if (!keep.contains(file)) { superfluousPaths.append(file); @@ -1479,7 +1498,7 @@ SyncJournalErrorBlacklistRecord SyncJournalDb::errorBlacklistEntry(const QString _getErrorBlacklistQuery.reset_and_clear_bindings(); _getErrorBlacklistQuery.bindValue(1, file); if (_getErrorBlacklistQuery.exec()) { - if (_getErrorBlacklistQuery.next()) { + if (_getErrorBlacklistQuery.next().hasData) { entry._lastTryEtag = _getErrorBlacklistQuery.baValue(0); entry._lastTryModtime = _getErrorBlacklistQuery.int64Value(1); entry._retryCount = _getErrorBlacklistQuery.intValue(2); @@ -1515,7 +1534,7 @@ bool SyncJournalDb::deleteStaleErrorBlacklistEntries(const QSet &keep) QStringList superfluousPaths; - while (query.next()) { + while (query.next().hasData) { const QString file = query.stringValue(0); if (!keep.contains(file)) { superfluousPaths.append(file); @@ -1538,7 +1557,7 @@ int SyncJournalDb::errorBlackListEntryCount() if (!query.exec()) { sqlFail("Count number of blacklist entries failed", query); } - if (query.next()) { + if (query.next().hasData) { re = query.intValue(0); } } @@ -1642,7 +1661,7 @@ QVector SyncJournalDb::getPollInfos() return res; } - while (query.next()) { + while (query.next().hasData) { PollInfo info; info._file = query.stringValue(0); info._modtime = query.int64Value(1); @@ -1696,7 +1715,15 @@ QStringList SyncJournalDb::getSelectiveSyncList(SyncJournalDb::SelectiveSyncList *ok = false; return result; } - while (_getSelectiveSyncListQuery.next()) { + forever { + auto next = _getSelectiveSyncListQuery.next(); + if (!next.ok) { + *ok = false; + return result; + } + if (!next.hasData) + break; + auto entry = _getSelectiveSyncListQuery.stringValue(0); if (!entry.endsWith(QLatin1Char('/'))) { entry.append(QLatin1Char('/')); @@ -1819,12 +1846,12 @@ QByteArray SyncJournalDb::getChecksumType(int checksumTypeId) return {}; query.bindValue(1, checksumTypeId); if (!query.exec()) { - return 0; + return QByteArray(); } - if (!query.next()) { + if (!query.next().hasData) { qCWarning(lcDb) << "No checksum type mapping found for" << checksumTypeId; - return 0; + return QByteArray(); } return query.baValue(0); } @@ -1855,7 +1882,7 @@ int SyncJournalDb::mapChecksumType(const QByteArray &checksumType) return 0; } - if (!_getChecksumTypeIdQuery.next()) { + if (!_getChecksumTypeIdQuery.next().hasData) { qCWarning(lcDb) << "No checksum type mapping found for" << checksumType; return 0; } @@ -1878,7 +1905,7 @@ QByteArray SyncJournalDb::dataFingerprint() return QByteArray(); } - if (!_getDataFingerprintQuery.next()) { + if (!_getDataFingerprintQuery.next().hasData) { return QByteArray(); } return _getDataFingerprintQuery.baValue(0); @@ -1933,7 +1960,7 @@ ConflictRecord SyncJournalDb::conflictRecord(const QByteArray &path) ASSERT(query.initOrReset(QByteArrayLiteral("SELECT baseFileId, baseModtime, baseEtag, basePath FROM conflicts WHERE path=?1;"), _db)); query.bindValue(1, path); ASSERT(query.exec()); - if (!query.next()) + if (!query.next().hasData) return entry; entry.path = path; @@ -1966,7 +1993,7 @@ QByteArrayList SyncJournalDb::conflictRecordPaths() ASSERT(query.exec()); QByteArrayList paths; - while (query.next()) + while (query.next().hasData) paths.append(query.baValue(0)); return paths; @@ -2031,8 +2058,11 @@ Optional SyncJournalDb::PinStateInterface::rawForPath(const QByteArray query.bindValue(1, path); query.exec(); + auto next = query.next(); + if (!next.ok) + return {}; // no-entry means Inherited - if (!query.next()) + if (!next.hasData) return PinState::Inherited; return static_cast(query.intValue(0)); @@ -2056,8 +2086,11 @@ Optional SyncJournalDb::PinStateInterface::effectiveForPath(const QByt query.bindValue(1, path); query.exec(); + auto next = query.next(); + if (!next.ok) + return {}; // If the root path has no setting, assume AlwaysLocal - if (!query.next()) + if (!next.hasData) return PinState::AlwaysLocal; return static_cast(query.intValue(0)); @@ -2110,7 +2143,12 @@ SyncJournalDb::PinStateInterface::rawList() query.exec(); QVector> result; - while (query.next()) { + forever { + auto next = query.next(); + if (!next.ok) + return {}; + if (!next.hasData) + break; result.append({ query.baValue(0), static_cast(query.intValue(1)) }); } return result; diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 913eff61eb..f5106a28b1 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -766,6 +766,13 @@ void Application::openVirtualFile(const QString &filename) }); } +void Application::tryTrayAgain() +{ + qCInfo(lcApplication) << "Trying tray icon, tray available:" << QSystemTrayIcon::isSystemTrayAvailable(); + if (!_gui->contextMenuVisible()) + _gui->hideAndShowTray(); +} + bool Application::event(QEvent *event) { #ifdef Q_OS_MAC diff --git a/src/gui/application.h b/src/gui/application.h index c29ca8b41d..f4d46b04a8 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -80,6 +80,9 @@ public slots: */ void openVirtualFile(const QString &filename); + /// Attempt to show() the tray icon again. Used if no systray was available initially. + void tryTrayAgain(); + protected: void parseOptions(const QStringList &); void setupTranslations(); diff --git a/src/gui/main.cpp b/src/gui/main.cpp index efe87a9d6b..e1f7b4f406 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -127,6 +127,7 @@ int main(int argc, char **argv) } return 0; } + // We can't call isSystemTrayAvailable with appmenu-qt5 begause it hides the systemtray // (issue #4693) if (qgetenv("QT_QPA_PLATFORMTHEME") != "appmenu-qt5") @@ -135,27 +136,34 @@ int main(int argc, char **argv) // If the systemtray is not there, we will wait one second for it to maybe start // (eg boot time) then we show the settings dialog if there is still no systemtray. // On XFCE however, we show a message box with explainaition how to install a systemtray. + qCInfo(lcApplication) << "System tray is not available, waiting..."; Utility::sleep(1); + auto desktopSession = qgetenv("XDG_CURRENT_DESKTOP").toLower(); if (desktopSession.isEmpty()) { desktopSession = qgetenv("DESKTOP_SESSION").toLower(); } if (desktopSession == "xfce") { int attempts = 0; - forever { - if (!QSystemTrayIcon::isSystemTrayAvailable()) { - Utility::sleep(1); - attempts++; - if (attempts < 30) - continue; - } else { + while (!QSystemTrayIcon::isSystemTrayAvailable()) { + attempts++; + if (attempts >= 30) { + qCWarning(lcApplication) << "System tray unavailable (xfce)"; + warnSystray(); break; } - warnSystray(); + Utility::sleep(1); } } - if (!QSystemTrayIcon::isSystemTrayAvailable() && desktopSession != "ubuntu") { + + if (QSystemTrayIcon::isSystemTrayAvailable()) { + app.tryTrayAgain(); + } else if (desktopSession != "ubuntu") { + qCInfo(lcApplication) << "System tray still not available, showing window and trying again later"; app.showSettingsDialog(); + QTimer::singleShot(10000, &app, &Application::tryTrayAgain); + } else { + qCInfo(lcApplication) << "System tray still not available, but assuming it's fine on 'ubuntu' desktop"; } } } diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index c8b4234799..9d9248f312 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -426,6 +426,12 @@ bool ownCloudGui::contextMenuVisible() const return _contextMenu->isVisible(); } +void ownCloudGui::hideAndShowTray() +{ + _tray->hide(); + _tray->show(); +} + static bool minimalTrayMenu() { static QByteArray var = qgetenv("OWNCLOUD_MINIMAL_TRAY_MENU"); diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h index b12fdf7b55..b76e4e06a8 100644 --- a/src/gui/owncloudgui.h +++ b/src/gui/owncloudgui.h @@ -61,6 +61,8 @@ public: /// Whether the tray menu is visible bool contextMenuVisible() const; + void hideAndShowTray(); + signals: void setupProxy(); diff --git a/src/gui/sharelinkwidget.cpp b/src/gui/sharelinkwidget.cpp index 219f4270c6..c56a81c67d 100644 --- a/src/gui/sharelinkwidget.cpp +++ b/src/gui/sharelinkwidget.cpp @@ -145,7 +145,7 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account, if (_account->capabilities().sharePublicLinkEnforceExpireDate()) { _ui->checkBox_expire->setEnabled(false); _ui->calendar->setMaximumDate(QDate::currentDate().addDays( - _account->capabilities().sharePublicLinkExpireDateDays())); + _account->capabilities().sharePublicLinkDefaultExpireDateDays())); _expiryRequired = true; } diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index 8384ce3751..87dec54685 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -487,6 +487,7 @@ public: GetOrCreatePublicLinkShare(const AccountPtr &account, const QString &localFile, std::function targetFun, QObject *parent) : QObject(parent) + , _account(account) , _shareManager(account) , _localFile(localFile) , _targetFun(targetFun) @@ -509,6 +510,12 @@ private slots: void sharesFetched(const QList> &shares) { auto shareName = SocketApi::tr("Context menu share"); + + // If shares will expire, create a new one every day. + if (_account->capabilities().sharePublicLinkDefaultExpire()) { + shareName = SocketApi::tr("Context menu share %1").arg(QDate::currentDate().toString(Qt::ISODate)); + } + // If there already is a context menu share, reuse it for (const auto &share : shares) { const auto linkShare = qSharedPointerDynamicCast(share); @@ -551,6 +558,7 @@ private: deleteLater(); } + AccountPtr _account; ShareManager _shareManager; QString _localFile; std::function _targetFun; @@ -778,7 +786,6 @@ void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketLi // Is is possible to create a public link without user choices? bool canCreateDefaultPublicLink = publicLinksEnabled - && !capabilities.sharePublicLinkEnforceExpireDate() && !capabilities.sharePublicLinkEnforcePassword(); if (canCreateDefaultPublicLink) { diff --git a/src/libsync/capabilities.cpp b/src/libsync/capabilities.cpp index 4f98d88a72..e860964282 100644 --- a/src/libsync/capabilities.cpp +++ b/src/libsync/capabilities.cpp @@ -60,16 +60,21 @@ bool Capabilities::sharePublicLinkEnforcePassword() const return _capabilities["files_sharing"].toMap()["public"].toMap()["password"].toMap()["enforced"].toBool(); } +bool Capabilities::sharePublicLinkDefaultExpire() const +{ + return _capabilities["files_sharing"].toMap()["public"].toMap()["expire_date"].toMap()["enabled"].toBool(); +} + +int Capabilities::sharePublicLinkDefaultExpireDateDays() const +{ + return _capabilities["files_sharing"].toMap()["public"].toMap()["expire_date"].toMap()["days"].toInt(); +} + bool Capabilities::sharePublicLinkEnforceExpireDate() const { return _capabilities["files_sharing"].toMap()["public"].toMap()["expire_date"].toMap()["enforced"].toBool(); } -int Capabilities::sharePublicLinkExpireDateDays() const -{ - return _capabilities["files_sharing"].toMap()["public"].toMap()["expire_date"].toMap()["days"].toInt(); -} - bool Capabilities::sharePublicLinkMultiple() const { return _capabilities["files_sharing"].toMap()["public"].toMap()["multiple"].toBool(); diff --git a/src/libsync/capabilities.h b/src/libsync/capabilities.h index 65cd7e46fe..c00a625283 100644 --- a/src/libsync/capabilities.h +++ b/src/libsync/capabilities.h @@ -38,8 +38,9 @@ public: bool sharePublicLinkAllowUpload() const; bool sharePublicLinkSupportsUploadOnly() const; bool sharePublicLinkEnforcePassword() const; + bool sharePublicLinkDefaultExpire() const; + int sharePublicLinkDefaultExpireDateDays() const; bool sharePublicLinkEnforceExpireDate() const; - int sharePublicLinkExpireDateDays() const; bool sharePublicLinkMultiple() const; bool shareResharing() const; bool chunkingNg() const; diff --git a/test/testownsql.cpp b/test/testownsql.cpp index 86ec77d28d..58e9bc5b6a 100644 --- a/test/testownsql.cpp +++ b/test/testownsql.cpp @@ -71,7 +71,7 @@ private slots: q.prepare(sql); q.exec(); - while( q.next() ) { + while( q.next().hasData ) { qDebug() << "Name: " << q.stringValue(1); qDebug() << "Address: " << q.stringValue(2); } @@ -83,7 +83,7 @@ private slots: q.prepare(sql); q.bindValue(1, 2); q.exec(); - if( q.next() ) { + if( q.next().hasData ) { qDebug() << "Name:" << q.stringValue(1); qDebug() << "Address:" << q.stringValue(2); } @@ -96,7 +96,7 @@ private slots: int rc = q.prepare(sql); qDebug() << "Pragma:" << rc; q.exec(); - if( q.next() ) { + if( q.next().hasData ) { qDebug() << "P:" << q.stringValue(1); } } @@ -118,7 +118,7 @@ private slots: SqlQuery q(_db); q.prepare(sql); - if(q.next()) { + if(q.next().hasData) { QString name = q.stringValue(1); QString address = q.stringValue(2); QVERIFY( name == QString::fromUtf8("пятницы") ); diff --git a/test/testpermissions.cpp b/test/testpermissions.cpp index 696fd3db66..c7d5da8f4c 100644 --- a/test/testpermissions.cpp +++ b/test/testpermissions.cpp @@ -31,7 +31,7 @@ static void assertCsyncJournalOk(SyncJournalDb &journal) QVERIFY(db.openReadOnly(journal.databaseFilePath())); SqlQuery q("SELECT count(*) from metadata where length(fileId) == 0", db); QVERIFY(q.exec()); - QVERIFY(q.next()); + QVERIFY(q.next().hasData); QCOMPARE(q.intValue(0), 0); #if defined(Q_OS_WIN) // Make sure the file does not appear in the FileInfo FileSystem::setFileHidden(journal.databaseFilePath() + "-shm", true);