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