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 "downloaditem.h"
23 #include "httputils.h"
26 #include <QDesktopServices>
33 DownloadItem::DownloadItem(Video *video, QUrl url, QString filename, QObject *parent)
36 , m_startedSaving(false)
37 , m_finishedDownloading(false)
40 , sendStatusChanges(true)
46 speedCheckTimer = new QTimer(this);
47 speedCheckTimer->setInterval(2000);
48 speedCheckTimer->setSingleShot(true);
49 connect(speedCheckTimer, SIGNAL(timeout()), SLOT(speedCheck()));
55 DownloadItem::~DownloadItem() {
66 bool DownloadItem::needsDownload(qint64 offset) {
67 return offset < m_offset || offset > m_offset + m_bytesReceived;
70 bool DownloadItem::isBuffered(qint64 offset) {
71 QMap<qint64, qint64>::iterator i;
72 for (i = buffers.begin(); i != buffers.end(); ++i) {
73 if (offset >= i.key() && offset <= i.value()) {
74 // qDebug() << "Buffered! " << i.key() << ":" << i.value();
78 // qDebug() << offset << "is not buffered";
82 qint64 DownloadItem::blankAtOffset(qint64 offset) {
83 // qDebug() << buffers;
84 QMap<qint64, qint64>::iterator i;
85 for (i = buffers.begin(); i != buffers.end(); ++i) {
86 if (offset >= i.key() && offset <= i.value()) {
87 // qDebug() << "Offset was" << offset << "now" << i.value();
94 void DownloadItem::seekTo(qint64 offset, bool sendStatusChanges) {
95 // qDebug() << __PRETTY_FUNCTION__ << offset << sendStatusChanges;
97 if (m_bytesReceived > 0) {
98 bool bufferModified = false;
99 QMap<qint64, qint64>::iterator i;
100 for (i = buffers.begin(); i != buffers.end(); ++i) {
101 if (m_offset - 1 <= i.value()) {
103 qDebug() << "Extending existing buffer "
104 << i.key() << i.value() << "now" << m_offset + m_bytesReceived;
106 bufferModified = true;
107 i.value() = m_offset + m_bytesReceived;
112 buffers.insert(m_offset, m_offset + m_bytesReceived);
115 this->sendStatusChanges = sendStatusChanges;
116 bool seekSuccess = m_file.seek(offset);
118 qWarning() << "Cannot seek to offset" << offset << "in file" << m_file.fileName();
124 void DownloadItem::start() {
125 // qDebug() << "Starting download at" << m_offset;
128 if (m_offset > 0) req.offset = m_offset;
129 m_reply = HttpUtils::yt().networkReply(req);
134 void DownloadItem::init() {
140 m_startedSaving = false;
141 m_finishedDownloading = false;
143 // attach to the m_reply
144 m_url = m_reply->url();
145 connect(m_reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead()));
146 connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
147 this, SLOT(error(QNetworkReply::NetworkError)));
148 connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)),
149 this, SLOT(downloadProgress(qint64, qint64)));
150 connect(m_reply, SIGNAL(metaDataChanged()),
151 this, SLOT(metaDataChanged()));
152 connect(m_reply, SIGNAL(finished()),
153 this, SLOT(requestFinished()));
155 // start timer for the download estimation
157 m_downloadTime.start();
158 speedCheckTimer->start();
160 if (m_reply->error() != QNetworkReply::NoError) {
161 error(m_reply->error());
167 void DownloadItem::stop() {
169 m_reply->disconnect();
171 m_reply->deleteLater();
175 emit statusChanged();
178 void DownloadItem::open() {
179 QFileInfo info(m_file);
180 QUrl url = QUrl::fromLocalFile(info.absoluteFilePath());
181 QDesktopServices::openUrl(url);
184 void DownloadItem::openFolder() {
185 QFileInfo info(m_file);
187 mac::showInFinder(info.absoluteFilePath());
189 QUrl url = QUrl::fromLocalFile(info.absolutePath());
190 QDesktopServices::openUrl(url);
194 void DownloadItem::tryAgain() {
199 void DownloadItem::downloadReadyRead() {
200 if (!m_reply) return;
202 if (!m_file.isOpen()) {
203 if (!m_file.open(QIODevice::ReadWrite)) {
204 qWarning() << QString("Error opening output file: %1").arg(m_file.errorString());
206 emit statusChanged();
209 emit statusChanged();
212 // qWarning() << __PRETTY_FUNCTION__ << m_file.pos();
213 if (-1 == m_file.write(m_reply->readAll())) {
214 qWarning() << "Error saving." << m_file.errorString();
217 m_startedSaving = true;
219 // if (m_finishedDownloading) requestFinished();
223 void DownloadItem::error(QNetworkReply::NetworkError) {
226 qWarning() << m_reply->errorString() << m_reply->url().toEncoded();
227 m_errorMessage = m_reply->errorString();
236 QString DownloadItem::errorMessage() const {
237 return m_errorMessage;
240 void DownloadItem::metaDataChanged() {
241 if (!m_reply) return;
242 QVariant locationHeader = m_reply->header(QNetworkRequest::LocationHeader);
243 if (locationHeader.isValid()) {
244 m_url = locationHeader.toUrl();
245 // qDebug() << "Redirecting to" << m_url;
249 // qDebug() << m_reply->rawHeaderList();
252 int DownloadItem::initialBufferSize() {
253 // qDebug() << video->getDefinitionCode();
254 switch (video->getDefinitionCode()) {
265 void DownloadItem::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
267 // qDebug() << __PRETTY_FUNCTION__ << bytesReceived << bytesTotal << m_downloadTime.elapsed();
269 m_bytesReceived = bytesReceived;
271 if (!sendStatusChanges) return;
273 if (m_lastProgressTime.elapsed() < 150) return;
274 m_lastProgressTime.start();
276 if (m_status != Downloading) {
278 int neededBytes = (int) (bytesTotal * .005);
279 int bufferSize = initialBufferSize();
280 if (bufferSize > bytesTotal) bufferSize = bytesTotal;
281 // qDebug() << bytesReceived << bytesTotal << neededBytes << bufferSize << m_downloadTime.elapsed();
282 if (bytesReceived > bufferSize
283 && bytesReceived > neededBytes
284 && m_downloadTime.elapsed() > 2000) {
285 emit bufferProgress(100);
286 m_status = Downloading;
287 emit statusChanged();
289 int bytes = qMax(bufferSize, neededBytes);
290 int bufferPercent = 0;
292 bufferPercent = bytesReceived * 100 / bytes;
293 emit bufferProgress(bufferPercent);
298 if (bytesTotal > 0) {
299 int percent = bytesReceived * 100 / bytesTotal;
300 if (percent != this->percent) {
301 this->percent = percent;
302 emit progress(percent);
309 void DownloadItem::speedCheck() {
310 if (!m_reply) return;
311 int bytesTotal = m_reply->size();
312 int bufferSize = initialBufferSize();
313 if (bufferSize > bytesTotal) bufferSize = 0;
314 if (m_bytesReceived < bufferSize / 3) {
318 qDebug() << "Retrying...";
319 connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
320 video->loadStreamUrl();
324 void DownloadItem::gotStreamUrl(QUrl /*streamUrl*/) {
326 Video *video = static_cast<Video *>(sender());
328 qDebug() << "Cannot get sender";
331 video->disconnect(this);
333 m_url = video->getStreamUrl();
337 qint64 DownloadItem::bytesTotal() const {
338 if (!m_reply) return 0;
339 return m_reply->header(QNetworkRequest::ContentLengthHeader).toULongLong();
342 qint64 DownloadItem::bytesReceived() const {
343 return m_bytesReceived;
346 double DownloadItem::remainingTime() const {
347 if (m_finishedDownloading)
350 double speed = currentSpeed();
351 double timeRemaining = 0.0;
353 timeRemaining = ((double)(bytesTotal() - bytesReceived())) / speed;
355 // When downloading the eta should never be 0
356 if (timeRemaining == 0)
359 return timeRemaining;
362 double DownloadItem::currentSpeed() const {
363 if (m_finishedDownloading)
366 int elapsed = m_downloadTime.elapsed();
369 speed = m_bytesReceived * 1000.0 / elapsed;
373 void DownloadItem::requestFinished() {
374 if (!m_startedSaving) {
375 qDebug() << "Request finished but never started saving";
380 if (m_bytesReceived <= 0) {
381 qDebug() << "Request finished but saved 0 bytes";
386 m_finishedDownloading = true;
388 if (m_status == Starting) {
389 m_status = Downloading;
390 emit statusChanged();
392 if (m_offset == 0) m_file.close();
394 m_totalTime = m_downloadTime.elapsed() / 1000.0;
395 emit statusChanged();
397 m_reply->deleteLater();
401 QString DownloadItem::formattedFilesize(qint64 size) {
405 } else if (size < 1024*1024) {
412 return QString(QLatin1String("%1 %2")).arg(size).arg(unit);
415 QString DownloadItem::formattedSpeed(double speed) {
416 int speedInt = (int) speed;
418 if (speedInt < 1024) {
419 unit = tr("bytes/sec");
420 } else if (speedInt < 1024*1024) {
424 speedInt /= 1024*1024;
427 return QString(QLatin1String("%1 %2")).arg(speedInt).arg(unit);
430 QString DownloadItem::formattedTime(double timeRemaining, bool remaining) {
431 QString timeRemainingString = tr("seconds");
432 if (timeRemaining > 60) {
433 timeRemaining = timeRemaining / 60;
434 timeRemainingString = tr("minutes");
436 timeRemaining = floor(timeRemaining);
437 QString msg = remaining ? tr("%4 %5 remaining") : "%4 %5";
440 .arg(timeRemainingString);