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"
24 #include "httputils.h"
29 #include "iconutils.h"
31 YTChannel::YTChannel(const QString &channelId, QObject *parent)
32 : QObject(parent), id(0), channelId(channelId), loadingThumbnail(false), notifyCount(0),
33 checked(0), watched(0), loaded(0), loading(false) {}
35 QHash<QString, YTChannel *> YTChannel::cache;
37 YTChannel *YTChannel::forId(const QString &channelId) {
38 if (channelId.isEmpty()) return 0;
40 auto i = cache.constFind(channelId);
41 if (i != cache.constEnd()) return i.value();
43 QSqlDatabase db = Database::instance().getConnection();
45 query.prepare("select id,name,description,thumb_url,notify_count,watched,checked,loaded "
46 "from subscriptions where user_id=?");
47 query.bindValue(0, channelId);
48 bool success = query.exec();
49 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
51 YTChannel *channel = 0;
53 // Change userId to ChannelId
55 channel = new YTChannel(channelId);
56 channel->id = query.value(0).toInt();
57 channel->displayName = query.value(1).toString();
58 channel->description = query.value(2).toString();
59 channel->thumbnailUrl = query.value(3).toString();
60 channel->notifyCount = query.value(4).toInt();
61 channel->watched = query.value(5).toUInt();
62 channel->checked = query.value(6).toUInt();
63 channel->loaded = query.value(7).toUInt();
64 channel->thumbnail = QPixmap(channel->getThumbnailLocation());
65 channel->thumbnail.setDevicePixelRatio(2.0);
66 channel->maybeLoadfromAPI();
67 cache.insert(channelId, channel);
73 void YTChannel::maybeLoadfromAPI() {
75 if (channelId.isEmpty()) return;
77 uint now = QDateTime::currentDateTime().toTime_t();
78 static const int refreshInterval = 60 * 60 * 24 * 10;
79 if (loaded > now - refreshInterval) return;
83 QUrl url = YT3::instance().method("channels");
85 q.addQueryItem("id", channelId);
86 q.addQueryItem("part", "snippet");
89 QObject *reply = HttpUtils::yt().get(url);
90 connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResponse(QByteArray)));
91 connect(reply, SIGNAL(error(QString)), SLOT(requestError(QString)));
94 void YTChannel::parseResponse(const QByteArray &bytes) {
95 QJsonDocument doc = QJsonDocument::fromJson(bytes);
96 QJsonObject obj = doc.object();
97 const QJsonArray items = obj["items"].toArray();
98 for (const QJsonValue &v : items) {
99 QJsonObject item = v.toObject();
100 QJsonObject snippet = item["snippet"].toObject();
101 displayName = snippet["title"].toString();
102 description = snippet["description"].toString();
103 QJsonObject thumbnails = snippet["thumbnails"].toObject();
104 thumbnailUrl = thumbnails["medium"].toObject()["url"].toString();
105 qDebug() << displayName << description << thumbnailUrl;
113 void YTChannel::loadThumbnail() {
114 if (loadingThumbnail) return;
115 if (thumbnailUrl.isEmpty()) return;
116 loadingThumbnail = true;
118 QUrl url(thumbnailUrl);
119 QObject *reply = HttpUtils::yt().get(url);
120 connect(reply, SIGNAL(data(QByteArray)), SLOT(storeThumbnail(QByteArray)));
121 connect(reply, SIGNAL(error(QString)), SLOT(requestError(QString)));
124 const QString &YTChannel::getThumbnailDir() {
125 static const QString thumbDir =
126 QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/channels/";
130 QString YTChannel::getThumbnailLocation() {
131 return getThumbnailDir() + channelId;
134 QString YTChannel::latestVideoId() {
135 QSqlDatabase db = Database::instance().getConnection();
137 query.prepare("select video_id from subscriptions_videos where user_id=? order by published "
139 query.bindValue(0, channelId);
140 bool success = query.exec();
141 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
142 if (!query.next()) return QString();
143 return query.value(0).toString();
146 void YTChannel::unsubscribe() {
147 YTChannel::unsubscribe(channelId);
150 void YTChannel::storeThumbnail(const QByteArray &bytes) {
151 thumbnail.loadFromData(bytes);
152 qreal maxRatio = 2.0;
153 thumbnail.setDevicePixelRatio(maxRatio);
154 const int maxWidth = 88 * maxRatio;
157 dir.mkpath(getThumbnailDir());
159 if (thumbnail.width() > maxWidth) {
160 thumbnail = thumbnail.scaledToWidth(maxWidth, Qt::SmoothTransformation);
161 thumbnail.setDevicePixelRatio(maxRatio);
162 thumbnail.save(getThumbnailLocation(), "JPG");
164 QFile file(getThumbnailLocation());
165 if (!file.open(QIODevice::WriteOnly))
166 qWarning() << "Error opening file for writing" << file.fileName();
167 QDataStream stream(&file);
168 stream.writeRawData(bytes.constData(), bytes.size());
171 emit thumbnailLoaded();
172 loadingThumbnail = false;
175 void YTChannel::requestError(const QString &message) {
177 qWarning() << message;
179 loadingThumbnail = false;
182 void YTChannel::storeInfo() {
183 if (channelId.isEmpty()) return;
184 QSqlDatabase db = Database::instance().getConnection();
186 query.prepare("update subscriptions set "
187 "user_name=?, name=?, description=?, thumb_url=?, loaded=? "
189 qDebug() << userName;
190 query.bindValue(0, userName);
191 query.bindValue(1, displayName);
192 query.bindValue(2, description);
193 query.bindValue(3, thumbnailUrl);
194 query.bindValue(4, QDateTime::currentDateTime().toTime_t());
195 query.bindValue(5, channelId);
196 bool success = query.exec();
197 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
202 void YTChannel::subscribe(const QString &channelId) {
203 if (channelId.isEmpty()) return;
205 uint now = QDateTime::currentDateTime().toTime_t();
207 QSqlDatabase db = Database::instance().getConnection();
209 query.prepare("insert into subscriptions "
210 "(user_id,added,watched,checked,views,notify_count)"
211 " values (?,?,?,0,0,0)");
212 query.bindValue(0, channelId);
213 query.bindValue(1, now);
214 query.bindValue(2, now);
215 bool success = query.exec();
216 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
218 // This will call maybeLoadFromApi
219 YTChannel::forId(channelId);
222 void YTChannel::unsubscribe(const QString &channelId) {
223 if (channelId.isEmpty()) return;
224 QSqlDatabase db = Database::instance().getConnection();
226 query.prepare("delete from subscriptions where user_id=?");
227 query.bindValue(0, channelId);
228 bool success = query.exec();
229 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
231 query = QSqlQuery(db);
232 query.prepare("delete from subscriptions_videos where user_id=?");
233 query.bindValue(0, channelId);
234 success = query.exec();
235 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
237 YTChannel *user = cache.take(channelId);
238 if (user) user->deleteLater();
241 bool YTChannel::isSubscribed(const QString &channelId) {
242 if (!Database::exists()) return false;
243 if (channelId.isEmpty()) return false;
244 QSqlDatabase db = Database::instance().getConnection();
246 query.prepare("select count(*) from subscriptions where user_id=?");
247 query.bindValue(0, channelId);
248 bool success = query.exec();
249 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
250 if (query.next()) return query.value(0).toInt() > 0;
254 void YTChannel::updateChecked() {
255 if (channelId.isEmpty()) return;
257 uint now = QDateTime::currentDateTime().toTime_t();
260 QSqlDatabase db = Database::instance().getConnection();
262 query.prepare("update subscriptions set checked=? where user_id=?");
263 query.bindValue(0, now);
264 query.bindValue(1, channelId);
265 bool success = query.exec();
266 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
269 void YTChannel::updateWatched() {
270 if (channelId.isEmpty()) return;
272 uint now = QDateTime::currentDateTime().toTime_t();
275 emit notifyCountChanged();
277 QSqlDatabase db = Database::instance().getConnection();
280 "update subscriptions set watched=?, notify_count=0, views=views+1 where user_id=?");
281 query.bindValue(0, now);
282 query.bindValue(1, channelId);
283 bool success = query.exec();
284 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
287 void YTChannel::storeNotifyCount(int count) {
288 if (notifyCount != count) emit notifyCountChanged();
291 QSqlDatabase db = Database::instance().getConnection();
293 query.prepare("update subscriptions set notify_count=? where user_id=?");
294 query.bindValue(0, count);
295 query.bindValue(1, channelId);
296 bool success = query.exec();
297 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
300 bool YTChannel::updateNotifyCount() {
301 QSqlDatabase db = Database::instance().getConnection();
303 query.prepare("select count(*) from subscriptions_videos "
304 "where channel_id=? and added>? and published>? and watched=0");
305 query.bindValue(0, id);
306 query.bindValue(1, watched);
307 query.bindValue(2, watched);
308 bool success = query.exec();
309 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
311 qWarning() << __PRETTY_FUNCTION__ << "Count failed";
314 int count = query.value(0).toInt();
315 storeNotifyCount(count);
316 return count != notifyCount;