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"
22 #include "networkaccess.h"
25 #include <QDesktopServices>
33 NetworkAccess* http();
36 DownloadItem::DownloadItem(Video *video, QUrl url, QString filename, QObject *parent)
39 , m_startedSaving(false)
40 , m_finishedDownloading(false)
43 , sendStatusChanges(true)
49 speedCheckTimer = new QTimer(this);
50 speedCheckTimer->setInterval(2000);
51 speedCheckTimer->setSingleShot(true);
52 connect(speedCheckTimer, SIGNAL(timeout()), SLOT(speedCheck()));
58 DownloadItem::~DownloadItem() {
69 bool DownloadItem::needsDownload(qint64 offset) {
70 return offset < m_offset || offset > m_offset + m_bytesReceived;
73 bool DownloadItem::isBuffered(qint64 offset) {
74 QMap<qint64, qint64>::iterator i;
75 for (i = buffers.begin(); i != buffers.end(); ++i) {
76 if (offset >= i.key() && offset <= i.value()) {
77 // qDebug() << "Buffered! " << i.key() << ":" << i.value();
81 // qDebug() << offset << "is not buffered";
85 qint64 DownloadItem::blankAtOffset(qint64 offset) {
86 // qDebug() << buffers;
87 QMap<qint64, qint64>::iterator i;
88 for (i = buffers.begin(); i != buffers.end(); ++i) {
89 if (offset >= i.key() && offset <= i.value()) {
90 // qDebug() << "Offset was" << offset << "now" << i.value();
97 void DownloadItem::seekTo(qint64 offset, bool sendStatusChanges) {
98 // qDebug() << __PRETTY_FUNCTION__ << offset << sendStatusChanges;
100 if (m_bytesReceived > 0) {
101 bool bufferModified = false;
102 QMap<qint64, qint64>::iterator i;
103 for (i = buffers.begin(); i != buffers.end(); ++i) {
104 if (m_offset - 1 <= i.value()) {
106 qDebug() << "Extending existing buffer "
107 << i.key() << i.value() << "now" << m_offset + m_bytesReceived;
109 bufferModified = true;
110 i.value() = m_offset + m_bytesReceived;
115 buffers.insert(m_offset, m_offset + m_bytesReceived);
118 this->sendStatusChanges = sendStatusChanges;
119 bool seekSuccess = m_file.seek(offset);
121 qWarning() << "Cannot seek to offset" << offset << "in file" << m_file.fileName();
127 void DownloadItem::start() {
128 // qDebug() << "Starting download at" << m_offset;
130 m_reply = The::http()->request(m_url, QNetworkAccessManager::GetOperation, QByteArray(), m_offset);
132 m_reply = The::http()->request(m_url);
136 void DownloadItem::init() {
142 m_startedSaving = false;
143 m_finishedDownloading = false;
145 // attach to the m_reply
146 m_url = m_reply->url();
147 connect(m_reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead()));
148 connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
149 this, SLOT(error(QNetworkReply::NetworkError)));
150 connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)),
151 this, SLOT(downloadProgress(qint64, qint64)));
152 connect(m_reply, SIGNAL(metaDataChanged()),
153 this, SLOT(metaDataChanged()));
154 connect(m_reply, SIGNAL(finished()),
155 this, SLOT(requestFinished()));
157 // start timer for the download estimation
159 m_downloadTime.start();
160 speedCheckTimer->start();
162 if (m_reply->error() != QNetworkReply::NoError) {
163 error(m_reply->error());
169 void DownloadItem::stop() {
171 m_reply->disconnect();
173 m_reply->deleteLater();
177 emit statusChanged();
180 void DownloadItem::open() {
181 QFileInfo info(m_file);
182 QUrl url = QUrl::fromLocalFile(info.absoluteFilePath());
183 QDesktopServices::openUrl(url);
186 void DownloadItem::openFolder() {
187 QFileInfo info(m_file);
189 mac::showInFinder(info.absoluteFilePath());
191 QUrl url = QUrl::fromLocalFile(info.absolutePath());
192 QDesktopServices::openUrl(url);
196 void DownloadItem::tryAgain() {
201 void DownloadItem::downloadReadyRead() {
202 if (!m_reply) return;
204 if (!m_file.isOpen()) {
205 if (!m_file.open(QIODevice::ReadWrite)) {
206 qWarning() << QString("Error opening output file: %1").arg(m_file.errorString());
208 emit statusChanged();
211 emit statusChanged();
214 // qWarning() << __PRETTY_FUNCTION__ << m_file.pos();
215 if (-1 == m_file.write(m_reply->readAll())) {
216 qWarning() << "Error saving." << m_file.errorString();
219 m_startedSaving = true;
221 // if (m_finishedDownloading) requestFinished();
225 void DownloadItem::error(QNetworkReply::NetworkError) {
228 qWarning() << m_reply->errorString() << m_reply->url().toEncoded();
229 m_errorMessage = m_reply->errorString();
238 QString DownloadItem::errorMessage() const {
239 return m_errorMessage;
242 void DownloadItem::metaDataChanged() {
243 if (!m_reply) return;
244 QVariant locationHeader = m_reply->header(QNetworkRequest::LocationHeader);
245 if (locationHeader.isValid()) {
246 m_url = locationHeader.toUrl();
247 // qDebug() << "Redirecting to" << m_url;
251 // qDebug() << m_reply->rawHeaderList();
254 int DownloadItem::initialBufferSize() {
255 // qDebug() << video->getDefinitionCode();
256 switch (video->getDefinitionCode()) {
267 void DownloadItem::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
269 // qDebug() << __PRETTY_FUNCTION__ << bytesReceived << bytesTotal << m_downloadTime.elapsed();
271 m_bytesReceived = bytesReceived;
273 if (!sendStatusChanges) return;
275 if (m_lastProgressTime.elapsed() < 150) return;
276 m_lastProgressTime.start();
278 if (m_status != Downloading) {
280 int neededBytes = (int) (bytesTotal * .005);
281 int bufferSize = initialBufferSize();
282 if (bufferSize > bytesTotal) bufferSize = bytesTotal;
283 // qDebug() << bytesReceived << bytesTotal << neededBytes << bufferSize << m_downloadTime.elapsed();
284 if (bytesReceived > bufferSize
285 && bytesReceived > neededBytes
286 && m_downloadTime.elapsed() > 2000) {
287 emit bufferProgress(100);
288 m_status = Downloading;
289 emit statusChanged();
291 int bytes = qMax(bufferSize, neededBytes);
292 int bufferPercent = 0;
294 bufferPercent = bytesReceived * 100 / bytes;
295 emit bufferProgress(bufferPercent);
300 if (bytesTotal > 0) {
301 int percent = bytesReceived * 100 / bytesTotal;
302 if (percent != this->percent) {
303 this->percent = percent;
304 emit progress(percent);
311 void DownloadItem::speedCheck() {
312 if (!m_reply) return;
313 int bytesTotal = m_reply->size();
314 int bufferSize = initialBufferSize();
315 if (bufferSize > bytesTotal) bufferSize = 0;
316 if (m_bytesReceived < bufferSize / 3) {
320 qDebug() << "Retrying...";
321 connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
322 video->loadStreamUrl();
326 void DownloadItem::gotStreamUrl(QUrl /*streamUrl*/) {
328 Video *video = static_cast<Video *>(sender());
330 qDebug() << "Cannot get sender";
333 video->disconnect(this);
335 m_url = video->getStreamUrl();
339 qint64 DownloadItem::bytesTotal() const {
340 if (!m_reply) return 0;
341 return m_reply->header(QNetworkRequest::ContentLengthHeader).toULongLong();
344 qint64 DownloadItem::bytesReceived() const {
345 return m_bytesReceived;
348 double DownloadItem::remainingTime() const {
349 if (m_finishedDownloading)
352 double speed = currentSpeed();
353 double timeRemaining = 0.0;
355 timeRemaining = ((double)(bytesTotal() - bytesReceived())) / speed;
357 // When downloading the eta should never be 0
358 if (timeRemaining == 0)
361 return timeRemaining;
364 double DownloadItem::currentSpeed() const {
365 if (m_finishedDownloading)
368 int elapsed = m_downloadTime.elapsed();
371 speed = m_bytesReceived * 1000.0 / elapsed;
375 void DownloadItem::requestFinished() {
376 if (!m_startedSaving) {
377 qDebug() << "Request finished but never started saving";
382 if (m_bytesReceived <= 0) {
383 qDebug() << "Request finished but saved 0 bytes";
388 m_finishedDownloading = true;
390 if (m_status == Starting) {
391 m_status = Downloading;
392 emit statusChanged();
394 if (m_offset == 0) m_file.close();
396 m_totalTime = m_downloadTime.elapsed() / 1000.0;
397 emit statusChanged();
399 m_reply->deleteLater();
403 QString DownloadItem::formattedFilesize(qint64 size) {
407 } else if (size < 1024*1024) {
414 return QString(QLatin1String("%1 %2")).arg(size).arg(unit);
417 QString DownloadItem::formattedSpeed(double speed) {
418 int speedInt = (int) speed;
420 if (speedInt < 1024) {
421 unit = tr("bytes/sec");
422 } else if (speedInt < 1024*1024) {
426 speedInt /= 1024*1024;
429 return QString(QLatin1String("%1 %2")).arg(speedInt).arg(unit);
432 QString DownloadItem::formattedTime(double timeRemaining, bool remaining) {
433 QString timeRemainingString = tr("seconds");
434 if (timeRemaining > 60) {
435 timeRemaining = timeRemaining / 60;
436 timeRemainingString = tr("minutes");
438 timeRemaining = floor(timeRemaining);
439 QString msg = remaining ? tr("%4 %5 remaining") : "%4 %5";
442 .arg(timeRemainingString);