1 
2 #include <QtGui>
3 #include <QtDebug>
4 #include <limits>
5 
6 #include "comic_item.h"
7 #include "comic_model.h"
8 #include "data_base_management.h"
9 #include "qnaturalsorting.h"
10 #include "comic_db.h"
11 #include "db_helper.h"
12 #include "query_parser.h"
13 #include "reading_list_model.h"
14 
15 //ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read
16 #include "QsLog.h"
17 
ComicModel(QObject * parent)18 ComicModel::ComicModel(QObject *parent)
19     : QAbstractItemModel(parent)
20 {
21     connect(this, SIGNAL(beforeReset()), this, SIGNAL(modelAboutToBeReset()));
22     connect(this, SIGNAL(reset()), this, SIGNAL(modelReset()));
23 }
24 
ComicModel(QSqlQuery & sqlquery,QObject * parent)25 ComicModel::ComicModel(QSqlQuery &sqlquery, QObject *parent)
26     : QAbstractItemModel(parent)
27 {
28     setupModelData(sqlquery);
29 }
30 
~ComicModel()31 ComicModel::~ComicModel()
32 {
33     qDeleteAll(_data);
34 }
35 
columnCount(const QModelIndex & parent) const36 int ComicModel::columnCount(const QModelIndex &parent) const
37 {
38     Q_UNUSED(parent)
39     if (_data.isEmpty())
40         return 0;
41     return _data.first()->columnCount();
42 }
43 
canDropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent) const44 bool ComicModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
45 {
46     Q_UNUSED(action);
47     Q_UNUSED(row);
48     Q_UNUSED(column);
49     Q_UNUSED(parent);
50 
51     if (!enableResorting)
52         return false;
53     return data->formats().contains(YACReader::YACReaderLibrarComiscSelectionMimeDataFormat);
54 }
55 
56 //TODO: optimize this method (seriously)
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)57 bool ComicModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
58 {
59 
60     QAbstractItemModel::dropMimeData(data, action, row, column, parent);
61     QLOG_TRACE() << ">>>>>>>>>>>>>>dropMimeData ComicModel<<<<<<<<<<<<<<<<<" << parent << row << "," << column;
62 
63     if (!data->formats().contains(YACReader::YACReaderLibrarComiscSelectionMimeDataFormat))
64         return false;
65 
66     QList<qulonglong> comicIds = YACReader::mimeDataToComicsIds(data);
67     QList<int> currentIndexes;
68     int i;
69     foreach (qulonglong id, comicIds) {
70         i = 0;
71         foreach (ComicItem *item, _data) {
72             if (item->data(Id) == id) {
73                 currentIndexes << i;
74                 break;
75             }
76             i++;
77         }
78     }
79 
80     std::sort(currentIndexes.begin(), currentIndexes.end());
81     QList<ComicItem *> resortedData;
82 
83     if (currentIndexes.contains(row)) //no resorting
84         return false;
85 
86     ComicItem *destinationItem;
87     if (row == -1 || row >= _data.length())
88         destinationItem = 0;
89     else
90         destinationItem = _data.at(row);
91 
92     QList<int> newSorting;
93 
94     i = 0;
95     foreach (ComicItem *item, _data) {
96         if (!currentIndexes.contains(i)) {
97 
98             if (item == destinationItem) {
99                 foreach (int index, currentIndexes) {
100                     resortedData << _data.at(index);
101                     newSorting << index;
102                 }
103             }
104 
105             resortedData << item;
106             newSorting << i;
107         }
108 
109         i++;
110     }
111 
112     if (destinationItem == 0) {
113         foreach (int index, currentIndexes) {
114             resortedData << _data.at(index);
115             newSorting << index;
116         }
117     }
118 
119     QLOG_TRACE() << newSorting;
120 
121     int tempRow = row;
122 
123     if (tempRow < 0)
124         tempRow = _data.count();
125 
126     foreach (qulonglong id, comicIds) {
127         int i = 0;
128         foreach (ComicItem *item, _data) {
129             if (item->data(Id) == id) {
130                 beginMoveRows(parent, i, i, parent, tempRow);
131 
132                 bool skipElement = i == tempRow || i + 1 == tempRow;
133 
134                 if (!skipElement) {
135                     if (i > tempRow)
136                         _data.move(i, tempRow);
137                     else
138                         _data.move(i, tempRow - 1);
139                 }
140 
141                 endMoveRows();
142 
143                 if (i > tempRow)
144                     tempRow++;
145 
146                 break;
147             }
148             i++;
149         }
150     }
151 
152     //TODO fix selection
153     QList<qulonglong> allComicIds;
154     foreach (ComicItem *item, _data) {
155         allComicIds << item->data(Id).toULongLong();
156     }
157     QString connectionName = "";
158     {
159         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
160         switch (mode) {
161         case Favorites:
162             DBHelper::reasignOrderToComicsInFavorites(allComicIds, db);
163             break;
164         case Label:
165             DBHelper::reasignOrderToComicsInLabel(sourceId, allComicIds, db);
166             break;
167         case ReadingList:
168             DBHelper::reasignOrderToComicsInReadingList(sourceId, allComicIds, db);
169             break;
170         default:
171             break;
172         }
173         connectionName = db.connectionName();
174     }
175     QSqlDatabase::removeDatabase(connectionName);
176 
177     //endMoveRows();
178 
179     emit resortedIndexes(newSorting);
180     int destSelectedIndex = row < 0 ? _data.length() : row;
181 
182     if (destSelectedIndex > currentIndexes.at(0))
183         emit newSelectedIndex(index(qMax(0, destSelectedIndex - 1), 0, parent));
184     else
185         emit newSelectedIndex(index(qMax(0, destSelectedIndex), 0, parent));
186 
187     return true;
188 }
189 
canBeResorted()190 bool ComicModel::canBeResorted()
191 {
192     return enableResorting;
193 }
194 
mimeData(const QModelIndexList & indexes) const195 QMimeData *ComicModel::mimeData(const QModelIndexList &indexes) const
196 {
197     //custom model data
198     //application/yacreader-comics-ids + list of ids in a QByteArray
199     QList<qulonglong> ids;
200     foreach (QModelIndex index, indexes) {
201         QLOG_DEBUG() << "dragging : " << index.data(IdRole).toULongLong();
202         ids << index.data(IdRole).toULongLong();
203     }
204 
205     QByteArray data;
206     QDataStream out(&data, QIODevice::WriteOnly);
207     out << ids; //serialize the list of identifiers
208 
209     auto mimeData = new QMimeData();
210     mimeData->setData(YACReader::YACReaderLibrarComiscSelectionMimeDataFormat, data);
211 
212     return mimeData;
213 }
214 
mimeTypes() const215 QStringList ComicModel::mimeTypes() const
216 {
217     QLOG_DEBUG() << "mimeTypes";
218     QStringList list;
219     list << YACReader::YACReaderLibrarComiscSelectionMimeDataFormat;
220     return list;
221 }
222 
roleNames() const223 QHash<int, QByteArray> ComicModel::roleNames() const
224 {
225     QHash<int, QByteArray> roles;
226 
227     roles[NumberRole] = "number";
228     roles[TitleRole] = "title";
229     roles[FileNameRole] = "file_name";
230     roles[NumPagesRole] = "num_pages";
231     roles[IdRole] = "id";
232     roles[Parent_IdRole] = "parent_id";
233     roles[PathRole] = "path";
234     roles[HashRole] = "hash";
235     roles[ReadColumnRole] = "read_column";
236     roles[IsBisRole] = "is_bis";
237     roles[CurrentPageRole] = "current_page";
238     roles[RatingRole] = "rating";
239     roles[HasBeenOpenedRole] = "has_been_opened";
240     roles[CoverPathRole] = "cover_path";
241 
242     return roles;
243 }
244 
data(const QModelIndex & index,int role) const245 QVariant ComicModel::data(const QModelIndex &index, int role) const
246 {
247     if (!index.isValid())
248         return QVariant();
249 
250     /*if (index.column() == TableModel::Rating && role == Qt::DecorationRole)
251 	{
252 		TableItem *item = static_cast<TableItem*>(index.internalPointer());
253 		return QPixmap(QString(":/images/rating%1.png").arg(item->data(index.column()).toInt()));
254 	}*/
255 
256     if (role == Qt::DecorationRole) {
257         return QVariant();
258     }
259 
260     if (role == Qt::TextAlignmentRole) {
261         switch (index.column()) //TODO obtener esto de la query
262         {
263         case ComicModel::Number:
264             return QVariant(Qt::AlignRight | Qt::AlignVCenter);
265         case ComicModel::NumPages:
266             return QVariant(Qt::AlignRight | Qt::AlignVCenter);
267         case ComicModel::Hash:
268             return QVariant(Qt::AlignRight | Qt::AlignVCenter);
269         case ComicModel::CurrentPage:
270             return QVariant(Qt::AlignRight | Qt::AlignVCenter);
271         default:
272             return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
273         }
274     }
275 
276     //TODO check here if any view is asking for TableModel::Roles
277     //these roles will be used from QML/GridView
278 
279     auto item = static_cast<ComicItem *>(index.internalPointer());
280 
281     if (role == NumberRole)
282         return item->data(Number);
283     else if (role == TitleRole)
284         return item->data(Title).isNull() ? item->data(FileName) : item->data(Title);
285     else if (role == FileNameRole)
286         return item->data(FileName);
287     else if (role == RatingRole)
288         return item->data(Rating);
289     else if (role == CoverPathRole)
290         return getCoverUrlPathForComicHash(item->data(Hash).toString());
291     else if (role == NumPagesRole)
292         return item->data(NumPages);
293     else if (role == CurrentPageRole)
294         return item->data(CurrentPage);
295     else if (role == ReadColumnRole)
296         return item->data(ReadColumn).toBool();
297     else if (role == HasBeenOpenedRole)
298         return item->data(ComicModel::HasBeenOpened);
299     else if (role == IdRole)
300         return item->data(Id);
301 
302     if (role != Qt::DisplayRole)
303         return QVariant();
304 
305     if (index.column() == ComicModel::Hash)
306         return QString::number(item->data(index.column()).toString().right(item->data(index.column()).toString().length() - 40).toInt() / 1024.0 / 1024.0, 'f', 2) + "Mb";
307     if (index.column() == ComicModel::ReadColumn)
308         return (item->data(ComicModel::CurrentPage).toInt() == item->data(ComicModel::NumPages).toInt() || item->data(ComicModel::ReadColumn).toBool()) ? QVariant(tr("yes")) : QVariant(tr("no"));
309     if (index.column() == ComicModel::CurrentPage)
310         return item->data(ComicModel::HasBeenOpened).toBool() ? item->data(index.column()) : QVariant("-");
311 
312     if (index.column() == ComicModel::Rating)
313         return QVariant();
314 
315     return item->data(index.column());
316 }
317 
flags(const QModelIndex & index) const318 Qt::ItemFlags ComicModel::flags(const QModelIndex &index) const
319 {
320     if (!index.isValid())
321         return {};
322     if (index.column() == ComicModel::Rating)
323         return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
324     return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
325 }
326 
headerData(int section,Qt::Orientation orientation,int role) const327 QVariant ComicModel::headerData(int section, Qt::Orientation orientation,
328                                 int role) const
329 {
330     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
331         switch (section) //TODO obtener esto de la query
332         {
333         case ComicModel::Number:
334             return QVariant(QString("#"));
335         case ComicModel::Title:
336             return QVariant(QString(tr("Title")));
337         case ComicModel::FileName:
338             return QVariant(QString(tr("File Name")));
339         case ComicModel::NumPages:
340             return QVariant(QString(tr("Pages")));
341         case ComicModel::Hash:
342             return QVariant(QString(tr("Size")));
343         case ComicModel::ReadColumn:
344             return QVariant(QString(tr("Read")));
345         case ComicModel::CurrentPage:
346             return QVariant(QString(tr("Current Page")));
347         case ComicModel::Rating:
348             return QVariant(QString(tr("Rating")));
349         }
350     }
351 
352     if (orientation == Qt::Horizontal && role == Qt::TextAlignmentRole) {
353         switch (section) //TODO obtener esto de la query
354         {
355         case ComicModel::Number:
356             return QVariant(Qt::AlignRight | Qt::AlignVCenter);
357         case ComicModel::NumPages:
358             return QVariant(Qt::AlignRight | Qt::AlignVCenter);
359         case ComicModel::Hash:
360             return QVariant(Qt::AlignRight | Qt::AlignVCenter);
361         case ComicModel::CurrentPage:
362             return QVariant(Qt::AlignRight | Qt::AlignVCenter);
363         default:
364             return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
365         }
366     }
367 
368     if (orientation == Qt::Vertical && role == Qt::DecorationRole) {
369         QString fileName = _data.value(section)->data(ComicModel::FileName).toString();
370         QFileInfo fi(fileName);
371         QString ext = fi.suffix();
372 
373         if (ext.compare("cbr", Qt::CaseInsensitive) == 0)
374             return QVariant(QIcon(":/images/comicRar.png"));
375         else if (ext.compare("cbz", Qt::CaseInsensitive) == 0)
376             return QVariant(QIcon(":/images/comicZip.png"));
377         else if (ext.compare("pdf", Qt::CaseInsensitive) == 0)
378             return QVariant(QIcon(":/images/pdf.png"));
379         else if (ext.compare("tar", Qt::CaseInsensitive) == 0)
380             return QVariant(QIcon(":/images/tar.png"));
381         else if (ext.compare("zip", Qt::CaseInsensitive) == 0)
382             return QVariant(QIcon(":/images/zip.png"));
383         else if (ext.compare("rar", Qt::CaseInsensitive) == 0)
384             return QVariant(QIcon(":/images/rar.png"));
385 #ifndef use_unarr
386         else if (ext.compare("7z", Qt::CaseInsensitive) == 0)
387             return QVariant(QIcon(":/images/7z.png"));
388         else if (ext.compare("cb7", Qt::CaseInsensitive) == 0)
389             return QVariant(QIcon(":/images/comic7z.png"));
390 #endif
391         else if (ext.compare("cbt", Qt::CaseInsensitive) == 0)
392             return QVariant(QIcon(":/images/comicTar.png"));
393     }
394 
395     return QVariant();
396 }
397 
index(int row,int column,const QModelIndex & parent) const398 QModelIndex ComicModel::index(int row, int column, const QModelIndex &parent)
399         const
400 {
401     if (!hasIndex(row, column, parent))
402         return QModelIndex();
403 
404     return createIndex(row, column, _data.at(row));
405 }
406 
parent(const QModelIndex & index) const407 QModelIndex ComicModel::parent(const QModelIndex &index) const
408 {
409     Q_UNUSED(index)
410     return QModelIndex();
411 }
412 
rowCount(const QModelIndex & parent) const413 int ComicModel::rowCount(const QModelIndex &parent) const
414 {
415     if (parent.column() > 0)
416         return 0;
417 
418     if (!parent.isValid())
419         return _data.count();
420 
421     return 0;
422 }
423 
getPaths(const QString & _source)424 QStringList ComicModel::getPaths(const QString &_source)
425 {
426     QStringList paths;
427     QString source = _source + "/.yacreaderlibrary/covers/";
428     QList<ComicItem *>::ConstIterator itr;
429     for (itr = _data.constBegin(); itr != _data.constEnd(); itr++) {
430         QString hash = (*itr)->data(ComicModel::Hash).toString();
431         paths << source + hash + ".jpg";
432     }
433 
434     return paths;
435 }
436 
setupFolderModelData(unsigned long long int folderId,const QString & databasePath)437 void ComicModel::setupFolderModelData(unsigned long long int folderId, const QString &databasePath)
438 {
439     enableResorting = false;
440     mode = Folder;
441     sourceId = folderId;
442 
443     beginResetModel();
444     qDeleteAll(_data);
445     _data.clear();
446 
447     _databasePath = databasePath;
448     QString connectionName = "";
449     {
450         QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath);
451         QSqlQuery selectQuery(db);
452         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 "
453                             "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
454                             "WHERE c.parentId = :parentId");
455         selectQuery.bindValue(":parentId", folderId);
456         selectQuery.exec();
457         setupModelData(selectQuery);
458         connectionName = db.connectionName();
459     }
460     QSqlDatabase::removeDatabase(connectionName);
461     endResetModel();
462 }
463 
setupLabelModelData(unsigned long long parentLabel,const QString & databasePath)464 void ComicModel::setupLabelModelData(unsigned long long parentLabel, const QString &databasePath)
465 {
466     enableResorting = true;
467     mode = Label;
468     sourceId = parentLabel;
469 
470     beginResetModel();
471     qDeleteAll(_data);
472     _data.clear();
473 
474     _databasePath = databasePath;
475     QString connectionName = "";
476     {
477         QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath);
478         QSqlQuery selectQuery(db);
479         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 "
480                             "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
481                             "INNER JOIN comic_label cl ON (c.id == cl.comic_id) "
482                             "WHERE cl.label_id = :parentLabelId "
483                             "ORDER BY cl.ordering");
484         selectQuery.bindValue(":parentLabelId", parentLabel);
485         selectQuery.exec();
486         setupModelDataForList(selectQuery);
487         connectionName = db.connectionName();
488     }
489     QSqlDatabase::removeDatabase(connectionName);
490     endResetModel();
491 }
492 
setupReadingListModelData(unsigned long long parentReadingList,const QString & databasePath)493 void ComicModel::setupReadingListModelData(unsigned long long parentReadingList, const QString &databasePath)
494 {
495     mode = ReadingList;
496     sourceId = parentReadingList;
497 
498     beginResetModel();
499     qDeleteAll(_data);
500     _data.clear();
501 
502     _databasePath = databasePath;
503     QString connectionName = "";
504     {
505         QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath);
506         QList<qulonglong> ids;
507         ids << parentReadingList;
508 
509         QSqlQuery subfolders(db);
510         subfolders.prepare("SELECT id "
511                            "FROM reading_list "
512                            "WHERE parentId = :parentId "
513                            "ORDER BY ordering ASC");
514         subfolders.bindValue(":parentId", parentReadingList);
515         subfolders.exec();
516         while (subfolders.next())
517             ids << subfolders.record().value(0).toULongLong();
518 
519         enableResorting = ids.length() == 1; //only resorting if no sublists exist
520 
521         foreach (qulonglong id, ids) {
522             QSqlQuery selectQuery(db);
523             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 "
524                                 "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
525                                 "INNER JOIN comic_reading_list crl ON (c.id == crl.comic_id) "
526                                 "WHERE crl.reading_list_id = :parentReadingList "
527                                 "ORDER BY crl.ordering");
528             selectQuery.bindValue(":parentReadingList", id);
529             selectQuery.exec();
530 
531             //TODO, extra information is needed (resorting)
532             QList<ComicItem *> tempData = _data;
533             _data.clear();
534 
535             setupModelDataForList(selectQuery);
536 
537             _data = tempData << _data;
538         }
539         connectionName = db.connectionName();
540     }
541     QSqlDatabase::removeDatabase(connectionName);
542     endResetModel();
543 }
544 
setupFavoritesModelData(const QString & databasePath)545 void ComicModel::setupFavoritesModelData(const QString &databasePath)
546 {
547     enableResorting = true;
548     mode = Favorites;
549     sourceId = -1;
550 
551     beginResetModel();
552     qDeleteAll(_data);
553     _data.clear();
554 
555     _databasePath = databasePath;
556     QString connectionName = "";
557     {
558         QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath);
559         QSqlQuery selectQuery(db);
560         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 "
561                             "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
562                             "INNER JOIN comic_default_reading_list cdrl ON (c.id == cdrl.comic_id) "
563                             "WHERE cdrl.default_reading_list_id = :parentDefaultListId "
564                             "ORDER BY cdrl.ordering");
565         selectQuery.bindValue(":parentDefaultListId", 1);
566         selectQuery.exec();
567         setupModelDataForList(selectQuery);
568         connectionName = db.connectionName();
569     }
570     QSqlDatabase::removeDatabase(connectionName);
571     endResetModel();
572 }
573 
setupReadingModelData(const QString & databasePath)574 void ComicModel::setupReadingModelData(const QString &databasePath)
575 {
576     enableResorting = false;
577     mode = Reading;
578     sourceId = -1;
579 
580     beginResetModel();
581     qDeleteAll(_data);
582     _data.clear();
583 
584     _databasePath = databasePath;
585     QString connectionName = "";
586     {
587         QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath);
588         QSqlQuery selectQuery(db);
589         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 "
590                             "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
591                             "WHERE ci.hasBeenOpened = 1 AND ci.read = 0 "
592                             "ORDER BY ci.lastTimeOpened DESC");
593         selectQuery.exec();
594 
595         setupModelDataForList(selectQuery);
596         connectionName = db.connectionName();
597     }
598     QSqlDatabase::removeDatabase(connectionName);
599     endResetModel();
600 }
601 
setModelData(QList<ComicItem * > * data,const QString & databasePath)602 void ComicModel::setModelData(QList<ComicItem *> *data, const QString &databasePath)
603 {
604     _databasePath = databasePath;
605 
606     beginResetModel();
607 
608     qDeleteAll(_data);
609 
610     _data.clear();
611 
612     _data.append(*data);
613 
614     endResetModel();
615 
616     emit searchNumResults(_data.length());
617 
618     delete data;
619 }
620 
getComicPath(QModelIndex mi)621 QString ComicModel::getComicPath(QModelIndex mi)
622 {
623     if (mi.isValid())
624         return _data.at(mi.row())->data(ComicModel::Path).toString();
625     return "";
626 }
627 
setupModelData(QSqlQuery & sqlquery)628 void ComicModel::setupModelData(QSqlQuery &sqlquery)
629 {
630     int numColumns = sqlquery.record().count();
631 
632     while (sqlquery.next()) {
633         QList<QVariant> data;
634 
635         for (int i = 0; i < numColumns; i++)
636             data << sqlquery.value(i);
637 
638         _data.append(new ComicItem(data));
639     }
640 
641     std::sort(_data.begin(), _data.end(), [](const ComicItem *c1, const ComicItem *c2) {
642         if (c1->data(ComicModel::Number).isNull() && c2->data(ComicModel::Number).isNull()) {
643             return naturalSortLessThanCI(c1->data(ComicModel::FileName).toString(), c2->data(ComicModel::FileName).toString());
644         } else {
645             if (c1->data(ComicModel::Number).isNull() == false && c2->data(ComicModel::Number).isNull() == false) {
646                 return c1->data(ComicModel::Number).toInt() < c2->data(ComicModel::Number).toInt();
647             } else {
648                 return c2->data(ComicModel::Number).isNull();
649             }
650         }
651     });
652 }
653 
654 //comics are sorted by "ordering", the sorting is done in the sql query
setupModelDataForList(QSqlQuery & sqlquery)655 void ComicModel::setupModelDataForList(QSqlQuery &sqlquery)
656 {
657     int numColumns = sqlquery.record().count();
658 
659     while (sqlquery.next()) {
660         QList<QVariant> data;
661         for (int i = 0; i < numColumns; i++)
662             data << sqlquery.value(i);
663 
664         _data.append(new ComicItem(data));
665     }
666 }
667 
getComic(const QModelIndex & mi)668 ComicDB ComicModel::getComic(const QModelIndex &mi)
669 {
670     ComicDB c;
671     QString connectionName = "";
672     {
673         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
674         c = DBHelper::loadComic(_data.at(mi.row())->data(ComicModel::Id).toULongLong(), db);
675         connectionName = db.connectionName();
676     }
677     QSqlDatabase::removeDatabase(connectionName);
678 
679     return c;
680 }
681 
_getComic(const QModelIndex & mi)682 ComicDB ComicModel::_getComic(const QModelIndex &mi)
683 {
684     ComicDB c;
685     QString connectionName = "";
686     {
687         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
688         c = DBHelper::loadComic(_data.at(mi.row())->data(ComicModel::Id).toULongLong(), db);
689         connectionName = db.connectionName();
690     }
691     QSqlDatabase::removeDatabase(connectionName);
692 
693     return c;
694 }
695 
getReadList()696 QVector<YACReaderComicReadStatus> ComicModel::getReadList()
697 {
698     int numComics = _data.count();
699     QVector<YACReaderComicReadStatus> readList(numComics);
700     for (int i = 0; i < numComics; i++) {
701         if (_data.value(i)->data(ComicModel::ReadColumn).toBool())
702             readList[i] = YACReader::Read;
703         else if (_data.value(i)->data(ComicModel::CurrentPage).toInt() == _data.value(i)->data(ComicModel::NumPages).toInt())
704             readList[i] = YACReader::Read;
705         else if (_data.value(i)->data(ComicModel::HasBeenOpened).toBool())
706             readList[i] = YACReader::Opened;
707         else
708             readList[i] = YACReader::Unread;
709     }
710     return readList;
711 }
712 //TODO untested, this method is no longer used
setAllComicsRead(YACReaderComicReadStatus read)713 QVector<YACReaderComicReadStatus> ComicModel::setAllComicsRead(YACReaderComicReadStatus read)
714 {
715     return setComicsRead(persistentIndexList(), read);
716 }
717 
getAllComics()718 QList<ComicDB> ComicModel::getAllComics()
719 {
720     QList<ComicDB> comics;
721     QString connectionName = "";
722     {
723         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
724         db.transaction();
725 
726         int numComics = _data.count();
727         for (int i = 0; i < numComics; i++) {
728             comics.append(DBHelper::loadComic(_data.value(i)->data(ComicModel::Id).toULongLong(), db));
729         }
730 
731         db.commit();
732         connectionName = db.connectionName();
733     }
734     QSqlDatabase::removeDatabase(connectionName);
735 
736     return comics;
737 }
738 
getComics(QList<QModelIndex> list)739 QList<ComicDB> ComicModel::getComics(QList<QModelIndex> list)
740 {
741     QList<ComicDB> comics;
742     for (auto itr = list.constBegin(); itr != list.constEnd(); itr++) {
743         comics.append(_getComic(*itr));
744     }
745     return comics;
746 }
747 //TODO
setComicsRead(QList<QModelIndex> list,YACReaderComicReadStatus read)748 QVector<YACReaderComicReadStatus> ComicModel::setComicsRead(QList<QModelIndex> list, YACReaderComicReadStatus read)
749 {
750     QString connectionName = "";
751     {
752         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
753         db.transaction();
754         foreach (QModelIndex mi, list) {
755             if (read == YACReader::Read) {
756                 _data.value(mi.row())->setData(ComicModel::ReadColumn, QVariant(true));
757                 ComicDB c = DBHelper::loadComic(_data.value(mi.row())->data(ComicModel::Id).toULongLong(), db);
758                 c.info.read = true;
759                 DBHelper::update(&(c.info), db);
760             }
761             if (read == YACReader::Unread) {
762                 _data.value(mi.row())->setData(ComicModel::ReadColumn, QVariant(false));
763                 _data.value(mi.row())->setData(ComicModel::CurrentPage, QVariant(1));
764                 _data.value(mi.row())->setData(ComicModel::HasBeenOpened, QVariant(false));
765                 ComicDB c = DBHelper::loadComic(_data.value(mi.row())->data(ComicModel::Id).toULongLong(), db);
766                 c.info.read = false;
767                 c.info.currentPage = 1;
768                 c.info.hasBeenOpened = false;
769                 c.info.lastTimeOpened.setValue(QVariant());
770                 DBHelper::update(&(c.info), db);
771             }
772         }
773         db.commit();
774         connectionName = db.connectionName();
775     }
776     QSqlDatabase::removeDatabase(connectionName);
777 
778     emit dataChanged(index(list.first().row(), ComicModel::ReadColumn), index(list.last().row(), ComicModel::HasBeenOpened), QVector<int>() << ReadColumnRole << CurrentPageRole << HasBeenOpenedRole);
779 
780     return getReadList();
781 }
782 
setComicsManga(QList<QModelIndex> list,bool isManga)783 void ComicModel::setComicsManga(QList<QModelIndex> list, bool isManga)
784 {
785     QString connectionName = "";
786     {
787         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
788         db.transaction();
789         foreach (QModelIndex mi, list) {
790             ComicDB c = DBHelper::loadComic(_data.value(mi.row())->data(ComicModel::Id).toULongLong(), db);
791             c.info.manga = isManga;
792             DBHelper::update(&(c.info), db);
793         }
794         db.commit();
795         connectionName = db.connectionName();
796     }
797     QSqlDatabase::removeDatabase(connectionName);
798 }
799 
asignNumbers(QList<QModelIndex> list,int startingNumber)800 qint64 ComicModel::asignNumbers(QList<QModelIndex> list, int startingNumber)
801 {
802     qint64 idFirst;
803     QString connectionName = "";
804     {
805         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
806         db.transaction();
807         idFirst = _data.value(list[0].row())->data(ComicModel::Id).toULongLong();
808         int i = 0;
809         foreach (QModelIndex mi, list) {
810             ComicDB c = DBHelper::loadComic(_data.value(mi.row())->data(ComicModel::Id).toULongLong(), db);
811             c.info.number = startingNumber + i;
812             c.info.edited = true;
813             DBHelper::update(&(c.info), db);
814             i++;
815         }
816         db.commit();
817         connectionName = db.connectionName();
818     }
819 
820     QSqlDatabase::removeDatabase(connectionName);
821 
822     return idFirst;
823 }
getIndexFromId(quint64 id)824 QModelIndex ComicModel::getIndexFromId(quint64 id)
825 {
826     QList<ComicItem *>::ConstIterator itr;
827     int i = 0;
828     for (itr = _data.constBegin(); itr != _data.constEnd(); itr++) {
829         if ((*itr)->data(ComicModel::Id).toULongLong() == id)
830             break;
831         i++;
832     }
833 
834     return index(i, 0);
835 }
836 
837 //TODO completely inefficiently
getIndexesFromIds(const QList<qulonglong> & comicIds)838 QList<QModelIndex> ComicModel::getIndexesFromIds(const QList<qulonglong> &comicIds)
839 {
840     QList<QModelIndex> comicsIndexes;
841 
842     foreach (qulonglong id, comicIds)
843         comicsIndexes << getIndexFromId(id);
844 
845     return comicsIndexes;
846 }
847 
startTransaction()848 void ComicModel::startTransaction()
849 {
850     auto dbTransaction = DataBaseManagement::loadDatabase(_databasePath);
851     _databaseConnection = dbTransaction.connectionName();
852     dbTransaction.transaction();
853 }
854 
finishTransaction()855 void ComicModel::finishTransaction()
856 {
857     {
858         QSqlDatabase::database(_databaseConnection).commit();
859     }
860     QSqlDatabase::removeDatabase(_databaseConnection);
861 }
862 
removeInTransaction(int row)863 void ComicModel::removeInTransaction(int row)
864 {
865     auto dbTransaction = QSqlDatabase::database(_databaseConnection);
866     ComicDB c = DBHelper::loadComic(_data.at(row)->data(ComicModel::Id).toULongLong(), dbTransaction);
867 
868     DBHelper::removeFromDB(&c, dbTransaction);
869     beginRemoveRows(QModelIndex(), row, row);
870     removeRow(row);
871     delete _data.at(row);
872     _data.removeAt(row);
873 
874     endRemoveRows();
875 }
876 
remove(int row)877 void ComicModel::remove(int row)
878 {
879     removeInTransaction(row);
880 }
881 
reload(const ComicDB & comic)882 void ComicModel::reload(const ComicDB &comic)
883 {
884     int row = 0;
885     bool found = false;
886     foreach (ComicItem *item, _data) {
887         if (item->data(ComicModel::Id).toULongLong() == comic.id) {
888             found = true;
889             item->setData(ComicModel::ReadColumn, comic.info.read);
890             item->setData(ComicModel::CurrentPage, comic.info.currentPage);
891             item->setData(ComicModel::HasBeenOpened, true);
892             break;
893         }
894         row++;
895     }
896     if (found)
897         emit dataChanged(index(row, ReadColumn), index(row, HasBeenOpened), QVector<int>() << ReadColumnRole << CurrentPageRole << HasBeenOpenedRole);
898 }
899 
resetComicRating(const QModelIndex & mi)900 void ComicModel::resetComicRating(const QModelIndex &mi)
901 {
902     ComicDB comic = getComic(mi);
903     QString connectionName = "";
904     {
905         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
906 
907         comic.info.rating = 0;
908         _data[mi.row()]->setData(ComicModel::Rating, 0);
909         DBHelper::update(&(comic.info), db);
910 
911         emit dataChanged(mi, mi);
912         connectionName = db.connectionName();
913     }
914     QSqlDatabase::removeDatabase(connectionName);
915 }
916 
getCoverUrlPathForComicHash(const QString & hash) const917 QUrl ComicModel::getCoverUrlPathForComicHash(const QString &hash) const
918 {
919     return QUrl("file:" + _databasePath + "/covers/" + hash + ".jpg");
920 }
921 
addComicsToFavorites(const QList<qulonglong> & comicIds)922 void ComicModel::addComicsToFavorites(const QList<qulonglong> &comicIds)
923 {
924     addComicsToFavorites(getIndexesFromIds(comicIds));
925 }
926 
addComicsToFavorites(const QList<QModelIndex> & comicsList)927 void ComicModel::addComicsToFavorites(const QList<QModelIndex> &comicsList)
928 {
929     QList<ComicDB> comics = getComics(comicsList);
930     QString connectionName = "";
931     {
932         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
933 
934         DBHelper::insertComicsInFavorites(comics, db);
935         connectionName = db.connectionName();
936     }
937     QSqlDatabase::removeDatabase(connectionName);
938 }
939 
addComicsToLabel(const QList<qulonglong> & comicIds,qulonglong labelId)940 void ComicModel::addComicsToLabel(const QList<qulonglong> &comicIds, qulonglong labelId)
941 {
942     addComicsToLabel(getIndexesFromIds(comicIds), labelId);
943 }
944 
addComicsToLabel(const QList<QModelIndex> & comicsList,qulonglong labelId)945 void ComicModel::addComicsToLabel(const QList<QModelIndex> &comicsList, qulonglong labelId)
946 {
947     QList<ComicDB> comics = getComics(comicsList);
948     QString connectionName = "";
949     {
950         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
951 
952         DBHelper::insertComicsInLabel(comics, labelId, db);
953         connectionName = db.connectionName();
954     }
955     QSqlDatabase::removeDatabase(connectionName);
956 }
957 
addComicsToReadingList(const QList<qulonglong> & comicIds,qulonglong readingListId)958 void ComicModel::addComicsToReadingList(const QList<qulonglong> &comicIds, qulonglong readingListId)
959 {
960     addComicsToReadingList(getIndexesFromIds(comicIds), readingListId);
961 }
962 
addComicsToReadingList(const QList<QModelIndex> & comicsList,qulonglong readingListId)963 void ComicModel::addComicsToReadingList(const QList<QModelIndex> &comicsList, qulonglong readingListId)
964 {
965     QList<ComicDB> comics = getComics(comicsList);
966     QString connectionName = "";
967     {
968         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
969 
970         DBHelper::insertComicsInReadingList(comics, readingListId, db);
971         connectionName = db.connectionName();
972     }
973     QSqlDatabase::removeDatabase(connectionName);
974 }
975 
deleteComicsFromFavorites(const QList<QModelIndex> & comicsList)976 void ComicModel::deleteComicsFromFavorites(const QList<QModelIndex> &comicsList)
977 {
978     QList<ComicDB> comics = getComics(comicsList);
979     QString connectionName = "";
980     {
981         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
982 
983         DBHelper::deleteComicsFromFavorites(comics, db);
984         connectionName = db.connectionName();
985     }
986     QSqlDatabase::removeDatabase(connectionName);
987 
988     if (mode == Favorites)
989         deleteComicsFromModel(comicsList);
990 }
991 
deleteComicsFromReading(const QList<QModelIndex> & comicsList)992 void ComicModel::deleteComicsFromReading(const QList<QModelIndex> &comicsList)
993 {
994     QList<ComicDB> comics = getComics(comicsList);
995     QString connectionName = "";
996     {
997         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
998 
999         DBHelper::deleteComicsFromReading(comics, db);
1000         connectionName = db.connectionName();
1001     }
1002     QSqlDatabase::removeDatabase(connectionName);
1003 
1004     if (mode == Reading)
1005         deleteComicsFromModel(comicsList);
1006 }
1007 
deleteComicsFromSpecialList(const QList<QModelIndex> & comicsList,qulonglong specialListId)1008 void ComicModel::deleteComicsFromSpecialList(const QList<QModelIndex> &comicsList, qulonglong specialListId)
1009 {
1010     auto type = (ReadingListModel::TypeSpecialList)specialListId;
1011 
1012     switch (type) {
1013     case ReadingListModel::TypeSpecialList::Reading:
1014         deleteComicsFromReading(comicsList);
1015         break;
1016     case ReadingListModel::TypeSpecialList::Favorites:
1017         deleteComicsFromFavorites(comicsList);
1018         break;
1019     }
1020 }
1021 
deleteComicsFromLabel(const QList<QModelIndex> & comicsList,qulonglong labelId)1022 void ComicModel::deleteComicsFromLabel(const QList<QModelIndex> &comicsList, qulonglong labelId)
1023 {
1024     QList<ComicDB> comics = getComics(comicsList);
1025     QString connectionName = "";
1026     {
1027         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
1028 
1029         DBHelper::deleteComicsFromLabel(comics, labelId, db);
1030         connectionName = db.connectionName();
1031     }
1032     QSqlDatabase::removeDatabase(connectionName);
1033 
1034     deleteComicsFromModel(comicsList);
1035 }
1036 
deleteComicsFromReadingList(const QList<QModelIndex> & comicsList,qulonglong readingListId)1037 void ComicModel::deleteComicsFromReadingList(const QList<QModelIndex> &comicsList, qulonglong readingListId)
1038 {
1039     QList<ComicDB> comics = getComics(comicsList);
1040     QString connectionName = "";
1041     {
1042         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
1043 
1044         DBHelper::deleteComicsFromReadingList(comics, readingListId, db);
1045         connectionName = db.connectionName();
1046     }
1047     QSqlDatabase::removeDatabase(connectionName);
1048 
1049     deleteComicsFromModel(comicsList);
1050 }
1051 
deleteComicsFromModel(const QList<QModelIndex> & comicsList)1052 void ComicModel::deleteComicsFromModel(const QList<QModelIndex> &comicsList)
1053 {
1054     QListIterator<QModelIndex> it(comicsList);
1055     it.toBack();
1056     while (it.hasPrevious()) {
1057         int row = it.previous().row();
1058         beginRemoveRows(QModelIndex(), row, row);
1059         _data.removeAt(row);
1060         endRemoveRows();
1061     }
1062 
1063     if (_data.isEmpty())
1064         emit isEmpty();
1065 }
1066 
isFavorite(const QModelIndex & index)1067 bool ComicModel::isFavorite(const QModelIndex &index)
1068 {
1069     bool isFavorite;
1070     QString connectionName = "";
1071     {
1072         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
1073 
1074         isFavorite = DBHelper::isFavoriteComic(_data[index.row()]->data(Id).toLongLong(), db);
1075         connectionName = db.connectionName();
1076     }
1077     QSqlDatabase::removeDatabase(connectionName);
1078 
1079     return isFavorite;
1080 }
1081 
updateRating(int rating,QModelIndex mi)1082 void ComicModel::updateRating(int rating, QModelIndex mi)
1083 {
1084     ComicDB comic = getComic(mi);
1085     QString connectionName = "";
1086     {
1087         QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
1088         //TODO optimize update
1089 
1090         comic.info.rating = rating;
1091         _data[mi.row()]->setData(ComicModel::Rating, rating);
1092         DBHelper::update(&(comic.info), db);
1093 
1094         emit dataChanged(mi, mi);
1095         connectionName = db.connectionName();
1096     }
1097     QSqlDatabase::removeDatabase(connectionName);
1098 }
1099