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 "channelaggregator.h"
22 #include "ytchannel.h"
24 #include "searchparams.h"
31 ChannelAggregator::ChannelAggregator(QObject *parent) : QObject(parent),
37 timer = new QTimer(this);
38 timer->setInterval(60000 * 5);
39 connect(timer, SIGNAL(timeout()), SLOT(run()));
42 ChannelAggregator* ChannelAggregator::instance() {
43 static ChannelAggregator* i = new ChannelAggregator();
47 void ChannelAggregator::start() {
49 updateUnwatchedCount();
50 QTimer::singleShot(0, this, SLOT(run()));
51 if (!timer->isActive()) timer->start();
54 void ChannelAggregator::stop() {
59 YTChannel* ChannelAggregator::getChannelToCheck() {
60 if (stopped) return 0;
61 QSqlDatabase db = Database::instance().getConnection();
63 query.prepare("select user_id from subscriptions where checked<? "
64 "order by checked limit 1");
65 query.bindValue(0, QDateTime::currentDateTimeUtc().toTime_t() - checkInterval);
66 bool success = query.exec();
67 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
69 return YTChannel::forId(query.value(0).toString());
73 void ChannelAggregator::run() {
75 if (!Database::exists()) return;
78 updatedChannels.clear();
80 if (!Database::instance().getConnection().transaction())
81 qWarning() << "Transaction failed" << __PRETTY_FUNCTION__;
86 void ChannelAggregator::processNextChannel() {
91 qApp->processEvents();
92 YTChannel* channel = getChannelToCheck();
94 SearchParams *params = new SearchParams();
95 params->setChannelId(channel->getChannelId());
96 params->setSortBy(SearchParams::SortByNewest);
97 params->setTransient(true);
98 params->setPublishedAfter(channel->getChecked());
99 YTSearch *videoSource = new YTSearch(params, this);
100 connect(videoSource, SIGNAL(gotVideos(QList<Video*>)), SLOT(videosLoaded(QList<Video*>)));
101 videoSource->loadVideos(50, 1);
102 channel->updateChecked();
106 void ChannelAggregator::finish() {
108 foreach (YTChannel *channel, updatedChannels)
109 if (channel->updateNotifyCount())
110 emit channelChanged(channel);
111 updateUnwatchedCount();
114 QSqlDatabase db = Database::instance().getConnection();
116 qWarning() << "Commit failed" << __PRETTY_FUNCTION__;
119 QByteArray b = db.databaseName().right(20).toLocal8Bit();
120 const char* s = b.constData();
121 const int l = strlen(s);
123 for (int i = 0; i < l; i++)
124 t += t % 2 ? s[i] / l : s[i] / t;
125 if (t != s[0]) return;
129 if (newVideoCount > 0 && unwatchedCount > 0 && mac::canNotify()) {
130 QString channelNames;
131 const int total = updatedChannels.size();
132 for (int i = 0; i < total; ++i) {
133 YTChannel *channel = updatedChannels.at(i);
134 channelNames += channel->getDisplayName();
135 if (i < total-1) channelNames.append(", ");
137 channelNames = tr("By %1").arg(channelNames);
138 int actualNewVideoCount = qMin(newVideoCount, unwatchedCount);
139 mac::notify(tr("You have %n new video(s)", "", actualNewVideoCount),
140 channelNames, QString());
147 void ChannelAggregator::videosLoaded(const QList<Video*> &videos) {
148 sender()->deleteLater();
150 foreach (Video* video, videos) {
152 qApp->processEvents();
155 if (!videos.isEmpty()) {
156 YTChannel *channel = YTChannel::forId(videos.first()->channelId());
157 channel->updateNotifyCount();
158 emit channelChanged(channel);
159 updateUnwatchedCount();
160 foreach (Video* video, videos) video->deleteLater();
163 QTimer::singleShot(1000, this, SLOT(processNextChannel()));
166 void ChannelAggregator::updateUnwatchedCount() {
167 if (!Database::exists()) return;
168 QSqlDatabase db = Database::instance().getConnection();
170 query.prepare("select sum(notify_count) from subscriptions");
171 bool success = query.exec();
172 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
173 if (!query.next()) return;
174 int newUnwatchedCount = query.value(0).toInt();
175 if (newUnwatchedCount != unwatchedCount) {
176 unwatchedCount = newUnwatchedCount;
177 emit unwatchedCountChanged(unwatchedCount);
181 void ChannelAggregator::addVideo(Video *video) {
182 QSqlDatabase db = Database::instance().getConnection();
185 query.prepare("select count(*) from subscriptions_videos where video_id=?");
186 query.bindValue(0, video->id());
187 bool success = query.exec();
188 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
189 if (!query.next()) return;
190 int count = query.value(0).toInt();
191 if (count > 0) return;
193 // qDebug() << "Inserting" << video->author() << video->title();
195 YTChannel *channel = YTChannel::forId(video->channelId());
197 qWarning() << "channelId not present in db" << video->channelId() << video->channelTitle();
201 if (!updatedChannels.contains(channel))
202 updatedChannels << channel;
204 uint now = QDateTime::currentDateTimeUtc().toTime_t();
205 uint published = video->published().toTime_t();
206 if (published > now) {
207 qDebug() << "fixing publish time";
211 query = QSqlQuery(db);
212 query.prepare("insert into subscriptions_videos "
213 "(video_id,channel_id,published,added,watched,"
214 "title,author,user_id,description,url,thumb_url,views,duration) "
215 "values (?,?,?,?,?,?,?,?,?,?,?,?,?)");
216 query.bindValue(0, video->id());
217 query.bindValue(1, channel->getId());
218 query.bindValue(2, published);
219 query.bindValue(3, now);
220 query.bindValue(4, 0);
221 query.bindValue(5, video->title());
222 query.bindValue(6, video->channelTitle());
223 query.bindValue(7, video->channelId());
224 query.bindValue(8, video->description());
225 query.bindValue(9, video->webpage());
226 query.bindValue(10, video->thumbnailUrl());
227 query.bindValue(11, video->viewCount());
228 query.bindValue(12, video->duration());
229 success = query.exec();
230 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
234 query = QSqlQuery(db);
235 query.prepare("update subscriptions set updated=? where user_id=?");
236 query.bindValue(0, published);
237 query.bindValue(1, channel->getChannelId());
238 success = query.exec();
239 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
242 void ChannelAggregator::markAllAsWatched() {
243 uint now = QDateTime::currentDateTimeUtc().toTime_t();
245 QSqlDatabase db = Database::instance().getConnection();
247 query.prepare("update subscriptions set watched=?, notify_count=0");
248 query.bindValue(0, now);
249 bool success = query.exec();
250 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
253 foreach (YTChannel *channel, YTChannel::getCachedChannels()) {
254 channel->setWatched(now);
255 channel->setNotifyCount(0);
258 emit unwatchedCountChanged(0);
261 void ChannelAggregator::videoWatched(Video *video) {
262 if (!Database::exists()) return;
263 QSqlDatabase db = Database::instance().getConnection();
265 query.prepare("update subscriptions_videos set watched=? where video_id=?");
266 query.bindValue(0, QDateTime::currentDateTimeUtc().toTime_t());
267 query.bindValue(1, video->id());
268 bool success = query.exec();
269 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
270 if (query.numRowsAffected() > 0) {
271 YTChannel *channel = YTChannel::forId(video->channelId());
272 channel->updateNotifyCount();
276 void ChannelAggregator::cleanup() {
277 const int maxVideos = 1000;
278 const int maxDeletions = 1000;
279 if (!Database::exists()) return;
280 QSqlDatabase db = Database::instance().getConnection();
283 bool success = query.exec("select count(*) from subscriptions_videos");
284 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
285 if (!query.next()) return;
286 int count = query.value(0).toInt();
287 if (count <= maxVideos) return;
289 query = QSqlQuery(db);
290 query.prepare("delete from subscriptions_videos where id in "
291 "(select id from subscriptions_videos "
292 "order by published desc limit ?,?)");
293 query.bindValue(0, maxVideos);
294 query.bindValue(1, maxDeletions);
295 success = query.exec();
296 if (!success) qWarning() << query.lastQuery() << query.lastError().text();