/** * Retrieves the details for the specified location * * @param pczLocation The string containing the name/code of the location * * @return A pointer to a list of LocationInfo entries, possibly empty, * if no details were found. Caller is responsible for freeing the list. */ GList * getLocationInfo(const gchar * pczLocation) { gint iRetCode = 0; gint iDataSize = 0; GList * pList = NULL; gchar * pcEscapedLocation = convertToASCII(pczLocation); gsize len = getWOEIDQueryLength(pcEscapedLocation); gchar * cQueryBuffer = g_malloc0(len); gint iRet = getWOEIDQuery(cQueryBuffer, pcEscapedLocation); g_free(pcEscapedLocation); LXW_LOG(LXW_DEBUG, "yahooutil::getLocationInfo(%s): query[%d]: %s", pczLocation, iRet, cQueryBuffer); gpointer pResponse = getURL(cQueryBuffer, &iRetCode, &iDataSize); if (!pResponse || iRetCode != HTTP_STATUS_OK) { LXW_LOG(LXW_ERROR, "yahooutil::getLocationInfo(%s): Failed with error code %d", pczLocation, iRetCode); } else { LXW_LOG(LXW_DEBUG, "yahooutil::getLocationInfo(%s): Response code: %d, size: %d", pczLocation, iRetCode, iDataSize); LXW_LOG(LXW_VERBOSE, "yahooutil::getLocation(%s): Contents: %s", pczLocation, (const char *)pResponse); iRet = parseResponse(pResponse, &pList, NULL); LXW_LOG(LXW_DEBUG, "yahooutil::getLocation(%s): Response parsing returned %d", pczLocation, iRet); if (iRet) { // failure g_list_free_full(pList, freeLocation); } } g_free(cQueryBuffer); g_free(pResponse); return pList; }
/** * Retrieves the forecast for the specified location WOEID * * @param pczWOEID The string containing the WOEID of the location * @param czUnits The character containing the units for the forecast (c|f) * @param pForecast The pointer to the forecast to be filled. If set to NULL, * a new one will be allocated. * */ void getForecastInfo(const gchar * pczWOEID, const gchar czUnits, gpointer * pForecast) { gint iRetCode = 0; gint iDataSize = 0; gsize len = getForecastQueryLength(pczWOEID); gchar * cQueryBuffer = g_malloc(len + 1); gint iRet = getForecastQuery(cQueryBuffer, pczWOEID, czUnits); LXW_LOG(LXW_DEBUG, "yahooutil::getForecastInfo(%s): query[%d]: %s", pczWOEID, iRet, cQueryBuffer); gpointer pResponse = getURL(cQueryBuffer, &iRetCode, &iDataSize); if (!pResponse || iRetCode != HTTP_STATUS_OK) { LXW_LOG(LXW_ERROR, "yahooutil::getForecastInfo(%s): Failed with error code %d", pczWOEID, iRetCode); } else { LXW_LOG(LXW_DEBUG, "yahooutil::getForecastInfo(%s): Response code: %d, size: %d", pczWOEID, iRetCode, iDataSize); LXW_LOG(LXW_VERBOSE, "yahooutil::getForecastInfo(%s): Contents: %s", pczWOEID, (const char *)pResponse); iRet = parseResponse(pResponse, NULL, pForecast); LXW_LOG(LXW_DEBUG, "yahooutil::getForecastInfo(%s): Response parsing returned %d", pczWOEID, iRet); if (iRet) { freeForecast(*pForecast); *pForecast = NULL; } } g_free(cQueryBuffer); g_free(pResponse); }
/** * Converts the passed-in string from UTF-8 to ASCII for http transmisison. * * @param pczInString String to convert * * @return The converted string which MUST BE FREED BY THE CALLER. */ static gchar * convertToASCII(const gchar *pczInString) { // for UTF-8 to ASCII conversions setlocale(LC_CTYPE, "en_US"); GError * pError = NULL; gsize szBytesRead = 0; gsize szBytesWritten = 0; gchar * pcConvertedString = g_convert(pczInString, strlen(pczInString), "ASCII//TRANSLIT", "UTF-8", &szBytesRead, &szBytesWritten, &pError); if (pError) { LXW_LOG(LXW_ERROR, "yahooutil::convertToASCII(%s): Error: %s", pczInString, pError->message); g_error_free(pError); pcConvertedString = g_strndup(pczInString, strlen(pczInString)); } // now escape space, if any xmlChar * pxEscapedString = xmlURIEscapeStr((const xmlChar *)pcConvertedString, NULL); if (pxEscapedString) { // release ConvertedString, reset it, then release EscapedString. // I know it's confusing, but keeps everything as a gchar and g_free g_free(pcConvertedString); pcConvertedString = g_strndup((const gchar *)pxEscapedString, strlen((const gchar *)pxEscapedString)); xmlFree(pxEscapedString); } // restore locale to default setlocale(LC_CTYPE, ""); return pcConvertedString; }
/** * Parses the response and fills in the supplied list with entries (if any) * * @param pResponse Pointer to the response received. * @param pList Pointer to the pointer to the list to populate. * @param pForecast Pointer to the pointer to the forecast to retrieve. * * @return 0 on success, -1 on failure * * @note If the pList pointer is NULL or the pForecast pointer is NULL, * nothing is done and failure is returned. Otherwise, the appropriate * pointer is set based on the name of the XML element: * 'Result' for GList (pList) * 'channel' for Forecast (pForecast) */ static gint parseResponse(gpointer pResponse, GList ** pList, gpointer * pForecast) { int iLocation = (pList)?1:0; xmlDocPtr pDoc = xmlReadMemory(CONSTCHAR_P(pResponse), strlen(pResponse), "", NULL, 0); if (!pDoc) { // failed LXW_LOG(LXW_ERROR, "yahooutil::parseResponse(): Failed to parse response %s", CONSTCHAR_P(pResponse)); return -1; } xmlNodePtr pRoot = xmlDocGetRootElement(pDoc); // the second part of the if can be broken out if (!pRoot || !xmlStrEqual(pRoot->name, CONSTXMLCHAR_P("query"))) { // failed LXW_LOG(LXW_ERROR, "yahooutil::parseResponse(): Failed to retrieve root %s", CONSTCHAR_P(pResponse)); xmlFreeDoc(pDoc); return -1; } // use xpath to find /query/results/Result xmlXPathInit(); xmlXPathContextPtr pXCtxt = xmlXPathNewContext(pDoc); const char * pczExpression = "/query/results/channel"; if (iLocation) { xmlXPathRegisterNs(pXCtxt, CONSTXMLCHAR_P("ns1"), CONSTXMLCHAR_P("http://where.yahooapis.com/v1/schema.rng")); pczExpression = "///ns1:place"; } // have some results... xmlNodeSetPtr pNodeSet = evaluateXPathExpression(pXCtxt, pczExpression); if (!pNodeSet) { // error, or no results found -- failed xmlXPathFreeContext(pXCtxt); xmlFreeDoc(pDoc); return -1; } int iCount = 0; int iSize = pNodeSet->nodeNr; gint iRetVal = 0; for (; iCount < iSize; ++iCount) { if (pNodeSet->nodeTab) { xmlNodePtr pNode = pNodeSet->nodeTab[iCount]; if (pNode && pNode->type == XML_ELEMENT_NODE) { if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("place"))) { gpointer pEntry = processResultNode(pNode); if (pEntry && pList) { *pList = g_list_prepend(*pList, pEntry); } } else if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("channel"))) { ForecastInfo * pEntry = NULL; gboolean bNewed = FALSE; /* Check if forecast is allocated, if not, * allocate and populate */ if (pForecast) { if (*pForecast) { pEntry = (ForecastInfo *)*pForecast; } else { pEntry = (ForecastInfo *)g_try_new0(ForecastInfo, 1); bNewed = TRUE; } if (!pEntry) { iRetVal = -1; } else { *pForecast = processChannelNode(pNode, pEntry); if (!*pForecast) { /* Failed, forecast is freed by caller */ /* Unless it was just newed... */ if (bNewed) { g_free(pEntry); } iRetVal = -1; } } }// end else if pForecast }// end else if 'channel' }// end if element }// end if nodeTab }// end for noteTab size xmlXPathFreeNodeSet(pNodeSet); xmlXPathFreeContext(pXCtxt); xmlFreeDoc(pDoc); return iRetVal; }
/** * Processes the passed-in node to generate a ForecastInfo entry * * @param pNode Pointer to the XML Channel Node. * @param pEntry Pointer to the ForecastInfo entry to be filled in. * * @return A newly created ForecastInfo entry on success, or NULL on failure. */ static gpointer processChannelNode(xmlNodePtr pNode, ForecastInfo * pEntry) { if (!pNode) { return NULL; } xmlNodePtr pCurr = pNode->xmlChildrenNode; for (; pCurr != NULL; pCurr = pCurr->next) { if (pCurr->type == XML_ELEMENT_NODE) { if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("title"))) { /* Evaluate title to see if there was an error */ char * pcContent = CHAR_P(xmlNodeListGetString(pCurr->doc, pCurr->xmlChildrenNode, 1)); if (strstr(pcContent, "Error")) { xmlFree(XMLCHAR_P(pcContent)); do { pCurr = pCurr->next; } while (pCurr && !xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("item"))); xmlNodePtr pChild = (pCurr)?pCurr->xmlChildrenNode:NULL; for (; pChild != NULL; pChild = pChild->next) { if (pChild->type == XML_ELEMENT_NODE && xmlStrEqual(pChild->name, CONSTXMLCHAR_P("title"))) { pcContent = CHAR_P(xmlNodeListGetString(pChild->doc, pChild->xmlChildrenNode, 1)); LXW_LOG(LXW_ERROR, "yahooutil::processChannelNode(): Forecast retrieval error: %s", pcContent); xmlFree(XMLCHAR_P(pcContent)); } } return NULL; } else { xmlFree(XMLCHAR_P(pcContent)); /* ...and continue... */ } } else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("item"))) { /* item child element gets 'special' treatment */ processItemNode((gpointer *)&pEntry, pCurr); } else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("units"))) { // distance const char * pczDistance = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("distance"))); gsize distanceLength = ((pczDistance)?strlen(pczDistance):0); setStringIfDifferent(&pEntry->units_.pcDistance_, pczDistance, distanceLength); xmlFree(XMLCHAR_P(pczDistance)); // pressure const char * pczPressure = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("pressure"))); gsize pressureLength = ((pczPressure)?strlen(pczPressure):0); setStringIfDifferent(&pEntry->units_.pcPressure_, pczPressure, pressureLength); xmlFree(XMLCHAR_P(pczPressure)); // speed const char * pczSpeed = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("speed"))); gsize speedLength = ((pczSpeed)?strlen(pczSpeed):0); setStringIfDifferent(&pEntry->units_.pcSpeed_, pczSpeed, speedLength); xmlFree(XMLCHAR_P(pczSpeed)); // temperature const char * pczTemperature = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("temperature"))); gsize temperatureLength = ((pczTemperature)?strlen(pczTemperature):0); setStringIfDifferent(&pEntry->units_.pcTemperature_, pczTemperature, temperatureLength); xmlFree(XMLCHAR_P(pczTemperature)); } else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("wind"))) { // chill const char * pczChill = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("chill"))); setIntIfDifferent(&pEntry->iWindChill_, pczChill); xmlFree(XMLCHAR_P(pczChill)); // direction const char * pczDirection = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("direction"))); gint iValue = (gint)g_ascii_strtoll((pczDirection)?pczDirection:"999", NULL, 10); const gchar * pczDir = WIND_DIRECTION(iValue); setStringIfDifferent(&pEntry->pcWindDirection_, pczDir, strlen(pczDir)); xmlFree(XMLCHAR_P(pczDirection)); // speed const char * pczSpeed = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("speed"))); setIntIfDifferent(&pEntry->iWindSpeed_, pczSpeed); xmlFree(XMLCHAR_P(pczSpeed)); } else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("atmosphere"))) { // humidity const char * pczHumidity = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("humidity"))); setIntIfDifferent(&pEntry->iHumidity_, pczHumidity); xmlFree(XMLCHAR_P(pczHumidity)); // pressure const char * pczPressure = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("pressure"))); pEntry->dPressure_ = g_ascii_strtod((pczPressure)?pczPressure:"0", NULL); xmlFree(XMLCHAR_P(pczPressure)); // visibility const char * pczVisibility = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("visibility"))); pEntry->dVisibility_ = g_ascii_strtod((pczVisibility)?pczVisibility:"0", NULL); // need to divide by 100 //pEntry->dVisibility_ = pEntry->dVisibility_/100; xmlFree(XMLCHAR_P(pczVisibility)); // state const char * pczState = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("rising"))); pEntry->pressureState_ = (PressureState)g_ascii_strtoll((pczState)?pczState:"0", NULL, 10); xmlFree(XMLCHAR_P(pczState)); } else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("astronomy"))) { // sunrise const char * pczSunrise = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("sunrise"))); gsize sunriseLength = ((pczSunrise)?strlen(pczSunrise):0); setStringIfDifferent(&pEntry->pcSunrise_, pczSunrise, sunriseLength); xmlFree(XMLCHAR_P(pczSunrise)); // sunset const char * pczSunset = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("sunset"))); gsize sunsetLength = ((pczSunset)?strlen(pczSunset):0); setStringIfDifferent(&pEntry->pcSunset_, pczSunset, sunsetLength); xmlFree(XMLCHAR_P(pczSunset)); } } } return pEntry; }
/** * Processes the passed-in node to generate a LocationInfo entry * * @param pEntry Pointer to the pointer to the ForecastInfo entry being filled. * @param pNode Pointer to the XML Item Node. * * @return 0 on success, -1 on failure */ static gint processItemNode(gpointer * pEntry, xmlNodePtr pNode) { if (!pNode || !pEntry) { return -1; } ForecastInfo * pInfo = *((ForecastInfo **)pEntry); xmlNodePtr pCurr = pNode->xmlChildrenNode; int iForecastCount = 0; for (; pCurr != NULL; pCurr = pCurr->next) { if (pCurr->type == XML_ELEMENT_NODE) { if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("condition"))) { const char * pczDate = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("date"))); const char * pczTemp = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("temp"))); const char * pczText = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("text"))); gsize dateLength = ((pczDate)?strlen(pczDate):0); gsize textLength = ((pczText)?strlen(pczText):0); setStringIfDifferent(&pInfo->pcTime_, pczDate, dateLength); setIntIfDifferent(&pInfo->iTemperature_, pczTemp); setStringIfDifferent(&pInfo->pcConditions_, pczText, textLength); xmlFree(XMLCHAR_P(pczDate)); xmlFree(XMLCHAR_P(pczTemp)); xmlFree(XMLCHAR_P(pczText)); } else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("description"))) { char * pcContent = CHAR_P(xmlNodeListGetString(pCurr->doc, pCurr->xmlChildrenNode, 1)); char * pcSavePtr = NULL; // initial call to find the first '"' strtok_r(pcContent, "\"", &pcSavePtr); // second call to find the second '"' char * pcImageURL = strtok_r(NULL, "\"", &pcSavePtr); // found the image if (pcImageURL && strstr(pcImageURL, "yimg.com")) { LXW_LOG(LXW_DEBUG, "yahooutil::processItemNode(): IMG URL: %s", pcImageURL); setImageIfDifferent(&pInfo->pcImageURL_, &pInfo->pImage_, pcImageURL, strlen(pcImageURL)); } xmlFree(XMLCHAR_P(pcContent)); } else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("forecast"))) { ++iForecastCount; const char * pczDay = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("day"))); const char * pczHigh = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("high"))); const char * pczLow = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("low"))); const char * pczText = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("text"))); gsize dayLength = ((pczDay)?strlen(pczDay):0); gsize textLength = ((pczText)?strlen(pczText):0); if (iForecastCount == 1) { setStringIfDifferent(&pInfo->today_.pcDay_, pczDay, dayLength); setIntIfDifferent(&pInfo->today_.iHigh_, pczHigh); setIntIfDifferent(&pInfo->today_.iLow_, pczLow); setStringIfDifferent(&pInfo->today_.pcConditions_, pczText, textLength); } else { setStringIfDifferent(&pInfo->tomorrow_.pcDay_, pczDay, dayLength); setIntIfDifferent(&pInfo->tomorrow_.iHigh_, pczHigh); setIntIfDifferent(&pInfo->tomorrow_.iLow_, pczLow); setStringIfDifferent(&pInfo->tomorrow_.pcConditions_, pczText, textLength); } xmlFree(XMLCHAR_P(pczDay)); xmlFree(XMLCHAR_P(pczHigh)); xmlFree(XMLCHAR_P(pczLow)); xmlFree(XMLCHAR_P(pczText)); } } } return 0; }
/** * Compares the URL of an image to the 'new' value. If the two * are different, the image at the 'new' URL is retrieved and replaces * the old one. The old one is freed. * * @param pcStorage Pointer to the storage location with the first value. * @param pImage Pointer to the image storage. * @param pczNewURL The new url. * @param szURLLength The length of the new URL. * * @return 0 on succes, -1 on failure. */ static gint setImageIfDifferent(gchar ** pcStorage, GdkPixbuf ** pImage, const gchar * pczNewURL, const gsize szURLLength) { int err = 0; // if diffrent, clear and set if (g_strcmp0(*pcStorage, pczNewURL)) { g_free(*pcStorage); *pcStorage = g_strndup(pczNewURL, szURLLength); if (*pImage) { g_object_unref(*pImage); *pImage = NULL; } // retrieve the URL and create the new image gint iRetCode = 0; gint iDataSize = 0; gpointer pResponse = getURL(pczNewURL, &iRetCode, &iDataSize); if (!pResponse || iRetCode != HTTP_STATUS_OK) { LXW_LOG(LXW_ERROR, "yahooutil::setImageIfDifferent(): Failed to get URL (%d, %d)", iRetCode, iDataSize); return -1; } GInputStream * pInputStream = g_memory_input_stream_new_from_data(pResponse, iDataSize, g_free); GError * pError = NULL; *pImage = gdk_pixbuf_new_from_stream(pInputStream, NULL, &pError); if (!*pImage) { LXW_LOG(LXW_ERROR, "yahooutil::setImageIfDifferent(): PixBuff allocation failed: %s", pError->message); g_error_free(pError); err = -1; } if (!g_input_stream_close(pInputStream, NULL, &pError)) { LXW_LOG(LXW_ERROR, "yahooutil::setImageIfDifferent(): InputStream closure failed: %s", pError->message); g_error_free(pError); err = -1; } } return err; }
/** * Prints the contents of the supplied entry to stdout * * @param pEntry Entry contents of which to print. * */ void printForecast(gpointer pEntry G_GNUC_UNUSED) { #ifdef DEBUG if (!pEntry) { LXW_LOG(LXW_ERROR, "forecast::printForecast(): Entry: NULL"); return; } ForecastInfo * pInfo = (ForecastInfo *)pEntry; LXW_LOG(LXW_VERBOSE, "Forecast at %s:", (const char *)pInfo->pcTime_); LXW_LOG(LXW_VERBOSE, "\tTemperature: %d%s", pInfo->iTemperature_, (const char *)pInfo->units_.pcTemperature_); LXW_LOG(LXW_VERBOSE, "\tHumidity: %d%s", pInfo->iHumidity_, "%"); LXW_LOG(LXW_VERBOSE, "\tWind chill: %d%s, speed: %d%s, direction %s", pInfo->iWindChill_, (const char *)pInfo->units_.pcTemperature_, pInfo->iWindSpeed_, (const char *)pInfo->units_.pcSpeed_, pInfo->pcWindDirection_); LXW_LOG(LXW_VERBOSE, "\tPressure: %2.02f%s and %s", pInfo->dPressure_, (const char *)pInfo->units_.pcPressure_, ((pInfo->pressureState_ == STEADY)?"steady": (pInfo->pressureState_ == RISING)?"rising": (pInfo->pressureState_ == FALLING)?"falling":"?")); LXW_LOG(LXW_VERBOSE, "\tConditions: %s", (const char *)pInfo->pcConditions_); LXW_LOG(LXW_VERBOSE, "\tVisibility: %3.02f%s", pInfo->dVisibility_, (const char *)pInfo->units_.pcDistance_); LXW_LOG(LXW_VERBOSE, "\tSunrise: %s", (const char *)pInfo->pcSunrise_); LXW_LOG(LXW_VERBOSE, "\tSunset: %s", (const char *)pInfo->pcSunset_); LXW_LOG(LXW_VERBOSE, "\tImage URL: %s", pInfo->pcImageURL_); LXW_LOG(LXW_VERBOSE, "\tTwo-day forecast:"); LXW_LOG(LXW_VERBOSE, "\t\t%s: High: %d%s, Low: %d%s, Conditions: %s", (const char *)pInfo->today_.pcDay_, pInfo->today_.iHigh_, (const char *)pInfo->units_.pcTemperature_, pInfo->today_.iLow_, (const char *)pInfo->units_.pcTemperature_, (const char *)pInfo->today_.pcConditions_); LXW_LOG(LXW_VERBOSE, "\t\t%s: High: %d%s, Low: %d%s, Conditions: %s", (const char *)pInfo->tomorrow_.pcDay_, pInfo->tomorrow_.iHigh_, (const char *)pInfo->units_.pcTemperature_, pInfo->tomorrow_.iLow_, (const char *)pInfo->units_.pcTemperature_, (const char *)pInfo->tomorrow_.pcConditions_); #endif }