]> git.sur5r.net Git - minitube/blob - src/ytchannel.cpp
Update watchfile
[minitube] / src / ytchannel.cpp
1 /* $BEGIN_LICENSE
2
3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
5
6 Minitube is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Minitube is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Minitube.  If not, see <http://www.gnu.org/licenses/>.
18
19 $END_LICENSE */
20
21 #include "ytchannel.h"
22 #include "networkaccess.h"
23 #include "database.h"
24 #include <QtSql>
25
26 #ifdef APP_YT3
27 #include "yt3.h"
28 #include <QtScript>
29 #endif
30
31 namespace The {
32 NetworkAccess* http();
33 }
34
35 YTChannel::YTChannel(const QString &channelId, QObject *parent) : QObject(parent),
36     id(0),
37     channelId(channelId),
38     loadingThumbnail(false),
39     notifyCount(0),
40     checked(0),
41     watched(0),
42     loaded(0),
43     loading(false) { }
44
45 QHash<QString, YTChannel*> YTChannel::cache;
46
47 YTChannel* YTChannel::forId(const QString &channelId) {
48     if (channelId.isEmpty()) return 0;
49
50     if (cache.contains(channelId))
51         return cache.value(channelId);
52
53     QSqlDatabase db = Database::instance().getConnection();
54     QSqlQuery query(db);
55     query.prepare("select id,name,description,thumb_url,notify_count,watched,checked,loaded "
56                   "from subscriptions where user_id=?");
57     query.bindValue(0, channelId);
58     bool success = query.exec();
59     if (!success) qWarning() << query.lastQuery() << query.lastError().text();
60
61     YTChannel* channel = 0;
62     if (query.next()) {
63         // Change userId to ChannelId
64
65         channel = new YTChannel(channelId);
66         channel->id = query.value(0).toInt();
67         channel->displayName = query.value(1).toString();
68         channel->description = query.value(2).toString();
69         channel->thumbnailUrl = query.value(3).toString();
70         channel->notifyCount = query.value(4).toInt();
71         channel->watched = query.value(5).toUInt();
72         channel->checked = query.value(6).toUInt();
73         channel->loaded = query.value(7).toUInt();
74         channel->thumbnail = QPixmap(channel->getThumbnailLocation());
75         channel->maybeLoadfromAPI();
76         cache.insert(channelId, channel);
77     }
78
79     return channel;
80 }
81
82 void YTChannel::maybeLoadfromAPI() {
83     if (loading) return;
84     if (channelId.isEmpty()) return;
85
86     uint now = QDateTime::currentDateTime().toTime_t();
87     static const int refreshInterval = 60 * 60 * 24 * 10;
88     if (loaded > now - refreshInterval) return;
89
90     loading = true;
91
92 #ifdef APP_YT3
93
94     QUrl url = YT3::instance().method("channels");
95 #if QT_VERSION >= 0x050000
96     {
97         QUrl &u = url;
98         QUrlQuery url;
99 #endif
100         url.addQueryItem("id", channelId);
101         url.addQueryItem("part", "snippet");
102 #if QT_VERSION >= 0x050000
103         u.setQuery(url);
104     }
105 #endif
106
107 #else
108
109     QUrl url("http://gdata.youtube.com/feeds/api/users/" + channelId);
110 #if QT_VERSION >= 0x050000
111     {
112         QUrl &u = url;
113         QUrlQuery url;
114 #endif
115         url.addQueryItem("v", "2");
116 #if QT_VERSION >= 0x050000
117         u.setQuery(url);
118     }
119 #endif
120
121 #endif
122
123     QObject *reply = The::http()->get(url);
124     connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResponse(QByteArray)));
125     connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
126 }
127
128 #ifdef APP_YT3
129
130 void YTChannel::parseResponse(const QByteArray &bytes) {
131     QScriptEngine engine;
132     QScriptValue json = engine.evaluate("(" + QString::fromUtf8(bytes) + ")");
133     QScriptValue items = json.property("items");
134     if (items.isArray()) {
135         QScriptValueIterator it(items);
136         while (it.hasNext()) {
137             it.next();
138             QScriptValue item = it.value();
139             // For some reason the array has an additional element containing its size.
140             if (item.isObject()) {
141                 QScriptValue snippet = item.property("snippet");
142                 displayName = snippet.property("title").toString();
143                 description = snippet.property("description").toString();
144                 QScriptValue thumbnails = snippet.property("thumbnails");
145                 thumbnailUrl = thumbnails.property("default").property("url").toString();
146                 qDebug() << displayName << description << thumbnailUrl;
147             }
148         }
149     }
150
151     emit infoLoaded();
152     storeInfo();
153     loading = false;
154 }
155
156 #else
157
158 void YTChannel::parseResponse(const QByteArray &bytes) {
159     QXmlStreamReader xml(bytes);
160     xml.readNextStartElement();
161     if (xml.name() == QLatin1String("entry"))
162         while(xml.readNextStartElement()) {
163             const QStringRef n = xml.name();
164             if (n == QLatin1String("summary"))
165                 description = xml.readElementText().simplified();
166             else if (n == QLatin1String("title"))
167                 displayName = xml.readElementText();
168             else if (n == QLatin1String("thumbnail")) {
169                 thumbnailUrl = xml.attributes().value("url").toString();
170                 xml.skipCurrentElement();
171             } else if (n == QLatin1String("username"))
172                 userName = xml.readElementText();
173             else xml.skipCurrentElement();
174         }
175
176     if (xml.hasError()) {
177         emit error(xml.errorString());
178         qWarning() << xml.errorString();
179     }
180
181     emit infoLoaded();
182     storeInfo();
183     loading = false;
184 }
185
186 #endif
187
188 void YTChannel::loadThumbnail() {
189     if (loadingThumbnail) return;
190     if (thumbnailUrl.isEmpty()) return;
191     loadingThumbnail = true;
192
193 #ifdef Q_OS_WIN
194     thumbnailUrl.replace(QLatin1String("https://"), QLatin1String("http://"));
195 #endif
196
197     QUrl url(thumbnailUrl);
198     QObject *reply = The::http()->get(url);
199     connect(reply, SIGNAL(data(QByteArray)), SLOT(storeThumbnail(QByteArray)));
200     connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
201 }
202
203 const QString & YTChannel::getThumbnailDir() {
204     static const QString thumbDir =
205         #if QT_VERSION >= 0x050000
206             QStandardPaths::writableLocation(QStandardPaths::DataLocation)
207         #else
208             QDesktopServices::storageLocation(QDesktopServices::DataLocation)
209         #endif
210             + "/channels/";
211     return thumbDir;
212 }
213
214 QString YTChannel::getThumbnailLocation() {
215     return getThumbnailDir() + channelId;
216 }
217
218 void YTChannel::unsubscribe() {
219     YTChannel::unsubscribe(channelId);
220 }
221
222 void YTChannel::storeThumbnail(const QByteArray &bytes) {
223     thumbnail.loadFromData(bytes);
224     static const int maxWidth = 88;
225
226     QDir dir;
227     dir.mkpath(getThumbnailDir());
228
229     if (thumbnail.width() > maxWidth) {
230         thumbnail = thumbnail.scaledToWidth(maxWidth, Qt::SmoothTransformation);
231         thumbnail.save(getThumbnailLocation(), "JPG");
232     } else {
233         QFile file(getThumbnailLocation());
234         if (!file.open(QIODevice::WriteOnly))
235             qWarning() << "Error opening file for writing" << file.fileName();
236         QDataStream stream(&file);
237         stream.writeRawData(bytes.constData(), bytes.size());
238     }
239
240     emit thumbnailLoaded();
241     loadingThumbnail = false;
242 }
243
244 void YTChannel::requestError(QNetworkReply *reply) {
245     emit error(reply->errorString());
246     qWarning() << reply->errorString();
247     loading = false;
248     loadingThumbnail = false;
249 }
250
251 void YTChannel::storeInfo() {
252     if (channelId.isEmpty()) return;
253     QSqlDatabase db = Database::instance().getConnection();
254     QSqlQuery query(db);
255     query.prepare("update subscriptions set "
256                   "user_name=?, name=?, description=?, thumb_url=?, loaded=? "
257                   "where user_id=?");
258     qDebug() << userName;
259     query.bindValue(0, userName);
260     query.bindValue(1, displayName);
261     query.bindValue(2, description);
262     query.bindValue(3, thumbnailUrl);
263     query.bindValue(4, QDateTime::currentDateTime().toTime_t());
264     query.bindValue(5, channelId);
265     bool success = query.exec();
266     if (!success) qWarning() << query.lastQuery() << query.lastError().text();
267
268     loadThumbnail();
269 }
270
271 void YTChannel::subscribe(const QString &channelId) {
272     if (channelId.isEmpty()) return;
273
274     uint now = QDateTime::currentDateTime().toTime_t();
275
276     QSqlDatabase db = Database::instance().getConnection();
277     QSqlQuery query(db);
278     query.prepare("insert into subscriptions "
279                   "(user_id,added,watched,checked,views,notify_count)"
280                   " values (?,?,?,0,0,0)");
281     query.bindValue(0, channelId);
282     query.bindValue(1, now);
283     query.bindValue(2, now);
284     bool success = query.exec();
285     if (!success) qWarning() << query.lastQuery() << query.lastError().text();
286
287     // This will call maybeLoadFromApi
288     YTChannel::forId(channelId);
289 }
290
291 void YTChannel::unsubscribe(const QString &channelId) {
292     if (channelId.isEmpty()) return;
293     QSqlDatabase db = Database::instance().getConnection();
294     QSqlQuery query(db);
295     query.prepare("delete from subscriptions where user_id=?");
296     query.bindValue(0, channelId);
297     bool success = query.exec();
298     if (!success) qWarning() << query.lastQuery() << query.lastError().text();
299
300     query = QSqlQuery(db);
301     query.prepare("delete from subscriptions_videos where user_id=?");
302     query.bindValue(0, channelId);
303     success = query.exec();
304     if (!success) qWarning() << query.lastQuery() << query.lastError().text();
305
306     YTChannel *user = cache.take(channelId);
307     if (user) user->deleteLater();
308 }
309
310 bool YTChannel::isSubscribed(const QString &channelId) {
311     if (!Database::exists()) return false;
312     if (channelId.isEmpty()) return false;
313     QSqlDatabase db = Database::instance().getConnection();
314     QSqlQuery query(db);
315     query.prepare("select count(*) from subscriptions where user_id=?");
316     query.bindValue(0, channelId);
317     bool success = query.exec();
318     if (!success) qWarning() << query.lastQuery() << query.lastError().text();
319     if (query.next())
320         return query.value(0).toInt() > 0;
321     return false;
322 }
323
324 void YTChannel::updateChecked() {
325     if (channelId.isEmpty()) return;
326
327     uint now = QDateTime::currentDateTime().toTime_t();
328     checked = now;
329
330     QSqlDatabase db = Database::instance().getConnection();
331     QSqlQuery query(db);
332     query.prepare("update subscriptions set checked=? where user_id=?");
333     query.bindValue(0, now);
334     query.bindValue(1, channelId);
335     bool success = query.exec();
336     if (!success) qWarning() << query.lastQuery() << query.lastError().text();
337 }
338
339 void YTChannel::updateWatched() {
340     if (channelId.isEmpty()) return;
341
342     uint now = QDateTime::currentDateTime().toTime_t();
343     watched = now;
344     notifyCount = 0;
345     emit notifyCountChanged();
346
347     QSqlDatabase db = Database::instance().getConnection();
348     QSqlQuery query(db);
349     query.prepare("update subscriptions set watched=?, notify_count=0, views=views+1 where user_id=?");
350     query.bindValue(0, now);
351     query.bindValue(1, channelId);
352     bool success = query.exec();
353     if (!success) qWarning() << query.lastQuery() << query.lastError().text();
354 }
355
356 void YTChannel::storeNotifyCount(int count) {
357     if (notifyCount != count)
358         emit notifyCountChanged();
359     notifyCount = count;
360
361     QSqlDatabase db = Database::instance().getConnection();
362     QSqlQuery query(db);
363     query.prepare("update subscriptions set notify_count=? where user_id=?");
364     query.bindValue(0, count);
365     query.bindValue(1, channelId);
366     bool success = query.exec();
367     if (!success) qWarning() << query.lastQuery() << query.lastError().text();
368 }
369
370 bool YTChannel::updateNotifyCount() {
371     QSqlDatabase db = Database::instance().getConnection();
372     QSqlQuery query(db);
373     query.prepare("select count(*) from subscriptions_videos "
374                   "where channel_id=? and added>? and published>? and watched=0");
375     query.bindValue(0, id);
376     query.bindValue(1, watched);
377     query.bindValue(2, watched);
378     bool success = query.exec();
379     if (!success) qWarning() << query.lastQuery() << query.lastError().text();
380     if (!query.next()) {
381         qWarning() << __PRETTY_FUNCTION__ << "Count failed";
382         return false;
383     }
384     int count = query.value(0).toInt();
385     storeNotifyCount(count);
386     return count != notifyCount;
387 }