3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
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.
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.
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/>.
21 #include "ytchannel.h"
22 #include "networkaccess.h"
32 NetworkAccess* http();
35 YTChannel::YTChannel(const QString &channelId, QObject *parent) : QObject(parent),
38 loadingThumbnail(false),
45 QHash<QString, YTChannel*> YTChannel::cache;
47 YTChannel* YTChannel::forId(const QString &channelId) {
48 if (channelId.isEmpty()) return 0;
50 if (cache.contains(channelId))
51 return cache.value(channelId);
53 QSqlDatabase db = Database::instance().getConnection();
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();
61 YTChannel* channel = 0;
63 // Change userId to ChannelId
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);
82 void YTChannel::maybeLoadfromAPI() {
84 if (channelId.isEmpty()) return;
86 uint now = QDateTime::currentDateTime().toTime_t();
87 static const int refreshInterval = 60 * 60 * 24 * 10;
88 if (loaded > now - refreshInterval) return;
94 QUrl url = YT3::instance().method("channels");
95 #if QT_VERSION >= 0x050000
100 url.addQueryItem("id", channelId);
101 url.addQueryItem("part", "snippet");
102 #if QT_VERSION >= 0x050000
109 QUrl url("http://gdata.youtube.com/feeds/api/users/" + channelId);
110 #if QT_VERSION >= 0x050000
115 url.addQueryItem("v", "2");
116 #if QT_VERSION >= 0x050000
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*)));
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()) {
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;
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();
176 if (xml.hasError()) {
177 emit error(xml.errorString());
178 qWarning() << xml.errorString();
188 void YTChannel::loadThumbnail() {
189 if (loadingThumbnail) return;
190 if (thumbnailUrl.isEmpty()) return;
191 loadingThumbnail = true;
194 thumbnailUrl.replace(QLatin1String("https://"), QLatin1String("http://"));
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*)));
203 const QString & YTChannel::getThumbnailDir() {
204 static const QString thumbDir =
205 #if QT_VERSION >= 0x050000
206 QStandardPaths::writableLocation(QStandardPaths::DataLocation)
208 QDesktopServices::storageLocation(QDesktopServices::DataLocation)
214 QString YTChannel::getThumbnailLocation() {
215 return getThumbnailDir() + channelId;
218 void YTChannel::unsubscribe() {
219 YTChannel::unsubscribe(channelId);
222 void YTChannel::storeThumbnail(const QByteArray &bytes) {
223 thumbnail.loadFromData(bytes);
224 static const int maxWidth = 88;
227 dir.mkpath(getThumbnailDir());
229 if (thumbnail.width() > maxWidth) {
230 thumbnail = thumbnail.scaledToWidth(maxWidth, Qt::SmoothTransformation);
231 thumbnail.save(getThumbnailLocation(), "JPG");
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());
240 emit thumbnailLoaded();
241 loadingThumbnail = false;
244 void YTChannel::requestError(QNetworkReply *reply) {
245 emit error(reply->errorString());
246 qWarning() << reply->errorString();
248 loadingThumbnail = false;
251 void YTChannel::storeInfo() {
252 if (channelId.isEmpty()) return;
253 QSqlDatabase db = Database::instance().getConnection();
255 query.prepare("update subscriptions set "
256 "user_name=?, name=?, description=?, thumb_url=?, loaded=? "
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();
271 void YTChannel::subscribe(const QString &channelId) {
272 if (channelId.isEmpty()) return;
274 uint now = QDateTime::currentDateTime().toTime_t();
276 QSqlDatabase db = Database::instance().getConnection();
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();
287 // This will call maybeLoadFromApi
288 YTChannel::forId(channelId);
291 void YTChannel::unsubscribe(const QString &channelId) {
292 if (channelId.isEmpty()) return;
293 QSqlDatabase db = Database::instance().getConnection();
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();
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();
306 YTChannel *user = cache.take(channelId);
307 if (user) user->deleteLater();
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();
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();
320 return query.value(0).toInt() > 0;
324 void YTChannel::updateChecked() {
325 if (channelId.isEmpty()) return;
327 uint now = QDateTime::currentDateTime().toTime_t();
330 QSqlDatabase db = Database::instance().getConnection();
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();
339 void YTChannel::updateWatched() {
340 if (channelId.isEmpty()) return;
342 uint now = QDateTime::currentDateTime().toTime_t();
345 emit notifyCountChanged();
347 QSqlDatabase db = Database::instance().getConnection();
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();
356 void YTChannel::storeNotifyCount(int count) {
357 if (notifyCount != count)
358 emit notifyCountChanged();
361 QSqlDatabase db = Database::instance().getConnection();
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();
370 bool YTChannel::updateNotifyCount() {
371 QSqlDatabase db = Database::instance().getConnection();
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();
381 qWarning() << __PRETTY_FUNCTION__ << "Count failed";
384 int count = query.value(0).toInt();
385 storeNotifyCount(count);
386 return count != notifyCount;