]> git.sur5r.net Git - minitube/blob - src/downloaditem.cpp
Merge tag 'upstream/2.5.2'
[minitube] / src / downloaditem.cpp
1 /* $BEGIN_LICENSE
2
3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
5
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.
10
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.
15
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/>.
18
19 $END_LICENSE */
20
21 #include "downloaditem.h"
22 #include "networkaccess.h"
23 #include "video.h"
24
25 #include <QDesktopServices>
26 #include <QDebug>
27
28 #ifdef APP_MAC
29 #include "macutils.h"
30 #endif
31
32 namespace The {
33     NetworkAccess* http();
34 }
35
36 DownloadItem::DownloadItem(Video *video, QUrl url, QString filename, QObject *parent)
37     : QObject(parent)
38     , m_bytesReceived(0)
39     , m_startedSaving(false)
40     , m_finishedDownloading(false)
41     , m_url(url)
42     , m_offset(0)
43     , sendStatusChanges(true)
44     , m_file(filename)
45     , m_reply(0)
46     , video(video)
47     , m_status(Idle)
48 {
49     speedCheckTimer = new QTimer(this);
50     speedCheckTimer->setInterval(2000);
51     speedCheckTimer->setSingleShot(true);
52     connect(speedCheckTimer, SIGNAL(timeout()), SLOT(speedCheck()));
53
54     if (m_file.exists())
55         m_file.remove();
56 }
57
58 DownloadItem::~DownloadItem() {
59     if (m_reply) {
60         delete m_reply;
61         m_reply = 0;
62     }
63     if (video) {
64         delete video;
65         video = 0;
66     }
67 }
68
69 bool DownloadItem::needsDownload(qint64 offset) {
70     return offset < m_offset || offset > m_offset + m_bytesReceived;
71 }
72
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();
78             return true;
79         }
80     }
81     // qDebug() << offset << "is not buffered";
82     return false;
83 }
84
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();
91             return i.value() + 1;
92         }
93     }
94     return offset;
95 }
96
97 void DownloadItem::seekTo(qint64 offset, bool sendStatusChanges) {
98     // qDebug() << __PRETTY_FUNCTION__ << offset << sendStatusChanges;
99     stop();
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()) {
105                 /*
106                 qDebug() << "Extending existing buffer "
107                          << i.key() << i.value() << "now" << m_offset + m_bytesReceived;
108                 */
109                 bufferModified = true;
110                 i.value() = m_offset + m_bytesReceived;
111                 break;
112             }
113         }
114         if (!bufferModified)
115             buffers.insert(m_offset, m_offset + m_bytesReceived);
116     }
117     m_offset = offset;
118     this->sendStatusChanges = sendStatusChanges;
119     bool seekSuccess = m_file.seek(offset);
120     if (!seekSuccess) {
121         qWarning() << "Cannot seek to offset" << offset << "in file" << m_file.fileName();
122         return;
123     }
124     start();
125 }
126
127 void DownloadItem::start() {
128     // qDebug() << "Starting download at" << m_offset;
129     if (m_offset > 0)
130         m_reply = The::http()->request(m_url, QNetworkAccessManager::GetOperation, QByteArray(), m_offset);
131     else
132         m_reply = The::http()->request(m_url);
133     init();
134 }
135
136 void DownloadItem::init() {
137     if (!m_reply)
138         return;
139
140     m_status = Starting;
141     m_bytesReceived = 0;
142     m_startedSaving = false;
143     m_finishedDownloading = false;
144
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()));
156
157     // start timer for the download estimation
158     m_totalTime = 0;
159     m_downloadTime.start();
160     speedCheckTimer->start();
161
162     if (m_reply->error() != QNetworkReply::NoError) {
163         error(m_reply->error());
164         requestFinished();
165     }
166 }
167
168
169 void DownloadItem::stop() {
170     if (m_reply) {
171         m_reply->disconnect();
172         m_reply->abort();
173         m_reply->deleteLater();
174         m_reply = 0;
175     }
176     m_status = Idle;
177     emit statusChanged();
178 }
179
180 void DownloadItem::open() {
181     QFileInfo info(m_file);
182     QUrl url = QUrl::fromLocalFile(info.absoluteFilePath());
183     QDesktopServices::openUrl(url);
184 }
185
186 void DownloadItem::openFolder() {
187     QFileInfo info(m_file);
188 #ifdef APP_MAC
189     mac::showInFinder(info.absoluteFilePath());
190 #else
191     QUrl url = QUrl::fromLocalFile(info.absolutePath());
192     QDesktopServices::openUrl(url);
193 #endif
194 }
195
196 void DownloadItem::tryAgain() {
197     stop();
198     start();
199 }
200
201 void DownloadItem::downloadReadyRead() {
202     if (!m_reply) return;
203
204     if (!m_file.isOpen()) {
205         if (!m_file.open(QIODevice::ReadWrite)) {
206             qWarning() << QString("Error opening output file: %1").arg(m_file.errorString());
207             stop();
208             emit statusChanged();
209             return;
210         }
211         emit statusChanged();
212     }
213
214     // qWarning() << __PRETTY_FUNCTION__ << m_file.pos();
215     if (-1 == m_file.write(m_reply->readAll())) {
216         qWarning() << "Error saving." << m_file.errorString();
217     } else {
218
219         m_startedSaving = true;
220
221         // if (m_finishedDownloading) requestFinished();
222     }
223 }
224
225 void DownloadItem::error(QNetworkReply::NetworkError) {
226
227     if (m_reply) {
228         qWarning() << m_reply->errorString() << m_reply->url().toEncoded();
229         m_errorMessage = m_reply->errorString();
230     }
231
232     m_reply = 0;
233     m_status = Failed;
234
235     emit finished();
236 }
237
238 QString DownloadItem::errorMessage() const {
239     return m_errorMessage;
240 }
241
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;
248         tryAgain();
249         return;
250     }
251     // qDebug() << m_reply->rawHeaderList();
252 }
253
254 int DownloadItem::initialBufferSize() {
255     // qDebug() << video->getDefinitionCode();
256     switch (video->getDefinitionCode()) {
257     case 18:
258         return 1024*512;
259     case 22:
260         return 1024*1024;
261     case 37:
262         return 1024*1024*2;
263     }
264     return 1024*128;
265 }
266
267 void DownloadItem::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
268
269     // qDebug() << __PRETTY_FUNCTION__ << bytesReceived << bytesTotal << m_downloadTime.elapsed();
270
271     m_bytesReceived = bytesReceived;
272
273     if (!sendStatusChanges) return;
274
275     if (m_lastProgressTime.elapsed() < 150) return;
276     m_lastProgressTime.start();
277
278     if (m_status != Downloading) {
279
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();
290         } else {
291             int bytes = qMax(bufferSize, neededBytes);
292             int bufferPercent = 0;
293             if (bytes > 0)
294                 bufferPercent = bytesReceived * 100 / bytes;
295             emit bufferProgress(bufferPercent);
296         }
297
298     } else {
299
300         if (bytesTotal > 0) {
301             int percent = bytesReceived * 100 / bytesTotal;
302             if (percent != this->percent) {
303                 this->percent = percent;
304                 emit progress(percent);
305             }
306         }
307
308     }
309 }
310
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) {
317         stop();
318
319         // too slow! retry
320         qDebug() << "Retrying...";
321         connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
322         video->loadStreamUrl();
323     }
324 }
325
326 void DownloadItem::gotStreamUrl(QUrl /*streamUrl*/) {
327
328     Video *video = static_cast<Video *>(sender());
329     if (!video) {
330         qDebug() << "Cannot get sender";
331         return;
332     }
333     video->disconnect(this);
334
335     m_url = video->getStreamUrl();
336     start();
337 }
338
339 qint64 DownloadItem::bytesTotal() const {
340     if (!m_reply) return 0;
341     return m_reply->header(QNetworkRequest::ContentLengthHeader).toULongLong();
342 }
343
344 qint64 DownloadItem::bytesReceived() const {
345     return m_bytesReceived;
346 }
347
348 double DownloadItem::remainingTime() const {
349     if (m_finishedDownloading)
350         return -1.0;
351
352     double speed = currentSpeed();
353     double timeRemaining = 0.0;
354     if (speed > 0.0)
355         timeRemaining = ((double)(bytesTotal() - bytesReceived())) / speed;
356
357     // When downloading the eta should never be 0
358     if (timeRemaining == 0)
359         timeRemaining = 1;
360
361     return timeRemaining;
362 }
363
364 double DownloadItem::currentSpeed() const {
365     if (m_finishedDownloading)
366         return -1.0;
367
368     int elapsed = m_downloadTime.elapsed();
369     double speed = -1.0;
370     if (elapsed > 0)
371         speed = m_bytesReceived * 1000.0 / elapsed;
372     return speed;
373 }
374
375 void DownloadItem::requestFinished() {
376     if (!m_startedSaving) {
377         qDebug() << "Request finished but never started saving";
378         tryAgain();
379         return;
380     }
381
382     if (m_bytesReceived <= 0) {
383         qDebug() << "Request finished but saved 0 bytes";
384         tryAgain();
385         return;
386     }
387
388     m_finishedDownloading = true;
389
390     if (m_status == Starting) {
391         m_status = Downloading;
392         emit statusChanged();
393     }
394     if (m_offset == 0) m_file.close();
395     m_status = Finished;
396     m_totalTime = m_downloadTime.elapsed() / 1000.0;
397     emit statusChanged();
398     emit finished();
399     m_reply->deleteLater();
400     m_reply = 0;
401 }
402
403 QString DownloadItem::formattedFilesize(qint64 size) {
404     QString unit;
405     if (size < 1024) {
406         unit = tr("bytes");
407     } else if (size < 1024*1024) {
408         size /= 1024;
409         unit = tr("KB");
410     } else {
411         size /= 1024*1024;
412         unit = tr("MB");
413     }
414     return QString(QLatin1String("%1 %2")).arg(size).arg(unit);
415 }
416
417 QString DownloadItem::formattedSpeed(double speed) {
418     int speedInt = (int) speed;
419     QString unit;
420     if (speedInt < 1024) {
421         unit = tr("bytes/sec");
422     } else if (speedInt < 1024*1024) {
423         speedInt /= 1024;
424         unit = tr("KB/sec");
425     } else {
426         speedInt /= 1024*1024;
427         unit = tr("MB/sec");
428     }
429     return QString(QLatin1String("%1 %2")).arg(speedInt).arg(unit);
430 }
431
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");
437     }
438     timeRemaining = floor(timeRemaining);
439     QString msg = remaining ? tr("%4 %5 remaining") : "%4 %5";
440         return msg
441             .arg(timeRemaining)
442             .arg(timeRemainingString);
443 }