#include "db_helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "reading_list.h" #include "library_item.h" #include "comic_db.h" #include "data_base_management.h" #include "folder.h" #include "yacreader_libraries.h" #include "qnaturalsorting.h" #include "QsLog.h" // server YACReaderLibraries DBHelper::getLibraries() { YACReaderLibraries libraries; libraries.load(); return libraries; } QList DBHelper::getFolderSubfoldersFromLibrary(qulonglong libraryId, qulonglong folderId) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; QList list; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); list = DBHelper::getFoldersFromParent(folderId, db, false); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); return list; } QList DBHelper::getFolderComicsFromLibrary(qulonglong libraryId, qulonglong folderId) { return DBHelper::getFolderComicsFromLibrary(libraryId, folderId, false); } QList DBHelper::getFolderComicsFromLibraryForReading(qulonglong libraryId, qulonglong folderId) { auto list = DBHelper::getFolderComicsFromLibrary(libraryId, folderId, false); std::sort(list.begin(), list.end(), [](LibraryItem *i1, LibraryItem *i2) { auto c1 = static_cast(i1); auto c2 = static_cast(i2); if (c1->info.number.isNull() && c2->info.number.isNull()) { return naturalSortLessThanCI(c1->name, c2->name); } else { if (c1->info.number.isNull() == false && c2->info.number.isNull() == false) { return naturalSortLessThanCI(c1->info.number.toString(), c2->info.number.toString()); } else { return c2->info.number.isNull(); } } }); return list; } QList DBHelper::getFolderComicsFromLibrary(qulonglong libraryId, qulonglong folderId, bool sort) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; QList list; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); list = DBHelper::getComicsFromParent(folderId, db, sort); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); return list; } quint32 DBHelper::getNumChildrenFromFolder(qulonglong libraryId, qulonglong folderId) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); quint32 result = 0; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); QSqlQuery selectQuery(db); selectQuery.prepare("SELECT count(*) FROM folder WHERE parentId = :parentId and id <> 1"); selectQuery.bindValue(":parentId", folderId); selectQuery.exec(); result += selectQuery.record().value(0).toULongLong(); selectQuery.prepare("SELECT count(*) FROM comic c WHERE c.parentId = :parentId"); selectQuery.bindValue(":parentId", folderId); selectQuery.exec(); result += selectQuery.record().value(0).toULongLong(); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); return result; } qulonglong DBHelper::getParentFromComicFolderId(qulonglong libraryId, qulonglong id) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; Folder f; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); f = DBHelper::loadFolder(id, db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); return f.parentId; } ComicDB DBHelper::getComicInfo(qulonglong libraryId, qulonglong id) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; ComicDB comic; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); bool found; comic = DBHelper::loadComic(id, db, found); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); return comic; } QList DBHelper::getSiblings(qulonglong libraryId, qulonglong parentId) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; QList comics; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); comics = DBHelper::getSortedComicsFromParent(parentId, db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); return comics; } QString DBHelper::getFolderName(qulonglong libraryId, qulonglong id) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString name = ""; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); QSqlQuery selectQuery(db); // TODO check selectQuery.prepare("SELECT name FROM folder WHERE id = :id"); selectQuery.bindValue(":id", id); selectQuery.exec(); if (selectQuery.next()) { name = selectQuery.value(0).toString(); } connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); return name; } Folder DBHelper::getFolder(qulonglong libraryId, qulonglong id) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); Folder folder; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); QSqlQuery selectQuery(db); // TODO check selectQuery.prepare("SELECT * FROM folder WHERE id = :id"); selectQuery.bindValue(":id", id); selectQuery.exec(); auto record = selectQuery.record(); int name = record.indexOf("name"); int path = record.indexOf("path"); int finished = record.indexOf("finished"); int completed = record.indexOf("completed"); int id = record.indexOf("id"); int parentId = record.indexOf("parentId"); int numChildren = record.indexOf("numChildren"); int firstChildHash = record.indexOf("firstChildHash"); int customImage = record.indexOf("customImage"); int type = record.indexOf("type"); int added = record.indexOf("added"); int updated = record.indexOf("updated"); if (selectQuery.next()) { folder = Folder(selectQuery.value(id).toULongLong(), selectQuery.value(parentId).toULongLong(), selectQuery.value(name).toString(), selectQuery.value(path).toString()); folder.finished = selectQuery.value(finished).toBool(); folder.completed = selectQuery.value(completed).toBool(); if (!selectQuery.value(numChildren).isNull() && selectQuery.value(numChildren).isValid()) { folder.numChildren = selectQuery.value(numChildren).toInt(); } folder.firstChildHash = selectQuery.value(firstChildHash).toString(); folder.customImage = selectQuery.value(customImage).toString(); folder.type = selectQuery.value(type).value(); folder.added = selectQuery.value(added).toLongLong(); folder.updated = selectQuery.value(updated).toLongLong(); } connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); return folder; } QList DBHelper::getLibrariesNames() { auto names = getLibraries().getNames(); std::sort(names.begin(), names.end(), naturalSortLessThanCI); return names; } QString DBHelper::getLibraryName(int id) { return getLibraries().getName(id); } QList DBHelper::getLabelComics(qulonglong libraryId, qulonglong labelId) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QList list; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); QSqlQuery selectQuery(db); selectQuery.prepare("SELECT c.id,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio " "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " "INNER JOIN comic_label cl ON (c.id == cl.comic_id) " "WHERE cl.label_id = :parentLabelId " "ORDER BY cl.ordering"); selectQuery.bindValue(":parentLabelId", labelId); selectQuery.exec(); while (selectQuery.next()) { ComicDB comic; comic.id = selectQuery.value(0).toULongLong(); comic.parentId = labelId; comic.name = selectQuery.value(1).toString(); comic.info.title = selectQuery.value(2).toString(); comic.info.currentPage = selectQuery.value(3).toInt(); comic.info.numPages = selectQuery.value(4).toInt(); comic.info.hash = selectQuery.value(5).toString(); comic.info.read = selectQuery.value(6).toBool(); comic.info.coverSizeRatio = selectQuery.value(7).toFloat(); list.append(comic); } connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); return list; } QList DBHelper::getFavorites(qulonglong libraryId) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QList list; const int FAV_ID = 1; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); QSqlQuery selectQuery(db); selectQuery.prepare("SELECT c.id,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio " "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " "INNER JOIN comic_default_reading_list cdrl ON (c.id == cdrl.comic_id) " "WHERE cdrl.default_reading_list_id = :parentDefaultListId " "ORDER BY cdrl.ordering"); selectQuery.bindValue(":parentDefaultListId", FAV_ID); selectQuery.exec(); while (selectQuery.next()) { ComicDB comic; comic.id = selectQuery.value(0).toULongLong(); comic.parentId = FAV_ID; comic.name = selectQuery.value(1).toString(); comic.info.title = selectQuery.value(2).toString(); comic.info.currentPage = selectQuery.value(3).toInt(); comic.info.numPages = selectQuery.value(4).toInt(); comic.info.hash = selectQuery.value(5).toString(); comic.info.read = selectQuery.value(6).toBool(); comic.info.coverSizeRatio = selectQuery.value(7).toFloat(); list.append(comic); } connectionName = db.connectionName(); } // TODO ? QSqlDatabase::removeDatabase(connectionName); return list; } QList DBHelper::getReading(qulonglong libraryId) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QList list; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); QSqlQuery selectQuery(db); selectQuery.prepare("SELECT c.id,c.parentId,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio,ci.number " "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " "WHERE ci.hasBeenOpened = 1 AND ci.read = 0 " "ORDER BY ci.lastTimeOpened DESC"); selectQuery.exec(); while (selectQuery.next()) { ComicDB comic; // TODO: use QVariant when possible to keep nulls comic.id = selectQuery.value(0).toULongLong(); comic.parentId = selectQuery.value(1).toULongLong(); comic.name = selectQuery.value(2).toString(); comic.info.title = selectQuery.value(3).toString(); comic.info.currentPage = selectQuery.value(4).toInt(); comic.info.numPages = selectQuery.value(5).toInt(); comic.info.hash = selectQuery.value(6).toString(); comic.info.read = selectQuery.value(7).toBool(); comic.info.coverSizeRatio = selectQuery.value(8).toFloat(); comic.info.number = selectQuery.value(9); list.append(comic); } connectionName = db.connectionName(); } // TODO ? QSqlDatabase::removeDatabase(connectionName); return list; } QList DBHelper::getReadingLists(qulonglong libraryId) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; QList list; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); QSqlQuery selectQuery("SELECT * from reading_list WHERE parentId IS NULL ORDER BY name DESC", db); selectQuery.exec(); QSqlRecord record = selectQuery.record(); int name = record.indexOf("name"); int id = record.indexOf("id"); int ordering = record.indexOf("ordering"); while (selectQuery.next()) { ReadingList item(selectQuery.value(name).toString(), selectQuery.value(id).toLongLong(), selectQuery.value(ordering).toInt()); if (list.isEmpty()) { list.append(item); } else { int i = 0; while (i < list.length() && naturalSortLessThanCI(list.at(i).getName(), item.getName())) i++; list.insert(i, item); } } connectionName = db.connectionName(); } // TODO ? QSqlDatabase::removeDatabase(connectionName); return list; } QList DBHelper::getReadingListFullContent(qulonglong libraryId, qulonglong readingListId, bool getFullComicInfoFields) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QList list; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); QList ids; ids << readingListId; QSqlQuery subfolders(db); subfolders.prepare("SELECT id " "FROM reading_list " "WHERE parentId = :parentId " "ORDER BY ordering ASC"); subfolders.bindValue(":parentId", readingListId); subfolders.exec(); while (subfolders.next()) ids << subfolders.value(0).toULongLong(); foreach (qulonglong id, ids) { QSqlQuery selectQuery(db); QString params; if (getFullComicInfoFields) { params = "*"; } else { params = "c.id,c.parentId,c.fileName,c.path,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio,ci.number"; } selectQuery.prepare("SELECT " + params + " " "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " "INNER JOIN comic_reading_list crl ON (c.id == crl.comic_id) " "WHERE crl.reading_list_id = :parentReadingList " "ORDER BY crl.ordering"); selectQuery.bindValue(":parentReadingList", id); selectQuery.exec(); auto record = selectQuery.record(); int idComicIndex = record.indexOf("id"); int parentIdIndex = record.indexOf("parentId"); int fileName = record.indexOf("fileName"); int path = record.indexOf("path"); while (selectQuery.next()) { ComicDB comic; if (getFullComicInfoFields) { comic.id = selectQuery.value(idComicIndex).toULongLong(); comic.parentId = selectQuery.value(parentIdIndex).toULongLong(); comic.name = selectQuery.value(fileName).toString(); comic.path = selectQuery.value(path).toString(); comic.info = getComicInfoFromQuery(selectQuery, "comicInfoId"); } else { comic.id = selectQuery.value(0).toULongLong(); comic.parentId = selectQuery.value(1).toULongLong(); comic.name = selectQuery.value(2).toString(); comic.path = selectQuery.value(3).toString(); comic.info.title = selectQuery.value(4).toString(); comic.info.currentPage = selectQuery.value(5).toInt(); comic.info.numPages = selectQuery.value(6).toInt(); comic.info.hash = selectQuery.value(7).toString(); comic.info.read = selectQuery.value(8).toBool(); comic.info.coverSizeRatio = selectQuery.value(9).toFloat(); comic.info.number = selectQuery.value(9).toInt(); } list.append(comic); } } connectionName = db.connectionName(); } // TODO ? QSqlDatabase::removeDatabase(connectionName); return list; } // objects management // deletes void DBHelper::removeFromDB(LibraryItem *item, QSqlDatabase &db) { if (item->isDir()) DBHelper::removeFromDB(dynamic_cast(item), db); else DBHelper::removeFromDB(dynamic_cast(item), db); } void DBHelper::removeFromDB(Folder *folder, QSqlDatabase &db) { QSqlQuery query(db); query.prepare("DELETE FROM folder WHERE id = :id"); query.bindValue(":id", folder->id); query.exec(); } void DBHelper::removeFromDB(ComicDB *comic, QSqlDatabase &db) { QSqlQuery query(db); query.prepare("DELETE FROM comic WHERE id = :id"); query.bindValue(":id", comic->id); query.exec(); } void DBHelper::removeLabelFromDB(qulonglong id, QSqlDatabase &db) { QSqlQuery query(db); query.prepare("DELETE FROM label WHERE id = :id"); query.bindValue(":id", id); query.exec(); } void DBHelper::removeListFromDB(qulonglong id, QSqlDatabase &db) { QSqlQuery query(db); query.prepare("DELETE FROM reading_list WHERE id = :id"); query.bindValue(":id", id); query.exec(); } void DBHelper::deleteComicsFromFavorites(const QList &comicsList, QSqlDatabase &db) { db.transaction(); QLOG_DEBUG() << "deleteComicsFromFavorites----------------------------------"; QSqlQuery query(db); query.prepare("DELETE FROM comic_default_reading_list WHERE comic_id = :comic_id AND default_reading_list_id = 1"); foreach (ComicDB comic, comicsList) { query.bindValue(":comic_id", comic.id); query.exec(); } db.commit(); } // a.k.a set comics as unread by reverting the conditions used to load the comics -> void ComicModel::setupReadingModelData(const QString &databasePath) void DBHelper::deleteComicsFromReading(const QList &comicsList, QSqlDatabase &db) { db.transaction(); QLOG_DEBUG() << "deleteComicsFromReading----------------------------------"; for (auto comic : comicsList) { comic.info.hasBeenOpened = false; comic.info.currentPage = 0; // update sets hasBeenOpened to true if currentPage > 0; DBHelper::update(&comic.info, db); } db.commit(); } void DBHelper::deleteComicsFromLabel(const QList &comicsList, qulonglong labelId, QSqlDatabase &db) { db.transaction(); QLOG_DEBUG() << "deleteComicsFromLabel----------------------------------"; QSqlQuery query(db); query.prepare("DELETE FROM comic_label WHERE comic_id = :comic_id AND label_id = :label_id"); foreach (ComicDB comic, comicsList) { query.bindValue(":comic_id", comic.id); query.bindValue(":label_id", labelId); query.exec(); QLOG_DEBUG() << "cid = " << comic.id << "lid = " << labelId; QLOG_DEBUG() << query.lastError().databaseText() << "-" << query.lastError().driverText(); } db.commit(); } void DBHelper::deleteComicsFromReadingList(const QList &comicsList, qulonglong readingListId, QSqlDatabase &db) { db.transaction(); QLOG_DEBUG() << "deleteComicsFromReadingList----------------------------------"; QSqlQuery query(db); query.prepare("DELETE FROM comic_reading_list WHERE comic_id = :comic_id AND reading_list_id = :reading_list_id"); foreach (ComicDB comic, comicsList) { query.bindValue(":comic_id", comic.id); query.bindValue(":reading_list_id", readingListId); query.exec(); } db.commit(); } // updates void DBHelper::update(ComicDB *comic, QSqlDatabase &db) { Q_UNUSED(comic) Q_UNUSED(db) // do nothing } void DBHelper::update(qulonglong libraryId, ComicInfo &comicInfo) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); DBHelper::update(&comicInfo, db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) { if (comicInfo == nullptr) return; QSqlQuery updateComicInfo(db); updateComicInfo.prepare("UPDATE comic_info SET " "title = :title," "coverPage = :coverPage," "numPages = :numPages," "number = :number," "isBis = :isBis," "count = :count," "volume = :volume," "storyArc = :storyArc," "arcNumber = :arcNumber," "arcCount = :arcCount," "genere = :genere," "writer = :writer," "penciller = :penciller," "inker = :inker," "colorist = :colorist," "letterer = :letterer," "coverArtist = :coverArtist," "date = :date," "publisher = :publisher," "format = :format," "color = :color," "ageRating = :ageRating," "synopsis = :synopsis," "characters = :characters," "notes = :notes," "read = :read," "edited = :edited," // new 7.0 fields "hasBeenOpened = :hasBeenOpened," "currentPage = :currentPage," "bookmark1 = :bookmark1," "bookmark2 = :bookmark2," "bookmark3 = :bookmark3," "brightness = :brightness," "contrast = :contrast, " "gamma = :gamma," "rating = :rating," // new 7.1 fields "comicVineID = :comicVineID," // new 9.5 fields "lastTimeOpened = :lastTimeOpened," "coverSizeRatio = :coverSizeRatio," "originalCoverSize = :originalCoverSize," //-- // new 9.8 fields // "manga = :manga," removed in 9.13 //-- // new 9.13 fields "added = :added," "type = :type," "editor = :editor," "imprint = :imprint," "teams = :teams," "locations = :locations," "series = :series," "alternateSeries = :alternateSeries," "alternateNumber = :alternateNumber," "alternateCount = :alternateCount," "languageISO = :languageISO," "seriesGroup = :seriesGroup," "mainCharacterOrTeam = :mainCharacterOrTeam," "review = :review," "tags = :tags" //-- " WHERE id = :id"); updateComicInfo.bindValue(":title", comicInfo->title); updateComicInfo.bindValue(":coverPage", comicInfo->coverPage); updateComicInfo.bindValue(":numPages", comicInfo->numPages); updateComicInfo.bindValue(":number", comicInfo->number); updateComicInfo.bindValue(":isBis", comicInfo->isBis); updateComicInfo.bindValue(":count", comicInfo->count); updateComicInfo.bindValue(":volume", comicInfo->volume); updateComicInfo.bindValue(":storyArc", comicInfo->storyArc); updateComicInfo.bindValue(":arcNumber", comicInfo->arcNumber); updateComicInfo.bindValue(":arcCount", comicInfo->arcCount); updateComicInfo.bindValue(":genere", comicInfo->genere); updateComicInfo.bindValue(":writer", comicInfo->writer); updateComicInfo.bindValue(":penciller", comicInfo->penciller); updateComicInfo.bindValue(":inker", comicInfo->inker); updateComicInfo.bindValue(":colorist", comicInfo->colorist); updateComicInfo.bindValue(":letterer", comicInfo->letterer); updateComicInfo.bindValue(":coverArtist", comicInfo->coverArtist); updateComicInfo.bindValue(":date", comicInfo->date); updateComicInfo.bindValue(":publisher", comicInfo->publisher); updateComicInfo.bindValue(":format", comicInfo->format); updateComicInfo.bindValue(":color", comicInfo->color); updateComicInfo.bindValue(":ageRating", comicInfo->ageRating); updateComicInfo.bindValue(":synopsis", comicInfo->synopsis); updateComicInfo.bindValue(":characters", comicInfo->characters); updateComicInfo.bindValue(":notes", comicInfo->notes); bool read = comicInfo->read || comicInfo->currentPage == comicInfo->numPages.toInt(); // if current page is the las page, the comic is read(completed) comicInfo->read = read; updateComicInfo.bindValue(":read", read ? 1 : 0); updateComicInfo.bindValue(":id", comicInfo->id); updateComicInfo.bindValue(":edited", comicInfo->edited ? 1 : 0); updateComicInfo.bindValue(":hasBeenOpened", comicInfo->hasBeenOpened ? 1 : 0 || comicInfo->currentPage > 1); updateComicInfo.bindValue(":currentPage", comicInfo->currentPage); updateComicInfo.bindValue(":bookmark1", comicInfo->bookmark1); updateComicInfo.bindValue(":bookmark2", comicInfo->bookmark2); updateComicInfo.bindValue(":bookmark3", comicInfo->bookmark3); updateComicInfo.bindValue(":brightness", comicInfo->brightness); updateComicInfo.bindValue(":contrast", comicInfo->contrast); updateComicInfo.bindValue(":gamma", comicInfo->gamma); updateComicInfo.bindValue(":rating", comicInfo->rating); updateComicInfo.bindValue(":comicVineID", comicInfo->comicVineID); updateComicInfo.bindValue(":lastTimeOpened", comicInfo->lastTimeOpened); updateComicInfo.bindValue(":coverSizeRatio", comicInfo->coverSizeRatio); updateComicInfo.bindValue(":originalCoverSize", comicInfo->originalCoverSize); updateComicInfo.bindValue(":added", comicInfo->added); auto intType = static_cast(comicInfo->type.value()); updateComicInfo.bindValue(":type", intType); updateComicInfo.bindValue(":editor", comicInfo->editor); updateComicInfo.bindValue(":imprint", comicInfo->imprint); updateComicInfo.bindValue(":teams", comicInfo->teams); updateComicInfo.bindValue(":locations", comicInfo->locations); updateComicInfo.bindValue(":series", comicInfo->series); updateComicInfo.bindValue(":alternateSeries", comicInfo->alternateSeries); updateComicInfo.bindValue(":alternateNumber", comicInfo->alternateNumber); updateComicInfo.bindValue(":alternateCount", comicInfo->alternateCount); updateComicInfo.bindValue(":languageISO", comicInfo->languageISO); updateComicInfo.bindValue(":seriesGroup", comicInfo->seriesGroup); updateComicInfo.bindValue(":mainCharacterOrTeam", comicInfo->mainCharacterOrTeam); updateComicInfo.bindValue(":review", comicInfo->review); updateComicInfo.bindValue(":tags", comicInfo->tags); updateComicInfo.exec(); QLOG_INFO() << updateComicInfo.lastError().databaseText(); QLOG_INFO() << updateComicInfo.lastError().text(); QLOG_INFO() << updateComicInfo.lastQuery(); } void DBHelper::updateRead(ComicInfo *comicInfo, QSqlDatabase &db) { QSqlQuery updateComicInfo(db); updateComicInfo.prepare("UPDATE comic_info SET " "read = :read" " WHERE id = :id "); updateComicInfo.bindValue(":read", comicInfo->read ? 1 : 0); updateComicInfo.bindValue(":id", comicInfo->id); updateComicInfo.exec(); } void DBHelper::updateAdded(ComicInfo *comicInfo, QSqlDatabase &db) { QSqlQuery updateComicInfo(db); updateComicInfo.prepare("UPDATE comic_info SET " "added = :added" " WHERE id = :id "); updateComicInfo.bindValue(":added", comicInfo->added); updateComicInfo.bindValue(":id", comicInfo->id); updateComicInfo.exec(); } void DBHelper::update(const Folder &folder, QSqlDatabase &db) { QSqlQuery updateFolderInfo(db); updateFolderInfo.prepare("UPDATE folder SET " "finished = :finished, " "completed = :completed " "WHERE id = :id "); updateFolderInfo.bindValue(":finished", folder.finished ? 1 : 0); updateFolderInfo.bindValue(":completed", folder.completed ? 1 : 0); updateFolderInfo.bindValue(":id", folder.id); updateFolderInfo.exec(); } void DBHelper::propagateFolderUpdatesToParent(const Folder &folder, QSqlDatabase &db) { auto currentParentId = folder.parentId; auto currentId = folder.id; while (currentParentId != 1 && currentParentId != 0) { // currentParentId is 0 if `folder` is the root folder auto f = loadFolder(currentParentId, db); currentParentId = f.parentId; currentId = f.id; } if (currentId != folder.id) { updateChildrenInfo(currentId, db); } } Folder DBHelper::updateChildrenInfo(qulonglong folderId, QSqlDatabase &db) { auto folder = loadFolder(folderId, db); QList subfolders = DBHelper::getFoldersFromParent(folderId, db, true); QList comics = DBHelper::getComicsFromParent(folderId, db, true); QList updatedSubfolders; for (auto sf : subfolders) { updatedSubfolders.append(new Folder(updateChildrenInfo(static_cast(sf)->id, db))); } QString coverHash = ""; if (!comics.isEmpty()) { auto c = static_cast(comics[0]); coverHash = c->info.hash; } else { for (auto item : updatedSubfolders) { auto f = static_cast(item); auto firstChildHash = f->firstChildHash; if (!firstChildHash.isEmpty()) { coverHash = firstChildHash; break; } } } folder.numChildren = subfolders.count() + comics.count(); folder.firstChildHash = coverHash; QSqlQuery updateFolderInfo(db); updateFolderInfo.prepare("UPDATE folder SET " "numChildren = :numChildren, " "firstChildHash = :firstChildHash " "WHERE id = :id "); updateFolderInfo.bindValue(":numChildren", folder.numChildren); updateFolderInfo.bindValue(":firstChildHash", folder.firstChildHash); updateFolderInfo.bindValue(":id", folderId); updateFolderInfo.exec(); qDeleteAll(subfolders); qDeleteAll(updatedSubfolders); qDeleteAll(comics); return folder; } void DBHelper::updateChildrenInfo(QSqlDatabase &db) { QSqlQuery selectQuery(db); // TODO check selectQuery.prepare("SELECT id FROM folder f WHERE f.parentId = 1 AND f.id <> 1"); selectQuery.exec(); while (selectQuery.next()) { DBHelper::updateChildrenInfo(selectQuery.value(0).toULongLong(), db); } } void DBHelper::updateProgress(qulonglong libraryId, const ComicInfo &comicInfo) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); bool found; ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); comic.info.currentPage = comicInfo.currentPage; comic.info.hasBeenOpened = comicInfo.currentPage > 0 || comic.info.hasBeenOpened; comic.info.read = comic.info.read || comic.info.currentPage == comic.info.numPages; DBHelper::updateReadingRemoteProgress(comic.info, db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } void DBHelper::setComicAsReading(qulonglong libraryId, const ComicInfo &comicInfo) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); bool found; ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); comic.info.hasBeenOpened = true; comic.info.read = comic.info.read || comic.info.currentPage == comic.info.numPages; DBHelper::updateReadingRemoteProgress(comic.info, db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } void DBHelper::updateReadingRemoteProgress(const ComicInfo &comicInfo, QSqlDatabase &db) { QSqlQuery updateComicInfo(db); updateComicInfo.prepare("UPDATE comic_info SET " "read = :read, " "currentPage = :currentPage, " "hasBeenOpened = :hasBeenOpened, " "lastTimeOpened = :lastTimeOpened, " "rating = :rating" " WHERE id = :id "); updateComicInfo.bindValue(":read", comicInfo.read ? 1 : 0); updateComicInfo.bindValue(":currentPage", comicInfo.currentPage); updateComicInfo.bindValue(":hasBeenOpened", comicInfo.hasBeenOpened ? 1 : 0); updateComicInfo.bindValue(":lastTimeOpened", QDateTime::currentSecsSinceEpoch()); updateComicInfo.bindValue(":id", comicInfo.id); updateComicInfo.bindValue(":rating", comicInfo.rating); updateComicInfo.exec(); updateComicInfo.clear(); } // server v1 void DBHelper::updateFromRemoteClient(qulonglong libraryId, const ComicInfo &comicInfo) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); bool found; ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); if (comic.info.hash == comicInfo.hash) { if (comicInfo.currentPage > 0) { comic.info.currentPage = comicInfo.currentPage; if (comic.info.currentPage == comic.info.numPages) comic.info.read = true; comic.info.hasBeenOpened = true; if (comic.info.lastTimeOpened.toULongLong() < comicInfo.lastTimeOpened.toULongLong()) comic.info.lastTimeOpened = comicInfo.lastTimeOpened; } if (comicInfo.rating > 0) comic.info.rating = comicInfo.rating; DBHelper::updateReadingRemoteProgress(comic.info, db); } connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } QMap> DBHelper::updateFromRemoteClient(const QMap> &comics, bool clientSendsHasBeenOpened) { QMap> moreRecentComics; foreach (qulonglong libraryId, comics.keys()) { QList libraryMoreRecentComics; QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); db.transaction(); QSqlQuery updateComicInfo(db); updateComicInfo.prepare("UPDATE comic_info SET " "read = :read, " "currentPage = :currentPage, " "hasBeenOpened = :hasBeenOpened, " "lastTimeOpened = :lastTimeOpened, " "rating = :rating" " WHERE id = :id "); foreach (ComicInfo comicInfo, comics[libraryId]) { bool found; // TODO: sanitize this -> comicInfo.id contains comic id ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); if (comic.info.hash == comicInfo.hash) { bool isMoreRecent = false; // completion takes precedence over lastTimeOpened, if we just want to synchronize the lastest status we should use only lastTimeOpened if ((comic.info.currentPage > 1 && comic.info.currentPage > comicInfo.currentPage) || (comic.info.read && !comicInfo.read)) { isMoreRecent = true; } if (comic.info.hasBeenOpened && comic.info.currentPage > comicInfo.currentPage) { isMoreRecent = true; } if (comic.info.lastTimeOpened.toULongLong() > 0 && comicInfo.lastTimeOpened.toULongLong() == 0) { isMoreRecent = true; } comic.info.currentPage = qMax(comic.info.currentPage, comicInfo.currentPage); if (comic.info.currentPage == comic.info.numPages) comic.info.read = true; comic.info.read = comic.info.read || comicInfo.read; if (clientSendsHasBeenOpened) { comic.info.hasBeenOpened = comic.info.hasBeenOpened || comicInfo.hasBeenOpened; // android } else { comic.info.hasBeenOpened = comic.info.hasBeenOpened || comicInfo.currentPage > 0; // ios (legacy) } if (comic.info.lastTimeOpened.toULongLong() < comicInfo.lastTimeOpened.toULongLong() && comicInfo.lastTimeOpened.toULongLong() > 0) comic.info.lastTimeOpened = comicInfo.lastTimeOpened; if (comicInfo.rating > 0) comic.info.rating = comicInfo.rating; updateComicInfo.bindValue(":read", comic.info.read ? 1 : 0); updateComicInfo.bindValue(":currentPage", comic.info.currentPage); updateComicInfo.bindValue(":hasBeenOpened", comic.info.hasBeenOpened ? 1 : 0); updateComicInfo.bindValue(":lastTimeOpened", comic.info.lastTimeOpened); updateComicInfo.bindValue(":id", comic.info.id); updateComicInfo.bindValue(":rating", comic.info.rating); updateComicInfo.exec(); if (isMoreRecent) { libraryMoreRecentComics.append(comic); } } } if (!libraryMoreRecentComics.isEmpty()) { moreRecentComics[libraryId] = libraryMoreRecentComics; } db.commit(); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } return moreRecentComics; } void DBHelper::updateFromRemoteClientWithHash(const QList &comics) { YACReaderLibraries libraries = DBHelper::getLibraries(); QStringList names = libraries.getNames(); foreach (QString name, names) { QString libraryPath = DBHelper::getLibraries().getPath(libraries.getId(name)); QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); db.transaction(); QSqlQuery updateComicInfo(db); updateComicInfo.prepare("UPDATE comic_info SET " "read = :read, " "currentPage = :currentPage, " "hasBeenOpened = :hasBeenOpened, " "lastTimeOpened = :lastTimeOpened, " "rating = :rating" " WHERE id = :id "); foreach (ComicInfo comicInfo, comics) { ComicInfo info = loadComicInfo(comicInfo.hash, db); if (!info.existOnDb) { continue; } if (comicInfo.currentPage > 0) { info.currentPage = comicInfo.currentPage; if (info.currentPage == info.numPages) info.read = true; info.hasBeenOpened = true; if (info.lastTimeOpened.toULongLong() < comicInfo.lastTimeOpened.toULongLong()) info.lastTimeOpened = comicInfo.lastTimeOpened; } if (comicInfo.rating > 0) { info.rating = comicInfo.rating; } updateComicInfo.bindValue(":read", info.read ? 1 : 0); updateComicInfo.bindValue(":currentPage", info.currentPage); updateComicInfo.bindValue(":hasBeenOpened", info.hasBeenOpened ? 1 : 0); updateComicInfo.bindValue(":lastTimeOpened", QDateTime::currentSecsSinceEpoch()); updateComicInfo.bindValue(":id", info.id); updateComicInfo.bindValue(":rating", info.rating); updateComicInfo.exec(); } db.commit(); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } } void DBHelper::renameLabel(qulonglong id, const QString &name, QSqlDatabase &db) { QSqlQuery renameLabelQuery(db); renameLabelQuery.prepare("UPDATE label SET " "name = :name " "WHERE id = :id"); renameLabelQuery.bindValue(":name", name); renameLabelQuery.bindValue(":id", id); renameLabelQuery.exec(); QLOG_DEBUG() << renameLabelQuery.lastError().databaseText(); } void DBHelper::renameList(qulonglong id, const QString &name, QSqlDatabase &db) { QSqlQuery renameLabelQuery(db); renameLabelQuery.prepare("UPDATE reading_list SET " "name = :name " "WHERE id = :id"); renameLabelQuery.bindValue(":name", name); renameLabelQuery.bindValue(":id", id); renameLabelQuery.exec(); } void DBHelper::reasignOrderToSublists(QList ids, QSqlDatabase &db) { QSqlQuery updateOrdering(db); updateOrdering.prepare("UPDATE reading_list SET " "ordering = :ordering " "WHERE id = :id"); db.transaction(); int order = 0; foreach (qulonglong id, ids) { updateOrdering.bindValue(":ordering", order++); updateOrdering.bindValue(":id", id); updateOrdering.exec(); } db.commit(); } void DBHelper::reasignOrderToComicsInFavorites(QList comicIds, QSqlDatabase &db) { QSqlQuery updateOrdering(db); updateOrdering.prepare("UPDATE comic_default_reading_list SET " "ordering = :ordering " "WHERE comic_id = :comic_id AND default_reading_list_id = 1"); db.transaction(); int order = 0; foreach (qulonglong id, comicIds) { updateOrdering.bindValue(":ordering", order++); updateOrdering.bindValue(":comic_id", id); updateOrdering.exec(); } db.commit(); } void DBHelper::reasignOrderToComicsInLabel(qulonglong labelId, QList comicIds, QSqlDatabase &db) { QSqlQuery updateOrdering(db); updateOrdering.prepare("UPDATE comic_label SET " "ordering = :ordering " "WHERE comic_id = :comic_id AND label_id = :label_id"); db.transaction(); int order = 0; foreach (qulonglong id, comicIds) { updateOrdering.bindValue(":ordering", order++); updateOrdering.bindValue(":comic_id", id); updateOrdering.bindValue(":label_id", labelId); updateOrdering.exec(); } db.commit(); } void DBHelper::reasignOrderToComicsInReadingList(qulonglong readingListId, QList comicIds, QSqlDatabase &db) { QSqlQuery updateOrdering(db); updateOrdering.prepare("UPDATE comic_reading_list SET " "ordering = :ordering " "WHERE comic_id = :comic_id AND reading_list_id = :reading_list_id"); db.transaction(); int order = 0; foreach (qulonglong id, comicIds) { updateOrdering.bindValue(":ordering", order++); updateOrdering.bindValue(":comic_id", id); updateOrdering.bindValue(":reading_list_id", readingListId); updateOrdering.exec(); QLOG_TRACE() << updateOrdering.lastError().databaseText() << "-" << updateOrdering.lastError().driverText(); } db.commit(); } void DBHelper::updateComicsInfo(QList &comics, const QString &databasePath) { QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); db.open(); db.transaction(); foreach (ComicDB comic, comics) { DBHelper::update(&(comic.info), db); } db.commit(); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } // inserts qulonglong DBHelper::insert(Folder *folder, QSqlDatabase &db) { auto added = QDateTime::currentSecsSinceEpoch(); folder->added = added; QSqlQuery query(db); query.prepare("INSERT INTO folder (parentId, name, path, added, type) " "VALUES (:parentId, :name, :path, :added, :type)"); query.bindValue(":parentId", folder->parentId); query.bindValue(":name", folder->name); query.bindValue(":path", folder->path); query.bindValue(":added", added); auto intType = static_cast(folder->type); query.bindValue(":type", intType); query.exec(); return query.lastInsertId().toULongLong(); } qulonglong DBHelper::insert(ComicDB *comic, QSqlDatabase &db, bool insertAllInfo) { auto added = QDateTime::currentSecsSinceEpoch(); if (!comic->info.existOnDb) { QSqlQuery comicInfoInsert(db); comicInfoInsert.prepare("INSERT INTO comic_info (hash,numPages,coverSizeRatio,originalCoverSize,added,type) " "VALUES (:hash,:numPages,:coverSizeRatio,:originalCoverSize,:added,:type)"); comicInfoInsert.bindValue(":hash", comic->info.hash); comicInfoInsert.bindValue(":numPages", comic->info.numPages); comicInfoInsert.bindValue(":coverSizeRatio", comic->info.coverSizeRatio); comicInfoInsert.bindValue(":originalCoverSize", comic->info.originalCoverSize); comicInfoInsert.bindValue(":added", added); auto intType = static_cast(comic->info.type.value()); comicInfoInsert.bindValue(":type", intType); comicInfoInsert.exec(); comic->info.id = comicInfoInsert.lastInsertId().toULongLong(); comic->info.added = added; comic->_hasCover = false; if (insertAllInfo) { DBHelper::update(&(comic->info), db); // TODO use insert to insert all the info values, the common binding need to be extracted and shared between update and insert } } else comic->_hasCover = true; QSqlQuery query(db); query.prepare("INSERT INTO comic (parentId, comicInfoId, fileName, path) " "VALUES (:parentId,:comicInfoId,:name, :path)"); query.bindValue(":parentId", comic->parentId); query.bindValue(":comicInfoId", comic->info.id); query.bindValue(":name", comic->name); query.bindValue(":path", comic->path); query.exec(); // loop through parents and update their updated field // TODO: use stored procedures QSqlQuery updateFolder(db); updateFolder.prepare("UPDATE folder SET " "updated = :updated " "WHERE id = :id "); auto currentParentId = comic->parentId; while (currentParentId != 1 && currentParentId != 0) { updateFolder.bindValue(":updated", added); updateFolder.bindValue(":id", currentParentId); updateFolder.exec(); auto f = loadFolder(currentParentId, db); currentParentId = f.parentId; } //---- return query.lastInsertId().toULongLong(); } qulonglong DBHelper::insertLabel(const QString &name, YACReader::LabelColors color, QSqlDatabase &db) { QSqlQuery query(db); query.prepare("INSERT INTO label (name, color, ordering) " "VALUES (:name, :color, :ordering)"); query.bindValue(":name", name); query.bindValue(":color", YACReader::colorToName(color)); query.bindValue(":ordering", color); query.exec(); return query.lastInsertId().toULongLong(); } qulonglong DBHelper::insertReadingList(const QString &name, QSqlDatabase &db) { QSqlQuery query(db); query.prepare("INSERT INTO reading_list (name) " "VALUES (:name)"); query.bindValue(":name", name); query.exec(); return query.lastInsertId().toULongLong(); } qulonglong DBHelper::insertReadingSubList(const QString &name, qulonglong parentId, int ordering, QSqlDatabase &db) { QSqlQuery query(db); query.prepare("INSERT INTO reading_list (name, parentId, ordering) " "VALUES (:name, :parentId, :ordering)"); query.bindValue(":name", name); query.bindValue(":parentId", parentId); query.bindValue(":ordering", ordering); query.exec(); return query.lastInsertId().toULongLong(); } void DBHelper::insertComicsInFavorites(const QList &comicsList, QSqlDatabase &db) { QSqlQuery getNumComicsInFavoritesQuery("SELECT count(*) FROM comic_default_reading_list WHERE default_reading_list_id = 1;", db); getNumComicsInFavoritesQuery.next(); int numComics = getNumComicsInFavoritesQuery.value(0).toInt(); db.transaction(); QSqlQuery query(db); query.prepare("INSERT INTO comic_default_reading_list (default_reading_list_id, comic_id, ordering) " "VALUES (1, :comic_id, :ordering)"); foreach (ComicDB comic, comicsList) { query.bindValue(":comic_id", comic.id); query.bindValue(":ordering", numComics++); query.exec(); } QLOG_TRACE() << query.lastError(); db.commit(); } void DBHelper::insertComicsInLabel(const QList &comicsList, qulonglong labelId, QSqlDatabase &db) { QSqlQuery getNumComics(QString("SELECT count(*) FROM comic_label WHERE label_id = %1;").arg(labelId), db); getNumComics.next(); int numComics = getNumComics.value(0).toInt(); db.transaction(); QSqlQuery query(db); query.prepare("INSERT INTO comic_label (label_id, comic_id, ordering) " "VALUES (:label_id, :comic_id, :ordering)"); foreach (ComicDB comic, comicsList) { query.bindValue(":label_id", labelId); query.bindValue(":comic_id", comic.id); query.bindValue(":ordering", numComics++); query.exec(); } QLOG_TRACE() << query.lastError(); db.commit(); } void DBHelper::insertComicsInReadingList(const QList &comicsList, qulonglong readingListId, QSqlDatabase &db) { QSqlQuery getNumComics("SELECT count(*) FROM comic_reading_list;", db); getNumComics.next(); int numComics = getNumComics.value(0).toInt(); db.transaction(); QSqlQuery query(db); query.prepare("INSERT INTO comic_reading_list (reading_list_id, comic_id, ordering) " "VALUES (:reading_list_id, :comic_id, :ordering)"); foreach (ComicDB comic, comicsList) { query.bindValue(":reading_list_id", readingListId); query.bindValue(":comic_id", comic.id); query.bindValue(":ordering", numComics++); query.exec(); } db.commit(); } // queries QList DBHelper::getFoldersFromParent(qulonglong parentId, QSqlDatabase &db, bool sort) { QList list; QSqlQuery selectQuery(db); // TODO check selectQuery.prepare("SELECT * FROM folder WHERE parentId = :parentId and id <> 1"); selectQuery.bindValue(":parentId", parentId); selectQuery.exec(); QSqlRecord record = selectQuery.record(); int name = record.indexOf("name"); int path = record.indexOf("path"); int finished = record.indexOf("finished"); int completed = record.indexOf("completed"); int id = record.indexOf("id"); int numChildren = record.indexOf("numChildren"); int firstChildHash = record.indexOf("firstChildHash"); int customImage = record.indexOf("customImage"); int type = record.indexOf("type"); int added = record.indexOf("added"); int updated = record.indexOf("updated"); Folder *currentItem; while (selectQuery.next()) { // TODO sort by sort indicator and name currentItem = new Folder(selectQuery.value(id).toULongLong(), parentId, selectQuery.value(name).toString(), selectQuery.value(path).toString()); currentItem->finished = selectQuery.value(finished).toBool(); currentItem->completed = selectQuery.value(completed).toBool(); if (!selectQuery.value(numChildren).isNull() && selectQuery.value(numChildren).isValid()) { currentItem->numChildren = selectQuery.value(numChildren).toInt(); } currentItem->firstChildHash = selectQuery.value(firstChildHash).toString(); currentItem->customImage = selectQuery.value(customImage).toString(); currentItem->type = selectQuery.value(type).value(); currentItem->added = selectQuery.value(added).toLongLong(); currentItem->updated = selectQuery.value(updated).toLongLong(); int lessThan = 0; if (list.isEmpty() || !sort) list.append(currentItem); else { auto last = static_cast(list.back()); QString nameLast = last->name; QString nameCurrent = currentItem->name; QList::iterator i; i = list.end(); i--; while ((0 > (lessThan = naturalCompare(nameCurrent, nameLast, Qt::CaseInsensitive))) && i != list.begin()) { i--; nameLast = (*i)->name; } if (lessThan >= 0) // si se ha encontrado un elemento menor que current, se inserta justo después list.insert(++i, currentItem); else list.insert(i, currentItem); } } return list; } QList DBHelper::getSortedComicsFromParent(qulonglong parentId, QSqlDatabase &db) { QList list; QSqlQuery selectQuery(db); selectQuery.setForwardOnly(true); selectQuery.prepare("select * from comic c inner join comic_info ci on (c.comicInfoId = ci.id) where c.parentId = :parentId"); selectQuery.bindValue(":parentId", parentId); selectQuery.exec(); QSqlRecord record = selectQuery.record(); int id = record.indexOf("id"); // int parentIdIndex = record.indexOf("parentId"); int fileName = record.indexOf("fileName"); int path = record.indexOf("path"); ComicDB currentItem; while (selectQuery.next()) { currentItem.id = selectQuery.value(id).toULongLong(); currentItem.parentId = parentId; // selectQuery.value(parentId).toULongLong(); currentItem.name = selectQuery.value(fileName).toString(); currentItem.path = selectQuery.value(path).toString(); currentItem.info = getComicInfoFromQuery(selectQuery, "comicInfoId"); list.append(currentItem); } std::sort(list.begin(), list.end(), [](const ComicDB &c1, const ComicDB &c2) { if (c1.info.number.isNull() && c2.info.number.isNull()) { return naturalSortLessThanCI(c1.name, c2.name); } else { if (c1.info.number.isNull() == false && c2.info.number.isNull() == false) { return c1.info.number.toInt() < c2.info.number.toInt(); } else { return c2.info.number.isNull(); } } }); // selectQuery.finish(); return list; } QList DBHelper::getComicsFromParent(qulonglong parentId, QSqlDatabase &db, bool sort) { QList list; QSqlQuery selectQuery(db); selectQuery.prepare("select c.id,c.parentId,c.fileName,c.path,ci.hash from comic c inner join comic_info ci on (c.comicInfoId = ci.id) where c.parentId = :parentId"); selectQuery.bindValue(":parentId", parentId); selectQuery.exec(); QSqlRecord record = selectQuery.record(); int id = record.indexOf("id"); ComicDB *currentItem; while (selectQuery.next()) { currentItem = new ComicDB(); currentItem->id = selectQuery.value(id).toULongLong(); currentItem->parentId = selectQuery.value(1).toULongLong(); currentItem->name = selectQuery.value(2).toString(); currentItem->path = selectQuery.value(3).toString(); currentItem->info = DBHelper::loadComicInfo(selectQuery.value(4).toString(), db); list.append(currentItem); } if (sort) { std::sort(list.begin(), list.end(), [](const LibraryItem *c1, const LibraryItem *c2) { return c1->name.localeAwareCompare(c2->name) < 0; }); } return list; } QList