Update to support both Qt 4 and Qt 5
[apps/thermostat-demo.git] / webdataengine / openweathermapdataengine.cpp
1 #include "openweathermapdataengine.h"
3 #include <qglobal.h>
4 #include <QNetworkAccessManager>
5 #include <QNetworkProxy>
6 #include <QNetworkReply>
7 #include <QNetworkRequest>
8 #include <QScriptEngine>
9 #include <QtScript>
10 #include <QtDebug>
12 #include "globalsettings.h"
13 #include "forecastdata.h"
14 #include "weatherdata.h"
16 OpenWeatherMapDataEngine::OpenWeatherMapDataEngine(QNetworkAccessManager *manager, QObject *parent) :
17     WebDataEngine(manager, parent)
18 {
19     generateJSONWeatherLookupTables();
20     connect(&m_networkTimer, SIGNAL(timeout()), this, SLOT(handleNetworkTimeout()));
21     connect(&m_forecastNetworkTimer, SIGNAL(timeout()), this, SLOT(handleNetworkTimeout()));
22 }
24 void OpenWeatherMapDataEngine::setCity(QString city)
25 {
26     //save the full city name for use later
27     m_fullCity = city;
28     //openweathermap searches for a city name alone, so chop anything attached by commas
29     m_preparedCity = city.left(city.lastIndexOf(","));
30 }
32 void OpenWeatherMapDataEngine::dispatchRequest()
33 {
34     m_weatherData = new WeatherData;
35     m_forecastReceived = false;
36     m_weatherReceived = false;
38     //for openweather map we first must find the city
39     QString cityUrl = "http://openweathermap.org/data/2.1/find/name?q="+m_preparedCity;
41     // receive document and parse it
42     QNetworkRequest request;
43     request.setUrl(QUrl(cityUrl));
45     //set up timer to check for network timeout
46     connect(&m_networkTimer, SIGNAL(timeout()), this, SLOT(handleNetworkTimeout()));
47     m_networkTimer.start(15000);
49     //make actual network request
50     m_reply = m_manager->get(request);
52     connect(m_reply, SIGNAL(finished()),this,SLOT(responseReceived()));
53 }
55 void OpenWeatherMapDataEngine::handleNetworkTimeout()
56 {
57     m_networkTimer.stop();
58     m_forecastNetworkTimer.stop();
59     emit(networkTimeout());
60 }
62 void OpenWeatherMapDataEngine::responseReceived()
63 {
64     m_networkTimer.stop();
66     if(m_reply->error() == QNetworkReply::NoError)
67     {
68         QByteArray data = m_reply->readAll();
69         //qDebug() << data;
71         #if QT_VERSION >= 0x050000
72             m_cityId = parseCityInformation(QString::fromLatin1(data));
73         #else
74             m_cityId = parseCityInformation(QString::fromAscii(data));
75         #endif
77         //docs say do not delete in the slot so well pass it off to the event loop
78         m_reply->deleteLater();
79         if(m_cityId == -1)
80             emit networkTimeout();
81         else
82             //now we need to grab current weather information and forecast data
83             dispatchWeatherDataRequests();
84     }
85     else
86     {
87         qDebug() << "Network Error: " << m_reply->errorString();
88         loadLocalData();
89         emit networkTimeout();
90     }
91 }
93 void OpenWeatherMapDataEngine::dispatchWeatherDataRequests()
94 {
96     m_weatherData->setCurrentCity(m_fullCity);
98     //first send request for current weather
99     QString currentWeatherURL = "http://openweathermap.org/data/2.1/weather/city/"+QString::number(m_cityId);
101     QNetworkRequest request;
102     request.setUrl(QUrl(currentWeatherURL));
104     //set up timer to check for network timeout
106     m_networkTimer.start(15000);
108     //make actual network request
109     m_reply = m_manager->get(request);
110     connect(m_reply,SIGNAL(finished()),this,SLOT(currentWeatherResponseReceived()));
112     //next send request for current weather
113     QString forecastWeatherURL = "http://openweathermap.org/data/2.1/forecast/city/"+QString::number(m_cityId);
115     request.setUrl(QUrl(forecastWeatherURL));
117     //set up timer to check for network timeout
118     m_forecastNetworkTimer.start(15000);
120     //make actual network request
121     m_forecastReply = m_manager->get(request);
122     connect(m_forecastReply,SIGNAL(finished()),this, SLOT(forecastResponseReceived()));
125 void OpenWeatherMapDataEngine::currentWeatherResponseReceived()
127     m_networkTimer.stop();
129     if(m_reply->error() != QNetworkReply::NoError)
130     {
131         qDebug() << "Network Error: " << m_reply->errorString();
132         emit networkTimeout();
133         m_reply->deleteLater();
134         return;
135     }
136     m_rawJSONWeatherString = m_reply->readAll();
137     bool result = parseJSONWeatherData(&m_rawJSONWeatherString, m_weatherData);
138     if(!result)
139         emit networkTimeout();
140     else
141     {
142         m_weatherReceived = true;
143         checkIfDone();
144     }
147 void OpenWeatherMapDataEngine::forecastResponseReceived()
149     if(m_forecastReply->error() != QNetworkReply::NoError)
150     {
151         qDebug() << "Network Error: " << m_reply->errorString();
152         emit networkTimeout();
153         m_forecastReply->deleteLater();
154         return;
155     }
156     m_forecastNetworkTimer.stop();
157     m_rawJSONForecastString = m_forecastReply->readAll();
158     bool result = parseJSONForecastData(&m_rawJSONForecastString, m_weatherData);
159     if(!result)
160         emit networkTimeout();
161     else
162     {
163         m_forecastReceived = true;
164         checkIfDone();
165     }
168 void OpenWeatherMapDataEngine::checkIfDone()
170     if(m_forecastReceived && m_weatherReceived)
171     {
172         m_weatherData->setLastUpdated(QDateTime::currentDateTime());
173         writeToCache();
174         emit(dataAvailable(m_weatherData));
175     }
178 bool OpenWeatherMapDataEngine::parseJSONWeatherData(QString *jsonData, WeatherData *weatherData)
180     QScriptEngine engine;
181     QScriptValue result = engine.evaluate("weatherObject="+*jsonData);
183     if(result.isError())return false;
185     int temp = kelvinToFahrenheit(result.property("main").property("temp").toNumber());
186     QDateTime localTime = QDateTime::fromTime_t(result.property("dt").toNumber());
188     int iconIndex = convertImageNameToIndex(result.property("weather").property(0).property("icon").toString());
190     QString icon = m_iconNameToWeatherHash[iconIndex];
192     weatherData->setCurrentTemp(temp);
193     weatherData->setLocalTime(localTime);
194     weatherData->setIcon(icon);
195     return true;
198 bool OpenWeatherMapDataEngine::parseJSONForecastData(QString *jsonData, WeatherData* weatherData)
200     QScriptEngine engine;
201     QScriptValue result = engine.evaluate("weatherObject="+*jsonData);
204     if(result.isError())return false;
206     QScriptValueIterator it(result.property("list"));
208     int high = 0;
209     int low = 500;
210     QString icon = "";
212     QDateTime workingDateTime;
213     while (it.hasNext()) {
214         it.next();
215         //qDebug() << it.name() << it.value().toString() << it.value().property("img").toNumber();
217         QDateTime dateTime = QDateTime::fromTime_t(it.value().property("dt").toNumber());
219         //on the first iteration initialize the workingDateTime to parsedDateTime so we dont end up with a blank first day
220         if(workingDateTime.isNull())
221             workingDateTime = dateTime;
223         if(workingDateTime.date().day() == dateTime.date().day())
224         {
225             if(high < it.value().property("main").property("temp_max").toNumber()) high = it.value().property("main").property("temp_max").toInteger();
226             if(low > it.value().property("main").property("temp_min").toNumber()) low = it.value().property("main").property("temp_min").toInteger();
228             int iconIndex = convertImageNameToIndex(it.value().property("weather").property(0).property("icon").toString());
230             if(m_weatherPriorityHash[icon]< m_weatherPriorityHash[m_iconNameToWeatherHash[iconIndex]]) icon = m_iconNameToWeatherHash[iconIndex];
232         }
233         else
234         {
235             ForecastData *forecastDay = new ForecastData;
236             forecastDay->setDay(workingDateTime.date().toString("ddd"));
237             forecastDay->setHighTemp(kelvinToFahrenheit(high));
238             forecastDay->setLowTemp(kelvinToFahrenheit(low));
239             forecastDay->setIcon(icon);
240             weatherData->addForecastDay(forecastDay);
242             //initialize for next day of parsing;
243             workingDateTime = dateTime;
244             high = 0;
245             low=500;
246             icon = "";
247         }
248     }
249     return true;
253 void OpenWeatherMapDataEngine::generateJSONWeatherLookupTables()
255     m_iconNameToWeatherHash.insert(1, "sunny");
256     m_iconNameToWeatherHash.insert(2, "partlysunny");
257     m_iconNameToWeatherHash.insert(3, "cloudy");
258     m_iconNameToWeatherHash.insert(4, "cloudy");
259     m_iconNameToWeatherHash.insert(5, "rain");
260     m_iconNameToWeatherHash.insert(6, "tstorms");
261     m_iconNameToWeatherHash.insert(7, "sleet");
262     m_iconNameToWeatherHash.insert(8, "snow");
263     m_iconNameToWeatherHash.insert(9, "rain");
264     m_iconNameToWeatherHash.insert(10, "rain");
265     m_iconNameToWeatherHash.insert(11, "tstorms");
266     m_iconNameToWeatherHash.insert(12, "sleet");
267     m_iconNameToWeatherHash.insert(13, "snow");
268     m_iconNameToWeatherHash.insert(14, "snow");
269     m_iconNameToWeatherHash.insert(15, "cloudy");
270     m_iconNameToWeatherHash.insert(16, "sunny");
271     m_iconNameToWeatherHash.insert(17, "partlysunny");
272     m_iconNameToWeatherHash.insert(18, "rain");
273     m_iconNameToWeatherHash.insert(19, "snow");
276     m_weatherPriorityHash.insert("", 0);
277     m_weatherPriorityHash.insert("sunny", 1);
278     m_weatherPriorityHash.insert("partlysunny", 2);
279     m_weatherPriorityHash.insert("cloudy", 3);
280     m_weatherPriorityHash.insert("rain", 4);
281     m_weatherPriorityHash.insert("tstorms", 5);
282     m_weatherPriorityHash.insert("sleet", 6);
283     m_weatherPriorityHash.insert("snow", 7);
287 qlonglong OpenWeatherMapDataEngine::parseCityInformation(QString jsonData)
289     QScriptEngine engine;
291     //must have an object set equal to the class data received from the web or qt throws parse error
292     QScriptValue result = engine.evaluate("weatherObject="+jsonData);
294     if(result.property("message").toString() != "")
295         return -1;
296     else
297         return result.property("list").property("0").property("id").toInteger();
300 int OpenWeatherMapDataEngine::kelvinToFahrenheit(double k)
302     return qRound((k-273.15)*1.8+32);
305 int OpenWeatherMapDataEngine::convertImageNameToIndex(QString img)
307     QString iconName = img;
309     int lastSlash = iconName.lastIndexOf("/");
310     int lastDot = iconName.lastIndexOf(".");
311     int endShift=1;
313     if(lastSlash == -1 && lastDot == -1 && iconName[iconName.size()-1].isLetter())
314         return iconName.mid(0,iconName.size()-1).toInt();
316     if(iconName[lastDot-1].isLetter())
317         endShift = 2;
319    return  iconName.mid(lastSlash+1, lastDot-lastSlash-endShift).toInt();
322 void OpenWeatherMapDataEngine::loadLocalData()
324     m_weatherData = new WeatherData;
325     m_weatherData->setCachedDataFlag();
327     bool result = readFromCache();
328     //if we can't read from the cache file, read from the one included in the qrc!
329     if(!result)
330         readFromCache(":/data/cache.dat");
332     //qDebug() << m_rawJSONWeatherString << m_rawJSONForecastString;
334     if(!parseJSONWeatherData(&m_rawJSONWeatherString, m_weatherData))
335     {
336         emit networkTimeout();
337     }
338     else if(!parseJSONForecastData(&m_rawJSONForecastString, m_weatherData))
339     {
340         emit networkTimeout();
341     }
342     else
343     {
344         emit(dataAvailable(m_weatherData));
345     }
348 //FUNCTION writeToCache
349 //
350 //  *xmlData : QByteArray containing the XML byte stream retrieved through the API
351 //             call
352 //
353 //  Writes the data to a local cache file called cache.xml found in the same location
354 //  as the configuration file.
355 //
357 bool OpenWeatherMapDataEngine::writeToCache()
359     QFile cacheFile(m_globalSettings->dataPath() +"/cache.dat");
361     bool result = cacheFile.open(QFile::WriteOnly);
363     if(!result)
364     {
365         //qDebug() << "Cannot open cache file for writing!";
366         return false;
367     }
369     QDataStream stream(&cacheFile);
370     stream.setVersion(QDataStream::Qt_4_6);
371     stream << m_weatherData->currentCity();
372     stream << m_weatherData->lastUpdated();
373     stream << m_rawJSONWeatherString;
374     stream << m_rawJSONForecastString;
376     if(cacheFile.error() != QFile::NoError)
377     {
378         //qDebug() << "Cannot write to cache file!";
379         return false;
380     }
382     cacheFile.close();
383     //if we made it this far things are looking good, return true
385     return true;
388 //FUNCTION readFromCache
389 //
390 //  *xmlData : QByteArray to read the local json stream to.
391 //  alternateCacheFile: QString that can contain a different cache to read from,
392 //                      by default it will read from the same as the write function
393 //
394 //  Reads the xml data from a local cache or if desired will read from somewhere else.
395 //  like a resource file.
397 bool OpenWeatherMapDataEngine::readFromCache(QString alternateCacheFile)
399     QFile cacheFile;
401     if(alternateCacheFile.length() > 0)
402         cacheFile.setFileName(alternateCacheFile);
403     else
404         cacheFile.setFileName(m_globalSettings->dataPath() +"/cache.dat");
406     bool result = cacheFile.open(QFile::ReadOnly);
408     if(!result)
409     {
410         //qDebug() << "XMLCACHE: Cannot open cache file for reading!";
411         return false;
412     }
414     QDataStream stream(&cacheFile);
415     stream.setVersion(QDataStream::Qt_4_6);
416     QString cityString;
417     stream >> cityString;
418     m_weatherData->setCurrentCity(cityString);
419     QDateTime lastUpdated;
420     stream >> lastUpdated;
421     m_weatherData->setLastUpdated(lastUpdated);
422     stream >> m_rawJSONWeatherString;
423     stream >> m_rawJSONForecastString;
425     //we can at least assume if the size is zero, something is not right.
426     if(cacheFile.error() != QFile::NoError)
427     {
428         //qDebug() << "XMLCACHE: No data loaded...";
429         return false;
430     }
432     cacheFile.close();
433     //if we made it this far things are looking good, return true
435     return true;
438 QString OpenWeatherMapDataEngine::licenseString()
440     return QString("Weather data from  openweathermap.org under CC-BY-SA");