]> git.sur5r.net Git - minitube/blob - src/downloaditem.cpp
0ff2396bd90f1583264c33771009a363d6ff613f
[minitube] / src / downloaditem.cpp
1 #include "downloaditem.h"
2 #include "networkaccess.h"
3 #include "video.h"
4
5 #include <QDesktopServices>
6 #include <QDebug>
7
8 #ifdef APP_MAC
9 #include "macutils.h"
10 #endif
11
12 namespace The {
13     NetworkAccess* http();
14 }
15
16 DownloadItem::DownloadItem(Video *video, QUrl url, QString filename, QObject *parent)
17     : QObject(parent)
18     , m_bytesReceived(0)
19     , m_startedSaving(false)
20     , m_finishedDownloading(false)
21     , m_url(url)
22     , m_file(filename)
23     , m_reply(0)
24     , video(video)
25     , m_status(Idle)
26 {
27     speedCheckTimer = new QTimer(this);
28     speedCheckTimer->setInterval(2000);
29     speedCheckTimer->setSingleShot(true);
30     connect(speedCheckTimer, SIGNAL(timeout()), SLOT(speedCheck()));
31 }
32
33 DownloadItem::~DownloadItem() {
34     if (m_reply) {
35         delete m_reply;
36         m_reply = 0;
37     }
38     if (video) {
39         delete video;
40         video = 0;
41     }
42 }
43
44 void DownloadItem::start() {
45     m_reply = The::http()->request(m_url);
46     init();
47 }
48
49 void DownloadItem::init() {
50     if (!m_reply)
51         return;
52
53     if (m_file.exists())
54         m_file.remove();
55
56     m_status = Starting;
57
58     m_startedSaving = false;
59     m_finishedDownloading = false;
60
61     // attach to the m_reply
62     m_url = m_reply->url();
63     connect(m_reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead()));
64     connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
65             this, SLOT(error(QNetworkReply::NetworkError)));
66     connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)),
67             this, SLOT(downloadProgress(qint64, qint64)));
68     connect(m_reply, SIGNAL(metaDataChanged()),
69             this, SLOT(metaDataChanged()));
70     connect(m_reply, SIGNAL(finished()),
71             this, SLOT(requestFinished()));
72
73     // start timer for the download estimation
74     m_totalTime = 0;
75     m_downloadTime.start();
76     speedCheckTimer->start();
77
78     if (m_reply->error() != QNetworkReply::NoError) {
79         error(m_reply->error());
80         requestFinished();
81     }
82 }
83
84
85 void DownloadItem::stop() {
86     if (m_reply) {
87         m_reply->disconnect();
88         m_reply->abort();
89         m_reply->deleteLater();
90         m_reply = 0;
91     }
92     m_status = Idle;
93     emit statusChanged();
94 }
95
96 void DownloadItem::open() {
97     QFileInfo info(m_file);
98     QUrl url = QUrl::fromLocalFile(info.absoluteFilePath());
99     QDesktopServices::openUrl(url);
100 }
101
102 void DownloadItem::openFolder() {
103     QFileInfo info(m_file);
104 #ifdef APP_MAC
105     mac::showInFinder(info.absoluteFilePath());
106 #else
107     QUrl url = QUrl::fromLocalFile(info.absolutePath());
108     QDesktopServices::openUrl(url);
109 #endif
110 }
111
112 void DownloadItem::tryAgain() {
113     stop();
114     start();
115 }
116
117 void DownloadItem::downloadReadyRead() {
118     if (!m_reply) return;
119
120     if (!m_file.isOpen()) {
121         if (!m_file.open(QIODevice::ReadWrite)) {
122             qWarning() << QString("Error opening output file: %1").arg(m_file.errorString());
123             stop();
124             emit statusChanged();
125             return;
126         }
127         emit statusChanged();
128     }
129
130     if (-1 == m_file.write(m_reply->readAll())) {
131         qWarning() << "Error saving." << m_file.errorString();
132     } else {
133
134         m_startedSaving = true;
135
136         // if (m_finishedDownloading) requestFinished();
137     }
138 }
139
140 void DownloadItem::error(QNetworkReply::NetworkError) {
141
142     if (m_reply) {
143         qWarning() << m_reply->errorString() << m_reply->url().toEncoded();
144         m_errorMessage = m_reply->errorString();
145     }
146
147     m_reply = 0;
148     m_status = Failed;
149
150     emit finished();
151 }
152
153 QString DownloadItem::errorMessage() const {
154     return m_errorMessage;
155 }
156
157 void DownloadItem::metaDataChanged() {
158     if (!m_reply) return;
159     QVariant locationHeader = m_reply->header(QNetworkRequest::LocationHeader);
160     if (locationHeader.isValid()) {
161         m_url = locationHeader.toUrl();
162         // qDebug() << "Redirecting to" << m_url;
163         tryAgain();
164         return;
165     }
166
167 #ifdef DOWNLOADMANAGER_DEBUG
168     qDebug() << "DownloadItem::" << __FUNCTION__ << "not handled.";
169 #endif
170 }
171
172 int DownloadItem::initialBufferSize() {
173     // qDebug() << video->getDefinitionCode();
174     switch (video->getDefinitionCode()) {
175     case 18:
176         return 1024*512;
177     case 22:
178         return 1024*1024;
179     case 37:
180         return 1024*1024*2;
181     }
182     return 1024*128;
183 }
184
185 void DownloadItem::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
186
187     // qDebug() << bytesReceived << bytesTotal << m_downloadTime.elapsed();
188
189     if (m_lastProgressTime.elapsed() < 150) return;
190     m_lastProgressTime.start();
191
192     m_bytesReceived = bytesReceived;
193
194     if (m_status != Downloading) {
195
196         int neededBytes = (int) (bytesTotal * .005);
197         int bufferSize = initialBufferSize();
198         if (bufferSize > bytesTotal) bufferSize = bytesTotal;
199         // qDebug() << bytesReceived << bytesTotal << neededBytes << bufferSize << m_downloadTime.elapsed();
200         if (bytesReceived > bufferSize
201             && bytesReceived > neededBytes
202             && m_downloadTime.elapsed() > 2000) {
203             emit bufferProgress(100);
204             m_status = Downloading;
205             emit statusChanged();
206         } else {
207             int bytes = qMax(bufferSize, neededBytes);
208             int bufferPercent = 0;
209             if (bytes > 0)
210                 bufferPercent = bytesReceived * 100 / bytes;
211             emit bufferProgress(bufferPercent);
212         }
213
214     } else {
215
216         if (bytesTotal > 0) {
217             int percent = bytesReceived * 100 / bytesTotal;
218             if (percent != this->percent) {
219                 this->percent = percent;
220                 emit progress(percent);
221             }
222         }
223
224     }
225 }
226
227 void DownloadItem::speedCheck() {
228     if (!m_reply) return;
229     int bytesTotal = m_reply->size();
230     int bufferSize = initialBufferSize();
231     if (bufferSize > bytesTotal) bufferSize = 0;
232     if (m_bytesReceived < bufferSize / 3) {
233         stop();
234
235         // too slow! retry
236         qDebug() << "Retrying...";
237         connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
238         video->loadStreamUrl();
239     }
240 }
241
242 void DownloadItem::gotStreamUrl(QUrl /*streamUrl*/) {
243
244     Video *video = static_cast<Video *>(sender());
245     if (!video) {
246         qDebug() << "Cannot get sender";
247         return;
248     }
249     video->disconnect(this);
250
251     m_url = video->getStreamUrl();
252     start();
253 }
254
255 qint64 DownloadItem::bytesTotal() const {
256     if (!m_reply) return 0;
257     return m_reply->header(QNetworkRequest::ContentLengthHeader).toULongLong();
258 }
259
260 qint64 DownloadItem::bytesReceived() const {
261     return m_bytesReceived;
262 }
263
264 double DownloadItem::remainingTime() const {
265     if (m_finishedDownloading)
266         return -1.0;
267
268     double speed = currentSpeed();
269     double timeRemaining = 0.0;
270     if (speed > 0.0)
271         timeRemaining = ((double)(bytesTotal() - bytesReceived())) / speed;
272
273     // When downloading the eta should never be 0
274     if (timeRemaining == 0)
275         timeRemaining = 1;
276
277     return timeRemaining;
278 }
279
280 double DownloadItem::currentSpeed() const {
281     if (m_finishedDownloading)
282         return -1.0;
283
284     int elapsed = m_downloadTime.elapsed();
285     double speed = -1.0;
286     if (elapsed > 0)
287         speed = m_bytesReceived * 1000.0 / elapsed;
288     return speed;
289 }
290
291 void DownloadItem::requestFinished() {
292     if (!m_startedSaving) {
293         qDebug() << "Request finished but never started saving";
294         tryAgain();
295         return;
296     }
297
298     if (m_bytesReceived <= 0) {
299         qDebug() << "Request finished but saved 0 bytes";
300         tryAgain();
301         return;
302     }
303
304     m_finishedDownloading = true;
305
306     if (m_status == Starting) {
307         m_status = Downloading;
308         emit statusChanged();
309     }
310     m_file.close();
311     m_status = Finished;
312     m_totalTime = m_downloadTime.elapsed() / 1000.0;
313     emit statusChanged();
314     emit finished();
315     m_reply->deleteLater();
316     m_reply = 0;
317 }
318
319 QString DownloadItem::formattedFilesize(qint64 size) {
320     QString unit;
321     if (size < 1024) {
322         unit = tr("bytes");
323     } else if (size < 1024*1024) {
324         size /= 1024;
325         unit = tr("KB");
326     } else {
327         size /= 1024*1024;
328         unit = tr("MB");
329     }
330     return QString(QLatin1String("%1 %2")).arg(size).arg(unit);
331 }
332
333 QString DownloadItem::formattedSpeed(double speed) {
334     int speedInt = (int) speed;
335     QString unit;
336     if (speedInt < 1024) {
337         unit = tr("bytes/sec");
338     } else if (speedInt < 1024*1024) {
339         speedInt /= 1024;
340         unit = tr("KB/sec");
341     } else {
342         speedInt /= 1024*1024;
343         unit = tr("MB/sec");
344     }
345     return QString(QLatin1String("%1 %2")).arg(speedInt).arg(unit);
346 }
347
348 QString DownloadItem::formattedTime(double timeRemaining, bool remaining) {
349     QString timeRemainingString = tr("seconds");
350     if (timeRemaining > 60) {
351         timeRemaining = timeRemaining / 60;
352         timeRemainingString = tr("minutes");
353     }
354     timeRemaining = floor(timeRemaining);
355     QString msg = remaining ? tr("%4 %5 remaining") : "%4 %5";
356         return msg
357             .arg(timeRemaining)
358             .arg(timeRemainingString);
359 }