diff --git a/YACReader/main_window_viewer.cpp b/YACReader/main_window_viewer.cpp index b5df0749..179540e4 100644 --- a/YACReader/main_window_viewer.cpp +++ b/YACReader/main_window_viewer.cpp @@ -1575,7 +1575,7 @@ void MainWindowViewer::sendComic() auto client = new YACReaderLocalClient; connect(client, &YACReaderLocalClient::finished, client, &YACReaderLocalClient::deleteLater); - currentComicDB.info.lastTimeOpened = QDateTime::currentMSecsSinceEpoch() / 1000; + currentComicDB.info.lastTimeOpened = QDateTime::currentSecsSinceEpoch(); viewer->updateComic(currentComicDB); diff --git a/YACReaderLibrary/db/comic_model.cpp b/YACReaderLibrary/db/comic_model.cpp index e5fcce18..34f6c1eb 100644 --- a/YACReaderLibrary/db/comic_model.cpp +++ b/YACReaderLibrary/db/comic_model.cpp @@ -239,6 +239,7 @@ QHash ComicModel::roleNames() const roles[CoverPathRole] = "cover_path"; roles[PublicationDate] = "date"; roles[ReadableTitle] = "readable_title"; + roles[Added] = "added_date"; return roles; } @@ -301,6 +302,10 @@ QVariant ComicModel::data(const QModelIndex &index, int role) const return item->data(Id); else if (role == PublicationDateRole) return QVariant(localizedDate(item->data(PublicationDate).toString())); + else if (role == AddedRole) + return item->data(Added); + else if (role == TypeRole) + return item->data(Type); if (role != Qt::DisplayRole) return QVariant(); @@ -443,6 +448,8 @@ QStringList ComicModel::getPaths(const QString &_source) return paths; } +#define COMIC_MODEL_QUERY_FIELDS "ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.isBis,ci.currentPage,ci.rating,ci.hasBeenOpened,ci.date,ci.added,ci.type" + void ComicModel::setupFolderModelData(unsigned long long int folderId, const QString &databasePath) { enableResorting = false; @@ -458,7 +465,7 @@ void ComicModel::setupFolderModelData(unsigned long long int folderId, const QSt { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.isBis,ci.currentPage,ci.rating,ci.hasBeenOpened,ci.date " + selectQuery.prepare("SELECT " COMIC_MODEL_QUERY_FIELDS " " "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " "WHERE c.parentId = :parentId"); selectQuery.bindValue(":parentId", folderId); @@ -485,7 +492,7 @@ void ComicModel::setupLabelModelData(unsigned long long parentLabel, const QStri { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.isBis,ci.currentPage,ci.rating,ci.hasBeenOpened,ci.date " + selectQuery.prepare("SELECT " COMIC_MODEL_QUERY_FIELDS " " "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 " @@ -529,7 +536,7 @@ void ComicModel::setupReadingListModelData(unsigned long long parentReadingList, foreach (qulonglong id, ids) { QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.isBis,ci.currentPage,ci.rating,ci.hasBeenOpened,ci.date " + selectQuery.prepare("SELECT " COMIC_MODEL_QUERY_FIELDS " " "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 " @@ -566,7 +573,7 @@ void ComicModel::setupFavoritesModelData(const QString &databasePath) { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.isBis,ci.currentPage,ci.rating,ci.hasBeenOpened,ci.date " + selectQuery.prepare("SELECT " COMIC_MODEL_QUERY_FIELDS " " "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 " @@ -595,7 +602,7 @@ void ComicModel::setupReadingModelData(const QString &databasePath) { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.isBis,ci.currentPage,ci.rating,ci.hasBeenOpened,ci.date " + selectQuery.prepare("SELECT " COMIC_MODEL_QUERY_FIELDS " " "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"); @@ -652,7 +659,7 @@ void ComicModel::setupModelData(QSqlQuery &sqlquery) return naturalSortLessThanCI(c1->data(ComicModel::FileName).toString(), c2->data(ComicModel::FileName).toString()); } else { if (c1->data(ComicModel::Number).isNull() == false && c2->data(ComicModel::Number).isNull() == false) { - return c1->data(ComicModel::Number).toInt() < c2->data(ComicModel::Number).toInt(); + return naturalSortLessThanCI(c1->data(ComicModel::Number).toString(), c2->data(ComicModel::Number).toString()); } else { return c2->data(ComicModel::Number).isNull(); } @@ -794,7 +801,7 @@ QVector ComicModel::setComicsRead(QList l return getReadList(); } -void ComicModel::setComicsManga(QList list, bool isManga) +void ComicModel::setComicsType(QList list, FileType type) { QString connectionName = ""; { @@ -803,7 +810,7 @@ void ComicModel::setComicsManga(QList list, bool isManga) foreach (QModelIndex mi, list) { bool found; ComicDB c = DBHelper::loadComic(_data.value(mi.row())->data(ComicModel::Id).toULongLong(), db, found); - c.info.manga = isManga; + c.info.type = QVariant::fromValue(type); DBHelper::update(&(c.info), db); } db.commit(); diff --git a/YACReaderLibrary/db/comic_model.h b/YACReaderLibrary/db/comic_model.h index af6e05bd..b4b5eeda 100644 --- a/YACReaderLibrary/db/comic_model.h +++ b/YACReaderLibrary/db/comic_model.h @@ -31,11 +31,13 @@ public: Path = 6, Hash = 7, ReadColumn = 8, - IsBis = 9, + IsBis = 9, // TODO_METADATA: Remove this column CurrentPage = 10, Rating = 11, HasBeenOpened = 12, PublicationDate = 13, + Added = 14, + Type = 15, }; enum Roles { @@ -55,6 +57,8 @@ public: CoverPathRole, PublicationDateRole, ReadableTitle, + AddedRole, + TypeRole, }; enum Mode { @@ -107,7 +111,7 @@ public: // setComicInfoForAllComics(); --> inserta la información común a todos los cómics de una sola vez. // setComicInfoForSelectedComis(QList list); -->inserta la información común para los comics seleccionados QVector setComicsRead(QList list, YACReaderComicReadStatus read); - void setComicsManga(QList list, bool isManga); + void setComicsType(QList list, FileType type); qint64 asignNumbers(QList list, int startingNumber); // void remove(ComicDB * comic, int row); void removeInTransaction(int row); diff --git a/YACReaderLibrary/db/data_base_management.cpp b/YACReaderLibrary/db/data_base_management.cpp index 0e1d0196..0aa71b9e 100644 --- a/YACReaderLibrary/db/data_base_management.cpp +++ b/YACReaderLibrary/db/data_base_management.cpp @@ -64,23 +64,29 @@ static QString fields = "title," //"coverSizeRatio," cover may have changed since the info was exported... //"originalCoverSize," // h/w // new 9.8 fields - "manga"; + "manga," + // new 9.13 fields + "added," + "type," + "editor," + "imprint," + "teams," + "locations," + "series," + "alternateSeries," + "alternateNumber," + "alternateCount," + "languageISO," + "seriesGroup," + "mainCharacterOrTeam," + "review," + "tags"; DataBaseManagement::DataBaseManagement() : QObject(), dataBasesList() { } -/*TreeModel * DataBaseManagement::newTreeModel(QString path) -{ - //la consulta se ejecuta... - QSqlQuery selectQuery(loadDatabase(path)); - selectQuery.setForwardOnly(true); - selectQuery.exec("select * from folder order by parentId,name"); - //selectQuery.finish(); - return new TreeModel(selectQuery); -}*/ - QSqlDatabase DataBaseManagement::createDatabase(QString name, QString path) { return createDatabase(QDir::cleanPath(path) + "/" + name + ".ydb"); @@ -106,8 +112,6 @@ QSqlDatabase DataBaseManagement::createDatabase(QString dest) "VALUES (1,'root', '/')", db); } - // query.finish(); - // db.close(); return db; } @@ -156,13 +160,13 @@ bool DataBaseManagement::createTables(QSqlDatabase &database) "coverPage INTEGER DEFAULT 1," "numPages INTEGER," - "number INTEGER," + "number TEXT," // changed to text from INTEGER (9.13) "isBis BOOLEAN," "count INTEGER," "volume TEXT," "storyArc TEXT," - "arcNumber INTEGER," + "arcNumber TEXT," // changed to text from INTEGER (9.13) "arcCount INTEGER," "genere TEXT," @@ -174,7 +178,7 @@ bool DataBaseManagement::createTables(QSqlDatabase &database) "letterer TEXT," "coverArtist TEXT," - "date TEXT," // dd/mm/yyyy --> se mostrará en 3 campos diferentes + "date TEXT," // publication date dd/mm/yyyy --> se mostrará en 3 campos diferentes "publisher TEXT," "format TEXT," "color BOOLEAN," @@ -190,7 +194,7 @@ bool DataBaseManagement::createTables(QSqlDatabase &database) // new 7.0 fields "hasBeenOpened BOOLEAN DEFAULT 0," - "rating INTEGER DEFAULT 0," + "rating INTEGER DEFAULT 0," // TODO_METADATA change type to REAL with two decimals "currentPage INTEGER DEFAULT 1, " "bookmark1 INTEGER DEFAULT -1, " "bookmark2 INTEGER DEFAULT -1, " @@ -205,8 +209,23 @@ bool DataBaseManagement::createTables(QSqlDatabase &database) "coverSizeRatio REAL," "originalCoverSize STRING," // h/w // new 9.8 fields - "manga BOOLEAN DEFAULT 0" - + "manga BOOLEAN DEFAULT 0," // deprecated 9.13 + // new 9.13 fields + "added INTEGER," + "type INTEGER DEFAULT 0," // 0 = comic, 1 = manga, 2 = manga left to right, 3 = webcomic, 4 = 4koma + "editor TEXT," + "imprint TEXT," + "teams TEXT," + "locations TEXT," + "series TEXT," + "alternateSeries TEXT," + "alternateNumber TEXT," + "alternateCount INTEGER," + "languageISO TEXT," + "seriesGroup TEXT," + "mainCharacterOrTeam TEXT," + "review TEXT," + "tags TEXT" ")"); success = success && queryComicInfo.exec(); // queryComicInfo.finish(); @@ -226,7 +245,11 @@ bool DataBaseManagement::createTables(QSqlDatabase &database) "firstChildHash TEXT," "customImage TEXT," // new 9.8 fields - "manga BOOLEAN DEFAULT 0," + "manga BOOLEAN DEFAULT 0," // deprecated 9.13 + // new 9.13 fields + "type INTEGER DEFAULT 0," // 0 = comic, 1 = manga, 2 = manga left to right, 3 = webcomic, 4 = 4koma + "added INTEGER," + "updated INTEGER," // updated when the folder gets new content "FOREIGN KEY(parentId) REFERENCES folder(id) ON DELETE CASCADE)"); success = success && queryFolder.exec(); @@ -460,7 +483,25 @@ bool DataBaseManagement::importComicsInfo(QString source, QString dest) //-- // new 9.8 fields - "manga = :manga" + "manga = :manga," + + // new 9.13 fields + "added = :added," + "type = :type," // 0 = comic, 1 = manga, 2 = manga left to right, 3 = webcomic, + "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 hash = :hash "); @@ -496,6 +537,21 @@ bool DataBaseManagement::importComicsInfo(QString source, QString dest) "comicVineID," "lastTimeOpened," "coverSizeRatio," + "manga," + "added," + "type," + "editor," + "imprint," + "teams," + "locations," + "series," + "alternateSeries," + "alternateNumber," + "alternateCount," + "languageISO," + "seriesGroup," + "mainCharacterOrTeam," + "review," "hash)" "VALUES (:title," @@ -539,6 +595,23 @@ bool DataBaseManagement::importComicsInfo(QString source, QString dest) ":coverSizeRatio," ":originalCoverSize," + ":manga," + ":added," + ":type," + ":editor," + ":imprint," + ":teams," + ":locations," + ":series," + ":alternateSeries," + ":alternateNumber," + ":alternateCount," + ":languageISO," + ":seriesGroup," + ":mainCharacterOrTeam," + ":review," + ":tags," + ":hash )"); QSqlRecord record = newInfo.record(); @@ -596,6 +669,8 @@ bool DataBaseManagement::importComicsInfo(QString source, QString dest) return b; } + +// TODO: update fields // TODO fix these bindings void DataBaseManagement::bindValuesFromRecord(const QSqlRecord &record, QSqlQuery &query) { @@ -654,6 +729,22 @@ void DataBaseManagement::bindValuesFromRecord(const QSqlRecord &record, QSqlQuer bindValue("coverSizeRatio", record, query); bindValue("originalCoverSize", record, query); + bindValue("added", record, query); + bindValue("type", record, query); + bindValue("editor", record, query); + bindValue("imprint", record, query); + bindValue("teams", record, query); + bindValue("locations", record, query); + bindValue("series", record, query); + bindValue("alternateSeries", record, query); + bindValue("alternateNumber", record, query); + bindValue("alternateCount", record, query); + bindValue("languageISO", record, query); + bindValue("seriesGroup", record, query); + bindValue("mainCharacterOrTeam", record, query); + bindValue("review", record, query); + bindValue("tags", record, query); + bindValue("hash", record, query); } @@ -757,6 +848,7 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &path) bool pre8 = false; bool pre9_5 = false; bool pre9_8 = false; + bool pre9_13 = false; QString fullPath = path + "/library.ydb"; @@ -770,6 +862,8 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &path) pre9_5 = true; if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "9.8.0") < 0) pre9_8 = true; + if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "9.13.0") < 0) + pre9_13 = true; QString connectionName = ""; bool returnValue = false; @@ -895,6 +989,53 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &path) returnValue = returnValue && successAddingColumns; } } + + if (pre9_13) { + { // comic_info + QStringList columnDefs; + columnDefs << "added INTEGER"; + columnDefs << "type INTEGER DEFAULT 0"; // 0 = comic, 1 = manga, 2 = manga left to right, 3 = webcomic, + columnDefs << "editor TEXT"; + columnDefs << "imprint TEXT"; + columnDefs << "teams TEXT"; + columnDefs << "locations TEXT"; + columnDefs << "series TEXT"; + columnDefs << "alternateSeries TEXT"; + columnDefs << "alternateNumber TEXT"; + columnDefs << "alternateCount INTEGER"; + columnDefs << "languageISO TEXT"; + columnDefs << "seriesGroup TEXT"; + columnDefs << "mainCharacterOrTeam TEXT"; + columnDefs << "review TEXT"; + columnDefs << "tags TEXT"; + bool successAddingColumns = addColumns("comic_info", columnDefs, db); + returnValue = returnValue && successAddingColumns; + + QSqlQuery updateTypeQueryToManga(db); + updateTypeQueryToManga.prepare("UPDATE comic_info SET type = manga"); + bool successMigratingManga = updateTypeQueryToManga.exec(); + returnValue = returnValue && successMigratingManga; + + QSqlQuery updateNumberQueryToBis(db); + updateNumberQueryToBis.prepare("UPDATE comic_info SET number = number + 0.5 WHERE isBis = 1"); + bool successMigratingBis = updateNumberQueryToBis.exec(); + returnValue = returnValue && successMigratingBis; + } + { // folder + QStringList columnDefs; + columnDefs << "added INTEGER"; + columnDefs << "updated INTEGER"; + columnDefs << "type INTEGER DEFAULT 0"; + + bool successAddingColumns = addColumns("folder", columnDefs, db); + returnValue = returnValue && successAddingColumns; + + QSqlQuery updateTypeQueryToManga(db); + updateTypeQueryToManga.prepare("UPDATE folder SET type = manga"); + bool successMigratingManga = updateTypeQueryToManga.exec(); + returnValue = returnValue && successMigratingManga; + } + } } connectionName = db.connectionName(); } diff --git a/YACReaderLibrary/db/data_base_management.h b/YACReaderLibrary/db/data_base_management.h index 2c269e2e..29302162 100644 --- a/YACReaderLibrary/db/data_base_management.h +++ b/YACReaderLibrary/db/data_base_management.h @@ -42,7 +42,6 @@ private: public: DataBaseManagement(); - // TreeModel * newTreeModel(QString path); // crea una base de datos y todas sus tablas static QSqlDatabase createDatabase(QString name, QString path); static QSqlDatabase createDatabase(QString dest); diff --git a/YACReaderLibrary/db/folder_model.cpp b/YACReaderLibrary/db/folder_model.cpp index 97a61760..e2db8912 100644 --- a/YACReaderLibrary/db/folder_model.cpp +++ b/YACReaderLibrary/db/folder_model.cpp @@ -6,8 +6,6 @@ #include "db_helper.h" #include "qnaturalsorting.h" #include "yacreader_global_gui.h" -#include "QsLog.h" -#include "query_parser.h" #include @@ -61,14 +59,12 @@ FolderModel::FolderModel(QObject *parent) FolderModel::FolderModel(QSqlQuery &sqlquery, QObject *parent) : QAbstractItemModel(parent), isSubfolder(false), rootItem(nullptr) { - // lo m�s probable es que el nodo ra�z no necesite tener informaci�n QList rootData; - rootData << "root"; // id 0, padre 0, title "root" (el id, y el id del padre van a ir en la clase TreeItem) + rootData << "root"; // id 1, parent 1, title "root" rootItem = new FolderItem(rootData); rootItem->id = ROOT; rootItem->parentItem = nullptr; setupModelData(sqlquery, rootItem); - // sqlquery.finish(); } FolderModel::~FolderModel() @@ -98,7 +94,11 @@ QHash FolderModel::roleNames() const roles[IdRole] = "id"; roles[MangaRole] = "is_manga"; roles[CoverPathRole] = "cover_path"; - roles[FolderName] = "name"; + roles[FolderNameRole] = "name"; + roles[NumChildrenRole] = "num_children"; + roles[TypeRole] = "type"; + roles[AddedRole] = "added"; + roles[UpdatedRole] = "type"; return roles; } @@ -150,7 +150,7 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const #endif } - if (role == FolderModel::FolderName) { + if (role == FolderModel::FolderNameRole) { return item->data(FolderModel::Name); } @@ -169,6 +169,18 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const if (role == FolderModel::CoverPathRole) return getCoverUrlPathForComicHash(item->data(FirstChildHash).toString()); + if (role == FolderModel::NumChildrenRole) + return item->data(NumChildren); + + if (role == FolderModel::TypeRole) + return item->data(Type); + + if (role == FolderModel::AddedRole) + return item->data(Added); + + if (role == FolderModel::UpdatedRole) + return item->data(Updated); + if (role != Qt::DisplayRole) return QVariant(); @@ -323,65 +335,39 @@ void FolderModel::setupModelData(QSqlQuery &sqlquery, FolderItem *parent) int manga = record.indexOf("manga"); 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"); while (sqlquery.next()) { QList data; - data << sqlquery.value(name).toString(); - data << sqlquery.value(path).toString(); - data << sqlquery.value(finished).toBool(); - data << sqlquery.value(completed).toBool(); - data << sqlquery.value(manga).toBool(); - data << sqlquery.value(firstChildHash).toString(); + data << sqlquery.value(name); + data << sqlquery.value(path); + data << sqlquery.value(finished); + data << sqlquery.value(completed); + data << sqlquery.value(manga); + data << sqlquery.value(numChildren); + data << sqlquery.value(firstChildHash); + data << sqlquery.value(customImage); + data << sqlquery.value(type); + data << sqlquery.value(added); + data << sqlquery.value(updated); + auto item = new FolderItem(data); item->id = sqlquery.value(id).toULongLong(); // la inserci�n de hijos se hace de forma ordenada FolderItem *parent = items.value(sqlquery.value(parentId).toULongLong()); - // if(parent !=0) //TODO if parent==0 the parent of item was removed from the DB and delete on cascade didn't work, ERROR. parent->appendChild(item); // se a�ade el item al map, de forma que se pueda encontrar como padre en siguientes iteraciones items.insert(item->id, item); } } -void FolderModel::updateFolderModelData(QSqlQuery &sqlquery, FolderItem *parent) -{ - Q_UNUSED(parent); - - QSqlRecord record = sqlquery.record(); - - int name = record.indexOf("name"); - int path = record.indexOf("path"); - int finished = record.indexOf("finished"); - int completed = record.indexOf("completed"); - int manga = record.indexOf("manga"); - int id = record.indexOf("id"); - int parentId = record.indexOf("parentId"); - int firstChildHash = record.indexOf("firstChildHash"); - - while (sqlquery.next()) { - QList data; - - data << sqlquery.value(name).toString(); - data << sqlquery.value(path).toString(); - data << sqlquery.value(finished).toBool(); - data << sqlquery.value(completed).toBool(); - data << sqlquery.value(manga).toBool(); - data << sqlquery.value(firstChildHash).toString(); - auto item = new FolderItem(data); - - item->id = sqlquery.value(id).toULongLong(); - // la inserci�n de hijos se hace de forma ordenada - FolderItem *parent = items.value(sqlquery.value(parentId).toULongLong()); - if (parent != 0) // TODO if parent==0 the parent of item was removed from the DB and delete on cascade didn't work, ERROR. - parent->appendChild(item); - // se a�ade el item al map, de forma que se pueda encontrar como padre en siguientes iteraciones - items.insert(item->id, item); - } -} - QString FolderModel::getDatabase() { return _databasePath; @@ -406,7 +392,7 @@ void FolderModel::updateFolderCompletedStatus(const QModelIndexList &list, bool if (!isSubfolder) { Folder f = DBHelper::loadFolder(item->id, db); - f.setCompleted(status); + f.completed = status; DBHelper::update(f, db); } } @@ -430,7 +416,7 @@ void FolderModel::updateFolderFinishedStatus(const QModelIndexList &list, bool s if (!isSubfolder) { Folder f = DBHelper::loadFolder(item->id, db); - f.setFinished(status); + f.finished = status; DBHelper::update(f, db); } } @@ -439,10 +425,10 @@ void FolderModel::updateFolderFinishedStatus(const QModelIndexList &list, bool s } QSqlDatabase::removeDatabase(connectionName); - emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::FirstChildHash)); + emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::Updated)); } -void FolderModel::updateFolderManga(const QModelIndexList &list, bool manga) +void FolderModel::updateFolderType(const QModelIndexList &list, YACReader::FileType type) { QString connectionName = ""; { @@ -451,19 +437,19 @@ void FolderModel::updateFolderManga(const QModelIndexList &list, bool manga) foreach (QModelIndex mi, list) { auto item = static_cast(mi.internalPointer()); - std::function setManga; - setManga = [&setManga](FolderItem *item, bool manga) -> void { - item->setData(FolderModel::Manga, manga); + std::function setType; + setType = [&setType](FolderItem *item, YACReader::FileType type) -> void { + item->setData(FolderModel::Type, QVariant::fromValue(type)); for (auto child : item->children()) { - setManga(child, manga); + setType(child, type); } }; - setManga(item, manga); + setType(item, type); if (!isSubfolder) { - DBHelper::updateFolderTreeManga(item->id, db, manga); + DBHelper::updateFolderTreeType(item->id, db, type); } } db.commit(); @@ -471,7 +457,7 @@ void FolderModel::updateFolderManga(const QModelIndexList &list, bool manga) } QSqlDatabase::removeDatabase(connectionName); - emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::FirstChildHash)); + emit dataChanged(index(list.first().row(), FolderModel::Name), index(list.last().row(), FolderModel::Updated)); } QStringList FolderModel::getSubfoldersNames(const QModelIndex &mi) @@ -552,7 +538,13 @@ Folder FolderModel::getFolder(const QModelIndex &mi) folderItem->parent()->data(Columns::Path).toString() + "/" + name, folderItem->data(Columns::Completed).toBool(), folderItem->data(Columns::Finished).toBool(), - folderItem->data(Columns::Manga).toBool()); + folderItem->data(Columns::Manga).toBool(), + folderItem->data(Columns::NumChildren).toInt(), + folderItem->data(Columns::FirstChildHash).toString(), + folderItem->data(Columns::CustomImage).toString(), + folderItem->data(Columns::Type).value(), + folderItem->data(Columns::Added).toLongLong(), + folderItem->data(Columns::Updated).toLongLong()); return folder; } @@ -589,74 +581,6 @@ QModelIndex FolderModel::getIndexFromFolder(const Folder &folder, const QModelIn return QModelIndex(); } -void FolderModel::fetchMoreFromDB(const QModelIndex &parent) -{ - FolderItem *item; - if (parent.isValid()) - item = static_cast(parent.internalPointer()); - else - item = rootItem; - - // Remove all children - if (item->childCount() > 0) { - beginRemoveRows(parent, 0, item->childCount() - 1); - item->clearChildren(); - endRemoveRows(); - } - - QString connectionName = ""; - { - QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - - QList items; - QList nextLevelItems; - - QSqlQuery selectQuery(db); - selectQuery.prepare("select * from folder where id <> 1 and parentId = :parentId order by parentId,name"); - - items << item; - bool firstLevelUpdated = false; - while (items.size() > 0) { - nextLevelItems.clear(); - foreach (FolderItem *item, items) { - QLOG_DEBUG() << "ID " << item->id; - selectQuery.bindValue(":parentId", item->id); - - selectQuery.exec(); - - if (!firstLevelUpdated) { - // NO size support - int numResults = 0; - while (selectQuery.next()) - numResults++; - - if (!selectQuery.seek(-1)) - selectQuery.exec(); - // END no size support - - beginInsertRows(parent, 0, numResults - 1); - } - - updateFolderModelData(selectQuery, item); - - if (!firstLevelUpdated) { - endInsertRows(); - firstLevelUpdated = true; - } - - nextLevelItems << item->children(); - } - - items.clear(); - items = nextLevelItems; - } - connectionName = db.connectionName(); - } - QLOG_DEBUG() << "item->childCount()-1" << item->childCount() - 1; - - QSqlDatabase::removeDatabase(connectionName); -} - QModelIndex FolderModel::addFolderAtParent(const QString &folderName, const QModelIndex &parent) { FolderItem *parentItem; @@ -670,7 +594,9 @@ QModelIndex FolderModel::addFolderAtParent(const QString &folderName, const QMod newFolder.name = folderName; newFolder.parentId = parentItem->id; newFolder.path = parentItem->data(Columns::Path).toString() + "/" + folderName; - newFolder.setManga(parentItem->data(Columns::Manga).toBool()); + newFolder.manga = parentItem->data(Columns::Manga).toBool(); + newFolder.type = parentItem->data(Columns::Type).value(); + QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); @@ -687,7 +613,13 @@ QModelIndex FolderModel::addFolderAtParent(const QString &folderName, const QMod data << newFolder.path; data << false; // finished data << true; // completed - data << newFolder.isManga(); + data << newFolder.manga; + data << 0; // numChildren + data << QVariant(); // first child hash, new folder is empty + data << QVariant(); // custom cover + data << QVariant::fromValue(newFolder.type); + data << newFolder.added; + data << newFolder.updated; auto item = new FolderItem(data); item->id = newFolder.id; diff --git a/YACReaderLibrary/db/folder_model.h b/YACReaderLibrary/db/folder_model.h index cae4de9e..6dae78c5 100644 --- a/YACReaderLibrary/db/folder_model.h +++ b/YACReaderLibrary/db/folder_model.h @@ -66,13 +66,10 @@ public: void setupModelData(QString path); QString getDatabase(); QString getFolderPath(const QModelIndex &folder); - // QModelIndex indexFromItem(FolderItem * item, int column); - - // bool isFilterEnabled(){return filterEnabled;}; void updateFolderCompletedStatus(const QModelIndexList &list, bool status); void updateFolderFinishedStatus(const QModelIndexList &list, bool status); - void updateFolderManga(const QModelIndexList &list, bool manga); + void updateFolderType(const QModelIndexList &list, YACReader::FileType type); QStringList getSubfoldersNames(const QModelIndex &mi); FolderModel *getSubfoldersModel(const QModelIndex &mi); @@ -80,28 +77,35 @@ public: Folder getFolder(const QModelIndex &mi); QModelIndex getIndexFromFolder(const Folder &folder, const QModelIndex &parent = QModelIndex()); - void fetchMoreFromDB(const QModelIndex &parent); - QModelIndex addFolderAtParent(const QString &folderName, const QModelIndex &parent); Q_INVOKABLE QUrl getCoverUrlPathForComicHash(const QString &hash) const; enum Columns { Name = 0, - Path = 1, - Finished = 2, - Completed = 3, - Manga = 4, - FirstChildHash = 5 - }; // id INTEGER PRIMARY KEY, parentId INTEGER NOT NULL, name TEXT NOT NULL, path TEXT NOT NULL + Path, + Finished, + Completed, + Manga, // deprecated + NumChildren, + FirstChildHash, + CustomImage, + Type, // FileType + Added, + Updated, + }; enum Roles { FinishedRole = Qt::UserRole + 1, CompletedRole, IdRole, - MangaRole, + MangaRole, // deprecated CoverPathRole, - FolderName, + FolderNameRole, + NumChildrenRole, + TypeRole, + AddedRole, + UpdatedRole, }; bool isSubfolder; @@ -112,9 +116,7 @@ public slots: private: void fullSetup(QSqlQuery &sqlquery, FolderItem *parent); - void setupModelData(QSqlQuery &sqlquery, FolderItem *parent); - void updateFolderModelData(QSqlQuery &sqlquery, FolderItem *parent); FolderItem *rootItem; // el árbol QMap items; // relación entre folders diff --git a/YACReaderLibrary/db/query_parser.cpp b/YACReaderLibrary/db/query_parser.cpp index 2015fd73..f9d19e00 100644 --- a/YACReaderLibrary/db/query_parser.cpp +++ b/YACReaderLibrary/db/query_parser.cpp @@ -5,14 +5,17 @@ #include #include +#include + const std::map> QueryParser::fieldNames { - { FieldType::numeric, { "numpages", "number", "count", "arcnumber", "arccount" } }, - { FieldType::text, { "title", "volume", "storyarc", "genere", "writer", "penciller", "inker", "colorist", "letterer", "coverartist", "publisher", "format", "agerating", "synopsis", "characters", "notes" } }, - { FieldType::boolean, { "isbis", "color", "read", "manga" } }, - { FieldType::date, { "date" } }, + { FieldType::numeric, { "numpages", "count", "arccount", "alternateCount" } }, + { FieldType::text, { "number", "arcnumber", "title", "volume", "storyarc", "genere", "writer", "penciller", "inker", "colorist", "letterer", "coverartist", "publisher", "format", "agerating", "synopsis", "characters", "notes", "editor", "imprint", "teams", "locations", "series", "alternateSeries", "alternateNumber", "languageISO", "seriesGroup", "mainCharacterOrTeam", "review", "tags" } }, + { FieldType::boolean, { "isbis", "color", "read" } }, + { FieldType::date, { "date", "added", "lastTimeOpened" } }, { FieldType::filename, { "filename" } }, { FieldType::folder, { "folder" } }, - { FieldType::booleanFolder, { "completed", "finished" } }, + { FieldType::booleanFolder, { "completed", "finished" } }, // TODO_METADTA include new folder fields, e.g. type + { FieldType::enumField, { "type" } } }; int QueryParser::TreeNode::buildSqlString(std::string &sqlString, int bindPosition) const @@ -26,7 +29,7 @@ int QueryParser::TreeNode::buildSqlString(std::string &sqlString, int bindPositi } sqlString += "UPPER(c.filename) LIKE UPPER(:bindPosition" + std::to_string(bindPosition) + ") OR "; sqlString += "UPPER(f.name) LIKE UPPER(:bindPosition" + std::to_string(bindPosition) + ")) "; - } else if (isIn(fieldType(children[0].t), { FieldType::numeric, FieldType::boolean })) { + } else if (isIn(fieldType(children[0].t), { FieldType::numeric, FieldType::boolean, FieldType::enumField })) { sqlString += "ci." + children[0].t + " = :bindPosition" + std::to_string(bindPosition) + " "; } else if (fieldType(children[0].t) == FieldType::filename) { sqlString += "(UPPER(c." + children[0].t + ") LIKE UPPER(:bindPosition" + std::to_string(bindPosition) + ")) "; @@ -67,6 +70,24 @@ int QueryParser::TreeNode::bindValues(QSqlQuery &selectQuery, int bindPosition) } else { selectQuery.bindValue(QString::fromStdString(bind_string), std::stoi(value)); } + } else if ((isIn(fieldType(children[0].t), { FieldType::enumField }))) { + auto enumType = children[0].t; + auto value = toLower(children[1].t); + if (enumType == "type") { + if (value == "comic") { + selectQuery.bindValue(QString::fromStdString(bind_string), 0); + } else if (value == "manga") { + selectQuery.bindValue(QString::fromStdString(bind_string), 1); + } else if (value == "westernmanga") { + selectQuery.bindValue(QString::fromStdString(bind_string), 2); + } else if (value == "webcomic" || value == "web") { + selectQuery.bindValue(QString::fromStdString(bind_string), 3); + } else if (value == "4koma" || value == "yonkoma") { + selectQuery.bindValue(QString::fromStdString(bind_string), 4); + } + } else { + selectQuery.bindValue(QString::fromStdString(bind_string), std::stoi(children[1].t)); + } } else { selectQuery.bindValue(QString::fromStdString(bind_string), QString::fromStdString("%%" + children[1].t + "%%")); } @@ -232,6 +253,7 @@ QueryParser::TreeNode QueryParser::baseToken() return TreeNode("token", { TreeNode("all", {}), TreeNode(token(true), {}) }); } + // TODO ":" should come from the lexer as a token auto words(split(token(true), ':')); if (words.size() > 1 && fieldType(words[0].toStdString()) != FieldType::unknown) { diff --git a/YACReaderLibrary/db/query_parser.h b/YACReaderLibrary/db/query_parser.h index 69a5a315..427723c2 100644 --- a/YACReaderLibrary/db/query_parser.h +++ b/YACReaderLibrary/db/query_parser.h @@ -85,7 +85,8 @@ private: date, folder, booleanFolder, - filename }; + filename, + enumField }; static FieldType fieldType(const std::string &str); static std::string join(const QStringList &strings, const std::string &delim); diff --git a/YACReaderLibrary/db_helper.cpp b/YACReaderLibrary/db_helper.cpp index 7d2bb8f1..98f77424 100644 --- a/YACReaderLibrary/db_helper.cpp +++ b/YACReaderLibrary/db_helper.cpp @@ -66,7 +66,7 @@ QList DBHelper::getFolderComicsFromLibraryForReading(qulonglong l 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(); + return naturalSortLessThanCI(c1->info.number.toString(), c2->info.number.toString()); } else { return c2->info.number.isNull(); } @@ -637,9 +637,28 @@ void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) //-- // new 9.8 fields - "manga = :manga" + "manga = :manga," //-- - " WHERE id = :id "); + + // 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); @@ -698,7 +717,28 @@ void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) 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) @@ -718,12 +758,10 @@ void DBHelper::update(const Folder &folder, QSqlDatabase &db) QSqlQuery updateFolderInfo(db); updateFolderInfo.prepare("UPDATE folder SET " "finished = :finished, " - "completed = :completed, " - "manga = :manga " + "completed = :completed " "WHERE id = :id "); - updateFolderInfo.bindValue(":finished", folder.isFinished() ? 1 : 0); - updateFolderInfo.bindValue(":completed", folder.isCompleted() ? 1 : 0); - updateFolderInfo.bindValue(":manga", folder.isManga() ? 1 : 0); + updateFolderInfo.bindValue(":finished", folder.finished ? 1 : 0); + updateFolderInfo.bindValue(":completed", folder.completed ? 1 : 0); updateFolderInfo.bindValue(":id", folder.id); updateFolderInfo.exec(); } @@ -762,7 +800,7 @@ Folder DBHelper::updateChildrenInfo(qulonglong folderId, QSqlDatabase &db) } else { for (auto item : updatedSubfolders) { auto f = static_cast(item); - auto firstChildHash = f->getFirstChildHash(); + auto firstChildHash = f->firstChildHash; if (!firstChildHash.isEmpty()) { coverHash = firstChildHash; break; @@ -770,16 +808,16 @@ Folder DBHelper::updateChildrenInfo(qulonglong folderId, QSqlDatabase &db) } } - folder.setNumChildren(subfolders.count() + comics.count()); - folder.setFirstChildHash(coverHash); + 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.getNumChildren()); - updateFolderInfo.bindValue(":firstChildHash", folder.getFirstChildHash()); + updateFolderInfo.bindValue(":numChildren", folder.numChildren); + updateFolderInfo.bindValue(":firstChildHash", folder.firstChildHash); updateFolderInfo.bindValue(":id", folderId); updateFolderInfo.exec(); @@ -854,7 +892,7 @@ void DBHelper::updateReadingRemoteProgress(const ComicInfo &comicInfo, QSqlDatab updateComicInfo.bindValue(":read", comicInfo.read ? 1 : 0); updateComicInfo.bindValue(":currentPage", comicInfo.currentPage); updateComicInfo.bindValue(":hasBeenOpened", comicInfo.hasBeenOpened ? 1 : 0); - updateComicInfo.bindValue(":lastTimeOpened", QDateTime::currentMSecsSinceEpoch() / 1000); + updateComicInfo.bindValue(":lastTimeOpened", QDateTime::currentSecsSinceEpoch()); updateComicInfo.bindValue(":id", comicInfo.id); updateComicInfo.bindValue(":rating", comicInfo.rating); updateComicInfo.exec(); @@ -1067,7 +1105,7 @@ void DBHelper::updateFromRemoteClientWithHash(const QList &comics) updateComicInfo.bindValue(":read", info.read ? 1 : 0); updateComicInfo.bindValue(":currentPage", info.currentPage); updateComicInfo.bindValue(":hasBeenOpened", info.hasBeenOpened ? 1 : 0); - updateComicInfo.bindValue(":lastTimeOpened", QDateTime::currentMSecsSinceEpoch() / 1000); + updateComicInfo.bindValue(":lastTimeOpened", QDateTime::currentSecsSinceEpoch()); updateComicInfo.bindValue(":id", info.id); updateComicInfo.bindValue(":rating", info.rating); updateComicInfo.exec(); @@ -1195,11 +1233,12 @@ void DBHelper::updateComicsInfo(QList &comics, const QString &databaseP qulonglong DBHelper::insert(Folder *folder, QSqlDatabase &db) { QSqlQuery query(db); - query.prepare("INSERT INTO folder (parentId, name, path) " - "VALUES (:parentId, :name, :path)"); + query.prepare("INSERT INTO folder (parentId, name, path, added) " + "VALUES (:parentId, :name, :path, :added)"); query.bindValue(":parentId", folder->parentId); query.bindValue(":name", folder->name); query.bindValue(":path", folder->path); + query.bindValue(":added", QDateTime::currentSecsSinceEpoch()); query.exec(); return query.lastInsertId().toULongLong(); @@ -1207,16 +1246,21 @@ qulonglong DBHelper::insert(Folder *folder, QSqlDatabase &db) 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) " - "VALUES (:hash,:numPages,:coverSizeRatio,:originalCoverSize)"); + + comicInfoInsert.prepare("INSERT INTO comic_info (hash,numPages,coverSizeRatio,originalCoverSize,added) " + "VALUES (:hash,:numPages,:coverSizeRatio,:originalCoverSize,:added)"); 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); comicInfoInsert.exec(); comic->info.id = comicInfoInsert.lastInsertId().toULongLong(); + comic->info.added = added; comic->_hasCover = false; if (insertAllInfo) { @@ -1234,6 +1278,14 @@ qulonglong DBHelper::insert(ComicDB *comic, QSqlDatabase &db, bool insertAllInfo query.bindValue(":path", comic->path); query.exec(); + QSqlQuery updateFolder(db); + updateFolder.prepare("UPDATE folder SET " + "updated = :updated " + "WHERE id = :id "); + updateFolder.bindValue(":updated", added); + updateFolder.bindValue(":id", comic->parentId); + updateFolder.exec(); + return query.lastInsertId().toULongLong(); } @@ -1342,6 +1394,7 @@ void DBHelper::insertComicsInReadingList(const QList &comicsList, qulon db.commit(); } + // queries QList DBHelper::getFoldersFromParent(qulonglong parentId, QSqlDatabase &db, bool sort) { @@ -1356,20 +1409,33 @@ QList DBHelper::getFoldersFromParent(qulonglong parentId, QSqlDat int name = record.indexOf("name"); int path = record.indexOf("path"); + int finished = record.indexOf("finished"); + int completed = record.indexOf("completed"); + int manga = record.indexOf("manga"); 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()); - if (!selectQuery.value(numChildren).isNull() && selectQuery.value(numChildren).isValid()) - currentItem->setNumChildren(selectQuery.value(numChildren).toInt()); - currentItem->setFirstChildHash(selectQuery.value(firstChildHash).toString()); - currentItem->setCustomImage(selectQuery.value(customImage).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->manga = selectQuery.value(manga).toBool(); + currentItem->type = selectQuery.value(type).value(); + currentItem->added = selectQuery.value(added).toLongLong(); + currentItem->updated = selectQuery.value(updated).toLongLong(); int lessThan = 0; @@ -1524,21 +1590,21 @@ QList