1 /*
2     SPDX-FileCopyrightText: 2008 Joris Guisson <joris.guisson@gmail.com>
3     SPDX-FileCopyrightText: 2008 Ivan Vasic <ivasic@gmail.com>
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include <QDateTime>
8 #include <QDomElement>
9 #include <QIcon>
10 #include <QLocale>
11 
12 #include <KLocalizedString>
13 
14 #include <Syndication/Enclosure>
15 #include <Syndication/Item>
16 
17 #include "feedwidgetmodel.h"
18 #include "ktfeed.h"
19 #include <util/log.h>
20 
21 using namespace bt;
22 
23 namespace kt
24 {
FeedWidgetModel(QObject * parent)25 FeedWidgetModel::FeedWidgetModel(QObject *parent)
26     : QAbstractTableModel(parent)
27     , feed(nullptr)
28 {
29 }
30 
~FeedWidgetModel()31 FeedWidgetModel::~FeedWidgetModel()
32 {
33 }
34 
setCurrentFeed(Feed * f)35 void FeedWidgetModel::setCurrentFeed(Feed *f)
36 {
37     beginResetModel();
38     items.clear();
39     if (feed)
40         disconnect(feed, &Feed::updated, this, &FeedWidgetModel::updated);
41 
42     feed = f;
43     if (feed) {
44         Syndication::FeedPtr ptr = feed->feedData();
45         if (ptr)
46             items = ptr->items();
47         connect(feed, &Feed::updated, this, &FeedWidgetModel::updated);
48     }
49     endResetModel();
50 }
51 
rowCount(const QModelIndex & parent) const52 int FeedWidgetModel::rowCount(const QModelIndex &parent) const
53 {
54     if (!parent.isValid())
55         return items.count();
56     else
57         return 0;
58 }
59 
columnCount(const QModelIndex & parent) const60 int FeedWidgetModel::columnCount(const QModelIndex &parent) const
61 {
62     if (!parent.isValid())
63         return 3;
64     else
65         return 0;
66 }
67 
headerData(int section,Qt::Orientation orientation,int role) const68 QVariant FeedWidgetModel::headerData(int section, Qt::Orientation orientation, int role) const
69 {
70     if (role != Qt::DisplayRole || section < 0 || section >= 3 || orientation != Qt::Horizontal)
71         return QVariant();
72 
73     switch (section) {
74     case 0:
75         return i18n("Title");
76     case 1:
77         return i18n("Date Published");
78     case 2:
79         return i18n("Torrent");
80     default:
81         return QVariant();
82     }
83 }
84 
data(const QModelIndex & index,int role) const85 QVariant FeedWidgetModel::data(const QModelIndex &index, int role) const
86 {
87     if (!index.isValid() || !feed)
88         return QVariant();
89 
90     if (index.row() < 0 || index.row() >= items.count())
91         return QVariant();
92 
93     Syndication::ItemPtr item = items.at(index.row());
94     if (role == Qt::DisplayRole) {
95         switch (index.column()) {
96         case 0:
97             return item->title();
98         case 1:
99             return QLocale().toString(QDateTime::fromTime_t(item->datePublished()), QLocale::ShortFormat);
100         case 2:
101             return TorrentUrlFromItem(item);
102         default:
103             return QVariant();
104         }
105     } else if (role == Qt::DecorationRole && index.column() == 0 && feed->downloaded(item))
106         return QIcon::fromTheme(QStringLiteral("go-down"));
107 
108     return QVariant();
109 }
110 
removeRows(int row,int count,const QModelIndex & parent)111 bool FeedWidgetModel::removeRows(int row, int count, const QModelIndex &parent)
112 {
113     Q_UNUSED(parent);
114     beginRemoveRows(QModelIndex(), row, row + count - 1);
115     endRemoveRows();
116     return true;
117 }
118 
insertRows(int row,int count,const QModelIndex & parent)119 bool FeedWidgetModel::insertRows(int row, int count, const QModelIndex &parent)
120 {
121     Q_UNUSED(parent);
122     beginInsertRows(QModelIndex(), row, row + count - 1);
123     endInsertRows();
124     return true;
125 }
126 
itemForIndex(const QModelIndex & index)127 Syndication::ItemPtr FeedWidgetModel::itemForIndex(const QModelIndex &index)
128 {
129     if (index.row() < 0 || index.row() >= items.count())
130         return Syndication::ItemPtr();
131 
132     return items.at(index.row());
133 }
134 
TorrentUrlFromItem(Syndication::ItemPtr item)135 QString TorrentUrlFromItem(Syndication::ItemPtr item)
136 {
137     const QList<Syndication::EnclosurePtr> encs = item->enclosures();
138     for (const Syndication::EnclosurePtr &e : encs) {
139         if (e->type() == QStringLiteral("application/x-bittorrent") || e->url().endsWith(QStringLiteral(".torrent")))
140             return e->url();
141     }
142 
143     // Search for magnets on item's link as some RSS feeds only use this item to post the magnet link.
144     QString link = item->link();
145     if (!link.isEmpty()) {
146         // Note that syndication library prepends the channel link to the item link by default, so
147         // we need to extract the magnet from the string.
148         int magnetStartIndex = link.indexOf(QStringLiteral("magnet:"));
149         if (magnetStartIndex >= 0)
150             return link.right(link.size() - magnetStartIndex);
151     }
152 
153     QMultiMap<QString, QDomElement> props = item->additionalProperties();
154     QMultiMap<QString, QDomElement>::iterator itr = props.begin();
155     while (itr != props.end()) {
156         const QDomElement &elem = itr.value();
157         if (elem.nodeName() == QStringLiteral("torrent")) {
158             QDomElement uri = elem.firstChildElement(QStringLiteral("magnetURI"));
159             if (!uri.isNull())
160                 return uri.text();
161         }
162         itr++;
163     }
164 
165     return QString();
166 }
167 
updated()168 void FeedWidgetModel::updated()
169 {
170     if (!feed)
171         return;
172 
173     beginResetModel();
174     items.clear();
175     Syndication::FeedPtr ptr = feed->feedData();
176     if (ptr)
177         items = ptr->items();
178     endResetModel();
179 }
180 }
181