1 /*
2     SPDX-FileCopyrightText: 2007 Joris Guisson <joris.guisson@gmail.com>
3     SPDX-FileCopyrightText: 2007 Ivan Vasic <ivasic@gmail.com>
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 #include "queuemanagermodel.h"
7 
8 #include <QApplication>
9 #include <QColor>
10 #include <QIcon>
11 #include <QLocale>
12 #include <QMimeData>
13 
14 #include <KLocalizedString>
15 
16 #include "settings.h"
17 #include <interfaces/torrentinterface.h>
18 #include <torrent/queuemanager.h>
19 #include <util/functions.h>
20 #include <util/log.h>
21 
22 using namespace bt;
23 
24 namespace kt
25 {
QueueManagerModel(QueueManager * qman,QObject * parent)26 QueueManagerModel::QueueManagerModel(QueueManager *qman, QObject *parent)
27     : QAbstractTableModel(parent)
28     , qman(qman)
29     , show_uploads(true)
30     , show_downloads(true)
31     , show_not_queud(true)
32 {
33     connect(qman, &QueueManager::queueOrdered, this, &QueueManagerModel::onQueueOrdered);
34     for (bt::TorrentInterface *tc : qAsConst(*qman)) {
35         connect(tc, &bt::TorrentInterface::statusChanged, this, &QueueManagerModel::onTorrentStatusChanged);
36 
37         if (visible(tc)) {
38             Item item = {tc, 0};
39             queue.append(item);
40         }
41     }
42 
43     // dumpQueue();
44 }
45 
~QueueManagerModel()46 QueueManagerModel::~QueueManagerModel()
47 {
48 }
49 
onQueueOrdered()50 void QueueManagerModel::onQueueOrdered()
51 {
52     updateQueue();
53 }
54 
softReset()55 void QueueManagerModel::softReset()
56 {
57     Q_EMIT dataChanged(index(0, 0), index(queue.count() - 1, columnCount(QModelIndex()) - 1));
58 }
59 
updateQueue()60 void QueueManagerModel::updateQueue()
61 {
62     int count = queue.count();
63     queue.clear();
64 
65     for (bt::TorrentInterface *tc : qAsConst(*qman)) {
66         if (visible(tc)) {
67             Item item = {tc, 0};
68             queue.append(item);
69         }
70     }
71 
72     if (count == queue.count()) {
73         softReset();
74     } else if (queue.count() > count) {
75         insertRows(0, queue.count() - count, QModelIndex());
76         softReset();
77     } else { // queue.count() < count)
78         removeRows(0, count - queue.count(), QModelIndex());
79         softReset();
80     }
81 }
82 
setShowDownloads(bool on)83 void QueueManagerModel::setShowDownloads(bool on)
84 {
85     show_downloads = on;
86     updateQueue();
87 }
88 
setShowUploads(bool on)89 void QueueManagerModel::setShowUploads(bool on)
90 {
91     show_uploads = on;
92     updateQueue();
93 }
94 
setShowNotQueued(bool on)95 void QueueManagerModel::setShowNotQueued(bool on)
96 {
97     show_not_queud = on;
98     updateQueue();
99 }
100 
onTorrentAdded(bt::TorrentInterface * tc)101 void QueueManagerModel::onTorrentAdded(bt::TorrentInterface *tc)
102 {
103     connect(tc, &bt::TorrentInterface::statusChanged, this, &QueueManagerModel::onTorrentStatusChanged);
104 }
105 
onTorrentRemoved(bt::TorrentInterface * tc)106 void QueueManagerModel::onTorrentRemoved(bt::TorrentInterface *tc)
107 {
108     disconnect(tc, &bt::TorrentInterface::statusChanged, this, &QueueManagerModel::onTorrentStatusChanged);
109     int r = 0;
110     bool found = false;
111 
112     for (const auto &i : qAsConst(queue)) {
113         if (tc == i.tc) {
114             found = true;
115             break;
116         }
117         r++;
118     }
119 
120     if (found) {
121         queue.removeAt(r);
122         removeRow(r);
123     }
124 }
125 
onTorrentStatusChanged(bt::TorrentInterface * tc)126 void QueueManagerModel::onTorrentStatusChanged(bt::TorrentInterface *tc)
127 {
128     int r = 0;
129     bool found = false;
130     for (const Item &i : qAsConst(queue)) {
131         if (tc == i.tc) {
132             found = true;
133             break;
134         }
135 
136         r++;
137     }
138 
139     if (found) {
140         if (!visible(tc)) {
141             queue.removeAt(r);
142             removeRow(r);
143         } else {
144             QModelIndex idx = index(r, 2);
145             Q_EMIT dataChanged(idx, idx);
146         }
147         return;
148     }
149 
150     if (visible(tc)) {
151         updateQueue();
152     }
153 }
154 
rowCount(const QModelIndex & parent) const155 int QueueManagerModel::rowCount(const QModelIndex &parent) const
156 {
157     if (parent.isValid())
158         return 0;
159     else
160         return queue.count();
161 }
162 
columnCount(const QModelIndex & parent) const163 int QueueManagerModel::columnCount(const QModelIndex &parent) const
164 {
165     if (parent.isValid())
166         return 0;
167     else
168         return 4;
169 }
170 
headerData(int section,Qt::Orientation orientation,int role) const171 QVariant QueueManagerModel::headerData(int section, Qt::Orientation orientation, int role) const
172 {
173     if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
174         return QVariant();
175 
176     switch (section) {
177     case 0:
178         return i18n("Order");
179     case 1:
180         return i18n("Name");
181     case 2:
182         return i18n("Status");
183     case 3:
184         return i18n("Time Stalled");
185     case 4:
186         return i18n("Priority");
187     default:
188         return QVariant();
189     }
190 }
191 
data(const QModelIndex & index,int role) const192 QVariant QueueManagerModel::data(const QModelIndex &index, int role) const
193 {
194     if (!index.isValid() || index.row() >= queue.count() || index.row() < 0)
195         return QVariant();
196 
197     const bt::TorrentInterface *tc = queue.at(index.row()).tc;
198     if (role == Qt::ForegroundRole) {
199         if (index.column() == 2) {
200             if (tc->getStats().running)
201                 return QColor(40, 205, 40); // green
202             else if (tc->getStats().status == bt::QUEUED)
203                 return QColor(255, 174, 0); // yellow
204             else
205                 return QVariant();
206         }
207         return QVariant();
208     } else if (role == Qt::DisplayRole) {
209         switch (index.column()) {
210         case 0:
211             return index.row() + 1;
212         case 1:
213             return tc->getDisplayName();
214         case 2:
215             if (tc->getStats().running)
216                 return i18n("Running");
217             else if (tc->getStats().status == bt::QUEUED)
218                 return i18n("Queued");
219             else
220                 return i18n("Not queued");
221             break;
222         case 3: {
223             if (!tc->getStats().running)
224                 return QVariant();
225 
226             Int64 stalled_time = queue.at(index.row()).stalled_time;
227             if (stalled_time >= 1)
228                 return i18n("%1", DurationToString(stalled_time));
229             else
230                 return QVariant();
231         } break;
232         case 4:
233             return tc->getPriority();
234         default:
235             return QVariant();
236         }
237     } else if (role == Qt::ToolTipRole && index.column() == 0) {
238         return i18n("Order of a torrent in the queue.\nUse drag and drop or the move up and down buttons on the right to change the order.");
239     } else if (role == Qt::DecorationRole && index.column() == 1) {
240         if (!tc->getStats().completed)
241             return QIcon::fromTheme(QStringLiteral("arrow-down"));
242         else
243             return QIcon::fromTheme(QStringLiteral("arrow-up"));
244     } else if (role == Qt::FontRole && !search_text.isEmpty()) {
245         QFont f = QApplication::font();
246         if (tc->getDisplayName().contains(search_text, Qt::CaseInsensitive))
247             f.setBold(true);
248 
249         return f;
250     }
251 
252     return QVariant();
253 }
254 
flags(const QModelIndex & index) const255 Qt::ItemFlags QueueManagerModel::flags(const QModelIndex &index) const
256 {
257     Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index);
258 
259     if (index.isValid())
260         return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
261     else
262         return Qt::ItemIsDropEnabled | defaultFlags;
263 }
264 
supportedDropActions() const265 Qt::DropActions QueueManagerModel::supportedDropActions() const
266 {
267     return Qt::CopyAction | Qt::MoveAction;
268 }
269 
mimeTypes() const270 QStringList QueueManagerModel::mimeTypes() const
271 {
272     QStringList types;
273     types << QStringLiteral("application/vnd.text.list");
274     return types;
275 }
276 
mimeData(const QModelIndexList & indexes) const277 QMimeData *QueueManagerModel::mimeData(const QModelIndexList &indexes) const
278 {
279     QMimeData *mimeData = new QMimeData();
280 
281     dragged_items.clear();
282 
283     for (const QModelIndex &index : indexes) {
284         if (index.isValid() && !dragged_items.contains(index.row()))
285             dragged_items.append(index.row());
286     }
287 
288     mimeData->setData(QStringLiteral("application/vnd.text.list"), QByteArrayLiteral("stuff"));
289     return mimeData;
290 }
291 
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)292 bool QueueManagerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
293 {
294     Q_UNUSED(column);
295     if (action == Qt::IgnoreAction)
296         return true;
297 
298     if (!data->hasFormat(QStringLiteral("application/vnd.text.list")))
299         return false;
300 
301     int begin_row = row;
302     if (row != -1) {
303         begin_row = row;
304     } else if (parent.isValid()) {
305         begin_row = parent.row();
306     } else {
307         moveBottom(dragged_items.front(), dragged_items.count());
308         return true;
309     }
310 
311     int from = dragged_items.front();
312     int count = dragged_items.count();
313     if (from < begin_row) {
314         while (from < begin_row) {
315             for (int i = count - 1; i >= 0; i--)
316                 swapItems(from + i, from + i + 1);
317             from++;
318         }
319     } else {
320         while (from > begin_row) {
321             for (int i = 0; i < count; i++)
322                 swapItems(from + i, from + i - 1);
323             from--;
324         }
325     }
326 
327     updatePriorities();
328     // reorder the queue
329     qman->orderQueue();
330     endResetModel();
331     return true;
332 }
333 
removeRows(int row,int count,const QModelIndex & parent)334 bool QueueManagerModel::removeRows(int row, int count, const QModelIndex &parent)
335 {
336     Q_UNUSED(parent);
337     beginInsertRows(QModelIndex(), row, row + count - 1);
338     endInsertRows();
339     return true;
340 }
341 
insertRows(int row,int count,const QModelIndex & parent)342 bool QueueManagerModel::insertRows(int row, int count, const QModelIndex &parent)
343 {
344     Q_UNUSED(parent);
345     beginInsertRows(QModelIndex(), row, row + count - 1);
346     endInsertRows();
347     return true;
348 }
349 
moveUp(int row,int count)350 void QueueManagerModel::moveUp(int row, int count)
351 {
352     if (row <= 0 || row > qman->count())
353         return;
354 
355     for (int i = 0; i < count; i++) {
356         swapItems(row + i, row + i - 1);
357     }
358 
359     updatePriorities();
360     // dumpQueue();
361     // reorder the queue
362     qman->orderQueue();
363     endResetModel();
364 }
365 
moveDown(int row,int count)366 void QueueManagerModel::moveDown(int row, int count)
367 {
368     if (row < 0 || row >= qman->count() - 1)
369         return;
370 
371     for (int i = count - 1; i >= 0; i--) {
372         swapItems(row + i, row + i + 1);
373     }
374 
375     updatePriorities();
376     // dumpQueue();
377     // reorder the queue
378     qman->orderQueue();
379     endResetModel();
380 }
381 
moveTop(int row,int count)382 void QueueManagerModel::moveTop(int row, int count)
383 {
384     if (row < 0 || row >= qman->count())
385         return;
386 
387     while (row > 0) {
388         for (int i = 0; i < count; i++) {
389             swapItems(row + i, row + i - 1);
390         }
391         row--;
392     }
393 
394     updatePriorities();
395     // dumpQueue();
396     // reorder the queue
397     qman->orderQueue();
398     endResetModel();
399 }
400 
moveBottom(int row,int count)401 void QueueManagerModel::moveBottom(int row, int count)
402 {
403     if (row < 0 || row >= qman->count())
404         return;
405 
406     while (row + count < queue.count()) {
407         for (int i = count - 1; i >= 0; i--) {
408             swapItems(row + i, row + i + 1);
409         }
410         row++;
411     }
412 
413     updatePriorities();
414     // dumpQueue();
415     // reorder the queue
416     qman->orderQueue();
417     endResetModel();
418 }
419 
dumpQueue()420 void QueueManagerModel::dumpQueue()
421 {
422     int idx = 0;
423     for (const Item &item : qAsConst(queue)) {
424         Out(SYS_GEN | LOG_DEBUG) << "Item " << idx << ": " << item.tc->getDisplayName() << " " << item.tc->getPriority() << endl;
425         idx++;
426     }
427 }
428 
updatePriorities()429 void QueueManagerModel::updatePriorities()
430 {
431     int idx = queue.size();
432     for (const Item &i : qAsConst(queue))
433         i.tc->setPriority(idx--);
434 }
435 
update()436 void QueueManagerModel::update()
437 {
438     TimeStamp now = bt::CurrentTime();
439     int r = 0;
440     for (Item &i : queue) {
441         bt::TorrentInterface *tc = i.tc;
442         if (!tc->getStats().running) {
443             if (i.stalled_time != -1) {
444                 i.stalled_time = -1;
445                 Q_EMIT dataChanged(createIndex(r, 3), createIndex(r, 3));
446             }
447         } else {
448             Int64 stalled_time = 0;
449             if (tc->getStats().completed)
450                 stalled_time = (now - tc->getStats().last_upload_activity_time) / 1000;
451             else
452                 stalled_time = (now - tc->getStats().last_download_activity_time) / 1000;
453 
454             if (i.stalled_time != stalled_time) {
455                 i.stalled_time = stalled_time;
456                 Q_EMIT dataChanged(createIndex(r, 3), createIndex(r, 3));
457             }
458         }
459         r++;
460     }
461 }
462 
find(const QString & text)463 QModelIndex QueueManagerModel::find(const QString &text)
464 {
465     search_text = text;
466     if (text.isEmpty()) {
467         endResetModel();
468         return QModelIndex();
469     }
470 
471     int idx = 0;
472     for (const Item &i : qAsConst(queue)) {
473         bt::TorrentInterface *tc = i.tc;
474         if (tc->getDisplayName().contains(text, Qt::CaseInsensitive)) {
475             endResetModel();
476             return index(idx, 0);
477         }
478         idx++;
479     }
480 
481     endResetModel();
482     return QModelIndex();
483 }
484 
visible(const bt::TorrentInterface * tc)485 bool QueueManagerModel::visible(const bt::TorrentInterface *tc)
486 {
487     if (!show_uploads && tc->getStats().completed)
488         return false;
489 
490     if (!show_downloads && !tc->getStats().completed)
491         return false;
492 
493     if (!show_not_queud && !tc->isAllowedToStart())
494         return false;
495 
496     return true;
497 }
498 
swapItems(int a,int b)499 void QueueManagerModel::swapItems(int a, int b)
500 {
501     if (a < 0 || a >= queue.count() || b < 0 || b >= queue.count())
502         return;
503 
504     queue.swapItemsAt(a, b);
505 }
506 
507 }
508