/*
* Copyright (C) 2005-2013 Team XBMC
* http://xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBMC; see the file COPYING. If not, see
* .
*
*/
#if (defined HAVE_CONFIG_H) && (!defined TARGET_WINDOWS)
#include "config.h"
#endif
#include "Weather.h"
#include "filesystem/ZipManager.h"
#include "XMLUtils.h"
#include "utils/POUtils.h"
#include "Temperature.h"
#include "network/Network.h"
#include "Application.h"
#include "settings/lib/Setting.h"
#include "settings/Settings.h"
#include "guilib/GUIWindowManager.h"
#include "GUIUserMessages.h"
#include "XBDateTime.h"
#include "LangInfo.h"
#include "guilib/WindowIDs.h"
#include "guilib/LocalizeStrings.h"
#include "filesystem/Directory.h"
#include "StringUtils.h"
#include "URIUtils.h"
#include "log.h"
#include "addons/AddonManager.h"
#include "interfaces/generic/ScriptInvocationManager.h"
#include "CharsetConverter.h"
#include "addons/GUIDialogAddonSettings.h"
using namespace std;
using namespace ADDON;
using namespace XFILE;
#define LOCALIZED_TOKEN_FIRSTID 370
#define LOCALIZED_TOKEN_LASTID 395
#define LOCALIZED_TOKEN_FIRSTID2 1350
#define LOCALIZED_TOKEN_LASTID2 1449
#define LOCALIZED_TOKEN_FIRSTID3 11
#define LOCALIZED_TOKEN_LASTID3 17
#define LOCALIZED_TOKEN_FIRSTID4 71
#define LOCALIZED_TOKEN_LASTID4 97
/*
FIXME'S
>strings are not centered
*/
#define WEATHER_BASE_PATH "special://temp/weather/"
#define WEATHER_ICON_PATH "special://temp/weather/"
#define WEATHER_SOURCE_FILE "special://xbmc/media/weather.zip"
bool CWeatherJob::m_imagesOkay = false;
CWeatherJob::CWeatherJob(int location)
{
m_location = location;
}
bool CWeatherJob::DoWork()
{
// wait for the network
if (!g_application.getNetwork().IsAvailable(true))
return false;
AddonPtr addon;
if (!ADDON::CAddonMgr::Get().GetAddon(CSettings::Get().GetString("weather.addon"), addon, ADDON_SCRIPT_WEATHER))
return false;
// initialize our sys.argv variables
std::vector argv;
argv.push_back(addon->LibPath());
std::string strSetting = StringUtils::Format("%i", m_location);
argv.push_back(strSetting);
// Download our weather
CLog::Log(LOGINFO, "WEATHER: Downloading weather");
// call our script, passing the areacode
int scriptId = -1;
if ((scriptId = CScriptInvocationManager::Get().Execute(argv[0], addon, argv)) >= 0)
{
while (true)
{
if (!CScriptInvocationManager::Get().IsRunning(scriptId))
break;
Sleep(100);
}
if (!m_imagesOkay)
{
CDirectory::Create(WEATHER_BASE_PATH);
g_ZipManager.ExtractArchive(WEATHER_SOURCE_FILE, WEATHER_BASE_PATH);
m_imagesOkay = true;
}
SetFromProperties();
// and send a message that we're done
CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_WEATHER_FETCHED);
g_windowManager.SendThreadMessage(msg);
}
else
CLog::Log(LOGERROR, "WEATHER: Weather download failed!");
return true;
}
const CWeatherInfo &CWeatherJob::GetInfo() const
{
return m_info;
}
void CWeatherJob::LocalizeOverviewToken(std::string &token)
{
// This routine is case-insensitive.
std::string strLocStr;
if (!token.empty())
{
ilocalizedTokens i;
i = m_localizedTokens.find(token);
if (i != m_localizedTokens.end())
{
strLocStr = g_localizeStrings.Get(i->second);
}
}
if (strLocStr == "")
strLocStr = token; //if not found, let fallback
token = strLocStr;
}
void CWeatherJob::LocalizeOverview(std::string &str)
{
vector words = StringUtils::Split(str, " ");
for (vector::iterator i = words.begin(); i != words.end(); ++i)
LocalizeOverviewToken(*i);
str = StringUtils::Join(words, " ");
}
// input param must be kmh
int CWeatherJob::ConvertSpeed(int curSpeed)
{
switch (g_langInfo.GetSpeedUnit())
{
case CLangInfo::SPEED_UNIT_KMH:
break;
case CLangInfo::SPEED_UNIT_MPS:
curSpeed=(int)(curSpeed * (1000.0 / 3600.0) + 0.5);
break;
case CLangInfo::SPEED_UNIT_MPH:
curSpeed=(int)(curSpeed / (8.0 / 5.0));
break;
case CLangInfo::SPEED_UNIT_MPMIN:
curSpeed=(int)(curSpeed * (1000.0 / 3600.0) + 0.5*60);
break;
case CLangInfo::SPEED_UNIT_FTH:
curSpeed=(int)(curSpeed * 3280.8398888889f);
break;
case CLangInfo::SPEED_UNIT_FTMIN:
curSpeed=(int)(curSpeed * 54.6805555556f);
break;
case CLangInfo::SPEED_UNIT_FTS:
curSpeed=(int)(curSpeed * 0.911344f);
break;
case CLangInfo::SPEED_UNIT_KTS:
curSpeed=(int)(curSpeed * 0.5399568f);
break;
case CLangInfo::SPEED_UNIT_INCHPS:
curSpeed=(int)(curSpeed * 10.9361388889f);
break;
case CLangInfo::SPEED_UNIT_YARDPS:
curSpeed=(int)(curSpeed * 0.3037814722f);
break;
case CLangInfo::SPEED_UNIT_FPF:
curSpeed=(int)(curSpeed * 1670.25f);
break;
case CLangInfo::SPEED_UNIT_BEAUFORT:
{
float knot=(float)curSpeed * 0.5399568f; // to kts first
if(knot<=1.0) curSpeed=0;
if(knot>1.0 && knot<3.5) curSpeed=1;
if(knot>=3.5 && knot<6.5) curSpeed=2;
if(knot>=6.5 && knot<10.5) curSpeed=3;
if(knot>=10.5 && knot<16.5) curSpeed=4;
if(knot>=16.5 && knot<21.5) curSpeed=5;
if(knot>=21.5 && knot<27.5) curSpeed=6;
if(knot>=27.5 && knot<33.5) curSpeed=7;
if(knot>=33.5 && knot<40.5) curSpeed=8;
if(knot>=40.5 && knot<47.5) curSpeed=9;
if(knot>=47.5 && knot<55.5) curSpeed=10;
if(knot>=55.5 && knot<63.5) curSpeed=11;
if(knot>=63.5 && knot<74.5) curSpeed=12;
if(knot>=74.5 && knot<80.5) curSpeed=13;
if(knot>=80.5 && knot<89.5) curSpeed=14;
if(knot>=89.5) curSpeed=15;
}
break;
default:
assert(false);
}
return curSpeed;
}
void CWeatherJob::FormatTemperature(std::string &text, int temp)
{
CTemperature temperature = CTemperature::CreateFromCelsius(temp);
text = StringUtils::Format("%.0f", temperature.ToLocale());
}
void CWeatherJob::LoadLocalizedToken()
{
// We load the english strings in to get our tokens
// Try the strings PO file first
CPODocument PODoc;
if (PODoc.LoadFile("special://xbmc/language/English/strings.po"))
{
int counter = 0;
while (PODoc.GetNextEntry())
{
if (PODoc.GetEntryType() != ID_FOUND)
continue;
uint32_t id = PODoc.GetEntryID();
PODoc.ParseEntry(ISSOURCELANG);
if (id > LOCALIZED_TOKEN_LASTID2) break;
if ((LOCALIZED_TOKEN_FIRSTID <= id && id <= LOCALIZED_TOKEN_LASTID) ||
(LOCALIZED_TOKEN_FIRSTID2 <= id && id <= LOCALIZED_TOKEN_LASTID2) ||
(LOCALIZED_TOKEN_FIRSTID3 <= id && id <= LOCALIZED_TOKEN_LASTID3) ||
(LOCALIZED_TOKEN_FIRSTID4 <= id && id <= LOCALIZED_TOKEN_LASTID4))
{
if (!PODoc.GetMsgid().empty())
{
m_localizedTokens.insert(make_pair(PODoc.GetMsgid(), id));
counter++;
}
}
}
CLog::Log(LOGDEBUG, "POParser: loaded %i weather tokens", counter);
return;
}
CLog::Log(LOGDEBUG,
"Weather: no PO string file available, to load English tokens, "
"fallback to strings.xml file");
// We load the tokens from the strings.xml file
std::string strLanguagePath = "special://xbmc/language/English/strings.xml";
CXBMCTinyXML xmlDoc;
if (!xmlDoc.LoadFile(strLanguagePath) || !xmlDoc.RootElement())
{
CLog::Log(LOGERROR, "Weather: unable to load %s: %s at line %d", strLanguagePath.c_str(), xmlDoc.ErrorDesc(), xmlDoc.ErrorRow());
return;
}
TiXmlElement* pRootElement = xmlDoc.RootElement();
if (pRootElement->ValueStr() != "strings")
return;
const TiXmlElement *pChild = pRootElement->FirstChildElement();
while (pChild)
{
std::string strValue = pChild->ValueStr();
if (strValue == "string")
{ // Load new style language file with id as attribute
const char* attrId = pChild->Attribute("id");
if (attrId && !pChild->NoChildren())
{
int id = atoi(attrId);
if ((LOCALIZED_TOKEN_FIRSTID <= id && id <= LOCALIZED_TOKEN_LASTID) ||
(LOCALIZED_TOKEN_FIRSTID2 <= id && id <= LOCALIZED_TOKEN_LASTID2) ||
(LOCALIZED_TOKEN_FIRSTID3 <= id && id <= LOCALIZED_TOKEN_LASTID3) ||
(LOCALIZED_TOKEN_FIRSTID4 <= id && id <= LOCALIZED_TOKEN_LASTID4))
{
std::string utf8Label(pChild->FirstChild()->ValueStr());
if (!utf8Label.empty())
m_localizedTokens.insert(make_pair(utf8Label, id));
}
}
}
pChild = pChild->NextSiblingElement();
}
}
static std::string ConstructPath(std::string in) // copy intended
{
if (in.find("/") != std::string::npos || in.find("\\") != std::string::npos)
return in;
if (in.empty() || in == "N/A")
in = "na.png";
return URIUtils::AddFileToFolder(WEATHER_ICON_PATH,in);
}
void CWeatherJob::SetFromProperties()
{
// Load in our tokens if necessary
if (m_localizedTokens.empty())
LoadLocalizedToken();
CGUIWindow* window = g_windowManager.GetWindow(WINDOW_WEATHER);
if (window)
{
CDateTime time = CDateTime::GetCurrentDateTime();
m_info.lastUpdateTime = time.GetAsLocalizedDateTime(false, false);
m_info.currentConditions = window->GetProperty("Current.Condition").asString();
m_info.currentIcon = ConstructPath(window->GetProperty("Current.OutlookIcon").asString());
LocalizeOverview(m_info.currentConditions);
FormatTemperature(m_info.currentTemperature,
strtol(window->GetProperty("Current.Temperature").asString().c_str(),0,10));
FormatTemperature(m_info.currentFeelsLike,
strtol(window->GetProperty("Current.FeelsLike").asString().c_str(),0,10));
m_info.currentUVIndex = window->GetProperty("Current.UVIndex").asString();
LocalizeOverview(m_info.currentUVIndex);
int speed = ConvertSpeed(strtol(window->GetProperty("Current.Wind").asString().c_str(),0,10));
std::string direction = window->GetProperty("Current.WindDirection").asString();
if (direction == "CALM")
m_info.currentWind = g_localizeStrings.Get(1410);
else
{
LocalizeOverviewToken(direction);
m_info.currentWind = StringUtils::Format(g_localizeStrings.Get(434).c_str(),
direction.c_str(), speed, g_langInfo.GetSpeedUnitString().c_str());
}
std::string windspeed = StringUtils::Format("%i %s",speed,g_langInfo.GetSpeedUnitString().c_str());
window->SetProperty("Current.WindSpeed",windspeed);
FormatTemperature(m_info.currentDewPoint,
strtol(window->GetProperty("Current.DewPoint").asString().c_str(),0,10));
if (window->GetProperty("Current.Humidity").asString().empty())
m_info.currentHumidity.clear();
else
m_info.currentHumidity = StringUtils::Format("%s%%", window->GetProperty("Current.Humidity").asString().c_str());
m_info.location = window->GetProperty("Current.Location").asString();
for (int i=0;iGetProperty(strDay).asString();
LocalizeOverviewToken(m_info.forecast[i].m_day);
strDay = StringUtils::Format("Day%i.HighTemp",i);
FormatTemperature(m_info.forecast[i].m_high,
strtol(window->GetProperty(strDay).asString().c_str(),0,10));
strDay = StringUtils::Format("Day%i.LowTemp",i);
FormatTemperature(m_info.forecast[i].m_low,
strtol(window->GetProperty(strDay).asString().c_str(),0,10));
strDay = StringUtils::Format("Day%i.OutlookIcon",i);
m_info.forecast[i].m_icon = ConstructPath(window->GetProperty(strDay).asString());
strDay = StringUtils::Format("Day%i.Outlook",i);
m_info.forecast[i].m_overview = window->GetProperty(strDay).asString();
LocalizeOverview(m_info.forecast[i].m_overview);
}
}
}
CWeather::CWeather(void) : CInfoLoader(30 * 60 * 1000) // 30 minutes
{
Reset();
}
CWeather::~CWeather(void)
{
}
std::string CWeather::BusyInfo(int info) const
{
if (info == WEATHER_IMAGE_CURRENT_ICON)
return URIUtils::AddFileToFolder(WEATHER_ICON_PATH,"na.png");
return CInfoLoader::BusyInfo(info);
}
std::string CWeather::TranslateInfo(int info) const
{
if (info == WEATHER_LABEL_CURRENT_COND) return m_info.currentConditions;
else if (info == WEATHER_IMAGE_CURRENT_ICON) return m_info.currentIcon;
else if (info == WEATHER_LABEL_CURRENT_TEMP) return m_info.currentTemperature;
else if (info == WEATHER_LABEL_CURRENT_FEEL) return m_info.currentFeelsLike;
else if (info == WEATHER_LABEL_CURRENT_UVID) return m_info.currentUVIndex;
else if (info == WEATHER_LABEL_CURRENT_WIND) return m_info.currentWind;
else if (info == WEATHER_LABEL_CURRENT_DEWP) return m_info.currentDewPoint;
else if (info == WEATHER_LABEL_CURRENT_HUMI) return m_info.currentHumidity;
else if (info == WEATHER_LABEL_LOCATION) return m_info.location;
return "";
}
/*!
\brief Retrieve the city name for the specified location from the settings
\param iLocation the location index (can be in the range [1..MAXLOCATION])
\return the city name (without the accompanying region area code)
*/
std::string CWeather::GetLocation(int iLocation)
{
CGUIWindow* window = g_windowManager.GetWindow(WINDOW_WEATHER);
if (window)
{
std::string setting = StringUtils::Format("Location%i", iLocation);
return window->GetProperty(setting).asString();
}
return "";
}
void CWeather::Reset()
{
m_info.Reset();
}
bool CWeather::IsFetched()
{
// call GetInfo() to make sure that we actually start up
GetInfo(0);
return !m_info.lastUpdateTime.empty();
}
const day_forecast &CWeather::GetForecast(int day) const
{
return m_info.forecast[day];
}
/*!
\brief Saves the specified location index to the settings. Call Refresh()
afterwards to update weather info for the new location.
\param iLocation the new location index (can be in the range [1..MAXLOCATION])
*/
void CWeather::SetArea(int iLocation)
{
CSettings::Get().SetInt("weather.currentlocation", iLocation);
CSettings::Get().Save();
}
/*!
\brief Retrieves the current location index from the settings
\return the active location index (will be in the range [1..MAXLOCATION])
*/
int CWeather::GetArea() const
{
return CSettings::Get().GetInt("weather.currentlocation");
}
CJob *CWeather::GetJob() const
{
return new CWeatherJob(GetArea());
}
void CWeather::OnJobComplete(unsigned int jobID, bool success, CJob *job)
{
m_info = ((CWeatherJob *)job)->GetInfo();
CInfoLoader::OnJobComplete(jobID, success, job);
}
void CWeather::OnSettingChanged(const CSetting *setting)
{
if (setting == NULL)
return;
const std::string settingId = setting->GetId();
if (settingId == "weather.addon")
{
// clear "WeatherProviderLogo" property that some weather addons set
CGUIWindow* window = g_windowManager.GetWindow(WINDOW_WEATHER);
window->SetProperty("WeatherProviderLogo", "");
Refresh();
}
}
void CWeather::OnSettingAction(const CSetting *setting)
{
if (setting == NULL)
return;
const std::string settingId = setting->GetId();
if (settingId == "weather.addonsettings")
{
AddonPtr addon;
if (CAddonMgr::Get().GetAddon(CSettings::Get().GetString("weather.addon"), addon, ADDON_SCRIPT_WEATHER) && addon != NULL)
{ // TODO: maybe have ShowAndGetInput return a bool if settings changed, then only reset weather if true.
CGUIDialogAddonSettings::ShowAndGetInput(addon);
Refresh();
}
}
}