Implemented incremental retry download timer.
[medard] / src / medarddownloader.cpp
1 /*
2  *  Medard for Maemo.
3  *  Copyright (C) 2011 Roman Moravcik
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19
20 #include <QDebug>
21 #include <QTimer>
22 #include <QImage>
23 #include <QDir>
24 #include <QFile>
25
26 #include "medarddownloader.h"
27
28 #define DOWNLOAD_CACHE_DIR ".cache/medard-downloader"
29
30 #define MEDARD_URL "http://www.medard-online.cz"
31 #define MEDARD_IMAGE_URL "http://www.medard-online.cz/scripts/getimage.php?initDate=%1&domain=%2&variable=%3&offset=%4"
32
33 #define FC_SEA_LEVEL_PRESSURE "slp"
34 #define FC_PRECIPITATION "precip"
35 #define FC_WIND_VELOCITY "wv"
36 #define FC_CLOUDINESS "cloud"
37 #define FC_TEMPERATURE "temp"
38
39 #define FD_EUROPE "1"
40 #define FD_CZECH_REPUBLIC "2"
41
42 #define MIN_OFFSET 0
43 #define MAX_OFFSET 72
44
45 #define IMAGE_WIDTH 556
46 #define IMAGE_HEIGHT 408
47
48 #define MAX_DOWNLOAD_RETRIES 3
49 #define RETRY_TIMEOUT 5000
50
51 MedardDownloader::MedardDownloader() : QObject()
52 {
53     m_forecastType = FC_SEA_LEVEL_PRESSURE;
54     m_forecastDomain = FD_EUROPE;
55     m_forecastInitialDateCode.clear();
56     m_forecastDateOffset = 0;
57
58     m_network = new QNetworkAccessManager();
59     m_reply = 0;
60
61     m_retryCounter = 0;
62     m_retryTimer = new QTimer();
63     connect(m_retryTimer, SIGNAL(timeout()), this, SLOT(retryTimerEvent()));
64
65     m_cachePath = QString("%1/%2")
66                   .arg(QDir().homePath())
67                   .arg(DOWNLOAD_CACHE_DIR);
68
69     createCacheDirectory();
70 }
71
72 MedardDownloader::~MedardDownloader()
73 {
74     if (m_retryTimer->isActive())
75         m_retryTimer->stop();
76
77     if (m_reply) {
78         m_reply->abort();
79         delete m_reply;
80     }
81 }
82
83 QSize MedardDownloader::imageSize()
84 {
85     return QSize(IMAGE_WIDTH, IMAGE_HEIGHT);
86 }
87
88 void MedardDownloader::setForecastType(ForecastType type)
89 {
90     switch (type) {
91         case SeaLevelPressure:
92             m_forecastType = FC_SEA_LEVEL_PRESSURE;
93             break;
94
95         case Precipitation:
96             m_forecastType = FC_PRECIPITATION;
97             break;
98
99         case WindVelocity:
100             m_forecastType = FC_WIND_VELOCITY;
101             break;
102
103         case Cloudiness:
104             m_forecastType = FC_CLOUDINESS;
105             break;
106
107         case Temperature:
108             m_forecastType = FC_TEMPERATURE;
109             break;
110     }
111 }
112
113 void MedardDownloader::setForecastDomain(ForecastDomain domain)
114 {
115     switch (domain) {
116         case Europe:
117             m_forecastDomain = FD_EUROPE;
118             break;
119
120         case CzechRepublic:
121             m_forecastDomain = FD_CZECH_REPUBLIC;
122             break;
123     }
124 }
125
126 void MedardDownloader::setForecastInitialDate(QDateTime date)
127 {
128     QString offset;
129
130     m_forecastInitialDate = date.toUTC();
131
132     if (date.toUTC().time().hour() >= 18) {
133         m_forecastInitialDate.setTime(QTime(18, 0, 0));
134         offset = "12";
135     } else if (date.toUTC().time().hour() >= 12) {
136         m_forecastInitialDate.setTime(QTime(12, 0, 0));
137         offset = "06";
138     } else if (date.toUTC().time().hour() >= 6) {
139         m_forecastInitialDate.setTime(QTime(6, 0, 0));
140         offset = "00";
141     } else {
142         m_forecastInitialDate.setTime(QTime(0, 0, 0));
143         offset = "18";
144     }
145
146     if (offset == "18") {
147         /* use previous day */
148         m_forecastInitialDateCode = QString("%1_%2")
149                                     .arg(date.addDays(-1).toUTC().toString("yyMMdd"))
150                                     .arg(offset);
151     } else {
152         /* use current day */
153         m_forecastInitialDateCode = QString("%1_%2")
154                                     .arg(date.toUTC().toString("yyMMdd"))
155                                     .arg(offset);
156     }
157
158     cleanCacheDirectory();
159 }
160
161 QDateTime MedardDownloader::forecastInitialDate()
162 {
163     return m_forecastInitialDate.toLocalTime();
164 }
165
166 QDateTime MedardDownloader::forecastDate()
167 {
168     return m_forecastInitialDate.addSecs(3600 * m_forecastDateOffset).toLocalTime();
169 }
170
171 void MedardDownloader::setForecastDateOffset(int offset)
172 {
173     m_forecastDateOffset = offset;
174     if (m_forecastDateOffset > MAX_OFFSET)
175         m_forecastDateOffset = MAX_OFFSET;
176
177     if (m_forecastDateOffset < MIN_OFFSET)
178         m_forecastDateOffset = MIN_OFFSET;
179 }
180
181 void MedardDownloader::retryTimerEvent()
182 {
183     if (m_retryTimer->isActive())
184         m_retryTimer->stop();
185
186     downloadImage();
187 }
188
189 void MedardDownloader::tryDownloadImageAgain()
190 {
191     m_retryCounter++;
192
193     if (m_retryCounter < MAX_DOWNLOAD_RETRIES) {
194         m_retryTimer->setInterval(RETRY_TIMEOUT * (m_retryCounter + 1));
195         m_retryTimer->start();
196     } else {
197         m_retryCounter = 0;
198         emit downloadFailed();
199     }
200 }
201
202 void MedardDownloader::clearDownloadRequest()
203 {
204     qDebug() << "clearDownloadRequest: m_reply=" << m_reply;
205
206     delete m_reply;
207     m_reply = 0;
208 }
209
210 void MedardDownloader::downloadImageFinished()
211
212     qDebug() << "downloadImageFinished: m_reply=" << m_reply;
213
214     QByteArray picture = m_reply->readAll();
215
216     if (picture.isNull() || picture.size() <= 0)
217         return;
218
219     m_retryCounter = 0;
220
221     QImage image;
222     if (!image.loadFromData(picture, "png"))
223         return;
224
225     QString filename = QString("%1/%2_%3_%4_%5.png")
226                        .arg(m_cachePath)
227                        .arg(m_forecastType)
228                        .arg(m_forecastDomain)
229                        .arg(m_forecastInitialDateCode)
230                        .arg(QString().number(m_forecastDateOffset));
231
232     if ((image.width() == 512) && (image.height() == 512)) {
233         QImage croped(512, 400, QImage::Format_ARGB32_Premultiplied);
234         croped = image.copy(0, 52, 512, IMAGE_HEIGHT);
235         croped.save(filename, "png");
236     } else {
237         QImage croped(560, 400, QImage::Format_ARGB32_Premultiplied);
238         croped = image.copy(10, 96, IMAGE_WIDTH, IMAGE_HEIGHT);
239         croped.save(filename, "png");
240     }
241
242     qDebug() << "downloadImageFinished: downloadFinished=" << filename;
243     emit downloadFinished(filename, forecastDate());
244
245     QTimer::singleShot(0, this, SLOT(clearDownloadRequest()));
246 }
247
248 void MedardDownloader::downloadImageError(QNetworkReply::NetworkError /* code */)
249 {
250     tryDownloadImageAgain();
251     QTimer::singleShot(0, this, SLOT(clearDownloadRequest()));
252 }
253
254 void MedardDownloader::downloadImage()
255 {
256     qDebug() << "downloadImage: retry=" << m_retryCounter << "date:" << m_forecastInitialDate.toString("dd.MM.yyyy hh:mm");
257
258     if (m_forecastInitialDateCode.isNull()) {
259         retrieveForecastInitialDate();
260         tryDownloadImageAgain();
261         return;
262     }
263
264     QString filename = QString("%1/%2_%3_%4_%5.png")
265                        .arg(m_cachePath)
266                        .arg(m_forecastType)
267                        .arg(m_forecastDomain)
268                        .arg(m_forecastInitialDateCode)
269                        .arg(QString().number(m_forecastDateOffset));
270
271     if (isDownloaded(filename)) {
272         qDebug() << "downloadImage: downloadFinished=" << filename;
273
274         emit downloadFinished(filename, forecastDate());
275         return;
276     }
277
278     QString imageUrl = QString(MEDARD_IMAGE_URL)
279                                .arg(m_forecastInitialDateCode)
280                                .arg(m_forecastDomain)
281                                .arg(m_forecastType)
282                                .arg(QString().number(m_forecastDateOffset));
283
284     QUrl url(imageUrl);
285     QNetworkRequest request(url);
286
287     if (m_reply)
288         clearDownloadRequest();
289     m_reply = m_network->get(request);
290
291     connect(m_reply, SIGNAL(finished()), this, SLOT(downloadImageFinished()));
292     connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this,
293             SLOT(downloadImageError(QNetworkReply::NetworkError)));
294 }
295
296 bool MedardDownloader::isDownloaded(const QString filename)
297 {
298     return QFile(filename).exists();
299 }
300
301 int MedardDownloader::forecastDateOffset()
302 {
303     return m_forecastDateOffset;
304 }
305
306 int MedardDownloader::minForecastDateOffset()
307 {
308     return MIN_OFFSET;
309 }
310
311 int MedardDownloader::maxForecastDateOffset()
312 {
313     return MAX_OFFSET;
314 }
315
316 void MedardDownloader::retrieveForecastInitialDateFinished()
317 {
318     qDebug() << "retrieveForecastInitialDateFinished: m_reply=" << m_reply;
319
320     QByteArray data = m_reply->readAll();
321
322     int index1 = data.indexOf("var fcst_initDatestamp=\"", 0);
323     int index2 = data.indexOf("\";", index1);
324     if (index1 != -1) {
325         QString temp;
326         for (int i = index1 + 24; i < index2; i++) {
327             temp.append(data.at(i));
328         }
329         QDateTime date = QDateTime::fromTime_t(temp.toULong() + 6 * 3600);
330         if (!date.isNull()) {
331             setForecastInitialDate(date.toLocalTime());
332
333             int forecastDateOffset = date.toLocalTime().secsTo(QDateTime().currentDateTime()) / 3600;
334             setForecastDateOffset(forecastDateOffset);
335         }
336         m_retryCounter = 0;
337     }
338
339     QTimer::singleShot(0, this, SLOT(clearDownloadRequest()));
340 }
341
342 void MedardDownloader::retrieveForecastInitialDateError(QNetworkReply::NetworkError /* code */)
343 {
344     qDebug() << "retrieveForecastInitialDateError: m_reply=" << m_reply;
345 }
346
347 void MedardDownloader::retrieveForecastInitialDate()
348 {
349     qDebug() << "retrieveForecastInitialDate: m_reply=" << m_reply;
350
351     QString serverUrl = QString(MEDARD_URL);
352
353     QUrl url(serverUrl);
354     QNetworkRequest request(url);
355
356     if (m_reply)
357         clearDownloadRequest();
358     m_reply = m_network->get(request);
359
360     connect(m_reply, SIGNAL(finished()), this, SLOT(retrieveForecastInitialDateFinished()));
361     connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this,
362             SLOT(retrieveForecastInitialDateError(QNetworkReply::NetworkError)));
363 }
364
365 void MedardDownloader::createCacheDirectory()
366 {
367     QDir cacheDir(m_cachePath);
368     if (!cacheDir.exists())
369         cacheDir.mkpath(cacheDir.path());
370 }
371
372 void MedardDownloader::cleanCacheDirectory()
373 {
374     QDir cacheDir(m_cachePath);
375     QStringList list = cacheDir.entryList();
376     for (int i = 0; i < list.size(); i++) {
377         if (!list.at(i).contains(m_forecastInitialDateCode)) {
378             QFile(m_cachePath + "/" + list.at(i)).remove();
379         }
380     }
381 }