diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index a94d7e017e..dc12518479 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -712,9 +712,40 @@ bool SyncJournalDb::updateDatabaseStructure() return true; } +bool SyncJournalDb::hasDefaultValue(const QString &columnName) +{ + SqlQuery query(_db); + const auto selectDefault = QStringLiteral("SELECT dflt_value FROM pragma_table_info('metadata') WHERE name = '%1';").arg(columnName); + query.prepare(selectDefault.toLatin1()); + if (!query.exec()) { + sqlFail(QStringLiteral("check default value for: %1").arg(columnName), query); + return false; + } + + if (const auto result = query.next();!result.ok || !result.hasData) { + qCWarning(lcDb) << "database error:" << query.error(); + return false; + } + + return !query.nullValue(0); +} + +bool SyncJournalDb::removeColumn(const QString &columnName) +{ + SqlQuery query(_db); + const auto request = QStringLiteral("ALTER TABLE metadata DROP COLUMN %1;").arg(columnName); + query.prepare(request.toLatin1()); + if (!query.exec()) { + sqlFail(QStringLiteral("update metadata structure: drop %1 column").arg(columnName), query); + return false; + } + + commitInternal(QStringLiteral("update database structure: drop %1 column").arg(columnName)); + return true; +} + bool SyncJournalDb::updateMetadataTableStructure() { - auto columns = tableColumns("metadata"); bool re = true; @@ -723,11 +754,18 @@ bool SyncJournalDb::updateMetadataTableStructure() return false; } - const auto addColumn = [this, &columns, &re] (const QString &columnName, const QString &dataType, const bool withIndex = false) { - const auto latin1ColumnName = columnName.toLatin1(); - if (columns.indexOf(latin1ColumnName) == -1) { + const auto columnExists = [&columns] (const QString &columnName) -> bool { + return columns.indexOf(columnName.toLatin1()) > -1; + }; + + const auto addColumn = [this, &re, &columnExists] (const QString &columnName, const QString &dataType, const bool withIndex = false, const QString defaultCommand = {}) { + if (!columnExists(columnName)) { SqlQuery query(_db); - const auto request = QStringLiteral("ALTER TABLE metadata ADD COLUMN %1 %2;").arg(columnName).arg(dataType); + auto request = QStringLiteral("ALTER TABLE metadata ADD COLUMN %1 %2").arg(columnName).arg(dataType); + if (!defaultCommand.isEmpty()) { + request.append(QStringLiteral(" ") + defaultCommand); + } + request.append(QStringLiteral(";")); query.prepare(request.toLatin1()); if (!query.exec()) { sqlFail(QStringLiteral("updateMetadataTableStructure: add %1 column").arg(columnName), query); @@ -851,8 +889,29 @@ bool SyncJournalDb::updateMetadataTableStructure() addColumn(QStringLiteral("isLivePhoto"), QStringLiteral("INTEGER")); addColumn(QStringLiteral("livePhotoFile"), QStringLiteral("TEXT")); - addColumn(QStringLiteral("quotaBytesUsed"), QStringLiteral("BIGINT")); - addColumn(QStringLiteral("quotaBytesAvailable"), QStringLiteral("BIGINT")); + + { + const auto quotaBytesUsed = QStringLiteral("quotaBytesUsed"); + const auto quotaBytesAvailable = QStringLiteral("quotaBytesAvailable"); + const auto defaultCommand = QStringLiteral("DEFAULT -1 NOT NULL"); + const auto bigInt = QStringLiteral("BIGINT"); + auto result = false; + + if (columnExists(quotaBytesUsed) && !hasDefaultValue(quotaBytesUsed)) { + result = removeColumn(quotaBytesUsed); + } + + if (columnExists(quotaBytesAvailable) && !hasDefaultValue(quotaBytesAvailable)) { + result = removeColumn(quotaBytesAvailable); + } + + if (result) { + columns = tableColumns("metadata"); + } + + addColumn(quotaBytesUsed, bigInt, false, defaultCommand); + addColumn(quotaBytesAvailable, bigInt, false, defaultCommand); + } return re; } diff --git a/src/common/syncjournaldb.h b/src/common/syncjournaldb.h index 71ceb470c5..14ba342227 100644 --- a/src/common/syncjournaldb.h +++ b/src/common/syncjournaldb.h @@ -21,6 +21,8 @@ #include "common/result.h" #include "common/pinstate.h" +class TestSyncJournalDB; + namespace OCC { class SyncJournalFileRecord; @@ -399,6 +401,8 @@ private: [[nodiscard]] bool updateDatabaseStructure(); [[nodiscard]] bool updateMetadataTableStructure(); [[nodiscard]] bool updateErrorBlacklistTableStructure(); + [[nodiscard]] bool removeColumn(const QString &columnName); + [[nodiscard]] bool hasDefaultValue(const QString &columnName); bool sqlFail(const QString &log, const SqlQuery &query); void commitInternal(const QString &context, bool startTrans = true); void startTransaction(); @@ -444,6 +448,8 @@ private: QByteArray _journalMode; PreparedSqlQueryManager _queryManager; + + friend class ::TestSyncJournalDB; }; bool OCSYNC_EXPORT diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 919d176892..118e31b346 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -1085,7 +1085,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(const SyncFileItemPtr &it int64_t ProcessDirectoryJob::folderBytesAvailable(const SyncFileItemPtr &item, const FolderQuota::ServerEntry serverEntry) const { const auto unlimitedFreeSpace = -3; - if (item->_size == 0 || item->_direction != SyncFileItem::Up || item->isDirectory()) { + if (item->_size == 0 || item->_direction != SyncFileItem::Up || item->isDirectory() || item->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE) { return unlimitedFreeSpace; } @@ -1093,8 +1093,7 @@ int64_t ProcessDirectoryJob::folderBytesAvailable(const SyncFileItemPtr &item, c return unlimitedFreeSpace; } - if (item->_instruction != CSYNC_INSTRUCTION_TYPE_CHANGE - && (serverEntry == FolderQuota::ServerEntry::Valid || !_dirItem)) { + if (serverEntry == FolderQuota::ServerEntry::Valid || !_dirItem) { return _folderQuota.bytesAvailable; } diff --git a/test/testsyncjournaldb.cpp b/test/testsyncjournaldb.cpp index 7c5febf12b..424b439af3 100644 --- a/test/testsyncjournaldb.cpp +++ b/test/testsyncjournaldb.cpp @@ -98,6 +98,52 @@ private slots: QVERIFY(!record.isValid()); } + void testFolderQuota() + { + const auto bigFolderRecord = QByteArray("bigfolder"); + SyncJournalFileRecord record; + record._path = bigFolderRecord; + record._inode = std::numeric_limits::max() + 12ull; + record._modtime = dropMsecs(QDateTime::currentDateTime()); + record._type = ItemTypeDirectory; + record._etag = "123123"; + record._fileId = "abcd"; + record._fileSize = 213089999; + QVERIFY(_db.setFileRecord(record)); + + SyncJournalFileRecord storedRecord; + QVERIFY(_db.getFileRecord(bigFolderRecord, &storedRecord)); + QVERIFY(storedRecord == record); + // default values + QCOMPARE(storedRecord._folderQuota.bytesAvailable, -1); + QCOMPARE(storedRecord._folderQuota.bytesUsed, -1); + + record._folderQuota.bytesUsed = 100; + record._folderQuota.bytesAvailable = 5000; + QVERIFY(_db.setFileRecord(record)); + QVERIFY(_db.getFileRecord(bigFolderRecord, &storedRecord)); + QVERIFY(storedRecord == record); + QCOMPARE(storedRecord._folderQuota.bytesAvailable, 5000); + QCOMPARE(storedRecord._folderQuota.bytesUsed, 100); + } + + void testFolderMigration() { + const auto quotaBytesUsed = QStringLiteral("quotaBytesUsed"); + const auto quotaBytesAvailable = QStringLiteral("quotaBytesAvailable"); + const auto metadata = QStringLiteral("metadata"); + const auto columnsBeforeRemoval = _db.tableColumns(metadata.toLatin1()); + QCOMPARE_GT(columnsBeforeRemoval.indexOf(quotaBytesUsed.toLatin1()), -1); + QCOMPARE_GT(columnsBeforeRemoval.indexOf(quotaBytesAvailable.toLatin1()), -1); + QVERIFY(_db.removeColumn(quotaBytesAvailable)); + QVERIFY(_db.removeColumn(quotaBytesUsed)); + QVERIFY(_db.updateMetadataTableStructure()); + const auto columnsAfterConnect = _db.tableColumns(metadata.toLatin1()); + QVERIFY(columnsAfterConnect.indexOf(quotaBytesUsed.toLatin1()) > -1); + QVERIFY(columnsAfterConnect.indexOf(quotaBytesAvailable.toLatin1()) > -1); + QVERIFY(_db.hasDefaultValue(quotaBytesUsed)); + QVERIFY(_db.hasDefaultValue(quotaBytesAvailable)); + } + void testFileRecordChecksum() { // Try with and without a checksum