mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-27 17:37:36 +03:00
dcf34316fd
The idea is that the user's question is "is this folder's data available offline?" and not "does this folder have AlwaysLocal pin state?". The the answers to the two questions can differ: an always-local folder can have subitems that are not always-local and are dehydrated. The new availability enum intends to describe the answer to the user's actual question and can be derived from pin states. If pin states aren't stored in the database the way of calculating availability will depend on the vfs plugin.
461 lines
18 KiB
C++
461 lines
18 KiB
C++
/*
|
|
* This software is in the public domain, furnished "as is", without technical
|
|
* support, and with no warranty, express or implied, as to its usefulness for
|
|
* any purpose.
|
|
* */
|
|
|
|
#include <QtTest>
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#include "common/syncjournaldb.h"
|
|
#include "common/syncjournalfilerecord.h"
|
|
|
|
using namespace OCC;
|
|
|
|
class TestSyncJournalDB : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
QTemporaryDir _tempDir;
|
|
|
|
public:
|
|
TestSyncJournalDB()
|
|
: _db((_tempDir.path() + "/sync.db"))
|
|
{
|
|
QVERIFY(_tempDir.isValid());
|
|
}
|
|
|
|
qint64 dropMsecs(QDateTime time)
|
|
{
|
|
return Utility::qDateTimeToTime_t(time);
|
|
}
|
|
|
|
private slots:
|
|
|
|
void initTestCase()
|
|
{
|
|
}
|
|
|
|
void cleanupTestCase()
|
|
{
|
|
const QString file = _db.databaseFilePath();
|
|
QFile::remove(file);
|
|
}
|
|
|
|
void testFileRecord()
|
|
{
|
|
SyncJournalFileRecord record;
|
|
QVERIFY(_db.getFileRecord(QByteArrayLiteral("nonexistant"), &record));
|
|
QVERIFY(!record.isValid());
|
|
|
|
record._path = "foo";
|
|
// Use a value that exceeds uint32 and isn't representable by the
|
|
// signed int being cast to uint64 either (like uint64::max would be)
|
|
record._inode = std::numeric_limits<quint32>::max() + 12ull;
|
|
record._modtime = dropMsecs(QDateTime::currentDateTime());
|
|
record._type = ItemTypeDirectory;
|
|
record._etag = "789789";
|
|
record._fileId = "abcd";
|
|
record._remotePerm = RemotePermissions::fromDbValue("RW");
|
|
record._fileSize = 213089055;
|
|
record._checksumHeader = "MD5:mychecksum";
|
|
QVERIFY(_db.setFileRecord(record));
|
|
|
|
SyncJournalFileRecord storedRecord;
|
|
QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo"), &storedRecord));
|
|
QVERIFY(storedRecord == record);
|
|
|
|
// Update checksum
|
|
record._checksumHeader = "Adler32:newchecksum";
|
|
_db.updateFileRecordChecksum("foo", "newchecksum", "Adler32");
|
|
QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo"), &storedRecord));
|
|
QVERIFY(storedRecord == record);
|
|
|
|
// Update metadata
|
|
record._modtime = dropMsecs(QDateTime::currentDateTime().addDays(1));
|
|
// try a value that only fits uint64, not int64
|
|
record._inode = std::numeric_limits<quint64>::max() - std::numeric_limits<quint32>::max() - 1;
|
|
record._type = ItemTypeFile;
|
|
record._etag = "789FFF";
|
|
record._fileId = "efg";
|
|
record._remotePerm = RemotePermissions::fromDbValue("NV");
|
|
record._fileSize = 289055;
|
|
_db.setFileRecord(record);
|
|
QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo"), &storedRecord));
|
|
QVERIFY(storedRecord == record);
|
|
|
|
QVERIFY(_db.deleteFileRecord("foo"));
|
|
QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo"), &record));
|
|
QVERIFY(!record.isValid());
|
|
}
|
|
|
|
void testFileRecordChecksum()
|
|
{
|
|
// Try with and without a checksum
|
|
{
|
|
SyncJournalFileRecord record;
|
|
record._path = "foo-checksum";
|
|
record._remotePerm = RemotePermissions::fromDbValue(" ");
|
|
record._checksumHeader = "MD5:mychecksum";
|
|
record._modtime = Utility::qDateTimeToTime_t(QDateTime::currentDateTimeUtc());
|
|
QVERIFY(_db.setFileRecord(record));
|
|
|
|
SyncJournalFileRecord storedRecord;
|
|
QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo-checksum"), &storedRecord));
|
|
QVERIFY(storedRecord._path == record._path);
|
|
QVERIFY(storedRecord._remotePerm == record._remotePerm);
|
|
QVERIFY(storedRecord._checksumHeader == record._checksumHeader);
|
|
|
|
// qDebug()<< "OOOOO " << storedRecord._modtime.toTime_t() << record._modtime.toTime_t();
|
|
|
|
// Attention: compare time_t types here, as QDateTime seem to maintain
|
|
// milliseconds internally, which disappear in sqlite. Go for full seconds here.
|
|
QVERIFY(storedRecord._modtime == record._modtime);
|
|
QVERIFY(storedRecord == record);
|
|
}
|
|
{
|
|
SyncJournalFileRecord record;
|
|
record._path = "foo-nochecksum";
|
|
record._remotePerm = RemotePermissions();
|
|
record._modtime = Utility::qDateTimeToTime_t(QDateTime::currentDateTimeUtc());
|
|
|
|
QVERIFY(_db.setFileRecord(record));
|
|
|
|
SyncJournalFileRecord storedRecord;
|
|
QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo-nochecksum"), &storedRecord));
|
|
QVERIFY(storedRecord == record);
|
|
}
|
|
}
|
|
|
|
void testDownloadInfo()
|
|
{
|
|
using Info = SyncJournalDb::DownloadInfo;
|
|
Info record = _db.getDownloadInfo("nonexistant");
|
|
QVERIFY(!record._valid);
|
|
|
|
record._errorCount = 5;
|
|
record._etag = "ABCDEF";
|
|
record._valid = true;
|
|
record._tmpfile = "/tmp/foo";
|
|
_db.setDownloadInfo("foo", record);
|
|
|
|
Info storedRecord = _db.getDownloadInfo("foo");
|
|
QVERIFY(storedRecord == record);
|
|
|
|
_db.setDownloadInfo("foo", Info());
|
|
Info wipedRecord = _db.getDownloadInfo("foo");
|
|
QVERIFY(!wipedRecord._valid);
|
|
}
|
|
|
|
void testUploadInfo()
|
|
{
|
|
using Info = SyncJournalDb::UploadInfo;
|
|
Info record = _db.getUploadInfo("nonexistant");
|
|
QVERIFY(!record._valid);
|
|
|
|
record._errorCount = 5;
|
|
record._chunk = 12;
|
|
record._transferid = 812974891;
|
|
record._size = 12894789147;
|
|
record._modtime = dropMsecs(QDateTime::currentDateTime());
|
|
record._valid = true;
|
|
_db.setUploadInfo("foo", record);
|
|
|
|
Info storedRecord = _db.getUploadInfo("foo");
|
|
QVERIFY(storedRecord == record);
|
|
|
|
_db.setUploadInfo("foo", Info());
|
|
Info wipedRecord = _db.getUploadInfo("foo");
|
|
QVERIFY(!wipedRecord._valid);
|
|
}
|
|
|
|
void testNumericId()
|
|
{
|
|
SyncJournalFileRecord record;
|
|
|
|
// Typical 8-digit padded id
|
|
record._fileId = "00000001abcd";
|
|
QCOMPARE(record.numericFileId(), QByteArray("00000001"));
|
|
|
|
// When the numeric id overflows the 8-digit boundary
|
|
record._fileId = "123456789ocidblaabcd";
|
|
QCOMPARE(record.numericFileId(), QByteArray("123456789"));
|
|
}
|
|
|
|
void testConflictRecord()
|
|
{
|
|
ConflictRecord record;
|
|
record.path = "abc";
|
|
record.baseFileId = "def";
|
|
record.baseModtime = 1234;
|
|
record.baseEtag = "ghi";
|
|
|
|
QVERIFY(!_db.conflictRecord(record.path).isValid());
|
|
|
|
_db.setConflictRecord(record);
|
|
auto newRecord = _db.conflictRecord(record.path);
|
|
QVERIFY(newRecord.isValid());
|
|
QCOMPARE(newRecord.path, record.path);
|
|
QCOMPARE(newRecord.baseFileId, record.baseFileId);
|
|
QCOMPARE(newRecord.baseModtime, record.baseModtime);
|
|
QCOMPARE(newRecord.baseEtag, record.baseEtag);
|
|
|
|
_db.deleteConflictRecord(record.path);
|
|
QVERIFY(!_db.conflictRecord(record.path).isValid());
|
|
}
|
|
|
|
void testAvoidReadFromDbOnNextSync()
|
|
{
|
|
auto invalidEtag = QByteArray("_invalid_");
|
|
auto initialEtag = QByteArray("etag");
|
|
auto makeEntry = [&](const QByteArray &path, ItemType type) {
|
|
SyncJournalFileRecord record;
|
|
record._path = path;
|
|
record._type = type;
|
|
record._etag = initialEtag;
|
|
_db.setFileRecord(record);
|
|
};
|
|
auto getEtag = [&](const QByteArray &path) {
|
|
SyncJournalFileRecord record;
|
|
_db.getFileRecord(path, &record);
|
|
return record._etag;
|
|
};
|
|
|
|
makeEntry("foodir", ItemTypeDirectory);
|
|
makeEntry("otherdir", ItemTypeDirectory);
|
|
makeEntry("foo%", ItemTypeDirectory); // wildcards don't apply
|
|
makeEntry("foodi_", ItemTypeDirectory); // wildcards don't apply
|
|
makeEntry("foodir/file", ItemTypeFile);
|
|
makeEntry("foodir/subdir", ItemTypeDirectory);
|
|
makeEntry("foodir/subdir/file", ItemTypeFile);
|
|
makeEntry("foodir/otherdir", ItemTypeDirectory);
|
|
makeEntry("fo", ItemTypeDirectory); // prefix, but does not match
|
|
makeEntry("foodir/sub", ItemTypeDirectory); // prefix, but does not match
|
|
makeEntry("foodir/subdir/subsubdir", ItemTypeDirectory);
|
|
makeEntry("foodir/subdir/subsubdir/file", ItemTypeFile);
|
|
makeEntry("foodir/subdir/otherdir", ItemTypeDirectory);
|
|
|
|
_db.schedulePathForRemoteDiscovery(QByteArray("foodir/subdir"));
|
|
|
|
// Direct effects of parent directories being set to _invalid_
|
|
QCOMPARE(getEtag("foodir"), invalidEtag);
|
|
QCOMPARE(getEtag("foodir/subdir"), invalidEtag);
|
|
QCOMPARE(getEtag("foodir/subdir/subsubdir"), initialEtag);
|
|
|
|
QCOMPARE(getEtag("foodir/file"), initialEtag);
|
|
QCOMPARE(getEtag("foodir/subdir/file"), initialEtag);
|
|
QCOMPARE(getEtag("foodir/subdir/subsubdir/file"), initialEtag);
|
|
|
|
QCOMPARE(getEtag("fo"), initialEtag);
|
|
QCOMPARE(getEtag("foo%"), initialEtag);
|
|
QCOMPARE(getEtag("foodi_"), initialEtag);
|
|
QCOMPARE(getEtag("otherdir"), initialEtag);
|
|
QCOMPARE(getEtag("foodir/otherdir"), initialEtag);
|
|
QCOMPARE(getEtag("foodir/sub"), initialEtag);
|
|
QCOMPARE(getEtag("foodir/subdir/otherdir"), initialEtag);
|
|
|
|
// Indirect effects: setFileRecord() calls filter etags
|
|
initialEtag = "etag2";
|
|
|
|
makeEntry("foodir", ItemTypeDirectory);
|
|
QCOMPARE(getEtag("foodir"), invalidEtag);
|
|
makeEntry("foodir/subdir", ItemTypeDirectory);
|
|
QCOMPARE(getEtag("foodir/subdir"), invalidEtag);
|
|
makeEntry("foodir/subdir/subsubdir", ItemTypeDirectory);
|
|
QCOMPARE(getEtag("foodir/subdir/subsubdir"), initialEtag);
|
|
makeEntry("fo", ItemTypeDirectory);
|
|
QCOMPARE(getEtag("fo"), initialEtag);
|
|
makeEntry("foodir/sub", ItemTypeDirectory);
|
|
QCOMPARE(getEtag("foodir/sub"), initialEtag);
|
|
}
|
|
|
|
void testRecursiveDelete()
|
|
{
|
|
auto makeEntry = [&](const QByteArray &path) {
|
|
SyncJournalFileRecord record;
|
|
record._path = path;
|
|
_db.setFileRecord(record);
|
|
};
|
|
|
|
QByteArrayList elements;
|
|
elements
|
|
<< "foo"
|
|
<< "foo/file"
|
|
<< "bar"
|
|
<< "moo"
|
|
<< "moo/file"
|
|
<< "foo%bar"
|
|
<< "foo bla bar/file"
|
|
<< "fo_"
|
|
<< "fo_/file";
|
|
for (const auto& elem : elements)
|
|
makeEntry(elem);
|
|
|
|
auto checkElements = [&]() {
|
|
bool ok = true;
|
|
for (const auto& elem : elements) {
|
|
SyncJournalFileRecord record;
|
|
_db.getFileRecord(elem, &record);
|
|
if (!record.isValid()) {
|
|
qWarning() << "Missing record: " << elem;
|
|
ok = false;
|
|
}
|
|
}
|
|
return ok;
|
|
};
|
|
|
|
_db.deleteFileRecord("moo", true);
|
|
elements.removeAll("moo");
|
|
elements.removeAll("moo/file");
|
|
QVERIFY(checkElements());
|
|
|
|
_db.deleteFileRecord("fo_", true);
|
|
elements.removeAll("fo_");
|
|
elements.removeAll("fo_/file");
|
|
QVERIFY(checkElements());
|
|
|
|
_db.deleteFileRecord("foo%bar", true);
|
|
elements.removeAll("foo%bar");
|
|
QVERIFY(checkElements());
|
|
}
|
|
|
|
void testPinState()
|
|
{
|
|
auto make = [&](const QByteArray &path, PinState state) {
|
|
_db.internalPinStates().setForPath(path, state);
|
|
auto pinState = _db.internalPinStates().rawForPath(path);
|
|
QVERIFY(pinState);
|
|
QCOMPARE(*pinState, state);
|
|
};
|
|
auto get = [&](const QByteArray &path) -> PinState {
|
|
auto state = _db.internalPinStates().effectiveForPath(path);
|
|
if (!state) {
|
|
QTest::qFail("couldn't read pin state", __FILE__, __LINE__);
|
|
return PinState::Inherited;
|
|
}
|
|
return *state;
|
|
};
|
|
auto getRecursive = [&](const QByteArray &path) -> PinState {
|
|
auto state = _db.internalPinStates().effectiveForPathRecursive(path);
|
|
if (!state) {
|
|
QTest::qFail("couldn't read pin state", __FILE__, __LINE__);
|
|
return PinState::Inherited;
|
|
}
|
|
return *state;
|
|
};
|
|
auto getRaw = [&](const QByteArray &path) -> PinState {
|
|
auto state = _db.internalPinStates().rawForPath(path);
|
|
if (!state) {
|
|
QTest::qFail("couldn't read pin state", __FILE__, __LINE__);
|
|
return PinState::Inherited;
|
|
}
|
|
return *state;
|
|
};
|
|
|
|
_db.internalPinStates().wipeForPathAndBelow("");
|
|
auto list = _db.internalPinStates().rawList();
|
|
QCOMPARE(list->size(), 0);
|
|
|
|
// Make a thrice-nested setup
|
|
make("", PinState::AlwaysLocal);
|
|
make("local", PinState::AlwaysLocal);
|
|
make("online", PinState::OnlineOnly);
|
|
make("inherit", PinState::Inherited);
|
|
for (auto base : {"local/", "online/", "inherit/"}) {
|
|
make(QByteArray(base) + "inherit", PinState::Inherited);
|
|
make(QByteArray(base) + "local", PinState::AlwaysLocal);
|
|
make(QByteArray(base) + "online", PinState::OnlineOnly);
|
|
|
|
for (auto base2 : {"local/", "online/", "inherit/"}) {
|
|
make(QByteArray(base) + base2 + "inherit", PinState::Inherited);
|
|
make(QByteArray(base) + base2 + "local", PinState::AlwaysLocal);
|
|
make(QByteArray(base) + base2 + "online", PinState::OnlineOnly);
|
|
}
|
|
}
|
|
|
|
list = _db.internalPinStates().rawList();
|
|
QCOMPARE(list->size(), 4 + 9 + 27);
|
|
|
|
// Baseline direct checks (the fallback for unset root pinstate is AlwaysLocal)
|
|
QCOMPARE(get(""), PinState::AlwaysLocal);
|
|
QCOMPARE(get("local"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("online"), PinState::OnlineOnly);
|
|
QCOMPARE(get("inherit"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("nonexistant"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("online/local"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("local/online"), PinState::OnlineOnly);
|
|
QCOMPARE(get("inherit/local"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("inherit/online"), PinState::OnlineOnly);
|
|
QCOMPARE(get("inherit/inherit"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("inherit/nonexistant"), PinState::AlwaysLocal);
|
|
|
|
// Inheriting checks, level 1
|
|
QCOMPARE(get("local/inherit"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("local/nonexistant"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("online/inherit"), PinState::OnlineOnly);
|
|
QCOMPARE(get("online/nonexistant"), PinState::OnlineOnly);
|
|
|
|
// Inheriting checks, level 2
|
|
QCOMPARE(get("local/inherit/inherit"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("local/local/inherit"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("local/local/nonexistant"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("local/online/inherit"), PinState::OnlineOnly);
|
|
QCOMPARE(get("local/online/nonexistant"), PinState::OnlineOnly);
|
|
QCOMPARE(get("online/inherit/inherit"), PinState::OnlineOnly);
|
|
QCOMPARE(get("online/local/inherit"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("online/local/nonexistant"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("online/online/inherit"), PinState::OnlineOnly);
|
|
QCOMPARE(get("online/online/nonexistant"), PinState::OnlineOnly);
|
|
|
|
// Spot check the recursive variant
|
|
QCOMPARE(getRecursive(""), PinState::Inherited);
|
|
QCOMPARE(getRecursive("local"), PinState::Inherited);
|
|
QCOMPARE(getRecursive("online"), PinState::Inherited);
|
|
QCOMPARE(getRecursive("inherit"), PinState::Inherited);
|
|
QCOMPARE(getRecursive("online/local"), PinState::Inherited);
|
|
QCOMPARE(getRecursive("online/local/inherit"), PinState::AlwaysLocal);
|
|
QCOMPARE(getRecursive("inherit/inherit/inherit"), PinState::AlwaysLocal);
|
|
QCOMPARE(getRecursive("inherit/online/inherit"), PinState::OnlineOnly);
|
|
QCOMPARE(getRecursive("inherit/online/local"), PinState::AlwaysLocal);
|
|
make("local/local/local/local", PinState::AlwaysLocal);
|
|
QCOMPARE(getRecursive("local/local/local"), PinState::AlwaysLocal);
|
|
QCOMPARE(getRecursive("local/local/local/local"), PinState::AlwaysLocal);
|
|
|
|
// Check changing the root pin state
|
|
make("", PinState::OnlineOnly);
|
|
QCOMPARE(get("local"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("online"), PinState::OnlineOnly);
|
|
QCOMPARE(get("inherit"), PinState::OnlineOnly);
|
|
QCOMPARE(get("nonexistant"), PinState::OnlineOnly);
|
|
make("", PinState::AlwaysLocal);
|
|
QCOMPARE(get("local"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("online"), PinState::OnlineOnly);
|
|
QCOMPARE(get("inherit"), PinState::AlwaysLocal);
|
|
QCOMPARE(get("nonexistant"), PinState::AlwaysLocal);
|
|
|
|
// Wiping
|
|
QCOMPARE(getRaw("local/local"), PinState::AlwaysLocal);
|
|
_db.internalPinStates().wipeForPathAndBelow("local/local");
|
|
QCOMPARE(getRaw("local"), PinState::AlwaysLocal);
|
|
QCOMPARE(getRaw("local/local"), PinState::Inherited);
|
|
QCOMPARE(getRaw("local/local/local"), PinState::Inherited);
|
|
QCOMPARE(getRaw("local/local/online"), PinState::Inherited);
|
|
list = _db.internalPinStates().rawList();
|
|
QCOMPARE(list->size(), 4 + 9 + 27 - 4);
|
|
|
|
// Wiping everything
|
|
_db.internalPinStates().wipeForPathAndBelow("");
|
|
QCOMPARE(getRaw(""), PinState::Inherited);
|
|
QCOMPARE(getRaw("local"), PinState::Inherited);
|
|
QCOMPARE(getRaw("online"), PinState::Inherited);
|
|
list = _db.internalPinStates().rawList();
|
|
QCOMPARE(list->size(), 0);
|
|
}
|
|
|
|
private:
|
|
SyncJournalDb _db;
|
|
};
|
|
|
|
QTEST_APPLESS_MAIN(TestSyncJournalDB)
|
|
#include "testsyncjournaldb.moc"
|