fix crash problems and update the openweathermap URLs
[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://api.openweathermap.org/data/2.5/find?q="+m_preparedCity+"&appid=62da841a6b62ce48d249c6e2d00a2102";
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() << "responseReceived(): Network Error: (" << m_reply->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://api.openweathermap.org/data/2.5/weather?id="+QString::number(m_cityId)+"&appid=62da841a6b62ce48d249c6e2d00a2102";
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://api.openweathermap.org/data/2.5/forecast?id="+QString::number(m_cityId)+"&appid=62da841a6b62ce48d249c6e2d00a2102";
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() << "OpenWeatherMapDataEngine::currentWeatherResponseReceived()";
132         qDebug() << "Network Error: (" << m_reply->error() << ")" << m_reply->errorString();
133         emit networkTimeout();
134         m_reply->deleteLater();
135         return;
136     }
137     m_rawJSONWeatherString = m_reply->readAll();
138     bool result = parseJSONWeatherData(&m_rawJSONWeatherString, m_weatherData);
139     if(!result)
140         emit networkTimeout();
141     else
142     {
143         m_weatherReceived = true;
144         checkIfDone();
145     }
148 void OpenWeatherMapDataEngine::forecastResponseReceived()
150     if(m_forecastReply->error() != QNetworkReply::NoError)
151     {
152         qDebug() << "OpenWeatherMapDataEngine::forecastResponseReceived()";
153         qDebug() << "Network Error: (" << m_reply->error() << ")" << m_reply->errorString();
154         emit networkTimeout();
155         m_forecastReply->deleteLater();
156         return;
157     }
158     m_forecastNetworkTimer.stop();
159     m_rawJSONForecastString = m_forecastReply->readAll();
160     bool result = parseJSONForecastData(&m_rawJSONForecastString, m_weatherData);
161     if(!result)
162         emit networkTimeout();
163     else
164     {
165         m_forecastReceived = true;
166         checkIfDone();
167     }
170 void OpenWeatherMapDataEngine::checkIfDone()
172     if(m_forecastReceived && m_weatherReceived)
173     {
174         m_weatherData->setLastUpdated(QDateTime::currentDateTime());
175         writeToCache();
176         emit(dataAvailable(m_weatherData));
177     }
180 bool OpenWeatherMapDataEngine::parseJSONWeatherData(QString *jsonData, WeatherData *weatherData)
182     QScriptEngine engine;
183     QScriptValue result = engine.evaluate("weatherObject="+*jsonData);
185     if(result.isError())return false;
187     int temp = kelvinToFahrenheit(result.property("main").property("temp").toNumber());
188     QDateTime localTime = QDateTime::fromTime_t(result.property("dt").toNumber());
190     int iconIndex = convertImageNameToIndex(result.property("weather").property(0).property("icon").toString());
192     QString icon = m_iconNameToWeatherHash[iconIndex];
194     weatherData->setCurrentTemp(temp);
195     weatherData->setLocalTime(localTime);
196     weatherData->setIcon(icon);
197     return true;
200 bool OpenWeatherMapDataEngine::parseJSONForecastData(QString *jsonData, WeatherData* weatherData)
202     QScriptEngine engine;
203     QScriptValue result = engine.evaluate("weatherObject="+*jsonData);
206     if(result.isError())return false;
208     QScriptValueIterator it(result.property("list"));
210     int high = 0;
211     int low = 500;
212     QString icon = "";
214     QDateTime workingDateTime;
215     while (it.hasNext()) {
216         it.next();
217         //qDebug() << it.name() << it.value().toString() << it.value().property("img").toNumber();
219         QDateTime dateTime = QDateTime::fromTime_t(it.value().property("dt").toNumber());
221         //on the first iteration initialize the workingDateTime to parsedDateTime so we dont end up with a blank first day
222         if(workingDateTime.isNull())
223             workingDateTime = dateTime;
225         if(workingDateTime.date().day() == dateTime.date().day())
226         {
227             if(high < it.value().property("main").property("temp_max").toNumber()) high = it.value().property("main").property("temp_max").toInteger();
228             if(low > it.value().property("main").property("temp_min").toNumber()) low = it.value().property("main").property("temp_min").toInteger();
230             int iconIndex = convertImageNameToIndex(it.value().property("weather").property(0).property("icon").toString());
232             if(m_weatherPriorityHash[icon]< m_weatherPriorityHash[m_iconNameToWeatherHash[iconIndex]]) icon = m_iconNameToWeatherHash[iconIndex];
234         }
235         else
236         {
237             ForecastData *forecastDay = new ForecastData;
238             forecastDay->setDay(workingDateTime.date().toString("ddd"));
239             forecastDay->setHighTemp(kelvinToFahrenheit(high));
240             forecastDay->setLowTemp(kelvinToFahrenheit(low));
241             forecastDay->setIcon(icon);
242             weatherData->addForecastDay(forecastDay);
244             //initialize for next day of parsing;
245             workingDateTime = dateTime;
246             high = 0;
247             low=500;
248             icon = "";
249         }
250     }
251     return true;
255 void OpenWeatherMapDataEngine::generateJSONWeatherLookupTables()
257     m_iconNameToWeatherHash.insert(1, "sunny");
258     m_iconNameToWeatherHash.insert(2, "partlysunny");
259     m_iconNameToWeatherHash.insert(3, "cloudy");
260     m_iconNameToWeatherHash.insert(4, "cloudy");
261     m_iconNameToWeatherHash.insert(5, "rain");
262     m_iconNameToWeatherHash.insert(6, "tstorms");
263     m_iconNameToWeatherHash.insert(7, "sleet");
264     m_iconNameToWeatherHash.insert(8, "snow");
265     m_iconNameToWeatherHash.insert(9, "rain");
266     m_iconNameToWeatherHash.insert(10, "rain");
267     m_iconNameToWeatherHash.insert(11, "tstorms");
268     m_iconNameToWeatherHash.insert(12, "sleet");
269     m_iconNameToWeatherHash.insert(13, "snow");
270     m_iconNameToWeatherHash.insert(14, "snow");
271     m_iconNameToWeatherHash.insert(15, "cloudy");
272     m_iconNameToWeatherHash.insert(16, "sunny");
273     m_iconNameToWeatherHash.insert(17, "partlysunny");
274     m_iconNameToWeatherHash.insert(18, "rain");
275     m_iconNameToWeatherHash.insert(19, "snow");
278     m_weatherPriorityHash.insert("", 0);
279     m_weatherPriorityHash.insert("sunny", 1);
280     m_weatherPriorityHash.insert("partlysunny", 2);
281     m_weatherPriorityHash.insert("cloudy", 3);
282     m_weatherPriorityHash.insert("rain", 4);
283     m_weatherPriorityHash.insert("tstorms", 5);
284     m_weatherPriorityHash.insert("sleet", 6);
285     m_weatherPriorityHash.insert("snow", 7);
289 qlonglong OpenWeatherMapDataEngine::parseCityInformation(QString jsonData)
291     QScriptEngine engine;
293     //must have an object set equal to the class data received from the web or qt throws parse error
294     QScriptValue result = engine.evaluate("weatherObject="+jsonData);
296     if(result.property("message").toString() != "accurate")
297         return -1;
298     else
299         return result.property("list").property("0").property("id").toInteger();
302 int OpenWeatherMapDataEngine::kelvinToFahrenheit(double k)
304     return qRound((k-273.15)*1.8+32);
307 int OpenWeatherMapDataEngine::convertImageNameToIndex(QString img)
309     QString iconName = img;
311     int lastSlash = iconName.lastIndexOf("/");
312     int lastDot = iconName.lastIndexOf(".");
313     int endShift=1;
315     if(lastSlash == -1 && lastDot == -1 && iconName[iconName.size()-1].isLetter())
316         return iconName.mid(0,iconName.size()-1).toInt();
318     if(iconName[lastDot-1].isLetter())
319         endShift = 2;
321    return  iconName.mid(lastSlash+1, lastDot-lastSlash-endShift).toInt();
324 void OpenWeatherMapDataEngine::loadLocalData()
326     m_weatherData = new WeatherData;
327     m_weatherData->setCachedDataFlag();
329     bool result = readFromCache();
330     //if we can't read from the cache file, read from the one included in the qrc!
331     if(!result)
332         readFromCache(":/data/cache.dat");
334     //qDebug() << m_rawJSONWeatherString << m_rawJSONForecastString;
336     if(!parseJSONWeatherData(&m_rawJSONWeatherString, m_weatherData))
337     {
338         emit networkTimeout();
339     }
340     else if(!parseJSONForecastData(&m_rawJSONForecastString, m_weatherData))
341     {
342         emit networkTimeout();
343     }
344     else
345     {
346         emit(dataAvailable(m_weatherData));
347     }
350 //FUNCTION writeToCache
351 //
352 //  *xmlData : QByteArray containing the XML byte stream retrieved through the API
353 //             call
354 //
355 //  Writes the data to a local cache file called cache.xml found in the same location
356 //  as the configuration file.
357 //
359 bool OpenWeatherMapDataEngine::writeToCache()
361     QFile cacheFile(m_globalSettings->dataPath() +"/cache.dat");
363     bool result = cacheFile.open(QFile::WriteOnly);
365     if(!result)
366     {
367         //qDebug() << "Cannot open cache file for writing!";
368         return false;
369     }
371     QDataStream stream(&cacheFile);
372     stream.setVersion(QDataStream::Qt_4_6);
373     stream << m_weatherData->currentCity();
374     stream << m_weatherData->lastUpdated();
375     stream << m_rawJSONWeatherString;
376     stream << m_rawJSONForecastString;
378     if(cacheFile.error() != QFile::NoError)
379     {
380         //qDebug() << "Cannot write to cache file!";
381         return false;
382     }
384     cacheFile.close();
385     //if we made it this far things are looking good, return true
387     return true;
390 //FUNCTION readFromCache
391 //
392 //  *xmlData : QByteArray to read the local json stream to.
393 //  alternateCacheFile: QString that can contain a different cache to read from,
394 //                      by default it will read from the same as the write function
395 //
396 //  Reads the xml data from a local cache or if desired will read from somewhere else.
397 //  like a resource file.
399 bool OpenWeatherMapDataEngine::readFromCache(QString alternateCacheFile)
401     QFile cacheFile;
403     if(alternateCacheFile.length() > 0)
404         cacheFile.setFileName(alternateCacheFile);
405     else
406         cacheFile.setFileName(m_globalSettings->dataPath() +"/cache.dat");
408     bool result = cacheFile.open(QFile::ReadOnly);
410     if(!result)
411     {
412         //qDebug() << "XMLCACHE: Cannot open cache file for reading!";
413         return false;
414     }
416     QDataStream stream(&cacheFile);
417     stream.setVersion(QDataStream::Qt_4_6);
418     QString cityString;
419     stream >> cityString;
420     m_weatherData->setCurrentCity(cityString);
421     QDateTime lastUpdated;
422     stream >> lastUpdated;
423     m_weatherData->setLastUpdated(lastUpdated);
424     stream >> m_rawJSONWeatherString;
425     stream >> m_rawJSONForecastString;
427     //we can at least assume if the size is zero, something is not right.
428     if(cacheFile.error() != QFile::NoError)
429     {
430         //qDebug() << "XMLCACHE: No data loaded...";
431         return false;
432     }
434     cacheFile.close();
435     //if we made it this far things are looking good, return true
437     return true;
440 QString OpenWeatherMapDataEngine::licenseString()
442     return QString("Weather data from openweathermap.org under CC-BY-SA 4.0");