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