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"
30 #include "compatibility/qurlqueryhelper.h"
31 #include "compatibility/pathsservice.h"
32 #include "iconutils.h"
35 NetworkAccess* http();
38 YTChannel::YTChannel(const QString &channelId, QObject *parent) : QObject(parent),
41 loadingThumbnail(false),
48 QHash<QString, YTChannel*> YTChannel::cache;
50 YTChannel* YTChannel::forId(const QString &channelId) {
51 if (channelId.isEmpty()) return 0;
53 if (cache.contains(channelId))
54 return cache.value(channelId);
56 QSqlDatabase db = Database::instance().getConnection();
58 query.prepare("select id,name,description,thumb_url,notify_count,watched,checked,loaded "
59 "from subscriptions where user_id=?");
60 query.bindValue(0, channelId);
61 bool success = query.exec();
62 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
64 YTChannel* channel = 0;
66 // Change userId to ChannelId
68 channel = new YTChannel(channelId);
69 channel->id = query.value(0).toInt();
70 channel->displayName = query.value(1).toString();
71 channel->description = query.value(2).toString();
72 channel->thumbnailUrl = query.value(3).toString();
73 channel->notifyCount = query.value(4).toInt();
74 channel->watched = query.value(5).toUInt();
75 channel->checked = query.value(6).toUInt();
76 channel->loaded = query.value(7).toUInt();
77 channel->thumbnail = QPixmap(channel->getThumbnailLocation());
78 channel->thumbnail.setDevicePixelRatio(IconUtils::maxSupportedPixelRatio());
79 channel->maybeLoadfromAPI();
80 cache.insert(channelId, channel);
86 void YTChannel::maybeLoadfromAPI() {
88 if (channelId.isEmpty()) return;
90 uint now = QDateTime::currentDateTime().toTime_t();
91 static const int refreshInterval = 60 * 60 * 24 * 10;
92 if (loaded > now - refreshInterval) return;
98 QUrl url = YT3::instance().method("channels");
100 QUrlQueryHelper urlHelper(url);
101 urlHelper.addQueryItem("id", channelId);
102 urlHelper.addQueryItem("part", "snippet");
107 QUrl url("http://gdata.youtube.com/feeds/api/users/" + channelId);
109 QUrlQueryHelper urlHelper(url);
110 urlHelper.addQueryItem("v", "2");
115 QObject *reply = The::http()->get(url);
116 connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResponse(QByteArray)));
117 connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
122 void YTChannel::parseResponse(const QByteArray &bytes) {
123 QScriptEngine engine;
124 QScriptValue json = engine.evaluate("(" + QString::fromUtf8(bytes) + ")");
125 QScriptValue items = json.property("items");
126 if (items.isArray()) {
127 QScriptValueIterator it(items);
128 while (it.hasNext()) {
130 QScriptValue item = it.value();
131 // For some reason the array has an additional element containing its size.
132 if (item.isObject()) {
133 QScriptValue snippet = item.property("snippet");
134 displayName = snippet.property("title").toString();
135 description = snippet.property("description").toString();
136 QScriptValue thumbnails = snippet.property("thumbnails");
137 thumbnailUrl = thumbnails.property("medium").property("url").toString();
138 qDebug() << displayName << description << thumbnailUrl;
150 void YTChannel::parseResponse(const QByteArray &bytes) {
151 QXmlStreamReader xml(bytes);
152 xml.readNextStartElement();
153 if (xml.name() == QLatin1String("entry"))
154 while(xml.readNextStartElement()) {
155 const QStringRef n = xml.name();
156 if (n == QLatin1String("summary"))
157 description = xml.readElementText().simplified();
158 else if (n == QLatin1String("title"))
159 displayName = xml.readElementText();
160 else if (n == QLatin1String("thumbnail")) {
161 thumbnailUrl = xml.attributes().value("url").toString();
162 xml.skipCurrentElement();
163 } else if (n == QLatin1String("username"))
164 userName = xml.readElementText();
165 else xml.skipCurrentElement();
168 if (xml.hasError()) {
169 emit error(xml.errorString());
170 qWarning() << xml.errorString();
180 void YTChannel::loadThumbnail() {
181 if (loadingThumbnail) return;
182 if (thumbnailUrl.isEmpty()) return;
183 loadingThumbnail = true;
185 QUrl url(thumbnailUrl);
186 QObject *reply = The::http()->get(url);
187 connect(reply, SIGNAL(data(QByteArray)), SLOT(storeThumbnail(QByteArray)));
188 connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
191 const QString & YTChannel::getThumbnailDir() {
192 static const QString thumbDir = Paths::getCacheLocation() + "/channels/";
196 QString YTChannel::getThumbnailLocation() {
197 return getThumbnailDir() + channelId;
200 QString YTChannel::latestVideoId() {
201 QSqlDatabase db = Database::instance().getConnection();
203 query.prepare("select video_id from subscriptions_videos where user_id=? order by published desc limit 1");
204 query.bindValue(0, channelId);
205 bool success = query.exec();
206 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
207 if (!query.next()) return QString();
208 return query.value(0).toString();
211 void YTChannel::unsubscribe() {
212 YTChannel::unsubscribe(channelId);
215 void YTChannel::storeThumbnail(const QByteArray &bytes) {
216 thumbnail.loadFromData(bytes);
217 qreal maxRatio = IconUtils::maxSupportedPixelRatio();
218 thumbnail.setDevicePixelRatio(maxRatio);
219 const int maxWidth = 88 * maxRatio;
222 dir.mkpath(getThumbnailDir());
224 if (thumbnail.width() > maxWidth) {
225 thumbnail = thumbnail.scaledToWidth(maxWidth, Qt::SmoothTransformation);
226 thumbnail.setDevicePixelRatio(maxRatio);
227 thumbnail.save(getThumbnailLocation(), "JPG");
229 QFile file(getThumbnailLocation());
230 if (!file.open(QIODevice::WriteOnly))
231 qWarning() << "Error opening file for writing" << file.fileName();
232 QDataStream stream(&file);
233 stream.writeRawData(bytes.constData(), bytes.size());
236 emit thumbnailLoaded();
237 loadingThumbnail = false;
240 void YTChannel::requestError(QNetworkReply *reply) {
241 emit error(reply->errorString());
242 qWarning() << reply->errorString();
244 loadingThumbnail = false;
247 void YTChannel::storeInfo() {
248 if (channelId.isEmpty()) return;
249 QSqlDatabase db = Database::instance().getConnection();
251 query.prepare("update subscriptions set "
252 "user_name=?, name=?, description=?, thumb_url=?, loaded=? "
254 qDebug() << userName;
255 query.bindValue(0, userName);
256 query.bindValue(1, displayName);
257 query.bindValue(2, description);
258 query.bindValue(3, thumbnailUrl);
259 query.bindValue(4, QDateTime::currentDateTime().toTime_t());
260 query.bindValue(5, channelId);
261 bool success = query.exec();
262 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
267 void YTChannel::subscribe(const QString &channelId) {
268 if (channelId.isEmpty()) return;
270 uint now = QDateTime::currentDateTime().toTime_t();
272 QSqlDatabase db = Database::instance().getConnection();
274 query.prepare("insert into subscriptions "
275 "(user_id,added,watched,checked,views,notify_count)"
276 " values (?,?,?,0,0,0)");
277 query.bindValue(0, channelId);
278 query.bindValue(1, now);
279 query.bindValue(2, now);
280 bool success = query.exec();
281 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
283 // This will call maybeLoadFromApi
284 YTChannel::forId(channelId);
287 void YTChannel::unsubscribe(const QString &channelId) {
288 if (channelId.isEmpty()) return;
289 QSqlDatabase db = Database::instance().getConnection();
291 query.prepare("delete from subscriptions where user_id=?");
292 query.bindValue(0, channelId);
293 bool success = query.exec();
294 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
296 query = QSqlQuery(db);
297 query.prepare("delete from subscriptions_videos where user_id=?");
298 query.bindValue(0, channelId);
299 success = query.exec();
300 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
302 YTChannel *user = cache.take(channelId);
303 if (user) user->deleteLater();
306 bool YTChannel::isSubscribed(const QString &channelId) {
307 if (!Database::exists()) return false;
308 if (channelId.isEmpty()) return false;
309 QSqlDatabase db = Database::instance().getConnection();
311 query.prepare("select count(*) from subscriptions where user_id=?");
312 query.bindValue(0, channelId);
313 bool success = query.exec();
314 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
316 return query.value(0).toInt() > 0;
320 void YTChannel::updateChecked() {
321 if (channelId.isEmpty()) return;
323 uint now = QDateTime::currentDateTime().toTime_t();
326 QSqlDatabase db = Database::instance().getConnection();
328 query.prepare("update subscriptions set checked=? where user_id=?");
329 query.bindValue(0, now);
330 query.bindValue(1, channelId);
331 bool success = query.exec();
332 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
335 void YTChannel::updateWatched() {
336 if (channelId.isEmpty()) return;
338 uint now = QDateTime::currentDateTime().toTime_t();
341 emit notifyCountChanged();
343 QSqlDatabase db = Database::instance().getConnection();
345 query.prepare("update subscriptions set watched=?, notify_count=0, views=views+1 where user_id=?");
346 query.bindValue(0, now);
347 query.bindValue(1, channelId);
348 bool success = query.exec();
349 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
352 void YTChannel::storeNotifyCount(int count) {
353 if (notifyCount != count)
354 emit notifyCountChanged();
357 QSqlDatabase db = Database::instance().getConnection();
359 query.prepare("update subscriptions set notify_count=? where user_id=?");
360 query.bindValue(0, count);
361 query.bindValue(1, channelId);
362 bool success = query.exec();
363 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
366 bool YTChannel::updateNotifyCount() {
367 QSqlDatabase db = Database::instance().getConnection();
369 query.prepare("select count(*) from subscriptions_videos "
370 "where channel_id=? and added>? and published>? and watched=0");
371 query.bindValue(0, id);
372 query.bindValue(1, watched);
373 query.bindValue(2, watched);
374 bool success = query.exec();
375 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
377 qWarning() << __PRETTY_FUNCTION__ << "Count failed";
380 int count = query.value(0).toInt();
381 storeNotifyCount(count);
382 return count != notifyCount;