File indexing completed on 2024-04-28 03:51:41

0001 /*
0002     This file is part of the KDE Baloo Project
0003     SPDX-FileCopyrightText: 2013-2015 Vishesh Handa <vhanda@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #include "metadatamover.h"
0009 
0010 #include "database.h"
0011 #include "transaction.h"
0012 #include "document.h"
0013 #include "termgenerator.h"
0014 
0015 #include <memory>
0016 #include <QDir>
0017 #include <QTemporaryDir>
0018 #include <QTest>
0019 
0020 using namespace Baloo;
0021 
0022 class MetadataMoverTest : public QObject
0023 {
0024     Q_OBJECT
0025 public:
0026     MetadataMoverTest(QObject* parent = nullptr);
0027 
0028 private Q_SLOTS:
0029 
0030     void init();
0031     void cleanup();
0032 
0033     void testRemoveFile();
0034     void testRenameFile();
0035     void testMoveFile();
0036     void testMoveRenameFile();
0037     void testMoveFolder();
0038     void testMoveTwiceSequential();
0039     void testMoveDeleteFile();
0040     void testMoveFileRenameParent();
0041     void testRenameMoveFileRenameParent();
0042     void testMoveFileMoveParent();
0043 
0044 private:
0045     quint64 insertDoc(const QString& url, quint64 parentId);
0046 
0047     std::unique_ptr<QTemporaryDir> m_tempDir;
0048     std::unique_ptr<Database> m_db;
0049 
0050     struct DocumentInfo {
0051         QString url;
0052         DocumentTimeDB::TimeInfo timeInfo;
0053         QVector<QByteArray> docTerms;
0054         QVector<QByteArray> filenameTerms;
0055     };
0056     DocumentInfo documentInfo(quint64 docId);
0057 };
0058 
0059 MetadataMoverTest::MetadataMoverTest(QObject* parent)
0060     : QObject(parent)
0061 {
0062 }
0063 
0064 void MetadataMoverTest::cleanup()
0065 {
0066     m_db.reset();
0067     m_tempDir.reset();
0068 }
0069 
0070 void MetadataMoverTest::init()
0071 {
0072     m_tempDir = std::make_unique<QTemporaryDir>();
0073     m_db = std::make_unique<Database>(m_tempDir->path());
0074     m_db->open(Database::CreateDatabase);
0075     QVERIFY(m_db->isOpen());
0076 }
0077 
0078 quint64 MetadataMoverTest::insertDoc(const QString& url, quint64 parentId)
0079 {
0080     static quint64 fileId = 1;
0081     fileId++;
0082 
0083     Document doc;
0084     const QByteArray path = QFile::encodeName(url);
0085     doc.setUrl(path);
0086     doc.setId(fileId);
0087     doc.setParentId(parentId);
0088 
0089     // Docterms are mandatory. Use "Type:Empty" as placeholder
0090     doc.addTerm("T0");
0091 
0092     auto lastSlash = path.lastIndexOf('/');
0093     const QByteArray fileName = path.mid(lastSlash + 1);
0094     TermGenerator tg(doc);
0095     tg.indexFileNameText(QFile::decodeName(fileName));
0096 
0097     Transaction tr(m_db.get(), Transaction::ReadWrite);
0098     tr.addDocument(doc);
0099     tr.commit();
0100     return doc.id();
0101 }
0102 
0103 MetadataMoverTest::DocumentInfo MetadataMoverTest::documentInfo(quint64 id)
0104 {
0105     Transaction tr(m_db.get(), Transaction::ReadOnly);
0106     if (!tr.hasDocument(id)) {
0107         return DocumentInfo();
0108     }
0109 
0110     DocumentInfo docInfo;
0111     docInfo.url = QFile::decodeName(tr.documentUrl(id));
0112     docInfo.timeInfo = tr.documentTimeInfo(id);
0113     docInfo.docTerms = tr.documentTerms(id);
0114     docInfo.filenameTerms = tr.documentFileNameTerms(id);
0115 
0116     return docInfo;
0117 }
0118 
0119 void MetadataMoverTest::testRemoveFile()
0120 {
0121     QString url = QStringLiteral("/somefile");
0122     quint64 fid = insertDoc(url, 99);
0123 
0124     {
0125         Transaction tr(m_db.get(), Transaction::ReadOnly);
0126         QVERIFY(tr.hasDocument(fid));
0127     }
0128 
0129     MetadataMover mover(m_db.get(), this);
0130     mover.removeFileMetadata(url);
0131 
0132     {
0133         Transaction tr(m_db.get(), Transaction::ReadOnly);
0134         QVERIFY(!tr.hasDocument(fid));
0135     }
0136 }
0137 
0138 // Rename "file" to "file2"
0139 // mv <tmpdir>/file <tmpdir>/file2
0140 void MetadataMoverTest::testRenameFile()
0141 {
0142     QTemporaryDir dir;
0143     auto parentId = filePathToId(QFile::encodeName(dir.path()));
0144 
0145     QString url = dir.path() + QStringLiteral("/file");
0146     quint64 fid = insertDoc(url, parentId);
0147 
0148     DocumentInfo oldInfo = documentInfo(fid);
0149     QVERIFY(!oldInfo.url.isEmpty());
0150     QCOMPARE(oldInfo.url, url);
0151 
0152     MetadataMover mover(m_db.get(), this);
0153     QString url2 = dir.path() + QStringLiteral("/file2");
0154     mover.moveFileMetadata(url, url2);
0155 
0156     DocumentInfo newInfo = documentInfo(fid);
0157     QCOMPARE(newInfo.url, url2);
0158     QCOMPARE(newInfo.docTerms, oldInfo.docTerms);
0159     QVERIFY(newInfo.filenameTerms != oldInfo.filenameTerms);
0160     QVERIFY(!newInfo.filenameTerms.empty());
0161 }
0162 
0163 // Change parent directory of "file"
0164 // mv <tmpdir>/a/b/c/file <tmpdir>/file
0165 void MetadataMoverTest::testMoveFile()
0166 {
0167     QTemporaryDir dir;
0168     auto parentId = filePathToId(QFile::encodeName(dir.path()));
0169 
0170     auto idA = insertDoc(dir.path() + QStringLiteral("/a"), parentId);
0171     auto idB = insertDoc(dir.path() + QStringLiteral("/a/b"), idA);
0172     auto idC = insertDoc(dir.path() + QStringLiteral("/a/b/c"), idB);
0173 
0174     QString url = dir.path() + QStringLiteral("/a/b/c/file");
0175     quint64 fid = insertDoc(url, idC);
0176 
0177     DocumentInfo oldInfo = documentInfo(fid);
0178     QVERIFY(!oldInfo.url.isEmpty());
0179     QCOMPARE(oldInfo.url, url);
0180 
0181     MetadataMover mover(m_db.get(), this);
0182     QString url2 = dir.path() + QStringLiteral("/file");
0183     mover.moveFileMetadata(url, url2);
0184 
0185     DocumentInfo newInfo = documentInfo(fid);
0186     QCOMPARE(newInfo.url, url2);
0187     QCOMPARE(newInfo.docTerms, oldInfo.docTerms);
0188     QCOMPARE(newInfo.filenameTerms, oldInfo.filenameTerms);
0189 }
0190 
0191 // Change parent directory of "file", and rename to "file2"
0192 // mv <tmpdir>/a/b/c/file <tmpdir>/file2
0193 void MetadataMoverTest::testMoveRenameFile()
0194 {
0195     QTemporaryDir dir;
0196     auto parentId = filePathToId(QFile::encodeName(dir.path()));
0197 
0198     auto idA = insertDoc(dir.path() + QStringLiteral("/a"), parentId);
0199     auto idB = insertDoc(dir.path() + QStringLiteral("/a/b"), idA);
0200     auto idC = insertDoc(dir.path() + QStringLiteral("/a/b/c"), idB);
0201 
0202     QString url = dir.path() + QStringLiteral("/a/b/c/file");
0203     quint64 fid = insertDoc(url, idC);
0204 
0205     DocumentInfo oldInfo = documentInfo(fid);
0206     QVERIFY(!oldInfo.url.isEmpty());
0207     QCOMPARE(oldInfo.url, url);
0208 
0209     MetadataMover mover(m_db.get(), this);
0210     QString url2 = dir.path() + QStringLiteral("/file2");
0211     mover.moveFileMetadata(url, url2);
0212 
0213     DocumentInfo newInfo = documentInfo(fid);
0214     QCOMPARE(newInfo.url, url2);
0215     QCOMPARE(newInfo.docTerms, oldInfo.docTerms);
0216     QVERIFY(newInfo.filenameTerms != oldInfo.filenameTerms);
0217     QVERIFY(!newInfo.filenameTerms.empty());
0218 }
0219 
0220 // Rename parent directory of "file"
0221 // mv <tmpdir>/folder <tmpdir>/dir
0222 // New path of <tmpdir>/folder/file becomes <tmpdir>/dir/file
0223 void MetadataMoverTest::testMoveFolder()
0224 {
0225     QTemporaryDir dir;
0226     auto parentId = filePathToId(QFile::encodeName(dir.path()));
0227 
0228     QString folder = dir.path() + QStringLiteral("/folder");
0229     quint64 did = insertDoc(folder, parentId);
0230 
0231     QString fileUrl = folder + QStringLiteral("/file");
0232     quint64 fid = insertDoc(fileUrl, did);
0233 
0234     {
0235         Transaction tr(m_db.get(), Transaction::ReadOnly);
0236         QVERIFY(tr.hasDocument(did));
0237         QVERIFY(tr.hasDocument(fid));
0238     }
0239 
0240     QString newFolderUrl = dir.path() + QStringLiteral("/dir");
0241     MetadataMover mover(m_db.get(), this);
0242     mover.moveFileMetadata(folder, newFolderUrl);
0243 
0244     {
0245         Transaction tr(m_db.get(), Transaction::ReadOnly);
0246         QVERIFY(tr.hasDocument(did));
0247         QVERIFY(tr.hasDocument(fid));
0248         QCOMPARE(tr.documentUrl(did), QFile::encodeName(newFolderUrl));
0249         QCOMPARE(tr.documentUrl(fid), QFile::encodeName(newFolderUrl) + "/file");
0250     }
0251 }
0252 
0253 // Rename a file twice in a row
0254 // Mimic Inotify/Filewatch behavior as if there was sufficient
0255 // time to process the first rename before the second happens
0256 void MetadataMoverTest::testMoveTwiceSequential()
0257 {
0258     QTemporaryDir dir;
0259     auto did = filePathToId(QFile::encodeName(dir.path()));
0260 
0261     const QString fileUrl = dir.path() + QStringLiteral("/file");
0262     const quint64 fid = insertDoc(fileUrl, did);
0263 
0264     {
0265         Transaction tr(m_db.get(), Transaction::ReadOnly);
0266         QVERIFY(tr.hasDocument(did));
0267         QVERIFY(tr.hasDocument(fid));
0268     }
0269 
0270     MetadataMover mover(m_db.get(), this);
0271 
0272     // First rename
0273     const QString fileUrl1 = dir.path() + QStringLiteral("/file1");
0274     mover.moveFileMetadata(fileUrl, fileUrl1);
0275 
0276     // Second rename
0277     const QString fileUrl2 = dir.path() + QStringLiteral("/file2");
0278     mover.moveFileMetadata(fileUrl1, fileUrl2);
0279 
0280     // Check result
0281     {
0282         Transaction tr(m_db.get(), Transaction::ReadOnly);
0283         QVERIFY(tr.hasDocument(did));
0284         QVERIFY(tr.hasDocument(fid));
0285         QCOMPARE(tr.documentUrl(did), QFile::encodeName(dir.path()));
0286         QCOMPARE(tr.documentUrl(fid), QFile::encodeName(fileUrl2));
0287     }
0288 }
0289 
0290 // Rename a file and then immediately delete it
0291 void MetadataMoverTest::testMoveDeleteFile()
0292 {
0293     QTemporaryDir dir;
0294     auto did = filePathToId(QFile::encodeName(dir.path()));
0295 
0296     const QString fileUrl = dir.path() + QStringLiteral("/file");
0297     const quint64 fid = insertDoc(fileUrl, did);
0298 
0299     {
0300         Transaction tr(m_db.get(), Transaction::ReadOnly);
0301         QVERIFY(tr.hasDocument(did));
0302         QVERIFY(tr.hasDocument(fid));
0303     }
0304 
0305     // Rename
0306     const QString fileUrl1 = dir.path() + QStringLiteral("/file1");
0307 
0308     MetadataMover mover(m_db.get(), this);
0309     mover.moveFileMetadata(fileUrl, fileUrl1);
0310 
0311     // Delete
0312     mover.removeFileMetadata(fileUrl1);
0313 
0314     // Check result
0315     {
0316         Transaction tr(m_db.get(), Transaction::ReadOnly);
0317         QVERIFY(tr.hasDocument(did));
0318         QCOMPARE(tr.documentUrl(did), QFile::encodeName(dir.path()));
0319         QVERIFY(!tr.hasDocument(fid));
0320     }
0321 }
0322 
0323 // Move a file to a different parent directory and rename the new parent
0324 void MetadataMoverTest::testMoveFileRenameParent()
0325 {
0326     QTemporaryDir dir;
0327     auto did = filePathToId(QFile::encodeName(dir.path()));
0328 
0329     quint64 did_a = insertDoc(dir.path() + QStringLiteral("/a"), did);
0330     quint64 did_b = insertDoc(dir.path() + QStringLiteral("/b"), did);
0331 
0332     const QString fileUrl = dir.path() + QStringLiteral("/a/file");
0333     const quint64 fid = insertDoc(fileUrl, did_a);
0334 
0335     {
0336         Transaction tr(m_db.get(), Transaction::ReadOnly);
0337         QVERIFY(tr.hasDocument(did));
0338         QVERIFY(tr.hasDocument(did_a));
0339         QVERIFY(tr.hasDocument(did_b));
0340         QVERIFY(tr.hasDocument(fid));
0341     }
0342 
0343     // Move to new parent
0344     const QString fileUrl_b1 = dir.path() + QStringLiteral("/b/file1");
0345 
0346     MetadataMover mover(m_db.get(), this);
0347     mover.moveFileMetadata(fileUrl, fileUrl_b1);
0348 
0349     // Rename parent (delete old dir first)
0350     mover.removeFileMetadata(dir.path() + QStringLiteral("/a"));
0351     mover.moveFileMetadata(dir.path() + QStringLiteral("/b"), dir.path() + QStringLiteral("/a"));
0352 
0353     // Check result
0354     {
0355         Transaction tr(m_db.get(), Transaction::ReadOnly);
0356         QVERIFY(tr.hasDocument(did));
0357         QCOMPARE(tr.documentUrl(did), QFile::encodeName(dir.path()));
0358         QCOMPARE(tr.documentUrl(did_b), QFile::encodeName(dir.path()) + "/a");
0359         QVERIFY(tr.hasDocument(fid));
0360         QCOMPARE(tr.documentUrl(fid), QFile::encodeName(dir.path()) + "/a/file1");
0361     }
0362 }
0363 
0364 // Rename a file, move to a different parent directory and rename the new parent
0365 void MetadataMoverTest::testRenameMoveFileRenameParent()
0366 {
0367     QTemporaryDir dir;
0368     auto did = filePathToId(QFile::encodeName(dir.path()));
0369 
0370     quint64 did_a = insertDoc(dir.path() + QStringLiteral("/a"), did);
0371     quint64 did_b = insertDoc(dir.path() + QStringLiteral("/b"), did);
0372 
0373     const QString fileUrl = dir.path() + QStringLiteral("/a/file");
0374     const quint64 fid = insertDoc(fileUrl, did_a);
0375 
0376     {
0377         Transaction tr(m_db.get(), Transaction::ReadOnly);
0378         QVERIFY(tr.hasDocument(did));
0379         QVERIFY(tr.hasDocument(did_a));
0380         QVERIFY(tr.hasDocument(did_b));
0381         QVERIFY(tr.hasDocument(fid));
0382     }
0383 
0384     // First rename
0385     const QString fileUrl_a1 = dir.path() + QStringLiteral("/a/file1");
0386 
0387     MetadataMover mover(m_db.get(), this);
0388     mover.moveFileMetadata(fileUrl, fileUrl_a1);
0389 
0390     // Move to new parent
0391     const QString fileUrl_b1 = dir.path() + QStringLiteral("/b/file1");
0392     mover.moveFileMetadata(fileUrl_a1, fileUrl_b1);
0393 
0394     // Rename parent (delete old dir first)
0395     mover.removeFileMetadata(dir.path() + QStringLiteral("/a"));
0396     mover.moveFileMetadata(dir.path() + QStringLiteral("/b"), dir.path() + QStringLiteral("/a"));
0397 
0398     // Check result
0399     {
0400         Transaction tr(m_db.get(), Transaction::ReadOnly);
0401         QVERIFY(tr.hasDocument(did));
0402         QCOMPARE(tr.documentUrl(did), QFile::encodeName(dir.path()));
0403         QCOMPARE(tr.documentUrl(did_b), QFile::encodeName(dir.path()) + "/a");
0404         QVERIFY(tr.hasDocument(fid));
0405         QCOMPARE(tr.documentUrl(fid), QFile::encodeName(dir.path()) + "/a/file1");
0406     }
0407 }
0408 
0409 // Rename a file and then immediately rename a parent directory
0410 void MetadataMoverTest::testMoveFileMoveParent()
0411 {
0412     QTemporaryDir dir;
0413     auto did = filePathToId(QFile::encodeName(dir.path()));
0414 
0415     quint64 did_a = insertDoc(dir.path() + QStringLiteral("/a"), did);
0416 
0417     const QString fileUrl = dir.path() + QStringLiteral("/a/file");
0418     const quint64 fid = insertDoc(fileUrl, did_a);
0419 
0420     {
0421         Transaction tr(m_db.get(), Transaction::ReadOnly);
0422         QVERIFY(tr.hasDocument(did));
0423         QVERIFY(tr.hasDocument(did_a));
0424         QVERIFY(tr.hasDocument(fid));
0425     }
0426 
0427     // First rename
0428     const QString fileUrl1 = dir.path() + QStringLiteral("/a/file1");
0429 
0430     MetadataMover mover(m_db.get(), this);
0431     mover.moveFileMetadata(fileUrl, fileUrl1);
0432 
0433     // Rename parent
0434     mover.moveFileMetadata(dir.path() + QStringLiteral("/a"), dir.path() + QStringLiteral("/b"));
0435 
0436     // Check result
0437     {
0438         Transaction tr(m_db.get(), Transaction::ReadOnly);
0439         QVERIFY(tr.hasDocument(did));
0440         QVERIFY(tr.hasDocument(did_a));
0441         QCOMPARE(tr.documentUrl(did), QFile::encodeName(dir.path()));
0442         QCOMPARE(tr.documentUrl(did_a), QFile::encodeName(dir.path()) + "/b");
0443         QVERIFY(tr.hasDocument(fid));
0444         QCOMPARE(tr.documentUrl(fid), QFile::encodeName(dir.path()) + "/b/file1");
0445     }
0446 }
0447 
0448 QTEST_GUILESS_MAIN(MetadataMoverTest)
0449 
0450 #include "metadatamovertest.moc"