File indexing completed on 2024-05-12 04:21:06
0001 /* 0002 * SPDX-FileCopyrightText: (C) 2014 Vishesh Handa <vhanda@kde.org> 0003 * SPDX-FileCopyrightText: (C) 2017 Atul Sharma <atulsharma406@gmail.com> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.1-or-later 0006 */ 0007 0008 #include "filesystemtracker.h" 0009 0010 #include "filesystemimagefetcher.h" 0011 0012 #include <QSqlDatabase> 0013 #include <QSqlError> 0014 #include <QSqlQuery> 0015 0016 #include <QDBusConnection> 0017 0018 #include <QDebug> 0019 #include <QDir> 0020 #include <QMimeDatabase> 0021 #include <QStandardPaths> 0022 0023 #include <KDirNotify> 0024 #include <kdirwatch.h> 0025 0026 FileSystemTracker::FileSystemTracker(QObject *parent) 0027 : QObject(parent) 0028 { 0029 QObject::connect(KDirWatch::self(), &KDirWatch::dirty, this, &FileSystemTracker::setSubFolder); 0030 0031 org::kde::KDirNotify *kdirnotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this); 0032 0033 connect(kdirnotify, &org::kde::KDirNotify::FilesRemoved, this, [this](const QStringList &files) { 0034 for (const QString &filePath : files) { 0035 removeFile(filePath); 0036 } 0037 }); 0038 connect(kdirnotify, &org::kde::KDirNotify::FilesAdded, this, &FileSystemTracker::setSubFolder); 0039 connect(kdirnotify, &org::kde::KDirNotify::FileRenamedWithLocalPath, this, [this](const QString &src, const QString &dst, const QString &) { 0040 removeFile(src); 0041 slotNewFiles({dst}); 0042 }); 0043 connect(kdirnotify, &org::kde::KDirNotify::FilesChanged, this, [this](const QStringList &files) { 0044 for (const QString &filePath : files) { 0045 removeFile(filePath); 0046 } 0047 slotNewFiles(files); 0048 }); 0049 0050 connect(this, &FileSystemTracker::subFolderChanged, this, &FileSystemTracker::reindexSubFolder); 0051 } 0052 0053 void FileSystemTracker::setupDb() 0054 { 0055 static QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/koko/"; 0056 QDir().mkpath(dir); 0057 0058 QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("fstracker")); 0059 db.setDatabaseName(dir + "/fstracker.sqlite3"); 0060 if (!db.open()) { 0061 qWarning() << "Failed to open db" << db.lastError().text(); 0062 return; 0063 } 0064 0065 if (db.tables().contains("files")) { 0066 QSqlQuery query(db); 0067 query.prepare("PRAGMA table_info(files)"); 0068 bool metadataChangeTime_present = false; 0069 if (!query.exec()) { 0070 qDebug() << "Failed to read db" << query.lastError(); 0071 return; 0072 } 0073 while (query.next()) { 0074 if (query.value(1).toString() == "metadataChangeTime") { 0075 metadataChangeTime_present = true; 0076 } 0077 } 0078 if (metadataChangeTime_present) { 0079 return; 0080 } else { 0081 // reindex everything 0082 qDebug() << "REINDEXING files"; 0083 query.exec("DROP TABLE files"); 0084 } 0085 } 0086 0087 QSqlQuery query(db); 0088 bool ret = 0089 query.exec(QLatin1String("CREATE TABLE files(" 0090 "id INTEGER PRIMARY KEY, " 0091 "metadataChangeTime STRING NOT NULL," 0092 "url TEXT NOT NULL UNIQUE)")); 0093 if (!ret) { 0094 qWarning() << "Could not create files table" << query.lastError().text(); 0095 return; 0096 } 0097 0098 ret = query.exec(QLatin1String("CREATE INDEX fileUrl_index ON files (url)")); 0099 if (!ret) { 0100 qWarning() << "Could not create tags index" << query.lastError().text(); 0101 return; 0102 } 0103 0104 // 0105 // WAL Journaling mode has much lower io writes than the traditional journal 0106 // based indexing. 0107 // 0108 ret = query.exec(QLatin1String("PRAGMA journal_mode = WAL")); 0109 if (!ret) { 0110 qWarning() << "Could not set WAL journaling mode" << query.lastError().text(); 0111 return; 0112 } 0113 } 0114 0115 FileSystemTracker::~FileSystemTracker() 0116 { 0117 QSqlDatabase::removeDatabase(QStringLiteral("fstracker")); 0118 } 0119 0120 void FileSystemTracker::slotImageResult(const QString &file) 0121 { 0122 QString filePath = file; 0123 filePath.replace("file://", ""); 0124 QSqlQuery query(QSqlDatabase::database("fstracker")); 0125 query.prepare("SELECT id, metadataChangeTime from files where url = ?"); 0126 query.addBindValue(filePath); 0127 if (!query.exec()) { 0128 qDebug() << query.lastError(); 0129 return; 0130 } 0131 0132 bool indexed = query.next(); 0133 0134 if (indexed && query.value(1).toString() != QFileInfo(filePath).metadataChangeTime().toString(Qt::ISODate)) { 0135 // reindex if metadata has changed 0136 removeFile(filePath); 0137 indexed = false; 0138 qDebug() << "REINDEXING" << filePath; 0139 } 0140 0141 if (!indexed) { 0142 QSqlQuery query(QSqlDatabase::database("fstracker")); 0143 query.prepare("INSERT into files(url, metadataChangeTime) VALUES (?, ?)"); 0144 query.addBindValue(filePath); 0145 query.addBindValue(QFileInfo(filePath).metadataChangeTime().toString(Qt::ISODate)); 0146 if (!query.exec()) { 0147 qDebug() << "slotImageResult: " << query.lastError(); 0148 return; 0149 } 0150 qDebug() << "ADDED" << filePath; 0151 emit imageAdded(filePath); 0152 } 0153 0154 m_filePaths << filePath; 0155 } 0156 0157 void FileSystemTracker::slotFetchFinished() 0158 { 0159 QSqlQuery query(QSqlDatabase::database("fstracker")); 0160 query.prepare("SELECT url from files"); 0161 if (!query.exec()) { 0162 qDebug() << query.lastError(); 0163 return; 0164 } 0165 0166 while (query.next()) { 0167 QString filePath = query.value(0).toString(); 0168 0169 if (filePath.contains(m_subFolder) && !m_filePaths.contains(filePath)) { 0170 removeFile(filePath); 0171 } 0172 } 0173 0174 QSqlDatabase::database("fstracker").commit(); 0175 0176 m_filePaths.clear(); 0177 emit initialScanComplete(); 0178 } 0179 0180 void FileSystemTracker::removeFile(const QString &file) 0181 { 0182 QString filePath = file; 0183 filePath.replace("file://", ""); 0184 qDebug() << "REMOVED" << filePath; 0185 emit imageRemoved(filePath); 0186 QSqlQuery query(QSqlDatabase::database("fstracker")); 0187 query.prepare("DELETE from files where url = ?"); 0188 query.addBindValue(filePath); 0189 if (!query.exec()) { 0190 qWarning() << query.lastError(); 0191 } 0192 } 0193 0194 void FileSystemTracker::slotNewFiles(const QStringList &files) 0195 { 0196 if (!m_filePaths.isEmpty()) { 0197 // A scan is already going on. No point interrupting it. 0198 return; 0199 } 0200 0201 QMimeDatabase db; 0202 for (const QString &file : files) { 0203 QMimeType mimetype = db.mimeTypeForFile(file); 0204 if (mimetype.name().startsWith("image/") || mimetype.name().startsWith("video/")) { 0205 slotImageResult(file); 0206 } 0207 } 0208 0209 m_filePaths.clear(); 0210 } 0211 0212 void FileSystemTracker::setFolder(const QString &folder) 0213 { 0214 if (m_folder == folder) { 0215 return; 0216 } 0217 0218 KDirWatch::self()->removeDir(m_folder); 0219 m_folder = folder; 0220 KDirWatch::self()->addDir(m_folder, KDirWatch::WatchSubDirs); 0221 } 0222 0223 QString FileSystemTracker::folder() const 0224 { 0225 return m_folder; 0226 } 0227 0228 void FileSystemTracker::setSubFolder(const QString &folder) 0229 { 0230 if (QFileInfo(folder).isDir()) { 0231 m_subFolder = folder; 0232 emit subFolderChanged(); 0233 } 0234 } 0235 0236 void FileSystemTracker::reindexSubFolder() 0237 { 0238 FileSystemImageFetcher *fetcher = new FileSystemImageFetcher(m_subFolder); 0239 connect(fetcher, &FileSystemImageFetcher::imageResult, this, &FileSystemTracker::slotImageResult, Qt::QueuedConnection); 0240 connect( 0241 fetcher, 0242 &FileSystemImageFetcher::finished, 0243 this, 0244 [this, fetcher] { 0245 slotFetchFinished(); 0246 fetcher->deleteLater(); 0247 }, 0248 Qt::QueuedConnection); 0249 0250 fetcher->fetch(); 0251 0252 QSqlDatabase::database("fstracker").transaction(); 0253 }