// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "FileIndexerAgent.hpp" #include "FileIndexer_queries.hpp" #include #include #include #include FileIndexerAgent::FileIndexerAgent(sys::Service *parentService) : DatabaseAgent(parentService) { database = std::make_unique(getDbFilePath().c_str()); } void FileIndexerAgent::initDb() { auto notifications = database->query(FileIndexer::Statements::getAllNotifications); if (nullptr == notifications || FileIndexer::ONE_ROW_FOUND == notifications->getRowCount()) { return; } if (notifications->getFieldCount() == FileIndexer::NOTIFICATION_RECORD_COLUMN_COUNT) { fileChangeRecipents.clear(); do { std::string directory = (*notifications)[1].getString(); std::string service = (*notifications)[2].getString(); fileChangeRecipents[directory].insert(service); } while (notifications->nextRow()); } } void FileIndexerAgent::deinitDb() { database->deinitialize(); } void FileIndexerAgent::registerMessages() { // connect handler & message in parent service using std::placeholders::_1; // all API asynchronic parentService->connect(FileIndexer::Messages::GetListDirMessage(), std::bind(&FileIndexerAgent::handleListDir, this, _1)); parentService->connect(FileIndexer::Messages::GetRecordMessage(), std::bind(&FileIndexerAgent::handleGetRecord, this, _1)); parentService->connect(FileIndexer::Messages::SetRecordMessage(), std::bind(&FileIndexerAgent::handleSetRecord, this, _1)); parentService->connect(FileIndexer::Messages::GetPropertyMessage(), std::bind(&FileIndexerAgent::handleGetProperty, this, _1)); parentService->connect(FileIndexer::Messages::SetPropertyMessage(), std::bind(&FileIndexerAgent::handleSetProperty, this, _1)); parentService->connect(FileIndexer::Messages::GetAllPropertiesMessage(), std::bind(&FileIndexerAgent::handleGetAllProperties, this, _1)); parentService->connect(FileIndexer::Messages::SetPropertiesMessage(), std::bind(&FileIndexerAgent::handleSetProperties, this, _1)); parentService->connect(FileIndexer::Messages::DeleteFileMessage(), std::bind(&FileIndexerAgent::handleDeleteFile, this, _1)); parentService->connect(FileIndexer::Messages::DeleteAllFilesInDirMessage(), std::bind(&FileIndexerAgent::handleDeleteAllFilesInDir, this, _1)); parentService->connect(FileIndexer::Messages::RegisterOnFileChange(), std::bind(&FileIndexerAgent::handleRegisterOnFileChange, this, _1)); parentService->connect(FileIndexer::Messages::UnregisterOnFileChange(), std::bind(&FileIndexerAgent::handleUnregisterOnFileChange, this, _1)); } auto FileIndexerAgent::getDbInitString() -> const std::string { return {}; } auto FileIndexerAgent::getDbFilePath() -> const std::string { return (purefs::dir::getUserDiskPath() / "file_indexer.db").string(); } auto FileIndexerAgent::getAgentName() -> const std::string { return std::string("fileIndexerAgent"); } auto FileIndexerAgent::dbRegisterFileChange(std::string dir, std::string service) -> bool { auto retQuery = database->query(FileIndexer::Statements::getNotification, dir.c_str(), service.c_str()); if (nullptr == retQuery || FileIndexer::ZERO_ROWS_FOUND == retQuery->getRowCount()) { // notification does not exist in db, insert a new one return database->execute(FileIndexer::Statements::setNotification, dir.c_str(), service.c_str()); } // service already registered for the dir return false; } auto FileIndexerAgent::dbUnregisterFileChange(std::string dir, std::string service) -> bool { return database->execute(FileIndexer::Statements::clearNotificationdRow, dir.c_str(), service.c_str()); } auto FileIndexerAgent::handleRegisterOnFileChange(sys::Message *req) -> sys::MessagePointer { std::string directory; if (auto msg = dynamic_cast(req)) { if (msg->directory != nullptr) { directory = *(msg->directory); if (dbRegisterFileChange(directory, msg->sender)) { auto it = fileChangeRecipents.find(directory); if (fileChangeRecipents.end() == it) { fileChangeRecipents[directory] = {msg->sender}; } else { it->second.insert(msg->sender); } } } } LOG_INFO("RegisterOnFileChange from %s", req->sender.c_str()); return std::make_shared(); } auto FileIndexerAgent::handleUnregisterOnFileChange(sys::Message *req) -> sys::MessagePointer { std::string directory; if (auto msg = dynamic_cast(req)) { if (msg->directory != nullptr) { directory = *(msg->directory); if (dbUnregisterFileChange(directory, msg->sender)) { auto it = fileChangeRecipents.find(directory); if (fileChangeRecipents.end() != it) { it->second.erase(msg->sender); } } } } LOG_INFO("UnRegisterOnFileChange from %s", req->sender.c_str()); return std::make_shared(); } auto FileIndexerAgent::dbGetFilesCount() -> unsigned int { auto retQuery = database->query(FileIndexer::Statements::getFilesCount); if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) { return 0; } return (*retQuery)[0].getUInt32(); } auto FileIndexerAgent::dbGeMetadataCount() -> unsigned int { auto retQuery = database->query(FileIndexer::Statements::getMetadataCount); if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) { return 0; } return (*retQuery)[0].getUInt32(); } auto FileIndexerAgent::dbListDir(std::unique_ptr listDir) -> std::unique_ptr { auto retQuery = database->query(FileIndexer::Statements::getFilesByDir, listDir->directory.c_str()); if (nullptr == retQuery || FileIndexer::ZERO_ROWS_FOUND == retQuery->getRowCount()) { listDir->count = 0; listDir->fileList.clear(); return listDir; } listDir->count = retQuery->getRowCount(); unsigned int index = 0; // returns all files in directory if (listDir->list_limit == 0) { do { listDir->fileList.push_back(FileIndexer::FileRecord(retQuery.get())); } while (retQuery->nextRow()); return listDir; } // returns files from directory in chunks (offset,limit) // validate offset against count if (listDir->list_offset > listDir->count) { listDir->count = 0; return listDir; } // Navigate to the proper row specified by offset if (listDir->list_offset > 0) { do { index++; } while (retQuery->nextRow() && (index < listDir->list_offset)); } // Add records to the file list starting form list_offset and limited by list_limit do { listDir->fileList.push_back(FileIndexer::FileRecord(retQuery.get())); index++; } while (retQuery->nextRow() && (index < listDir->list_offset + listDir->list_limit)); return listDir; } auto FileIndexerAgent::handleListDir(sys::Message *req) -> sys::MessagePointer { if (auto msg = dynamic_cast(req)) { return std::make_shared(dbListDir(std::move(msg->listDir))); } return std::make_shared(); } auto FileIndexerAgent::dbGetRecord(std::unique_ptr record) -> FileIndexer::FileRecord { std::unique_ptr retQuery = nullptr; if (false == record->path.empty()) { retQuery = database->query(FileIndexer::Statements::getFileInfoByPath, record->path.c_str()); if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) { return FileIndexer::FileRecord{}; } } else if (record->file_id != FileIndexer::FILE_ID_NOT_EXISTS) { retQuery = database->query(FileIndexer::Statements::getFileInfoById, record->file_id); if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) { return FileIndexer::FileRecord{}; } } else { return FileIndexer::FileRecord{}; } return FileIndexer::FileRecord(retQuery.get()); } auto FileIndexerAgent::dbSetRecord(std::unique_ptr record) -> bool { auto retQuery = database->query(FileIndexer::Statements::getFileInfoByPath, record->path.c_str()); FileIndexer::FileRecord retRecord(retQuery.get()); if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) { // file do not exist in db, insert a new file record return database->execute(FileIndexer::Statements::insertFileInfo, // retRecord.file_id, record->path.c_str(), record->size, record->mime_type, record->mtime, record->directory.c_str(), record->file_type); } // update existing file record return database->execute(FileIndexer::Statements::updateFileInfo, record->path.c_str(), record->size, record->mime_type, record->mtime, record->directory.c_str(), record->file_type, retRecord.file_id); } auto FileIndexerAgent::dbDeleteFile(std::unique_ptr record) -> bool { unsigned int file_id = FileIndexer::FILE_ID_NOT_EXISTS; if (false == record->path.empty()) { auto retQuery = database->query(FileIndexer::Statements::getFileIdByPath, record->path.c_str()); if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) { return false; } else { file_id = (*retQuery)[0].getUInt32(); } } else { file_id = record->file_id; } return database->execute(FileIndexer::Statements::deleteFileById, file_id, file_id); } auto FileIndexerAgent::dbDeleteAllFilesInDir(std::unique_ptr record) -> bool { bool retStatus = false; if (false == record->directory.empty()) { auto retQuery = database->query(FileIndexer::Statements::getFilesIdByDir, record->directory.c_str()); if (nullptr == retQuery || FileIndexer::ZERO_ROWS_FOUND == retQuery->getRowCount()) { return false; } else { do { unsigned int file_id = (*retQuery)[0].getUInt32(); retStatus = database->execute(FileIndexer::Statements::deleteFileById, file_id, file_id); } while (retQuery->nextRow() && retStatus); return retStatus; } } else { return false; } } auto FileIndexerAgent::dbUpdateRecord(std::unique_ptr record) -> bool { return database->execute(FileIndexer::Statements::updateFileInfo, record->path.c_str(), record->size, record->mime_type, record->mtime, record->directory.c_str(), record->file_type, record->file_id); } auto FileIndexerAgent::handleGetRecord(sys::Message *req) -> sys::MessagePointer { if (auto msg = dynamic_cast(req)) { auto record = std::move(msg->record); msg->dbRecord = dbGetRecord(std::move(record)); return std::make_shared( std::make_unique(msg->dbRecord)); } return app::msgHandled(); } auto FileIndexerAgent::handleSetRecord(sys::Message *req) -> sys::MessagePointer { if (auto msg = dynamic_cast(req)) { auto recordPtr = std::move(msg->record); FileIndexer::FileRecord record = *recordPtr; msg->dbRecord = dbGetRecord(std::make_unique(record)); if (!(msg->dbRecord.RecordEqual(record))) { auto updateMsg = std::make_shared( std::move(recordPtr), std::make_unique(msg->dbRecord)); parentService->bus.sendUnicast(std::move(updateMsg), msg->sender); dbSetRecord(std::make_unique(record)); for (auto recipient : fileChangeRecipents[record.directory]) { auto notifyMsg = std::make_shared( std::make_unique(record.directory)); parentService->bus.sendUnicast(std::move(notifyMsg), recipient); } } } return app::msgHandled(); } auto FileIndexerAgent::handleDeleteFile(sys::Message *req) -> sys::MessagePointer { if (auto msg = dynamic_cast(req)) { auto recordPtr = std::move(msg->record); FileIndexer::FileRecord record = *recordPtr; msg->dbRecord = dbGetRecord(std::make_unique(record)); auto deleteMsg = std::make_shared(std::move(recordPtr)); parentService->bus.sendUnicast(std::move(deleteMsg), msg->sender); dbDeleteFile(std::make_unique(record)); for (auto recipient : fileChangeRecipents[record.directory]) { auto notifyMsg = std::make_shared( std::make_unique(record.directory)); parentService->bus.sendUnicast(std::move(notifyMsg), recipient); } } return app::msgHandled(); } auto FileIndexerAgent::handleDeleteAllFilesInDir(sys::Message *req) -> sys::MessagePointer { if (auto msg = dynamic_cast(req)) { auto recordPtr = std::move(msg->record); FileIndexer::FileRecord record = *recordPtr; msg->dbRecord = dbGetRecord(std::make_unique(record)); auto deleteMsg = std::make_shared(std::move(recordPtr)); parentService->bus.sendUnicast(std::move(deleteMsg), msg->sender); dbDeleteAllFilesInDir(std::make_unique(record)); for (auto recipient : fileChangeRecipents[record.directory]) { auto notifyMsg = std::make_shared( std::make_unique(record.directory)); parentService->bus.sendUnicast(std::move(notifyMsg), recipient); } } return app::msgHandled(); } auto FileIndexerAgent::dbGetProperty(std::unique_ptr metaData) -> FileIndexer::FileMetadata { auto itr = metaData->properties.begin(); auto property = itr->first; std::unique_ptr retQuery = nullptr; retQuery = database->query(FileIndexer::Statements::getPropertyValue, property.c_str(), metaData->path.c_str()); if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) { return *metaData; } FileIndexer::FileMetadata retMetaData; retMetaData.file_id = (*retQuery)[0].getUInt32(); retMetaData.properties.clear(); retMetaData.properties.emplace((*retQuery)[1].getString(), (*retQuery)[2].getString()); return retMetaData; } auto FileIndexerAgent::dbGetAllProperties(std::unique_ptr metaData) -> FileIndexer::FileMetadata { std::unique_ptr retQuery = nullptr; FileIndexer::FileMetadata retMetaData = {}; retQuery = database->query(FileIndexer::Statements::getAllProperties, metaData->path.c_str()); if (nullptr == retQuery) { return retMetaData; } if (retQuery->getRowCount() > 0) { retMetaData.file_id = (*retQuery)[0].getUInt32(); retMetaData.properties.clear(); do { retMetaData.properties.emplace((*retQuery)[1].getString(), (*retQuery)[2].getString()); } while (retQuery->nextRow()); } return retMetaData; } auto FileIndexerAgent::dbSetProperty(std::unique_ptr metaData) -> bool { std::unique_ptr retQuery = nullptr; unsigned int fileId; if (false == metaData->path.empty()) { retQuery = database->query(FileIndexer::Statements::getFileInfoByPath, metaData->path.c_str()); if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) { return false; } fileId = (*retQuery)[0].getUInt32(); } else if (metaData->file_id != FileIndexer::FILE_ID_NOT_EXISTS) { retQuery = database->query(FileIndexer::Statements::getFileInfoById, metaData->file_id); if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) { return false; } fileId = metaData->file_id; } else { // there is no way to identify the file (id or path) return false; } auto itr = metaData->properties.begin(); auto property = itr->first; auto value = itr->second; return database->execute(FileIndexer::Statements::insertPropertyValue, fileId, property.c_str(), value.c_str()); } auto FileIndexerAgent::dbSetProperties(std::unique_ptr metaData) -> bool { bool statusCode = true; std::unique_ptr retQuery = nullptr; unsigned int fileId; if (false == metaData->path.empty()) { retQuery = database->query(FileIndexer::Statements::getFileInfoByPath, metaData->path.c_str()); if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) { return false; } fileId = (*retQuery)[0].getUInt32(); } else if (metaData->file_id != FileIndexer::FILE_ID_NOT_EXISTS) { retQuery = database->query(FileIndexer::Statements::getFileInfoById, metaData->file_id); if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) { return false; } fileId = metaData->file_id; } else { // there is no way to identify the file (id or path) return false; } for (auto propVal : metaData->properties) { statusCode = database->execute( FileIndexer::Statements::insertPropertyValue, fileId, propVal.first.c_str(), propVal.second.c_str()); if (!statusCode) return statusCode; } return statusCode; } auto FileIndexerAgent::dbUpdateProperties(std::unique_ptr metaData) -> bool { bool statusCode = true; for (auto propVal : metaData->properties) { statusCode = database->execute(FileIndexer::Statements::updatePropertyValue, propVal.second.c_str(), metaData->file_id, propVal.first.c_str()); if (!statusCode) return statusCode; } return statusCode; } auto FileIndexerAgent::handleGetProperty(sys::Message *req) -> sys::MessagePointer { if (auto msg = dynamic_cast(req)) { auto metaDataPtr = std::move(msg->metaData); FileIndexer::FileMetadata metaData = *metaDataPtr; msg->dbMetaData = dbGetProperty(std::make_unique(metaData)); return std::make_shared( std::make_unique(msg->dbMetaData)); } return app::msgHandled(); } auto FileIndexerAgent::handleGetAllProperties(sys::Message *req) -> sys::MessagePointer { if (auto msg = dynamic_cast(req)) { auto metaDataPtr = std::move(msg->metaData); FileIndexer::FileMetadata metaData = *metaDataPtr; msg->dbMetaData = dbGetAllProperties(std::make_unique(metaData)); return std::make_shared( std::make_unique(msg->dbMetaData)); } return app::msgHandled(); } auto FileIndexerAgent::handleSetProperty(sys::Message *req) -> sys::MessagePointer { if (auto msg = dynamic_cast(req)) { auto metaDataPtr = std::move(msg->metaData); FileIndexer::FileMetadata metaData = *metaDataPtr; auto itr = metaDataPtr->properties.begin(); auto value = itr->second; msg->dbMetaData = dbGetProperty(std::make_unique(metaData)); auto dbItr = msg->dbMetaData.properties.begin(); auto dbValue = dbItr->second; if (dbValue != value) { auto updateMsg = std::make_shared( std::move(metaDataPtr), std::make_unique(msg->dbMetaData)); parentService->bus.sendUnicast(std::move(updateMsg), msg->sender); dbSetProperty(std::make_unique(metaData)); } } return app::msgHandled(); } auto FileIndexerAgent::handleSetProperties(sys::Message *req) -> sys::MessagePointer { if (auto msg = dynamic_cast(req)) { auto metaDataPtr = std::move(msg->metaData); FileIndexer::FileMetadata metaData = *metaDataPtr; msg->dbMetaData = dbGetAllProperties(std::make_unique(metaData)); auto updateMsg = std::make_shared( std::move(metaDataPtr), std::make_unique(msg->dbMetaData)); parentService->bus.sendUnicast(std::move(updateMsg), msg->sender); dbSetProperties(std::make_unique(metaData)); for (auto recipient : fileChangeRecipents[metaData.directory]) { auto notifyMsg = std::make_shared( std::make_unique(metaData.directory)); parentService->bus.sendUnicast(std::move(notifyMsg), recipient); } } return app::msgHandled(); }