1 /*
2 * This file Copyright (C) 2009-2015 Mnemosyne LLC
3 *
4 * It may be used under the GNU GPL versions 2 or 3
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 */
8
9 #include <algorithm>
10 #include <iostream>
11 #include <utility>
12
13 #include <libtransmission/transmission.h>
14 #include <libtransmission/variant.h>
15
16 #include "Speed.h"
17 #include "Torrent.h"
18 #include "TorrentDelegate.h"
19 #include "TorrentModel.h"
20
21 /***
22 ****
23 ***/
24
25 namespace
26 {
27
28 struct TorrentIdLessThan
29 {
operator ()__anon15b5ca8b0111::TorrentIdLessThan30 bool operator ()(Torrent* left, Torrent* right) const
31 {
32 return left->id() < right->id();
33 }
34
operator ()__anon15b5ca8b0111::TorrentIdLessThan35 bool operator ()(int leftId, Torrent* right) const
36 {
37 return leftId < right->id();
38 }
39
operator ()__anon15b5ca8b0111::TorrentIdLessThan40 bool operator ()(Torrent* left, int rightId) const
41 {
42 return left->id() < rightId;
43 }
44 };
45
46 template<typename Iter>
getIds(Iter it,Iter end)47 auto getIds(Iter it, Iter end)
48 {
49 torrent_ids_t ids;
50
51 for ( ; it != end; ++it)
52 {
53 ids.insert((*it)->id());
54 }
55
56 return ids;
57 }
58
59 } // namespace
60
61 /***
62 ****
63 ***/
64
TorrentModel(Prefs const & prefs)65 TorrentModel::TorrentModel(Prefs const& prefs) :
66 myPrefs(prefs)
67 {
68 }
69
~TorrentModel()70 TorrentModel::~TorrentModel()
71 {
72 clear();
73 }
74
clear()75 void TorrentModel::clear()
76 {
77 beginResetModel();
78 qDeleteAll(myTorrents);
79 myTorrents.clear();
80 endResetModel();
81 }
82
rowCount(QModelIndex const & parent) const83 int TorrentModel::rowCount(QModelIndex const& parent) const
84 {
85 Q_UNUSED(parent)
86
87 return myTorrents.size();
88 }
89
data(QModelIndex const & index,int role) const90 QVariant TorrentModel::data(QModelIndex const& index, int role) const
91 {
92 QVariant var;
93
94 Torrent const* t = myTorrents.value(index.row(), nullptr);
95
96 if (t != nullptr)
97 {
98 switch (role)
99 {
100 case Qt::DisplayRole:
101 var.setValue(t->name());
102 break;
103
104 case Qt::DecorationRole:
105 var.setValue(t->getMimeTypeIcon());
106 break;
107
108 case TorrentRole:
109 var = qVariantFromValue(t);
110 break;
111
112 default:
113 // std::cerr << "Unhandled role: " << role << std::endl;
114 break;
115 }
116 }
117
118 return var;
119 }
120
121 /***
122 ****
123 ***/
124
removeTorrents(tr_variant * list)125 void TorrentModel::removeTorrents(tr_variant* list)
126 {
127 torrents_t torrents;
128 torrents.reserve(tr_variantListSize(list));
129
130 int i = 0;
131 tr_variant* child;
132 while ((child = tr_variantListChild(list, i++)) != nullptr)
133 {
134 int64_t id;
135 Torrent* torrent = nullptr;
136
137 if (tr_variantGetInt(child, &id))
138 {
139 torrent = getTorrentFromId(id);
140 }
141
142 if (torrent != nullptr)
143 {
144 torrents.push_back(torrent);
145 }
146 }
147
148 if (!torrents.empty())
149 {
150 rowsRemove(torrents);
151 }
152 }
153
updateTorrents(tr_variant * torrents,bool isCompleteList)154 void TorrentModel::updateTorrents(tr_variant* torrents, bool isCompleteList)
155 {
156 auto const old = isCompleteList ? myTorrents : torrents_t{};
157 auto added = torrent_ids_t{};
158 auto changed = torrent_ids_t{};
159 auto completed = torrent_ids_t{};
160 auto instantiated = torrents_t{};
161 auto needinfo = torrent_ids_t{};
162 auto processed = torrents_t{};
163
164 auto const now = time(nullptr);
165 auto const recently_added = [now](auto const& tor)
166 {
167 static auto constexpr max_age = 60;
168 auto const date = tor->dateAdded();
169 return (date != 0) && (difftime(now, date) < max_age);
170 };
171
172 // build a list of the property keys
173 tr_variant* const firstChild = tr_variantListChild(torrents, 0);
174 bool const table = tr_variantIsList(firstChild);
175 std::vector<tr_quark> keys;
176 if (table)
177 {
178 // In 'table' format, the first entry in 'torrents' is an array of keys.
179 // All the other entries are an array of the values for one torrent.
180 char const* str;
181 size_t len;
182 size_t i = 0;
183 keys.reserve(tr_variantListSize(firstChild));
184 while (tr_variantGetStr(tr_variantListChild(firstChild, i++), &str, &len))
185 {
186 keys.push_back(tr_quark_new(str, len));
187 }
188 }
189 else
190 {
191 // In 'object' format, every entry is an object with the same set of properties
192 size_t i = 0;
193 tr_quark key;
194 tr_variant* value;
195 while (firstChild && tr_variantDictChild(firstChild, i++, &key, &value))
196 {
197 keys.push_back(key);
198 }
199 }
200
201 // Find the position of TR_KEY_id so we can do torrent lookup
202 auto const id_it = std::find(std::begin(keys), std::end(keys), TR_KEY_id);
203 if (id_it == std::end(keys)) // no ids provided; we can't proceed
204 {
205 return;
206 }
207
208 auto const id_pos = std::distance(std::begin(keys), id_it);
209
210 // Loop through the torrent records...
211 std::vector<tr_variant*> values;
212 values.reserve(keys.size());
213 size_t tor_index = table ? 1 : 0;
214 tr_variant* v;
215 processed.reserve(tr_variantListSize(torrents));
216 while ((v = tr_variantListChild(torrents, tor_index++)))
217 {
218 // Build an array of values
219 values.clear();
220 if (table)
221 {
222 // In table mode, v is already a list of values
223 size_t i = 0;
224 tr_variant* val;
225 while ((val = tr_variantListChild(v, i++)))
226 {
227 values.push_back(val);
228 }
229 }
230 else
231 {
232 // In object mode, v is an object of torrent property key/vals
233 size_t i = 0;
234 tr_quark key;
235 tr_variant* value;
236 while (tr_variantDictChild(v, i++, &key, &value))
237 {
238 values.push_back(value);
239 }
240 }
241
242 // Find the torrent id
243 int64_t id;
244 if (!tr_variantGetInt(values[id_pos], &id))
245 {
246 continue;
247 }
248
249 Torrent* tor = getTorrentFromId(id);
250 std::optional<uint64_t> leftUntilDone;
251 bool is_new = false;
252
253 if (tor == nullptr)
254 {
255 tor = new Torrent(myPrefs, id);
256 instantiated.push_back(tor);
257 is_new = true;
258 }
259 else
260 {
261 leftUntilDone = tor->leftUntilDone();
262 }
263
264 if (tor->update(keys.data(), values.data(), keys.size()))
265 {
266 changed.insert(id);
267 }
268
269 if (is_new && !tor->hasName())
270 {
271 needinfo.insert(id);
272 }
273
274 if (recently_added(tor) && tor->hasName() && !myAlreadyAdded.count(id))
275 {
276 added.insert(id);
277 myAlreadyAdded.insert(id);
278 }
279
280 if (leftUntilDone && (*leftUntilDone > 0) && (tor->leftUntilDone() == 0) && (tor->downloadedEver() > 0))
281 {
282 completed.insert(id);
283 }
284
285 processed.push_back(tor);
286 }
287
288 // model upkeep
289
290 if (!instantiated.empty())
291 {
292 rowsAdd(instantiated);
293 }
294
295 if (!changed.empty())
296 {
297 rowsEmitChanged(changed);
298 }
299
300 // emit signals
301
302 if (!added.empty())
303 {
304 emit torrentsAdded(added);
305 }
306
307 if (!needinfo.empty())
308 {
309 emit torrentsNeedInfo(needinfo);
310 }
311
312 if (!changed.empty())
313 {
314 emit torrentsChanged(changed);
315 }
316
317 if (!completed.empty())
318 {
319 emit torrentsCompleted(completed);
320 }
321
322 // model upkeep
323
324 if (isCompleteList)
325 {
326 std::sort(processed.begin(), processed.end(), TorrentIdLessThan());
327 torrents_t removed;
328 removed.reserve(old.size());
329 std::set_difference(old.begin(), old.end(), processed.begin(), processed.end(), std::back_inserter(removed));
330 rowsRemove(removed);
331 }
332 }
333
334 /***
335 ****
336 ***/
337
getRow(int id) const338 std::optional<int> TorrentModel::getRow(int id) const
339 {
340 std::optional<int> row;
341
342 auto const it = std::equal_range(myTorrents.begin(), myTorrents.end(), id, TorrentIdLessThan());
343 if (it.first != it.second)
344 {
345 row = std::distance(myTorrents.begin(), it.first);
346 assert(myTorrents[*row]->id() == id);
347 }
348
349 return row;
350 }
351
getTorrentFromId(int id)352 Torrent* TorrentModel::getTorrentFromId(int id)
353 {
354 auto const row = getRow(id);
355 return row ? myTorrents[*row] : nullptr;
356 }
357
getTorrentFromId(int id) const358 Torrent const* TorrentModel::getTorrentFromId(int id) const
359 {
360 auto const row = getRow(id);
361 return row ? myTorrents[*row] : nullptr;
362 }
363
364 /***
365 ****
366 ***/
367
getSpans(torrent_ids_t const & ids) const368 std::vector<TorrentModel::span_t> TorrentModel::getSpans(torrent_ids_t const& ids) const
369 {
370 // ids -> rows
371 std::vector<int> rows;
372 rows.reserve(ids.size());
373 for (auto const& id : ids)
374 {
375 auto const row = getRow(id);
376 if (row)
377 {
378 rows.push_back(*row);
379 }
380 }
381
382 std::sort(rows.begin(), rows.end());
383
384 // rows -> spans
385 std::vector<span_t> spans;
386 spans.reserve(rows.size());
387 span_t span;
388 bool in_span = false;
389 for (auto const& row : rows)
390 {
391 if (in_span)
392 {
393 if (span.second + 1 == row)
394 {
395 span.second = row;
396 }
397 else
398 {
399 spans.push_back(span);
400 in_span = false;
401 }
402 }
403
404 if (!in_span)
405 {
406 span.first = span.second = row;
407 in_span = true;
408 }
409 }
410
411 if (in_span)
412 {
413 spans.push_back(span);
414 }
415
416 return spans;
417 }
418
419 /***
420 ****
421 ***/
422
rowsEmitChanged(torrent_ids_t const & ids)423 void TorrentModel::rowsEmitChanged(torrent_ids_t const& ids)
424 {
425 for (auto const& span : getSpans(ids))
426 {
427 emit dataChanged(index(span.first), index(span.second));
428 }
429 }
430
rowsAdd(torrents_t const & torrents)431 void TorrentModel::rowsAdd(torrents_t const& torrents)
432 {
433 auto const compare = TorrentIdLessThan();
434
435 if (myTorrents.empty())
436 {
437 beginInsertRows(QModelIndex(), 0, torrents.size() - 1);
438 myTorrents = torrents;
439 std::sort(myTorrents.begin(), myTorrents.end(), TorrentIdLessThan());
440 endInsertRows();
441 }
442 else
443 {
444 for (auto const& tor : torrents)
445 {
446 auto const it = std::lower_bound(myTorrents.begin(), myTorrents.end(), tor, compare);
447 auto const row = std::distance(myTorrents.begin(), it);
448
449 beginInsertRows(QModelIndex(), row, row);
450 myTorrents.insert(it, tor);
451 endInsertRows();
452 }
453 }
454 }
455
rowsRemove(torrents_t const & torrents)456 void TorrentModel::rowsRemove(torrents_t const& torrents)
457 {
458 // must walk in reverse to avoid invalidating row numbers
459 auto const& spans = getSpans(getIds(torrents.begin(), torrents.end()));
460 for (auto it = spans.rbegin(), end = spans.rend(); it != end; ++it)
461 {
462 auto const& span = *it;
463
464 beginRemoveRows(QModelIndex(), span.first, span.second);
465 auto const n = span.second + 1 - span.first;
466 myTorrents.remove(span.first, n);
467 endRemoveRows();
468 }
469
470 qDeleteAll(torrents);
471 }
472
473 /***
474 ****
475 ***/
476
hasTorrent(QString const & hashString) const477 bool TorrentModel::hasTorrent(QString const& hashString) const
478 {
479 auto test = [hashString](auto const& tor) { return tor->hashString() == hashString; };
480 return std::any_of(myTorrents.cbegin(), myTorrents.cend(), test);
481 }
482