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 <cassert>
10 #include <iostream>
11 
12 #include <QApplication>
13 #include <QString>
14 #include <QUrl>
15 #include <QVariant>
16 
17 #include <libtransmission/transmission.h>
18 #include <libtransmission/utils.h> /* tr_new0, tr_strdup */
19 #include <libtransmission/variant.h>
20 
21 #include "Application.h"
22 #include "Prefs.h"
23 #include "Torrent.h"
24 #include "Utils.h"
25 
26 struct Property
27 {
28     int id;
29     tr_quark key;
30     int type;
31 };
32 
33 Property constexpr myProperties[] =
34 {
35     { Torrent::UPLOAD_SPEED, TR_KEY_rateUpload, QVariant::ULongLong } /* Bps */,
36     { Torrent::DOWNLOAD_SPEED, TR_KEY_rateDownload, QVariant::ULongLong }, /* Bps */
37     { Torrent::DOWNLOAD_DIR, TR_KEY_downloadDir, QVariant::String },
38     { Torrent::ACTIVITY, TR_KEY_status, QVariant::Int },
39     { Torrent::NAME, TR_KEY_name, QVariant::String },
40     { Torrent::ERROR, TR_KEY_error, QVariant::Int },
41     { Torrent::ERROR_STRING, TR_KEY_errorString, QVariant::String },
42     { Torrent::SIZE_WHEN_DONE, TR_KEY_sizeWhenDone, QVariant::ULongLong },
43     { Torrent::LEFT_UNTIL_DONE, TR_KEY_leftUntilDone, QVariant::ULongLong },
44     { Torrent::HAVE_UNCHECKED, TR_KEY_haveUnchecked, QVariant::ULongLong },
45     { Torrent::HAVE_VERIFIED, TR_KEY_haveValid, QVariant::ULongLong },
46     { Torrent::DESIRED_AVAILABLE, TR_KEY_desiredAvailable, QVariant::ULongLong },
47     { Torrent::TOTAL_SIZE, TR_KEY_totalSize, QVariant::ULongLong },
48     { Torrent::PIECE_SIZE, TR_KEY_pieceSize, QVariant::ULongLong },
49     { Torrent::PIECE_COUNT, TR_KEY_pieceCount, QVariant::Int },
50     { Torrent::PEERS_GETTING_FROM_US, TR_KEY_peersGettingFromUs, QVariant::Int },
51     { Torrent::PEERS_SENDING_TO_US, TR_KEY_peersSendingToUs, QVariant::Int },
52     { Torrent::WEBSEEDS_SENDING_TO_US, TR_KEY_webseedsSendingToUs, QVariant::Int },
53     { Torrent::PERCENT_DONE, TR_KEY_percentDone, QVariant::Double },
54     { Torrent::METADATA_PERCENT_DONE, TR_KEY_metadataPercentComplete, QVariant::Double },
55     { Torrent::PERCENT_VERIFIED, TR_KEY_recheckProgress, QVariant::Double },
56     { Torrent::DATE_ACTIVITY, TR_KEY_activityDate, QVariant::DateTime },
57     { Torrent::DATE_ADDED, TR_KEY_addedDate, QVariant::DateTime },
58     { Torrent::DATE_STARTED, TR_KEY_startDate, QVariant::DateTime },
59     { Torrent::DATE_CREATED, TR_KEY_dateCreated, QVariant::DateTime },
60     { Torrent::PEERS_CONNECTED, TR_KEY_peersConnected, QVariant::Int },
61     { Torrent::ETA, TR_KEY_eta, QVariant::Int },
62     { Torrent::DOWNLOADED_EVER, TR_KEY_downloadedEver, QVariant::ULongLong },
63     { Torrent::UPLOADED_EVER, TR_KEY_uploadedEver, QVariant::ULongLong },
64     { Torrent::FAILED_EVER, TR_KEY_corruptEver, QVariant::ULongLong },
65     { Torrent::TRACKERSTATS, TR_KEY_trackerStats, CustomVariantType::TrackerStatsList },
66     { Torrent::MIME_ICON, TR_KEY_NONE, QVariant::Icon },
67     { Torrent::SEED_RATIO_LIMIT, TR_KEY_seedRatioLimit, QVariant::Double },
68     { Torrent::SEED_RATIO_MODE, TR_KEY_seedRatioMode, QVariant::Int },
69     { Torrent::SEED_IDLE_LIMIT, TR_KEY_seedIdleLimit, QVariant::Int },
70     { Torrent::SEED_IDLE_MODE, TR_KEY_seedIdleMode, QVariant::Int },
71     { Torrent::DOWN_LIMIT, TR_KEY_downloadLimit, QVariant::Int }, /* KB/s */
72     { Torrent::DOWN_LIMITED, TR_KEY_downloadLimited, QVariant::Bool },
73     { Torrent::UP_LIMIT, TR_KEY_uploadLimit, QVariant::Int }, /* KB/s */
74     { Torrent::UP_LIMITED, TR_KEY_uploadLimited, QVariant::Bool },
75     { Torrent::HONORS_SESSION_LIMITS, TR_KEY_honorsSessionLimits, QVariant::Bool },
76     { Torrent::PEER_LIMIT, TR_KEY_peer_limit, QVariant::Int },
77     { Torrent::HASH_STRING, TR_KEY_hashString, QVariant::String },
78     { Torrent::IS_FINISHED, TR_KEY_isFinished, QVariant::Bool },
79     { Torrent::IS_PRIVATE, TR_KEY_isPrivate, QVariant::Bool },
80     { Torrent::IS_STALLED, TR_KEY_isStalled, QVariant::Bool },
81     { Torrent::COMMENT, TR_KEY_comment, QVariant::String },
82     { Torrent::CREATOR, TR_KEY_creator, QVariant::String },
83     { Torrent::MANUAL_ANNOUNCE_TIME, TR_KEY_manualAnnounceTime, QVariant::DateTime },
84     { Torrent::PEERS, TR_KEY_peers, CustomVariantType::PeerList },
85     { Torrent::BANDWIDTH_PRIORITY, TR_KEY_bandwidthPriority, QVariant::Int },
86     { Torrent::QUEUE_POSITION, TR_KEY_queuePosition, QVariant::Int },
87     { Torrent::EDIT_DATE, TR_KEY_editDate, QVariant::Int },
88 };
89 
90 /***
91 ****
92 ***/
93 
94 // unchanging fields needed by the main window
95 Torrent::KeyList const Torrent::mainInfoKeys{
96     TR_KEY_addedDate,
97     TR_KEY_downloadDir,
98     TR_KEY_hashString,
99     TR_KEY_id, // must be in every req
100     TR_KEY_name,
101     TR_KEY_totalSize,
102     TR_KEY_trackers,
103 };
104 
105 // changing fields needed by the main window
106 Torrent::KeyList const Torrent::mainStatKeys{
107     TR_KEY_downloadedEver,
108     TR_KEY_error,
109     TR_KEY_errorString,
110     TR_KEY_eta,
111     TR_KEY_haveUnchecked,
112     TR_KEY_haveValid,
113     TR_KEY_id, // must be in every req
114     TR_KEY_isFinished,
115     TR_KEY_leftUntilDone,
116     TR_KEY_manualAnnounceTime,
117     TR_KEY_metadataPercentComplete,
118     TR_KEY_peersConnected,
119     TR_KEY_peersGettingFromUs,
120     TR_KEY_peersSendingToUs,
121     TR_KEY_percentDone,
122     TR_KEY_queuePosition,
123     TR_KEY_rateDownload,
124     TR_KEY_rateUpload,
125     TR_KEY_recheckProgress,
126     TR_KEY_seedRatioLimit,
127     TR_KEY_seedRatioMode,
128     TR_KEY_sizeWhenDone,
129     TR_KEY_status,
130     TR_KEY_uploadedEver,
131     TR_KEY_webseedsSendingToUs
132 };
133 
134 Torrent::KeyList const Torrent::allMainKeys = Torrent::mainInfoKeys + Torrent::mainStatKeys;
135 
136 // unchanging fields needed by the details dialog
137 Torrent::KeyList const Torrent::detailInfoKeys{
138     TR_KEY_comment,
139     TR_KEY_creator,
140     TR_KEY_dateCreated,
141     TR_KEY_files,
142     TR_KEY_id, // must be in every req
143     TR_KEY_isPrivate,
144     TR_KEY_pieceCount,
145     TR_KEY_pieceSize,
146     TR_KEY_trackers
147 };
148 
149 // changing fields needed by the details dialog
150 Torrent::KeyList const Torrent::detailStatKeys{
151     TR_KEY_activityDate,
152     TR_KEY_bandwidthPriority,
153     TR_KEY_corruptEver,
154     TR_KEY_desiredAvailable,
155     TR_KEY_downloadedEver,
156     TR_KEY_downloadLimit,
157     TR_KEY_downloadLimited,
158     TR_KEY_fileStats,
159     TR_KEY_honorsSessionLimits,
160     TR_KEY_id, // must be in every req
161     TR_KEY_peer_limit,
162     TR_KEY_peers,
163     TR_KEY_seedIdleLimit,
164     TR_KEY_seedIdleMode,
165     TR_KEY_startDate,
166     TR_KEY_trackerStats,
167     TR_KEY_uploadLimit,
168     TR_KEY_uploadLimited
169 };
170 
171 /***
172 ****
173 ***/
174 
Torrent(Prefs const & prefs,int id)175 Torrent::Torrent(Prefs const& prefs, int id) :
176     myId(id),
177     myPrefs(prefs)
178 {
179     static_assert(TR_N_ELEMENTS(myProperties) == PROPERTY_COUNT);
180 
181     static_assert(([] () constexpr
182     {
183         int i = 0;
184 
185         for (auto const& property : myProperties)
186         {
187             if (property.id != i)
188             {
189                 return false;
190             }
191 
192             ++i;
193         }
194 
195         return true;
196     })());
197 
198     setIcon(MIME_ICON, Utils::getFileIcon());
199 }
200 
201 /***
202 ****
203 ***/
204 
setInt(int i,int value)205 bool Torrent::setInt(int i, int value)
206 {
207     bool changed = false;
208 
209     assert(0 <= i && i < PROPERTY_COUNT);
210     assert(myProperties[i].type == QVariant::Int);
211 
212     if (myValues[i].isNull() || myValues[i].toInt() != value)
213     {
214         myValues[i].setValue(value);
215         changed = true;
216     }
217 
218     return changed;
219 }
220 
setBool(int i,bool value)221 bool Torrent::setBool(int i, bool value)
222 {
223     bool changed = false;
224 
225     assert(0 <= i && i < PROPERTY_COUNT);
226     assert(myProperties[i].type == QVariant::Bool);
227 
228     if (myValues[i].isNull() || myValues[i].toBool() != value)
229     {
230         myValues[i].setValue(value);
231         changed = true;
232     }
233 
234     return changed;
235 }
236 
setDouble(int i,double value)237 bool Torrent::setDouble(int i, double value)
238 {
239     bool changed = false;
240 
241     assert(0 <= i && i < PROPERTY_COUNT);
242     assert(myProperties[i].type == QVariant::Double);
243 
244     if (myValues[i] != value)
245     {
246         myValues[i].setValue(value);
247         changed = true;
248     }
249 
250     return changed;
251 }
252 
setTime(int i,time_t value)253 bool Torrent::setTime(int i, time_t value)
254 {
255     bool changed = false;
256 
257     assert(0 <= i && i < PROPERTY_COUNT);
258     assert(myProperties[i].type == QVariant::DateTime);
259 
260     auto& oldval = myValues[i];
261     auto const newval = qlonglong(value);
262 
263     if (oldval != newval)
264     {
265         oldval = newval;
266         changed = true;
267     }
268 
269     return changed;
270 }
271 
setSize(int i,qulonglong value)272 bool Torrent::setSize(int i, qulonglong value)
273 {
274     bool changed = false;
275 
276     assert(0 <= i && i < PROPERTY_COUNT);
277     assert(myProperties[i].type == QVariant::ULongLong);
278 
279     if (myValues[i].isNull() || myValues[i].toULongLong() != value)
280     {
281         myValues[i].setValue(value);
282         changed = true;
283     }
284 
285     return changed;
286 }
287 
setString(int i,char const * value,size_t len)288 bool Torrent::setString(int i, char const* value, size_t len)
289 {
290     bool changed = false;
291 
292     assert(0 <= i && i < PROPERTY_COUNT);
293     assert(myProperties[i].type == QVariant::String);
294 
295     auto& oldval = myValues[i];
296     auto const newval = QString::fromUtf8(value, len);
297 
298     if (oldval != newval)
299     {
300         oldval = newval;
301         changed = true;
302     }
303 
304     return changed;
305 }
306 
setIcon(int i,QIcon const & value)307 bool Torrent::setIcon(int i, QIcon const& value)
308 {
309     assert(0 <= i && i < PROPERTY_COUNT);
310     assert(myProperties[i].type == QVariant::Icon);
311 
312     myValues[i].setValue(value);
313     return true;
314 }
315 
getInt(int i) const316 int Torrent::getInt(int i) const
317 {
318     assert(0 <= i && i < PROPERTY_COUNT);
319     assert(myProperties[i].type == QVariant::Int);
320     // assert(!myValues[i].isNull());
321 
322     return myValues[i].toInt();
323 }
324 
getTime(int i) const325 time_t Torrent::getTime(int i) const
326 {
327     assert(0 <= i && i < PROPERTY_COUNT);
328     assert(myProperties[i].type == QVariant::DateTime);
329     // assert((i == DATE_ADDED) || !myValues[i].isNull());
330 
331     return time_t(myValues[i].toLongLong());
332 }
333 
getBool(int i) const334 bool Torrent::getBool(int i) const
335 {
336     assert(0 <= i && i < PROPERTY_COUNT);
337     assert(myProperties[i].type == QVariant::Bool);
338     // assert(!myValues[i].isNull());
339 
340     return myValues[i].toBool();
341 }
342 
getSize(int i) const343 qulonglong Torrent::getSize(int i) const
344 {
345     assert(0 <= i && i < PROPERTY_COUNT);
346     assert(myProperties[i].type == QVariant::ULongLong);
347     // assert(!myValues[i].isNull());
348 
349     return myValues[i].toULongLong();
350 }
351 
getDouble(int i) const352 double Torrent::getDouble(int i) const
353 {
354     assert(0 <= i && i < PROPERTY_COUNT);
355     assert(myProperties[i].type == QVariant::Double);
356     // assert(!myValues[i].isNull());
357 
358     return myValues[i].toDouble();
359 }
360 
getString(int i) const361 QString Torrent::getString(int i) const
362 {
363     assert(0 <= i && i < PROPERTY_COUNT);
364     assert(myProperties[i].type == QVariant::String);
365     // assert((i == HASH_STRING) || !myValues[i].isNull());
366 
367     return myValues[i].toString();
368 }
369 
getIcon(int i) const370 QIcon Torrent::getIcon(int i) const
371 {
372     assert(0 <= i && i < PROPERTY_COUNT);
373     assert(myProperties[i].type == QVariant::Icon);
374     // assert(!myValues[i].isNull());
375 
376     return myValues[i].value<QIcon>();
377 }
378 
379 /***
380 ****
381 ***/
382 
getSeedRatio(double & ratio) const383 bool Torrent::getSeedRatio(double& ratio) const
384 {
385     bool isLimited;
386 
387     switch (seedRatioMode())
388     {
389     case TR_RATIOLIMIT_SINGLE:
390         isLimited = true;
391         ratio = seedRatioLimit();
392         break;
393 
394     case TR_RATIOLIMIT_GLOBAL:
395         if ((isLimited = myPrefs.getBool(Prefs::RATIO_ENABLED)))
396         {
397             ratio = myPrefs.getDouble(Prefs::RATIO);
398         }
399 
400         break;
401 
402     default: // TR_RATIOLIMIT_UNLIMITED:
403         isLimited = false;
404         break;
405     }
406 
407     return isLimited;
408 }
409 
hasTrackerSubstring(QString const & substr) const410 bool Torrent::hasTrackerSubstring(QString const& substr) const
411 {
412     for (auto const& s : trackers())
413     {
414         if (s.contains(substr, Qt::CaseInsensitive))
415         {
416             return true;
417         }
418     }
419 
420     return false;
421 }
422 
compareSeedRatio(Torrent const & that) const423 int Torrent::compareSeedRatio(Torrent const& that) const
424 {
425     double a;
426     double b;
427     bool const has_a = getSeedRatio(a);
428     bool const has_b = that.getSeedRatio(b);
429 
430     if (!has_a && !has_b)
431     {
432         return 0;
433     }
434 
435     if (!has_a || !has_b)
436     {
437         return has_a ? -1 : 1;
438     }
439 
440     if (a < b)
441     {
442         return -1;
443     }
444 
445     if (a > b)
446     {
447         return 1;
448     }
449 
450     return 0;
451 }
452 
compareRatio(Torrent const & that) const453 int Torrent::compareRatio(Torrent const& that) const
454 {
455     double const a = ratio();
456     double const b = that.ratio();
457 
458     if (static_cast<int>(a) == TR_RATIO_INF && static_cast<int>(b) == TR_RATIO_INF)
459     {
460         return 0;
461     }
462 
463     if (static_cast<int>(a) == TR_RATIO_INF)
464     {
465         return 1;
466     }
467 
468     if (static_cast<int>(b) == TR_RATIO_INF)
469     {
470         return -1;
471     }
472 
473     if (a < b)
474     {
475         return -1;
476     }
477 
478     if (a > b)
479     {
480         return 1;
481     }
482 
483     return 0;
484 }
485 
compareETA(Torrent const & that) const486 int Torrent::compareETA(Torrent const& that) const
487 {
488     bool const haveA(hasETA());
489     bool const haveB(that.hasETA());
490 
491     if (haveA && haveB)
492     {
493         return getETA() - that.getETA();
494     }
495 
496     if (haveA)
497     {
498         return 1;
499     }
500 
501     if (haveB)
502     {
503         return -1;
504     }
505 
506     return 0;
507 }
508 
509 /***
510 ****
511 ***/
512 
updateMimeIcon()513 void Torrent::updateMimeIcon()
514 {
515     FileList const& files(myFiles);
516 
517     QIcon icon;
518 
519     if (files.size() > 1)
520     {
521         icon = Utils::getFolderIcon();
522     }
523     else if (files.size() == 1)
524     {
525         icon = Utils::guessMimeIcon(files.at(0).filename);
526     }
527     else
528     {
529         icon = Utils::guessMimeIcon(name());
530     }
531 
532     setIcon(MIME_ICON, icon);
533 }
534 
535 /***
536 ****
537 ***/
538 
update(tr_quark const * keys,tr_variant ** values,size_t n)539 bool Torrent::update(tr_quark const* keys, tr_variant** values, size_t n)
540 {
541     static bool lookup_initialized = false;
542     static int key_to_property_index[TR_N_KEYS];
543     bool changed = false;
544 
545     if (!lookup_initialized)
546     {
547         lookup_initialized = true;
548 
549         for (int i = 0; i < TR_N_KEYS; ++i)
550         {
551             key_to_property_index[i] = -1;
552         }
553 
554         for (int i = 0; i < PROPERTY_COUNT; i++)
555         {
556             key_to_property_index[myProperties[i].key] = i;
557         }
558     }
559 
560     for (size_t pos = 0; pos < n; ++pos)
561     {
562         tr_quark key = keys[pos];
563         tr_variant* child = values[pos];
564 
565         int const property_index = key_to_property_index[key];
566 
567         if (property_index == -1) // we're not interested in this one
568         {
569             continue;
570         }
571 
572         assert(myProperties[property_index].key == key);
573 
574         switch (myProperties[property_index].type)
575         {
576         case QVariant::Int:
577             {
578                 int64_t val;
579 
580                 if (tr_variantGetInt(child, &val))
581                 {
582                     changed |= setInt(property_index, val);
583                 }
584 
585                 break;
586             }
587 
588         case QVariant::Bool:
589             {
590                 bool val;
591 
592                 if (tr_variantGetBool(child, &val))
593                 {
594                     changed |= setBool(property_index, val);
595                 }
596 
597                 break;
598             }
599 
600         case QVariant::String:
601             {
602                 char const* val;
603                 size_t len;
604 
605                 if (tr_variantGetStr(child, &val, &len))
606                 {
607                     bool const field_changed = setString(property_index, val, len);
608                     changed |= field_changed;
609 
610                     if (field_changed && key == TR_KEY_name)
611                     {
612                         updateMimeIcon();
613                     }
614                 }
615 
616                 break;
617             }
618 
619         case QVariant::ULongLong:
620             {
621                 int64_t val;
622 
623                 if (tr_variantGetInt(child, &val))
624                 {
625                     changed |= setSize(property_index, val);
626                 }
627 
628                 break;
629             }
630 
631         case QVariant::Double:
632             {
633                 double val;
634 
635                 if (tr_variantGetReal(child, &val))
636                 {
637                     changed |= setDouble(property_index, val);
638                 }
639 
640                 break;
641             }
642 
643         case QVariant::DateTime:
644             {
645                 int64_t val;
646                 if (tr_variantGetInt(child, &val) && val &&
647                     setTime(property_index, time_t(val)))
648                 {
649                     changed = true;
650 
651                     if (key == TR_KEY_editDate)
652                     {
653                         // FIXME
654                         // emit torrentEdited(*this);
655                     }
656                 }
657 
658                 break;
659             }
660 
661         case CustomVariantType::PeerList:
662             // handled below
663             break;
664 
665         default:
666             std::cerr << __FILE__ << ':' << __LINE__ << "unhandled type: " << tr_quark_get_string(key, nullptr) << std::endl;
667             assert(false && "unhandled type");
668         }
669     }
670 
671     auto it = std::find(keys, keys + n, TR_KEY_files);
672     if (it != keys + n)
673     {
674         tr_variant* files = values[std::distance(keys, it)];
675         char const* str;
676         int64_t intVal;
677         int i = 0;
678         tr_variant* child;
679 
680         myFiles.clear();
681         myFiles.reserve(tr_variantListSize(files));
682 
683         while ((child = tr_variantListChild(files, i)) != nullptr)
684         {
685             TorrentFile file;
686             size_t len;
687             file.index = i++;
688 
689             if (tr_variantDictFindStr(child, TR_KEY_name, &str, &len))
690             {
691                 file.filename = QString::fromUtf8(str, len);
692             }
693 
694             if (tr_variantDictFindInt(child, TR_KEY_length, &intVal))
695             {
696                 file.size = intVal;
697             }
698 
699             myFiles.append(file);
700         }
701 
702         updateMimeIcon();
703         changed = true;
704     }
705 
706     it = std::find(keys, keys + n, TR_KEY_fileStats);
707     if (it != keys + n)
708     {
709         tr_variant* files = values[std::distance(keys, it)];
710         int const n = tr_variantListSize(files);
711 
712         for (int i = 0; i < n && i < myFiles.size(); ++i)
713         {
714             int64_t intVal;
715             bool boolVal;
716             tr_variant* child = tr_variantListChild(files, i);
717             TorrentFile& file(myFiles[i]);
718 
719             if (tr_variantDictFindInt(child, TR_KEY_bytesCompleted, &intVal))
720             {
721                 file.have = intVal;
722             }
723 
724             if (tr_variantDictFindBool(child, TR_KEY_wanted, &boolVal))
725             {
726                 file.wanted = boolVal;
727             }
728 
729             if (tr_variantDictFindInt(child, TR_KEY_priority, &intVal))
730             {
731                 file.priority = intVal;
732             }
733         }
734 
735         changed = true;
736     }
737 
738     it = std::find(keys, keys + n, TR_KEY_trackers);
739     if (it != keys + n)
740     {
741         tr_variant* v = values[std::distance(keys, it)];
742 
743         // build the new tracker list
744         QStringList trackers;
745         trackers.reserve(tr_variantListSize(v));
746         tr_variant* child;
747         int i = 0;
748         while ((child = tr_variantListChild(v, i++)) != nullptr)
749         {
750             char const* str;
751             size_t len;
752             if (tr_variantDictFindStr(child, TR_KEY_announce, &str, &len))
753             {
754                 trackers.append(QString::fromUtf8(str, len));
755             }
756         }
757 
758         // update the trackers
759         if (trackers_ != trackers)
760         {
761             QStringList displayNames;
762             displayNames.reserve(trackers.size());
763             for (auto const& tracker : trackers)
764             {
765                 auto const url = QUrl(tracker);
766                 auto const key = qApp->faviconCache().add(url);
767                 displayNames.append(FaviconCache::getDisplayName(key));
768             }
769 
770             displayNames.removeDuplicates();
771 
772             trackers_.swap(trackers);
773             trackerDisplayNames_.swap(displayNames);
774             changed = true;
775         }
776     }
777 
778     it = std::find(keys, keys + n, TR_KEY_trackerStats);
779     if (it != keys + n)
780     {
781         tr_variant* trackerStats = values[std::distance(keys, it)];
782         tr_variant* child;
783         TrackerStatsList trackerStatsList;
784         int childNum = 0;
785 
786         while ((child = tr_variantListChild(trackerStats, childNum++)) != nullptr)
787         {
788             bool b;
789             int64_t i;
790             size_t len;
791             char const* str;
792             TrackerStat trackerStat;
793 
794             if (tr_variantDictFindStr(child, TR_KEY_announce, &str, &len))
795             {
796                 trackerStat.announce = QString::fromUtf8(str, len);
797             }
798 
799             if (tr_variantDictFindInt(child, TR_KEY_announceState, &i))
800             {
801                 trackerStat.announceState = i;
802             }
803 
804             if (tr_variantDictFindInt(child, TR_KEY_downloadCount, &i))
805             {
806                 trackerStat.downloadCount = i;
807             }
808 
809             if (tr_variantDictFindBool(child, TR_KEY_hasAnnounced, &b))
810             {
811                 trackerStat.hasAnnounced = b;
812             }
813 
814             if (tr_variantDictFindBool(child, TR_KEY_hasScraped, &b))
815             {
816                 trackerStat.hasScraped = b;
817             }
818 
819             if (tr_variantDictFindStr(child, TR_KEY_host, &str, &len))
820             {
821                 trackerStat.host = QString::fromUtf8(str, len);
822             }
823 
824             if (tr_variantDictFindInt(child, TR_KEY_id, &i))
825             {
826                 trackerStat.id = i;
827             }
828 
829             if (tr_variantDictFindBool(child, TR_KEY_isBackup, &b))
830             {
831                 trackerStat.isBackup = b;
832             }
833 
834             if (tr_variantDictFindInt(child, TR_KEY_lastAnnouncePeerCount, &i))
835             {
836                 trackerStat.lastAnnouncePeerCount = i;
837             }
838 
839             if (tr_variantDictFindStr(child, TR_KEY_lastAnnounceResult, &str, &len))
840             {
841                 trackerStat.lastAnnounceResult = QString::fromUtf8(str, len);
842             }
843 
844             if (tr_variantDictFindInt(child, TR_KEY_lastAnnounceStartTime, &i))
845             {
846                 trackerStat.lastAnnounceStartTime = i;
847             }
848 
849             if (tr_variantDictFindBool(child, TR_KEY_lastAnnounceSucceeded, &b))
850             {
851                 trackerStat.lastAnnounceSucceeded = b;
852             }
853 
854             if (tr_variantDictFindInt(child, TR_KEY_lastAnnounceTime, &i))
855             {
856                 trackerStat.lastAnnounceTime = i;
857             }
858 
859             if (tr_variantDictFindBool(child, TR_KEY_lastAnnounceTimedOut, &b))
860             {
861                 trackerStat.lastAnnounceTimedOut = b;
862             }
863 
864             if (tr_variantDictFindStr(child, TR_KEY_lastScrapeResult, &str, &len))
865             {
866                 trackerStat.lastScrapeResult = QString::fromUtf8(str, len);
867             }
868 
869             if (tr_variantDictFindInt(child, TR_KEY_lastScrapeStartTime, &i))
870             {
871                 trackerStat.lastScrapeStartTime = i;
872             }
873 
874             if (tr_variantDictFindBool(child, TR_KEY_lastScrapeSucceeded, &b))
875             {
876                 trackerStat.lastScrapeSucceeded = b;
877             }
878 
879             if (tr_variantDictFindInt(child, TR_KEY_lastScrapeTime, &i))
880             {
881                 trackerStat.lastScrapeTime = i;
882             }
883 
884             if (tr_variantDictFindBool(child, TR_KEY_lastScrapeTimedOut, &b))
885             {
886                 trackerStat.lastScrapeTimedOut = b;
887             }
888 
889             if (tr_variantDictFindInt(child, TR_KEY_leecherCount, &i))
890             {
891                 trackerStat.leecherCount = i;
892             }
893 
894             if (tr_variantDictFindInt(child, TR_KEY_nextAnnounceTime, &i))
895             {
896                 trackerStat.nextAnnounceTime = i;
897             }
898 
899             if (tr_variantDictFindInt(child, TR_KEY_nextScrapeTime, &i))
900             {
901                 trackerStat.nextScrapeTime = i;
902             }
903 
904             if (tr_variantDictFindInt(child, TR_KEY_scrapeState, &i))
905             {
906                 trackerStat.scrapeState = i;
907             }
908 
909             if (tr_variantDictFindInt(child, TR_KEY_seederCount, &i))
910             {
911                 trackerStat.seederCount = i;
912             }
913 
914             if (tr_variantDictFindInt(child, TR_KEY_tier, &i))
915             {
916                 trackerStat.tier = i;
917             }
918 
919             trackerStatsList << trackerStat;
920         }
921 
922         myValues[TRACKERSTATS].setValue(trackerStatsList);
923         changed = true;
924     }
925 
926     it = std::find(keys, keys + n, TR_KEY_peers);
927     if (it != keys + n)
928     {
929         tr_variant* peers = values[std::distance(keys, it)];
930         tr_variant* child;
931         PeerList peerList;
932         int childNum = 0;
933 
934         while ((child = tr_variantListChild(peers, childNum++)) != nullptr)
935         {
936             double d;
937             bool b;
938             int64_t i;
939             size_t len;
940             char const* str;
941             Peer peer;
942 
943             if (tr_variantDictFindStr(child, TR_KEY_address, &str, &len))
944             {
945                 peer.address = QString::fromUtf8(str, len);
946             }
947 
948             if (tr_variantDictFindStr(child, TR_KEY_clientName, &str, &len))
949             {
950                 peer.clientName = QString::fromUtf8(str, len);
951             }
952 
953             if (tr_variantDictFindBool(child, TR_KEY_clientIsChoked, &b))
954             {
955                 peer.clientIsChoked = b;
956             }
957 
958             if (tr_variantDictFindBool(child, TR_KEY_clientIsInterested, &b))
959             {
960                 peer.clientIsInterested = b;
961             }
962 
963             if (tr_variantDictFindStr(child, TR_KEY_flagStr, &str, &len))
964             {
965                 peer.flagStr = QString::fromUtf8(str, len);
966             }
967 
968             if (tr_variantDictFindBool(child, TR_KEY_isDownloadingFrom, &b))
969             {
970                 peer.isDownloadingFrom = b;
971             }
972 
973             if (tr_variantDictFindBool(child, TR_KEY_isEncrypted, &b))
974             {
975                 peer.isEncrypted = b;
976             }
977 
978             if (tr_variantDictFindBool(child, TR_KEY_isIncoming, &b))
979             {
980                 peer.isIncoming = b;
981             }
982 
983             if (tr_variantDictFindBool(child, TR_KEY_isUploadingTo, &b))
984             {
985                 peer.isUploadingTo = b;
986             }
987 
988             if (tr_variantDictFindBool(child, TR_KEY_peerIsChoked, &b))
989             {
990                 peer.peerIsChoked = b;
991             }
992 
993             if (tr_variantDictFindBool(child, TR_KEY_peerIsInterested, &b))
994             {
995                 peer.peerIsInterested = b;
996             }
997 
998             if (tr_variantDictFindInt(child, TR_KEY_port, &i))
999             {
1000                 peer.port = i;
1001             }
1002 
1003             if (tr_variantDictFindReal(child, TR_KEY_progress, &d))
1004             {
1005                 peer.progress = d;
1006             }
1007 
1008             if (tr_variantDictFindInt(child, TR_KEY_rateToClient, &i))
1009             {
1010                 peer.rateToClient = Speed::fromBps(i);
1011             }
1012 
1013             if (tr_variantDictFindInt(child, TR_KEY_rateToPeer, &i))
1014             {
1015                 peer.rateToPeer = Speed::fromBps(i);
1016             }
1017 
1018             peerList << peer;
1019         }
1020 
1021         myValues[PEERS].setValue(peerList);
1022         changed = true;
1023     }
1024 
1025     return changed;
1026 }
1027 
activityString() const1028 QString Torrent::activityString() const
1029 {
1030     QString str;
1031 
1032     switch (getActivity())
1033     {
1034     case TR_STATUS_STOPPED:
1035         str = isFinished() ? tr("Finished") : tr("Paused");
1036         break;
1037 
1038     case TR_STATUS_CHECK_WAIT:
1039         str = tr("Queued for verification");
1040         break;
1041 
1042     case TR_STATUS_CHECK:
1043         str = tr("Verifying local data");
1044         break;
1045 
1046     case TR_STATUS_DOWNLOAD_WAIT:
1047         str = tr("Queued for download");
1048         break;
1049 
1050     case TR_STATUS_DOWNLOAD:
1051         str = tr("Downloading");
1052         break;
1053 
1054     case TR_STATUS_SEED_WAIT:
1055         str = tr("Queued for seeding");
1056         break;
1057 
1058     case TR_STATUS_SEED:
1059         str = tr("Seeding");
1060         break;
1061     }
1062 
1063     return str;
1064 }
1065 
getError() const1066 QString Torrent::getError() const
1067 {
1068     QString s = getString(ERROR_STRING);
1069 
1070     switch (getInt(ERROR))
1071     {
1072     case TR_STAT_TRACKER_WARNING:
1073         s = tr("Tracker gave a warning: %1").arg(s);
1074         break;
1075 
1076     case TR_STAT_TRACKER_ERROR:
1077         s = tr("Tracker gave an error: %1").arg(s);
1078         break;
1079 
1080     case TR_STAT_LOCAL_ERROR:
1081         s = tr("Error: %1").arg(s);
1082         break;
1083 
1084     default:
1085         s.clear();
1086         break;
1087     }
1088 
1089     return s;
1090 }
1091 
getFavicon() const1092 QPixmap TrackerStat::getFavicon() const
1093 {
1094     return qApp->faviconCache().find(QUrl(announce));
1095 }
1096