diff options
author | Fneufneu <fneufneu@xbmc.org> | 2014-10-10 15:09:51 +0200 |
---|---|---|
committer | Fneufneu <fneufneu@xbmc.org> | 2014-12-15 21:20:40 +0100 |
commit | 135fe8734924f79cedace50986a0fa4f12d76647 (patch) | |
tree | 5f7612f64385d75e72b78d3f71a97fc2058f4015 /src/utils | |
parent | f981c1dd3c364c05901b3d51ae53899127a4f2e6 (diff) |
rename xbmc folder to src
Diffstat (limited to 'src/utils')
236 files changed, 50601 insertions, 0 deletions
diff --git a/src/utils/AMLUtils.cpp b/src/utils/AMLUtils.cpp new file mode 100644 index 0000000000..95537453c7 --- /dev/null +++ b/src/utils/AMLUtils.cpp @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <string> + +#include "AMLUtils.h" +#include "utils/CPUInfo.h" +#include "utils/log.h" +#include "utils/StringUtils.h" +#include "utils/AMLUtils.h" +#include "guilib/gui3d.h" + +int aml_set_sysfs_str(const char *path, const char *val) +{ + int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd >= 0) + { + write(fd, val, strlen(val)); + close(fd); + return 0; + } + return -1; +} + +int aml_get_sysfs_str(const char *path, char *valstr, const int size) +{ + int fd = open(path, O_RDONLY); + if (fd >= 0) + { + read(fd, valstr, size - 1); + valstr[strlen(valstr)] = '\0'; + close(fd); + return 0; + } + + sprintf(valstr, "%s", "fail"); + return -1; +} + +int aml_set_sysfs_int(const char *path, const int val) +{ + int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd >= 0) + { + char bcmd[16]; + sprintf(bcmd, "%d", val); + write(fd, bcmd, strlen(bcmd)); + close(fd); + return 0; + } + return -1; +} + +int aml_get_sysfs_int(const char *path) +{ + int val = -1; + int fd = open(path, O_RDONLY); + if (fd >= 0) + { + char bcmd[16]; + read(fd, bcmd, sizeof(bcmd)); + val = strtol(bcmd, NULL, 16); + close(fd); + } + return val; +} + +bool aml_present() +{ + static int has_aml = -1; + if (has_aml == -1) + { + int rtn = aml_get_sysfs_int("/sys/class/audiodsp/digital_raw"); + if (rtn != -1) + has_aml = 1; + else + has_aml = 0; + if (has_aml) + CLog::Log(LOGNOTICE, "aml_present, rtn(%d)", rtn); + } + return has_aml == 1; +} + +bool aml_hw3d_present() +{ + static int has_hw3d = -1; + if (has_hw3d == -1) + { + if (aml_get_sysfs_int("/sys/class/ppmgr/ppmgr_3d_mode") != -1) + has_hw3d = 1; + else + has_hw3d = 0; + } + return has_hw3d == 1; +} + +bool aml_wired_present() +{ + static int has_wired = -1; + if (has_wired == -1) + { + char test[64] = {0}; + if (aml_get_sysfs_str("/sys/class/net/eth0/operstate", test, 63) != -1) + has_wired = 1; + else + has_wired = 0; + } + return has_wired == 1; +} + +void aml_permissions() +{ + if (!aml_present()) + return; + + // most all aml devices are already rooted. + int ret = system("ls /system/xbin/su"); + if (ret != 0) + { + CLog::Log(LOGWARNING, "aml_permissions: missing su, playback might fail"); + } + else + { + // certain aml devices have 664 permission, we need 666. + system("su -c chmod 666 /dev/amvideo"); + system("su -c chmod 666 /dev/amstream*"); + system("su -c chmod 666 /sys/class/video/axis"); + system("su -c chmod 666 /sys/class/video/screen_mode"); + system("su -c chmod 666 /sys/class/video/disable_video"); + system("su -c chmod 666 /sys/class/tsync/pts_pcrscr"); + system("su -c chmod 666 /sys/class/audiodsp/digital_raw"); + system("su -c chmod 666 /sys/class/ppmgr/ppmgr_3d_mode"); + system("su -c chmod 666 /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq"); + system("su -c chmod 666 /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"); + system("su -c chmod 666 /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"); + CLog::Log(LOGINFO, "aml_permissions: permissions changed"); + } +} + +enum AML_DEVICE_TYPE aml_get_device_type() +{ + static enum AML_DEVICE_TYPE aml_device_type = AML_DEVICE_TYPE_UNINIT; + if (aml_device_type == AML_DEVICE_TYPE_UNINIT) + { + std::string cpu_hardware = g_cpuInfo.getCPUHardware(); + + if (cpu_hardware.find("MESON-M1") != std::string::npos) + aml_device_type = AML_DEVICE_TYPE_M1; + else if (cpu_hardware.find("MESON-M3") != std::string::npos + || cpu_hardware.find("MESON3") != std::string::npos) + aml_device_type = AML_DEVICE_TYPE_M3; + else if (cpu_hardware.find("Meson6") != std::string::npos) + aml_device_type = AML_DEVICE_TYPE_M6; + else if (cpu_hardware.find("Meson8") != std::string::npos) + aml_device_type = AML_DEVICE_TYPE_M8; + else + aml_device_type = AML_DEVICE_TYPE_UNKNOWN; + } + + return aml_device_type; +} + +void aml_cpufreq_min(bool limit) +{ +// do not touch scaling_min_freq on android +#if !defined(TARGET_ANDROID) + // only needed for m1/m3 SoCs + if ( aml_get_device_type() != AML_DEVICE_TYPE_UNKNOWN + && aml_get_device_type() <= AML_DEVICE_TYPE_M3) + { + int cpufreq = 300000; + if (limit) + cpufreq = 600000; + + aml_set_sysfs_int("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", cpufreq); + } +#endif +} + +void aml_cpufreq_max(bool limit) +{ + if (!aml_wired_present() && aml_get_device_type() == AML_DEVICE_TYPE_M6) + { + // this is a MX Stick, they cannot substain 1GHz + // operation without overheating so limit them to 800MHz. + int cpufreq = 1000000; + if (limit) + cpufreq = 800000; + + aml_set_sysfs_int("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", cpufreq); + aml_set_sysfs_str("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "ondemand"); + } +} + +void aml_set_audio_passthrough(bool passthrough) +{ + if ( aml_present() + && aml_get_device_type() != AML_DEVICE_TYPE_UNKNOWN + && aml_get_device_type() <= AML_DEVICE_TYPE_M8) + { + // m1 uses 1, m3 and above uses 2 + int raw = aml_get_device_type() == AML_DEVICE_TYPE_M1 ? 1:2; + aml_set_sysfs_int("/sys/class/audiodsp/digital_raw", passthrough ? raw:0); + } +} + +void aml_probe_hdmi_audio() +{ + // Audio {format, channel, freq, cce} + // {1, 7, 7f, 7} + // {7, 5, 1e, 0} + // {2, 5, 7, 0} + // {11, 7, 7e, 1} + // {10, 7, 6, 0} + // {12, 7, 7e, 0} + + int fd = open("/sys/class/amhdmitx/amhdmitx0/edid", O_RDONLY); + if (fd >= 0) + { + char valstr[1024] = {0}; + + read(fd, valstr, sizeof(valstr) - 1); + valstr[strlen(valstr)] = '\0'; + close(fd); + + std::vector<std::string> probe_str = StringUtils::Split(valstr, "\n"); + + for (std::vector<std::string>::const_iterator i = probe_str.begin(); i != probe_str.end(); ++i) + { + if (i->find("Audio") == std::string::npos) + { + for (std::vector<std::string>::const_iterator j = i + 1; j != probe_str.end(); ++j) + { + if (j->find("{1,") != std::string::npos) + printf(" PCM found {1,\n"); + else if (j->find("{2,") != std::string::npos) + printf(" AC3 found {2,\n"); + else if (j->find("{3,") != std::string::npos) + printf(" MPEG1 found {3,\n"); + else if (j->find("{4,") != std::string::npos) + printf(" MP3 found {4,\n"); + else if (j->find("{5,") != std::string::npos) + printf(" MPEG2 found {5,\n"); + else if (j->find("{6,") != std::string::npos) + printf(" AAC found {6,\n"); + else if (j->find("{7,") != std::string::npos) + printf(" DTS found {7,\n"); + else if (j->find("{8,") != std::string::npos) + printf(" ATRAC found {8,\n"); + else if (j->find("{9,") != std::string::npos) + printf(" One_Bit_Audio found {9,\n"); + else if (j->find("{10,") != std::string::npos) + printf(" Dolby found {10,\n"); + else if (j->find("{11,") != std::string::npos) + printf(" DTS_HD found {11,\n"); + else if (j->find("{12,") != std::string::npos) + printf(" MAT found {12,\n"); + else if (j->find("{13,") != std::string::npos) + printf(" ATRAC found {13,\n"); + else if (j->find("{14,") != std::string::npos) + printf(" WMA found {14,\n"); + else + break; + } + break; + } + } + } +} + +int aml_axis_value(AML_DISPLAY_AXIS_PARAM param) +{ + char axis[20] = {0}; + int value[8]; + + aml_get_sysfs_str("/sys/class/display/axis", axis, 19); + sscanf(axis, "%d %d %d %d %d %d %d %d", &value[0], &value[1], &value[2], &value[3], &value[4], &value[5], &value[6], &value[7]); + + return value[param]; +} + +bool aml_mode_to_resolution(const char *mode, RESOLUTION_INFO *res) +{ + if (!res) + return false; + + res->iWidth = 0; + res->iHeight= 0; + + if(!mode) + return false; + + CStdString fromMode = mode; + StringUtils::Trim(fromMode); + // strips, for example, 720p* to 720p + // the * indicate the 'native' mode of the display + if (StringUtils::EndsWith(fromMode, "*")) + fromMode.erase(fromMode.size() - 1); + + if (fromMode.Equals("panel")) + { + res->iWidth = aml_axis_value(AML_DISPLAY_AXIS_PARAM_WIDTH); + res->iHeight= aml_axis_value(AML_DISPLAY_AXIS_PARAM_HEIGHT); + res->iScreenWidth = aml_axis_value(AML_DISPLAY_AXIS_PARAM_WIDTH); + res->iScreenHeight= aml_axis_value(AML_DISPLAY_AXIS_PARAM_HEIGHT); + res->fRefreshRate = 60; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else if (fromMode.Equals("720p")) + { + res->iWidth = 1280; + res->iHeight= 720; + res->iScreenWidth = 1280; + res->iScreenHeight= 720; + res->fRefreshRate = 60; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else if (fromMode.Equals("720p50hz")) + { + res->iWidth = 1280; + res->iHeight= 720; + res->iScreenWidth = 1280; + res->iScreenHeight= 720; + res->fRefreshRate = 50; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else if (fromMode.Equals("1080p")) + { + res->iWidth = 1920; + res->iHeight= 1080; + res->iScreenWidth = 1920; + res->iScreenHeight= 1080; + res->fRefreshRate = 60; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else if (fromMode.Equals("1080p24hz")) + { + res->iWidth = 1920; + res->iHeight= 1080; + res->iScreenWidth = 1920; + res->iScreenHeight= 1080; + res->fRefreshRate = 24; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else if (fromMode.Equals("1080p30hz")) + { + res->iWidth = 1920; + res->iHeight= 1080; + res->iScreenWidth = 1920; + res->iScreenHeight= 1080; + res->fRefreshRate = 30; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else if (fromMode.Equals("1080p50hz")) + { + res->iWidth = 1920; + res->iHeight= 1080; + res->iScreenWidth = 1920; + res->iScreenHeight= 1080; + res->fRefreshRate = 50; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else if (fromMode.Equals("1080i")) + { + res->iWidth = 1920; + res->iHeight= 1080; + res->iScreenWidth = 1920; + res->iScreenHeight= 1080; + res->fRefreshRate = 60; + res->dwFlags = D3DPRESENTFLAG_INTERLACED; + } + else if (fromMode.Equals("1080i50hz")) + { + res->iWidth = 1920; + res->iHeight= 1080; + res->iScreenWidth = 1920; + res->iScreenHeight= 1080; + res->fRefreshRate = 50; + res->dwFlags = D3DPRESENTFLAG_INTERLACED; + } + else if (fromMode.Equals("4k2ksmpte")) + { + res->iWidth = 1920; + res->iHeight= 1080; + res->iScreenWidth = 4096; + res->iScreenHeight= 2160; + res->fRefreshRate = 24; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else if (fromMode.Equals("4k2k24hz")) + { + res->iWidth = 1920; + res->iHeight= 1080; + res->iScreenWidth = 3840; + res->iScreenHeight= 2160; + res->fRefreshRate = 24; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else if (fromMode.Equals("4k2k25hz")) + { + res->iWidth = 1920; + res->iHeight= 1080; + res->iScreenWidth = 3840; + res->iScreenHeight= 2160; + res->fRefreshRate = 25; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else if (fromMode.Equals("4k2k30hz")) + { + res->iWidth = 1920; + res->iHeight= 1080; + res->iScreenWidth = 3840; + res->iScreenHeight= 2160; + res->fRefreshRate = 30; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else + { + return false; + } + + + res->iScreen = 0; + res->bFullScreen = true; + res->iSubtitles = (int)(0.965 * res->iHeight); + res->fPixelRatio = 1.0f; + res->strMode = StringUtils::Format("%dx%d @ %.2f%s - Full Screen", res->iScreenWidth, res->iScreenHeight, res->fRefreshRate, + res->dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : ""); + + return res->iWidth > 0 && res->iHeight> 0; +} + diff --git a/src/utils/AMLUtils.h b/src/utils/AMLUtils.h new file mode 100644 index 0000000000..9778e9b65d --- /dev/null +++ b/src/utils/AMLUtils.h @@ -0,0 +1,55 @@ +#pragma once +/* + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "guilib/Resolution.h" + +enum AML_DEVICE_TYPE +{ + AML_DEVICE_TYPE_UNINIT = -2, + AML_DEVICE_TYPE_UNKNOWN = -1, + AML_DEVICE_TYPE_M1, + AML_DEVICE_TYPE_M3, + AML_DEVICE_TYPE_M6, + AML_DEVICE_TYPE_M8 +}; + +enum AML_DISPLAY_AXIS_PARAM +{ + AML_DISPLAY_AXIS_PARAM_X = 0, + AML_DISPLAY_AXIS_PARAM_Y, + AML_DISPLAY_AXIS_PARAM_WIDTH, + AML_DISPLAY_AXIS_PARAM_HEIGHT +}; + +int aml_set_sysfs_str(const char *path, const char *val); +int aml_get_sysfs_str(const char *path, char *valstr, const int size); +int aml_set_sysfs_int(const char *path, const int val); +int aml_get_sysfs_int(const char *path); + +bool aml_present(); +void aml_permissions(); +bool aml_hw3d_present(); +bool aml_wired_present(); +enum AML_DEVICE_TYPE aml_get_device_type(); +void aml_cpufreq_min(bool limit); +void aml_cpufreq_max(bool limit); +void aml_set_audio_passthrough(bool passthrough); +bool aml_mode_to_resolution(const char *mode, RESOLUTION_INFO *res); diff --git a/src/utils/ActorProtocol.cpp b/src/utils/ActorProtocol.cpp new file mode 100644 index 0000000000..9ea07bd5d6 --- /dev/null +++ b/src/utils/ActorProtocol.cpp @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "ActorProtocol.h" + +using namespace Actor; + +void Message::Release() +{ + bool skip; + origin->Lock(); + skip = isSync ? !isSyncFini : false; + isSyncFini = true; + origin->Unlock(); + + if (skip) + return; + + // free data buffer + if (data != buffer) + delete [] data; + + // delete event in case of sync message + if (event) + delete event; + + origin->ReturnMessage(this); +} + +bool Message::Reply(int sig, void *data /* = NULL*/, int size /* = 0 */) +{ + if (!isSync) + { + if (isOut) + return origin->SendInMessage(sig, data, size); + else + return origin->SendOutMessage(sig, data, size); + } + + origin->Lock(); + + if (!isSyncTimeout) + { + Message *msg = origin->GetMessage(); + msg->signal = sig; + msg->isOut = !isOut; + replyMessage = msg; + if (data) + { + if (size > MSG_INTERNAL_BUFFER_SIZE) + msg->data = new uint8_t[size]; + else + msg->data = msg->buffer; + memcpy(msg->data, data, size); + } + } + + origin->Unlock(); + + if (event) + event->Set(); + + return true; +} + +Protocol::~Protocol() +{ + Message *msg; + Purge(); + while (!freeMessageQueue.empty()) + { + msg = freeMessageQueue.front(); + freeMessageQueue.pop(); + delete msg; + } +} + +Message *Protocol::GetMessage() +{ + Message *msg; + + CSingleLock lock(criticalSection); + + if (!freeMessageQueue.empty()) + { + msg = freeMessageQueue.front(); + freeMessageQueue.pop(); + } + else + msg = new Message(); + + msg->isSync = false; + msg->isSyncFini = false; + msg->isSyncTimeout = false; + msg->event = NULL; + msg->data = NULL; + msg->payloadSize = 0; + msg->replyMessage = NULL; + msg->origin = this; + + return msg; +} + +void Protocol::ReturnMessage(Message *msg) +{ + CSingleLock lock(criticalSection); + + freeMessageQueue.push(msg); +} + +bool Protocol::SendOutMessage(int signal, void *data /* = NULL */, int size /* = 0 */, Message *outMsg /* = NULL */) +{ + Message *msg; + if (outMsg) + msg = outMsg; + else + msg = GetMessage(); + + msg->signal = signal; + msg->isOut = true; + + if (data) + { + if (size > MSG_INTERNAL_BUFFER_SIZE) + msg->data = new uint8_t[size]; + else + msg->data = msg->buffer; + memcpy(msg->data, data, size); + } + + { CSingleLock lock(criticalSection); + outMessages.push(msg); + } + containerOutEvent->Set(); + + return true; +} + +bool Protocol::SendInMessage(int signal, void *data /* = NULL */, int size /* = 0 */, Message *outMsg /* = NULL */) +{ + Message *msg; + if (outMsg) + msg = outMsg; + else + msg = GetMessage(); + + msg->signal = signal; + msg->isOut = false; + + if (data) + { + if (size > MSG_INTERNAL_BUFFER_SIZE) + msg->data = new uint8_t[size]; + else + msg->data = msg->buffer; + memcpy(msg->data, data, size); + } + + { CSingleLock lock(criticalSection); + inMessages.push(msg); + } + containerInEvent->Set(); + + return true; +} + + +bool Protocol::SendOutMessageSync(int signal, Message **retMsg, int timeout, void *data /* = NULL */, int size /* = 0 */) +{ + Message *msg = GetMessage(); + msg->isOut = true; + msg->isSync = true; + msg->event = new CEvent; + msg->event->Reset(); + SendOutMessage(signal, data, size, msg); + + if (!msg->event->WaitMSec(timeout)) + { + msg->origin->Lock(); + if (msg->replyMessage) + *retMsg = msg->replyMessage; + else + { + *retMsg = NULL; + msg->isSyncTimeout = true; + } + msg->origin->Unlock(); + } + else + *retMsg = msg->replyMessage; + + msg->Release(); + + if (*retMsg) + return true; + else + return false; +} + +bool Protocol::ReceiveOutMessage(Message **msg) +{ + CSingleLock lock(criticalSection); + + if (outMessages.empty() || outDefered) + return false; + + *msg = outMessages.front(); + outMessages.pop(); + + return true; +} + +bool Protocol::ReceiveInMessage(Message **msg) +{ + CSingleLock lock(criticalSection); + + if (inMessages.empty() || inDefered) + return false; + + *msg = inMessages.front(); + inMessages.pop(); + + return true; +} + + +void Protocol::Purge() +{ + Message *msg; + + while (ReceiveInMessage(&msg)) + msg->Release(); + + while (ReceiveOutMessage(&msg)) + msg->Release(); +} + +void Protocol::PurgeIn(int signal) +{ + Message *msg; + std::queue<Message*> msgs; + + CSingleLock lock(criticalSection); + + while (!inMessages.empty()) + { + msg = inMessages.front(); + inMessages.pop(); + if (msg->signal != signal) + msgs.push(msg); + } + while (!msgs.empty()) + { + msg = msgs.front(); + msgs.pop(); + inMessages.push(msg); + } +} + +void Protocol::PurgeOut(int signal) +{ + Message *msg; + std::queue<Message*> msgs; + + CSingleLock lock(criticalSection); + + while (!outMessages.empty()) + { + msg = outMessages.front(); + outMessages.pop(); + if (msg->signal != signal) + msgs.push(msg); + } + while (!msgs.empty()) + { + msg = msgs.front(); + msgs.pop(); + outMessages.push(msg); + } +} diff --git a/src/utils/ActorProtocol.h b/src/utils/ActorProtocol.h new file mode 100644 index 0000000000..100b1acd0b --- /dev/null +++ b/src/utils/ActorProtocol.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#pragma once + +#include "threads/Thread.h" +#include "utils/log.h" +#include <queue> +#include "memory.h" + +#define MSG_INTERNAL_BUFFER_SIZE 32 + +namespace Actor +{ + +class Protocol; + +class Message +{ + friend class Protocol; +public: + int signal; + bool isSync; + bool isSyncFini; + bool isOut; + bool isSyncTimeout; + int payloadSize; + uint8_t buffer[MSG_INTERNAL_BUFFER_SIZE]; + uint8_t *data; + Message *replyMessage; + Protocol *origin; + CEvent *event; + + void Release(); + bool Reply(int sig, void *data = NULL, int size = 0); + +private: + Message() {isSync = false; data = NULL; event = NULL; replyMessage = NULL;}; +}; + +class Protocol +{ +public: + Protocol(std::string name, CEvent* inEvent, CEvent *outEvent) + : portName(name), inDefered(false), outDefered(false) {containerInEvent = inEvent; containerOutEvent = outEvent;}; + virtual ~Protocol(); + Message *GetMessage(); + void ReturnMessage(Message *msg); + bool SendOutMessage(int signal, void *data = NULL, int size = 0, Message *outMsg = NULL); + bool SendInMessage(int signal, void *data = NULL, int size = 0, Message *outMsg = NULL); + bool SendOutMessageSync(int signal, Message **retMsg, int timeout, void *data = NULL, int size = 0); + bool ReceiveOutMessage(Message **msg); + bool ReceiveInMessage(Message **msg); + void Purge(); + void PurgeIn(int signal); + void PurgeOut(int signal); + void DeferIn(bool value) {inDefered = value;}; + void DeferOut(bool value) {outDefered = value;}; + void Lock() {criticalSection.lock();}; + void Unlock() {criticalSection.unlock();}; + std::string portName; + +protected: + CEvent *containerInEvent, *containerOutEvent; + CCriticalSection criticalSection; + std::queue<Message*> outMessages; + std::queue<Message*> inMessages; + std::queue<Message*> freeMessageQueue; + bool inDefered, outDefered; +}; + +} diff --git a/src/utils/AlarmClock.cpp b/src/utils/AlarmClock.cpp new file mode 100644 index 0000000000..24ca8fb5cb --- /dev/null +++ b/src/utils/AlarmClock.cpp @@ -0,0 +1,155 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "AlarmClock.h" +#include "ApplicationMessenger.h" +#include "guilib/LocalizeStrings.h" +#include "threads/SingleLock.h" +#include "log.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "utils/StringUtils.h" + +using namespace std; + +CAlarmClock::CAlarmClock() : CThread("AlarmClock"), m_bIsRunning(false) +{ +} + +CAlarmClock::~CAlarmClock() +{ +} + +void CAlarmClock::Start(const std::string& strName, float n_secs, const std::string& strCommand, bool bSilent /* false */, bool bLoop /* false */) +{ + // make lower case so that lookups are case-insensitive + std::string lowerName(strName); + StringUtils::ToLower(lowerName); + Stop(lowerName); + SAlarmClockEvent event; + event.m_fSecs = n_secs; + event.m_strCommand = strCommand; + event.m_loop = bLoop; + if (!m_bIsRunning) + { + StopThread(); + Create(); + m_bIsRunning = true; + } + + std::string strAlarmClock; + std::string strStarted; + if (StringUtils::EqualsNoCase(strName, "shutdowntimer")) + { + strAlarmClock = g_localizeStrings.Get(20144); + strStarted = g_localizeStrings.Get(20146); + } + else + { + strAlarmClock = g_localizeStrings.Get(13208); + strStarted = g_localizeStrings.Get(13210); + } + + std::string strMessage = StringUtils::Format(strStarted.c_str(), + static_cast<int>(event.m_fSecs)/60, + static_cast<int>(event.m_fSecs)%60); + + if(!bSilent) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, strAlarmClock, strMessage); + + event.watch.StartZero(); + CSingleLock lock(m_events); + m_event.insert(make_pair(lowerName,event)); + CLog::Log(LOGDEBUG,"started alarm with name: %s",lowerName.c_str()); +} + +void CAlarmClock::Stop(const std::string& strName, bool bSilent /* false */) +{ + CSingleLock lock(m_events); + + std::string lowerName(strName); + StringUtils::ToLower(lowerName); // lookup as lowercase only + map<std::string,SAlarmClockEvent>::iterator iter = m_event.find(lowerName); + + if (iter == m_event.end()) + return; + + std::string strAlarmClock; + if (StringUtils::EqualsNoCase(strName, "shutdowntimer")) + strAlarmClock = g_localizeStrings.Get(20144); + else + strAlarmClock = g_localizeStrings.Get(13208); + + std::string strMessage; + float elapsed = 0.f; + + if (iter->second.watch.IsRunning()) + elapsed = iter->second.watch.GetElapsedSeconds(); + + if( elapsed > iter->second.m_fSecs ) + strMessage = g_localizeStrings.Get(13211); + else + { + float remaining = static_cast<float>(iter->second.m_fSecs-elapsed); + std::string strStarted = g_localizeStrings.Get(13212); + strMessage = StringUtils::Format(strStarted.c_str(), + static_cast<int>(remaining)/60, + static_cast<int>(remaining)%60); + } + if (iter->second.m_strCommand.empty() || iter->second.m_fSecs > elapsed) + { + if(!bSilent) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, strAlarmClock, strMessage); + } + else + { + CApplicationMessenger::Get().ExecBuiltIn(iter->second.m_strCommand); + if (iter->second.m_loop) + { + iter->second.watch.Reset(); + return; + } + } + + iter->second.watch.Stop(); + m_event.erase(iter); +} + +void CAlarmClock::Process() +{ + while( !m_bStop) + { + std::string strLast; + { + CSingleLock lock(m_events); + for (map<std::string,SAlarmClockEvent>::iterator iter=m_event.begin();iter != m_event.end(); ++iter) + if ( iter->second.watch.IsRunning() + && iter->second.watch.GetElapsedSeconds() >= iter->second.m_fSecs) + { + Stop(iter->first); + if ((iter = m_event.find(strLast)) == m_event.end()) + break; + } + else + strLast = iter->first; + } + Sleep(100); + } +} + diff --git a/src/utils/AlarmClock.h b/src/utils/AlarmClock.h new file mode 100644 index 0000000000..ed9fc49fbb --- /dev/null +++ b/src/utils/AlarmClock.h @@ -0,0 +1,79 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Stopwatch.h" +#include "threads/CriticalSection.h" +#include "threads/Thread.h" + +#include <map> +#include <string> + +struct SAlarmClockEvent +{ + CStopWatch watch; + double m_fSecs; + std::string m_strCommand; + bool m_loop; +}; + +class CAlarmClock : public CThread +{ +public: + CAlarmClock(); + ~CAlarmClock(); + void Start(const std::string& strName, float n_secs, const std::string& strCommand, bool bSilent = false, bool bLoop = false); + inline bool IsRunning() const + { + return m_bIsRunning; + } + + inline bool HasAlarm(const std::string& strName) + { + // note: strName should be lower case only here + // No point checking it at the moment due to it only being called + // from GUIInfoManager (which is always lowercase) + // CLog::Log(LOGDEBUG,"checking for %s",strName.c_str()); + return (m_event.find(strName) != m_event.end()); + } + + double GetRemaining(const std::string& strName) + { + std::map<std::string,SAlarmClockEvent>::iterator iter; + if ((iter=m_event.find(strName)) != m_event.end()) + { + return iter->second.m_fSecs-(iter->second.watch.IsRunning() ? iter->second.watch.GetElapsedSeconds() : 0.f); + } + + return 0.f; + } + + void Stop(const std::string& strName, bool bSilent = false); + virtual void Process(); +private: + std::map<std::string,SAlarmClockEvent> m_event; + CCriticalSection m_events; + + bool m_bIsRunning; +}; + +extern CAlarmClock g_alarmClock; + diff --git a/src/utils/AliasShortcutUtils.cpp b/src/utils/AliasShortcutUtils.cpp new file mode 100644 index 0000000000..ae46610fec --- /dev/null +++ b/src/utils/AliasShortcutUtils.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2009-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#if defined(TARGET_DARWIN_OSX) +#include <CoreServices/CoreServices.h> +#include "utils/URIUtils.h" +#elif defined(TARGET_POSIX) +#else +#endif + +#include "AliasShortcutUtils.h" + +bool IsAliasShortcut(const std::string& path) +{ + bool rtn = false; + +#if defined(TARGET_DARWIN_OSX) + // Note: regular files that have an .alias extension can be + // reported as an alias when clearly, they are not. Trap them out. + if (!URIUtils::HasExtension(path, ".alias")) + { + FSRef fileRef; + Boolean targetIsFolder, wasAliased; + + // It is better to call FSPathMakeRefWithOptions and pass kFSPathMakeRefDefaultOptions + // since it will succeed for paths such as "/Volumes" unlike FSPathMakeRef. + if (noErr == FSPathMakeRefWithOptions((UInt8*)path.c_str(), kFSPathMakeRefDefaultOptions, &fileRef, NULL)) + { + if (noErr == FSIsAliasFile(&fileRef, &wasAliased, &targetIsFolder)) + { + if (wasAliased) + { + rtn = true; + } + } + } + } +#elif defined(TARGET_POSIX) + // Linux does not use alias or shortcut methods +#elif defined(TARGET_WINDOWS) +/* Needs testing under Windows platform so ignore shortcuts for now + if (CUtil::GetExtension(path) == ".lnk") + { + rtn = true; + } +*/ +#endif + return(rtn); +} + +void TranslateAliasShortcut(std::string& path) +{ +#if defined(TARGET_DARWIN_OSX) + FSRef fileRef; + Boolean targetIsFolder, wasAliased; + + if (noErr == FSPathMakeRefWithOptions((UInt8*)path.c_str(), kFSPathMakeRefDefaultOptions, &fileRef, NULL)) + { + if (noErr == FSResolveAliasFileWithMountFlags(&fileRef, TRUE, &targetIsFolder, &wasAliased, kResolveAliasFileNoUI)) + { + if (wasAliased) + { + char real_path[PATH_MAX]; + if (noErr == FSRefMakePath(&fileRef, (UInt8*)real_path, PATH_MAX)) + { + path = real_path; + } + } + } + } +#elif defined(TARGET_POSIX) + // Linux does not use alias or shortcut methods + +#elif defined(TARGET_WINDOWS) +/* Needs testing under Windows platform so ignore shortcuts for now + CComPtr<IShellLink> ipShellLink; + + // Get a pointer to the IShellLink interface + if (NOERROR == CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)&ipShellLink)) + WCHAR wszTemp[MAX_PATH]; + + // Get a pointer to the IPersistFile interface + CComQIPtr<IPersistFile> ipPersistFile(ipShellLink); + + // IPersistFile is using LPCOLESTR so make sure that the string is Unicode +#if !defined _UNICODE + MultiByteToWideChar(CP_ACP, 0, lpszShortcutPath, -1, wszTemp, MAX_PATH); +#else + wcsncpy(wszTemp, lpszShortcutPath, MAX_PATH); +#endif + + // Open the shortcut file and initialize it from its contents + if (NOERROR == ipPersistFile->Load(wszTemp, STGM_READ)) + { + // Try to find the target of a shortcut even if it has been moved or renamed + if (NOERROR == ipShellLink->Resolve(NULL, SLR_UPDATE)) + { + WIN32_FIND_DATA wfd; + TCHAR real_path[PATH_MAX]; + // Get the path to the shortcut target + if (NOERROR == ipShellLink->GetPath(real_path, MAX_PATH, &wfd, SLGP_RAWPATH)) + { + // Get the description of the target + TCHAR szDesc[MAX_PATH]; + if (NOERROR == ipShellLink->GetDescription(szDesc, MAX_PATH)) + { + path = real_path; + } + } + } + } + } +*/ +#endif +} diff --git a/src/utils/AliasShortcutUtils.h b/src/utils/AliasShortcutUtils.h new file mode 100644 index 0000000000..73b50834f5 --- /dev/null +++ b/src/utils/AliasShortcutUtils.h @@ -0,0 +1,25 @@ +#pragma once +/* + * Copyright (C) 2009-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> + +bool IsAliasShortcut(const std::string& path); +void TranslateAliasShortcut(std::string &path); diff --git a/src/utils/Archive.cpp b/src/utils/Archive.cpp new file mode 100644 index 0000000000..14cd61c404 --- /dev/null +++ b/src/utils/Archive.cpp @@ -0,0 +1,441 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <cstring> +#include "Archive.h" +#include "IArchivable.h" +#include "filesystem/File.h" +#include "Variant.h" +#include "utils/log.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wlong-long" +#endif + +using namespace XFILE; + +CArchive::CArchive(CFile* pFile, int mode) +{ + m_pFile = pFile; + m_iMode = mode; + + m_pBuffer = new uint8_t[CARCHIVE_BUFFER_MAX]; + memset(m_pBuffer, 0, CARCHIVE_BUFFER_MAX); + if (mode == load) + { + m_BufferPos = m_pBuffer + CARCHIVE_BUFFER_MAX; + m_BufferRemain = 0; + } + else + { + m_BufferPos = m_pBuffer; + m_BufferRemain = CARCHIVE_BUFFER_MAX; + } +} + +CArchive::~CArchive() +{ + FlushBuffer(); + delete[] m_pBuffer; +} + +void CArchive::Close() +{ + FlushBuffer(); +} + +bool CArchive::IsLoading() const +{ + return (m_iMode == load); +} + +bool CArchive::IsStoring() const +{ + return (m_iMode == store); +} + +CArchive& CArchive::operator<<(float f) +{ + return streamout(&f, sizeof(f)); +} + +CArchive& CArchive::operator<<(double d) +{ + return streamout(&d, sizeof(d)); +} + +CArchive& CArchive::operator<<(short int s) +{ + return streamout(&s, sizeof(s)); +} + +CArchive& CArchive::operator<<(unsigned short int us) +{ + return streamout(&us, sizeof(us)); +} + +CArchive& CArchive::operator<<(int i) +{ + return streamout(&i, sizeof(i)); +} + +CArchive& CArchive::operator<<(unsigned int ui) +{ + return streamout(&ui, sizeof(ui)); +} + +CArchive& CArchive::operator<<(long int l) +{ + return streamout(&l, sizeof(l)); +} + +CArchive& CArchive::operator<<(unsigned long int ul) +{ + return streamout(&ul, sizeof(ul)); +} + +CArchive& CArchive::operator<<(long long int ll) +{ + return streamout(&ll, sizeof(ll)); +} + +CArchive& CArchive::operator<<(unsigned long long int ull) +{ + return streamout(&ull, sizeof(ull)); +} + +CArchive& CArchive::operator<<(bool b) +{ + return streamout(&b, sizeof(b)); +} + +CArchive& CArchive::operator<<(char c) +{ + return streamout(&c, sizeof(c)); +} + +CArchive& CArchive::operator<<(const std::string& str) +{ + *this << str.size(); + + return streamout(str.data(), str.size() * sizeof(char)); +} + +CArchive& CArchive::operator<<(const std::wstring& wstr) +{ + *this << wstr.size(); + + return streamout(wstr.data(), wstr.size() * sizeof(wchar_t)); +} + +CArchive& CArchive::operator<<(const SYSTEMTIME& time) +{ + return streamout(&time, sizeof(SYSTEMTIME)); +} + +CArchive& CArchive::operator<<(IArchivable& obj) +{ + obj.Archive(*this); + + return *this; +} + +CArchive& CArchive::operator<<(const CVariant& variant) +{ + *this << (int)variant.type(); + switch (variant.type()) + { + case CVariant::VariantTypeInteger: + *this << variant.asInteger(); + break; + case CVariant::VariantTypeUnsignedInteger: + *this << variant.asUnsignedInteger(); + break; + case CVariant::VariantTypeBoolean: + *this << variant.asBoolean(); + break; + case CVariant::VariantTypeString: + *this << variant.asString(); + break; + case CVariant::VariantTypeWideString: + *this << variant.asWideString(); + break; + case CVariant::VariantTypeDouble: + *this << variant.asDouble(); + break; + case CVariant::VariantTypeArray: + *this << variant.size(); + for (unsigned int index = 0; index < variant.size(); index++) + *this << variant[index]; + break; + case CVariant::VariantTypeObject: + *this << variant.size(); + for (CVariant::const_iterator_map itr = variant.begin_map(); itr != variant.end_map(); ++itr) + { + *this << itr->first; + *this << itr->second; + } + break; + case CVariant::VariantTypeNull: + case CVariant::VariantTypeConstNull: + default: + break; + } + + return *this; +} + +CArchive& CArchive::operator<<(const std::vector<std::string>& strArray) +{ + *this << strArray.size(); + for (size_t index = 0; index < strArray.size(); index++) + *this << strArray.at(index); + + return *this; +} + +CArchive& CArchive::operator<<(const std::vector<int>& iArray) +{ + *this << iArray.size(); + for (size_t index = 0; index < iArray.size(); index++) + *this << iArray.at(index); + + return *this; +} + +CArchive& CArchive::operator>>(std::string& str) +{ + size_t iLength = 0; + *this >> iLength; + + char *s = new char[iLength]; + streamin(s, iLength * sizeof(char)); + str.assign(s, iLength); + delete[] s; + + return *this; +} + +CArchive& CArchive::operator>>(std::wstring& wstr) +{ + size_t iLength = 0; + *this >> iLength; + + wchar_t * const p = new wchar_t[iLength]; + streamin(p, iLength * sizeof(wchar_t)); + wstr.assign(p, iLength); + delete[] p; + + return *this; +} + +CArchive& CArchive::operator>>(SYSTEMTIME& time) +{ + return streamin(&time, sizeof(SYSTEMTIME)); +} + +CArchive& CArchive::operator>>(IArchivable& obj) +{ + obj.Archive(*this); + + return *this; +} + +CArchive& CArchive::operator>>(CVariant& variant) +{ + int type; + *this >> type; + variant = CVariant((CVariant::VariantType)type); + + switch (variant.type()) + { + case CVariant::VariantTypeInteger: + { + int64_t value; + *this >> value; + variant = value; + break; + } + case CVariant::VariantTypeUnsignedInteger: + { + uint64_t value; + *this >> value; + variant = value; + break; + } + case CVariant::VariantTypeBoolean: + { + bool value; + *this >> value; + variant = value; + break; + } + case CVariant::VariantTypeString: + { + std::string value; + *this >> value; + variant = value; + break; + } + case CVariant::VariantTypeWideString: + { + std::wstring value; + *this >> value; + variant = value; + break; + } + case CVariant::VariantTypeDouble: + { + double value; + *this >> value; + variant = value; + break; + } + case CVariant::VariantTypeArray: + { + unsigned int size; + *this >> size; + for (; size > 0; size--) + { + CVariant value; + *this >> value; + variant.append(value); + } + break; + } + case CVariant::VariantTypeObject: + { + unsigned int size; + *this >> size; + for (; size > 0; size--) + { + std::string name; + CVariant value; + *this >> name; + *this >> value; + variant[name] = value; + } + break; + } + case CVariant::VariantTypeNull: + case CVariant::VariantTypeConstNull: + default: + break; + } + + return *this; +} + +CArchive& CArchive::operator>>(std::vector<std::string>& strArray) +{ + size_t size; + *this >> size; + strArray.clear(); + for (size_t index = 0; index < size; index++) + { + std::string str; + *this >> str; + strArray.push_back(str); + } + + return *this; +} + +CArchive& CArchive::operator>>(std::vector<int>& iArray) +{ + size_t size; + *this >> size; + iArray.clear(); + for (size_t index = 0; index < size; index++) + { + int i; + *this >> i; + iArray.push_back(i); + } + + return *this; +} + +void CArchive::FlushBuffer() +{ + if (m_iMode == store && m_BufferPos != m_pBuffer) + { + if (m_pFile->Write(m_pBuffer, m_BufferPos - m_pBuffer) != m_BufferPos - m_pBuffer) + CLog::Log(LOGERROR, "%s: Error flushing buffer", __FUNCTION__); + else + { + m_BufferPos = m_pBuffer; + m_BufferRemain = CARCHIVE_BUFFER_MAX; + } + } +} + +CArchive &CArchive::streamout_bufferwrap(const uint8_t *ptr, size_t size) +{ + do + { + size_t chunkSize = std::min(size, m_BufferRemain); + m_BufferPos = std::copy(ptr, ptr + chunkSize, m_BufferPos); + ptr += chunkSize; + size -= chunkSize; + m_BufferRemain -= chunkSize; + if (m_BufferRemain == 0) + FlushBuffer(); + } while (size > 0); + return *this; +} + +void CArchive::FillBuffer() +{ + if (m_iMode == load && m_BufferRemain == 0) + { + ssize_t read = m_pFile->Read(m_pBuffer, CARCHIVE_BUFFER_MAX); + if (read > 0) + { + m_BufferRemain = read; + m_BufferPos = m_pBuffer; + } + } +} + +CArchive &CArchive::streamin_bufferwrap(uint8_t *ptr, size_t size) +{ + uint8_t *orig_ptr = ptr; + size_t orig_size = size; + do + { + if (m_BufferRemain == 0) + { + FillBuffer(); + if (m_BufferRemain < CARCHIVE_BUFFER_MAX && m_BufferRemain < size) + { + CLog::Log(LOGERROR, "%s: can't stream in: requested %lu bytes, was read %lu bytes", __FUNCTION__, (unsigned long) orig_size, (unsigned long) (ptr - orig_ptr + m_BufferRemain)); + memset(orig_ptr, 0, orig_size); + return *this; + } + } + size_t chunkSize = std::min(size, m_BufferRemain); + ptr = std::copy(m_BufferPos, m_BufferPos + chunkSize, ptr); + m_BufferPos += chunkSize; + m_BufferRemain -= chunkSize; + size -= chunkSize; + } while (size > 0); + return *this; +} diff --git a/src/utils/Archive.h b/src/utils/Archive.h new file mode 100644 index 0000000000..6ed0f8fe37 --- /dev/null +++ b/src/utils/Archive.h @@ -0,0 +1,196 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <vector> +#include "PlatformDefs.h" // for SYSTEMTIME + +#define CARCHIVE_BUFFER_MAX 4096 + +namespace XFILE +{ + class CFile; +} +class CVariant; +class IArchivable; + +class CArchive +{ +public: + CArchive(XFILE::CFile* pFile, int mode); + ~CArchive(); + + /* CArchive support storing and loading of all C basic integer types + * C basic types was chosen instead of fixed size ints (int16_t - int64_t) to support all integer typedefs + * For example size_t can be typedef of unsigned int, long or long long depending on platform + * while int32_t and int64_t are usually unsigned short, int or long long, but not long + * and even if int and long can have same binary representation they are different types for compiler + * According to section 5.2.4.2.1 of C99 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf + * minimal size of short int is 16 bits + * minimal size of int is 16 bits (usually 32 or 64 bits, larger or equal to short int) + * minimal size of long int is 32 bits (larger or equal to int) + * minimal size of long long int is 64 bits (larger or equal to long int) */ + // storing + CArchive& operator<<(float f); + CArchive& operator<<(double d); + CArchive& operator<<(short int s); + CArchive& operator<<(unsigned short int us); + CArchive& operator<<(int i); + CArchive& operator<<(unsigned int ui); + CArchive& operator<<(long int l); + CArchive& operator<<(unsigned long int ul); + CArchive& operator<<(long long int ll); + CArchive& operator<<(unsigned long long int ull); + CArchive& operator<<(bool b); + CArchive& operator<<(char c); + CArchive& operator<<(const std::string &str); + CArchive& operator<<(const std::wstring& wstr); + CArchive& operator<<(const SYSTEMTIME& time); + CArchive& operator<<(IArchivable& obj); + CArchive& operator<<(const CVariant& variant); + CArchive& operator<<(const std::vector<std::string>& strArray); + CArchive& operator<<(const std::vector<int>& iArray); + + // loading + inline CArchive& operator>>(float& f) + { + return streamin(&f, sizeof(f)); + } + + inline CArchive& operator>>(double& d) + { + return streamin(&d, sizeof(d)); + } + + inline CArchive& operator>>(short int& s) + { + return streamin(&s, sizeof(s)); + } + + inline CArchive& operator>>(unsigned short int& us) + { + return streamin(&us, sizeof(us)); + } + + inline CArchive& operator>>(int& i) + { + return streamin(&i, sizeof(i)); + } + + inline CArchive& operator>>(unsigned int& ui) + { + return streamin(&ui, sizeof(ui)); + } + + inline CArchive& operator>>(long int& l) + { + return streamin(&l, sizeof(l)); + } + + inline CArchive& operator>>(unsigned long int& ul) + { + return streamin(&ul, sizeof(ul)); + } + + inline CArchive& operator>>(long long int& ll) + { + return streamin(&ll, sizeof(ll)); + } + + inline CArchive& operator>>(unsigned long long int& ull) + { + return streamin(&ull, sizeof(ull)); + } + + inline CArchive& operator>>(bool& b) + { + return streamin(&b, sizeof(b)); + } + + inline CArchive& operator>>(char& c) + { + return streamin(&c, sizeof(c)); + } + + CArchive& operator>>(std::string &str); + CArchive& operator>>(std::wstring& wstr); + CArchive& operator>>(SYSTEMTIME& time); + CArchive& operator>>(IArchivable& obj); + CArchive& operator>>(CVariant& variant); + CArchive& operator>>(std::vector<std::string>& strArray); + CArchive& operator>>(std::vector<int>& iArray); + + bool IsLoading() const; + bool IsStoring() const; + + void Close(); + + enum Mode {load = 0, store}; + +protected: + inline CArchive &streamout(const void *dataPtr, size_t size) + { + const uint8_t *ptr = (const uint8_t *) dataPtr; + /* Note, the buffer is flushed as soon as it is full (m_BufferRemain == size) rather + * than waiting until we attempt to put more data into an already full buffer */ + if (m_BufferRemain > size) + { + memcpy(m_BufferPos, ptr, size); + m_BufferPos += size; + m_BufferRemain -= size; + return *this; + } + else + { + return streamout_bufferwrap(ptr, size); + } + } + + inline CArchive &streamin(void *dataPtr, size_t size) + { + uint8_t *ptr = (uint8_t *) dataPtr; + /* Note, refilling the buffer is deferred until we know we need to read more from it */ + if (m_BufferRemain >= size) + { + memcpy(ptr, m_BufferPos, size); + m_BufferPos += size; + m_BufferRemain -= size; + return *this; + } + else + { + return streamin_bufferwrap(ptr, size); + } + } + + XFILE::CFile* m_pFile; + int m_iMode; + uint8_t *m_pBuffer; + uint8_t *m_BufferPos; + size_t m_BufferRemain; + +private: + void FlushBuffer(); + CArchive &streamout_bufferwrap(const uint8_t *ptr, size_t size); + void FillBuffer(); + CArchive &streamin_bufferwrap(uint8_t *ptr, size_t size); +}; diff --git a/src/utils/AsyncFileCopy.cpp b/src/utils/AsyncFileCopy.cpp new file mode 100644 index 0000000000..d48a0fd3ba --- /dev/null +++ b/src/utils/AsyncFileCopy.cpp @@ -0,0 +1,113 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "threads/SystemClock.h" +#include "AsyncFileCopy.h" +#include "dialogs/GUIDialogProgress.h" +#include "guilib/GUIWindowManager.h" +#include "log.h" +#include "utils/TimeUtils.h" +#include "utils/StringUtils.h" +#include "URL.h" + +CAsyncFileCopy::CAsyncFileCopy() : CThread("AsyncFileCopy") +{ + m_cancelled = false; + m_succeeded = false; + m_running = false; + m_percent = 0; + m_speed = 0; +} + +CAsyncFileCopy::~CAsyncFileCopy() +{ + StopThread(); +} + +bool CAsyncFileCopy::Copy(const CStdString &from, const CStdString &to, const CStdString &heading) +{ + // reset the variables to their appropriate states + m_from = from; + m_to = to; + m_cancelled = false; + m_succeeded = false; + m_percent = 0; + m_speed = 0; + m_running = true; + CURL url1(from); + CURL url2(to); + + // create our thread, which starts the file copy operation + Create(); + CGUIDialogProgress *dlg = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS); + unsigned int time = XbmcThreads::SystemClockMillis(); + while (m_running) + { + m_event.WaitMSec(1000 / 30); + if (!m_running) + break; + // start the dialog up as needed + if (dlg && !dlg->IsDialogRunning() && (XbmcThreads::SystemClockMillis() - time) > 500) // wait 0.5 seconds before starting dialog + { + dlg->SetHeading(heading); + dlg->SetLine(0, url1.GetWithoutUserDetails()); + dlg->SetLine(1, url2.GetWithoutUserDetails()); + dlg->SetPercentage(0); + dlg->StartModal(); + } + // and update the dialog as we go + if (dlg && dlg->IsDialogRunning()) + { + CStdString speedString = StringUtils::Format("%2.2f KB/s", m_speed / 1024); + dlg->SetHeading(heading); + dlg->SetLine(0, url1.Get()); + dlg->SetLine(1, url2.Get()); + dlg->SetLine(2, speedString); + dlg->SetPercentage(m_percent); + dlg->Progress(); + m_cancelled = dlg->IsCanceled(); + } + } + if (dlg) + dlg->Close(); + return !m_cancelled && m_succeeded; +} + +bool CAsyncFileCopy::OnFileCallback(void *pContext, int ipercent, float avgSpeed) +{ + m_percent = ipercent; + m_speed = avgSpeed; + m_event.Set(); + return !m_cancelled; +} + +void CAsyncFileCopy::Process() +{ + try + { + m_succeeded = XFILE::CFile::Copy(m_from, m_to, this); + } + catch (...) + { + m_succeeded = false; + CLog::Log(LOGERROR, "%s: unhandled exception copying file", __FUNCTION__); + } + m_running = false; +} diff --git a/src/utils/AsyncFileCopy.h b/src/utils/AsyncFileCopy.h new file mode 100644 index 0000000000..c4bd9cde67 --- /dev/null +++ b/src/utils/AsyncFileCopy.h @@ -0,0 +1,54 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "threads/Thread.h" +#include "filesystem/File.h" +#include "utils/StdString.h" + +class CAsyncFileCopy : public CThread, public XFILE::IFileCallback +{ +public: + CAsyncFileCopy(); + virtual ~CAsyncFileCopy(); + + /// \brief Main routine to copy files from one source to another. + /// \return true if successful, and false if it failed or was cancelled. + bool Copy(const CStdString &from, const CStdString &to, const CStdString &heading); + + /// \brief callback from CFile::Copy() + virtual bool OnFileCallback(void *pContext, int ipercent, float avgSpeed); + +protected: + virtual void Process(); + +private: + /// volatile variables as we access these from both threads + volatile int m_percent; ///< current percentage (0..100) + volatile float m_speed; ///< current speed (in bytes per second) + volatile bool m_cancelled; ///< whether or not we cancelled the operation + volatile bool m_running; ///< whether or not the copy operation is still in progress + + bool m_succeeded; ///< whether or not the copy operation was successful + CStdString m_from; ///< source URL to copy from + CStdString m_to; ///< destination URL to copy to + CEvent m_event; ///< event to set to force an update +}; diff --git a/src/utils/AutoPtrHandle.cpp b/src/utils/AutoPtrHandle.cpp new file mode 100644 index 0000000000..1b82d9e249 --- /dev/null +++ b/src/utils/AutoPtrHandle.cpp @@ -0,0 +1,136 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "AutoPtrHandle.h" + +using namespace AUTOPTR; + +CAutoPtrHandle::CAutoPtrHandle(HANDLE hHandle) + : m_hHandle(hHandle) +{} + +CAutoPtrHandle::~CAutoPtrHandle(void) +{ + Cleanup(); +} + +CAutoPtrHandle::operator HANDLE() +{ + return m_hHandle; +} + +void CAutoPtrHandle::attach(HANDLE hHandle) +{ + Cleanup(); + m_hHandle = hHandle; +} + +HANDLE CAutoPtrHandle::release() +{ + HANDLE hTmp = m_hHandle; + m_hHandle = INVALID_HANDLE_VALUE; + return hTmp; +} + +void CAutoPtrHandle::Cleanup() +{ + if ( isValid() ) + { + CloseHandle(m_hHandle); + m_hHandle = INVALID_HANDLE_VALUE; + } +} + +bool CAutoPtrHandle::isValid() const +{ + if ( INVALID_HANDLE_VALUE != m_hHandle) + return true; + return false; +} +void CAutoPtrHandle::reset() +{ + Cleanup(); +} + +//------------------------------------------------------------------------------- +CAutoPtrFind ::CAutoPtrFind(HANDLE hHandle) + : CAutoPtrHandle(hHandle) +{} +CAutoPtrFind::~CAutoPtrFind(void) +{ + Cleanup(); +} + +void CAutoPtrFind::Cleanup() +{ + if ( isValid() ) + { + FindClose(m_hHandle); + m_hHandle = INVALID_HANDLE_VALUE; + } +} + +//------------------------------------------------------------------------------- +CAutoPtrSocket::CAutoPtrSocket(SOCKET hSocket) + : m_hSocket(hSocket) +{} + +CAutoPtrSocket::~CAutoPtrSocket(void) +{ + Cleanup(); +} + +CAutoPtrSocket::operator SOCKET() +{ + return m_hSocket; +} + +void CAutoPtrSocket::attach(SOCKET hSocket) +{ + Cleanup(); + m_hSocket = hSocket; +} + +SOCKET CAutoPtrSocket::release() +{ + SOCKET hTmp = m_hSocket; + m_hSocket = INVALID_SOCKET; + return hTmp; +} + +void CAutoPtrSocket::Cleanup() +{ + if ( isValid() ) + { + closesocket(m_hSocket); + m_hSocket = INVALID_SOCKET; + } +} + +bool CAutoPtrSocket::isValid() const +{ + if ( INVALID_SOCKET != m_hSocket) + return true; + return false; +} +void CAutoPtrSocket::reset() +{ + Cleanup(); +} diff --git a/src/utils/AutoPtrHandle.h b/src/utils/AutoPtrHandle.h new file mode 100644 index 0000000000..cd72c3ae64 --- /dev/null +++ b/src/utils/AutoPtrHandle.h @@ -0,0 +1,110 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" // for HANDLE and SOCKET +#include <stdlib.h> + +namespace AUTOPTR +{ +class CAutoPtrHandle +{ +public: + CAutoPtrHandle(HANDLE hHandle); + virtual ~CAutoPtrHandle(void); + operator HANDLE(); + void attach(HANDLE hHandle); + HANDLE release(); + bool isValid() const; + void reset(); +protected: + virtual void Cleanup(); + HANDLE m_hHandle; +}; + +class CAutoPtrFind : public CAutoPtrHandle +{ +public: + CAutoPtrFind(HANDLE hHandle); + virtual ~CAutoPtrFind(void); +protected: + virtual void Cleanup(); +}; + + +class CAutoPtrSocket +{ +public: + CAutoPtrSocket(SOCKET hSocket); + virtual ~CAutoPtrSocket(void); + operator SOCKET(); + void attach(SOCKET hSocket); + SOCKET release(); + bool isValid() const; + void reset(); +protected: + virtual void Cleanup(); + SOCKET m_hSocket; +}; + +/* + * This template class is very similar to the standard "auto_ptr", but it is + * used for *array* pointers rather than *object* pointers, i.e. the pointer + * passed to it must have been allocated with "new[]", and "auto_aptr" will + * delete it with "delete[]". + * + * Class released under GPL and was taken from: + * http://userpage.fu-berlin.de/~mbayer/tools/html2text.html + */ +template <class T> +class auto_aptr +{ + +public: + + // Constructor/copy/destroy + + explicit auto_aptr(T *x = 0) : p(x) {} + auto_aptr(const auto_aptr<T> &x) : p(x.p) { ((auto_aptr<T> *) &x)->p = 0; } + auto_aptr<T>& operator=(const auto_aptr<T> &x) + { delete[] p; p = x.p; ((auto_aptr<T> *) &x)->p = 0; return *this; } + // Extension: "operator=(T *)" is identical to "auto_aptr::reset(T *)". + void operator=(T *x) { delete[] p; p = x; } + ~auto_aptr() { delete[] p; } + + // Members + + T &operator[](size_t idx) const { if (!p) abort(); return p[idx]; } +T *get() const { return (T *) p; } + T *release() { T *tmp = p; p = 0; return tmp; } + void reset(T *x = 0) { delete[] p; p = x; } + + // These would make a nice extension, but are not provided by many other + // implementations. + //operator const void *() const { return p; } + //int operator!() const { return p == 0; } + +private: + T *p; +}; + + +} diff --git a/src/utils/Base64.cpp b/src/utils/Base64.cpp new file mode 100644 index 0000000000..54e16f7836 --- /dev/null +++ b/src/utils/Base64.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Base64.h" + +#define PADDING '=' + +using namespace std; + +const std::string Base64::m_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +void Base64::Encode(const char* input, unsigned int length, std::string &output) +{ + if (input == NULL || length == 0) + return; + + long l; + output.clear(); + output.reserve(((length + 2) / 3) * 4); + + for (unsigned int i = 0; i < length; i += 3) + { + l = ((((unsigned long) input[i]) << 16) & 0xFFFFFF) | + ((((i + 1) < length) ? (((unsigned long) input[i + 1]) << 8) : 0) & 0xFFFF) | + ((((i + 2) < length) ? (((unsigned long) input[i + 2]) << 0) : 0) & 0x00FF); + + output.push_back(m_characters[(l >> 18) & 0x3F]); + output.push_back(m_characters[(l >> 12) & 0x3F]); + + if (i + 1 < length) + output.push_back(m_characters[(l >> 6) & 0x3F]); + if (i + 2 < length) + output.push_back(m_characters[(l >> 0) & 0x3F]); + } + + int left = 3 - (length % 3); + + if (length % 3) + { + for (int i = 0; i < left; i++) + output.push_back(PADDING); + } +} + +std::string Base64::Encode(const char* input, unsigned int length) +{ + std::string output; + Encode(input, length, output); + + return output; +} + +void Base64::Encode(const std::string &input, std::string &output) +{ + Encode(input.c_str(), input.size(), output); +} + +std::string Base64::Encode(const std::string &input) +{ + std::string output; + Encode(input, output); + + return output; +} + +void Base64::Decode(const char* input, unsigned int length, std::string &output) +{ + if (input == NULL || length == 0) + return; + + long l; + output.clear(); + + for (unsigned int index = 0; index < length; index++) + { + if (input[index] == '=') + { + length = index; + break; + } + } + + output.reserve(length - ((length + 2) / 4)); + + for (unsigned int i = 0; i < length; i += 4) + { + l = ((((unsigned long) m_characters.find(input[i])) & 0x3F) << 18); + l |= (((i + 1) < length) ? ((((unsigned long) m_characters.find(input[i + 1])) & 0x3F) << 12) : 0); + l |= (((i + 2) < length) ? ((((unsigned long) m_characters.find(input[i + 2])) & 0x3F) << 6) : 0); + l |= (((i + 3) < length) ? ((((unsigned long) m_characters.find(input[i + 3])) & 0x3F) << 0) : 0); + + output.push_back((char)((l >> 16) & 0xFF)); + if (i + 2 < length) + output.push_back((char)((l >> 8) & 0xFF)); + if (i + 3 < length) + output.push_back((char)((l >> 0) & 0xFF)); + } +} + +std::string Base64::Decode(const char* input, unsigned int length) +{ + std::string output; + Decode(input, length, output); + + return output; +} + +void Base64::Decode(const std::string &input, std::string &output) +{ + size_t length = input.find_first_of(PADDING); + if (length == string::npos) + length = input.size(); + + Decode(input.c_str(), length, output); +} + +std::string Base64::Decode(const std::string &input) +{ + std::string output; + Decode(input, output); + + return output; +} diff --git a/src/utils/Base64.h b/src/utils/Base64.h new file mode 100644 index 0000000000..a37bbe49d8 --- /dev/null +++ b/src/utils/Base64.h @@ -0,0 +1,38 @@ +#pragma once +/* + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> + +class Base64 +{ +public: + static void Encode(const char* input, unsigned int length, std::string &output); + static std::string Encode(const char* input, unsigned int length); + static void Encode(const std::string &input, std::string &output); + static std::string Encode(const std::string &input); + static void Decode(const char* input, unsigned int length, std::string &output); + static std::string Decode(const char* input, unsigned int length); + static void Decode(const std::string &input, std::string &output); + static std::string Decode(const std::string &input); + +private: + static const std::string m_characters; +}; diff --git a/src/utils/BitstreamConverter.cpp b/src/utils/BitstreamConverter.cpp new file mode 100644 index 0000000000..11d0673ec2 --- /dev/null +++ b/src/utils/BitstreamConverter.cpp @@ -0,0 +1,1216 @@ +/* + * Copyright (C) 2010-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/log.h" +#include "assert.h" + +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif + +#include "BitstreamConverter.h" + +enum { + NAL_SLICE=1, + NAL_DPA, + NAL_DPB, + NAL_DPC, + NAL_IDR_SLICE, + NAL_SEI, + NAL_SPS, + NAL_PPS, + NAL_AUD, + NAL_END_SEQUENCE, + NAL_END_STREAM, + NAL_FILLER_DATA, + NAL_SPS_EXT, + NAL_AUXILIARY_SLICE=19 +}; + +//////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// +// GStreamer h264 parser +// Copyright (C) 2005 Michal Benes <michal.benes@itonis.tv> +// (C) 2008 Wim Taymans <wim.taymans@gmail.com> +// gsth264parse.c: +// * License as published by the Free Software Foundation; either +// * version 2.1 of the License, or (at your option) any later version. +static void nal_bs_init(nal_bitstream *bs, const uint8_t *data, size_t size) +{ + bs->data = data; + bs->end = data + size; + bs->head = 0; + // fill with something other than 0 to detect + // emulation prevention bytes + bs->cache = 0xffffffff; +} + +static uint32_t nal_bs_read(nal_bitstream *bs, int n) +{ + uint32_t res = 0; + int shift; + + if (n == 0) + return res; + + // fill up the cache if we need to + while (bs->head < n) + { + uint8_t a_byte; + bool check_three_byte; + + check_three_byte = true; +next_byte: + if (bs->data >= bs->end) + { + // we're at the end, can't produce more than head number of bits + n = bs->head; + break; + } + // get the byte, this can be an emulation_prevention_three_byte that we need + // to ignore. + a_byte = *bs->data++; + if (check_three_byte && a_byte == 0x03 && ((bs->cache & 0xffff) == 0)) + { + // next byte goes unconditionally to the cache, even if it's 0x03 + check_three_byte = false; + goto next_byte; + } + // shift bytes in cache, moving the head bits of the cache left + bs->cache = (bs->cache << 8) | a_byte; + bs->head += 8; + } + + // bring the required bits down and truncate + if ((shift = bs->head - n) > 0) + res = bs->cache >> shift; + else + res = bs->cache; + + // mask out required bits + if (n < 32) + res &= (1 << n) - 1; + bs->head = shift; + + return res; +} + +static bool nal_bs_eos(nal_bitstream *bs) +{ + return (bs->data >= bs->end) && (bs->head == 0); +} + +// read unsigned Exp-Golomb code +static int nal_bs_read_ue(nal_bitstream *bs) +{ + int i = 0; + + while (nal_bs_read(bs, 1) == 0 && !nal_bs_eos(bs) && i < 32) + i++; + + return ((1 << i) - 1 + nal_bs_read(bs, i)); +} + +static const uint8_t* avc_find_startcode_internal(const uint8_t *p, const uint8_t *end) +{ + const uint8_t *a = p + 4 - ((intptr_t)p & 3); + + for (end -= 3; p < a && p < end; p++) + { + if (p[0] == 0 && p[1] == 0 && p[2] == 1) + return p; + } + + for (end -= 3; p < end; p += 4) + { + uint32_t x = *(const uint32_t*)p; + if ((x - 0x01010101) & (~x) & 0x80808080) // generic + { + if (p[1] == 0) + { + if (p[0] == 0 && p[2] == 1) + return p; + if (p[2] == 0 && p[3] == 1) + return p+1; + } + if (p[3] == 0) + { + if (p[2] == 0 && p[4] == 1) + return p+2; + if (p[4] == 0 && p[5] == 1) + return p+3; + } + } + } + + for (end += 3; p < end; p++) + { + if (p[0] == 0 && p[1] == 0 && p[2] == 1) + return p; + } + + return end + 3; +} + +static const uint8_t* avc_find_startcode(const uint8_t *p, const uint8_t *end) +{ + const uint8_t *out = avc_find_startcode_internal(p, end); + if (p<out && out<end && !out[-1]) + out--; + return out; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// +CBitstreamParser::CBitstreamParser() +{ +} + +CBitstreamParser::~CBitstreamParser() +{ + Close(); +} + +bool CBitstreamParser::Open() +{ + return true; +} + +void CBitstreamParser::Close() +{ +} + +const uint8_t* CBitstreamParser::find_start_code(const uint8_t *p, + const uint8_t *end, uint32_t *state) +{ + assert(p <= end); + if (p >= end) + return end; + + for (int i = 0; i < 3; i++) { + uint32_t tmp = *state << 8; + *state = tmp + *(p++); + if (tmp == 0x100 || p == end) + return p; + } + + while (p < end) { + if (p[-1] > 1 ) p += 3; + else if (p[-2] ) p += 2; + else if (p[-3]|(p[-1]-1)) p++; + else { + p++; + break; + } + } + + p = FFMIN(p, end) - 4; + *state = BS_RB32(p); + + return p + 4; +} + +bool CBitstreamParser::FindIdrSlice(const uint8_t *buf, int buf_size) +{ + if (!buf) + return false; + + bool rtn = false; + uint32_t state = -1; + const uint8_t *buf_end = buf + buf_size; + + for(;;) + { + buf = find_start_code(buf, buf_end, &state); + if (buf >= buf_end) + { + //CLog::Log(LOGDEBUG, "FindIdrSlice: buf(%p), buf_end(%p)", buf, buf_end); + break; + } + + --buf; + int src_length = buf_end - buf; + switch (state & 0x1f) + { + default: + CLog::Log(LOGDEBUG, "FindIdrSlice: found nal_type(%d)", state & 0x1f); + break; + case NAL_SLICE: + CLog::Log(LOGDEBUG, "FindIdrSlice: found NAL_SLICE"); + break; + case NAL_IDR_SLICE: + CLog::Log(LOGDEBUG, "FindIdrSlice: found NAL_IDR_SLICE"); + rtn = true; + break; + case NAL_SEI: + CLog::Log(LOGDEBUG, "FindIdrSlice: found NAL_SEI"); + break; + case NAL_SPS: + CLog::Log(LOGDEBUG, "FindIdrSlice: found NAL_SPS"); + break; + case NAL_PPS: + CLog::Log(LOGDEBUG, "FindIdrSlice: found NAL_PPS"); + break; + } + buf += src_length; + } + + return rtn; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// +CBitstreamConverter::CBitstreamConverter() +{ + m_convert_bitstream = false; + m_convertBuffer = NULL; + m_convertSize = 0; + m_inputBuffer = NULL; + m_inputSize = 0; + m_to_annexb = false; + m_extradata = NULL; + m_extrasize = 0; + m_convert_3byteTo4byteNALSize = false; + m_convert_bytestream = false; + m_sps_pps_context.sps_pps_data = NULL; +} + +CBitstreamConverter::~CBitstreamConverter() +{ + Close(); +} + +bool CBitstreamConverter::Open(enum AVCodecID codec, uint8_t *in_extradata, int in_extrasize, bool to_annexb) +{ + m_to_annexb = to_annexb; + + m_codec = codec; + switch(m_codec) + { + case AV_CODEC_ID_H264: + if (in_extrasize < 7 || in_extradata == NULL) + { + CLog::Log(LOGERROR, "CBitstreamConverter::Open avcC data too small or missing"); + return false; + } + // valid avcC data (bitstream) always starts with the value 1 (version) + if(m_to_annexb) + { + if ( in_extradata[0] == 1 ) + { + CLog::Log(LOGINFO, "CBitstreamConverter::Open bitstream to annexb init"); + m_extrasize = in_extrasize; + m_extradata = (uint8_t*)av_malloc(in_extrasize); + memcpy(m_extradata, in_extradata, in_extrasize); + m_convert_bitstream = BitstreamConvertInit(m_extradata, m_extrasize); + return true; + } + } + else + { + // valid avcC atom data always starts with the value 1 (version) + if ( in_extradata[0] != 1 ) + { + if ( (in_extradata[0] == 0 && in_extradata[1] == 0 && in_extradata[2] == 0 && in_extradata[3] == 1) || + (in_extradata[0] == 0 && in_extradata[1] == 0 && in_extradata[2] == 1) ) + { + CLog::Log(LOGINFO, "CBitstreamConverter::Open annexb to bitstream init"); + // video content is from x264 or from bytestream h264 (AnnexB format) + // NAL reformating to bitstream format needed + AVIOContext *pb; + if (avio_open_dyn_buf(&pb) < 0) + return false; + m_convert_bytestream = true; + // create a valid avcC atom data from ffmpeg's extradata + isom_write_avcc(pb, in_extradata, in_extrasize); + // unhook from ffmpeg's extradata + in_extradata = NULL; + // extract the avcC atom data into extradata then write it into avcCData for VDADecoder + in_extrasize = avio_close_dyn_buf(pb, &in_extradata); + // make a copy of extradata contents + m_extradata = (uint8_t *)av_malloc(in_extrasize); + memcpy(m_extradata, in_extradata, in_extrasize); + m_extrasize = in_extrasize; + // done with the converted extradata, we MUST free using av_free + av_free(in_extradata); + return true; + } + else + { + CLog::Log(LOGNOTICE, "CBitstreamConverter::Open invalid avcC atom data"); + return false; + } + } + else + { + if (in_extradata[4] == 0xFE) + { + CLog::Log(LOGINFO, "CBitstreamConverter::Open annexb to bitstream init 3 byte to 4 byte nal"); + // video content is from so silly encoder that think 3 byte NAL sizes + // are valid, setup to convert 3 byte NAL sizes to 4 byte. + in_extradata[4] = 0xFF; + m_convert_3byteTo4byteNALSize = true; + + m_extradata = (uint8_t *)av_malloc(in_extrasize); + memcpy(m_extradata, in_extradata, in_extrasize); + m_extrasize = in_extrasize; + return true; + } + } + // valid avcC atom + m_extradata = (uint8_t*)av_malloc(in_extrasize); + memcpy(m_extradata, in_extradata, in_extrasize); + m_extrasize = in_extrasize; + return true; + } + return false; + break; + default: + return false; + break; + } + return false; +} + +void CBitstreamConverter::Close(void) +{ + if (m_sps_pps_context.sps_pps_data) + av_free(m_sps_pps_context.sps_pps_data), m_sps_pps_context.sps_pps_data = NULL; + + if (m_convertBuffer) + av_free(m_convertBuffer), m_convertBuffer = NULL; + m_convertSize = 0; + + if (m_extradata) + av_free(m_extradata), m_extradata = NULL; + m_extrasize = 0; + + m_inputSize = 0; + m_inputBuffer = NULL; + + m_convert_bitstream = false; + m_convert_bytestream = false; + m_convert_3byteTo4byteNALSize = false; +} + +bool CBitstreamConverter::Convert(uint8_t *pData, int iSize) +{ + if (m_convertBuffer) + { + av_free(m_convertBuffer); + m_convertBuffer = NULL; + } + m_inputSize = 0; + m_convertSize = 0; + m_inputBuffer = NULL; + + if (pData) + { + if (m_codec == AV_CODEC_ID_H264) + { + if (m_to_annexb) + { + int demuxer_bytes = iSize; + uint8_t *demuxer_content = pData; + + if (m_convert_bitstream) + { + // convert demuxer packet from bitstream to bytestream (AnnexB) + int bytestream_size = 0; + uint8_t *bytestream_buff = NULL; + + BitstreamConvert(demuxer_content, demuxer_bytes, &bytestream_buff, &bytestream_size); + if (bytestream_buff && (bytestream_size > 0)) + { + m_convertSize = bytestream_size; + m_convertBuffer = bytestream_buff; + return true; + } + else + { + m_convertSize = 0; + m_convertBuffer = NULL; + CLog::Log(LOGERROR, "CBitstreamConverter::Convert: error converting."); + return false; + } + } + else + { + m_inputSize = iSize; + m_inputBuffer = pData; + return true; + } + } + else + { + m_inputSize = iSize; + m_inputBuffer = pData; + + if (m_convert_bytestream) + { + if(m_convertBuffer) + { + av_free(m_convertBuffer); + m_convertBuffer = NULL; + } + m_convertSize = 0; + + // convert demuxer packet from bytestream (AnnexB) to bitstream + AVIOContext *pb; + + if(avio_open_dyn_buf(&pb) < 0) + { + return false; + } + m_convertSize = avc_parse_nal_units(pb, pData, iSize); + m_convertSize = avio_close_dyn_buf(pb, &m_convertBuffer); + } + else if (m_convert_3byteTo4byteNALSize) + { + if(m_convertBuffer) + { + av_free(m_convertBuffer); + m_convertBuffer = NULL; + } + m_convertSize = 0; + + // convert demuxer packet from 3 byte NAL sizes to 4 byte + AVIOContext *pb; + if (avio_open_dyn_buf(&pb) < 0) + return false; + + uint32_t nal_size; + uint8_t *end = pData + iSize; + uint8_t *nal_start = pData; + while (nal_start < end) + { + nal_size = BS_RB24(nal_start); + avio_wb16(pb, nal_size); + nal_start += 3; + avio_write(pb, nal_start, nal_size); + nal_start += nal_size; + } + + m_convertSize = avio_close_dyn_buf(pb, &m_convertBuffer); + } + return true; + } + } + } + + return false; +} + + +uint8_t *CBitstreamConverter::GetConvertBuffer() const +{ + if((m_convert_bitstream || m_convert_bytestream || m_convert_3byteTo4byteNALSize) && m_convertBuffer != NULL) + return m_convertBuffer; + else + return m_inputBuffer; +} + +int CBitstreamConverter::GetConvertSize() const +{ + if((m_convert_bitstream || m_convert_bytestream || m_convert_3byteTo4byteNALSize) && m_convertBuffer != NULL) + return m_convertSize; + else + return m_inputSize; +} + +uint8_t *CBitstreamConverter::GetExtraData() const +{ + if(m_convert_bitstream) + return m_sps_pps_context.sps_pps_data; + else + return m_extradata; +} +int CBitstreamConverter::GetExtraSize() const +{ + if(m_convert_bitstream) + return m_sps_pps_context.size; + else + return m_extrasize; +} + +bool CBitstreamConverter::BitstreamConvertInit(void *in_extradata, int in_extrasize) +{ + // based on h264_mp4toannexb_bsf.c (ffmpeg) + // which is Copyright (c) 2007 Benoit Fouet <benoit.fouet@free.fr> + // and Licensed GPL 2.1 or greater + + m_sps_pps_size = 0; + m_sps_pps_context.sps_pps_data = NULL; + + // nothing to filter + if (!in_extradata || in_extrasize < 6) + return false; + + uint16_t unit_size; + uint32_t total_size = 0; + uint8_t *out = NULL, unit_nb, sps_done = 0, sps_seen = 0, pps_seen = 0; + const uint8_t *extradata = (uint8_t*)in_extradata + 4; + static const uint8_t nalu_header[4] = {0, 0, 0, 1}; + + // retrieve length coded size + m_sps_pps_context.length_size = (*extradata++ & 0x3) + 1; + + // retrieve sps and pps unit(s) + unit_nb = *extradata++ & 0x1f; // number of sps unit(s) + if (!unit_nb) + { + goto pps; + } + else + { + sps_seen = 1; + } + + while (unit_nb--) + { + void *tmp; + + unit_size = extradata[0] << 8 | extradata[1]; + total_size += unit_size + 4; + + if (total_size > INT_MAX - FF_INPUT_BUFFER_PADDING_SIZE || + (extradata + 2 + unit_size) > ((uint8_t*)in_extradata + in_extrasize)) + { + av_free(out); + return false; + } + tmp = av_realloc(out, total_size + FF_INPUT_BUFFER_PADDING_SIZE); + if (!tmp) + { + av_free(out); + return false; + } + out = (uint8_t*)tmp; + memcpy(out + total_size - unit_size - 4, nalu_header, 4); + memcpy(out + total_size - unit_size, extradata + 2, unit_size); + extradata += 2 + unit_size; + +pps: + if (!unit_nb && !sps_done++) + { + unit_nb = *extradata++; // number of pps unit(s) + if (unit_nb) + pps_seen = 1; + } + } + + if (out) + memset(out + total_size, 0, FF_INPUT_BUFFER_PADDING_SIZE); + + if (!sps_seen) + CLog::Log(LOGDEBUG, "SPS NALU missing or invalid. The resulting stream may not play"); + if (!pps_seen) + CLog::Log(LOGDEBUG, "PPS NALU missing or invalid. The resulting stream may not play"); + + m_sps_pps_context.sps_pps_data = out; + m_sps_pps_context.size = total_size; + m_sps_pps_context.first_idr = 1; + m_sps_pps_context.idr_sps_pps_seen = 0; + + return true; +} + +bool CBitstreamConverter::BitstreamConvert(uint8_t* pData, int iSize, uint8_t **poutbuf, int *poutbuf_size) +{ + // based on h264_mp4toannexb_bsf.c (ffmpeg) + // which is Copyright (c) 2007 Benoit Fouet <benoit.fouet@free.fr> + // and Licensed GPL 2.1 or greater + + int i; + uint8_t *buf = pData; + uint32_t buf_size = iSize; + uint8_t unit_type; + int32_t nal_size; + uint32_t cumul_size = 0; + const uint8_t *buf_end = buf + buf_size; + + do + { + if (buf + m_sps_pps_context.length_size > buf_end) + goto fail; + + for (nal_size = 0, i = 0; i < m_sps_pps_context.length_size; i++) + nal_size = (nal_size << 8) | buf[i]; + + buf += m_sps_pps_context.length_size; + unit_type = *buf & 0x1f; + + if (buf + nal_size > buf_end || nal_size < 0) + goto fail; + + // Don't add sps/pps if the unit already contain them + if (m_sps_pps_context.first_idr && (unit_type == 7 || unit_type == 8)) + m_sps_pps_context.idr_sps_pps_seen = 1; + + // prepend only to the first access unit of an IDR picture, if no sps/pps already present + if (m_sps_pps_context.first_idr && unit_type == 5 && !m_sps_pps_context.idr_sps_pps_seen) + { + BitstreamAllocAndCopy(poutbuf, poutbuf_size, + m_sps_pps_context.sps_pps_data, m_sps_pps_context.size, buf, nal_size); + m_sps_pps_context.first_idr = 0; + } + else + { + BitstreamAllocAndCopy(poutbuf, poutbuf_size, NULL, 0, buf, nal_size); + if (!m_sps_pps_context.first_idr && unit_type == 1) + { + m_sps_pps_context.first_idr = 1; + m_sps_pps_context.idr_sps_pps_seen = 0; + } + } + + buf += nal_size; + cumul_size += nal_size + m_sps_pps_context.length_size; + } while (cumul_size < buf_size); + + return true; + +fail: + av_free(*poutbuf), *poutbuf = NULL; + *poutbuf_size = 0; + return false; +} + +void CBitstreamConverter::BitstreamAllocAndCopy( uint8_t **poutbuf, int *poutbuf_size, + const uint8_t *sps_pps, uint32_t sps_pps_size, const uint8_t *in, uint32_t in_size) +{ + // based on h264_mp4toannexb_bsf.c (ffmpeg) + // which is Copyright (c) 2007 Benoit Fouet <benoit.fouet@free.fr> + // and Licensed GPL 2.1 or greater + + uint32_t offset = *poutbuf_size; + uint8_t nal_header_size = offset ? 3 : 4; + void *tmp; + + *poutbuf_size += sps_pps_size + in_size + nal_header_size; + tmp = av_realloc(*poutbuf, *poutbuf_size); + if (!tmp) + return; + *poutbuf = (uint8_t*)tmp; + if (sps_pps) + memcpy(*poutbuf + offset, sps_pps, sps_pps_size); + + memcpy(*poutbuf + sps_pps_size + nal_header_size + offset, in, in_size); + if (!offset) + { + BS_WB32(*poutbuf + sps_pps_size, 1); + } + else + { + (*poutbuf + offset + sps_pps_size)[0] = 0; + (*poutbuf + offset + sps_pps_size)[1] = 0; + (*poutbuf + offset + sps_pps_size)[2] = 1; + } +} + +const int CBitstreamConverter::avc_parse_nal_units(AVIOContext *pb, const uint8_t *buf_in, int size) +{ + const uint8_t *p = buf_in; + const uint8_t *end = p + size; + const uint8_t *nal_start, *nal_end; + + size = 0; + nal_start = avc_find_startcode(p, end); + + for (;;) { + while (nal_start < end && !*(nal_start++)); + if (nal_start == end) + break; + + nal_end = avc_find_startcode(nal_start, end); + avio_wb32(pb, nal_end - nal_start); + avio_write(pb, nal_start, nal_end - nal_start); + size += 4 + nal_end - nal_start; + nal_start = nal_end; + } + return size; +} + +const int CBitstreamConverter::avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size) +{ + AVIOContext *pb; + int ret = avio_open_dyn_buf(&pb); + if (ret < 0) + return ret; + + avc_parse_nal_units(pb, buf_in, *size); + + av_freep(buf); + *size = avio_close_dyn_buf(pb, buf); + return 0; +} + +const int CBitstreamConverter::isom_write_avcc(AVIOContext *pb, const uint8_t *data, int len) +{ + // extradata from bytestream h264, convert to avcC atom data for bitstream + if (len > 6) + { + /* check for h264 start code */ + if (BS_RB32(data) == 0x00000001 || BS_RB24(data) == 0x000001) + { + uint8_t *buf=NULL, *end, *start; + uint32_t sps_size=0, pps_size=0; + uint8_t *sps=0, *pps=0; + + int ret = avc_parse_nal_units_buf(data, &buf, &len); + if (ret < 0) + return ret; + start = buf; + end = buf + len; + + /* look for sps and pps */ + while (end - buf > 4) + { + uint32_t size; + uint8_t nal_type; + size = FFMIN(BS_RB32(buf), end - buf - 4); + buf += 4; + nal_type = buf[0] & 0x1f; + if (nal_type == 7) /* SPS */ + { + sps = buf; + sps_size = size; + } + else if (nal_type == 8) /* PPS */ + { + pps = buf; + pps_size = size; + } + buf += size; + } + if (!sps || !pps || sps_size < 4 || sps_size > UINT16_MAX || pps_size > UINT16_MAX) + assert(0); + + avio_w8(pb, 1); /* version */ + avio_w8(pb, sps[1]); /* profile */ + avio_w8(pb, sps[2]); /* profile compat */ + avio_w8(pb, sps[3]); /* level */ + avio_w8(pb, 0xff); /* 6 bits reserved (111111) + 2 bits nal size length - 1 (11) */ + avio_w8(pb, 0xe1); /* 3 bits reserved (111) + 5 bits number of sps (00001) */ + + avio_wb16(pb, sps_size); + avio_write(pb, sps, sps_size); + if (pps) + { + avio_w8(pb, 1); /* number of pps */ + avio_wb16(pb, pps_size); + avio_write(pb, pps, pps_size); + } + av_free(start); + } + else + { + avio_write(pb, data, len); + } + } + return 0; +} + +void CBitstreamConverter::bits_reader_set( bits_reader_t *br, uint8_t *buf, int len ) +{ + br->buffer = br->start = buf; + br->offbits = 0; + br->length = len; + br->oflow = 0; +} + +uint32_t CBitstreamConverter::read_bits( bits_reader_t *br, int nbits ) +{ + int i, nbytes; + uint32_t ret = 0; + uint8_t *buf; + + buf = br->buffer; + nbytes = (br->offbits + nbits)/8; + if ( ((br->offbits + nbits) %8 ) > 0 ) + nbytes++; + if ( (buf + nbytes) > (br->start + br->length) ) { + br->oflow = 1; + return 0; + } + for ( i=0; i<nbytes; i++ ) + ret += buf[i]<<((nbytes-i-1)*8); + i = (4-nbytes)*8+br->offbits; + ret = ((ret<<i)>>i)>>((nbytes*8)-nbits-br->offbits); + + br->offbits += nbits; + br->buffer += br->offbits / 8; + br->offbits %= 8; + + return ret; +} + +void CBitstreamConverter::skip_bits( bits_reader_t *br, int nbits ) +{ + br->offbits += nbits; + br->buffer += br->offbits / 8; + br->offbits %= 8; + if ( br->buffer > (br->start + br->length) ) { + br->oflow = 1; + } +} + +uint32_t CBitstreamConverter::get_bits( bits_reader_t *br, int nbits ) +{ + int i, nbytes; + uint32_t ret = 0; + uint8_t *buf; + + buf = br->buffer; + nbytes = (br->offbits + nbits)/8; + if ( ((br->offbits + nbits) %8 ) > 0 ) + nbytes++; + if ( (buf + nbytes) > (br->start + br->length) ) { + br->oflow = 1; + return 0; + } + for ( i=0; i<nbytes; i++ ) + ret += buf[i]<<((nbytes-i-1)*8); + i = (4-nbytes)*8+br->offbits; + ret = ((ret<<i)>>i)>>((nbytes*8)-nbits-br->offbits); + + return ret; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// +void CBitstreamConverter::init_bits_writer(bits_writer_t *s, uint8_t *buffer, int buffer_size, int writer_le) +{ + if (buffer_size < 0) + { + buffer_size = 0; + buffer = NULL; + } + + s->size_in_bits = 8 * buffer_size; + s->buf = buffer; + s->buf_end = s->buf + buffer_size; + s->buf_ptr = s->buf; + s->bit_left = 32; + s->bit_buf = 0; + s->writer_le = writer_le; +} + +void CBitstreamConverter::write_bits(bits_writer_t *s, int n, unsigned int value) +{ + // Write up to 32 bits into a bitstream. + unsigned int bit_buf; + int bit_left; + + if (n == 32) + { + // Write exactly 32 bits into a bitstream. + // danger, recursion in play. + int lo = value & 0xffff; + int hi = value >> 16; + if (s->writer_le) + { + write_bits(s, 16, lo); + write_bits(s, 16, hi); + } + else + { + write_bits(s, 16, hi); + write_bits(s, 16, lo); + } + return; + } + + bit_buf = s->bit_buf; + bit_left = s->bit_left; + + if (s->writer_le) + { + bit_buf |= value << (32 - bit_left); + if (n >= bit_left) { + BS_WL32(s->buf_ptr, bit_buf); + s->buf_ptr += 4; + bit_buf = (bit_left == 32) ? 0 : value >> bit_left; + bit_left += 32; + } + bit_left -= n; + } + else + { + if (n < bit_left) { + bit_buf = (bit_buf << n) | value; + bit_left -= n; + } else { + bit_buf <<= bit_left; + bit_buf |= value >> (n - bit_left); + BS_WB32(s->buf_ptr, bit_buf); + s->buf_ptr += 4; + bit_left += 32 - n; + bit_buf = value; + } + } + + s->bit_buf = bit_buf; + s->bit_left = bit_left; +} + +void CBitstreamConverter::skip_bits(bits_writer_t *s, int n) +{ + // Skip the given number of bits. + // Must only be used if the actual values in the bitstream do not matter. + // If n is 0 the behavior is undefined. + s->bit_left -= n; + s->buf_ptr -= 4 * (s->bit_left >> 5); + s->bit_left &= 31; +} + +void CBitstreamConverter::flush_bits(bits_writer_t *s) +{ + if (!s->writer_le) + { + if (s->bit_left < 32) + s->bit_buf <<= s->bit_left; + } + while (s->bit_left < 32) + { + + if (s->writer_le) + { + *s->buf_ptr++ = s->bit_buf; + s->bit_buf >>= 8; + } + else + { + *s->buf_ptr++ = s->bit_buf >> 24; + s->bit_buf <<= 8; + } + s->bit_left += 8; + } + s->bit_left = 32; + s->bit_buf = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// +bool CBitstreamConverter::mpeg2_sequence_header(const uint8_t *data, const uint32_t size, mpeg2_sequence *sequence) +{ + // parse nal's until a sequence_header_code is found + // and return the width, height, aspect ratio and frame rate if changed. + bool changed = false; + + if (!data) + return changed; + + const uint8_t *p = data; + const uint8_t *end = p + size; + const uint8_t *nal_start, *nal_end; + + nal_start = avc_find_startcode(p, end); + while (nal_start < end) + { + while (!*(nal_start++)); + nal_end = avc_find_startcode(nal_start, end); + if (*nal_start == 0xB3) + { + nal_bitstream bs; + nal_bs_init(&bs, nal_start, end - nal_start); + + // sequence_header_code + nal_bs_read(&bs, 8); + + // width + // nal_start + 12 bits == horizontal_size_value + uint32_t width = nal_bs_read(&bs, 12); + if (width != sequence->width) + { + changed = true; + sequence->width = width; + } + // height + // nal_start + 24 bits == vertical_size_value + uint32_t height = nal_bs_read(&bs, 12); + if (height != sequence->height) + { + changed = true; + sequence->height = height; + } + + // aspect ratio + // nal_start + 28 bits == aspect_ratio_information + float ratio = sequence->ratio; + uint32_t ratio_info = nal_bs_read(&bs, 4); + switch(ratio_info) + { + case 0x01: + ratio = 1.0; + break; + default: + case 0x02: + ratio = 4.0/3.0; + break; + case 0x03: + ratio = 16.0/9.0; + break; + case 0x04: + ratio = 2.21; + break; + } + if (ratio_info != sequence->ratio_info) + { + changed = true; + sequence->ratio = ratio; + sequence->ratio_info = ratio_info; + } + + // frame rate + // nal_start + 32 bits == frame_rate_code + float rate = sequence->rate; + uint32_t rate_info = nal_bs_read(&bs, 4); + switch(rate_info) + { + default: + case 0x01: + rate = 24000.0 / 1001.0; + break; + case 0x02: + rate = 24000.0 / 1000.0; + break; + case 0x03: + rate = 25000.0 / 1000.0; + break; + case 0x04: + rate = 30000.0 / 1001.0; + break; + case 0x05: + rate = 30000.0 / 1000.0; + break; + case 0x06: + rate = 50000.0 / 1000.0; + break; + case 0x07: + rate = 60000.0 / 1001.0; + break; + case 0x08: + rate = 60000.0 / 1000.0; + break; + } + if (rate_info != sequence->rate_info) + { + changed = true; + sequence->rate = rate; + sequence->rate_info = rate_info; + } + /* + if (changed) + { + CLog::Log(LOGDEBUG, "CBitstreamConverter::mpeg2_sequence_header: " + "width(%d), height(%d), ratio(%f), rate(%f)", width, height, ratio, rate); + } + */ + } + nal_start = nal_end; + } + + return changed; +} + +void CBitstreamConverter::parseh264_sps(const uint8_t *sps, const uint32_t sps_size, bool *interlaced, int32_t *max_ref_frames) +{ + nal_bitstream bs; + sps_info_struct sps_info; + + nal_bs_init(&bs, sps, sps_size); + + sps_info.profile_idc = nal_bs_read(&bs, 8); + nal_bs_read(&bs, 1); // constraint_set0_flag + nal_bs_read(&bs, 1); // constraint_set1_flag + nal_bs_read(&bs, 1); // constraint_set2_flag + nal_bs_read(&bs, 1); // constraint_set3_flag + nal_bs_read(&bs, 4); // reserved + sps_info.level_idc = nal_bs_read(&bs, 8); + sps_info.sps_id = nal_bs_read_ue(&bs); + + if (sps_info.profile_idc == 100 || + sps_info.profile_idc == 110 || + sps_info.profile_idc == 122 || + sps_info.profile_idc == 244 || + sps_info.profile_idc == 44 || + sps_info.profile_idc == 83 || + sps_info.profile_idc == 86) + { + sps_info.chroma_format_idc = nal_bs_read_ue(&bs); + if (sps_info.chroma_format_idc == 3) + sps_info.separate_colour_plane_flag = nal_bs_read(&bs, 1); + sps_info.bit_depth_luma_minus8 = nal_bs_read_ue(&bs); + sps_info.bit_depth_chroma_minus8 = nal_bs_read_ue(&bs); + sps_info.qpprime_y_zero_transform_bypass_flag = nal_bs_read(&bs, 1); + + sps_info.seq_scaling_matrix_present_flag = nal_bs_read (&bs, 1); + if (sps_info.seq_scaling_matrix_present_flag) + { + /* TODO: unfinished */ + } + } + sps_info.log2_max_frame_num_minus4 = nal_bs_read_ue(&bs); + if (sps_info.log2_max_frame_num_minus4 > 12) + { // must be between 0 and 12 + return; + } + sps_info.pic_order_cnt_type = nal_bs_read_ue(&bs); + if (sps_info.pic_order_cnt_type == 0) + { + sps_info.log2_max_pic_order_cnt_lsb_minus4 = nal_bs_read_ue(&bs); + } + else if (sps_info.pic_order_cnt_type == 1) + { // TODO: unfinished + /* + delta_pic_order_always_zero_flag = gst_nal_bs_read (bs, 1); + offset_for_non_ref_pic = gst_nal_bs_read_se (bs); + offset_for_top_to_bottom_field = gst_nal_bs_read_se (bs); + + num_ref_frames_in_pic_order_cnt_cycle = gst_nal_bs_read_ue (bs); + for( i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ ) + offset_for_ref_frame[i] = gst_nal_bs_read_se (bs); + */ + } + + sps_info.max_num_ref_frames = nal_bs_read_ue(&bs); + sps_info.gaps_in_frame_num_value_allowed_flag = nal_bs_read(&bs, 1); + sps_info.pic_width_in_mbs_minus1 = nal_bs_read_ue(&bs); + sps_info.pic_height_in_map_units_minus1 = nal_bs_read_ue(&bs); + + sps_info.frame_mbs_only_flag = nal_bs_read(&bs, 1); + if (!sps_info.frame_mbs_only_flag) + sps_info.mb_adaptive_frame_field_flag = nal_bs_read(&bs, 1); + + sps_info.direct_8x8_inference_flag = nal_bs_read(&bs, 1); + + sps_info.frame_cropping_flag = nal_bs_read(&bs, 1); + if (sps_info.frame_cropping_flag) + { + sps_info.frame_crop_left_offset = nal_bs_read_ue(&bs); + sps_info.frame_crop_right_offset = nal_bs_read_ue(&bs); + sps_info.frame_crop_top_offset = nal_bs_read_ue(&bs); + sps_info.frame_crop_bottom_offset = nal_bs_read_ue(&bs); + } + + *interlaced = !sps_info.frame_mbs_only_flag; + *max_ref_frames = sps_info.max_num_ref_frames; +} diff --git a/src/utils/BitstreamConverter.h b/src/utils/BitstreamConverter.h new file mode 100644 index 0000000000..9ab5ac3dea --- /dev/null +++ b/src/utils/BitstreamConverter.h @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2010-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef _BITSTREAMCONVERTER_H_ +#define _BITSTREAMCONVERTER_H_ + +#include <stdint.h> + +extern "C" { +#include "libavutil/avutil.h" +#include "libavformat/avformat.h" +#include "libavfilter/avfilter.h" +#include "libavcodec/avcodec.h" +} + +typedef struct { + int writer_le; + uint32_t bit_buf; + int bit_left; + uint8_t *buf, *buf_ptr, *buf_end; + int size_in_bits; +} bits_writer_t; + +typedef struct { + uint8_t *buffer, *start; + int offbits, length, oflow; +} bits_reader_t; + +//////////////////////////////////////////////////////////////////////////////////////////// +// TODO: refactor this so as not to need these ffmpeg routines. +// These are not exposed in ffmpeg's API so we dupe them here. +// AVC helper functions for muxers, +// * Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@smartjog.com> +// This is part of FFmpeg +// * License as published by the Free Software Foundation; either +// * version 2.1 of the License, or (at your option) any later version. +#define BS_RB16(x) \ + ((((const uint8_t*)(x))[0] << 8) | \ + ((const uint8_t*)(x)) [1]) + +#define BS_RB24(x) \ + ((((const uint8_t*)(x))[0] << 16) | \ + (((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[2]) + +#define BS_RB32(x) \ + ((((const uint8_t*)(x))[0] << 24) | \ + (((const uint8_t*)(x))[1] << 16) | \ + (((const uint8_t*)(x))[2] << 8) | \ + ((const uint8_t*)(x))[3]) + +#define BS_WB32(p, d) { \ + ((uint8_t*)(p))[3] = (d); \ + ((uint8_t*)(p))[2] = (d) >> 8; \ + ((uint8_t*)(p))[1] = (d) >> 16; \ + ((uint8_t*)(p))[0] = (d) >> 24; } + +#define BS_WL32(p, d) { \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d) >> 8; \ + ((uint8_t*)(p))[2] = (d) >> 16; \ + ((uint8_t*)(p))[3] = (d) >> 24; } + +typedef struct +{ + const uint8_t *data; + const uint8_t *end; + int head; + uint64_t cache; +} nal_bitstream; + +typedef struct mpeg2_sequence +{ + uint32_t width; + uint32_t height; + float rate; + uint32_t rate_info; + float ratio; + uint32_t ratio_info; +} mpeg2_sequence; + +typedef struct +{ + int profile_idc; + int level_idc; + int sps_id; + + int chroma_format_idc; + int separate_colour_plane_flag; + int bit_depth_luma_minus8; + int bit_depth_chroma_minus8; + int qpprime_y_zero_transform_bypass_flag; + int seq_scaling_matrix_present_flag; + + int log2_max_frame_num_minus4; + int pic_order_cnt_type; + int log2_max_pic_order_cnt_lsb_minus4; + + int max_num_ref_frames; + int gaps_in_frame_num_value_allowed_flag; + int pic_width_in_mbs_minus1; + int pic_height_in_map_units_minus1; + + int frame_mbs_only_flag; + int mb_adaptive_frame_field_flag; + + int direct_8x8_inference_flag; + + int frame_cropping_flag; + int frame_crop_left_offset; + int frame_crop_right_offset; + int frame_crop_top_offset; + int frame_crop_bottom_offset; +} sps_info_struct; + +class CBitstreamParser +{ +public: + CBitstreamParser(); + ~CBitstreamParser(); + + static bool Open(); + static void Close(); + static bool FindIdrSlice(const uint8_t *buf, int buf_size); + +protected: + static const uint8_t* find_start_code(const uint8_t *p, const uint8_t *end, uint32_t *state); +}; + +class CBitstreamConverter +{ +public: + CBitstreamConverter(); + ~CBitstreamConverter(); + + bool Open(enum AVCodecID codec, uint8_t *in_extradata, int in_extrasize, bool to_annexb); + void Close(void); + bool NeedConvert(void) const { return m_convert_bitstream; }; + bool Convert(uint8_t *pData, int iSize); + uint8_t* GetConvertBuffer(void) const; + int GetConvertSize() const; + uint8_t* GetExtraData(void) const; + int GetExtraSize() const; + + static void bits_reader_set( bits_reader_t *br, uint8_t *buf, int len ); + static uint32_t read_bits( bits_reader_t *br, int nbits ); + static void skip_bits( bits_reader_t *br, int nbits ); + static uint32_t get_bits( bits_reader_t *br, int nbits ); + + static void init_bits_writer(bits_writer_t *s, uint8_t *buffer, int buffer_size, int writer_le); + static void write_bits(bits_writer_t *s, int n, unsigned int value); + static void skip_bits( bits_writer_t *s, int n); + static void flush_bits(bits_writer_t *s); + + static void parseh264_sps(const uint8_t *sps, const uint32_t sps_size, bool *interlaced, int32_t *max_ref_frames); + static bool mpeg2_sequence_header(const uint8_t *data, const uint32_t size, mpeg2_sequence *sequence); + +protected: + static const int avc_parse_nal_units(AVIOContext *pb, const uint8_t *buf_in, int size); + static const int avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size); + const int isom_write_avcc(AVIOContext *pb, const uint8_t *data, int len); + // bitstream to bytestream (Annex B) conversion support. + bool BitstreamConvertInit(void *in_extradata, int in_extrasize); + bool BitstreamConvert(uint8_t* pData, int iSize, uint8_t **poutbuf, int *poutbuf_size); + static void BitstreamAllocAndCopy(uint8_t **poutbuf, int *poutbuf_size, + const uint8_t *sps_pps, uint32_t sps_pps_size, const uint8_t *in, uint32_t in_size); + + typedef struct omx_bitstream_ctx { + uint8_t length_size; + uint8_t first_idr; + uint8_t idr_sps_pps_seen; + uint8_t *sps_pps_data; + uint32_t size; + } omx_bitstream_ctx; + + uint8_t *m_convertBuffer; + int m_convertSize; + uint8_t *m_inputBuffer; + int m_inputSize; + + uint32_t m_sps_pps_size; + omx_bitstream_ctx m_sps_pps_context; + bool m_convert_bitstream; + bool m_to_annexb; + + uint8_t *m_extradata; + int m_extrasize; + bool m_convert_3byteTo4byteNALSize; + bool m_convert_bytestream; + AVCodecID m_codec; +}; + +#endif diff --git a/src/utils/BitstreamStats.cpp b/src/utils/BitstreamStats.cpp new file mode 100644 index 0000000000..af325e91b1 --- /dev/null +++ b/src/utils/BitstreamStats.cpp @@ -0,0 +1,85 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "BitstreamStats.h" +#include "utils/TimeUtils.h" + +int64_t BitstreamStats::m_tmFreq; + +BitstreamStats::BitstreamStats(unsigned int nEstimatedBitrate) +{ + m_dBitrate = 0.0; + m_dMaxBitrate = 0.0; + m_dMinBitrate = -1.0; + + m_nBitCount = 0; + m_nEstimatedBitrate = nEstimatedBitrate; + m_tmStart = 0LL; + + if (m_tmFreq == 0LL) + m_tmFreq = CurrentHostFrequency(); +} + +BitstreamStats::~BitstreamStats() +{ +} + +void BitstreamStats::AddSampleBytes(unsigned int nBytes) +{ + AddSampleBits(nBytes*8); +} + +void BitstreamStats::AddSampleBits(unsigned int nBits) +{ + m_nBitCount += nBits; + if (m_nBitCount >= m_nEstimatedBitrate) + CalculateBitrate(); +} + +void BitstreamStats::Start() +{ + m_nBitCount = 0; + m_tmStart = CurrentHostCounter(); +} + +void BitstreamStats::CalculateBitrate() +{ + int64_t tmNow; + tmNow = CurrentHostCounter(); + + double elapsed = (double)(tmNow - m_tmStart) / (double)m_tmFreq; + // only update once every 2 seconds + if (elapsed >= 2) + { + m_dBitrate = (double)m_nBitCount / elapsed; + + if (m_dBitrate > m_dMaxBitrate) + m_dMaxBitrate = m_dBitrate; + + if (m_dBitrate < m_dMinBitrate || m_dMinBitrate == -1) + m_dMinBitrate = m_dBitrate; + + Start(); + } +} + + + + diff --git a/src/utils/BitstreamStats.h b/src/utils/BitstreamStats.h new file mode 100644 index 0000000000..1f2e5cd72a --- /dev/null +++ b/src/utils/BitstreamStats.h @@ -0,0 +1,61 @@ +#ifndef BITSTREAM_STATS__H__ +#define BITSTREAM_STATS__H__ + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#ifdef TARGET_POSIX +#include "linux/PlatformDefs.h" +#else +#include <stdint.h> +#endif + +class BitstreamStats +{ +public: + // in order not to cause a performance hit, we should only check the clock when + // we reach m_nEstimatedBitrate bits. + // if this value is 1, we will calculate bitrate on every sample. + BitstreamStats(unsigned int nEstimatedBitrate=(10240*8) /*10Kbit*/); + virtual ~BitstreamStats(); + + void AddSampleBytes(unsigned int nBytes); + void AddSampleBits(unsigned int nBits); + + inline double GetBitrate() const { return m_dBitrate; } + inline double GetMaxBitrate() const { return m_dMaxBitrate; } + inline double GetMinBitrate() const { return m_dMinBitrate; } + + void Start(); + void CalculateBitrate(); + +private: + double m_dBitrate; + double m_dMaxBitrate; + double m_dMinBitrate; + unsigned int m_nBitCount; + unsigned int m_nEstimatedBitrate; // when we reach this amount of bits we check current bitrate. + int64_t m_tmStart; + static int64_t m_tmFreq; +}; + +#endif + diff --git a/src/utils/BooleanLogic.cpp b/src/utils/BooleanLogic.cpp new file mode 100644 index 0000000000..867db67a7d --- /dev/null +++ b/src/utils/BooleanLogic.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "BooleanLogic.h" +#include "utils/log.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" + +bool CBooleanLogicValue::Deserialize(const TiXmlNode *node) +{ + if (node == NULL) + return false; + + const TiXmlElement *elem = node->ToElement(); + if (elem == NULL) + return false; + + if (node->FirstChild() != NULL && node->FirstChild()->Type() == TiXmlNode::TINYXML_TEXT) + m_value = node->FirstChild()->ValueStr(); + + m_negated = false; + const char *strNegated = elem->Attribute("negated"); + if (strNegated != NULL) + { + if (StringUtils::EqualsNoCase(strNegated, "true")) + m_negated = true; + else if (!StringUtils::EqualsNoCase(strNegated, "false")) + { + CLog::Log(LOGDEBUG, "CBooleanLogicValue: invalid negated value \"%s\"", strNegated); + return false; + } + } + + return true; +} + +CBooleanLogicOperation::~CBooleanLogicOperation() +{ + m_operations.clear(); + m_values.clear(); +} + +bool CBooleanLogicOperation::Deserialize(const TiXmlNode *node) +{ + if (node == NULL) + return false; + + // check if this is a simple operation with a single value directly expressed + // in the parent tag + if (node->FirstChild() == NULL || node->FirstChild()->Type() == TiXmlNode::TINYXML_TEXT) + { + CBooleanLogicValuePtr value = CBooleanLogicValuePtr(newValue()); + if (value == NULL || !value->Deserialize(node)) + { + CLog::Log(LOGDEBUG, "CBooleanLogicOperation: failed to deserialize implicit boolean value definition"); + return false; + } + + m_values.push_back(value); + return true; + } + + const TiXmlNode *operationNode = node->FirstChild(); + while (operationNode != NULL) + { + std::string tag = operationNode->ValueStr(); + if (StringUtils::EqualsNoCase(tag, "and") || StringUtils::EqualsNoCase(tag, "or")) + { + CBooleanLogicOperationPtr operation = CBooleanLogicOperationPtr(newOperation()); + if (operation == NULL) + return false; + + operation->SetOperation(StringUtils::EqualsNoCase(tag, "and") ? BooleanLogicOperationAnd : BooleanLogicOperationOr); + if (!operation->Deserialize(operationNode)) + { + CLog::Log(LOGDEBUG, "CBooleanLogicOperation: failed to deserialize <%s> definition", tag.c_str()); + return false; + } + + m_operations.push_back(operation); + } + else + { + CBooleanLogicValuePtr value = CBooleanLogicValuePtr(newValue()); + if (value == NULL) + return false; + + if (StringUtils::EqualsNoCase(tag, value->GetTag())) + { + if (!value->Deserialize(operationNode)) + { + CLog::Log(LOGDEBUG, "CBooleanLogicOperation: failed to deserialize <%s> definition", tag.c_str()); + return false; + } + + m_values.push_back(value); + } + else if (operationNode->Type() == TiXmlNode::TINYXML_ELEMENT) + CLog::Log(LOGDEBUG, "CBooleanLogicOperation: unknown <%s> definition encountered", tag.c_str()); + } + + operationNode = operationNode->NextSibling(); + } + + return true; +} + +bool CBooleanLogic::Deserialize(const TiXmlNode *node) +{ + if (node == NULL) + return false; + + if (m_operation == NULL) + { + m_operation = CBooleanLogicOperationPtr(new CBooleanLogicOperation()); + + if (m_operation == NULL) + return false; + } + + return m_operation->Deserialize(node); +} diff --git a/src/utils/BooleanLogic.h b/src/utils/BooleanLogic.h new file mode 100644 index 0000000000..e1af268996 --- /dev/null +++ b/src/utils/BooleanLogic.h @@ -0,0 +1,101 @@ +#pragma once +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <vector> + +#include <boost/shared_ptr.hpp> + +#include "utils/IXmlDeserializable.h" + +typedef enum { + BooleanLogicOperationOr = 0, + BooleanLogicOperationAnd +} BooleanLogicOperation; + +class CBooleanLogicValue : public IXmlDeserializable +{ +public: + CBooleanLogicValue(const std::string &value = "", bool negated = false) + : m_value(value), m_negated(negated) + { } + virtual ~CBooleanLogicValue() { } + + virtual bool Deserialize(const TiXmlNode *node); + + virtual const std::string& GetValue() const { return m_value; } + virtual bool IsNegated() const { return m_negated; } + virtual const char* GetTag() const { return "value"; } + + virtual void SetValue(const std::string &value) { m_value = value; } + virtual void SetNegated(bool negated) { m_negated = negated; } + +protected: + std::string m_value; + bool m_negated; +}; + +typedef boost::shared_ptr<CBooleanLogicValue> CBooleanLogicValuePtr; +typedef std::vector<CBooleanLogicValuePtr> CBooleanLogicValues; + +class CBooleanLogicOperation; +typedef boost::shared_ptr<CBooleanLogicOperation> CBooleanLogicOperationPtr; +typedef std::vector<CBooleanLogicOperationPtr> CBooleanLogicOperations; + +class CBooleanLogicOperation : public IXmlDeserializable +{ +public: + CBooleanLogicOperation(BooleanLogicOperation op = BooleanLogicOperationAnd) + : m_operation(op) + { } + virtual ~CBooleanLogicOperation(); + + virtual bool Deserialize(const TiXmlNode *node); + + virtual BooleanLogicOperation GetOperation() const { return m_operation; } + virtual const CBooleanLogicOperations& GetOperations() const { return m_operations; } + virtual const CBooleanLogicValues& GetValues() const { return m_values; } + + virtual void SetOperation(BooleanLogicOperation op) { m_operation = op; } + +protected: + virtual CBooleanLogicOperation* newOperation() { return new CBooleanLogicOperation(); } + virtual CBooleanLogicValue* newValue() { return new CBooleanLogicValue(); } + + BooleanLogicOperation m_operation; + CBooleanLogicOperations m_operations; + CBooleanLogicValues m_values; +}; + +class CBooleanLogic : public IXmlDeserializable +{ +public: + CBooleanLogic() { } + virtual ~CBooleanLogic() { } + + virtual bool Deserialize(const TiXmlNode *node); + + virtual const CBooleanLogicOperationPtr& Get() const { return m_operation; } + virtual CBooleanLogicOperationPtr Get() { return m_operation; } + +protected: + CBooleanLogicOperationPtr m_operation; +}; diff --git a/src/utils/CPUInfo.cpp b/src/utils/CPUInfo.cpp new file mode 100644 index 0000000000..71aa745481 --- /dev/null +++ b/src/utils/CPUInfo.cpp @@ -0,0 +1,957 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "CPUInfo.h" +#include "Temperature.h" +#include <string> +#include <string.h> + +#if defined(TARGET_DARWIN) +#include <sys/types.h> +#include <sys/sysctl.h> +#if defined(__ppc__) || defined (TARGET_DARWIN_IOS) +#include <mach-o/arch.h> +#endif // defined(__ppc__) || defined (TARGET_DARWIN_IOS) +#ifdef TARGET_DARWIN_OSX +#include "osx/smc.h" +#endif +#endif + +#if defined(TARGET_FREEBSD) +#include <sys/types.h> +#include <sys/sysctl.h> +#include <sys/resource.h> +#endif + +#if defined(TARGET_LINUX) && defined(__ARM_NEON__) && !defined(TARGET_ANDROID) +#include <fcntl.h> +#include <unistd.h> +#include <elf.h> +#include <linux/auxvec.h> +#include <asm/hwcap.h> +#endif + +#if defined(TARGET_ANDROID) +#include "android/activity/AndroidFeatures.h" +#endif + +#ifdef TARGET_WINDOWS +#include "utils/CharsetConverter.h" +#include <algorithm> +#include <intrin.h> +#include <Pdh.h> +#include <PdhMsg.h> +#pragma comment(lib, "Pdh.lib") + +// Defines to help with calls to CPUID +#define CPUID_INFOTYPE_STANDARD 0x00000001 +#define CPUID_INFOTYPE_EXTENDED 0x80000001 + +// Standard Features +// Bitmasks for the values returned by a call to cpuid with eax=0x00000001 +#define CPUID_00000001_ECX_SSE3 (1<<0) +#define CPUID_00000001_ECX_SSSE3 (1<<9) +#define CPUID_00000001_ECX_SSE4 (1<<19) +#define CPUID_00000001_ECX_SSE42 (1<<20) + +#define CPUID_00000001_EDX_MMX (1<<23) +#define CPUID_00000001_EDX_SSE (1<<25) +#define CPUID_00000001_EDX_SSE2 (1<<26) + +// Extended Features +// Bitmasks for the values returned by a call to cpuid with eax=0x80000001 +#define CPUID_80000001_EDX_MMX2 (1<<22) +#define CPUID_80000001_EDX_MMX (1<<23) +#define CPUID_80000001_EDX_3DNOWEXT (1<<30) +#define CPUID_80000001_EDX_3DNOW (1<<31) + + +// Help with the __cpuid intrinsic of MSVC +#define CPUINFO_EAX 0 +#define CPUINFO_EBX 1 +#define CPUINFO_ECX 2 +#define CPUINFO_EDX 3 + +#endif + +#include "log.h" +#include "settings/AdvancedSettings.h" +#include "utils/StringUtils.h" + +using namespace std; + +// In milliseconds +#define MINIMUM_TIME_BETWEEN_READS 500 + +CCPUInfo::CCPUInfo(void) +{ +#ifdef TARGET_POSIX + m_fProcStat = m_fProcTemperature = m_fCPUFreq = NULL; + m_cpuInfoForFreq = false; +#elif defined(TARGET_WINDOWS) + m_cpuQueryFreq = NULL; + m_cpuQueryLoad = NULL; +#endif + m_lastUsedPercentage = 0; + m_cpuFeatures = 0; + +#if defined(TARGET_DARWIN) + size_t len = 4; + std::string cpuVendor; + + // The number of cores. + if (sysctlbyname("hw.activecpu", &m_cpuCount, &len, NULL, 0) == -1) + m_cpuCount = 1; + + // The model. +#if defined(__ppc__) || defined (TARGET_DARWIN_IOS) + const NXArchInfo *info = NXGetLocalArchInfo(); + if (info != NULL) + m_cpuModel = info->description; +#else + // NXGetLocalArchInfo is ugly for intel so keep using this method + char buffer[512]; + len = 512; + if (sysctlbyname("machdep.cpu.brand_string", &buffer, &len, NULL, 0) == 0) + m_cpuModel = buffer; + + // The CPU vendor + len = 512; + if (sysctlbyname("machdep.cpu.vendor", &buffer, &len, NULL, 0) == 0) + cpuVendor = buffer; + +#endif + // Go through each core. + for (int i=0; i<m_cpuCount; i++) + { + CoreInfo core; + core.m_id = i; + core.m_strModel = m_cpuModel; + core.m_strVendor = cpuVendor; + m_cores[core.m_id] = core; + } + +#elif defined(TARGET_WINDOWS) + HKEY hKeyCpuRoot; + + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor", 0, KEY_READ, &hKeyCpuRoot) == ERROR_SUCCESS) + { + DWORD num = 0; + std::vector<CoreInfo> cpuCores; + wchar_t subKeyName[200]; // more than enough + DWORD subKeyNameLen = sizeof(subKeyName) / sizeof(wchar_t); + while (RegEnumKeyExW(hKeyCpuRoot, num++, subKeyName, &subKeyNameLen, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) + { + HKEY hCpuKey; + if (RegOpenKeyExW(hKeyCpuRoot, subKeyName, 0, KEY_QUERY_VALUE, &hCpuKey) == ERROR_SUCCESS) + { + CoreInfo cpuCore; + if (swscanf_s(subKeyName, L"%i", &cpuCore.m_id) != 1) + cpuCore.m_id = num - 1; + wchar_t buf[300]; // more than enough + DWORD bufSize = sizeof(buf); + DWORD valType; + if (RegQueryValueExW(hCpuKey, L"ProcessorNameString", NULL, &valType, (LPBYTE)buf, &bufSize) == ERROR_SUCCESS && + valType == REG_SZ) + { + g_charsetConverter.wToUTF8(std::wstring(buf, bufSize / sizeof(wchar_t)), cpuCore.m_strModel); + cpuCore.m_strModel = cpuCore.m_strModel.substr(0, cpuCore.m_strModel.find(char(0))); // remove extra null terminations + StringUtils::RemoveDuplicatedSpacesAndTabs(cpuCore.m_strModel); + StringUtils::Trim(cpuCore.m_strModel); + } + bufSize = sizeof(buf); + if (RegQueryValueExW(hCpuKey, L"VendorIdentifier", NULL, &valType, (LPBYTE)buf, &bufSize) == ERROR_SUCCESS && + valType == REG_SZ) + { + g_charsetConverter.wToUTF8(std::wstring(buf, bufSize / sizeof(wchar_t)), cpuCore.m_strVendor); + cpuCore.m_strVendor = cpuCore.m_strVendor.substr(0, cpuCore.m_strVendor.find(char(0))); // remove extra null terminations + } + DWORD mhzVal; + bufSize = sizeof(mhzVal); + if (RegQueryValueExW(hCpuKey, L"~MHz", NULL, &valType, (LPBYTE)&mhzVal, &bufSize) == ERROR_SUCCESS && + valType == REG_DWORD) + cpuCore.m_fSpeed = double(mhzVal); + + RegCloseKey(hCpuKey); + + if (cpuCore.m_strModel.empty()) + cpuCore.m_strModel = "Unknown"; + cpuCores.push_back(cpuCore); + } + subKeyNameLen = sizeof(subKeyName) / sizeof(wchar_t); // restore length value + } + RegCloseKey(hKeyCpuRoot); + std::sort(cpuCores.begin(), cpuCores.end()); // sort cores by id + for (size_t i = 0; i < cpuCores.size(); i++) + m_cores[i] = cpuCores[i]; // add in sorted order + } + + if (!m_cores.empty()) + m_cpuModel = m_cores.begin()->second.m_strModel; + else + m_cpuModel = "Unknown"; + + SYSTEM_INFO siSysInfo; + GetNativeSystemInfo(&siSysInfo); + m_cpuCount = siSysInfo.dwNumberOfProcessors; + + if (PdhOpenQueryW(NULL, 0, &m_cpuQueryFreq) == ERROR_SUCCESS) + { + if (PdhAddEnglishCounterW(m_cpuQueryFreq, L"\\Processor Information(0,0)\\Processor Frequency", 0, &m_cpuFreqCounter) != ERROR_SUCCESS) + m_cpuFreqCounter = NULL; + } + else + m_cpuQueryFreq = NULL; + + if (PdhOpenQueryW(NULL, 0, &m_cpuQueryLoad) == ERROR_SUCCESS) + { + for (size_t i = 0; i < m_cores.size(); i++) + { + if (PdhAddEnglishCounterW(m_cpuQueryLoad, StringUtils::Format(L"\\Processor(%d)\\%% Idle Time", int(i)).c_str(), 0, &m_cores[i].m_coreCounter) != ERROR_SUCCESS) + m_cores[i].m_coreCounter = NULL; + } + } + else + m_cpuQueryLoad = NULL; +#elif defined(TARGET_FREEBSD) + size_t len; + int i; + char cpumodel[512]; + + len = sizeof(m_cpuCount); + if (sysctlbyname("hw.ncpu", &m_cpuCount, &len, NULL, 0) != 0) + m_cpuCount = 1; + + len = sizeof(cpumodel); + if (sysctlbyname("hw.model", &cpumodel, &len, NULL, 0) != 0) + (void)strncpy(cpumodel, "Unknown", 8); + m_cpuModel = cpumodel; + + for (i = 0; i < m_cpuCount; i++) + { + CoreInfo core; + core.m_id = i; + core.m_strModel = m_cpuModel; + m_cores[core.m_id] = core; + } +#else + m_fProcStat = fopen("/proc/stat", "r"); + m_fProcTemperature = fopen("/proc/acpi/thermal_zone/THM0/temperature", "r"); + if (m_fProcTemperature == NULL) + m_fProcTemperature = fopen("/proc/acpi/thermal_zone/THRM/temperature", "r"); + if (m_fProcTemperature == NULL) + m_fProcTemperature = fopen("/proc/acpi/thermal_zone/THR0/temperature", "r"); + if (m_fProcTemperature == NULL) + m_fProcTemperature = fopen("/proc/acpi/thermal_zone/TZ0/temperature", "r"); + // read from the new location of the temperature data on new kernels, 2.6.39, 3.0 etc + if (m_fProcTemperature == NULL) + m_fProcTemperature = fopen("/sys/class/hwmon/hwmon0/temp1_input", "r"); + if (m_fProcTemperature == NULL) + m_fProcTemperature = fopen("/sys/class/thermal/thermal_zone0/temp", "r"); // On Raspberry PIs + + m_fCPUFreq = fopen ("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", "r"); + if (!m_fCPUFreq) + { + m_cpuInfoForFreq = true; + m_fCPUFreq = fopen("/proc/cpuinfo", "r"); + } + else + m_cpuInfoForFreq = false; + + + FILE* fCPUInfo = fopen("/proc/cpuinfo", "r"); + m_cpuCount = 0; + if (fCPUInfo) + { + char buffer[512]; + + int nCurrId = 0; + while (fgets(buffer, sizeof(buffer), fCPUInfo)) + { + if (strncmp(buffer, "processor", strlen("processor"))==0) + { + char *needle = strstr(buffer, ":"); + if (needle) + { + CoreInfo core; + core.m_id = atoi(needle+2); + nCurrId = core.m_id; + m_cores[core.m_id] = core; + } + m_cpuCount++; + } + else if (strncmp(buffer, "vendor_id", strlen("vendor_id"))==0) + { + char *needle = strstr(buffer, ":"); + if (needle && strlen(needle)>3) + { + needle+=2; + m_cores[nCurrId].m_strVendor = needle; + StringUtils::Trim(m_cores[nCurrId].m_strVendor); + } + } + else if (strncmp(buffer, "Processor", strlen("Processor"))==0) + { + char *needle = strstr(buffer, ":"); + if (needle && strlen(needle)>3) + { + needle+=2; + m_cpuModel = needle; + m_cores[nCurrId].m_strModel = m_cpuModel; + StringUtils::Trim(m_cores[nCurrId].m_strModel); + } + } + else if (strncmp(buffer, "BogoMIPS", strlen("BogoMIPS"))==0) + { + char *needle = strstr(buffer, ":"); + if (needle && strlen(needle)>3) + { + needle+=2; + m_cpuBogoMips = needle; + m_cores[nCurrId].m_strBogoMips = m_cpuBogoMips; + StringUtils::Trim(m_cores[nCurrId].m_strBogoMips); + } + } + else if (strncmp(buffer, "Hardware", strlen("Hardware"))==0) + { + char *needle = strstr(buffer, ":"); + if (needle && strlen(needle)>3) + { + needle+=2; + m_cpuHardware = needle; + m_cores[nCurrId].m_strHardware = m_cpuHardware; + StringUtils::Trim(m_cores[nCurrId].m_strHardware); + } + } + else if (strncmp(buffer, "Revision", strlen("Revision"))==0) + { + char *needle = strstr(buffer, ":"); + if (needle && strlen(needle)>3) + { + needle+=2; + m_cpuRevision = needle; + m_cores[nCurrId].m_strRevision = m_cpuRevision; + StringUtils::Trim(m_cores[nCurrId].m_strRevision); + } + } + else if (strncmp(buffer, "Serial", strlen("Serial"))==0) + { + char *needle = strstr(buffer, ":"); + if (needle && strlen(needle)>3) + { + needle+=2; + m_cpuSerial = needle; + m_cores[nCurrId].m_strSerial = m_cpuSerial; + StringUtils::Trim(m_cores[nCurrId].m_strSerial); + } + } + else if (strncmp(buffer, "model name", strlen("model name"))==0) + { + char *needle = strstr(buffer, ":"); + if (needle && strlen(needle)>3) + { + needle+=2; + m_cpuModel = needle; + m_cores[nCurrId].m_strModel = m_cpuModel; + StringUtils::Trim(m_cores[nCurrId].m_strModel); + } + } + else if (strncmp(buffer, "flags", 5) == 0) + { + char* needle = strchr(buffer, ':'); + if (needle) + { + char* tok = NULL, + * save; + needle++; + tok = strtok_r(needle, " ", &save); + while (tok) + { + if (0 == strcmp(tok, "mmx")) + m_cpuFeatures |= CPU_FEATURE_MMX; + else if (0 == strcmp(tok, "mmxext")) + m_cpuFeatures |= CPU_FEATURE_MMX2; + else if (0 == strcmp(tok, "sse")) + m_cpuFeatures |= CPU_FEATURE_SSE; + else if (0 == strcmp(tok, "sse2")) + m_cpuFeatures |= CPU_FEATURE_SSE2; + else if (0 == strcmp(tok, "sse3")) + m_cpuFeatures |= CPU_FEATURE_SSE3; + else if (0 == strcmp(tok, "ssse3")) + m_cpuFeatures |= CPU_FEATURE_SSSE3; + else if (0 == strcmp(tok, "sse4_1")) + m_cpuFeatures |= CPU_FEATURE_SSE4; + else if (0 == strcmp(tok, "sse4_2")) + m_cpuFeatures |= CPU_FEATURE_SSE42; + else if (0 == strcmp(tok, "3dnow")) + m_cpuFeatures |= CPU_FEATURE_3DNOW; + else if (0 == strcmp(tok, "3dnowext")) + m_cpuFeatures |= CPU_FEATURE_3DNOWEXT; + tok = strtok_r(NULL, " ", &save); + } + } + } + } + fclose(fCPUInfo); + } + else + { + m_cpuCount = 1; + m_cpuModel = "Unknown"; + } + +#endif + StringUtils::Replace(m_cpuModel, '\r', ' '); + StringUtils::Replace(m_cpuModel, '\n', ' '); + StringUtils::RemoveDuplicatedSpacesAndTabs(m_cpuModel); + StringUtils::Trim(m_cpuModel); + + /* Set some default for empty string variables */ + if (m_cpuBogoMips.empty()) + m_cpuBogoMips = "N/A"; + if (m_cpuHardware.empty()) + m_cpuHardware = "N/A"; + if (m_cpuRevision.empty()) + m_cpuRevision = "N/A"; + if (m_cpuSerial.empty()) + m_cpuSerial = "N/A"; + + readProcStat(m_userTicks, m_niceTicks, m_systemTicks, m_idleTicks, m_ioTicks); + m_nextUsedReadTime.Set(MINIMUM_TIME_BETWEEN_READS); + + ReadCPUFeatures(); + + // Set MMX2 when SSE is present as SSE is a superset of MMX2 and Intel doesn't set the MMX2 cap + if (m_cpuFeatures & CPU_FEATURE_SSE) + m_cpuFeatures |= CPU_FEATURE_MMX2; + + if (HasNeon()) + m_cpuFeatures |= CPU_FEATURE_NEON; + +} + +CCPUInfo::~CCPUInfo() +{ +#ifdef TARGET_POSIX + if (m_fProcStat != NULL) + fclose(m_fProcStat); + + if (m_fProcTemperature != NULL) + fclose(m_fProcTemperature); + + if (m_fCPUFreq != NULL) + fclose(m_fCPUFreq); +#elif defined(TARGET_WINDOWS) + if (m_cpuQueryFreq) + PdhCloseQuery(m_cpuQueryFreq); + + if (m_cpuQueryLoad) + PdhCloseQuery(m_cpuQueryLoad); +#endif +} + +int CCPUInfo::getUsedPercentage() +{ + if (!m_nextUsedReadTime.IsTimePast()) + return m_lastUsedPercentage; + + unsigned long long userTicks; + unsigned long long niceTicks; + unsigned long long systemTicks; + unsigned long long idleTicks; + unsigned long long ioTicks; + + if (!readProcStat(userTicks, niceTicks, systemTicks, idleTicks, ioTicks)) + return m_lastUsedPercentage; + + userTicks -= m_userTicks; + niceTicks -= m_niceTicks; + systemTicks -= m_systemTicks; + idleTicks -= m_idleTicks; + ioTicks -= m_ioTicks; + + if(userTicks + niceTicks + systemTicks + idleTicks + ioTicks == 0) + return m_lastUsedPercentage; + int result = (int) (double(userTicks + niceTicks + systemTicks) * 100.0 / double(userTicks + niceTicks + systemTicks + idleTicks + ioTicks) + 0.5); + + m_userTicks += userTicks; + m_niceTicks += niceTicks; + m_systemTicks += systemTicks; + m_idleTicks += idleTicks; + m_ioTicks += ioTicks; + + m_lastUsedPercentage = result; + m_nextUsedReadTime.Set(MINIMUM_TIME_BETWEEN_READS); + + return result; +} + +float CCPUInfo::getCPUFrequency() +{ + // Get CPU frequency, scaled to MHz. +#if defined(TARGET_DARWIN) + long long hz = 0; + size_t len = sizeof(hz); + if (sysctlbyname("hw.cpufrequency", &hz, &len, NULL, 0) == -1) + return 0.f; + return hz / 1000000.0; +#elif defined TARGET_WINDOWS + if (m_cpuFreqCounter && PdhCollectQueryData(m_cpuQueryFreq) == ERROR_SUCCESS) + { + PDH_RAW_COUNTER cnt; + DWORD cntType; + if (PdhGetRawCounterValue(m_cpuFreqCounter, &cntType, &cnt) == ERROR_SUCCESS && + (cnt.CStatus == PDH_CSTATUS_VALID_DATA || cnt.CStatus == PDH_CSTATUS_NEW_DATA)) + { + return float(cnt.FirstValue); + } + } + + if (!m_cores.empty()) + return float(m_cores.begin()->second.m_fSpeed); + else + return 0.f; +#elif defined(TARGET_FREEBSD) + int hz = 0; + size_t len = sizeof(hz); + if (sysctlbyname("dev.cpu.0.freq", &hz, &len, NULL, 0) != 0) + hz = 0; + return (float)hz; +#else + int value = 0; + if (m_fCPUFreq && !m_cpuInfoForFreq) + { + rewind(m_fCPUFreq); + fflush(m_fCPUFreq); + fscanf(m_fCPUFreq, "%d", &value); + value /= 1000.0; + } + if (m_fCPUFreq && m_cpuInfoForFreq) + { + rewind(m_fCPUFreq); + fflush(m_fCPUFreq); + float mhz, avg=0.0; + int n, cpus=0; + while(EOF!=(n=fscanf(m_fCPUFreq," MHz : %f ", &mhz))) + { + if (n>0) { + cpus++; + avg += mhz; + } + fscanf(m_fCPUFreq,"%*s"); + } + + if (cpus > 0) + value = avg/cpus; + } + return value; +#endif +} + +bool CCPUInfo::getTemperature(CTemperature& temperature) +{ + int value = 0; + char scale = 0; + +#ifdef TARGET_POSIX +#if defined(TARGET_DARWIN_OSX) + value = SMCGetTemperature(SMC_KEY_CPU_TEMP); + scale = 'c'; +#else + int ret = 0; + FILE *p = NULL; + CStdString cmd = g_advancedSettings.m_cpuTempCmd; + + temperature.SetState(CTemperature::invalid); + + if (cmd.empty() && m_fProcTemperature == NULL) + return false; + + if (!cmd.empty()) + { + p = popen (cmd.c_str(), "r"); + if (p) + { + ret = fscanf(p, "%d %c", &value, &scale); + pclose(p); + } + } + else + { + // procfs is deprecated in the linux kernel, we should move away from + // using it for temperature data. It doesn't seem that sysfs has a + // general enough interface to bother implementing ATM. + + rewind(m_fProcTemperature); + fflush(m_fProcTemperature); + ret = fscanf(m_fProcTemperature, "temperature: %d %c", &value, &scale); + + // read from the temperature file of the new kernels + if (!ret) + { + ret = fscanf(m_fProcTemperature, "%d", &value); + value = value / 1000; + scale = 'c'; + ret++; + } + } + + if (ret != 2) + return false; +#endif +#endif // TARGET_POSIX + + if (scale == 'C' || scale == 'c') + temperature = CTemperature::CreateFromCelsius(value); + else if (scale == 'F' || scale == 'f') + temperature = CTemperature::CreateFromFahrenheit(value); + else + return false; + + return true; +} + +bool CCPUInfo::HasCoreId(int nCoreId) const +{ + map<int, CoreInfo>::const_iterator iter = m_cores.find(nCoreId); + if (iter != m_cores.end()) + return true; + return false; +} + +const CoreInfo &CCPUInfo::GetCoreInfo(int nCoreId) +{ + map<int, CoreInfo>::iterator iter = m_cores.find(nCoreId); + if (iter != m_cores.end()) + return iter->second; + + static CoreInfo dummy; + return dummy; +} + +bool CCPUInfo::readProcStat(unsigned long long& user, unsigned long long& nice, + unsigned long long& system, unsigned long long& idle, unsigned long long& io) +{ + +#ifdef TARGET_WINDOWS + FILETIME idleTime; + FILETIME kernelTime; + FILETIME userTime; + if (GetSystemTimes(&idleTime, &kernelTime, &userTime) == 0) + return false; + + idle = (uint64_t(idleTime.dwHighDateTime) << 32) + uint64_t(idleTime.dwLowDateTime); + // returned "kernelTime" includes "idleTime" + system = (uint64_t(kernelTime.dwHighDateTime) << 32) + uint64_t(kernelTime.dwLowDateTime) - idle; + user = (uint64_t(userTime.dwHighDateTime) << 32) + uint64_t(userTime.dwLowDateTime); + nice = 0; + io = 0; + + if (m_cpuFreqCounter && PdhCollectQueryData(m_cpuQueryLoad) == ERROR_SUCCESS) + { + for (std::map<int, CoreInfo>::iterator it = m_cores.begin(); it != m_cores.end(); ++it) + { + CoreInfo& curCore = it->second; // simplify usage + PDH_RAW_COUNTER cnt; + DWORD cntType; + if (curCore.m_coreCounter && PdhGetRawCounterValue(curCore.m_coreCounter, &cntType, &cnt) == ERROR_SUCCESS && + (cnt.CStatus == PDH_CSTATUS_VALID_DATA || cnt.CStatus == PDH_CSTATUS_NEW_DATA)) + { + const LONGLONG coreTotal = cnt.SecondValue, + coreIdle = cnt.FirstValue; + const LONGLONG deltaTotal = coreTotal - curCore.m_total, + deltaIdle = coreIdle - curCore.m_idle; + const double load = (double(deltaTotal - deltaIdle) * 100.0) / double(deltaTotal); + + // win32 has some problems with calculation of load if load close to zero + curCore.m_fPct = (load < 0) ? 0 : load; + if (load >= 0 || deltaTotal > 5 * 10 * 1000 * 1000) // do not update (smooth) values for 5 seconds on negative loads + { + curCore.m_total = coreTotal; + curCore.m_idle = coreIdle; + } + } + else + curCore.m_fPct = double(m_lastUsedPercentage); // use CPU average as fallback + } + } + else + for (std::map<int, CoreInfo>::iterator it = m_cores.begin(); it != m_cores.end(); ++it) + it->second.m_fPct = double(m_lastUsedPercentage); // use CPU average as fallback +#elif defined(TARGET_FREEBSD) + long *cptimes; + size_t len; + int i; + + len = sizeof(long) * 32 * CPUSTATES; + if (sysctlbyname("kern.cp_times", NULL, &len, NULL, 0) != 0) + return false; + cptimes = (long*)malloc(len); + if (cptimes == NULL) + return false; + if (sysctlbyname("kern.cp_times", cptimes, &len, NULL, 0) != 0) + { + free(cptimes); + return false; + } + user = 0; + nice = 0; + system = 0; + idle = 0; + io = 0; + for (i = 0; i < m_cpuCount; i++) + { + long coreUser, coreNice, coreSystem, coreIdle, coreIO; + double total; + + coreUser = cptimes[i * CPUSTATES + CP_USER]; + coreNice = cptimes[i * CPUSTATES + CP_NICE]; + coreSystem = cptimes[i * CPUSTATES + CP_SYS]; + coreIO = cptimes[i * CPUSTATES + CP_INTR]; + coreIdle = cptimes[i * CPUSTATES + CP_IDLE]; + + map<int, CoreInfo>::iterator iter = m_cores.find(i); + if (iter != m_cores.end()) + { + coreUser -= iter->second.m_user; + coreNice -= iter->second.m_nice; + coreSystem -= iter->second.m_system; + coreIdle -= iter->second.m_idle; + coreIO -= iter->second.m_io; + + total = (double)(coreUser + coreNice + coreSystem + coreIdle + coreIO); + if(total != 0.0f) + iter->second.m_fPct = ((double)(coreUser + coreNice + coreSystem) * 100.0) / total; + + iter->second.m_user += coreUser; + iter->second.m_nice += coreNice; + iter->second.m_system += coreSystem; + iter->second.m_idle += coreIdle; + iter->second.m_io += coreIO; + + user += coreUser; + nice += coreNice; + system += coreSystem; + idle += coreIdle; + io += coreIO; + } + } + free(cptimes); +#else + if (m_fProcStat == NULL) + return false; + +#ifdef TARGET_ANDROID + // Just another (vanilla) NDK quirk: + // rewind + fflush do not actually flush the buffers, + // the same initial content is returned rather than re-read + fclose(m_fProcStat); + m_fProcStat = fopen("/proc/stat", "r"); +#else + rewind(m_fProcStat); + fflush(m_fProcStat); +#endif + + char buf[256]; + if (!fgets(buf, sizeof(buf), m_fProcStat)) + return false; + + int num = sscanf(buf, "cpu %llu %llu %llu %llu %llu %*s\n", &user, &nice, &system, &idle, &io); + if (num < 5) + io = 0; + + while (fgets(buf, sizeof(buf), m_fProcStat) && num >= 4) + { + unsigned long long coreUser, coreNice, coreSystem, coreIdle, coreIO; + int nCpu=0; + num = sscanf(buf, "cpu%d %llu %llu %llu %llu %llu %*s\n", &nCpu, &coreUser, &coreNice, &coreSystem, &coreIdle, &coreIO); + if (num < 6) + coreIO = 0; + + map<int, CoreInfo>::iterator iter = m_cores.find(nCpu); + if (num > 4 && iter != m_cores.end()) + { + coreUser -= iter->second.m_user; + coreNice -= iter->second.m_nice; + coreSystem -= iter->second.m_system; + coreIdle -= iter->second.m_idle; + coreIO -= iter->second.m_io; + + double total = (double)(coreUser + coreNice + coreSystem + coreIdle + coreIO); + if(total == 0.0f) + iter->second.m_fPct = 0.0f; + else + iter->second.m_fPct = ((double)(coreUser + coreNice + coreSystem) * 100.0) / total; + + iter->second.m_user += coreUser; + iter->second.m_nice += coreNice; + iter->second.m_system += coreSystem; + iter->second.m_idle += coreIdle; + iter->second.m_io += coreIO; + } + } +#endif + + return true; +} + +std::string CCPUInfo::GetCoresUsageString() const +{ + std::string strCores; + for (std::map<int, CoreInfo>::const_iterator it = m_cores.begin(); it != m_cores.end(); ++it) + { + if (!strCores.empty()) + strCores += ' '; + if (it->second.m_fPct < 10.0) + strCores += StringUtils::Format("CPU%d: %1.1f%%", it->first, it->second.m_fPct); + else + strCores += StringUtils::Format("CPU%d: %3.0f%%", it->first, it->second.m_fPct); + } + + return strCores; +} + +void CCPUInfo::ReadCPUFeatures() +{ +#ifdef TARGET_WINDOWS + + int CPUInfo[4]; // receives EAX, EBX, ECD and EDX in that order + + __cpuid(CPUInfo, 0); + int MaxStdInfoType = CPUInfo[0]; + + if (MaxStdInfoType >= CPUID_INFOTYPE_STANDARD) + { + __cpuid(CPUInfo, CPUID_INFOTYPE_STANDARD); + if (CPUInfo[CPUINFO_EDX] & CPUID_00000001_EDX_MMX) + m_cpuFeatures |= CPU_FEATURE_MMX; + if (CPUInfo[CPUINFO_EDX] & CPUID_00000001_EDX_SSE) + m_cpuFeatures |= CPU_FEATURE_SSE; + if (CPUInfo[CPUINFO_EDX] & CPUID_00000001_EDX_SSE2) + m_cpuFeatures |= CPU_FEATURE_SSE2; + if (CPUInfo[CPUINFO_ECX] & CPUID_00000001_ECX_SSE3) + m_cpuFeatures |= CPU_FEATURE_SSE3; + if (CPUInfo[CPUINFO_ECX] & CPUID_00000001_ECX_SSSE3) + m_cpuFeatures |= CPU_FEATURE_SSSE3; + if (CPUInfo[CPUINFO_ECX] & CPUID_00000001_ECX_SSE4) + m_cpuFeatures |= CPU_FEATURE_SSE4; + if (CPUInfo[CPUINFO_ECX] & CPUID_00000001_ECX_SSE42) + m_cpuFeatures |= CPU_FEATURE_SSE42; + } + + __cpuid(CPUInfo, 0x80000000); + int MaxExtInfoType = CPUInfo[0]; + + if (MaxExtInfoType >= CPUID_INFOTYPE_EXTENDED) + { + __cpuid(CPUInfo, CPUID_INFOTYPE_EXTENDED); + + if (CPUInfo[CPUINFO_EDX] & CPUID_80000001_EDX_MMX) + m_cpuFeatures |= CPU_FEATURE_MMX; + if (CPUInfo[CPUINFO_EDX] & CPUID_80000001_EDX_MMX2) + m_cpuFeatures |= CPU_FEATURE_MMX2; + if (CPUInfo[CPUINFO_EDX] & CPUID_80000001_EDX_3DNOW) + m_cpuFeatures |= CPU_FEATURE_3DNOW; + if (CPUInfo[CPUINFO_EDX] & CPUID_80000001_EDX_3DNOWEXT) + m_cpuFeatures |= CPU_FEATURE_3DNOWEXT; + } + +#elif defined(TARGET_DARWIN) + #if defined(__ppc__) + m_cpuFeatures |= CPU_FEATURE_ALTIVEC; + #elif defined(TARGET_DARWIN_IOS) + #else + size_t len = 512 - 1; // '-1' for trailing space + char buffer[512] ={0}; + + if (sysctlbyname("machdep.cpu.features", &buffer, &len, NULL, 0) == 0) + { + strcat(buffer, " "); + if (strstr(buffer,"MMX ")) + m_cpuFeatures |= CPU_FEATURE_MMX; + if (strstr(buffer,"MMXEXT ")) + m_cpuFeatures |= CPU_FEATURE_MMX2; + if (strstr(buffer,"SSE ")) + m_cpuFeatures |= CPU_FEATURE_SSE; + if (strstr(buffer,"SSE2 ")) + m_cpuFeatures |= CPU_FEATURE_SSE2; + if (strstr(buffer,"SSE3 ")) + m_cpuFeatures |= CPU_FEATURE_SSE3; + if (strstr(buffer,"SSSE3 ")) + m_cpuFeatures |= CPU_FEATURE_SSSE3; + if (strstr(buffer,"SSE4.1 ")) + m_cpuFeatures |= CPU_FEATURE_SSE4; + if (strstr(buffer,"SSE4.2 ")) + m_cpuFeatures |= CPU_FEATURE_SSE42; + if (strstr(buffer,"3DNOW ")) + m_cpuFeatures |= CPU_FEATURE_3DNOW; + if (strstr(buffer,"3DNOWEXT ")) + m_cpuFeatures |= CPU_FEATURE_3DNOWEXT; + } + else + m_cpuFeatures |= CPU_FEATURE_MMX; + #endif +#elif defined(LINUX) +// empty on purpose, the implementation is in the constructor +#elif !defined(__powerpc__) && !defined(__ppc__) && !defined(__arm__) + m_cpuFeatures |= CPU_FEATURE_MMX; +#elif defined(__powerpc__) || defined(__ppc__) + m_cpuFeatures |= CPU_FEATURE_ALTIVEC; +#endif +} + +bool CCPUInfo::HasNeon() +{ + static int has_neon = -1; +#if defined (TARGET_ANDROID) + if (has_neon == -1) + has_neon = (CAndroidFeatures::HasNeon()) ? 1 : 0; + +#elif defined(TARGET_DARWIN_IOS) + has_neon = 1; + +#elif defined(TARGET_LINUX) && defined(__ARM_NEON__) + if (has_neon == -1) + { + has_neon = 0; + // why are we not looking at the Features in + // /proc/cpuinfo for neon ? + int fd = open("/proc/self/auxv", O_RDONLY); + if (fd >= 0) + { + Elf32_auxv_t auxv; + while (read(fd, &auxv, sizeof(Elf32_auxv_t)) == sizeof(Elf32_auxv_t)) + { + if (auxv.a_type == AT_HWCAP) + { + has_neon = (auxv.a_un.a_val & HWCAP_NEON) ? 1 : 0; + break; + } + } + close(fd); + } + } + +#endif + + return has_neon == 1; +} + +CCPUInfo g_cpuInfo; diff --git a/src/utils/CPUInfo.h b/src/utils/CPUInfo.h new file mode 100644 index 0000000000..baf87514e9 --- /dev/null +++ b/src/utils/CPUInfo.h @@ -0,0 +1,142 @@ +#ifndef CPUINFO_H +#define CPUINFO_H + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <time.h> +#include <string> +#include <map> +#include "threads/SystemClock.h" + +#ifdef TARGET_WINDOWS +// avoid inclusion of <windows.h> and others +typedef void* HANDLE; +typedef HANDLE PDH_HQUERY; +typedef HANDLE PDH_HCOUNTER; +#endif +class CTemperature; + +#define CPU_FEATURE_MMX 1 << 0 +#define CPU_FEATURE_MMX2 1 << 1 +#define CPU_FEATURE_SSE 1 << 2 +#define CPU_FEATURE_SSE2 1 << 3 +#define CPU_FEATURE_SSE3 1 << 4 +#define CPU_FEATURE_SSSE3 1 << 5 +#define CPU_FEATURE_SSE4 1 << 6 +#define CPU_FEATURE_SSE42 1 << 7 +#define CPU_FEATURE_3DNOW 1 << 8 +#define CPU_FEATURE_3DNOWEXT 1 << 9 +#define CPU_FEATURE_ALTIVEC 1 << 10 +#define CPU_FEATURE_NEON 1 << 11 + +struct CoreInfo +{ + int m_id; + double m_fSpeed; + double m_fPct; +#ifdef TARGET_POSIX + unsigned long long m_user; + unsigned long long m_nice; + unsigned long long m_system; + unsigned long long m_io; +#elif defined(TARGET_WINDOWS) + PDH_HCOUNTER m_coreCounter; + unsigned long long m_total; +#endif + unsigned long long m_idle; + std::string m_strVendor; + std::string m_strModel; + std::string m_strBogoMips; + std::string m_strHardware; + std::string m_strRevision; + std::string m_strSerial; +#ifdef TARGET_POSIX + CoreInfo() : m_id(0), m_fSpeed(.0), m_fPct(.0), m_user(0LL), m_nice(0LL), m_system(0LL), m_io(0LL), m_idle(0LL) {} +#elif defined(TARGET_WINDOWS) + CoreInfo() : m_id(0), m_fSpeed(.0), m_fPct(.0), m_coreCounter(NULL), m_total(0LL), m_idle(0LL) {} +#endif + bool operator<(const CoreInfo& other) const { return m_id < other.m_id; } +}; + +class CCPUInfo +{ +public: + CCPUInfo(void); + ~CCPUInfo(); + + int getUsedPercentage(); + int getCPUCount() const { return m_cpuCount; } + float getCPUFrequency(); + bool getTemperature(CTemperature& temperature); + std::string& getCPUModel() { return m_cpuModel; } + std::string& getCPUBogoMips() { return m_cpuBogoMips; } + std::string& getCPUHardware() { return m_cpuHardware; } + std::string& getCPURevision() { return m_cpuRevision; } + std::string& getCPUSerial() { return m_cpuSerial; } + + const CoreInfo &GetCoreInfo(int nCoreId); + bool HasCoreId(int nCoreId) const; + + std::string GetCoresUsageString() const; + + unsigned int GetCPUFeatures() const { return m_cpuFeatures; } + +private: + bool readProcStat(unsigned long long& user, unsigned long long& nice, unsigned long long& system, + unsigned long long& idle, unsigned long long& io); + void ReadCPUFeatures(); + static bool HasNeon(); + +#ifdef TARGET_POSIX + FILE* m_fProcStat; + FILE* m_fProcTemperature; + FILE* m_fCPUFreq; + bool m_cpuInfoForFreq; +#elif defined(TARGET_WINDOWS) + PDH_HQUERY m_cpuQueryFreq; + PDH_HQUERY m_cpuQueryLoad; + PDH_HCOUNTER m_cpuFreqCounter; +#endif + + unsigned long long m_userTicks; + unsigned long long m_niceTicks; + unsigned long long m_systemTicks; + unsigned long long m_idleTicks; + unsigned long long m_ioTicks; + + int m_lastUsedPercentage; + XbmcThreads::EndTime m_nextUsedReadTime; + std::string m_cpuModel; + std::string m_cpuBogoMips; + std::string m_cpuHardware; + std::string m_cpuRevision; + std::string m_cpuSerial; + int m_cpuCount; + unsigned int m_cpuFeatures; + + std::map<int, CoreInfo> m_cores; + +}; + +extern CCPUInfo g_cpuInfo; + +#endif diff --git a/src/utils/CharsetConverter.cpp b/src/utils/CharsetConverter.cpp new file mode 100644 index 0000000000..23bf0822f0 --- /dev/null +++ b/src/utils/CharsetConverter.cpp @@ -0,0 +1,892 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "CharsetConverter.h" +#include "Util.h" +#include "utils/StringUtils.h" +#include <fribidi/fribidi.h> +#include "LangInfo.h" +#include "guilib/LocalizeStrings.h" +#include "settings/lib/Setting.h" +#include "settings/Settings.h" +#include "threads/SingleLock.h" +#include "utils/Utf8Utils.h" +#include "log.h" + +#include <errno.h> +#include <iconv.h> + +#if !defined(TARGET_WINDOWS) && defined(HAVE_CONFIG_H) + #include "config.h" +#endif + +#ifdef WORDS_BIGENDIAN + #define ENDIAN_SUFFIX "BE" +#else + #define ENDIAN_SUFFIX "LE" +#endif + +#if defined(TARGET_DARWIN) + #define WCHAR_IS_UCS_4 1 + #define UTF16_CHARSET "UTF-16" ENDIAN_SUFFIX + #define UTF32_CHARSET "UTF-32" ENDIAN_SUFFIX + #define UTF8_SOURCE "UTF-8-MAC" + #define WCHAR_CHARSET UTF32_CHARSET +#elif defined(TARGET_WINDOWS) + #define WCHAR_IS_UTF16 1 + #define UTF16_CHARSET "UTF-16" ENDIAN_SUFFIX + #define UTF32_CHARSET "UTF-32" ENDIAN_SUFFIX + #define UTF8_SOURCE "UTF-8" + #define WCHAR_CHARSET UTF16_CHARSET + #pragma comment(lib, "libfribidi.lib") + #pragma comment(lib, "libiconv.lib") +#elif defined(TARGET_ANDROID) + #define WCHAR_IS_UCS_4 1 + #define UTF16_CHARSET "UTF-16" ENDIAN_SUFFIX + #define UTF32_CHARSET "UTF-32" ENDIAN_SUFFIX + #define UTF8_SOURCE "UTF-8" + #define WCHAR_CHARSET UTF32_CHARSET +#else + #define UTF16_CHARSET "UTF-16" ENDIAN_SUFFIX + #define UTF32_CHARSET "UTF-32" ENDIAN_SUFFIX + #define UTF8_SOURCE "UTF-8" + #define WCHAR_CHARSET "WCHAR_T" + #if __STDC_ISO_10646__ + #ifdef SIZEOF_WCHAR_T + #if SIZEOF_WCHAR_T == 4 + #define WCHAR_IS_UCS_4 1 + #elif SIZEOF_WCHAR_T == 2 + #define WCHAR_IS_UCS_2 1 + #endif + #endif + #endif +#endif + +#define NO_ICONV ((iconv_t)-1) + +enum SpecialCharset +{ + NotSpecialCharset = 0, + SystemCharset, + UserCharset /* locale.charset */, + SubtitleCharset /* subtitles.charset */, + KaraokeCharset /* karaoke.charset */ +}; + + +class CConverterType : public CCriticalSection +{ +public: + CConverterType(const std::string& sourceCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen = 1); + CConverterType(enum SpecialCharset sourceSpecialCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen = 1); + CConverterType(const std::string& sourceCharset, enum SpecialCharset targetSpecialCharset, unsigned int targetSingleCharMaxLen = 1); + CConverterType(enum SpecialCharset sourceSpecialCharset, enum SpecialCharset targetSpecialCharset, unsigned int targetSingleCharMaxLen = 1); + CConverterType(const CConverterType& other); + ~CConverterType(); + + iconv_t GetConverter(CSingleLock& converterLock); + + void Reset(void); + void ReinitTo(const std::string& sourceCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen = 1); + std::string GetSourceCharset(void) const { return m_sourceCharset; } + std::string GetTargetCharset(void) const { return m_targetCharset; } + unsigned int GetTargetSingleCharMaxLen(void) const { return m_targetSingleCharMaxLen; } + +private: + static std::string ResolveSpecialCharset(enum SpecialCharset charset); + + enum SpecialCharset m_sourceSpecialCharset; + std::string m_sourceCharset; + enum SpecialCharset m_targetSpecialCharset; + std::string m_targetCharset; + iconv_t m_iconv; + unsigned int m_targetSingleCharMaxLen; +}; + +CConverterType::CConverterType(const std::string& sourceCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen /*= 1*/) : CCriticalSection(), + m_sourceSpecialCharset(NotSpecialCharset), + m_sourceCharset(sourceCharset), + m_targetSpecialCharset(NotSpecialCharset), + m_targetCharset(targetCharset), + m_iconv(NO_ICONV), + m_targetSingleCharMaxLen(targetSingleCharMaxLen) +{ +} + +CConverterType::CConverterType(enum SpecialCharset sourceSpecialCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen /*= 1*/) : CCriticalSection(), + m_sourceSpecialCharset(sourceSpecialCharset), + m_sourceCharset(), + m_targetSpecialCharset(NotSpecialCharset), + m_targetCharset(targetCharset), + m_iconv(NO_ICONV), + m_targetSingleCharMaxLen(targetSingleCharMaxLen) +{ +} + +CConverterType::CConverterType(const std::string& sourceCharset, enum SpecialCharset targetSpecialCharset, unsigned int targetSingleCharMaxLen /*= 1*/) : CCriticalSection(), + m_sourceSpecialCharset(NotSpecialCharset), + m_sourceCharset(sourceCharset), + m_targetSpecialCharset(targetSpecialCharset), + m_targetCharset(), + m_iconv(NO_ICONV), + m_targetSingleCharMaxLen(targetSingleCharMaxLen) +{ +} + +CConverterType::CConverterType(enum SpecialCharset sourceSpecialCharset, enum SpecialCharset targetSpecialCharset, unsigned int targetSingleCharMaxLen /*= 1*/) : CCriticalSection(), + m_sourceSpecialCharset(sourceSpecialCharset), + m_sourceCharset(), + m_targetSpecialCharset(targetSpecialCharset), + m_targetCharset(), + m_iconv(NO_ICONV), + m_targetSingleCharMaxLen(targetSingleCharMaxLen) +{ +} + +CConverterType::CConverterType(const CConverterType& other) : CCriticalSection(), + m_sourceSpecialCharset(other.m_sourceSpecialCharset), + m_sourceCharset(other.m_sourceCharset), + m_targetSpecialCharset(other.m_targetSpecialCharset), + m_targetCharset(other.m_targetCharset), + m_iconv(NO_ICONV), + m_targetSingleCharMaxLen(other.m_targetSingleCharMaxLen) +{ +} + + +CConverterType::~CConverterType() +{ + CSingleLock lock(*this); + if (m_iconv != NO_ICONV) + iconv_close(m_iconv); + lock.Leave(); // ensure unlocking before final destruction +} + + +iconv_t CConverterType::GetConverter(CSingleLock& converterLock) +{ + // ensure that this unique instance is locked externally + if (&converterLock.get_underlying() != this) + return NO_ICONV; + + if (m_iconv == NO_ICONV) + { + if (m_sourceSpecialCharset) + m_sourceCharset = ResolveSpecialCharset(m_sourceSpecialCharset); + if (m_targetSpecialCharset) + m_targetCharset = ResolveSpecialCharset(m_targetSpecialCharset); + + m_iconv = iconv_open(m_targetCharset.c_str(), m_sourceCharset.c_str()); + + if (m_iconv == NO_ICONV) + CLog::Log(LOGERROR, "%s: iconv_open() for \"%s\" -> \"%s\" failed, errno = %d (%s)", + __FUNCTION__, m_sourceCharset.c_str(), m_targetCharset.c_str(), errno, strerror(errno)); + } + + return m_iconv; +} + + +void CConverterType::Reset(void) +{ + CSingleLock lock(*this); + if (m_iconv != NO_ICONV) + { + iconv_close(m_iconv); + m_iconv = NO_ICONV; + } + + if (m_sourceSpecialCharset) + m_sourceCharset.clear(); + if (m_targetSpecialCharset) + m_targetCharset.clear(); + +} + +void CConverterType::ReinitTo(const std::string& sourceCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen /*= 1*/) +{ + CSingleLock lock(*this); + if (sourceCharset != m_sourceCharset || targetCharset != m_targetCharset) + { + if (m_iconv != NO_ICONV) + { + iconv_close(m_iconv); + m_iconv = NO_ICONV; + } + + m_sourceSpecialCharset = NotSpecialCharset; + m_sourceCharset = sourceCharset; + m_targetSpecialCharset = NotSpecialCharset; + m_targetCharset = targetCharset; + m_targetSingleCharMaxLen = targetSingleCharMaxLen; + } +} + +std::string CConverterType::ResolveSpecialCharset(enum SpecialCharset charset) +{ + switch (charset) + { + case SystemCharset: + return ""; + case UserCharset: + return g_langInfo.GetGuiCharSet(); + case SubtitleCharset: + return g_langInfo.GetSubtitleCharSet(); + case KaraokeCharset: + { + CSetting* karaokeSetting = CSettings::Get().GetSetting("karaoke.charset"); + if (karaokeSetting == NULL || ((CSettingString*)karaokeSetting)->GetValue() == "DEFAULT") + return g_langInfo.GetGuiCharSet(); + + return ((CSettingString*)karaokeSetting)->GetValue(); + } + case NotSpecialCharset: + default: + return "UTF-8"; /* dummy value */ + } +} + + +enum StdConversionType /* Keep it in sync with CCharsetConverter::CInnerConverter::m_stdConversion */ +{ + NoConversion = -1, + Utf8ToUtf32 = 0, + Utf32ToUtf8, + Utf32ToW, + WToUtf32, + SubtitleCharsetToUtf8, + Utf8ToUserCharset, + UserCharsetToUtf8, + Utf32ToUserCharset, + WtoUtf8, + Utf16LEtoW, + Utf16BEtoUtf8, + Utf16LEtoUtf8, + Utf8toW, + Utf8ToSystem, + SystemToUtf8, + Ucs2CharsetToUtf8, + NumberOfStdConversionTypes /* Dummy sentinel entry */ +}; + + +/* We don't want to pollute header file with many additional includes and definitions, so put + here all staff that require usage of types defined in this file or in additional headers */ +class CCharsetConverter::CInnerConverter +{ +public: + static bool logicalToVisualBiDi(const std::u32string& stringSrc, std::u32string& stringDst, FriBidiCharType base = FRIBIDI_TYPE_LTR, const bool failOnBadString = false); + + template<class INPUT,class OUTPUT> + static bool stdConvert(StdConversionType convertType, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar = false); + template<class INPUT,class OUTPUT> + static bool customConvert(const std::string& sourceCharset, const std::string& targetCharset, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar = false); + + template<class INPUT,class OUTPUT> + static bool convert(iconv_t type, int multiplier, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar = false); + + static CConverterType m_stdConversion[NumberOfStdConversionTypes]; + static CCriticalSection m_critSectionFriBiDi; +}; + +/* single symbol sizes in chars */ +const int CCharsetConverter::m_Utf8CharMinSize = 1; +const int CCharsetConverter::m_Utf8CharMaxSize = 4; + +CConverterType CCharsetConverter::CInnerConverter::m_stdConversion[NumberOfStdConversionTypes] = /* keep it in sync with enum StdConversionType */ +{ + /* Utf8ToUtf32 */ CConverterType(UTF8_SOURCE, UTF32_CHARSET), + /* Utf32ToUtf8 */ CConverterType(UTF32_CHARSET, "UTF-8", CCharsetConverter::m_Utf8CharMaxSize), + /* Utf32ToW */ CConverterType(UTF32_CHARSET, WCHAR_CHARSET), + /* WToUtf32 */ CConverterType(WCHAR_CHARSET, UTF32_CHARSET), + /* SubtitleCharsetToUtf8*/CConverterType(SubtitleCharset, "UTF-8", CCharsetConverter::m_Utf8CharMaxSize), + /* Utf8ToUserCharset */ CConverterType(UTF8_SOURCE, UserCharset), + /* UserCharsetToUtf8 */ CConverterType(UserCharset, "UTF-8", CCharsetConverter::m_Utf8CharMaxSize), + /* Utf32ToUserCharset */ CConverterType(UTF32_CHARSET, UserCharset), + /* WtoUtf8 */ CConverterType(WCHAR_CHARSET, "UTF-8", CCharsetConverter::m_Utf8CharMaxSize), + /* Utf16LEtoW */ CConverterType("UTF-16LE", WCHAR_CHARSET), + /* Utf16BEtoUtf8 */ CConverterType("UTF-16BE", "UTF-8", CCharsetConverter::m_Utf8CharMaxSize), + /* Utf16LEtoUtf8 */ CConverterType("UTF-16LE", "UTF-8", CCharsetConverter::m_Utf8CharMaxSize), + /* Utf8toW */ CConverterType(UTF8_SOURCE, WCHAR_CHARSET), + /* Utf8ToSystem */ CConverterType(UTF8_SOURCE, SystemCharset), + /* SystemToUtf8 */ CConverterType(SystemCharset, UTF8_SOURCE), + /* Ucs2CharsetToUtf8 */ CConverterType("UCS-2LE", "UTF-8", CCharsetConverter::m_Utf8CharMaxSize) +}; + +CCriticalSection CCharsetConverter::CInnerConverter::m_critSectionFriBiDi; + + + +template<class INPUT,class OUTPUT> +bool CCharsetConverter::CInnerConverter::stdConvert(StdConversionType convertType, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar /*= false*/) +{ + strDest.clear(); + if (strSource.empty()) + return true; + + if (convertType < 0 || convertType >= NumberOfStdConversionTypes) + return false; + + CConverterType& convType = m_stdConversion[convertType]; + CSingleLock converterLock(convType); + + return convert(convType.GetConverter(converterLock), convType.GetTargetSingleCharMaxLen(), strSource, strDest, failOnInvalidChar); +} + +template<class INPUT,class OUTPUT> +bool CCharsetConverter::CInnerConverter::customConvert(const std::string& sourceCharset, const std::string& targetCharset, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar /*= false*/) +{ + strDest.clear(); + if (strSource.empty()) + return true; + + iconv_t conv = iconv_open(targetCharset.c_str(), sourceCharset.c_str()); + if (conv == NO_ICONV) + { + CLog::Log(LOGERROR, "%s: iconv_open() for \"%s\" -> \"%s\" failed, errno = %d (%s)", + __FUNCTION__, sourceCharset.c_str(), targetCharset.c_str(), errno, strerror(errno)); + return false; + } + const int dstMultp = (targetCharset.compare(0, 5, "UTF-8") == 0) ? CCharsetConverter::m_Utf8CharMaxSize : 1; + const bool result = convert(conv, dstMultp, strSource, strDest, failOnInvalidChar); + iconv_close(conv); + + return result; +} + + +/* iconv may declare inbuf to be char** rather than const char** depending on platform and version, + so provide a wrapper that handles both */ +struct charPtrPtrAdapter +{ + const char** pointer; + charPtrPtrAdapter(const char** p) : + pointer(p) { } + operator char**() + { return const_cast<char**>(pointer); } + operator const char**() + { return pointer; } +}; + +template<class INPUT,class OUTPUT> +bool CCharsetConverter::CInnerConverter::convert(iconv_t type, int multiplier, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar /*= false*/) +{ + if (type == NO_ICONV) + return false; + + //input buffer for iconv() is the buffer from strSource + size_t inBufSize = (strSource.length() + 1) * sizeof(typename INPUT::value_type); + const char* inBuf = (const char*)strSource.c_str(); + + //allocate output buffer for iconv() + size_t outBufSize = (strSource.length() + 1) * sizeof(typename OUTPUT::value_type) * multiplier; + char* outBuf = (char*)malloc(outBufSize); + if (outBuf == NULL) + { + CLog::Log(LOGSEVERE, "%s: malloc failed", __FUNCTION__); + return false; + } + + size_t inBytesAvail = inBufSize; //how many bytes iconv() can read + size_t outBytesAvail = outBufSize; //how many bytes iconv() can write + const char* inBufStart = inBuf; //where in our input buffer iconv() should start reading + char* outBufStart = outBuf; //where in out output buffer iconv() should start writing + + size_t returnV; + while(1) + { + //iconv() will update inBufStart, inBytesAvail, outBufStart and outBytesAvail + returnV = iconv(type, charPtrPtrAdapter(&inBufStart), &inBytesAvail, &outBufStart, &outBytesAvail); + + if (returnV == (size_t)-1) + { + if (errno == E2BIG) //output buffer is not big enough + { + //save where iconv() ended converting, realloc might make outBufStart invalid + size_t bytesConverted = outBufSize - outBytesAvail; + + //make buffer twice as big + outBufSize *= 2; + char* newBuf = (char*)realloc(outBuf, outBufSize); + if (!newBuf) + { + CLog::Log(LOGSEVERE, "%s realloc failed with errno=%d(%s)", + __FUNCTION__, errno, strerror(errno)); + break; + } + outBuf = newBuf; + + //update the buffer pointer and counter + outBufStart = outBuf + bytesConverted; + outBytesAvail = outBufSize - bytesConverted; + + //continue in the loop and convert the rest + continue; + } + else if (errno == EILSEQ) //An invalid multibyte sequence has been encountered in the input + { + if (failOnInvalidChar) + break; + + //skip invalid byte + inBufStart++; + inBytesAvail--; + //continue in the loop and convert the rest + continue; + } + else if (errno == EINVAL) /* Invalid sequence at the end of input buffer */ + { + if (!failOnInvalidChar) + returnV = 0; /* reset error status to use converted part */ + + break; + } + else //iconv() had some other error + { + CLog::Log(LOGERROR, "%s: iconv() failed, errno=%d (%s)", + __FUNCTION__, errno, strerror(errno)); + } + } + break; + } + + //complete the conversion (reset buffers), otherwise the current data will prefix the data on the next call + if (iconv(type, NULL, NULL, &outBufStart, &outBytesAvail) == (size_t)-1) + CLog::Log(LOGERROR, "%s failed cleanup errno=%d(%s)", __FUNCTION__, errno, strerror(errno)); + + if (returnV == (size_t)-1) + { + free(outBuf); + return false; + } + //we're done + + const typename OUTPUT::size_type sizeInChars = (typename OUTPUT::size_type) (outBufSize - outBytesAvail) / sizeof(typename OUTPUT::value_type); + typename OUTPUT::const_pointer strPtr = (typename OUTPUT::const_pointer) outBuf; + /* Make sure that all buffer is assigned and string is stopped at end of buffer */ + if (strPtr[sizeInChars-1] == 0 && strSource[strSource.length()-1] != 0) + strDest.assign(strPtr, sizeInChars-1); + else + strDest.assign(strPtr, sizeInChars); + + free(outBuf); + + return true; +} + +bool CCharsetConverter::CInnerConverter::logicalToVisualBiDi(const std::u32string& stringSrc, std::u32string& stringDst, FriBidiCharType base /*= FRIBIDI_TYPE_LTR*/, const bool failOnBadString /*= false*/) +{ + stringDst.clear(); + + const size_t srcLen = stringSrc.length(); + if (srcLen == 0) + return true; + + stringDst.reserve(srcLen); + size_t lineStart = 0; + + // libfribidi is not threadsafe, so make sure we make it so + CSingleLock lock(m_critSectionFriBiDi); + do + { + size_t lineEnd = stringSrc.find('\n', lineStart); + if (lineEnd >= srcLen) // equal to 'lineEnd == std::string::npos' + lineEnd = srcLen; + else + lineEnd++; // include '\n' + + const size_t lineLen = lineEnd - lineStart; + + FriBidiChar* visual = (FriBidiChar*) malloc((lineLen + 1) * sizeof(FriBidiChar)); + if (visual == NULL) + { + free(visual); + CLog::Log(LOGSEVERE, "%s: can't allocate memory", __FUNCTION__); + return false; + } + + bool bidiFailed = false; + FriBidiCharType baseCopy = base; // preserve same value for all lines, required because fribidi_log2vis will modify parameter value + if (fribidi_log2vis((const FriBidiChar*)(stringSrc.c_str() + lineStart), lineLen, &baseCopy, visual, NULL, NULL, NULL)) + { + // Removes bidirectional marks + const int newLen = fribidi_remove_bidi_marks(visual, lineLen, NULL, NULL, NULL); + if (newLen > 0) + stringDst.append((const char32_t*)visual, (size_t)newLen); + else if (newLen < 0) + bidiFailed = failOnBadString; + } + else + bidiFailed = failOnBadString; + + free(visual); + + if (bidiFailed) + return false; + + lineStart = lineEnd; + } while (lineStart < srcLen); + + return !stringDst.empty(); +} + + +static struct SCharsetMapping +{ + const char* charset; + const char* caption; +} g_charsets[] = { + { "ISO-8859-1", "Western Europe (ISO)" } + , { "ISO-8859-2", "Central Europe (ISO)" } + , { "ISO-8859-3", "South Europe (ISO)" } + , { "ISO-8859-4", "Baltic (ISO)" } + , { "ISO-8859-5", "Cyrillic (ISO)" } + , { "ISO-8859-6", "Arabic (ISO)" } + , { "ISO-8859-7", "Greek (ISO)" } + , { "ISO-8859-8", "Hebrew (ISO)" } + , { "ISO-8859-9", "Turkish (ISO)" } + , { "CP1250", "Central Europe (Windows)" } + , { "CP1251", "Cyrillic (Windows)" } + , { "CP1252", "Western Europe (Windows)" } + , { "CP1253", "Greek (Windows)" } + , { "CP1254", "Turkish (Windows)" } + , { "CP1255", "Hebrew (Windows)" } + , { "CP1256", "Arabic (Windows)" } + , { "CP1257", "Baltic (Windows)" } + , { "CP1258", "Vietnamesse (Windows)" } + , { "CP874", "Thai (Windows)" } + , { "BIG5", "Chinese Traditional (Big5)" } + , { "GBK", "Chinese Simplified (GBK)" } + , { "SHIFT_JIS", "Japanese (Shift-JIS)" } + , { "CP949", "Korean" } + , { "BIG5-HKSCS", "Hong Kong (Big5-HKSCS)" } + , { NULL, NULL } +}; + + +CCharsetConverter::CCharsetConverter() +{ +} + +void CCharsetConverter::OnSettingChanged(const CSetting* setting) +{ + if (setting == NULL) + return; + + const std::string& settingId = setting->GetId(); + if (settingId == "locale.charset") + resetUserCharset(); + else if (settingId == "subtitles.charset") + resetSubtitleCharset(); + else if (settingId == "karaoke.charset") + resetKaraokeCharset(); +} + +void CCharsetConverter::clear() +{ +} + +std::vector<std::string> CCharsetConverter::getCharsetLabels() +{ + std::vector<std::string> lab; + for(SCharsetMapping* c = g_charsets; c->charset; c++) + lab.push_back(c->caption); + + return lab; +} + +std::string CCharsetConverter::getCharsetLabelByName(const std::string& charsetName) +{ + for(SCharsetMapping* c = g_charsets; c->charset; c++) + { + if (StringUtils::EqualsNoCase(charsetName,c->charset)) + return c->caption; + } + + return ""; +} + +std::string CCharsetConverter::getCharsetNameByLabel(const std::string& charsetLabel) +{ + for(SCharsetMapping* c = g_charsets; c->charset; c++) + { + if (StringUtils::EqualsNoCase(charsetLabel, c->caption)) + return c->charset; + } + + return ""; +} + +void CCharsetConverter::reset(void) +{ + for (int i = 0; i < NumberOfStdConversionTypes; i++) + CInnerConverter::m_stdConversion[i].Reset(); +} + +void CCharsetConverter::resetSystemCharset(void) +{ + CInnerConverter::m_stdConversion[Utf8ToSystem].Reset(); + CInnerConverter::m_stdConversion[SystemToUtf8].Reset(); +} + +void CCharsetConverter::resetUserCharset(void) +{ + CInnerConverter::m_stdConversion[UserCharsetToUtf8].Reset(); + CInnerConverter::m_stdConversion[UserCharsetToUtf8].Reset(); + CInnerConverter::m_stdConversion[Utf32ToUserCharset].Reset(); + resetSubtitleCharset(); + resetKaraokeCharset(); +} + +void CCharsetConverter::resetSubtitleCharset(void) +{ + CInnerConverter::m_stdConversion[SubtitleCharsetToUtf8].Reset(); +} + +void CCharsetConverter::resetKaraokeCharset(void) +{ +} + +void CCharsetConverter::reinitCharsetsFromSettings(void) +{ + resetUserCharset(); // this will also reinit Subtitle and Karaoke charsets +} + +bool CCharsetConverter::utf8ToUtf32(const std::string& utf8StringSrc, std::u32string& utf32StringDst, bool failOnBadChar /*= true*/) +{ + return CInnerConverter::stdConvert(Utf8ToUtf32, utf8StringSrc, utf32StringDst, failOnBadChar); +} + +std::u32string CCharsetConverter::utf8ToUtf32(const std::string& utf8StringSrc, bool failOnBadChar /*= true*/) +{ + std::u32string converted; + utf8ToUtf32(utf8StringSrc, converted, failOnBadChar); + return converted; +} + +bool CCharsetConverter::utf8ToUtf32Visual(const std::string& utf8StringSrc, std::u32string& utf32StringDst, bool bVisualBiDiFlip /*= false*/, bool forceLTRReadingOrder /*= false*/, bool failOnBadChar /*= false*/) +{ + if (bVisualBiDiFlip) + { + std::u32string converted; + if (!CInnerConverter::stdConvert(Utf8ToUtf32, utf8StringSrc, converted, failOnBadChar)) + return false; + + return CInnerConverter::logicalToVisualBiDi(converted, utf32StringDst, forceLTRReadingOrder ? FRIBIDI_TYPE_LTR : FRIBIDI_TYPE_PDF, failOnBadChar); + } + return CInnerConverter::stdConvert(Utf8ToUtf32, utf8StringSrc, utf32StringDst, failOnBadChar); +} + +bool CCharsetConverter::utf32ToUtf8(const std::u32string& utf32StringSrc, std::string& utf8StringDst, bool failOnBadChar /*= true*/) +{ + return CInnerConverter::stdConvert(Utf32ToUtf8, utf32StringSrc, utf8StringDst, failOnBadChar); +} + +std::string CCharsetConverter::utf32ToUtf8(const std::u32string& utf32StringSrc, bool failOnBadChar /*= false*/) +{ + std::string converted; + utf32ToUtf8(utf32StringSrc, converted, failOnBadChar); + return converted; +} + +bool CCharsetConverter::utf32ToW(const std::u32string& utf32StringSrc, std::wstring& wStringDst, bool failOnBadChar /*= true*/) +{ +#ifdef WCHAR_IS_UCS_4 + wStringDst.assign((const wchar_t*)utf32StringSrc.c_str(), utf32StringSrc.length()); + return true; +#else // !WCHAR_IS_UCS_4 + return CInnerConverter::stdConvert(Utf32ToW, utf32StringSrc, wStringDst, failOnBadChar); +#endif // !WCHAR_IS_UCS_4 +} + +bool CCharsetConverter::utf32logicalToVisualBiDi(const std::u32string& logicalStringSrc, std::u32string& visualStringDst, bool forceLTRReadingOrder /*= false*/, bool failOnBadString /*= false*/) +{ + return CInnerConverter::logicalToVisualBiDi(logicalStringSrc, visualStringDst, forceLTRReadingOrder ? FRIBIDI_TYPE_LTR : FRIBIDI_TYPE_PDF, failOnBadString); +} + +bool CCharsetConverter::wToUtf32(const std::wstring& wStringSrc, std::u32string& utf32StringDst, bool failOnBadChar /*= true*/) +{ +#ifdef WCHAR_IS_UCS_4 + /* UCS-4 is almost equal to UTF-32, but UTF-32 has strict limits on possible values, while UCS-4 is usually unchecked. + * With this "conversion" we ensure that output will be valid UTF-32 string. */ +#endif + return CInnerConverter::stdConvert(WToUtf32, wStringSrc, utf32StringDst, failOnBadChar); +} + +// The bVisualBiDiFlip forces a flip of characters for hebrew/arabic languages, only set to false if the flipping +// of the string is already made or the string is not displayed in the GUI +bool CCharsetConverter::utf8ToW(const std::string& utf8StringSrc, std::wstring& wStringDst, bool bVisualBiDiFlip /*= true*/, + bool forceLTRReadingOrder /*= false*/, bool failOnBadChar /*= false*/) +{ + // Try to flip hebrew/arabic characters, if any + if (bVisualBiDiFlip) + { + wStringDst.clear(); + std::u32string utf32str; + if (!CInnerConverter::stdConvert(Utf8ToUtf32, utf8StringSrc, utf32str, failOnBadChar)) + return false; + + std::u32string utf32flipped; + const bool bidiResult = CInnerConverter::logicalToVisualBiDi(utf32str, utf32flipped, forceLTRReadingOrder ? FRIBIDI_TYPE_LTR : FRIBIDI_TYPE_PDF, failOnBadChar); + + return CInnerConverter::stdConvert(Utf32ToW, utf32flipped, wStringDst, failOnBadChar) && bidiResult; + } + + return CInnerConverter::stdConvert(Utf8toW, utf8StringSrc, wStringDst, failOnBadChar); +} + +bool CCharsetConverter::subtitleCharsetToUtf8(const std::string& stringSrc, std::string& utf8StringDst) +{ + return CInnerConverter::stdConvert(SubtitleCharsetToUtf8, stringSrc, utf8StringDst, false); +} + +bool CCharsetConverter::fromW(const std::wstring& wStringSrc, + std::string& stringDst, const std::string& enc) +{ + return CInnerConverter::customConvert(WCHAR_CHARSET, enc, wStringSrc, stringDst); +} + +bool CCharsetConverter::toW(const std::string& stringSrc, + std::wstring& wStringDst, const std::string& enc) +{ + return CInnerConverter::customConvert(enc, WCHAR_CHARSET, stringSrc, wStringDst); +} + +bool CCharsetConverter::utf8ToStringCharset(const std::string& utf8StringSrc, std::string& stringDst) +{ + return CInnerConverter::stdConvert(Utf8ToUserCharset, utf8StringSrc, stringDst); +} + +bool CCharsetConverter::utf8ToStringCharset(std::string& stringSrcDst) +{ + std::string strSrc(stringSrcDst); + return utf8ToStringCharset(strSrc, stringSrcDst); +} + +bool CCharsetConverter::ToUtf8(const std::string& strSourceCharset, const std::string& stringSrc, std::string& utf8StringDst, bool failOnBadChar /*= false*/) +{ + if (strSourceCharset == "UTF-8") + { // simple case - no conversion necessary + utf8StringDst = stringSrc; + return true; + } + + return CInnerConverter::customConvert(strSourceCharset, "UTF-8", stringSrc, utf8StringDst, failOnBadChar); +} + +bool CCharsetConverter::utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::string& stringDst) +{ + if (strDestCharset == "UTF-8") + { // simple case - no conversion necessary + stringDst = utf8StringSrc; + return true; + } + + return CInnerConverter::customConvert(UTF8_SOURCE, strDestCharset, utf8StringSrc, stringDst); +} + +bool CCharsetConverter::utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::u16string& utf16StringDst) +{ + return CInnerConverter::customConvert(UTF8_SOURCE, strDestCharset, utf8StringSrc, utf16StringDst); +} + +bool CCharsetConverter::utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::u32string& utf32StringDst) +{ + return CInnerConverter::customConvert(UTF8_SOURCE, strDestCharset, utf8StringSrc, utf32StringDst); +} + +bool CCharsetConverter::unknownToUTF8(std::string& stringSrcDst) +{ + std::string source(stringSrcDst); + return unknownToUTF8(source, stringSrcDst); +} + +bool CCharsetConverter::unknownToUTF8(const std::string& stringSrc, std::string& utf8StringDst, bool failOnBadChar /*= false*/) +{ + // checks whether it's utf8 already, and if not converts using the sourceCharset if given, else the string charset + if (CUtf8Utils::isValidUtf8(stringSrc)) + { + utf8StringDst = stringSrc; + return true; + } + return CInnerConverter::stdConvert(UserCharsetToUtf8, stringSrc, utf8StringDst, failOnBadChar); +} + +bool CCharsetConverter::wToUTF8(const std::wstring& wStringSrc, std::string& utf8StringDst, bool failOnBadChar /*= false*/) +{ + return CInnerConverter::stdConvert(WtoUtf8, wStringSrc, utf8StringDst, failOnBadChar); +} + +bool CCharsetConverter::utf16BEtoUTF8(const std::u16string& utf16StringSrc, std::string& utf8StringDst) +{ + return CInnerConverter::stdConvert(Utf16BEtoUtf8, utf16StringSrc, utf8StringDst); +} + +bool CCharsetConverter::utf16LEtoUTF8(const std::u16string& utf16StringSrc, + std::string& utf8StringDst) +{ + return CInnerConverter::stdConvert(Utf16LEtoUtf8, utf16StringSrc, utf8StringDst); +} + +bool CCharsetConverter::ucs2ToUTF8(const std::u16string& ucs2StringSrc, std::string& utf8StringDst) +{ + return CInnerConverter::stdConvert(Ucs2CharsetToUtf8, ucs2StringSrc,utf8StringDst); +} + +bool CCharsetConverter::utf16LEtoW(const std::u16string& utf16String, std::wstring& wString) +{ + return CInnerConverter::stdConvert(Utf16LEtoW, utf16String, wString); +} + +bool CCharsetConverter::utf32ToStringCharset(const std::u32string& utf32StringSrc, std::string& stringDst) +{ + return CInnerConverter::stdConvert(Utf32ToUserCharset, utf32StringSrc, stringDst); +} + +bool CCharsetConverter::utf8ToSystem(std::string& stringSrcDst, bool failOnBadChar /*= false*/) +{ + std::string strSrc(stringSrcDst); + return CInnerConverter::stdConvert(Utf8ToSystem, strSrc, stringSrcDst, failOnBadChar); +} + +bool CCharsetConverter::systemToUtf8(const std::string& sysStringSrc, std::string& utf8StringDst, bool failOnBadChar /*= false*/) +{ + return CInnerConverter::stdConvert(SystemToUtf8, sysStringSrc, utf8StringDst, failOnBadChar); +} + +bool CCharsetConverter::utf8logicalToVisualBiDi(const std::string& utf8StringSrc, std::string& utf8StringDst, bool failOnBadString /*= false*/) +{ + utf8StringDst.clear(); + std::u32string utf32flipped; + if (!utf8ToUtf32Visual(utf8StringSrc, utf32flipped, true, true, failOnBadString)) + return false; + + return CInnerConverter::stdConvert(Utf32ToUtf8, utf32flipped, utf8StringDst, failOnBadString); +} + +void CCharsetConverter::SettingOptionsCharsetsFiller(const CSetting* setting, std::vector< std::pair<std::string, std::string> >& list, std::string& current, void *data) +{ + std::vector<std::string> vecCharsets = g_charsetConverter.getCharsetLabels(); + sort(vecCharsets.begin(), vecCharsets.end(), sortstringbyname()); + + list.push_back(make_pair(g_localizeStrings.Get(13278), "DEFAULT")); // "Default" + for (int i = 0; i < (int) vecCharsets.size(); ++i) + list.push_back(make_pair(vecCharsets[i], g_charsetConverter.getCharsetNameByLabel(vecCharsets[i]))); +} diff --git a/src/utils/CharsetConverter.h b/src/utils/CharsetConverter.h new file mode 100644 index 0000000000..b3453c101f --- /dev/null +++ b/src/utils/CharsetConverter.h @@ -0,0 +1,179 @@ +#ifndef CCHARSET_CONVERTER +#define CCHARSET_CONVERTER + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "settings/lib/ISettingCallback.h" +#include "threads/CriticalSection.h" +#include "utils/GlobalsHandling.h" +#include "utils/uXstrings.h" + +#include <string> +#include <vector> + +class CSetting; + +class CCharsetConverter : public ISettingCallback +{ +public: + CCharsetConverter(); + + virtual void OnSettingChanged(const CSetting* setting); + + static void reset(); + static void resetSystemCharset(); + static void reinitCharsetsFromSettings(void); + + static void clear(); + + /** + * Convert UTF-8 string to UTF-32 string. + * No RTL logical-visual transformation is performed. + * @param utf8StringSrc is source UTF-8 string to convert + * @param utf32StringDst is output UTF-32 string, empty on any error + * @param failOnBadChar if set to true function will fail on invalid character, + * otherwise invalid character will be skipped + * @return true on successful conversion, false on any error + */ + static bool utf8ToUtf32(const std::string& utf8StringSrc, std::u32string& utf32StringDst, bool failOnBadChar = true); + /** + * Convert UTF-8 string to UTF-32 string. + * No RTL logical-visual transformation is performed. + * @param utf8StringSrc is source UTF-8 string to convert + * @param failOnBadChar if set to true function will fail on invalid character, + * otherwise invalid character will be skipped + * @return converted string on successful conversion, empty string on any error + */ + static std::u32string utf8ToUtf32(const std::string& utf8StringSrc, bool failOnBadChar = true); + /** + * Convert UTF-8 string to UTF-32 string. + * RTL logical-visual transformation is optionally performed. + * Use it for readable text, GUI strings etc. + * @param utf8StringSrc is source UTF-8 string to convert + * @param utf32StringDst is output UTF-32 string, empty on any error + * @param bVisualBiDiFlip allow RTL visual-logical transformation if set to true, must be set + * to false is logical-visual transformation is already done + * @param forceLTRReadingOrder force LTR reading order + * @param failOnBadChar if set to true function will fail on invalid character, + * otherwise invalid character will be skipped + * @return true on successful conversion, false on any error + */ + static bool utf8ToUtf32Visual(const std::string& utf8StringSrc, std::u32string& utf32StringDst, bool bVisualBiDiFlip = false, bool forceLTRReadingOrder = false, bool failOnBadChar = false); + /** + * Convert UTF-32 string to UTF-8 string. + * No RTL visual-logical transformation is performed. + * @param utf32StringSrc is source UTF-32 string to convert + * @param utf8StringDst is output UTF-8 string, empty on any error + * @param failOnBadChar if set to true function will fail on invalid character, + * otherwise invalid character will be skipped + * @return true on successful conversion, false on any error + */ + static bool utf32ToUtf8(const std::u32string& utf32StringSrc, std::string& utf8StringDst, bool failOnBadChar = false); + /** + * Convert UTF-32 string to UTF-8 string. + * No RTL visual-logical transformation is performed. + * @param utf32StringSrc is source UTF-32 string to convert + * @param failOnBadChar if set to true function will fail on invalid character, + * otherwise invalid character will be skipped + * @return converted string on successful conversion, empty string on any error + */ + static std::string utf32ToUtf8(const std::u32string& utf32StringSrc, bool failOnBadChar = false); + /** + * Convert UTF-32 string to wchar_t string (wstring). + * No RTL visual-logical transformation is performed. + * @param utf32StringSrc is source UTF-32 string to convert + * @param wStringDst is output wchar_t string, empty on any error + * @param failOnBadChar if set to true function will fail on invalid character, + * otherwise invalid character will be skipped + * @return true on successful conversion, false on any error + */ + static bool utf32ToW(const std::u32string& utf32StringSrc, std::wstring& wStringDst, bool failOnBadChar = false); + /** + * Perform logical to visual flip. + * @param logicalStringSrc is source string with logical characters order + * @param visualStringDst is output string with visual characters order, empty on any error + * @param forceLTRReadingOrder force LTR reading order + * @return true on success, false otherwise + */ + static bool utf32logicalToVisualBiDi(const std::u32string& logicalStringSrc, std::u32string& visualStringDst, bool forceLTRReadingOrder = false, bool failOnBadString = false); + /** + * Strictly convert wchar_t string (wstring) to UTF-32 string. + * No RTL visual-logical transformation is performed. + * @param wStringSrc is source wchar_t string to convert + * @param utf32StringDst is output UTF-32 string, empty on any error + * @param failOnBadChar if set to true function will fail on invalid character, + * otherwise invalid character will be skipped + * @return true on successful conversion, false on any error + */ + static bool wToUtf32(const std::wstring& wStringSrc, std::u32string& utf32StringDst, bool failOnBadChar = false); + + static bool utf8ToW(const std::string& utf8StringSrc, std::wstring& wStringDst, + bool bVisualBiDiFlip = true, bool forceLTRReadingOrder = false, + bool failOnBadChar = false); + + static bool utf16LEtoW(const std::u16string& utf16String, std::wstring& wString); + + static bool subtitleCharsetToUtf8(const std::string& stringSrc, std::string& utf8StringDst); + + static bool utf8ToStringCharset(const std::string& utf8StringSrc, std::string& stringDst); + + static bool utf8ToStringCharset(std::string& stringSrcDst); + static bool utf8ToSystem(std::string& stringSrcDst, bool failOnBadChar = false); + static bool systemToUtf8(const std::string& sysStringSrc, std::string& utf8StringDst, bool failOnBadChar = false); + + static bool utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::string& stringDst); + static bool utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::u16string& utf16StringDst); + static bool utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::u32string& utf32StringDst); + + static bool ToUtf8(const std::string& strSourceCharset, const std::string& stringSrc, std::string& utf8StringDst, bool failOnBadChar = false); + + static bool wToUTF8(const std::wstring& wStringSrc, std::string& utf8StringDst, bool failOnBadChar = false); + static bool utf16BEtoUTF8(const std::u16string& utf16StringSrc, std::string& utf8StringDst); + static bool utf16LEtoUTF8(const std::u16string& utf16StringSrc, std::string& utf8StringDst); + static bool ucs2ToUTF8(const std::u16string& ucs2StringSrc, std::string& utf8StringDst); + + static bool utf8logicalToVisualBiDi(const std::string& utf8StringSrc, std::string& utf8StringDst, bool failOnBadString = false); + + static bool utf32ToStringCharset(const std::u32string& utf32StringSrc, std::string& stringDst); + + static std::vector<std::string> getCharsetLabels(); + static std::string getCharsetLabelByName(const std::string& charsetName); + static std::string getCharsetNameByLabel(const std::string& charsetLabel); + + static bool unknownToUTF8(std::string& stringSrcDst); + static bool unknownToUTF8(const std::string& stringSrc, std::string& utf8StringDst, bool failOnBadChar = false); + + static bool toW(const std::string& stringSrc, std::wstring& wStringDst, const std::string& enc); + static bool fromW(const std::wstring& wStringSrc, std::string& stringDst, const std::string& enc); + + static void SettingOptionsCharsetsFiller(const CSetting* setting, std::vector< std::pair<std::string, std::string> >& list, std::string& current, void *data); +private: + static void resetUserCharset(void); + static void resetSubtitleCharset(void); + static void resetKaraokeCharset(void); + + static const int m_Utf8CharMinSize, m_Utf8CharMaxSize; + class CInnerConverter; +}; + +XBMC_GLOBAL(CCharsetConverter,g_charsetConverter); + +#endif diff --git a/src/utils/CharsetDetection.cpp b/src/utils/CharsetDetection.cpp new file mode 100644 index 0000000000..810b01f07f --- /dev/null +++ b/src/utils/CharsetDetection.cpp @@ -0,0 +1,649 @@ +/* +* Copyright (C) 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 +* <http://www.gnu.org/licenses/>. +* +*/ + +#include <algorithm> +#include "CharsetDetection.h" +#include "utils/CharsetConverter.h" +#include "utils/StringUtils.h" +#include "utils/Utf8Utils.h" +#include "LangInfo.h" +#include "utils/log.h" + +/* XML declaration can be virtually any size (with many-many whitespaces) + * but for in real world we don't need to process megabytes of data + * so limit search for XML declaration to reasonable value */ +const size_t CCharsetDetection::m_XmlDeclarationMaxLength = 250; + +/* According to http://www.w3.org/TR/2013/CR-html5-20130806/single-page.html#charset + * encoding must be placed in first 1024 bytes of document */ +const size_t CCharsetDetection::m_HtmlCharsetEndSearchPos = 1024; + +/* According to http://www.w3.org/TR/2013/CR-html5-20130806/single-page.html#space-character + * tab, LF, FF, CR or space can be used as whitespace */ +const std::string CCharsetDetection::m_HtmlWhitespaceChars("\x09\x0A\x0C\x0D\x20"); // tab, LF, FF, CR and space + +std::string CCharsetDetection::GetBomEncoding(const char* const content, const size_t contentLength) +{ + if (contentLength < 2) + return ""; + if (content[0] == (char)0xFE && content[1] == (char)0xFF) + return "UTF-16BE"; + if (contentLength >= 4 && content[0] == (char)0xFF && content[1] == (char)0xFE && content[2] == (char)0x00 && content[3] == (char)0x00) + return "UTF-32LE"; /* first two bytes are same for UTF-16LE and UTF-32LE, so first check for full UTF-32LE mark */ + if (content[0] == (char)0xFF && content[1] == (char)0xFE) + return "UTF-16LE"; + if (contentLength < 3) + return ""; + if (content[0] == (char)0xEF && content[1] == (char)0xBB && content[2] == (char)0xBF) + return "UTF-8"; + if (contentLength < 4) + return ""; + if (content[0] == (char)0x00 && content[1] == (char)0x00 && content[2] == (char)0xFE && content[3] == (char)0xFF) + return "UTF-32BE"; + if (contentLength >= 5 && content[0] == (char)0x2B && content[1] == (char)0x2F && content[2] == (char)0x76 && + (content[4] == (char)0x32 || content[4] == (char)0x39 || content[4] == (char)0x2B || content[4] == (char)0x2F)) + return "UTF-7"; + if (content[0] == (char)0x84 && content[1] == (char)0x31 && content[2] == (char)0x95 && content[3] == (char)0x33) + return "GB18030"; + + return ""; +} + +bool CCharsetDetection::DetectXmlEncoding(const char* const xmlContent, const size_t contentLength, std::string& detectedEncoding) +{ + detectedEncoding.clear(); + + if (contentLength < 2) + return false; // too short for any detection + + /* Byte Order Mark has priority over "encoding=" parameter */ + detectedEncoding = GetBomEncoding(xmlContent, contentLength); + if (!detectedEncoding.empty()) + return true; + + /* try to read encoding from XML declaration */ + if (GetXmlEncodingFromDeclaration(xmlContent, contentLength, detectedEncoding)) + { + StringUtils::ToUpper(detectedEncoding); + + /* make some safety checks */ + if (detectedEncoding == "UTF-8") + return true; // fast track for most common case + + if (StringUtils::StartsWith(detectedEncoding, "UCS-") || StringUtils::StartsWith(detectedEncoding, "UTF-")) + { + if (detectedEncoding == "UTF-7") + return true; + + /* XML declaration was detected in UTF-8 mode (by 'GetXmlEncodingFromDeclaration') so we know + * that text in single byte encoding, but declaration itself wrongly specify multibyte encoding */ + detectedEncoding.clear(); + return false; + } + return true; + } + + /* try to detect basic encoding */ + std::string guessedEncoding; + if (!GuessXmlEncoding(xmlContent, contentLength, guessedEncoding)) + return false; /* can't detect any encoding */ + + /* have some guessed encoding, try to use it */ + std::string convertedXml; + /* use 'm_XmlDeclarationMaxLength * 4' below for UTF-32-like encodings */ + if (!g_charsetConverter.ToUtf8(guessedEncoding, std::string(xmlContent, std::min(contentLength, m_XmlDeclarationMaxLength * 4)), convertedXml) + || convertedXml.empty()) + return false; /* can't convert, guessed encoding is wrong */ + + /* text converted, hopefully at least XML declaration is in UTF-8 now */ + std::string declaredEncoding; + /* try to read real encoding from converted XML declaration */ + if (!GetXmlEncodingFromDeclaration(convertedXml.c_str(), convertedXml.length(), declaredEncoding)) + { /* did not find real encoding in XML declaration, use guessed encoding */ + detectedEncoding = guessedEncoding; + return true; + } + + /* found encoding in converted XML declaration, we know correct endianness and number of bytes per char */ + /* make some safety checks */ + StringUtils::ToUpper(declaredEncoding); + if (declaredEncoding == guessedEncoding) + return true; + + if (StringUtils::StartsWith(guessedEncoding, "UCS-4")) + { + if (declaredEncoding.length() < 5 || + (!StringUtils::StartsWith(declaredEncoding, "UTF-32") && !StringUtils::StartsWith(declaredEncoding, "UCS-4"))) + { /* Guessed encoding was correct because we can convert and read XML declaration, but declaration itself is wrong (not 4-bytes encoding) */ + detectedEncoding = guessedEncoding; + return true; + } + } + else if (StringUtils::StartsWith(guessedEncoding, "UTF-16")) + { + if (declaredEncoding.length() < 5 || + (!StringUtils::StartsWith(declaredEncoding, "UTF-16") && !StringUtils::StartsWith(declaredEncoding, "UCS-2"))) + { /* Guessed encoding was correct because we can read XML declaration, but declaration is wrong (not 2-bytes encoding) */ + detectedEncoding = guessedEncoding; + return true; + } + } + + if (StringUtils::StartsWith(guessedEncoding, "UCS-4") || StringUtils::StartsWith(guessedEncoding, "UTF-16")) + { + /* Check endianness in declared encoding. We already know correct endianness as XML declaration was detected after conversion. */ + /* Guessed UTF/UCS encoding always ends with endianness */ + std::string guessedEndianness(guessedEncoding, guessedEncoding.length() - 2); + + if (!StringUtils::EndsWith(declaredEncoding, "BE") && !StringUtils::EndsWith(declaredEncoding, "LE")) /* Declared encoding without endianness */ + detectedEncoding = declaredEncoding + guessedEndianness; /* add guessed endianness */ + else if (!StringUtils::EndsWith(declaredEncoding, guessedEndianness)) /* Wrong endianness in declared encoding */ + detectedEncoding = declaredEncoding.substr(0, declaredEncoding.length() - 2) + guessedEndianness; /* replace endianness by guessed endianness */ + else + detectedEncoding = declaredEncoding; /* declared encoding with correct endianness */ + + return true; + } + else if (StringUtils::StartsWith(guessedEncoding, "EBCDIC")) + { + if (declaredEncoding.find("EBCDIC") != std::string::npos) + detectedEncoding = declaredEncoding; /* Declared encoding is some specific EBCDIC encoding */ + else + detectedEncoding = guessedEncoding; + + return true; + } + + /* should be unreachable */ + return false; +} + +bool CCharsetDetection::GetXmlEncodingFromDeclaration(const char* const xmlContent, const size_t contentLength, std::string& declaredEncoding) +{ + // following code is std::string-processing analog of regular expression-processing + // regular expression: "<\\?xml([ \n\r\t]+[^ \n\t\r>]+)*[ \n\r\t]+encoding[ \n\r\t]*=[ \n\r\t]*('[^ \n\t\r>']+'|\"[^ \n\t\r>\"]+\")" + // on win32 x86 machine regular expression is slower that std::string 20-40 times and can slowdown XML processing for several times + // seems that this regular expression is too slow due to many variable length parts, regexp for '&'-fixing is much faster + + declaredEncoding.clear(); + + // avoid extra large search + std::string strXml(xmlContent, std::min(contentLength, m_XmlDeclarationMaxLength)); + + size_t pos = strXml.find("<?xml"); + if (pos == std::string::npos || pos + 6 > strXml.length() || pos > strXml.find('<')) + return false; // no "<?xml" declaration, "<?xml" is not first element or "<?xml" is incomplete + + pos += 5; // 5 is length of "<?xml" + + const size_t declLength = std::min(std::min(m_XmlDeclarationMaxLength, contentLength - pos), strXml.find('>', pos) - pos); + const std::string xmlDecl(xmlContent + pos, declLength); + const char* const xmlDeclC = xmlDecl.c_str(); // for faster processing of [] and for null-termination + + static const char* const whiteSpaceChars = " \n\r\t"; // according to W3C Recommendation for XML, any of them can be used as separator + pos = 0; + + while (pos + 12 <= declLength) // 12 is minimal length of "encoding='x'" + { + pos = xmlDecl.find_first_of(whiteSpaceChars, pos); + if (pos == std::string::npos) + return false; // no " encoding=" in declaration + + pos = xmlDecl.find_first_not_of(whiteSpaceChars, pos); + if (pos == std::string::npos) + return false; // no "encoding=" in declaration + + if (xmlDecl.compare(pos, 8, "encoding", 8) != 0) + continue; // not "encoding" parameter + pos += 8; // length of "encoding" + + if (xmlDeclC[pos] == ' ' || xmlDeclC[pos] == '\n' || xmlDeclC[pos] == '\r' || xmlDeclC[pos] == '\t') // no buffer overrun as string is null-terminated + { + pos = xmlDecl.find_first_not_of(whiteSpaceChars, pos); + if (pos == std::string::npos) + return false; // this " encoding" is incomplete, only whitespace chars remains + } + if (xmlDeclC[pos] != '=') + { // "encoding" without "=", try to find other + pos--; // step back to whitespace + continue; + } + + pos++; // skip '=' + if (xmlDeclC[pos] == ' ' || xmlDeclC[pos] == '\n' || xmlDeclC[pos] == '\r' || xmlDeclC[pos] == '\t') // no buffer overrun as string is null-terminated + { + pos = xmlDecl.find_first_not_of(whiteSpaceChars, pos); + if (pos == std::string::npos) + return false; // this " encoding" is incomplete, only whitespace chars remains + } + size_t encNameEndPos; + if (xmlDeclC[pos] == '"') + encNameEndPos = xmlDecl.find('"', ++pos); + else if (xmlDeclC[pos] == '\'') + encNameEndPos = xmlDecl.find('\'', ++pos); + else + continue; // no quote or double quote after 'encoding=', try to find other + + if (encNameEndPos != std::string::npos) + { + declaredEncoding.assign(xmlDecl, pos, encNameEndPos - pos); + return true; + } + // no closing quote or double quote after 'encoding="x', try to find other + } + + return false; +} + +bool CCharsetDetection::GuessXmlEncoding(const char* const xmlContent, const size_t contentLength, std::string& supposedEncoding) +{ + supposedEncoding.clear(); + if (contentLength < 4) + return false; // too little data to guess + + if (xmlContent[0] == 0 && xmlContent[1] == 0 && xmlContent[2] == 0 && xmlContent[3] == (char)0x3C) // '<' == '00 00 00 3C' in UCS-4 (UTF-32) big-endian + supposedEncoding = "UCS-4BE"; // use UCS-4 according to W3C recommendation + else if (xmlContent[0] == (char)0x3C && xmlContent[1] == 0 && xmlContent[2] == 0 && xmlContent[3] == 0) // '<' == '3C 00 00 00' in UCS-4 (UTF-32) little-endian + supposedEncoding = "UCS-4LE"; // use UCS-4 according to W3C recommendation + else if (xmlContent[0] == 0 && xmlContent[1] == (char)0x3C && xmlContent[2] == 0 && xmlContent[3] == (char)0x3F) // "<?" == "00 3C 00 3F" in UTF-16 (UCS-2) big-endian + supposedEncoding = "UTF-16BE"; + else if (xmlContent[0] == (char)0x3C && xmlContent[1] == 0 && xmlContent[2] == (char)0x3F && xmlContent[3] == 0) // "<?" == "3C 00 3F 00" in UTF-16 (UCS-2) little-endian + supposedEncoding = "UTF-16LE"; + else if (xmlContent[0] == (char)0x4C && xmlContent[1] == (char)0x6F && xmlContent[2] == (char)0xA7 && xmlContent[3] == (char)0x94) // "<?xm" == "4C 6F A7 94" in most EBCDIC encodings + supposedEncoding = "EBCDIC-CP-US"; // guessed value, real value must be read from declaration + else + return false; + + return true; +} + +bool CCharsetDetection::ConvertHtmlToUtf8(const std::string& htmlContent, std::string& converted, const std::string& serverReportedCharset, std::string& usedHtmlCharset) +{ + converted.clear(); + usedHtmlCharset.clear(); + if (htmlContent.empty()) + { + usedHtmlCharset = "UTF-8"; // any charset can be used for empty content, use UTF-8 as default + return false; + } + + // this is relaxed implementation of http://www.w3.org/TR/2013/CR-html5-20130806/single-page.html#determining-the-character-encoding + + // try to get charset from Byte Order Mark + std::string bomCharset(GetBomEncoding(htmlContent)); + if (checkConversion(bomCharset, htmlContent, converted)) + { + usedHtmlCharset = bomCharset; + return true; + } + + // try charset from HTTP header (or from other out-of-band source) + if (checkConversion(serverReportedCharset, htmlContent, converted)) + { + usedHtmlCharset = serverReportedCharset; + return true; + } + + // try to find charset in HTML + std::string declaredCharset(GetHtmlEncodingFromHead(htmlContent)); + if (!declaredCharset.empty()) + { + if (declaredCharset.compare(0, 3, "UTF", 3) == 0) + declaredCharset = "UTF-8"; // charset string was found in singlebyte mode, charset can't be multibyte encoding + if (checkConversion(declaredCharset, htmlContent, converted)) + { + usedHtmlCharset = declaredCharset; + return true; + } + } + + // try UTF-8 if not tried before + if (bomCharset != "UTF-8" && serverReportedCharset != "UTF-8" && declaredCharset != "UTF-8" && checkConversion("UTF-8", htmlContent, converted)) + { + usedHtmlCharset = "UTF-8"; + return false; // only guessed value + } + + // try user charset + std::string userCharset(g_langInfo.GetGuiCharSet()); + if (checkConversion(userCharset, htmlContent, converted)) + { + usedHtmlCharset = userCharset; + return false; // only guessed value + } + + // try WINDOWS-1252 + if (checkConversion("WINDOWS-1252", htmlContent, converted)) + { + usedHtmlCharset = "WINDOWS-1252"; + return false; // only guessed value + } + + // can't find exact charset + // use one of detected as fallback + if (!bomCharset.empty()) + usedHtmlCharset = bomCharset; + else if (!serverReportedCharset.empty()) + usedHtmlCharset = serverReportedCharset; + else if (!declaredCharset.empty()) + usedHtmlCharset = declaredCharset; + else if (!userCharset.empty()) + usedHtmlCharset = userCharset; + else + usedHtmlCharset = "WINDOWS-1252"; + + CLog::Log(LOGWARNING, "%s: Can't correctly convert to UTF-8 charset, converting as \"%s\"", __FUNCTION__, usedHtmlCharset.c_str()); + g_charsetConverter.ToUtf8(usedHtmlCharset, htmlContent, converted, false); + + return false; +} + +bool CCharsetDetection::ConvertPlainTextToUtf8(const std::string& textContent, std::string& converted, const std::string& serverReportedCharset, std::string& usedCharset) +{ + converted.clear(); + usedCharset.clear(); + if (textContent.empty()) + { + usedCharset = "UTF-8"; // any charset can be used for empty content, use UTF-8 as default + return true; + } + + // try to get charset from Byte Order Mark + std::string bomCharset(GetBomEncoding(textContent)); + if (checkConversion(bomCharset, textContent, converted)) + { + usedCharset = bomCharset; + return true; + } + + // try charset from HTTP header (or from other out-of-band source) + if (checkConversion(serverReportedCharset, textContent, converted)) + { + usedCharset = serverReportedCharset; + return true; + } + + // try UTF-8 if not tried before + if (bomCharset != "UTF-8" && serverReportedCharset != "UTF-8" && checkConversion("UTF-8", textContent, converted)) + { + usedCharset = "UTF-8"; + return true; + } + + // try user charset + std::string userCharset(g_langInfo.GetGuiCharSet()); + if (checkConversion(userCharset, textContent, converted)) + { + usedCharset = userCharset; + return true; + } + + // try system default charset + if (g_charsetConverter.systemToUtf8(textContent, converted, true)) + { + usedCharset = "char"; // synonym to system charset + return true; + } + + // try WINDOWS-1252 + if (checkConversion("WINDOWS-1252", textContent, converted)) + { + usedCharset = "WINDOWS-1252"; + return true; + } + + // can't find correct charset + // use one of detected as fallback + if (!serverReportedCharset.empty()) + usedCharset = serverReportedCharset; + else if (!bomCharset.empty()) + usedCharset = bomCharset; + else if (!userCharset.empty()) + usedCharset = userCharset; + else + usedCharset = "WINDOWS-1252"; + + CLog::Log(LOGWARNING, "%s: Can't correctly convert to UTF-8 charset, converting as \"%s\"", __FUNCTION__, usedCharset.c_str()); + g_charsetConverter.ToUtf8(usedCharset, textContent, converted, false); + + return false; +} + + +bool CCharsetDetection::checkConversion(const std::string& srcCharset, const std::string& src, std::string& dst) +{ + if (srcCharset.empty()) + return false; + + if (srcCharset != "UTF-8") + { + if (g_charsetConverter.ToUtf8(srcCharset, src, dst, true)) + return true; + } + else if (CUtf8Utils::isValidUtf8(src)) + { + dst = src; + return true; + } + + return false; +} + +std::string CCharsetDetection::GetHtmlEncodingFromHead(const std::string& htmlContent) +{ + std::string smallerHtmlContent; + if (htmlContent.length() > 2 * m_HtmlCharsetEndSearchPos) + smallerHtmlContent.assign(htmlContent, 0, 2 * m_HtmlCharsetEndSearchPos); // use twice more bytes to search for charset for safety + + const std::string& html = smallerHtmlContent.empty() ? htmlContent : smallerHtmlContent; // limit search + const char* const htmlC = html.c_str(); // for null-termination + const size_t len = html.length(); + + // this is an implementation of http://www.w3.org/TR/2013/CR-html5-20130806/single-page.html#prescan-a-byte-stream-to-determine-its-encoding + // labels in comments correspond to the labels in HTML5 standard + // note: opposite to standard, everything is converted to uppercase instead of lower case + size_t pos = 0; + while (pos < len) // "loop" label + { + if (html.compare(pos, 4, "<!--", 4) == 0) + { + pos = html.find("-->", pos + 2); + if (pos == std::string::npos) + return ""; + pos += 2; + } + else if (htmlC[pos] == '<' && (htmlC[pos + 1] == 'm' || htmlC[pos + 1] == 'M') && (htmlC[pos + 2] == 'e' || htmlC[pos + 2] == 'E') + && (htmlC[pos + 3] == 't' || htmlC[pos + 3] == 'T') && (htmlC[pos + 4] == 'a' || htmlC[pos + 4] == 'A') + && (htmlC[pos + 5] == 0x09 || htmlC[pos + 5] == 0x0A || htmlC[pos + 5] == 0x0C || htmlC[pos + 5] == 0x0D || htmlC[pos + 5] == 0x20 || htmlC[pos + 5] == 0x2F)) + { // this is case insensitive "<meta" and one of tab, LF, FF, CR, space or slash + pos += 5; // "pos" points to symbol after "<meta" + std::string attrName, attrValue; + bool gotPragma = false; + std::string contentCharset; + do // "attributes" label + { + pos = GetHtmlAttribute(html, pos, attrName, attrValue); + if (attrName == "HTTP-EQUIV" && attrValue == "CONTENT-TYPE") + gotPragma = true; + else if (attrName == "CONTENT") + contentCharset = ExtractEncodingFromHtmlMeta(attrValue); + else if (attrName == "CHARSET") + { + StringUtils::Trim(attrValue, m_HtmlWhitespaceChars.c_str()); // tab, LF, FF, CR, space + if (!attrValue.empty()) + return attrValue; + } + } while (!attrName.empty() && pos < len); + + // "processing" label + if (gotPragma && !contentCharset.empty()) + return contentCharset; + } + else if (htmlC[pos] == '<' && ((htmlC[pos + 1] >= 'A' && htmlC[pos + 1] <= 'Z') || (htmlC[pos + 1] >= 'a' && htmlC[pos + 1] <= 'z'))) + { + pos = html.find_first_of("\x09\x0A\x0C\x0D >", pos); // tab, LF, FF, CR, space or '>' + std::string attrName, attrValue; + do + { + pos = GetHtmlAttribute(html, pos, attrName, attrValue); + } while (pos < len && !attrName.empty()); + } + else if (html.compare(pos, 2, "<!", 2) == 0 || html.compare(pos, 2, "</", 2) == 0 || html.compare(pos, 2, "<?", 2) == 0) + pos = html.find('>', pos); + + if (pos == std::string::npos) + return ""; + + // "next byte" label + pos++; + } + + return ""; // no charset was found +} + +size_t CCharsetDetection::GetHtmlAttribute(const std::string& htmlContent, size_t pos, std::string& attrName, std::string& attrValue) +{ + attrName.clear(); + attrValue.clear(); + static const char* const htmlWhitespaceSlash = "\x09\x0A\x0C\x0D\x20\x2F"; // tab, LF, FF, CR, space or slash + const char* const htmlC = htmlContent.c_str(); + const size_t len = htmlContent.length(); + + // this is an implementation of http://www.w3.org/TR/2013/CR-html5-20130806/single-page.html#concept-get-attributes-when-sniffing + // labels in comments correspond to the labels in HTML5 standard + // note: opposite to standard, everything is converted to uppercase instead of lower case + pos = htmlContent.find_first_not_of(htmlWhitespaceSlash, pos); + if (pos == std::string::npos || htmlC[pos] == '>') + return pos; // only white spaces or slashes up to the end of the htmlContent or no more attributes + + while (pos < len && htmlC[pos] != '=') + { + const char chr = htmlC[pos]; + if (chr == '/' || chr == '>') + return pos; // no attributes or empty attribute value + else if (m_HtmlWhitespaceChars.find(chr) != std::string::npos) // chr is one of whitespaces + { + pos = htmlContent.find_first_not_of(m_HtmlWhitespaceChars, pos); // "spaces" label + if (pos == std::string::npos || htmlC[pos] != '=') + return pos; // only white spaces up to the end or no attribute value + break; + } + else + appendCharAsAsciiUpperCase(attrName, chr); + + pos++; + } + + if (pos >= len) + return std::string::npos; // no '=', '/' or '>' were found up to the end of htmlContent + + pos++; // advance pos to character after '=' + + pos = htmlContent.find_first_not_of(m_HtmlWhitespaceChars, pos); // "value" label + if (pos == std::string::npos) + return pos; // only white spaces remain in htmlContent + + if (htmlC[pos] == '>') + return pos; // empty attribute value + else if (htmlC[pos] == '"' || htmlC[pos] == '\'') + { + const char qChr = htmlC[pos]; + // "quote loop" label + while (++pos < len) + { + const char chr = htmlC[pos]; + if (chr == qChr) + return pos + 1; + else + appendCharAsAsciiUpperCase(attrValue, chr); + } + return std::string::npos; // no closing quote is found + } + + appendCharAsAsciiUpperCase(attrValue, htmlC[pos]); + pos++; + + while (pos < len) + { + const char chr = htmlC[pos]; + if (m_HtmlWhitespaceChars.find(chr) != std::string::npos || chr == '>') + return pos; + else + appendCharAsAsciiUpperCase(attrValue, chr); + + pos++; + } + + return std::string::npos; // rest of htmlContent was attribute value +} + +std::string CCharsetDetection::ExtractEncodingFromHtmlMeta(std::string metaContent, size_t pos /*= 0*/) +{ + size_t len = metaContent.length(); + if (pos >= len) + return ""; + + const char* const metaContentC = metaContent.c_str(); + + // this is an implementation of http://www.w3.org/TR/2013/CR-html5-20130806/single-page.html#algorithm-for-extracting-a-character-encoding-from-a-meta-element + // labels in comments correspond to the labels in HTML5 standard + // note: opposite to standard, case sensitive match is used as argument is always in uppercase + std::string charset; + do + { + // "loop" label + pos = metaContent.find("CHARSET", pos); + if (pos == std::string::npos) + return ""; + + pos = metaContent.find_first_not_of(m_HtmlWhitespaceChars, pos + 7); // '7' is the length of 'CHARSET' + if (pos != std::string::npos && metaContentC[pos] == '=') + { + pos = metaContent.find_first_not_of(m_HtmlWhitespaceChars, pos + 1); + if (pos != std::string::npos) + { + if (metaContentC[pos] == '\'' || metaContentC[pos] == '"') + { + const char qChr = metaContentC[pos]; + pos++; + const size_t closeQpos = metaContent.find(qChr, pos); + if (closeQpos != std::string::npos) + charset.assign(metaContent, pos, closeQpos - pos); + } + else + charset.assign(metaContent, pos, metaContent.find("\x09\x0A\x0C\x0D ;", pos) - pos); // assign content up to the next tab, LF, FF, CR, space, semicolon or end of string + } + break; + } + } while (pos < len); + + static const char* const htmlWhitespaceCharsC = m_HtmlWhitespaceChars.c_str(); + StringUtils::Trim(charset, htmlWhitespaceCharsC); + + return charset; +} + +inline void CCharsetDetection::appendCharAsAsciiUpperCase(std::string& str, const char chr) +{ + if (chr >= 'a' && chr <= 'z') + str.push_back(chr - ('a' - 'A')); // convert to upper case + else + str.push_back(chr); +} diff --git a/src/utils/CharsetDetection.h b/src/utils/CharsetDetection.h new file mode 100644 index 0000000000..171b460053 --- /dev/null +++ b/src/utils/CharsetDetection.h @@ -0,0 +1,106 @@ +#pragma once + +/* +* Copyright (C) 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 +* <http://www.gnu.org/licenses/>. +* +*/ + +#include <string> + + +class CCharsetDetection +{ +public: + /** + * Detect text encoding by Byte Order Mark + * Multibyte encodings (UTF-16/32) always ends with explicit endianness (LE/BE) + * @param content pointer to text to analyze + * @param contentLength length of text + * @return detected encoding or empty string if BOM not detected + */ + static std::string GetBomEncoding(const char* const content, const size_t contentLength); + /** + * Detect text encoding by Byte Order Mark + * Multibyte encodings (UTF-16/32) always ends with explicit endianness (LE/BE) + * @param content the text to analyze + * @return detected encoding or empty string if BOM not detected + */ + static inline std::string GetBomEncoding(const std::string& content) + { return GetBomEncoding(content.c_str(), content.length()); } + + static inline bool DetectXmlEncoding(const std::string& xmlContent, std::string& detectedEncoding) + { return DetectXmlEncoding(xmlContent.c_str(), xmlContent.length(), detectedEncoding); } + + static bool DetectXmlEncoding(const char* const xmlContent, const size_t contentLength, std::string& detectedEncoding); + + /** + * Detect HTML charset and HTML convert to UTF-8 + * @param htmlContent content of HTML file + * @param converted receive result of conversion + * @param serverReportedCharset charset from HTTP header or from other out-of-band source, empty if unknown or unset + * @return true if charset is properly detected and HTML is correctly converted, false if charset is only guessed + */ + static inline bool ConvertHtmlToUtf8(const std::string& htmlContent, std::string& converted, const std::string& serverReportedCharset = "") + { + std::string usedHtmlCharset; + return ConvertHtmlToUtf8(htmlContent, converted, serverReportedCharset, usedHtmlCharset); + } + /** + * Detect HTML charset and HTML convert to UTF-8 + * @param htmlContent content of HTML file + * @param converted receive result of conversion + * @param serverReportedCharset charset from HTTP header or from other out-of-band source, empty if unknown or unset + * @param usedHtmlCharset receive charset used for conversion + * @return true if charset is properly detected and HTML is correctly converted, false if charset is only guessed + */ + static bool ConvertHtmlToUtf8(const std::string& htmlContent, std::string& converted, const std::string& serverReportedCharset, std::string& usedHtmlCharset); + + /** + * Try to convert plain text to UTF-8 using best suitable charset + * @param textContent text to convert + * @param converted receive result of conversion + * @param serverReportedCharset charset from HTTP header or from other out-of-band source, empty if unknown or unset + * @param usedCharset receive charset used for conversion + * @return true if converted without errors, false otherwise + */ + static bool ConvertPlainTextToUtf8(const std::string& textContent, std::string& converted, const std::string& serverReportedCharset, std::string& usedCharset); + +private: + static bool GetXmlEncodingFromDeclaration(const char* const xmlContent, const size_t contentLength, std::string& declaredEncoding); + /** + * Try to guess text encoding by searching for '<?xml' mark in different encodings + * Multibyte encodings (UTF/UCS) always ends with explicit endianness (LE/BE) + * @param content pointer to text to analyze + * @param contentLength length of text + * @param detectedEncoding reference to variable that receive supposed encoding + * @return true if any encoding supposed, false otherwise + */ + static bool GuessXmlEncoding(const char* const xmlContent, const size_t contentLength, std::string& supposedEncoding); + + static std::string GetHtmlEncodingFromHead(const std::string& htmlContent); + static size_t GetHtmlAttribute(const std::string& htmlContent, size_t pos, std::string& atrName, std::string& strValue); + static std::string ExtractEncodingFromHtmlMeta(std::string metaContent, size_t pos = 0); + + static bool checkConversion(const std::string& srcCharset, const std::string& src, std::string& dst); + static void appendCharAsAsciiUpperCase(std::string& str, const char chr); + + static const size_t m_XmlDeclarationMaxLength; + static const size_t m_HtmlCharsetEndSearchPos; + + static const std::string m_HtmlWhitespaceChars; +}; diff --git a/src/utils/Crc32.cpp b/src/utils/Crc32.cpp new file mode 100644 index 0000000000..2607b4088a --- /dev/null +++ b/src/utils/Crc32.cpp @@ -0,0 +1,119 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Crc32.h" +#include "utils/StringUtils.h" + +uint32_t crc_tab[256] = +{ + 0x00000000L, 0x04C11DB7L, 0x09823B6EL, 0x0D4326D9L, + 0x130476DCL, 0x17C56B6BL, 0x1A864DB2L, 0x1E475005L, + 0x2608EDB8L, 0x22C9F00FL, 0x2F8AD6D6L, 0x2B4BCB61L, + 0x350C9B64L, 0x31CD86D3L, 0x3C8EA00AL, 0x384FBDBDL, + 0x4C11DB70L, 0x48D0C6C7L, 0x4593E01EL, 0x4152FDA9L, + 0x5F15ADACL, 0x5BD4B01BL, 0x569796C2L, 0x52568B75L, + 0x6A1936C8L, 0x6ED82B7FL, 0x639B0DA6L, 0x675A1011L, + 0x791D4014L, 0x7DDC5DA3L, 0x709F7B7AL, 0x745E66CDL, + 0x9823B6E0L, 0x9CE2AB57L, 0x91A18D8EL, 0x95609039L, + 0x8B27C03CL, 0x8FE6DD8BL, 0x82A5FB52L, 0x8664E6E5L, + 0xBE2B5B58L, 0xBAEA46EFL, 0xB7A96036L, 0xB3687D81L, + 0xAD2F2D84L, 0xA9EE3033L, 0xA4AD16EAL, 0xA06C0B5DL, + 0xD4326D90L, 0xD0F37027L, 0xDDB056FEL, 0xD9714B49L, + 0xC7361B4CL, 0xC3F706FBL, 0xCEB42022L, 0xCA753D95L, + 0xF23A8028L, 0xF6FB9D9FL, 0xFBB8BB46L, 0xFF79A6F1L, + 0xE13EF6F4L, 0xE5FFEB43L, 0xE8BCCD9AL, 0xEC7DD02DL, + 0x34867077L, 0x30476DC0L, 0x3D044B19L, 0x39C556AEL, + 0x278206ABL, 0x23431B1CL, 0x2E003DC5L, 0x2AC12072L, + 0x128E9DCFL, 0x164F8078L, 0x1B0CA6A1L, 0x1FCDBB16L, + 0x018AEB13L, 0x054BF6A4L, 0x0808D07DL, 0x0CC9CDCAL, + 0x7897AB07L, 0x7C56B6B0L, 0x71159069L, 0x75D48DDEL, + 0x6B93DDDBL, 0x6F52C06CL, 0x6211E6B5L, 0x66D0FB02L, + 0x5E9F46BFL, 0x5A5E5B08L, 0x571D7DD1L, 0x53DC6066L, + 0x4D9B3063L, 0x495A2DD4L, 0x44190B0DL, 0x40D816BAL, + 0xACA5C697L, 0xA864DB20L, 0xA527FDF9L, 0xA1E6E04EL, + 0xBFA1B04BL, 0xBB60ADFCL, 0xB6238B25L, 0xB2E29692L, + 0x8AAD2B2FL, 0x8E6C3698L, 0x832F1041L, 0x87EE0DF6L, + 0x99A95DF3L, 0x9D684044L, 0x902B669DL, 0x94EA7B2AL, + 0xE0B41DE7L, 0xE4750050L, 0xE9362689L, 0xEDF73B3EL, + 0xF3B06B3BL, 0xF771768CL, 0xFA325055L, 0xFEF34DE2L, + 0xC6BCF05FL, 0xC27DEDE8L, 0xCF3ECB31L, 0xCBFFD686L, + 0xD5B88683L, 0xD1799B34L, 0xDC3ABDEDL, 0xD8FBA05AL, + 0x690CE0EEL, 0x6DCDFD59L, 0x608EDB80L, 0x644FC637L, + 0x7A089632L, 0x7EC98B85L, 0x738AAD5CL, 0x774BB0EBL, + 0x4F040D56L, 0x4BC510E1L, 0x46863638L, 0x42472B8FL, + 0x5C007B8AL, 0x58C1663DL, 0x558240E4L, 0x51435D53L, + 0x251D3B9EL, 0x21DC2629L, 0x2C9F00F0L, 0x285E1D47L, + 0x36194D42L, 0x32D850F5L, 0x3F9B762CL, 0x3B5A6B9BL, + 0x0315D626L, 0x07D4CB91L, 0x0A97ED48L, 0x0E56F0FFL, + 0x1011A0FAL, 0x14D0BD4DL, 0x19939B94L, 0x1D528623L, + 0xF12F560EL, 0xF5EE4BB9L, 0xF8AD6D60L, 0xFC6C70D7L, + 0xE22B20D2L, 0xE6EA3D65L, 0xEBA91BBCL, 0xEF68060BL, + 0xD727BBB6L, 0xD3E6A601L, 0xDEA580D8L, 0xDA649D6FL, + 0xC423CD6AL, 0xC0E2D0DDL, 0xCDA1F604L, 0xC960EBB3L, + 0xBD3E8D7EL, 0xB9FF90C9L, 0xB4BCB610L, 0xB07DABA7L, + 0xAE3AFBA2L, 0xAAFBE615L, 0xA7B8C0CCL, 0xA379DD7BL, + 0x9B3660C6L, 0x9FF77D71L, 0x92B45BA8L, 0x9675461FL, + 0x8832161AL, 0x8CF30BADL, 0x81B02D74L, 0x857130C3L, + 0x5D8A9099L, 0x594B8D2EL, 0x5408ABF7L, 0x50C9B640L, + 0x4E8EE645L, 0x4A4FFBF2L, 0x470CDD2BL, 0x43CDC09CL, + 0x7B827D21L, 0x7F436096L, 0x7200464FL, 0x76C15BF8L, + 0x68860BFDL, 0x6C47164AL, 0x61043093L, 0x65C52D24L, + 0x119B4BE9L, 0x155A565EL, 0x18197087L, 0x1CD86D30L, + 0x029F3D35L, 0x065E2082L, 0x0B1D065BL, 0x0FDC1BECL, + 0x3793A651L, 0x3352BBE6L, 0x3E119D3FL, 0x3AD08088L, + 0x2497D08DL, 0x2056CD3AL, 0x2D15EBE3L, 0x29D4F654L, + 0xC5A92679L, 0xC1683BCEL, 0xCC2B1D17L, 0xC8EA00A0L, + 0xD6AD50A5L, 0xD26C4D12L, 0xDF2F6BCBL, 0xDBEE767CL, + 0xE3A1CBC1L, 0xE760D676L, 0xEA23F0AFL, 0xEEE2ED18L, + 0xF0A5BD1DL, 0xF464A0AAL, 0xF9278673L, 0xFDE69BC4L, + 0x89B8FD09L, 0x8D79E0BEL, 0x803AC667L, 0x84FBDBD0L, + 0x9ABC8BD5L, 0x9E7D9662L, 0x933EB0BBL, 0x97FFAD0CL, + 0xAFB010B1L, 0xAB710D06L, 0xA6322BDFL, 0xA2F33668L, + 0xBCB4666DL, 0xB8757BDAL, 0xB5365D03L, 0xB1F740B4L +}; + +Crc32::Crc32() +{ + Reset(); +} + +void Crc32::Reset() +{ + m_crc = 0xFFFFFFFF; +} + +void Crc32::Compute(const char* buffer, size_t count) +{ + while (count--) + m_crc = (m_crc << 8) ^ crc_tab[((m_crc >> 24) ^ *buffer++) & 0xFF]; +} + +void Crc32::Compute(const std::string& strValue) +{ + Compute(strValue.c_str(), strValue.size()); +} + +void Crc32::ComputeFromLowerCase(const std::string& strValue) +{ + std::string strLower = strValue; + StringUtils::ToLower(strLower); + Compute(strLower.c_str(), strLower.size()); +} + diff --git a/src/utils/Crc32.h b/src/utils/Crc32.h new file mode 100644 index 0000000000..3d30c8fac7 --- /dev/null +++ b/src/utils/Crc32.h @@ -0,0 +1,43 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <string> +#include <stdint.h> + +class Crc32 +{ +public: + Crc32(); + void Reset(); + void Compute(const char* buffer, size_t count); + void Compute(const std::string& strValue); + void ComputeFromLowerCase(const std::string& strValue); + + operator uint32_t () const + { + return m_crc; + } + +private: + uint32_t m_crc; +}; + diff --git a/src/utils/CryptThreading.cpp b/src/utils/CryptThreading.cpp new file mode 100644 index 0000000000..49a24e557f --- /dev/null +++ b/src/utils/CryptThreading.cpp @@ -0,0 +1,123 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#ifdef TARGET_WINDOWS +#error "The threading options for the cryptography libraries don't need to be and shouldn't be set on Windows. Do not include CryptThreading in your windows project." +#endif + +#include "CryptThreading.h" +#include "threads/Thread.h" +#include "utils/log.h" + +#if (defined HAVE_CONFIG_H) && (!defined TARGET_WINDOWS) + #include "config.h" +#else +#define HAVE_OPENSSL +#endif + +#ifdef HAVE_OPENSSL +#include <openssl/crypto.h> +#endif + +#ifdef HAVE_GCRYPT +#include <gcrypt.h> +#include <errno.h> + +GCRY_THREAD_OPTION_PTHREAD_IMPL; +#endif + +/* ========================================================================= */ +/* openssl locking implementation for curl */ +static CCriticalSection* getlock(int index) +{ + return g_cryptThreadingInitializer.get_lock(index); +} + +static void lock_callback(int mode, int type, const char* file, int line) +{ + if (mode & 0x01 /* CRYPTO_LOCK from openssl/crypto.h */ ) + getlock(type)->lock(); + else + getlock(type)->unlock(); +} + +static unsigned long thread_id() +{ + return (unsigned long)CThread::GetCurrentThreadId(); +} +/* ========================================================================= */ + +CryptThreadingInitializer::CryptThreadingInitializer() +{ + bool attemptedToSetSSLMTHook = false; +#ifdef HAVE_OPENSSL + // set up OpenSSL + numlocks = CRYPTO_num_locks(); + CRYPTO_set_id_callback(thread_id); + CRYPTO_set_locking_callback(lock_callback); + attemptedToSetSSLMTHook = true; +#else + numlocks = 1; +#endif + + locks = new CCriticalSection*[numlocks]; + for (int i = 0; i < numlocks; i++) + locks[i] = NULL; + +#ifdef HAVE_GCRYPT + // set up gcrypt + gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + attemptedToSetSSLMTHook = true; +#endif + + if (!attemptedToSetSSLMTHook) + CLog::Log(LOGWARNING, "Could not determine the libcurl security library to set the locking scheme. This may cause problem with multithreaded use of ssl or libraries that depend on it (libcurl)."); + +} + +CryptThreadingInitializer::~CryptThreadingInitializer() +{ + CSingleLock l(locksLock); +#ifdef HAVE_OPENSSL + CRYPTO_set_locking_callback(NULL); +#endif + + for (int i = 0; i < numlocks; i++) + delete locks[i]; // I always forget ... delete is NULL safe. + + delete [] locks; +} + +CCriticalSection* CryptThreadingInitializer::get_lock(int index) +{ + CSingleLock l(locksLock); + CCriticalSection* curlock = locks[index]; + if (curlock == NULL) + { + curlock = new CCriticalSection(); + locks[index] = curlock; + } + + return curlock; +} + + + + diff --git a/src/utils/CryptThreading.h b/src/utils/CryptThreading.h new file mode 100644 index 0000000000..32a4c0743b --- /dev/null +++ b/src/utils/CryptThreading.h @@ -0,0 +1,44 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include "utils/GlobalsHandling.h" +#include "threads/CriticalSection.h" + +class CryptThreadingInitializer +{ + CCriticalSection** locks; + int numlocks; + CCriticalSection locksLock; + +public: + CryptThreadingInitializer(); + ~CryptThreadingInitializer(); + + CCriticalSection* get_lock(int index); + +private: + CryptThreadingInitializer(const CryptThreadingInitializer &rhs); + CryptThreadingInitializer& operator=(const CryptThreadingInitializer&); +}; + +XBMC_GLOBAL_REF(CryptThreadingInitializer,g_cryptThreadingInitializer); +#define g_cryptThreadingInitializer XBMC_GLOBAL_USE(CryptThreadingInitializer) diff --git a/src/utils/DatabaseUtils.cpp b/src/utils/DatabaseUtils.cpp new file mode 100644 index 0000000000..f5b7ca6f10 --- /dev/null +++ b/src/utils/DatabaseUtils.cpp @@ -0,0 +1,658 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <sstream> + +#include "DatabaseUtils.h" +#include "dbwrappers/dataset.h" +#include "music/MusicDatabase.h" +#include "utils/log.h" +#include "utils/Variant.h" +#include "utils/StringUtils.h" +#include "video/VideoDatabase.h" + +MediaType DatabaseUtils::MediaTypeFromVideoContentType(int videoContentType) +{ + VIDEODB_CONTENT_TYPE type = (VIDEODB_CONTENT_TYPE)videoContentType; + switch (type) + { + case VIDEODB_CONTENT_MOVIES: + return MediaTypeMovie; + + case VIDEODB_CONTENT_MOVIE_SETS: + return MediaTypeVideoCollection; + + case VIDEODB_CONTENT_TVSHOWS: + return MediaTypeTvShow; + + case VIDEODB_CONTENT_EPISODES: + return MediaTypeEpisode; + + case VIDEODB_CONTENT_MUSICVIDEOS: + return MediaTypeMusicVideo; + + default: + break; + } + + return MediaTypeNone; +} + +std::string DatabaseUtils::GetField(Field field, const MediaType &mediaType, DatabaseQueryPart queryPart) +{ + if (field == FieldNone || mediaType == MediaTypeNone) + return ""; + + if (mediaType == MediaTypeAlbum) + { + if (field == FieldId) return "albumview.idAlbum"; + else if (field == FieldAlbum) return "albumview.strAlbum"; + else if (field == FieldArtist || field == FieldAlbumArtist) return "albumview.strArtists"; + else if (field == FieldGenre) return "albumview.strGenre"; + else if (field == FieldYear) return "albumview.iYear"; + else if (field == FieldMoods) return "albumview.strMoods"; + else if (field == FieldStyles) return "albumview.strStyles"; + else if (field == FieldThemes) return "albumview.strThemes"; + else if (field == FieldReview) return "albumview.strReview"; + else if (field == FieldMusicLabel) return "albumview.strLabel"; + else if (field == FieldAlbumType) return "albumview.strType"; + else if (field == FieldRating) return "albumview.iRating"; + else if (field == FieldDateAdded && queryPart == DatabaseQueryPartOrderBy) return "albumview.idalbum"; // only used for order clauses + else if (field == FieldPlaycount) return "albumview.iTimesPlayed"; + } + else if (mediaType == MediaTypeSong) + { + if (field == FieldId) return "songview.idSong"; + else if (field == FieldTitle) return "songview.strTitle"; + else if (field == FieldTrackNumber) return "songview.iTrack"; + else if (field == FieldTime) return "songview.iDuration"; + else if (field == FieldYear) return "songview.iYear"; + else if (field == FieldFilename) return "songview.strFilename"; + else if (field == FieldPlaycount) return "songview.iTimesPlayed"; + else if (field == FieldStartOffset) return "songview.iStartOffset"; + else if (field == FieldEndOffset) return "songview.iEndOffset"; + else if (field == FieldLastPlayed) return "songview.lastPlayed"; + else if (field == FieldRating) return "songview.rating"; + else if (field == FieldComment) return "songview.comment"; + else if (field == FieldAlbum) return "songview.strAlbum"; + else if (field == FieldPath) return "songview.strPath"; + else if (field == FieldArtist || field == FieldAlbumArtist) return "songview.strArtists"; + else if (field == FieldGenre) return "songview.strGenre"; + else if (field == FieldDateAdded && queryPart == DatabaseQueryPartOrderBy) return "songview.idSong"; // only used for order clauses + } + else if (mediaType == MediaTypeArtist) + { + if (field == FieldId) return "artistview.idArtist"; + else if (field == FieldArtist) return "artistview.strArtist"; + else if (field == FieldGenre) return "artistview.strGenres"; + else if (field == FieldMoods) return "artistview.strMoods"; + else if (field == FieldStyles) return "artistview.strStyles"; + else if (field == FieldInstruments) return "artistview.strInstruments"; + else if (field == FieldBiography) return "artistview.strBiography"; + else if (field == FieldBorn) return "artistview.strBorn"; + else if (field == FieldBandFormed) return "artistview.strFormed"; + else if (field == FieldDisbanded) return "artistview.strDisbanded"; + else if (field == FieldDied) return "artistview.strDied"; + } + else if (mediaType == MediaTypeMusicVideo) + { + CStdString result; + if (field == FieldId) return "musicvideoview.idMVideo"; + else if (field == FieldTitle) result = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_TITLE); + else if (field == FieldTime) result = StringUtils::Format("musicvideoview.c%02d", VIDEODB_ID_MUSICVIDEO_RUNTIME); + else if (field == FieldDirector) result = StringUtils::Format("musicvideoview.c%02d", VIDEODB_ID_MUSICVIDEO_DIRECTOR); + else if (field == FieldStudio) result = StringUtils::Format("musicvideoview.c%02d", VIDEODB_ID_MUSICVIDEO_STUDIOS); + else if (field == FieldYear) result = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_YEAR); + else if (field == FieldPlot) result = StringUtils::Format("musicvideoview.c%02d", VIDEODB_ID_MUSICVIDEO_PLOT); + else if (field == FieldAlbum) result = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_ALBUM); + else if (field == FieldArtist) result = StringUtils::Format("musicvideoview.c%02d", VIDEODB_ID_MUSICVIDEO_ARTIST); + else if (field == FieldGenre) result = StringUtils::Format("musicvideoview.c%02d", VIDEODB_ID_MUSICVIDEO_GENRE); + else if (field == FieldTrackNumber) result = StringUtils::Format("musicvideoview.c%02d", VIDEODB_ID_MUSICVIDEO_TRACK); + else if (field == FieldFilename) return "musicvideoview.strFilename"; + else if (field == FieldPath) return "musicvideoview.strPath"; + else if (field == FieldPlaycount) return "musicvideoview.playCount"; + else if (field == FieldLastPlayed) return "musicvideoview.lastPlayed"; + else if (field == FieldDateAdded) return "musicvideoview.dateAdded"; + + if (!result.empty()) + return result; + } + else if (mediaType == MediaTypeMovie) + { + CStdString result; + if (field == FieldId) return "movieview.idMovie"; + else if (field == FieldTitle) + { + // We need some extra logic to get the title value if sorttitle isn't set + if (queryPart == DatabaseQueryPartOrderBy) + result = StringUtils::Format("CASE WHEN length(movieview.c%02d) > 0 THEN movieview.c%02d ELSE movieview.c%02d END", VIDEODB_ID_SORTTITLE, VIDEODB_ID_SORTTITLE, VIDEODB_ID_TITLE); + else + result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_TITLE); + } + else if (field == FieldPlot) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_PLOT); + else if (field == FieldPlotOutline) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_PLOTOUTLINE); + else if (field == FieldTagline) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_TAGLINE); + else if (field == FieldVotes) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_VOTES); + else if (field == FieldRating) + { + if (queryPart == DatabaseQueryPartOrderBy) + result = StringUtils::Format("CAST(movieview.c%02d as DECIMAL(5,3))", VIDEODB_ID_RATING); + else + result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_RATING); + } + else if (field == FieldWriter) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_CREDITS); + else if (field == FieldYear) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_YEAR); + else if (field == FieldSortTitle) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_SORTTITLE); + else if (field == FieldTime) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_RUNTIME); + else if (field == FieldMPAA) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_MPAA); + else if (field == FieldTop250) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_TOP250); + else if (field == FieldSet) return "movieview.strSet"; + else if (field == FieldGenre) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_GENRE); + else if (field == FieldDirector) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_DIRECTOR); + else if (field == FieldStudio) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_STUDIOS); + else if (field == FieldTrailer) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_TRAILER); + else if (field == FieldCountry) result = StringUtils::Format("movieview.c%02d", VIDEODB_ID_COUNTRY); + else if (field == FieldFilename) return "movieview.strFilename"; + else if (field == FieldPath) return "movieview.strPath"; + else if (field == FieldPlaycount) return "movieview.playCount"; + else if (field == FieldLastPlayed) return "movieview.lastPlayed"; + else if (field == FieldDateAdded) return "movieview.dateAdded"; + + if (!result.empty()) + return result; + } + else if (mediaType == MediaTypeTvShow) + { + CStdString result; + if (field == FieldId) return "tvshowview.idShow"; + else if (field == FieldTitle) + { + // We need some extra logic to get the title value if sorttitle isn't set + if (queryPart == DatabaseQueryPartOrderBy) + result = StringUtils::Format("CASE WHEN length(tvshowview.c%02d) > 0 THEN tvshowview.c%02d ELSE tvshowview.c%02d END", VIDEODB_ID_TV_SORTTITLE, VIDEODB_ID_TV_SORTTITLE, VIDEODB_ID_TV_TITLE); + else + result = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_TITLE); + } + else if (field == FieldPlot) result = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_PLOT); + else if (field == FieldTvShowStatus) result = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_STATUS); + else if (field == FieldVotes) result = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_VOTES); + else if (field == FieldRating) result = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_RATING); + else if (field == FieldYear) result = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_PREMIERED); + else if (field == FieldGenre) result = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_GENRE); + else if (field == FieldMPAA) result = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_MPAA); + else if (field == FieldStudio) result = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_STUDIOS); + else if (field == FieldSortTitle) result = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_SORTTITLE); + else if (field == FieldPath) return "tvshowview.strPath"; + else if (field == FieldDateAdded) return "tvshowview.dateAdded"; + else if (field == FieldLastPlayed) return "tvshowview.lastPlayed"; + else if (field == FieldSeason) return "tvshowview.totalSeasons"; + else if (field == FieldNumberOfEpisodes) return "tvshowview.totalCount"; + else if (field == FieldNumberOfWatchedEpisodes) return "tvshowview.watchedcount"; + + if (!result.empty()) + return result; + } + else if (mediaType == MediaTypeEpisode) + { + CStdString result; + if (field == FieldId) return "episodeview.idEpisode"; + else if (field == FieldTitle) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_TITLE); + else if (field == FieldPlot) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_PLOT); + else if (field == FieldVotes) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_VOTES); + else if (field == FieldRating) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_RATING); + else if (field == FieldWriter) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_CREDITS); + else if (field == FieldAirDate) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_AIRED); + else if (field == FieldTime) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_RUNTIME); + else if (field == FieldDirector) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_DIRECTOR); + else if (field == FieldSeason) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_SEASON); + else if (field == FieldEpisodeNumber) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_EPISODE); + else if (field == FieldUniqueId) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_UNIQUEID); + else if (field == FieldEpisodeNumberSpecialSort) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_SORTEPISODE); + else if (field == FieldSeasonSpecialSort) result = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_SORTSEASON); + else if (field == FieldFilename) return "episodeview.strFilename"; + else if (field == FieldPath) return "episodeview.strPath"; + else if (field == FieldPlaycount) return "episodeview.playCount"; + else if (field == FieldLastPlayed) return "episodeview.lastPlayed"; + else if (field == FieldDateAdded) return "episodeview.dateAdded"; + else if (field == FieldTvShowTitle) return "episodeview.strTitle"; + else if (field == FieldYear) return "episodeview.premiered"; + else if (field == FieldMPAA) return "episodeview.mpaa"; + else if (field == FieldStudio) return "episodeview.strStudio"; + + if (!result.empty()) + return result; + } + + if (field == FieldRandom && queryPart == DatabaseQueryPartOrderBy) + return "RANDOM()"; + + return ""; +} + +int DatabaseUtils::GetField(Field field, const MediaType &mediaType) +{ + if (field == FieldNone || mediaType == MediaTypeNone) + return -1; + + return GetField(field, mediaType, false); +} + +int DatabaseUtils::GetFieldIndex(Field field, const MediaType &mediaType) +{ + if (field == FieldNone || mediaType == MediaTypeNone) + return -1; + + return GetField(field, mediaType, true); +} + +bool DatabaseUtils::GetSelectFields(const Fields &fields, const MediaType &mediaType, FieldList &selectFields) +{ + if (mediaType == MediaTypeNone || fields.empty()) + return false; + + Fields sortFields = fields; + + // add necessary fields to create the label + if (mediaType == MediaTypeSong || mediaType == MediaTypeVideo || mediaType == MediaTypeVideoCollection || + mediaType == MediaTypeMusicVideo || mediaType == MediaTypeMovie || mediaType == MediaTypeTvShow || mediaType == MediaTypeEpisode) + sortFields.insert(FieldTitle); + if (mediaType == MediaTypeEpisode) + { + sortFields.insert(FieldSeason); + sortFields.insert(FieldEpisodeNumber); + } + else if (mediaType == MediaTypeAlbum) + sortFields.insert(FieldAlbum); + else if (mediaType == MediaTypeSong) + sortFields.insert(FieldTrackNumber); + else if (mediaType == MediaTypeArtist) + sortFields.insert(FieldArtist); + + selectFields.clear(); + for (Fields::const_iterator it = sortFields.begin(); it != sortFields.end(); ++it) + { + // ignore FieldLabel because it needs special handling (see further up) + if (*it == FieldLabel) + continue; + + if (GetField(*it, mediaType, DatabaseQueryPartSelect).empty()) + { + CLog::Log(LOGDEBUG, "DatabaseUtils::GetSortFieldList: unknown field %d", *it); + continue; + } + selectFields.push_back(*it); + } + + return !selectFields.empty(); +} + +bool DatabaseUtils::GetFieldValue(const dbiplus::field_value &fieldValue, CVariant &variantValue) +{ + if (fieldValue.get_isNull()) + { + variantValue = CVariant::ConstNullVariant; + return true; + } + + switch (fieldValue.get_fType()) + { + case dbiplus::ft_String: + case dbiplus::ft_WideString: + case dbiplus::ft_Object: + variantValue = fieldValue.get_asString(); + return true; + case dbiplus::ft_Char: + case dbiplus::ft_WChar: + variantValue = fieldValue.get_asChar(); + return true; + case dbiplus::ft_Boolean: + variantValue = fieldValue.get_asBool(); + return true; + case dbiplus::ft_Short: + variantValue = fieldValue.get_asShort(); + return true; + case dbiplus::ft_UShort: + variantValue = fieldValue.get_asShort(); + return true; + case dbiplus::ft_Int: + variantValue = fieldValue.get_asInt(); + return true; + case dbiplus::ft_UInt: + variantValue = fieldValue.get_asUInt(); + return true; + case dbiplus::ft_Float: + variantValue = fieldValue.get_asFloat(); + return true; + case dbiplus::ft_Double: + case dbiplus::ft_LongDouble: + variantValue = fieldValue.get_asDouble(); + return true; + case dbiplus::ft_Int64: + variantValue = fieldValue.get_asInt64(); + return true; + } + + return false; +} + +bool DatabaseUtils::GetDatabaseResults(const MediaType &mediaType, const FieldList &fields, const std::auto_ptr<dbiplus::Dataset> &dataset, DatabaseResults &results) +{ + if (dataset->num_rows() == 0) + return true; + + const dbiplus::result_set &resultSet = dataset->get_result_set(); + unsigned int offset = results.size(); + + if (fields.empty()) + { + DatabaseResult result; + for (unsigned int index = 0; index < resultSet.records.size(); index++) + { + result[FieldRow] = index + offset; + results.push_back(result); + } + + return true; + } + + if (resultSet.record_header.size() < fields.size()) + return false; + + std::vector<int> fieldIndexLookup; + fieldIndexLookup.reserve(fields.size()); + for (FieldList::const_iterator it = fields.begin(); it != fields.end(); ++it) + fieldIndexLookup.push_back(GetFieldIndex(*it, mediaType)); + + results.reserve(resultSet.records.size() + offset); + for (unsigned int index = 0; index < resultSet.records.size(); index++) + { + DatabaseResult result; + result[FieldRow] = index + offset; + + unsigned int lookupIndex = 0; + for (FieldList::const_iterator it = fields.begin(); it != fields.end(); ++it) + { + int fieldIndex = fieldIndexLookup[lookupIndex++]; + if (fieldIndex < 0) + return false; + + std::pair<Field, CVariant> value; + value.first = *it; + if (!GetFieldValue(resultSet.records[index]->at(fieldIndex), value.second)) + CLog::Log(LOGWARNING, "GetDatabaseResults: unable to retrieve value of field %s", resultSet.record_header[fieldIndex].name.c_str()); + + if (value.first == FieldYear && + (mediaType == MediaTypeTvShow || mediaType == MediaTypeEpisode)) + { + CDateTime dateTime; + dateTime.SetFromDBDate(value.second.asString()); + if (dateTime.IsValid()) + { + value.second.clear(); + value.second = dateTime.GetYear(); + } + } + + result.insert(value); + } + + result[FieldMediaType] = mediaType; + if (mediaType == MediaTypeMovie || mediaType == MediaTypeVideoCollection || + mediaType == MediaTypeTvShow || mediaType == MediaTypeMusicVideo) + result[FieldLabel] = result.at(FieldTitle).asString(); + else if (mediaType == MediaTypeEpisode) + { + std::ostringstream label; + label << (int)(result.at(FieldSeason).asInteger() * 100 + result.at(FieldEpisodeNumber).asInteger()); + label << ". "; + label << result.at(FieldTitle).asString(); + result[FieldLabel] = label.str(); + } + else if (mediaType == MediaTypeAlbum) + result[FieldLabel] = result.at(FieldAlbum).asString(); + else if (mediaType == MediaTypeSong) + { + std::ostringstream label; + label << (int)result.at(FieldTrackNumber).asInteger(); + label << ". "; + label << result.at(FieldTitle).asString(); + result[FieldLabel] = label.str(); + } + else if (mediaType == MediaTypeArtist) + result[FieldLabel] = result.at(FieldArtist).asString(); + + results.push_back(result); + } + + return true; +} + +std::string DatabaseUtils::BuildLimitClause(int end, int start /* = 0 */) +{ + std::ostringstream sql; + sql << " LIMIT "; + if (start > 0) + { + if (end > 0) + { + end = end - start; + if (end < 0) + end = 0; + } + + sql << start << "," << end; + } + else + sql << end; + + return sql.str(); +} + +int DatabaseUtils::GetField(Field field, const MediaType &mediaType, bool asIndex) +{ + if (field == FieldNone || mediaType == MediaTypeNone) + return -1; + + int index = -1; + + if (mediaType == MediaTypeAlbum) + { + if (field == FieldId) return CMusicDatabase::album_idAlbum; + else if (field == FieldAlbum) return CMusicDatabase::album_strAlbum; + else if (field == FieldArtist || field == FieldAlbumArtist) return CMusicDatabase::album_strArtists; + else if (field == FieldGenre) return CMusicDatabase::album_strGenres; + else if (field == FieldYear) return CMusicDatabase::album_iYear; + else if (field == FieldMoods) return CMusicDatabase::album_strMoods; + else if (field == FieldStyles) return CMusicDatabase::album_strStyles; + else if (field == FieldThemes) return CMusicDatabase::album_strThemes; + else if (field == FieldReview) return CMusicDatabase::album_strReview; + else if (field == FieldMusicLabel) return CMusicDatabase::album_strLabel; + else if (field == FieldAlbumType) return CMusicDatabase::album_strType; + else if (field == FieldRating) return CMusicDatabase::album_iRating; + else if (field == FieldPlaycount) return CMusicDatabase::album_iTimesPlayed; + } + else if (mediaType == MediaTypeSong) + { + if (field == FieldId) return CMusicDatabase::song_idSong; + else if (field == FieldTitle) return CMusicDatabase::song_strTitle; + else if (field == FieldTrackNumber) return CMusicDatabase::song_iTrack; + else if (field == FieldTime) return CMusicDatabase::song_iDuration; + else if (field == FieldYear) return CMusicDatabase::song_iYear; + else if (field == FieldFilename) return CMusicDatabase::song_strFileName; + else if (field == FieldPlaycount) return CMusicDatabase::song_iTimesPlayed; + else if (field == FieldStartOffset) return CMusicDatabase::song_iStartOffset; + else if (field == FieldEndOffset) return CMusicDatabase::song_iEndOffset; + else if (field == FieldLastPlayed) return CMusicDatabase::song_lastplayed; + else if (field == FieldRating) return CMusicDatabase::song_rating; + else if (field == FieldComment) return CMusicDatabase::song_comment; + else if (field == FieldAlbum) return CMusicDatabase::song_strAlbum; + else if (field == FieldPath) return CMusicDatabase::song_strPath; + else if (field == FieldGenre) return CMusicDatabase::song_strGenres; + else if (field == FieldArtist || field == FieldAlbumArtist) return CMusicDatabase::song_strArtists; + } + else if (mediaType == MediaTypeArtist) + { + if (field == FieldId) return CMusicDatabase::artist_idArtist; + else if (field == FieldArtist) return CMusicDatabase::artist_strArtist; + else if (field == FieldGenre) return CMusicDatabase::artist_strGenres; + else if (field == FieldMoods) return CMusicDatabase::artist_strMoods; + else if (field == FieldStyles) return CMusicDatabase::artist_strStyles; + else if (field == FieldInstruments) return CMusicDatabase::artist_strInstruments; + else if (field == FieldBiography) return CMusicDatabase::artist_strBiography; + else if (field == FieldBorn) return CMusicDatabase::artist_strBorn; + else if (field == FieldBandFormed) return CMusicDatabase::artist_strFormed; + else if (field == FieldDisbanded) return CMusicDatabase::artist_strDisbanded; + else if (field == FieldDied) return CMusicDatabase::artist_strDied; + } + else if (mediaType == MediaTypeMusicVideo) + { + if (field == FieldId) return 0; + else if (field == FieldTitle) index = VIDEODB_ID_MUSICVIDEO_TITLE; + else if (field == FieldTime) index = VIDEODB_ID_MUSICVIDEO_RUNTIME; + else if (field == FieldDirector) index = VIDEODB_ID_MUSICVIDEO_DIRECTOR; + else if (field == FieldStudio) index = VIDEODB_ID_MUSICVIDEO_STUDIOS; + else if (field == FieldYear) index = VIDEODB_ID_MUSICVIDEO_YEAR; + else if (field == FieldPlot) index = VIDEODB_ID_MUSICVIDEO_PLOT; + else if (field == FieldAlbum) index = VIDEODB_ID_MUSICVIDEO_ALBUM; + else if (field == FieldArtist) index = VIDEODB_ID_MUSICVIDEO_ARTIST; + else if (field == FieldGenre) index = VIDEODB_ID_MUSICVIDEO_GENRE; + else if (field == FieldTrackNumber) index = VIDEODB_ID_MUSICVIDEO_TRACK; + else if (field == FieldFilename) return VIDEODB_DETAILS_MUSICVIDEO_FILE; + else if (field == FieldPath) return VIDEODB_DETAILS_MUSICVIDEO_PATH; + else if (field == FieldPlaycount) return VIDEODB_DETAILS_MUSICVIDEO_PLAYCOUNT; + else if (field == FieldLastPlayed) return VIDEODB_DETAILS_MUSICVIDEO_LASTPLAYED; + else if (field == FieldDateAdded) return VIDEODB_DETAILS_MUSICVIDEO_DATEADDED; + + if (index < 0) + return index; + + if (asIndex) + { + // see VideoDatabase.h + // the first field is the item's ID and the second is the item's file ID + index += 2; + } + } + else if (mediaType == MediaTypeMovie) + { + if (field == FieldId) return 0; + else if (field == FieldTitle) index = VIDEODB_ID_TITLE; + else if (field == FieldSortTitle) index = VIDEODB_ID_SORTTITLE; + else if (field == FieldPlot) index = VIDEODB_ID_PLOT; + else if (field == FieldPlotOutline) index = VIDEODB_ID_PLOTOUTLINE; + else if (field == FieldTagline) index = VIDEODB_ID_TAGLINE; + else if (field == FieldVotes) index = VIDEODB_ID_VOTES; + else if (field == FieldRating) index = VIDEODB_ID_RATING; + else if (field == FieldWriter) index = VIDEODB_ID_CREDITS; + else if (field == FieldYear) index = VIDEODB_ID_YEAR; + else if (field == FieldTime) index = VIDEODB_ID_RUNTIME; + else if (field == FieldMPAA) index = VIDEODB_ID_MPAA; + else if (field == FieldTop250) index = VIDEODB_ID_TOP250; + else if (field == FieldSet) return VIDEODB_DETAILS_MOVIE_SET_NAME; + else if (field == FieldGenre) index = VIDEODB_ID_GENRE; + else if (field == FieldDirector) index = VIDEODB_ID_DIRECTOR; + else if (field == FieldStudio) index = VIDEODB_ID_STUDIOS; + else if (field == FieldTrailer) index = VIDEODB_ID_TRAILER; + else if (field == FieldCountry) index = VIDEODB_ID_COUNTRY; + else if (field == FieldFilename) index = VIDEODB_DETAILS_MOVIE_FILE; + else if (field == FieldPath) return VIDEODB_DETAILS_MOVIE_PATH; + else if (field == FieldPlaycount) return VIDEODB_DETAILS_MOVIE_PLAYCOUNT; + else if (field == FieldLastPlayed) return VIDEODB_DETAILS_MOVIE_LASTPLAYED; + else if (field == FieldDateAdded) return VIDEODB_DETAILS_MOVIE_DATEADDED; + + if (index < 0) + return index; + + if (asIndex) + { + // see VideoDatabase.h + // the first field is the item's ID and the second is the item's file ID + index += 2; + } + } + else if (mediaType == MediaTypeTvShow) + { + if (field == FieldId) return 0; + else if (field == FieldTitle) index = VIDEODB_ID_TV_TITLE; + else if (field == FieldSortTitle) index = VIDEODB_ID_TV_SORTTITLE; + else if (field == FieldPlot) index = VIDEODB_ID_TV_PLOT; + else if (field == FieldTvShowStatus) index = VIDEODB_ID_TV_STATUS; + else if (field == FieldVotes) index = VIDEODB_ID_TV_VOTES; + else if (field == FieldRating) index = VIDEODB_ID_TV_RATING; + else if (field == FieldYear) index = VIDEODB_ID_TV_PREMIERED; + else if (field == FieldGenre) index = VIDEODB_ID_TV_GENRE; + else if (field == FieldMPAA) index = VIDEODB_ID_TV_MPAA; + else if (field == FieldStudio) index = VIDEODB_ID_TV_STUDIOS; + else if (field == FieldPath) return VIDEODB_DETAILS_TVSHOW_PATH; + else if (field == FieldDateAdded) return VIDEODB_DETAILS_TVSHOW_DATEADDED; + else if (field == FieldLastPlayed) return VIDEODB_DETAILS_TVSHOW_LASTPLAYED; + else if (field == FieldNumberOfEpisodes) return VIDEODB_DETAILS_TVSHOW_NUM_EPISODES; + else if (field == FieldNumberOfWatchedEpisodes) return VIDEODB_DETAILS_TVSHOW_NUM_WATCHED; + else if (field == FieldSeason) return VIDEODB_DETAILS_TVSHOW_NUM_SEASONS; + + if (index < 0) + return index; + + if (asIndex) + { + // see VideoDatabase.h + // the first field is the item's ID + index += 1; + } + } + else if (mediaType == MediaTypeEpisode) + { + if (field == FieldId) return 0; + else if (field == FieldTitle) index = VIDEODB_ID_EPISODE_TITLE; + else if (field == FieldPlot) index = VIDEODB_ID_EPISODE_PLOT; + else if (field == FieldVotes) index = VIDEODB_ID_EPISODE_VOTES; + else if (field == FieldRating) index = VIDEODB_ID_EPISODE_RATING; + else if (field == FieldWriter) index = VIDEODB_ID_EPISODE_CREDITS; + else if (field == FieldAirDate) index = VIDEODB_ID_EPISODE_AIRED; + else if (field == FieldTime) index = VIDEODB_ID_EPISODE_RUNTIME; + else if (field == FieldDirector) index = VIDEODB_ID_EPISODE_DIRECTOR; + else if (field == FieldSeason) index = VIDEODB_ID_EPISODE_SEASON; + else if (field == FieldEpisodeNumber) index = VIDEODB_ID_EPISODE_EPISODE; + else if (field == FieldUniqueId) index = VIDEODB_ID_EPISODE_UNIQUEID; + else if (field == FieldEpisodeNumberSpecialSort) index = VIDEODB_ID_EPISODE_SORTEPISODE; + else if (field == FieldSeasonSpecialSort) index = VIDEODB_ID_EPISODE_SORTSEASON; + else if (field == FieldFilename) return VIDEODB_DETAILS_EPISODE_FILE; + else if (field == FieldPath) return VIDEODB_DETAILS_EPISODE_PATH; + else if (field == FieldPlaycount) return VIDEODB_DETAILS_EPISODE_PLAYCOUNT; + else if (field == FieldLastPlayed) return VIDEODB_DETAILS_EPISODE_LASTPLAYED; + else if (field == FieldDateAdded) return VIDEODB_DETAILS_EPISODE_DATEADDED; + else if (field == FieldTvShowTitle) return VIDEODB_DETAILS_EPISODE_TVSHOW_NAME; + else if (field == FieldStudio) return VIDEODB_DETAILS_EPISODE_TVSHOW_STUDIO; + else if (field == FieldYear) return VIDEODB_DETAILS_EPISODE_TVSHOW_AIRED; + else if (field == FieldMPAA) return VIDEODB_DETAILS_EPISODE_TVSHOW_MPAA; + + if (index < 0) + return index; + + if (asIndex) + { + // see VideoDatabase.h + // the first field is the item's ID and the second is the item's file ID + index += 2; + } + } + + return index; +} diff --git a/src/utils/DatabaseUtils.h b/src/utils/DatabaseUtils.h new file mode 100644 index 0000000000..3ac9f36183 --- /dev/null +++ b/src/utils/DatabaseUtils.h @@ -0,0 +1,161 @@ +#pragma once +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "media/MediaType.h" + +class CVariant; + +namespace dbiplus +{ + class Dataset; + class field_value; +} + +typedef enum { + // special fields used during sorting + FieldUnknown = -1, + FieldNone = 0, + FieldSort, // used to store the string to use for sorting + FieldSortSpecial, // whether the item needs special handling (0 = no, 1 = sort on top, 2 = sort on bottom) + FieldLabel, + FieldFolder, + FieldMediaType, + FieldRow, // the row number in a dataset + + // special fields not retrieved from the database + FieldSize, + FieldDate, + FieldDriveType, + FieldStartOffset, + FieldEndOffset, + FieldProgramCount, + FieldBitrate, + FieldListeners, + FieldPlaylist, + FieldVirtualFolder, + FieldRandom, + FieldDateTaken, + + // fields retrievable from the database + FieldId, + FieldGenre, + FieldAlbum, + FieldArtist, + FieldAlbumArtist, + FieldTitle, + FieldSortTitle, + FieldYear, + FieldTime, + FieldTrackNumber, + FieldFilename, + FieldPath, + FieldPlaycount, + FieldLastPlayed, + FieldInProgress, + FieldRating, + FieldComment, + FieldDateAdded, + FieldTvShowTitle, + FieldPlot, + FieldPlotOutline, + FieldTagline, + FieldTvShowStatus, + FieldVotes, + FieldDirector, + FieldActor, + FieldStudio, + FieldCountry, + FieldMPAA, + FieldTop250, + FieldSet, + FieldNumberOfEpisodes, + FieldNumberOfWatchedEpisodes, + FieldWriter, + FieldAirDate, + FieldEpisodeNumber, + FieldUniqueId, + FieldSeason, + FieldEpisodeNumberSpecialSort, + FieldSeasonSpecialSort, + FieldReview, + FieldThemes, + FieldMoods, + FieldStyles, + FieldAlbumType, + FieldMusicLabel, + FieldTrailer, + FieldVideoResolution, + FieldVideoAspectRatio, + FieldVideoCodec, + FieldAudioChannels, + FieldAudioCodec, + FieldAudioLanguage, + FieldSubtitleLanguage, + FieldProductionCode, + FieldTag, + FieldChannelName, + FieldChannelNumber, + FieldInstruments, + FieldBiography, + FieldBorn, + FieldBandFormed, + FieldDisbanded, + FieldDied, + FieldStereoMode, + FieldMax +} Field; + +typedef std::set<Field> Fields; +typedef std::vector<Field> FieldList; + +typedef enum { + DatabaseQueryPartSelect, + DatabaseQueryPartWhere, + DatabaseQueryPartOrderBy, +} DatabaseQueryPart; + +typedef std::map<Field, CVariant> DatabaseResult; +typedef std::vector<DatabaseResult> DatabaseResults; + +class DatabaseUtils +{ +public: + static MediaType MediaTypeFromVideoContentType(int videoContentType); + + static std::string GetField(Field field, const MediaType &mediaType, DatabaseQueryPart queryPart); + static int GetField(Field field, const MediaType &mediaType); + static int GetFieldIndex(Field field, const MediaType &mediaType); + static bool GetSelectFields(const Fields &fields, const MediaType &mediaType, FieldList &selectFields); + + static bool GetFieldValue(const dbiplus::field_value &fieldValue, CVariant &variantValue); + static bool GetDatabaseResults(const MediaType &mediaType, const FieldList &fields, const std::auto_ptr<dbiplus::Dataset> &dataset, DatabaseResults &results); + + static std::string BuildLimitClause(int end, int start = 0); + +private: + static int GetField(Field field, const MediaType &mediaType, bool asIndex); +}; diff --git a/src/utils/EdenVideoArtUpdater.cpp b/src/utils/EdenVideoArtUpdater.cpp new file mode 100644 index 0000000000..448f390860 --- /dev/null +++ b/src/utils/EdenVideoArtUpdater.cpp @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "EdenVideoArtUpdater.h" +#include "video/VideoDatabase.h" +#include "video/VideoInfoScanner.h" +#include "FileItem.h" +#include "utils/log.h" +#include "utils/Crc32.h" +#include "utils/URIUtils.h" +#include "utils/ScraperUrl.h" +#include "utils/StringUtils.h" +#include "TextureCache.h" +#include "TextureCacheJob.h" +#include "pictures/Picture.h" +#include "profiles/ProfilesManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "guilib/Texture.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "filesystem/File.h" +#include "filesystem/StackDirectory.h" +#include "dialogs/GUIDialogExtendedProgressBar.h" +#include "interfaces/AnnouncementManager.h" + +using namespace std; +using namespace VIDEO; +using namespace XFILE; + +CEdenVideoArtUpdater::CEdenVideoArtUpdater() : CThread("VideoArtUpdater") +{ + m_textureDB.Open(); +} + +CEdenVideoArtUpdater::~CEdenVideoArtUpdater() +{ + m_textureDB.Close(); +} + +void CEdenVideoArtUpdater::Start() +{ + CEdenVideoArtUpdater *updater = new CEdenVideoArtUpdater(); + updater->Create(true); // autodelete +} + +void CEdenVideoArtUpdater::Process() +{ + // grab all movies... + CVideoDatabase db; + if (!db.Open()) + return; + + CFileItemList items; + + CGUIDialogExtendedProgressBar* dialog = + (CGUIDialogExtendedProgressBar*)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS); + + CGUIDialogProgressBarHandle *handle = dialog->GetHandle(g_localizeStrings.Get(314)); + handle->SetTitle(g_localizeStrings.Get(12349)); + + // movies + db.GetMoviesByWhere("videodb://movies/titles/", CDatabase::Filter(), items); + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + handle->SetProgress(i, items.Size()); + handle->SetText(StringUtils::Format(g_localizeStrings.Get(12350).c_str(), item->GetLabel().c_str())); + string cachedThumb = GetCachedVideoThumb(*item); + string cachedFanart = GetCachedFanart(*item); + + item->SetPath(item->GetVideoInfoTag()->m_strFileNameAndPath); + item->GetVideoInfoTag()->m_fanart.Unpack(); + item->GetVideoInfoTag()->m_strPictureURL.Parse(); + + map<string, string> artwork; + if (!db.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artwork) + || (artwork.size() == 1 && artwork.find("thumb") != artwork.end())) + { + CStdString art = CVideoInfoScanner::GetImage(item.get(), true, item->GetVideoInfoTag()->m_basePath != item->GetPath(), "thumb"); + std::string type; + if (CacheTexture(art, cachedThumb, item->GetLabel(), type)) + artwork.insert(make_pair(type, art)); + + art = CVideoInfoScanner::GetFanart(item.get(), true); + if (CacheTexture(art, cachedFanart, item->GetLabel())) + artwork.insert(make_pair("fanart", art)); + + if (artwork.empty()) + artwork.insert(make_pair("thumb", "")); + db.SetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artwork); + } + } + items.Clear(); + + // music videos + db.GetMusicVideosNav("videodb://musicvideos/titles/", items, false); + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + handle->SetProgress(i, items.Size()); + handle->SetText(StringUtils::Format(g_localizeStrings.Get(12350).c_str(), item->GetLabel().c_str())); + string cachedThumb = GetCachedVideoThumb(*item); + string cachedFanart = GetCachedFanart(*item); + + item->SetPath(item->GetVideoInfoTag()->m_strFileNameAndPath); + item->GetVideoInfoTag()->m_fanart.Unpack(); + item->GetVideoInfoTag()->m_strPictureURL.Parse(); + + map<string, string> artwork; + if (!db.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artwork) + || (artwork.size() == 1 && artwork.find("thumb") != artwork.end())) + { + CStdString art = CVideoInfoScanner::GetImage(item.get(), true, item->GetVideoInfoTag()->m_basePath != item->GetPath(), "thumb"); + std::string type; + if (CacheTexture(art, cachedThumb, item->GetLabel(), type)) + artwork.insert(make_pair(type, art)); + + art = CVideoInfoScanner::GetFanart(item.get(), true); + if (CacheTexture(art, cachedFanart, item->GetLabel())) + artwork.insert(make_pair("fanart", art)); + + if (artwork.empty()) + artwork.insert(make_pair("thumb", "")); + db.SetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artwork); + } + } + items.Clear(); + + // tvshows + // count the number of episodes + db.GetTvShowsNav("videodb://tvshows/titles/", items); + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + handle->SetText(StringUtils::Format(g_localizeStrings.Get(12350).c_str(), item->GetLabel().c_str())); + string cachedThumb = GetCachedVideoThumb(*item); + string cachedFanart = GetCachedFanart(*item); + + item->SetPath(item->GetVideoInfoTag()->m_strPath); + item->GetVideoInfoTag()->m_fanart.Unpack(); + item->GetVideoInfoTag()->m_strPictureURL.Parse(); + + map<string, string> artwork; + if (!db.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artwork) + || (artwork.size() == 1 && artwork.find("thumb") != artwork.end())) + { + CStdString art = CVideoInfoScanner::GetImage(item.get(), true, false, "thumb"); + std::string type; + if (CacheTexture(art, cachedThumb, item->GetLabel(), type)) + artwork.insert(make_pair(type, art)); + + art = CVideoInfoScanner::GetFanart(item.get(), true); + if (CacheTexture(art, cachedFanart, item->GetLabel())) + artwork.insert(make_pair("fanart", art)); + + if (artwork.empty()) + artwork.insert(make_pair("thumb", "")); + db.SetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artwork); + } + + // now season art... + map<int, map<string, string> > seasons; + vector<string> artTypes; artTypes.push_back("thumb"); + CVideoInfoScanner::GetSeasonThumbs(*item->GetVideoInfoTag(), seasons, artTypes, true); + for (map<int, map<string, string> >::const_iterator j = seasons.begin(); j != seasons.end(); ++j) + { + if (j->second.empty()) + continue; + int idSeason = db.AddSeason(item->GetVideoInfoTag()->m_iDbId, j->first); + map<string, string> seasonArt; + if (idSeason > -1 && !db.GetArtForItem(idSeason, MediaTypeSeason, seasonArt)) + { + std::string cachedSeason = GetCachedSeasonThumb(j->first, item->GetVideoInfoTag()->m_strPath); + std::string type; + std::string originalUrl = j->second.begin()->second; + if (CacheTexture(originalUrl, cachedSeason, "", type)) + db.SetArtForItem(idSeason, MediaTypeSeason, type, originalUrl); + } + } + + // now episodes... + CFileItemList items2; + db.GetEpisodesByWhere("videodb://tvshows/titles/-1/-1/", db.PrepareSQL("episodeview.idShow=%d", item->GetVideoInfoTag()->m_iDbId), items2); + for (int j = 0; j < items2.Size(); j++) + { + handle->SetProgress(j, items2.Size()); + CFileItemPtr episode = items2[j]; + string cachedThumb = GetCachedEpisodeThumb(*episode); + if (!CFile::Exists(cachedThumb)) + cachedThumb = GetCachedVideoThumb(*episode); + episode->SetPath(episode->GetVideoInfoTag()->m_strFileNameAndPath); + episode->GetVideoInfoTag()->m_strPictureURL.Parse(); + + map<string, string> artwork; + if (!db.GetArtForItem(episode->GetVideoInfoTag()->m_iDbId, episode->GetVideoInfoTag()->m_type, artwork) + || (artwork.size() == 1 && artwork.find("thumb") != artwork.end())) + { + CStdString art = CVideoInfoScanner::GetImage(episode.get(), true, episode->GetVideoInfoTag()->m_basePath != episode->GetPath(), "thumb"); + if (CacheTexture(art, cachedThumb, episode->GetLabel())) + artwork.insert(make_pair("thumb", art)); + else + artwork.insert(make_pair("thumb", "")); + db.SetArtForItem(episode->GetVideoInfoTag()->m_iDbId, episode->GetVideoInfoTag()->m_type, artwork); + } + } + } + items.Clear(); + + // now sets + db.GetSetsNav("videodb://movies/sets/", items, VIDEODB_CONTENT_MOVIES); + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + handle->SetProgress(i, items.Size()); + handle->SetText(StringUtils::Format(g_localizeStrings.Get(12350).c_str(), item->GetLabel().c_str())); + map<string, string> artwork; + if (!db.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artwork)) + { // grab the first movie from this set + CFileItemList items2; + db.GetMoviesNav("videodb://movies/titles/", items2, -1, -1, -1, -1, -1, -1, item->GetVideoInfoTag()->m_iDbId); + if (items2.Size() > 1) + { + if (db.GetArtForItem(items2[0]->GetVideoInfoTag()->m_iDbId, items2[0]->GetVideoInfoTag()->m_type, artwork)) + db.SetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artwork); + } + } + } + items.Clear(); + + // now actors + if (CSettings::Get().GetBool("videolibrary.actorthumbs")) + { + db.GetActorsNav("videodb://movies/titles/", items, VIDEODB_CONTENT_MOVIES); + db.GetActorsNav("videodb://tvshows/titles/", items, VIDEODB_CONTENT_TVSHOWS); + db.GetActorsNav("videodb://tvshows/titles/", items, VIDEODB_CONTENT_EPISODES); + db.GetActorsNav("videodb://musicvideos/titles/", items, VIDEODB_CONTENT_MUSICVIDEOS); + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + handle->SetProgress(i, items.Size()); + handle->SetText(StringUtils::Format(g_localizeStrings.Get(12350).c_str(), item->GetLabel().c_str())); + map<string, string> artwork; + if (!db.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artwork)) + { + item->GetVideoInfoTag()->m_strPictureURL.Parse(); + string cachedThumb = GetCachedActorThumb(*item); + + string art = CScraperUrl::GetThumbURL(item->GetVideoInfoTag()->m_strPictureURL.GetFirstThumb()); + if (CacheTexture(art, cachedThumb, item->GetLabel())) + artwork.insert(make_pair("thumb", art)); + else + artwork.insert(make_pair("thumb", "")); + db.SetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artwork); + } + } + } + handle->MarkFinished(); + + ANNOUNCEMENT::CAnnouncementManager::Get().Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnScanFinished"); + + items.Clear(); +} + +bool CEdenVideoArtUpdater::CacheTexture(std::string &originalUrl, const std::string &cachedFile, const std::string &label) +{ + std::string type; + return CacheTexture(originalUrl, cachedFile, label, type); +} + +bool CEdenVideoArtUpdater::CacheTexture(std::string &originalUrl, const std::string &cachedFile, const std::string &label, std::string &type) +{ + if (!CFile::Exists(cachedFile)) + { + CLog::Log(LOGERROR, "%s No cached art for item %s (should be %s)", __FUNCTION__, label.c_str(), cachedFile.c_str()); + return false; + } + if (originalUrl.empty()) + { + originalUrl = GetThumb(cachedFile, "http://unknown/video/", true); + CLog::Log(LOGERROR, "%s No original url for item %s, but cached art exists, using %s", __FUNCTION__, label.c_str(), originalUrl.c_str()); + } + + CTextureDetails details; + details.updateable = false; + details.hash = "NOHASH"; + type = "thumb"; // unknown art type + + CBaseTexture *texture = CTextureCacheJob::LoadImage(cachedFile, 0, 0, ""); + if (texture) + { + if (texture->HasAlpha()) + details.file = CTextureCache::GetCacheFile(originalUrl) + ".png"; + else + details.file = CTextureCache::GetCacheFile(originalUrl) + ".jpg"; + + CLog::Log(LOGDEBUG, "Caching image '%s' ('%s') to '%s' for item '%s'", originalUrl.c_str(), cachedFile.c_str(), details.file.c_str(), label.c_str()); + + uint32_t width = 0, height = 0; + if (CPicture::CacheTexture(texture, width, height, CTextureCache::GetCachedPath(details.file))) + { + details.width = width; + details.height = height; + type = CVideoInfoScanner::GetArtTypeFromSize(details.width, details.height); + delete texture; + m_textureDB.AddCachedTexture(originalUrl, details); + return true; + } + } + CLog::Log(LOGERROR, "Can't cache image '%s' ('%s') for item '%s'", originalUrl.c_str(), cachedFile.c_str(), label.c_str()); + return false; +} + +CStdString CEdenVideoArtUpdater::GetCachedActorThumb(const CFileItem &item) +{ + return GetThumb("actor" + item.GetLabel(), CProfilesManager::Get().GetVideoThumbFolder(), true); +} + +CStdString CEdenVideoArtUpdater::GetCachedSeasonThumb(int season, const CStdString &path) +{ + CStdString label; + if (season == -1) + label = g_localizeStrings.Get(20366); + else if (season == 0) + label = g_localizeStrings.Get(20381); + else + label = StringUtils::Format(g_localizeStrings.Get(20358).c_str(), season); + return GetThumb("season" + path + label, CProfilesManager::Get().GetVideoThumbFolder(), true); +} + +CStdString CEdenVideoArtUpdater::GetCachedEpisodeThumb(const CFileItem &item) +{ + // get the locally cached thumb + CStdString strCRC = StringUtils::Format("%sepisode%i", + item.GetVideoInfoTag()->m_strFileNameAndPath.c_str(), + item.GetVideoInfoTag()->m_iEpisode); + return GetThumb(strCRC, CProfilesManager::Get().GetVideoThumbFolder(), true); +} + +CStdString CEdenVideoArtUpdater::GetCachedVideoThumb(const CFileItem &item) +{ + if (item.m_bIsFolder && !item.GetVideoInfoTag()->m_strPath.empty()) + return GetThumb(item.GetVideoInfoTag()->m_strPath, CProfilesManager::Get().GetVideoThumbFolder(), true); + else if (!item.GetVideoInfoTag()->m_strFileNameAndPath.empty()) + { + CStdString path = item.GetVideoInfoTag()->m_strFileNameAndPath; + if (URIUtils::IsStack(path)) + path = CStackDirectory::GetFirstStackedFile(path); + return GetThumb(path, CProfilesManager::Get().GetVideoThumbFolder(), true); + } + return GetThumb(item.GetPath(), CProfilesManager::Get().GetVideoThumbFolder(), true); +} + +CStdString CEdenVideoArtUpdater::GetCachedFanart(const CFileItem &item) +{ + if (!item.GetVideoInfoTag()->m_artist.empty()) + return GetThumb(StringUtils::Join(item.GetVideoInfoTag()->m_artist, g_advancedSettings.m_videoItemSeparator), URIUtils::AddFileToFolder(CProfilesManager::Get().GetThumbnailsFolder(), "Music/Fanart/"), false); + CStdString path = item.GetVideoInfoTag()->GetPath(); + if (path.empty()) + return ""; + return GetThumb(path, URIUtils::AddFileToFolder(CProfilesManager::Get().GetVideoThumbFolder(), "Fanart/"), false); +} + +CStdString CEdenVideoArtUpdater::GetThumb(const CStdString &path, const CStdString &path2, bool split) +{ + // get the locally cached thumb + Crc32 crc; + crc.ComputeFromLowerCase(path); + + CStdString thumb; + if (split) + { + CStdString hex = StringUtils::Format("%08x", (__int32)crc); + thumb = StringUtils::Format("%c\\%08x.tbn", hex[0], (unsigned __int32)crc); + } + else + thumb = StringUtils::Format("%08x.tbn", (unsigned __int32)crc); + + return URIUtils::AddFileToFolder(path2, thumb); +} diff --git a/src/utils/EdenVideoArtUpdater.h b/src/utils/EdenVideoArtUpdater.h new file mode 100644 index 0000000000..312ff593b3 --- /dev/null +++ b/src/utils/EdenVideoArtUpdater.h @@ -0,0 +1,56 @@ +#pragma once +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include "threads/Thread.h" +#include "TextureDatabase.h" + +class CFileItem; + +class CEdenVideoArtUpdater : CThread +{ +public: + CEdenVideoArtUpdater(); + ~CEdenVideoArtUpdater(); + + static void Start(); + + virtual void Process(); + +private: + /*! \brief Caches the texture from oldCachedFile as if it came from originalUrl into the texture cache. + \param originalUrl [in/out] the url that we think the oldCachedFile came from. May be set if it's empty and an oldCachedFile exists. + \param oldCachedFile the old cached file + \param label the label of the item for logging + \param type [out] the type of art (poster/banner/thumb) + */ + bool CacheTexture(std::string &originalUrl, const std::string &cachedFile, const std::string &label, std::string &type); + bool CacheTexture(std::string &originalUrl, const std::string &oldCachedFile, const std::string &label); + + CStdString GetCachedActorThumb(const CFileItem &item); + CStdString GetCachedSeasonThumb(int season, const CStdString &path); + CStdString GetCachedEpisodeThumb(const CFileItem &item); + CStdString GetCachedVideoThumb(const CFileItem &item); + CStdString GetCachedFanart(const CFileItem &item); + CStdString GetThumb(const CStdString &path, const CStdString &path2, bool split /* = false */); + + CTextureDatabase m_textureDB; +}; diff --git a/src/utils/EndianSwap.cpp b/src/utils/EndianSwap.cpp new file mode 100644 index 0000000000..7f6b1b6c99 --- /dev/null +++ b/src/utils/EndianSwap.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "EndianSwap.h" + +/* based on libavformat/spdif.c */ +void Endian_Swap16_buf(uint16_t *dst, uint16_t *src, int w) +{ + int i; + + for (i = 0; i + 8 <= w; i += 8) { + dst[i + 0] = Endian_Swap16(src[i + 0]); + dst[i + 1] = Endian_Swap16(src[i + 1]); + dst[i + 2] = Endian_Swap16(src[i + 2]); + dst[i + 3] = Endian_Swap16(src[i + 3]); + dst[i + 4] = Endian_Swap16(src[i + 4]); + dst[i + 5] = Endian_Swap16(src[i + 5]); + dst[i + 6] = Endian_Swap16(src[i + 6]); + dst[i + 7] = Endian_Swap16(src[i + 7]); + } + + for (; i < w; i++) + dst[i + 0] = Endian_Swap16(src[i + 0]); +} + diff --git a/src/utils/EndianSwap.h b/src/utils/EndianSwap.h new file mode 100644 index 0000000000..8ab6398a1c --- /dev/null +++ b/src/utils/EndianSwap.h @@ -0,0 +1,108 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + + /* Endian_SwapXX functions taken from SDL (SDL_endian.h) */ + +#ifndef __ENDIAN_SWAP_H__ +#define __ENDIAN_SWAP_H__ + +/* Include config.h to define (or not) WORDS_BIGENDIAN + File created by configure */ +#if defined(TARGET_POSIX) +#include "config.h" +#include <inttypes.h> +#endif +#ifdef TARGET_WINDOWS +#define __inline__ __inline +#include <stdint.h> +#endif + + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +static __inline__ uint16_t Endian_Swap16(uint16_t x) +{ + uint16_t result; + + __asm__("rlwimi %0,%2,8,16,23" : "=&r" (result) : "0" (x >> 8), "r" (x)); + return result; +} + +static __inline__ uint32_t Endian_Swap32(uint32_t x) +{ + uint32_t result; + + __asm__("rlwimi %0,%2,24,16,23" : "=&r" (result) : "0" (x>>24), "r" (x)); + __asm__("rlwimi %0,%2,8,8,15" : "=&r" (result) : "0" (result), "r" (x)); + __asm__("rlwimi %0,%2,24,0,7" : "=&r" (result) : "0" (result), "r" (x)); + return result; +} +#else +static __inline__ uint16_t Endian_Swap16(uint16_t x) { + return((x<<8)|(x>>8)); +} + +static __inline__ uint32_t Endian_Swap32(uint32_t x) { + return((x<<24)|((x<<8)&0x00FF0000)|((x>>8)&0x0000FF00)|(x>>24)); +} +#endif + +static __inline__ uint64_t Endian_Swap64(uint64_t x) { + uint32_t hi, lo; + + /* Separate into high and low 32-bit values and swap them */ + lo = (uint32_t)(x&0xFFFFFFFF); + x >>= 32; + hi = (uint32_t)(x&0xFFFFFFFF); + x = Endian_Swap32(lo); + x <<= 32; + x |= Endian_Swap32(hi); + return(x); + +} + +void Endian_Swap16_buf(uint16_t *dst, uint16_t *src, int w); + +#ifndef WORDS_BIGENDIAN +#define Endian_SwapLE16(X) (X) +#define Endian_SwapLE32(X) (X) +#define Endian_SwapLE64(X) (X) +#define Endian_SwapBE16(X) Endian_Swap16(X) +#define Endian_SwapBE32(X) Endian_Swap32(X) +#define Endian_SwapBE64(X) Endian_Swap64(X) +#else +#define Endian_SwapLE16(X) Endian_Swap16(X) +#define Endian_SwapLE32(X) Endian_Swap32(X) +#define Endian_SwapLE64(X) Endian_Swap64(X) +#define Endian_SwapBE16(X) (X) +#define Endian_SwapBE32(X) (X) +#define Endian_SwapBE64(X) (X) +#endif + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + +#endif /* __ENDIAN_SWAP_H__ */ diff --git a/src/utils/Environment.cpp b/src/utils/Environment.cpp new file mode 100644 index 0000000000..149b399e8c --- /dev/null +++ b/src/utils/Environment.cpp @@ -0,0 +1,263 @@ +/* + * Copyright (C) 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 + * <http://www.gnu.org/licenses/>. + * + */ + +/** + * \file utils\Environment.cpp + * \brief Implements CEnvironment class functions. + * + * Some ideas were inspired by PostgreSQL's pgwin32_putenv function. + * Refined, updated, enhanced and modified for XBMC by Karlson2k. + */ + +#include "Environment.h" +#include <stdlib.h> +#ifdef TARGET_WINDOWS +#include <Windows.h> +#include "utils\SystemInfo.h" +#endif + +// --------------------- Helper Functions --------------------- + +#ifdef TARGET_WINDOWS + +std::wstring CEnvironment::win32ConvertUtf8ToW(const std::string &text, bool *resultSuccessful /* = NULL*/) +{ + if (text.empty()) + { + if (resultSuccessful != NULL) + *resultSuccessful = true; + return L""; + } + if (resultSuccessful != NULL) + *resultSuccessful = false; + + int bufSize = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.c_str(), -1, NULL, 0); + if (bufSize == 0) + return L""; + wchar_t *converted = new wchar_t[bufSize]; + if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.c_str(), -1, converted, bufSize) != bufSize) + { + delete[] converted; + return L""; + } + + std::wstring Wret (converted); + delete[] converted; + + if (resultSuccessful != NULL) + *resultSuccessful = true; + return Wret; +} + +std::string CEnvironment::win32ConvertWToUtf8(const std::wstring &text, bool *resultSuccessful /*= NULL*/) +{ + if (text.empty()) + { + if (resultSuccessful != NULL) + *resultSuccessful = true; + return ""; + } + if (resultSuccessful != NULL) + *resultSuccessful = false; + + int bufSize = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, text.c_str(), -1, NULL, 0, NULL, NULL); + if (bufSize == 0) + return ""; + char * converted = new char[bufSize]; + if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, text.c_str(), -1, converted, bufSize, NULL, NULL) != bufSize) + { + delete[] converted; + return ""; + } + + std::string ret(converted); + delete[] converted; + + if (resultSuccessful != NULL) + *resultSuccessful = true; + return ret; +} + +// --------------------- Internal Function --------------------- + +typedef int (_cdecl * wputenvPtr) (const wchar_t *envstring); + +/** + * \fn int CEnvironment::win32_setenv(const std::wstring &name, const std::wstring &value = L"", + * updateAction action = autoDetect) + * \brief Internal function used to manipulate with environment variables on win32. + * + * This function make all dirty work with setting, deleting and modifying environment variables. + * + * \param name The environment variable name. + * \param value (optional) the new value of environment variable. + * \param action (optional) the action. + * \return Zero on success, 2 if at least one external runtime update failed, 4 if process + * environment update failed, 8 if our runtime environment update failed or, in case of + * several errors, sum of all errors values; non-zero in case of other errors. + */ +int CEnvironment::win32_setenv(const std::string &name, const std::string &value /* = "" */, enum updateAction action /* = autoDetect */) +{ + std::wstring Wname (win32ConvertUtf8ToW(name)); + if (Wname.empty() || name.find('=') != std::wstring::npos) + return -1; + if ( (action == addOnly || action == addOrUpdateOnly) && value.empty() ) + return -1; + if (action == addOnly && !(getenv(name).empty()) ) + return 0; + + bool convIsOK; + std::wstring Wvalue (win32ConvertUtf8ToW(value,&convIsOK)); + if (!convIsOK) + return -1; + + int retValue = 0; + std::wstring EnvString; + if (action == deleteVariable) + EnvString = Wname + L"="; + else + EnvString = Wname + L"=" + Wvalue; + + static const wchar_t *modulesList[] = + { + /*{ L"msvcrt20.dll" }, // Visual C++ 2.0 / 2.1 / 2.2 + { L"msvcrt40.dll" }, // Visual C++ 4.0 / 4.1 */ // too old and no UNICODE support - ignoring + { L"msvcrt.dll" }, // Visual Studio 6.0 / MinGW[-w64] + { L"msvcr70.dll" }, // Visual Studio 2002 + { L"msvcr71.dll" }, // Visual Studio 2003 + { L"msvcr80.dll" }, // Visual Studio 2005 + { L"msvcr90.dll" }, // Visual Studio 2008 + { L"msvcr100.dll" }, // Visual Studio 2010 +#ifdef _DEBUG + { L"msvcr100d.dll" },// Visual Studio 2010 (debug) +#endif + { L"msvcr110.dll" }, // Visual Studio 2012 +#ifdef _DEBUG + { L"msvcr110d.dll" },// Visual Studio 2012 (debug) +#endif + { L"msvcr120.dll" }, // Visual Studio 2013 +#ifdef _DEBUG + { L"msvcr120d.dll" },// Visual Studio 2013 (debug) +#endif + { NULL } // Terminating NULL for list + }; + + // Check all modules each function run, because modules can be loaded/unloaded at runtime + for (int i = 0; modulesList[i]; i++) + { + HMODULE hModule; + if (!GetModuleHandleExW(0, modulesList[i], &hModule) || hModule == NULL) // Flag 0 ensures that module will be kept loaded until it'll be freed + continue; // Module not loaded + + wputenvPtr wputenvFunc = (wputenvPtr) GetProcAddress(hModule, "_wputenv"); + if (wputenvFunc != NULL && wputenvFunc(EnvString.c_str()) != 0) + retValue |= 2; // At lest one external runtime library Environment update failed + FreeLibrary(hModule); + } + + // Update process Environment used for current process and for future new child processes + if (action == deleteVariable || value.empty()) + retValue += SetEnvironmentVariableW(Wname.c_str(), NULL) ? 0 : 4; // 4 if failed + else + retValue += SetEnvironmentVariableW(Wname.c_str(), Wvalue.c_str()) ? 0 : 4; // 4 if failed + + // Finally update our runtime Environment + retValue += (::_wputenv(EnvString.c_str()) == 0) ? 0 : 8; // 8 if failed + + return retValue; +} +#endif + +// --------------------- Main Functions --------------------- + +int CEnvironment::setenv(const std::string &name, const std::string &value, int overwrite /*= 1*/) +{ +#ifdef TARGET_WINDOWS + return (win32_setenv(name, value, overwrite ? autoDetect : addOnly)==0) ? 0 : -1; +#else + if (value.empty() && overwrite != 0) + return ::unsetenv(name.c_str()); + return ::setenv(name.c_str(), value.c_str(), overwrite); +#endif +} + +std::string CEnvironment::getenv(const std::string &name) +{ +#ifdef TARGET_WINDOWS + std::wstring Wname (win32ConvertUtf8ToW(name)); + if (Wname.empty()) + return ""; + + wchar_t * wStr = ::_wgetenv(Wname.c_str()); + if (wStr != NULL) + return win32ConvertWToUtf8(wStr); + + // Not found in Environment of runtime library + // Try Environment of process as fallback + unsigned int varSize = GetEnvironmentVariableW(Wname.c_str(), NULL, 0); + if (varSize == 0) + return ""; // Not found + wchar_t * valBuf = new wchar_t[varSize]; + if (GetEnvironmentVariableW(Wname.c_str(), valBuf, varSize) != varSize-1) + { + delete[] valBuf; + return ""; + } + std::wstring Wvalue (valBuf); + delete[] valBuf; + + return win32ConvertWToUtf8(Wvalue); +#else + char * str = ::getenv(name.c_str()); + if (str == NULL) + return ""; + return str; +#endif +} + +int CEnvironment::unsetenv(const std::string &name) +{ +#ifdef TARGET_WINDOWS + return (win32_setenv(name, "", deleteVariable)) == 0 ? 0 : -1; +#else + return ::unsetenv(name.c_str()); +#endif +} + +int CEnvironment::putenv(const std::string &envstring) +{ + if (envstring.empty()) + return 0; + size_t pos = envstring.find('='); + if (pos == 0) // '=' is the first character + return -1; + if (pos == std::string::npos) + return unsetenv(envstring); + if (pos == envstring.length()-1) // '=' is in last position + { + std::string name(envstring); + name.erase(name.length()-1, 1); + return unsetenv(name); + } + std::string name(envstring, 0, pos), value(envstring, pos+1); + + return setenv(name, value); +} + diff --git a/src/utils/Environment.h b/src/utils/Environment.h new file mode 100644 index 0000000000..30aea82e66 --- /dev/null +++ b/src/utils/Environment.h @@ -0,0 +1,106 @@ +#pragma once +#ifndef XBMC_SETENV_H +#define XBMC_SETENV_H +/* + * Copyright (C) 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 + * <http://www.gnu.org/licenses/>. + * + */ + +/** + * \file utils\Environment.h + * \brief Declares CEnvironment class for platform-independent environment variables manipulations. + * + */ +#include <string> + +/** + * @class CEnvironment + * + * @brief Platform-independent environment variables manipulations. + * + * Provide analog for POSIX functions: + * + setenv + * + unsetenv + * + putenv + * + getenv + * + * You can generally use the functions as you would normally in POSIX-style. + * The differences below are just to make things more convenient through use of std::string (2,3), + * and to also allow the Win32-style of unsetting variables (4,5) if wanted. + * 1. CEnvironment::setenv parameter 'overwrite' is optional, set by default to 1 (allow overwrite). + * 2. CEnvironment::putenv uses copy of provided string (rather than string itself) to change environment, + * so you can free parameter variable right after call of function. + * 3. CEnvironment::getenv returns a copy of environment variable value instead of pointer to value. + * 4. CEnvironment::setenv can be used to unset variables. Just pass empty string for 'value' parameter. + * 5. CEnvironment::putenv can be used to unset variables. Set parameter to 'var=' (Windows style) or + * just 'var' (POSIX style), and 'var' will be unset. + * + * All 'std::string' types are supposed to be in UTF-8 encoding. + * All functions work on all platforms. Special care is taken on Windows platform where Environment is changed for process itself, + * for process runtime library and for all runtime libraries (MSVCRT) loaded by third-party modules. + * Functions internally make all necessary UTF-8 <-> wide conversions.* + */ + +class CEnvironment +{ +public: + /** + * \fn static int CEnvironment::setenv(const std::string &name, const std::string &value, + * int overwrite = 1); + * \brief Sets or unsets environment variable. + * \param name The environment variable name to add/modify/delete. + * \param value The environment variable new value. If set to empty string, variable will be + * deleted from the environment. + * \param overwrite (optional) If set to non-zero, existing variable will be overwritten. If set to zero and + * variable is already present, then variable will be unchanged and function returns success. + * \return Zero on success, non-zero on error. + */ + static int setenv(const std::string &name, const std::string &value, int overwrite = 1); + /** + * \fn static int CEnvironment::unsetenv(const std::string &name); + * \brief Deletes environment variable. + * \param name The environment variable name to delete. + * \return Zero on success, non-zero on error. + */ + static int unsetenv(const std::string &name); + + /** + * \fn static int CEnvironment:putenv(const std::string &envstring); + * \brief Adds/modifies/deletes environment variable. + * \param envstring The variable-value string in form 'var=value'. If set to 'var=' or 'var', then variable + * will be deleted from the environment. + * \return Zero on success, non-zero on error. + */ + static int putenv(const std::string &envstring); + /** + * \fn static std::string CEnvironment::getenv(const std::string &name); + * \brief Gets value of environment variable in UTF-8 encoding. + * \param name The name of environment variable. + * \return Copy of of environment variable value or empty string if variable in not present in environment. + * \sa xbmc_getenvUtf8, xbmc_getenvW + */ + static std::string getenv(const std::string &name); +#ifdef TARGET_WINDOWS +private: + static std::wstring win32ConvertUtf8ToW(const std::string &text, bool *resultSuccessful = NULL); + static std::string win32ConvertWToUtf8(const std::wstring &text, bool *resultSuccessful = NULL); + enum updateAction:int {addOrUpdateOnly = -2, deleteVariable = -1, addOnly = 0, autoDetect = 1}; + static int win32_setenv(const std::string &name, const std::string &value = "", updateAction action = autoDetect); +#endif // TARGET_WINDOWS +}; +#endif diff --git a/src/utils/Fanart.cpp b/src/utils/Fanart.cpp new file mode 100644 index 0000000000..9a86bd052a --- /dev/null +++ b/src/utils/Fanart.cpp @@ -0,0 +1,172 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Fanart.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "URIUtils.h" +#include "StringUtils.h" + +const unsigned int CFanart::max_fanart_colors=3; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// CFanart Functions +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +CFanart::CFanart() +{ +} + +void CFanart::Pack() +{ + // Take our data and pack it into the m_xml string + m_xml.clear(); + TiXmlElement fanart("fanart"); + for (std::vector<SFanartData>::const_iterator it = m_fanart.begin(); it != m_fanart.end(); ++it) + { + TiXmlElement thumb("thumb"); + thumb.SetAttribute("dim", it->strResolution.c_str()); + thumb.SetAttribute("colors", it->strColors.c_str()); + thumb.SetAttribute("preview", it->strPreview.c_str()); + TiXmlText text(it->strImage); + thumb.InsertEndChild(text); + fanart.InsertEndChild(thumb); + } + m_xml << fanart; +} + +bool CFanart::Unpack() +{ + CXBMCTinyXML doc; + doc.Parse(m_xml); + + m_fanart.clear(); + + TiXmlElement *fanart = doc.FirstChildElement("fanart"); + while (fanart) + { + std::string url = XMLUtils::GetAttribute(fanart, "url"); + TiXmlElement *fanartThumb = fanart->FirstChildElement("thumb"); + while (fanartThumb) + { + if (!fanartThumb->NoChildren()) + { + SFanartData data; + if (url.empty()) + { + data.strImage = fanartThumb->FirstChild()->ValueStr(); + data.strPreview = XMLUtils::GetAttribute(fanartThumb, "preview"); + } + else + { + data.strImage = URIUtils::AddFileToFolder(url, fanartThumb->FirstChild()->ValueStr()); + if (fanartThumb->Attribute("preview")) + data.strPreview = URIUtils::AddFileToFolder(url, fanartThumb->Attribute("preview")); + } + data.strResolution = XMLUtils::GetAttribute(fanartThumb, "dim"); + ParseColors(XMLUtils::GetAttribute(fanartThumb, "colors"), data.strColors); + m_fanart.push_back(data); + } + fanartThumb = fanartThumb->NextSiblingElement("thumb"); + } + fanart = fanart->NextSiblingElement("fanart"); + } + return true; +} + +std::string CFanart::GetImageURL(unsigned int index) const +{ + if (index >= m_fanart.size()) + return ""; + + return m_fanart[index].strImage; +} + +std::string CFanart::GetPreviewURL(unsigned int index) const +{ + if (index >= m_fanart.size()) + return ""; + + return m_fanart[index].strPreview.empty() ? m_fanart[index].strImage : m_fanart[index].strPreview; +} + +const std::string CFanart::GetColor(unsigned int index) const +{ + if (index >= max_fanart_colors || m_fanart.size() == 0 || + m_fanart[0].strColors.size() < index*9+8) + return "FFFFFFFF"; + + // format is AARRGGBB,AARRGGBB etc. + return m_fanart[0].strColors.substr(index*9, 8); +} + +bool CFanart::SetPrimaryFanart(unsigned int index) +{ + if (index >= m_fanart.size()) + return false; + + std::iter_swap(m_fanart.begin()+index, m_fanart.begin()); + + // repack our data + Pack(); + + return true; +} + +unsigned int CFanart::GetNumFanarts() const +{ + return m_fanart.size(); +} + +bool CFanart::ParseColors(const std::string &colorsIn, std::string &colorsOut) +{ + // Formats: + // 0: XBMC ARGB Hexadecimal string comma seperated "FFFFFFFF,DDDDDDDD,AAAAAAAA" + // 1: The TVDB RGB Int Triplets, pipe seperate with leading/trailing pipes "|68,69,59|69,70,58|78,78,68|" + + // Essentially we read the colors in using the proper format, and store them in our own fixed temporary format (3 DWORDS), and then + // write them back in in the specified format. + + if (colorsIn.empty()) + return false; + + // check for the TVDB RGB triplets "|68,69,59|69,70,58|78,78,68|" + if (colorsIn[0] == '|') + { // need conversion + colorsOut.clear(); + std::vector<std::string> strColors = StringUtils::Split(colorsIn, "|"); + for (int i = 0; i < std::min((int)strColors.size()-1, (int)max_fanart_colors); i++) + { // split up each color + std::vector<std::string> strTriplets = StringUtils::Split(strColors[i+1], ","); + if (strTriplets.size() == 3) + { // convert + if (colorsOut.size()) + colorsOut += ","; + colorsOut += StringUtils::Format("FF%2lx%2lx%2lx", atol(strTriplets[0].c_str()), atol(strTriplets[1].c_str()), atol(strTriplets[2].c_str())); + } + } + } + else + { // assume is our format + colorsOut = colorsIn; + } + return true; +} diff --git a/src/utils/Fanart.h b/src/utils/Fanart.h new file mode 100644 index 0000000000..53464e7074 --- /dev/null +++ b/src/utils/Fanart.h @@ -0,0 +1,120 @@ +// Fanart.h +////////////////////////////////////////////////////////////////////// + +#if !defined(FANART_H_) +#define FANART_H_ + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <vector> + +#pragma once + +/// +/// /brief CFanart is the core of fanart support and contains all fanart data for a specific show +/// +/// CFanart stores all data related to all available fanarts for a given TV show and provides +/// functions required to manipulate and access that data. +/// In order to provide an interface between the fanart data and the XBMC database, all data +/// is stored internally it its own form, as well as packed into an XML formatted string +/// stored in the member variable m_xml. +/// Information on multiple fanarts for a given show is stored, but XBMC only cares about the +/// very first fanart stored. These interfaces provide a means to access the data in that first +/// fanart record, as well as to set which fanart is the first record. Externally, all XBMC needs +/// to care about is getting and setting that first record. Everything else is maintained internally +/// by CFanart. This point is key to using the interface properly. +class CFanart +{ +public: + /// + /// Standard constructor doesn't need to do anything + CFanart(); + /// + /// Takes the internal fanart data and packs it into an XML formatted string in m_xml + /// \sa m_xml + void Pack(); + /// + /// Takes the XML formatted string m_xml and unpacks the fanart data contained into the internal data + /// \return A boolean indicating success or failure + /// \sa m_xml + bool Unpack(); + /// + /// Retrieves the fanart full res image URL + /// \param index - index of image to retrieve (defaults to 0) + /// \return A string containing the full URL to the full resolution fanart image + std::string GetImageURL(unsigned int index = 0) const; + /// + /// Retrieves the fanart preview image URL, or full res image URL if that doesn't exist + /// \param index - index of image to retrieve (defaults to 0) + /// \return A string containing the full URL to the full resolution fanart image + std::string GetPreviewURL(unsigned int index = 0) const; + /// + /// Used to return a specified fanart theme color value + /// \param index: 0 based index of the color to retrieve. A fanart theme contains 3 colors, indices 0-2, arranged from darkest to lightest. + const std::string GetColor(unsigned int index) const; + /// + /// Sets a particular fanart to be the "primary" fanart, or in other words, sets which fanart is actually used by XBMC + /// + /// This is the one of the only instances in the public interface where there is any hint that more than one fanart exists, but its by neccesity. + /// \param index: 0 based index of which fanart to set as the primary fanart + /// \return A boolean value indicating success or failure. This should only return false if the specified index is not a valid fanart + bool SetPrimaryFanart(unsigned int index); + /// + /// Returns how many fanarts are stored + /// \return An integer indicating how many fanarts are stored in the class. Fanart indices are 0 to (GetNumFanarts() - 1) + unsigned int GetNumFanarts() const; + /// + /// m_xml contains an XML formatted string which is all fanart packed into one string. + /// + /// This string is the "interface" as it were to the XBMC database, and MUST be kept in sync with the rest of the class. Therefore + /// anytime this string is changed, the change should be followed up by a call to CFanart::UnPack(). This XML formaytted string is + /// also the interface used to pass the fanart data from the scraper to CFanart. + std::string m_xml; +private: + static const unsigned int max_fanart_colors; + /// + /// Parse various color formats as returned by the sites scraped into a format we recognize + /// + /// Supported Formats: + /// + /// * The TVDB RGB Int Triplets, pipe seperate with leading/trailing pipes "|68,69,59|69,70,58|78,78,68|" + /// * XBMC ARGB Hexadecimal string comma seperated "FFFFFFFF,DDDDDDDD,AAAAAAAA" + /// + /// \param colorsIn: string containing colors in some format to be converted + /// \param colorsOut: XBMC ARGB Hexadecimal string comma seperated "FFFFFFFF,DDDDDDDD,AAAAAAAA" + /// \return boolean indicating success or failure. + static bool ParseColors(const std::string&colorsIn, std::string&colorsOut); + + struct SFanartData + { + std::string strImage; + std::string strResolution; + std::string strColors; + std::string strPreview; + }; + + /// + /// std::vector that stores all our fanart data + std::vector<SFanartData> m_fanart; +}; + +#endif diff --git a/src/utils/FileOperationJob.cpp b/src/utils/FileOperationJob.cpp new file mode 100644 index 0000000000..099ae8fb42 --- /dev/null +++ b/src/utils/FileOperationJob.cpp @@ -0,0 +1,374 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "FileOperationJob.h" +#include "filesystem/File.h" +#include "filesystem/Directory.h" +#include "filesystem/ZipManager.h" +#include "filesystem/FileDirectoryFactory.h" +#include "filesystem/MultiPathDirectory.h" +#include "filesystem/SpecialProtocol.h" +#include "log.h" +#include "Util.h" +#include "URIUtils.h" +#include "utils/StringUtils.h" +#include "URL.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/GUIWindowManager.h" +#include "dialogs/GUIDialogExtendedProgressBar.h" + +#ifdef HAS_FILESYSTEM_RAR +#include "filesystem/RarManager.h" +#endif + +using namespace std; +using namespace XFILE; + +CFileOperationJob::CFileOperationJob() +{ + m_action = ActionCopy; + m_heading = 0; + m_line = 0; + m_handle = NULL; + m_displayProgress = false; +} + +CFileOperationJob::CFileOperationJob(FileAction action, CFileItemList & items, + const CStdString& strDestFile, + bool displayProgress, + int heading, int line) +{ + m_handle = NULL; + m_displayProgress = displayProgress; + m_heading = heading; + m_line = line; + SetFileOperation(action, items, strDestFile); +} + +void CFileOperationJob::SetFileOperation(FileAction action, CFileItemList &items, const CStdString &strDestFile) +{ + m_action = action; + m_strDestFile = strDestFile; + + m_items.Clear(); + for (int i = 0; i < items.Size(); i++) + m_items.Add(CFileItemPtr(new CFileItem(*items[i]))); +} + +bool CFileOperationJob::DoWork() +{ + FileOperationList ops; + double totalTime = 0.0; + + if (m_displayProgress) + { + CGUIDialogExtendedProgressBar* dialog = + (CGUIDialogExtendedProgressBar*)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS); + m_handle = dialog->GetHandle(GetActionString(m_action)); + } + + bool success = DoProcess(m_action, m_items, m_strDestFile, ops, totalTime); + + unsigned int size = ops.size(); + + double opWeight = 100.0 / totalTime; + double current = 0.0; + + for (unsigned int i = 0; i < size && success; i++) + success &= ops[i].ExecuteOperation(this, current, opWeight); + + if (m_handle) + m_handle->MarkFinished(); + + return success; +} + +bool CFileOperationJob::DoProcessFile(FileAction action, const CStdString& strFileA, const CStdString& strFileB, FileOperationList &fileOperations, double &totalTime) +{ + int64_t time = 1; + + if (action == ActionCopy || action == ActionReplace || (action == ActionMove && !CanBeRenamed(strFileA, strFileB))) + { + struct __stat64 data; + if(CFile::Stat(strFileA, &data) == 0) + time += data.st_size; + } + + fileOperations.push_back(CFileOperation(action, strFileA, strFileB, time)); + + totalTime += time; + + return true; +} + +bool CFileOperationJob::DoProcessFolder(FileAction action, const CStdString& strPath, const CStdString& strDestFile, FileOperationList &fileOperations, double &totalTime) +{ + // check whether this folder is a filedirectory - if so, we don't process it's contents + CFileItem item(strPath, false); + IFileDirectory *file = CFileDirectoryFactory::Create(item.GetURL(), &item); + if (file) + { + delete file; + return true; + } + CLog::Log(LOGDEBUG,"FileManager, processing folder: %s",strPath.c_str()); + CFileItemList items; + //m_rootDir.GetDirectory(strPath, items); + CDirectory::GetDirectory(strPath, items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_GET_HIDDEN); + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr pItem = items[i]; + pItem->Select(true); + CLog::Log(LOGDEBUG," -- %s",pItem->GetPath().c_str()); + } + + if (!DoProcess(action, items, strDestFile, fileOperations, totalTime)) return false; + + if (action == ActionMove) + { + fileOperations.push_back(CFileOperation(ActionDeleteFolder, strPath, "", 1)); + totalTime += 1.0; + } + + return true; +} + +bool CFileOperationJob::DoProcess(FileAction action, CFileItemList & items, const CStdString& strDestFile, FileOperationList &fileOperations, double &totalTime) +{ + for (int iItem = 0; iItem < items.Size(); ++iItem) + { + CFileItemPtr pItem = items[iItem]; + if (pItem->IsSelected()) + { + CStdString strNoSlash = pItem->GetPath(); + URIUtils::RemoveSlashAtEnd(strNoSlash); + CStdString strFileName = URIUtils::GetFileName(strNoSlash); + + // special case for upnp + if (URIUtils::IsUPnP(items.GetPath()) || URIUtils::IsUPnP(pItem->GetPath())) + { + // get filename from label instead of path + strFileName = pItem->GetLabel(); + + if(!pItem->m_bIsFolder && !URIUtils::HasExtension(strFileName)) + { + // FIXME: for now we only work well if the url has the extension + // we should map the content type to the extension otherwise + strFileName += URIUtils::GetExtension(pItem->GetPath()); + } + + strFileName = CUtil::MakeLegalFileName(strFileName); + } + + CStdString strnewDestFile; + if(!strDestFile.empty()) // only do this if we have a destination + strnewDestFile = URIUtils::ChangeBasePath(pItem->GetPath(), strFileName, strDestFile); // Convert (URL) encoding + slashes (if source / target differ) + + if (pItem->m_bIsFolder) + { + // in ActionReplace mode all subdirectories will be removed by the below + // DoProcessFolder(ActionDelete) call as well, so ActionCopy is enough when + // processing those + FileAction subdirAction = (action == ActionReplace) ? ActionCopy : action; + // create folder on dest. drive + if (action != ActionDelete && action != ActionDeleteFolder) + DoProcessFile(ActionCreateFolder, strnewDestFile, "", fileOperations, totalTime); + if (action == ActionReplace && CDirectory::Exists(strnewDestFile)) + DoProcessFolder(ActionDelete, strnewDestFile, "", fileOperations, totalTime); + if (!DoProcessFolder(subdirAction, pItem->GetPath(), strnewDestFile, fileOperations, totalTime)) + return false; + if (action == ActionDelete || action == ActionDeleteFolder) + DoProcessFile(ActionDeleteFolder, pItem->GetPath(), "", fileOperations, totalTime); + } + else + DoProcessFile(action, pItem->GetPath(), strnewDestFile, fileOperations, totalTime); + } + } + return true; +} + +CFileOperationJob::CFileOperation::CFileOperation(FileAction action, const CStdString &strFileA, const CStdString &strFileB, int64_t time) : m_action(action), m_strFileA(strFileA), m_strFileB(strFileB), m_time(time) +{ +} + +struct DataHolder +{ + CFileOperationJob *base; + double current; + double opWeight; +}; + +CStdString CFileOperationJob::GetActionString(FileAction action) +{ + CStdString result; + switch (action) + { + case ActionCopy: + case ActionReplace: + result = g_localizeStrings.Get(115); + break; + case ActionMove: + result = g_localizeStrings.Get(116); + break; + case ActionDelete: + case ActionDeleteFolder: + result = g_localizeStrings.Get(117); + break; + case ActionCreateFolder: + result = g_localizeStrings.Get(119); + break; + default: + break; + } + + return result; +} + +bool CFileOperationJob::CFileOperation::ExecuteOperation(CFileOperationJob *base, double ¤t, double opWeight) +{ + bool bResult = true; + + base->m_currentFile = CURL(m_strFileA).GetFileNameWithoutPath(); + base->m_currentOperation = GetActionString(m_action); + + if (base->ShouldCancel((unsigned)current, 100)) + return false; + + if (base->m_handle) + { + base->m_handle->SetText(base->GetCurrentFile()); + base->m_handle->SetPercentage((float)current); + } + + DataHolder data = {base, current, opWeight}; + + switch (m_action) + { + case ActionCopy: + case ActionReplace: + { + CLog::Log(LOGDEBUG,"FileManager: copy %s -> %s\n", m_strFileA.c_str(), m_strFileB.c_str()); + + bResult = CFile::Copy(m_strFileA, m_strFileB, this, &data); + } + break; + case ActionMove: + { + CLog::Log(LOGDEBUG,"FileManager: move %s -> %s\n", m_strFileA.c_str(), m_strFileB.c_str()); + + if (CanBeRenamed(m_strFileA, m_strFileB)) + bResult = CFile::Rename(m_strFileA, m_strFileB); + else if (CFile::Copy(m_strFileA, m_strFileB, this, &data)) + bResult = CFile::Delete(m_strFileA); + else + bResult = false; + } + break; + case ActionDelete: + { + CLog::Log(LOGDEBUG,"FileManager: delete %s\n", m_strFileA.c_str()); + + bResult = CFile::Delete(m_strFileA); + } + break; + case ActionDeleteFolder: + { + CLog::Log(LOGDEBUG,"FileManager: delete folder %s\n", m_strFileA.c_str()); + + bResult = CDirectory::Remove(m_strFileA); + } + break; + case ActionCreateFolder: + { + CLog::Log(LOGDEBUG,"FileManager: create folder %s\n", m_strFileA.c_str()); + + bResult = CDirectory::Create(m_strFileA); + } + break; + } + + current += (double)m_time * opWeight; + + return bResult; +} + +inline bool CFileOperationJob::CanBeRenamed(const CStdString &strFileA, const CStdString &strFileB) +{ +#ifndef TARGET_POSIX + if (strFileA[1] == ':' && strFileA[0] == strFileB[0]) + return true; +#else + if (URIUtils::IsHD(strFileA) && URIUtils::IsHD(strFileB)) + return true; +#endif + return false; +} + +void CFileOperationJob::CFileOperation::Debug() +{ + printf("%i | %s > %s\n", m_action, m_strFileA.c_str(), m_strFileB.c_str()); +} + +bool CFileOperationJob::CFileOperation::OnFileCallback(void* pContext, int ipercent, float avgSpeed) +{ + DataHolder *data = (DataHolder *)pContext; + double current = data->current + ((double)ipercent * data->opWeight * (double)m_time)/ 100.0; + + if (avgSpeed > 1000000.0f) + data->base->m_avgSpeed = StringUtils::Format("%.1f MB/s", avgSpeed / 1000000.0f); + else + data->base->m_avgSpeed = StringUtils::Format("%.1f KB/s", avgSpeed / 1000.0f); + + if (data->base->m_handle) + { + CStdString line; + line = StringUtils::Format("%s (%s)", + data->base->GetCurrentFile().c_str(), + data->base->GetAverageSpeed().c_str()); + data->base->m_handle->SetText(line); + data->base->m_handle->SetPercentage((float)current); + } + + return !data->base->ShouldCancel((unsigned)current, 100); +} + +bool CFileOperationJob::operator==(const CJob* job) const +{ + if (strcmp(job->GetType(),GetType()) == 0) + { + const CFileOperationJob* rjob = dynamic_cast<const CFileOperationJob*>(job); + if (rjob) + { + if (GetAction() == rjob->GetAction() && + m_strDestFile == rjob->m_strDestFile && + m_items.Size() == rjob->m_items.Size()) + { + for (int i=0;i<m_items.Size();++i) + { + if (m_items[i]->GetPath() != rjob->m_items[i]->GetPath() || + m_items[i]->IsSelected() != rjob->m_items[i]->IsSelected()) + return false; + } + return true; + } + } + } + return false; +} diff --git a/src/utils/FileOperationJob.h b/src/utils/FileOperationJob.h new file mode 100644 index 0000000000..908da10329 --- /dev/null +++ b/src/utils/FileOperationJob.h @@ -0,0 +1,93 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" +#include "FileItem.h" +#include "Job.h" +#include "filesystem/File.h" + +class CGUIDialogProgressBarHandle; + +class CFileOperationJob : public CJob +{ +public: + enum FileAction + { + ActionCopy = 1, + ActionMove, + ActionDelete, + ActionReplace, ///< Copy, emptying any existing destination directories first + ActionCreateFolder, + ActionDeleteFolder, + }; + + CFileOperationJob(); + CFileOperationJob(FileAction action, CFileItemList & items, + const CStdString& strDestFile, + bool displayProgress=false, + int errorHeading=0, int errorLine=0); + + void SetFileOperation(FileAction action, CFileItemList &items, const CStdString &strDestFile); + + virtual bool operator==(const CJob *job) const; + + static CStdString GetActionString(FileAction action); + + const char* GetType() const { return m_displayProgress?"filemanager":""; } + + virtual bool DoWork(); + const CStdString &GetAverageSpeed() const { return m_avgSpeed; } + const CStdString &GetCurrentOperation() const { return m_currentOperation; } + const CStdString &GetCurrentFile() const { return m_currentFile; } + const CFileItemList &GetItems() const { return m_items; } + FileAction GetAction() const { return m_action; } + int GetHeading() const { return m_heading; } + int GetLine() const { return m_line; } +private: + class CFileOperation : public XFILE::IFileCallback + { + public: + CFileOperation(FileAction action, const CStdString &strFileA, const CStdString &strFileB, int64_t time); + bool ExecuteOperation(CFileOperationJob *base, double ¤t, double opWeight); + void Debug(); + virtual bool OnFileCallback(void* pContext, int ipercent, float avgSpeed); + private: + FileAction m_action; + CStdString m_strFileA, m_strFileB; + int64_t m_time; + }; + friend class CFileOperation; + typedef std::vector<CFileOperation> FileOperationList; + bool DoProcess(FileAction action, CFileItemList & items, const CStdString& strDestFile, FileOperationList &fileOperations, double &totalTime); + bool DoProcessFolder(FileAction action, const CStdString& strPath, const CStdString& strDestFile, FileOperationList &fileOperations, double &totalTime); + bool DoProcessFile(FileAction action, const CStdString& strFileA, const CStdString& strFileB, FileOperationList &fileOperations, double &totalTime); + + static inline bool CanBeRenamed(const CStdString &strFileA, const CStdString &strFileB); + + FileAction m_action; + CFileItemList m_items; + CStdString m_strDestFile; + CStdString m_avgSpeed, m_currentOperation, m_currentFile; + CGUIDialogProgressBarHandle* m_handle; + bool m_displayProgress; + int m_heading; + int m_line; +}; diff --git a/src/utils/FileUtils.cpp b/src/utils/FileUtils.cpp new file mode 100644 index 0000000000..2b54f363f3 --- /dev/null +++ b/src/utils/FileUtils.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2010-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 + * <http://www.gnu.org/licenses/>. + * + */ +#include "FileUtils.h" +#include "guilib/GUIWindowManager.h" +#include "dialogs/GUIDialogYesNo.h" +#include "guilib/GUIKeyboardFactory.h" +#include "utils/log.h" +#include "guilib/LocalizeStrings.h" +#include "JobManager.h" +#include "FileOperationJob.h" +#include "URIUtils.h" +#include "filesystem/MultiPathDirectory.h" +#include <vector> +#include "settings/MediaSourceSettings.h" +#include "Util.h" +#include "StringUtils.h" +#include "URL.h" +#include "settings/Settings.h" + +using namespace XFILE; +using namespace std; + +bool CFileUtils::DeleteItem(const CStdString &strPath, bool force) +{ + CFileItemPtr item(new CFileItem(strPath)); + item->SetPath(strPath); + item->m_bIsFolder = URIUtils::HasSlashAtEnd(strPath); + item->Select(true); + return DeleteItem(item, force); +} + +bool CFileUtils::DeleteItem(const CFileItemPtr &item, bool force) +{ + if (!item || item->IsParentFolder()) + return false; + + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + if (!force && pDialog) + { + pDialog->SetHeading(122); + pDialog->SetLine(0, 125); + pDialog->SetLine(1, CURL(item->GetPath()).GetWithoutUserDetails()); + pDialog->SetLine(2, ""); + pDialog->DoModal(); + if (!pDialog->IsConfirmed()) return false; + } + + // Create a temporary item list containing the file/folder for deletion + CFileItemPtr pItemTemp(new CFileItem(*item)); + pItemTemp->Select(true); + CFileItemList items; + items.Add(pItemTemp); + + // grab the real filemanager window, set up the progress bar, + // and process the delete action + CFileOperationJob op(CFileOperationJob::ActionDelete, items, ""); + + return op.DoWork(); +} + +bool CFileUtils::RenameFile(const CStdString &strFile) +{ + CStdString strFileAndPath(strFile); + URIUtils::RemoveSlashAtEnd(strFileAndPath); + CStdString strFileName = URIUtils::GetFileName(strFileAndPath); + CStdString strPath = URIUtils::GetDirectory(strFileAndPath); + if (CGUIKeyboardFactory::ShowAndGetInput(strFileName, g_localizeStrings.Get(16013), false)) + { + strPath = URIUtils::AddFileToFolder(strPath, strFileName); + CLog::Log(LOGINFO,"FileUtils: rename %s->%s\n", strFileAndPath.c_str(), strPath.c_str()); + if (URIUtils::IsMultiPath(strFileAndPath)) + { // special case for multipath renames - rename all the paths. + vector<std::string> paths; + CMultiPathDirectory::GetPaths(strFileAndPath, paths); + bool success = false; + for (unsigned int i = 0; i < paths.size(); ++i) + { + CStdString filePath(paths[i]); + URIUtils::RemoveSlashAtEnd(filePath); + filePath = URIUtils::GetDirectory(filePath); + filePath = URIUtils::AddFileToFolder(filePath, strFileName); + if (CFile::Rename(paths[i], filePath)) + success = true; + } + return success; + } + return CFile::Rename(strFileAndPath, strPath); + } + return false; +} + +bool CFileUtils::RemoteAccessAllowed(const CStdString &strPath) +{ + const unsigned int SourcesSize = 5; + CStdString SourceNames[] = { "programs", "files", "video", "music", "pictures" }; + + string realPath = URIUtils::GetRealPath(strPath); + // for rar:// and zip:// paths we need to extract the path to the archive + // instead of using the VFS path + while (URIUtils::IsInArchive(realPath)) + realPath = CURL(realPath).GetHostName(); + + if (StringUtils::StartsWithNoCase(realPath, "virtualpath://upnproot/")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "musicdb://")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "videodb://")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "library://video")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "sources://video")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "special://musicplaylists")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "special://profile/playlists")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "special://videoplaylists")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "special://skin")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "special://profile/addon_data")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "addons://sources")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "upnp://")) + return true; + else if (StringUtils::StartsWithNoCase(realPath, "plugin://")) + return true; + else + { + std::string strPlaylistsPath = CSettings::Get().GetString("system.playlistspath"); + URIUtils::RemoveSlashAtEnd(strPlaylistsPath); + if (StringUtils::StartsWithNoCase(realPath, strPlaylistsPath)) + return true; + } + bool isSource; + for (unsigned int index = 0; index < SourcesSize; index++) + { + VECSOURCES* sources = CMediaSourceSettings::Get().GetSources(SourceNames[index]); + int sourceIndex = CUtil::GetMatchingSource(realPath, *sources, isSource); + if (sourceIndex >= 0 && sourceIndex < (int)sources->size() && sources->at(sourceIndex).m_iHasLock != 2 && sources->at(sourceIndex).m_allowSharing) + return true; + } + return false; +} diff --git a/src/utils/FileUtils.h b/src/utils/FileUtils.h new file mode 100644 index 0000000000..a73ab62401 --- /dev/null +++ b/src/utils/FileUtils.h @@ -0,0 +1,31 @@ +#pragma once +/* + * Copyright (C) 2010-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 + * <http://www.gnu.org/licenses/>. + * + */ +#include <string> +#include "FileItem.h" + +class CFileUtils +{ +public: + static bool DeleteItem(const CFileItemPtr &item, bool force=false); + static bool DeleteItem(const CStdString &strPath, bool force=false); + static bool RenameFile(const CStdString &strFile); + static bool RemoteAccessAllowed(const CStdString &strPath); +}; diff --git a/src/utils/GLUtils.cpp b/src/utils/GLUtils.cpp new file mode 100644 index 0000000000..83d47d1663 --- /dev/null +++ b/src/utils/GLUtils.cpp @@ -0,0 +1,148 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" + +#include "GLUtils.h" +#include "log.h" +#include "settings/AdvancedSettings.h" +#include "windowing/WindowingFactory.h" + +void _VerifyGLState(const char* szfile, const char* szfunction, int lineno){ +#if defined(HAS_GL) && defined(_DEBUG) +#define printMatrix(matrix) \ + { \ + for (int ixx = 0 ; ixx<4 ; ixx++) \ + { \ + CLog::Log(LOGDEBUG, "% 3.3f % 3.3f % 3.3f % 3.3f ", \ + matrix[ixx*4], matrix[ixx*4+1], matrix[ixx*4+2], \ + matrix[ixx*4+3]); \ + } \ + } + if (g_advancedSettings.m_logLevel < LOG_LEVEL_DEBUG_FREEMEM) + return; + GLenum err = glGetError(); + if (err==GL_NO_ERROR) + return; + CLog::Log(LOGERROR, "GL ERROR: %s\n", gluErrorString(err)); + if (szfile && szfunction) + CLog::Log(LOGERROR, "In file:%s function:%s line:%d", szfile, szfunction, lineno); + GLboolean bools[16]; + GLfloat matrix[16]; + glGetFloatv(GL_SCISSOR_BOX, matrix); + CLog::Log(LOGDEBUG, "Scissor box: %f, %f, %f, %f", matrix[0], matrix[1], matrix[2], matrix[3]); + glGetBooleanv(GL_SCISSOR_TEST, bools); + CLog::Log(LOGDEBUG, "Scissor test enabled: %d", (int)bools[0]); + glGetFloatv(GL_VIEWPORT, matrix); + CLog::Log(LOGDEBUG, "Viewport: %f, %f, %f, %f", matrix[0], matrix[1], matrix[2], matrix[3]); + glGetFloatv(GL_PROJECTION_MATRIX, matrix); + CLog::Log(LOGDEBUG, "Projection Matrix:"); + printMatrix(matrix); + glGetFloatv(GL_MODELVIEW_MATRIX, matrix); + CLog::Log(LOGDEBUG, "Modelview Matrix:"); + printMatrix(matrix); +// abort(); +#endif +} + +void LogGraphicsInfo() +{ +#if defined(HAS_GL) || defined(HAS_GLES) + const GLubyte *s; + + s = glGetString(GL_VENDOR); + if (s) + CLog::Log(LOGNOTICE, "GL_VENDOR = %s", s); + else + CLog::Log(LOGNOTICE, "GL_VENDOR = NULL"); + + s = glGetString(GL_RENDERER); + if (s) + CLog::Log(LOGNOTICE, "GL_RENDERER = %s", s); + else + CLog::Log(LOGNOTICE, "GL_RENDERER = NULL"); + + s = glGetString(GL_VERSION); + if (s) + CLog::Log(LOGNOTICE, "GL_VERSION = %s", s); + else + CLog::Log(LOGNOTICE, "GL_VERSION = NULL"); + + s = glGetString(GL_SHADING_LANGUAGE_VERSION); + if (s) + CLog::Log(LOGNOTICE, "GL_SHADING_LANGUAGE_VERSION = %s", s); + else + CLog::Log(LOGNOTICE, "GL_SHADING_LANGUAGE_VERSION = NULL"); + + //GL_NVX_gpu_memory_info extension +#define GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX 0x9047 +#define GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX 0x9048 +#define GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX 0x9049 +#define GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX 0x904A +#define GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX 0x904B + + if (g_Windowing.IsExtSupported("GL_NVX_gpu_memory_info")) + { + GLint mem = 0; + + glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &mem); + CLog::Log(LOGNOTICE, "GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX = %i", mem); + + //this seems to be the amount of ram on the videocard + glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &mem); + CLog::Log(LOGNOTICE, "GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX = %i", mem); + } + + s = glGetString(GL_EXTENSIONS); + if (s) + CLog::Log(LOGNOTICE, "GL_EXTENSIONS = %s", s); + else + CLog::Log(LOGNOTICE, "GL_EXTENSIONS = NULL"); + +#else /* !HAS_GL */ + CLog::Log(LOGNOTICE, + "Please define LogGraphicsInfo for your chosen graphics libary"); +#endif /* !HAS_GL */ +} + +int glFormatElementByteCount(GLenum format) +{ + switch (format) + { +#ifndef HAS_GLES + case GL_BGRA: +#endif + case GL_RGBA: + return 4; +#ifndef HAS_GLES + case GL_BGR: +#endif + case GL_RGB: + return 3; + case GL_LUMINANCE_ALPHA: + return 2; + case GL_LUMINANCE: + case GL_ALPHA: + return 1; + default: + CLog::Log(LOGERROR, "glFormatElementByteCount - Unknown format %u", format); + return 1; + } +} diff --git a/src/utils/GLUtils.h b/src/utils/GLUtils.h new file mode 100644 index 0000000000..7ea50643e8 --- /dev/null +++ b/src/utils/GLUtils.h @@ -0,0 +1,46 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +// GL Error checking macro +// this function is useful for tracking down GL errors, which otherwise +// just result in undefined behavior and can be difficult to track down. +// +// Just call it 'VerifyGLState()' after a sequence of GL calls +// +// if GL_DEBUGGING and HAS_GL are defined, the function checks +// for GL errors and prints the current state of the various matrices; +// if not it's just an empty inline stub, and thus won't affect performance +// and will be optimized out. + +#include "system.h" +#include "system_gl.h" + +void _VerifyGLState(const char* szfile, const char* szfunction, int lineno); +#if defined(GL_DEBUGGING) && (defined(HAS_GL) || defined(HAS_GLES)) +#define VerifyGLState() _VerifyGLState(__FILE__, __FUNCTION__, __LINE__) +#else +#define VerifyGLState() +#endif + +void LogGraphicsInfo(); + +int glFormatElementByteCount(GLenum format); diff --git a/src/utils/GlobalsHandling.h b/src/utils/GlobalsHandling.h new file mode 100644 index 0000000000..c27f76eb46 --- /dev/null +++ b/src/utils/GlobalsHandling.h @@ -0,0 +1,224 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +/** + * This file contains the pattern for moving "globals" from the BSS Segment to the heap. + * A note on usage of this pattern for globals replacement: + * + * This pattern uses a singleton pattern and some compiler/C preprocessor sugar to allow + * "global" variables to be lazy instantiated and initialized and moved from the BSS segment + * to the heap (that is, they are instantiated on the heap when they are first used rather + * than relying on the startup code to initialize the BSS segment). This eliminates the + * problem associated with global variable dependencies across compilation units. + * + * Reference counting from the BSS segment is used to destruct these globals at the time the + * last compilation unit that knows about it is finalized by the post-main shutdown. The book + * keeping is done by smuggling a smart pointer into every file that references a particular + * "global class" through the use of a 'static' declaration of an instance of that smart + * pointer in the header file of the global class (did you ever think you'd see a file scope + * 'static' variable in a header file - on purpose?) + * + * There are two different ways to use this pattern when replacing global variables. + * The selection of which one to use depends on whether or not there is a possiblity + * that the code in the .cpp file for the global can be executed from a static method + * somewhere. This may take some explanation. + * + * The (at least) two ways to do this: + * + * 1) You can use the reference object boost::shared_ptr to access the global variable. + * + * This would be the preferred means since it is (very slightly) more efficent than + * the alternative. To use this pattern you replace standard static references to + * the global with access through the reference. If you use the C preprocessor to + * do this for you can put the following code in the header file where the global's + * class is declared: + * + * static boost::shared_ptr<GlobalVariableClass> g_globalVariableRef(xbmcutil::GlobalsSingleton<GlobalVariableClass>::getInstance()); + * #define g_globalVariable (*(g_globalVariableRef.get())) + * + * Note what this does. In every file that includes this header there will be a *static* + * instance of the boost::shared_ptr<GlobalVariableClass> smart pointer. This effectively + * reference counts the singleton from every compilation unit (ie, object code file that + * results from a compilation of a .c/.cpp file) that references this global directly. + * + * There is a problem with this, however. Keep in mind that the instance of the smart pointer + * (being in the BSS segment of the compilation unit) is ITSELF an object that depends on + * the BSS segment initialization in order to be initialized with an instance from the + * singleton. That means, depending on the code structure, it is possible to get into a + * circumstance where the above #define could be exercised PRIOR TO the setting of the + * value of the smart pointer. + * + * Some reflection on this should lead you to the conclusion that the only way for this to + * happen is if access to this global can take place through a static/global method, directly + * or indirectly (ie, the static/global method can call another method that uses the + * reference), where that static is called from initialization code exercised prior to + * the start of 'main.' + * + * Because of the "indirectly" in the above statement, this situation can be difficult to + * determine beforehand. + * + * 2) Alternatively, when you KNOW that the global variable can suffer from the above described + * problem, you can restrict all access to the variable to the singleton by changing + * the #define to: + * + * #define g_globalVariable (*(xbmcutil::Singleton<GlobalVariableClass>::getInstance())) + * + * A few things to note about this. First, this separates the reference counting aspect + * from the access aspect of this solution. The smart pointers are no longer used for + * access, only for reference counting. Secondly, all access is through the singleton directly + * so there is no reliance on the state of the BSS segment for the code to operate + * correctly. + * + * This solution is required for g_Windowing because it's accessed (both directly and + * indirectly) from the static methods of CLog which are called repeatedly from + * code exercised during the initialization of the BSS segment. + */ + +namespace xbmcutil +{ + /** + * This class is an implementation detail of the macros defined below and + * is NOT meant to be used as a general purpose utility. IOW, DO NOT USE THIS + * CLASS to support a general singleton design pattern, it's specialized + * for solving the initialization/finalization order/dependency problem + * with global variables and should only be used via the macros below. + * + * Currently THIS IS NOT THREAD SAFE! Why not just add a lock you ask? + * Because this singleton is used to initialize global variables and + * there is an issue with having the lock used prior to its + * initialization. No matter what, if this class is used as a replacement + * for global variables there's going to be a race condition if it's used + * anywhere else. So currently this is the only prescribed use. + * + * Therefore this hack depends on the fact that compilation unit global/static + * initialization is done in a single thread. + */ + template <class T> class GlobalsSingleton + { + /** + * This thing just deletes the shared_ptr when the 'instance' + * goes out of scope (when the bss segment of the compilation unit + * that 'instance' is sitting in is deinitialized). See the comment + * on 'instance' for more information. + */ + template <class K> class Deleter + { + public: + K* guarded; + ~Deleter() { if (guarded) delete guarded; } + }; + + /** + * Is it possible that getInstance can be called prior to the shared_ptr 'instance' + * being initialized as a global? If so, then the shared_ptr constructor would + * effectively 'reset' the shared pointer after it had been set by the prior + * getInstance call, and a second instance would be created. We really don't + * want this to happen so 'instance' is a pointer to a smart pointer so that + * we can deterministally handle its construction. It is guarded by the + * Deleter class above so that when the bss segment that this static is + * sitting in is deinitialized, the shared_ptr pointer will be cleaned up. + */ + static Deleter<boost::shared_ptr<T> > instance; + + /** + * See 'getQuick' below. + */ + static T* quick; + public: + + /** + * Retrieve an instance of the singleton using a shared pointer for + * referenece counting. + */ + inline static boost::shared_ptr<T> getInstance() + { + if (!instance.guarded) + { + if (!quick) + quick = new T; + instance.guarded = new boost::shared_ptr<T>(quick); + } + return *(instance.guarded); + } + + /** + * This is for quick access when using form (2) of the pattern. Before 'mdd' points + * it out, this might be a case of 'solving problems we don't have' but this access + * is used frequently within the event loop so any help here should benefit the + * overall performance and there is nothing complicated or tricky here and not + * a lot of code to maintain. + */ + inline static T* getQuick() + { + if (!quick) + quick = new T; + + return quick; + } + + }; + + template <class T> typename GlobalsSingleton<T>::template Deleter<boost::shared_ptr<T> > GlobalsSingleton<T>::instance; + template <class T> T* GlobalsSingleton<T>::quick; + + /** + * This is another bit of hackery that will act as a flag for + * whether or not a global/static has been initialized yet. An instance + * should be placed in the cpp file after the static/global it's meant to + * monitor. + */ + class InitFlag { public: InitFlag(bool& flag) { flag = true; } }; +} + +/** + * For pattern (2) above, you can use the following macro. This pattern is safe to + * use in all cases but may be very slightly less efficient. + * + * Also, you must also use a #define to replace the actual global variable since + * there's no way to use a macro to add a #define. An example would be: + * + * XBMC_GLOBAL_REF(CWinSystemWin32DX, g_Windowing); + * #define g_Windowing XBMC_GLOBAL_USE(CWinSystemWin32DX) + * + */ +#define XBMC_GLOBAL_REF(classname,g_variable) \ + static boost::shared_ptr<classname> g_variable##Ref(xbmcutil::GlobalsSingleton<classname>::getInstance()) + +/** + * This declares the actual use of the variable. It needs to be used in another #define + * of the form: + * + * #define g_variable XBMC_GLOBAL_USE(classname) + */ +#define XBMC_GLOBAL_USE(classname) (*(xbmcutil::GlobalsSingleton<classname>::getQuick())) + +/** + * For pattern (1) above, you can use the following macro. WARNING: This should only + * be used when the global in question is never accessed, directly or indirectly, from + * a static method called (again, directly or indirectly) during startup or shutdown. + */ +#define XBMC_GLOBAL(classname,g_variable) \ + XBMC_GLOBAL_REF(classname,g_variable); \ + static classname & g_variable = (*(g_variable##Ref.get())) + diff --git a/src/utils/GroupUtils.cpp b/src/utils/GroupUtils.cpp new file mode 100644 index 0000000000..208403eb18 --- /dev/null +++ b/src/utils/GroupUtils.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> +#include <set> + +#include "GroupUtils.h" +#include "FileItem.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "video/VideoDbUrl.h" +#include "video/VideoInfoTag.h" +#include "utils/URIUtils.h" +#include "filesystem/MultiPathDirectory.h" + +using namespace std; + +typedef map<int, set<CFileItemPtr> > SetMap; + +bool GroupUtils::Group(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItems, GroupAttribute groupAttributes /* = GroupAttributeNone */) +{ + if (groupBy == GroupByNone) + return false; + + // nothing to do if there are no items to group + if (items.Size() <= 0) + return true; + + SetMap setMap; + for (int index = 0; index < items.Size(); index++) + { + bool add = true; + const CFileItemPtr item = items.Get(index); + + // group by sets + if ((groupBy & GroupBySet) && + item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iSetId > 0) + { + add = false; + setMap[item->GetVideoInfoTag()->m_iSetId].insert(item); + } + + if (add) + groupedItems.Add(item); + } + + if ((groupBy & GroupBySet) && !setMap.empty()) + { + CVideoDbUrl itemsUrl; + if (!itemsUrl.FromString(baseDir)) + return false; + + for (SetMap::const_iterator set = setMap.begin(); set != setMap.end(); ++set) + { + // only one item in the set, so just re-add it + if (set->second.size() == 1 && (groupAttributes & GroupAttributeIgnoreSingleItems)) + { + groupedItems.Add(*set->second.begin()); + continue; + } + + CFileItemPtr pItem(new CFileItem((*set->second.begin())->GetVideoInfoTag()->m_strSet)); + pItem->GetVideoInfoTag()->m_iDbId = set->first; + pItem->GetVideoInfoTag()->m_type = MediaTypeVideoCollection; + + std::string basePath = StringUtils::Format("videodb://movies/sets/%i/", set->first); + CVideoDbUrl videoUrl; + if (!videoUrl.FromString(basePath)) + pItem->SetPath(basePath); + else + { + videoUrl.AddOptions(itemsUrl.GetOptionsString()); + pItem->SetPath(videoUrl.ToString()); + } + pItem->m_bIsFolder = true; + + CVideoInfoTag* setInfo = pItem->GetVideoInfoTag(); + setInfo->m_strPath = pItem->GetPath(); + setInfo->m_strTitle = pItem->GetLabel(); + + int ratings = 0; + int iWatched = 0; // have all the movies been played at least once? + std::set<std::string> pathSet; + for (std::set<CFileItemPtr>::const_iterator movie = set->second.begin(); movie != set->second.end(); ++movie) + { + CVideoInfoTag* movieInfo = (*movie)->GetVideoInfoTag(); + // handle rating + if (movieInfo->m_fRating > 0.0f) + { + ratings++; + setInfo->m_fRating += movieInfo->m_fRating; + } + + // handle year + if (movieInfo->m_iYear > setInfo->m_iYear) + setInfo->m_iYear = movieInfo->m_iYear; + + // handle lastplayed + if (movieInfo->m_lastPlayed.IsValid() && movieInfo->m_lastPlayed > setInfo->m_lastPlayed) + setInfo->m_lastPlayed = movieInfo->m_lastPlayed; + + // handle dateadded + if (movieInfo->m_dateAdded.IsValid() && movieInfo->m_dateAdded > setInfo->m_dateAdded) + setInfo->m_dateAdded = movieInfo->m_dateAdded; + + // handle playcount/watched + setInfo->m_playCount += movieInfo->m_playCount; + if (movieInfo->m_playCount > 0) + iWatched++; + + //accumulate the path for a multipath construction + CFileItem video(movieInfo->m_basePath, false); + if (video.IsVideo()) + pathSet.insert(URIUtils::GetParentPath(movieInfo->m_basePath)); + else + pathSet.insert(movieInfo->m_basePath); + } + setInfo->m_basePath = XFILE::CMultiPathDirectory::ConstructMultiPath(pathSet); + + if (ratings > 1) + pItem->GetVideoInfoTag()->m_fRating /= ratings; + + setInfo->m_playCount = iWatched >= (int)set->second.size() ? (setInfo->m_playCount / set->second.size()) : 0; + pItem->SetProperty("total", (int)set->second.size()); + pItem->SetProperty("watched", iWatched); + pItem->SetProperty("unwatched", (int)set->second.size() - iWatched); + pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, setInfo->m_playCount > 0); + + groupedItems.Add(pItem); + } + } + + return true; +} + +bool GroupUtils::GroupAndSort(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, const SortDescription &sortDescription, CFileItemList &groupedItems, GroupAttribute groupAttributes /* = GroupAttributeNone */) +{ + if (!Group(groupBy, baseDir, items, groupedItems, groupAttributes)) + return false; + + groupedItems.Sort(sortDescription); + return true; +} diff --git a/src/utils/GroupUtils.h b/src/utils/GroupUtils.h new file mode 100644 index 0000000000..99fefac900 --- /dev/null +++ b/src/utils/GroupUtils.h @@ -0,0 +1,42 @@ +#pragma once +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "SortUtils.h" + +class CFileItemList; + +// can be used as a flag +typedef enum { + GroupByNone = 0x0, + GroupBySet = 0x1 +} GroupBy; + +typedef enum { + GroupAttributeNone = 0x0, + GroupAttributeIgnoreSingleItems = 0x1 +} GroupAttribute; + +class GroupUtils +{ +public: + static bool Group(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItems, GroupAttribute groupAttributes = GroupAttributeNone); + static bool GroupAndSort(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, const SortDescription &sortDescription, CFileItemList &groupedItems, GroupAttribute groupAttributes = GroupAttributeNone); +}; diff --git a/src/utils/HTMLTable.cpp b/src/utils/HTMLTable.cpp new file mode 100644 index 0000000000..1cd7e9dd4b --- /dev/null +++ b/src/utils/HTMLTable.cpp @@ -0,0 +1,121 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "HTMLTable.h" +#include "HTMLUtil.h" + + +using namespace HTML; + +CHTMLRow::CHTMLRow(void) +{} + +CHTMLRow::~CHTMLRow(void) +{} + +int CHTMLRow::GetColumns() const +{ + return (int)m_vecColums.size(); +} + +const std::string& CHTMLRow::GetColumValue(int iColumn) const +{ + return m_vecColums[iColumn]; +} + +void CHTMLRow::Parse(const std::string& strTable) +{ + CHTMLUtil util; + std::string strTag; + int iTableRowStart = 0; + do + { + iTableRowStart = util.FindTag(strTable, "<td", strTag, iTableRowStart); + if (iTableRowStart >= 0) + { + iTableRowStart += (int)strTag.size(); + int iTableRowEnd = util.FindClosingTag(strTable, "td", strTag, iTableRowStart) - 1; + if (iTableRowEnd < -1) + break; + + std::string strRow = strTable.substr(iTableRowStart, 1 + iTableRowEnd - iTableRowStart); + m_vecColums.push_back(strRow); + + iTableRowStart = iTableRowEnd + 1; + + } + } + while (iTableRowStart >= 0); +} +//------------------------------------------------------------------------------ +CHTMLTable::CHTMLTable(void) +{} + +CHTMLTable::~CHTMLTable(void) +{} + +int CHTMLTable::GetRows() const +{ + return (int)m_vecRows.size(); +} + +const CHTMLRow& CHTMLTable::GetRow(int iRow) const +{ + return m_vecRows[iRow]; +} + +void CHTMLTable::Parse(const std::string& strHTML) +{ + m_vecRows.erase(m_vecRows.begin(), m_vecRows.end()); + CHTMLUtil util; + std::string strTag; + int iPosStart = util.FindTag(strHTML, "<table", strTag); + if (iPosStart >= 0) + { + iPosStart += (int)strTag.size(); + int iPosEnd = util.FindClosingTag(strHTML, "table", strTag, iPosStart) - 1; + if (iPosEnd < 0) + { + iPosEnd = (int)strHTML.size(); + } + + std::string strTable = strHTML.substr(iPosStart, 1 + iPosEnd - iPosStart); + int iTableRowStart = 0; + do + { + iTableRowStart = util.FindTag(strTable, "<tr", strTag, iTableRowStart); + if (iTableRowStart >= 0) + { + iTableRowStart += (int)strTag.size(); + int iTableRowEnd = util.FindClosingTag(strTable, "tr", strTag, iTableRowStart) - 1; + if (iTableRowEnd < 0) + break; + + std::string strRow = strTable.substr(iTableRowStart, 1 + iTableRowEnd - iTableRowStart); + CHTMLRow row; + row.Parse(strRow); + m_vecRows.push_back(row); + iTableRowStart = iTableRowEnd + 1; + } + } + while (iTableRowStart >= 0); + } +} + diff --git a/src/utils/HTMLTable.h b/src/utils/HTMLTable.h new file mode 100644 index 0000000000..d9705ba706 --- /dev/null +++ b/src/utils/HTMLTable.h @@ -0,0 +1,52 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <vector> + +namespace HTML +{ +class CHTMLRow +{ +public: + CHTMLRow(void); + virtual ~CHTMLRow(void); + int GetColumns() const; + const std::string& GetColumValue(int iColumn) const; + void Parse(const std::string& strTableRow); + +protected: + std::vector<std::string> m_vecColums; +}; + +class CHTMLTable +{ +public: + CHTMLTable(void); + virtual ~CHTMLTable(void); + void Parse(const std::string& strHTML); + int GetRows() const; + const CHTMLRow& GetRow(int iRow) const; +protected: + std::vector<CHTMLRow> m_vecRows; +}; +} diff --git a/src/utils/HTMLUtil.cpp b/src/utils/HTMLUtil.cpp new file mode 100644 index 0000000000..feb9edcf19 --- /dev/null +++ b/src/utils/HTMLUtil.cpp @@ -0,0 +1,334 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "HTMLUtil.h" +#include "utils/StringUtils.h" +#include <wctype.h> + +using namespace std; +using namespace HTML; + + +CHTMLUtil::CHTMLUtil(void) +{} + +CHTMLUtil::~CHTMLUtil(void) +{} + +int CHTMLUtil::FindTag(const std::string& strHTML, const std::string& strTag, std::string& strtagFound, int iPos) +{ + std::string strHTMLLow = strHTML; + std::string strTagLow = strTag; + StringUtils::ToLower(strHTMLLow); + StringUtils::ToLower(strTagLow); + strtagFound = ""; + + size_t iStart = strHTMLLow.find(strTag, iPos); + if (iStart == std::string::npos) + return -1; + + size_t iEnd = strHTMLLow.find(">", iStart); + if (iEnd == std::string::npos) + iEnd = strHTMLLow.size(); + + strtagFound = strHTMLLow.substr(iStart, (iEnd + 1) - iStart); + return iStart; +} + +int CHTMLUtil::FindClosingTag(const std::string& strHTML, const std::string& strTag, std::string& strtagFound, int iPos) +{ + std::string strHTMLLow = strHTML; + std::string strTagLow = strTag; + StringUtils::ToLower(strHTMLLow); + StringUtils::ToLower(strTagLow); + strtagFound = ""; + + size_t iStart = strHTMLLow.find("</" + strTag, iPos); + if (iStart == std::string::npos) + return -1; + + size_t iOpenStart = strHTMLLow.find("<" + strTag, iPos); + while (iOpenStart < iStart && iOpenStart != std::string::npos) + { + iStart = strHTMLLow.find("</" + strTag, iStart + 1); + iOpenStart = strHTMLLow.find("<" + strTag, iOpenStart + 1); + } + + size_t iEnd = strHTMLLow.find(">", iStart); + if (iEnd == std::string::npos) + iEnd = strHTMLLow.size(); + + strtagFound = strHTMLLow.substr(iStart, (iEnd + 1) - iStart); + return iStart; +} + +void CHTMLUtil::getValueOfTag(const std::string& strTagAndValue, std::string& strValue) +{ + // strTagAndValue contains: + // like <a href=blablabla.....>value</a> + strValue = strTagAndValue; + size_t iStart = strTagAndValue.find(">"); + size_t iEnd = strTagAndValue.find("<", iStart + 1); + if (iStart != std::string::npos && + iEnd != std::string::npos) + { + iStart++; + strValue = strTagAndValue.substr(iStart, iEnd - iStart); + } +} + +void CHTMLUtil::getAttributeOfTag(const std::string& strTagAndValue, const std::string& strTag, std::string& strValue) +{ + // strTagAndValue contains: + // like <a href=""value"..... + strValue = strTagAndValue; + size_t iStart = strTagAndValue.find(strTag); + if (iStart == std::string::npos) + return ; + + iStart += strTag.size(); + + while (strTagAndValue[iStart + 1] == 0x20 || + strTagAndValue[iStart + 1] == 0x27 || + strTagAndValue[iStart + 1] == 34) + iStart++; + + size_t iEnd = iStart + 1; + while (strTagAndValue[iEnd] != 0x27 && + strTagAndValue[iEnd] != 0x20 && + strTagAndValue[iEnd] != 34 && + strTagAndValue[iEnd] != '>') + iEnd++; + + if (iStart != std::string::npos && iEnd != std::string::npos) + { + strValue = strTagAndValue.substr(iStart, iEnd - iStart); + } +} + +void CHTMLUtil::RemoveTags(std::string& strHTML) +{ + int iNested = 0; + std::string strReturn = ""; + for (int i = 0; i < (int) strHTML.size(); ++i) + { + if (strHTML[i] == '<') iNested++; + else if (strHTML[i] == '>') iNested--; + else + { + if (!iNested) + { + strReturn += strHTML[i]; + } + } + } + + strHTML = strReturn; +} + +typedef struct +{ + const wchar_t* html; + const wchar_t w; +} HTMLMapping; + +static const HTMLMapping mappings[] = + {{L"&", 0x0026}, + {L"'", 0x0027}, + {L"´", 0x00B4}, + {L"à", 0x00E0}, + {L"á", 0x00E1}, + {L"â", 0x00E2}, + {L"ã", 0x00E3}, + {L"ä", 0x00E4}, + {L"å", 0x00E5}, + {L"æ", 0x00E6}, + {L"À", 0x00C0}, + {L"Á", 0x00C1}, + {L"Â", 0x00C2}, + {L"Ã", 0x00C3}, + {L"Ä", 0x00C4}, + {L"Å", 0x00C5}, + {L"Æ", 0x00C6}, + {L"„", 0x201E}, + {L"¦", 0x00A6}, + {L"•", 0x2022}, + {L"•", 0x2022}, + {L"¢", 0x00A2}, + {L"ˆ", 0x02C6}, + {L"¤", 0x00A4}, + {L"©", 0x00A9}, + {L"¸", 0x00B8}, + {L"Ç", 0x00C7}, + {L"ç", 0x00E7}, + {L"†", 0x2020}, + {L"°", 0x00B0}, + {L"÷", 0x00F7}, + {L"‡", 0x2021}, + {L"è", 0x00E8}, + {L"é", 0x00E9}, + {L"ê", 0x00EA}, + {L" ", 0x2003}, + {L" ", 0x2002}, + {L"ë", 0x00EB}, + {L"ð", 0x00F0}, + {L"€", 0x20AC}, + {L"È", 0x00C8}, + {L"É", 0x00C9}, + {L"Ê", 0x00CA}, + {L"Ë", 0x00CB}, + {L"Ð", 0x00D0}, + {L""", 0x0022}, + {L"⁄", 0x2044}, + {L"¼", 0x00BC}, + {L"½", 0x00BD}, + {L"¾", 0x00BE}, + {L">", 0x003E}, + {L"…", 0x2026}, + {L"¡", 0x00A1}, + {L"¿", 0x00BF}, + {L"ì", 0x00EC}, + {L"í", 0x00ED}, + {L"î", 0x00EE}, + {L"ï", 0x00EF}, + {L"Ì", 0x00CC}, + {L"Í", 0x00CD}, + {L"Î", 0x00CE}, + {L"Ï", 0x00CF}, + {L"‎", 0x200E}, + {L"<", 0x003C}, + {L"«", 0x00AB}, + {L"“", 0x201C}, + {L"‹", 0x2039}, + {L"‘", 0x2018}, + {L"¯", 0x00AF}, + {L"µ", 0x00B5}, + {L"·", 0x00B7}, + {L"—", 0x2014}, + {L" ", 0x00A0}, + {L"–", 0x2013}, + {L"ñ", 0x00F1}, + {L"¬", 0x00AC}, + {L"Ñ", 0x00D1}, + {L"ª", 0x00AA}, + {L"º", 0x00BA}, + {L"œ", 0x0153}, + {L"ò", 0x00F2}, + {L"ó", 0x00F3}, + {L"ô", 0x00F4}, + {L"õ", 0x00F5}, + {L"ö", 0x00F6}, + {L"ø", 0x00F8}, + {L"Œ", 0x0152}, + {L"Ò", 0x00D2}, + {L"Ó", 0x00D3}, + {L"Ô", 0x00D4}, + {L"Õ", 0x00D5}, + {L"Ö", 0x00D6}, + {L"Ø", 0x00D8}, + {L"¶", 0x00B6}, + {L"‰", 0x2030}, + {L"±", 0x00B1}, + {L"£", 0x00A3}, + {L"»", 0x00BB}, + {L"”", 0x201D}, + {L"®", 0x00AE}, + {L"‏", 0x200F}, + {L"›", 0x203A}, + {L"’", 0x2019}, + {L"‚", 0x201A}, + {L"š", 0x0161}, + {L"§", 0x00A7}, + {L"­", 0x00AD}, + {L"¹", 0x00B9}, + {L"²", 0x00B2}, + {L"³", 0x00B3}, + {L"ß", 0x00DF}, + {L"Š", 0x0160}, + {L" ", 0x2009}, + {L"þ", 0x00FE}, + {L"˜", 0x02DC}, + {L"×", 0x00D7}, + {L"™", 0x2122}, + {L"Þ", 0x00DE}, + {L"¨", 0x00A8}, + {L"ù", 0x00F9}, + {L"ú", 0x00FA}, + {L"û", 0x00FB}, + {L"ü", 0x00FC}, + {L"Ù", 0x00D9}, + {L"Ú", 0x00DA}, + {L"Û", 0x00DB}, + {L"Ü", 0x00DC}, + {L"¥", 0x00A5}, + {L"ÿ", 0x00FF}, + {L"ý", 0x00FD}, + {L"Ý", 0x00DD}, + {L"Ÿ", 0x0178}, + {L"‍", 0x200D}, + {L"‌", 0x200C}, + {NULL, L'\0'}}; + +void CHTMLUtil::ConvertHTMLToW(const std::wstring& strHTML, std::wstring& strStripped) +{ + /* TODO:STRING_CLEANUP */ + if (strHTML.size() == 0) + { + strStripped.clear(); + return ; + } + size_t iPos = 0; + strStripped = strHTML; + while (mappings[iPos].html) + { + StringUtils::Replace(strStripped, mappings[iPos].html,std::wstring(1, mappings[iPos].w)); + iPos++; + } + + iPos = strStripped.find(L"&#"); + while (iPos > 0 && iPos < strStripped.size() - 4) + { + size_t iStart = iPos + 1; + iPos += 2; + std::wstring num; + int base = 10; + if (strStripped[iPos] == L'x') + { + base = 16; + iPos++; + } + + size_t i = iPos; + while (iPos < strStripped.size() && + (base == 16 ? iswxdigit(strStripped[iPos]) : iswdigit(strStripped[iPos]))) + iPos++; + + num = strStripped.substr(i, iPos-i); + wchar_t val = (wchar_t)wcstol(num.c_str(),NULL,base); + if (base == 10) + num = StringUtils::Format(L"&#%ls;", num.c_str()); + else + num = StringUtils::Format(L"&#x%ls;", num.c_str()); + + StringUtils::Replace(strStripped, num,std::wstring(1,val)); + iPos = strStripped.find(L"&#", iStart); + } +} + diff --git a/src/utils/HTMLUtil.h b/src/utils/HTMLUtil.h new file mode 100644 index 0000000000..7d67634e20 --- /dev/null +++ b/src/utils/HTMLUtil.h @@ -0,0 +1,39 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> + +namespace HTML +{ +class CHTMLUtil +{ +public: + CHTMLUtil(void); + virtual ~CHTMLUtil(void); + static int FindTag(const std::string& strHTML, const std::string& strTag, std::string& strtagFound, int iPos = 0); + static int FindClosingTag(const std::string& strHTML, const std::string& strTag, std::string& strtagFound, int iPos); + static void getValueOfTag(const std::string& strTagAndValue, std::string& strValue); + static void getAttributeOfTag(const std::string& strTagAndValue, const std::string& strTag, std::string& strValue); + static void RemoveTags(std::string& strHTML); + static void ConvertHTMLToW(const std::wstring& strHTML, std::wstring& strStripped); +}; +} diff --git a/src/utils/HttpHeader.cpp b/src/utils/HttpHeader.cpp new file mode 100644 index 0000000000..3e80789f44 --- /dev/null +++ b/src/utils/HttpHeader.cpp @@ -0,0 +1,252 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "HttpHeader.h" +#include "utils/StringUtils.h" + +// header white space characters according to RFC 2616 +const char* const CHttpHeader::m_whitespaceChars = " \t"; + + +CHttpHeader::CHttpHeader() +{ + m_headerdone = false; +} + +CHttpHeader::~CHttpHeader() +{ +} + +void CHttpHeader::Parse(const std::string& strData) +{ + size_t pos = 0; + const size_t len = strData.length(); + const char* const strDataC = strData.c_str(); + + // According to RFC 2616 any header line can have continuation on next line, if next line is started from whitespace char + // This code at first checks for whitespace char at the begging of the line, and if found, then current line is appended to m_lastHeaderLine + // If current line is NOT started from whitespace char, then previously stored (and completed) m_lastHeaderLine is parsed and current line is assigned to m_lastHeaderLine (to be parsed later) + while (pos < len) + { + size_t lineEnd = strData.find('\x0a', pos); // use '\x0a' instead of '\n' to be platform independent + + if (lineEnd == std::string::npos) + return; // error: expected only complete lines + + const size_t nextLine = lineEnd + 1; + if (lineEnd > pos && strDataC[lineEnd - 1] == '\x0d') // use '\x0d' instead of '\r' to be platform independent + lineEnd--; + + if (m_headerdone) + Clear(); // clear previous header and process new one + + if (strDataC[pos] == ' ' || strDataC[pos] == '\t') // same chars as in CHttpHeader::m_whitespaceChars + { // line is started from whitespace char: this is continuation of previous line + pos = strData.find_first_not_of(m_whitespaceChars, pos); + + m_lastHeaderLine.push_back(' '); // replace all whitespace chars at start of the line with single space + m_lastHeaderLine.append(strData, pos, lineEnd - pos); // append current line + } + else + { // this line is NOT continuation, this line is new header line + if (!m_lastHeaderLine.empty()) + ParseLine(m_lastHeaderLine); // process previously stored completed line (if any) + + m_lastHeaderLine.assign(strData, pos, lineEnd - pos); // store current line to (possibly) complete later. Will be parsed on next turns. + + if (pos == lineEnd) + m_headerdone = true; // current line is bare "\r\n" (or "\n"), means end of header; no need to process current m_lastHeaderLine + } + + pos = nextLine; // go to next line (if any) + } +} + +bool CHttpHeader::ParseLine(const std::string& headerLine) +{ + const size_t valueStart = headerLine.find(':'); + + if (valueStart != std::string::npos) + { + std::string strParam(headerLine, 0, valueStart); + std::string strValue(headerLine, valueStart + 1); + + StringUtils::Trim(strParam, m_whitespaceChars); + StringUtils::ToLower(strParam); + + StringUtils::Trim(strValue, m_whitespaceChars); + + if (!strParam.empty() && !strValue.empty()) + m_params.push_back(HeaderParams::value_type(strParam, strValue)); + else + return false; + } + else if (m_protoLine.empty()) + m_protoLine = headerLine; + + return true; +} + +void CHttpHeader::AddParam(const std::string& param, const std::string& value, const bool overwrite /*= false*/) +{ + std::string paramLower(param); + StringUtils::ToLower(paramLower); + StringUtils::Trim(paramLower, m_whitespaceChars); + if (paramLower.empty()) + return; + + if (overwrite) + { // delete ALL parameters with the same name + // note: 'GetValue' always returns last added parameter, + // so you probably don't need to overwrite + for (size_t i = 0; i < m_params.size();) + { + if (m_params[i].first == paramLower) + m_params.erase(m_params.begin() + i); + else + ++i; + } + } + + std::string valueTrim(value); + StringUtils::Trim(valueTrim, m_whitespaceChars); + if (valueTrim.empty()) + return; + + m_params.push_back(HeaderParams::value_type(paramLower, valueTrim)); +} + +std::string CHttpHeader::GetValue(const std::string& strParam) const +{ + std::string paramLower(strParam); + StringUtils::ToLower(paramLower); + + return GetValueRaw(paramLower); +} + +std::string CHttpHeader::GetValueRaw(const std::string& strParam) const +{ + // look in reverse to find last parameter (probably most important) + for (HeaderParams::const_reverse_iterator iter = m_params.rbegin(); iter != m_params.rend(); ++iter) + { + if (iter->first == strParam) + return iter->second; + } + + return ""; +} + +std::vector<std::string> CHttpHeader::GetValues(std::string strParam) const +{ + StringUtils::ToLower(strParam); + std::vector<std::string> values; + + for (HeaderParams::const_iterator iter = m_params.begin(); iter != m_params.end(); ++iter) + { + if (iter->first == strParam) + values.push_back(iter->second); + } + + return values; +} + +std::string CHttpHeader::GetHeader(void) const +{ + if (m_protoLine.empty() && m_params.empty()) + return ""; + + std::string strHeader(m_protoLine + "\r\n"); + + for (HeaderParams::const_iterator iter = m_params.begin(); iter != m_params.end(); ++iter) + strHeader += ((*iter).first + ": " + (*iter).second + "\r\n"); + + strHeader += "\r\n"; + return strHeader; +} + +std::string CHttpHeader::GetMimeType(void) const +{ + std::string strValue(GetValueRaw("content-type")); + + std::string mimeType(strValue, 0, strValue.find(';')); + StringUtils::TrimRight(mimeType, m_whitespaceChars); + + return mimeType; +} + +std::string CHttpHeader::GetCharset(void) const +{ + std::string strValue(GetValueRaw("content-type")); + if (strValue.empty()) + return strValue; + + StringUtils::ToUpper(strValue); + const size_t len = strValue.length(); + + // extract charset value from 'contenttype/contentsubtype;pram1=param1Val ; charset=XXXX\t;param2=param2Val' + // most common form: 'text/html; charset=XXXX' + // charset value can be in double quotes: 'text/xml; charset="XXX XX"' + + size_t pos = strValue.find(';'); + while (pos < len) + { + // move to the next non-whitespace character + pos = strValue.find_first_not_of(m_whitespaceChars, pos + 1); + + if (pos != std::string::npos) + { + if (strValue.compare(pos, 8, "CHARSET=", 8) == 0) + { + pos += 8; // move position to char after 'CHARSET=' + int len = strValue.find(';', pos); + if (len != std::string::npos) + len -= pos; + std::string charset(strValue, pos, len); // intentionally ignoring possible ';' inside quoted string + // as we don't support any charset with ';' in name + StringUtils::Trim(charset, m_whitespaceChars); + if (!charset.empty()) + { + if (charset[0] != '"') + return charset; + else + { // charset contains quoted string (allowed according to RFC 2616) + StringUtils::Replace(charset, "\\", ""); // unescape chars, ignoring possible '\"' and '\\' + const size_t closingQ = charset.find('"', 1); + if (closingQ == std::string::npos) + return ""; // no closing quote + + return charset.substr(1, closingQ - 1); + } + } + } + pos = strValue.find(';', pos); // find next parameter + } + } + + return ""; // no charset is detected +} + +void CHttpHeader::Clear() +{ + m_params.clear(); + m_protoLine.clear(); + m_headerdone = false; + m_lastHeaderLine.clear(); +} diff --git a/src/utils/HttpHeader.h b/src/utils/HttpHeader.h new file mode 100644 index 0000000000..38875240b0 --- /dev/null +++ b/src/utils/HttpHeader.h @@ -0,0 +1,65 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <utility> +#include <vector> +#include <string> + +class CHttpHeader +{ +public: + typedef std::pair<std::string, std::string> HeaderParamValue; + typedef std::vector<HeaderParamValue> HeaderParams; + typedef HeaderParams::iterator HeaderParamsIter; + + CHttpHeader(); + ~CHttpHeader(); + + void Parse(const std::string& strData); + void AddParam(const std::string& param, const std::string& value, const bool overwrite = false); + + std::string GetValue(const std::string& strParam) const; + std::vector<std::string> GetValues(std::string strParam) const; + + std::string GetHeader(void) const; + + std::string GetMimeType(void) const; + std::string GetCharset(void) const; + inline std::string GetProtoLine() const + { return m_protoLine; } + + inline bool IsHeaderDone(void) const + { return m_headerdone; } + + void Clear(); + +protected: + std::string GetValueRaw(const std::string& strParam) const; + bool ParseLine(const std::string& headerLine); + + HeaderParams m_params; + std::string m_protoLine; + bool m_headerdone; + std::string m_lastHeaderLine; + static const char* const m_whitespaceChars; +}; + diff --git a/src/utils/HttpParser.cpp b/src/utils/HttpParser.cpp new file mode 100644 index 0000000000..deab1a5e2c --- /dev/null +++ b/src/utils/HttpParser.cpp @@ -0,0 +1,266 @@ +/* + * This code implements parsing of HTTP requests. + * This code was written by Steve Hanov in 2009, no copyright is claimed. + * This code is in the public domain. + * Code was taken from http://refactormycode.com/codes/778-an-efficient-http-parser + * + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "HttpParser.h" + +HttpParser::HttpParser() : + _headerStart(0), + _bodyStart(0), + _parsedTo( 0 ), + _state( 0 ), + _keyIndex(0), + _valueIndex(0), + _contentLength(0), + _contentStart(0), + _uriIndex(0), + _status( Incomplete ) +{ + +} + +HttpParser::~HttpParser() +{ + +} + +void +HttpParser::parseHeader() +{ + // run the fsm. + const int CR = 13; + const int LF = 10; + const int ANY = 256; + + enum Action { + // make lower case + LOWER = 0x1, + + // convert current character to null. + NULLIFY = 0x2, + + // set the header index to the current position + SET_HEADER_START = 0x4, + + // set the key index to the current position + SET_KEY = 0x8, + + // set value index to the current position. + SET_VALUE = 0x10, + + // store current key/value pair. + STORE_KEY_VALUE = 0x20, + + // sets content start to current position + 1 + SET_CONTENT_START = 0x40 + }; + + static const struct FSM { + State curState; + int c; + State nextState; + unsigned actions; + } fsm[] = { + { p_request_line, CR, p_request_line_cr, NULLIFY }, + { p_request_line, ANY, p_request_line, 0 }, + { p_request_line_cr, LF, p_request_line_crlf, 0 }, + { p_request_line_crlf, CR, p_request_line_crlfcr, 0 }, + { p_request_line_crlf, ANY, p_key, SET_HEADER_START | SET_KEY | LOWER }, + { p_request_line_crlfcr, LF, p_content, SET_CONTENT_START }, + { p_key, ':', p_key_colon, NULLIFY }, + { p_key, ANY, p_key, LOWER }, + { p_key_colon, ' ', p_key_colon_sp, 0 }, + { p_key_colon_sp, ANY, p_value, SET_VALUE }, + { p_value, CR, p_value_cr, NULLIFY | STORE_KEY_VALUE }, + { p_value, ANY, p_value, 0 }, + { p_value_cr, LF, p_value_crlf, 0 }, + { p_value_crlf, CR, p_value_crlfcr, 0 }, + { p_value_crlf, ANY, p_key, SET_KEY | LOWER }, + { p_value_crlfcr, LF, p_content, SET_CONTENT_START }, + { p_error, ANY, p_error, 0 } + }; + + for( unsigned i = _parsedTo; i < _data.length(); ++i) { + char c = _data[i]; + State nextState = p_error; + + for ( unsigned d = 0; d < sizeof(fsm) / sizeof(FSM); ++d ) { + if ( fsm[d].curState == _state && + ( c == fsm[d].c || fsm[d].c == ANY ) ) { + + nextState = fsm[d].nextState; + + if ( fsm[d].actions & LOWER ) { + _data[i] = tolower( _data[i] ); + } + + if ( fsm[d].actions & NULLIFY ) { + _data[i] = 0; + } + + if ( fsm[d].actions & SET_HEADER_START ) { + _headerStart = i; + } + + if ( fsm[d].actions & SET_KEY ) { + _keyIndex = i; + } + + if ( fsm[d].actions & SET_VALUE ) { + _valueIndex = i; + } + + if ( fsm[d].actions & SET_CONTENT_START ) { + _contentStart = i + 1; + } + + if ( fsm[d].actions & STORE_KEY_VALUE ) { + // store position of first character of key. + _keys.push_back( _keyIndex ); + } + + break; + } + } + + _state = nextState; + + if ( _state == p_content ) { + const char* str = getValue("content-length"); + if ( str ) { + _contentLength = atoi( str ); + } + break; + } + } + + _parsedTo = _data.length(); + +} + +bool +HttpParser::parseRequestLine() +{ + size_t sp1; + size_t sp2; + + sp1 = _data.find( ' ', 0 ); + if ( sp1 == std::string::npos ) return false; + sp2 = _data.find( ' ', sp1 + 1 ); + if ( sp2 == std::string::npos ) return false; + + _data[sp1] = 0; + _data[sp2] = 0; + _uriIndex = sp1 + 1; + return true; +} + +HttpParser::status_t +HttpParser::addBytes( const char* bytes, unsigned len ) +{ + if ( _status != Incomplete ) { + return _status; + } + + // append the bytes to data. + _data.append( bytes, len ); + + if ( _state < p_content ) { + parseHeader(); + } + + if ( _state == p_error ) { + _status = Error; + } else if ( _state == p_content ) { + if ( _contentLength == 0 || _data.length() - _contentStart >= _contentLength ) { + if ( parseRequestLine() ) { + _status = Done; + } else { + _status = Error; + } + } + } + + return _status; +} + +const char* +HttpParser::getMethod() const +{ + return &_data[0]; +} + +const char* +HttpParser::getUri() const +{ + return &_data[_uriIndex]; +} + +const char* +HttpParser::getQueryString() const +{ + const char* pos = getUri(); + while( *pos ) { + if ( *pos == '?' ) { + pos++; + break; + } + pos++; + } + return pos; +} + +const char* +HttpParser::getBody() const +{ + if ( _contentLength > 0 ) { + return &_data[_contentStart]; + } else { + return NULL; + } +} + +// key should be in lower case. +const char* +HttpParser::getValue( const char* key ) const +{ + for( IntArray::const_iterator iter = _keys.begin(); + iter != _keys.end(); ++iter ) + { + unsigned index = *iter; + if ( strcmp( &_data[index], key ) == 0 ) { + return &_data[index + strlen(key) + 2]; + } + + } + + return NULL; +} + +unsigned +HttpParser::getContentLength() const +{ + return _contentLength; +} + diff --git a/src/utils/HttpParser.h b/src/utils/HttpParser.h new file mode 100644 index 0000000000..0e91ad26fe --- /dev/null +++ b/src/utils/HttpParser.h @@ -0,0 +1,112 @@ +/* + * This code implements parsing of HTTP requests. + * This code was written by Steve Hanov in 2009, no copyright is claimed. + * This code is in the public domain. + * Code was taken from http://refactormycode.com/codes/778-an-efficient-http-parser + * + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef HTTPPARSER_H_ +#define HTTPPARSER_H_ +#include <stdlib.h> +#include <vector> +#include <string> +#include <string.h> + +// A class to incrementally parse an HTTP header as it comes in. It +// lets you know when it has received all required bytes, as specified +// by the content-length header (if present). If there is no content-length, +// it will stop reading after the final "\n\r". +// +// Example usage: +// +// HttpParser parser; +// HttpParser::status_t status; +// +// for( ;; ) { +// // read bytes from socket into buffer, break on error +// status = parser.addBytes( buffer, length ); +// if ( status != HttpParser::Incomplete ) break; +// } +// +// if ( status == HttpParser::Done ) { +// // parse fully formed http message. +// } + + +class HttpParser +{ +public: + HttpParser(); + ~HttpParser(); + + enum status_t { + Done, + Error, + Incomplete + }; + + status_t addBytes( const char* bytes, unsigned len ); + + const char* getMethod() const; + const char* getUri() const; + const char* getQueryString() const; + const char* getBody() const; + // key should be in lower case when looking up. + const char* getValue( const char* key ) const; + unsigned getContentLength() const; + +private: + void parseHeader(); + bool parseRequestLine(); + + std::string _data; + unsigned _headerStart; + unsigned _bodyStart; + unsigned _parsedTo; + int _state; + unsigned _keyIndex; + unsigned _valueIndex; + unsigned _contentLength; + unsigned _contentStart; + unsigned _uriIndex; + + typedef std::vector<unsigned> IntArray; + IntArray _keys; + + enum State { + p_request_line=0, + p_request_line_cr=1, + p_request_line_crlf=2, + p_request_line_crlfcr=3, + p_key=4, + p_key_colon=5, + p_key_colon_sp=6, + p_value=7, + p_value_cr=8, + p_value_crlf=9, + p_value_crlfcr=10, + p_content=11, // here we are done parsing the header. + p_error=12 // here an error has occurred and the parse failed. + }; + + status_t _status; +}; +#endif//HTTPPARSER_H_ diff --git a/src/utils/HttpResponse.cpp b/src/utils/HttpResponse.cpp new file mode 100644 index 0000000000..9d741c2f9f --- /dev/null +++ b/src/utils/HttpResponse.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> + +#include "HttpResponse.h" + +#define SPACE " " +#define SEPARATOR ": " +#define LINEBREAK "\r\n" + +#define HEADER_CONTENT_LENGTH "Content-Length" + +std::map<HTTP::StatusCode, std::string> CHttpResponse::m_statusCodeText = CHttpResponse::createStatusCodes(); + +CHttpResponse::CHttpResponse(HTTP::Method method, HTTP::StatusCode status, HTTP::Version version /* = HTTPVersion1_1 */) +{ + m_method = method; + m_status = status; + m_version = version; + + m_content = NULL; + m_contentLength = 0; +} + +void CHttpResponse::AddHeader(const std::string &field, const std::string &value) +{ + if (field.empty()) + return; + + m_headers.push_back(std::pair<std::string, std::string>(field, value)); +} + +void CHttpResponse::SetContent(const char* data, unsigned int length) +{ + m_content = data; + + if (m_content == NULL) + m_contentLength = 0; + else + m_contentLength = length; +} + +unsigned int CHttpResponse::Create(char *&response) +{ + m_buffer.clear(); + + m_buffer.append("HTTP/"); + switch (m_version) + { + case HTTP::Version1_0: + m_buffer.append("1.0"); + break; + + case HTTP::Version1_1: + m_buffer.append("1.1"); + break; + + default: + return 0; + } + + char statusBuffer[4]; + sprintf(statusBuffer, "%d", (int)m_status); + m_buffer.append(SPACE); + m_buffer.append(statusBuffer); + + m_buffer.append(SPACE); + m_buffer.append(m_statusCodeText.find(m_status)->second); + m_buffer.append(LINEBREAK); + + bool hasContentLengthHeader = false; + for (unsigned int index = 0; index < m_headers.size(); index++) + { + m_buffer.append(m_headers[index].first); + m_buffer.append(SEPARATOR); + m_buffer.append(m_headers[index].second); + m_buffer.append(LINEBREAK); + + if (m_headers[index].first.compare(HEADER_CONTENT_LENGTH) == 0) + hasContentLengthHeader = true; + } + + if (!hasContentLengthHeader && m_content != NULL && m_contentLength > 0) + { + m_buffer.append(HEADER_CONTENT_LENGTH); + m_buffer.append(SEPARATOR); + char lengthBuffer[11]; + sprintf(lengthBuffer, "%u", m_contentLength); + m_buffer.append(lengthBuffer); + m_buffer.append(LINEBREAK); + } + + m_buffer.append(LINEBREAK); + if (m_content != NULL && m_contentLength > 0) + m_buffer.append(m_content, m_contentLength); + + response = (char *)m_buffer.c_str(); + return m_buffer.size(); +} + +std::map<HTTP::StatusCode, std::string> CHttpResponse::createStatusCodes() +{ + std::map<HTTP::StatusCode, std::string> map; + map[HTTP::Continue] = "Continue"; + map[HTTP::SwitchingProtocols] = "Switching Protocols"; + map[HTTP::Processing] = "Processing"; + map[HTTP::ConnectionTimedOut] = "Connection timed out"; + map[HTTP::OK] = "OK"; + map[HTTP::Created] = "Created"; + map[HTTP::Accepted] = "Accepted"; + map[HTTP::NonAuthoritativeInformation] = "Non-Authoritative Information"; + map[HTTP::NoContent] = "No Content"; + map[HTTP::ResetContent] = "Reset Content"; + map[HTTP::PartialContent] = "Partial Content"; + map[HTTP::MultiStatus] = "Multi-Status"; + map[HTTP::MultipleChoices] = "Multiple Choices"; + map[HTTP::MovedPermanently] = "Moved Permanently"; + map[HTTP::Found] = "Found"; + map[HTTP::SeeOther] = "See Other"; + map[HTTP::NotModified] = "Not Modified"; + map[HTTP::UseProxy] = "Use Proxy"; + //map[HTTP::SwitchProxy] = "Switch Proxy"; + map[HTTP::TemporaryRedirect] = "Temporary Redirect"; + map[HTTP::BadRequest] = "Bad Request"; + map[HTTP::Unauthorized] = "Unauthorized"; + map[HTTP::PaymentRequired] = "Payment Required"; + map[HTTP::Forbidden] = "Forbidden"; + map[HTTP::NotFound] = "Not Found"; + map[HTTP::MethodNotAllowed] = "Method Not Allowed"; + map[HTTP::NotAcceptable] = "Not Acceptable"; + map[HTTP::ProxyAuthenticationRequired] = "Proxy Authentication Required"; + map[HTTP::RequestTimeout] = "Request Time-out"; + map[HTTP::Conflict] = "Conflict"; + map[HTTP::Gone] = "Gone"; + map[HTTP::LengthRequired] = "Length Required"; + map[HTTP::PreconditionFailed] = "Precondition Failed"; + map[HTTP::RequestEntityTooLarge] = "Request Entity Too Large"; + map[HTTP::RequestURITooLong] = "Request-URI Too Long"; + map[HTTP::UnsupportedMediaType] = "Unsupported Media Type"; + map[HTTP::RequestedRangeNotSatisfiable] = "Requested range not satisfiable"; + map[HTTP::ExpectationFailed] = "Expectation Failed"; + map[HTTP::ImATeapot] = "I'm a Teapot"; + map[HTTP::TooManyConnections] = "There are too many connections from your internet address"; + map[HTTP::UnprocessableEntity] = "Unprocessable Entity"; + map[HTTP::Locked] = "Locked"; + map[HTTP::FailedDependency] = "Failed Dependency"; + map[HTTP::UnorderedCollection] = "UnorderedCollection"; + map[HTTP::UpgradeRequired] = "Upgrade Required"; + map[HTTP::InternalServerError] = "Internal Server Error"; + map[HTTP::NotImplemented] = "Not Implemented"; + map[HTTP::BadGateway] = "Bad Gateway"; + map[HTTP::ServiceUnavailable] = "Service Unavailable"; + map[HTTP::GatewayTimeout] = "Gateway Time-out"; + map[HTTP::HTTPVersionNotSupported] = "HTTP Version not supported"; + map[HTTP::VariantAlsoNegotiates] = "Variant Also Negotiates"; + map[HTTP::InsufficientStorage] = "Insufficient Storage"; + map[HTTP::BandwidthLimitExceeded] = "Bandwidth Limit Exceeded"; + map[HTTP::NotExtended] = "Not Extended"; + + return map; +} diff --git a/src/utils/HttpResponse.h b/src/utils/HttpResponse.h new file mode 100644 index 0000000000..a2705b7b1e --- /dev/null +++ b/src/utils/HttpResponse.h @@ -0,0 +1,135 @@ +#pragma once +/* + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <vector> +#include <map> + +namespace HTTP +{ + enum Version + { + Version1_0, + Version1_1 + }; + + enum Method + { + Get, + Head, + POST, + PUT, + Delete, + Trace, + Connect + }; + + enum StatusCode + { + // Information 1xx + Continue = 100, + SwitchingProtocols = 101, + Processing = 102, + ConnectionTimedOut = 103, + + // Success 2xx + OK = 200, + Created = 201, + Accepted = 202, + NonAuthoritativeInformation = 203, + NoContent = 204, + ResetContent = 205, + PartialContent = 206, + MultiStatus = 207, + + // Redirects 3xx + MultipleChoices = 300, + MovedPermanently = 301, + Found = 302, + SeeOther = 303, + NotModified = 304, + UseProxy = 305, + //SwitchProxy = 306, + TemporaryRedirect = 307, + + // Client errors 4xx + BadRequest = 400, + Unauthorized = 401, + PaymentRequired = 402, + Forbidden = 403, + NotFound = 404, + MethodNotAllowed = 405, + NotAcceptable = 406, + ProxyAuthenticationRequired = 407, + RequestTimeout = 408, + Conflict = 409, + Gone = 410, + LengthRequired = 411, + PreconditionFailed = 412, + RequestEntityTooLarge = 413, + RequestURITooLong = 414, + UnsupportedMediaType = 415, + RequestedRangeNotSatisfiable = 416, + ExpectationFailed = 417, + ImATeapot = 418, + TooManyConnections = 421, + UnprocessableEntity = 422, + Locked = 423, + FailedDependency = 424, + UnorderedCollection = 425, + UpgradeRequired = 426, + + // Server errors 5xx + InternalServerError = 500, + NotImplemented = 501, + BadGateway = 502, + ServiceUnavailable = 503, + GatewayTimeout = 504, + HTTPVersionNotSupported = 505, + VariantAlsoNegotiates = 506, + InsufficientStorage = 507, + BandwidthLimitExceeded = 509, + NotExtended = 510 + }; +} + +class CHttpResponse +{ +public: + CHttpResponse(HTTP::Method method, HTTP::StatusCode status, HTTP::Version version = HTTP::Version1_1); + + void AddHeader(const std::string &field, const std::string &value); + void SetContent(const char* data, unsigned int length); + + unsigned int Create(char *&response); + +private: + HTTP::Method m_method; + HTTP::StatusCode m_status; + HTTP::Version m_version; + std::vector< std::pair<std::string, std::string> > m_headers; + const char* m_content; + unsigned int m_contentLength; + std::string m_buffer; + + static std::map<HTTP::StatusCode, std::string> m_statusCodeText; + static std::map<HTTP::StatusCode, std::string> createStatusCodes(); +}; diff --git a/src/utils/IArchivable.h b/src/utils/IArchivable.h new file mode 100644 index 0000000000..3738edacb8 --- /dev/null +++ b/src/utils/IArchivable.h @@ -0,0 +1,31 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +class CArchive; + +class IArchivable +{ +public: + virtual void Archive(CArchive& ar) = 0; + virtual ~IArchivable() {} +}; + diff --git a/src/utils/IRssObserver.h b/src/utils/IRssObserver.h new file mode 100644 index 0000000000..eb930b6a60 --- /dev/null +++ b/src/utils/IRssObserver.h @@ -0,0 +1,32 @@ +#pragma once +/* + * Copyright (C) 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 + * <http://www.gnu.org/licenses/>. + * + */ + +typedef uint32_t character_t; +typedef std::vector<character_t> vecText; + +class IRssObserver +{ +public: + virtual ~IRssObserver() {} + + virtual void OnFeedUpdate(const vecText &feed) = 0; + virtual void OnFeedRelease() = 0; +};
\ No newline at end of file diff --git a/src/utils/ISerializable.h b/src/utils/ISerializable.h new file mode 100644 index 0000000000..2dab5ce4d2 --- /dev/null +++ b/src/utils/ISerializable.h @@ -0,0 +1,29 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +class CVariant; + +class ISerializable +{ +public: + virtual void Serialize(CVariant& value) const = 0; + virtual ~ISerializable() {} +}; diff --git a/src/utils/ISortable.h b/src/utils/ISortable.h new file mode 100644 index 0000000000..21bc712b59 --- /dev/null +++ b/src/utils/ISortable.h @@ -0,0 +1,31 @@ +#pragma once +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> + +#include "SortUtils.h" + +class ISortable +{ +public: + virtual ~ISortable() { } + virtual void ToSortable(SortItem& sortable, Field field) const = 0; +}; diff --git a/src/utils/IXmlDeserializable.h b/src/utils/IXmlDeserializable.h new file mode 100644 index 0000000000..866a84ec67 --- /dev/null +++ b/src/utils/IXmlDeserializable.h @@ -0,0 +1,30 @@ +#pragma once +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +class TiXmlNode; + +class IXmlDeserializable +{ +public: + virtual ~IXmlDeserializable() { } + + virtual bool Deserialize(const TiXmlNode *node) = 0; +}; diff --git a/src/utils/InfoLoader.cpp b/src/utils/InfoLoader.cpp new file mode 100644 index 0000000000..429964a273 --- /dev/null +++ b/src/utils/InfoLoader.cpp @@ -0,0 +1,72 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "InfoLoader.h" +#include "guilib/LocalizeStrings.h" +#include "JobManager.h" +#include "TimeUtils.h" + +CInfoLoader::CInfoLoader(unsigned int timeToRefresh) +{ + m_refreshTime = 0; + m_timeToRefresh = timeToRefresh; + m_busy = false; +} + +CInfoLoader::~CInfoLoader() +{ +} + +void CInfoLoader::OnJobComplete(unsigned int jobID, bool success, CJob *job) +{ + m_refreshTime = CTimeUtils::GetFrameTime() + m_timeToRefresh; + m_busy = false; +} + +std::string CInfoLoader::GetInfo(int info) +{ + // Refresh if need be + if (m_refreshTime < CTimeUtils::GetFrameTime() && !m_busy) + { // queue up the job + m_busy = true; + CJobManager::GetInstance().AddJob(GetJob(), this); + } + if (m_busy && CTimeUtils::GetFrameTime() - m_refreshTime > 1000) + { + return BusyInfo(info); + } + return TranslateInfo(info); +} + +std::string CInfoLoader::BusyInfo(int info) const +{ + return g_localizeStrings.Get(503); +} + +std::string CInfoLoader::TranslateInfo(int info) const +{ + return ""; +} + +void CInfoLoader::Refresh() +{ + m_refreshTime = CTimeUtils::GetFrameTime(); +} + diff --git a/src/utils/InfoLoader.h b/src/utils/InfoLoader.h new file mode 100644 index 0000000000..d72d5e12ab --- /dev/null +++ b/src/utils/InfoLoader.h @@ -0,0 +1,44 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Job.h" +#include <string> + +class CInfoLoader : public IJobCallback +{ +public: + CInfoLoader(unsigned int timeToRefresh = 5 * 60 * 1000); + virtual ~CInfoLoader(); + + std::string GetInfo(int info); + void Refresh(); + + virtual void OnJobComplete(unsigned int jobID, bool success, CJob *job); +protected: + virtual CJob *GetJob() const=0; + virtual std::string TranslateInfo(int info) const; + virtual std::string BusyInfo(int info) const; +private: + unsigned int m_refreshTime; + unsigned int m_timeToRefresh; + bool m_busy; +}; diff --git a/src/utils/JSONVariantParser.cpp b/src/utils/JSONVariantParser.cpp new file mode 100644 index 0000000000..db08849e49 --- /dev/null +++ b/src/utils/JSONVariantParser.cpp @@ -0,0 +1,236 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "JSONVariantParser.h" + +yajl_callbacks CJSONVariantParser::callbacks = { + CJSONVariantParser::ParseNull, + CJSONVariantParser::ParseBoolean, + CJSONVariantParser::ParseInteger, + CJSONVariantParser::ParseDouble, + NULL, + CJSONVariantParser::ParseString, + CJSONVariantParser::ParseMapStart, + CJSONVariantParser::ParseMapKey, + CJSONVariantParser::ParseMapEnd, + CJSONVariantParser::ParseArrayStart, + CJSONVariantParser::ParseArrayEnd +}; + +CJSONVariantParser::CJSONVariantParser(IParseCallback *callback) +{ + m_callback = callback; + +#if YAJL_MAJOR == 2 + m_handler = yajl_alloc(&callbacks, NULL, this); + + yajl_config(m_handler, yajl_allow_comments, 1); + yajl_config(m_handler, yajl_dont_validate_strings, 0); +#else + yajl_parser_config cfg = { 1, 1 }; + + m_handler = yajl_alloc(&callbacks, &cfg, NULL, this); +#endif + + m_status = ParseVariable; +} + +CJSONVariantParser::~CJSONVariantParser() +{ +#if YAJL_MAJOR == 2 + yajl_complete_parse(m_handler); +#else + yajl_parse_complete(m_handler); +#endif + yajl_free(m_handler); +} + +void CJSONVariantParser::push_buffer(const unsigned char *buffer, unsigned int length) +{ + yajl_parse(m_handler, buffer, length); +} + +CVariant CJSONVariantParser::Parse(const unsigned char *json, unsigned int length) +{ + CSimpleParseCallback callback; + CJSONVariantParser parser(&callback); + + parser.push_buffer(json, length); + + return callback.GetOutput(); +} + +int CJSONVariantParser::ParseNull(void * ctx) +{ + CJSONVariantParser *parser = (CJSONVariantParser *)ctx; + + parser->PushObject(CVariant::VariantTypeNull); + parser->PopObject(); + + return 1; +} + +int CJSONVariantParser::ParseBoolean(void * ctx, int boolean) +{ + CJSONVariantParser *parser = (CJSONVariantParser *)ctx; + + parser->PushObject(CVariant(boolean != 0)); + parser->PopObject(); + + return 1; +} + +#if YAJL_MAJOR ==2 +int CJSONVariantParser::ParseInteger(void * ctx, long long integerVal) +#else +int CJSONVariantParser::ParseInteger(void * ctx, long integerVal) +#endif +{ + CJSONVariantParser *parser = (CJSONVariantParser *)ctx; + + parser->PushObject(CVariant((int64_t)integerVal)); + parser->PopObject(); + + return 1; +} + +int CJSONVariantParser::ParseDouble(void * ctx, double doubleVal) +{ + CJSONVariantParser *parser = (CJSONVariantParser *)ctx; + + parser->PushObject(CVariant((float)doubleVal)); + parser->PopObject(); + + return 1; +} + +#if YAJL_MAJOR == 2 +int CJSONVariantParser::ParseString(void * ctx, const unsigned char * stringVal, size_t stringLen) +#else +int CJSONVariantParser::ParseString(void * ctx, const unsigned char * stringVal, unsigned int stringLen) +#endif +{ + CJSONVariantParser *parser = (CJSONVariantParser *)ctx; + + parser->PushObject(CVariant((const char *)stringVal, stringLen)); + parser->PopObject(); + + return 1; +} + +int CJSONVariantParser::ParseMapStart(void * ctx) +{ + CJSONVariantParser *parser = (CJSONVariantParser *)ctx; + + parser->PushObject(CVariant::VariantTypeObject); + + return 1; +} + +#if YAJL_MAJOR == 2 +int CJSONVariantParser::ParseMapKey(void * ctx, const unsigned char * stringVal, size_t stringLen) +#else +int CJSONVariantParser::ParseMapKey(void * ctx, const unsigned char * stringVal, unsigned int stringLen) +#endif +{ + CJSONVariantParser *parser = (CJSONVariantParser *)ctx; + + parser->m_key = std::string((const char *)stringVal, 0, stringLen); + + return 1; +} + +int CJSONVariantParser::ParseMapEnd(void * ctx) +{ + CJSONVariantParser *parser = (CJSONVariantParser *)ctx; + + parser->PopObject(); + + return 1; +} + +int CJSONVariantParser::ParseArrayStart(void * ctx) +{ + CJSONVariantParser *parser = (CJSONVariantParser *)ctx; + + parser->PushObject(CVariant::VariantTypeArray); + + return 1; +} + +int CJSONVariantParser::ParseArrayEnd(void * ctx) +{ + CJSONVariantParser *parser = (CJSONVariantParser *)ctx; + + parser->PopObject(); + + return 1; +} + +void CJSONVariantParser::PushObject(CVariant variant) +{ + if (m_status == ParseObject) + { + (*m_parse[m_parse.size() - 1])[m_key] = variant; + m_parse.push_back(&(*m_parse[m_parse.size() - 1])[m_key]); + } + else if (m_status == ParseArray) + { + CVariant *temp = m_parse[m_parse.size() - 1]; + temp->push_back(variant); + m_parse.push_back(&(*temp)[temp->size() - 1]); + } + else if (m_parse.size() == 0) + { + m_parse.push_back(new CVariant(variant)); + } + + if (variant.isObject()) + m_status = ParseObject; + else if (variant.isArray()) + m_status = ParseArray; + else + m_status = ParseVariable; +} + +void CJSONVariantParser::PopObject() +{ + CVariant *variant = m_parse[m_parse.size() - 1]; + m_parse.pop_back(); + + if (m_parse.size()) + { + variant = m_parse[m_parse.size() - 1]; + if (variant->isObject()) + m_status = ParseObject; + else if (variant->isArray()) + m_status = ParseArray; + else + m_status = ParseVariable; + } + else if (m_callback) + { + m_callback->onParsed(variant); + delete variant; + + m_parse.clear(); + m_status = ParseVariable; + } +} diff --git a/src/utils/JSONVariantParser.h b/src/utils/JSONVariantParser.h new file mode 100644 index 0000000000..38f88a9ad6 --- /dev/null +++ b/src/utils/JSONVariantParser.h @@ -0,0 +1,102 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" +#include "Variant.h" + +#include <yajl/yajl_parse.h> +#include <yajl/yajl_gen.h> +#ifdef HAVE_YAJL_YAJL_VERSION_H +#include <yajl/yajl_version.h> +#endif + +class IParseCallback +{ +public: + virtual ~IParseCallback() { } + + virtual void onParsed(CVariant *variant) = 0; +}; + +class CSimpleParseCallback : public IParseCallback +{ +public: + virtual void onParsed(CVariant *variant) { m_parsed = *variant; } + CVariant &GetOutput() { return m_parsed; } + +private: + CVariant m_parsed; +}; + +class CJSONVariantParser +{ +public: + CJSONVariantParser(IParseCallback *callback); + ~CJSONVariantParser(); + + void push_buffer(const unsigned char *buffer, unsigned int length); + + static CVariant Parse(const unsigned char *json, unsigned int length); + +private: + static int ParseNull(void * ctx); + static int ParseBoolean(void * ctx, int boolean); +#if YAJL_MAJOR == 2 + static int ParseInteger(void * ctx, long long integerVal); +#else + static int ParseInteger(void * ctx, long integerVal); +#endif + static int ParseDouble(void * ctx, double doubleVal); +#if YAJL_MAJOR == 2 + static int ParseString(void * ctx, const unsigned char * stringVal, size_t stringLen); +#else + static int ParseString(void * ctx, const unsigned char * stringVal, unsigned int stringLen); +#endif + static int ParseMapStart(void * ctx); +#if YAJL_MAJOR == 2 + static int ParseMapKey(void * ctx, const unsigned char * stringVal, size_t stringLen); +#else + static int ParseMapKey(void * ctx, const unsigned char * stringVal, unsigned int stringLen); +#endif + static int ParseMapEnd(void * ctx); + static int ParseArrayStart(void * ctx); + static int ParseArrayEnd(void * ctx); + + void PushObject(CVariant variant); + void PopObject(); + + static yajl_callbacks callbacks; + + IParseCallback *m_callback; + yajl_handle m_handler; + + CVariant m_parsedObject; + std::vector<CVariant *> m_parse; + std::string m_key; + + enum PARSE_STATUS + { + ParseArray = 1, + ParseObject = 2, + ParseVariable = 0 + }; + PARSE_STATUS m_status; +}; diff --git a/src/utils/JSONVariantWriter.cpp b/src/utils/JSONVariantWriter.cpp new file mode 100644 index 0000000000..54309992be --- /dev/null +++ b/src/utils/JSONVariantWriter.cpp @@ -0,0 +1,142 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <locale> + +#include "JSONVariantWriter.h" + +using namespace std; + +string CJSONVariantWriter::Write(const CVariant &value, bool compact) +{ + string output; + +#if YAJL_MAJOR == 2 + yajl_gen g = yajl_gen_alloc(NULL); + yajl_gen_config(g, yajl_gen_beautify, compact ? 0 : 1); + yajl_gen_config(g, yajl_gen_indent_string, "\t"); +#else + yajl_gen_config conf = { compact ? 0 : 1, "\t" }; + yajl_gen g = yajl_gen_alloc(&conf, NULL); +#endif + + // Set locale to classic ("C") to ensure valid JSON numbers + const char *currentLocale = setlocale(LC_NUMERIC, NULL); + std::string backupLocale; + if (currentLocale != NULL) + { + backupLocale = currentLocale; + setlocale(LC_NUMERIC, "C"); + } + + if (InternalWrite(g, value)) + { + const unsigned char * buffer; + +#if YAJL_MAJOR == 2 + size_t length; + yajl_gen_get_buf(g, &buffer, &length); +#else + unsigned int length; + yajl_gen_get_buf(g, &buffer, &length); +#endif + output = string((const char *)buffer, length); + } + + // Re-set locale to what it was before using yajl + if (!backupLocale.empty()) + setlocale(LC_NUMERIC, backupLocale.c_str()); + + yajl_gen_clear(g); + yajl_gen_free(g); + + return output; +} + +bool CJSONVariantWriter::InternalWrite(yajl_gen g, const CVariant &value) +{ + bool success = false; + + switch (value.type()) + { + case CVariant::VariantTypeInteger: +#if YAJL_MAJOR == 2 + success = yajl_gen_status_ok == yajl_gen_integer(g, (long long int)value.asInteger()); +#else + success = yajl_gen_status_ok == yajl_gen_integer(g, (long int)value.asInteger()); +#endif + break; + case CVariant::VariantTypeUnsignedInteger: +#if YAJL_MAJOR == 2 + success = yajl_gen_status_ok == yajl_gen_integer(g, (long long int)value.asUnsignedInteger()); +#else + success = yajl_gen_status_ok == yajl_gen_integer(g, (long int)value.asUnsignedInteger()); +#endif + break; + case CVariant::VariantTypeDouble: + success = yajl_gen_status_ok == yajl_gen_double(g, value.asDouble()); + break; + case CVariant::VariantTypeBoolean: + success = yajl_gen_status_ok == yajl_gen_bool(g, value.asBoolean() ? 1 : 0); + break; + case CVariant::VariantTypeString: +#if YAJL_MAJOR == 2 + success = yajl_gen_status_ok == yajl_gen_string(g, (const unsigned char*)value.c_str(), (size_t)value.size()); +#else + success = yajl_gen_status_ok == yajl_gen_string(g, (const unsigned char*)value.c_str(), value.size()); +#endif + break; + case CVariant::VariantTypeArray: + success = yajl_gen_status_ok == yajl_gen_array_open(g); + + for (CVariant::const_iterator_array itr = value.begin_array(); itr != value.end_array() && success; ++itr) + success &= InternalWrite(g, *itr); + + if (success) + success = yajl_gen_status_ok == yajl_gen_array_close(g); + + break; + case CVariant::VariantTypeObject: + success = yajl_gen_status_ok == yajl_gen_map_open(g); + + for (CVariant::const_iterator_map itr = value.begin_map(); itr != value.end_map() && success; ++itr) + { +#if YAJL_MAJOR == 2 + success &= yajl_gen_status_ok == yajl_gen_string(g, (const unsigned char*)itr->first.c_str(), (size_t)itr->first.length()); +#else + success &= yajl_gen_status_ok == yajl_gen_string(g, (const unsigned char*)itr->first.c_str(), itr->first.length()); +#endif + if (success) + success &= InternalWrite(g, itr->second); + } + + if (success) + success &= yajl_gen_status_ok == yajl_gen_map_close(g); + + break; + case CVariant::VariantTypeConstNull: + case CVariant::VariantTypeNull: + default: + success = yajl_gen_status_ok == yajl_gen_null(g); + break; + } + + return success; +} diff --git a/src/utils/JSONVariantWriter.h b/src/utils/JSONVariantWriter.h new file mode 100644 index 0000000000..877df81afc --- /dev/null +++ b/src/utils/JSONVariantWriter.h @@ -0,0 +1,35 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" +#include "Variant.h" +#include <yajl/yajl_gen.h> +#ifdef HAVE_YAJL_YAJL_VERSION_H +#include <yajl/yajl_version.h> +#endif + +class CJSONVariantWriter +{ +public: + static std::string Write(const CVariant &value, bool compact); +private: + static bool InternalWrite(yajl_gen g, const CVariant &value); +}; diff --git a/src/utils/Job.h b/src/utils/Job.h new file mode 100644 index 0000000000..61c5220fa1 --- /dev/null +++ b/src/utils/Job.h @@ -0,0 +1,170 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +class CJob; + +#include <stddef.h> + +#define kJobTypeMediaFlags "mediaflags" +#define kJobTypeCacheImage "cacheimage" +#define kJobTypeDDSCompress "ddscompress" + +/*! + \ingroup jobs + \brief Callback interface for asynchronous jobs. + + Used by clients of the CJobManager to receive progress and completion notification of jobs. + Clients of small jobs wishing to perform actions on job completion should implement the + IJobCallback::OnJobComplete() function. Clients of larger jobs may choose to implement the + IJobCallback::OnJobProgress() function in order to be kept informed of progress. + + \sa CJobManager and CJob + */ +class IJobCallback +{ +public: + /*! + \brief Destructor for job call back objects. + + \sa CJobManager and CJob + */ + virtual ~IJobCallback() {}; + + /*! + \brief The callback used when a job completes. + + OnJobComplete is called at the completion of the job's DoWork() function, and is used + to return information to the caller on the result of the job. On returning form this function + the CJobManager will destroy this job. + + \param jobID the unique id of the job (as retrieved from CJobManager::AddJob) + \param success the result from the DoWork call + \param job the job that has been processed. The job will be destroyed after this function returns + \sa CJobManager and CJob + */ + virtual void OnJobComplete(unsigned int jobID, bool success, CJob *job)=0; + + /*! + \brief An optional callback function that a job may call while processing. + + OnJobProgress may be called periodically by a job during it's DoWork() function. It is used + by the job to report on progress. + + \param jobID the unique id of the job (as retrieved from CJobManager::AddJob) + \param progress the current progress of the job, out of total. + \param total the total amount of work to be processed. + \param job the job that has been processed. + \sa CJobManager and CJob + */ + virtual void OnJobProgress(unsigned int jobID, unsigned int progress, unsigned int total, const CJob *job) {}; +}; + +class CJobManager; + +/*! + \ingroup jobs + \brief Base class for jobs that are executed asyncronously. + + Clients of the CJobManager should subclass CJob and provide the DoWork() function. Data should be + passed to the job on creation, and any data sharing between the job and the client should be kept to within + the callback functions if possible, and guarded with critical sections as appropriate. + + Jobs typically fall into two groups: small jobs that perform a single function, and larger jobs that perform a + sequence of functions. Clients with small jobs should implement the IJobCallback::OnJobComplete() callback to receive results. + Clients with larger jobs may wish to implement both the IJobCallback::OnJobComplete() and IJobCallback::OnJobProgress() + callbacks to receive updates. Jobs may be cancelled at any point by the client via CJobManager::CancelJob(), however + effort should be taken to ensure that any callbacks and cancellation is suitably guarded against simultaneous thread access. + + Handling cancellation of jobs within the OnJobProgress callback is a threadsafe operation, as all execution is + then in the Job thread. + + \sa CJobManager and IJobCallback + */ +class CJob +{ +public: + /*! + \brief Priority levels for jobs, specified by clients when adding jobs to the CJobManager. + \sa CJobManager + */ + enum PRIORITY { + PRIORITY_LOW_PAUSABLE = 0, + PRIORITY_LOW, + PRIORITY_NORMAL, + PRIORITY_HIGH + }; + CJob() { m_callback = NULL; }; + + /*! + \brief Destructor for job objects. + + Jobs are destroyed by the CJobManager after the OnJobComplete() callback is complete. + CJob subclasses should therefore supply a virtual destructor to cleanup any memory allocated by + complete or cancelled jobs. + + \sa CJobManager + */ + virtual ~CJob() {}; + + /*! + \brief Main workhorse function of CJob instances + + All CJob subclasses must implement this function, performing all processing. Once this function + is complete, the OnJobComplete() callback is called, and the job is then destroyed. + + \sa CJobManager, IJobCallback::OnJobComplete() + */ + virtual bool DoWork() = 0; // function to do the work + + /*! + \brief Function that returns the type of job. + + CJob subclasses may optionally implement this function to specify the type of job. + This is useful for the CJobManager::AddLIFOJob() routine, which preempts similar jobs + with the new job. + + \return a unique character string describing the job. + \sa CJobManager + */ + virtual const char *GetType() const { return ""; }; + + virtual bool operator==(const CJob* job) const + { + return false; + } + + /*! + \brief Function for longer jobs to report progress and check whether they have been cancelled. + + Jobs that contain loops that may take time should check this routine each iteration of the loop, + both to (optionally) report progress, and to check for cancellation. + + \param progress the amount of the job performed, out of total. + \param total the total amount of processing to be performed + \return if true, the job has been asked to cancel. + + \sa IJobCallback::OnJobProgress() + */ + bool ShouldCancel(unsigned int progress, unsigned int total) const; +private: + friend class CJobManager; + CJobManager *m_callback; +}; diff --git a/src/utils/JobManager.cpp b/src/utils/JobManager.cpp new file mode 100644 index 0000000000..d2d2a13f7b --- /dev/null +++ b/src/utils/JobManager.cpp @@ -0,0 +1,432 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "JobManager.h" +#include <algorithm> +#include <stdexcept> +#include "threads/SingleLock.h" +#include "utils/log.h" + +#include "system.h" + + +using namespace std; + +bool CJob::ShouldCancel(unsigned int progress, unsigned int total) const +{ + if (m_callback) + return m_callback->OnJobProgress(progress, total, this); + return false; +} + +CJobWorker::CJobWorker(CJobManager *manager) : CThread("JobWorker") +{ + m_jobManager = manager; + Create(true); // start work immediately, and kill ourselves when we're done +} + +CJobWorker::~CJobWorker() +{ + // while we should already be removed from the job manager, if an exception + // occurs during processing that we haven't caught, we may skip over that step. + // Thus, before we go out of scope, ensure the job manager knows we're gone. + m_jobManager->RemoveWorker(this); + if(!IsAutoDelete()) + StopThread(); +} + +void CJobWorker::Process() +{ + SetPriority( GetMinPriority() ); + while (true) + { + // request an item from our manager (this call is blocking) + CJob *job = m_jobManager->GetNextJob(this); + if (!job) + break; + + bool success = false; + try + { + success = job->DoWork(); + } + catch (...) + { + CLog::Log(LOGERROR, "%s error processing job %s", __FUNCTION__, job->GetType()); + } + m_jobManager->OnJobComplete(success, job); + } +} + +void CJobQueue::CJobPointer::CancelJob() +{ + CJobManager::GetInstance().CancelJob(m_id); + m_id = 0; +} + +CJobQueue::CJobQueue(bool lifo, unsigned int jobsAtOnce, CJob::PRIORITY priority) +: m_jobsAtOnce(jobsAtOnce), m_priority(priority), m_lifo(lifo) +{ +} + +CJobQueue::~CJobQueue() +{ + CancelJobs(); +} + +void CJobQueue::OnJobComplete(unsigned int jobID, bool success, CJob *job) +{ + CSingleLock lock(m_section); + // check if this job is in our processing list + Processing::iterator i = find(m_processing.begin(), m_processing.end(), job); + if (i != m_processing.end()) + m_processing.erase(i); + // request a new job be queued + QueueNextJob(); +} + +void CJobQueue::CancelJob(const CJob *job) +{ + CSingleLock lock(m_section); + Processing::iterator i = find(m_processing.begin(), m_processing.end(), job); + if (i != m_processing.end()) + { + i->CancelJob(); + m_processing.erase(i); + return; + } + Queue::iterator j = find(m_jobQueue.begin(), m_jobQueue.end(), job); + if (j != m_jobQueue.end()) + { + j->FreeJob(); + m_jobQueue.erase(j); + } +} + +void CJobQueue::AddJob(CJob *job) +{ + CSingleLock lock(m_section); + // check if we have this job already. If so, we're done. + if (find(m_jobQueue.begin(), m_jobQueue.end(), job) != m_jobQueue.end() || + find(m_processing.begin(), m_processing.end(), job) != m_processing.end()) + { + delete job; + return; + } + + if (m_lifo) + m_jobQueue.push_back(CJobPointer(job)); + else + m_jobQueue.push_front(CJobPointer(job)); + QueueNextJob(); +} + +void CJobQueue::QueueNextJob() +{ + CSingleLock lock(m_section); + if (m_jobQueue.size() && m_processing.size() < m_jobsAtOnce) + { + CJobPointer &job = m_jobQueue.back(); + job.m_id = CJobManager::GetInstance().AddJob(job.m_job, this, m_priority); + m_processing.push_back(job); + m_jobQueue.pop_back(); + } +} + +void CJobQueue::CancelJobs() +{ + CSingleLock lock(m_section); + for_each(m_processing.begin(), m_processing.end(), mem_fun_ref(&CJobPointer::CancelJob)); + for_each(m_jobQueue.begin(), m_jobQueue.end(), mem_fun_ref(&CJobPointer::FreeJob)); + m_jobQueue.clear(); + m_processing.clear(); +} + + +bool CJobQueue::QueueEmpty() const +{ + CSingleLock lock(m_section); + return m_jobQueue.empty(); +} + +CJobManager &CJobManager::GetInstance() +{ + static CJobManager sJobManager; + return sJobManager; +} + +CJobManager::CJobManager() +{ + m_jobCounter = 0; + m_running = true; + m_pauseJobs = false; +} + +void CJobManager::Restart() +{ + CSingleLock lock(m_section); + + if (m_running) + throw std::logic_error("CJobManager already running"); + m_running = true; +} + +void CJobManager::CancelJobs() +{ + CSingleLock lock(m_section); + m_running = false; + + // clear any pending jobs + for (unsigned int priority = CJob::PRIORITY_LOW_PAUSABLE; priority <= CJob::PRIORITY_HIGH; ++priority) + { + for_each(m_jobQueue[priority].begin(), m_jobQueue[priority].end(), mem_fun_ref(&CWorkItem::FreeJob)); + m_jobQueue[priority].clear(); + } + + // cancel any callbacks on jobs still processing + for_each(m_processing.begin(), m_processing.end(), mem_fun_ref(&CWorkItem::Cancel)); + + // tell our workers to finish + while (m_workers.size()) + { + lock.Leave(); + m_jobEvent.Set(); + Sleep(0); // yield after setting the event to give the workers some time to die + lock.Enter(); + } +} + +CJobManager::~CJobManager() +{ +} + +unsigned int CJobManager::AddJob(CJob *job, IJobCallback *callback, CJob::PRIORITY priority) +{ + CSingleLock lock(m_section); + + if (!m_running) + return 0; + + // increment the job counter, ensuring 0 (invalid job) is never hit + m_jobCounter++; + if (m_jobCounter == 0) + m_jobCounter++; + + // create a work item for this job + CWorkItem work(job, m_jobCounter, priority, callback); + m_jobQueue[priority].push_back(work); + + StartWorkers(priority); + return work.m_id; +} + +void CJobManager::CancelJob(unsigned int jobID) +{ + CSingleLock lock(m_section); + + // check whether we have this job in the queue + for (unsigned int priority = CJob::PRIORITY_LOW_PAUSABLE; priority <= CJob::PRIORITY_HIGH; ++priority) + { + JobQueue::iterator i = find(m_jobQueue[priority].begin(), m_jobQueue[priority].end(), jobID); + if (i != m_jobQueue[priority].end()) + { + delete i->m_job; + m_jobQueue[priority].erase(i); + return; + } + } + // or if we're processing it + Processing::iterator it = find(m_processing.begin(), m_processing.end(), jobID); + if (it != m_processing.end()) + it->m_callback = NULL; // job is in progress, so only thing to do is to remove callback +} + +void CJobManager::StartWorkers(CJob::PRIORITY priority) +{ + CSingleLock lock(m_section); + + // check how many free threads we have + if (m_processing.size() >= GetMaxWorkers(priority)) + return; + + // do we have any sleeping threads? + if (m_processing.size() < m_workers.size()) + { + m_jobEvent.Set(); + return; + } + + // everyone is busy - we need more workers + m_workers.push_back(new CJobWorker(this)); +} + +CJob *CJobManager::PopJob() +{ + CSingleLock lock(m_section); + for (int priority = CJob::PRIORITY_HIGH; priority >= CJob::PRIORITY_LOW_PAUSABLE; --priority) + { + // Check whether we're pausing pausable jobs + if (priority == CJob::PRIORITY_LOW_PAUSABLE && m_pauseJobs) + continue; + + if (m_jobQueue[priority].size() && m_processing.size() < GetMaxWorkers(CJob::PRIORITY(priority))) + { + // pop the job off the queue + CWorkItem job = m_jobQueue[priority].front(); + m_jobQueue[priority].pop_front(); + + // add to the processing vector + m_processing.push_back(job); + job.m_job->m_callback = this; + return job.m_job; + } + } + return NULL; +} + +void CJobManager::PauseJobs() +{ + CSingleLock lock(m_section); + m_pauseJobs = true; +} + +void CJobManager::UnPauseJobs() +{ + CSingleLock lock(m_section); + m_pauseJobs = false; +} + +bool CJobManager::IsProcessing(const CJob::PRIORITY &priority) const +{ + CSingleLock lock(m_section); + + if (m_pauseJobs) + return false; + + for(Processing::const_iterator it = m_processing.begin(); it < m_processing.end(); ++it) + { + if (priority == it->m_priority) + return true; + } + return false; +} + +int CJobManager::IsProcessing(const std::string &type) const +{ + int jobsMatched = 0; + CSingleLock lock(m_section); + + if (m_pauseJobs) + return 0; + + for(Processing::const_iterator it = m_processing.begin(); it < m_processing.end(); ++it) + { + if (type == std::string(it->m_job->GetType())) + jobsMatched++; + } + return jobsMatched; +} + +CJob *CJobManager::GetNextJob(const CJobWorker *worker) +{ + CSingleLock lock(m_section); + while (m_running) + { + // grab a job off the queue if we have one + CJob *job = PopJob(); + if (job) + return job; + // no jobs are left - sleep for 30 seconds to allow new jobs to come in + lock.Leave(); + bool newJob = m_jobEvent.WaitMSec(30000); + lock.Enter(); + if (!newJob) + break; + } + // ensure no jobs have come in during the period after + // timeout and before we held the lock + CJob *job = PopJob(); + if (job) + return job; + // have no jobs + RemoveWorker(worker); + return NULL; +} + +bool CJobManager::OnJobProgress(unsigned int progress, unsigned int total, const CJob *job) const +{ + CSingleLock lock(m_section); + // find the job in the processing queue, and check whether it's cancelled (no callback) + Processing::const_iterator i = find(m_processing.begin(), m_processing.end(), job); + if (i != m_processing.end()) + { + CWorkItem item(*i); + lock.Leave(); // leave section prior to call + if (item.m_callback) + { + item.m_callback->OnJobProgress(item.m_id, progress, total, job); + return false; + } + } + return true; // couldn't find the job, or it's been cancelled +} + +void CJobManager::OnJobComplete(bool success, CJob *job) +{ + CSingleLock lock(m_section); + // remove the job from the processing queue + Processing::iterator i = find(m_processing.begin(), m_processing.end(), job); + if (i != m_processing.end()) + { + // tell any listeners we're done with the job, then delete it + CWorkItem item(*i); + lock.Leave(); + try + { + if (item.m_callback) + item.m_callback->OnJobComplete(item.m_id, success, item.m_job); + } + catch (...) + { + CLog::Log(LOGERROR, "%s error processing job %s", __FUNCTION__, item.m_job->GetType()); + } + lock.Enter(); + Processing::iterator j = find(m_processing.begin(), m_processing.end(), job); + if (j != m_processing.end()) + m_processing.erase(j); + lock.Leave(); + item.FreeJob(); + } +} + +void CJobManager::RemoveWorker(const CJobWorker *worker) +{ + CSingleLock lock(m_section); + // remove our worker + Workers::iterator i = find(m_workers.begin(), m_workers.end(), worker); + if (i != m_workers.end()) + m_workers.erase(i); // workers auto-delete +} + +unsigned int CJobManager::GetMaxWorkers(CJob::PRIORITY priority) +{ + static const unsigned int max_workers = 5; + return max_workers - (CJob::PRIORITY_HIGH - priority); +} diff --git a/src/utils/JobManager.h b/src/utils/JobManager.h new file mode 100644 index 0000000000..1df2287207 --- /dev/null +++ b/src/utils/JobManager.h @@ -0,0 +1,333 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <queue> +#include <vector> +#include <string> +#include "threads/CriticalSection.h" +#include "threads/Thread.h" +#include "Job.h" + +class CJobManager; + +class CJobWorker : public CThread +{ +public: + CJobWorker(CJobManager *manager); + virtual ~CJobWorker(); + + void Process(); +private: + CJobManager *m_jobManager; +}; + +/*! + \ingroup jobs + \brief Job Queue class to handle a queue of unique jobs to be processed sequentially + + Holds a queue of jobs to be processed sequentially, either first in,first out + or last in, first out. Jobs are unique, so queueing multiple copies of the same job + (based on the CJob::operator==) will not add additional jobs. + + Classes should subclass this class and override OnJobCallback should they require + information from the job. + + \sa CJob and IJobCallback + */ +class CJobQueue: public IJobCallback +{ + class CJobPointer + { + public: + CJobPointer(CJob *job) + { + m_job = job; + m_id = 0; + }; + void CancelJob(); + void FreeJob() + { + delete m_job; + m_job = NULL; + }; + bool operator==(const CJob *job) const + { + if (m_job) + return *m_job == job; + return false; + }; + CJob *m_job; + unsigned int m_id; + }; +public: + /*! + \brief CJobQueue constructor + \param lifo whether the queue should be processed last in first out or first in first out. Defaults to false (first in first out) + \param jobsAtOnce number of jobs at once to process. Defaults to 1. + \param priority priority of this queue. + \sa CJob + */ + CJobQueue(bool lifo = false, unsigned int jobsAtOnce = 1, CJob::PRIORITY priority = CJob::PRIORITY_LOW); + + /*! + \brief CJobQueue destructor + Cancels any in-process jobs, and destroys the job queue. + \sa CJob + */ + virtual ~CJobQueue(); + + /*! + \brief Add a job to the queue + On completion of the job (or destruction of the job queue) the CJob object will be destroyed. + \param job a pointer to the job to add. The job should be subclassed from CJob. + \sa CJob + */ + void AddJob(CJob *job); + + /*! + \brief Cancel a job in the queue + Cancels a job in the queue. Any job currently being processed may complete after this + call has completed, but OnJobComplete will not be performed. If the job is only queued + then it will be removed from the queue and deleted. + \param job a pointer to the job to cancel. The job should be subclassed from CJob. + \sa CJob + */ + void CancelJob(const CJob *job); + + /*! + \brief Cancel all jobs in the queue + Removes all jobs from the queue. Any job currently being processed may complete after this + call has completed, but OnJobComplete will not be performed. + \sa CJob + */ + void CancelJobs(); + + /*! + \brief The callback used when a job completes. + + OnJobComplete is called at the completion of the CJob::DoWork function, and is used + to return information to the caller on the result of the job. On returning from this function + the CJobManager will destroy this job. + + Subclasses should override this function if they wish to transfer information from the job prior + to it's deletion. They must then call this base class function, which will move on to the next + job. + + \sa CJobManager, IJobCallback and CJob + */ + virtual void OnJobComplete(unsigned int jobID, bool success, CJob *job); + +protected: + /*! + \brief Returns if we still have jobs waiting to be processed + NOTE: This function does not take into account the jobs that are currently processing + */ + bool QueueEmpty() const; + +private: + void QueueNextJob(); + + typedef std::deque<CJobPointer> Queue; + typedef std::vector<CJobPointer> Processing; + Queue m_jobQueue; + Processing m_processing; + + unsigned int m_jobsAtOnce; + CJob::PRIORITY m_priority; + CCriticalSection m_section; + bool m_lifo; +}; + +/*! + \ingroup jobs + \brief Job Manager class for scheduling asynchronous jobs. + + Controls asynchronous job execution, by allowing clients to add and cancel jobs. + Should be accessed via CJobManager::GetInstance(). Jobs are allocated based on + priority levels. Lower priority jobs are executed only if there are sufficient + spare worker threads free to allow for higher priority jobs that may arise. + + \sa CJob and IJobCallback + */ +class CJobManager +{ + class CWorkItem + { + public: + CWorkItem(CJob *job, unsigned int id, CJob::PRIORITY priority, IJobCallback *callback) + { + m_job = job; + m_id = id; + m_callback = callback; + m_priority = priority; + } + bool operator==(unsigned int jobID) const + { + return m_id == jobID; + }; + bool operator==(const CJob *job) const + { + return m_job == job; + }; + void FreeJob() + { + delete m_job; + m_job = NULL; + }; + void Cancel() + { + m_callback = NULL; + }; + CJob *m_job; + unsigned int m_id; + IJobCallback *m_callback; + CJob::PRIORITY m_priority; + }; + +public: + /*! + \brief The only way through which the global instance of the CJobManager should be accessed. + \return the global instance. + */ + static CJobManager &GetInstance(); + + /*! + \brief Add a job to the threaded job manager. + \param job a pointer to the job to add. The job should be subclassed from CJob + \param callback a pointer to an IJobCallback instance to receive job progress and completion notices. + \param priority the priority that this job should run at. + \return a unique identifier for this job, to be used with other interaction + \sa CJob, IJobCallback, CancelJob() + */ + unsigned int AddJob(CJob *job, IJobCallback *callback, CJob::PRIORITY priority = CJob::PRIORITY_LOW); + + /*! + \brief Cancel a job with the given id. + \param jobID the id of the job to cancel, retrieved previously from AddJob() + \sa AddJob() + */ + void CancelJob(unsigned int jobID); + + /*! + \brief Cancel all remaining jobs, preparing for shutdown + Should be called prior to destroying any objects that may be being used as callbacks + \sa CancelJob(), AddJob() + */ + void CancelJobs(); + + /*! + \brief Re-start accepting jobs again + Called after calling CancelJobs() to allow this manager to accept more jobs + \throws std::logic_error if the manager was not previously cancelled + \sa CancelJobs() + */ + void Restart(); + + /*! + \brief Checks to see if any jobs of a specific type are currently processing. + \param type Job type to search for + \return Number of matching jobs + */ + int IsProcessing(const std::string &type) const; + + /*! + \brief Suspends queueing of jobs with priority PRIORITY_LOW_PAUSABLE until unpaused + Useful to (for ex) stop queuing thumb jobs during video start/playback. + Does not affect currently processing jobs, use IsProcessing to see if any need to be waited on + \sa UnPauseJobs() + */ + void PauseJobs(); + + /*! + \brief Resumes queueing of (previously paused) jobs with priority PRIORITY_LOW_PAUSABLE + \sa PauseJobs() + */ + void UnPauseJobs(); + + /*! + \brief Checks to see if any jobs with specific priority are currently processing. + \param priority to search for + \return true if processing jobs, else returns false + */ + bool IsProcessing(const CJob::PRIORITY &priority) const; + +protected: + friend class CJobWorker; + friend class CJob; + + /*! + \brief Get a new job to process. Blocks until a new job is available, or a timeout has occurred. + \param worker a pointer to the current CJobWorker instance requesting a job. + \sa CJob + */ + CJob *GetNextJob(const CJobWorker *worker); + + /*! + \brief Callback from CJobWorker after a job has completed. + Calls IJobCallback::OnJobComplete(), and then destroys job. + \param job a pointer to the calling subclassed CJob instance. + \param success the result from the DoWork call + \sa IJobCallback, CJob + */ + void OnJobComplete(bool success, CJob *job); + + /*! + \brief Callback from CJob to report progress and check for cancellation. + Checks for cancellation, and calls IJobCallback::OnJobProgress(). + \param progress amount of processing performed to date, out of total. + \param total total amount of processing. + \param job pointer to the calling subclassed CJob instance. + \return true if the job has been cancelled, else returns false. + \sa IJobCallback, CJob + */ + bool OnJobProgress(unsigned int progress, unsigned int total, const CJob *job) const; + +private: + // private construction, and no assignements; use the provided singleton methods + CJobManager(); + CJobManager(const CJobManager&); + CJobManager const& operator=(CJobManager const&); + virtual ~CJobManager(); + + /*! \brief Pop a job off the job queue and add to the processing queue ready to process + \return the job to process, NULL if no jobs are available + */ + CJob *PopJob(); + + void StartWorkers(CJob::PRIORITY priority); + void RemoveWorker(const CJobWorker *worker); + static unsigned int GetMaxWorkers(CJob::PRIORITY priority); + + unsigned int m_jobCounter; + + typedef std::deque<CWorkItem> JobQueue; + typedef std::vector<CWorkItem> Processing; + typedef std::vector<CJobWorker*> Workers; + + JobQueue m_jobQueue[CJob::PRIORITY_HIGH+1]; + bool m_pauseJobs; + Processing m_processing; + Workers m_workers; + + CCriticalSection m_section; + CEvent m_jobEvent; + bool m_running; +}; diff --git a/src/utils/LabelFormatter.cpp b/src/utils/LabelFormatter.cpp new file mode 100644 index 0000000000..c54856964f --- /dev/null +++ b/src/utils/LabelFormatter.cpp @@ -0,0 +1,428 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "LabelFormatter.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "RegExp.h" +#include "Util.h" +#include "video/VideoInfoTag.h" +#include "music/tags/MusicInfoTag.h" +#include "pictures/PictureInfoTag.h" +#include "FileItem.h" +#include "StringUtils.h" +#include "URIUtils.h" +#include "guilib/LocalizeStrings.h" + +using namespace MUSIC_INFO; + +/* LabelFormatter + * ============== + * + * The purpose of this class is to parse a mask string of the form + * + * [%N. ][%T] - [%A][ (%Y)] + * + * and provide methods to format up a CFileItem's label(s). + * + * The %N/%A/%B masks are replaced with the corresponding metadata (if available). + * + * Square brackets are treated as a metadata block. Anything inside the block other + * than the metadata mask is treated as either a prefix or postfix to the metadata. This + * information is only included in the formatted string when the metadata is non-empty. + * + * Any metadata tags not enclosed with square brackets are treated as if it were immediately + * enclosed - i.e. with no prefix or postfix. + * + * The special characters %, [, and ] can be produced using %%, %[, and %] respectively. + * + * Any static text outside of the metadata blocks is only shown if the blocks on either side + * (or just one side in the case of an end) are both non-empty. + * + * Examples (using the above expression): + * + * Track Title Artist Year Resulting Label + * ----- ----- ------ ---- --------------- + * 10 "40" U2 1983 10. "40" - U2 (1983) + * "40" U2 1983 "40" - U2 (1983) + * 10 U2 1983 10. U2 (1983) + * 10 "40" 1983 "40" (1983) + * 10 "40" U2 10. "40" - U2 + * 10 "40" 10. "40" + * + * Available metadata masks: + * + * %A - Artist + * %B - Album + * %C - Programs count + * %D - Duration + * %E - episode number + * %F - FileName + * %G - Genre + * %H - season*100+episode + * %I - Size + * %J - Date + * %K - Movie/Game title + * %L - existing Label + * %M - number of episodes + * %N - Track Number + * %O - mpaa rating + * %P - production code + * %Q - file time + * %R - Movie rating + * %S - Disc Number + * %T - Title + * %U - studio + * %V - Playcount + * %W - Listeners + * %X - Bitrate + * %Y - Year + * %Z - tvshow title + * %a - Date Added + * %p - Last Played + * *t - Date Taken (suitable for Pictures) + */ + +#define MASK_CHARS "NSATBGYFLDIJRCKMEPHZOQUVXWapt" + +CLabelFormatter::CLabelFormatter(const std::string &mask, const std::string &mask2) +{ + // assemble our label masks + AssembleMask(0, mask); + AssembleMask(1, mask2); + // save a bool for faster lookups + m_hideFileExtensions = !CSettings::Get().GetBool("filelists.showextensions"); +} + +std::string CLabelFormatter::GetContent(unsigned int label, const CFileItem *item) const +{ + assert(label < 2); + assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1); + + if (!item) return ""; + + std::string strLabel, dynamicLeft, dynamicRight; + for (unsigned int i = 0; i < m_dynamicContent[label].size(); i++) + { + dynamicRight = GetMaskContent(m_dynamicContent[label][i], item); + if ((i == 0 || !dynamicLeft.empty()) && !dynamicRight.empty()) + strLabel += m_staticContent[label][i]; + strLabel += dynamicRight; + dynamicLeft = dynamicRight; + } + if (!dynamicLeft.empty()) + strLabel += m_staticContent[label][m_dynamicContent[label].size()]; + + return strLabel; +} + +void CLabelFormatter::FormatLabel(CFileItem *item) const +{ + std::string maskedLabel = GetContent(0, item); + if (!maskedLabel.empty()) + item->SetLabel(maskedLabel); + else if (!item->m_bIsFolder && m_hideFileExtensions) + item->RemoveExtension(); +} + +void CLabelFormatter::FormatLabel2(CFileItem *item) const +{ + item->SetLabel2(GetContent(1, item)); +} + +std::string CLabelFormatter::GetMaskContent(const CMaskString &mask, const CFileItem *item) const +{ + if (!item) return ""; + const CMusicInfoTag *music = item->GetMusicInfoTag(); + const CVideoInfoTag *movie = item->GetVideoInfoTag(); + const CPictureInfoTag *pic = item->GetPictureInfoTag(); + std::string value; + switch (mask.m_content) + { + case 'N': + if (music && music->GetTrackNumber() > 0) + value = StringUtils::Format("%02.2i", music->GetTrackNumber()); + if (movie&& movie->m_iTrack > 0) + value = StringUtils::Format("%02.2i", movie->m_iTrack); + break; + case 'S': + if (music && music->GetDiscNumber() > 0) + value = StringUtils::Format("%02.2i", music->GetDiscNumber()); + break; + case 'A': + if (music && music->GetArtist().size()) + value = StringUtils::Join(music->GetArtist(), g_advancedSettings.m_musicItemSeparator); + if (movie && movie->m_artist.size()) + value = StringUtils::Join(movie->m_artist, g_advancedSettings.m_videoItemSeparator); + break; + case 'T': + if (music && music->GetTitle().size()) + value = music->GetTitle(); + if (movie && movie->m_strTitle.size()) + value = movie->m_strTitle; + break; + case 'Z': + if (movie && !movie->m_strShowTitle.empty()) + value = movie->m_strShowTitle; + break; + case 'B': + if (music && music->GetAlbum().size()) + value = music->GetAlbum(); + else if (movie) + value = movie->m_strAlbum; + break; + case 'G': + if (music && music->GetGenre().size()) + value = StringUtils::Join(music->GetGenre(), g_advancedSettings.m_musicItemSeparator); + if (movie && movie->m_genre.size()) + value = StringUtils::Join(movie->m_genre, g_advancedSettings.m_videoItemSeparator); + break; + case 'Y': + if (music) + value = music->GetYearString(); + if (movie) + { + if (movie->m_firstAired.IsValid()) + value = movie->m_firstAired.GetAsLocalizedDate(); + else if (movie->m_premiered.IsValid()) + value = movie->m_premiered.GetAsLocalizedDate(); + else if (movie->m_iYear > 0) + value = StringUtils::Format("%i", movie->m_iYear); + } + break; + case 'F': // filename + value = CUtil::GetTitleFromPath(item->GetPath(), item->m_bIsFolder && !item->IsFileFolder()); + break; + case 'L': + value = item->GetLabel(); + // is the label the actual file or folder name? + if (value == URIUtils::GetFileName(item->GetPath())) + { // label is the same as filename, clean it up as appropriate + value = CUtil::GetTitleFromPath(item->GetPath(), item->m_bIsFolder && !item->IsFileFolder()); + } + break; + case 'D': + { // duration + int nDuration=0; + if (music) + nDuration = music->GetDuration(); + if (movie) + nDuration = movie->GetDuration(); + if (nDuration > 0) + value = StringUtils::SecondsToTimeString(nDuration, (nDuration >= 3600) ? TIME_FORMAT_H_MM_SS : TIME_FORMAT_MM_SS); + else if (item->m_dwSize > 0) + value = StringUtils::SizeToString(item->m_dwSize); + } + break; + case 'I': // size + if( !item->m_bIsFolder || item->m_dwSize != 0 ) + value = StringUtils::SizeToString(item->m_dwSize); + break; + case 'J': // date + if (item->m_dateTime.IsValid()) + value = item->m_dateTime.GetAsLocalizedDate(); + break; + case 'Q': // time + if (item->m_dateTime.IsValid()) + value = item->m_dateTime.GetAsLocalizedTime("", false); + break; + case 'R': // rating + if (music && music->GetRating() != '0') + value.assign(1, music->GetRating()); + else if (movie && movie->m_fRating != 0.f) + value = StringUtils::Format("%.1f", movie->m_fRating); + break; + case 'C': // programs count + value = StringUtils::Format("%i", item->m_iprogramCount); + break; + case 'K': + value = item->m_strTitle; + break; + case 'M': + if (movie && movie->m_iEpisode > 0) + value = StringUtils::Format("%i %s", + movie->m_iEpisode, + g_localizeStrings.Get(movie->m_iEpisode == 1 ? 20452 : 20453).c_str()); + break; + case 'E': + if (movie && movie->m_iEpisode > 0) + { // episode number + if (movie->m_iSeason == 0) + value = StringUtils::Format("S%02.2i", movie->m_iEpisode); + else + value = StringUtils::Format("%02.2i", movie->m_iEpisode); + } + break; + case 'P': + if (movie) // tvshow production code + value = movie->m_strProductionCode; + break; + case 'H': + if (movie && movie->m_iEpisode > 0) + { // season*100+episode number + if (movie->m_iSeason == 0) + value = StringUtils::Format("S%02.2i", movie->m_iEpisode); + else + value = StringUtils::Format("%ix%02.2i", movie->m_iSeason,movie->m_iEpisode); + } + break; + case 'O': + if (movie && movie->m_strMPAARating) + {// MPAA Rating + value = movie->m_strMPAARating; + } + break; + case 'U': + if (movie && movie->m_studio.size() > 0) + {// Studios + value = StringUtils::Join(movie ->m_studio, g_advancedSettings.m_videoItemSeparator); + } + break; + case 'V': // Playcount + if (music) + value = StringUtils::Format("%i", music->GetPlayCount()); + if (movie) + value = StringUtils::Format("%i", movie->m_playCount); + break; + case 'X': // Bitrate + if( !item->m_bIsFolder && item->m_dwSize != 0 ) + value = StringUtils::Format("%" PRId64" kbps", item->m_dwSize); + break; + case 'W': // Listeners + if( !item->m_bIsFolder && music && music->GetListeners() != 0 ) + value = StringUtils::Format("%i %s", + music->GetListeners(), + g_localizeStrings.Get(music->GetListeners() == 1 ? 20454 : 20455).c_str()); + break; + case 'a': // Date Added + if (movie && movie->m_dateAdded.IsValid()) + value = movie->m_dateAdded.GetAsLocalizedDate(); + break; + case 'p': // Last played + if (movie && movie->m_lastPlayed.IsValid()) + value = movie->m_lastPlayed.GetAsLocalizedDate(); + break; + case 't': // Date Taken + if (pic && pic->GetDateTimeTaken().IsValid()) + value = pic->GetDateTimeTaken().GetAsLocalizedDate(); + break; + } + if (!value.empty()) + return mask.m_prefix + value + mask.m_postfix; + return ""; +} + +void CLabelFormatter::SplitMask(unsigned int label, const std::string &mask) +{ + assert(label < 2); + CRegExp reg; + reg.RegComp("%([" MASK_CHARS "])"); + std::string work(mask); + int findStart = -1; + while ((findStart = reg.RegFind(work.c_str())) >= 0) + { // we've found a match + m_staticContent[label].push_back(work.substr(0, findStart)); + m_dynamicContent[label].push_back(CMaskString("", + reg.GetMatch(1)[0], "")); + work = work.substr(findStart + reg.GetFindLen()); + } + m_staticContent[label].push_back(work); +} + +void CLabelFormatter::AssembleMask(unsigned int label, const std::string& mask) +{ + assert(label < 2); + m_staticContent[label].clear(); + m_dynamicContent[label].clear(); + + // we want to match [<prefix>%A<postfix] + // but allow %%, %[, %] to be in the prefix and postfix. Anything before the first [ + // could be a mask that's not surrounded with [], so pass to SplitMask. + CRegExp reg; + reg.RegComp("(^|[^%])\\[(([^%]|%%|%\\]|%\\[)*)%([" MASK_CHARS "])(([^%]|%%|%\\]|%\\[)*)\\]"); + std::string work(mask); + int findStart = -1; + while ((findStart = reg.RegFind(work.c_str())) >= 0) + { // we've found a match for a pre/postfixed string + // send anything + SplitMask(label, work.substr(0, findStart) + reg.GetMatch(1)); + m_dynamicContent[label].push_back(CMaskString( + reg.GetMatch(2), + reg.GetMatch(4)[0], + reg.GetMatch(5))); + work = work.substr(findStart + reg.GetFindLen()); + } + SplitMask(label, work); + assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1); +} + +bool CLabelFormatter::FillMusicTag(const std::string &fileName, CMusicInfoTag *tag) const +{ + // run through and find static content to split the string up + size_t pos1 = fileName.find(m_staticContent[0][0], 0); + if (pos1 == std::string::npos) + return false; + for (unsigned int i = 1; i < m_staticContent[0].size(); i++) + { + size_t pos2 = m_staticContent[0][i].size() ? fileName.find(m_staticContent[0][i], pos1) : fileName.size(); + if (pos2 == std::string::npos) + return false; + // found static content - thus we have the dynamic content surrounded + FillMusicMaskContent(m_dynamicContent[0][i - 1].m_content, fileName.substr(pos1, pos2 - pos1), tag); + pos1 = pos2 + m_staticContent[0][i].size(); + } + return true; +} + +void CLabelFormatter::FillMusicMaskContent(const char mask, const std::string &value, CMusicInfoTag *tag) const +{ + if (!tag) return; + switch (mask) + { + case 'N': + tag->SetTrackNumber(atol(value.c_str())); + break; + case 'S': + tag->SetDiscNumber(atol(value.c_str())); + break; + case 'A': + tag->SetArtist(value); + break; + case 'T': + tag->SetTitle(value); + break; + case 'B': + tag->SetAlbum(value); + break; + case 'G': + tag->SetGenre(value); + break; + case 'Y': + tag->SetYear(atol(value.c_str())); + break; + case 'D': + tag->SetDuration(StringUtils::TimeStringToSeconds(value)); + break; + case 'R': // rating + tag->SetRating(value[0]); + break; + } +} + diff --git a/src/utils/LabelFormatter.h b/src/utils/LabelFormatter.h new file mode 100644 index 0000000000..d63e8041ca --- /dev/null +++ b/src/utils/LabelFormatter.h @@ -0,0 +1,88 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <vector> + +namespace MUSIC_INFO +{ + class CMusicInfoTag; +} + +class CFileItem; // forward + +struct LABEL_MASKS +{ + LABEL_MASKS(const std::string& strLabelFile="", const std::string& strLabel2File="", const std::string& strLabelFolder="", const std::string& strLabel2Folder="") : + m_strLabelFile(strLabelFile), + m_strLabel2File(strLabel2File), + m_strLabelFolder(strLabelFolder), + m_strLabel2Folder(strLabel2Folder) + {} + std::string m_strLabelFile; + std::string m_strLabel2File; + std::string m_strLabelFolder; + std::string m_strLabel2Folder; +}; + +class CLabelFormatter +{ +public: + CLabelFormatter(const std::string &mask, const std::string &mask2); + + void FormatLabel(CFileItem *item) const; + void FormatLabel2(CFileItem *item) const; + void FormatLabels(CFileItem *item) const // convenient shorthand + { + FormatLabel(item); + FormatLabel2(item); + } + + bool FillMusicTag(const std::string &fileName, MUSIC_INFO::CMusicInfoTag *tag) const; + +private: + class CMaskString + { + public: + CMaskString(const std::string &prefix, char content, const std::string &postfix) : + m_prefix(prefix), + m_postfix(postfix), + m_content(content) + {}; + std::string m_prefix; + std::string m_postfix; + char m_content; + }; + + // functions for assembling the mask vectors + void AssembleMask(unsigned int label, const std::string &mask); + void SplitMask(unsigned int label, const std::string &mask); + + // functions for retrieving content based on our mask vectors + std::string GetContent(unsigned int label, const CFileItem *item) const; + std::string GetMaskContent(const CMaskString &mask, const CFileItem *item) const; + void FillMusicMaskContent(const char mask, const std::string &value, MUSIC_INFO::CMusicInfoTag *tag) const; + + std::vector<std::string> m_staticContent[2]; + std::vector<CMaskString> m_dynamicContent[2]; + bool m_hideFileExtensions; +}; diff --git a/src/utils/LangCodeExpander.cpp b/src/utils/LangCodeExpander.cpp new file mode 100644 index 0000000000..599609c429 --- /dev/null +++ b/src/utils/LangCodeExpander.cpp @@ -0,0 +1,1679 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "LangCodeExpander.h" +#include "utils/XBMCTinyXML.h" +#include "LangInfo.h" +#include "utils/log.h" +#include "utils/StringUtils.h" +#include "Util.h" + +#define MAKECODE(a, b, c, d) ((((long)(a))<<24) | (((long)(b))<<16) | (((long)(c))<<8) | (long)(d)) +#define MAKETWOCHARCODE(a, b) ((((long)(a))<<8) | (long)(b)) + +typedef struct LCENTRY +{ + long code; + const char *name; +} LCENTRY; + +extern const struct LCENTRY g_iso639_1[186]; +extern const struct LCENTRY g_iso639_2[538]; + +struct CharCodeConvertionWithHack +{ + const char* old; + const char* id; + const char* win_id; +}; + +struct CharCodeConvertion +{ + const char* old; + const char* id; +}; + +// declared as extern to allow forward declaration +extern const CharCodeConvertionWithHack CharCode2To3[189]; +extern const CharCodeConvertion RegionCode2To3[246]; + +CLangCodeExpander::CLangCodeExpander(void) +{} + +CLangCodeExpander::~CLangCodeExpander(void) +{ +} + +void CLangCodeExpander::Clear() +{ + m_mapUser.clear(); +} + +void CLangCodeExpander::LoadUserCodes(const TiXmlElement* pRootElement) +{ + if (pRootElement) + { + m_mapUser.clear(); + + std::string sShort, sLong; + + const TiXmlNode* pLangCode = pRootElement->FirstChild("code"); + while (pLangCode) + { + const TiXmlNode* pShort = pLangCode->FirstChildElement("short"); + const TiXmlNode* pLong = pLangCode->FirstChildElement("long"); + if (pShort && pLong) + { + sShort = pShort->FirstChild()->Value(); + sLong = pLong->FirstChild()->Value(); + StringUtils::ToLower(sShort); + m_mapUser[sShort] = sLong; + } + pLangCode = pLangCode->NextSibling(); + } + } +} + +bool CLangCodeExpander::Lookup(std::string& desc, const std::string& code) +{ + int iSplit = code.find("-"); + if (iSplit > 0) + { + std::string strLeft, strRight; + const bool bLeft = Lookup(strLeft, code.substr(0, iSplit)); + const bool bRight = Lookup(strRight, code.substr(iSplit + 1)); + if (bLeft || bRight) + { + desc = ""; + if (strLeft.length() > 0) + desc = strLeft; + else + desc = code.substr(0, iSplit); + + if (strRight.length() > 0) + { + desc += " - "; + desc += strRight; + } + else + { + desc += " - "; + desc += code.substr(iSplit + 1); + } + return true; + } + return false; + } + else + { + if( LookupInMap(desc, code) ) + return true; + + if( LookupInDb(desc, code) ) + return true; + } + return false; +} + +bool CLangCodeExpander::Lookup(std::string& desc, const int code) +{ + + char lang[3]; + lang[2] = 0; + lang[1] = (code & 255); + lang[0] = (code >> 8) & 255; + + return Lookup(desc, lang); +} + +bool CLangCodeExpander::ConvertTwoToThreeCharCode(std::string& strThreeCharCode, const std::string& strTwoCharCode, bool checkWin32Locales /*= false*/) +{ + if ( strTwoCharCode.length() == 2 ) + { + std::string strTwoCharCodeLower( strTwoCharCode ); + StringUtils::ToLower(strTwoCharCodeLower); + StringUtils::Trim(strTwoCharCodeLower); + + for (unsigned int index = 0; index < ARRAY_SIZE(CharCode2To3); ++index) + { + if (strTwoCharCodeLower == CharCode2To3[index].old) + { + if (checkWin32Locales && CharCode2To3[index].win_id) + { + strThreeCharCode = CharCode2To3[index].win_id; + return true; + } + strThreeCharCode = CharCode2To3[index].id; + return true; + } + } + } + + // not a 2 char code + return false; +} + +bool CLangCodeExpander::ConvertToThreeCharCode(std::string& strThreeCharCode, const std::string& strCharCode, bool checkXbmcLocales /*= true*/, bool checkWin32Locales /*= false*/) +{ + if (strCharCode.size() == 2) + return g_LangCodeExpander.ConvertTwoToThreeCharCode(strThreeCharCode, strCharCode, checkWin32Locales); + else if (strCharCode.size() == 3) + { + std::string charCode(strCharCode); StringUtils::ToLower(charCode); + for (unsigned int index = 0; index < ARRAY_SIZE(CharCode2To3); ++index) + { + if (charCode == CharCode2To3[index].id || + (checkWin32Locales && CharCode2To3[index].win_id != NULL && charCode == CharCode2To3[index].win_id) ) + { + strThreeCharCode = charCode; + return true; + } + } + for (unsigned int index = 0; index < ARRAY_SIZE(RegionCode2To3); ++index) + { + if (charCode == RegionCode2To3[index].id) + { + strThreeCharCode = charCode; + return true; + } + } + } + else if (strCharCode.size() > 3) + { + for(unsigned int i = 0; i < sizeof(g_iso639_2) / sizeof(LCENTRY); i++) + { + if (StringUtils::EqualsNoCase(strCharCode, g_iso639_2[i].name)) + { + CodeToString(g_iso639_2[i].code, strThreeCharCode); + return true; + } + } + + if (checkXbmcLocales) + { + CLangInfo langInfo; + if (!langInfo.CheckLoadLanguage(strCharCode)) + return false; + + strThreeCharCode = langInfo.GetLanguageCode(); + return !strThreeCharCode.empty(); + } + } + + return false; +} + +#ifdef TARGET_WINDOWS +bool CLangCodeExpander::ConvertLinuxToWindowsRegionCodes(const std::string& strTwoCharCode, std::string& strThreeCharCode) +{ + if (strTwoCharCode.length() != 2) + return false; + + std::string strLower( strTwoCharCode ); + StringUtils::ToLower(strLower); + StringUtils::Trim(strLower); + for (unsigned int index = 0; index < ARRAY_SIZE(RegionCode2To3); ++index) + { + if (strLower == RegionCode2To3[index].old) + { + strThreeCharCode = RegionCode2To3[index].id; + return true; + } + } + + return true; +} + +bool CLangCodeExpander::ConvertWindowsToGeneralCharCode(const std::string& strWindowsCharCode, std::string& strThreeCharCode) +{ + if (strWindowsCharCode.length() != 3) + return false; + + std::string strLower(strWindowsCharCode); + StringUtils::ToLower(strLower); + for (unsigned int index = 0; index < ARRAY_SIZE(CharCode2To3); ++index) + { + if ((CharCode2To3[index].win_id && strLower == CharCode2To3[index].win_id) || + strLower == CharCode2To3[index].id) + { + strThreeCharCode = CharCode2To3[index].id; + return true; + } + } + + return true; +} +#endif + +bool CLangCodeExpander::ConvertToTwoCharCode(std::string& code, const std::string& lang, bool checkXbmcLocales /*= true*/) +{ + if (lang.empty()) + return false; + + if (lang.length() == 2) + { + std::string tmp; + if (Lookup(tmp, lang)) + { + code = lang; + return true; + } + } + else if (lang.length() == 3) + { + std::string lower(lang); StringUtils::ToLower(lower); + for (unsigned int index = 0; index < ARRAY_SIZE(CharCode2To3); ++index) + { + if (lower == CharCode2To3[index].id || (CharCode2To3[index].win_id && lower == CharCode2To3[index].win_id)) + { + code = CharCode2To3[index].old; + return true; + } + } + + for (unsigned int index = 0; index < ARRAY_SIZE(RegionCode2To3); ++index) + { + if (lower == RegionCode2To3[index].id) + { + code = RegionCode2To3[index].old; + return true; + } + } + } + + // check if lang is full language name + std::string tmp; + if (ReverseLookup(lang, tmp)) + { + if (tmp.length() == 2) + { + code = tmp; + return true; + } + else if (tmp.length() == 3) + return ConvertToTwoCharCode(code, tmp); + } + + if (!checkXbmcLocales) + return false; + + // try xbmc specific language names + CLangInfo langInfo; + if (!langInfo.CheckLoadLanguage(lang)) + return false; + + return ConvertToTwoCharCode(code, langInfo.GetLanguageCode(), false); +} + +bool CLangCodeExpander::ReverseLookup(const std::string& desc, std::string& code) +{ + if (desc.empty()) + return false; + + std::string descTmp(desc); + StringUtils::Trim(descTmp); + StringUtils::ToLower(descTmp); + STRINGLOOKUPTABLE::iterator it; + for (it = m_mapUser.begin(); it != m_mapUser.end() ; ++it) + { + if (StringUtils::EqualsNoCase(descTmp, it->second)) + { + code = it->first; + return true; + } + } + for(unsigned int i = 0; i < sizeof(g_iso639_1) / sizeof(LCENTRY); i++) + { + if (descTmp == g_iso639_1[i].name) + { + CodeToString(g_iso639_1[i].code, code); + return true; + } + } + for(unsigned int i = 0; i < sizeof(g_iso639_2) / sizeof(LCENTRY); i++) + { + if (descTmp == g_iso639_2[i].name) + { + CodeToString(g_iso639_2[i].code, code); + return true; + } + } + return false; +} + +bool CLangCodeExpander::LookupInMap(std::string& desc, const std::string& code) +{ + if (code.empty()) + return false; + + STRINGLOOKUPTABLE::iterator it; + //Make sure we convert to lowercase before trying to find it + std::string sCode(code); + StringUtils::ToLower(sCode); + StringUtils::Trim(sCode); + + it = m_mapUser.find(sCode); + if (it != m_mapUser.end()) + { + desc = it->second; + return true; + } + return false; +} + +bool CLangCodeExpander::LookupInDb(std::string& desc, const std::string& code) +{ + if (code.empty()) + return false; + + long longcode; + std::string sCode(code); + StringUtils::ToLower(sCode); + StringUtils::Trim(sCode); + + if(sCode.length() == 2) + { + longcode = MAKECODE('\0', '\0', sCode[0], sCode[1]); + for(unsigned int i = 0; i < sizeof(g_iso639_1) / sizeof(LCENTRY); i++) + { + if(g_iso639_1[i].code == longcode) + { + desc = g_iso639_1[i].name; + return true; + } + } + } + else if(code.length() == 3) + { + longcode = MAKECODE('\0', sCode[0], sCode[1], sCode[2]); + for(unsigned int i = 0; i < sizeof(g_iso639_2) / sizeof(LCENTRY); i++) + { + if(g_iso639_2[i].code == longcode) + { + desc = g_iso639_2[i].name; + return true; + } + } + } + return false; +} + +void CLangCodeExpander::CodeToString(long code, std::string& ret) +{ + ret.clear(); + for (unsigned int j = 0 ; j < 4 ; j++) + { + char c = (char) code & 0xFF; + if (c == '\0') + return; + ret.insert(0, 1, c); + code >>= 8; + } +} + +bool CLangCodeExpander::CompareFullLangNames(const std::string& lang1, const std::string& lang2) +{ + if (StringUtils::EqualsNoCase(lang1, lang2)) + return true; + + std::string expandedLang1, expandedLang2, code1, code2; + + if (!ReverseLookup(lang1, code1)) + return false; + else + code1 = lang1; + + if (!ReverseLookup(lang2, code2)) + return false; + else + code2 = lang2; + + Lookup(expandedLang1, code1); + Lookup(expandedLang2, code2); + return StringUtils::EqualsNoCase(expandedLang1, expandedLang2); +} + +std::vector<std::string> CLangCodeExpander::GetLanguageNames(LANGFORMATS format /* = CLangCodeExpander::ISO_639_1 */) +{ + std::vector<std::string> languages; + const LCENTRY *lang = g_iso639_1; + size_t length = sizeof(g_iso639_1); + if (format == CLangCodeExpander::ISO_639_2) + { + lang = g_iso639_2; + length = sizeof(g_iso639_2); + } + length /= sizeof(LCENTRY); + + for (size_t i = 0; i < length; i++) + { + languages.push_back(lang->name); + ++lang; + } + + return languages; +} + +bool CLangCodeExpander::CompareLangCodes(const std::string& code1, const std::string& code2) +{ + if (StringUtils::EqualsNoCase(code1, code2)) + return true; + + std::string expandedLang1, expandedLang2; + + if (!Lookup(expandedLang1, code1)) + return false; + + if (!Lookup(expandedLang2, code2)) + return false; + + return StringUtils::EqualsNoCase(expandedLang1, expandedLang2); +} + +std::string CLangCodeExpander::ConvertToISO6392T(const std::string& lang) +{ + if (lang.empty()) + return lang; + + std::string two, three; + if (ConvertToTwoCharCode(two, lang)) + { + if (ConvertToThreeCharCode(three, two)) + return three; + } + return lang; +} + +extern const LCENTRY g_iso639_1[186] = +{ + { MAKECODE('\0','\0','c','c'), "Closed Caption" }, + { MAKECODE('\0','\0','a','a'), "Afar" }, + { MAKECODE('\0','\0','a','b'), "Abkhazian" }, + { MAKECODE('\0','\0','a','e'), "Avestan" }, + { MAKECODE('\0','\0','a','f'), "Afrikaans" }, + { MAKECODE('\0','\0','a','k'), "Akan" }, + { MAKECODE('\0','\0','a','m'), "Amharic" }, + { MAKECODE('\0','\0','a','n'), "Aragonese" }, + { MAKECODE('\0','\0','a','r'), "Arabic" }, + { MAKECODE('\0','\0','a','s'), "Assamese" }, + { MAKECODE('\0','\0','a','v'), "Avaric" }, + { MAKECODE('\0','\0','a','y'), "Aymara" }, + { MAKECODE('\0','\0','a','z'), "Azerbaijani" }, + { MAKECODE('\0','\0','b','a'), "Bashkir" }, + { MAKECODE('\0','\0','b','e'), "Belarusian" }, + { MAKECODE('\0','\0','b','g'), "Bulgarian" }, + { MAKECODE('\0','\0','b','h'), "Bihari" }, + { MAKECODE('\0','\0','b','i'), "Bislama" }, + { MAKECODE('\0','\0','b','m'), "Bambara" }, + { MAKECODE('\0','\0','b','n'), "Bengali; Bangla" }, + { MAKECODE('\0','\0','b','o'), "Tibetan" }, + { MAKECODE('\0','\0','b','r'), "Breton" }, + { MAKECODE('\0','\0','b','s'), "Bosnian" }, + { MAKECODE('\0','\0','c','a'), "Catalan" }, + { MAKECODE('\0','\0','c','e'), "Chechen" }, + { MAKECODE('\0','\0','c','h'), "Chamorro" }, + { MAKECODE('\0','\0','c','o'), "Corsican" }, + { MAKECODE('\0','\0','c','r'), "Cree" }, + { MAKECODE('\0','\0','c','s'), "Czech" }, + { MAKECODE('\0','\0','c','u'), "Church Slavic" }, + { MAKECODE('\0','\0','c','v'), "Chuvash" }, + { MAKECODE('\0','\0','c','y'), "Welsh" }, + { MAKECODE('\0','\0','d','a'), "Danish" }, + { MAKECODE('\0','\0','d','e'), "German" }, + { MAKECODE('\0','\0','d','v'), "Dhivehi" }, + { MAKECODE('\0','\0','d','z'), "Dzongkha" }, + { MAKECODE('\0','\0','e','e'), "Ewe" }, + { MAKECODE('\0','\0','e','l'), "Greek" }, + { MAKECODE('\0','\0','e','n'), "English" }, + { MAKECODE('\0','\0','e','o'), "Esperanto" }, + { MAKECODE('\0','\0','e','s'), "Spanish" }, + { MAKECODE('\0','\0','e','t'), "Estonian" }, + { MAKECODE('\0','\0','e','u'), "Basque" }, + { MAKECODE('\0','\0','f','a'), "Persian" }, + { MAKECODE('\0','\0','f','f'), "Fulah" }, + { MAKECODE('\0','\0','f','i'), "Finnish" }, + { MAKECODE('\0','\0','f','j'), "Fijian" }, + { MAKECODE('\0','\0','f','o'), "Faroese" }, + { MAKECODE('\0','\0','f','r'), "French" }, + { MAKECODE('\0','\0','f','y'), "Western Frisian" }, + { MAKECODE('\0','\0','g','a'), "Irish" }, + { MAKECODE('\0','\0','g','d'), "Scottish Gaelic" }, + { MAKECODE('\0','\0','g','l'), "Galician" }, + { MAKECODE('\0','\0','g','n'), "Guarani" }, + { MAKECODE('\0','\0','g','u'), "Gujarati" }, + { MAKECODE('\0','\0','g','v'), "Manx" }, + { MAKECODE('\0','\0','h','a'), "Hausa" }, + { MAKECODE('\0','\0','h','e'), "Hebrew" }, + { MAKECODE('\0','\0','h','i'), "Hindi" }, + { MAKECODE('\0','\0','h','o'), "Hiri Motu" }, + { MAKECODE('\0','\0','h','r'), "Croatian" }, + { MAKECODE('\0','\0','h','t'), "Haitian" }, + { MAKECODE('\0','\0','h','u'), "Hungarian" }, + { MAKECODE('\0','\0','h','y'), "Armenian" }, + { MAKECODE('\0','\0','h','z'), "Herero" }, + { MAKECODE('\0','\0','i','a'), "Interlingua" }, + { MAKECODE('\0','\0','i','d'), "Indonesian" }, + { MAKECODE('\0','\0','i','e'), "Interlingue" }, + { MAKECODE('\0','\0','i','g'), "Igbo" }, + { MAKECODE('\0','\0','i','i'), "Sichuan Yi" }, + { MAKECODE('\0','\0','i','k'), "Inupiat" }, + { MAKECODE('\0','\0','i','o'), "Ido" }, + { MAKECODE('\0','\0','i','s'), "Icelandic" }, + { MAKECODE('\0','\0','i','t'), "Italian" }, + { MAKECODE('\0','\0','i','u'), "Inuktitut" }, + { MAKECODE('\0','\0','j','a'), "Japanese" }, + { MAKECODE('\0','\0','j','v'), "Javanese" }, + { MAKECODE('\0','\0','k','a'), "Georgian" }, + { MAKECODE('\0','\0','k','g'), "Kongo" }, + { MAKECODE('\0','\0','k','i'), "Kikuyu" }, + { MAKECODE('\0','\0','k','j'), "Kuanyama" }, + { MAKECODE('\0','\0','k','k'), "Kazakh" }, + { MAKECODE('\0','\0','k','l'), "Kalaallisut" }, + { MAKECODE('\0','\0','k','m'), "Khmer" }, + { MAKECODE('\0','\0','k','n'), "Kannada" }, + { MAKECODE('\0','\0','k','o'), "Korean" }, + { MAKECODE('\0','\0','k','r'), "Kanuri" }, + { MAKECODE('\0','\0','k','s'), "Kashmiri" }, + { MAKECODE('\0','\0','k','u'), "Kurdish" }, + { MAKECODE('\0','\0','k','v'), "Komi" }, + { MAKECODE('\0','\0','k','w'), "Cornish" }, + { MAKECODE('\0','\0','k','y'), "Kirghiz" }, + { MAKECODE('\0','\0','l','a'), "Latin" }, + { MAKECODE('\0','\0','l','b'), "Luxembourgish" }, + { MAKECODE('\0','\0','l','g'), "Ganda" }, + { MAKECODE('\0','\0','l','i'), "Limburgan" }, + { MAKECODE('\0','\0','l','n'), "Lingala" }, + { MAKECODE('\0','\0','l','o'), "Lao" }, + { MAKECODE('\0','\0','l','t'), "Lithuanian" }, + { MAKECODE('\0','\0','l','u'), "Luba-Katanga" }, + { MAKECODE('\0','\0','l','v'), "Latvian, Lettish" }, + { MAKECODE('\0','\0','m','g'), "Malagasy" }, + { MAKECODE('\0','\0','m','h'), "Marshallese" }, + { MAKECODE('\0','\0','m','i'), "Maori" }, + { MAKECODE('\0','\0','m','k'), "Macedonian" }, + { MAKECODE('\0','\0','m','l'), "Malayalam" }, + { MAKECODE('\0','\0','m','n'), "Mongolian" }, + { MAKECODE('\0','\0','m','r'), "Marathi" }, + { MAKECODE('\0','\0','m','s'), "Malay" }, + { MAKECODE('\0','\0','m','t'), "Maltese" }, + { MAKECODE('\0','\0','m','y'), "Burmese" }, + { MAKECODE('\0','\0','n','a'), "Nauru" }, + { MAKECODE('\0','\0','n','b'), "Bokm\xC3\xA5l, Norwegian" }, + { MAKECODE('\0','\0','n','d'), "Ndebele, North" }, + { MAKECODE('\0','\0','n','e'), "Nepali" }, + { MAKECODE('\0','\0','n','g'), "Ndonga" }, + { MAKECODE('\0','\0','n','l'), "Dutch" }, + { MAKECODE('\0','\0','n','n'), "Norwegian Nynorsk" }, + { MAKECODE('\0','\0','n','o'), "Norwegian" }, + { MAKECODE('\0','\0','n','r'), "Ndebele, South" }, + { MAKECODE('\0','\0','n','v'), "Navajo" }, + { MAKECODE('\0','\0','n','y'), "Chichewa" }, + { MAKECODE('\0','\0','o','c'), "Occitan" }, + { MAKECODE('\0','\0','o','j'), "Ojibwa" }, + { MAKECODE('\0','\0','o','m'), "Oromo" }, + { MAKECODE('\0','\0','o','r'), "Oriya" }, + { MAKECODE('\0','\0','o','s'), "Ossetic" }, + { MAKECODE('\0','\0','p','a'), "Punjabi" }, + { MAKECODE('\0','\0','p','i'), "Pali" }, + { MAKECODE('\0','\0','p','l'), "Polish" }, + { MAKECODE('\0','\0','p','s'), "Pashto, Pushto" }, + { MAKECODE('\0','\0','p','t'), "Portuguese" }, + { MAKECODE('\0','\0','q','u'), "Quechua" }, + { MAKECODE('\0','\0','r','m'), "Romansh" }, + { MAKECODE('\0','\0','r','n'), "Kirundi" }, + { MAKECODE('\0','\0','r','o'), "Romanian" }, + { MAKECODE('\0','\0','r','u'), "Russian" }, + { MAKECODE('\0','\0','r','w'), "Kinyarwanda" }, + { MAKECODE('\0','\0','s','a'), "Sanskrit" }, + { MAKECODE('\0','\0','s','c'), "Sardinian" }, + { MAKECODE('\0','\0','s','d'), "Sindhi" }, + { MAKECODE('\0','\0','s','e'), "Northern Sami" }, + { MAKECODE('\0','\0','s','g'), "Sangho" }, + { MAKECODE('\0','\0','s','h'), "Serbo-Croatian" }, + { MAKECODE('\0','\0','s','i'), "Sinhalese" }, + { MAKECODE('\0','\0','s','k'), "Slovak" }, + { MAKECODE('\0','\0','s','l'), "Slovenian" }, + { MAKECODE('\0','\0','s','m'), "Samoan" }, + { MAKECODE('\0','\0','s','n'), "Shona" }, + { MAKECODE('\0','\0','s','o'), "Somali" }, + { MAKECODE('\0','\0','s','q'), "Albanian" }, + { MAKECODE('\0','\0','s','r'), "Serbian" }, + { MAKECODE('\0','\0','s','s'), "Swati" }, + { MAKECODE('\0','\0','s','t'), "Sesotho" }, + { MAKECODE('\0','\0','s','u'), "Sundanese" }, + { MAKECODE('\0','\0','s','v'), "Swedish" }, + { MAKECODE('\0','\0','s','w'), "Swahili" }, + { MAKECODE('\0','\0','t','a'), "Tamil" }, + { MAKECODE('\0','\0','t','e'), "Telugu" }, + { MAKECODE('\0','\0','t','g'), "Tajik" }, + { MAKECODE('\0','\0','t','h'), "Thai" }, + { MAKECODE('\0','\0','t','i'), "Tigrinya" }, + { MAKECODE('\0','\0','t','k'), "Turkmen" }, + { MAKECODE('\0','\0','t','l'), "Tagalog" }, + { MAKECODE('\0','\0','t','n'), "Tswana" }, + { MAKECODE('\0','\0','t','o'), "Tonga" }, + { MAKECODE('\0','\0','t','r'), "Turkish" }, + { MAKECODE('\0','\0','t','s'), "Tsonga" }, + { MAKECODE('\0','\0','t','t'), "Tatar" }, + { MAKECODE('\0','\0','t','w'), "Twi" }, + { MAKECODE('\0','\0','t','y'), "Tahitian" }, + { MAKECODE('\0','\0','u','g'), "Uighur" }, + { MAKECODE('\0','\0','u','k'), "Ukrainian" }, + { MAKECODE('\0','\0','u','r'), "Urdu" }, + { MAKECODE('\0','\0','u','z'), "Uzbek" }, + { MAKECODE('\0','\0','v','e'), "Venda" }, + { MAKECODE('\0','\0','v','i'), "Vietnamese" }, + { MAKECODE('\0','\0','v','o'), "Volapuk" }, + { MAKECODE('\0','\0','w','a'), "Walloon" }, + { MAKECODE('\0','\0','w','o'), "Wolof" }, + { MAKECODE('\0','\0','x','h'), "Xhosa" }, + { MAKECODE('\0','\0','y','i'), "Yiddish" }, + { MAKECODE('\0','\0','y','o'), "Yoruba" }, + { MAKECODE('\0','\0','z','a'), "Zhuang" }, + { MAKECODE('\0','\0','z','h'), "Chinese" }, + { MAKECODE('\0','\0','z','u'), "Zulu" }, +}; + +extern const LCENTRY g_iso639_2[538] = +{ + { MAKECODE('\0','a','b','k'), "Abkhaz" }, + { MAKECODE('\0','a','b','k'), "Abkhazian" }, + { MAKECODE('\0','a','c','e'), "Achinese" }, + { MAKECODE('\0','a','c','h'), "Acoli" }, + { MAKECODE('\0','a','d','a'), "Adangme" }, + { MAKECODE('\0','a','d','y'), "Adygei" }, + { MAKECODE('\0','a','d','y'), "Adyghe" }, + { MAKECODE('\0','a','a','r'), "Afar" }, + { MAKECODE('\0','a','f','h'), "Afrihili" }, + { MAKECODE('\0','a','f','r'), "Afrikaans" }, + { MAKECODE('\0','a','f','a'), "Afro-Asiatic (Other)" }, + { MAKECODE('\0','a','k','a'), "Akan" }, + { MAKECODE('\0','a','k','k'), "Akkadian" }, + { MAKECODE('\0','a','l','b'), "Albanian" }, + { MAKECODE('\0','s','q','i'), "Albanian" }, + { MAKECODE('\0','a','l','e'), "Aleut" }, + { MAKECODE('\0','a','l','g'), "Algonquian languages" }, + { MAKECODE('\0','t','u','t'), "Altaic (Other)" }, + { MAKECODE('\0','a','m','h'), "Amharic" }, + { MAKECODE('\0','a','p','a'), "Apache languages" }, + { MAKECODE('\0','a','r','a'), "Arabic" }, + { MAKECODE('\0','a','r','g'), "Aragonese" }, + { MAKECODE('\0','a','r','c'), "Aramaic" }, + { MAKECODE('\0','a','r','p'), "Arapaho" }, + { MAKECODE('\0','a','r','n'), "Araucanian" }, + { MAKECODE('\0','a','r','w'), "Arawak" }, + { MAKECODE('\0','a','r','m'), "Armenian" }, + { MAKECODE('\0','h','y','e'), "Armenian" }, + { MAKECODE('\0','a','r','t'), "Artificial (Other)" }, + { MAKECODE('\0','a','s','m'), "Assamese" }, + { MAKECODE('\0','a','s','t'), "Asturian" }, + { MAKECODE('\0','a','t','h'), "Athapascan languages" }, + { MAKECODE('\0','a','u','s'), "Australian languages" }, + { MAKECODE('\0','m','a','p'), "Austronesian (Other)" }, + { MAKECODE('\0','a','v','a'), "Avaric" }, + { MAKECODE('\0','a','v','e'), "Avestan" }, + { MAKECODE('\0','a','w','a'), "Awadhi" }, + { MAKECODE('\0','a','y','m'), "Aymara" }, + { MAKECODE('\0','a','z','e'), "Azerbaijani" }, + { MAKECODE('\0','a','s','t'), "Bable" }, + { MAKECODE('\0','b','a','n'), "Balinese" }, + { MAKECODE('\0','b','a','t'), "Baltic (Other)" }, + { MAKECODE('\0','b','a','l'), "Baluchi" }, + { MAKECODE('\0','b','a','m'), "Bambara" }, + { MAKECODE('\0','b','a','i'), "Bamileke languages" }, + { MAKECODE('\0','b','a','d'), "Banda" }, + { MAKECODE('\0','b','n','t'), "Bantu (Other)" }, + { MAKECODE('\0','b','a','s'), "Basa" }, + { MAKECODE('\0','b','a','k'), "Bashkir" }, + { MAKECODE('\0','b','a','q'), "Basque" }, + { MAKECODE('\0','e','u','s'), "Basque" }, + { MAKECODE('\0','b','t','k'), "Batak (Indonesia)" }, + { MAKECODE('\0','b','e','j'), "Beja" }, + { MAKECODE('\0','b','e','l'), "Belarusian" }, + { MAKECODE('\0','b','e','m'), "Bemba" }, + { MAKECODE('\0','b','e','n'), "Bengali" }, + { MAKECODE('\0','b','e','r'), "Berber (Other)" }, + { MAKECODE('\0','b','h','o'), "Bhojpuri" }, + { MAKECODE('\0','b','i','h'), "Bihari" }, + { MAKECODE('\0','b','i','k'), "Bikol" }, + { MAKECODE('\0','b','y','n'), "Bilin" }, + { MAKECODE('\0','b','i','n'), "Bini" }, + { MAKECODE('\0','b','i','s'), "Bislama" }, + { MAKECODE('\0','b','y','n'), "Blin" }, + { MAKECODE('\0','n','o','b'), "Bokm\xC3\xA5l, Norwegian" }, + { MAKECODE('\0','b','o','s'), "Bosnian" }, + { MAKECODE('\0','b','r','a'), "Braj" }, + { MAKECODE('\0','b','r','e'), "Breton" }, + { MAKECODE('\0','b','u','g'), "Buginese" }, + { MAKECODE('\0','b','u','l'), "Bulgarian" }, + { MAKECODE('\0','b','u','a'), "Buriat" }, + { MAKECODE('\0','b','u','r'), "Burmese" }, + { MAKECODE('\0','m','y','a'), "Burmese" }, + { MAKECODE('\0','c','a','d'), "Caddo" }, + { MAKECODE('\0','c','a','r'), "Carib" }, + { MAKECODE('\0','s','p','a'), "Spanish" }, + { MAKECODE('\0','c','a','t'), "Catalan" }, + { MAKECODE('\0','c','a','u'), "Caucasian (Other)" }, + { MAKECODE('\0','c','e','b'), "Cebuano" }, + { MAKECODE('\0','c','e','l'), "Celtic (Other)" }, + { MAKECODE('\0','c','h','g'), "Chagatai" }, + { MAKECODE('\0','c','m','c'), "Chamic languages" }, + { MAKECODE('\0','c','h','a'), "Chamorro" }, + { MAKECODE('\0','c','h','e'), "Chechen" }, + { MAKECODE('\0','c','h','r'), "Cherokee" }, + { MAKECODE('\0','n','y','a'), "Chewa" }, + { MAKECODE('\0','c','h','y'), "Cheyenne" }, + { MAKECODE('\0','c','h','b'), "Chibcha" }, + { MAKECODE('\0','n','y','a'), "Chichewa" }, + { MAKECODE('\0','c','h','i'), "Chinese" }, + { MAKECODE('\0','z','h','o'), "Chinese" }, + { MAKECODE('\0','c','h','n'), "Chinook jargon" }, + { MAKECODE('\0','c','h','p'), "Chipewyan" }, + { MAKECODE('\0','c','h','o'), "Choctaw" }, + { MAKECODE('\0','z','h','a'), "Chuang" }, + { MAKECODE('\0','c','h','u'), "Church Slavonic" }, + { MAKECODE('\0','c','h','k'), "Chuukese" }, + { MAKECODE('\0','c','h','v'), "Chuvash" }, + { MAKECODE('\0','n','w','c'), "Classical Nepal Bhasa" }, + { MAKECODE('\0','n','w','c'), "Classical Newari" }, + { MAKECODE('\0','c','o','p'), "Coptic" }, + { MAKECODE('\0','c','o','r'), "Cornish" }, + { MAKECODE('\0','c','o','s'), "Corsican" }, + { MAKECODE('\0','c','r','e'), "Cree" }, + { MAKECODE('\0','m','u','s'), "Creek" }, + { MAKECODE('\0','c','r','p'), "Creoles and pidgins (Other)" }, + { MAKECODE('\0','c','p','e'), "English-based (Other)" }, + { MAKECODE('\0','c','p','f'), "French-based (Other)" }, + { MAKECODE('\0','c','p','p'), "Portuguese-based (Other)" }, + { MAKECODE('\0','c','r','h'), "Crimean Tatar" }, + { MAKECODE('\0','c','r','h'), "Crimean Turkish" }, + { MAKECODE('\0','h','r','v'), "Croatian" }, + { MAKECODE('\0','s','c','r'), "Croatian" }, + { MAKECODE('\0','c','u','s'), "Cushitic (Other)" }, + { MAKECODE('\0','c','z','e'), "Czech" }, + { MAKECODE('\0','c','e','s'), "Czech" }, + { MAKECODE('\0','d','a','k'), "Dakota" }, + { MAKECODE('\0','d','a','n'), "Danish" }, + { MAKECODE('\0','d','a','r'), "Dargwa" }, + { MAKECODE('\0','d','a','y'), "Dayak" }, + { MAKECODE('\0','d','e','l'), "Delaware" }, + { MAKECODE('\0','d','i','n'), "Dinka" }, + { MAKECODE('\0','d','i','v'), "Divehi" }, + { MAKECODE('\0','d','o','i'), "Dogri" }, + { MAKECODE('\0','d','g','r'), "Dogrib" }, + { MAKECODE('\0','d','r','a'), "Dravidian (Other)" }, + { MAKECODE('\0','d','u','a'), "Duala" }, + { MAKECODE('\0','d','u','t'), "Dutch" }, + { MAKECODE('\0','n','l','d'), "Dutch" }, + { MAKECODE('\0','d','u','m'), "Dutch, Middle (ca. 1050-1350)" }, + { MAKECODE('\0','d','y','u'), "Dyula" }, + { MAKECODE('\0','d','z','o'), "Dzongkha" }, + { MAKECODE('\0','e','f','i'), "Efik" }, + { MAKECODE('\0','e','g','y'), "Egyptian (Ancient)" }, + { MAKECODE('\0','e','k','a'), "Ekajuk" }, + { MAKECODE('\0','e','l','x'), "Elamite" }, + { MAKECODE('\0','e','n','g'), "English" }, + { MAKECODE('\0','e','n','m'), "English, Middle (1100-1500)" }, + { MAKECODE('\0','a','n','g'), "English, Old (ca.450-1100)" }, + { MAKECODE('\0','m','y','v'), "Erzya" }, + { MAKECODE('\0','e','p','o'), "Esperanto" }, + { MAKECODE('\0','e','s','t'), "Estonian" }, + { MAKECODE('\0','e','w','e'), "Ewe" }, + { MAKECODE('\0','e','w','o'), "Ewondo" }, + { MAKECODE('\0','f','a','n'), "Fang" }, + { MAKECODE('\0','f','a','t'), "Fanti" }, + { MAKECODE('\0','f','a','o'), "Faroese" }, + { MAKECODE('\0','f','i','j'), "Fijian" }, + { MAKECODE('\0','f','i','l'), "Filipino" }, + { MAKECODE('\0','f','i','n'), "Finnish" }, + { MAKECODE('\0','f','i','u'), "Finno-Ugrian (Other)" }, + { MAKECODE('\0','d','u','t'), "Flemish" }, + { MAKECODE('\0','n','l','d'), "Flemish" }, + { MAKECODE('\0','f','o','n'), "Fon" }, + { MAKECODE('\0','f','r','e'), "French" }, + { MAKECODE('\0','f','r','a'), "French" }, + { MAKECODE('\0','f','r','m'), "French, Middle (ca.1400-1600)" }, + { MAKECODE('\0','f','r','o'), "French, Old (842-ca.1400)" }, + { MAKECODE('\0','f','r','y'), "Frisian" }, + { MAKECODE('\0','f','u','r'), "Friulian" }, + { MAKECODE('\0','f','u','l'), "Fulah" }, + { MAKECODE('\0','g','a','a'), "Ga" }, + { MAKECODE('\0','g','l','a'), "Gaelic" }, + { MAKECODE('\0','g','l','g'), "Gallegan" }, + { MAKECODE('\0','l','u','g'), "Ganda" }, + { MAKECODE('\0','g','a','y'), "Gayo" }, + { MAKECODE('\0','g','b','a'), "Gbaya" }, + { MAKECODE('\0','g','e','z'), "Geez" }, + { MAKECODE('\0','g','e','o'), "Georgian" }, + { MAKECODE('\0','k','a','t'), "Georgian" }, + { MAKECODE('\0','g','e','r'), "German" }, + { MAKECODE('\0','d','e','u'), "German" }, + { MAKECODE('\0','n','d','s'), "German, Low" }, + { MAKECODE('\0','g','m','h'), "German, Middle High (ca.1050-1500)" }, + { MAKECODE('\0','g','o','h'), "German, Old High (ca.750-1050)" }, + { MAKECODE('\0','g','e','m'), "Germanic (Other)" }, + { MAKECODE('\0','k','i','k'), "Gikuyu" }, + { MAKECODE('\0','g','i','l'), "Gilbertese" }, + { MAKECODE('\0','g','o','n'), "Gondi" }, + { MAKECODE('\0','g','o','r'), "Gorontalo" }, + { MAKECODE('\0','g','o','t'), "Gothic" }, + { MAKECODE('\0','g','r','b'), "Grebo" }, + { MAKECODE('\0','g','r','c'), "Greek, Ancient (to 1453)" }, + { MAKECODE('\0','g','r','e'), "Greek, Modern (1453-)" }, + { MAKECODE('\0','e','l','l'), "Greek, Modern (1453-)" }, + { MAKECODE('\0','k','a','l'), "Greenlandic" }, + { MAKECODE('\0','g','r','n'), "Guarani" }, + { MAKECODE('\0','g','u','j'), "Gujarati" }, + { MAKECODE('\0','g','w','i'), "Gwich\xC2\xB4in" }, + { MAKECODE('\0','h','a','i'), "Haida" }, + { MAKECODE('\0','h','a','t'), "Haitian" }, + { MAKECODE('\0','h','a','t'), "Haitian Creole" }, + { MAKECODE('\0','h','a','u'), "Hausa" }, + { MAKECODE('\0','h','a','w'), "Hawaiian" }, + { MAKECODE('\0','h','e','b'), "Hebrew" }, + { MAKECODE('\0','h','e','r'), "Herero" }, + { MAKECODE('\0','h','i','l'), "Hiligaynon" }, + { MAKECODE('\0','h','i','m'), "Himachali" }, + { MAKECODE('\0','h','i','n'), "Hindi" }, + { MAKECODE('\0','h','m','o'), "Hiri Motu" }, + { MAKECODE('\0','h','i','t'), "Hittite" }, + { MAKECODE('\0','h','m','n'), "Hmong" }, + { MAKECODE('\0','h','u','n'), "Hungarian" }, + { MAKECODE('\0','h','u','p'), "Hupa" }, + { MAKECODE('\0','i','b','a'), "Iban" }, + { MAKECODE('\0','i','c','e'), "Icelandic" }, + { MAKECODE('\0','i','s','l'), "Icelandic" }, + { MAKECODE('\0','i','d','o'), "Ido" }, + { MAKECODE('\0','i','b','o'), "Igbo" }, + { MAKECODE('\0','i','j','o'), "Ijo" }, + { MAKECODE('\0','i','l','o'), "Iloko" }, + { MAKECODE('\0','s','m','n'), "Inari Sami" }, + { MAKECODE('\0','i','n','c'), "Indic (Other)" }, + { MAKECODE('\0','i','n','e'), "Indo-European (Other)" }, + { MAKECODE('\0','i','n','d'), "Indonesian" }, + { MAKECODE('\0','i','n','h'), "Ingush" }, + { MAKECODE('\0','i','n','a'), "Auxiliary Language Association)" }, + { MAKECODE('\0','i','l','e'), "Interlingue" }, + { MAKECODE('\0','i','k','u'), "Inuktitut" }, + { MAKECODE('\0','i','p','k'), "Inupiaq" }, + { MAKECODE('\0','i','r','a'), "Iranian (Other)" }, + { MAKECODE('\0','g','l','e'), "Irish" }, + { MAKECODE('\0','m','g','a'), "Irish, Middle (900-1200)" }, + { MAKECODE('\0','s','g','a'), "Irish, Old (to 900)" }, + { MAKECODE('\0','i','r','o'), "Iroquoian languages" }, + { MAKECODE('\0','i','t','a'), "Italian" }, + { MAKECODE('\0','j','p','n'), "Japanese" }, + { MAKECODE('\0','j','a','v'), "Javanese" }, + { MAKECODE('\0','j','r','b'), "Judeo-Arabic" }, + { MAKECODE('\0','j','p','r'), "Judeo-Persian" }, + { MAKECODE('\0','k','b','d'), "Kabardian" }, + { MAKECODE('\0','k','a','b'), "Kabyle" }, + { MAKECODE('\0','k','a','c'), "Kachin" }, + { MAKECODE('\0','k','a','l'), "Kalaallisut" }, + { MAKECODE('\0','x','a','l'), "Kalmyk" }, + { MAKECODE('\0','k','a','m'), "Kamba" }, + { MAKECODE('\0','k','a','n'), "Kannada" }, + { MAKECODE('\0','k','a','u'), "Kanuri" }, + { MAKECODE('\0','k','r','c'), "Karachay-Balkar" }, + { MAKECODE('\0','k','a','a'), "Kara-Kalpak" }, + { MAKECODE('\0','k','a','r'), "Karen" }, + { MAKECODE('\0','k','a','s'), "Kashmiri" }, + { MAKECODE('\0','c','s','b'), "Kashubian" }, + { MAKECODE('\0','k','a','w'), "Kawi" }, + { MAKECODE('\0','k','a','z'), "Kazakh" }, + { MAKECODE('\0','k','h','a'), "Khasi" }, + { MAKECODE('\0','k','h','m'), "Khmer" }, + { MAKECODE('\0','k','h','i'), "Khoisan (Other)" }, + { MAKECODE('\0','k','h','o'), "Khotanese" }, + { MAKECODE('\0','k','i','k'), "Kikuyu" }, + { MAKECODE('\0','k','m','b'), "Kimbundu" }, + { MAKECODE('\0','k','i','n'), "Kinyarwanda" }, + { MAKECODE('\0','k','i','r'), "Kirghiz" }, + { MAKECODE('\0','t','l','h'), "Klingon" }, + { MAKECODE('\0','k','o','m'), "Komi" }, + { MAKECODE('\0','k','o','n'), "Kongo" }, + { MAKECODE('\0','k','o','k'), "Konkani" }, + { MAKECODE('\0','k','o','r'), "Korean" }, + { MAKECODE('\0','k','o','s'), "Kosraean" }, + { MAKECODE('\0','k','p','e'), "Kpelle" }, + { MAKECODE('\0','k','r','o'), "Kru" }, + { MAKECODE('\0','k','u','a'), "Kuanyama" }, + { MAKECODE('\0','k','u','m'), "Kumyk" }, + { MAKECODE('\0','k','u','r'), "Kurdish" }, + { MAKECODE('\0','k','r','u'), "Kurukh" }, + { MAKECODE('\0','k','u','t'), "Kutenai" }, + { MAKECODE('\0','k','u','a'), "Kwanyama, Kuanyama" }, + { MAKECODE('\0','l','a','d'), "Ladino" }, + { MAKECODE('\0','l','a','h'), "Lahnda" }, + { MAKECODE('\0','l','a','m'), "Lamba" }, + { MAKECODE('\0','l','a','o'), "Lao" }, + { MAKECODE('\0','l','a','t'), "Latin" }, + { MAKECODE('\0','l','a','v'), "Latvian" }, + { MAKECODE('\0','l','t','z'), "Letzeburgesch" }, + { MAKECODE('\0','l','e','z'), "Lezghian" }, + { MAKECODE('\0','l','i','m'), "Limburgan" }, + { MAKECODE('\0','l','i','m'), "Limburger" }, + { MAKECODE('\0','l','i','m'), "Limburgish" }, + { MAKECODE('\0','l','i','n'), "Lingala" }, + { MAKECODE('\0','l','i','t'), "Lithuanian" }, + { MAKECODE('\0','j','b','o'), "Lojban" }, + { MAKECODE('\0','n','d','s'), "Low German" }, + { MAKECODE('\0','n','d','s'), "Low Saxon" }, + { MAKECODE('\0','d','s','b'), "Lower Sorbian" }, + { MAKECODE('\0','l','o','z'), "Lozi" }, + { MAKECODE('\0','l','u','b'), "Luba-Katanga" }, + { MAKECODE('\0','l','u','a'), "Luba-Lulua" }, + { MAKECODE('\0','l','u','i'), "Luiseno" }, + { MAKECODE('\0','s','m','j'), "Lule Sami" }, + { MAKECODE('\0','l','u','n'), "Lunda" }, + { MAKECODE('\0','l','u','o'), "Luo (Kenya and Tanzania)" }, + { MAKECODE('\0','l','u','s'), "Lushai" }, + { MAKECODE('\0','l','t','z'), "Luxembourgish" }, + { MAKECODE('\0','m','a','c'), "Macedonian" }, + { MAKECODE('\0','m','k','d'), "Macedonian" }, + { MAKECODE('\0','m','a','d'), "Madurese" }, + { MAKECODE('\0','m','a','g'), "Magahi" }, + { MAKECODE('\0','m','a','i'), "Maithili" }, + { MAKECODE('\0','m','a','k'), "Makasar" }, + { MAKECODE('\0','m','l','g'), "Malagasy" }, + { MAKECODE('\0','m','a','y'), "Malay" }, + { MAKECODE('\0','m','s','a'), "Malay" }, + { MAKECODE('\0','m','a','l'), "Malayalam" }, + { MAKECODE('\0','m','l','t'), "Maltese" }, + { MAKECODE('\0','m','n','c'), "Manchu" }, + { MAKECODE('\0','m','d','r'), "Mandar" }, + { MAKECODE('\0','m','a','n'), "Mandingo" }, + { MAKECODE('\0','m','n','i'), "Manipuri" }, + { MAKECODE('\0','m','n','o'), "Manobo languages" }, + { MAKECODE('\0','g','l','v'), "Manx" }, + { MAKECODE('\0','m','a','o'), "Maori" }, + { MAKECODE('\0','m','r','i'), "Maori" }, + { MAKECODE('\0','m','a','r'), "Marathi" }, + { MAKECODE('\0','c','h','m'), "Mari" }, + { MAKECODE('\0','m','a','h'), "Marshallese" }, + { MAKECODE('\0','m','w','r'), "Marwari" }, + { MAKECODE('\0','m','a','s'), "Masai" }, + { MAKECODE('\0','m','y','n'), "Mayan languages" }, + { MAKECODE('\0','m','e','n'), "Mende" }, + { MAKECODE('\0','m','i','c'), "Micmac" }, + { MAKECODE('\0','m','i','c'), "Mi'kmaq" }, + { MAKECODE('\0','m','i','n'), "Minangkabau" }, + { MAKECODE('\0','m','w','l'), "Mirandese" }, + { MAKECODE('\0','m','i','s'), "Miscellaneous languages" }, + { MAKECODE('\0','m','o','h'), "Mohawk" }, + { MAKECODE('\0','m','d','f'), "Moksha" }, + { MAKECODE('\0','m','o','l'), "Moldavian" }, + { MAKECODE('\0','m','k','h'), "Mon-Khmer (Other)" }, + { MAKECODE('\0','l','o','l'), "Mongo" }, + { MAKECODE('\0','m','o','n'), "Mongolian" }, + { MAKECODE('\0','m','o','s'), "Mossi" }, + { MAKECODE('\0','m','u','l'), "Multiple languages" }, + { MAKECODE('\0','m','u','n'), "Munda languages" }, + { MAKECODE('\0','n','a','h'), "Nahuatl" }, + { MAKECODE('\0','n','a','u'), "Nauru" }, + { MAKECODE('\0','n','a','v'), "Navaho, Navajo" }, + { MAKECODE('\0','n','a','v'), "Navajo" }, + { MAKECODE('\0','n','d','e'), "Ndebele, North" }, + { MAKECODE('\0','n','b','l'), "Ndebele, South" }, + { MAKECODE('\0','n','d','o'), "Ndonga" }, + { MAKECODE('\0','n','a','p'), "Neapolitan" }, + { MAKECODE('\0','n','e','w'), "Nepal Bhasa" }, + { MAKECODE('\0','n','e','p'), "Nepali" }, + { MAKECODE('\0','n','e','w'), "Newari" }, + { MAKECODE('\0','n','i','a'), "Nias" }, + { MAKECODE('\0','n','i','c'), "Niger-Kordofanian (Other)" }, + { MAKECODE('\0','s','s','a'), "Nilo-Saharan (Other)" }, + { MAKECODE('\0','n','i','u'), "Niuean" }, + { MAKECODE('\0','z','x','x'), "No linguistic content" }, + { MAKECODE('\0','n','o','g'), "Nogai" }, + { MAKECODE('\0','n','o','n'), "Norse, Old" }, + { MAKECODE('\0','n','a','i'), "North American Indian (Other)" }, + { MAKECODE('\0','s','m','e'), "Northern Sami" }, + { MAKECODE('\0','n','s','o'), "Northern Sotho" }, + { MAKECODE('\0','n','d','e'), "North Ndebele" }, + { MAKECODE('\0','n','o','r'), "Norwegian" }, + { MAKECODE('\0','n','o','b'), "Norwegian Bokm\xC3\xA5l" }, + { MAKECODE('\0','n','n','o'), "Norwegian Nynorsk" }, + { MAKECODE('\0','n','u','b'), "Nubian languages" }, + { MAKECODE('\0','n','y','m'), "Nyamwezi" }, + { MAKECODE('\0','n','y','a'), "Nyanja" }, + { MAKECODE('\0','n','y','n'), "Nyankole" }, + { MAKECODE('\0','n','n','o'), "Nynorsk, Norwegian" }, + { MAKECODE('\0','n','y','o'), "Nyoro" }, + { MAKECODE('\0','n','z','i'), "Nzima" }, + { MAKECODE('\0','o','c','i'), "Occitan (post 1500)" }, + { MAKECODE('\0','o','j','i'), "Ojibwa" }, + { MAKECODE('\0','c','h','u'), "Old Bulgarian" }, + { MAKECODE('\0','c','h','u'), "Old Church Slavonic" }, + { MAKECODE('\0','n','w','c'), "Old Newari" }, + { MAKECODE('\0','c','h','u'), "Old Slavonic" }, + { MAKECODE('\0','o','r','i'), "Oriya" }, + { MAKECODE('\0','o','r','m'), "Oromo" }, + { MAKECODE('\0','o','s','a'), "Osage" }, + { MAKECODE('\0','o','s','s'), "Ossetian" }, + { MAKECODE('\0','o','s','s'), "Ossetic" }, + { MAKECODE('\0','o','t','o'), "Otomian languages" }, + { MAKECODE('\0','p','a','l'), "Pahlavi" }, + { MAKECODE('\0','p','a','u'), "Palauan" }, + { MAKECODE('\0','p','l','i'), "Pali" }, + { MAKECODE('\0','p','a','m'), "Pampanga" }, + { MAKECODE('\0','p','a','g'), "Pangasinan" }, + { MAKECODE('\0','p','a','n'), "Panjabi" }, + { MAKECODE('\0','p','a','p'), "Papiamento" }, + { MAKECODE('\0','p','a','a'), "Papuan (Other)" }, + { MAKECODE('\0','n','s','o'), "Pedi" }, + { MAKECODE('\0','p','e','r'), "Persian" }, + { MAKECODE('\0','f','a','s'), "Persian" }, + { MAKECODE('\0','p','e','o'), "Persian, Old (ca.600-400 B.C.)" }, + { MAKECODE('\0','p','h','i'), "Philippine (Other)" }, + { MAKECODE('\0','p','h','n'), "Phoenician" }, + { MAKECODE('\0','f','i','l'), "Pilipino" }, + { MAKECODE('\0','p','o','n'), "Pohnpeian" }, + { MAKECODE('\0','p','o','l'), "Polish" }, + { MAKECODE('\0','p','o','r'), "Portuguese" }, + { MAKECODE('\0','p','r','a'), "Prakrit languages" }, + { MAKECODE('\0','o','c','i'), "Proven\xC3\xA7""al" }, + { MAKECODE('\0','p','r','o'), "Proven\xC3\xA7""al, Old (to 1500)" }, + { MAKECODE('\0','p','a','n'), "Punjabi" }, + { MAKECODE('\0','p','u','s'), "Pushto" }, + { MAKECODE('\0','q','u','e'), "Quechua" }, + { MAKECODE('\0','r','o','h'), "Raeto-Romance" }, + { MAKECODE('\0','r','a','j'), "Rajasthani" }, + { MAKECODE('\0','r','a','p'), "Rapanui" }, + { MAKECODE('\0','r','a','r'), "Rarotongan" }, +// { "qaa-qtz", "Reserved for local use" }, + { MAKECODE('\0','r','o','a'), "Romance (Other)" }, + { MAKECODE('\0','r','u','m'), "Romanian" }, + { MAKECODE('\0','r','o','n'), "Romanian" }, + { MAKECODE('\0','r','o','m'), "Romany" }, + { MAKECODE('\0','r','u','n'), "Rundi" }, + { MAKECODE('\0','r','u','s'), "Russian" }, + { MAKECODE('\0','s','a','l'), "Salishan languages" }, + { MAKECODE('\0','s','a','m'), "Samaritan Aramaic" }, + { MAKECODE('\0','s','m','i'), "Sami languages (Other)" }, + { MAKECODE('\0','s','m','o'), "Samoan" }, + { MAKECODE('\0','s','a','d'), "Sandawe" }, + { MAKECODE('\0','s','a','g'), "Sango" }, + { MAKECODE('\0','s','a','n'), "Sanskrit" }, + { MAKECODE('\0','s','a','t'), "Santali" }, + { MAKECODE('\0','s','r','d'), "Sardinian" }, + { MAKECODE('\0','s','a','s'), "Sasak" }, + { MAKECODE('\0','n','d','s'), "Saxon, Low" }, + { MAKECODE('\0','s','c','o'), "Scots" }, + { MAKECODE('\0','g','l','a'), "Scottish Gaelic" }, + { MAKECODE('\0','s','e','l'), "Selkup" }, + { MAKECODE('\0','s','e','m'), "Semitic (Other)" }, + { MAKECODE('\0','n','s','o'), "Sepedi" }, + { MAKECODE('\0','s','c','c'), "Serbian" }, + { MAKECODE('\0','s','r','p'), "Serbian" }, + { MAKECODE('\0','s','r','r'), "Serer" }, + { MAKECODE('\0','s','h','n'), "Shan" }, + { MAKECODE('\0','s','n','a'), "Shona" }, + { MAKECODE('\0','i','i','i'), "Sichuan Yi" }, + { MAKECODE('\0','s','c','n'), "Sicilian" }, + { MAKECODE('\0','s','i','d'), "Sidamo" }, + { MAKECODE('\0','s','g','n'), "Sign languages" }, + { MAKECODE('\0','b','l','a'), "Siksika" }, + { MAKECODE('\0','s','n','d'), "Sindhi" }, + { MAKECODE('\0','s','i','n'), "Sinhala" }, + { MAKECODE('\0','s','i','n'), "Sinhalese" }, + { MAKECODE('\0','s','i','t'), "Sino-Tibetan (Other)" }, + { MAKECODE('\0','s','i','o'), "Siouan languages" }, + { MAKECODE('\0','s','m','s'), "Skolt Sami" }, + { MAKECODE('\0','d','e','n'), "Slave (Athapascan)" }, + { MAKECODE('\0','s','l','a'), "Slavic (Other)" }, + { MAKECODE('\0','s','l','o'), "Slovak" }, + { MAKECODE('\0','s','l','k'), "Slovak" }, + { MAKECODE('\0','s','l','v'), "Slovenian" }, + { MAKECODE('\0','s','o','g'), "Sogdian" }, + { MAKECODE('\0','s','o','m'), "Somali" }, + { MAKECODE('\0','s','o','n'), "Songhai" }, + { MAKECODE('\0','s','n','k'), "Soninke" }, + { MAKECODE('\0','w','e','n'), "Sorbian languages" }, + { MAKECODE('\0','n','s','o'), "Sotho, Northern" }, + { MAKECODE('\0','s','o','t'), "Sotho, Southern" }, + { MAKECODE('\0','s','a','i'), "South American Indian (Other)" }, + { MAKECODE('\0','s','m','a'), "Southern Sami" }, + { MAKECODE('\0','n','b','l'), "South Ndebele" }, + { MAKECODE('\0','s','p','a'), "Castilian" }, + { MAKECODE('\0','s','u','k'), "Sukuma" }, + { MAKECODE('\0','s','u','x'), "Sumerian" }, + { MAKECODE('\0','s','u','n'), "Sundanese" }, + { MAKECODE('\0','s','u','s'), "Susu" }, + { MAKECODE('\0','s','w','a'), "Swahili" }, + { MAKECODE('\0','s','s','w'), "Swati" }, + { MAKECODE('\0','s','w','e'), "Swedish" }, + { MAKECODE('\0','s','y','r'), "Syriac" }, + { MAKECODE('\0','t','g','l'), "Tagalog" }, + { MAKECODE('\0','t','a','h'), "Tahitian" }, + { MAKECODE('\0','t','a','i'), "Tai (Other)" }, + { MAKECODE('\0','t','g','k'), "Tajik" }, + { MAKECODE('\0','t','m','h'), "Tamashek" }, + { MAKECODE('\0','t','a','m'), "Tamil" }, + { MAKECODE('\0','t','a','t'), "Tatar" }, + { MAKECODE('\0','t','e','l'), "Telugu" }, + { MAKECODE('\0','t','e','r'), "Tereno" }, + { MAKECODE('\0','t','e','t'), "Tetum" }, + { MAKECODE('\0','t','h','a'), "Thai" }, + { MAKECODE('\0','t','i','b'), "Tibetan" }, + { MAKECODE('\0','b','o','d'), "Tibetan" }, + { MAKECODE('\0','t','i','g'), "Tigre" }, + { MAKECODE('\0','t','i','r'), "Tigrinya" }, + { MAKECODE('\0','t','e','m'), "Timne" }, + { MAKECODE('\0','t','i','v'), "Tiv" }, + { MAKECODE('\0','t','l','h'), "tlhIngan-Hol" }, + { MAKECODE('\0','t','l','i'), "Tlingit" }, + { MAKECODE('\0','t','p','i'), "Tok Pisin" }, + { MAKECODE('\0','t','k','l'), "Tokelau" }, + { MAKECODE('\0','t','o','g'), "Tonga (Nyasa)" }, + { MAKECODE('\0','t','o','n'), "Tonga (Tonga Islands)" }, + { MAKECODE('\0','t','s','i'), "Tsimshian" }, + { MAKECODE('\0','t','s','o'), "Tsonga" }, + { MAKECODE('\0','t','s','n'), "Tswana" }, + { MAKECODE('\0','t','u','m'), "Tumbuka" }, + { MAKECODE('\0','t','u','p'), "Tupi languages" }, + { MAKECODE('\0','t','u','r'), "Turkish" }, + { MAKECODE('\0','o','t','a'), "Turkish, Ottoman (1500-1928)" }, + { MAKECODE('\0','t','u','k'), "Turkmen" }, + { MAKECODE('\0','t','v','l'), "Tuvalu" }, + { MAKECODE('\0','t','y','v'), "Tuvinian" }, + { MAKECODE('\0','t','w','i'), "Twi" }, + { MAKECODE('\0','u','d','m'), "Udmurt" }, + { MAKECODE('\0','u','g','a'), "Ugaritic" }, + { MAKECODE('\0','u','i','g'), "Uighur" }, + { MAKECODE('\0','u','k','r'), "Ukrainian" }, + { MAKECODE('\0','u','m','b'), "Umbundu" }, + { MAKECODE('\0','u','n','d'), "Undetermined" }, + { MAKECODE('\0','h','s','b'), "Upper Sorbian" }, + { MAKECODE('\0','u','r','d'), "Urdu" }, + { MAKECODE('\0','u','i','g'), "Uyghur" }, + { MAKECODE('\0','u','z','b'), "Uzbek" }, + { MAKECODE('\0','v','a','i'), "Vai" }, + { MAKECODE('\0','c','a','t'), "Valencian" }, + { MAKECODE('\0','v','e','n'), "Venda" }, + { MAKECODE('\0','v','i','e'), "Vietnamese" }, + { MAKECODE('\0','v','o','l'), "Volap\xC3\xBCk" }, + { MAKECODE('\0','v','o','t'), "Votic" }, + { MAKECODE('\0','w','a','k'), "Wakashan languages" }, + { MAKECODE('\0','w','a','l'), "Walamo" }, + { MAKECODE('\0','w','l','n'), "Walloon" }, + { MAKECODE('\0','w','a','r'), "Waray" }, + { MAKECODE('\0','w','a','s'), "Washo" }, + { MAKECODE('\0','w','e','l'), "Welsh" }, + { MAKECODE('\0','c','y','m'), "Welsh" }, + { MAKECODE('\0','w','o','l'), "Wolof" }, + { MAKECODE('\0','x','h','o'), "Xhosa" }, + { MAKECODE('\0','s','a','h'), "Yakut" }, + { MAKECODE('\0','y','a','o'), "Yao" }, + { MAKECODE('\0','y','a','p'), "Yapese" }, + { MAKECODE('\0','y','i','d'), "Yiddish" }, + { MAKECODE('\0','y','o','r'), "Yoruba" }, + { MAKECODE('\0','y','p','k'), "Yupik languages" }, + { MAKECODE('\0','z','n','d'), "Zande" }, + { MAKECODE('\0','z','a','p'), "Zapotec" }, + { MAKECODE('\0','z','e','n'), "Zenaga" }, + { MAKECODE('\0','z','h','a'), "Zhuang" }, + { MAKECODE('\0','z','u','l'), "Zulu" }, + { MAKECODE('\0','z','u','n'), "Zuni" }, +}; + +const CharCodeConvertionWithHack CharCode2To3[189] = +{ + { "aa", "aar", NULL }, + { "ab", "abk", NULL }, + { "af", "afr", NULL }, + { "ak", "aka", NULL }, + { "am", "amh", NULL }, + { "ar", "ara", NULL }, + { "an", "arg", NULL }, + { "as", "asm", NULL }, + { "av", "ava", NULL }, + { "ae", "ave", NULL }, + { "ay", "aym", NULL }, + { "az", "aze", NULL }, + { "ba", "bak", NULL }, + { "bm", "bam", NULL }, + { "be", "bel", NULL }, + { "bn", "ben", NULL }, + { "bh", "bih", NULL }, + { "bi", "bis", NULL }, + { "bo", "tib", NULL }, + { "bs", "bos", NULL }, + { "br", "bre", NULL }, + { "bg", "bul", NULL }, + { "ca", "cat", NULL }, + { "cs", "cze", "ces" }, + { "ch", "cha", NULL }, + { "ce", "che", NULL }, + { "cu", "chu", NULL }, + { "cv", "chv", NULL }, + { "kw", "cor", NULL }, + { "co", "cos", NULL }, + { "cr", "cre", NULL }, + { "cy", "wel", NULL }, + { "da", "dan", NULL }, + { "de", "ger", "deu" }, + { "dv", "div", NULL }, + { "dz", "dzo", NULL }, + { "el", "gre", "ell" }, + { "en", "eng", NULL }, + { "eo", "epo", NULL }, + { "et", "est", NULL }, + { "eu", "baq", NULL }, + { "ee", "ewe", NULL }, + { "fo", "fao", NULL }, + { "fa", "per", NULL }, + { "fj", "fij", NULL }, + { "fi", "fin", NULL }, + { "fr", "fre", "fra" }, + { "fy", "fry", NULL }, + { "ff", "ful", NULL }, + { "gd", "gla", NULL }, + { "ga", "gle", NULL }, + { "gl", "glg", NULL }, + { "gv", "glv", NULL }, + { "gn", "grn", NULL }, + { "gu", "guj", NULL }, + { "ht", "hat", NULL }, + { "ha", "hau", NULL }, + { "he", "heb", NULL }, + { "hz", "her", NULL }, + { "hi", "hin", NULL }, + { "ho", "hmo", NULL }, + { "hr", "hrv", NULL }, + { "hu", "hun", NULL }, + { "hy", "arm", NULL }, + { "ig", "ibo", NULL }, + { "io", "ido", NULL }, + { "ii", "iii", NULL }, + { "iu", "iku", NULL }, + { "ie", "ile", NULL }, + { "ia", "ina", NULL }, + { "id", "ind", NULL }, + { "ik", "ipk", NULL }, + { "is", "ice", "isl" }, + { "it", "ita", NULL }, + { "jv", "jav", NULL }, + { "ja", "jpn", NULL }, + { "kl", "kal", NULL }, + { "kn", "kan", NULL }, + { "ks", "kas", NULL }, + { "ka", "geo", NULL }, + { "kr", "kau", NULL }, + { "kk", "kaz", NULL }, + { "km", "khm", NULL }, + { "ki", "kik", NULL }, + { "rw", "kin", NULL }, + { "ky", "kir", NULL }, + { "kv", "kom", NULL }, + { "kg", "kon", NULL }, + { "ko", "kor", NULL }, + { "kj", "kua", NULL }, + { "ku", "kur", NULL }, + { "lo", "lao", NULL }, + { "la", "lat", NULL }, + { "lv", "lav", NULL }, + { "li", "lim", NULL }, + { "ln", "lin", NULL }, + { "lt", "lit", NULL }, + { "lb", "ltz", NULL }, + { "lu", "lub", NULL }, + { "lg", "lug", NULL }, + { "mk", "mac", NULL }, + { "mh", "mah", NULL }, + { "ml", "mal", NULL }, + { "mi", "mao", NULL }, + { "mr", "mar", NULL }, + { "ms", "may", NULL }, + { "mg", "mlg", NULL }, + { "mt", "mlt", NULL }, + { "mn", "mon", NULL }, + { "my", "bur", NULL }, + { "na", "nau", NULL }, + { "nv", "nav", NULL }, + { "nr", "nbl", NULL }, + { "nd", "nde", NULL }, + { "ng", "ndo", NULL }, + { "ne", "nep", NULL }, + { "nl", "dut", "nld" }, + { "nn", "nno", NULL }, + { "nb", "nob", NULL }, + { "no", "nor", NULL }, + { "ny", "nya", NULL }, + { "oc", "oci", NULL }, + { "oj", "oji", NULL }, + { "or", "ori", NULL }, + { "om", "orm", NULL }, + { "os", "oss", NULL }, + { "pa", "pan", NULL }, + { "pi", "pli", NULL }, + { "pl", "pol", "plk" }, + { "pt", "por", "ptg" }, + { "ps", "pus", NULL }, + { "qu", "que", NULL }, + { "rm", "roh", NULL }, + { "ro", "rum", "ron" }, + { "rn", "run", NULL }, + { "ru", "rus", NULL }, + { "sh", "scr", NULL }, + { "sg", "sag", NULL }, + { "sa", "san", NULL }, + { "si", "sin", NULL }, + { "sk", "slo", "sky" }, + { "sl", "slv", NULL }, + { "se", "sme", NULL }, + { "sm", "smo", NULL }, + { "sn", "sna", NULL }, + { "sd", "snd", NULL }, + { "so", "som", NULL }, + { "st", "sot", NULL }, + { "es", "spa", "esp" }, + { "sq", "alb", NULL }, + { "sc", "srd", NULL }, + { "sr", "srp", NULL }, + { "ss", "ssw", NULL }, + { "su", "sun", NULL }, + { "sw", "swa", NULL }, + { "sv", "swe", "sve" }, + { "ty", "tah", NULL }, + { "ta", "tam", NULL }, + { "tt", "tat", NULL }, + { "te", "tel", NULL }, + { "tg", "tgk", NULL }, + { "tl", "tgl", NULL }, + { "th", "tha", NULL }, + { "ti", "tir", NULL }, + { "to", "ton", NULL }, + { "tn", "tsn", NULL }, + { "ts", "tso", NULL }, + { "tk", "tuk", NULL }, + { "tr", "tur", "trk" }, + { "tw", "twi", NULL }, + { "ug", "uig", NULL }, + { "uk", "ukr", NULL }, + { "ur", "urd", NULL }, + { "uz", "uzb", NULL }, + { "ve", "ven", NULL }, + { "vi", "vie", NULL }, + { "vo", "vol", NULL }, + { "wa", "wln", NULL }, + { "wo", "wol", NULL }, + { "xh", "xho", NULL }, + { "yi", "yid", NULL }, + { "yo", "yor", NULL }, + { "za", "zha", NULL }, + { "zh", "chi", "zho" }, + { "zu", "zul", NULL }, + { "zv", "und", NULL }, // Kodi intern mapping for missing "Undetermined" iso639-1 code + { "zx", "zxx", NULL }, // Kodi intern mapping for missing "No linguistic content" iso639-1 code + { "zy", "mis", NULL }, // Kodi intern mapping for missing "Miscellaneous languages" iso639-1 code + { "zz", "mul", NULL } // Kodi intern mapping for missing "Multiple languages" iso639-1 code +}; + +// Based on ISO 3166 +const CharCodeConvertion RegionCode2To3[246] = +{ + { "af", "afg" }, + { "ax", "ala" }, + { "al", "alb" }, + { "dz", "dza" }, + { "as", "asm" }, + { "ad", "and" }, + { "ao", "ago" }, + { "ai", "aia" }, + { "aq", "ata" }, + { "ag", "atg" }, + { "ar", "arg" }, + { "am", "arm" }, + { "aw", "abw" }, + { "au", "aus" }, + { "at", "aut" }, + { "az", "aze" }, + { "bs", "bhs" }, + { "bh", "bhr" }, + { "bd", "bgd" }, + { "bb", "brb" }, + { "by", "blr" }, + { "be", "bel" }, + { "bz", "blz" }, + { "bj", "ben" }, + { "bm", "bmu" }, + { "bt", "btn" }, + { "bo", "bol" }, + { "ba", "bih" }, + { "bw", "bwa" }, + { "bv", "bvt" }, + { "br", "bra" }, + { "io", "iot" }, + { "bn", "brn" }, + { "bg", "bgr" }, + { "bf", "bfa" }, + { "bi", "bdi" }, + { "kh", "khm" }, + { "cm", "cmr" }, + { "ca", "can" }, + { "cv", "cpv" }, + { "ky", "cym" }, + { "cf", "caf" }, + { "td", "tcd" }, + { "cl", "chl" }, + { "cn", "chn" }, + { "cx", "cxr" }, + { "cc", "cck" }, + { "co", "col" }, + { "km", "com" }, + { "cg", "cog" }, + { "cd", "cod" }, + { "ck", "cok" }, + { "cr", "cri" }, + { "ci", "civ" }, + { "hr", "hrv" }, + { "cu", "cub" }, + { "cy", "cyp" }, + { "cz", "cze" }, + { "dk", "dnk" }, + { "dj", "dji" }, + { "dm", "dma" }, + { "do", "dom" }, + { "ec", "ecu" }, + { "eg", "egy" }, + { "sv", "slv" }, + { "gq", "gnq" }, + { "er", "eri" }, + { "ee", "est" }, + { "et", "eth" }, + { "fk", "flk" }, + { "fo", "fro" }, + { "fj", "fji" }, + { "fi", "fin" }, + { "fr", "fra" }, + { "gf", "guf" }, + { "pf", "pyf" }, + { "tf", "atf" }, + { "ga", "gab" }, + { "gm", "gmb" }, + { "ge", "geo" }, + { "de", "deu" }, + { "gh", "gha" }, + { "gi", "gib" }, + { "gr", "grc" }, + { "gl", "grl" }, + { "gd", "grd" }, + { "gp", "glp" }, + { "gu", "gum" }, + { "gt", "gtm" }, + { "gg", "ggy" }, + { "gn", "gin" }, + { "gw", "gnb" }, + { "gy", "guy" }, + { "ht", "hti" }, + { "hm", "hmd" }, + { "va", "vat" }, + { "hn", "hnd" }, + { "hk", "hkg" }, + { "hu", "hun" }, + { "is", "isl" }, + { "in", "ind" }, + { "id", "idn" }, + { "ir", "irn" }, + { "iq", "irq" }, + { "ie", "irl" }, + { "im", "imn" }, + { "il", "isr" }, + { "it", "ita" }, + { "jm", "jam" }, + { "jp", "jpn" }, + { "je", "jey" }, + { "jo", "jor" }, + { "kz", "kaz" }, + { "ke", "ken" }, + { "ki", "kir" }, + { "kp", "prk" }, + { "kr", "kor" }, + { "kw", "kwt" }, + { "kg", "kgz" }, + { "la", "lao" }, + { "lv", "lva" }, + { "lb", "lbn" }, + { "ls", "lso" }, + { "lr", "lbr" }, + { "ly", "lby" }, + { "li", "lie" }, + { "lt", "ltu" }, + { "lu", "lux" }, + { "mo", "mac" }, + { "mk", "mkd" }, + { "mg", "mdg" }, + { "mw", "mwi" }, + { "my", "mys" }, + { "mv", "mdv" }, + { "ml", "mli" }, + { "mt", "mlt" }, + { "mh", "mhl" }, + { "mq", "mtq" }, + { "mr", "mrt" }, + { "mu", "mus" }, + { "yt", "myt" }, + { "mx", "mex" }, + { "fm", "fsm" }, + { "md", "mda" }, + { "mc", "mco" }, + { "mn", "mng" }, + { "me", "mne" }, + { "ms", "msr" }, + { "ma", "mar" }, + { "mz", "moz" }, + { "mm", "mmr" }, + { "na", "nam" }, + { "nr", "nru" }, + { "np", "npl" }, + { "nl", "nld" }, + { "an", "ant" }, + { "nc", "ncl" }, + { "nz", "nzl" }, + { "ni", "nic" }, + { "ne", "ner" }, + { "ng", "nga" }, + { "nu", "niu" }, + { "nf", "nfk" }, + { "mp", "mnp" }, + { "no", "nor" }, + { "om", "omn" }, + { "pk", "pak" }, + { "pw", "plw" }, + { "ps", "pse" }, + { "pa", "pan" }, + { "pg", "png" }, + { "py", "pry" }, + { "pe", "per" }, + { "ph", "phl" }, + { "pn", "pcn" }, + { "pl", "pol" }, + { "pt", "prt" }, + { "pr", "pri" }, + { "qa", "qat" }, + { "re", "reu" }, + { "ro", "rou" }, + { "ru", "rus" }, + { "rw", "rwa" }, + { "bl", "blm" }, + { "sh", "shn" }, + { "kn", "kna" }, + { "lc", "lca" }, + { "mf", "maf" }, + { "pm", "spm" }, + { "vc", "vct" }, + { "ws", "wsm" }, + { "sm", "smr" }, + { "st", "stp" }, + { "sa", "sau" }, + { "sn", "sen" }, + { "rs", "srb" }, + { "sc", "syc" }, + { "sl", "sle" }, + { "sg", "sgp" }, + { "sk", "svk" }, + { "si", "svn" }, + { "sb", "slb" }, + { "so", "som" }, + { "za", "zaf" }, + { "gs", "sgs" }, + { "es", "esp" }, + { "lk", "lka" }, + { "sd", "sdn" }, + { "sr", "sur" }, + { "sj", "sjm" }, + { "sz", "swz" }, + { "se", "swe" }, + { "ch", "che" }, + { "sy", "syr" }, + { "tw", "twn" }, + { "tj", "tjk" }, + { "tz", "tza" }, + { "th", "tha" }, + { "tl", "tls" }, + { "tg", "tgo" }, + { "tk", "tkl" }, + { "to", "ton" }, + { "tt", "tto" }, + { "tn", "tun" }, + { "tr", "tur" }, + { "tm", "tkm" }, + { "tc", "tca" }, + { "tv", "tuv" }, + { "ug", "uga" }, + { "ua", "ukr" }, + { "ae", "are" }, + { "gb", "gbr" }, + { "us", "usa" }, + { "um", "umi" }, + { "uy", "ury" }, + { "uz", "uzb" }, + { "vu", "vut" }, + { "ve", "ven" }, + { "vn", "vnm" }, + { "vg", "vgb" }, + { "vi", "vir" }, + { "wf", "wlf" }, + { "eh", "esh" }, + { "ye", "yem" }, + { "zm", "zmb" }, + { "zw", "zwe" } +}; diff --git a/src/utils/LangCodeExpander.h b/src/utils/LangCodeExpander.h new file mode 100644 index 0000000000..400493aec8 --- /dev/null +++ b/src/utils/LangCodeExpander.h @@ -0,0 +1,114 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> +#include <string> +#include <vector> + +class TiXmlElement; + +class CLangCodeExpander +{ +public: + + enum LANGFORMATS + { + ISO_639_1, + ISO_639_2, + ENGLISH_NAME + }; + + CLangCodeExpander(void); + ~CLangCodeExpander(void); + + bool Lookup(std::string& desc, const std::string& code); + bool Lookup(std::string& desc, const int code); + + /** \brief Determines if two english language names represent the same language. + * \param[in] lang1 The first language string to compare given as english language name. + * \param[in] lang2 The second language string to compare given as english language name. + * \return true if the two language strings represent the same language, false otherwise. + * For example "Abkhaz" and "Abkhazian" represent the same language. + */ + bool CompareFullLangNames(const std::string& lang1, const std::string& lang2); + + /** \brief Determines if two languages given as ISO 639-1, ISO 639-2/T, or ISO 639-2/B codes represent the same language. + * \param[in] code1 The first language to compare given as ISO 639-1, ISO 639-2/T, or ISO 639-2/B code. + * \param[in] code2 The second language to compare given as ISO 639-1, ISO 639-2/T, or ISO 639-2/B code. + * \return true if the two language codes represent the same language, false otherwise. + * For example "ger", "deu" and "de" represent the same language. + */ + bool CompareLangCodes(const std::string& code1, const std::string& code2); + + /** \brief Converts a language given as 2-Char (ISO 639-1), + * 3-Char (ISO 639-2/T or ISO 639-2/B), + * or full english name string to a 2-Char (ISO 639-1) code. + * \param[out] code The 2-Char language code of the given language lang. + * \param[in] lang The language that should be converted. + * \param[in] checkXbmcLocales Try to find in XBMC specific locales + * \return true if the conversion succeeded, false otherwise. + */ + bool ConvertToTwoCharCode(std::string& code, const std::string& lang, bool checkXbmcLocales = true); + + /** \brief Converts a language given as 2-Char (ISO 639-1), + * 3-Char (ISO 639-2/T or ISO 639-2/B), + * or full english name string to a 3-Char ISO 639-2/T code. + * \param[in] lang The language that should be converted. + * \return The 3-Char ISO 639-2/T code of lang if that code exists, lang otherwise. + */ + std::string ConvertToISO6392T(const std::string& lang); + static bool ConvertTwoToThreeCharCode(std::string& strThreeCharCode, const std::string& strTwoCharCode, bool checkWin32Locales = false); + static bool ConvertToThreeCharCode(std::string& strThreeCharCode, const std::string& strCharCode, bool checkXbmcLocales = true, bool checkWin32Locales = false); + +#ifdef TARGET_WINDOWS + static bool ConvertLinuxToWindowsRegionCodes(const std::string& strTwoCharCode, std::string& strThreeCharCode); + static bool ConvertWindowsToGeneralCharCode(const std::string& strWindowsCharCode, std::string& strThreeCharCode); +#endif + + void LoadUserCodes(const TiXmlElement* pRootElement); + void Clear(); + + static std::vector<std::string> GetLanguageNames(LANGFORMATS format = ISO_639_1); +protected: + + /** \brief Converts a language code given as a long, see #MAKECODE(a, b, c, d) + * to its string representation. + * \param[in] code The language code given as a long, see #MAKECODE(a, b, c, d). + * \param[out] ret The string representation of the given language code code. + */ + static void CodeToString(long code, std::string& ret); + + typedef std::map<std::string, std::string> STRINGLOOKUPTABLE; + STRINGLOOKUPTABLE m_mapUser; + + static bool LookupInDb(std::string& desc, const std::string& code); + bool LookupInMap(std::string& desc, const std::string& code); + + /** \brief Looks up the ISO 639-1, ISO 639-2/T, or ISO 639-2/B, whichever it finds first, + * code of the given english language name. + * \param[in] desc The english language name for which a code is looked for. + * \param[out] code The ISO 639-1, ISO 639-2/T, or ISO 639-2/B code of the given language desc. + * \return true if the a code was found, false otherwise. + */ + bool ReverseLookup(const std::string& desc, std::string& code); +}; + +extern CLangCodeExpander g_LangCodeExpander; diff --git a/src/utils/LegacyPathTranslation.cpp b/src/utils/LegacyPathTranslation.cpp new file mode 100644 index 0000000000..f13a578eb8 --- /dev/null +++ b/src/utils/LegacyPathTranslation.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "LegacyPathTranslation.h" +#include "utils/StringUtils.h" +#include "URL.h" + +typedef struct Translator { + const char *legacyPath; + const char *newPath; +} Translator; + +// ATTENTION: Make sure the longer match strings go first +// because the string match is performed with StringUtils::StartsWith() +static Translator s_videoDbTranslator[] = { + { "videodb://1/1", "videodb://movies/genres" }, + { "videodb://1/2", "videodb://movies/titles" }, + { "videodb://1/3", "videodb://movies/years" }, + { "videodb://1/4", "videodb://movies/actors" }, + { "videodb://1/5", "videodb://movies/directors" }, + { "videodb://1/6", "videodb://movies/studios" }, + { "videodb://1/7", "videodb://movies/sets" }, + { "videodb://1/8", "videodb://movies/countries" }, + { "videodb://1/9", "videodb://movies/tags" }, + { "videodb://1", "videodb://movies" }, + { "videodb://2/1", "videodb://tvshows/genres" }, + { "videodb://2/2", "videodb://tvshows/titles" }, + { "videodb://2/3", "videodb://tvshows/years" }, + { "videodb://2/4", "videodb://tvshows/actors" }, + { "videodb://2/5", "videodb://tvshows/studios" }, + { "videodb://2/9", "videodb://tvshows/tags" }, + { "videodb://2", "videodb://tvshows" }, + { "videodb://3/1", "videodb://musicvideos/genres" }, + { "videodb://3/2", "videodb://musicvideos/titles" }, + { "videodb://3/3", "videodb://musicvideos/years" }, + { "videodb://3/4", "videodb://musicvideos/artists" }, + { "videodb://3/5", "videodb://musicvideos/albums" }, + { "videodb://3/9", "videodb://musicvideos/tags" }, + { "videodb://3", "videodb://musicvideos" }, + { "videodb://4", "videodb://recentlyaddedmovies" }, + { "videodb://5", "videodb://recentlyaddedepisodes" }, + { "videodb://6", "videodb://recentlyaddedmusicvideos" } +}; + +#define VideoDbTranslatorSize sizeof(s_videoDbTranslator) / sizeof(Translator) + +// ATTENTION: Make sure the longer match strings go first +// because the string match is performed with StringUtils::StartsWith() +static Translator s_musicDbTranslator[] = { + { "musicdb://10", "musicdb://singles" }, + { "musicdb://1", "musicdb://genres" }, + { "musicdb://2", "musicdb://artists" }, + { "musicdb://3", "musicdb://albums" }, + { "musicdb://4", "musicdb://songs" }, + { "musicdb://5/1", "musicdb://top100/albums" }, + { "musicdb://5/2", "musicdb://top100/songs" }, + { "musicdb://5", "musicdb://top100" }, + { "musicdb://6", "musicdb://recentlyaddedalbums" }, + { "musicdb://7", "musicdb://recentlyplayedalbums" }, + { "musicdb://8", "musicdb://compilations" }, + { "musicdb://9", "musicdb://years" } +}; + +#define MusicDbTranslatorSize sizeof(s_musicDbTranslator) / sizeof(Translator) + +std::string CLegacyPathTranslation::TranslateVideoDbPath(const CURL &legacyPath) +{ + return TranslatePath(legacyPath.Get(), s_videoDbTranslator, VideoDbTranslatorSize); +} + +std::string CLegacyPathTranslation::TranslateMusicDbPath(const CURL &legacyPath) +{ + return TranslatePath(legacyPath.Get(), s_musicDbTranslator, MusicDbTranslatorSize); +} + +std::string CLegacyPathTranslation::TranslateVideoDbPath(const std::string &legacyPath) +{ + return TranslatePath(legacyPath, s_videoDbTranslator, VideoDbTranslatorSize); +} + +std::string CLegacyPathTranslation::TranslateMusicDbPath(const std::string &legacyPath) +{ + return TranslatePath(legacyPath, s_musicDbTranslator, MusicDbTranslatorSize); +} + +std::string CLegacyPathTranslation::TranslatePath(const std::string &legacyPath, Translator *translationMap, size_t translationMapSize) +{ + std::string newPath = legacyPath; + for (size_t index = 0; index < translationMapSize; index++) + { + if (StringUtils::StartsWithNoCase(newPath, translationMap[index].legacyPath)) + { + StringUtils::Replace(newPath, translationMap[index].legacyPath, translationMap[index].newPath); + break; + } + } + + return newPath; +} diff --git a/src/utils/LegacyPathTranslation.h b/src/utils/LegacyPathTranslation.h new file mode 100644 index 0000000000..26aa6d59d6 --- /dev/null +++ b/src/utils/LegacyPathTranslation.h @@ -0,0 +1,58 @@ +#pragma once +/* + * Copyright (C) 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> + +typedef struct Translator Translator; + +class CURL; + +/*! + \brief Translates old internal paths into new ones + + Translates old videodb:// and musicdb:// paths which used numbers + to indicate a specific category to new paths using more descriptive + strings to indicate categories. + */ +class CLegacyPathTranslation +{ +public: + /*! + \brief Translates old videodb:// paths to new ones + + \param legacyPath Path in the old videodb:// format using numbers + \return Path in the new videodb:// format using descriptive strings + */ + static std::string TranslateVideoDbPath(const CURL &legacyPath); + static std::string TranslateVideoDbPath(const std::string &legacyPath); + + /*! + \brief Translates old musicdb:// paths to new ones + + \param legacyPath Path in the old musicdb:// format using numbers + \return Path in the new musicdb:// format using descriptive strings + */ + static std::string TranslateMusicDbPath(const CURL &legacyPath); + static std::string TranslateMusicDbPath(const std::string &legacyPath); + +private: + static std::string TranslatePath(const std::string &legacyPath, Translator *translationMap, size_t translationMapSize); +}; diff --git a/src/utils/Makefile.in b/src/utils/Makefile.in new file mode 100644 index 0000000000..e2034470c6 --- /dev/null +++ b/src/utils/Makefile.in @@ -0,0 +1,90 @@ +SRCS += AlarmClock.cpp +SRCS += AliasShortcutUtils.cpp +SRCS += Archive.cpp +SRCS += AsyncFileCopy.cpp +SRCS += AutoPtrHandle.cpp +SRCS += auto_buffer.cpp +SRCS += Base64.cpp +SRCS += BitstreamConverter.cpp +SRCS += BitstreamStats.cpp +SRCS += BooleanLogic.cpp +SRCS += CharsetConverter.cpp +SRCS += CharsetDetection.cpp +SRCS += CPUInfo.cpp +SRCS += Crc32.cpp +SRCS += CryptThreading.cpp +SRCS += DatabaseUtils.cpp +SRCS += EndianSwap.cpp +SRCS += EdenVideoArtUpdater.cpp +SRCS += Environment.cpp +SRCS += Fanart.cpp +SRCS += fastmemcpy.c +SRCS += fastmemcpy-arm.S +SRCS += FileOperationJob.cpp +SRCS += FileUtils.cpp +SRCS += fstrcmp.c +SRCS += fft.cpp +SRCS += GLUtils.cpp +SRCS += GroupUtils.cpp +SRCS += HTMLTable.cpp +SRCS += HTMLUtil.cpp +SRCS += HttpHeader.cpp +SRCS += HttpParser.cpp +SRCS += HttpResponse.cpp +SRCS += InfoLoader.cpp +SRCS += JobManager.cpp +SRCS += JSONVariantParser.cpp +SRCS += JSONVariantWriter.cpp +SRCS += LabelFormatter.cpp +SRCS += LangCodeExpander.cpp +SRCS += LegacyPathTranslation.cpp +SRCS += log.cpp +SRCS += md5.cpp +SRCS += MarkWatchedJob.cpp +SRCS += Mime.cpp +SRCS += Observer.cpp +SRCS += PerformanceSample.cpp +SRCS += PerformanceStats.cpp +SRCS += posix/PosixInterfaceForCLog.cpp +SRCS += POUtils.cpp +SRCS += RecentlyAddedJob.cpp +SRCS += RegExp.cpp +SRCS += RingBuffer.cpp +SRCS += RssManager.cpp +SRCS += RssReader.cpp +SRCS += SaveFileStateJob.cpp +SRCS += ScraperParser.cpp +SRCS += ScraperUrl.cpp +SRCS += Screenshot.cpp +SRCS += SeekHandler.cpp +SRCS += SortUtils.cpp +SRCS += Splash.cpp +SRCS += Stopwatch.cpp +SRCS += StreamDetails.cpp +SRCS += StreamUtils.cpp +SRCS += StringUtils.cpp +SRCS += StringValidation.cpp +SRCS += SystemInfo.cpp +SRCS += TextSearch.cpp +SRCS += TimeSmoother.cpp +SRCS += TimeUtils.cpp +SRCS += TuxBoxUtil.cpp +SRCS += URIUtils.cpp +SRCS += UrlOptions.cpp +SRCS += Variant.cpp +SRCS += Vector.cpp +SRCS += Weather.cpp +SRCS += XBMCTinyXML.cpp +SRCS += XMLUtils.cpp +SRCS += Utf8Utils.cpp +SRCS += XSLTUtils.cpp +SRCS += ActorProtocol.cpp + +ifeq (@USE_OPENGLES@,1) +SRCS += AMLUtils.cpp +endif + +LIB = utils.a + +include @abs_top_srcdir@/Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(patsubst %.S,,$(SRCS)))) diff --git a/src/utils/MarkWatchedJob.cpp b/src/utils/MarkWatchedJob.cpp new file mode 100644 index 0000000000..054da5c124 --- /dev/null +++ b/src/utils/MarkWatchedJob.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2014 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "FileItem.h" +#include "filesystem/Directory.h" +#include "GUIUserMessages.h" +#include "guilib/GUIWindowManager.h" +#include "MarkWatchedJob.h" +#include "profiles/ProfilesManager.h" +#include "Util.h" +#include "utils/URIUtils.h" +#include "video/VideoDatabase.h" + +#ifdef HAS_UPNP +#include "network/upnp/UPnP.h" +#endif + +#include <vector> + +CMarkWatchedJob::CMarkWatchedJob(const CFileItemPtr &item, bool bMark) +{ + m_item = item; + m_bMark = bMark; +} + +CMarkWatchedJob::~CMarkWatchedJob() +{ +} + +bool CMarkWatchedJob::operator==(const CJob* job) const +{ + if (strcmp(job->GetType(), GetType()) == 0) + { + const CMarkWatchedJob* markJob = dynamic_cast<const CMarkWatchedJob*>(job); + if (markJob) + return (m_item->IsSamePath(markJob->m_item.get()) && markJob->m_bMark == m_bMark); + } + return false; +} + +bool CMarkWatchedJob::DoWork() +{ + if (!CProfilesManager::Get().GetCurrentProfile().canWriteDatabases()) + return false; + + CFileItemList items; + items.Add(CFileItemPtr(new CFileItem(*m_item))); + + if (m_item->m_bIsFolder) + CUtil::GetRecursiveListing(m_item->GetPath(), items, "", XFILE::DIR_FLAG_NO_FILE_INFO); + + std::vector<CFileItemPtr> markItems; + for (int i = 0; i < items.Size(); i++) + { + if (items[i]->HasVideoInfoTag() && + (( m_bMark && items[i]->GetVideoInfoTag()->m_playCount) || + (!m_bMark && !(items[i]->GetVideoInfoTag()->m_playCount)))) + continue; + +#ifdef HAS_UPNP + if (!URIUtils::IsUPnP(items[i]->GetPath()) || !UPNP::CUPnP::MarkWatched(*items[i], m_bMark)) +#endif + { + markItems.push_back(items[i]); + } + } + + if (!markItems.empty()) + { + CVideoDatabase database; + if (!database.Open()) + return false; + + database.BeginTransaction(); + + for (std::vector<CFileItemPtr>::const_iterator iter = markItems.begin(); iter != markItems.end(); ++iter) + { + CFileItemPtr pItem = *iter; + if (m_bMark) + { + std::string path(pItem->GetPath()); + if (pItem->HasVideoInfoTag()) + path = pItem->GetVideoInfoTag()->GetPath(); + + database.ClearBookMarksOfFile(path, CBookmark::RESUME); + database.IncrementPlayCount(*pItem); + } + else + database.SetPlayCount(*pItem, 0); + } + + database.CommitTransaction(); + database.Close(); + } + + return true; +} + +CMarkWatchedQueue &CMarkWatchedQueue::Get() +{ + static CMarkWatchedQueue markWatchedQueue; + return markWatchedQueue; +} + +void CMarkWatchedQueue::OnJobComplete(unsigned int jobID, bool success, CJob *job) +{ + if (success) + { + if (QueueEmpty()) + { + CUtil::DeleteVideoDatabaseDirectoryCache(); + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE); + g_windowManager.SendThreadMessage(msg); + } + return CJobQueue::OnJobComplete(jobID, success, job); + } + CancelJobs(); +} diff --git a/src/utils/MarkWatchedJob.h b/src/utils/MarkWatchedJob.h new file mode 100644 index 0000000000..cd59fd746e --- /dev/null +++ b/src/utils/MarkWatchedJob.h @@ -0,0 +1,44 @@ +#pragma once +/* + * Copyright (C) 2014 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Job.h" +#include "utils/JobManager.h" + +class CMarkWatchedJob : public CJob +{ +public: + CMarkWatchedJob(const CFileItemPtr &item, bool bMark); +private: + virtual ~CMarkWatchedJob(); + virtual const char *GetType() const { return "markwatched"; } + virtual bool operator==(const CJob* job) const; + virtual bool DoWork(); + CFileItemPtr m_item; + bool m_bMark; +}; + +class CMarkWatchedQueue: public CJobQueue +{ +public: + static CMarkWatchedQueue &Get(); +private: + virtual void OnJobComplete(unsigned int jobID, bool success, CJob *job); +}; diff --git a/src/utils/MathUtils.h b/src/utils/MathUtils.h new file mode 100644 index 0000000000..ea32f37c71 --- /dev/null +++ b/src/utils/MathUtils.h @@ -0,0 +1,204 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <cassert> +#include <climits> +#include <cmath> + +#ifdef __SSE2__ +#include <emmintrin.h> +#endif + +// use real compiler defines in here as we want to +// avoid including system.h or other magic includes. +// use 'gcc -dM -E - < /dev/null' or similar to find them. + +#if defined(__ppc__) || \ + defined(__powerpc__) || \ + defined(__arm__) + #define DISABLE_MATHUTILS_ASM_ROUND_INT +#endif + +/*! \brief Math utility class. + Note that the test() routine should return true for all implementations + + See http://ldesoras.free.fr/doc/articles/rounding_en.pdf for an explanation + of the technique used on x86. + */ +namespace MathUtils +{ + // GCC does something stupid with optimization on release builds if we try + // to assert in these functions + + /*! \brief Round to nearest integer. + This routine does fast rounding to the nearest integer. + In the case (k + 0.5 for any integer k) we round up to k+1, and in all other + instances we should return the nearest integer. + Thus, { -1.5, -0.5, 0.5, 1.5 } is rounded to { -1, 0, 1, 2 }. + It preserves the property that round(k) - round(k-1) = 1 for all doubles k. + + Make sure MathUtils::test() returns true for each implementation. + \sa truncate_int, test + */ + inline int round_int(double x) + { + assert(x > static_cast<double>(INT_MIN / 2) - 1.0); + assert(x < static_cast<double>(INT_MAX / 2) + 1.0); + +#if defined(DISABLE_MATHUTILS_ASM_ROUND_INT) + /* This implementation warrants some further explanation. + * + * First, a couple of notes on rounding: + * 1) C casts from float/double to integer round towards zero. + * 2) Float/double additions are rounded according to the normal rules, + * in other words: on some architectures, it's fixed at compile-time, + * and on others it can be set using fesetround()). The following + * analysis assumes round-to-nearest with ties rounding to even. This + * is a fairly sensible choice, and is the default with ARM VFP. + * + * What this function wants is round-to-nearest with ties rounding to + * +infinity. This isn't an IEEE rounding mode, even if we could guarantee + * that all architectures supported fesetround(), which they don't. Instead, + * this adds an offset of 2147483648.5 (= 0x80000000.8p0), then casts to + * an unsigned int (crucially, all possible inputs are now in a range where + * round to zero acts the same as round to -infinity) and then subtracts + * 0x80000000 in the integer domain. The 0.5 component of the offset + * converts what is effectively a round down into a round to nearest, with + * ties rounding up, as desired. + * + * There is a catch, that because there is a double rounding, there is a + * small region where the input falls just *below* a tie, where the addition + * of the offset causes a round *up* to an exact integer, due to the finite + * level of precision available in floating point. You need to be aware of + * this when calling this function, although at present it is not believed + * that XBMC ever attempts to round numbers in this window. + * + * It is worth proving the size of the affected window. Recall that double + * precision employs a mantissa of 52 bits. + * 1) For all inputs -0.5 <= x <= INT_MAX + * Once the offset is applied, the most significant binary digit in the + * floating-point representation is +2^31. + * At this magnitude, the smallest step representable in double precision + * is 2^31 / 2^52 = 0.000000476837158203125 + * So the size of the range which is rounded up due to the addition is + * half the size of this step, or 0.0000002384185791015625 + * + * 2) For all inputs INT_MIN/2 < x < -0.5 + * Once the offset is applied, the most significant binary digit in the + * floating-point representation is +2^30. + * At this magnitude, the smallest step representable in double precision + * is 2^30 / 2^52 = 0.0000002384185791015625 + * So the size of the range which is rounded up due to the addition is + * half the size of this step, or 0.00000011920928955078125 + * + * 3) For all inputs INT_MIN <= x <= INT_MIN/2 + * The representation once the offset is applied has equal or greater + * precision than the input, so the addition does not cause rounding. + */ + return ((unsigned int) (x + 0x80000000.8p0)) - 0x80000000; + +#else + const float round_to_nearest = 0.5f; + int i; +#if defined(__SSE2__) + const float round_dn_to_nearest = 0.4999999f; + i = (x > 0) ? _mm_cvttsd_si32(_mm_set_sd(x + round_to_nearest)) : _mm_cvttsd_si32(_mm_set_sd(x - round_dn_to_nearest)); + +#elif defined(TARGET_WINDOWS) + __asm + { + fld x + fadd st, st (0) + fadd round_to_nearest + fistp i + sar i, 1 + } + +#else + __asm__ __volatile__ ( + "fadd %%st\n\t" + "fadd %%st(1)\n\t" + "fistpl %0\n\t" + "sarl $1, %0\n" + : "=m"(i) : "u"(round_to_nearest), "t"(x) : "st" + ); + +#endif + return i; +#endif + } + + /*! \brief Truncate to nearest integer. + This routine does fast truncation to an integer. + It should simply drop the fractional portion of the floating point number. + + Make sure MathUtils::test() returns true for each implementation. + \sa round_int, test + */ + inline int truncate_int(double x) + { + assert(x > static_cast<double>(INT_MIN / 2) - 1.0); + assert(x < static_cast<double>(INT_MAX / 2) + 1.0); + return x; + } + + inline int64_t abs(int64_t a) + { + return (a < 0) ? -a : a; + } + + inline unsigned bitcount(unsigned v) + { + unsigned c = 0; + for (c = 0; v; c++) + v &= v - 1; // clear the least significant bit set + return c; + } + + inline void hack() + { + // stupid hack to keep compiler from dropping these + // functions as unused + MathUtils::round_int(0.0); + MathUtils::truncate_int(0.0); + MathUtils::abs(0); + } + +#if 0 + /*! \brief test routine for round_int and truncate_int + Must return true on all platforms. + */ + inline bool test() + { + for (int i = -8; i < 8; ++i) + { + double d = 0.25*i; + int r = (i < 0) ? (i - 1) / 4 : (i + 2) / 4; + int t = i / 4; + if (round_int(d) != r || truncate_int(d) != t) + return false; + } + return true; + } +#endif +} // namespace MathUtils + diff --git a/src/utils/Mime.cpp b/src/utils/Mime.cpp new file mode 100644 index 0000000000..874085f5b1 --- /dev/null +++ b/src/utils/Mime.cpp @@ -0,0 +1,697 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <algorithm> + +#include "Mime.h" +#include "FileItem.h" +#include "StdString.h" +#include "URIUtils.h" +#include "music/tags/MusicInfoTag.h" +#include "video/VideoInfoTag.h" +#include "URL.h" +#include "utils/StringUtils.h" +#include "filesystem/CurlFile.h" + +using namespace std; + +map<string, string> fillMimeTypes() +{ + map<string, string> mimeTypes; + + mimeTypes.insert(pair<string, string>("3dm", "x-world/x-3dmf")); + mimeTypes.insert(pair<string, string>("3dmf", "x-world/x-3dmf")); + mimeTypes.insert(pair<string, string>("a", "application/octet-stream")); + mimeTypes.insert(pair<string, string>("aab", "application/x-authorware-bin")); + mimeTypes.insert(pair<string, string>("aam", "application/x-authorware-map")); + mimeTypes.insert(pair<string, string>("aas", "application/x-authorware-seg")); + mimeTypes.insert(pair<string, string>("abc", "text/vnd.abc")); + mimeTypes.insert(pair<string, string>("acgi", "text/html")); + mimeTypes.insert(pair<string, string>("afl", "video/animaflex")); + mimeTypes.insert(pair<string, string>("ai", "application/postscript")); + mimeTypes.insert(pair<string, string>("aif", "audio/aiff")); + mimeTypes.insert(pair<string, string>("aifc", "audio/x-aiff")); + mimeTypes.insert(pair<string, string>("aiff", "audio/aiff")); + mimeTypes.insert(pair<string, string>("aim", "application/x-aim")); + mimeTypes.insert(pair<string, string>("aip", "text/x-audiosoft-intra")); + mimeTypes.insert(pair<string, string>("ani", "application/x-navi-animation")); + mimeTypes.insert(pair<string, string>("aos", "application/x-nokia-9000-communicator-add-on-software")); + mimeTypes.insert(pair<string, string>("aps", "application/mime")); + mimeTypes.insert(pair<string, string>("arc", "application/octet-stream")); + mimeTypes.insert(pair<string, string>("arj", "application/arj")); + mimeTypes.insert(pair<string, string>("art", "image/x-jg")); + mimeTypes.insert(pair<string, string>("asf", "video/x-ms-asf")); + mimeTypes.insert(pair<string, string>("asm", "text/x-asm")); + mimeTypes.insert(pair<string, string>("asp", "text/asp")); + mimeTypes.insert(pair<string, string>("asx", "video/x-ms-asf")); + mimeTypes.insert(pair<string, string>("au", "audio/basic")); + mimeTypes.insert(pair<string, string>("avi", "video/avi")); + mimeTypes.insert(pair<string, string>("avs", "video/avs-video")); + mimeTypes.insert(pair<string, string>("bcpio", "application/x-bcpio")); + mimeTypes.insert(pair<string, string>("bin", "application/octet-stream")); + mimeTypes.insert(pair<string, string>("bm", "image/bmp")); + mimeTypes.insert(pair<string, string>("bmp", "image/bmp")); + mimeTypes.insert(pair<string, string>("boo", "application/book")); + mimeTypes.insert(pair<string, string>("book", "application/book")); + mimeTypes.insert(pair<string, string>("boz", "application/x-bzip2")); + mimeTypes.insert(pair<string, string>("bsh", "application/x-bsh")); + mimeTypes.insert(pair<string, string>("bz", "application/x-bzip")); + mimeTypes.insert(pair<string, string>("bz2", "application/x-bzip2")); + mimeTypes.insert(pair<string, string>("c", "text/plain")); + mimeTypes.insert(pair<string, string>("c++", "text/plain")); + mimeTypes.insert(pair<string, string>("cat", "application/vnd.ms-pki.seccat")); + mimeTypes.insert(pair<string, string>("cc", "text/plain")); + mimeTypes.insert(pair<string, string>("ccad", "application/clariscad")); + mimeTypes.insert(pair<string, string>("cco", "application/x-cocoa")); + mimeTypes.insert(pair<string, string>("cdf", "application/cdf")); + mimeTypes.insert(pair<string, string>("cer", "application/pkix-cert")); + mimeTypes.insert(pair<string, string>("cer", "application/x-x509-ca-cert")); + mimeTypes.insert(pair<string, string>("cha", "application/x-chat")); + mimeTypes.insert(pair<string, string>("chat", "application/x-chat")); + mimeTypes.insert(pair<string, string>("class", "application/java")); + mimeTypes.insert(pair<string, string>("com", "application/octet-stream")); + mimeTypes.insert(pair<string, string>("conf", "text/plain")); + mimeTypes.insert(pair<string, string>("cpio", "application/x-cpio")); + mimeTypes.insert(pair<string, string>("cpp", "text/x-c")); + mimeTypes.insert(pair<string, string>("cpt", "application/x-cpt")); + mimeTypes.insert(pair<string, string>("crl", "application/pkcs-crl")); + mimeTypes.insert(pair<string, string>("crt", "application/pkix-cert")); + mimeTypes.insert(pair<string, string>("csh", "application/x-csh")); + mimeTypes.insert(pair<string, string>("css", "text/css")); + mimeTypes.insert(pair<string, string>("cxx", "text/plain")); + mimeTypes.insert(pair<string, string>("dcr", "application/x-director")); + mimeTypes.insert(pair<string, string>("deepv", "application/x-deepv")); + mimeTypes.insert(pair<string, string>("def", "text/plain")); + mimeTypes.insert(pair<string, string>("der", "application/x-x509-ca-cert")); + mimeTypes.insert(pair<string, string>("dif", "video/x-dv")); + mimeTypes.insert(pair<string, string>("dir", "application/x-director")); + mimeTypes.insert(pair<string, string>("dl", "video/dl")); + mimeTypes.insert(pair<string, string>("divx", "video/x-msvideo")); + mimeTypes.insert(pair<string, string>("doc", "application/msword")); + mimeTypes.insert(pair<string, string>("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")); + mimeTypes.insert(pair<string, string>("dot", "application/msword")); + mimeTypes.insert(pair<string, string>("dp", "application/commonground")); + mimeTypes.insert(pair<string, string>("drw", "application/drafting")); + mimeTypes.insert(pair<string, string>("dump", "application/octet-stream")); + mimeTypes.insert(pair<string, string>("dv", "video/x-dv")); + mimeTypes.insert(pair<string, string>("dvi", "application/x-dvi")); + mimeTypes.insert(pair<string, string>("dwf", "model/vnd.dwf")); + mimeTypes.insert(pair<string, string>("dwg", "image/vnd.dwg")); + mimeTypes.insert(pair<string, string>("dxf", "image/vnd.dwg")); + mimeTypes.insert(pair<string, string>("dxr", "application/x-director")); + mimeTypes.insert(pair<string, string>("el", "text/x-script.elisp")); + mimeTypes.insert(pair<string, string>("elc", "application/x-elc")); + mimeTypes.insert(pair<string, string>("env", "application/x-envoy")); + mimeTypes.insert(pair<string, string>("eps", "application/postscript")); + mimeTypes.insert(pair<string, string>("es", "application/x-esrehber")); + mimeTypes.insert(pair<string, string>("etx", "text/x-setext")); + mimeTypes.insert(pair<string, string>("evy", "application/envoy")); + mimeTypes.insert(pair<string, string>("exe", "application/octet-stream")); + mimeTypes.insert(pair<string, string>("f", "text/x-fortran")); + mimeTypes.insert(pair<string, string>("f77", "text/x-fortran")); + mimeTypes.insert(pair<string, string>("f90", "text/x-fortran")); + mimeTypes.insert(pair<string, string>("fdf", "application/vnd.fdf")); + mimeTypes.insert(pair<string, string>("fif", "image/fif")); + mimeTypes.insert(pair<string, string>("flac", "audio/flac")); + mimeTypes.insert(pair<string, string>("fli", "video/fli")); + mimeTypes.insert(pair<string, string>("flo", "image/florian")); + mimeTypes.insert(pair<string, string>("flv", "video/x-flv")); + mimeTypes.insert(pair<string, string>("flx", "text/vnd.fmi.flexstor")); + mimeTypes.insert(pair<string, string>("fmf", "video/x-atomic3d-feature")); + mimeTypes.insert(pair<string, string>("for", "text/plain")); + mimeTypes.insert(pair<string, string>("for", "text/x-fortran")); + mimeTypes.insert(pair<string, string>("fpx", "image/vnd.fpx")); + mimeTypes.insert(pair<string, string>("frl", "application/freeloader")); + mimeTypes.insert(pair<string, string>("funk", "audio/make")); + mimeTypes.insert(pair<string, string>("g", "text/plain")); + mimeTypes.insert(pair<string, string>("g3", "image/g3fax")); + mimeTypes.insert(pair<string, string>("gif", "image/gif")); + mimeTypes.insert(pair<string, string>("gl", "video/x-gl")); + mimeTypes.insert(pair<string, string>("gsd", "audio/x-gsm")); + mimeTypes.insert(pair<string, string>("gsm", "audio/x-gsm")); + mimeTypes.insert(pair<string, string>("gsp", "application/x-gsp")); + mimeTypes.insert(pair<string, string>("gss", "application/x-gss")); + mimeTypes.insert(pair<string, string>("gtar", "application/x-gtar")); + mimeTypes.insert(pair<string, string>("gz", "application/x-compressed")); + mimeTypes.insert(pair<string, string>("gzip", "application/x-gzip")); + mimeTypes.insert(pair<string, string>("h", "text/plain")); + mimeTypes.insert(pair<string, string>("hdf", "application/x-hdf")); + mimeTypes.insert(pair<string, string>("help", "application/x-helpfile")); + mimeTypes.insert(pair<string, string>("hgl", "application/vnd.hp-hpgl")); + mimeTypes.insert(pair<string, string>("hh", "text/plain")); + mimeTypes.insert(pair<string, string>("hlb", "text/x-script")); + mimeTypes.insert(pair<string, string>("hlp", "application/hlp")); + mimeTypes.insert(pair<string, string>("hpg", "application/vnd.hp-hpgl")); + mimeTypes.insert(pair<string, string>("hpgl", "application/vnd.hp-hpgl")); + mimeTypes.insert(pair<string, string>("hqx", "application/binhex")); + mimeTypes.insert(pair<string, string>("hta", "application/hta")); + mimeTypes.insert(pair<string, string>("htc", "text/x-component")); + mimeTypes.insert(pair<string, string>("htm", "text/html")); + mimeTypes.insert(pair<string, string>("html", "text/html")); + mimeTypes.insert(pair<string, string>("htmls", "text/html")); + mimeTypes.insert(pair<string, string>("htt", "text/webviewhtml")); + mimeTypes.insert(pair<string, string>("htx", "text/html")); + mimeTypes.insert(pair<string, string>("ice", "x-conference/x-cooltalk")); + mimeTypes.insert(pair<string, string>("ico", "image/x-icon")); + mimeTypes.insert(pair<string, string>("idc", "text/plain")); + mimeTypes.insert(pair<string, string>("ief", "image/ief")); + mimeTypes.insert(pair<string, string>("iefs", "image/ief")); + mimeTypes.insert(pair<string, string>("iges", "application/iges")); + mimeTypes.insert(pair<string, string>("igs", "application/iges")); + mimeTypes.insert(pair<string, string>("igs", "model/iges")); + mimeTypes.insert(pair<string, string>("ima", "application/x-ima")); + mimeTypes.insert(pair<string, string>("imap", "application/x-httpd-imap")); + mimeTypes.insert(pair<string, string>("inf", "application/inf")); + mimeTypes.insert(pair<string, string>("ins", "application/x-internett-signup")); + mimeTypes.insert(pair<string, string>("ip", "application/x-ip2")); + mimeTypes.insert(pair<string, string>("isu", "video/x-isvideo")); + mimeTypes.insert(pair<string, string>("it", "audio/it")); + mimeTypes.insert(pair<string, string>("iv", "application/x-inventor")); + mimeTypes.insert(pair<string, string>("ivr", "i-world/i-vrml")); + mimeTypes.insert(pair<string, string>("ivy", "application/x-livescreen")); + mimeTypes.insert(pair<string, string>("jam", "audio/x-jam")); + mimeTypes.insert(pair<string, string>("jav", "text/x-java-source")); + mimeTypes.insert(pair<string, string>("java", "text/x-java-source")); + mimeTypes.insert(pair<string, string>("jcm", "application/x-java-commerce")); + mimeTypes.insert(pair<string, string>("jfif", "image/jpeg")); + mimeTypes.insert(pair<string, string>("jfif-tbnl", "image/jpeg")); + mimeTypes.insert(pair<string, string>("jpe", "image/jpeg")); + mimeTypes.insert(pair<string, string>("jpeg", "image/jpeg")); + mimeTypes.insert(pair<string, string>("jpg", "image/jpeg")); + mimeTypes.insert(pair<string, string>("jps", "image/x-jps")); + mimeTypes.insert(pair<string, string>("js", "application/javascript")); + mimeTypes.insert(pair<string, string>("json", "application/json")); + mimeTypes.insert(pair<string, string>("jut", "image/jutvision")); + mimeTypes.insert(pair<string, string>("kar", "music/x-karaoke")); + mimeTypes.insert(pair<string, string>("ksh", "application/x-ksh")); + mimeTypes.insert(pair<string, string>("ksh", "text/x-script.ksh")); + mimeTypes.insert(pair<string, string>("la", "audio/nspaudio")); + mimeTypes.insert(pair<string, string>("lam", "audio/x-liveaudio")); + mimeTypes.insert(pair<string, string>("latex", "application/x-latex")); + mimeTypes.insert(pair<string, string>("lha", "application/lha")); + mimeTypes.insert(pair<string, string>("lhx", "application/octet-stream")); + mimeTypes.insert(pair<string, string>("list", "text/plain")); + mimeTypes.insert(pair<string, string>("lma", "audio/nspaudio")); + mimeTypes.insert(pair<string, string>("log", "text/plain")); + mimeTypes.insert(pair<string, string>("lsp", "application/x-lisp")); + mimeTypes.insert(pair<string, string>("lst", "text/plain")); + mimeTypes.insert(pair<string, string>("lsx", "text/x-la-asf")); + mimeTypes.insert(pair<string, string>("ltx", "application/x-latex")); + mimeTypes.insert(pair<string, string>("lzh", "application/x-lzh")); + mimeTypes.insert(pair<string, string>("lzx", "application/lzx")); + mimeTypes.insert(pair<string, string>("m", "text/x-m")); + mimeTypes.insert(pair<string, string>("m1v", "video/mpeg")); + mimeTypes.insert(pair<string, string>("m2a", "audio/mpeg")); + mimeTypes.insert(pair<string, string>("m2v", "video/mpeg")); + mimeTypes.insert(pair<string, string>("m3u", "audio/x-mpequrl")); + mimeTypes.insert(pair<string, string>("man", "application/x-troff-man")); + mimeTypes.insert(pair<string, string>("map", "application/x-navimap")); + mimeTypes.insert(pair<string, string>("mar", "text/plain")); + mimeTypes.insert(pair<string, string>("mbd", "application/mbedlet")); + mimeTypes.insert(pair<string, string>("mc$", "application/x-magic-cap-package-1.0")); + mimeTypes.insert(pair<string, string>("mcd", "application/x-mathcad")); + mimeTypes.insert(pair<string, string>("mcf", "text/mcf")); + mimeTypes.insert(pair<string, string>("mcp", "application/netmc")); + mimeTypes.insert(pair<string, string>("me", "application/x-troff-me")); + mimeTypes.insert(pair<string, string>("mht", "message/rfc822")); + mimeTypes.insert(pair<string, string>("mhtml", "message/rfc822")); + mimeTypes.insert(pair<string, string>("mid", "audio/midi")); + mimeTypes.insert(pair<string, string>("midi", "audio/midi")); + mimeTypes.insert(pair<string, string>("mif", "application/x-mif")); + mimeTypes.insert(pair<string, string>("mime", "message/rfc822")); + mimeTypes.insert(pair<string, string>("mjf", "audio/x-vnd.audioexplosion.mjuicemediafile")); + mimeTypes.insert(pair<string, string>("mjpg", "video/x-motion-jpeg")); + mimeTypes.insert(pair<string, string>("mka", "audio/x-matroska")); + mimeTypes.insert(pair<string, string>("mkv", "video/x-matroska")); + mimeTypes.insert(pair<string, string>("mk3d", "video/x-matroska-3d")); + mimeTypes.insert(pair<string, string>("mm", "application/x-meme")); + mimeTypes.insert(pair<string, string>("mme", "application/base64")); + mimeTypes.insert(pair<string, string>("mod", "audio/mod")); + mimeTypes.insert(pair<string, string>("moov", "video/quicktime")); + mimeTypes.insert(pair<string, string>("mov", "video/quicktime")); + mimeTypes.insert(pair<string, string>("movie", "video/x-sgi-movie")); + mimeTypes.insert(pair<string, string>("mp2", "audio/mpeg")); + mimeTypes.insert(pair<string, string>("mp3", "audio/mpeg3")); + mimeTypes.insert(pair<string, string>("mp4", "video/mp4")); + mimeTypes.insert(pair<string, string>("mpa", "audio/mpeg")); + mimeTypes.insert(pair<string, string>("mpc", "application/x-project")); + mimeTypes.insert(pair<string, string>("mpe", "video/mpeg")); + mimeTypes.insert(pair<string, string>("mpeg", "video/mpeg")); + mimeTypes.insert(pair<string, string>("mpg", "video/mpeg")); + mimeTypes.insert(pair<string, string>("mpga", "audio/mpeg")); + mimeTypes.insert(pair<string, string>("mpp", "application/vnd.ms-project")); + mimeTypes.insert(pair<string, string>("mpt", "application/x-project")); + mimeTypes.insert(pair<string, string>("mpv", "application/x-project")); + mimeTypes.insert(pair<string, string>("mpx", "application/x-project")); + mimeTypes.insert(pair<string, string>("mrc", "application/marc")); + mimeTypes.insert(pair<string, string>("ms", "application/x-troff-ms")); + mimeTypes.insert(pair<string, string>("mv", "video/x-sgi-movie")); + mimeTypes.insert(pair<string, string>("my", "audio/make")); + mimeTypes.insert(pair<string, string>("mzz", "application/x-vnd.audioexplosion.mzz")); + mimeTypes.insert(pair<string, string>("nap", "image/naplps")); + mimeTypes.insert(pair<string, string>("naplps", "image/naplps")); + mimeTypes.insert(pair<string, string>("nc", "application/x-netcdf")); + mimeTypes.insert(pair<string, string>("ncm", "application/vnd.nokia.configuration-message")); + mimeTypes.insert(pair<string, string>("nfo", "text/xml")); + mimeTypes.insert(pair<string, string>("nif", "image/x-niff")); + mimeTypes.insert(pair<string, string>("niff", "image/x-niff")); + mimeTypes.insert(pair<string, string>("nix", "application/x-mix-transfer")); + mimeTypes.insert(pair<string, string>("nsc", "application/x-conference")); + mimeTypes.insert(pair<string, string>("nvd", "application/x-navidoc")); + mimeTypes.insert(pair<string, string>("o", "application/octet-stream")); + mimeTypes.insert(pair<string, string>("oda", "application/oda")); + mimeTypes.insert(pair<string, string>("ogg", "audio/ogg")); + mimeTypes.insert(pair<string, string>("omc", "application/x-omc")); + mimeTypes.insert(pair<string, string>("omcd", "application/x-omcdatamaker")); + mimeTypes.insert(pair<string, string>("omcr", "application/x-omcregerator")); + mimeTypes.insert(pair<string, string>("p", "text/x-pascal")); + mimeTypes.insert(pair<string, string>("p10", "application/pkcs10")); + mimeTypes.insert(pair<string, string>("p12", "application/pkcs-12")); + mimeTypes.insert(pair<string, string>("p7a", "application/x-pkcs7-signature")); + mimeTypes.insert(pair<string, string>("p7c", "application/pkcs7-mime")); + mimeTypes.insert(pair<string, string>("p7m", "application/pkcs7-mime")); + mimeTypes.insert(pair<string, string>("p7r", "application/x-pkcs7-certreqresp")); + mimeTypes.insert(pair<string, string>("p7s", "application/pkcs7-signature")); + mimeTypes.insert(pair<string, string>("part", "application/pro_eng")); + mimeTypes.insert(pair<string, string>("pas", "text/pascal")); + mimeTypes.insert(pair<string, string>("pbm", "image/x-portable-bitmap")); + mimeTypes.insert(pair<string, string>("pcl", "application/vnd.hp-pcl")); + mimeTypes.insert(pair<string, string>("pct", "image/x-pict")); + mimeTypes.insert(pair<string, string>("pcx", "image/x-pcx")); + mimeTypes.insert(pair<string, string>("pdb", "chemical/x-pdb")); + mimeTypes.insert(pair<string, string>("pdf", "application/pdf")); + mimeTypes.insert(pair<string, string>("pfunk", "audio/make.my.funk")); + mimeTypes.insert(pair<string, string>("pgm", "image/x-portable-greymap")); + mimeTypes.insert(pair<string, string>("pic", "image/pict")); + mimeTypes.insert(pair<string, string>("pict", "image/pict")); + mimeTypes.insert(pair<string, string>("pkg", "application/x-newton-compatible-pkg")); + mimeTypes.insert(pair<string, string>("pko", "application/vnd.ms-pki.pko")); + mimeTypes.insert(pair<string, string>("pl", "text/x-script.perl")); + mimeTypes.insert(pair<string, string>("plx", "application/x-pixclscript")); + mimeTypes.insert(pair<string, string>("pm", "text/x-script.perl-module")); + mimeTypes.insert(pair<string, string>("pm4", "application/x-pagemaker")); + mimeTypes.insert(pair<string, string>("pm5", "application/x-pagemaker")); + mimeTypes.insert(pair<string, string>("png", "image/png")); + mimeTypes.insert(pair<string, string>("pnm", "application/x-portable-anymap")); + mimeTypes.insert(pair<string, string>("pot", "application/vnd.ms-powerpoint")); + mimeTypes.insert(pair<string, string>("pov", "model/x-pov")); + mimeTypes.insert(pair<string, string>("ppa", "application/vnd.ms-powerpoint")); + mimeTypes.insert(pair<string, string>("ppm", "image/x-portable-pixmap")); + mimeTypes.insert(pair<string, string>("pps", "application/mspowerpoint")); + mimeTypes.insert(pair<string, string>("ppt", "application/mspowerpoint")); + mimeTypes.insert(pair<string, string>("ppz", "application/mspowerpoint")); + mimeTypes.insert(pair<string, string>("pre", "application/x-freelance")); + mimeTypes.insert(pair<string, string>("prt", "application/pro_eng")); + mimeTypes.insert(pair<string, string>("ps", "application/postscript")); + mimeTypes.insert(pair<string, string>("psd", "application/octet-stream")); + mimeTypes.insert(pair<string, string>("pvu", "paleovu/x-pv")); + mimeTypes.insert(pair<string, string>("pwz", "application/vnd.ms-powerpoint")); + mimeTypes.insert(pair<string, string>("py", "text/x-script.phyton")); + mimeTypes.insert(pair<string, string>("pyc", "applicaiton/x-bytecode.python")); + mimeTypes.insert(pair<string, string>("qcp", "audio/vnd.qcelp")); + mimeTypes.insert(pair<string, string>("qd3", "x-world/x-3dmf")); + mimeTypes.insert(pair<string, string>("qd3d", "x-world/x-3dmf")); + mimeTypes.insert(pair<string, string>("qif", "image/x-quicktime")); + mimeTypes.insert(pair<string, string>("qt", "video/quicktime")); + mimeTypes.insert(pair<string, string>("qtc", "video/x-qtc")); + mimeTypes.insert(pair<string, string>("qti", "image/x-quicktime")); + mimeTypes.insert(pair<string, string>("qtif", "image/x-quicktime")); + mimeTypes.insert(pair<string, string>("ra", "audio/x-realaudio")); + mimeTypes.insert(pair<string, string>("ram", "audio/x-pn-realaudio")); + mimeTypes.insert(pair<string, string>("ras", "image/cmu-raster")); + mimeTypes.insert(pair<string, string>("rast", "image/cmu-raster")); + mimeTypes.insert(pair<string, string>("rexx", "text/x-script.rexx")); + mimeTypes.insert(pair<string, string>("rf", "image/vnd.rn-realflash")); + mimeTypes.insert(pair<string, string>("rgb", "image/x-rgb")); + mimeTypes.insert(pair<string, string>("rm", "audio/x-pn-realaudio")); + mimeTypes.insert(pair<string, string>("rmi", "audio/mid")); + mimeTypes.insert(pair<string, string>("rmm", "audio/x-pn-realaudio")); + mimeTypes.insert(pair<string, string>("rmp", "audio/x-pn-realaudio")); + mimeTypes.insert(pair<string, string>("rng", "application/ringing-tones")); + mimeTypes.insert(pair<string, string>("rnx", "application/vnd.rn-realplayer")); + mimeTypes.insert(pair<string, string>("roff", "application/x-troff")); + mimeTypes.insert(pair<string, string>("rp", "image/vnd.rn-realpix")); + mimeTypes.insert(pair<string, string>("rpm", "audio/x-pn-realaudio-plugin")); + mimeTypes.insert(pair<string, string>("rt", "text/richtext")); + mimeTypes.insert(pair<string, string>("rtf", "text/richtext")); + mimeTypes.insert(pair<string, string>("rtx", "text/richtext")); + mimeTypes.insert(pair<string, string>("rv", "video/vnd.rn-realvideo")); + mimeTypes.insert(pair<string, string>("s", "text/x-asm")); + mimeTypes.insert(pair<string, string>("s3m", "audio/s3m")); + mimeTypes.insert(pair<string, string>("saveme", "application/octet-stream")); + mimeTypes.insert(pair<string, string>("sbk", "application/x-tbook")); + mimeTypes.insert(pair<string, string>("scm", "video/x-scm")); + mimeTypes.insert(pair<string, string>("sdml", "text/plain")); + mimeTypes.insert(pair<string, string>("sdp", "application/sdp")); + mimeTypes.insert(pair<string, string>("sdr", "application/sounder")); + mimeTypes.insert(pair<string, string>("sea", "application/sea")); + mimeTypes.insert(pair<string, string>("set", "application/set")); + mimeTypes.insert(pair<string, string>("sgm", "text/sgml")); + mimeTypes.insert(pair<string, string>("sgml", "text/sgml")); + mimeTypes.insert(pair<string, string>("sh", "text/x-script.sh")); + mimeTypes.insert(pair<string, string>("shar", "application/x-bsh")); + mimeTypes.insert(pair<string, string>("shtml", "text/html")); + mimeTypes.insert(pair<string, string>("shtml", "text/x-server-parsed-html")); + mimeTypes.insert(pair<string, string>("sid", "audio/x-psid")); + mimeTypes.insert(pair<string, string>("sit", "application/x-sit")); + mimeTypes.insert(pair<string, string>("sit", "application/x-stuffit")); + mimeTypes.insert(pair<string, string>("skd", "application/x-koan")); + mimeTypes.insert(pair<string, string>("skm", "application/x-koan")); + mimeTypes.insert(pair<string, string>("skp", "application/x-koan")); + mimeTypes.insert(pair<string, string>("skt", "application/x-koan")); + mimeTypes.insert(pair<string, string>("sl", "application/x-seelogo")); + mimeTypes.insert(pair<string, string>("smi", "application/smil")); + mimeTypes.insert(pair<string, string>("smil", "application/smil")); + mimeTypes.insert(pair<string, string>("snd", "audio/basic")); + mimeTypes.insert(pair<string, string>("sol", "application/solids")); + mimeTypes.insert(pair<string, string>("spc", "text/x-speech")); + mimeTypes.insert(pair<string, string>("spl", "application/futuresplash")); + mimeTypes.insert(pair<string, string>("spr", "application/x-sprite")); + mimeTypes.insert(pair<string, string>("sprite", "application/x-sprite")); + mimeTypes.insert(pair<string, string>("src", "application/x-wais-source")); + mimeTypes.insert(pair<string, string>("ssi", "text/x-server-parsed-html")); + mimeTypes.insert(pair<string, string>("ssm", "application/streamingmedia")); + mimeTypes.insert(pair<string, string>("sst", "application/vnd.ms-pki.certstore")); + mimeTypes.insert(pair<string, string>("step", "application/step")); + mimeTypes.insert(pair<string, string>("stl", "application/sla")); + mimeTypes.insert(pair<string, string>("stp", "application/step")); + mimeTypes.insert(pair<string, string>("sv4cpio", "application/x-sv4cpio")); + mimeTypes.insert(pair<string, string>("sv4crc", "application/x-sv4crc")); + mimeTypes.insert(pair<string, string>("svf", "image/vnd.dwg")); + mimeTypes.insert(pair<string, string>("svr", "application/x-world")); + mimeTypes.insert(pair<string, string>("swf", "application/x-shockwave-flash")); + mimeTypes.insert(pair<string, string>("t", "application/x-troff")); + mimeTypes.insert(pair<string, string>("talk", "text/x-speech")); + mimeTypes.insert(pair<string, string>("tar", "application/x-tar")); + mimeTypes.insert(pair<string, string>("tbk", "application/toolbook")); + mimeTypes.insert(pair<string, string>("tcl", "text/x-script.tcl")); + mimeTypes.insert(pair<string, string>("tcsh", "text/x-script.tcsh")); + mimeTypes.insert(pair<string, string>("tex", "application/x-tex")); + mimeTypes.insert(pair<string, string>("texi", "application/x-texinfo")); + mimeTypes.insert(pair<string, string>("texinfo", "application/x-texinfo")); + mimeTypes.insert(pair<string, string>("text", "text/plain")); + mimeTypes.insert(pair<string, string>("tgz", "application/x-compressed")); + mimeTypes.insert(pair<string, string>("tif", "image/tiff")); + mimeTypes.insert(pair<string, string>("tiff", "image/tiff")); + mimeTypes.insert(pair<string, string>("tr", "application/x-troff")); + mimeTypes.insert(pair<string, string>("ts", "video/mp2t")); + mimeTypes.insert(pair<string, string>("tsi", "audio/tsp-audio")); + mimeTypes.insert(pair<string, string>("tsp", "audio/tsplayer")); + mimeTypes.insert(pair<string, string>("tsv", "text/tab-separated-values")); + mimeTypes.insert(pair<string, string>("turbot", "image/florian")); + mimeTypes.insert(pair<string, string>("txt", "text/plain")); + mimeTypes.insert(pair<string, string>("uil", "text/x-uil")); + mimeTypes.insert(pair<string, string>("uni", "text/uri-list")); + mimeTypes.insert(pair<string, string>("unis", "text/uri-list")); + mimeTypes.insert(pair<string, string>("unv", "application/i-deas")); + mimeTypes.insert(pair<string, string>("uri", "text/uri-list")); + mimeTypes.insert(pair<string, string>("uris", "text/uri-list")); + mimeTypes.insert(pair<string, string>("ustar", "application/x-ustar")); + mimeTypes.insert(pair<string, string>("uu", "text/x-uuencode")); + mimeTypes.insert(pair<string, string>("uue", "text/x-uuencode")); + mimeTypes.insert(pair<string, string>("vcd", "application/x-cdlink")); + mimeTypes.insert(pair<string, string>("vcs", "text/x-vcalendar")); + mimeTypes.insert(pair<string, string>("vda", "application/vda")); + mimeTypes.insert(pair<string, string>("vdo", "video/vdo")); + mimeTypes.insert(pair<string, string>("vew", "application/groupwise")); + mimeTypes.insert(pair<string, string>("viv", "video/vivo")); + mimeTypes.insert(pair<string, string>("vivo", "video/vivo")); + mimeTypes.insert(pair<string, string>("vmd", "application/vocaltec-media-desc")); + mimeTypes.insert(pair<string, string>("vmf", "application/vocaltec-media-file")); + mimeTypes.insert(pair<string, string>("voc", "audio/voc")); + mimeTypes.insert(pair<string, string>("vos", "video/vosaic")); + mimeTypes.insert(pair<string, string>("vox", "audio/voxware")); + mimeTypes.insert(pair<string, string>("vqe", "audio/x-twinvq-plugin")); + mimeTypes.insert(pair<string, string>("vqf", "audio/x-twinvq")); + mimeTypes.insert(pair<string, string>("vql", "audio/x-twinvq-plugin")); + mimeTypes.insert(pair<string, string>("vrml", "application/x-vrml")); + mimeTypes.insert(pair<string, string>("vrt", "x-world/x-vrt")); + mimeTypes.insert(pair<string, string>("vsd", "application/x-visio")); + mimeTypes.insert(pair<string, string>("vst", "application/x-visio")); + mimeTypes.insert(pair<string, string>("vsw", "application/x-visio")); + mimeTypes.insert(pair<string, string>("w60", "application/wordperfect6.0")); + mimeTypes.insert(pair<string, string>("w61", "application/wordperfect6.1")); + mimeTypes.insert(pair<string, string>("w6w", "application/msword")); + mimeTypes.insert(pair<string, string>("wav", "audio/wav")); + mimeTypes.insert(pair<string, string>("wb1", "application/x-qpro")); + mimeTypes.insert(pair<string, string>("wbmp", "image/vnd.wap.wbmp")); + mimeTypes.insert(pair<string, string>("web", "application/vnd.xara")); + mimeTypes.insert(pair<string, string>("wiz", "application/msword")); + mimeTypes.insert(pair<string, string>("wk1", "application/x-123")); + mimeTypes.insert(pair<string, string>("wma", "audio/x-ms-wma")); + mimeTypes.insert(pair<string, string>("wmf", "windows/metafile")); + mimeTypes.insert(pair<string, string>("wml", "text/vnd.wap.wml")); + mimeTypes.insert(pair<string, string>("wmlc", "application/vnd.wap.wmlc")); + mimeTypes.insert(pair<string, string>("wmls", "text/vnd.wap.wmlscript")); + mimeTypes.insert(pair<string, string>("wmlsc", "application/vnd.wap.wmlscriptc")); + mimeTypes.insert(pair<string, string>("wmv", "video/x-ms-wmv")); + mimeTypes.insert(pair<string, string>("word", "application/msword")); + mimeTypes.insert(pair<string, string>("wp", "application/wordperfect")); + mimeTypes.insert(pair<string, string>("wp5", "application/wordperfect")); + mimeTypes.insert(pair<string, string>("wp6", "application/wordperfect")); + mimeTypes.insert(pair<string, string>("wpd", "application/wordperfect")); + mimeTypes.insert(pair<string, string>("wq1", "application/x-lotus")); + mimeTypes.insert(pair<string, string>("wri", "application/mswrite")); + mimeTypes.insert(pair<string, string>("wrl", "model/vrml")); + mimeTypes.insert(pair<string, string>("wrz", "model/vrml")); + mimeTypes.insert(pair<string, string>("wsc", "text/scriplet")); + mimeTypes.insert(pair<string, string>("wsrc", "application/x-wais-source")); + mimeTypes.insert(pair<string, string>("wtk", "application/x-wintalk")); + mimeTypes.insert(pair<string, string>("xbm", "image/xbm")); + mimeTypes.insert(pair<string, string>("xdr", "video/x-amt-demorun")); + mimeTypes.insert(pair<string, string>("xgz", "xgl/drawing")); + mimeTypes.insert(pair<string, string>("xif", "image/vnd.xiff")); + mimeTypes.insert(pair<string, string>("xl", "application/excel")); + mimeTypes.insert(pair<string, string>("xla", "application/excel")); + mimeTypes.insert(pair<string, string>("xlb", "application/excel")); + mimeTypes.insert(pair<string, string>("xlc", "application/excel")); + mimeTypes.insert(pair<string, string>("xld", "application/excel")); + mimeTypes.insert(pair<string, string>("xlk", "application/excel")); + mimeTypes.insert(pair<string, string>("xll", "application/excel")); + mimeTypes.insert(pair<string, string>("xlm", "application/excel")); + mimeTypes.insert(pair<string, string>("xls", "application/excel")); + mimeTypes.insert(pair<string, string>("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")); + mimeTypes.insert(pair<string, string>("xlt", "application/excel")); + mimeTypes.insert(pair<string, string>("xlv", "application/excel")); + mimeTypes.insert(pair<string, string>("xlw", "application/excel")); + mimeTypes.insert(pair<string, string>("xm", "audio/xm")); + mimeTypes.insert(pair<string, string>("xml", "text/xml")); + mimeTypes.insert(pair<string, string>("xmz", "xgl/movie")); + mimeTypes.insert(pair<string, string>("xpix", "application/x-vnd.ls-xpix")); + mimeTypes.insert(pair<string, string>("xpm", "image/xpm")); + mimeTypes.insert(pair<string, string>("x-png", "image/png")); + mimeTypes.insert(pair<string, string>("xsr", "video/x-amt-showrun")); + mimeTypes.insert(pair<string, string>("xvid", "video/x-msvideo")); + mimeTypes.insert(pair<string, string>("xwd", "image/x-xwd")); + mimeTypes.insert(pair<string, string>("xyz", "chemical/x-pdb")); + mimeTypes.insert(pair<string, string>("z", "application/x-compressed")); + mimeTypes.insert(pair<string, string>("zip", "application/zip")); + mimeTypes.insert(pair<string, string>("zoo", "application/octet-stream")); + mimeTypes.insert(pair<string, string>("zsh", "text/x-script.zsh")); + + return mimeTypes; +} + +map<string, string> CMime::m_mimetypes = fillMimeTypes(); + +string CMime::GetMimeType(const string &extension) +{ + if (extension.empty()) + return ""; + + string ext = extension; + size_t posNotPoint = ext.find_first_not_of('.'); + if (posNotPoint != string::npos && posNotPoint > 0) + ext = extension.substr(posNotPoint); + transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + + map<string, string>::const_iterator it = m_mimetypes.find(ext); + if (it != m_mimetypes.end()) + return it->second; + + return ""; +} + +string CMime::GetMimeType(const CFileItem &item) +{ + CStdString path = item.GetPath(); + if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->GetPath().empty()) + path = item.GetVideoInfoTag()->GetPath(); + else if (item.HasMusicInfoTag() && !item.GetMusicInfoTag()->GetURL().empty()) + path = item.GetMusicInfoTag()->GetURL(); + + return GetMimeType(URIUtils::GetExtension(path)); +} + +string CMime::GetMimeType(const CURL &url, bool lookup) +{ + + std::string strMimeType; + + if( url.IsProtocol("shout") || url.IsProtocol("http") || url.IsProtocol("https")) + { + // If lookup is false, bail out early to leave mime type empty + if (!lookup) + return strMimeType; + + CStdString strmime; + XFILE::CCurlFile::GetMimeType(url, strmime); + + // try to get mime-type again but with an NSPlayer User-Agent + // in order for server to provide correct mime-type. Allows us + // to properly detect an MMS stream + if (StringUtils::StartsWithNoCase(strmime, "video/x-ms-")) + XFILE::CCurlFile::GetMimeType(url, strmime, "NSPlayer/11.00.6001.7000"); + + // make sure there are no options set in mime-type + // mime-type can look like "video/x-ms-asf ; charset=utf8" + size_t i = strmime.find(';'); + if(i != std::string::npos) + strmime.erase(i, strmime.length() - i); + StringUtils::Trim(strmime); + strMimeType = strmime; + } + else + strMimeType = GetMimeType(url.GetFileType()); + + // if it's still empty set to an unknown type + if (strMimeType.empty()) + strMimeType = "application/octet-stream"; + + return strMimeType; +} + +CMime::EFileType CMime::GetFileTypeFromMime(const std::string& mimeType) +{ + // based on http://mimesniff.spec.whatwg.org/ + + std::string type, subtype; + if (!parseMimeType(mimeType, type, subtype)) + return FileTypeUnknown; + + if (type == "application") + { + if (subtype == "zip") + return FileTypeZip; + if (subtype == "x-gzip") + return FileTypeGZip; + if (subtype == "x-rar-compressed") + return FileTypeRar; + + if (subtype == "xml") + return FileTypeXml; + } + else if (type == "text") + { + if (subtype == "xml") + return FileTypeXml; + if (subtype == "html") + return FileTypeHtml; + if (subtype == "plain") + return FileTypePlainText; + } + else if (type == "image") + { + if (subtype == "bmp") + return FileTypeBmp; + if (subtype == "gif") + return FileTypeGif; + if (subtype == "png") + return FileTypePng; + if (subtype == "jpeg" || subtype == "pjpeg") + return FileTypeJpeg; + } + + if (StringUtils::EndsWith(subtype, "+zip")) + return FileTypeZip; + if (StringUtils::EndsWith(subtype, "+xml")) + return FileTypeXml; + + return FileTypeUnknown; +} + +CMime::EFileType CMime::GetFileTypeFromContent(const std::string& fileContent) +{ + // based on http://mimesniff.spec.whatwg.org/#matching-a-mime-type-pattern + + const size_t len = fileContent.length(); + if (len < 2) + return FileTypeUnknown; + + const unsigned char* const b = (const unsigned char*)fileContent.c_str(); + + // TODO: add detection for text types + + // check image types + if (b[0] == 'B' && b[1] == 'M') + return FileTypeBmp; + if (len >= 6 && b[0] == 'G' && b[1] == 'I' && b[2] == 'F' && b[3] == '8' && (b[4] == '7' || b[4] == '9') && b[5] == 'a') + return FileTypeGif; + if (len >= 8 && b[0] == 0x89 && b[1] == 'P' && b[2] == 'N' && b[3] == 'G' && b[4] == 0x0D && b[5] == 0x0A && b[6] == 0x1A && b[7] == 0x0A) + return FileTypePng; + if (len >= 3 && b[0] == 0xFF && b[1] == 0xD8 && b[2] == 0xFF) + return FileTypeJpeg; + + // check archive types + if (len >= 3 && b[0] == 0x1F && b[1] == 0x8B && b[2] == 0x08) + return FileTypeGZip; + if (len >= 4 && b[0] == 'P' && b[1] == 'K' && b[2] == 0x03 && b[3] == 0x04) + return FileTypeZip; + if (len >= 7 && b[0] == 'R' && b[1] == 'a' && b[2] == 'r' && b[3] == ' ' && b[4] == 0x1A && b[5] == 0x07 && b[6] == 0x00) + return FileTypeRar; + + // TODO: add detection for other types if required + + return FileTypeUnknown; +} + +bool CMime::parseMimeType(const std::string& mimeType, std::string& type, std::string& subtype) +{ + static const char* const whitespaceChars = "\x09\x0A\x0C\x0D\x20"; // tab, LF, FF, CR and space + + type.clear(); + subtype.clear(); + + const size_t slashPos = mimeType.find('/'); + if (slashPos == std::string::npos) + return false; + + type.assign(mimeType, 0, slashPos); + subtype.assign(mimeType, slashPos + 1, std::string::npos); + + const size_t semicolonPos = subtype.find(';'); + if (semicolonPos != std::string::npos) + subtype.erase(semicolonPos); + + StringUtils::Trim(type, whitespaceChars); + StringUtils::Trim(subtype, whitespaceChars); + + if (type.empty() || subtype.empty()) + { + type.clear(); + subtype.clear(); + return false; + } + + StringUtils::ToLower(type); + StringUtils::ToLower(subtype); + + return true; +} diff --git a/src/utils/Mime.h b/src/utils/Mime.h new file mode 100644 index 0000000000..330b01f55e --- /dev/null +++ b/src/utils/Mime.h @@ -0,0 +1,56 @@ +#pragma once +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <map> + +class CURL; + +class CFileItem; + +class CMime +{ +public: + static std::string GetMimeType(const std::string &extension); + static std::string GetMimeType(const CFileItem &item); + static std::string GetMimeType(const CURL &url, bool lookup = true); + + enum EFileType + { + FileTypeUnknown = 0, + FileTypeHtml, + FileTypeXml, + FileTypePlainText, + FileTypeZip, + FileTypeGZip, + FileTypeRar, + FileTypeBmp, + FileTypeGif, + FileTypePng, + FileTypeJpeg, + }; + static EFileType GetFileTypeFromMime(const std::string& mimeType); + static EFileType GetFileTypeFromContent(const std::string& fileContent); + static bool parseMimeType(const std::string& mimeType, std::string& type, std::string& subtype); + +private: + static std::map<std::string, std::string> m_mimetypes; +}; diff --git a/src/utils/Observer.cpp b/src/utils/Observer.cpp new file mode 100644 index 0000000000..9d3ef14a99 --- /dev/null +++ b/src/utils/Observer.cpp @@ -0,0 +1,155 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Application.h" +#include "Observer.h" +#include "threads/SingleLock.h" +#include "utils/JobManager.h" + +using namespace std; + +Observer::~Observer(void) +{ + StopObserving(); +} + +void Observer::StopObserving(void) +{ + CSingleLock lock(m_obsCritSection); + std::vector<Observable *> observables = m_observables; + for (unsigned int iObsPtr = 0; iObsPtr < observables.size(); iObsPtr++) + observables.at(iObsPtr)->UnregisterObserver(this); +} + +bool Observer::IsObserving(const Observable &obs) const +{ + CSingleLock lock(m_obsCritSection); + return find(m_observables.begin(), m_observables.end(), &obs) != m_observables.end(); +} + +void Observer::RegisterObservable(Observable *obs) +{ + CSingleLock lock(m_obsCritSection); + if (!IsObserving(*obs)) + m_observables.push_back(obs); +} + +void Observer::UnregisterObservable(Observable *obs) +{ + CSingleLock lock(m_obsCritSection); + vector<Observable *>::iterator it = find(m_observables.begin(), m_observables.end(), obs); + if (it != m_observables.end()) + m_observables.erase(it); +} + +Observable::Observable() : + m_bObservableChanged(false) +{ +} + +Observable::~Observable() +{ + StopObserver(); +} + +Observable &Observable::operator=(const Observable &observable) +{ + CSingleLock lock(m_obsCritSection); + + m_bObservableChanged = observable.m_bObservableChanged; + m_observers.clear(); + for (unsigned int iObsPtr = 0; iObsPtr < observable.m_observers.size(); iObsPtr++) + m_observers.push_back(observable.m_observers.at(iObsPtr)); + + return *this; +} + +void Observable::StopObserver(void) +{ + CSingleLock lock(m_obsCritSection); + std::vector<Observer *> observers = m_observers; + for (unsigned int iObsPtr = 0; iObsPtr < observers.size(); iObsPtr++) + observers.at(iObsPtr)->UnregisterObservable(this); +} + +bool Observable::IsObserving(const Observer &obs) const +{ + CSingleLock lock(m_obsCritSection); + return find(m_observers.begin(), m_observers.end(), &obs) != m_observers.end(); +} + +void Observable::RegisterObserver(Observer *obs) +{ + CSingleLock lock(m_obsCritSection); + if (!IsObserving(*obs)) + { + m_observers.push_back(obs); + obs->RegisterObservable(this); + } +} + +void Observable::UnregisterObserver(Observer *obs) +{ + CSingleLock lock(m_obsCritSection); + vector<Observer *>::iterator it = find(m_observers.begin(), m_observers.end(), obs); + if (it != m_observers.end()) + { + obs->UnregisterObservable(this); + m_observers.erase(it); + } +} + +void Observable::NotifyObservers(const ObservableMessage message /* = ObservableMessageNone */) +{ + bool bNotify(false); + { + CSingleLock lock(m_obsCritSection); + if (m_bObservableChanged && !g_application.m_bStop) + bNotify = true; + m_bObservableChanged = false; + } + + if (bNotify) + SendMessage(*this, message); +} + +void Observable::SetChanged(bool SetTo) +{ + CSingleLock lock(m_obsCritSection); + m_bObservableChanged = SetTo; +} + +void Observable::SendMessage(const Observable& obs, const ObservableMessage message) +{ + CSingleLock lock(obs.m_obsCritSection); + for(int ptr = obs.m_observers.size() - 1; ptr >= 0; ptr--) + { + if (ptr < (int)obs.m_observers.size()) + { + Observer *observer = obs.m_observers.at(ptr); + if (observer) + { + lock.Leave(); + observer->Notify(obs, message); + lock.Enter(); + } + } + } +} diff --git a/src/utils/Observer.h b/src/utils/Observer.h new file mode 100644 index 0000000000..3ac032a056 --- /dev/null +++ b/src/utils/Observer.h @@ -0,0 +1,146 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "threads/CriticalSection.h" +#include <vector> + +class Observable; +class ObservableMessageJob; + +typedef enum +{ + ObservableMessageNone, + ObservableMessageCurrentItem, + ObservableMessageAddons, + ObservableMessageEpg, + ObservableMessageEpgContainer, + ObservableMessageEpgActiveItem, + ObservableMessageChannelGroup, + ObservableMessageChannelGroupReset, + ObservableMessageTimers, + ObservableMessageTimersReset, + ObservableMessageRecordings, + ObservableMessagePeripheralsChanged, + ObservableMessageManagerStateChanged +} ObservableMessage; + +class Observer +{ + friend class Observable; + +public: + Observer(void) {}; + virtual ~Observer(void); + + /*! + * @brief Remove this observer from all observables. + */ + virtual void StopObserving(void); + + /*! + * @brief Check whether this observer is observing an observable. + * @param obs The observable to check. + * @return True if this observer is observing the given observable, false otherwise. + */ + virtual bool IsObserving(const Observable &obs) const; + + /*! + * @brief Process a message from an observable. + * @param obs The observable that sends the message. + * @param msg The message. + */ + virtual void Notify(const Observable &obs, const ObservableMessage msg) = 0; + +protected: + /*! + * @brief Callback to register an observable. + * @param obs The observable to register. + */ + virtual void RegisterObservable(Observable *obs); + + /*! + * @brief Callback to unregister an observable. + * @param obs The observable to unregister. + */ + virtual void UnregisterObservable(Observable *obs); + + std::vector<Observable *> m_observables; /*!< all observables that are watched */ + CCriticalSection m_obsCritSection; /*!< mutex */ +}; + +class Observable +{ + friend class ObservableMessageJob; + +public: + Observable(); + virtual ~Observable(); + virtual Observable &operator=(const Observable &observable); + + /*! + * @brief Remove this observable from all observers. + */ + virtual void StopObserver(void); + + /*! + * @brief Register an observer. + * @param obs The observer to register. + */ + virtual void RegisterObserver(Observer *obs); + + /*! + * @brief Unregister an observer. + * @param obs The observer to unregister. + */ + virtual void UnregisterObserver(Observer *obs); + + /*! + * @brief Send a message to all observers when m_bObservableChanged is true. + * @param message The message to send. + */ + virtual void NotifyObservers(const ObservableMessage message = ObservableMessageNone); + + /*! + * @brief Mark an observable changed. + * @param bSetTo True to mark the observable changed, false to mark it as unchanged. + */ + virtual void SetChanged(bool bSetTo = true); + + /*! + * @brief Check whether this observable is being observed by an observer. + * @param obs The observer to check. + * @return True if this observable is being observed by the given observer, false otherwise. + */ + virtual bool IsObserving(const Observer &obs) const; + +protected: + /*! + * @brief Send a message to all observer when m_bObservableChanged is true. + * @param obs The observer that sends the message. + * @param message The message to send. + */ + static void SendMessage(const Observable& obs, const ObservableMessage message); + + bool m_bObservableChanged; /*!< true when the observable is marked as changed, false otherwise */ + std::vector<Observer *> m_observers; /*!< all observers */ + CCriticalSection m_obsCritSection; /*!< mutex */ +}; diff --git a/src/utils/POUtils.cpp b/src/utils/POUtils.cpp new file mode 100644 index 0000000000..ce9ea3b516 --- /dev/null +++ b/src/utils/POUtils.cpp @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/POUtils.h" +#include "URL.h" +#include "filesystem/File.h" +#include "utils/log.h" +#include <stdlib.h> + +CPODocument::CPODocument() +{ + m_CursorPos = 0; + m_nextEntryPos = 0; + m_POfilelength = 0; + m_Entry.msgStrPlural.clear(); + m_Entry.msgStrPlural.resize(1); +} + +CPODocument::~CPODocument() {} + +bool CPODocument::LoadFile(const std::string &pofilename) +{ + CURL poFileUrl(pofilename); + if (!XFILE::CFile::Exists(poFileUrl)) + return false; + + XFILE::CFile file; + XFILE::auto_buffer buf; + if (file.LoadFile(poFileUrl, buf) < 18) // at least a size of a minimalistic header + { + CLog::Log(LOGERROR, "%s: can't load file \"%s\" or file is too small", __FUNCTION__, pofilename.c_str()); + return false; + } + + m_strBuffer = '\n'; + m_strBuffer.append(buf.get(), buf.size()); + buf.clear(); + + ConvertLineEnds(pofilename); + + // we make sure, to have an LF at the end of buffer + if (*m_strBuffer.rbegin() != '\n') + { + m_strBuffer += "\n"; + } + + m_POfilelength = m_strBuffer.size(); + + if (GetNextEntry() && m_Entry.Type == MSGID_FOUND) + return true; + + CLog::Log(LOGERROR, "POParser: unable to read PO file header from file: %s", pofilename.c_str()); + return false; +} + +bool CPODocument::GetNextEntry() +{ + do + { + // if we don't find LFLF, we reached the end of the buffer and the last entry to check + // we indicate this with setting m_nextEntryPos to the end of the buffer + if ((m_nextEntryPos = m_strBuffer.find("\n\n", m_CursorPos)) == std::string::npos) + m_nextEntryPos = m_POfilelength-1; + + // now we read the actual entry into a temp string for further processing + m_Entry.Content.assign(m_strBuffer, m_CursorPos, m_nextEntryPos - m_CursorPos +1); + m_CursorPos = m_nextEntryPos+1; // jump cursor to the second LF character + + if (FindLineStart ("\nmsgid ", m_Entry.msgID.Pos)) + { + if (FindLineStart ("\nmsgctxt \"#", m_Entry.xIDPos) && ParseNumID()) + { + m_Entry.Type = ID_FOUND; // we found an entry with a valid numeric id + return true; + } + + size_t plurPos; + if (FindLineStart ("\nmsgid_plural ", plurPos)) + { + m_Entry.Type = MSGID_PLURAL_FOUND; // we found a pluralized entry + return true; + } + + m_Entry.Type = MSGID_FOUND; // we found a normal entry, with no numeric id + return true; + } + } + while (m_nextEntryPos != m_POfilelength-1); + // we reached the end of buffer AND we have not found a valid entry + + return false; +} + +void CPODocument::ParseEntry(bool bisSourceLang) +{ + if (bisSourceLang) + { + if (m_Entry.Type == ID_FOUND) + GetString(m_Entry.msgID); + else + m_Entry.msgID.Str.clear(); + return; + } + + if (m_Entry.Type != ID_FOUND) + { + GetString(m_Entry.msgID); + if (FindLineStart ("\nmsgctxt ", m_Entry.msgCtxt.Pos)) + GetString(m_Entry.msgCtxt); + else + m_Entry.msgCtxt.Str.clear(); + } + + if (m_Entry.Type != MSGID_PLURAL_FOUND) + { + if (FindLineStart ("\nmsgstr ", m_Entry.msgStr.Pos)) + { + GetString(m_Entry.msgStr); + GetString(m_Entry.msgID); + } + else + { + CLog::Log(LOGERROR, "POParser: missing msgstr line in entry. Failed entry: %s", + m_Entry.Content.c_str()); + m_Entry.msgStr.Str.clear(); + } + return; + } + + // We found a plural form entry. We read it into a vector of CStrEntry types + m_Entry.msgStrPlural.clear(); + std::string strPattern = "\nmsgstr[0] "; + CStrEntry strEntry; + + for (int n=0; n<7 ; n++) + { + strPattern[8] = static_cast<char>(n+'0'); + if (FindLineStart (strPattern, strEntry.Pos)) + { + GetString(strEntry); + if (strEntry.Str.empty()) + break; + m_Entry.msgStrPlural.push_back(strEntry); + } + else + break; + } + + if (m_Entry.msgStrPlural.size() == 0) + { + CLog::Log(LOGERROR, "POParser: msgstr[] plural lines have zero valid strings. " + "Failed entry: %s", m_Entry.Content.c_str()); + m_Entry.msgStrPlural.resize(1); // Put 1 element with an empty string into the vector + } + + return; +} + +const std::string& CPODocument::GetPlurMsgstr(size_t plural) const +{ + if (m_Entry.msgStrPlural.size() < plural+1) + { + CLog::Log(LOGERROR, "POParser: msgstr[%i] plural field requested, but not found in PO file. " + "Failed entry: %s", static_cast<int>(plural), m_Entry.Content.c_str()); + plural = m_Entry.msgStrPlural.size()-1; + } + return m_Entry.msgStrPlural[plural].Str; +} + +std::string CPODocument::UnescapeString(const std::string &strInput) +{ + std::string strOutput; + if (strInput.empty()) + return strOutput; + + char oescchar; + strOutput.reserve(strInput.size()); + std::string::const_iterator it = strInput.begin(); + while (it < strInput.end()) + { + oescchar = *it++; + if (oescchar == '\\') + { + if (it == strInput.end()) + { + CLog::Log(LOGERROR, + "POParser: warning, unhandled escape character " + "at line-end. Problematic entry: %s", + m_Entry.Content.c_str()); + break; + } + switch (*it++) + { + case 'a': oescchar = '\a'; break; + case 'b': oescchar = '\b'; break; + case 'v': oescchar = '\v'; break; + case 'n': oescchar = '\n'; break; + case 't': oescchar = '\t'; break; + case 'r': oescchar = '\r'; break; + case '"': oescchar = '"' ; break; + case '0': oescchar = '\0'; break; + case 'f': oescchar = '\f'; break; + case '?': oescchar = '\?'; break; + case '\'': oescchar = '\''; break; + case '\\': oescchar = '\\'; break; + + default: + { + CLog::Log(LOGERROR, + "POParser: warning, unhandled escape character. Problematic entry: %s", + m_Entry.Content.c_str()); + continue; + } + } + } + strOutput.push_back(oescchar); + } + return strOutput; +} + +bool CPODocument::FindLineStart(const std::string &strToFind, size_t &FoundPos) +{ + + FoundPos = m_Entry.Content.find(strToFind); + + if (FoundPos == std::string::npos || FoundPos + strToFind.size() + 2 > m_Entry.Content.size()) + return false; // if we don't find the string or if we don't have at least one char after it + + FoundPos += strToFind.size(); // to set the pos marker to the exact start of the real data + return true; +} + +bool CPODocument::ParseNumID() +{ + if (isdigit(m_Entry.Content.at(m_Entry.xIDPos))) // verify if the first char is digit + { + // we check for the numeric id for the fist 10 chars (uint32) + m_Entry.xID = strtol(&m_Entry.Content[m_Entry.xIDPos], NULL, 10); + return true; + } + + CLog::Log(LOGERROR, "POParser: found numeric id descriptor, but no valid id can be read, " + "entry was handled as normal msgid entry"); + CLog::Log(LOGERROR, "POParser: The problematic entry: %s", + m_Entry.Content.c_str()); + return false; +} + +void CPODocument::GetString(CStrEntry &strEntry) +{ + size_t nextLFPos; + size_t startPos = strEntry.Pos; + strEntry.Str.clear(); + + while (startPos < m_Entry.Content.size()) + { + nextLFPos = m_Entry.Content.find("\n", startPos); + if (nextLFPos == std::string::npos) + nextLFPos = m_Entry.Content.size(); + + // check syntax, if it really is a valid quoted string line + if (nextLFPos-startPos < 2 || m_Entry.Content[startPos] != '\"' || + m_Entry.Content[nextLFPos-1] != '\"') + break; + + strEntry.Str.append(m_Entry.Content, startPos+1, nextLFPos-2-startPos); + startPos = nextLFPos+1; + } + + strEntry.Str = UnescapeString(strEntry.Str); +} + +void CPODocument::ConvertLineEnds(const std::string &filename) +{ + size_t foundPos = m_strBuffer.find_first_of("\r"); + if (foundPos == std::string::npos) + return; // We have only Linux style line endings in the file, nothing to do + + if (foundPos+1 >= m_strBuffer.size() || m_strBuffer[foundPos+1] != '\n') + CLog::Log(LOGDEBUG, "POParser: PO file has Mac Style Line Endings. " + "Converted in memory to Linux LF for file: %s", filename.c_str()); + else + CLog::Log(LOGDEBUG, "POParser: PO file has Win Style Line Endings. " + "Converted in memory to Linux LF for file: %s", filename.c_str()); + + std::string strTemp; + strTemp.reserve(m_strBuffer.size()); + for (std::string::const_iterator it = m_strBuffer.begin(); it < m_strBuffer.end(); ++it) + { + if (*it == '\r') + { + if (it+1 == m_strBuffer.end() || *(it+1) != '\n') + strTemp.push_back('\n'); // convert Mac style line ending and continue + continue; // we have Win style line ending so we exclude this CR now + } + strTemp.push_back(*it); + } + m_strBuffer.swap(strTemp); + m_POfilelength = m_strBuffer.size(); +} diff --git a/src/utils/POUtils.h b/src/utils/POUtils.h new file mode 100644 index 0000000000..7eb2cc30a8 --- /dev/null +++ b/src/utils/POUtils.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <vector> +#include <stdint.h> + +enum +{ + ID_FOUND = 0, // We have an entry with a numeric (previously XML) identification number. + MSGID_FOUND = 1, // We have a classic gettext entry with textual msgid. No numeric ID. + MSGID_PLURAL_FOUND = 2 // We have a classic gettext entry with textual msgid in plural form. +}; + +enum +{ + ISSOURCELANG=true +}; + +// Struct to hold current position and text of the string field in the main PO entry. +struct CStrEntry +{ + size_t Pos; + std::string Str; +}; + +// Struct to collect all important data of the current processed entry. +struct CPOEntry +{ + int Type; + uint32_t xID; + size_t xIDPos; + std::string Content; + CStrEntry msgCtxt; + CStrEntry msgID; + CStrEntry msgStr; + std::vector<CStrEntry> msgStrPlural; +}; + +class CPODocument +{ +public: + CPODocument(); + ~CPODocument(); + + /*! \brief Tries to load a PO file into a temporary memory buffer. + * It also tries to parse the header of the PO file. + \param pofilename filename of the PO file to load. + \return true if the load was successful, unless return false + */ + bool LoadFile(const std::string &pofilename); + + /*! \brief Fast jumps to the next entry in PO buffer. + * Finds next entry started with "#: id:" or msgctx or msgid. + * to be as fast as possible this does not even get the id number + * just the type of the entry found. GetEntryID() has to be called + * for getting the id. After that ParseEntry() needs a call for + * actually getting the msg strings. The reason for this is to + * have calls and checks as fast as possible generally and specially + * for parsing weather tokens and to parse only the needed strings from + * the fallback language (missing from the gui language translation) + \return true if there was an entry found, false if reached the end of buffer + */ + bool GetNextEntry(); + + /*! \brief Gets the type of entry found with GetNextEntry. + \return the type of entry: ID_FOUND || MSGID_FOUND || MSGID_PLURAL_FOUND + */ + int GetEntryType() const {return m_Entry.Type;} + + /*! \brief Parses the numeric ID from current entry. + * This function can only be called right after GetNextEntry() + * to make sure that we have a valid entry detected. + \return parsed ID number + */ + uint32_t GetEntryID() const {return m_Entry.xID;} + + /*! \brief Parses current entry. + * Reads msgid, msgstr, msgstr[x], msgctxt strings. + * Note that this function also back-converts the c++ style escape sequences. + * The function only parses the needed strings, considering if it is a source language file. + \param bisSourceLang if we parse a source English file. + */ + void ParseEntry(bool bisSourceLang); + + /*! \brief Gets the msgctxt string previously parsed by ParseEntry(). + \return string* containing the msgctxt string, unescaped and linked together. + */ + const std::string& GetMsgctxt() const {return m_Entry.msgCtxt.Str;} + + /*! \brief Gets the msgid string previously parsed by ParseEntry(). + \return string* containing the msgid string, unescaped and linked together. + */ + const std::string& GetMsgid() const {return m_Entry.msgID.Str;} + + /*! \brief Gets the msgstr string previously parsed by ParseEntry(). + \return string* containing the msgstr string, unescaped and linked together. + */ + const std::string& GetMsgstr() const {return m_Entry.msgStr.Str;} + + /*! \brief Gets the msgstr[x] string previously parsed by ParseEntry(). + \param plural the number of plural-form expected to get (0-6). + \return string* containing the msgstr string, unescaped and linked together. + */ + const std::string& GetPlurMsgstr (size_t plural) const; + +protected: + + /*! \brief Converts c++ style char escape sequences back to char. + * Supports: \a \v \n \t \r \" \0 \f \? \' \\ + \param strInput string contains the string to be unescaped. + \return unescaped string. + */ + std::string UnescapeString(const std::string &strInput); + + /*! \brief Finds the position of line, starting with a given string in current entry. + * This function can only be called after GetNextEntry() + \param strToFind a string what we look for, at beginning of the lines. + \param FoundPos will get the position where we found the line starting with the string. + \return false if no line like that can be found in the entry (m_Entry) + */ + bool FindLineStart(const std::string &strToFind, size_t &FoundPos); + + /*! \brief Reads, and links together the quoted strings found with ParseEntry(). + * This function can only be called after GetNextEntry() called. + \param strEntry.Str a string where we get the appended string lines. + \param strEntry.Pos the position in m_Entry.Content to start reading the string. + */ + void GetString(CStrEntry &strEntry); + + /*! \brief Parses the numeric id and checks if it is valid. + * This function can only be called after GetNextEntry() + * It checks m_Entry.Content at position m_Entry.xIDPos for the numeric id. + * The converted ID number goes into m_Entry.xID for public read out. + \return false, if parse and convert of the id number was unsuccessful. + */ + bool ParseNumID(); + + /*! \brief If we have Windows or Mac line-end chars in PO file, convert them to Unix LFs + */ + void ConvertLineEnds(const std::string &filename); + + // Temporary string buffer to read file in. + std::string m_strBuffer; + // Size of the string buffer. + size_t m_POfilelength; + + // Current cursor position in m_strBuffer. + size_t m_CursorPos; + // The next PO entry position in m_strBuffer. + size_t m_nextEntryPos; + + // Variable to hold all data of currently processed entry. + CPOEntry m_Entry; +}; diff --git a/src/utils/PerformanceSample.cpp b/src/utils/PerformanceSample.cpp new file mode 100644 index 0000000000..0da95ab29b --- /dev/null +++ b/src/utils/PerformanceSample.cpp @@ -0,0 +1,109 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" +#include "PerformanceSample.h" + +#ifdef TARGET_POSIX +#include "linux/PlatformInclude.h" +#endif + +#include "Application.h" +#include "log.h" +#include "TimeUtils.h" + +using namespace std; + +int64_t CPerformanceSample::m_tmFreq; + +CPerformanceSample::CPerformanceSample(const string &statName, bool bCheckWhenDone) : m_statName(statName) +{ + m_bCheckWhenDone = bCheckWhenDone; + if (m_tmFreq == 0LL) + m_tmFreq = CurrentHostFrequency(); + + Reset(); +} + +CPerformanceSample::~CPerformanceSample() +{ + if (m_bCheckWhenDone) + CheckPoint(); +} + +void CPerformanceSample::Reset() +{ + m_tmStart = CurrentHostCounter(); +#ifdef TARGET_POSIX + if (getrusage(RUSAGE_SELF, &m_usage) == -1) + CLog::Log(LOGERROR,"error %d in getrusage", errno); +#endif +} + +void CPerformanceSample::CheckPoint() +{ +#ifdef HAS_PERFORMANCE_SAMPLE + int64_t tmNow; + tmNow = CurrentHostCounter(); + double elapsed = (double)(tmNow - m_tmStart) / (double)m_tmFreq.QuadPart; + + double dUser=0.0, dSys=0.0; +#ifdef TARGET_POSIX + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) == -1) + CLog::Log(LOGERROR,"error %d in getrusage", errno); + else + { + dUser = ( ((double)usage.ru_utime.tv_sec + (double)usage.ru_utime.tv_usec / 1000000.0) - + ((double)m_usage.ru_utime.tv_sec + (double)m_usage.ru_utime.tv_usec / 1000000.0) ); + dSys = ( ((double)usage.ru_stime.tv_sec + (double)usage.ru_stime.tv_usec / 1000000.0) - + ((double)m_usage.ru_stime.tv_sec + (double)m_usage.ru_stime.tv_usec / 1000000.0) ); + } +#endif + + g_application.GetPerformanceStats().AddSample(m_statName, PerformanceCounter(elapsed,dUser,dSys)); +#endif + + Reset(); +} + +double CPerformanceSample::GetEstimatedError() +{ + if (m_tmFreq == 0LL) + m_tmFreq = CurrentHostFrequency(); + + int64_t tmStart, tmEnd; + tmStart = CurrentHostCounter(); + + for (int i=0; i<100000;i++) + { + DECLARE_UNUSED(int64_t,tmDummy); + tmDummy = CurrentHostCounter(); + } + + tmEnd = CurrentHostCounter(); + double elapsed = (double)(tmEnd - tmStart) / (double)m_tmFreq; + + return (elapsed / 100000.0) * 2.0; // one measure at start time and another when done. +} + + + + diff --git a/src/utils/PerformanceSample.h b/src/utils/PerformanceSample.h new file mode 100644 index 0000000000..d148cc4c7d --- /dev/null +++ b/src/utils/PerformanceSample.h @@ -0,0 +1,68 @@ +#ifndef __PERFORMANCE_SAMPLE__ +#define __PERFORMANCE_SAMPLE__ + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#ifdef TARGET_POSIX +#include "linux/PlatformDefs.h" +#include <sys/time.h> +#include <sys/times.h> +#include <sys/resource.h> +#elif TARGET_WINDOWS +#include "win32/PlatformDefs.h" +#endif + +#include <string> + +#ifndef NO_PERFORMANCE_MEASURE +#define MEASURE_FUNCTION CPerformanceSample aSample(__FUNCTION__,true); +#define BEGIN_MEASURE_BLOCK(n) { CPerformanceSample aSample(n,true); +#define END_MEASURE_BLOCK } +#else +#define MEASURE_FUNCTION +#define BEGIN_MEASURE_BLOCK(n) +#define END_MEASURE_BLOCK +#endif + +class CPerformanceSample +{ +public: + CPerformanceSample(const std::string &statName, bool bCheckWhenDone=true); + virtual ~CPerformanceSample(); + + void Reset(); + void CheckPoint(); // will add a sample to stats and restart counting. + + static double GetEstimatedError(); + +protected: + std::string m_statName; + bool m_bCheckWhenDone; + +#ifdef TARGET_POSIX + struct rusage m_usage; +#endif + + int64_t m_tmStart; + static int64_t m_tmFreq; +}; + +#endif diff --git a/src/utils/PerformanceStats.cpp b/src/utils/PerformanceStats.cpp new file mode 100644 index 0000000000..5ef8cadadf --- /dev/null +++ b/src/utils/PerformanceStats.cpp @@ -0,0 +1,78 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PerformanceStats.h" +#include "PerformanceSample.h" +#include "log.h" + +using namespace std; + +CPerformanceStats::CPerformanceStats() +{ +} + + +CPerformanceStats::~CPerformanceStats() +{ + map<string, PerformanceCounter*>::iterator iter = m_mapStats.begin(); + while (iter != m_mapStats.end()) + { + delete iter->second; + ++iter; + } + m_mapStats.clear(); +} + +void CPerformanceStats::AddSample(const string &strStatName, const PerformanceCounter &perf) +{ + map<string, PerformanceCounter*>::iterator iter = m_mapStats.find(strStatName); + if (iter == m_mapStats.end()) + m_mapStats[strStatName] = new PerformanceCounter(perf); + else + { + iter->second->m_time += perf.m_time; + iter->second->m_samples += perf.m_samples; + } +} + +void CPerformanceStats::AddSample(const string &strStatName, double dTime) +{ + AddSample(strStatName, PerformanceCounter(dTime)); +} + +void CPerformanceStats::DumpStats() +{ + double dError = CPerformanceSample::GetEstimatedError(); + CLog::Log(LOGINFO, "%s - estimated error: %f", __FUNCTION__, dError); + CLog::Log(LOGINFO, "%s - ignore user/sys values when sample count is low", __FUNCTION__); + + map<string, PerformanceCounter*>::iterator iter = m_mapStats.begin(); + while (iter != m_mapStats.end()) + { + double dAvg = iter->second->m_time / (double)iter->second->m_samples; + double dAvgUser = iter->second->m_user / (double)iter->second->m_samples; + double dAvgSys = iter->second->m_sys / (double)iter->second->m_samples; + CLog::Log(LOGINFO, "%s - counter <%s>. avg duration: <%f sec>, avg user: <%f>, avg sys: <%f> (%" PRIu64" samples)", + __FUNCTION__, iter->first.c_str(), dAvg, dAvgUser, dAvgSys, iter->second->m_samples); + ++iter; + } +} + + diff --git a/src/utils/PerformanceStats.h b/src/utils/PerformanceStats.h new file mode 100644 index 0000000000..4f72d6dca2 --- /dev/null +++ b/src/utils/PerformanceStats.h @@ -0,0 +1,58 @@ +#ifndef PERFORMANCESTATS_H +#define PERFORMANCESTATS_H + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> +#include <string> +#include "PlatformDefs.h" +#include "threads/CriticalSection.h" + +class PerformanceCounter +{ +public: + double m_time; + double m_user; + double m_sys; + int64_t m_samples; + + PerformanceCounter(double dTime=0.0, double dUser=0.0, double dSys=0.0, int64_t nSamples=1LL) : + m_time(dTime), m_user(dUser), m_sys(dSys), m_samples(nSamples) { } + virtual ~PerformanceCounter() { } +}; + +/** +*/ +class CPerformanceStats{ +public: + CPerformanceStats(); + virtual ~CPerformanceStats(); + + void AddSample(const std::string &strStatName, const PerformanceCounter &perf); + void AddSample(const std::string &strStatName, double dTime); + void DumpStats(); + +protected: + CCriticalSection m_lock; + std::map<std::string, PerformanceCounter*> m_mapStats; +}; + +#endif diff --git a/src/utils/RecentlyAddedJob.cpp b/src/utils/RecentlyAddedJob.cpp new file mode 100644 index 0000000000..f4770c714f --- /dev/null +++ b/src/utils/RecentlyAddedJob.cpp @@ -0,0 +1,363 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/VideoInfoTag.h" +#include "FileItem.h" +#include "RecentlyAddedJob.h" +#include "guilib/GUIWindow.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/WindowIDs.h" +#include "music/MusicDatabase.h" +#include "music/tags/MusicInfoTag.h" +#include "utils/Variant.h" +#include "utils/StringUtils.h" +#include "settings/AdvancedSettings.h" +#include "music/MusicThumbLoader.h" +#include "video/VideoThumbLoader.h" + +#define NUM_ITEMS 10 + +CRecentlyAddedJob::CRecentlyAddedJob(int flag) +{ + m_flag = flag; +} + +bool CRecentlyAddedJob::UpdateVideo() +{ + CGUIWindow* home = g_windowManager.GetWindow(WINDOW_HOME); + + if ( home == NULL ) + return false; + + CLog::Log(LOGDEBUG, "CRecentlyAddedJob::UpdateVideos() - Running RecentlyAdded home screen update"); + + int i = 0; + CFileItemList items; + CVideoDatabase videodatabase; + CVideoThumbLoader loader; + loader.OnLoaderStart(); + + videodatabase.Open(); + + if (videodatabase.GetRecentlyAddedMoviesNav("videodb://recentlyaddedmovies/", items, NUM_ITEMS)) + { + for (; i < items.Size(); ++i) + { + CFileItemPtr item = items.Get(i); + CStdString value = StringUtils::Format("%i", i + 1); + CStdString strRating = StringUtils::Format("%.1f", item->GetVideoInfoTag()->m_fRating);; + + home->SetProperty("LatestMovie." + value + ".Title" , item->GetLabel()); + home->SetProperty("LatestMovie." + value + ".Rating" , strRating); + home->SetProperty("LatestMovie." + value + ".Year" , item->GetVideoInfoTag()->m_iYear); + home->SetProperty("LatestMovie." + value + ".Plot" , item->GetVideoInfoTag()->m_strPlot); + home->SetProperty("LatestMovie." + value + ".RunningTime" , item->GetVideoInfoTag()->GetDuration() / 60); + home->SetProperty("LatestMovie." + value + ".Path" , item->GetVideoInfoTag()->m_strFileNameAndPath); + home->SetProperty("LatestMovie." + value + ".Trailer" , item->GetVideoInfoTag()->m_strTrailer); + + if (!item->HasArt("thumb")) + loader.LoadItem(item.get()); + + home->SetProperty("LatestMovie." + value + ".Thumb" , item->GetArt("thumb")); + home->SetProperty("LatestMovie." + value + ".Fanart" , item->GetArt("fanart")); + } + } + for (; i < NUM_ITEMS; ++i) + { + CStdString value = StringUtils::Format("%i", i + 1); + home->SetProperty("LatestMovie." + value + ".Title" , ""); + home->SetProperty("LatestMovie." + value + ".Thumb" , ""); + home->SetProperty("LatestMovie." + value + ".Rating" , ""); + home->SetProperty("LatestMovie." + value + ".Year" , ""); + home->SetProperty("LatestMovie." + value + ".Plot" , ""); + home->SetProperty("LatestMovie." + value + ".RunningTime" , ""); + home->SetProperty("LatestMovie." + value + ".Path" , ""); + home->SetProperty("LatestMovie." + value + ".Trailer" , ""); + home->SetProperty("LatestMovie." + value + ".Fanart" , ""); + } + + i = 0; + CFileItemList TVShowItems; + + if (videodatabase.GetRecentlyAddedEpisodesNav("videodb://recentlyaddedepisodes/", TVShowItems, NUM_ITEMS)) + { + for (; i < TVShowItems.Size(); ++i) + { + CFileItemPtr item = TVShowItems.Get(i); + int EpisodeSeason = item->GetVideoInfoTag()->m_iSeason; + int EpisodeNumber = item->GetVideoInfoTag()->m_iEpisode; + CStdString EpisodeNo = StringUtils::Format("s%02de%02d", EpisodeSeason, EpisodeNumber); + CStdString value = StringUtils::Format("%i", i + 1); + CStdString strRating = StringUtils::Format("%.1f", item->GetVideoInfoTag()->m_fRating); + + home->SetProperty("LatestEpisode." + value + ".ShowTitle" , item->GetVideoInfoTag()->m_strShowTitle); + home->SetProperty("LatestEpisode." + value + ".EpisodeTitle" , item->GetVideoInfoTag()->m_strTitle); + home->SetProperty("LatestEpisode." + value + ".Rating" , strRating); + home->SetProperty("LatestEpisode." + value + ".Plot" , item->GetVideoInfoTag()->m_strPlot); + home->SetProperty("LatestEpisode." + value + ".EpisodeNo" , EpisodeNo); + home->SetProperty("LatestEpisode." + value + ".EpisodeSeason" , EpisodeSeason); + home->SetProperty("LatestEpisode." + value + ".EpisodeNumber" , EpisodeNumber); + home->SetProperty("LatestEpisode." + value + ".Path" , item->GetVideoInfoTag()->m_strFileNameAndPath); + + if (!item->HasArt("thumb")) + loader.LoadItem(item.get()); + + std::string seasonThumb; + if (item->GetVideoInfoTag()->m_iIdSeason > 0) + seasonThumb = videodatabase.GetArtForItem(item->GetVideoInfoTag()->m_iIdSeason, MediaTypeSeason, "thumb"); + + home->SetProperty("LatestEpisode." + value + ".Thumb" , item->GetArt("thumb")); + home->SetProperty("LatestEpisode." + value + ".ShowThumb" , item->GetArt("tvshow.thumb")); + home->SetProperty("LatestEpisode." + value + ".SeasonThumb" , seasonThumb); + home->SetProperty("LatestEpisode." + value + ".Fanart" , item->GetArt("fanart")); + } + } + for (; i < NUM_ITEMS; ++i) + { + CStdString value = StringUtils::Format("%i", i + 1); + home->SetProperty("LatestEpisode." + value + ".ShowTitle" , ""); + home->SetProperty("LatestEpisode." + value + ".EpisodeTitle" , ""); + home->SetProperty("LatestEpisode." + value + ".Rating" , ""); + home->SetProperty("LatestEpisode." + value + ".Plot" , ""); + home->SetProperty("LatestEpisode." + value + ".EpisodeNo" , ""); + home->SetProperty("LatestEpisode." + value + ".EpisodeSeason" , ""); + home->SetProperty("LatestEpisode." + value + ".EpisodeNumber" , ""); + home->SetProperty("LatestEpisode." + value + ".Path" , ""); + home->SetProperty("LatestEpisode." + value + ".Thumb" , ""); + home->SetProperty("LatestEpisode." + value + ".ShowThumb" , ""); + home->SetProperty("LatestEpisode." + value + ".SeasonThumb" , ""); + home->SetProperty("LatestEpisode." + value + ".Fanart" , ""); + } + + i = 0; + CFileItemList MusicVideoItems; + + if (videodatabase.GetRecentlyAddedMusicVideosNav("videodb://recentlyaddedmusicvideos/", MusicVideoItems, NUM_ITEMS)) + { + for (; i < MusicVideoItems.Size(); ++i) + { + CFileItemPtr item = MusicVideoItems.Get(i); + CStdString value = StringUtils::Format("%i", i + 1); + + home->SetProperty("LatestMusicVideo." + value + ".Title" , item->GetLabel()); + home->SetProperty("LatestMusicVideo." + value + ".Year" , item->GetVideoInfoTag()->m_iYear); + home->SetProperty("LatestMusicVideo." + value + ".Plot" , item->GetVideoInfoTag()->m_strPlot); + home->SetProperty("LatestMusicVideo." + value + ".RunningTime" , item->GetVideoInfoTag()->GetDuration() / 60); + home->SetProperty("LatestMusicVideo." + value + ".Path" , item->GetVideoInfoTag()->m_strFileNameAndPath); + home->SetProperty("LatestMusicVideo." + value + ".Artist" , StringUtils::Join(item->GetVideoInfoTag()->m_artist, g_advancedSettings.m_videoItemSeparator)); + + if (!item->HasArt("thumb")) + loader.LoadItem(item.get()); + + home->SetProperty("LatestMusicVideo." + value + ".Thumb" , item->GetArt("thumb")); + home->SetProperty("LatestMusicVideo." + value + ".Fanart" , item->GetArt("fanart")); + } + } + for (; i < NUM_ITEMS; ++i) + { + CStdString value = StringUtils::Format("%i", i + 1); + home->SetProperty("LatestMusicVideo." + value + ".Title" , ""); + home->SetProperty("LatestMusicVideo." + value + ".Thumb" , ""); + home->SetProperty("LatestMusicVideo." + value + ".Year" , ""); + home->SetProperty("LatestMusicVideo." + value + ".Plot" , ""); + home->SetProperty("LatestMusicVideo." + value + ".RunningTime" , ""); + home->SetProperty("LatestMusicVideo." + value + ".Path" , ""); + home->SetProperty("LatestMusicVideo." + value + ".Artist" , ""); + home->SetProperty("LatestMusicVideo." + value + ".Fanart" , ""); + } + + videodatabase.Close(); + return true; +} + +bool CRecentlyAddedJob::UpdateMusic() +{ + CGUIWindow* home = g_windowManager.GetWindow(WINDOW_HOME); + + if ( home == NULL ) + return false; + + CLog::Log(LOGDEBUG, "CRecentlyAddedJob::UpdateMusic() - Running RecentlyAdded home screen update"); + + int i = 0; + CFileItemList musicItems; + CMusicDatabase musicdatabase; + CMusicThumbLoader loader; + loader.OnLoaderStart(); + + musicdatabase.Open(); + + if (musicdatabase.GetRecentlyAddedAlbumSongs("musicdb://songs/", musicItems, NUM_ITEMS)) + { + long idAlbum = -1; + CStdString strAlbumThumb; + CStdString strAlbumFanart; + for (; i < musicItems.Size(); ++i) + { + CFileItemPtr item = musicItems.Get(i); + CStdString value = StringUtils::Format("%i", i + 1); + + CStdString strRating; + CStdString strAlbum = item->GetMusicInfoTag()->GetAlbum(); + CStdString strArtist = StringUtils::Join(item->GetMusicInfoTag()->GetArtist(), g_advancedSettings.m_musicItemSeparator); + + if (idAlbum != item->GetMusicInfoTag()->GetAlbumId()) + { + strAlbumThumb.clear(); + strAlbumFanart.clear(); + idAlbum = item->GetMusicInfoTag()->GetAlbumId(); + + if (loader.LoadItem(item.get())) + { + strAlbumThumb = item->GetArt("thumb"); + strAlbumFanart = item->GetArt("fanart"); + } + } + + strRating = StringUtils::Format("%c", item->GetMusicInfoTag()->GetRating()); + + home->SetProperty("LatestSong." + value + ".Title" , item->GetMusicInfoTag()->GetTitle()); + home->SetProperty("LatestSong." + value + ".Year" , item->GetMusicInfoTag()->GetYear()); + home->SetProperty("LatestSong." + value + ".Artist" , strArtist); + home->SetProperty("LatestSong." + value + ".Album" , strAlbum); + home->SetProperty("LatestSong." + value + ".Rating" , strRating); + home->SetProperty("LatestSong." + value + ".Path" , item->GetMusicInfoTag()->GetURL()); + home->SetProperty("LatestSong." + value + ".Thumb" , strAlbumThumb); + home->SetProperty("LatestSong." + value + ".Fanart" , strAlbumFanart); + } + } + for (; i < NUM_ITEMS; ++i) + { + CStdString value = StringUtils::Format("%i", i + 1); + home->SetProperty("LatestSong." + value + ".Title" , ""); + home->SetProperty("LatestSong." + value + ".Year" , ""); + home->SetProperty("LatestSong." + value + ".Artist" , ""); + home->SetProperty("LatestSong." + value + ".Album" , ""); + home->SetProperty("LatestSong." + value + ".Rating" , ""); + home->SetProperty("LatestSong." + value + ".Path" , ""); + home->SetProperty("LatestSong." + value + ".Thumb" , ""); + home->SetProperty("LatestSong." + value + ".Fanart" , ""); + } + + i = 0; + VECALBUMS albums; + + if (musicdatabase.GetRecentlyAddedAlbums(albums, NUM_ITEMS)) + { + for (; i < (int)albums.size(); ++i) + { + CAlbum& album=albums[i]; + CStdString value = StringUtils::Format("%i", i + 1); + CStdString strThumb = musicdatabase.GetArtForItem(album.idAlbum, MediaTypeAlbum, "thumb"); + CStdString strFanart = musicdatabase.GetArtistArtForItem(album.idAlbum, MediaTypeAlbum, "fanart"); + CStdString strDBpath = StringUtils::Format("musicdb://albums/%li/", album.idAlbum); + CStdString strSQLAlbum = StringUtils::Format("idAlbum=%li", album.idAlbum); + CStdString strArtist = musicdatabase.GetSingleValue("albumview", "strArtists", strSQLAlbum); + + home->SetProperty("LatestAlbum." + value + ".Title" , album.strAlbum); + home->SetProperty("LatestAlbum." + value + ".Year" , album.iYear); + home->SetProperty("LatestAlbum." + value + ".Artist" , strArtist); + home->SetProperty("LatestAlbum." + value + ".Rating" , album.iRating); + home->SetProperty("LatestAlbum." + value + ".Path" , strDBpath); + home->SetProperty("LatestAlbum." + value + ".Thumb" , strThumb); + home->SetProperty("LatestAlbum." + value + ".Fanart" , strFanart); + } + } + for (; i < NUM_ITEMS; ++i) + { + CStdString value = StringUtils::Format("%i", i + 1); + home->SetProperty("LatestAlbum." + value + ".Title" , ""); + home->SetProperty("LatestAlbum." + value + ".Year" , ""); + home->SetProperty("LatestAlbum." + value + ".Artist" , ""); + home->SetProperty("LatestAlbum." + value + ".Rating" , ""); + home->SetProperty("LatestAlbum." + value + ".Path" , ""); + home->SetProperty("LatestAlbum." + value + ".Thumb" , ""); + home->SetProperty("LatestAlbum." + value + ".Fanart" , ""); + } + + musicdatabase.Close(); + return true; +} + +bool CRecentlyAddedJob::UpdateTotal() +{ + CGUIWindow* home = g_windowManager.GetWindow(WINDOW_HOME); + + if ( home == NULL ) + return false; + + CLog::Log(LOGDEBUG, "CRecentlyAddedJob::UpdateTotal() - Running RecentlyAdded home screen update"); + + CVideoDatabase videodatabase; + CMusicDatabase musicdatabase; + + musicdatabase.Open(); + int MusSongTotals = atoi(musicdatabase.GetSingleValue("songview" , "count(1)").c_str()); + int MusAlbumTotals = atoi(musicdatabase.GetSingleValue("songview" , "count(distinct strAlbum)").c_str()); + int MusArtistTotals = atoi(musicdatabase.GetSingleValue("songview" , "count(distinct strArtists)").c_str()); + musicdatabase.Close(); + + videodatabase.Open(); + int tvShowCount = atoi(videodatabase.GetSingleValue("tvshowview" , "count(1)").c_str()); + int movieTotals = atoi(videodatabase.GetSingleValue("movieview" , "count(1)").c_str()); + int movieWatched = atoi(videodatabase.GetSingleValue("movieview" , "count(playCount)").c_str()); + int MusVidTotals = atoi(videodatabase.GetSingleValue("musicvideoview" , "count(1)").c_str()); + int MusVidWatched = atoi(videodatabase.GetSingleValue("musicvideoview" , "count(playCount)").c_str()); + int EpWatched = atoi(videodatabase.GetSingleValue("tvshowview" , "sum(watchedcount)").c_str()); + int EpCount = atoi(videodatabase.GetSingleValue("tvshowview" , "sum(totalcount)").c_str()); + int TvShowsWatched = atoi(videodatabase.GetSingleValue("tvshowview" , "sum(watchedcount = totalcount)").c_str()); + videodatabase.Close(); + + home->SetProperty("TVShows.Count" , tvShowCount); + home->SetProperty("TVShows.Watched" , TvShowsWatched); + home->SetProperty("TVShows.UnWatched" , tvShowCount - TvShowsWatched); + home->SetProperty("Episodes.Count" , EpCount); + home->SetProperty("Episodes.Watched" , EpWatched); + home->SetProperty("Episodes.UnWatched" , EpCount-EpWatched); + home->SetProperty("Movies.Count" , movieTotals); + home->SetProperty("Movies.Watched" , movieWatched); + home->SetProperty("Movies.UnWatched" , movieTotals - movieWatched); + home->SetProperty("MusicVideos.Count" , MusVidTotals); + home->SetProperty("MusicVideos.Watched" , MusVidWatched); + home->SetProperty("MusicVideos.UnWatched" , MusVidTotals - MusVidWatched); + home->SetProperty("Music.SongsCount" , MusSongTotals); + home->SetProperty("Music.AlbumsCount" , MusAlbumTotals); + home->SetProperty("Music.ArtistsCount" , MusArtistTotals); + + return true; +} + + +bool CRecentlyAddedJob::DoWork() +{ + bool ret = true; + if (m_flag & Audio) + ret &= UpdateMusic(); + + if (m_flag & Video) + ret &= UpdateVideo(); + + if (m_flag & Totals) + ret &= UpdateTotal(); + + return ret; +} diff --git a/src/utils/RecentlyAddedJob.h b/src/utils/RecentlyAddedJob.h new file mode 100644 index 0000000000..380b4c7002 --- /dev/null +++ b/src/utils/RecentlyAddedJob.h @@ -0,0 +1,41 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Job.h" + +enum ERecentlyAddedFlag +{ + Audio = 0x1, + Video = 0x2, + Totals = 0x4 +}; + +class CRecentlyAddedJob : public CJob +{ +public: + CRecentlyAddedJob(int flag); + static bool UpdateVideo(); + static bool UpdateMusic(); + static bool UpdateTotal(); + virtual bool DoWork(); +private: + int m_flag; +}; diff --git a/src/utils/RegExp.cpp b/src/utils/RegExp.cpp new file mode 100644 index 0000000000..97a8bcbec9 --- /dev/null +++ b/src/utils/RegExp.cpp @@ -0,0 +1,650 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <string.h> +#include <algorithm> +#include "RegExp.h" +#include "StdString.h" +#include "log.h" +#include "utils/StringUtils.h" +#include "utils/Utf8Utils.h" + +using namespace PCRE; + +#ifndef PCRE_UCP +#define PCRE_UCP 0 +#endif // PCRE_UCP + +#ifdef PCRE_CONFIG_JIT +#define PCRE_HAS_JIT_CODE 1 +#endif + +#ifndef PCRE_STUDY_JIT_COMPILE +#define PCRE_STUDY_JIT_COMPILE 0 +#endif +#ifndef PCRE_INFO_JIT +// some unused number +#define PCRE_INFO_JIT 2048 +#endif +#ifndef PCRE_HAS_JIT_CODE +#define pcre_free_study(x) pcre_free((x)) +#endif + +int CRegExp::m_Utf8Supported = -1; +int CRegExp::m_UcpSupported = -1; +int CRegExp::m_JitSupported = -1; + + +CRegExp::CRegExp(bool caseless /*= false*/, CRegExp::utf8Mode utf8 /*= asciiOnly*/) +{ + InitValues(caseless, utf8); +} + +void CRegExp::InitValues(bool caseless /*= false*/, CRegExp::utf8Mode utf8 /*= asciiOnly*/) +{ + m_utf8Mode = utf8; + m_re = NULL; + m_sd = NULL; + m_iOptions = PCRE_DOTALL | PCRE_NEWLINE_ANY; + if(caseless) + m_iOptions |= PCRE_CASELESS; + if (m_utf8Mode == forceUtf8) + { + if (IsUtf8Supported()) + m_iOptions |= PCRE_UTF8; + if (AreUnicodePropertiesSupported()) + m_iOptions |= PCRE_UCP; + } + + m_offset = 0; + m_jitCompiled = false; + m_bMatched = false; + m_iMatchCount = 0; + m_jitStack = NULL; + + memset(m_iOvector, 0, sizeof(m_iOvector)); +} + +CRegExp::CRegExp(bool caseless, CRegExp::utf8Mode utf8, const char *re, studyMode study /*= NoStudy*/) +{ + if (utf8 == autoUtf8) + utf8 = requireUtf8(re) ? forceUtf8 : asciiOnly; + + InitValues(caseless, utf8); + RegComp(re, study); +} + +bool CRegExp::requireUtf8(const std::string& regexp) +{ + // enable UTF-8 mode if regexp string has UTF-8 multibyte sequences + if (CUtf8Utils::checkStrForUtf8(regexp) == CUtf8Utils::utf8string) + return true; + + // check for explicit Unicode Properties (\p, \P, \X) and for Unicode character codes (greater than 0xFF) in form \x{hhh..} + // note: PCRE change meaning of \w, \s, \d (and \W, \S, \D) when Unicode Properties are enabled, + // but in auto mode we enable UNP for US-ASCII regexp only if regexp contains explicit \p, \P, \X or Unicode character code + const char* const regexpC = regexp.c_str(); + const size_t len = regexp.length(); + size_t pos = 0; + + while (pos < len) + { + const char chr = regexpC[pos]; + if (chr == '\\') + { + const char nextChr = regexpC[pos + 1]; + + if (nextChr == 'p' || nextChr == 'P' || nextChr == 'X') + return true; // found Unicode Properties + else if (nextChr == 'Q') + pos = regexp.find("\\E", pos + 2); // skip all literals in "\Q...\E" + else if (nextChr == 'x' && regexpC[pos + 2] == '{') + { // Unicode character with hex code + if (readCharXCode(regexp, pos) >= 0x100) + return true; // found Unicode character code + } + else if (nextChr == '\\' || nextChr == '(' || nextChr == ')' + || nextChr == '[' || nextChr == ']') + pos++; // exclude next character from analyze + + } // chr != '\\' + else if (chr == '(' && regexpC[pos + 1] == '?' && regexpC[pos + 2] == '#') // comment in regexp + pos = regexp.find(')', pos); // skip comment + else if (chr == '[') + { + if (isCharClassWithUnicode(regexp, pos)) + return true; + } + + if (pos == std::string::npos) // check results of regexp.find() and isCharClassWithUnicode + return false; + + pos++; + } + + // no Unicode Properties was found + return false; +} + +inline int CRegExp::readCharXCode(const std::string& regexp, size_t& pos) +{ + // read hex character code in form "\x{hh..}" + // 'pos' must point to '\' + if (pos >= regexp.length()) + return -1; + const char* const regexpC = regexp.c_str(); + if (regexpC[pos] != '\\' || regexpC[pos + 1] != 'x' || regexpC[pos + 2] != '{') + return -1; + + pos++; + const size_t startPos = pos; // 'startPos' points to 'x' + const size_t closingBracketPos = regexp.find('}', startPos + 2); + if (closingBracketPos == std::string::npos) + return 0; // return character zero code, leave 'pos' at 'x' + + pos++; // 'pos' points to '{' + int chCode = 0; + while (++pos < closingBracketPos) + { + const int xdigitVal = StringUtils::asciixdigitvalue(regexpC[pos]); + if (xdigitVal >= 0) + chCode = chCode * 16 + xdigitVal; + else + { // found non-hexdigit + pos = startPos; // reset 'pos' to 'startPos', process "{hh..}" as non-code + return 0; // return character zero code + } + } + + return chCode; +} + +bool CRegExp::isCharClassWithUnicode(const std::string& regexp, size_t& pos) +{ + const char* const regexpC = regexp.c_str(); + const size_t len = regexp.length(); + if (pos > len || regexpC[pos] != '[') + return false; + + // look for Unicode character code "\x{hhh..}" and Unicode properties "\P", "\p" and "\X" + // find end (terminating ']') of character class (like "[a-h45]") + // detect nested POSIX classes like "[[:lower:]]" and escaped brackets like "[\]]" + bool needUnicode = false; + while (++pos < len) + { + if (regexpC[pos] == '[' && regexpC[pos + 1] == ':') + { // possible POSIX character class, like "[:alpha:]" + const size_t nextClosingBracketPos = regexp.find(']', pos + 2); // don't care about "\]", as it produce error if used inside POSIX char class + + if (nextClosingBracketPos == std::string::npos) + { // error in regexp: no closing ']' for character class + pos = std::string::npos; + return needUnicode; + } + else if (regexpC[nextClosingBracketPos - 1] == ':') + pos = nextClosingBracketPos; // skip POSIX character class + // if ":]" is not found, process "[:..." as part of normal character class + } + else if (regexpC[pos] == ']') + return needUnicode; // end of character class + else if (regexpC[pos] == '\\') + { + const char nextChar = regexpC[pos + 1]; + if (nextChar == ']' || nextChar == '[') + pos++; // skip next character + else if (nextChar == 'Q') + { + pos = regexp.find("\\E", pos + 2); + if (pos == std::string::npos) + return needUnicode; // error in regexp: no closing "\E" after "\Q" in character class + else + pos++; // skip "\E" + } + else if (nextChar == 'p' || nextChar == 'P' || nextChar == 'X') + needUnicode = true; // don't care about property name as it can contain only ASCII chars + else if (nextChar == 'x') + { + if (readCharXCode(regexp, pos) >= 0x100) + needUnicode = true; + } + } + } + pos = std::string::npos; // closing square bracket was not found + + return needUnicode; +} + + +CRegExp::CRegExp(const CRegExp& re) +{ + m_re = NULL; + m_sd = NULL; + m_jitStack = NULL; + m_utf8Mode = re.m_utf8Mode; + m_iOptions = re.m_iOptions; + *this = re; +} + +CRegExp& CRegExp::operator=(const CRegExp& re) +{ + size_t size; + Cleanup(); + m_jitCompiled = false; + m_pattern = re.m_pattern; + if (re.m_re) + { + if (pcre_fullinfo(re.m_re, NULL, PCRE_INFO_SIZE, &size) >= 0) + { + if ((m_re = (pcre*)malloc(size))) + { + memcpy(m_re, re.m_re, size); + memcpy(m_iOvector, re.m_iOvector, OVECCOUNT*sizeof(int)); + m_offset = re.m_offset; + m_iMatchCount = re.m_iMatchCount; + m_bMatched = re.m_bMatched; + m_subject = re.m_subject; + m_iOptions = re.m_iOptions; + } + else + CLog::Log(LOGSEVERE, "%s: Failed to allocate memory", __FUNCTION__); + } + } + return *this; +} + +CRegExp::~CRegExp() +{ + Cleanup(); +} + +bool CRegExp::RegComp(const char *re, studyMode study /*= NoStudy*/) +{ + if (!re) + return false; + + m_offset = 0; + m_jitCompiled = false; + m_bMatched = false; + m_iMatchCount = 0; + const char *errMsg = NULL; + int errOffset = 0; + int options = m_iOptions; + if (m_utf8Mode == autoUtf8 && requireUtf8(re)) + options |= (IsUtf8Supported() ? PCRE_UTF8 : 0) | (AreUnicodePropertiesSupported() ? PCRE_UCP : 0); + + Cleanup(); + + m_re = pcre_compile(re, options, &errMsg, &errOffset, NULL); + if (!m_re) + { + m_pattern.clear(); + CLog::Log(LOGERROR, "PCRE: %s. Compilation failed at offset %d in expression '%s'", + errMsg, errOffset, re); + return false; + } + + m_pattern = re; + + if (study) + { + const bool jitCompile = (study == StudyWithJitComp) && IsJitSupported(); + const int studyOptions = jitCompile ? PCRE_STUDY_JIT_COMPILE : 0; + + m_sd = pcre_study(m_re, studyOptions, &errMsg); + if (errMsg != NULL) + { + CLog::Log(LOGWARNING, "%s: PCRE error \"%s\" while studying expression", __FUNCTION__, errMsg); + if (m_sd != NULL) + { + pcre_free_study(m_sd); + m_sd = NULL; + } + } + else if (jitCompile) + { + int jitPresent = 0; + m_jitCompiled = (pcre_fullinfo(m_re, m_sd, PCRE_INFO_JIT, &jitPresent) == 0 && jitPresent == 1); + } + } + + return true; +} + +int CRegExp::RegFind(const char *str, unsigned int startoffset /*= 0*/, int maxNumberOfCharsToTest /*= -1*/) +{ + return PrivateRegFind(strlen(str), str, startoffset, maxNumberOfCharsToTest); +} + +int CRegExp::PrivateRegFind(size_t bufferLen, const char *str, unsigned int startoffset /* = 0*/, int maxNumberOfCharsToTest /*= -1*/) +{ + m_offset = 0; + m_bMatched = false; + m_iMatchCount = 0; + + if (!m_re) + { + CLog::Log(LOGERROR, "PCRE: Called before compilation"); + return -1; + } + + if (!str) + { + CLog::Log(LOGERROR, "PCRE: Called without a string to match"); + return -1; + } + + if (startoffset > bufferLen) + { + CLog::Log(LOGERROR, "%s: startoffset is beyond end of string to match", __FUNCTION__); + return -1; + } + +#ifdef PCRE_HAS_JIT_CODE + if (m_jitCompiled && !m_jitStack) + { + m_jitStack = pcre_jit_stack_alloc(32*1024, 512*1024); + if (m_jitStack == NULL) + CLog::Log(LOGWARNING, "%s: can't allocate address space for JIT stack", __FUNCTION__); + + pcre_assign_jit_stack(m_sd, NULL, m_jitStack); + } +#endif + + if (maxNumberOfCharsToTest >= 0) + bufferLen = std::min<size_t>(bufferLen, startoffset + maxNumberOfCharsToTest); + + m_subject.assign(str + startoffset, bufferLen - startoffset); + int rc = pcre_exec(m_re, NULL, m_subject.c_str(), m_subject.length(), 0, 0, m_iOvector, OVECCOUNT); + + if (rc<1) + { + static const int fragmentLen = 80; // length of excerpt before erroneous char for log + switch(rc) + { + case PCRE_ERROR_NOMATCH: + return -1; + + case PCRE_ERROR_MATCHLIMIT: + CLog::Log(LOGERROR, "PCRE: Match limit reached"); + return -1; + +#ifdef PCRE_ERROR_SHORTUTF8 + case PCRE_ERROR_SHORTUTF8: + { + const size_t startPos = (m_subject.length() > fragmentLen) ? CUtf8Utils::RFindValidUtf8Char(m_subject, m_subject.length() - fragmentLen) : 0; + if (startPos != std::string::npos) + CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character at the end of string. Text before bad character: \"%s\"", m_subject.substr(startPos).c_str()); + else + CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character at the end of string"); + return -1; + } +#endif + case PCRE_ERROR_BADUTF8: + { + const size_t startPos = (m_iOvector[0] > fragmentLen) ? CUtf8Utils::RFindValidUtf8Char(m_subject, m_iOvector[0] - fragmentLen) : 0; + if (m_iOvector[0] >= 0 && startPos != std::string::npos) + CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character, error code: %d, position: %d. Text before bad char: \"%s\"", m_iOvector[1], m_iOvector[0], m_subject.substr(startPos, m_iOvector[0] - startPos + 1).c_str()); + else + CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character, error code: %d, position: %d", m_iOvector[1], m_iOvector[0]); + return -1; + } + case PCRE_ERROR_BADUTF8_OFFSET: + CLog::Log(LOGERROR, "PCRE: Offset is pointing to the middle of UTF-8 character"); + return -1; + + default: + CLog::Log(LOGERROR, "PCRE: Unknown error: %d", rc); + return -1; + } + } + m_offset = startoffset; + m_bMatched = true; + m_iMatchCount = rc; + return m_iOvector[0] + m_offset; +} + +int CRegExp::GetCaptureTotal() const +{ + int c = -1; + if (m_re) + pcre_fullinfo(m_re, NULL, PCRE_INFO_CAPTURECOUNT, &c); + return c; +} + +std::string CRegExp::GetReplaceString(const std::string& sReplaceExp) const +{ + if (!m_bMatched || sReplaceExp.empty()) + return ""; + + const char* const expr = sReplaceExp.c_str(); + + size_t pos = sReplaceExp.find_first_of("\\&"); + std::string result(sReplaceExp, 0, pos); + result.reserve(sReplaceExp.size()); // very rough estimate + + while(pos != std::string::npos) + { + if (expr[pos] == '\\') + { + // string is null-terminated and current char isn't null, so it's safe to advance to next char + pos++; // advance to next char + const char nextChar = expr[pos]; + if (nextChar == '&' || nextChar == '\\') + { // this is "\&" or "\\" combination + result.push_back(nextChar); // add '&' or '\' to result + pos++; + } + else if (isdigit(nextChar)) + { // this is "\0" - "\9" combination + int subNum = nextChar - '0'; + pos++; // advance to second next char + const char secondNextChar = expr[pos]; + if (isdigit(secondNextChar)) + { // this is "\00" - "\99" combination + subNum = subNum * 10 + (secondNextChar - '0'); + pos++; + } + result.append(GetMatch(subNum)); + } + } + else + { // '&' char + result.append(GetMatch(0)); + pos++; + } + + const size_t nextPos = sReplaceExp.find_first_of("\\&", pos); + result.append(sReplaceExp, pos, nextPos - pos); + pos = nextPos; + } + + return result; +} + +int CRegExp::GetSubStart(int iSub) const +{ + if (!IsValidSubNumber(iSub)) + return -1; + + return m_iOvector[iSub*2] + m_offset; +} + +int CRegExp::GetSubStart(const std::string& subName) const +{ + return GetSubStart(GetNamedSubPatternNumber(subName.c_str())); +} + +int CRegExp::GetSubLength(int iSub) const +{ + if (!IsValidSubNumber(iSub)) + return -1; + + return m_iOvector[(iSub*2)+1] - m_iOvector[(iSub*2)]; +} + +int CRegExp::GetSubLength(const std::string& subName) const +{ + return GetSubLength(GetNamedSubPatternNumber(subName.c_str())); +} + +std::string CRegExp::GetMatch(int iSub /* = 0 */) const +{ + if (!IsValidSubNumber(iSub)) + return ""; + + int pos = m_iOvector[(iSub*2)]; + int len = m_iOvector[(iSub*2)+1] - pos; + if (pos < 0 || len <= 0) + return ""; + + return m_subject.substr(pos, len); +} + +std::string CRegExp::GetMatch(const std::string& subName) const +{ + return GetMatch(GetNamedSubPatternNumber(subName.c_str())); +} + +bool CRegExp::GetNamedSubPattern(const char* strName, std::string& strMatch) const +{ + strMatch.clear(); + int iSub = pcre_get_stringnumber(m_re, strName); + if (!IsValidSubNumber(iSub)) + return false; + strMatch = GetMatch(iSub); + return true; +} + +int CRegExp::GetNamedSubPatternNumber(const char* strName) const +{ + return pcre_get_stringnumber(m_re, strName); +} + +void CRegExp::DumpOvector(int iLog /* = LOGDEBUG */) +{ + if (iLog < LOGDEBUG || iLog > LOGNONE) + return; + + CStdString str = "{"; + int size = GetSubCount(); // past the subpatterns is junk + for (int i = 0; i <= size; i++) + { + CStdString t = StringUtils::Format("[%i,%i]", m_iOvector[(i*2)], m_iOvector[(i*2)+1]); + if (i != size) + t += ","; + str += t; + } + str += "}"; + CLog::Log(iLog, "regexp ovector=%s", str.c_str()); +} + +void CRegExp::Cleanup() +{ + if (m_re) + { + pcre_free(m_re); + m_re = NULL; + } + + if (m_sd) + { + pcre_free_study(m_sd); + m_sd = NULL; + } + +#ifdef PCRE_HAS_JIT_CODE + if (m_jitStack) + { + pcre_jit_stack_free(m_jitStack); + m_jitStack = NULL; + } +#endif +} + +inline bool CRegExp::IsValidSubNumber(int iSub) const +{ + return iSub >= 0 && iSub <= m_iMatchCount && iSub <= m_MaxNumOfBackrefrences; +} + + +bool CRegExp::IsUtf8Supported(void) +{ + if (m_Utf8Supported == -1) + { + if (pcre_config(PCRE_CONFIG_UTF8, &m_Utf8Supported) != 0) + m_Utf8Supported = 0; + } + + return m_Utf8Supported == 1; +} + +bool CRegExp::AreUnicodePropertiesSupported(void) +{ +#if defined(PCRE_CONFIG_UNICODE_PROPERTIES) && PCRE_UCP != 0 + if (m_UcpSupported == -1) + { + if (pcre_config(PCRE_CONFIG_UNICODE_PROPERTIES, &m_UcpSupported) != 0) + m_UcpSupported = 0; + } +#endif + + return m_UcpSupported == 1; +} + +bool CRegExp::LogCheckUtf8Support(void) +{ + bool utf8FullSupport = true; + + if (!CRegExp::IsUtf8Supported()) + { + utf8FullSupport = false; + CLog::Log(LOGWARNING, "UTF-8 is not supported in PCRE lib, support for national symbols is limited!"); + } + + if (!CRegExp::AreUnicodePropertiesSupported()) + { + utf8FullSupport = false; + CLog::Log(LOGWARNING, "Unicode properties are not enabled in PCRE lib, support for national symbols may be limited!"); + } + + if (!utf8FullSupport) + { + CLog::Log(LOGNOTICE, "Consider installing PCRE lib version 8.10 or later with enabled Unicode properties and UTF-8 support. Your PCRE lib version: %s", PCRE::pcre_version()); +#if PCRE_UCP == 0 + CLog::Log(LOGNOTICE, "You will need to rebuild XBMC after PCRE lib update."); +#endif + } + + return utf8FullSupport; +} + +bool CRegExp::IsJitSupported(void) +{ + if (m_JitSupported == -1) + { +#ifdef PCRE_HAS_JIT_CODE + if (pcre_config(PCRE_CONFIG_JIT, &m_JitSupported) != 0) +#endif + m_JitSupported = 0; + } + + return m_JitSupported == 1; +} diff --git a/src/utils/RegExp.h b/src/utils/RegExp.h new file mode 100644 index 0000000000..69483494ec --- /dev/null +++ b/src/utils/RegExp.h @@ -0,0 +1,182 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef REGEXP_H +#define REGEXP_H + +#include <string> +#include <vector> + +namespace PCRE { +struct real_pcre_jit_stack; // forward declaration for PCRE without JIT +typedef struct real_pcre_jit_stack pcre_jit_stack; +#ifdef TARGET_WINDOWS +#define PCRE_STATIC 1 +#ifdef _DEBUG +#pragma comment(lib, "pcred.lib") +#else // ! _DEBUG +#pragma comment(lib, "pcre.lib") +#endif // ! _DEBUG +#endif // TARGET_WINDOWS +#include <pcre.h> +} + +class CRegExp +{ +public: + enum studyMode + { + NoStudy = 0, // do not study expression + StudyRegExp = 1, // study expression (slower compilation, faster find) + StudyWithJitComp // study expression and JIT-compile it, if possible (heavyweight optimization) + }; + enum utf8Mode + { + autoUtf8 = -1, // analyze regexp for UTF-8 multi-byte chars, for Unicode codes > 0xFF + // or explicit Unicode properties (\p, \P and \X), enable UTF-8 mode if any of them are found + asciiOnly = 0, // process regexp and strings as single-byte encoded strings + forceUtf8 = 1 // enable UTF-8 mode (with Unicode properties) + }; + + static const int m_MaxNumOfBackrefrences = 20; + /** + * @param caseless (optional) Matching will be case insensitive if set to true + * or case sensitive if set to false + * @param utf8 (optional) Control UTF-8 processing + */ + CRegExp(bool caseless = false, utf8Mode utf8 = asciiOnly); + /** + * Create new CRegExp object and compile regexp expression in one step + * @warning Use only with hardcoded regexp when you're sure that regexp is compiled without errors + * @param caseless Matching will be case insensitive if set to true + * or case sensitive if set to false + * @param utf8 Control UTF-8 processing + * @param re The regular expression + * @param study (optional) Controls study of expression, useful if expression will be used + * several times + */ + CRegExp(bool caseless, utf8Mode utf8, const char *re, studyMode study = NoStudy); + + CRegExp(const CRegExp& re); + ~CRegExp(); + + /** + * Compile (prepare) regular expression + * @param re The regular expression + * @param study (optional) Controls study of expression, useful if expression will be used + * several times + * @return true on success, false on any error + */ + bool RegComp(const char *re, studyMode study = NoStudy); + + /** + * Compile (prepare) regular expression + * @param re The regular expression + * @param study (optional) Controls study of expression, useful if expression will be used + * several times + * @return true on success, false on any error + */ + bool RegComp(const std::string& re, studyMode study = NoStudy) + { return RegComp(re.c_str(), study); } + + /** + * Find first match of regular expression in given string + * @param str The string to match against regular expression + * @param startoffset (optional) The string offset to start matching + * @param maxNumberOfCharsToTest (optional) The maximum number of characters to test (match) in + * string. If set to -1 string checked up to the end. + * @return staring position of match in string, negative value in case of error or no match + */ + int RegFind(const char* str, unsigned int startoffset = 0, int maxNumberOfCharsToTest = -1); + /** + * Find first match of regular expression in given string + * @param str The string to match against regular expression + * @param startoffset (optional) The string offset to start matching + * @param maxNumberOfCharsToTest (optional) The maximum number of characters to test (match) in + * string. If set to -1 string checked up to the end. + * @return staring position of match in string, negative value in case of error or no match + */ + int RegFind(const std::string& str, unsigned int startoffset = 0, int maxNumberOfCharsToTest = -1) + { return PrivateRegFind(str.length(), str.c_str(), startoffset, maxNumberOfCharsToTest); } + std::string GetReplaceString(const std::string& sReplaceExp) const; + int GetFindLen() const + { + if (!m_re || !m_bMatched) + return 0; + + return (m_iOvector[1] - m_iOvector[0]); + }; + int GetSubCount() const { return m_iMatchCount - 1; } // PCRE returns the number of sub-patterns + 1 + int GetSubStart(int iSub) const; + int GetSubStart(const std::string& subName) const; + int GetSubLength(int iSub) const; + int GetSubLength(const std::string& subName) const; + int GetCaptureTotal() const; + std::string GetMatch(int iSub = 0) const; + std::string GetMatch(const std::string& subName) const; + const std::string& GetPattern() const { return m_pattern; } + bool GetNamedSubPattern(const char* strName, std::string& strMatch) const; + int GetNamedSubPatternNumber(const char* strName) const; + void DumpOvector(int iLog); + /** + * Check is RegExp object is ready for matching + * @return true if RegExp object is ready for matching, false otherwise + */ + inline bool IsCompiled(void) const + { return !m_pattern.empty(); } + CRegExp& operator= (const CRegExp& re); + static bool IsUtf8Supported(void); + static bool AreUnicodePropertiesSupported(void); + static bool LogCheckUtf8Support(void); + static bool IsJitSupported(void); + +private: + int PrivateRegFind(size_t bufferLen, const char *str, unsigned int startoffset = 0, int maxNumberOfCharsToTest = -1); + void InitValues(bool caseless = false, CRegExp::utf8Mode utf8 = asciiOnly); + static bool requireUtf8(const std::string& regexp); + static int readCharXCode(const std::string& regexp, size_t& pos); + static bool isCharClassWithUnicode(const std::string& regexp, size_t& pos); + + void Cleanup(); + inline bool IsValidSubNumber(int iSub) const; + + PCRE::pcre* m_re; + PCRE::pcre_extra* m_sd; + static const int OVECCOUNT=(m_MaxNumOfBackrefrences + 1) * 3; + unsigned int m_offset; + int m_iOvector[OVECCOUNT]; + utf8Mode m_utf8Mode; + int m_iMatchCount; + int m_iOptions; + bool m_jitCompiled; + bool m_bMatched; + PCRE::pcre_jit_stack* m_jitStack; + std::string m_subject; + std::string m_pattern; + static int m_Utf8Supported; + static int m_UcpSupported; + static int m_JitSupported; +}; + +typedef std::vector<CRegExp> VECCREGEXP; + +#endif + diff --git a/src/utils/RingBuffer.cpp b/src/utils/RingBuffer.cpp new file mode 100644 index 0000000000..1afc8940a2 --- /dev/null +++ b/src/utils/RingBuffer.cpp @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2010-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "RingBuffer.h" +#include "threads/SingleLock.h" + +#include <cstring> +#include <cstdlib> +#include <algorithm> + +/* Constructor */ +CRingBuffer::CRingBuffer() +{ + m_buffer = NULL; + m_size = 0; + m_readPtr = 0; + m_writePtr = 0; + m_fillCount = 0; +} + +/* Destructor */ +CRingBuffer::~CRingBuffer() +{ + Destroy(); +} + +/* Create a ring buffer with the specified 'size' */ +bool CRingBuffer::Create(unsigned int size) +{ + CSingleLock lock(m_critSection); + m_buffer = (char*)malloc(size); + if (m_buffer != NULL) + { + m_size = size; + return true; + } + return false; +} + +/* Free the ring buffer and set all values to NULL or 0 */ +void CRingBuffer::Destroy() +{ + CSingleLock lock(m_critSection); + if (m_buffer != NULL) + { + free(m_buffer); + m_buffer = NULL; + } + m_size = 0; + m_readPtr = 0; + m_writePtr = 0; + m_fillCount = 0; +} + +/* Clear the ring buffer */ +void CRingBuffer::Clear() +{ + CSingleLock lock(m_critSection); + m_readPtr = 0; + m_writePtr = 0; + m_fillCount = 0; +} + +/* Read in data from the ring buffer to the supplied buffer 'buf'. The amount + * read in is specified by 'size'. + */ +bool CRingBuffer::ReadData(char *buf, unsigned int size) +{ + CSingleLock lock(m_critSection); + if (size > m_fillCount) + { + return false; + } + if (size + m_readPtr > m_size) + { + unsigned int chunk = m_size - m_readPtr; + memcpy(buf, m_buffer + m_readPtr, chunk); + memcpy(buf + chunk, m_buffer, size - chunk); + m_readPtr = size - chunk; + } + else + { + memcpy(buf, m_buffer + m_readPtr, size); + m_readPtr += size; + } + if (m_readPtr == m_size) + m_readPtr = 0; + m_fillCount -= size; + return true; +} + +/* Read in data from the ring buffer to another ring buffer object specified by + * 'rBuf'. + */ +bool CRingBuffer::ReadData(CRingBuffer &rBuf, unsigned int size) +{ + CSingleLock lock(m_critSection); + if (rBuf.getBuffer() == NULL) + rBuf.Create(size); + + bool bOk = size <= rBuf.getMaxWriteSize() && size <= getMaxReadSize(); + if (bOk) + { + unsigned int chunksize = std::min(size, m_size - m_readPtr); + bOk = rBuf.WriteData(&getBuffer()[m_readPtr], chunksize); + if (bOk && chunksize < size) + bOk = rBuf.WriteData(&getBuffer()[0], size - chunksize); + if (bOk) + SkipBytes(size); + } + + return bOk; +} + +/* Write data to ring buffer from buffer specified in 'buf'. Amount read in is + * specified by 'size'. + */ +bool CRingBuffer::WriteData(const char *buf, unsigned int size) +{ + CSingleLock lock(m_critSection); + if (size > m_size - m_fillCount) + { + return false; + } + if (size + m_writePtr > m_size) + { + unsigned int chunk = m_size - m_writePtr; + memcpy(m_buffer + m_writePtr, buf, chunk); + memcpy(m_buffer, buf + chunk, size - chunk); + m_writePtr = size - chunk; + } + else + { + memcpy(m_buffer + m_writePtr, buf, size); + m_writePtr += size; + } + if (m_writePtr == m_size) + m_writePtr = 0; + m_fillCount += size; + return true; +} + +/* Write data to ring buffer from another ring buffer object specified by + * 'rBuf'. + */ +bool CRingBuffer::WriteData(CRingBuffer &rBuf, unsigned int size) +{ + CSingleLock lock(m_critSection); + if (m_buffer == NULL) + Create(size); + + bool bOk = size <= rBuf.getMaxReadSize() && size <= getMaxWriteSize(); + if (bOk) + { + unsigned int readpos = rBuf.getReadPtr(); + unsigned int chunksize = std::min(size, rBuf.getSize() - readpos); + bOk = WriteData(&rBuf.getBuffer()[readpos], chunksize); + if (bOk && chunksize < size) + bOk = WriteData(&rBuf.getBuffer()[0], size - chunksize); + } + + return bOk; +} + +/* Skip bytes in buffer to be read */ +bool CRingBuffer::SkipBytes(int skipSize) +{ + CSingleLock lock(m_critSection); + if (skipSize < 0) + { + return false; // skipping backwards is not supported + } + + unsigned int size = skipSize; + if (size > m_fillCount) + { + return false; + } + if (size + m_readPtr > m_size) + { + unsigned int chunk = m_size - m_readPtr; + m_readPtr = size - chunk; + } + else + { + m_readPtr += size; + } + if (m_readPtr == m_size) + m_readPtr = 0; + m_fillCount -= size; + return true; +} + +/* Append all content from ring buffer 'rBuf' to this ring buffer */ +bool CRingBuffer::Append(CRingBuffer &rBuf) +{ + return WriteData(rBuf, rBuf.getMaxReadSize()); +} + +/* Copy all content from ring buffer 'rBuf' to this ring buffer overwriting any existing data */ +bool CRingBuffer::Copy(CRingBuffer &rBuf) +{ + Clear(); + return Append(rBuf); +} + +/* Our various 'get' methods */ +char *CRingBuffer::getBuffer() +{ + return m_buffer; +} + +unsigned int CRingBuffer::getSize() +{ + CSingleLock lock(m_critSection); + return m_size; +} + +unsigned int CRingBuffer::getReadPtr() const +{ + return m_readPtr; +} + +unsigned int CRingBuffer::getWritePtr() +{ + CSingleLock lock(m_critSection); + return m_writePtr; +} + +unsigned int CRingBuffer::getMaxReadSize() +{ + CSingleLock lock(m_critSection); + return m_fillCount; +} + +unsigned int CRingBuffer::getMaxWriteSize() +{ + CSingleLock lock(m_critSection); + return m_size - m_fillCount; +} diff --git a/src/utils/RingBuffer.h b/src/utils/RingBuffer.h new file mode 100644 index 0000000000..2264e8e06a --- /dev/null +++ b/src/utils/RingBuffer.h @@ -0,0 +1,51 @@ +#pragma once +/* + * Copyright (C) 2010-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "threads/CriticalSection.h" + +class CRingBuffer +{ + CCriticalSection m_critSection; + char *m_buffer; + unsigned int m_size; + unsigned int m_readPtr; + unsigned int m_writePtr; + unsigned int m_fillCount; +public: + CRingBuffer(); + ~CRingBuffer(); + bool Create(unsigned int size); + void Destroy(); + void Clear(); + bool ReadData(char *buf, unsigned int size); + bool ReadData(CRingBuffer &rBuf, unsigned int size); + bool WriteData(const char *buf, unsigned int size); + bool WriteData(CRingBuffer &rBuf, unsigned int size); + bool SkipBytes(int skipSize); + bool Append(CRingBuffer &rBuf); + bool Copy(CRingBuffer &rBuf); + char *getBuffer(); + unsigned int getSize(); + unsigned int getReadPtr() const; + unsigned int getWritePtr(); + unsigned int getMaxReadSize(); + unsigned int getMaxWriteSize(); +}; diff --git a/src/utils/RssManager.cpp b/src/utils/RssManager.cpp new file mode 100644 index 0000000000..b4fd24e353 --- /dev/null +++ b/src/utils/RssManager.cpp @@ -0,0 +1,200 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "RssManager.h" +#include "addons/AddonInstaller.h" +#include "addons/AddonManager.h" +#include "dialogs/GUIDialogYesNo.h" +#include "filesystem/File.h" +#include "interfaces/Builtins.h" +#include "profiles/ProfilesManager.h" +#include "settings/lib/Setting.h" +#include "threads/SingleLock.h" +#include "utils/log.h" +#include "utils/RssReader.h" +#include "utils/StringUtils.h" + +using namespace std; +using namespace XFILE; + +CRssManager::CRssManager() +{ + m_bActive = false; +} + +CRssManager::~CRssManager() +{ + Stop(); +} + +CRssManager& CRssManager::Get() +{ + static CRssManager sRssManager; + return sRssManager; +} + +void CRssManager::OnSettingsLoaded() +{ + Load(); +} + +void CRssManager::OnSettingsUnloaded() +{ + Clear(); +} + +void CRssManager::OnSettingAction(const CSetting *setting) +{ + if (setting == NULL) + return; + + const std::string &settingId = setting->GetId(); + if (settingId == "lookandfeel.rssedit") + { + ADDON::AddonPtr addon; + ADDON::CAddonMgr::Get().GetAddon("script.rss.editor",addon); + if (!addon) + { + if (!CGUIDialogYesNo::ShowAndGetInput(g_localizeStrings.Get(24076), g_localizeStrings.Get(24100), "RSS Editor", g_localizeStrings.Get(24101))) + return; + CAddonInstaller::Get().Install("script.rss.editor", true, "", false); + } + CBuiltins::Execute("RunScript(script.rss.editor)"); + } +} + +void CRssManager::Start() + { + m_bActive = true; +} + +void CRssManager::Stop() +{ + CSingleLock lock(m_critical); + m_bActive = false; + for (unsigned int i = 0; i < m_readers.size(); i++) + { + if (m_readers[i].reader) + delete m_readers[i].reader; + } + m_readers.clear(); +} + +bool CRssManager::Load() +{ + CSingleLock lock(m_critical); + string rssXML = CProfilesManager::Get().GetUserDataItem("RssFeeds.xml"); + if (!CFile::Exists(rssXML)) + return false; + + CXBMCTinyXML rssDoc; + if (!rssDoc.LoadFile(rssXML)) + { + CLog::Log(LOGERROR, "CRssManager: error loading %s, Line %d\n%s", rssXML.c_str(), rssDoc.ErrorRow(), rssDoc.ErrorDesc()); + return false; + } + + const TiXmlElement *pRootElement = rssDoc.RootElement(); + if (pRootElement == NULL || !StringUtils::EqualsNoCase(pRootElement->ValueStr(), "rssfeeds")) + { + CLog::Log(LOGERROR, "CRssManager: error loading %s, no <rssfeeds> node", rssXML.c_str()); + return false; + } + + m_mapRssUrls.clear(); + const TiXmlElement* pSet = pRootElement->FirstChildElement("set"); + while (pSet != NULL) + { + int iId; + if (pSet->QueryIntAttribute("id", &iId) == TIXML_SUCCESS) + { + RssSet set; + set.rtl = pSet->Attribute("rtl") != NULL && strcasecmp(pSet->Attribute("rtl"), "true") == 0; + const TiXmlElement* pFeed = pSet->FirstChildElement("feed"); + while (pFeed != NULL) + { + int iInterval; + if (pFeed->QueryIntAttribute("updateinterval", &iInterval) != TIXML_SUCCESS) + { + iInterval = 30; // default to 30 min + CLog::Log(LOGDEBUG, "CRssManager: no interval set, default to 30!"); + } + + if (pFeed->FirstChild() != NULL) + { + // TODO: UTF-8: Do these URLs need to be converted to UTF-8? + // What about the xml encoding? + string strUrl = pFeed->FirstChild()->ValueStr(); + set.url.push_back(strUrl); + set.interval.push_back(iInterval); + } + pFeed = pFeed->NextSiblingElement("feed"); + } + + m_mapRssUrls.insert(make_pair(iId,set)); + } + else + CLog::Log(LOGERROR, "CRssManager: found rss url set with no id in RssFeeds.xml, ignored"); + + pSet = pSet->NextSiblingElement("set"); + } + + return true; +} + +bool CRssManager::Reload() +{ + Stop(); + if (!Load()) + return false; + Start(); + + return true; +} + +void CRssManager::Clear() +{ + CSingleLock lock(m_critical); + m_mapRssUrls.clear(); +} + +// returns true if the reader doesn't need creating, false otherwise +bool CRssManager::GetReader(int controlID, int windowID, IRssObserver* observer, CRssReader *&reader) +{ + CSingleLock lock(m_critical); + // check to see if we've already created this reader + for (unsigned int i = 0; i < m_readers.size(); i++) + { + if (m_readers[i].controlID == controlID && m_readers[i].windowID == windowID) + { + reader = m_readers[i].reader; + reader->SetObserver(observer); + reader->UpdateObserver(); + return true; + } + } + // need to create a new one + READERCONTROL readerControl; + readerControl.controlID = controlID; + readerControl.windowID = windowID; + reader = readerControl.reader = new CRssReader; + m_readers.push_back(readerControl); + return false; +} diff --git a/src/utils/RssManager.h b/src/utils/RssManager.h new file mode 100644 index 0000000000..36b8e85bc6 --- /dev/null +++ b/src/utils/RssManager.h @@ -0,0 +1,80 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> +#include <vector> +#include <string> + +#include "threads/CriticalSection.h" + +#include "settings/lib/ISettingCallback.h" +#include "settings/lib/ISettingsHandler.h" + +class CRssReader; +class IRssObserver; + +typedef struct +{ + bool rtl; + std::vector<int> interval; + std::vector<std::string> url; +} RssSet; +typedef std::map<int, RssSet> RssUrls; + +class CRssManager : public ISettingCallback, public ISettingsHandler +{ +public: + static CRssManager& Get(); + + virtual void OnSettingsLoaded(); + virtual void OnSettingsUnloaded(); + + virtual void OnSettingAction(const CSetting *setting); + + void Start(); + void Stop(); + bool Load(); + bool Reload(); + void Clear(); + bool IsActive() const { return m_bActive; } + + bool GetReader(int controlID, int windowID, IRssObserver* observer, CRssReader *&reader); + const RssUrls& GetUrls() const { return m_mapRssUrls; } + +protected: + CRssManager(); + ~CRssManager(); + +private: + CRssManager(const CRssManager&); + CRssManager& operator=(const CRssManager&); + struct READERCONTROL + { + int controlID; + int windowID; + CRssReader *reader; + }; + + std::vector<READERCONTROL> m_readers; + RssUrls m_mapRssUrls; + bool m_bActive; + CCriticalSection m_critical; +}; diff --git a/src/utils/RssReader.cpp b/src/utils/RssReader.cpp new file mode 100644 index 0000000000..53831562db --- /dev/null +++ b/src/utils/RssReader.cpp @@ -0,0 +1,422 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "network/Network.h" +#include "threads/SystemClock.h" +#include "RssReader.h" +#include "utils/HTMLUtil.h" +#include "Application.h" +#include "CharsetConverter.h" +#include "StringUtils.h" +#include "URL.h" +#include "filesystem/File.h" +#include "filesystem/CurlFile.h" +#if defined(TARGET_DARWIN) +#include "osx/CocoaInterface.h" +#endif +#include "settings/AdvancedSettings.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/GUIRSSControl.h" +#include "utils/TimeUtils.h" +#include "threads/SingleLock.h" +#include "log.h" +#include "utils/FileUtils.h" + +#define RSS_COLOR_BODY 0 +#define RSS_COLOR_HEADLINE 1 +#define RSS_COLOR_CHANNEL 2 + +using namespace std; +using namespace XFILE; + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CRssReader::CRssReader() : CThread("RSSReader") +{ + m_pObserver = NULL; + m_spacesBetweenFeeds = 0; + m_bIsRunning = false; + m_SavedScrollPos = 0; + m_rtlText = false; + m_requestRefresh = false; +} + +CRssReader::~CRssReader() +{ + if (m_pObserver) + m_pObserver->OnFeedRelease(); + StopThread(); + for (unsigned int i = 0; i < m_vecTimeStamps.size(); i++) + delete m_vecTimeStamps[i]; +} + +void CRssReader::Create(IRssObserver* aObserver, const vector<string>& aUrls, const vector<int> ×, int spacesBetweenFeeds, bool rtl) +{ + CSingleLock lock(m_critical); + + m_pObserver = aObserver; + m_spacesBetweenFeeds = spacesBetweenFeeds; + m_vecUrls = aUrls; + m_strFeed.resize(aUrls.size()); + m_strColors.resize(aUrls.size()); + // set update times + m_vecUpdateTimes = times; + m_rtlText = rtl; + m_requestRefresh = false; + + // update each feed on creation + for (unsigned int i = 0; i < m_vecUpdateTimes.size(); ++i) + { + AddToQueue(i); + SYSTEMTIME* time = new SYSTEMTIME; + GetLocalTime(time); + m_vecTimeStamps.push_back(time); + } +} + +void CRssReader::requestRefresh() +{ + m_requestRefresh = true; +} + +void CRssReader::AddToQueue(int iAdd) +{ + CSingleLock lock(m_critical); + if (iAdd < (int)m_vecUrls.size()) + m_vecQueue.push_back(iAdd); + if (!m_bIsRunning) + { + StopThread(); + m_bIsRunning = true; + CThread::Create(false, THREAD_MINSTACKSIZE); + } +} + +void CRssReader::OnExit() +{ + m_bIsRunning = false; +} + +int CRssReader::GetQueueSize() +{ + CSingleLock lock(m_critical); + return m_vecQueue.size(); +} + +void CRssReader::Process() +{ + while (GetQueueSize()) + { + CSingleLock lock(m_critical); + + int iFeed = m_vecQueue.front(); + m_vecQueue.erase(m_vecQueue.begin()); + + m_strFeed[iFeed].clear(); + m_strColors[iFeed].clear(); + + CCurlFile http; + http.SetUserAgent(g_advancedSettings.m_userAgent); + http.SetTimeout(2); + std::string strXML; + std::string strUrl = m_vecUrls[iFeed]; + lock.Leave(); + + int nRetries = 3; + CURL url(strUrl); + std::string fileCharset; + + // we wait for the network to come up + if ((url.IsProtocol("http") || url.IsProtocol("https")) && + !g_application.getNetwork().IsAvailable(true)) + { + CLog::Log(LOGWARNING, "RSS: No network connection"); + strXML = "<rss><item><title>"+g_localizeStrings.Get(15301)+"</title></item></rss>"; + } + else + { + XbmcThreads::EndTime timeout(15000); + while (!m_bStop && nRetries > 0) + { + if (timeout.IsTimePast()) + { + CLog::Log(LOGERROR, "Timeout whilst retrieving %s", strUrl.c_str()); + http.Cancel(); + break; + } + nRetries--; + + if (!url.IsProtocol("http") && !url.IsProtocol("https")) + { + CFile file; + auto_buffer buffer; + if (file.LoadFile(strUrl, buffer) > 0) + { + strXML.assign(buffer.get(), buffer.length()); + break; + } + } + else + if (http.Get(strUrl, strXML)) + { + fileCharset = http.GetServerReportedCharset(); + CLog::Log(LOGDEBUG, "Got rss feed: %s", strUrl.c_str()); + break; + } + } + http.Cancel(); + } + if (!strXML.empty() && m_pObserver) + { + // erase any <content:encoded> tags (also unsupported by tinyxml) + size_t iStart = strXML.find("<content:encoded>"); + size_t iEnd = 0; + while (iStart != std::string::npos) + { + // get <content:encoded> end position + iEnd = strXML.find("</content:encoded>", iStart) + 18; + + // erase the section + strXML = strXML.erase(iStart, iEnd - iStart); + + iStart = strXML.find("<content:encoded>"); + } + + if (Parse(strXML, iFeed, fileCharset)) + CLog::Log(LOGDEBUG, "Parsed rss feed: %s", strUrl.c_str()); + } + } + UpdateObserver(); +} + +void CRssReader::getFeed(vecText &text) +{ + text.clear(); + // double the spaces at the start of the set + for (int j = 0; j < m_spacesBetweenFeeds; j++) + text.push_back(L' '); + for (unsigned int i = 0; i < m_strFeed.size(); i++) + { + for (int j = 0; j < m_spacesBetweenFeeds; j++) + text.push_back(L' '); + + for (unsigned int j = 0; j < m_strFeed[i].size(); j++) + { + character_t letter = m_strFeed[i][j] | ((m_strColors[i][j] - 48) << 16); + text.push_back(letter); + } + } +} + +void CRssReader::AddTag(const std::string &aString) +{ + m_tagSet.push_back(aString); +} + +void CRssReader::AddString(std::wstring aString, int aColour, int iFeed) +{ + if (m_rtlText) + m_strFeed[iFeed] = aString + m_strFeed[iFeed]; + else + m_strFeed[iFeed] += aString; + + size_t nStringLength = aString.size(); + + for (size_t i = 0;i < nStringLength;i++) + aString[i] = (CHAR) (48 + aColour); + + if (m_rtlText) + m_strColors[iFeed] = aString + m_strColors[iFeed]; + else + m_strColors[iFeed] += aString; +} + +void CRssReader::GetNewsItems(TiXmlElement* channelXmlNode, int iFeed) +{ + HTML::CHTMLUtil html; + + TiXmlElement * itemNode = channelXmlNode->FirstChildElement("item"); + map <std::string, std::wstring> mTagElements; + typedef pair <std::string, std::wstring> StrPair; + list <std::string>::iterator i; + + // Add the title tag in if we didn't pass any tags in at all + // Represents default behaviour before configurability + + if (m_tagSet.empty()) + AddTag("title"); + + while (itemNode > 0) + { + TiXmlNode* childNode = itemNode->FirstChild(); + mTagElements.clear(); + while (childNode > 0) + { + std::string strName = childNode->ValueStr(); + + for (i = m_tagSet.begin(); i != m_tagSet.end(); ++i) + { + if (!childNode->NoChildren() && *i == strName) + { + std::string htmlText = childNode->FirstChild()->ValueStr(); + + // This usually happens in right-to-left languages where they want to + // specify in the RSS body that the text should be RTL. + // <title> + // <div dir="RTL">��� ����: ���� �� �����</div> + // </title> + if (htmlText == "div" || htmlText == "span") + htmlText = childNode->FirstChild()->FirstChild()->ValueStr(); + + std::wstring unicodeText, unicodeText2; + + g_charsetConverter.utf8ToW(htmlText, unicodeText2, m_rtlText); + html.ConvertHTMLToW(unicodeText2, unicodeText); + + mTagElements.insert(StrPair(*i, unicodeText)); + } + } + childNode = childNode->NextSibling(); + } + + int rsscolour = RSS_COLOR_HEADLINE; + for (i = m_tagSet.begin(); i != m_tagSet.end(); ++i) + { + map <std::string, std::wstring>::iterator j = mTagElements.find(*i); + + if (j == mTagElements.end()) + continue; + + std::wstring& text = j->second; + AddString(text, rsscolour, iFeed); + rsscolour = RSS_COLOR_BODY; + text = L" - "; + AddString(text, rsscolour, iFeed); + } + itemNode = itemNode->NextSiblingElement("item"); + } +} + +bool CRssReader::Parse(const std::string& data, int iFeed, const std::string& charset) +{ + m_xml.Clear(); + m_xml.Parse(data, charset); + + CLog::Log(LOGDEBUG, "RSS feed encoding: %s", m_xml.GetUsedCharset().c_str()); + + return Parse(iFeed); +} + +bool CRssReader::Parse(int iFeed) +{ + TiXmlElement* rootXmlNode = m_xml.RootElement(); + + if (!rootXmlNode) + return false; + + TiXmlElement* rssXmlNode = NULL; + + std::string strValue = rootXmlNode->ValueStr(); + if (strValue.find("rss") != std::string::npos || + strValue.find("rdf") != std::string::npos) + rssXmlNode = rootXmlNode; + else + { + // Unable to find root <rss> or <rdf> node + return false; + } + + TiXmlElement* channelXmlNode = rssXmlNode->FirstChildElement("channel"); + if (channelXmlNode) + { + TiXmlElement* titleNode = channelXmlNode->FirstChildElement("title"); + if (titleNode && !titleNode->NoChildren()) + { + std::string strChannel = titleNode->FirstChild()->Value(); + std::wstring strChannelUnicode; + g_charsetConverter.utf8ToW(strChannel, strChannelUnicode, m_rtlText); + AddString(strChannelUnicode, RSS_COLOR_CHANNEL, iFeed); + + AddString(L":", RSS_COLOR_CHANNEL, iFeed); + AddString(L" ", RSS_COLOR_CHANNEL, iFeed); + } + + GetNewsItems(channelXmlNode,iFeed); + } + + GetNewsItems(rssXmlNode,iFeed); + + // avoid trailing ' - ' + if (m_strFeed[iFeed].size() > 3 && m_strFeed[iFeed].substr(m_strFeed[iFeed].size() - 3) == L" - ") + { + if (m_rtlText) + { + m_strFeed[iFeed].erase(0, 3); + m_strColors[iFeed].erase(0, 3); + } + else + { + m_strFeed[iFeed].erase(m_strFeed[iFeed].length() - 3); + m_strColors[iFeed].erase(m_strColors[iFeed].length() - 3); + } + } + return true; +} + +void CRssReader::SetObserver(IRssObserver *observer) +{ + m_pObserver = observer; +} + +void CRssReader::UpdateObserver() +{ + if (!m_pObserver) + return; + + vecText feed; + getFeed(feed); + if (feed.size() > 0) + { + CSingleLock lock(g_graphicsContext); + if (m_pObserver) // need to check again when locked to make sure observer wasnt removed + m_pObserver->OnFeedUpdate(feed); + } +} + +void CRssReader::CheckForUpdates() +{ + SYSTEMTIME time; + GetLocalTime(&time); + + for (unsigned int i = 0;i < m_vecUpdateTimes.size(); ++i ) + { + if (m_requestRefresh || + ((time.wDay * 24 * 60) + (time.wHour * 60) + time.wMinute) - ((m_vecTimeStamps[i]->wDay * 24 * 60) + (m_vecTimeStamps[i]->wHour * 60) + m_vecTimeStamps[i]->wMinute) > m_vecUpdateTimes[i]) + { + CLog::Log(LOGDEBUG, "Updating RSS"); + GetLocalTime(m_vecTimeStamps[i]); + AddToQueue(i); + } + } + + m_requestRefresh = false; +} diff --git a/src/utils/RssReader.h b/src/utils/RssReader.h new file mode 100644 index 0000000000..2cda7267f1 --- /dev/null +++ b/src/utils/RssReader.h @@ -0,0 +1,73 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <list> +#include <string> +#include <vector> + +#include "threads/CriticalSection.h" +#include "threads/Thread.h" +#include "utils/IRssObserver.h" +#include "utils/XBMCTinyXML.h" + +class CRssReader : public CThread +{ +public: + CRssReader(); + virtual ~CRssReader(); + + void Create(IRssObserver* aObserver, const std::vector<std::string>& aUrl, const std::vector<int>& times, int spacesBetweenFeeds, bool rtl); + bool Parse(const std::string& data, int iFeed, const std::string& charset); + void getFeed(vecText &text); + void AddTag(const std::string &addTag); + void AddToQueue(int iAdd); + void UpdateObserver(); + void SetObserver(IRssObserver* observer); + void CheckForUpdates(); + void requestRefresh(); + unsigned int m_SavedScrollPos; + +private: + void Process(); + bool Parse(int iFeed); + void GetNewsItems(TiXmlElement* channelXmlNode, int iFeed); + void AddString(std::wstring aString, int aColour, int iFeed); + void UpdateFeed(); + virtual void OnExit(); + int GetQueueSize(); + + IRssObserver* m_pObserver; + + std::vector<std::wstring> m_strFeed; + std::vector<std::wstring> m_strColors; + std::vector<SYSTEMTIME *> m_vecTimeStamps; + std::vector<int> m_vecUpdateTimes; + int m_spacesBetweenFeeds; + CXBMCTinyXML m_xml; + std::list<std::string> m_tagSet; + std::vector<std::string> m_vecUrls; + std::vector<int> m_vecQueue; + bool m_bIsRunning; + bool m_rtlText; + bool m_requestRefresh; + + CCriticalSection m_critical; +}; diff --git a/src/utils/SaveFileStateJob.cpp b/src/utils/SaveFileStateJob.cpp new file mode 100644 index 0000000000..3be229bb38 --- /dev/null +++ b/src/utils/SaveFileStateJob.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2010-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "SaveFileStateJob.h" +#include "pvr/PVRManager.h" +#include "pvr/recordings/PVRRecordings.h" +#include "settings/MediaSettings.h" +#include "network/upnp/UPnP.h" +#include "StringUtils.h" +#include "Variant.h" +#include "URIUtils.h" +#include "URL.h" +#include "log.h" +#include "video/VideoDatabase.h" +#include "interfaces/AnnouncementManager.h" +#include "Util.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "GUIUserMessages.h" +#include "music/MusicDatabase.h" + +bool CSaveFileStateJob::DoWork() +{ + std::string progressTrackingFile = m_item.GetPath(); + + if (m_item.HasVideoInfoTag() && StringUtils::StartsWith(m_item.GetVideoInfoTag()->m_strFileNameAndPath, "removable://")) + progressTrackingFile = m_item.GetVideoInfoTag()->m_strFileNameAndPath; // this variable contains removable:// suffixed by disc label+uniqueid or is empty if label not uniquely identified + else if (m_item.HasProperty("original_listitem_url")) + { + // only use original_listitem_url for Python, UPnP and Bluray sources + std::string original = m_item.GetProperty("original_listitem_url").asString(); + if (URIUtils::IsPlugin(original) || URIUtils::IsUPnP(original) || URIUtils::IsBluray(m_item.GetPath())) + progressTrackingFile = original; + } + + if (progressTrackingFile != "") + { +#ifdef HAS_UPNP + // checks if UPnP server of this file is available and supports updating + if (URIUtils::IsUPnP(progressTrackingFile) + && UPNP::CUPnP::SaveFileState(m_item, m_bookmark, m_updatePlayCount)) { + return true; + } +#endif + if (m_item.IsVideo()) + { + std::string redactPath = CURL::GetRedacted(progressTrackingFile); + CLog::Log(LOGDEBUG, "%s - Saving file state for video item %s", __FUNCTION__, redactPath.c_str()); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + { + CLog::Log(LOGWARNING, "%s - Unable to open video database. Can not save file state!", __FUNCTION__); + } + else + { + bool updateListing = false; + // No resume & watched status for livetv + if (!m_item.IsLiveTV()) + { + if (m_updatePlayCount) + { + CLog::Log(LOGDEBUG, "%s - Marking video item %s as watched", __FUNCTION__, redactPath.c_str()); + + // consider this item as played + videodatabase.IncrementPlayCount(m_item); + m_item.GetVideoInfoTag()->m_playCount++; + + // PVR: Set recording's play count on the backend (if supported) + if (m_item.HasPVRRecordingInfoTag()) + m_item.GetPVRRecordingInfoTag()->IncrementPlayCount(); + + m_item.SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, true); + updateListing = true; + } + else + videodatabase.UpdateLastPlayed(m_item); + + if (!m_item.HasVideoInfoTag() || m_item.GetVideoInfoTag()->m_resumePoint.timeInSeconds != m_bookmark.timeInSeconds) + { + if (m_bookmark.timeInSeconds <= 0.0f) + videodatabase.ClearBookMarksOfFile(progressTrackingFile, CBookmark::RESUME); + else + videodatabase.AddBookMarkToFile(progressTrackingFile, m_bookmark, CBookmark::RESUME); + if (m_item.HasVideoInfoTag()) + m_item.GetVideoInfoTag()->m_resumePoint = m_bookmark; + + // PVR: Set/clear recording's resume bookmark on the backend (if supported) + if (m_item.HasPVRRecordingInfoTag()) + { + PVR::CPVRRecording *recording = m_item.GetPVRRecordingInfoTag(); + recording->SetLastPlayedPosition(m_bookmark.timeInSeconds <= 0.0f ? 0 : (int)m_bookmark.timeInSeconds); + recording->m_resumePoint = m_bookmark; + } + + // UPnP announce resume point changes to clients + // however not if playcount is modified as that already announces + if (m_item.IsVideoDb() && !m_updatePlayCount) + { + CVariant data; + data["id"] = m_item.GetVideoInfoTag()->m_iDbId; + data["type"] = m_item.GetVideoInfoTag()->m_type; + ANNOUNCEMENT::CAnnouncementManager::Get().Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnUpdate", data); + } + + updateListing = true; + } + } + + if (m_videoSettings != CMediaSettings::Get().GetDefaultVideoSettings()) + { + videodatabase.SetVideoSettings(progressTrackingFile, m_videoSettings); + } + + if (m_item.HasVideoInfoTag() && m_item.GetVideoInfoTag()->HasStreamDetails()) + { + CFileItem dbItem(m_item); + + // Check whether the item's db streamdetails need updating + if (!videodatabase.GetStreamDetails(dbItem) || dbItem.GetVideoInfoTag()->m_streamDetails != m_item.GetVideoInfoTag()->m_streamDetails) + { + videodatabase.SetStreamDetailsForFile(m_item.GetVideoInfoTag()->m_streamDetails, progressTrackingFile); + updateListing = true; + } + } + + // in order to properly update the the list, we need to update the stack item which is held in g_application.m_stackFileItemToUpdate + if (m_item.HasProperty("stackFileItemToUpdate")) + { + m_item = m_item_discstack; // as of now, the item is replaced by the discstack item + videodatabase.GetResumePoint(*m_item.GetVideoInfoTag()); + } + videodatabase.Close(); + + if (updateListing) + { + CUtil::DeleteVideoDatabaseDirectoryCache(); + CFileItemPtr msgItem(new CFileItem(m_item)); + if (m_item.HasProperty("original_listitem_url")) + msgItem->SetPath(m_item.GetProperty("original_listitem_url").asString()); + CGUIMessage message(GUI_MSG_NOTIFY_ALL, g_windowManager.GetActiveWindow(), 0, GUI_MSG_UPDATE_ITEM, 1, msgItem); // 1 to update the listing as well + g_windowManager.SendThreadMessage(message); + } + } + } + + if (m_item.IsAudio()) + { + std::string redactPath = CURL::GetRedacted(progressTrackingFile); + CLog::Log(LOGDEBUG, "%s - Saving file state for audio item %s", __FUNCTION__, redactPath.c_str()); + + if (m_updatePlayCount) + { +#if 0 + // Can't write to the musicdatabase while scanning for music info + CGUIDialogMusicScan *dialog = (CGUIDialogMusicScan *)g_windowManager.GetWindow(WINDOW_DIALOG_MUSIC_SCAN); + if (dialog && !dialog->IsDialogRunning()) +#endif + { + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + { + CLog::Log(LOGWARNING, "%s - Unable to open music database. Can not save file state!", __FUNCTION__); + } + else + { + // consider this item as played + CLog::Log(LOGDEBUG, "%s - Marking audio item %s as listened", __FUNCTION__, redactPath.c_str()); + + musicdatabase.IncrementPlayCount(m_item); + musicdatabase.Close(); + } + } + } + } + } + return true; +} diff --git a/src/utils/SaveFileStateJob.h b/src/utils/SaveFileStateJob.h new file mode 100644 index 0000000000..90b5e5702e --- /dev/null +++ b/src/utils/SaveFileStateJob.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2010-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 + * <http://www.gnu.org/licenses/>. + * + */ +#ifndef SAVE_FILE_STATE_H__ +#define SAVE_FILE_STATE_H__ + +#include "Job.h" +#include "FileItem.h" +#include "video/Bookmark.h" +#include "settings/VideoSettings.h" + +class CSaveFileStateJob : public CJob +{ + CFileItem m_item; + CFileItem m_item_discstack; + CBookmark m_bookmark; + bool m_updatePlayCount; + CVideoSettings m_videoSettings; +public: + CSaveFileStateJob(const CFileItem& item, + const CFileItem& item_discstack, + const CBookmark& bookmark, + bool updatePlayCount, + const CVideoSettings &videoSettings) + : m_item(item), + m_item_discstack(item_discstack), + m_bookmark(bookmark), + m_updatePlayCount(updatePlayCount), + m_videoSettings(videoSettings) {} + virtual ~CSaveFileStateJob() {} + virtual bool DoWork(); +}; + +#endif // SAVE_FILE_STATE_H__ diff --git a/src/utils/ScraperParser.cpp b/src/utils/ScraperParser.cpp new file mode 100644 index 0000000000..4dc3b485b7 --- /dev/null +++ b/src/utils/ScraperParser.cpp @@ -0,0 +1,616 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "ScraperParser.h" + +#include "addons/AddonManager.h" +#include "RegExp.h" +#include "HTMLUtil.h" +#include "addons/Scraper.h" +#include "URL.h" +#include "Util.h" +#include "utils/StringUtils.h" +#include "log.h" +#include "CharsetConverter.h" +#include "utils/StringUtils.h" +#include "utils/XSLTUtils.h" +#include "utils/XMLUtils.h" +#include <sstream> +#include <cstring> + +using namespace std; +using namespace ADDON; +using namespace XFILE; + +CScraperParser::CScraperParser() +{ + m_pRootElement = NULL; + m_document = NULL; + m_SearchStringEncoding = "UTF-8"; + m_scraper = NULL; + m_isNoop = true; +} + +CScraperParser::CScraperParser(const CScraperParser& parser) +{ + m_pRootElement = NULL; + m_document = NULL; + m_SearchStringEncoding = "UTF-8"; + m_scraper = NULL; + m_isNoop = true; + *this = parser; +} + +CScraperParser &CScraperParser::operator=(const CScraperParser &parser) +{ + if (this != &parser) + { + Clear(); + if (parser.m_document) + { + m_scraper = parser.m_scraper; + m_document = new CXBMCTinyXML(*parser.m_document); + LoadFromXML(); + } + else + m_scraper = NULL; + } + return *this; +} + +CScraperParser::~CScraperParser() +{ + Clear(); +} + +void CScraperParser::Clear() +{ + m_pRootElement = NULL; + delete m_document; + + m_document = NULL; + m_strFile.clear(); +} + +bool CScraperParser::Load(const std::string& strXMLFile) +{ + Clear(); + + m_document = new CXBMCTinyXML(); + + if (!m_document) + return false; + + m_strFile = strXMLFile; + + if (m_document->LoadFile(strXMLFile)) + return LoadFromXML(); + + delete m_document; + m_document = NULL; + return false; +} + +bool CScraperParser::LoadFromXML() +{ + if (!m_document) + return false; + + m_pRootElement = m_document->RootElement(); + std::string strValue = m_pRootElement->ValueStr(); + if (strValue == "scraper") + { + TiXmlElement* pChildElement = m_pRootElement->FirstChildElement("CreateSearchUrl"); + if (pChildElement) + { + m_isNoop = false; + if (!(m_SearchStringEncoding = pChildElement->Attribute("SearchStringEncoding"))) + m_SearchStringEncoding = "UTF-8"; + } + + pChildElement = m_pRootElement->FirstChildElement("CreateArtistSearchUrl"); + if (pChildElement) + { + m_isNoop = false; + if (!(m_SearchStringEncoding = pChildElement->Attribute("SearchStringEncoding"))) + m_SearchStringEncoding = "UTF-8"; + } + pChildElement = m_pRootElement->FirstChildElement("CreateAlbumSearchUrl"); + if (pChildElement) + { + m_isNoop = false; + if (!(m_SearchStringEncoding = pChildElement->Attribute("SearchStringEncoding"))) + m_SearchStringEncoding = "UTF-8"; + } + + return true; + } + + delete m_document; + m_document = NULL; + m_pRootElement = NULL; + return false; +} + +void CScraperParser::ReplaceBuffers(std::string& strDest) +{ + // insert buffers + size_t iIndex; + for (int i=MAX_SCRAPER_BUFFERS-1; i>=0; i--) + { + iIndex = 0; + std::string temp = StringUtils::Format("$$%i",i+1); + while ((iIndex = strDest.find(temp,iIndex)) != std::string::npos) + { + strDest.replace(strDest.begin()+iIndex,strDest.begin()+iIndex+temp.size(),m_param[i]); + iIndex += m_param[i].length(); + } + } + // insert settings + iIndex = 0; + while ((iIndex = strDest.find("$INFO[", iIndex)) != std::string::npos) + { + size_t iEnd = strDest.find("]", iIndex); + std::string strInfo = strDest.substr(iIndex+6, iEnd - iIndex - 6); + std::string strReplace; + if (m_scraper) + strReplace = m_scraper->GetSetting(strInfo); + strDest.replace(strDest.begin()+iIndex,strDest.begin()+iEnd+1,strReplace); + iIndex += strReplace.length(); + } + // insert localize strings + iIndex = 0; + while ((iIndex = strDest.find("$LOCALIZE[", iIndex)) != std::string::npos) + { + size_t iEnd = strDest.find("]", iIndex); + std::string strInfo = strDest.substr(iIndex+10, iEnd - iIndex - 10); + std::string strReplace; + if (m_scraper) + strReplace = m_scraper->GetString(strtol(strInfo.c_str(),NULL,10)); + strDest.replace(strDest.begin()+iIndex,strDest.begin()+iEnd+1,strReplace); + iIndex += strReplace.length(); + } + iIndex = 0; + while ((iIndex = strDest.find("\\n",iIndex)) != std::string::npos) + strDest.replace(strDest.begin()+iIndex,strDest.begin()+iIndex+2,"\n"); +} + +void CScraperParser::ParseExpression(const std::string& input, std::string& dest, TiXmlElement* element, bool bAppend) +{ + std::string strOutput = XMLUtils::GetAttribute(element, "output"); + + TiXmlElement* pExpression = element->FirstChildElement("expression"); + if (pExpression) + { + bool bInsensitive=true; + const char* sensitive = pExpression->Attribute("cs"); + if (sensitive) + if (stricmp(sensitive,"yes") == 0) + bInsensitive=false; // match case sensitive + + CRegExp::utf8Mode eUtf8 = CRegExp::autoUtf8; + const char* const strUtf8 = pExpression->Attribute("utf8"); + if (strUtf8) + { + if (stricmp(strUtf8, "yes") == 0) + eUtf8 = CRegExp::forceUtf8; + else if (stricmp(strUtf8, "no") == 0) + eUtf8 = CRegExp::asciiOnly; + else if (stricmp(strUtf8, "auto") == 0) + eUtf8 = CRegExp::autoUtf8; + } + + CRegExp reg(bInsensitive, eUtf8); + std::string strExpression; + if (pExpression->FirstChild()) + strExpression = pExpression->FirstChild()->Value(); + else + strExpression = "(.*)"; + ReplaceBuffers(strExpression); + ReplaceBuffers(strOutput); + + if (!reg.RegComp(strExpression.c_str())) + { + return; + } + + bool bRepeat = false; + const char* szRepeat = pExpression->Attribute("repeat"); + if (szRepeat) + if (stricmp(szRepeat,"yes") == 0) + bRepeat = true; + + const char* szClear = pExpression->Attribute("clear"); + if (szClear) + if (stricmp(szClear,"yes") == 0) + dest=""; // clear no matter if regexp fails + + bool bClean[MAX_SCRAPER_BUFFERS]; + GetBufferParams(bClean,pExpression->Attribute("noclean"),true); + + bool bTrim[MAX_SCRAPER_BUFFERS]; + GetBufferParams(bTrim,pExpression->Attribute("trim"),false); + + bool bFixChars[MAX_SCRAPER_BUFFERS]; + GetBufferParams(bFixChars,pExpression->Attribute("fixchars"),false); + + bool bEncode[MAX_SCRAPER_BUFFERS]; + GetBufferParams(bEncode,pExpression->Attribute("encode"),false); + + int iOptional = -1; + pExpression->QueryIntAttribute("optional",&iOptional); + + int iCompare = -1; + pExpression->QueryIntAttribute("compare",&iCompare); + if (iCompare > -1) + StringUtils::ToLower(m_param[iCompare-1]); + std::string curInput = input; + for (int iBuf=0;iBuf<MAX_SCRAPER_BUFFERS;++iBuf) + { + if (bClean[iBuf]) + InsertToken(strOutput,iBuf+1,"!!!CLEAN!!!"); + if (bTrim[iBuf]) + InsertToken(strOutput,iBuf+1,"!!!TRIM!!!"); + if (bFixChars[iBuf]) + InsertToken(strOutput,iBuf+1,"!!!FIXCHARS!!!"); + if (bEncode[iBuf]) + InsertToken(strOutput,iBuf+1,"!!!ENCODE!!!"); + } + int i = reg.RegFind(curInput.c_str()); + while (i > -1 && (i < (int)curInput.size() || curInput.size() == 0)) + { + if (!bAppend) + { + dest = ""; + bAppend = true; + } + std::string strCurOutput=strOutput; + + if (iOptional > -1) // check that required param is there + { + char temp[4]; + sprintf(temp,"\\%i",iOptional); + std::string szParam = reg.GetReplaceString(temp); + CRegExp reg2; + reg2.RegComp("(.*)(\\\\\\(.*\\\\2.*)\\\\\\)(.*)"); + int i2=reg2.RegFind(strCurOutput.c_str()); + while (i2 > -1) + { + std::string szRemove(reg2.GetMatch(2)); + int iRemove = szRemove.size(); + int i3 = strCurOutput.find(szRemove); + if (!szParam.empty()) + { + strCurOutput.erase(i3+iRemove,2); + strCurOutput.erase(i3,2); + } + else + strCurOutput.replace(strCurOutput.begin()+i3,strCurOutput.begin()+i3+iRemove+2,""); + + i2 = reg2.RegFind(strCurOutput.c_str()); + } + } + + int iLen = reg.GetFindLen(); + // nasty hack #1 - & means \0 in a replace string + StringUtils::Replace(strCurOutput, "&","!!!AMPAMP!!!"); + std::string result = reg.GetReplaceString(strCurOutput.c_str()); + if (!result.empty()) + { + std::string strResult(result); + StringUtils::Replace(strResult, "!!!AMPAMP!!!","&"); + Clean(strResult); + ReplaceBuffers(strResult); + if (iCompare > -1) + { + std::string strResultNoCase = strResult; + StringUtils::ToLower(strResultNoCase); + if (strResultNoCase.find(m_param[iCompare-1]) != std::string::npos) + dest += strResult; + } + else + dest += strResult; + } + if (bRepeat && iLen > 0) + { + curInput.erase(0,i+iLen>(int)curInput.size()?curInput.size():i+iLen); + i = reg.RegFind(curInput.c_str()); + } + else + i = -1; + } + } +} + +void CScraperParser::ParseXSLT(const std::string& input, std::string& dest, TiXmlElement* element, bool bAppend) +{ + TiXmlElement* pSheet = element->FirstChildElement(); + if (pSheet) + { + XSLTUtils xsltUtils; + std::string strXslt; + strXslt << *pSheet; + ReplaceBuffers(strXslt); + + if (!xsltUtils.SetInput(input)) + CLog::Log(LOGDEBUG, "could not parse input XML"); + + if (!xsltUtils.SetStylesheet(strXslt)) + CLog::Log(LOGDEBUG, "could not parse stylesheet XML"); + + xsltUtils.XSLTTransform(dest); + } +} + +TiXmlElement *FirstChildScraperElement(TiXmlElement *element) +{ + for (TiXmlElement *child = element->FirstChildElement(); child; child = child->NextSiblingElement()) + { + if (child->ValueStr() == "RegExp" || child->ValueStr() == "XSLT") + return child; + } + return NULL; +} + +TiXmlElement *NextSiblingScraperElement(TiXmlElement *element) +{ + for (TiXmlElement *next = element->NextSiblingElement(); next; next = next->NextSiblingElement()) + { + if (next->ValueStr() == "RegExp" || next->ValueStr() == "XSLT") + return next; + } + return NULL; +} + +void CScraperParser::ParseNext(TiXmlElement* element) +{ + TiXmlElement* pReg = element; + while (pReg) + { + TiXmlElement* pChildReg = FirstChildScraperElement(pReg); + if (pChildReg) + ParseNext(pChildReg); + else + { + TiXmlElement* pChildReg = pReg->FirstChildElement("clear"); + if (pChildReg) + ParseNext(pChildReg); + } + + int iDest = 1; + bool bAppend = false; + const char* szDest = pReg->Attribute("dest"); + if (szDest && strlen(szDest)) + { + if (szDest[strlen(szDest)-1] == '+') + bAppend = true; + + iDest = atoi(szDest); + } + + const char *szInput = pReg->Attribute("input"); + std::string strInput; + if (szInput) + { + strInput = szInput; + ReplaceBuffers(strInput); + } + else + strInput = m_param[0]; + + const char* szConditional = pReg->Attribute("conditional"); + bool bExecute = true; + if (szConditional) + { + bool bInverse=false; + if (szConditional[0] == '!') + { + bInverse = true; + szConditional++; + } + std::string strSetting; + if (m_scraper && m_scraper->HasSettings()) + strSetting = m_scraper->GetSetting(szConditional); + bExecute = bInverse != (strSetting == "true"); + } + + if (bExecute) + { + if (iDest-1 < MAX_SCRAPER_BUFFERS && iDest-1 > -1) + { + if (pReg->ValueStr() == "XSLT") + ParseXSLT(strInput, m_param[iDest - 1], pReg, bAppend); + else + ParseExpression(strInput, m_param[iDest - 1],pReg,bAppend); + } + else + CLog::Log(LOGERROR,"CScraperParser::ParseNext: destination buffer " + "out of bounds, skipping expression"); + } + pReg = NextSiblingScraperElement(pReg); + } +} + +const std::string CScraperParser::Parse(const std::string& strTag, + CScraper* scraper) +{ + TiXmlElement* pChildElement = m_pRootElement->FirstChildElement(strTag.c_str()); + if(pChildElement == NULL) + { + CLog::Log(LOGERROR,"%s: Could not find scraper function %s",__FUNCTION__,strTag.c_str()); + return ""; + } + int iResult = 1; // default to param 1 + pChildElement->QueryIntAttribute("dest",&iResult); + TiXmlElement* pChildStart = FirstChildScraperElement(pChildElement); + m_scraper = scraper; + ParseNext(pChildStart); + std::string tmp = m_param[iResult-1]; + + const char* szClearBuffers = pChildElement->Attribute("clearbuffers"); + if (!szClearBuffers || stricmp(szClearBuffers,"no") != 0) + ClearBuffers(); + + return tmp; +} + +void CScraperParser::Clean(std::string& strDirty) +{ + size_t i = 0; + std::string strBuffer; + while ((i = strDirty.find("!!!CLEAN!!!",i)) != std::string::npos) + { + size_t i2; + if ((i2 = strDirty.find("!!!CLEAN!!!",i+11)) != std::string::npos) + { + strBuffer = strDirty.substr(i+11,i2-i-11); + std::string strConverted(strBuffer); + HTML::CHTMLUtil::RemoveTags(strConverted); + StringUtils::Trim(strConverted); + strDirty.replace(i, i2-i+11, strConverted); + i += strConverted.size(); + } + else + break; + } + i=0; + while ((i = strDirty.find("!!!TRIM!!!",i)) != std::string::npos) + { + size_t i2; + if ((i2 = strDirty.find("!!!TRIM!!!",i+10)) != std::string::npos) + { + strBuffer = strDirty.substr(i+10,i2-i-10); + StringUtils::Trim(strBuffer); + strDirty.replace(i, i2-i+10, strBuffer); + i += strBuffer.size(); + } + else + break; + } + i=0; + while ((i = strDirty.find("!!!FIXCHARS!!!",i)) != std::string::npos) + { + size_t i2; + if ((i2 = strDirty.find("!!!FIXCHARS!!!",i+14)) != std::string::npos) + { + strBuffer = strDirty.substr(i+14,i2-i-14); + std::wstring wbuffer; + g_charsetConverter.utf8ToW(strBuffer, wbuffer, false, false, false); + std::wstring wConverted; + HTML::CHTMLUtil::ConvertHTMLToW(wbuffer,wConverted); + g_charsetConverter.wToUTF8(wConverted, strBuffer, false); + StringUtils::Trim(strBuffer); + ConvertJSON(strBuffer); + strDirty.replace(i, i2-i+14, strBuffer); + i += strBuffer.size(); + } + else + break; + } + i=0; + while ((i=strDirty.find("!!!ENCODE!!!",i)) != std::string::npos) + { + size_t i2; + if ((i2 = strDirty.find("!!!ENCODE!!!",i+12)) != std::string::npos) + { + strBuffer = CURL::Encode(strDirty.substr(i + 12, i2 - i - 12)); + strDirty.replace(i, i2-i+12, strBuffer); + i += strBuffer.size(); + } + else + break; + } +} + +void CScraperParser::ConvertJSON(std::string &string) +{ + CRegExp reg; + reg.RegComp("\\\\u([0-f]{4})"); + while (reg.RegFind(string.c_str()) > -1) + { + int pos = reg.GetSubStart(1); + std::string szReplace(reg.GetMatch(1)); + + std::string replace = StringUtils::Format("&#x%s;", szReplace.c_str()); + string.replace(string.begin()+pos-2, string.begin()+pos+4, replace); + } + + CRegExp reg2; + reg2.RegComp("\\\\x([0-9]{2})([^\\\\]+;)"); + while (reg2.RegFind(string.c_str()) > -1) + { + int pos1 = reg2.GetSubStart(1); + int pos2 = reg2.GetSubStart(2); + std::string szHexValue(reg2.GetMatch(1)); + + std::string replace = StringUtils::Format("%li", strtol(szHexValue.c_str(), NULL, 16)); + string.replace(string.begin()+pos1-2, string.begin()+pos2+reg2.GetSubLength(2), replace); + } + + StringUtils::Replace(string, "\\\"","\""); +} + +void CScraperParser::ClearBuffers() +{ + //clear all m_param strings + for (int i=0;i<MAX_SCRAPER_BUFFERS;++i) + m_param[i].clear(); +} + +void CScraperParser::GetBufferParams(bool* result, const char* attribute, bool defvalue) +{ + for (int iBuf=0;iBuf<MAX_SCRAPER_BUFFERS;++iBuf) + result[iBuf] = defvalue;; + if (attribute) + { + vector<std::string> vecBufs; + StringUtils::Tokenize(attribute,vecBufs,","); + for (size_t nToken=0; nToken < vecBufs.size(); nToken++) + { + int index = atoi(vecBufs[nToken].c_str())-1; + if (index < MAX_SCRAPER_BUFFERS) + result[index] = !defvalue; + } + } +} + +void CScraperParser::InsertToken(std::string& strOutput, int buf, const char* token) +{ + char temp[4]; + sprintf(temp,"\\%i",buf); + size_t i2=0; + while ((i2 = strOutput.find(temp,i2)) != std::string::npos) + { + strOutput.insert(i2,token); + i2 += strlen(token) + strlen(temp); + strOutput.insert(i2,token); + } +} + +void CScraperParser::AddDocument(const CXBMCTinyXML* doc) +{ + const TiXmlNode* node = doc->RootElement()->FirstChild(); + while (node) + { + m_pRootElement->InsertEndChild(*node); + node = node->NextSibling(); + } +} + diff --git a/src/utils/ScraperParser.h b/src/utils/ScraperParser.h new file mode 100644 index 0000000000..8129ef7e75 --- /dev/null +++ b/src/utils/ScraperParser.h @@ -0,0 +1,94 @@ +#ifndef SCRAPER_PARSER_H +#define SCRAPER_PARSER_H + +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <vector> + +#define MAX_SCRAPER_BUFFERS 20 + +namespace ADDON +{ + class CScraper; +} + +class TiXmlElement; +class CXBMCTinyXML; + +class CScraperSettings; + +class CScraperParser +{ +public: + CScraperParser(); + CScraperParser(const CScraperParser& parser); + ~CScraperParser(); + CScraperParser& operator= (const CScraperParser& parser); + bool Load(const std::string& strXMLFile); + bool IsNoop() const { return m_isNoop; }; + + void Clear(); + const std::string& GetFilename() const { return m_strFile; } + std::string GetSearchStringEncoding() const + { return m_SearchStringEncoding; } + const std::string Parse(const std::string& strTag, + ADDON::CScraper* scraper); + + void AddDocument(const CXBMCTinyXML* doc); + + std::string m_param[MAX_SCRAPER_BUFFERS]; + +private: + bool LoadFromXML(); + void ReplaceBuffers(std::string& strDest); + void ParseExpression(const std::string& input, std::string& dest, TiXmlElement* element, bool bAppend); + + /*! \brief Parse an 'XSLT' declaration from the scraper + This allow us to transform an inbound XML document using XSLT + to a different type of XML document, ready to be output direct + to the album loaders or similar + \param input the input document + \param dest the output destation for the conversion + \param element the current XML element + \param bAppend append or clear the buffer + */ + void ParseXSLT(const std::string& input, std::string& dest, TiXmlElement* element, bool bAppend); + void ParseNext(TiXmlElement* element); + void Clean(std::string& strDirty); + void ConvertJSON(std::string &string); + void ClearBuffers(); + void GetBufferParams(bool* result, const char* attribute, bool defvalue); + void InsertToken(std::string& strOutput, int buf, const char* token); + + CXBMCTinyXML* m_document; + TiXmlElement* m_pRootElement; + + const char* m_SearchStringEncoding; + bool m_isNoop; + + std::string m_strFile; + ADDON::CScraper* m_scraper; +}; + +#endif + + diff --git a/src/utils/ScraperUrl.cpp b/src/utils/ScraperUrl.cpp new file mode 100644 index 0000000000..a9eafddc9c --- /dev/null +++ b/src/utils/ScraperUrl.cpp @@ -0,0 +1,366 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "XMLUtils.h" +#include "ScraperUrl.h" +#include "settings/AdvancedSettings.h" +#include "HTMLUtil.h" +#include "CharsetConverter.h" +#include "utils/CharsetDetection.h" +#include "utils/StringUtils.h" +#include "URL.h" +#include "filesystem/CurlFile.h" +#include "filesystem/ZipFile.h" +#include "URIUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/Mime.h" + +#include <cstring> +#include <sstream> + +using namespace std; + +CScraperUrl::CScraperUrl(const std::string& strUrl) +{ + relevance = 0; + ParseString(strUrl); +} + +CScraperUrl::CScraperUrl(const TiXmlElement* element) +{ + relevance = 0; + ParseElement(element); +} + +CScraperUrl::CScraperUrl() +{ + relevance = 0; +} + +CScraperUrl::~CScraperUrl() +{ +} + +void CScraperUrl::Clear() +{ + m_url.clear(); + m_spoof.clear(); + m_xml.clear(); + relevance = 0; +} + +bool CScraperUrl::Parse() +{ + std::string strToParse = m_xml; + m_xml.clear(); + return ParseString(strToParse); +} + +bool CScraperUrl::ParseElement(const TiXmlElement* element) +{ + if (!element || !element->FirstChild() || + !element->FirstChild()->Value()) return false; + + stringstream stream; + stream << *element; + m_xml += stream.str(); + + SUrlEntry url; + url.m_url = element->FirstChild()->Value(); + url.m_spoof = XMLUtils::GetAttribute(element, "spoof"); + const char* szPost=element->Attribute("post"); + if (szPost && stricmp(szPost,"yes") == 0) + url.m_post = true; + else + url.m_post = false; + const char* szIsGz=element->Attribute("gzip"); + if (szIsGz && stricmp(szIsGz,"yes") == 0) + url.m_isgz = true; + else + url.m_isgz = false; + url.m_cache = XMLUtils::GetAttribute(element, "cache"); + + const char* szType = element->Attribute("type"); + url.m_type = URL_TYPE_GENERAL; + url.m_season = -1; + if (szType && stricmp(szType,"season") == 0) + { + url.m_type = URL_TYPE_SEASON; + const char* szSeason = element->Attribute("season"); + if (szSeason) + url.m_season = atoi(szSeason); + } + url.m_aspect = XMLUtils::GetAttribute(element, "aspect"); + + m_url.push_back(url); + + return true; +} + +bool CScraperUrl::ParseString(std::string strUrl) +{ + if (strUrl.empty()) + return false; + + CXBMCTinyXML doc; + /* strUrl is coming from internal sources (usually generated by scraper or from database) + * so strUrl is always in UTF-8 */ + doc.Parse(strUrl, TIXML_ENCODING_UTF8); + + TiXmlElement* pElement = doc.RootElement(); + if (!pElement) + { + SUrlEntry url; + url.m_url = strUrl; + url.m_type = URL_TYPE_GENERAL; + url.m_season = -1; + url.m_post = false; + url.m_isgz = false; + m_url.push_back(url); + m_xml = strUrl; + } + else + { + while (pElement) + { + ParseElement(pElement); + pElement = pElement->NextSiblingElement(pElement->Value()); + } + } + + return true; +} + +const CScraperUrl::SUrlEntry CScraperUrl::GetFirstThumb(const std::string &type) const +{ + for (vector<SUrlEntry>::const_iterator iter=m_url.begin();iter != m_url.end();++iter) + { + if (iter->m_type == URL_TYPE_GENERAL && (type.empty() || type == "thumb" || iter->m_aspect == type)) + return *iter; + } + + SUrlEntry result; + result.m_type = URL_TYPE_GENERAL; + result.m_post = false; + result.m_isgz = false; + result.m_season = -1; + return result; +} + +const CScraperUrl::SUrlEntry CScraperUrl::GetSeasonThumb(int season, const std::string &type) const +{ + for (vector<SUrlEntry>::const_iterator iter=m_url.begin();iter != m_url.end();++iter) + { + if (iter->m_type == URL_TYPE_SEASON && iter->m_season == season && + (type.empty() || type == "thumb" || iter->m_aspect == type)) + return *iter; + } + + SUrlEntry result; + result.m_type = URL_TYPE_GENERAL; + result.m_post = false; + result.m_isgz = false; + result.m_season = -1; + return result; +} + +unsigned int CScraperUrl::GetMaxSeasonThumb() const +{ + unsigned int maxSeason = 0; + for (vector<SUrlEntry>::const_iterator iter=m_url.begin();iter != m_url.end();++iter) + { + if (iter->m_type == URL_TYPE_SEASON && iter->m_season > 0 && (unsigned int)iter->m_season > maxSeason) + maxSeason = iter->m_season; + } + return maxSeason; +} + +bool CScraperUrl::Get(const SUrlEntry& scrURL, std::string& strHTML, XFILE::CCurlFile& http, const std::string& cacheContext) +{ + CURL url(scrURL.m_url); + http.SetReferer(scrURL.m_spoof); + std::string strCachePath; + + if (scrURL.m_isgz) + http.SetContentEncoding("gzip"); + + if (!scrURL.m_cache.empty()) + { + strCachePath = URIUtils::AddFileToFolder(g_advancedSettings.m_cachePath, + "scrapers/" + cacheContext + "/" + scrURL.m_cache); + if (XFILE::CFile::Exists(strCachePath)) + { + XFILE::CFile file; + XFILE::auto_buffer buffer; + if (file.LoadFile(strCachePath, buffer) > 0) + { + strHTML.assign(buffer.get(), buffer.length()); + return true; + } + } + } + + std::string strHTML1(strHTML); + + if (scrURL.m_post) + { + std::string strOptions = url.GetOptions(); + strOptions = strOptions.substr(1); + url.SetOptions(""); + + if (!http.Post(url.Get(), strOptions, strHTML1)) + return false; + } + else + if (!http.Get(url.Get(), strHTML1)) + return false; + + strHTML = strHTML1; + + std::string mimeType(http.GetMimeType()); + CMime::EFileType ftype = CMime::GetFileTypeFromMime(mimeType); + if (ftype == CMime::FileTypeUnknown) + ftype = CMime::GetFileTypeFromContent(strHTML); + + if (ftype == CMime::FileTypeZip || ftype == CMime::FileTypeGZip) + { + XFILE::CZipFile file; + std::string strBuffer; + int iSize = file.UnpackFromMemory(strBuffer,strHTML,scrURL.m_isgz); // FIXME: use FileTypeGZip instead of scrURL.m_isgz? + if (iSize > 0) + { + strHTML = strBuffer; + CLog::Log(LOGDEBUG, "%s: Archive \"%s\" was unpacked in memory", __FUNCTION__, scrURL.m_url.c_str()); + } + else + CLog::Log(LOGWARNING, "%s: \"%s\" looks like archive, but cannot be unpacked", __FUNCTION__, scrURL.m_url.c_str()); + } + + std::string reportedCharset(http.GetServerReportedCharset()); + if (ftype == CMime::FileTypeHtml) + { + std::string realHtmlCharset, converted; + if (!CCharsetDetection::ConvertHtmlToUtf8(strHTML, converted, reportedCharset, realHtmlCharset)) + CLog::Log(LOGWARNING, "%s: Can't find precise charset for HTML \"%s\", using \"%s\" as fallback", __FUNCTION__, scrURL.m_url.c_str(), realHtmlCharset.c_str()); + else + CLog::Log(LOGDEBUG, "%s: Using \"%s\" charset for HTML \"%s\"", __FUNCTION__, realHtmlCharset.c_str(), scrURL.m_url.c_str()); + + strHTML = converted; + } + else if (ftype == CMime::FileTypeXml) + { + CXBMCTinyXML xmlDoc; + xmlDoc.Parse(strHTML, reportedCharset); + + std::string realXmlCharset(xmlDoc.GetUsedCharset()); + if (!realXmlCharset.empty()) + { + CLog::Log(LOGDEBUG, "%s: Using \"%s\" charset for XML \"%s\"", __FUNCTION__, realXmlCharset.c_str(), scrURL.m_url.c_str()); + std::string converted; + g_charsetConverter.ToUtf8(realXmlCharset, strHTML, converted); + strHTML = converted; + } + } + else if (ftype == CMime::FileTypePlainText || StringUtils::CompareNoCase(mimeType.substr(0, 5), "text/") == 0) + { + std::string realTextCharset, converted; + CCharsetDetection::ConvertPlainTextToUtf8(strHTML, converted, reportedCharset, realTextCharset); + strHTML = converted; + if (reportedCharset != realTextCharset) + CLog::Log(LOGWARNING, "%s: Using \"%s\" charset for plain text \"%s\" instead of server reported \"%s\" charset", __FUNCTION__, realTextCharset.c_str(), scrURL.m_url.c_str(), reportedCharset.c_str()); + else + CLog::Log(LOGDEBUG, "%s: Using \"%s\" charset for plain text \"%s\"", __FUNCTION__, realTextCharset.c_str(), scrURL.m_url.c_str()); + } + else if (!reportedCharset.empty()) + { + CLog::Log(LOGDEBUG, "%s: Using \"%s\" charset for \"%s\"", __FUNCTION__, reportedCharset.c_str(), scrURL.m_url.c_str()); + if (reportedCharset != "UTF-8") + { + std::string converted; + g_charsetConverter.ToUtf8(reportedCharset, strHTML, converted); + strHTML = converted; + } + } + else + CLog::Log(LOGDEBUG, "%s: Using content of \"%s\" as binary or text with \"UTF-8\" charset", __FUNCTION__, scrURL.m_url.c_str()); + + if (!scrURL.m_cache.empty()) + { + std::string strCachePath = URIUtils::AddFileToFolder(g_advancedSettings.m_cachePath, + "scrapers/" + cacheContext + "/" + scrURL.m_cache); + XFILE::CFile file; + if (!file.OpenForWrite(strCachePath, true) || file.Write(strHTML.data(), strHTML.size()) != strHTML.size()) + return false; + } + return true; +} + +// XML format is of strUrls is: +// <TAG><url>...</url>...</TAG> (parsed by ParseElement) or <url>...</url> (ditto) +bool CScraperUrl::ParseEpisodeGuide(std::string strUrls) +{ + if (strUrls.empty()) + return false; + + // ok, now parse the xml file + CXBMCTinyXML doc; + /* strUrls is coming from internal sources so strUrls is always in UTF-8 */ + doc.Parse(strUrls, TIXML_ENCODING_UTF8); + if (doc.RootElement()) + { + TiXmlHandle docHandle( &doc ); + TiXmlElement *link = docHandle.FirstChild("episodeguide").Element(); + if (link->FirstChildElement("url")) + { + for (link = link->FirstChildElement("url"); link; link = link->NextSiblingElement("url")) + ParseElement(link); + } + else if (link->FirstChild() && link->FirstChild()->Value()) + ParseElement(link); + } + else + return false; + + return true; +} + +std::string CScraperUrl::GetThumbURL(const CScraperUrl::SUrlEntry &entry) +{ + if (entry.m_spoof.empty()) + return entry.m_url; + + return entry.m_url + "|Referer=" + CURL::Encode(entry.m_spoof); +} + +void CScraperUrl::GetThumbURLs(std::vector<std::string> &thumbs, const std::string &type, int season) const +{ + for (vector<SUrlEntry>::const_iterator iter = m_url.begin(); iter != m_url.end(); ++iter) + { + if (iter->m_aspect == type || type.empty() || type == "thumb" || iter->m_aspect.empty()) + { + if ((iter->m_type == CScraperUrl::URL_TYPE_GENERAL && season == -1) + || (iter->m_type == CScraperUrl::URL_TYPE_SEASON && iter->m_season == season)) + { + thumbs.push_back(GetThumbURL(*iter)); + } + } + } +} diff --git a/src/utils/ScraperUrl.h b/src/utils/ScraperUrl.h new file mode 100644 index 0000000000..d9871e8f8f --- /dev/null +++ b/src/utils/ScraperUrl.h @@ -0,0 +1,92 @@ +#ifndef SCRAPER_URL_H +#define SCRAPER_URL_H + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <vector> +#include <map> +#include <string> + +class TiXmlElement; +namespace XFILE { class CCurlFile; } + +class CScraperUrl +{ +public: + CScraperUrl(const std::string&); + CScraperUrl(const TiXmlElement*); + CScraperUrl(); + ~CScraperUrl(); + + enum URLTYPES + { + URL_TYPE_GENERAL = 1, + URL_TYPE_SEASON = 2 + }; + + struct SUrlEntry + { + std::string m_spoof; + std::string m_url; + std::string m_cache; + std::string m_aspect; + URLTYPES m_type; + bool m_post; + bool m_isgz; + int m_season; + }; + + bool Parse(); + bool ParseString(std::string); // copies by intention + bool ParseElement(const TiXmlElement*); + bool ParseEpisodeGuide(std::string strUrls); // copies by intention + + const SUrlEntry GetFirstThumb(const std::string &type = "") const; + const SUrlEntry GetSeasonThumb(int season, const std::string &type = "") const; + unsigned int GetMaxSeasonThumb() const; + + /*! \brief fetch the full URL (including referrer) of a thumb + \param URL entry to use to create the full URL + \return the full URL, including referrer + */ + static std::string GetThumbURL(const CScraperUrl::SUrlEntry &entry); + + /*! \brief fetch the full URL (including referrer) of thumbs + \param thumbs [out] vector of thumb URLs to fill + \param type the type of thumb URLs to fetch, if empty (the default) picks any + \param season number of season that we want thumbs for, -1 indicates no season (the default) + */ + void GetThumbURLs(std::vector<std::string> &thumbs, const std::string &type = "", int season = -1) const; + void Clear(); + static bool Get(const SUrlEntry&, std::string&, XFILE::CCurlFile& http, + const std::string& cacheContext); + + std::string m_xml; + std::string m_spoof; // for backwards compatibility only! + std::string strTitle; + std::string strId; + double relevance; + std::vector<SUrlEntry> m_url; +}; + +#endif + + diff --git a/src/utils/Screenshot.cpp b/src/utils/Screenshot.cpp new file mode 100644 index 0000000000..db73d4aa0b --- /dev/null +++ b/src/utils/Screenshot.cpp @@ -0,0 +1,290 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Screenshot.h" + +#include "system.h" +#include <vector> + +#include "Util.h" + +#include "Application.h" +#include "windowing/WindowingFactory.h" +#include "pictures/Picture.h" + +#ifdef TARGET_RASPBERRY_PI +#include "xbmc/linux/RBP.h" +#endif + +#ifdef HAS_VIDEO_PLAYBACK +#include "cores/VideoRenderers/RenderManager.h" +#endif + +#include "filesystem/File.h" +#include "guilib/GraphicContext.h" + +#include "utils/JobManager.h" +#include "utils/URIUtils.h" +#include "utils/log.h" +#include "settings/SettingPath.h" +#include "settings/Settings.h" +#include "settings/windows/GUIControlSettings.h" + +using namespace std; +using namespace XFILE; + +CScreenshotSurface::CScreenshotSurface() +{ + m_width = 0; + m_height = 0; + m_stride = 0; + m_buffer = NULL; +} + +CScreenshotSurface::~CScreenshotSurface() +{ + delete m_buffer; +} + +bool CScreenshotSurface::capture() +{ +#if defined(TARGET_RASPBERRY_PI) + g_RBP.GetDisplaySize(m_width, m_height); + m_buffer = g_RBP.CaptureDisplay(m_width, m_height, &m_stride, true, false); + if (!m_buffer) + return false; +#elif defined(HAS_DX) + LPDIRECT3DSURFACE9 lpSurface = NULL, lpBackbuffer = NULL; + g_graphicsContext.Lock(); + if (g_application.m_pPlayer->IsPlayingVideo()) + { +#ifdef HAS_VIDEO_PLAYBACK + g_renderManager.SetupScreenshot(); +#endif + } + g_application.RenderNoPresent(); + + if (FAILED(g_Windowing.Get3DDevice()->CreateOffscreenPlainSurface(g_Windowing.GetWidth(), g_Windowing.GetHeight(), D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, &lpSurface, NULL))) + return false; + + if (FAILED(g_Windowing.Get3DDevice()->GetRenderTarget(0, &lpBackbuffer))) + return false; + + // now take screenshot + if (SUCCEEDED(g_Windowing.Get3DDevice()->GetRenderTargetData(lpBackbuffer, lpSurface))) + { + D3DLOCKED_RECT lr; + D3DSURFACE_DESC desc; + lpSurface->GetDesc(&desc); + if (SUCCEEDED(lpSurface->LockRect(&lr, NULL, D3DLOCK_READONLY))) + { + m_width = desc.Width; + m_height = desc.Height; + m_stride = lr.Pitch; + m_buffer = new unsigned char[m_height * m_stride]; + memcpy(m_buffer, lr.pBits, m_height * m_stride); + lpSurface->UnlockRect(); + } + else + { + CLog::Log(LOGERROR, "%s LockRect failed", __FUNCTION__); + } + } + else + { + CLog::Log(LOGERROR, "%s GetBackBuffer failed", __FUNCTION__); + } + lpSurface->Release(); + lpBackbuffer->Release(); + + g_graphicsContext.Unlock(); + +#elif defined(HAS_GL) || defined(HAS_GLES) + + g_graphicsContext.BeginPaint(); + if (g_application.m_pPlayer->IsPlayingVideo()) + { +#ifdef HAS_VIDEO_PLAYBACK + g_renderManager.SetupScreenshot(); +#endif + } + g_application.RenderNoPresent(); +#ifndef HAS_GLES + glReadBuffer(GL_BACK); +#endif + //get current viewport + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + + m_width = viewport[2] - viewport[0]; + m_height = viewport[3] - viewport[1]; + m_stride = m_width * 4; + unsigned char* surface = new unsigned char[m_stride * m_height]; + + //read pixels from the backbuffer +#if HAS_GLES == 2 + glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)surface); +#else + glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid*)surface); +#endif + g_graphicsContext.EndPaint(); + + //make a new buffer and copy the read image to it with the Y axis inverted + m_buffer = new unsigned char[m_stride * m_height]; + for (int y = 0; y < m_height; y++) + { +#ifdef HAS_GLES + // we need to save in BGRA order so XOR Swap RGBA -> BGRA + unsigned char* swap_pixels = surface + (m_height - y - 1) * m_stride; + for (int x = 0; x < m_width; x++, swap_pixels+=4) + { + std::swap(swap_pixels[0], swap_pixels[2]); + } +#endif + memcpy(m_buffer + y * m_stride, surface + (m_height - y - 1) *m_stride, m_stride); + } + + delete [] surface; + +#else + //nothing to take a screenshot from + return false; +#endif + + return true; +} + +void CScreenShot::TakeScreenshot(const std::string &filename, bool sync) +{ + + CScreenshotSurface surface; + if (!surface.capture()) + { + CLog::Log(LOGERROR, "Screenshot %s failed", filename.c_str()); + return; + } + + CLog::Log(LOGDEBUG, "Saving screenshot %s", filename.c_str()); + + //set alpha byte to 0xFF + for (int y = 0; y < surface.m_height; y++) + { + unsigned char* alphaptr = surface.m_buffer - 1 + y * surface.m_stride; + for (int x = 0; x < surface.m_width; x++) + *(alphaptr += 4) = 0xFF; + } + + //if sync is true, the png file needs to be completely written when this function returns + if (sync) + { + if (!CPicture::CreateThumbnailFromSurface(surface.m_buffer, surface.m_width, surface.m_height, surface.m_stride, filename)) + CLog::Log(LOGERROR, "Unable to write screenshot %s", filename.c_str()); + + delete [] surface.m_buffer; + surface.m_buffer = NULL; + } + else + { + //make sure the file exists to avoid concurrency issues + FILE* fp = fopen(filename.c_str(), "w"); + if (fp) + fclose(fp); + else + CLog::Log(LOGERROR, "Unable to create file %s", filename.c_str()); + + //write .png file asynchronous with CThumbnailWriter, prevents stalling of the render thread + //buffer is deleted from CThumbnailWriter + CThumbnailWriter* thumbnailwriter = new CThumbnailWriter(surface.m_buffer, surface.m_width, surface.m_height, surface.m_stride, filename); + CJobManager::GetInstance().AddJob(thumbnailwriter, NULL); + surface.m_buffer = NULL; + } +} + +void CScreenShot::TakeScreenshot() +{ + static bool savingScreenshots = false; + static vector<std::string> screenShots; + bool promptUser = false; + std::string strDir; + + // check to see if we have a screenshot folder yet + CSettingPath *screenshotSetting = (CSettingPath*)CSettings::Get().GetSetting("debug.screenshotpath"); + if (screenshotSetting != NULL) + { + strDir = screenshotSetting->GetValue(); + if (strDir.empty()) + { + if (CGUIControlButtonSetting::GetPath(screenshotSetting)) + strDir = screenshotSetting->GetValue(); + } + } + + if (strDir.empty()) + { + strDir = "special://temp/"; + if (!savingScreenshots) + { + promptUser = true; + savingScreenshots = true; + screenShots.clear(); + } + } + URIUtils::RemoveSlashAtEnd(strDir); + + if (!strDir.empty()) + { + std::string file = CUtil::GetNextFilename(URIUtils::AddFileToFolder(strDir, "screenshot%03d.png"), 999); + + if (!file.empty()) + { + TakeScreenshot(file, false); + if (savingScreenshots) + screenShots.push_back(file); + if (promptUser) + { // grab the real directory + std::string newDir; + if (screenshotSetting != NULL) + { + newDir = screenshotSetting->GetValue(); + if (newDir.empty()) + { + if (CGUIControlButtonSetting::GetPath(screenshotSetting)) + newDir = screenshotSetting->GetValue(); + } + } + + if (!newDir.empty()) + { + for (unsigned int i = 0; i < screenShots.size(); i++) + { + std::string file = CUtil::GetNextFilename(URIUtils::AddFileToFolder(newDir, "screenshot%03d.png"), 999); + CFile::Copy(screenShots[i], file); + } + screenShots.clear(); + } + savingScreenshots = false; + } + } + else + { + CLog::Log(LOGWARNING, "Too many screen shots or invalid folder"); + } + } +} diff --git a/src/utils/Screenshot.h b/src/utils/Screenshot.h new file mode 100644 index 0000000000..200688c93d --- /dev/null +++ b/src/utils/Screenshot.h @@ -0,0 +1,44 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> + +class CScreenshotSurface +{ + +public: + int m_width; + int m_height; + int m_stride; + unsigned char* m_buffer; + + CScreenshotSurface(void); + ~CScreenshotSurface(); + bool capture( void ); +}; + +class CScreenShot +{ + +public: + static void TakeScreenshot(); + static void TakeScreenshot(const std::string &filename, bool sync); +}; diff --git a/src/utils/SeekHandler.cpp b/src/utils/SeekHandler.cpp new file mode 100644 index 0000000000..9c4e101800 --- /dev/null +++ b/src/utils/SeekHandler.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "SeekHandler.h" +#include "GUIInfoManager.h" +#include "Application.h" + +CSeekHandler::CSeekHandler() +: m_requireSeek(false), + m_percent(0.0f) +{ +} + +void CSeekHandler::Reset() +{ + m_requireSeek = false; + m_percent = 0; +} + +void CSeekHandler::Seek(bool forward, float amount, float duration) +{ + if (!m_requireSeek) + { // not yet seeking + if (g_infoManager.GetTotalPlayTime()) + m_percent = (float)g_infoManager.GetPlayTime() / g_infoManager.GetTotalPlayTime() * 0.1f; + else + m_percent = 0.0f; + + // tell info manager that we have started a seek operation + m_requireSeek = true; + g_infoManager.SetSeeking(true); + } + // calculate our seek amount + if (!g_infoManager.m_performingSeek) + { + //100% over 1 second. + float speed = 100.0f; + if( duration ) + speed *= duration; + else + speed /= g_infoManager.GetFPS(); + + if (forward) + m_percent += amount * amount * speed; + else + m_percent -= amount * amount * speed; + if (m_percent > 100.0f) m_percent = 100.0f; + if (m_percent < 0.0f) m_percent = 0.0f; + } + m_timer.StartZero(); +} + +float CSeekHandler::GetPercent() const +{ + return m_percent; +} + +bool CSeekHandler::InProgress() const +{ + return m_requireSeek; +} + +void CSeekHandler::Process() +{ + if (m_timer.GetElapsedMilliseconds() > time_before_seek) + { + if (!g_infoManager.m_performingSeek && m_timer.GetElapsedMilliseconds() > time_for_display) // TODO: Why? + g_infoManager.SetSeeking(false); + if (m_requireSeek) + { + g_infoManager.m_performingSeek = true; + double time = g_infoManager.GetTotalPlayTime() * m_percent * 0.01; + g_application.SeekTime(time); + m_requireSeek = false; + } + } +} diff --git a/src/utils/SeekHandler.h b/src/utils/SeekHandler.h new file mode 100644 index 0000000000..68e7820c84 --- /dev/null +++ b/src/utils/SeekHandler.h @@ -0,0 +1,41 @@ +#pragma once +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/Stopwatch.h" + +class CSeekHandler +{ +public: + CSeekHandler(); + + void Seek(bool forward, float amount, float duration = 0); + void Process(); + void Reset(); + + float GetPercent() const; + bool InProgress() const; +private: + static const int time_before_seek = 500; + static const int time_for_display = 2000; // TODO: WTF? + bool m_requireSeek; + float m_percent; + CStopWatch m_timer; +}; diff --git a/src/utils/SortUtils.cpp b/src/utils/SortUtils.cpp new file mode 100644 index 0000000000..a2601ddd50 --- /dev/null +++ b/src/utils/SortUtils.cpp @@ -0,0 +1,906 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "SortUtils.h" +#include "URL.h" +#include "Util.h" +#include "XBDateTime.h" +#include "settings/AdvancedSettings.h" +#include "utils/CharsetConverter.h" +#include "utils/StdString.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +using namespace std; + +string ArrayToString(SortAttribute attributes, const CVariant &variant, const string &seperator = " / ") +{ + vector<string> strArray; + if (variant.isArray()) + { + for (CVariant::const_iterator_array it = variant.begin_array(); it != variant.end_array(); it++) + { + if (attributes & SortAttributeIgnoreArticle) + strArray.push_back(SortUtils::RemoveArticles(it->asString())); + else + strArray.push_back(it->asString()); + } + + return StringUtils::Join(strArray, seperator); + } + else if (variant.isString()) + { + if (attributes & SortAttributeIgnoreArticle) + return SortUtils::RemoveArticles(variant.asString()); + else + return variant.asString(); + } + + return ""; +} + +string ByLabel(SortAttribute attributes, const SortItem &values) +{ + if (attributes & SortAttributeIgnoreArticle) + return SortUtils::RemoveArticles(values.at(FieldLabel).asString()); + + return values.at(FieldLabel).asString(); +} + +string ByFile(SortAttribute attributes, const SortItem &values) +{ + CURL url(values.at(FieldPath).asString()); + + return StringUtils::Format("%s %" PRId64, url.GetFileNameWithoutPath().c_str(), values.at(FieldStartOffset).asInteger()); +} + +string ByPath(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%s %" PRId64, values.at(FieldPath).asString().c_str(), values.at(FieldStartOffset).asInteger()); +} + +string ByLastPlayed(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%s %s", values.at(FieldLastPlayed).asString().c_str(), ByLabel(attributes, values).c_str()); +} + +string ByPlaycount(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%i %s", (int)values.at(FieldPlaycount).asInteger(), ByLabel(attributes, values).c_str()); +} + +string ByDate(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldDate).asString() + " " + ByLabel(attributes, values); +} + +string ByDateAdded(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%s %d", values.at(FieldDateAdded).asString().c_str(), (int)values.at(FieldId).asInteger());; +} + +string BySize(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%" PRId64, values.at(FieldSize).asInteger()); +} + +string ByDriveType(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%d %s", (int)values.at(FieldDriveType).asInteger(), ByLabel(attributes, values).c_str()); +} + +string ByTitle(SortAttribute attributes, const SortItem &values) +{ + if (attributes & SortAttributeIgnoreArticle) + return SortUtils::RemoveArticles(values.at(FieldTitle).asString()); + + return values.at(FieldTitle).asString(); +} + +string ByAlbum(SortAttribute attributes, const SortItem &values) +{ + string album = values.at(FieldAlbum).asString(); + if (attributes & SortAttributeIgnoreArticle) + album = SortUtils::RemoveArticles(album); + + CStdString label = StringUtils::Format("%s %s", album.c_str(), ArrayToString(attributes, values.at(FieldArtist)).c_str()); + + const CVariant &track = values.at(FieldTrackNumber); + if (!track.isNull()) + label += StringUtils::Format(" %i", (int)track.asInteger()); + + return label; +} + +string ByAlbumType(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldAlbumType).asString() + " " + ByLabel(attributes, values); +} + +string ByArtist(SortAttribute attributes, const SortItem &values) +{ + CStdString label = ArrayToString(attributes, values.at(FieldArtist)); + + const CVariant &year = values.at(FieldYear); + if (g_advancedSettings.m_bMusicLibraryAlbumsSortByArtistThenYear && + !year.isNull()) + label += StringUtils::Format(" %i", (int)year.asInteger()); + + const CVariant &album = values.at(FieldAlbum); + if (!album.isNull()) + label += " " + SortUtils::RemoveArticles(album.asString()); + + const CVariant &track = values.at(FieldTrackNumber); + if (!track.isNull()) + label += StringUtils::Format(" %i", (int)track.asInteger()); + + return label; +} + +string ByTrackNumber(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%i", (int)values.at(FieldTrackNumber).asInteger()); +} + +string ByTime(SortAttribute attributes, const SortItem &values) +{ + CStdString label; + const CVariant &time = values.at(FieldTime); + if (time.isInteger()) + label = StringUtils::Format("%i", (int)time.asInteger()); + else + label = StringUtils::Format("%s", time.asString().c_str()); + return label; +} + +string ByProgramCount(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%i", (int)values.at(FieldProgramCount).asInteger()); +} + +string ByPlaylistOrder(SortAttribute attributes, const SortItem &values) +{ + // TODO: Playlist order is hacked into program count variable (not nice, but ok until 2.0) + return ByProgramCount(attributes, values); +} + +string ByGenre(SortAttribute attributes, const SortItem &values) +{ + return ArrayToString(attributes, values.at(FieldGenre)); +} + +string ByCountry(SortAttribute attributes, const SortItem &values) +{ + return ArrayToString(attributes, values.at(FieldCountry)); +} + +string ByYear(SortAttribute attributes, const SortItem &values) +{ + CStdString label; + const CVariant &airDate = values.at(FieldAirDate); + if (!airDate.isNull() && !airDate.asString().empty()) + label = airDate.asString() + " "; + + label += StringUtils::Format("%i", (int)values.at(FieldYear).asInteger()); + + const CVariant &album = values.at(FieldAlbum); + if (!album.isNull()) + label += " " + SortUtils::RemoveArticles(album.asString()); + + const CVariant &track = values.at(FieldTrackNumber); + if (!track.isNull()) + label += StringUtils::Format(" %i", (int)track.asInteger()); + + label += " " + ByLabel(attributes, values); + + return label; +} + +string BySortTitle(SortAttribute attributes, const SortItem &values) +{ + string title = values.at(FieldSortTitle).asString(); + if (title.empty()) + title = values.at(FieldTitle).asString(); + + if (attributes & SortAttributeIgnoreArticle) + title = SortUtils::RemoveArticles(title); + + return title; +} + +string ByRating(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%f %s", values.at(FieldRating).asFloat(), ByLabel(attributes, values).c_str()); +} + +string ByVotes(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%d %s", (int)values.at(FieldVotes).asInteger(), ByLabel(attributes, values).c_str()); +} + +string ByTop250(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%d %s", (int)values.at(FieldTop250).asInteger(), ByLabel(attributes, values).c_str()); +} + +string ByMPAA(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldMPAA).asString() + " " + ByLabel(attributes, values); +} + +string ByStudio(SortAttribute attributes, const SortItem &values) +{ + return ArrayToString(attributes, values.at(FieldStudio)); +} + +string ByEpisodeNumber(SortAttribute attributes, const SortItem &values) +{ + // we calculate an offset number based on the episode's + // sort season and episode values. in addition + // we include specials 'episode' numbers to get proper + // sorting of multiple specials in a row. each + // of these are given their particular ranges to semi-ensure uniqueness. + // theoretical problem: if a show has > 2^15 specials and two of these are placed + // after each other they will sort backwards. if a show has > 2^32-1 seasons + // or if a season has > 2^16-1 episodes strange things will happen (overflow) + uint64_t num; + const CVariant &episodeSpecial = values.at(FieldEpisodeNumberSpecialSort); + const CVariant &seasonSpecial = values.at(FieldSeasonSpecialSort); + if (!episodeSpecial.isNull() && !seasonSpecial.isNull() && + (episodeSpecial.asInteger() > 0 || seasonSpecial.asInteger() > 0)) + num = ((uint64_t)seasonSpecial.asInteger() << 32) + (episodeSpecial.asInteger() << 16) - ((2 << 15) - values.at(FieldEpisodeNumber).asInteger()); + else + num = ((uint64_t)values.at(FieldSeason).asInteger() << 32) + (values.at(FieldEpisodeNumber).asInteger() << 16); + + std::string title; + if (values.find(FieldMediaType) != values.end() && values.at(FieldMediaType).asString() == MediaTypeMovie) + title = BySortTitle(attributes, values); + if (title.empty()) + title = ByLabel(attributes, values); + + return StringUtils::Format("%" PRIu64" %s", num, title.c_str()); +} + +string BySeason(SortAttribute attributes, const SortItem &values) +{ + int season = (int)values.at(FieldSeason).asInteger(); + const CVariant &specialSeason = values.at(FieldSeasonSpecialSort); + if (!specialSeason.isNull()) + season = (int)specialSeason.asInteger(); + + return StringUtils::Format("%i %s", season, ByLabel(attributes, values).c_str()); +} + +string ByNumberOfEpisodes(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%i %s", (int)values.at(FieldNumberOfEpisodes).asInteger(), ByLabel(attributes, values).c_str()); +} + +string ByNumberOfWatchedEpisodes(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%i %s", (int)values.at(FieldNumberOfWatchedEpisodes).asInteger(), ByLabel(attributes, values).c_str()); +} + +string ByTvShowStatus(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldTvShowStatus).asString() + " " + ByLabel(attributes, values); +} + +string ByTvShowTitle(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldTvShowTitle).asString() + " " + ByLabel(attributes, values); +} + +string ByProductionCode(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldProductionCode).asString(); +} + +string ByVideoResolution(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%i %s", (int)values.at(FieldVideoResolution).asInteger(), ByLabel(attributes, values).c_str()); +} + +string ByVideoCodec(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%s %s", values.at(FieldVideoCodec).asString().c_str(), ByLabel(attributes, values).c_str()); +} + +string ByVideoAspectRatio(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%.03f %s", values.at(FieldVideoAspectRatio).asFloat(), ByLabel(attributes, values).c_str()); +} + +string ByAudioChannels(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%i %s", (int)values.at(FieldAudioChannels).asInteger(), ByLabel(attributes, values).c_str()); +} + +string ByAudioCodec(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%s %s", values.at(FieldAudioCodec).asString().c_str(), ByLabel(attributes, values).c_str()); +} + +string ByAudioLanguage(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%s %s", values.at(FieldAudioLanguage).asString().c_str(), ByLabel(attributes, values).c_str());; +} + +string BySubtitleLanguage(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%s %s", values.at(FieldSubtitleLanguage).asString().c_str(), ByLabel(attributes, values).c_str()); +} + +string ByBitrate(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%" PRId64, values.at(FieldBitrate).asInteger()); +} + +string ByListeners(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%" PRId64, values.at(FieldListeners).asInteger());; +} + +string ByRandom(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%i", CUtil::GetRandomNumber());; +} + +string ByChannel(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldChannelName).asString(); +} + +string ByChannelNumber(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("%i", (int)values.at(FieldChannelNumber).asInteger()); +} + +string ByDateTaken(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldDateTaken).asString(); +} + +bool preliminarySort(const SortItem &left, const SortItem &right, bool handleFolder, bool &result, std::wstring &labelLeft, std::wstring &labelRight) +{ + // make sure both items have the necessary data to do the sorting + SortItem::const_iterator itLeftSort, itRightSort; + if ((itLeftSort = left.find(FieldSort)) == left.end()) + { + result = false; + return true; + } + if ((itRightSort = right.find(FieldSort)) == right.end()) + { + result = true; + return true; + } + + // look at special sorting behaviour + SortItem::const_iterator itLeft, itRight; + SortSpecial leftSortSpecial = SortSpecialNone; + SortSpecial rightSortSpecial = SortSpecialNone; + if ((itLeft = left.find(FieldSortSpecial)) != left.end() && itLeft->second.asInteger() <= (int64_t)SortSpecialOnBottom) + leftSortSpecial = (SortSpecial)itLeft->second.asInteger(); + if ((itRight = right.find(FieldSortSpecial)) != right.end() && itRight->second.asInteger() <= (int64_t)SortSpecialOnBottom) + rightSortSpecial = (SortSpecial)itRight->second.asInteger(); + + // one has a special sort + if (leftSortSpecial != rightSortSpecial) + { + // left should be sorted on top + // or right should be sorted on bottom + // => left is sorted above right + if (leftSortSpecial == SortSpecialOnTop || + rightSortSpecial == SortSpecialOnBottom) + { + result = true; + return true; + } + + // otherwise right is sorted above left + result = false; + return true; + } + // both have either sort on top or sort on bottom -> leave as-is + else if (leftSortSpecial != SortSpecialNone && leftSortSpecial == rightSortSpecial) + { + result = false; + return true; + } + + if (handleFolder) + { + itLeft = left.find(FieldFolder); + itRight = right.find(FieldFolder); + if (itLeft != left.end() && itRight != right.end() && + itLeft->second.asBoolean() != itRight->second.asBoolean()) + { + result = itLeft->second.asBoolean(); + return true; + } + } + + labelLeft = itLeftSort->second.asWideString(); + labelRight = itRightSort->second.asWideString(); + + return false; +} + +bool SorterAscending(const SortItem &left, const SortItem &right) +{ + bool result; + std::wstring labelLeft, labelRight; + if (preliminarySort(left, right, true, result, labelLeft, labelRight)) + return result; + + return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) < 0; +} + +bool SorterDescending(const SortItem &left, const SortItem &right) +{ + bool result; + std::wstring labelLeft, labelRight; + if (preliminarySort(left, right, true, result, labelLeft, labelRight)) + return result; + + return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) > 0; +} + +bool SorterIgnoreFoldersAscending(const SortItem &left, const SortItem &right) +{ + bool result; + std::wstring labelLeft, labelRight; + if (preliminarySort(left, right, false, result, labelLeft, labelRight)) + return result; + + return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) < 0; +} + +bool SorterIgnoreFoldersDescending(const SortItem &left, const SortItem &right) +{ + bool result; + std::wstring labelLeft, labelRight; + if (preliminarySort(left, right, false, result, labelLeft, labelRight)) + return result; + + return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) > 0; +} + +bool SorterIndirectAscending(const SortItemPtr &left, const SortItemPtr &right) +{ + return SorterAscending(*left, *right); +} + +bool SorterIndirectDescending(const SortItemPtr &left, const SortItemPtr &right) +{ + return SorterDescending(*left, *right); +} + +bool SorterIndirectIgnoreFoldersAscending(const SortItemPtr &left, const SortItemPtr &right) +{ + return SorterIgnoreFoldersAscending(*left, *right); +} + +bool SorterIndirectIgnoreFoldersDescending(const SortItemPtr &left, const SortItemPtr &right) +{ + return SorterIgnoreFoldersDescending(*left, *right); +} + +map<SortBy, SortUtils::SortPreparator> fillPreparators() +{ + map<SortBy, SortUtils::SortPreparator> preparators; + + preparators[SortByNone] = NULL; + preparators[SortByLabel] = ByLabel; + preparators[SortByDate] = ByDate; + preparators[SortBySize] = BySize; + preparators[SortByFile] = ByFile; + preparators[SortByPath] = ByPath; + preparators[SortByDriveType] = ByDriveType; + preparators[SortByTitle] = ByTitle; + preparators[SortByTrackNumber] = ByTrackNumber; + preparators[SortByTime] = ByTime; + preparators[SortByArtist] = ByArtist; + preparators[SortByAlbum] = ByAlbum; + preparators[SortByAlbumType] = ByAlbumType; + preparators[SortByGenre] = ByGenre; + preparators[SortByCountry] = ByCountry; + preparators[SortByYear] = ByYear; + preparators[SortByRating] = ByRating; + preparators[SortByVotes] = ByVotes; + preparators[SortByTop250] = ByTop250; + preparators[SortByProgramCount] = ByProgramCount; + preparators[SortByPlaylistOrder] = ByPlaylistOrder; + preparators[SortByEpisodeNumber] = ByEpisodeNumber; + preparators[SortBySeason] = BySeason; + preparators[SortByNumberOfEpisodes] = ByNumberOfEpisodes; + preparators[SortByNumberOfWatchedEpisodes] = ByNumberOfWatchedEpisodes; + preparators[SortByTvShowStatus] = ByTvShowStatus; + preparators[SortByTvShowTitle] = ByTvShowTitle; + preparators[SortBySortTitle] = BySortTitle; + preparators[SortByProductionCode] = ByProductionCode; + preparators[SortByMPAA] = ByMPAA; + preparators[SortByVideoResolution] = ByVideoResolution; + preparators[SortByVideoCodec] = ByVideoCodec; + preparators[SortByVideoAspectRatio] = ByVideoAspectRatio; + preparators[SortByAudioChannels] = ByAudioChannels; + preparators[SortByAudioCodec] = ByAudioCodec; + preparators[SortByAudioLanguage] = ByAudioLanguage; + preparators[SortBySubtitleLanguage] = BySubtitleLanguage; + preparators[SortByStudio] = ByStudio; + preparators[SortByDateAdded] = ByDateAdded; + preparators[SortByLastPlayed] = ByLastPlayed; + preparators[SortByPlaycount] = ByPlaycount; + preparators[SortByListeners] = ByListeners; + preparators[SortByBitrate] = ByBitrate; + preparators[SortByRandom] = ByRandom; + preparators[SortByChannel] = ByChannel; + preparators[SortByChannelNumber] = ByChannelNumber; + preparators[SortByDateTaken] = ByDateTaken; + + return preparators; +} + +map<SortBy, Fields> fillSortingFields() +{ + map<SortBy, Fields> sortingFields; + + sortingFields.insert(pair<SortBy, Fields>(SortByNone, Fields())); + + sortingFields[SortByLabel].insert(FieldLabel); + sortingFields[SortByDate].insert(FieldDate); + sortingFields[SortBySize].insert(FieldSize); + sortingFields[SortByFile].insert(FieldPath); + sortingFields[SortByFile].insert(FieldStartOffset); + sortingFields[SortByPath].insert(FieldPath); + sortingFields[SortByPath].insert(FieldStartOffset); + sortingFields[SortByDriveType].insert(FieldDriveType); + sortingFields[SortByTitle].insert(FieldTitle); + sortingFields[SortByTrackNumber].insert(FieldTrackNumber); + sortingFields[SortByTime].insert(FieldTime); + sortingFields[SortByArtist].insert(FieldArtist); + sortingFields[SortByArtist].insert(FieldYear); + sortingFields[SortByArtist].insert(FieldAlbum); + sortingFields[SortByArtist].insert(FieldTrackNumber); + sortingFields[SortByAlbum].insert(FieldAlbum); + sortingFields[SortByAlbum].insert(FieldArtist); + sortingFields[SortByAlbum].insert(FieldTrackNumber); + sortingFields[SortByAlbumType].insert(FieldAlbumType); + sortingFields[SortByGenre].insert(FieldGenre); + sortingFields[SortByCountry].insert(FieldCountry); + sortingFields[SortByYear].insert(FieldYear); + sortingFields[SortByYear].insert(FieldAirDate); + sortingFields[SortByYear].insert(FieldAlbum); + sortingFields[SortByYear].insert(FieldTrackNumber); + sortingFields[SortByRating].insert(FieldRating); + sortingFields[SortByVotes].insert(FieldVotes); + sortingFields[SortByTop250].insert(FieldTop250); + sortingFields[SortByProgramCount].insert(FieldProgramCount); + sortingFields[SortByPlaylistOrder].insert(FieldProgramCount); + sortingFields[SortByEpisodeNumber].insert(FieldEpisodeNumber); + sortingFields[SortByEpisodeNumber].insert(FieldSeason); + sortingFields[SortByEpisodeNumber].insert(FieldEpisodeNumberSpecialSort); + sortingFields[SortByEpisodeNumber].insert(FieldSeasonSpecialSort); + sortingFields[SortByEpisodeNumber].insert(FieldTitle); + sortingFields[SortByEpisodeNumber].insert(FieldSortTitle); + sortingFields[SortBySeason].insert(FieldSeason); + sortingFields[SortBySeason].insert(FieldSeasonSpecialSort); + sortingFields[SortByNumberOfEpisodes].insert(FieldNumberOfEpisodes); + sortingFields[SortByNumberOfWatchedEpisodes].insert(FieldNumberOfWatchedEpisodes); + sortingFields[SortByTvShowStatus].insert(FieldTvShowStatus); + sortingFields[SortByTvShowTitle].insert(FieldTvShowTitle); + sortingFields[SortBySortTitle].insert(FieldSortTitle); + sortingFields[SortBySortTitle].insert(FieldTitle); + sortingFields[SortByProductionCode].insert(FieldProductionCode); + sortingFields[SortByMPAA].insert(FieldMPAA); + sortingFields[SortByVideoResolution].insert(FieldVideoResolution); + sortingFields[SortByVideoCodec].insert(FieldVideoCodec); + sortingFields[SortByVideoAspectRatio].insert(FieldVideoAspectRatio); + sortingFields[SortByAudioChannels].insert(FieldAudioChannels); + sortingFields[SortByAudioCodec].insert(FieldAudioCodec); + sortingFields[SortByAudioLanguage].insert(FieldAudioLanguage); + sortingFields[SortBySubtitleLanguage].insert(FieldSubtitleLanguage); + sortingFields[SortByStudio].insert(FieldStudio); + sortingFields[SortByDateAdded].insert(FieldDateAdded); + sortingFields[SortByDateAdded].insert(FieldId); + sortingFields[SortByLastPlayed].insert(FieldLastPlayed); + sortingFields[SortByPlaycount].insert(FieldPlaycount); + sortingFields[SortByListeners].insert(FieldListeners); + sortingFields[SortByBitrate].insert(FieldBitrate); + sortingFields[SortByChannel].insert(FieldChannelName); + sortingFields[SortByChannelNumber].insert(FieldChannelNumber); + sortingFields[SortByDateTaken].insert(FieldDateTaken); + sortingFields.insert(pair<SortBy, Fields>(SortByRandom, Fields())); + + return sortingFields; +} + +map<SortBy, SortUtils::SortPreparator> SortUtils::m_preparators = fillPreparators(); +map<SortBy, Fields> SortUtils::m_sortingFields = fillSortingFields(); + +void SortUtils::Sort(SortBy sortBy, SortOrder sortOrder, SortAttribute attributes, DatabaseResults& items, int limitEnd /* = -1 */, int limitStart /* = 0 */) +{ + if (sortBy != SortByNone) + { + // get the matching SortPreparator + SortPreparator preparator = getPreparator(sortBy); + if (preparator != NULL) + { + Fields sortingFields = GetFieldsForSorting(sortBy); + + // Prepare the string used for sorting and store it under FieldSort + for (DatabaseResults::iterator item = items.begin(); item != items.end(); ++item) + { + // add all fields to the item that are required for sorting if they are currently missing + for (Fields::const_iterator field = sortingFields.begin(); field != sortingFields.end(); ++field) + { + if (item->find(*field) == item->end()) + item->insert(pair<Field, CVariant>(*field, CVariant::ConstNullVariant)); + } + + CStdStringW sortLabel; + g_charsetConverter.utf8ToW(preparator(attributes, *item), sortLabel, false); + item->insert(pair<Field, CVariant>(FieldSort, CVariant(sortLabel))); + } + + // Do the sorting + std::stable_sort(items.begin(), items.end(), getSorter(sortOrder, attributes)); + } + } + + if (limitStart > 0 && (size_t)limitStart < items.size()) + { + items.erase(items.begin(), items.begin() + limitStart); + limitEnd -= limitStart; + } + if (limitEnd > 0 && (size_t)limitEnd < items.size()) + items.erase(items.begin() + limitEnd, items.end()); +} + +void SortUtils::Sort(SortBy sortBy, SortOrder sortOrder, SortAttribute attributes, SortItems& items, int limitEnd /* = -1 */, int limitStart /* = 0 */) +{ + if (sortBy != SortByNone) + { + // get the matching SortPreparator + SortPreparator preparator = getPreparator(sortBy); + if (preparator != NULL) + { + Fields sortingFields = GetFieldsForSorting(sortBy); + + // Prepare the string used for sorting and store it under FieldSort + for (SortItems::iterator item = items.begin(); item != items.end(); ++item) + { + // add all fields to the item that are required for sorting if they are currently missing + for (Fields::const_iterator field = sortingFields.begin(); field != sortingFields.end(); ++field) + { + if ((*item)->find(*field) == (*item)->end()) + (*item)->insert(pair<Field, CVariant>(*field, CVariant::ConstNullVariant)); + } + + CStdStringW sortLabel; + g_charsetConverter.utf8ToW(preparator(attributes, **item), sortLabel, false); + (*item)->insert(pair<Field, CVariant>(FieldSort, CVariant(sortLabel))); + } + + // Do the sorting + std::stable_sort(items.begin(), items.end(), getSorterIndirect(sortOrder, attributes)); + } + } + + if (limitStart > 0 && (size_t)limitStart < items.size()) + { + items.erase(items.begin(), items.begin() + limitStart); + limitEnd -= limitStart; + } + if (limitEnd > 0 && (size_t)limitEnd < items.size()) + items.erase(items.begin() + limitEnd, items.end()); +} + +void SortUtils::Sort(const SortDescription &sortDescription, DatabaseResults& items) +{ + Sort(sortDescription.sortBy, sortDescription.sortOrder, sortDescription.sortAttributes, items, sortDescription.limitEnd, sortDescription.limitStart); +} + +void SortUtils::Sort(const SortDescription &sortDescription, SortItems& items) +{ + Sort(sortDescription.sortBy, sortDescription.sortOrder, sortDescription.sortAttributes, items, sortDescription.limitEnd, sortDescription.limitStart); +} + +bool SortUtils::SortFromDataset(const SortDescription &sortDescription, const MediaType &mediaType, const std::auto_ptr<dbiplus::Dataset> &dataset, DatabaseResults &results) +{ + FieldList fields; + if (!DatabaseUtils::GetSelectFields(SortUtils::GetFieldsForSorting(sortDescription.sortBy), mediaType, fields)) + fields.clear(); + + if (!DatabaseUtils::GetDatabaseResults(mediaType, fields, dataset, results)) + return false; + + SortDescription sorting = sortDescription; + if (sortDescription.sortBy == SortByNone) + { + sorting.limitStart = 0; + sorting.limitEnd = -1; + } + + Sort(sorting, results); + + return true; +} + +const SortUtils::SortPreparator& SortUtils::getPreparator(SortBy sortBy) +{ + map<SortBy, SortPreparator>::const_iterator it = m_preparators.find(sortBy); + if (it != m_preparators.end()) + return it->second; + + return m_preparators[SortByNone]; +} + +SortUtils::Sorter SortUtils::getSorter(SortOrder sortOrder, SortAttribute attributes) +{ + if (attributes & SortAttributeIgnoreFolders) + return sortOrder == SortOrderDescending ? SorterIgnoreFoldersDescending : SorterIgnoreFoldersAscending; + + return sortOrder == SortOrderDescending ? SorterDescending : SorterAscending; +} + +SortUtils::SorterIndirect SortUtils::getSorterIndirect(SortOrder sortOrder, SortAttribute attributes) +{ + if (attributes & SortAttributeIgnoreFolders) + return sortOrder == SortOrderDescending ? SorterIndirectIgnoreFoldersDescending : SorterIndirectIgnoreFoldersAscending; + + return sortOrder == SortOrderDescending ? SorterIndirectDescending : SorterIndirectAscending; +} + +const Fields& SortUtils::GetFieldsForSorting(SortBy sortBy) +{ + map<SortBy, Fields>::const_iterator it = m_sortingFields.find(sortBy); + if (it != m_sortingFields.end()) + return it->second; + + return m_sortingFields[SortByNone]; +} + +string SortUtils::RemoveArticles(const string &label) +{ + for (unsigned int i = 0; i < g_advancedSettings.m_vecTokens.size(); ++i) + { + if (g_advancedSettings.m_vecTokens[i].size() < label.size() && + strnicmp(g_advancedSettings.m_vecTokens[i].c_str(), label.c_str(), g_advancedSettings.m_vecTokens[i].size()) == 0) + return label.substr(g_advancedSettings.m_vecTokens[i].size()); + } + + return label; +} + +typedef struct +{ + SortBy sort; + SORT_METHOD old; + SortAttribute flags; + int label; +} sort_map; + +const sort_map table[] = { + { SortByLabel, SORT_METHOD_LABEL, SortAttributeNone, 551 }, + { SortByLabel, SORT_METHOD_LABEL_IGNORE_THE, SortAttributeIgnoreArticle, 551 }, + { SortByLabel, SORT_METHOD_LABEL_IGNORE_FOLDERS, SortAttributeIgnoreFolders, 551 }, + { SortByDate, SORT_METHOD_DATE, SortAttributeNone, 552 }, + { SortBySize, SORT_METHOD_SIZE, SortAttributeNone, 553 }, + { SortByBitrate, SORT_METHOD_BITRATE, SortAttributeNone, 623 }, + { SortByDriveType, SORT_METHOD_DRIVE_TYPE, SortAttributeNone, 564 }, + { SortByTrackNumber, SORT_METHOD_TRACKNUM, SortAttributeNone, 554 }, + { SortByEpisodeNumber, SORT_METHOD_EPISODE, SortAttributeNone, 20359 },// 20360 "Episodes" used for SORT_METHOD_EPISODE for sorting tvshows by episode count + { SortByTime, SORT_METHOD_DURATION, SortAttributeNone, 180 }, + { SortByTime, SORT_METHOD_VIDEO_RUNTIME, SortAttributeNone, 180 }, + { SortByTitle, SORT_METHOD_TITLE, SortAttributeNone, 556 }, + { SortByTitle, SORT_METHOD_TITLE_IGNORE_THE, SortAttributeIgnoreArticle, 556 }, + { SortByTitle, SORT_METHOD_VIDEO_TITLE, SortAttributeNone, 556 }, + { SortByArtist, SORT_METHOD_ARTIST, SortAttributeNone, 557 }, + { SortByArtist, SORT_METHOD_ARTIST_IGNORE_THE, SortAttributeIgnoreArticle, 557 }, + { SortByAlbum, SORT_METHOD_ALBUM, SortAttributeNone, 558 }, + { SortByAlbum, SORT_METHOD_ALBUM_IGNORE_THE, SortAttributeIgnoreArticle, 558 }, + { SortByGenre, SORT_METHOD_GENRE, SortAttributeNone, 515 }, + { SortByCountry, SORT_METHOD_COUNTRY, SortAttributeNone, 574 }, + { SortByDateAdded, SORT_METHOD_DATEADDED, SortAttributeIgnoreFolders, 570 }, + { SortByFile, SORT_METHOD_FILE, SortAttributeIgnoreFolders, 561 }, + { SortByRating, SORT_METHOD_SONG_RATING, SortAttributeNone, 563 }, + { SortByRating, SORT_METHOD_VIDEO_RATING, SortAttributeIgnoreFolders, 563 }, + { SortBySortTitle, SORT_METHOD_VIDEO_SORT_TITLE, SortAttributeIgnoreFolders, 171 }, + { SortBySortTitle, SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE, (SortAttribute)(SortAttributeIgnoreFolders | SortAttributeIgnoreArticle), 171 }, + { SortByYear, SORT_METHOD_YEAR, SortAttributeIgnoreFolders, 562 }, + { SortByProductionCode, SORT_METHOD_PRODUCTIONCODE, SortAttributeNone, 20368 }, + { SortByProgramCount, SORT_METHOD_PROGRAM_COUNT, SortAttributeNone, 567 }, // label is "play count" + { SortByPlaylistOrder, SORT_METHOD_PLAYLIST_ORDER, SortAttributeIgnoreFolders, 559 }, + { SortByMPAA, SORT_METHOD_MPAA_RATING, SortAttributeNone, 20074 }, + { SortByStudio, SORT_METHOD_STUDIO, SortAttributeNone, 572 }, + { SortByStudio, SORT_METHOD_STUDIO_IGNORE_THE, SortAttributeIgnoreArticle, 572 }, + { SortByPath, SORT_METHOD_FULLPATH, SortAttributeNone, 573 }, + { SortByLastPlayed, SORT_METHOD_LASTPLAYED, SortAttributeIgnoreFolders, 568 }, + { SortByPlaycount, SORT_METHOD_PLAYCOUNT, SortAttributeIgnoreFolders, 567 }, + { SortByListeners, SORT_METHOD_LISTENERS, SortAttributeNone, 20455 }, + { SortByChannel, SORT_METHOD_CHANNEL, SortAttributeNone, 19029 }, + { SortByChannel, SORT_METHOD_CHANNEL_NUMBER, SortAttributeNone, 549 }, + { SortByDateTaken, SORT_METHOD_DATE_TAKEN, SortAttributeIgnoreFolders, 577 }, + { SortByNone, SORT_METHOD_NONE, SortAttributeNone, 16018 }, + // the following have no corresponding SORT_METHOD_* + { SortByAlbumType, SORT_METHOD_NONE, SortAttributeNone, 564 }, + { SortByVotes, SORT_METHOD_NONE, SortAttributeNone, 205 }, + { SortByTop250, SORT_METHOD_NONE, SortAttributeNone, 13409 }, + { SortByMPAA, SORT_METHOD_NONE, SortAttributeNone, 20074 }, + { SortByDateAdded, SORT_METHOD_NONE, SortAttributeNone, 570 }, + { SortByTvShowTitle, SORT_METHOD_NONE, SortAttributeNone, 20364 }, + { SortByTvShowStatus, SORT_METHOD_NONE, SortAttributeNone, 126 }, + { SortBySeason, SORT_METHOD_NONE, SortAttributeNone, 20373 }, + { SortByNumberOfEpisodes, SORT_METHOD_NONE, SortAttributeNone, 20360 }, + { SortByNumberOfWatchedEpisodes, SORT_METHOD_NONE, SortAttributeNone, 21441 }, + { SortByVideoResolution, SORT_METHOD_NONE, SortAttributeNone, 21443 }, + { SortByVideoCodec, SORT_METHOD_NONE, SortAttributeNone, 21445 }, + { SortByVideoAspectRatio, SORT_METHOD_NONE, SortAttributeNone, 21374 }, + { SortByAudioChannels, SORT_METHOD_NONE, SortAttributeNone, 21444 }, + { SortByAudioCodec, SORT_METHOD_NONE, SortAttributeNone, 21446 }, + { SortByAudioLanguage, SORT_METHOD_NONE, SortAttributeNone, 21447 }, + { SortBySubtitleLanguage, SORT_METHOD_NONE, SortAttributeNone, 21448 }, + { SortByRandom, SORT_METHOD_NONE, SortAttributeNone, 590 } +}; + +SORT_METHOD SortUtils::TranslateOldSortMethod(SortBy sortBy, bool ignoreArticle) +{ + for (size_t i = 0; i < sizeof(table) / sizeof(sort_map); i++) + { + if (table[i].sort == sortBy) + { + if (ignoreArticle == ((table[i].flags & SortAttributeIgnoreArticle) == SortAttributeIgnoreArticle)) + return table[i].old; + } + } + for (size_t i = 0; i < sizeof(table) / sizeof(sort_map); i++) + { + if (table[i].sort == sortBy) + return table[i].old; + } + return SORT_METHOD_NONE; +} + +SortDescription SortUtils::TranslateOldSortMethod(SORT_METHOD sortBy) +{ + SortDescription description; + for (size_t i = 0; i < sizeof(table) / sizeof(sort_map); i++) + { + if (table[i].old == sortBy) + { + description.sortBy = table[i].sort; + description.sortAttributes = table[i].flags; + break; + } + } + return description; +} + +int SortUtils::GetSortLabel(SortBy sortBy) +{ + for (size_t i = 0; i < sizeof(table) / sizeof(sort_map); i++) + { + if (table[i].sort == sortBy) + return table[i].label; + } + return 16018; // None +} diff --git a/src/utils/SortUtils.h b/src/utils/SortUtils.h new file mode 100644 index 0000000000..0984af0e4b --- /dev/null +++ b/src/utils/SortUtils.h @@ -0,0 +1,154 @@ +#pragma once +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> +#include <string> +#include "boost/shared_ptr.hpp" + +#include "DatabaseUtils.h" +#include "SortFileItem.h" +#include "LabelFormatter.h" + +typedef enum { + SortOrderNone = 0, + SortOrderAscending, + SortOrderDescending +} SortOrder; + +typedef enum { + SortAttributeNone = 0x0, + SortAttributeIgnoreArticle = 0x1, + SortAttributeIgnoreFolders = 0x2 +} SortAttribute; + +typedef enum { + SortSpecialNone = 0, + SortSpecialOnTop = 1, + SortSpecialOnBottom = 2 +} SortSpecial; + +typedef enum { + SortByNone = 0, + SortByLabel, + SortByDate, + SortBySize, + SortByFile, + SortByPath, + SortByDriveType, + SortByTitle, + SortByTrackNumber, + SortByTime, + SortByArtist, + SortByAlbum, + SortByAlbumType, + SortByGenre, + SortByCountry, + SortByYear, + SortByRating, + SortByVotes, + SortByTop250, + SortByProgramCount, + SortByPlaylistOrder, + SortByEpisodeNumber, + SortBySeason, + SortByNumberOfEpisodes, + SortByNumberOfWatchedEpisodes, + SortByTvShowStatus, + SortByTvShowTitle, + SortBySortTitle, + SortByProductionCode, + SortByMPAA, + SortByVideoResolution, + SortByVideoCodec, + SortByVideoAspectRatio, + SortByAudioChannels, + SortByAudioCodec, + SortByAudioLanguage, + SortBySubtitleLanguage, + SortByStudio, + SortByDateAdded, + SortByLastPlayed, + SortByPlaycount, + SortByListeners, + SortByBitrate, + SortByRandom, + SortByChannel, + SortByChannelNumber, + SortByDateTaken +} SortBy; + +typedef struct SortDescription { + SortBy sortBy; + SortOrder sortOrder; + SortAttribute sortAttributes; + int limitStart; + int limitEnd; + + SortDescription() + : sortBy(SortByNone), sortOrder(SortOrderAscending), sortAttributes(SortAttributeNone), + limitStart(0), limitEnd(-1) + { } +} SortDescription; + +typedef struct +{ + SortDescription m_sortDescription; + int m_buttonLabel; + LABEL_MASKS m_labelMasks; +} SORT_METHOD_DETAILS; + +typedef DatabaseResult SortItem; +typedef boost::shared_ptr<SortItem> SortItemPtr; +typedef std::vector<SortItemPtr> SortItems; + +class SortUtils +{ +public: + static SORT_METHOD TranslateOldSortMethod(SortBy sortBy, bool ignoreArticle); + static SortDescription TranslateOldSortMethod(SORT_METHOD sortBy); + + /*! \brief retrieve the label id associated with a sort method for displaying in the UI. + \param sortBy the sort method in question. + \return the label id of the sort method. + */ + static int GetSortLabel(SortBy sortBy); + + static void Sort(SortBy sortBy, SortOrder sortOrder, SortAttribute attributes, DatabaseResults& items, int limitEnd = -1, int limitStart = 0); + static void Sort(SortBy sortBy, SortOrder sortOrder, SortAttribute attributes, SortItems& items, int limitEnd = -1, int limitStart = 0); + static void Sort(const SortDescription &sortDescription, DatabaseResults& items); + static void Sort(const SortDescription &sortDescription, SortItems& items); + static bool SortFromDataset(const SortDescription &sortDescription, const MediaType &mediaType, const std::auto_ptr<dbiplus::Dataset> &dataset, DatabaseResults &results); + + static const Fields& GetFieldsForSorting(SortBy sortBy); + static std::string RemoveArticles(const std::string &label); + + typedef std::string (*SortPreparator) (SortAttribute, const SortItem&); + typedef bool (*Sorter) (const DatabaseResult &, const DatabaseResult &); + typedef bool (*SorterIndirect) (const SortItemPtr &, const SortItemPtr &); + +private: + static const SortPreparator& getPreparator(SortBy sortBy); + static Sorter getSorter(SortOrder sortOrder, SortAttribute attributes); + static SorterIndirect getSorterIndirect(SortOrder sortOrder, SortAttribute attributes); + + static std::map<SortBy, SortPreparator> m_preparators; + static std::map<SortBy, Fields> m_sortingFields; +}; diff --git a/src/utils/Splash.cpp b/src/utils/Splash.cpp new file mode 100644 index 0000000000..764f64ab66 --- /dev/null +++ b/src/utils/Splash.cpp @@ -0,0 +1,136 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" +#include "Splash.h" +#include "guilib/GUIImage.h" +#include "guilib/GUILabelControl.h" +#include "guilib/GUIFontManager.h" +#include "filesystem/File.h" +#include "windowing/WindowingFactory.h" +#include "rendering/RenderSystem.h" +#include "log.h" + +using namespace XFILE; + +CSplash::CSplash(const std::string& imageName) : CThread("Splash"), m_ImageName(imageName) +{ + fade = 0.5; + m_messageLayout = NULL; + m_image = NULL; + m_layoutWasLoading = false; +} + + +CSplash::~CSplash() +{ + Stop(); + delete m_image; + delete m_messageLayout; +} + +void CSplash::OnStartup() +{} + +void CSplash::OnExit() +{} + +void CSplash::Show() +{ + Show(""); +} + +void CSplash::Show(const std::string& message) +{ + g_graphicsContext.Lock(); + g_graphicsContext.Clear(); + + RESOLUTION_INFO res(1280,720,0); + g_graphicsContext.SetRenderingResolution(res, true); + if (!m_image) + { + m_image = new CGUIImage(0, 0, 0, 0, 1280, 720, CTextureInfo(m_ImageName)); + m_image->SetAspectRatio(CAspectRatio::AR_CENTER); + } + + //render splash image + g_Windowing.BeginRender(); + + m_image->AllocResources(); + m_image->Render(); + m_image->FreeResources(); + + // render message + if (!message.empty()) + { + if (!m_layoutWasLoading) + { + // load arial font, white body, no shadow, size: 20, no additional styling + CGUIFont *messageFont = g_fontManager.LoadTTF("__splash__", "arial.ttf", 0xFFFFFFFF, 0, 20, FONT_STYLE_NORMAL, false, 1.0f, 1.0f, &res); + if (messageFont) + m_messageLayout = new CGUITextLayout(messageFont, true, 0); + m_layoutWasLoading = true; + } + if (m_messageLayout) + { + m_messageLayout->Update(message, 1150, false, true); + + float textWidth, textHeight; + m_messageLayout->GetTextExtent(textWidth, textHeight); + // ideally place text in center of empty area below splash image + float y = 540 + m_image->GetTextureHeight() / 4 - textHeight / 2; + if (y + textHeight > 720) // make sure entire text is visible + y = 720 - textHeight; + + m_messageLayout->RenderOutline(640, y, 0, 0xFF000000, XBFONT_CENTER_X, 1280); + } + } + + //show it on screen + g_Windowing.EndRender(); + CDirtyRegionList dirty; + g_graphicsContext.Flip(dirty); + g_graphicsContext.Unlock(); +} + +void CSplash::Hide() +{ +} + +void CSplash::Process() +{ + Show(); +} + +bool CSplash::Start() +{ + if (m_ImageName.empty() || !CFile::Exists(m_ImageName)) + { + CLog::Log(LOGDEBUG, "Splash image %s not found", m_ImageName.c_str()); + return false; + } + Create(); + return true; +} + +void CSplash::Stop() +{ + StopThread(); +} diff --git a/src/utils/Splash.h b/src/utils/Splash.h new file mode 100644 index 0000000000..2cd7a4b13c --- /dev/null +++ b/src/utils/Splash.h @@ -0,0 +1,59 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include "threads/Thread.h" + +class CGUITextLayout; +class CGUIImage; + +class CSplash : public CThread +{ +public: + CSplash(const std::string& imageName); + virtual ~CSplash(); + + bool Start(); + void Stop(); + + // In case you don't want to use another thread + void Show(); + void Show(const std::string& message); + void Hide(); + +private: + virtual void Process(); + virtual void OnStartup(); + virtual void OnExit(); + + float fade; + std::string m_ImageName; + + CGUITextLayout* m_messageLayout; + CGUIImage* m_image; + bool m_layoutWasLoading; +#ifdef HAS_DX + D3DGAMMARAMP newRamp; + D3DGAMMARAMP oldRamp; + +#endif +}; diff --git a/src/utils/StdString.h b/src/utils/StdString.h new file mode 100644 index 0000000000..6430f1008a --- /dev/null +++ b/src/utils/StdString.h @@ -0,0 +1,3154 @@ +#pragma once +#include <string> +#include <stdint.h> +#include <vector> + +#if defined(TARGET_WINDOWS) && !defined(va_copy) +#define va_copy(dst, src) ((dst) = (src)) +#endif + +// ============================================================================= +// FILE: StdString.h +// AUTHOR: Joe O'Leary (with outside help noted in comments) +// +// If you find any bugs in this code, please let me know: +// +// jmoleary@earthlink.net +// http://www.joeo.net/stdstring.htm (a bit outdated) +// +// The latest version of this code should always be available at the +// following link: +// +// http://www.joeo.net/code/StdString.zip (Dec 6, 2003) +// +// +// REMARKS: +// This header file declares the CStdStr template. This template derives +// the Standard C++ Library basic_string<> template and add to it the +// the following conveniences: +// - The full MFC CString set of functions (including implicit cast) +// - writing to/reading from COM IStream interfaces +// - Functional objects for use in STL algorithms +// +// From this template, we intstantiate two classes: CStdStringA and +// CStdStringW. The name "CStdString" is just a #define of one of these, +// based upone the UNICODE macro setting +// +// This header also declares our own version of the MFC/ATL UNICODE-MBCS +// conversion macros. Our version looks exactly like the Microsoft's to +// facilitate portability. +// +// NOTE: +// If you you use this in an MFC or ATL build, you should include either +// afx.h or atlbase.h first, as appropriate. +// +// PEOPLE WHO HAVE CONTRIBUTED TO THIS CLASS: +// +// Several people have helped me iron out problems and othewise improve +// this class. OK, this is a long list but in my own defense, this code +// has undergone two major rewrites. Many of the improvements became +// necessary after I rewrote the code as a template. Others helped me +// improve the CString facade. +// +// Anyway, these people are (in chronological order): +// +// - Pete the Plumber (???) +// - Julian Selman +// - Chris (of Melbsys) +// - Dave Plummer +// - John C Sipos +// - Chris Sells +// - Nigel Nunn +// - Fan Xia +// - Matthew Williams +// - Carl Engman +// - Mark Zeren +// - Craig Watson +// - Rich Zuris +// - Karim Ratib +// - Chris Conti +// - Baptiste Lepilleur +// - Greg Pickles +// - Jim Cline +// - Jeff Kohn +// - Todd Heckel +// - Ullrich Poll�hne +// - Joe Vitaterna +// - Joe Woodbury +// - Aaron (no last name) +// - Joldakowski (???) +// - Scott Hathaway +// - Eric Nitzche +// - Pablo Presedo +// - Farrokh Nejadlotfi +// - Jason Mills +// - Igor Kholodov +// - Mike Crusader +// - John James +// - Wang Haifeng +// - Tim Dowty +// - Arnt Witteveen +// - Glen Maynard +// - Paul DeMarco +// - Bagira (full name?) +// - Ronny Schulz +// - Jakko Van Hunen +// - Charles Godwin +// - Henk Demper +// - Greg Marr +// - Bill Carducci +// - Brian Groose +// - MKingman +// - Don Beusee +// +// REVISION HISTORY +// +// 2005-JAN-10 - Thanks to Don Beusee for pointing out the danger in mapping +// length-checked formatting functions to non-length-checked +// CRT equivalents. Also thanks to him for motivating me to +// optimize my implementation of Replace() +// +// 2004-APR-22 - A big, big thank you to "MKingman" (whoever you are) for +// finally spotting a silly little error in StdCodeCvt that +// has been causing me (and users of CStdString) problems for +// years in some relatively rare conversions. I had reversed +// two length arguments. +// +// 2003-NOV-24 - Thanks to a bunch of people for helping me clean up many +// compiler warnings (and yes, even a couple of actual compiler +// errors). These include Henk Demper for figuring out how +// to make the Intellisense work on with CStdString on VC6, +// something I was never able to do. Greg Marr pointed out +// a compiler warning about an unreferenced symbol and a +// problem with my version of Load in MFC builds. Bill +// Carducci took a lot of time with me to help me figure out +// why some implementations of the Standard C++ Library were +// returning error codes for apparently successful conversions +// between ASCII and UNICODE. Finally thanks to Brian Groose +// for helping me fix compiler signed unsigned warnings in +// several functions. +// +// 2003-JUL-10 - Thanks to Charles Godwin for making me realize my 'FmtArg' +// fixes had inadvertently broken the DLL-export code (which is +// normally commented out. I had to move it up higher. Also +// this helped me catch a bug in ssicoll that would prevent +// compilation, otherwise. +// +// 2003-MAR-14 - Thanks to Jakko Van Hunen for pointing out a copy-and-paste +// bug in one of the overloads of FmtArg. +// +// 2003-MAR-10 - Thanks to Ronny Schulz for (twice!) sending me some changes +// to help CStdString build on SGI and for pointing out an +// error in placement of my preprocessor macros for ssfmtmsg. +// +// 2002-NOV-26 - Thanks to Bagira for pointing out that my implementation of +// SpanExcluding was not properly handling the case in which +// the string did NOT contain any of the given characters +// +// 2002-OCT-21 - Many thanks to Paul DeMarco who was invaluable in helping me +// get this code working with Borland's free compiler as well +// as the Dev-C++ compiler (available free at SourceForge). +// +// 2002-SEP-13 - Thanks to Glen Maynard who helped me get rid of some loud +// but harmless warnings that were showing up on g++. Glen +// also pointed out that some pre-declarations of FmtArg<> +// specializations were unnecessary (and no good on G++) +// +// 2002-JUN-26 - Thanks to Arnt Witteveen for pointing out that I was using +// static_cast<> in a place in which I should have been using +// reinterpret_cast<> (the ctor for unsigned char strings). +// That's what happens when I don't unit-test properly! +// Arnt also noticed that CString was silently correcting the +// 'nCount' argument to Left() and Right() where CStdString was +// not (and crashing if it was bad). That is also now fixed! +// +// 2002-FEB-25 - Thanks to Tim Dowty for pointing out (and giving me the fix +// for) a conversion problem with non-ASCII MBCS characters. +// CStdString is now used in my favorite commercial MP3 player! +// +// 2001-DEC-06 - Thanks to Wang Haifeng for spotting a problem in one of the +// assignment operators (for _bstr_t) that would cause compiler +// errors when refcounting protection was turned off. +// +// 2001-NOV-27 - Remove calls to operator!= which involve reverse_iterators +// due to a conflict with the rel_ops operator!=. Thanks to +// John James for pointing this out. +// +// 2001-OCT-29 - Added a minor range checking fix for the Mid function to +// make it as forgiving as CString's version is. Thanks to +// Igor Kholodov for noticing this. +// - Added a specialization of std::swap for CStdString. Thanks +// to Mike Crusader for suggesting this! It's commented out +// because you're not supposed to inject your own code into the +// 'std' namespace. But if you don't care about that, it's +// there if you want it +// - Thanks to Jason Mills for catching a case where CString was +// more forgiving in the Delete() function than I was. +// +// 2001-JUN-06 - I was violating the Standard name lookup rules stated +// in [14.6.2(3)]. None of the compilers I've tried so +// far apparently caught this but HP-UX aCC 3.30 did. The +// fix was to add 'this->' prefixes in many places. +// Thanks to Farrokh Nejadlotfi for this! +// +// 2001-APR-27 - StreamLoad was calculating the number of BYTES in one +// case, not characters. Thanks to Pablo Presedo for this. +// +// 2001-FEB-23 - Replace() had a bug which caused infinite loops if the +// source string was empty. Fixed thanks to Eric Nitzsche. +// +// 2001-FEB-23 - Scott Hathaway was a huge help in providing me with the +// ability to build CStdString on Sun Unix systems. He +// sent me detailed build reports about what works and what +// does not. If CStdString compiles on your Unix box, you +// can thank Scott for it. +// +// 2000-DEC-29 - Joldakowski noticed one overload of Insert failed to do a +// range check as CString's does. Now fixed -- thanks! +// +// 2000-NOV-07 - Aaron pointed out that I was calling static member +// functions of char_traits via a temporary. This was not +// technically wrong, but it was unnecessary and caused +// problems for poor old buggy VC5. Thanks Aaron! +// +// 2000-JUL-11 - Joe Woodbury noted that the CString::Find docs don't match +// what the CString::Find code really ends up doing. I was +// trying to match the docs. Now I match the CString code +// - Joe also caught me truncating strings for GetBuffer() calls +// when the supplied length was less than the current length. +// +// 2000-MAY-25 - Better support for STLPORT's Standard library distribution +// - Got rid of the NSP macro - it interfered with Koenig lookup +// - Thanks to Joe Woodbury for catching a TrimLeft() bug that +// I introduced in January. Empty strings were not getting +// trimmed +// +// 2000-APR-17 - Thanks to Joe Vitaterna for pointing out that ReverseFind +// is supposed to be a const function. +// +// 2000-MAR-07 - Thanks to Ullrich Poll�hne for catching a range bug in one +// of the overloads of assign. +// +// 2000-FEB-01 - You can now use CStdString on the Mac with CodeWarrior! +// Thanks to Todd Heckel for helping out with this. +// +// 2000-JAN-23 - Thanks to Jim Cline for pointing out how I could make the +// Trim() function more efficient. +// - Thanks to Jeff Kohn for prompting me to find and fix a typo +// in one of the addition operators that takes _bstr_t. +// - Got rid of the .CPP file - you only need StdString.h now! +// +// 1999-DEC-22 - Thanks to Greg Pickles for helping me identify a problem +// with my implementation of CStdString::FormatV in which +// resulting string might not be properly NULL terminated. +// +// 1999-DEC-06 - Chris Conti pointed yet another basic_string<> assignment +// bug that MS has not fixed. CStdString did nothing to fix +// it either but it does now! The bug was: create a string +// longer than 31 characters, get a pointer to it (via c_str()) +// and then assign that pointer to the original string object. +// The resulting string would be empty. Not with CStdString! +// +// 1999-OCT-06 - BufferSet was erasing the string even when it was merely +// supposed to shrink it. Fixed. Thanks to Chris Conti. +// - Some of the Q172398 fixes were not checking for assignment- +// to-self. Fixed. Thanks to Baptiste Lepilleur. +// +// 1999-AUG-20 - Improved Load() function to be more efficient by using +// SizeOfResource(). Thanks to Rich Zuris for this. +// - Corrected resource ID constructor, again thanks to Rich. +// - Fixed a bug that occurred with UNICODE characters above +// the first 255 ANSI ones. Thanks to Craig Watson. +// - Added missing overloads of TrimLeft() and TrimRight(). +// Thanks to Karim Ratib for pointing them out +// +// 1999-JUL-21 - Made all calls to GetBuf() with no args check length first. +// +// 1999-JUL-10 - Improved MFC/ATL independence of conversion macros +// - Added SS_NO_REFCOUNT macro to allow you to disable any +// reference-counting your basic_string<> impl. may do. +// - Improved ReleaseBuffer() to be as forgiving as CString. +// Thanks for Fan Xia for helping me find this and to +// Matthew Williams for pointing it out directly. +// +// 1999-JUL-06 - Thanks to Nigel Nunn for catching a very sneaky bug in +// ToLower/ToUpper. They should call GetBuf() instead of +// data() in order to ensure the changed string buffer is not +// reference-counted (in those implementations that refcount). +// +// 1999-JUL-01 - Added a true CString facade. Now you can use CStdString as +// a drop-in replacement for CString. If you find this useful, +// you can thank Chris Sells for finally convincing me to give +// in and implement it. +// - Changed operators << and >> (for MFC CArchive) to serialize +// EXACTLY as CString's do. So now you can send a CString out +// to a CArchive and later read it in as a CStdString. I have +// no idea why you would want to do this but you can. +// +// 1999-JUN-21 - Changed the CStdString class into the CStdStr template. +// - Fixed FormatV() to correctly decrement the loop counter. +// This was harmless bug but a bug nevertheless. Thanks to +// Chris (of Melbsys) for pointing it out +// - Changed Format() to try a normal stack-based array before +// using to _alloca(). +// - Updated the text conversion macros to properly use code +// pages and to fit in better in MFC/ATL builds. In other +// words, I copied Microsoft's conversion stuff again. +// - Added equivalents of CString::GetBuffer, GetBufferSetLength +// - new sscpy() replacement of CStdString::CopyString() +// - a Trim() function that combines TrimRight() and TrimLeft(). +// +// 1999-MAR-13 - Corrected the "NotSpace" functional object to use _istpace() +// instead of _isspace() Thanks to Dave Plummer for this. +// +// 1999-FEB-26 - Removed errant line (left over from testing) that #defined +// _MFC_VER. Thanks to John C Sipos for noticing this. +// +// 1999-FEB-03 - Fixed a bug in a rarely-used overload of operator+() that +// caused infinite recursion and stack overflow +// - Added member functions to simplify the process of +// persisting CStdStrings to/from DCOM IStream interfaces +// - Added functional objects (e.g. StdStringLessNoCase) that +// allow CStdStrings to be used as keys STL map objects with +// case-insensitive comparison +// - Added array indexing operators (i.e. operator[]). I +// originally assumed that these were unnecessary and would be +// inherited from basic_string. However, without them, Visual +// C++ complains about ambiguous overloads when you try to use +// them. Thanks to Julian Selman to pointing this out. +// +// 1998-FEB-?? - Added overloads of assign() function to completely account +// for Q172398 bug. Thanks to "Pete the Plumber" for this +// +// 1998-FEB-?? - Initial submission +// +// COPYRIGHT: +// 2002 Joseph M. O'Leary. This code is 100% free. Use it anywhere you +// want. Rewrite it, restructure it, whatever. If you can write software +// that makes money off of it, good for you. I kinda like capitalism. +// Please don't blame me if it causes your $30 billion dollar satellite +// explode in orbit. If you redistribute it in any form, I'd appreciate it +// if you would leave this notice here. +// ============================================================================= + +// Avoid multiple inclusion + +#ifndef STDSTRING_H +#define STDSTRING_H + +// When using VC, turn off browser references +// Turn off unavoidable compiler warnings + +#if defined(_MSC_VER) && (_MSC_VER > 1100) + #pragma component(browser, off, references, "CStdString") + #pragma warning (disable : 4290) // C++ Exception Specification ignored + #pragma warning (disable : 4127) // Conditional expression is constant + #pragma warning (disable : 4097) // typedef name used as synonym for class name +#endif + +// Borland warnings to turn off + +#ifdef __BORLANDC__ + #pragma option push -w-inl +// #pragma warn -inl // Turn off inline function warnings +#endif + +// SS_IS_INTRESOURCE +// ----------------- +// A copy of IS_INTRESOURCE from VC7. Because old VC6 version of winuser.h +// doesn't have this. + +#define SS_IS_INTRESOURCE(_r) (false) + +#if !defined (SS_ANSI) && defined(_MSC_VER) + #undef SS_IS_INTRESOURCE + #if defined(_WIN64) + #define SS_IS_INTRESOURCE(_r) (((uint64_t)(_r) >> 16) == 0) + #else + #define SS_IS_INTRESOURCE(_r) (((unsigned long)(_r) >> 16) == 0) + #endif +#endif + + +// MACRO: SS_UNSIGNED +// ------------------ +// This macro causes the addition of a constructor and assignment operator +// which take unsigned characters. CString has such functions and in order +// to provide maximum CString-compatability, this code needs them as well. +// In practice you will likely never need these functions... + +//#define SS_UNSIGNED + +#ifdef SS_ALLOW_UNSIGNED_CHARS + #define SS_UNSIGNED +#endif + +// MACRO: SS_SAFE_FORMAT +// --------------------- +// This macro provides limited compatability with a questionable CString +// "feature". You can define it in order to avoid a common problem that +// people encounter when switching from CString to CStdString. +// +// To illustrate the problem -- With CString, you can do this: +// +// CString sName("Joe"); +// CString sTmp; +// sTmp.Format("My name is %s", sName); // WORKS! +// +// However if you were to try this with CStdString, your program would +// crash. +// +// CStdString sName("Joe"); +// CStdString sTmp; +// sTmp.Format("My name is %s", sName); // CRASHES! +// +// You must explicitly call c_str() or cast the object to the proper type +// +// sTmp.Format("My name is %s", sName.c_str()); // WORKS! +// sTmp.Format("My name is %s", static_cast<PCSTR>(sName));// WORKS! +// sTmp.Format("My name is %s", (PCSTR)sName); // WORKS! +// +// This is because it is illegal to pass anything but a POD type as a +// variadic argument to a variadic function (i.e. as one of the "..." +// arguments). The type const char* is a POD type. The type CStdString +// is not. Of course, neither is the type CString, but CString lets you do +// it anyway due to the way they laid out the class in binary. I have no +// control over this in CStdString since I derive from whatever +// implementation of basic_string is available. +// +// However if you have legacy code (which does this) that you want to take +// out of the MFC world and you don't want to rewrite all your calls to +// Format(), then you can define this flag and it will no longer crash. +// +// Note however that this ONLY works for Format(), not sprintf, fprintf, +// etc. If you pass a CStdString object to one of those functions, your +// program will crash. Not much I can do to get around this, short of +// writing substitutes for those functions as well. + +#define SS_SAFE_FORMAT // use new template style Format() function + + +// MACRO: SS_NO_IMPLICIT_CAST +// -------------------------- +// Some people don't like the implicit cast to const char* (or rather to +// const CT*) that CStdString (and MFC's CString) provide. That was the +// whole reason I created this class in the first place, but hey, whatever +// bakes your cake. Just #define this macro to get rid of the the implicit +// cast. + +//#define SS_NO_IMPLICIT_CAST // gets rid of operator const CT*() + + +// MACRO: SS_NO_REFCOUNT +// --------------------- +// turns off reference counting at the assignment level. Only needed +// for the version of basic_string<> that comes with Visual C++ versions +// 6.0 or earlier, and only then in some heavily multithreaded scenarios. +// Uncomment it if you feel you need it. + +//#define SS_NO_REFCOUNT + +// MACRO: SS_WIN32 +// --------------- +// When this flag is set, we are building code for the Win32 platform and +// may use Win32 specific functions (such as LoadString). This gives us +// a couple of nice extras for the code. +// +// Obviously, Microsoft's is not the only compiler available for Win32 out +// there. So I can't just check to see if _MSC_VER is defined to detect +// if I'm building on Win32. So for now, if you use MS Visual C++ or +// Borland's compiler, I turn this on. Otherwise you may turn it on +// yourself, if you prefer + +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(_WIN32) + #define SS_WIN32 +#endif + +// MACRO: SS_ANSI +// -------------- +// When this macro is defined, the code attempts only to use ANSI/ISO +// standard library functions to do it's work. It will NOT attempt to use +// any Win32 of Visual C++ specific functions -- even if they are +// available. You may define this flag yourself to prevent any Win32 +// of VC++ specific functions from being called. + +// If we're not on Win32, we MUST use an ANSI build + +#ifndef SS_WIN32 + #if !defined(SS_NO_ANSI) + #define SS_ANSI + #endif +#endif + +// MACRO: SS_ALLOCA +// ---------------- +// Some implementations of the Standard C Library have a non-standard +// function known as alloca(). This functions allows one to allocate a +// variable amount of memory on the stack. It is needed to implement +// the ASCII/MBCS conversion macros. +// +// I wanted to find some way to determine automatically if alloca() is +// available on this platform via compiler flags but that is asking for +// trouble. The crude test presented here will likely need fixing on +// other platforms. Therefore I'll leave it up to you to fiddle with +// this test to determine if it exists. Just make sure SS_ALLOCA is or +// is not defined as appropriate and you control this feature. + +#if defined(_MSC_VER) && !defined(SS_ANSI) + #define SS_ALLOCA +#endif + + +// MACRO: SS_MBCS +// -------------- +// Setting this macro means you are using MBCS characters. In MSVC builds, +// this macro gets set automatically by detection of the preprocessor flag +// _MBCS. For other platforms you may set it manually if you wish. The +// only effect it currently has is to cause the allocation of more space +// for wchar_t --> char conversions. +// Note that MBCS does not mean UNICODE. +// +// #define SS_MBCS +// + +#ifdef _MBCS + #define SS_MBCS +#endif + + +// MACRO SS_NO_LOCALE +// ------------------ +// If your implementation of the Standard C++ Library lacks the <locale> header, +// you can #define this macro to make your code build properly. Note that this +// is some of my newest code and frankly I'm not very sure of it, though it does +// pass my unit tests. + +// #define SS_NO_LOCALE + + +// Compiler Error regarding _UNICODE and UNICODE +// ----------------------------------------------- +// Microsoft header files are screwy. Sometimes they depend on a preprocessor +// flag named "_UNICODE". Other times they check "UNICODE" (note the lack of +// leading underscore in the second version". In several places, they silently +// "synchronize" these two flags this by defining one of the other was defined. +// In older version of this header, I used to try to do the same thing. +// +// However experience has taught me that this is a bad idea. You get weird +// compiler errors that seem to indicate things like LPWSTR and LPTSTR not being +// equivalent in UNICODE builds, stuff like that (when they MUST be in a proper +// UNICODE build). You end up scratching your head and saying, "But that HAS +// to compile!". +// +// So what should you do if you get this error? +// +// Make sure that both macros (_UNICODE and UNICODE) are defined before this +// file is included. You can do that by either +// +// a) defining both yourself before any files get included +// b) including the proper MS headers in the proper order +// c) including this file before any other file, uncommenting +// the #defines below, and commenting out the #errors +// +// Personally I recommend solution a) but it's your call. + +#ifdef _MSC_VER + #if defined (_UNICODE) && !defined (UNICODE) + #error UNICODE defined but not UNICODE + // #define UNICODE // no longer silently fix this + #endif + #if defined (UNICODE) && !defined (_UNICODE) + #error Warning, UNICODE defined but not _UNICODE + // #define _UNICODE // no longer silently fix this + #endif +#endif + + +// ----------------------------------------------------------------------------- +// MIN and MAX. The Standard C++ template versions go by so many names (at +// at least in the MS implementation) that you never know what's available +// ----------------------------------------------------------------------------- +template<class Type> +inline const Type& SSMIN(const Type& arg1, const Type& arg2) +{ + return arg2 < arg1 ? arg2 : arg1; +} +template<class Type> +inline const Type& SSMAX(const Type& arg1, const Type& arg2) +{ + return arg2 > arg1 ? arg2 : arg1; +} + +// If they have not #included W32Base.h (part of my W32 utility library) then +// we need to define some stuff. Otherwise, this is all defined there. + +#if !defined(W32BASE_H) + + // If they want us to use only standard C++ stuff (no Win32 stuff) + + #ifdef SS_ANSI + + // On Win32 we have TCHAR.H so just include it. This is NOT violating + // the spirit of SS_ANSI as we are not calling any Win32 functions here. + + #ifdef SS_WIN32 + + #include <TCHAR.H> + #include <WTYPES.H> + #ifndef STRICT + #define STRICT + #endif + + // ... but on non-Win32 platforms, we must #define the types we need. + + #else + + typedef const char* PCSTR; + typedef char* PSTR; + typedef const wchar_t* PCWSTR; + typedef wchar_t* PWSTR; + #ifdef UNICODE + typedef wchar_t TCHAR; + #else + typedef char TCHAR; + #endif + typedef wchar_t OLECHAR; + + #endif // #ifndef _WIN32 + + + // Make sure ASSERT and verify are defined using only ANSI stuff + + #ifndef ASSERT + #include <assert.h> + #define ASSERT(f) assert((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #else // ...else SS_ANSI is NOT defined + + #include <TCHAR.H> + #include <WTYPES.H> + #ifndef STRICT + #define STRICT + #endif + + // Make sure ASSERT and verify are defined + + #ifndef ASSERT + #include <crtdbg.h> + #define ASSERT(f) _ASSERTE((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #endif // #ifdef SS_ANSI + + #ifndef UNUSED + #define UNUSED(x) x + #endif + +#endif // #ifndef W32BASE_H + +// Standard headers needed + +#include <string> // basic_string +#include <algorithm> // for_each, etc. +#include <functional> // for StdStringLessNoCase, et al +#ifndef SS_NO_LOCALE + #include <locale> // for various facets +#endif + +// If this is a recent enough version of VC include comdef.h, so we can write +// member functions to deal with COM types & compiler support classes e.g. +// _bstr_t + +#if defined (_MSC_VER) && (_MSC_VER >= 1100) + #include <comdef.h> + #define SS_INC_COMDEF // signal that we #included MS comdef.h file + #define STDSTRING_INC_COMDEF + #define SS_NOTHROW __declspec(nothrow) +#else + #define SS_NOTHROW +#endif + +#ifndef TRACE + #define TRACE_DEFINED_HERE + #define TRACE +#endif + +// Microsoft defines PCSTR, PCWSTR, etc, but no PCTSTR. I hate to use the +// versions with the "L" in front of them because that's a leftover from Win 16 +// days, even though it evaluates to the same thing. Therefore, Define a PCSTR +// as an LPCTSTR. + +#if !defined(PCTSTR) && !defined(PCTSTR_DEFINED) + typedef const TCHAR* PCTSTR; + #define PCTSTR_DEFINED +#endif + +#if !defined(PCOLESTR) && !defined(PCOLESTR_DEFINED) + typedef const OLECHAR* PCOLESTR; + #define PCOLESTR_DEFINED +#endif + +#if !defined(POLESTR) && !defined(POLESTR_DEFINED) + typedef OLECHAR* POLESTR; + #define POLESTR_DEFINED +#endif + +#if !defined(PCUSTR) && !defined(PCUSTR_DEFINED) + typedef const unsigned char* PCUSTR; + typedef unsigned char* PUSTR; + #define PCUSTR_DEFINED +#endif + + +// SGI compiler 7.3 doesnt know these types - oh and btw, remember to use +// -LANG:std in the CXX Flags +#if defined(__sgi) + typedef unsigned long DWORD; + typedef void * LPCVOID; +#endif + + +// SS_USE_FACET macro and why we need it: +// +// Since I'm a good little Standard C++ programmer, I use locales. Thus, I +// need to make use of the use_facet<> template function here. Unfortunately, +// this need is complicated by the fact the MS' implementation of the Standard +// C++ Library has a non-standard version of use_facet that takes more +// arguments than the standard dictates. Since I'm trying to write CStdString +// to work with any version of the Standard library, this presents a problem. +// +// The upshot of this is that I can't do 'use_facet' directly. The MS' docs +// tell me that I have to use a macro, _USE() instead. Since _USE obviously +// won't be available in other implementations, this means that I have to write +// my OWN macro -- SS_USE_FACET -- that evaluates either to _USE or to the +// standard, use_facet. +// +// If you are having trouble with the SS_USE_FACET macro, in your implementation +// of the Standard C++ Library, you can define your own version of SS_USE_FACET. + +#ifndef schMSG + #define schSTR(x) #x + #define schSTR2(x) schSTR(x) + #define schMSG(desc) message(__FILE__ "(" schSTR2(__LINE__) "):" #desc) +#endif + +#ifndef SS_USE_FACET + + // STLPort #defines a macro (__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) for + // all MSVC builds, erroneously in my opinion. It causes problems for + // my SS_ANSI builds. In my code, I always comment out that line. You'll + // find it in \stlport\config\stl_msvc.h + + #if defined(__SGI_STL_PORT) && (__SGI_STL_PORT >= 0x400 ) + + #if defined(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) && defined(_MSC_VER) + #ifdef SS_ANSI + #pragma schMSG(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS defined!!) + #endif + #endif + #define SS_USE_FACET(loc, fac) std::use_facet<fac >(loc) + + #elif defined(_MSC_VER ) + + #define SS_USE_FACET(loc, fac) std::_USE(loc, fac) + + // ...and + #elif defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE) + + #define SS_USE_FACET(loc, fac) std::use_facet(loc, (fac*)0) + + #else + + #define SS_USE_FACET(loc, fac) std::use_facet<fac >(loc) + + #endif + +#endif + +// ============================================================================= +// UNICODE/MBCS conversion macros. Made to work just like the MFC/ATL ones. +// ============================================================================= + +#include <wchar.h> // Added to Std Library with Amendment #1. + +// First define the conversion helper functions. We define these regardless of +// any preprocessor macro settings since their names won't collide. + +// Not sure if we need all these headers. I believe ANSI says we do. + +#include <stdio.h> +#include <stdarg.h> +#include <wctype.h> +#include <ctype.h> +#include <stdlib.h> +#ifndef va_start + #include <varargs.h> +#endif + + +#ifdef SS_NO_LOCALE + + #if defined(_WIN32) || defined (_WIN32_WCE) + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + pDstW[0] = '\0'; + MultiByteToWideChar(acp, 0, pSrcA, nSrc, pDstW, nDst); + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, acp); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + pDstA[0] = '\0'; + WideCharToMultiByte(acp, 0, pSrcW, nSrc, pDstA, nDst, 0, 0); + return pDstA; + } + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, acp); + } + #else + #endif + +#else + + // StdCodeCvt - made to look like Win32 functions WideCharToMultiByte + // and MultiByteToWideChar but uses locales in SS_ANSI + // builds. There are a number of overloads. + // First argument is the destination buffer. + // Second argument is the source buffer + //#if defined (SS_ANSI) || !defined (SS_WIN32) + + // 'SSCodeCvt' - shorthand name for the codecvt facet we use + + typedef std::codecvt<wchar_t, char, mbstate_t> SSCodeCvt; + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + + pDstW[0] = '\0'; + + if ( nSrc > 0 ) + { + PCSTR pNextSrcA = pSrcA; + PWSTR pNextDstW = pDstW; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= {}; + SSCodeCvt::result res = conv.in(st, + pSrcA, pSrcA + nSrc, pNextSrcA, + pDstW, pDstW + nDst, pNextDstW); +#ifdef TARGET_POSIX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::ok == res); + ASSERT2(SSCodeCvt::error != res); + ASSERT2(pNextDstW >= pDstW); + ASSERT2(pNextSrcA >= pSrcA); +#undef ASSERT2 + // Null terminate the converted string + + if ( pNextDstW - pDstW > nDst ) + *(pDstW + nDst) = '\0'; + else + *pNextDstW = '\0'; + } + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, loc); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + + pDstA[0] = '\0'; + + if ( nSrc > 0 ) + { + PSTR pNextDstA = pDstA; + PCWSTR pNextSrcW = pSrcW; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= {}; + SSCodeCvt::result res = conv.out(st, + pSrcW, pSrcW + nSrc, pNextSrcW, + pDstA, pDstA + nDst, pNextDstA); +#ifdef TARGET_POSIX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::error != res); + ASSERT2(SSCodeCvt::ok == res); // strict, comment out for sanity + ASSERT2(pNextDstA >= pDstA); + ASSERT2(pNextSrcW >= pSrcW); +#undef ASSERT2 + + // Null terminate the converted string + + if ( pNextDstA - pDstA > nDst ) + *(pDstA + nDst) = '\0'; + else + *pNextDstA = '\0'; + } + return pDstA; + } + + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, loc); + } + +#endif + + + +// Unicode/MBCS conversion macros are only available on implementations of +// the "C" library that have the non-standard _alloca function. As far as I +// know that's only Microsoft's though I've heard that the function exists +// elsewhere. + +#if defined(SS_ALLOCA) && !defined SS_NO_CONVERSION + + #include <malloc.h> // needed for _alloca + + // Define our conversion macros to look exactly like Microsoft's to + // facilitate using this stuff both with and without MFC/ATL + + #ifdef _CONVERSION_USES_THREAD_LOCALE + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=GetACP(); \ + _acp; PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=GetACP();\ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt, _acp))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = sslen(_pw), \ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt, _acp))) + #else + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=CP_ACP; _acp;\ + PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=CP_ACP; \ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = (sslen(_pw)),\ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt))) + #endif + + #define SSA2CW(pa) ((PCWSTR)SSA2W((pa))) + #define SSW2CA(pw) ((PCSTR)SSW2A((pw))) + + #ifdef UNICODE + #define SST2A SSW2A + #define SSA2T SSA2W + #define SST2CA SSW2CA + #define SSA2CT SSA2CW + // (Did you get a compiler error here about not being able to convert + // PTSTR into PWSTR? Then your _UNICODE and UNICODE flags are messed + // up. Best bet: #define BOTH macros before including any MS headers.) + inline PWSTR SST2W(PTSTR p) { return p; } + inline PTSTR SSW2T(PWSTR p) { return p; } + inline PCWSTR SST2CW(PCTSTR p) { return p; } + inline PCTSTR SSW2CT(PCWSTR p) { return p; } + #else + #define SST2W SSA2W + #define SSW2T SSW2A + #define SST2CW SSA2CW + #define SSW2CT SSW2CA + inline PSTR SST2A(PTSTR p) { return p; } + inline PTSTR SSA2T(PSTR p) { return p; } + inline PCSTR SST2CA(PCTSTR p) { return p; } + inline PCTSTR SSA2CT(PCSTR p) { return p; } + #endif // #ifdef UNICODE + + #if defined(UNICODE) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #elif defined(OLE2ANSI) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #else + //CharNextW doesn't work on Win95 so we use this + #define SST2COLE(pa) SSA2CW((pa)) + #define SST2OLE(pa) SSA2W((pa)) + #define SSOLE2CT(po) SSW2CA((po)) + #define SSOLE2T(po) SSW2A((po)) + #endif + + #ifdef OLE2ANSI + #define SSW2OLE SSW2A + #define SSOLE2W SSA2W + #define SSW2COLE SSW2CA + #define SSOLE2CW SSA2CW + inline POLESTR SSA2OLE(PSTR p) { return p; } + inline PSTR SSOLE2A(POLESTR p) { return p; } + inline PCOLESTR SSA2COLE(PCSTR p) { return p; } + inline PCSTR SSOLE2CA(PCOLESTR p){ return p; } + #else + #define SSA2OLE SSA2W + #define SSOLE2A SSW2A + #define SSA2COLE SSA2CW + #define SSOLE2CA SSW2CA + inline POLESTR SSW2OLE(PWSTR p) { return p; } + inline PWSTR SSOLE2W(POLESTR p) { return p; } + inline PCOLESTR SSW2COLE(PCWSTR p) { return p; } + inline PCWSTR SSOLE2CW(PCOLESTR p){ return p; } + #endif + + // Above we've defined macros that look like MS' but all have + // an 'SS' prefix. Now we need the real macros. We'll either + // get them from the macros above or from MFC/ATL. + + #if defined (USES_CONVERSION) + + #define _NO_STDCONVERSION // just to be consistent + + #else + + #ifdef _MFC_VER + + #include <afxconv.h> + #define _NO_STDCONVERSION // just to be consistent + + #else + + #define USES_CONVERSION SSCVT + #define A2CW SSA2CW + #define W2CA SSW2CA + #define T2A SST2A + #define A2T SSA2T + #define T2W SST2W + #define W2T SSW2T + #define T2CA SST2CA + #define A2CT SSA2CT + #define T2CW SST2CW + #define W2CT SSW2CT + #define ocslen sslen + #define ocscpy sscpy + #define T2COLE SST2COLE + #define OLE2CT SSOLE2CT + #define T2OLE SST2COLE + #define OLE2T SSOLE2CT + #define A2OLE SSA2OLE + #define OLE2A SSOLE2A + #define W2OLE SSW2OLE + #define OLE2W SSOLE2W + #define A2COLE SSA2COLE + #define OLE2CA SSOLE2CA + #define W2COLE SSW2COLE + #define OLE2CW SSOLE2CW + + #endif // #ifdef _MFC_VER + #endif // #ifndef USES_CONVERSION +#endif // #ifndef SS_NO_CONVERSION + +// Define ostring - generic name for std::basic_string<OLECHAR> + +#if !defined(ostring) && !defined(OSTRING_DEFINED) + typedef std::basic_string<OLECHAR> ostring; + #define OSTRING_DEFINED +#endif + +// StdCodeCvt when there's no conversion to be done +template <typename T> +inline T* StdCodeCvt(T* pDst, int nDst, const T* pSrc, int nSrc) +{ + int nChars = SSMIN(nSrc, nDst); + + if ( nChars > 0 ) + { + pDst[0] = '\0'; + std::basic_string<T>::traits_type::copy(pDst, pSrc, nChars); +// std::char_traits<T>::copy(pDst, pSrc, nChars); + pDst[nChars] = '\0'; + } + + return pDst; +} +inline PSTR StdCodeCvt(PSTR pDst, int nDst, PCUSTR pSrc, int nSrc) +{ + return StdCodeCvt(pDst, nDst, (PCSTR)pSrc, nSrc); +} +inline PUSTR StdCodeCvt(PUSTR pDst, int nDst, PCSTR pSrc, int nSrc) +{ + return (PUSTR)StdCodeCvt((PSTR)pDst, nDst, pSrc, nSrc); +} + +// Define tstring -- generic name for std::basic_string<TCHAR> + +#if !defined(tstring) && !defined(TSTRING_DEFINED) + typedef std::basic_string<TCHAR> tstring; + #define TSTRING_DEFINED +#endif + +// a very shorthand way of applying the fix for KB problem Q172398 +// (basic_string assignment bug) + +#if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + #define Q172398(x) (x).erase() +#else + #define Q172398(x) +#endif + +// ============================================================================= +// INLINE FUNCTIONS ON WHICH CSTDSTRING RELIES +// +// Usually for generic text mapping, we rely on preprocessor macro definitions +// to map to string functions. However the CStdStr<> template cannot use +// macro-based generic text mappings because its character types do not get +// resolved until template processing which comes AFTER macro processing. In +// other words, the preprocessor macro UNICODE is of little help to us in the +// CStdStr template +// +// Therefore, to keep the CStdStr declaration simple, we have these inline +// functions. The template calls them often. Since they are inline (and NOT +// exported when this is built as a DLL), they will probably be resolved away +// to nothing. +// +// Without these functions, the CStdStr<> template would probably have to broken +// out into two, almost identical classes. Either that or it would be a huge, +// convoluted mess, with tons of "if" statements all over the place checking the +// size of template parameter CT. +// ============================================================================= + +#ifdef SS_NO_LOCALE + + // -------------------------------------------------------------------------- + // Win32 GetStringTypeEx wrappers + // -------------------------------------------------------------------------- + inline bool wsGetStringType(LCID lc, DWORD dwT, PCSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExA(lc, dwT, pS, nSize, pWd); + } + inline bool wsGetStringType(LCID lc, DWORD dwT, PCWSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExW(lc, dwT, pS, nSize, pWd); + } + + + template<typename CT> + inline bool ssisspace (CT t) + { + WORD toYourMother; + return wsGetStringType(GetThreadLocale(), CT_CTYPE1, &t, 1, &toYourMother) + && 0 != (C1_BLANK & toYourMother); + } + +#endif + +// If they defined SS_NO_REFCOUNT, then we must convert all assignments + +#if defined (_MSC_VER) && (_MSC_VER < 1300) + #ifdef SS_NO_REFCOUNT + #define SSREF(x) (x).c_str() + #else + #define SSREF(x) (x) + #endif +#else + #define SSREF(x) (x) +#endif + +// ----------------------------------------------------------------------------- +// sslen: strlen/wcslen wrappers +// ----------------------------------------------------------------------------- +template<typename CT> inline int sslen(const CT* pT) +{ + return 0 == pT ? 0 : (int)std::basic_string<CT>::traits_type::length(pT); +// return 0 == pT ? 0 : std::char_traits<CT>::length(pT); +} +inline SS_NOTHROW int sslen(const std::string& s) +{ + return static_cast<int>(s.length()); +} +inline SS_NOTHROW int sslen(const std::wstring& s) +{ + return static_cast<int>(s.length()); +} + +// ----------------------------------------------------------------------------- +// sstolower/sstoupper -- convert characters to upper/lower case +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + inline char sstoupper(char ch) { return (char)::toupper(ch); } + inline wchar_t sstoupper(wchar_t ch){ return (wchar_t)::towupper(ch); } + inline char sstolower(char ch) { return (char)::tolower(ch); } + inline wchar_t sstolower(wchar_t ch){ return (wchar_t)::tolower(ch); } +#else + template<typename CT> + inline CT sstolower(const CT& t, const std::locale& loc = std::locale()) + { + return std::tolower<CT>(t, loc); + } + template<typename CT> + inline CT sstoupper(const CT& t, const std::locale& loc = std::locale()) + { + return std::toupper<CT>(t, loc); + } +#endif + +// ----------------------------------------------------------------------------- +// ssasn: assignment functions -- assign "sSrc" to "sDst" +// ----------------------------------------------------------------------------- +typedef std::string::size_type SS_SIZETYPE; // just for shorthand, really +typedef std::string::pointer SS_PTRTYPE; +typedef std::wstring::size_type SW_SIZETYPE; +typedef std::wstring::pointer SW_PTRTYPE; + + +template <typename T> +inline void ssasn(std::basic_string<T>& sDst, const std::basic_string<T>& sSrc) +{ + if ( sDst.c_str() != sSrc.c_str() ) + { + sDst.erase(); + sDst.assign(SSREF(sSrc)); + } +} +template <typename T> +inline void ssasn(std::basic_string<T>& sDst, const T *pA) +{ + // Watch out for NULLs, as always. + + if ( 0 == pA ) + { + sDst.erase(); + } + + // If pA actually points to part of sDst, we must NOT erase(), but + // rather take a substring + + else if ( pA >= sDst.c_str() && pA <= sDst.c_str() + sDst.size() ) + { + sDst =sDst.substr(static_cast<typename std::basic_string<T>::size_type>(pA-sDst.c_str())); + } + + // Otherwise (most cases) apply the assignment bug fix, if applicable + // and do the assignment + + else + { + Q172398(sDst); + sDst.assign(pA); + } +} +inline void ssasn(std::string& sDst, const std::wstring& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nDst = static_cast<int>(sSrc.size()); + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + // In MBCS builds, we don't know how long the destination string will be. + nDst = static_cast<int>(static_cast<double>(nDst) * 1.3); + sDst.resize(nDst+1); + PCSTR szCvt = StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()), nDst, + sSrc.c_str(), static_cast<int>(sSrc.size())); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst+1); + StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()), nDst, + sSrc.c_str(), static_cast<int>(sSrc.size())); + sDst.resize(sSrc.size()); +#endif + } +} +inline void ssasn(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nSrc = sslen(pW); + int nDst = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nDst = static_cast<int>(static_cast<double>(nDst) * 1.3); + // In MBCS builds, we don't know how long the destination string will be. + sDst.resize(nDst + 1); + PCSTR szCvt = StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()), nDst, + pW, nSrc); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst + 1); + StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()), nDst, pW, nSrc); + sDst.resize(nDst); +#endif + } + else + { + sDst.erase(); + } +} +inline void ssasn(std::string& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(""); +} +#undef StrSizeType +inline void ssasn(std::wstring& sDst, const std::string& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nSrc = static_cast<int>(sSrc.size()); + int nDst = nSrc; + + sDst.resize(nSrc+1); + PCWSTR szCvt = StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()), nDst, + sSrc.c_str(), nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( 0 == nSrc ) + { + sDst.erase(); + } + else + { + int nDst = nSrc; + sDst.resize(nDst+1); + PCWSTR szCvt = StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()), nDst, pA, + nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(L""); +} + +// ----------------------------------------------------------------------------- +// ssadd: string object concatenation -- add second argument to first +// ----------------------------------------------------------------------------- +inline void ssadd(std::string& sDst, const std::wstring& sSrc) +{ + int nSrc = static_cast<int>(sSrc.size()); + + if ( nSrc > 0 ) + { + int nDst = static_cast<int>(sDst.size()); + int nAdd = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nAdd = static_cast<int>(static_cast<double>(nAdd) * 1.3); + sDst.resize(nDst+nAdd+1); + PCSTR szCvt = StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()+nDst), + nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst+nAdd+1); + StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()+nDst), nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + nAdd); +#endif + } +} +template <typename T> +inline void ssadd(typename std::basic_string<T>& sDst, const typename std::basic_string<T>& sSrc) +{ + sDst += sSrc; +} +inline void ssadd(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nDst = static_cast<int>(sDst.size()); + int nAdd = nSrc; + +#ifdef SS_MBCS + nAdd = static_cast<int>(static_cast<double>(nAdd) * 1.3); + sDst.resize(nDst + nAdd + 1); + PCSTR szCvt = StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()+nDst), + nAdd, pW, nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst + nAdd + 1); + StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()+nDst), nAdd, pW, nSrc); + sDst.resize(nDst + nSrc); +#endif + } +} +template <typename T> +inline void ssadd(typename std::basic_string<T>& sDst, const T *pA) +{ + if ( pA ) + { + // If the string being added is our internal string or a part of our + // internal string, then we must NOT do any reallocation without + // first copying that string to another object (since we're using a + // direct pointer) + + if ( pA >= sDst.c_str() && pA <= sDst.c_str()+sDst.length()) + { + if ( sDst.capacity() <= sDst.size()+sslen(pA) ) + sDst.append(std::basic_string<T>(pA)); + else + sDst.append(pA); + } + else + { + sDst.append(pA); + } + } +} +inline void ssadd(std::wstring& sDst, const std::string& sSrc) +{ + if ( !sSrc.empty() ) + { + int nSrc = static_cast<int>(sSrc.size()); + int nDst = static_cast<int>(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()+nDst), + nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()+nDst), nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} +inline void ssadd(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( nSrc > 0 ) + { + int nDst = static_cast<int>(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()+nDst), + nSrc, pA, nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()+nDst), nSrc, pA, nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} + +// ----------------------------------------------------------------------------- +// sscmp: comparison (case sensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template<typename CT> +inline int sscmp(const CT* pA1, const CT* pA2) +{ + CT f; + CT l; + + do + { + f = *(pA1++); + l = *(pA2++); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssicmp: comparison (case INsensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template<typename CT> +inline int ssicmp(const CT* pA1, const CT* pA2) +{ + // Using the "C" locale = "not affected by locale" + + std::locale loc = std::locale::classic(); + const std::ctype<CT>& ct = SS_USE_FACET(loc, std::ctype<CT>); + CT f; + CT l; + + do + { + f = ct.tolower(*(pA1++)); + l = ct.tolower(*(pA2++)); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssupr/sslwr: Uppercase/Lowercase conversion functions +// ----------------------------------------------------------------------------- + +template<typename CT> +inline void sslwr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype<CT>).tolower(pT, pT+nLen); +} +template<typename CT> +inline void ssupr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype<CT>).toupper(pT, pT+nLen); +} + +// ----------------------------------------------------------------------------- +// vsprintf/vswprintf or _vsnprintf/_vsnwprintf equivalents. In standard +// builds we can't use _vsnprintf/_vsnwsprintf because they're MS extensions. +// +// ----------------------------------------------------------------------------- +// Borland's headers put some ANSI "C" functions in the 'std' namespace. +// Promote them to the global namespace so we can use them here. + +#if defined(__BORLANDC__) + using std::vsprintf; + using std::vswprintf; +#endif + + // GNU is supposed to have vsnprintf and vsnwprintf. But only the newer + // distributions do. + +#if defined(__GNUC__) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return vswprintf(pW, nCount, pFmtW, vl); + } + + // Microsofties can use +#elif defined(_MSC_VER) && !defined(SS_ANSI) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return _vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return _vsnwprintf(pW, nCount, pFmtW, vl); + } + +#elif defined (SS_DANGEROUS_FORMAT) // ignore buffer size parameter if needed? + + inline int ssvsprintf(PSTR pA, size_t /*nCount*/, PCSTR pFmtA, va_list vl) + { + return vsprintf(pA, pFmtA, vl); + } + + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + // JMO: Some distributions of the "C" have a version of vswprintf that + // takes 3 arguments (e.g. Microsoft, Borland, GNU). Others have a + // version which takes 4 arguments (an extra "count" argument in the + // second position. The best stab I can take at this so far is that if + // you are NOT running with MS, Borland, or GNU, then I'll assume you + // have the version that takes 4 arguments. + // + // I'm sure that these checks don't catch every platform correctly so if + // you get compiler errors on one of the lines immediately below, it's + // probably because your implemntation takes a different number of + // arguments. You can comment out the offending line (and use the + // alternate version) or you can figure out what compiler flag to check + // and add that preprocessor check in. Regardless, if you get an error + // on these lines, I'd sure like to hear from you about it. + // + // Thanks to Ronny Schulz for the SGI-specific checks here. + +// #if !defined(__MWERKS__) && !defined(__SUNPRO_CC_COMPAT) && !defined(__SUNPRO_CC) + #if !defined(_MSC_VER) \ + && !defined (__BORLANDC__) \ + && !defined(__GNUC__) \ + && !defined(__sgi) + + return vswprintf(pW, nCount, pFmtW, vl); + + // suddenly with the current SGI 7.3 compiler there is no such function as + // vswprintf and the substitute needs explicit casts to compile + + #elif defined(__sgi) + + nCount; + return vsprintf( (char *)pW, (char *)pFmtW, vl); + + #else + + nCount; + return vswprintf(pW, pFmtW, vl); + + #endif + + } + +#endif + + // GOT COMPILER PROBLEMS HERE? + // --------------------------- + // Does your compiler choke on one or more of the following 2 functions? It + // probably means that you don't have have either vsnprintf or vsnwprintf in + // your version of the CRT. This is understandable since neither is an ANSI + // "C" function. However it still leaves you in a dilemma. In order to make + // this code build, you're going to have to to use some non-length-checked + // formatting functions that every CRT has: vsprintf and vswprintf. + // + // This is very dangerous. With the proper erroneous (or malicious) code, it + // can lead to buffer overlows and crashing your PC. Use at your own risk + // In order to use them, just #define SS_DANGEROUS_FORMAT at the top of + // this file. + // + // Even THEN you might not be all the way home due to some non-conforming + // distributions. More on this in the comments below. + + inline int ssnprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + #ifdef _MSC_VER + return _vsnprintf(pA, nCount, pFmtA, vl); + #else + return vsnprintf(pA, nCount, pFmtA, vl); + #endif + } + inline int ssnprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + #ifdef _MSC_VER + return _vsnwprintf(pW, nCount, pFmtW, vl); + #else + return vswprintf(pW, nCount, pFmtW, vl); + #endif + } + + + + +// ----------------------------------------------------------------------------- +// ssload: Type safe, overloaded ::LoadString wrappers +// There is no equivalent of these in non-Win32-specific builds. However, I'm +// thinking that with the message facet, there might eventually be one +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline int ssload(HMODULE hInst, UINT uId, PSTR pBuf, int nMax) + { + return ::LoadStringA(hInst, uId, pBuf, nMax); + } + inline int ssload(HMODULE hInst, UINT uId, PWSTR pBuf, int nMax) + { + return ::LoadStringW(hInst, uId, pBuf, nMax); + } +#if defined ( _MSC_VER ) && ( _MSC_VER >= 1500 ) + inline int ssload(HMODULE hInst, UINT uId, uint16_t *pBuf, int nMax) + { + return 0; + } + inline int ssload(HMODULE hInst, UINT uId, uint32_t *pBuf, int nMax) + { + return 0; + } +#endif +#endif + + +// ----------------------------------------------------------------------------- +// sscoll/ssicoll: Collation wrappers +// Note -- with MSVC I have reversed the arguments order here because the +// functions appear to return the opposite of what they should +// ----------------------------------------------------------------------------- +#ifndef SS_NO_LOCALE +template <typename CT> +inline int sscoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::collate<CT>& coll = + SS_USE_FACET(std::locale(), std::collate<CT>); + + return coll.compare(sz2, sz2+nLen2, sz1, sz1+nLen1); +} +template <typename CT> +inline int ssicoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::locale loc; + const std::collate<CT>& coll = SS_USE_FACET(loc, std::collate<CT>); + + // Some implementations seem to have trouble using the collate<> + // facet typedefs so we'll just default to basic_string and hope + // that's what the collate facet uses (which it generally should) + +// std::collate<CT>::string_type s1(sz1); +// std::collate<CT>::string_type s2(sz2); + const std::basic_string<CT> sEmpty; + std::basic_string<CT> s1(sz1 ? sz1 : sEmpty.c_str()); + std::basic_string<CT> s2(sz2 ? sz2 : sEmpty.c_str()); + + sslwr(const_cast<CT*>(s1.c_str()), nLen1, loc); + sslwr(const_cast<CT*>(s2.c_str()), nLen2, loc); + return coll.compare(s2.c_str(), s2.c_str()+nLen2, + s1.c_str(), s1.c_str()+nLen1); +} +#endif + + +// ----------------------------------------------------------------------------- +// ssfmtmsg: FormatMessage equivalents. Needed because I added a CString facade +// Again -- no equivalent of these on non-Win32 builds but their might one day +// be one if the message facet gets implemented +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageA(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PWSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageW(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } +#else +#endif + + + +// FUNCTION: sscpy. Copies up to 'nMax' characters from pSrc to pDst. +// ----------------------------------------------------------------------------- +// FUNCTION: sscpy +// inline int sscpy(PSTR pDst, PCSTR pSrc, int nMax=-1); +// inline int sscpy(PUSTR pDst, PCSTR pSrc, int nMax=-1) +// inline int sscpy(PSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCSTR pSrc, int nMax=-1); +// +// DESCRIPTION: +// This function is very much (but not exactly) like strcpy. These +// overloads simplify copying one C-style string into another by allowing +// the caller to specify two different types of strings if necessary. +// +// The strings must NOT overlap +// +// "Character" is expressed in terms of the destination string, not +// the source. If no 'nMax' argument is supplied, then the number of +// characters copied will be sslen(pSrc). A NULL terminator will +// also be added so pDst must actually be big enough to hold nMax+1 +// characters. The return value is the number of characters copied, +// not including the NULL terminator. +// +// PARAMETERS: +// pSrc - the string to be copied FROM. May be a char based string, an +// MBCS string (in Win32 builds) or a wide string (wchar_t). +// pSrc - the string to be copied TO. Also may be either MBCS or wide +// nMax - the maximum number of characters to be copied into szDest. Note +// that this is expressed in whatever a "character" means to pDst. +// If pDst is a wchar_t type string than this will be the maximum +// number of wchar_ts that my be copied. The pDst string must be +// large enough to hold least nMaxChars+1 characters. +// If the caller supplies no argument for nMax this is a signal to +// the routine to copy all the characters in pSrc, regardless of +// how long it is. +// +// RETURN VALUE: none +// ----------------------------------------------------------------------------- + +template<typename CT1, typename CT2> +inline int sscpycvt(CT1* pDst, const CT2* pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + int nSrc = sslen(pSrc); + + const CT1* szCvt = StdCodeCvt(pDst, nMax, pSrc, nSrc); + + // If we're copying the same size characters, then all the "code convert" + // just did was basically memcpy so the #of characters copied is the same + // as the number requested. I should probably specialize this function + // template to achieve this purpose as it is silly to do a runtime check + // of a fact known at compile time. I'll get around to it. + + return sslen(szCvt); +} + +template<typename T> +inline int sscpycvt(T* pDst, const T* pSrc, int nMax) +{ + int nCount = nMax; + for (; nCount > 0 && *pSrc; ++pSrc, ++pDst, --nCount) + std::basic_string<T>::traits_type::assign(*pDst, *pSrc); + + *pDst = 0; + return nMax - nCount; +} + +inline int sscpycvt(PWSTR pDst, PCSTR pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + const PWSTR szCvt = StdCodeCvt(pDst, nMax, pSrc, nMax); + return sslen(szCvt); +} + +template<typename CT1, typename CT2> +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax, int nLen) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, nLen)); +} +template<typename CT1, typename CT2> +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, sslen(pSrc))); +} +template<typename CT1, typename CT2> +inline int sscpy(CT1* pDst, const CT2* pSrc) +{ + return sscpycvt(pDst, pSrc, sslen(pSrc)); +} +template<typename CT1, typename CT2> +inline int sscpy(CT1* pDst, const std::basic_string<CT2>& sSrc, int nMax) +{ + return sscpycvt(pDst, sSrc.c_str(), SSMIN(nMax, (int)sSrc.length())); +} +template<typename CT1, typename CT2> +inline int sscpy(CT1* pDst, const std::basic_string<CT2>& sSrc) +{ + return sscpycvt(pDst, sSrc.c_str(), (int)sSrc.length()); +} + +#ifdef SS_INC_COMDEF + template<typename CT1> + inline int sscpy(CT1* pDst, const _bstr_t& bs, int nMax) + { + return sscpycvt(pDst, static_cast<PCOLESTR>(bs), + SSMIN(nMax, static_cast<int>(bs.length()))); + } + template<typename CT1> + inline int sscpy(CT1* pDst, const _bstr_t& bs) + { + return sscpy(pDst, bs, static_cast<int>(bs.length())); + } +#endif + + +// ----------------------------------------------------------------------------- +// Functional objects for changing case. They also let you pass locales +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + template<typename CT> + struct SSToUpper : public std::unary_function<CT, CT> + { + inline CT operator()(const CT& t) const + { + return sstoupper(t); + } + }; + template<typename CT> + struct SSToLower : public std::unary_function<CT, CT> + { + inline CT operator()(const CT& t) const + { + return sstolower(t); + } + }; +#else + template<typename CT> + struct SSToUpper : public std::binary_function<CT, std::locale, CT> + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstoupper<CT>(t, loc); + } + }; + template<typename CT> + struct SSToLower : public std::binary_function<CT, std::locale, CT> + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstolower<CT>(t, loc); + } + }; +#endif + +// This struct is used for TrimRight() and TrimLeft() function implementations. +//template<typename CT> +//struct NotSpace : public std::unary_function<CT, bool> +//{ +// const std::locale& loc; +// inline NotSpace(const std::locale& locArg) : loc(locArg) {} +// inline bool operator() (CT t) { return !std::isspace(t, loc); } +//}; +template<typename CT> +struct NotSpace : public std::unary_function<CT, bool> +{ + // DINKUMWARE BUG: + // Note -- using std::isspace in a COM DLL gives us access violations + // because it causes the dynamic addition of a function to be called + // when the library shuts down. Unfortunately the list is maintained + // in DLL memory but the function is in static memory. So the COM DLL + // goes away along with the function that was supposed to be called, + // and then later when the DLL CRT shuts down it unloads the list and + // tries to call the long-gone function. + // This is DinkumWare's implementation problem. If you encounter this + // problem, you may replace the calls here with good old isspace() and + // iswspace() from the CRT unless they specify SS_ANSI + +#ifdef SS_NO_LOCALE + + bool operator() (CT t) const { return !ssisspace(t); } + +#else + const std::locale loc; + NotSpace(const std::locale& locArg=std::locale()) : loc(locArg) {} + bool operator() (CT t) const { return !std::isspace(t, loc); } +#endif +}; + + + + +// Now we can define the template (finally!) +// ============================================================================= +// TEMPLATE: CStdStr +// template<typename CT> class CStdStr : public std::basic_string<CT> +// +// REMARKS: +// This template derives from basic_string<CT> and adds some MFC CString- +// like functionality +// +// Basically, this is my attempt to make Standard C++ library strings as +// easy to use as the MFC CString class. +// +// Note that although this is a template, it makes the assumption that the +// template argument (CT, the character type) is either char or wchar_t. +// ============================================================================= + +//#define CStdStr _SS // avoid compiler warning 4786 + +// template<typename ARG> ARG& FmtArg(ARG& arg) { return arg; } +// PCSTR FmtArg(const std::string& arg) { return arg.c_str(); } +// PCWSTR FmtArg(const std::wstring& arg) { return arg.c_str(); } + +template<typename ARG> +struct FmtArg +{ + explicit FmtArg(const ARG& arg) : a_(arg) {} + const ARG& operator()() const { return a_; } + const ARG& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template<typename CT> +class CStdStr : public std::basic_string<CT> +{ + // Typedefs for shorter names. Using these names also appears to help + // us avoid some ambiguities that otherwise arise on some platforms + + #define MYBASE std::basic_string<CT> // my base class + //typedef typename std::basic_string<CT> MYBASE; // my base class + typedef CStdStr<CT> MYTYPE; // myself + typedef typename MYBASE::const_pointer PCMYSTR; // PCSTR or PCWSTR + typedef typename MYBASE::pointer PMYSTR; // PSTR or PWSTR + typedef typename MYBASE::iterator MYITER; // my iterator type + typedef typename MYBASE::const_iterator MYCITER; // you get the idea... + typedef typename MYBASE::reverse_iterator MYRITER; + typedef typename MYBASE::size_type MYSIZE; + typedef typename MYBASE::value_type MYVAL; + typedef typename MYBASE::allocator_type MYALLOC; + +public: + // shorthand conversion from PCTSTR to string resource ID + #define SSRES(pctstr) LOWORD(reinterpret_cast<unsigned long>(pctstr)) + + bool TryLoad(const void* pT) + { + bool bLoaded = false; + +#if defined(SS_WIN32) && !defined(SS_ANSI) + if ( ( pT != NULL ) && SS_IS_INTRESOURCE(pT) ) + { + UINT nId = LOWORD(reinterpret_cast<unsigned long>(pT)); + if ( !LoadString(nId) ) + { + TRACE(_T("Can't load string %u\n"), SSRES(pT)); + } + bLoaded = true; + } +#endif + + return bLoaded; + } + + + // CStdStr inline constructors + CStdStr() + { + } + + CStdStr(const MYTYPE& str) : MYBASE(SSREF(str)) + { + } + + CStdStr(const std::string& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(const std::wstring& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(PCMYSTR pT, MYSIZE n) : MYBASE(pT, n) + { + } + + CStdStr(PCSTR pA) + { + #ifdef SS_ANSI + *this = pA; + #else + if ( !TryLoad(pA) ) + *this = pA; + #endif + } + + CStdStr(PCWSTR pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(MYCITER first, MYCITER last) + : MYBASE(first, last) + { + } + + CStdStr(MYSIZE nSize, MYVAL ch, const MYALLOC& al=MYALLOC()) + : MYBASE(nSize, ch, al) + { + } + + #ifdef SS_INC_COMDEF + CStdStr(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + this->append(static_cast<PCMYSTR>(bstr), bstr.length()); + } + #endif + + // CStdStr inline assignment operators -- the ssasn function now takes care + // of fixing the MSVC assignment bug (see knowledge base article Q172398). + MYTYPE& operator=(const MYTYPE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::string& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::wstring& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(PCSTR pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(PCWSTR pW) + { + ssasn(*this, pW); + return *this; + } + + // Overloads also needed to fix the MSVC assignment bug (KB: Q172398) + // *** Thanks to Pete The Plumber for catching this one *** + // They also are compiled if you have explicitly turned off refcounting + #if ( defined(_MSC_VER) && ( _MSC_VER < 1200 ) ) || defined(SS_NO_REFCOUNT) + + MYTYPE& assign(const MYTYPE& str) + { + Q172398(*this); + sscpy(GetBuffer(str.size()+1), SSREF(str)); + this->ReleaseBuffer(str.size()); + return *this; + } + + MYTYPE& assign(const MYTYPE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // <nChars> or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which <nChars> + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + MYTYPE strTemp(str.c_str()+nStart, nChars); + Q172398(*this); + this->assign(strTemp); + return *this; + } + + MYTYPE& assign(const MYBASE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& assign(const MYBASE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // <nChars> or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which <nChars> + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + + // Watch out for assignment to self + + if ( this == &str ) + { + MYTYPE strTemp(str.c_str() + nStart, nChars); + static_cast<MYBASE*>(this)->assign(strTemp); + } + else + { + Q172398(*this); + static_cast<MYBASE*>(this)->assign(str.c_str()+nStart, nChars); + } + return *this; + } + + MYTYPE& assign(const CT* pC, MYSIZE nChars) + { + // Q172398 only fix -- erase before assigning, but not if we're + // assigning from our own buffer + + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + if ( !this->empty() && + ( pC < this->data() || pC > this->data() + this->capacity() ) ) + { + this->erase(); + } + #endif + Q172398(*this); + static_cast<MYBASE*>(this)->assign(pC, nChars); + return *this; + } + + MYTYPE& assign(MYSIZE nChars, MYVAL val) + { + Q172398(*this); + static_cast<MYBASE*>(this)->assign(nChars, val); + return *this; + } + + MYTYPE& assign(const CT* pT) + { + return this->assign(pT, MYBASE::traits_type::length(pT)); + } + + MYTYPE& assign(MYCITER iterFirst, MYCITER iterLast) + { + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + // Q172398 fix. don't call erase() if we're assigning from ourself + if ( iterFirst < this->begin() || + iterFirst > this->begin() + this->size() ) + { + this->erase() + } + #endif + this->replace(this->begin(), this->end(), iterFirst, iterLast); + return *this; + } + #endif + + + // ------------------------------------------------------------------------- + // CStdStr inline concatenation. + // ------------------------------------------------------------------------- + MYTYPE& operator+=(const MYTYPE& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::string& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::wstring& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(PCSTR pA) + { + ssadd(*this, pA); + return *this; + } + + MYTYPE& operator+=(PCWSTR pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(CT t) + { + this->append(1, t); + return *this; + } + + // ------------------------------------------------------------------------- + // CStdStr -- Direct access to character buffer. In the MS' implementation, + // the at() function that we use here also calls _Freeze() providing us some + // protection from multithreading problems associated with ref-counting. + // In VC 7 and later, of course, the ref-counting stuff is gone. + // ------------------------------------------------------------------------- + + CT* GetBuf(int nMinLen=-1) + { + if ( static_cast<int>(this->size()) < nMinLen ) + this->resize(static_cast<MYSIZE>(nMinLen)); + + return this->empty() ? const_cast<CT*>(this->data()) : &(this->at(0)); + } + + CT* SetBuf(int nLen) + { + nLen = ( nLen > 0 ? nLen : 0 ); + if ( this->capacity() < 1 && nLen == 0 ) + this->resize(1); + + this->resize(static_cast<MYSIZE>(nLen)); + return const_cast<CT*>(this->data()); + } + void RelBuf(int nNewLen=-1) + { + this->resize(static_cast<MYSIZE>(nNewLen > -1 ? nNewLen : + sslen(this->c_str()))); + } + + void BufferRel() { RelBuf(); } // backwards compatability + CT* Buffer() { return GetBuf(); } // backwards compatability + CT* BufferSet(int nLen) { return SetBuf(nLen);}// backwards compatability + + bool Equals(const CT* pT, bool bUseCase=false) const + { + return 0 == (bUseCase ? this->compare(pT) : ssicmp(this->c_str(), pT)); + } + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Load + // REMARKS: + // Loads string from resource specified by nID + // + // PARAMETERS: + // nID - resource Identifier. Purely a Win32 thing in this case + // + // RETURN VALUE: + // true if successful, false otherwise + // ------------------------------------------------------------------------- + +#ifndef SS_ANSI + + bool Load(UINT nId, HMODULE hModule=NULL) + { + bool bLoaded = false; // set to true of we succeed. + + #ifdef _MFC_VER // When in Rome (or MFC land)... + + // If they gave a resource handle, use it. Note - this is archaic + // and not really what I would recommend. But then again, in MFC + // land, you ought to be using CString for resources anyway since + // it walks the resource chain for you. + + HMODULE hModuleOld = NULL; + + if ( NULL != hModule ) + { + hModuleOld = AfxGetResourceHandle(); + AfxSetResourceHandle(hModule); + } + + // ...load the string + + CString strRes; + bLoaded = FALSE != strRes.LoadString(nId); + + // ...and if we set the resource handle, restore it. + + if ( NULL != hModuleOld ) + AfxSetResourceHandle(hModule); + + if ( bLoaded ) + *this = strRes; + + #else // otherwise make our own hackneyed version of CString's Load + + // Get the resource name and module handle + + if ( NULL == hModule ) + hModule = GetResourceHandle(); + + PCTSTR szName = MAKEINTRESOURCE((nId>>4)+1); // lifted + DWORD dwSize = 0; + + // No sense continuing if we can't find the resource + + HRSRC hrsrc = ::FindResource(hModule, szName, RT_STRING); + + if ( NULL == hrsrc ) + { + TRACE(_T("Cannot find resource %d: 0x%X"), nId, ::GetLastError()); + } + else if ( 0 == (dwSize = ::SizeofResource(hModule, hrsrc) / sizeof(CT))) + { + TRACE(_T("Cant get size of resource %d 0x%X\n"),nId,GetLastError()); + } + else + { + bLoaded = 0 != ssload(hModule, nId, GetBuf(dwSize), dwSize); + ReleaseBuffer(); + } + + #endif // #ifdef _MFC_VER + + if ( !bLoaded ) + TRACE(_T("String not loaded 0x%X\n"), ::GetLastError()); + + return bLoaded; + } + +#endif // #ifdef SS_ANSI + + // ------------------------------------------------------------------------- + // CString Facade Functions: + // + // The following methods are intended to allow you to use this class as a + // near drop-in replacement for CString. + // ------------------------------------------------------------------------- + #ifdef SS_WIN32 + BSTR AllocSysString() const + { + ostring os; + ssasn(os, *this); + return ::SysAllocString(os.c_str()); + } + #endif + +#ifndef SS_NO_LOCALE + int Collate(PCMYSTR szThat) const + { + return sscoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } + + int CollateNoCase(PCMYSTR szThat) const + { + return ssicoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } +#endif + int FindOneOf(PCMYSTR szCharSet) const + { + MYSIZE nIdx = this->find_first_of(szCharSet); + return static_cast<int>(MYBASE::npos == nIdx ? -1 : nIdx); + } + +#ifndef SS_ANSI + void FormatMessage(PCMYSTR szFormat, ...) throw(std::exception) + { + va_list argList; + va_start(argList, szFormat); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + szFormat, 0, 0, + reinterpret_cast<PMYSTR>(&szTemp), 0, &argList) == 0 || + szTemp == 0 ) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } + + void FormatMessage(UINT nFormatId, ...) throw(std::exception) + { + MYTYPE sFormat; + VERIFY(sFormat.LoadString(nFormatId)); + va_list argList; + va_start(argList, nFormatId); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + sFormat, 0, 0, + reinterpret_cast<PMYSTR>(&szTemp), 0, &argList) == 0 || + szTemp == 0) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } +#endif + + // GetAllocLength -- an MSVC7 function but it costs us nothing to add it. + + int GetAllocLength() + { + return static_cast<int>(this->capacity()); + } + + // ------------------------------------------------------------------------- + // GetXXXX -- Direct access to character buffer + // ------------------------------------------------------------------------- + CT* GetBuffer(int nMinLen=-1) + { + return GetBuf(nMinLen); + } + + CT* GetBufferSetLength(int nLen) + { + return BufferSet(nLen); + } + +#ifndef SS_ANSI + bool LoadString(UINT nId) + { + return this->Load(nId); + } +#endif + + void MakeReverse() + { + std::reverse(this->begin(), this->end()); + } + + void ReleaseBuffer(int nNewLen=-1) + { + RelBuf(nNewLen); + } + +#ifndef SS_ANSI + BSTR SetSysString(BSTR* pbstr) const + { + ostring os; + ssasn(os, *this); + if ( !::SysReAllocStringLen(pbstr, os.c_str(), os.length()) ) + throw std::runtime_error("out of memory"); + + ASSERT(*pbstr != 0); + return *pbstr; + } +#endif + + MYTYPE SpanExcluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + + MYTYPE SpanIncluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_not_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + +#if defined SS_WIN32 && !defined(UNICODE) && !defined(SS_ANSI) + + // CString's OemToAnsi and AnsiToOem functions are available only in + // Unicode builds. However since we're a template we also need a + // runtime check of CT and a reinterpret_cast to account for the fact + // that CStdStringW gets instantiated even in non-Unicode builds. + + void AnsiToOem() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::CharToOem(reinterpret_cast<PCSTR>(this->c_str()), + reinterpret_cast<PSTR>(GetBuf())); + } + else + { + ASSERT(false); + } + } + + void OemToAnsi() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::OemToChar(reinterpret_cast<PCSTR>(this->c_str()), + reinterpret_cast<PSTR>(GetBuf())); + } + else + { + ASSERT(false); + } + } + +#endif + + void FreeExtra() + { + MYTYPE mt; + this->swap(mt); + if ( !mt.empty() ) + this->assign(mt.c_str(), mt.size()); + } + + // I have intentionally not implemented the following CString + // functions. You cannot make them work without taking advantage + // of implementation specific behavior. However if you absolutely + // MUST have them, uncomment out these lines for "sort-of-like" + // their behavior. You're on your own. + +// CT* LockBuffer() { return GetBuf(); }// won't really lock +// void UnlockBuffer(); { } // why have UnlockBuffer w/o LockBuffer? + + // Array-indexing operators. Required because we defined an implicit cast + // to operator const CT* (Thanks to Julian Selman for pointing this out) + + CT& operator[](int nIdx) + { + return static_cast<MYBASE*>(this)->operator[](static_cast<MYSIZE>(nIdx)); + } + + const CT& operator[](int nIdx) const + { + return static_cast<const MYBASE*>(this)->operator[](static_cast<MYSIZE>(nIdx)); + } + + CT& operator[](unsigned int nIdx) + { + return static_cast<MYBASE*>(this)->operator[](static_cast<MYSIZE>(nIdx)); + } + + const CT& operator[](unsigned int nIdx) const + { + return static_cast<const MYBASE*>(this)->operator[](static_cast<MYSIZE>(nIdx)); + } + + CT& operator[](unsigned long nIdx) + { + return static_cast<MYBASE*>(this)->operator[](static_cast<MYSIZE>(nIdx)); + } + + const CT& operator[](unsigned long nIdx) const + { + return static_cast<const MYBASE*>(this)->operator[](static_cast<MYSIZE>(nIdx)); + } + +#ifndef SS_NO_IMPLICIT_CAST + operator const CT*() const + { + return this->c_str(); + } +#endif + + // IStream related functions. Useful in IPersistStream implementations + +#ifdef SS_INC_COMDEF + + // struct SSSHDR - useful for non Std C++ persistence schemes. + typedef struct SSSHDR + { + BYTE byCtrl; + ULONG nChars; + } SSSHDR; // as in "Standard String Stream Header" + + #define SSSO_UNICODE 0x01 // the string is a wide string + #define SSSO_COMPRESS 0x02 // the string is compressed + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSize + // REMARKS: + // Returns how many bytes it will take to StreamSave() this CStdString + // object to an IStream. + // ------------------------------------------------------------------------- + ULONG StreamSize() const + { + // Control header plus string + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + return (this->size() * sizeof(CT)) + sizeof(SSSHDR); + } + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSave + // REMARKS: + // Saves this CStdString object to a COM IStream. + // ------------------------------------------------------------------------- + HRESULT StreamSave(IStream* pStream) const + { + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + HRESULT hr = E_FAIL; + ASSERT(pStream != 0); + SSSHDR hdr; + hdr.byCtrl = sizeof(CT) == 2 ? SSSO_UNICODE : 0; + hdr.nChars = this->size(); + + + if ( FAILED(hr=pStream->Write(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamSave: Cannot write control header, ERR=0x%X\n"),hr); + } + else if ( empty() ) + { + ; // nothing to write + } + else if ( FAILED(hr=pStream->Write(this->c_str(), + this->size()*sizeof(CT), 0)) ) + { + TRACE(_T("StreamSave: Cannot write string to stream 0x%X\n"), hr); + } + + return hr; + } + + + // ------------------------------------------------------------------------- + // FUNCTION: StreamLoad + // REMARKS: + // This method loads the object from an IStream. + // ------------------------------------------------------------------------- + HRESULT StreamLoad(IStream* pStream) + { + ASSERT(pStream != 0); + SSSHDR hdr; + HRESULT hr = E_FAIL; + + if ( FAILED(hr=pStream->Read(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamLoad: Cant read control header, ERR=0x%X\n"), hr); + } + else if ( hdr.nChars > 0 ) + { + ULONG nRead = 0; + PMYSTR pMyBuf = BufferSet(hdr.nChars); + + // If our character size matches the character size of the string + // we're trying to read, then we can read it directly into our + // buffer. Otherwise, we have to read into an intermediate buffer + // and convert. + + if ( (hdr.byCtrl & SSSO_UNICODE) != 0 ) + { + ULONG nBytes = hdr.nChars * sizeof(wchar_t); + if ( sizeof(CT) == sizeof(wchar_t) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PWSTR pBufW = reinterpret_cast<PWSTR>(_alloca((nBytes)+1)); + if ( FAILED(hr=pStream->Read(pBufW, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufW, hdr.nChars); + } + } + else + { + ULONG nBytes = hdr.nChars * sizeof(char); + if ( sizeof(CT) == sizeof(char) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PSTR pBufA = reinterpret_cast<PSTR>(_alloca(nBytes)); + if ( FAILED(hr=pStream->Read(pBufA, hdr.nChars, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufA, hdr.nChars); + } + } + } + else + { + this->erase(); + } + return hr; + } +#endif // #ifdef SS_INC_COMDEF + +#ifndef SS_ANSI + + // SetResourceHandle/GetResourceHandle. In MFC builds, these map directly + // to AfxSetResourceHandle and AfxGetResourceHandle. In non-MFC builds they + // point to a single static HINST so that those who call the member + // functions that take resource IDs can provide an alternate HINST of a DLL + // to search. This is not exactly the list of HMODULES that MFC provides + // but it's better than nothing. + + #ifdef _MFC_VER + static void SetResourceHandle(HMODULE hNew) + { + AfxSetResourceHandle(hNew); + } + static HMODULE GetResourceHandle() + { + return AfxGetResourceHandle(); + } + #else + static void SetResourceHandle(HMODULE hNew) + { + SSResourceHandle() = hNew; + } + static HMODULE GetResourceHandle() + { + return SSResourceHandle(); + } + #endif + +#endif +}; + +// ----------------------------------------------------------------------------- +// MSVC USERS: HOW TO EXPORT CSTDSTRING FROM A DLL +// +// If you are using MS Visual C++ and you want to export CStdStringA and +// CStdStringW from a DLL, then all you need to +// +// 1. make sure that all components link to the same DLL version +// of the CRT (not the static one). +// 2. Uncomment the 3 lines of code below +// 3. #define 2 macros per the instructions in MS KnowledgeBase +// article Q168958. The macros are: +// +// MACRO DEFINTION WHEN EXPORTING DEFINITION WHEN IMPORTING +// ----- ------------------------ ------------------------- +// SSDLLEXP (nothing, just #define it) extern +// SSDLLSPEC __declspec(dllexport) __declspec(dllimport) +// +// Note that these macros must be available to ALL clients who want to +// link to the DLL and use the class. If they +// +// A word of advice: Don't bother. +// +// Really, it is not necessary to export CStdString functions from a DLL. I +// never do. In my projects, I do generally link to the DLL version of the +// Standard C++ Library, but I do NOT attempt to export CStdString functions. +// I simply include the header where it is needed and allow for the code +// redundancy. +// +// That redundancy is a lot less than you think. This class does most of its +// work via the Standard C++ Library, particularly the base_class basic_string<> +// member functions. Most of the functions here are small enough to be inlined +// anyway. Besides, you'll find that in actual practice you use less than 1/2 +// of the code here, even in big projects and different modules will use as +// little as 10% of it. That means a lot less functions actually get linked +// your binaries. If you export this code from a DLL, it ALL gets linked in. +// +// I've compared the size of the binaries from exporting vs NOT exporting. Take +// my word for it -- exporting this code is not worth the hassle. +// +// ----------------------------------------------------------------------------- +//#pragma warning(disable:4231) // non-standard extension ("extern template") +// SSDLLEXP template class SSDLLSPEC CStdStr<char>; +// SSDLLEXP template class SSDLLSPEC CStdStr<wchar_t>; + + +// ============================================================================= +// END OF CStdStr INLINE FUNCTION DEFINITIONS +// ============================================================================= + +// Now typedef our class names based upon this humongous template + +typedef CStdStr<char> CStdStringA; // a better std::string +typedef CStdStr<wchar_t> CStdStringW; // a better std::wstring +typedef CStdStr<uint16_t> CStdString16; // a 16bit char string +typedef CStdStr<uint32_t> CStdString32; // a 32bit char string +typedef CStdStr<OLECHAR> CStdStringO; // almost always CStdStringW + +// ----------------------------------------------------------------------------- +// CStdStr addition functions defined as inline +// ----------------------------------------------------------------------------- + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringA& s2) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, CStdStringA::value_type t) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCSTR pA) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(pA); + return sRet; +} +inline CStdStringA operator+(PCSTR pA, const CStdStringA& sA) +{ + CStdStringA sRet; + CStdStringA::size_type nObjSize = sA.size(); + CStdStringA::size_type nLitSize = + static_cast<CStdStringA::size_type>(sslen(pA)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pA); + sRet.append(sA); + return sRet; +} + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringW& s2) +{ + return s1 + CStdStringA(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringW& s2) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCWSTR pW) +{ + return s1 + CStdStringA(pW); +} + +#ifdef UNICODE + inline CStdStringW operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringW(pW) + CStdStringW(SSREF(sA)); + } + inline CStdStringW operator+(PCSTR pA, const CStdStringW& sW) + { + return CStdStringW(pA) + sW; + } +#else + inline CStdStringA operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringA(pW) + sA; + } + inline CStdStringA operator+(PCSTR pA, const CStdStringW& sW) + { + return pA + CStdStringA(sW); + } +#endif + +// ...Now the wide string versions. +inline CStdStringW operator+(const CStdStringW& s1, CStdStringW::value_type t) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringW operator+(const CStdStringW& s1, PCWSTR pW) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(pW); + return sRet; +} +inline CStdStringW operator+(PCWSTR pW, const CStdStringW& sW) +{ + CStdStringW sRet; + CStdStringW::size_type nObjSize = sW.size(); + CStdStringA::size_type nLitSize = + static_cast<CStdStringW::size_type>(sslen(pW)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pW); + sRet.append(sW); + return sRet; +} + +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringA& s2) +{ + return s1 + CStdStringW(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, PCSTR pA) +{ + return s1 + CStdStringW(pA); +} + + +// New-style format function is a template + +#ifdef SS_SAFE_FORMAT + +template<> +struct FmtArg<CStdStringA> +{ + explicit FmtArg(const CStdStringA& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const CStdStringA& a_; +private: + FmtArg<CStdStringA>& operator=(const FmtArg<CStdStringA>&) { return *this; } +}; +template<> +struct FmtArg<CStdStringW> +{ + explicit FmtArg(const CStdStringW& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const CStdStringW& a_; +private: + FmtArg<CStdStringW>& operator=(const FmtArg<CStdStringW>&) { return *this; } +}; + +template<> +struct FmtArg<std::string> +{ + explicit FmtArg(const std::string& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const std::string& a_; +private: + FmtArg<std::string>& operator=(const FmtArg<std::string>&) { return *this; } +}; +template<> +struct FmtArg<std::wstring> +{ + explicit FmtArg(const std::wstring& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const std::wstring& a_; +private: + FmtArg<std::wstring>& operator=(const FmtArg<std::wstring>&) {return *this;} +}; +#endif // #ifdef SS_SAFEFORMAT + +#ifndef SS_ANSI + // SSResourceHandle: our MFC-like resource handle + inline HMODULE& SSResourceHandle() + { + static HMODULE hModuleSS = GetModuleHandle(0); + return hModuleSS; + } +#endif + + +// In MFC builds, define some global serialization operators +// Special operators that allow us to serialize CStdStrings to CArchives. +// Note that we use an intermediate CString object in order to ensure that +// we use the exact same format. + +#ifdef _MFC_VER + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringA& strA) + { + CString strTemp = strA; + return ar << strTemp; + } + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringW& strW) + { + CString strTemp = strW; + return ar << strTemp; + } + + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringA& strA) + { + CString strTemp; + ar >> strTemp; + strA = strTemp; + return ar; + } + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringW& strW) + { + CString strTemp; + ar >> strTemp; + strW = strTemp; + return ar; + } +#endif // #ifdef _MFC_VER -- (i.e. is this MFC?) + + +// Define TCHAR based friendly names for some of these functions + +#ifdef UNICODE + //#define CStdString CStdStringW + typedef CStdStringW CStdString; +#else + //#define CStdString CStdStringA + typedef CStdStringA CStdString; +#endif + +// ...and some shorter names for the space-efficient + +// ----------------------------------------------------------------------------- +// FUNCTIONAL COMPARATORS: +// REMARKS: +// These structs are derived from the std::binary_function template. They +// give us functional classes (which may be used in Standard C++ Library +// collections and algorithms) that perform case-insensitive comparisons of +// CStdString objects. This is useful for maps in which the key may be the +// proper string but in the wrong case. +// ----------------------------------------------------------------------------- +#define StdStringLessNoCaseW SSLNCW // avoid VC compiler warning 4786 +#define StdStringEqualsNoCaseW SSENCW +#define StdStringLessNoCaseA SSLNCA +#define StdStringEqualsNoCaseA SSENCA + +#ifdef UNICODE + #define StdStringLessNoCase SSLNCW + #define StdStringEqualsNoCase SSENCW +#else + #define StdStringLessNoCase SSLNCA + #define StdStringEqualsNoCase SSENCA +#endif + +struct StdStringLessNoCaseW + : std::binary_function<CStdStringW, CStdStringW, bool> +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseW + : std::binary_function<CStdStringW, CStdStringW, bool> +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; +struct StdStringLessNoCaseA + : std::binary_function<CStdStringA, CStdStringA, bool> +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseA + : std::binary_function<CStdStringA, CStdStringA, bool> +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; + +// If we had to define our own version of TRACE above, get rid of it now + +#ifdef TRACE_DEFINED_HERE + #undef TRACE + #undef TRACE_DEFINED_HERE +#endif + + +// These std::swap specializations come courtesy of Mike Crusader. + +//namespace std +//{ +// inline void swap(CStdStringA& s1, CStdStringA& s2) throw() +// { +// s1.swap(s2); +// } +// template<> +// inline void swap(CStdStringW& s1, CStdStringW& s2) throw() +// { +// s1.swap(s2); +// } +//} + +// Turn back on any Borland warnings we turned off. + +#ifdef __BORLANDC__ + #pragma option pop // Turn back on inline function warnings +// #pragma warn +inl // Turn back on inline function warnings +#endif + +#endif // #ifndef STDSTRING_H diff --git a/src/utils/Stopwatch.cpp b/src/utils/Stopwatch.cpp new file mode 100644 index 0000000000..6e41eedec3 --- /dev/null +++ b/src/utils/Stopwatch.cpp @@ -0,0 +1,59 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "threads/SystemClock.h" +#include "Stopwatch.h" +#if defined(TARGET_POSIX) && !defined(TARGET_DARWIN) && !defined(TARGET_FREEBSD) +#include <sys/sysinfo.h> +#endif +#include "utils/TimeUtils.h" + +CStopWatch::CStopWatch(bool useFrameTime /*=false*/) +{ + m_timerPeriod = 0.0f; + m_startTick = 0; + m_stopTick = 0; + m_isRunning = false; + m_useFrameTime = useFrameTime; + +#ifdef TARGET_POSIX + m_timerPeriod = 1.0f / 1000.0f; // we want seconds +#else + if (m_useFrameTime) + m_timerPeriod = 1.0f / 1000.0f; //frametime is in milliseconds + else + m_timerPeriod = 1.0f / (float)CurrentHostFrequency(); +#endif +} + +CStopWatch::~CStopWatch() +{ +} + +int64_t CStopWatch::GetTicks() const +{ + if (m_useFrameTime) + return CTimeUtils::GetFrameTime(); +#ifndef TARGET_POSIX + return CurrentHostCounter(); +#else + return XbmcThreads::SystemClockMillis(); +#endif +} diff --git a/src/utils/Stopwatch.h b/src/utils/Stopwatch.h new file mode 100644 index 0000000000..a3bcb8a0c7 --- /dev/null +++ b/src/utils/Stopwatch.h @@ -0,0 +1,112 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> + +class CStopWatch +{ +public: + CStopWatch(bool useFrameTime=false); + ~CStopWatch(); + + /*! + \brief Retrieve the running state of the stopwatch. + + \return True if stopwatch has been started but not stopped. + */ + inline bool IsRunning() const + { + return m_isRunning; + } + + /*! + \brief Record start time and change state to running. + */ + inline void StartZero() + { + m_startTick = GetTicks(); + m_isRunning = true; + } + + /*! + \brief Record start time and change state to running, only if the stopwatch is stopped. + */ + inline void Start() + { + if (!m_isRunning) + StartZero(); + } + + /*! + \brief Record stop time and change state to not running. + */ + inline void Stop() + { + if(m_isRunning) + { + m_stopTick = GetTicks(); + m_isRunning = false; + } + } + + /*! + \brief Set the start time such that time elapsed is now zero. + */ + void Reset() + { + if (m_isRunning) + m_startTick = GetTicks(); + else + m_startTick = m_stopTick; + } + + /*! + \brief Retrieve time elapsed between the last call to Start(), StartZero() + or Reset() and; if running, now; if stopped, the last call to Stop(). + + \return Elapsed time, in seconds, as a float. + */ + float GetElapsedSeconds() const + { + int64_t totalTicks = (m_isRunning ? GetTicks() : m_stopTick) - m_startTick; + return (float)totalTicks * m_timerPeriod; + } + + /*! + \brief Retrieve time elapsed between the last call to Start(), StartZero() + or Reset() and; if running, now; if stopped, the last call to Stop(). + + \return Elapsed time, in milliseconds, as a float. + */ + float GetElapsedMilliseconds() const + { + return GetElapsedSeconds() * 1000.0f; + } + +private: + int64_t GetTicks() const; + float m_timerPeriod; // to save division in GetElapsed...() + int64_t m_startTick; + int64_t m_stopTick; + bool m_isRunning; + bool m_useFrameTime; +}; diff --git a/src/utils/StreamDetails.cpp b/src/utils/StreamDetails.cpp new file mode 100644 index 0000000000..28b413d249 --- /dev/null +++ b/src/utils/StreamDetails.cpp @@ -0,0 +1,598 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <math.h> +#include "StreamDetails.h" +#include "StreamUtils.h" +#include "Variant.h" +#include "LangInfo.h" +#include "utils/LangCodeExpander.h" +#include "utils/Archive.h" + +const float VIDEOASPECT_EPSILON = 0.025f; + +void CStreamDetail::Archive(CArchive &ar) +{ + // there's nothing to do here, the type is stored externally and parent isn't stored +} +void CStreamDetail::Serialize(CVariant &value) const +{ + // there's nothing to do here, the type is stored externally and parent isn't stored +} + +CStreamDetailVideo::CStreamDetailVideo() : + CStreamDetail(CStreamDetail::VIDEO), m_iWidth(0), m_iHeight(0), m_fAspect(0.0), m_iDuration(0) +{ +} + +void CStreamDetailVideo::Archive(CArchive& ar) +{ + CStreamDetail::Archive(ar); + if (ar.IsStoring()) + { + ar << m_strCodec; + ar << m_fAspect; + ar << m_iHeight; + ar << m_iWidth; + ar << m_iDuration; + ar << m_strStereoMode; + } + else + { + ar >> m_strCodec; + ar >> m_fAspect; + ar >> m_iHeight; + ar >> m_iWidth; + ar >> m_iDuration; + ar >> m_strStereoMode; + } +} +void CStreamDetailVideo::Serialize(CVariant& value) const +{ + value["codec"] = m_strCodec; + value["aspect"] = m_fAspect; + value["height"] = m_iHeight; + value["width"] = m_iWidth; + value["duration"] = m_iDuration; + value["stereomode"] = m_strStereoMode; +} + +bool CStreamDetailVideo::IsWorseThan(CStreamDetail *that) +{ + if (that->m_eType != CStreamDetail::VIDEO) + return true; + + // Best video stream is that with the most pixels + CStreamDetailVideo *sdv = (CStreamDetailVideo *)that; + return (sdv->m_iWidth * sdv->m_iHeight) > (m_iWidth * m_iHeight); +} + +CStreamDetailAudio::CStreamDetailAudio() : + CStreamDetail(CStreamDetail::AUDIO), m_iChannels(-1) +{ +} + +void CStreamDetailAudio::Archive(CArchive& ar) +{ + CStreamDetail::Archive(ar); + if (ar.IsStoring()) + { + ar << m_strCodec; + ar << m_strLanguage; + ar << m_iChannels; + } + else + { + ar >> m_strCodec; + ar >> m_strLanguage; + ar >> m_iChannels; + } +} +void CStreamDetailAudio::Serialize(CVariant& value) const +{ + value["codec"] = m_strCodec; + value["language"] = m_strLanguage; + value["channels"] = m_iChannels; +} + +bool CStreamDetailAudio::IsWorseThan(CStreamDetail *that) +{ + if (that->m_eType != CStreamDetail::AUDIO) + return true; + + CStreamDetailAudio *sda = (CStreamDetailAudio *)that; + // First choice is the thing with the most channels + if (sda->m_iChannels > m_iChannels) + return true; + if (m_iChannels > sda->m_iChannels) + return false; + + // In case of a tie, revert to codec priority + return StreamUtils::GetCodecPriority(sda->m_strCodec) > StreamUtils::GetCodecPriority(m_strCodec); +} + +CStreamDetailSubtitle::CStreamDetailSubtitle() : + CStreamDetail(CStreamDetail::SUBTITLE) +{ +} + +void CStreamDetailSubtitle::Archive(CArchive& ar) +{ + CStreamDetail::Archive(ar); + if (ar.IsStoring()) + { + ar << m_strLanguage; + } + else + { + ar >> m_strLanguage; + } +} +void CStreamDetailSubtitle::Serialize(CVariant& value) const +{ + value["language"] = m_strLanguage; +} + +bool CStreamDetailSubtitle::IsWorseThan(CStreamDetail *that) +{ + if (that->m_eType != CStreamDetail::SUBTITLE) + return true; + + if (g_LangCodeExpander.CompareLangCodes(m_strLanguage, ((CStreamDetailSubtitle *)that)->m_strLanguage)) + return false; + + // the best subtitle should be the one in the user's preferred language + // If preferred language is set to "original" this is "eng" + return m_strLanguage.empty() || + g_LangCodeExpander.CompareLangCodes(((CStreamDetailSubtitle *)that)->m_strLanguage, g_langInfo.GetSubtitleLanguage()); +} + +CStreamDetailSubtitle& CStreamDetailSubtitle::operator=(const CStreamDetailSubtitle &that) +{ + if (this != &that) + { + this->m_pParent = that.m_pParent; + this->m_strLanguage = that.m_strLanguage; + } + return *this; +} + +CStreamDetails& CStreamDetails::operator=(const CStreamDetails &that) +{ + if (this != &that) + { + Reset(); + std::vector<CStreamDetail *>::const_iterator iter; + for (iter = that.m_vecItems.begin(); iter != that.m_vecItems.end(); ++iter) + { + switch ((*iter)->m_eType) + { + case CStreamDetail::VIDEO: + AddStream(new CStreamDetailVideo((const CStreamDetailVideo &)(**iter))); + break; + case CStreamDetail::AUDIO: + AddStream(new CStreamDetailAudio((const CStreamDetailAudio &)(**iter))); + break; + case CStreamDetail::SUBTITLE: + AddStream(new CStreamDetailSubtitle((const CStreamDetailSubtitle &)(**iter))); + break; + } + } + + DetermineBestStreams(); + } /* if this != that */ + + return *this; +} + +bool CStreamDetails::operator ==(const CStreamDetails &right) const +{ + if (this == &right) return true; + + if (GetVideoStreamCount() != right.GetVideoStreamCount() || + GetAudioStreamCount() != right.GetAudioStreamCount() || + GetSubtitleStreamCount() != right.GetSubtitleStreamCount()) + return false; + + for (int iStream=1; iStream<=GetVideoStreamCount(); iStream++) + { + if (GetVideoCodec(iStream) != right.GetVideoCodec(iStream) || + GetVideoWidth(iStream) != right.GetVideoWidth(iStream) || + GetVideoHeight(iStream) != right.GetVideoHeight(iStream) || + GetVideoDuration(iStream) != right.GetVideoDuration(iStream) || + fabs(GetVideoAspect(iStream) - right.GetVideoAspect(iStream)) > VIDEOASPECT_EPSILON) + return false; + } + + for (int iStream=1; iStream<=GetAudioStreamCount(); iStream++) + { + if (GetAudioCodec(iStream) != right.GetAudioCodec(iStream) || + GetAudioLanguage(iStream) != right.GetAudioLanguage(iStream) || + GetAudioChannels(iStream) != right.GetAudioChannels(iStream) ) + return false; + } + + for (int iStream=1; iStream<=GetSubtitleStreamCount(); iStream++) + { + if (GetSubtitleLanguage(iStream) != right.GetSubtitleLanguage(iStream) ) + return false; + } + + return true; +} + +bool CStreamDetails::operator !=(const CStreamDetails &right) const +{ + if (this == &right) return false; + + return !(*this == right); +} + +CStreamDetail *CStreamDetails::NewStream(CStreamDetail::StreamType type) +{ + CStreamDetail *retVal = NULL; + switch (type) + { + case CStreamDetail::VIDEO: + retVal = new CStreamDetailVideo(); + break; + case CStreamDetail::AUDIO: + retVal = new CStreamDetailAudio(); + break; + case CStreamDetail::SUBTITLE: + retVal = new CStreamDetailSubtitle(); + break; + } + + if (retVal) + AddStream(retVal); + + return retVal; +} + +int CStreamDetails::GetStreamCount(CStreamDetail::StreamType type) const +{ + int retVal = 0; + std::vector<CStreamDetail *>::const_iterator iter; + for (iter = m_vecItems.begin(); iter != m_vecItems.end(); ++iter) + if ((*iter)->m_eType == type) + retVal++; + return retVal; +} + +int CStreamDetails::GetVideoStreamCount(void) const +{ + return GetStreamCount(CStreamDetail::VIDEO); +} + +int CStreamDetails::GetAudioStreamCount(void) const +{ + return GetStreamCount(CStreamDetail::AUDIO); +} + +int CStreamDetails::GetSubtitleStreamCount(void) const +{ + return GetStreamCount(CStreamDetail::SUBTITLE); +} + +CStreamDetails::CStreamDetails(const CStreamDetails &that) +{ + *this = that; +} + +void CStreamDetails::AddStream(CStreamDetail *item) +{ + item->m_pParent = this; + m_vecItems.push_back(item); +} + +void CStreamDetails::Reset(void) +{ + m_pBestVideo = NULL; + m_pBestAudio = NULL; + m_pBestSubtitle = NULL; + + std::vector<CStreamDetail *>::iterator iter; + for (iter = m_vecItems.begin(); iter != m_vecItems.end(); ++iter) + delete *iter; + m_vecItems.clear(); +} + +const CStreamDetail* CStreamDetails::GetNthStream(CStreamDetail::StreamType type, int idx) const +{ + if (idx == 0) + { + switch (type) + { + case CStreamDetail::VIDEO: + return m_pBestVideo; + break; + case CStreamDetail::AUDIO: + return m_pBestAudio; + break; + case CStreamDetail::SUBTITLE: + return m_pBestSubtitle; + break; + default: + return NULL; + break; + } + } + + std::vector<CStreamDetail *>::const_iterator iter; + for (iter = m_vecItems.begin(); iter != m_vecItems.end(); ++iter) + if ((*iter)->m_eType == type) + { + idx--; + if (idx < 1) + return *iter; + } + + return NULL; +} + +std::string CStreamDetails::GetVideoCodec(int idx) const +{ + CStreamDetailVideo *item = (CStreamDetailVideo *)GetNthStream(CStreamDetail::VIDEO, idx); + if (item) + return item->m_strCodec; + else + return ""; +} + +float CStreamDetails::GetVideoAspect(int idx) const +{ + CStreamDetailVideo *item = (CStreamDetailVideo *)GetNthStream(CStreamDetail::VIDEO, idx); + if (item) + return item->m_fAspect; + else + return 0.0; +} + +int CStreamDetails::GetVideoWidth(int idx) const +{ + CStreamDetailVideo *item = (CStreamDetailVideo *)GetNthStream(CStreamDetail::VIDEO, idx); + if (item) + return item->m_iWidth; + else + return 0; +} + +int CStreamDetails::GetVideoHeight(int idx) const +{ + CStreamDetailVideo *item = (CStreamDetailVideo *)GetNthStream(CStreamDetail::VIDEO, idx); + if (item) + return item->m_iHeight; + else + return 0; +} + +int CStreamDetails::GetVideoDuration(int idx) const +{ + CStreamDetailVideo *item = (CStreamDetailVideo *)GetNthStream(CStreamDetail::VIDEO, idx); + if (item) + return item->m_iDuration; + else + return 0; +} + +void CStreamDetails::SetVideoDuration(int idx, const int duration) +{ + CStreamDetailVideo *item = (CStreamDetailVideo *)GetNthStream(CStreamDetail::VIDEO, idx); + if (item) + item->m_iDuration = duration; +} + +std::string CStreamDetails::GetStereoMode(int idx) const +{ + CStreamDetailVideo *item = (CStreamDetailVideo *)GetNthStream(CStreamDetail::VIDEO, idx); + if (item) + return item->m_strStereoMode; + else + return ""; +} + +std::string CStreamDetails::GetAudioCodec(int idx) const +{ + CStreamDetailAudio *item = (CStreamDetailAudio *)GetNthStream(CStreamDetail::AUDIO, idx); + if (item) + return item->m_strCodec; + else + return ""; +} + +std::string CStreamDetails::GetAudioLanguage(int idx) const +{ + CStreamDetailAudio *item = (CStreamDetailAudio *)GetNthStream(CStreamDetail::AUDIO, idx); + if (item) + return item->m_strLanguage; + else + return ""; +} + +int CStreamDetails::GetAudioChannels(int idx) const +{ + CStreamDetailAudio *item = (CStreamDetailAudio *)GetNthStream(CStreamDetail::AUDIO, idx); + if (item) + return item->m_iChannels; + else + return -1; +} + +std::string CStreamDetails::GetSubtitleLanguage(int idx) const +{ + CStreamDetailSubtitle *item = (CStreamDetailSubtitle *)GetNthStream(CStreamDetail::SUBTITLE, idx); + if (item) + return item->m_strLanguage; + else + return ""; +} + +void CStreamDetails::Archive(CArchive& ar) +{ + if (ar.IsStoring()) + { + ar << (int)m_vecItems.size(); + + std::vector<CStreamDetail *>::const_iterator iter; + for (iter = m_vecItems.begin(); iter != m_vecItems.end(); ++iter) + { + // the type goes before the actual item. When loading we need + // to know the type before we can construct an instance to serialize + ar << (int)(*iter)->m_eType; + ar << (**iter); + } + } + else + { + int count; + ar >> count; + + Reset(); + for (int i=0; i<count; i++) + { + int type; + CStreamDetail *p = NULL; + + ar >> type; + p = NewStream(CStreamDetail::StreamType(type)); + if (p) + ar >> (*p); + } + + DetermineBestStreams(); + } +} +void CStreamDetails::Serialize(CVariant& value) const +{ + // make sure these properties are always present + value["audio"] = CVariant(CVariant::VariantTypeArray); + value["video"] = CVariant(CVariant::VariantTypeArray); + value["subtitle"] = CVariant(CVariant::VariantTypeArray); + + std::vector<CStreamDetail *>::const_iterator iter; + CVariant v; + for (iter = m_vecItems.begin(); iter != m_vecItems.end(); ++iter) + { + v.clear(); + (*iter)->Serialize(v); + switch ((*iter)->m_eType) + { + case CStreamDetail::AUDIO: + value["audio"].push_back(v); + break; + case CStreamDetail::VIDEO: + value["video"].push_back(v); + break; + case CStreamDetail::SUBTITLE: + value["subtitle"].push_back(v); + break; + } + } +} + +void CStreamDetails::DetermineBestStreams(void) +{ + m_pBestVideo = NULL; + m_pBestAudio = NULL; + m_pBestSubtitle = NULL; + + std::vector<CStreamDetail *>::const_iterator iter; + for (iter = m_vecItems.begin(); iter != m_vecItems.end(); ++iter) + { + CStreamDetail **champion; + switch ((*iter)->m_eType) + { + case CStreamDetail::VIDEO: + champion = (CStreamDetail **)&m_pBestVideo; + break; + case CStreamDetail::AUDIO: + champion = (CStreamDetail **)&m_pBestAudio; + break; + case CStreamDetail::SUBTITLE: + champion = (CStreamDetail **)&m_pBestSubtitle; + break; + default: + champion = NULL; + } /* switch type */ + + if (!champion) + continue; + + if ((*champion == NULL) || (*champion)->IsWorseThan(*iter)) + *champion = *iter; + } /* for each */ +} + +std::string CStreamDetails::VideoDimsToResolutionDescription(int iWidth, int iHeight) +{ + if (iWidth == 0 || iHeight == 0) + return ""; + + else if (iWidth <= 720 && iHeight <= 480) + return "480"; + // 720x576 (PAL) (768 when rescaled for square pixels) + else if (iWidth <= 768 && iHeight <= 576) + return "576"; + // 960x540 (sometimes 544 which is multiple of 16) + else if (iWidth <= 960 && iHeight <= 544) + return "540"; + // 1280x720 + else if (iWidth <= 1280 && iHeight <= 720) + return "720"; + // 1920x1080 + else if (iWidth <= 1920 && iHeight <= 1080) + return "1080"; + // 4K + else if (iWidth * iHeight >= 6000000) + return "4K"; + else + return ""; +} + +std::string CStreamDetails::VideoAspectToAspectDescription(float fAspect) +{ + if (fAspect == 0.0f) + return ""; + + // Given that we're never going to be able to handle every single possibility in + // aspect ratios, particularly when cropping prior to video encoding is taken into account + // the best we can do is take the "common" aspect ratios, and return the closest one available. + // The cutoffs are the geometric mean of the two aspect ratios either side. + if (fAspect < 1.3499f) // sqrt(1.33*1.37) + return "1.33"; + else if (fAspect < 1.5080f) // sqrt(1.37*1.66) + return "1.37"; + else if (fAspect < 1.7190f) // sqrt(1.66*1.78) + return "1.66"; + else if (fAspect < 1.8147f) // sqrt(1.78*1.85) + return "1.78"; + else if (fAspect < 2.0174f) // sqrt(1.85*2.20) + return "1.85"; + else if (fAspect < 2.2738f) // sqrt(2.20*2.35) + return "2.20"; + else if (fAspect < 2.3749f) // sqrt(2.35*2.40) + return "2.35"; + else if (fAspect < 2.4739f) // sqrt(2.40*2.55) + return "2.40"; + else if (fAspect < 2.6529f) // sqrt(2.55*2.76) + return "2.55"; + return "2.76"; +} diff --git a/src/utils/StreamDetails.h b/src/utils/StreamDetails.h new file mode 100644 index 0000000000..fdda28eeab --- /dev/null +++ b/src/utils/StreamDetails.h @@ -0,0 +1,138 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/IArchivable.h" +#include "ISerializable.h" +#include <string> +#include <vector> + +class CStreamDetails; + +class CStreamDetail : public IArchivable, public ISerializable +{ +public: + enum StreamType { + VIDEO, + AUDIO, + SUBTITLE + }; + + CStreamDetail(StreamType type) : m_eType(type), m_pParent(NULL) {}; + virtual void Archive(CArchive& ar); + virtual void Serialize(CVariant& value) const; + virtual bool IsWorseThan(CStreamDetail *that) { return true; }; + + const StreamType m_eType; + +protected: + CStreamDetails *m_pParent; + friend class CStreamDetails; +}; + +class CStreamDetailVideo : public CStreamDetail +{ +public: + CStreamDetailVideo(); + virtual void Archive(CArchive& ar); + virtual void Serialize(CVariant& value) const; + virtual bool IsWorseThan(CStreamDetail *that); + + int m_iWidth; + int m_iHeight; + float m_fAspect; + int m_iDuration; + std::string m_strCodec; + std::string m_strStereoMode; +}; + +class CStreamDetailAudio : public CStreamDetail +{ +public: + CStreamDetailAudio(); + virtual void Archive(CArchive& ar); + virtual void Serialize(CVariant& value) const; + virtual bool IsWorseThan(CStreamDetail *that); + + int m_iChannels; + std::string m_strCodec; + std::string m_strLanguage; +}; + +class CStreamDetailSubtitle : public CStreamDetail +{ +public: + CStreamDetailSubtitle(); + CStreamDetailSubtitle& operator=(const CStreamDetailSubtitle &that); + virtual void Archive(CArchive& ar); + virtual void Serialize(CVariant& value) const; + virtual bool IsWorseThan(CStreamDetail *that); + + std::string m_strLanguage; +}; + +class CStreamDetails : public IArchivable, public ISerializable +{ +public: + CStreamDetails() { Reset(); }; + CStreamDetails(const CStreamDetails &that); + ~CStreamDetails() { Reset(); }; + CStreamDetails& operator=(const CStreamDetails &that); + bool operator ==(const CStreamDetails &that) const; + bool operator !=(const CStreamDetails &that) const; + + static std::string VideoDimsToResolutionDescription(int iWidth, int iHeight); + static std::string VideoAspectToAspectDescription(float fAspect); + + bool HasItems(void) const { return m_vecItems.size() > 0; }; + int GetStreamCount(CStreamDetail::StreamType type) const; + int GetVideoStreamCount(void) const; + int GetAudioStreamCount(void) const; + int GetSubtitleStreamCount(void) const; + const CStreamDetail* GetNthStream(CStreamDetail::StreamType type, int idx) const; + + std::string GetVideoCodec(int idx = 0) const; + float GetVideoAspect(int idx = 0) const; + int GetVideoWidth(int idx = 0) const; + int GetVideoHeight(int idx = 0) const; + int GetVideoDuration(int idx = 0) const; + void SetVideoDuration(int idx, const int duration); + std::string GetStereoMode(int idx = 0) const; + + std::string GetAudioCodec(int idx = 0) const; + std::string GetAudioLanguage(int idx = 0) const; + int GetAudioChannels(int idx = 0) const; + + std::string GetSubtitleLanguage(int idx = 0) const; + + void AddStream(CStreamDetail *item); + void Reset(void); + void DetermineBestStreams(void); + + virtual void Archive(CArchive& ar); + virtual void Serialize(CVariant& value) const; + +private: + CStreamDetail *NewStream(CStreamDetail::StreamType type); + std::vector<CStreamDetail *> m_vecItems; + CStreamDetailVideo *m_pBestVideo; + CStreamDetailAudio *m_pBestAudio; + CStreamDetailSubtitle *m_pBestSubtitle; +}; diff --git a/src/utils/StreamUtils.cpp b/src/utils/StreamUtils.cpp new file mode 100644 index 0000000000..e7ef9a149a --- /dev/null +++ b/src/utils/StreamUtils.cpp @@ -0,0 +1,44 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "StreamUtils.h" + +int StreamUtils::GetCodecPriority(const std::string &codec) +{ + /* + * Technically flac, truehd, and dtshd_ma are equivalently good as they're all lossless. However, + * ffmpeg can't decode dtshd_ma losslessy yet. + */ + if (codec == "flac") // Lossless FLAC + return 7; + if (codec == "truehd") // Dolby TrueHD + return 6; + if (codec == "dtshd_ma") // DTS-HD Master Audio (previously known as DTS++) + return 5; + if (codec == "dtshd_hra") // DTS-HD High Resolution Audio + return 4; + if (codec == "eac3") // Dolby Digital Plus + return 3; + if (codec == "dca") // DTS + return 2; + if (codec == "ac3") // Dolby Digital + return 1; + return 0; +} diff --git a/src/utils/StreamUtils.h b/src/utils/StreamUtils.h new file mode 100644 index 0000000000..46edebab12 --- /dev/null +++ b/src/utils/StreamUtils.h @@ -0,0 +1,28 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> + +class StreamUtils +{ +public: + static int GetCodecPriority(const std::string &codec); +}; diff --git a/src/utils/StringUtils.cpp b/src/utils/StringUtils.cpp new file mode 100644 index 0000000000..56bb28ef99 --- /dev/null +++ b/src/utils/StringUtils.cpp @@ -0,0 +1,1197 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ +//----------------------------------------------------------------------- +// +// File: StringUtils.cpp +// +// Purpose: ATL split string utility +// Author: Paul J. Weiss +// +// Modified to use J O'Leary's CStdString class by kraqh3d +// +//------------------------------------------------------------------------ + + +#include "StringUtils.h" +#include "utils/RegExp.h" +#include "utils/fstrcmp.h" +#include "Util.h" +#include <locale> + +#include <math.h> +#include <sstream> +#include <time.h> +#include <stdlib.h> + +#define FORMAT_BLOCK_SIZE 512 // # of bytes for initial allocation for printf + +using namespace std; + +const char* ADDON_GUID_RE = "^(\\{){0,1}[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}(\\}){0,1}$"; + +/* empty string for use in returns by ref */ +const CStdString StringUtils::EmptyString = ""; +const std::string StringUtils::Empty = ""; +CStdString StringUtils::m_lastUUID = ""; + +// Copyright (c) Leigh Brasington 2012. All rights reserved. +// This code may be used and reproduced without written permission. +// http://www.leighb.com/tounicupper.htm +// +// The tables were constructed from +// http://publib.boulder.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=%2Fnls%2Frbagslowtoupmaptable.htm + +static wchar_t unicode_lowers[] = { + (wchar_t)0x0061, (wchar_t)0x0062, (wchar_t)0x0063, (wchar_t)0x0064, (wchar_t)0x0065, (wchar_t)0x0066, (wchar_t)0x0067, (wchar_t)0x0068, (wchar_t)0x0069, + (wchar_t)0x006A, (wchar_t)0x006B, (wchar_t)0x006C, (wchar_t)0x006D, (wchar_t)0x006E, (wchar_t)0x006F, (wchar_t)0x0070, (wchar_t)0x0071, (wchar_t)0x0072, + (wchar_t)0x0073, (wchar_t)0x0074, (wchar_t)0x0075, (wchar_t)0x0076, (wchar_t)0x0077, (wchar_t)0x0078, (wchar_t)0x0079, (wchar_t)0x007A, (wchar_t)0x00E0, + (wchar_t)0x00E1, (wchar_t)0x00E2, (wchar_t)0x00E3, (wchar_t)0x00E4, (wchar_t)0x00E5, (wchar_t)0x00E6, (wchar_t)0x00E7, (wchar_t)0x00E8, (wchar_t)0x00E9, + (wchar_t)0x00EA, (wchar_t)0x00EB, (wchar_t)0x00EC, (wchar_t)0x00ED, (wchar_t)0x00EE, (wchar_t)0x00EF, (wchar_t)0x00F0, (wchar_t)0x00F1, (wchar_t)0x00F2, + (wchar_t)0x00F3, (wchar_t)0x00F4, (wchar_t)0x00F5, (wchar_t)0x00F6, (wchar_t)0x00F8, (wchar_t)0x00F9, (wchar_t)0x00FA, (wchar_t)0x00FB, (wchar_t)0x00FC, + (wchar_t)0x00FD, (wchar_t)0x00FE, (wchar_t)0x00FF, (wchar_t)0x0101, (wchar_t)0x0103, (wchar_t)0x0105, (wchar_t)0x0107, (wchar_t)0x0109, (wchar_t)0x010B, + (wchar_t)0x010D, (wchar_t)0x010F, (wchar_t)0x0111, (wchar_t)0x0113, (wchar_t)0x0115, (wchar_t)0x0117, (wchar_t)0x0119, (wchar_t)0x011B, (wchar_t)0x011D, + (wchar_t)0x011F, (wchar_t)0x0121, (wchar_t)0x0123, (wchar_t)0x0125, (wchar_t)0x0127, (wchar_t)0x0129, (wchar_t)0x012B, (wchar_t)0x012D, (wchar_t)0x012F, + (wchar_t)0x0131, (wchar_t)0x0133, (wchar_t)0x0135, (wchar_t)0x0137, (wchar_t)0x013A, (wchar_t)0x013C, (wchar_t)0x013E, (wchar_t)0x0140, (wchar_t)0x0142, + (wchar_t)0x0144, (wchar_t)0x0146, (wchar_t)0x0148, (wchar_t)0x014B, (wchar_t)0x014D, (wchar_t)0x014F, (wchar_t)0x0151, (wchar_t)0x0153, (wchar_t)0x0155, + (wchar_t)0x0157, (wchar_t)0x0159, (wchar_t)0x015B, (wchar_t)0x015D, (wchar_t)0x015F, (wchar_t)0x0161, (wchar_t)0x0163, (wchar_t)0x0165, (wchar_t)0x0167, + (wchar_t)0x0169, (wchar_t)0x016B, (wchar_t)0x016D, (wchar_t)0x016F, (wchar_t)0x0171, (wchar_t)0x0173, (wchar_t)0x0175, (wchar_t)0x0177, (wchar_t)0x017A, + (wchar_t)0x017C, (wchar_t)0x017E, (wchar_t)0x0183, (wchar_t)0x0185, (wchar_t)0x0188, (wchar_t)0x018C, (wchar_t)0x0192, (wchar_t)0x0199, (wchar_t)0x01A1, + (wchar_t)0x01A3, (wchar_t)0x01A5, (wchar_t)0x01A8, (wchar_t)0x01AD, (wchar_t)0x01B0, (wchar_t)0x01B4, (wchar_t)0x01B6, (wchar_t)0x01B9, (wchar_t)0x01BD, + (wchar_t)0x01C6, (wchar_t)0x01C9, (wchar_t)0x01CC, (wchar_t)0x01CE, (wchar_t)0x01D0, (wchar_t)0x01D2, (wchar_t)0x01D4, (wchar_t)0x01D6, (wchar_t)0x01D8, + (wchar_t)0x01DA, (wchar_t)0x01DC, (wchar_t)0x01DF, (wchar_t)0x01E1, (wchar_t)0x01E3, (wchar_t)0x01E5, (wchar_t)0x01E7, (wchar_t)0x01E9, (wchar_t)0x01EB, + (wchar_t)0x01ED, (wchar_t)0x01EF, (wchar_t)0x01F3, (wchar_t)0x01F5, (wchar_t)0x01FB, (wchar_t)0x01FD, (wchar_t)0x01FF, (wchar_t)0x0201, (wchar_t)0x0203, + (wchar_t)0x0205, (wchar_t)0x0207, (wchar_t)0x0209, (wchar_t)0x020B, (wchar_t)0x020D, (wchar_t)0x020F, (wchar_t)0x0211, (wchar_t)0x0213, (wchar_t)0x0215, + (wchar_t)0x0217, (wchar_t)0x0253, (wchar_t)0x0254, (wchar_t)0x0257, (wchar_t)0x0258, (wchar_t)0x0259, (wchar_t)0x025B, (wchar_t)0x0260, (wchar_t)0x0263, + (wchar_t)0x0268, (wchar_t)0x0269, (wchar_t)0x026F, (wchar_t)0x0272, (wchar_t)0x0275, (wchar_t)0x0283, (wchar_t)0x0288, (wchar_t)0x028A, (wchar_t)0x028B, + (wchar_t)0x0292, (wchar_t)0x03AC, (wchar_t)0x03AD, (wchar_t)0x03AE, (wchar_t)0x03AF, (wchar_t)0x03B1, (wchar_t)0x03B2, (wchar_t)0x03B3, (wchar_t)0x03B4, + (wchar_t)0x03B5, (wchar_t)0x03B6, (wchar_t)0x03B7, (wchar_t)0x03B8, (wchar_t)0x03B9, (wchar_t)0x03BA, (wchar_t)0x03BB, (wchar_t)0x03BC, (wchar_t)0x03BD, + (wchar_t)0x03BE, (wchar_t)0x03BF, (wchar_t)0x03C0, (wchar_t)0x03C1, (wchar_t)0x03C3, (wchar_t)0x03C4, (wchar_t)0x03C5, (wchar_t)0x03C6, (wchar_t)0x03C7, + (wchar_t)0x03C8, (wchar_t)0x03C9, (wchar_t)0x03CA, (wchar_t)0x03CB, (wchar_t)0x03CC, (wchar_t)0x03CD, (wchar_t)0x03CE, (wchar_t)0x03E3, (wchar_t)0x03E5, + (wchar_t)0x03E7, (wchar_t)0x03E9, (wchar_t)0x03EB, (wchar_t)0x03ED, (wchar_t)0x03EF, (wchar_t)0x0430, (wchar_t)0x0431, (wchar_t)0x0432, (wchar_t)0x0433, + (wchar_t)0x0434, (wchar_t)0x0435, (wchar_t)0x0436, (wchar_t)0x0437, (wchar_t)0x0438, (wchar_t)0x0439, (wchar_t)0x043A, (wchar_t)0x043B, (wchar_t)0x043C, + (wchar_t)0x043D, (wchar_t)0x043E, (wchar_t)0x043F, (wchar_t)0x0440, (wchar_t)0x0441, (wchar_t)0x0442, (wchar_t)0x0443, (wchar_t)0x0444, (wchar_t)0x0445, + (wchar_t)0x0446, (wchar_t)0x0447, (wchar_t)0x0448, (wchar_t)0x0449, (wchar_t)0x044A, (wchar_t)0x044B, (wchar_t)0x044C, (wchar_t)0x044D, (wchar_t)0x044E, + (wchar_t)0x044F, (wchar_t)0x0451, (wchar_t)0x0452, (wchar_t)0x0453, (wchar_t)0x0454, (wchar_t)0x0455, (wchar_t)0x0456, (wchar_t)0x0457, (wchar_t)0x0458, + (wchar_t)0x0459, (wchar_t)0x045A, (wchar_t)0x045B, (wchar_t)0x045C, (wchar_t)0x045E, (wchar_t)0x045F, (wchar_t)0x0461, (wchar_t)0x0463, (wchar_t)0x0465, + (wchar_t)0x0467, (wchar_t)0x0469, (wchar_t)0x046B, (wchar_t)0x046D, (wchar_t)0x046F, (wchar_t)0x0471, (wchar_t)0x0473, (wchar_t)0x0475, (wchar_t)0x0477, + (wchar_t)0x0479, (wchar_t)0x047B, (wchar_t)0x047D, (wchar_t)0x047F, (wchar_t)0x0481, (wchar_t)0x0491, (wchar_t)0x0493, (wchar_t)0x0495, (wchar_t)0x0497, + (wchar_t)0x0499, (wchar_t)0x049B, (wchar_t)0x049D, (wchar_t)0x049F, (wchar_t)0x04A1, (wchar_t)0x04A3, (wchar_t)0x04A5, (wchar_t)0x04A7, (wchar_t)0x04A9, + (wchar_t)0x04AB, (wchar_t)0x04AD, (wchar_t)0x04AF, (wchar_t)0x04B1, (wchar_t)0x04B3, (wchar_t)0x04B5, (wchar_t)0x04B7, (wchar_t)0x04B9, (wchar_t)0x04BB, + (wchar_t)0x04BD, (wchar_t)0x04BF, (wchar_t)0x04C2, (wchar_t)0x04C4, (wchar_t)0x04C8, (wchar_t)0x04CC, (wchar_t)0x04D1, (wchar_t)0x04D3, (wchar_t)0x04D5, + (wchar_t)0x04D7, (wchar_t)0x04D9, (wchar_t)0x04DB, (wchar_t)0x04DD, (wchar_t)0x04DF, (wchar_t)0x04E1, (wchar_t)0x04E3, (wchar_t)0x04E5, (wchar_t)0x04E7, + (wchar_t)0x04E9, (wchar_t)0x04EB, (wchar_t)0x04EF, (wchar_t)0x04F1, (wchar_t)0x04F3, (wchar_t)0x04F5, (wchar_t)0x04F9, (wchar_t)0x0561, (wchar_t)0x0562, + (wchar_t)0x0563, (wchar_t)0x0564, (wchar_t)0x0565, (wchar_t)0x0566, (wchar_t)0x0567, (wchar_t)0x0568, (wchar_t)0x0569, (wchar_t)0x056A, (wchar_t)0x056B, + (wchar_t)0x056C, (wchar_t)0x056D, (wchar_t)0x056E, (wchar_t)0x056F, (wchar_t)0x0570, (wchar_t)0x0571, (wchar_t)0x0572, (wchar_t)0x0573, (wchar_t)0x0574, + (wchar_t)0x0575, (wchar_t)0x0576, (wchar_t)0x0577, (wchar_t)0x0578, (wchar_t)0x0579, (wchar_t)0x057A, (wchar_t)0x057B, (wchar_t)0x057C, (wchar_t)0x057D, + (wchar_t)0x057E, (wchar_t)0x057F, (wchar_t)0x0580, (wchar_t)0x0581, (wchar_t)0x0582, (wchar_t)0x0583, (wchar_t)0x0584, (wchar_t)0x0585, (wchar_t)0x0586, + (wchar_t)0x10D0, (wchar_t)0x10D1, (wchar_t)0x10D2, (wchar_t)0x10D3, (wchar_t)0x10D4, (wchar_t)0x10D5, (wchar_t)0x10D6, (wchar_t)0x10D7, (wchar_t)0x10D8, + (wchar_t)0x10D9, (wchar_t)0x10DA, (wchar_t)0x10DB, (wchar_t)0x10DC, (wchar_t)0x10DD, (wchar_t)0x10DE, (wchar_t)0x10DF, (wchar_t)0x10E0, (wchar_t)0x10E1, + (wchar_t)0x10E2, (wchar_t)0x10E3, (wchar_t)0x10E4, (wchar_t)0x10E5, (wchar_t)0x10E6, (wchar_t)0x10E7, (wchar_t)0x10E8, (wchar_t)0x10E9, (wchar_t)0x10EA, + (wchar_t)0x10EB, (wchar_t)0x10EC, (wchar_t)0x10ED, (wchar_t)0x10EE, (wchar_t)0x10EF, (wchar_t)0x10F0, (wchar_t)0x10F1, (wchar_t)0x10F2, (wchar_t)0x10F3, + (wchar_t)0x10F4, (wchar_t)0x10F5, (wchar_t)0x1E01, (wchar_t)0x1E03, (wchar_t)0x1E05, (wchar_t)0x1E07, (wchar_t)0x1E09, (wchar_t)0x1E0B, (wchar_t)0x1E0D, + (wchar_t)0x1E0F, (wchar_t)0x1E11, (wchar_t)0x1E13, (wchar_t)0x1E15, (wchar_t)0x1E17, (wchar_t)0x1E19, (wchar_t)0x1E1B, (wchar_t)0x1E1D, (wchar_t)0x1E1F, + (wchar_t)0x1E21, (wchar_t)0x1E23, (wchar_t)0x1E25, (wchar_t)0x1E27, (wchar_t)0x1E29, (wchar_t)0x1E2B, (wchar_t)0x1E2D, (wchar_t)0x1E2F, (wchar_t)0x1E31, + (wchar_t)0x1E33, (wchar_t)0x1E35, (wchar_t)0x1E37, (wchar_t)0x1E39, (wchar_t)0x1E3B, (wchar_t)0x1E3D, (wchar_t)0x1E3F, (wchar_t)0x1E41, (wchar_t)0x1E43, + (wchar_t)0x1E45, (wchar_t)0x1E47, (wchar_t)0x1E49, (wchar_t)0x1E4B, (wchar_t)0x1E4D, (wchar_t)0x1E4F, (wchar_t)0x1E51, (wchar_t)0x1E53, (wchar_t)0x1E55, + (wchar_t)0x1E57, (wchar_t)0x1E59, (wchar_t)0x1E5B, (wchar_t)0x1E5D, (wchar_t)0x1E5F, (wchar_t)0x1E61, (wchar_t)0x1E63, (wchar_t)0x1E65, (wchar_t)0x1E67, + (wchar_t)0x1E69, (wchar_t)0x1E6B, (wchar_t)0x1E6D, (wchar_t)0x1E6F, (wchar_t)0x1E71, (wchar_t)0x1E73, (wchar_t)0x1E75, (wchar_t)0x1E77, (wchar_t)0x1E79, + (wchar_t)0x1E7B, (wchar_t)0x1E7D, (wchar_t)0x1E7F, (wchar_t)0x1E81, (wchar_t)0x1E83, (wchar_t)0x1E85, (wchar_t)0x1E87, (wchar_t)0x1E89, (wchar_t)0x1E8B, + (wchar_t)0x1E8D, (wchar_t)0x1E8F, (wchar_t)0x1E91, (wchar_t)0x1E93, (wchar_t)0x1E95, (wchar_t)0x1EA1, (wchar_t)0x1EA3, (wchar_t)0x1EA5, (wchar_t)0x1EA7, + (wchar_t)0x1EA9, (wchar_t)0x1EAB, (wchar_t)0x1EAD, (wchar_t)0x1EAF, (wchar_t)0x1EB1, (wchar_t)0x1EB3, (wchar_t)0x1EB5, (wchar_t)0x1EB7, (wchar_t)0x1EB9, + (wchar_t)0x1EBB, (wchar_t)0x1EBD, (wchar_t)0x1EBF, (wchar_t)0x1EC1, (wchar_t)0x1EC3, (wchar_t)0x1EC5, (wchar_t)0x1EC7, (wchar_t)0x1EC9, (wchar_t)0x1ECB, + (wchar_t)0x1ECD, (wchar_t)0x1ECF, (wchar_t)0x1ED1, (wchar_t)0x1ED3, (wchar_t)0x1ED5, (wchar_t)0x1ED7, (wchar_t)0x1ED9, (wchar_t)0x1EDB, (wchar_t)0x1EDD, + (wchar_t)0x1EDF, (wchar_t)0x1EE1, (wchar_t)0x1EE3, (wchar_t)0x1EE5, (wchar_t)0x1EE7, (wchar_t)0x1EE9, (wchar_t)0x1EEB, (wchar_t)0x1EED, (wchar_t)0x1EEF, + (wchar_t)0x1EF1, (wchar_t)0x1EF3, (wchar_t)0x1EF5, (wchar_t)0x1EF7, (wchar_t)0x1EF9, (wchar_t)0x1F00, (wchar_t)0x1F01, (wchar_t)0x1F02, (wchar_t)0x1F03, + (wchar_t)0x1F04, (wchar_t)0x1F05, (wchar_t)0x1F06, (wchar_t)0x1F07, (wchar_t)0x1F10, (wchar_t)0x1F11, (wchar_t)0x1F12, (wchar_t)0x1F13, (wchar_t)0x1F14, + (wchar_t)0x1F15, (wchar_t)0x1F20, (wchar_t)0x1F21, (wchar_t)0x1F22, (wchar_t)0x1F23, (wchar_t)0x1F24, (wchar_t)0x1F25, (wchar_t)0x1F26, (wchar_t)0x1F27, + (wchar_t)0x1F30, (wchar_t)0x1F31, (wchar_t)0x1F32, (wchar_t)0x1F33, (wchar_t)0x1F34, (wchar_t)0x1F35, (wchar_t)0x1F36, (wchar_t)0x1F37, (wchar_t)0x1F40, + (wchar_t)0x1F41, (wchar_t)0x1F42, (wchar_t)0x1F43, (wchar_t)0x1F44, (wchar_t)0x1F45, (wchar_t)0x1F51, (wchar_t)0x1F53, (wchar_t)0x1F55, (wchar_t)0x1F57, + (wchar_t)0x1F60, (wchar_t)0x1F61, (wchar_t)0x1F62, (wchar_t)0x1F63, (wchar_t)0x1F64, (wchar_t)0x1F65, (wchar_t)0x1F66, (wchar_t)0x1F67, (wchar_t)0x1F80, + (wchar_t)0x1F81, (wchar_t)0x1F82, (wchar_t)0x1F83, (wchar_t)0x1F84, (wchar_t)0x1F85, (wchar_t)0x1F86, (wchar_t)0x1F87, (wchar_t)0x1F90, (wchar_t)0x1F91, + (wchar_t)0x1F92, (wchar_t)0x1F93, (wchar_t)0x1F94, (wchar_t)0x1F95, (wchar_t)0x1F96, (wchar_t)0x1F97, (wchar_t)0x1FA0, (wchar_t)0x1FA1, (wchar_t)0x1FA2, + (wchar_t)0x1FA3, (wchar_t)0x1FA4, (wchar_t)0x1FA5, (wchar_t)0x1FA6, (wchar_t)0x1FA7, (wchar_t)0x1FB0, (wchar_t)0x1FB1, (wchar_t)0x1FD0, (wchar_t)0x1FD1, + (wchar_t)0x1FE0, (wchar_t)0x1FE1, (wchar_t)0x24D0, (wchar_t)0x24D1, (wchar_t)0x24D2, (wchar_t)0x24D3, (wchar_t)0x24D4, (wchar_t)0x24D5, (wchar_t)0x24D6, + (wchar_t)0x24D7, (wchar_t)0x24D8, (wchar_t)0x24D9, (wchar_t)0x24DA, (wchar_t)0x24DB, (wchar_t)0x24DC, (wchar_t)0x24DD, (wchar_t)0x24DE, (wchar_t)0x24DF, + (wchar_t)0x24E0, (wchar_t)0x24E1, (wchar_t)0x24E2, (wchar_t)0x24E3, (wchar_t)0x24E4, (wchar_t)0x24E5, (wchar_t)0x24E6, (wchar_t)0x24E7, (wchar_t)0x24E8, + (wchar_t)0x24E9, (wchar_t)0xFF41, (wchar_t)0xFF42, (wchar_t)0xFF43, (wchar_t)0xFF44, (wchar_t)0xFF45, (wchar_t)0xFF46, (wchar_t)0xFF47, (wchar_t)0xFF48, + (wchar_t)0xFF49, (wchar_t)0xFF4A, (wchar_t)0xFF4B, (wchar_t)0xFF4C, (wchar_t)0xFF4D, (wchar_t)0xFF4E, (wchar_t)0xFF4F, (wchar_t)0xFF50, (wchar_t)0xFF51, + (wchar_t)0xFF52, (wchar_t)0xFF53, (wchar_t)0xFF54, (wchar_t)0xFF55, (wchar_t)0xFF56, (wchar_t)0xFF57, (wchar_t)0xFF58, (wchar_t)0xFF59, (wchar_t)0xFF5A +}; + +static const wchar_t unicode_uppers[] = { + (wchar_t)0x0041, (wchar_t)0x0042, (wchar_t)0x0043, (wchar_t)0x0044, (wchar_t)0x0045, (wchar_t)0x0046, (wchar_t)0x0047, (wchar_t)0x0048, (wchar_t)0x0049, + (wchar_t)0x004A, (wchar_t)0x004B, (wchar_t)0x004C, (wchar_t)0x004D, (wchar_t)0x004E, (wchar_t)0x004F, (wchar_t)0x0050, (wchar_t)0x0051, (wchar_t)0x0052, + (wchar_t)0x0053, (wchar_t)0x0054, (wchar_t)0x0055, (wchar_t)0x0056, (wchar_t)0x0057, (wchar_t)0x0058, (wchar_t)0x0059, (wchar_t)0x005A, (wchar_t)0x00C0, + (wchar_t)0x00C1, (wchar_t)0x00C2, (wchar_t)0x00C3, (wchar_t)0x00C4, (wchar_t)0x00C5, (wchar_t)0x00C6, (wchar_t)0x00C7, (wchar_t)0x00C8, (wchar_t)0x00C9, + (wchar_t)0x00CA, (wchar_t)0x00CB, (wchar_t)0x00CC, (wchar_t)0x00CD, (wchar_t)0x00CE, (wchar_t)0x00CF, (wchar_t)0x00D0, (wchar_t)0x00D1, (wchar_t)0x00D2, + (wchar_t)0x00D3, (wchar_t)0x00D4, (wchar_t)0x00D5, (wchar_t)0x00D6, (wchar_t)0x00D8, (wchar_t)0x00D9, (wchar_t)0x00DA, (wchar_t)0x00DB, (wchar_t)0x00DC, + (wchar_t)0x00DD, (wchar_t)0x00DE, (wchar_t)0x0178, (wchar_t)0x0100, (wchar_t)0x0102, (wchar_t)0x0104, (wchar_t)0x0106, (wchar_t)0x0108, (wchar_t)0x010A, + (wchar_t)0x010C, (wchar_t)0x010E, (wchar_t)0x0110, (wchar_t)0x0112, (wchar_t)0x0114, (wchar_t)0x0116, (wchar_t)0x0118, (wchar_t)0x011A, (wchar_t)0x011C, + (wchar_t)0x011E, (wchar_t)0x0120, (wchar_t)0x0122, (wchar_t)0x0124, (wchar_t)0x0126, (wchar_t)0x0128, (wchar_t)0x012A, (wchar_t)0x012C, (wchar_t)0x012E, + (wchar_t)0x0049, (wchar_t)0x0132, (wchar_t)0x0134, (wchar_t)0x0136, (wchar_t)0x0139, (wchar_t)0x013B, (wchar_t)0x013D, (wchar_t)0x013F, (wchar_t)0x0141, + (wchar_t)0x0143, (wchar_t)0x0145, (wchar_t)0x0147, (wchar_t)0x014A, (wchar_t)0x014C, (wchar_t)0x014E, (wchar_t)0x0150, (wchar_t)0x0152, (wchar_t)0x0154, + (wchar_t)0x0156, (wchar_t)0x0158, (wchar_t)0x015A, (wchar_t)0x015C, (wchar_t)0x015E, (wchar_t)0x0160, (wchar_t)0x0162, (wchar_t)0x0164, (wchar_t)0x0166, + (wchar_t)0x0168, (wchar_t)0x016A, (wchar_t)0x016C, (wchar_t)0x016E, (wchar_t)0x0170, (wchar_t)0x0172, (wchar_t)0x0174, (wchar_t)0x0176, (wchar_t)0x0179, + (wchar_t)0x017B, (wchar_t)0x017D, (wchar_t)0x0182, (wchar_t)0x0184, (wchar_t)0x0187, (wchar_t)0x018B, (wchar_t)0x0191, (wchar_t)0x0198, (wchar_t)0x01A0, + (wchar_t)0x01A2, (wchar_t)0x01A4, (wchar_t)0x01A7, (wchar_t)0x01AC, (wchar_t)0x01AF, (wchar_t)0x01B3, (wchar_t)0x01B5, (wchar_t)0x01B8, (wchar_t)0x01BC, + (wchar_t)0x01C4, (wchar_t)0x01C7, (wchar_t)0x01CA, (wchar_t)0x01CD, (wchar_t)0x01CF, (wchar_t)0x01D1, (wchar_t)0x01D3, (wchar_t)0x01D5, (wchar_t)0x01D7, + (wchar_t)0x01D9, (wchar_t)0x01DB, (wchar_t)0x01DE, (wchar_t)0x01E0, (wchar_t)0x01E2, (wchar_t)0x01E4, (wchar_t)0x01E6, (wchar_t)0x01E8, (wchar_t)0x01EA, + (wchar_t)0x01EC, (wchar_t)0x01EE, (wchar_t)0x01F1, (wchar_t)0x01F4, (wchar_t)0x01FA, (wchar_t)0x01FC, (wchar_t)0x01FE, (wchar_t)0x0200, (wchar_t)0x0202, + (wchar_t)0x0204, (wchar_t)0x0206, (wchar_t)0x0208, (wchar_t)0x020A, (wchar_t)0x020C, (wchar_t)0x020E, (wchar_t)0x0210, (wchar_t)0x0212, (wchar_t)0x0214, + (wchar_t)0x0216, (wchar_t)0x0181, (wchar_t)0x0186, (wchar_t)0x018A, (wchar_t)0x018E, (wchar_t)0x018F, (wchar_t)0x0190, (wchar_t)0x0193, (wchar_t)0x0194, + (wchar_t)0x0197, (wchar_t)0x0196, (wchar_t)0x019C, (wchar_t)0x019D, (wchar_t)0x019F, (wchar_t)0x01A9, (wchar_t)0x01AE, (wchar_t)0x01B1, (wchar_t)0x01B2, + (wchar_t)0x01B7, (wchar_t)0x0386, (wchar_t)0x0388, (wchar_t)0x0389, (wchar_t)0x038A, (wchar_t)0x0391, (wchar_t)0x0392, (wchar_t)0x0393, (wchar_t)0x0394, + (wchar_t)0x0395, (wchar_t)0x0396, (wchar_t)0x0397, (wchar_t)0x0398, (wchar_t)0x0399, (wchar_t)0x039A, (wchar_t)0x039B, (wchar_t)0x039C, (wchar_t)0x039D, + (wchar_t)0x039E, (wchar_t)0x039F, (wchar_t)0x03A0, (wchar_t)0x03A1, (wchar_t)0x03A3, (wchar_t)0x03A4, (wchar_t)0x03A5, (wchar_t)0x03A6, (wchar_t)0x03A7, + (wchar_t)0x03A8, (wchar_t)0x03A9, (wchar_t)0x03AA, (wchar_t)0x03AB, (wchar_t)0x038C, (wchar_t)0x038E, (wchar_t)0x038F, (wchar_t)0x03E2, (wchar_t)0x03E4, + (wchar_t)0x03E6, (wchar_t)0x03E8, (wchar_t)0x03EA, (wchar_t)0x03EC, (wchar_t)0x03EE, (wchar_t)0x0410, (wchar_t)0x0411, (wchar_t)0x0412, (wchar_t)0x0413, + (wchar_t)0x0414, (wchar_t)0x0415, (wchar_t)0x0416, (wchar_t)0x0417, (wchar_t)0x0418, (wchar_t)0x0419, (wchar_t)0x041A, (wchar_t)0x041B, (wchar_t)0x041C, + (wchar_t)0x041D, (wchar_t)0x041E, (wchar_t)0x041F, (wchar_t)0x0420, (wchar_t)0x0421, (wchar_t)0x0422, (wchar_t)0x0423, (wchar_t)0x0424, (wchar_t)0x0425, + (wchar_t)0x0426, (wchar_t)0x0427, (wchar_t)0x0428, (wchar_t)0x0429, (wchar_t)0x042A, (wchar_t)0x042B, (wchar_t)0x042C, (wchar_t)0x042D, (wchar_t)0x042E, + (wchar_t)0x042F, (wchar_t)0x0401, (wchar_t)0x0402, (wchar_t)0x0403, (wchar_t)0x0404, (wchar_t)0x0405, (wchar_t)0x0406, (wchar_t)0x0407, (wchar_t)0x0408, + (wchar_t)0x0409, (wchar_t)0x040A, (wchar_t)0x040B, (wchar_t)0x040C, (wchar_t)0x040E, (wchar_t)0x040F, (wchar_t)0x0460, (wchar_t)0x0462, (wchar_t)0x0464, + (wchar_t)0x0466, (wchar_t)0x0468, (wchar_t)0x046A, (wchar_t)0x046C, (wchar_t)0x046E, (wchar_t)0x0470, (wchar_t)0x0472, (wchar_t)0x0474, (wchar_t)0x0476, + (wchar_t)0x0478, (wchar_t)0x047A, (wchar_t)0x047C, (wchar_t)0x047E, (wchar_t)0x0480, (wchar_t)0x0490, (wchar_t)0x0492, (wchar_t)0x0494, (wchar_t)0x0496, + (wchar_t)0x0498, (wchar_t)0x049A, (wchar_t)0x049C, (wchar_t)0x049E, (wchar_t)0x04A0, (wchar_t)0x04A2, (wchar_t)0x04A4, (wchar_t)0x04A6, (wchar_t)0x04A8, + (wchar_t)0x04AA, (wchar_t)0x04AC, (wchar_t)0x04AE, (wchar_t)0x04B0, (wchar_t)0x04B2, (wchar_t)0x04B4, (wchar_t)0x04B6, (wchar_t)0x04B8, (wchar_t)0x04BA, + (wchar_t)0x04BC, (wchar_t)0x04BE, (wchar_t)0x04C1, (wchar_t)0x04C3, (wchar_t)0x04C7, (wchar_t)0x04CB, (wchar_t)0x04D0, (wchar_t)0x04D2, (wchar_t)0x04D4, + (wchar_t)0x04D6, (wchar_t)0x04D8, (wchar_t)0x04DA, (wchar_t)0x04DC, (wchar_t)0x04DE, (wchar_t)0x04E0, (wchar_t)0x04E2, (wchar_t)0x04E4, (wchar_t)0x04E6, + (wchar_t)0x04E8, (wchar_t)0x04EA, (wchar_t)0x04EE, (wchar_t)0x04F0, (wchar_t)0x04F2, (wchar_t)0x04F4, (wchar_t)0x04F8, (wchar_t)0x0531, (wchar_t)0x0532, + (wchar_t)0x0533, (wchar_t)0x0534, (wchar_t)0x0535, (wchar_t)0x0536, (wchar_t)0x0537, (wchar_t)0x0538, (wchar_t)0x0539, (wchar_t)0x053A, (wchar_t)0x053B, + (wchar_t)0x053C, (wchar_t)0x053D, (wchar_t)0x053E, (wchar_t)0x053F, (wchar_t)0x0540, (wchar_t)0x0541, (wchar_t)0x0542, (wchar_t)0x0543, (wchar_t)0x0544, + (wchar_t)0x0545, (wchar_t)0x0546, (wchar_t)0x0547, (wchar_t)0x0548, (wchar_t)0x0549, (wchar_t)0x054A, (wchar_t)0x054B, (wchar_t)0x054C, (wchar_t)0x054D, + (wchar_t)0x054E, (wchar_t)0x054F, (wchar_t)0x0550, (wchar_t)0x0551, (wchar_t)0x0552, (wchar_t)0x0553, (wchar_t)0x0554, (wchar_t)0x0555, (wchar_t)0x0556, + (wchar_t)0x10A0, (wchar_t)0x10A1, (wchar_t)0x10A2, (wchar_t)0x10A3, (wchar_t)0x10A4, (wchar_t)0x10A5, (wchar_t)0x10A6, (wchar_t)0x10A7, (wchar_t)0x10A8, + (wchar_t)0x10A9, (wchar_t)0x10AA, (wchar_t)0x10AB, (wchar_t)0x10AC, (wchar_t)0x10AD, (wchar_t)0x10AE, (wchar_t)0x10AF, (wchar_t)0x10B0, (wchar_t)0x10B1, + (wchar_t)0x10B2, (wchar_t)0x10B3, (wchar_t)0x10B4, (wchar_t)0x10B5, (wchar_t)0x10B6, (wchar_t)0x10B7, (wchar_t)0x10B8, (wchar_t)0x10B9, (wchar_t)0x10BA, + (wchar_t)0x10BB, (wchar_t)0x10BC, (wchar_t)0x10BD, (wchar_t)0x10BE, (wchar_t)0x10BF, (wchar_t)0x10C0, (wchar_t)0x10C1, (wchar_t)0x10C2, (wchar_t)0x10C3, + (wchar_t)0x10C4, (wchar_t)0x10C5, (wchar_t)0x1E00, (wchar_t)0x1E02, (wchar_t)0x1E04, (wchar_t)0x1E06, (wchar_t)0x1E08, (wchar_t)0x1E0A, (wchar_t)0x1E0C, + (wchar_t)0x1E0E, (wchar_t)0x1E10, (wchar_t)0x1E12, (wchar_t)0x1E14, (wchar_t)0x1E16, (wchar_t)0x1E18, (wchar_t)0x1E1A, (wchar_t)0x1E1C, (wchar_t)0x1E1E, + (wchar_t)0x1E20, (wchar_t)0x1E22, (wchar_t)0x1E24, (wchar_t)0x1E26, (wchar_t)0x1E28, (wchar_t)0x1E2A, (wchar_t)0x1E2C, (wchar_t)0x1E2E, (wchar_t)0x1E30, + (wchar_t)0x1E32, (wchar_t)0x1E34, (wchar_t)0x1E36, (wchar_t)0x1E38, (wchar_t)0x1E3A, (wchar_t)0x1E3C, (wchar_t)0x1E3E, (wchar_t)0x1E40, (wchar_t)0x1E42, + (wchar_t)0x1E44, (wchar_t)0x1E46, (wchar_t)0x1E48, (wchar_t)0x1E4A, (wchar_t)0x1E4C, (wchar_t)0x1E4E, (wchar_t)0x1E50, (wchar_t)0x1E52, (wchar_t)0x1E54, + (wchar_t)0x1E56, (wchar_t)0x1E58, (wchar_t)0x1E5A, (wchar_t)0x1E5C, (wchar_t)0x1E5E, (wchar_t)0x1E60, (wchar_t)0x1E62, (wchar_t)0x1E64, (wchar_t)0x1E66, + (wchar_t)0x1E68, (wchar_t)0x1E6A, (wchar_t)0x1E6C, (wchar_t)0x1E6E, (wchar_t)0x1E70, (wchar_t)0x1E72, (wchar_t)0x1E74, (wchar_t)0x1E76, (wchar_t)0x1E78, + (wchar_t)0x1E7A, (wchar_t)0x1E7C, (wchar_t)0x1E7E, (wchar_t)0x1E80, (wchar_t)0x1E82, (wchar_t)0x1E84, (wchar_t)0x1E86, (wchar_t)0x1E88, (wchar_t)0x1E8A, + (wchar_t)0x1E8C, (wchar_t)0x1E8E, (wchar_t)0x1E90, (wchar_t)0x1E92, (wchar_t)0x1E94, (wchar_t)0x1EA0, (wchar_t)0x1EA2, (wchar_t)0x1EA4, (wchar_t)0x1EA6, + (wchar_t)0x1EA8, (wchar_t)0x1EAA, (wchar_t)0x1EAC, (wchar_t)0x1EAE, (wchar_t)0x1EB0, (wchar_t)0x1EB2, (wchar_t)0x1EB4, (wchar_t)0x1EB6, (wchar_t)0x1EB8, + (wchar_t)0x1EBA, (wchar_t)0x1EBC, (wchar_t)0x1EBE, (wchar_t)0x1EC0, (wchar_t)0x1EC2, (wchar_t)0x1EC4, (wchar_t)0x1EC6, (wchar_t)0x1EC8, (wchar_t)0x1ECA, + (wchar_t)0x1ECC, (wchar_t)0x1ECE, (wchar_t)0x1ED0, (wchar_t)0x1ED2, (wchar_t)0x1ED4, (wchar_t)0x1ED6, (wchar_t)0x1ED8, (wchar_t)0x1EDA, (wchar_t)0x1EDC, + (wchar_t)0x1EDE, (wchar_t)0x1EE0, (wchar_t)0x1EE2, (wchar_t)0x1EE4, (wchar_t)0x1EE6, (wchar_t)0x1EE8, (wchar_t)0x1EEA, (wchar_t)0x1EEC, (wchar_t)0x1EEE, + (wchar_t)0x1EF0, (wchar_t)0x1EF2, (wchar_t)0x1EF4, (wchar_t)0x1EF6, (wchar_t)0x1EF8, (wchar_t)0x1F08, (wchar_t)0x1F09, (wchar_t)0x1F0A, (wchar_t)0x1F0B, + (wchar_t)0x1F0C, (wchar_t)0x1F0D, (wchar_t)0x1F0E, (wchar_t)0x1F0F, (wchar_t)0x1F18, (wchar_t)0x1F19, (wchar_t)0x1F1A, (wchar_t)0x1F1B, (wchar_t)0x1F1C, + (wchar_t)0x1F1D, (wchar_t)0x1F28, (wchar_t)0x1F29, (wchar_t)0x1F2A, (wchar_t)0x1F2B, (wchar_t)0x1F2C, (wchar_t)0x1F2D, (wchar_t)0x1F2E, (wchar_t)0x1F2F, + (wchar_t)0x1F38, (wchar_t)0x1F39, (wchar_t)0x1F3A, (wchar_t)0x1F3B, (wchar_t)0x1F3C, (wchar_t)0x1F3D, (wchar_t)0x1F3E, (wchar_t)0x1F3F, (wchar_t)0x1F48, + (wchar_t)0x1F49, (wchar_t)0x1F4A, (wchar_t)0x1F4B, (wchar_t)0x1F4C, (wchar_t)0x1F4D, (wchar_t)0x1F59, (wchar_t)0x1F5B, (wchar_t)0x1F5D, (wchar_t)0x1F5F, + (wchar_t)0x1F68, (wchar_t)0x1F69, (wchar_t)0x1F6A, (wchar_t)0x1F6B, (wchar_t)0x1F6C, (wchar_t)0x1F6D, (wchar_t)0x1F6E, (wchar_t)0x1F6F, (wchar_t)0x1F88, + (wchar_t)0x1F89, (wchar_t)0x1F8A, (wchar_t)0x1F8B, (wchar_t)0x1F8C, (wchar_t)0x1F8D, (wchar_t)0x1F8E, (wchar_t)0x1F8F, (wchar_t)0x1F98, (wchar_t)0x1F99, + (wchar_t)0x1F9A, (wchar_t)0x1F9B, (wchar_t)0x1F9C, (wchar_t)0x1F9D, (wchar_t)0x1F9E, (wchar_t)0x1F9F, (wchar_t)0x1FA8, (wchar_t)0x1FA9, (wchar_t)0x1FAA, + (wchar_t)0x1FAB, (wchar_t)0x1FAC, (wchar_t)0x1FAD, (wchar_t)0x1FAE, (wchar_t)0x1FAF, (wchar_t)0x1FB8, (wchar_t)0x1FB9, (wchar_t)0x1FD8, (wchar_t)0x1FD9, + (wchar_t)0x1FE8, (wchar_t)0x1FE9, (wchar_t)0x24B6, (wchar_t)0x24B7, (wchar_t)0x24B8, (wchar_t)0x24B9, (wchar_t)0x24BA, (wchar_t)0x24BB, (wchar_t)0x24BC, + (wchar_t)0x24BD, (wchar_t)0x24BE, (wchar_t)0x24BF, (wchar_t)0x24C0, (wchar_t)0x24C1, (wchar_t)0x24C2, (wchar_t)0x24C3, (wchar_t)0x24C4, (wchar_t)0x24C5, + (wchar_t)0x24C6, (wchar_t)0x24C7, (wchar_t)0x24C8, (wchar_t)0x24C9, (wchar_t)0x24CA, (wchar_t)0x24CB, (wchar_t)0x24CC, (wchar_t)0x24CD, (wchar_t)0x24CE, + (wchar_t)0x24CF, (wchar_t)0xFF21, (wchar_t)0xFF22, (wchar_t)0xFF23, (wchar_t)0xFF24, (wchar_t)0xFF25, (wchar_t)0xFF26, (wchar_t)0xFF27, (wchar_t)0xFF28, + (wchar_t)0xFF29, (wchar_t)0xFF2A, (wchar_t)0xFF2B, (wchar_t)0xFF2C, (wchar_t)0xFF2D, (wchar_t)0xFF2E, (wchar_t)0xFF2F, (wchar_t)0xFF30, (wchar_t)0xFF31, + (wchar_t)0xFF32, (wchar_t)0xFF33, (wchar_t)0xFF34, (wchar_t)0xFF35, (wchar_t)0xFF36, (wchar_t)0xFF37, (wchar_t)0xFF38, (wchar_t)0xFF39, (wchar_t)0xFF3A +}; + +string StringUtils::Format(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + string str = FormatV(fmt, args); + va_end(args); + + return str; +} + +string StringUtils::FormatV(const char *fmt, va_list args) +{ + if (!fmt || !fmt[0]) + return ""; + + int size = FORMAT_BLOCK_SIZE; + va_list argCopy; + + while (1) + { + char *cstr = reinterpret_cast<char*>(malloc(sizeof(char) * size)); + if (!cstr) + return ""; + + va_copy(argCopy, args); + int nActual = vsnprintf(cstr, size, fmt, argCopy); + va_end(argCopy); + + if (nActual > -1 && nActual < size) // We got a valid result + { + std::string str(cstr, nActual); + free(cstr); + return str; + } + free(cstr); +#ifndef TARGET_WINDOWS + if (nActual > -1) // Exactly what we will need (glibc 2.1) + size = nActual + 1; + else // Let's try to double the size (glibc 2.0) + size *= 2; +#else // TARGET_WINDOWS + va_copy(argCopy, args); + size = _vscprintf(fmt, argCopy); + va_end(argCopy); + if (size < 0) + return ""; + else + size++; // increment for null-termination +#endif // TARGET_WINDOWS + } + + return ""; // unreachable +} + +wstring StringUtils::Format(const wchar_t *fmt, ...) +{ + va_list args; + va_start(args, fmt); + wstring str = FormatV(fmt, args); + va_end(args); + + return str; +} + +wstring StringUtils::FormatV(const wchar_t *fmt, va_list args) +{ + if (!fmt || !fmt[0]) + return L""; + + int size = FORMAT_BLOCK_SIZE; + va_list argCopy; + + while (1) + { + wchar_t *cstr = reinterpret_cast<wchar_t*>(malloc(sizeof(wchar_t) * size)); + if (!cstr) + return L""; + + va_copy(argCopy, args); + int nActual = vswprintf(cstr, size, fmt, argCopy); + va_end(argCopy); + + if (nActual > -1 && nActual < size) // We got a valid result + { + std::wstring str(cstr, nActual); + free(cstr); + return str; + } + free(cstr); + +#ifndef TARGET_WINDOWS + if (nActual > -1) // Exactly what we will need (glibc 2.1) + size = nActual + 1; + else // Let's try to double the size (glibc 2.0) + size *= 2; +#else // TARGET_WINDOWS + va_copy(argCopy, args); + size = _vscwprintf(fmt, argCopy); + va_end(argCopy); + if (size < 0) + return L""; + else + size++; // increment for null-termination +#endif // TARGET_WINDOWS + } + + return L""; +} + +int compareWchar (const void* a, const void* b) +{ + if (*(wchar_t*)a < *(wchar_t*)b) + return -1; + else if (*(wchar_t*)a > *(wchar_t*)b) + return 1; + return 0; +} + +wchar_t tolowerUnicode(const wchar_t& c) +{ + wchar_t* p = (wchar_t*) bsearch (&c, unicode_uppers, sizeof(unicode_uppers) / sizeof(wchar_t), sizeof(wchar_t), compareWchar); + if (p) + return *(unicode_lowers + (p - unicode_uppers)); + + return c; +} + +wchar_t toupperUnicode(const wchar_t& c) +{ + wchar_t* p = (wchar_t*) bsearch (&c, unicode_lowers, sizeof(unicode_lowers) / sizeof(wchar_t), sizeof(wchar_t), compareWchar); + if (p) + return *(unicode_uppers + (p - unicode_lowers)); + + return c; +} + +void StringUtils::ToUpper(string &str) +{ + transform(str.begin(), str.end(), str.begin(), ::toupper); +} + +void StringUtils::ToUpper(wstring &str) +{ + transform(str.begin(), str.end(), str.begin(), toupperUnicode); +} + +void StringUtils::ToLower(string &str) +{ + transform(str.begin(), str.end(), str.begin(), ::tolower); +} + +void StringUtils::ToLower(wstring &str) +{ + transform(str.begin(), str.end(), str.begin(), tolowerUnicode); +} + +bool StringUtils::EqualsNoCase(const std::string &str1, const std::string &str2) +{ + return EqualsNoCase(str1.c_str(), str2.c_str()); +} + +bool StringUtils::EqualsNoCase(const std::string &str1, const char *s2) +{ + return EqualsNoCase(str1.c_str(), s2); +} + +bool StringUtils::EqualsNoCase(const char *s1, const char *s2) +{ + char c2; // we need only one char outside the loop + do + { + const char c1 = *s1++; // const local variable should help compiler to optimize + c2 = *s2++; + if (c1 != c2 && ::tolower(c1) != ::tolower(c2)) // This includes the possibility that one of the characters is the null-terminator, which implies a string mismatch. + return false; + } while (c2 != '\0'); // At this point, we know c1 == c2, so there's no need to test them both. + return true; +} + +int StringUtils::CompareNoCase(const std::string &str1, const std::string &str2) +{ + return CompareNoCase(str1.c_str(), str2.c_str()); +} + +int StringUtils::CompareNoCase(const char *s1, const char *s2) +{ + char c2; // we need only one char outside the loop + do + { + const char c1 = *s1++; // const local variable should help compiler to optimize + c2 = *s2++; + if (c1 != c2 && ::tolower(c1) != ::tolower(c2)) // This includes the possibility that one of the characters is the null-terminator, which implies a string mismatch. + return ::tolower(c1) - ::tolower(c2); + } while (c2 != '\0'); // At this point, we know c1 == c2, so there's no need to test them both. + return 0; +} + +string StringUtils::Left(const string &str, size_t count) +{ + count = max((size_t)0, min(count, str.size())); + return str.substr(0, count); +} + +string StringUtils::Mid(const string &str, size_t first, size_t count /* = string::npos */) +{ + if (first + count > str.size()) + count = str.size() - first; + + if (first > str.size()) + return string(); + + ASSERT(first + count <= str.size()); + + return str.substr(first, count); +} + +string StringUtils::Right(const string &str, size_t count) +{ + count = max((size_t)0, min(count, str.size())); + return str.substr(str.size() - count); +} + +std::string& StringUtils::Trim(std::string &str) +{ + TrimLeft(str); + return TrimRight(str); +} + +std::string& StringUtils::Trim(std::string &str, const char* const chars) +{ + TrimLeft(str, chars); + return TrimRight(str, chars); +} + +// hack to check only first byte of UTF-8 character +// without this hack "TrimX" functions failed on Win32 and OS X with UTF-8 strings +static int isspace_c(char c) +{ + return (c & 0x80) == 0 && ::isspace(c); +} + +std::string& StringUtils::TrimLeft(std::string &str) +{ + str.erase(str.begin(), ::find_if(str.begin(), str.end(), ::not1(::ptr_fun(isspace_c)))); + return str; +} + +std::string& StringUtils::TrimLeft(std::string &str, const char* const chars) +{ + size_t nidx = str.find_first_not_of(chars); + str.erase(0, nidx); + return str; +} + +std::string& StringUtils::TrimRight(std::string &str) +{ + str.erase(::find_if(str.rbegin(), str.rend(), ::not1(::ptr_fun(isspace_c))).base(), str.end()); + return str; +} + +std::string& StringUtils::TrimRight(std::string &str, const char* const chars) +{ + size_t nidx = str.find_last_not_of(chars); + str.erase(str.npos == nidx ? 0 : ++nidx); + return str; +} + +std::string& StringUtils::RemoveDuplicatedSpacesAndTabs(std::string& str) +{ + std::string::iterator it = str.begin(); + bool onSpace = false; + while(it != str.end()) + { + if (*it == '\t') + *it = ' '; + + if (*it == ' ') + { + if (onSpace) + { + it = str.erase(it); + continue; + } + else + onSpace = true; + } + else + onSpace = false; + + ++it; + } + return str; +} + +int StringUtils::Replace(string &str, char oldChar, char newChar) +{ + int replacedChars = 0; + for (string::iterator it = str.begin(); it != str.end(); ++it) + { + if (*it == oldChar) + { + *it = newChar; + replacedChars++; + } + } + + return replacedChars; +} + +int StringUtils::Replace(std::string &str, const std::string &oldStr, const std::string &newStr) +{ + if (oldStr.empty()) + return 0; + + int replacedChars = 0; + size_t index = 0; + + while (index < str.size() && (index = str.find(oldStr, index)) != string::npos) + { + str.replace(index, oldStr.size(), newStr); + index += newStr.size(); + replacedChars++; + } + + return replacedChars; +} + +int StringUtils::Replace(std::wstring &str, const std::wstring &oldStr, const std::wstring &newStr) +{ + if (oldStr.empty()) + return 0; + + int replacedChars = 0; + size_t index = 0; + + while (index < str.size() && (index = str.find(oldStr, index)) != string::npos) + { + str.replace(index, oldStr.size(), newStr); + index += newStr.size(); + replacedChars++; + } + + return replacedChars; +} + +bool StringUtils::StartsWith(const std::string &str1, const std::string &str2) +{ + return str1.compare(0, str2.size(), str2) == 0; +} + +bool StringUtils::StartsWith(const std::string &str1, const char *s2) +{ + return StartsWith(str1.c_str(), s2); +} + +bool StringUtils::StartsWith(const char *s1, const char *s2) +{ + while (*s2 != '\0') + { + if (*s1 != *s2) + return false; + s1++; + s2++; + } + return true; +} + +bool StringUtils::StartsWithNoCase(const std::string &str1, const std::string &str2) +{ + return StartsWithNoCase(str1.c_str(), str2.c_str()); +} + +bool StringUtils::StartsWithNoCase(const std::string &str1, const char *s2) +{ + return StartsWithNoCase(str1.c_str(), s2); +} + +bool StringUtils::StartsWithNoCase(const char *s1, const char *s2) +{ + while (*s2 != '\0') + { + if (::tolower(*s1) != ::tolower(*s2)) + return false; + s1++; + s2++; + } + return true; +} + +bool StringUtils::EndsWith(const std::string &str1, const std::string &str2) +{ + if (str1.size() < str2.size()) + return false; + return str1.compare(str1.size() - str2.size(), str2.size(), str2) == 0; +} + +bool StringUtils::EndsWith(const std::string &str1, const char *s2) +{ + size_t len2 = strlen(s2); + if (str1.size() < len2) + return false; + return str1.compare(str1.size() - len2, len2, s2) == 0; +} + +bool StringUtils::EndsWithNoCase(const std::string &str1, const std::string &str2) +{ + if (str1.size() < str2.size()) + return false; + const char *s1 = str1.c_str() + str1.size() - str2.size(); + const char *s2 = str2.c_str(); + while (*s2 != '\0') + { + if (::tolower(*s1) != ::tolower(*s2)) + return false; + s1++; + s2++; + } + return true; +} + +bool StringUtils::EndsWithNoCase(const std::string &str1, const char *s2) +{ + size_t len2 = strlen(s2); + if (str1.size() < len2) + return false; + const char *s1 = str1.c_str() + str1.size() - len2; + while (*s2 != '\0') + { + if (::tolower(*s1) != ::tolower(*s2)) + return false; + s1++; + s2++; + } + return true; +} + +std::string StringUtils::Join(const vector<string> &strings, const std::string& delimiter) +{ + std::string result; + for(vector<string>::const_iterator it = strings.begin(); it != strings.end(); it++ ) + result += (*it) + delimiter; + + if (!result.empty()) + result.erase(result.size() - delimiter.size()); + return result; +} + +vector<string> StringUtils::Split(const std::string& input, const std::string& delimiter, unsigned int iMaxStrings /* = 0 */) +{ + std::vector<std::string> results; + if (input.empty()) + return results; + if (delimiter.empty()) + { + results.push_back(input); + return results; + } + + const size_t delimLen = delimiter.length(); + size_t nextDelim; + size_t textPos = 0; + do + { + if (--iMaxStrings == 0) + { + results.push_back(input.substr(textPos)); + break; + } + nextDelim = input.find(delimiter, textPos); + results.push_back(input.substr(textPos, nextDelim - textPos)); + textPos = nextDelim + delimLen; + } while (nextDelim != std::string::npos); + + return results; +} + +std::vector<std::string> StringUtils::Split(const std::string& input, const char delimiter, size_t iMaxStrings /*= 0*/) +{ + std::vector<std::string> results; + if (input.empty()) + return results; + + size_t nextDelim; + size_t textPos = 0; + do + { + if (--iMaxStrings == 0) + { + results.push_back(input.substr(textPos)); + break; + } + nextDelim = input.find(delimiter, textPos); + results.push_back(input.substr(textPos, nextDelim - textPos)); + textPos = nextDelim + 1; + } while (nextDelim != std::string::npos); + + return results; +} + + +// returns the number of occurrences of strFind in strInput. +int StringUtils::FindNumber(const CStdString& strInput, const CStdString &strFind) +{ + size_t pos = strInput.find(strFind, 0); + int numfound = 0; + while (pos != std::string::npos) + { + numfound++; + pos = strInput.find(strFind, pos + 1); + } + return numfound; +} + +// Compares separately the numeric and alphabetic parts of a string. +// returns negative if left < right, positive if left > right +// and 0 if they are identical (essentially calculates left - right) +int64_t StringUtils::AlphaNumericCompare(const wchar_t *left, const wchar_t *right) +{ + wchar_t *l = (wchar_t *)left; + wchar_t *r = (wchar_t *)right; + wchar_t *ld, *rd; + wchar_t lc, rc; + int64_t lnum, rnum; + const collate<wchar_t>& coll = use_facet< collate<wchar_t> >( locale() ); + int cmp_res = 0; + while (*l != 0 && *r != 0) + { + // check if we have a numerical value + if (*l >= L'0' && *l <= L'9' && *r >= L'0' && *r <= L'9') + { + ld = l; + lnum = 0; + while (*ld >= L'0' && *ld <= L'9' && ld < l + 15) + { // compare only up to 15 digits + lnum *= 10; + lnum += *ld++ - '0'; + } + rd = r; + rnum = 0; + while (*rd >= L'0' && *rd <= L'9' && rd < r + 15) + { // compare only up to 15 digits + rnum *= 10; + rnum += *rd++ - L'0'; + } + // do we have numbers? + if (lnum != rnum) + { // yes - and they're different! + return lnum - rnum; + } + l = ld; + r = rd; + continue; + } + // do case less comparison + lc = *l; + if (lc >= L'A' && lc <= L'Z') + lc += L'a'-L'A'; + rc = *r; + if (rc >= L'A' && rc <= L'Z') + rc += L'a'- L'A'; + + // ok, do a normal comparison, taking current locale into account. Add special case stuff (eg '(' characters)) in here later + if ((cmp_res = coll.compare(&lc, &lc + 1, &rc, &rc + 1)) != 0) + { + return cmp_res; + } + l++; r++; + } + if (*r) + { // r is longer + return -1; + } + else if (*l) + { // l is longer + return 1; + } + return 0; // files are the same +} + +int StringUtils::DateStringToYYYYMMDD(const CStdString &dateString) +{ + vector<string> days = StringUtils::Split(dateString, '-'); + if (days.size() == 1) + return atoi(days[0].c_str()); + else if (days.size() == 2) + return atoi(days[0].c_str())*100+atoi(days[1].c_str()); + else if (days.size() == 3) + return atoi(days[0].c_str())*10000+atoi(days[1].c_str())*100+atoi(days[2].c_str()); + else + return -1; +} + +long StringUtils::TimeStringToSeconds(const CStdString &timeString) +{ + CStdString strCopy(timeString); + StringUtils::Trim(strCopy); + if(StringUtils::EndsWithNoCase(strCopy, " min")) + { + // this is imdb format of "XXX min" + return 60 * atoi(strCopy.c_str()); + } + else + { + vector<string> secs = StringUtils::Split(strCopy, ':'); + int timeInSecs = 0; + for (unsigned int i = 0; i < 3 && i < secs.size(); i++) + { + timeInSecs *= 60; + timeInSecs += atoi(secs[i].c_str()); + } + return timeInSecs; + } +} + +CStdString StringUtils::SecondsToTimeString(long lSeconds, TIME_FORMAT format) +{ + int hh = lSeconds / 3600; + lSeconds = lSeconds % 3600; + int mm = lSeconds / 60; + int ss = lSeconds % 60; + + if (format == TIME_FORMAT_GUESS) + format = (hh >= 1) ? TIME_FORMAT_HH_MM_SS : TIME_FORMAT_MM_SS; + CStdString strHMS; + if (format & TIME_FORMAT_HH) + strHMS += StringUtils::Format("%02.2i", hh); + else if (format & TIME_FORMAT_H) + strHMS += StringUtils::Format("%i", hh); + if (format & TIME_FORMAT_MM) + strHMS += StringUtils::Format(strHMS.empty() ? "%02.2i" : ":%02.2i", mm); + if (format & TIME_FORMAT_SS) + strHMS += StringUtils::Format(strHMS.empty() ? "%02.2i" : ":%02.2i", ss); + return strHMS; +} + +bool StringUtils::IsNaturalNumber(const CStdString& str) +{ + size_t i = 0, n = 0; + // allow whitespace,digits,whitespace + while (i < str.size() && isspace((unsigned char) str[i])) + i++; + while (i < str.size() && isdigit((unsigned char) str[i])) + { + i++; n++; + } + while (i < str.size() && isspace((unsigned char) str[i])) + i++; + return i == str.size() && n > 0; +} + +bool StringUtils::IsInteger(const CStdString& str) +{ + size_t i = 0, n = 0; + // allow whitespace,-,digits,whitespace + while (i < str.size() && isspace((unsigned char) str[i])) + i++; + if (i < str.size() && str[i] == '-') + i++; + while (i < str.size() && isdigit((unsigned char) str[i])) + { + i++; n++; + } + while (i < str.size() && isspace((unsigned char) str[i])) + i++; + return i == str.size() && n > 0; +} + +int StringUtils::asciidigitvalue(char chr) +{ + if (!isasciidigit(chr)) + return -1; + + return chr - '0'; +} + +int StringUtils::asciixdigitvalue(char chr) +{ + int v = asciidigitvalue(chr); + if (v >= 0) + return v; + if (chr >= 'a' && chr <= 'f') + return chr - 'a' + 10; + if (chr >= 'A' && chr <= 'F') + return chr - 'A' + 10; + + return -1; +} + + +void StringUtils::RemoveCRLF(std::string& strLine) +{ + StringUtils::TrimRight(strLine, "\n\r"); +} + +CStdString StringUtils::SizeToString(int64_t size) +{ + CStdString strLabel; + const char prefixes[] = {' ','k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'}; + unsigned int i = 0; + double s = (double)size; + while (i < ARRAY_SIZE(prefixes) && s >= 1000.0) + { + s /= 1024.0; + i++; + } + + if (!i) + strLabel = StringUtils::Format("%.0lf %cB ", s, prefixes[i]); + else if (s >= 100.0) + strLabel = StringUtils::Format("%.1lf %cB", s, prefixes[i]); + else + strLabel = StringUtils::Format("%.2lf %cB", s, prefixes[i]); + + return strLabel; +} + +// return -1 if not, else return the utf8 char length. +int IsUTF8Letter(const unsigned char *str) +{ + // reference: + // unicode -> utf8 table: http://www.utf8-chartable.de/ + // latin characters in unicode: http://en.wikipedia.org/wiki/Latin_characters_in_Unicode + unsigned char ch = str[0]; + if (!ch) + return -1; + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) + return 1; + if (!(ch & 0x80)) + return -1; + unsigned char ch2 = str[1]; + if (!ch2) + return -1; + // check latin 1 letter table: http://en.wikipedia.org/wiki/C1_Controls_and_Latin-1_Supplement + if (ch == 0xC3 && ch2 >= 0x80 && ch2 <= 0xBF && ch2 != 0x97 && ch2 != 0xB7) + return 2; + // check latin extended A table: http://en.wikipedia.org/wiki/Latin_Extended-A + if (ch >= 0xC4 && ch <= 0xC7 && ch2 >= 0x80 && ch2 <= 0xBF) + return 2; + // check latin extended B table: http://en.wikipedia.org/wiki/Latin_Extended-B + // and International Phonetic Alphabet: http://en.wikipedia.org/wiki/IPA_Extensions_(Unicode_block) + if (((ch == 0xC8 || ch == 0xC9) && ch2 >= 0x80 && ch2 <= 0xBF) + || (ch == 0xCA && ch2 >= 0x80 && ch2 <= 0xAF)) + return 2; + return -1; +} + +size_t StringUtils::FindWords(const char *str, const char *wordLowerCase) +{ + // NOTE: This assumes word is lowercase! + unsigned char *s = (unsigned char *)str; + do + { + // start with a compare + unsigned char *c = s; + unsigned char *w = (unsigned char *)wordLowerCase; + bool same = true; + while (same && *c && *w) + { + unsigned char lc = *c++; + if (lc >= 'A' && lc <= 'Z') + lc += 'a'-'A'; + + if (lc != *w++) // different + same = false; + } + if (same && *w == 0) // only the same if word has been exhausted + return (const char *)s - str; + + // otherwise, skip current word (composed by latin letters) or number + int l; + if (*s >= '0' && *s <= '9') + { + ++s; + while (*s >= '0' && *s <= '9') ++s; + } + else if ((l = IsUTF8Letter(s)) > 0) + { + s += l; + while ((l = IsUTF8Letter(s)) > 0) s += l; + } + else + ++s; + while (*s && *s == ' ') s++; + + // and repeat until we're done + } while (*s); + + return CStdString::npos; +} + +// assumes it is called from after the first open bracket is found +int StringUtils::FindEndBracket(const CStdString &str, char opener, char closer, int startPos) +{ + int blocks = 1; + for (unsigned int i = startPos; i < str.size(); i++) + { + if (str[i] == opener) + blocks++; + else if (str[i] == closer) + { + blocks--; + if (!blocks) + return i; + } + } + + return (int)CStdString::npos; +} + +void StringUtils::WordToDigits(std::string &word) +{ + static const char word_to_letter[] = "22233344455566677778889999"; + StringUtils::ToLower(word); + for (unsigned int i = 0; i < word.size(); ++i) + { // NB: This assumes ascii, which probably needs extending at some point. + char letter = word[i]; + if ((letter >= 'a' && letter <= 'z')) // assume contiguous letter range + { + word[i] = word_to_letter[letter-'a']; + } + else if (letter < '0' || letter > '9') // We want to keep 0-9! + { + word[i] = ' '; // replace everything else with a space + } + } +} + +CStdString StringUtils::CreateUUID() +{ + /* This function generate a DCE 1.1, ISO/IEC 11578:1996 and IETF RFC-4122 + * Version 4 conform local unique UUID based upon random number generation. + */ + char UuidStrTmp[40]; + char *pUuidStr = UuidStrTmp; + int i; + + static bool m_uuidInitialized = false; + if (!m_uuidInitialized) + { + /* use current time as the seed for rand()*/ + srand(time(NULL)); + m_uuidInitialized = true; + } + + /*Data1 - 8 characters.*/ + for(i = 0; i < 8; i++, pUuidStr++) + ((*pUuidStr = (rand() % 16)) < 10) ? *pUuidStr += 48 : *pUuidStr += 55; + + /*Data2 - 4 characters.*/ + *pUuidStr++ = '-'; + for(i = 0; i < 4; i++, pUuidStr++) + ((*pUuidStr = (rand() % 16)) < 10) ? *pUuidStr += 48 : *pUuidStr += 55; + + /*Data3 - 4 characters.*/ + *pUuidStr++ = '-'; + for(i = 0; i < 4; i++, pUuidStr++) + ((*pUuidStr = (rand() % 16)) < 10) ? *pUuidStr += 48 : *pUuidStr += 55; + + /*Data4 - 4 characters.*/ + *pUuidStr++ = '-'; + for(i = 0; i < 4; i++, pUuidStr++) + ((*pUuidStr = (rand() % 16)) < 10) ? *pUuidStr += 48 : *pUuidStr += 55; + + /*Data5 - 12 characters.*/ + *pUuidStr++ = '-'; + for(i = 0; i < 12; i++, pUuidStr++) + ((*pUuidStr = (rand() % 16)) < 10) ? *pUuidStr += 48 : *pUuidStr += 55; + + *pUuidStr = '\0'; + + m_lastUUID = UuidStrTmp; + return UuidStrTmp; +} + +bool StringUtils::ValidateUUID(const CStdString &uuid) +{ + CRegExp guidRE; + guidRE.RegComp(ADDON_GUID_RE); + return (guidRE.RegFind(uuid.c_str()) == 0); +} + +double StringUtils::CompareFuzzy(const CStdString &left, const CStdString &right) +{ + return (0.5 + fstrcmp(left.c_str(), right.c_str(), 0.0) * (left.length() + right.length())) / 2.0; +} + +int StringUtils::FindBestMatch(const CStdString &str, const vector<string> &strings, double &matchscore) +{ + int best = -1; + matchscore = 0; + + int i = 0; + for (vector<string>::const_iterator it = strings.begin(); it != strings.end(); ++it, i++) + { + int maxlength = max(str.length(), it->length()); + double score = StringUtils::CompareFuzzy(str, *it) / maxlength; + if (score > matchscore) + { + matchscore = score; + best = i; + } + } + return best; +} + +bool StringUtils::ContainsKeyword(const CStdString &str, const vector<string> &keywords) +{ + for (vector<string>::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + if (str.find(*it) != str.npos) + return true; + } + return false; +} + +size_t StringUtils::utf8_strlen(const char *s) +{ + size_t length = 0; + while (*s) + { + if ((*s++ & 0xC0) != 0x80) + length++; + } + return length; +} + +std::string StringUtils::Paramify(const std::string ¶m) +{ + std::string result = param; + // escape backspaces + StringUtils::Replace(result, "\\", "\\\\"); + // escape double quotes + StringUtils::Replace(result, "\"", "\\\""); + + // add double quotes around the whole string + return "\"" + result + "\""; +} + +std::vector<std::string> StringUtils::Tokenize(const std::string &input, const std::string &delimiters) +{ + std::vector<std::string> tokens; + Tokenize(input, tokens, delimiters); + return tokens; +} + +void StringUtils::Tokenize(const std::string& input, std::vector<std::string>& tokens, const std::string& delimiters) +{ + tokens.clear(); + // Skip delimiters at beginning. + std::string::size_type dataPos = input.find_first_not_of(delimiters); + while (dataPos != std::string::npos) + { + // Find next delimiter + const std::string::size_type nextDelimPos = input.find_first_of(delimiters, dataPos); + // Found a token, add it to the vector. + tokens.push_back(input.substr(dataPos, nextDelimPos - dataPos)); + // Skip delimiters. Note the "not_of" + dataPos = input.find_first_not_of(delimiters, nextDelimPos); + } +} + +std::vector<std::string> StringUtils::Tokenize(const std::string &input, const char delimiter) +{ + std::vector<std::string> tokens; + Tokenize(input, tokens, delimiter); + return tokens; +} + +void StringUtils::Tokenize(const std::string& input, std::vector<std::string>& tokens, const char delimiter) +{ + tokens.clear(); + // Skip delimiters at beginning. + std::string::size_type dataPos = input.find_first_not_of(delimiter); + while (dataPos != std::string::npos) + { + // Find next delimiter + const std::string::size_type nextDelimPos = input.find(delimiter, dataPos); + // Found a token, add it to the vector. + tokens.push_back(input.substr(dataPos, nextDelimPos - dataPos)); + // Skip delimiters. Note the "not_of" + dataPos = input.find_first_not_of(delimiter, nextDelimPos); + } +} diff --git a/src/utils/StringUtils.h b/src/utils/StringUtils.h new file mode 100644 index 0000000000..998fae2442 --- /dev/null +++ b/src/utils/StringUtils.h @@ -0,0 +1,197 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ +//----------------------------------------------------------------------- +// +// File: StringUtils.h +// +// Purpose: ATL split string utility +// Author: Paul J. Weiss +// +// Modified to support J O'Leary's CStdString class by kraqh3d +// +//------------------------------------------------------------------------ + +#include <vector> +#include <stdint.h> +#include <string> + +#include "XBDateTime.h" +#include "utils/StdString.h" +#include "utils/params_check_macros.h" + +class StringUtils +{ +public: + /*! \brief Get a formatted string similar to sprintf + + Beware that this does not support directly passing in + std::string objects. You need to call c_str() to pass + the const char* buffer representing the value of the + std::string object. + + \param fmt Format of the resulting string + \param ... variable number of value type arguments + \return Formatted string + */ + static std::string Format(PRINTF_FORMAT_STRING const char *fmt, ...) PARAM1_PRINTF_FORMAT; + static std::string FormatV(PRINTF_FORMAT_STRING const char *fmt, va_list args); + static std::wstring Format(PRINTF_FORMAT_STRING const wchar_t *fmt, ...); + static std::wstring FormatV(PRINTF_FORMAT_STRING const wchar_t *fmt, va_list args); + static void ToUpper(std::string &str); + static void ToUpper(std::wstring &str); + static void ToLower(std::string &str); + static void ToLower(std::wstring &str); + static bool EqualsNoCase(const std::string &str1, const std::string &str2); + static bool EqualsNoCase(const std::string &str1, const char *s2); + static bool EqualsNoCase(const char *s1, const char *s2); + static int CompareNoCase(const std::string &str1, const std::string &str2); + static int CompareNoCase(const char *s1, const char *s2); + static std::string Left(const std::string &str, size_t count); + static std::string Mid(const std::string &str, size_t first, size_t count = std::string::npos); + static std::string Right(const std::string &str, size_t count); + static std::string& Trim(std::string &str); + static std::string& Trim(std::string &str, const char* const chars); + static std::string& TrimLeft(std::string &str); + static std::string& TrimLeft(std::string &str, const char* const chars); + static std::string& TrimRight(std::string &str); + static std::string& TrimRight(std::string &str, const char* const chars); + static std::string& RemoveDuplicatedSpacesAndTabs(std::string& str); + static int Replace(std::string &str, char oldChar, char newChar); + static int Replace(std::string &str, const std::string &oldStr, const std::string &newStr); + static int Replace(std::wstring &str, const std::wstring &oldStr, const std::wstring &newStr); + static bool StartsWith(const std::string &str1, const std::string &str2); + static bool StartsWith(const std::string &str1, const char *s2); + static bool StartsWith(const char *s1, const char *s2); + static bool StartsWithNoCase(const std::string &str1, const std::string &str2); + static bool StartsWithNoCase(const std::string &str1, const char *s2); + static bool StartsWithNoCase(const char *s1, const char *s2); + static bool EndsWith(const std::string &str1, const std::string &str2); + static bool EndsWith(const std::string &str1, const char *s2); + static bool EndsWithNoCase(const std::string &str1, const std::string &str2); + static bool EndsWithNoCase(const std::string &str1, const char *s2); + + static std::string Join(const std::vector<std::string> &strings, const std::string& delimiter); + /*! \brief Splits the given input string using the given delimiter into separate strings. + + If the given input string is empty the result will be an empty array (not + an array containing an empty string). + + \param input Input string to be split + \param delimiter Delimiter to be used to split the input string + \param iMaxStrings (optional) Maximum number of splitted strings + */ + static std::vector<std::string> Split(const std::string& input, const std::string& delimiter, unsigned int iMaxStrings = 0); + static std::vector<std::string> Split(const std::string& input, const char delimiter, size_t iMaxStrings = 0); + static int FindNumber(const CStdString& strInput, const CStdString &strFind); + static int64_t AlphaNumericCompare(const wchar_t *left, const wchar_t *right); + static long TimeStringToSeconds(const CStdString &timeString); + static void RemoveCRLF(std::string& strLine); + + /*! \brief utf8 version of strlen - skips any non-starting bytes in the count, thus returning the number of utf8 characters + \param s c-string to find the length of. + \return the number of utf8 characters in the string. + */ + static size_t utf8_strlen(const char *s); + + /*! \brief convert a time in seconds to a string based on the given time format + \param seconds time in seconds + \param format the format we want the time in. + \return the formatted time + \sa TIME_FORMAT + */ + static CStdString SecondsToTimeString(long seconds, TIME_FORMAT format = TIME_FORMAT_GUESS); + + /*! \brief check whether a string is a natural number. + Matches [ \t]*[0-9]+[ \t]* + \param str the string to check + \return true if the string is a natural number, false otherwise. + */ + static bool IsNaturalNumber(const CStdString& str); + + /*! \brief check whether a string is an integer. + Matches [ \t]*[\-]*[0-9]+[ \t]* + \param str the string to check + \return true if the string is an integer, false otherwise. + */ + static bool IsInteger(const CStdString& str); + + /* The next several isasciiXX and asciiXXvalue functions are locale independent (US-ASCII only), + * as opposed to standard ::isXX (::isalpha, ::isdigit...) which are locale dependent. + * Next functions get parameter as char and don't need double cast ((int)(unsigned char) is required for standard functions). */ + inline static bool isasciidigit(char chr) // locale independent + { + return chr >= '0' && chr <= '9'; + } + inline static bool isasciixdigit(char chr) // locale independent + { + return (chr >= '0' && chr <= '9') || (chr >= 'a' && chr <= 'f') || (chr >= 'A' && chr <= 'F'); + } + static int asciidigitvalue(char chr); // locale independent + static int asciixdigitvalue(char chr); // locale independent + inline static bool isasciiuppercaseletter(char chr) // locale independent + { + return (chr >= 'A' && chr <= 'Z'); + } + inline static bool isasciilowercaseletter(char chr) // locale independent + { + return (chr >= 'a' && chr <= 'z'); + } + inline static bool isasciialphanum(char chr) // locale independent + { + return isasciiuppercaseletter(chr) || isasciilowercaseletter(chr) || isasciidigit(chr); + } + static CStdString SizeToString(int64_t size); + static const CStdString EmptyString; + static const std::string Empty; + static size_t FindWords(const char *str, const char *wordLowerCase); + static int FindEndBracket(const CStdString &str, char opener, char closer, int startPos = 0); + static int DateStringToYYYYMMDD(const CStdString &dateString); + static void WordToDigits(std::string &word); + static CStdString CreateUUID(); + static bool ValidateUUID(const CStdString &uuid); // NB only validates syntax + static double CompareFuzzy(const CStdString &left, const CStdString &right); + static int FindBestMatch(const CStdString &str, const std::vector<std::string> &strings, double &matchscore); + static bool ContainsKeyword(const CStdString &str, const std::vector<std::string> &keywords); + + /*! \brief Escapes the given string to be able to be used as a parameter. + + Escapes backslashes and double-quotes with an additional backslash and + adds double-quotes around the whole string. + + \param param String to escape/paramify + \return Escaped/Paramified string + */ + static std::string Paramify(const std::string ¶m); + + /*! \brief Split a string by the specified delimiters. + Splits a string using one or more delimiting characters, ignoring empty tokens. + Differs from Split() in two ways: + 1. The delimiters are treated as individual characters, rather than a single delimiting string. + 2. Empty tokens are ignored. + \return a vector of tokens + */ + static std::vector<std::string> Tokenize(const std::string& input, const std::string& delimiters); + static void Tokenize(const std::string& input, std::vector<std::string>& tokens, const std::string& delimiters); + static std::vector<std::string> Tokenize(const std::string& input, const char delimiter); + static void Tokenize(const std::string& input, std::vector<std::string>& tokens, const char delimiter); +private: + static CStdString m_lastUUID; +}; diff --git a/src/utils/StringValidation.cpp b/src/utils/StringValidation.cpp new file mode 100644 index 0000000000..3f60dc51c6 --- /dev/null +++ b/src/utils/StringValidation.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "StringValidation.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +bool StringValidation::IsInteger(const std::string &input, void *data) +{ + return StringUtils::IsInteger(input); +} + +bool StringValidation::IsPositiveInteger(const std::string &input, void *data) +{ + return StringUtils::IsNaturalNumber(input); +} + +bool StringValidation::IsTime(const std::string &input, void *data) +{ + std::string strTime = input; + StringUtils::Trim(strTime); + + if (StringUtils::EndsWithNoCase(strTime, " min")) + { + strTime = StringUtils::Left(strTime, strTime.size() - 4); + StringUtils::TrimRight(strTime); + + return IsPositiveInteger(strTime, NULL); + } + else + { + // support [[HH:]MM:]SS + std::vector<std::string> bits = StringUtils::Split(input, ":"); + if (bits.size() > 3) + return false; + + for (std::vector<std::string>::const_iterator i = bits.begin(); i != bits.end(); ++i) + if (!IsPositiveInteger(*i, NULL)) + return false; + + return true; + } + return false; +} diff --git a/src/utils/StringValidation.h b/src/utils/StringValidation.h new file mode 100644 index 0000000000..eaf279c836 --- /dev/null +++ b/src/utils/StringValidation.h @@ -0,0 +1,36 @@ +#pragma once +/* + * Copyright (C) 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> + +class StringValidation +{ +public: + typedef bool (*Validator)(const std::string &input, void *data); + + static bool NonEmpty(const std::string &input, void *data) { return !input.empty(); } + static bool IsInteger(const std::string &input, void *data); + static bool IsPositiveInteger(const std::string &input, void *data); + static bool IsTime(const std::string &input, void *data); + +private: + StringValidation() { } +}; diff --git a/src/utils/SystemInfo.cpp b/src/utils/SystemInfo.cpp new file mode 100644 index 0000000000..b322191e45 --- /dev/null +++ b/src/utils/SystemInfo.cpp @@ -0,0 +1,1390 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <limits.h> + +#include "threads/SystemClock.h" +#include "system.h" +#include "SystemInfo.h" +#ifndef TARGET_POSIX +#include <conio.h> +#else +#include <sys/utsname.h> +#endif +#include "GUIInfoManager.h" +#include "filesystem/CurlFile.h" +#include "network/Network.h" +#include "Application.h" +#include "windowing/WindowingFactory.h" +#include "guilib/LocalizeStrings.h" +#include "CPUInfo.h" +#include "utils/TimeUtils.h" +#include "utils/log.h" +#include "CompileInfo.h" + +#ifdef TARGET_WINDOWS +#include "dwmapi.h" +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif // WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include "utils/CharsetConverter.h" +#endif +#if defined(TARGET_DARWIN) +#include "osx/DarwinUtils.h" +#include "osx/CocoaInterface.h" +#endif +#include "powermanagement/PowerManager.h" +#include "utils/StringUtils.h" +#include "utils/XMLUtils.h" +#if defined(TARGET_ANDROID) +#include "android/jni/Build.h" +#include "utils/AMLUtils.h" +#endif + +/* Platform identification */ +#if defined(TARGET_DARWIN) +#include <Availability.h> +#include <mach-o/arch.h> +#include <sys/sysctl.h> +#include "utils/auto_buffer.h" +#elif defined(TARGET_ANDROID) +#include <android/api-level.h> +#include <sys/system_properties.h> +#elif defined(TARGET_FREEBSD) +#include <sys/param.h> +#elif defined(TARGET_LINUX) +#include <linux/version.h> +#endif + +/* Expand macro before stringify */ +#define STR_MACRO(x) #x +#define XSTR_MACRO(x) STR_MACRO(x) + +#ifdef TARGET_WINDOWS +static bool sysGetVersionExWByRef(OSVERSIONINFOEXW& osVerInfo) +{ + ZeroMemory(&osVerInfo, sizeof(osVerInfo)); + osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo); + + typedef NTSTATUS(__stdcall *RtlGetVersionPtr)(RTL_OSVERSIONINFOEXW* pOsInfo); + static HMODULE hNtDll = GetModuleHandleW(L"ntdll.dll"); + if (hNtDll != NULL) + { + static RtlGetVersionPtr RtlGetVer = (RtlGetVersionPtr) GetProcAddress(hNtDll, "RtlGetVersion"); + if (RtlGetVer && RtlGetVer(&osVerInfo) == 0) + return true; + } + // failed to get OS information directly from ntdll.dll + // use GetVersionExW() as fallback + // note: starting from Windows 8.1 GetVersionExW() may return unfaithful information + if (GetVersionExW((OSVERSIONINFOW*) &osVerInfo) != 0) + return true; + + ZeroMemory(&osVerInfo, sizeof(osVerInfo)); + return false; +} +#endif // TARGET_WINDOWS + +#ifdef TARGET_LINUX +static std::string getValueFromOs_release(std::string key) +{ + FILE* os_rel = fopen("/etc/os-release", "r"); + if (!os_rel) + return ""; + + char* buf = new char[10 * 1024]; // more than enough + size_t len = fread(buf, 1, 10 * 1024, os_rel); + fclose(os_rel); + if (len == 0) + { + delete[] buf; + return ""; + } + + std::string content(buf, len); + delete[] buf; + + // find begin of value string + size_t valStart = 0, seachPos; + key += '='; + if (content.compare(0, key.length(), key) == 0) + valStart = key.length(); + else + { + key = "\n" + key; + seachPos = 0; + do + { + seachPos = content.find(key, seachPos); + if (seachPos == std::string::npos) + return ""; + if (seachPos == 0 || content[seachPos - 1] != '\\') + valStart = seachPos + key.length(); + else + seachPos++; + } while (valStart == 0); + } + + if (content[valStart] == '\n') + return ""; + + // find end of value string + seachPos = valStart; + do + { + seachPos = content.find('\n', seachPos + 1); + } while (seachPos != std::string::npos && content[seachPos - 1] == '\\'); + size_t const valEnd = seachPos; + + std::string value(content, valStart, valEnd - valStart); + if (value.empty()) + return value; + + // remove quotes + if (value[0] == '\'' || value[0] == '"') + { + if (value.length() < 2) + return value; + size_t qEnd = value.rfind(value[0]); + if (qEnd != std::string::npos) + { + value.erase(qEnd); + value.erase(0, 1); + } + } + + // unescape characters + for (size_t slashPos = value.find('\\'); slashPos < value.length() - 1; slashPos = value.find('\\', slashPos)) + { + if (value[slashPos + 1] == '\n') + value.erase(slashPos, 2); + else + { + value.erase(slashPos, 1); + slashPos++; // skip unescaped character + } + } + + return value; +} + +enum lsb_rel_info_type +{ + lsb_rel_distributor, + lsb_rel_description, + lsb_rel_release, + lsb_rel_codename +}; + +static std::string getValueFromLsb_release(enum lsb_rel_info_type infoType) +{ + std::string key, command("unset PYTHONHOME; unset PYTHONPATH; lsb_release "); + switch (infoType) + { + case lsb_rel_distributor: + command += "-i"; + key = "Distributor ID:\t"; + break; + case lsb_rel_description: + command += "-d"; + key = "Description:\t"; + break; + case lsb_rel_release: + command += "-r"; + key = "Release:\t"; + break; + case lsb_rel_codename: + command += "-c"; + key = "Codename:\t"; + break; + default: + return ""; + } + FILE* lsb_rel = popen(command.c_str(), "r"); + if (lsb_rel == NULL) + return ""; + + char buf[300]; // more than enough + if (fgets(buf, 300, lsb_rel) == NULL) + { + pclose(lsb_rel); + return ""; + } + pclose(lsb_rel); + + std::string response(buf); + if (response.compare(0, key.length(), key) != 0) + return ""; + + return response.substr(key.length(), response.find('\n') - key.length()); +} +#endif // TARGET_LINUX + +CSysInfo g_sysinfo; + +CSysInfoJob::CSysInfoJob() +{ +} + +bool CSysInfoJob::DoWork() +{ + m_info.systemUptime = GetSystemUpTime(false); + m_info.systemTotalUptime = GetSystemUpTime(true); + m_info.internetState = GetInternetState(); + m_info.videoEncoder = GetVideoEncoder(); + m_info.cpuFrequency = GetCPUFreqInfo(); + m_info.osVersionInfo = CSysInfo::GetOsPrettyNameWithVersion() + " (kernel: " + CSysInfo::GetKernelName() + " " + CSysInfo::GetKernelVersionFull() + ")"; + m_info.macAddress = GetMACAddress(); + m_info.batteryLevel = GetBatteryLevel(); + return true; +} + +const CSysData &CSysInfoJob::GetData() const +{ + return m_info; +} + +std::string CSysInfoJob::GetCPUFreqInfo() +{ + double CPUFreq = GetCPUFrequency(); + return StringUtils::Format("%4.0f MHz", CPUFreq);; +} + +CSysData::INTERNET_STATE CSysInfoJob::GetInternetState() +{ + // Internet connection state! + XFILE::CCurlFile http; + if (http.IsInternet()) + return CSysData::CONNECTED; + return CSysData::DISCONNECTED; +} + +std::string CSysInfoJob::GetMACAddress() +{ +#if defined(HAS_LINUX_NETWORK) || defined(HAS_WIN32_NETWORK) + CNetworkInterface* iface = g_application.getNetwork().GetFirstConnectedInterface(); + if (iface) + return iface->GetMacAddress(); +#endif + return ""; +} + +std::string CSysInfoJob::GetVideoEncoder() +{ + return "GPU: " + g_Windowing.GetRenderRenderer(); +} + +std::string CSysInfoJob::GetBatteryLevel() +{ + return StringUtils::Format("%d%%", g_powerManager.BatteryLevel()); +} + +double CSysInfoJob::GetCPUFrequency() +{ +#if defined (TARGET_POSIX) || defined(TARGET_WINDOWS) + return double (g_cpuInfo.getCPUFrequency()); +#else + return 0; +#endif +} + +bool CSysInfoJob::SystemUpTime(int iInputMinutes, int &iMinutes, int &iHours, int &iDays) +{ + iHours = 0; iDays = 0; + iMinutes = iInputMinutes; + if (iMinutes >= 60) // Hour's + { + iHours = iMinutes / 60; + iMinutes = iMinutes - (iHours *60); + } + if (iHours >= 24) // Days + { + iDays = iHours / 24; + iHours = iHours - (iDays * 24); + } + return true; +} + +std::string CSysInfoJob::GetSystemUpTime(bool bTotalUptime) +{ + std::string strSystemUptime; + int iInputMinutes, iMinutes,iHours,iDays; + + if(bTotalUptime) + { + //Total Uptime + iInputMinutes = g_sysinfo.GetTotalUptime() + ((int)(XbmcThreads::SystemClockMillis() / 60000)); + } + else + { + //Current UpTime + iInputMinutes = (int)(XbmcThreads::SystemClockMillis() / 60000); + } + + SystemUpTime(iInputMinutes,iMinutes, iHours, iDays); + if (iDays > 0) + { + strSystemUptime = StringUtils::Format("%i %s, %i %s, %i %s", + iDays, g_localizeStrings.Get(12393).c_str(), + iHours, g_localizeStrings.Get(12392).c_str(), + iMinutes, g_localizeStrings.Get(12391).c_str()); + } + else if (iDays == 0 && iHours >= 1 ) + { + strSystemUptime = StringUtils::Format("%i %s, %i %s", + iHours, g_localizeStrings.Get(12392).c_str(), + iMinutes, g_localizeStrings.Get(12391).c_str()); + } + else if (iDays == 0 && iHours == 0 && iMinutes >= 0) + { + strSystemUptime = StringUtils::Format("%i %s", + iMinutes, g_localizeStrings.Get(12391).c_str()); + } + return strSystemUptime; +} + +std::string CSysInfo::TranslateInfo(int info) const +{ + switch(info) + { + case SYSTEM_VIDEO_ENCODER_INFO: + return m_info.videoEncoder; + case NETWORK_MAC_ADDRESS: + return m_info.macAddress; + case SYSTEM_OS_VERSION_INFO: + return m_info.osVersionInfo; + case SYSTEM_CPUFREQUENCY: + return m_info.cpuFrequency; + case SYSTEM_UPTIME: + return m_info.systemUptime; + case SYSTEM_TOTALUPTIME: + return m_info.systemTotalUptime; + case SYSTEM_INTERNET_STATE: + if (m_info.internetState == CSysData::CONNECTED) + return g_localizeStrings.Get(13296); + else + return g_localizeStrings.Get(13297); + case SYSTEM_BATTERY_LEVEL: + return m_info.batteryLevel; + default: + return ""; + } +} + +void CSysInfo::Reset() +{ + m_info.Reset(); +} + +CSysInfo::CSysInfo(void) : CInfoLoader(15 * 1000) +{ + memset(MD5_Sign, 0, sizeof(MD5_Sign)); + m_iSystemTimeTotalUp = 0; +} + +CSysInfo::~CSysInfo() +{ +} + +bool CSysInfo::Load(const TiXmlNode *settings) +{ + if (settings == NULL) + return false; + + const TiXmlElement *pElement = settings->FirstChildElement("general"); + if (pElement) + XMLUtils::GetInt(pElement, "systemtotaluptime", m_iSystemTimeTotalUp, 0, INT_MAX); + + return true; +} + +bool CSysInfo::Save(TiXmlNode *settings) const +{ + if (settings == NULL) + return false; + + TiXmlNode *generalNode = settings->FirstChild("general"); + if (generalNode == NULL) + { + TiXmlElement generalNodeNew("general"); + generalNode = settings->InsertEndChild(generalNodeNew); + if (generalNode == NULL) + return false; + } + XMLUtils::SetInt(generalNode, "systemtotaluptime", m_iSystemTimeTotalUp); + + return true; +} + +const std::string& CSysInfo::GetAppName(void) +{ + assert(CCompileInfo::GetAppName() != NULL); + static const std::string appName(CCompileInfo::GetAppName()); + + return appName; +} + +bool CSysInfo::GetDiskSpace(const std::string& drive,int& iTotal, int& iTotalFree, int& iTotalUsed, int& iPercentFree, int& iPercentUsed) +{ + bool bRet= false; + ULARGE_INTEGER ULTotal= { { 0 } }; + ULARGE_INTEGER ULTotalFree= { { 0 } }; + + if( !drive.empty() && drive != "*" ) + { +#ifdef TARGET_WINDOWS + UINT uidriveType = GetDriveType(( drive + ":\\" ).c_str()); + if(uidriveType != DRIVE_UNKNOWN && uidriveType != DRIVE_NO_ROOT_DIR) + bRet= ( 0 != GetDiskFreeSpaceEx( ( drive + ":\\" ).c_str(), NULL, &ULTotal, &ULTotalFree) ); +#elif defined(TARGET_POSIX) + bRet = (0 != GetDiskFreeSpaceEx(drive.c_str(), NULL, &ULTotal, &ULTotalFree)); +#endif + } + else + { + ULARGE_INTEGER ULTotalTmp= { { 0 } }; + ULARGE_INTEGER ULTotalFreeTmp= { { 0 } }; +#ifdef TARGET_WINDOWS + char* pcBuffer= NULL; + DWORD dwStrLength= GetLogicalDriveStrings( 0, pcBuffer ); + if( dwStrLength != 0 ) + { + dwStrLength+= 1; + pcBuffer= new char [dwStrLength]; + GetLogicalDriveStrings( dwStrLength, pcBuffer ); + int iPos= 0; + do { + if( DRIVE_FIXED == GetDriveType( pcBuffer + iPos ) && + GetDiskFreeSpaceEx( ( pcBuffer + iPos ), NULL, &ULTotal, &ULTotalFree ) ) + { + ULTotalTmp.QuadPart+= ULTotal.QuadPart; + ULTotalFreeTmp.QuadPart+= ULTotalFree.QuadPart; + } + iPos += (strlen( pcBuffer + iPos) + 1 ); + }while( strlen( pcBuffer + iPos ) > 0 ); + } + delete[] pcBuffer; +#else // for linux and osx + if( GetDiskFreeSpaceEx( "/", NULL, &ULTotal, &ULTotalFree ) ) + { + ULTotalTmp.QuadPart+= ULTotal.QuadPart; + ULTotalFreeTmp.QuadPart+= ULTotalFree.QuadPart; + } +#endif + if( ULTotalTmp.QuadPart || ULTotalFreeTmp.QuadPart ) + { + ULTotal.QuadPart= ULTotalTmp.QuadPart; + ULTotalFree.QuadPart= ULTotalFreeTmp.QuadPart; + bRet= true; + } + } + + if( bRet ) + { + iTotal = (int)( ULTotal.QuadPart / MB ); + iTotalFree = (int)( ULTotalFree.QuadPart / MB ); + iTotalUsed = iTotal - iTotalFree; + if( ULTotal.QuadPart > 0 ) + { + iPercentUsed = (int)( 100.0f * ( ULTotal.QuadPart - ULTotalFree.QuadPart ) / ULTotal.QuadPart + 0.5f ); + } + else + { + iPercentUsed = 0; + } + iPercentFree = 100 - iPercentUsed; + } + + return bRet; +} + +std::string CSysInfo::GetCPUModel() +{ + return "CPU: " + g_cpuInfo.getCPUModel(); +} + +std::string CSysInfo::GetCPUBogoMips() +{ + return "BogoMips: " + g_cpuInfo.getCPUBogoMips(); +} + +std::string CSysInfo::GetCPUHardware() +{ + return "Hardware: " + g_cpuInfo.getCPUHardware(); +} + +std::string CSysInfo::GetCPURevision() +{ + return "Revision: " + g_cpuInfo.getCPURevision(); +} + +std::string CSysInfo::GetCPUSerial() +{ + return "Serial: " + g_cpuInfo.getCPUSerial(); +} + +std::string CSysInfo::GetKernelName(bool emptyIfUnknown /*= false*/) +{ + static std::string kernelName; + if (kernelName.empty()) + { +#if defined(TARGET_WINDOWS) + OSVERSIONINFOEXW osvi; + if (sysGetVersionExWByRef(osvi) && osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) + kernelName = "Windows NT"; +#elif defined(TARGET_POSIX) + struct utsname un; + if (uname(&un) == 0) + kernelName.assign(un.sysname); +#endif // defined(TARGET_POSIX) + + if (kernelName.empty()) + kernelName = "Unknown kernel"; // can't detect + } + + if (emptyIfUnknown && kernelName == "Unknown kernel") + return ""; + + return kernelName; +} + +std::string CSysInfo::GetKernelVersionFull(void) +{ + static std::string kernelVersionFull; + if (!kernelVersionFull.empty()) + return kernelVersionFull; + +#if defined(TARGET_WINDOWS) + OSVERSIONINFOEXW osvi; + if (sysGetVersionExWByRef(osvi)) + kernelVersionFull = StringUtils::Format("%d.%d", osvi.dwMajorVersion, osvi.dwMinorVersion); +#elif defined(TARGET_POSIX) + struct utsname un; + if (uname(&un) == 0) + kernelVersionFull.assign(un.release); +#endif // defined(TARGET_POSIX) + + if (kernelVersionFull.empty()) + kernelVersionFull = "0.0.0"; // can't detect + + return kernelVersionFull; +} + +std::string CSysInfo::GetKernelVersion(void) +{ + static std::string kernelVersionClear; + if (kernelVersionClear.empty()) + { + kernelVersionClear = GetKernelVersionFull(); + const size_t erasePos = kernelVersionClear.find_first_not_of("0123456789."); + if (erasePos != std::string::npos) + kernelVersionClear.erase(erasePos); + } + + return kernelVersionClear; +} + +std::string CSysInfo::GetOsName(bool emptyIfUnknown /* = false*/) +{ + static std::string osName; + if (osName.empty()) + { +#if defined (TARGET_WINDOWS) + osName = GetKernelName() + "-based OS"; +#elif defined(TARGET_FREEBSD) + osName = GetKernelName(true); // FIXME: for FreeBSD OS name is a kernel name +#elif defined(TARGET_DARWIN_IOS) + osName = "iOS"; +#elif defined(TARGET_DARWIN_OSX) + osName = "OS X"; +#elif defined (TARGET_ANDROID) + osName = "Android"; +#elif defined(TARGET_LINUX) + osName = getValueFromOs_release("NAME"); + if (osName.empty()) + osName = getValueFromLsb_release(lsb_rel_distributor); + if (osName.empty()) + osName = getValueFromOs_release("ID"); +#endif // defined(TARGET_LINUX) + + if (osName.empty()) + osName = "Unknown OS"; + } + + if (emptyIfUnknown && osName == "Unknown OS") + return ""; + + return osName; +} + +std::string CSysInfo::GetOsVersion(void) +{ + static std::string osVersion; + if (!osVersion.empty()) + return osVersion; + +#if defined(TARGET_WINDOWS) || defined(TARGET_FREEBSD) + osVersion = GetKernelVersion(); // FIXME: for Win32 and FreeBSD OS version is a kernel version +#elif defined(TARGET_DARWIN_IOS) + osVersion = CDarwinUtils::GetIOSVersionString(); +#elif defined(TARGET_DARWIN_OSX) + osVersion = CDarwinUtils::GetOSXVersionString(); +#elif defined(TARGET_ANDROID) + char versionCStr[PROP_VALUE_MAX]; + int propLen = __system_property_get("ro.build.version.release", versionCStr); + osVersion.assign(versionCStr, (propLen > 0 && propLen <= PROP_VALUE_MAX) ? propLen : 0); + + if (osVersion.empty() || std::string("0123456789").find(versionCStr[0]) == std::string::npos) + osVersion.clear(); // can't correctly detect Android version + else + { + size_t pointPos = osVersion.find('.'); + if (pointPos == std::string::npos) + osVersion += ".0.0"; + else if (osVersion.find('.', pointPos + 1) == std::string::npos) + osVersion += ".0"; + } +#elif defined(TARGET_LINUX) + osVersion = getValueFromOs_release("VERSION_ID"); + if (osVersion.empty()) + osVersion = getValueFromLsb_release(lsb_rel_release); +#endif // defined(TARGET_LINUX) + + if (osVersion.empty()) + osVersion = "0.0"; + + return osVersion; +} + +std::string CSysInfo::GetOsPrettyNameWithVersion(void) +{ + static std::string osNameVer; + if (!osNameVer.empty()) + return osNameVer; + +#if defined (TARGET_WINDOWS) + OSVERSIONINFOEXW osvi = {}; + + osNameVer = "Windows "; + if (sysGetVersionExWByRef(osvi)) + { + switch (GetWindowsVersion()) + { + case WindowsVersionVista: + if (osvi.wProductType == VER_NT_WORKSTATION) + osNameVer.append("Vista"); + else + osNameVer.append("Server 2008"); + break; + case WindowsVersionWin7: + if (osvi.wProductType == VER_NT_WORKSTATION) + osNameVer.append("7"); + else + osNameVer.append("Server 2008 R2"); + break; + case WindowsVersionWin8: + if (osvi.wProductType == VER_NT_WORKSTATION) + osNameVer.append("8"); + else + osNameVer.append("Server 2012"); + break; + case WindowsVersionWin8_1: + if (osvi.wProductType == VER_NT_WORKSTATION) + osNameVer.append("8.1"); + else + osNameVer.append("Server 2012 R2"); + break; + case WindowsVersionFuture: + osNameVer.append("Unknown Future Version"); + break; + default: + osNameVer.append("Unknown version"); + break; + } + + // Append Service Pack version if any + if (osvi.wServicePackMajor > 0 || osvi.wServicePackMinor > 0) + { + osNameVer.append(StringUtils::Format(" SP%d", osvi.wServicePackMajor)); + if (osvi.wServicePackMinor > 0) + { + osNameVer.append(StringUtils::Format(".%d", osvi.wServicePackMinor)); + } + } + } + else + osNameVer.append(" unknown"); +#elif defined(TARGET_FREEBSD) || defined(TARGET_DARWIN_IOS) || defined(TARGET_DARWIN_OSX) + osNameVer = GetOsName() + " " + GetOsVersion(); +#elif defined(TARGET_ANDROID) + osNameVer = GetOsName() + " " + GetOsVersion() + " API level " + StringUtils::Format("%d", CJNIBuild::SDK_INT); +#elif defined(TARGET_LINUX) + osNameVer = getValueFromOs_release("PRETTY_NAME"); + if (osNameVer.empty()) + { + osNameVer = getValueFromLsb_release(lsb_rel_description); + std::string osName(GetOsName(true)); + if (!osName.empty() && osNameVer.find(osName) == std::string::npos) + osNameVer = osName + osNameVer; + if (osNameVer.empty()) + osNameVer = "Unknown Linux Distribution"; + } + + if (osNameVer.find(GetOsVersion()) == std::string::npos) + osNameVer += " " + GetOsVersion(); +#endif // defined(TARGET_LINUX) + + if (osNameVer.empty()) + osNameVer = "Unknown OS Unknown version"; + + return osNameVer; + +} + +std::string CSysInfo::GetManufacturerName(void) +{ + static std::string manufName; + static bool inited = false; + if (!inited) + { +#if defined(TARGET_ANDROID) + char deviceCStr[PROP_VALUE_MAX]; + int propLen = __system_property_get("ro.product.manufacturer", deviceCStr); + manufName.assign(deviceCStr, (propLen > 0 && propLen <= PROP_VALUE_MAX) ? propLen : 0); +#elif defined(TARGET_DARWIN) + manufName = CDarwinUtils::GetManufacturer(); +#elif defined(TARGET_WINDOWS) + HKEY hKey; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\BIOS", 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + wchar_t buf[400]; // more than enough + DWORD bufSize = sizeof(buf); + DWORD valType; + if (RegQueryValueExW(hKey, L"SystemManufacturer", NULL, &valType, (LPBYTE)buf, &bufSize) == ERROR_SUCCESS && valType == REG_SZ) + { + g_charsetConverter.wToUTF8(std::wstring(buf, bufSize / sizeof(wchar_t)), manufName); + size_t zeroPos = manufName.find(char(0)); + if (zeroPos != std::string::npos) + manufName.erase(zeroPos); // remove any extra zero-terminations + std::string lower(manufName); + StringUtils::ToLower(lower); + if (lower == "system manufacturer" || lower == "to be filled by o.e.m." || lower == "unknown" || + lower == "unidentified") + manufName.clear(); + } + RegCloseKey(hKey); + } +#endif + inited = true; + } + + return manufName; +} + +std::string CSysInfo::GetModelName(void) +{ + static std::string modelName; + static bool inited = false; + if (!inited) + { +#if defined(TARGET_ANDROID) + char deviceCStr[PROP_VALUE_MAX]; + int propLen = __system_property_get("ro.product.model", deviceCStr); + modelName.assign(deviceCStr, (propLen > 0 && propLen <= PROP_VALUE_MAX) ? propLen : 0); +#elif defined(TARGET_DARWIN_IOS) + modelName = CDarwinUtils::getIosPlatformString(); +#elif defined(TARGET_DARWIN_OSX) + size_t nameLen = 0; // 'nameLen' should include terminating null + if (sysctlbyname("hw.model", NULL, &nameLen, NULL, NULL) == 0 && nameLen > 1) + { + XUTILS::auto_buffer buf(nameLen); + if (sysctlbyname("hw.model", buf.get(), &nameLen, NULL, NULL) == 0 && nameLen == buf.size()) + modelName.assign(buf.get(), nameLen - 1); // assign exactly 'nameLen-1' characters to 'modelName' + } +#elif defined(TARGET_WINDOWS) + HKEY hKey; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\BIOS", 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + wchar_t buf[400]; // more than enough + DWORD bufSize = sizeof(buf); + DWORD valType; + if (RegQueryValueExW(hKey, L"SystemProductName", NULL, &valType, (LPBYTE)buf, &bufSize) == ERROR_SUCCESS && valType == REG_SZ) + { + g_charsetConverter.wToUTF8(std::wstring(buf, bufSize / sizeof(wchar_t)), modelName); + size_t zeroPos = modelName.find(char(0)); + if (zeroPos != std::string::npos) + modelName.erase(zeroPos); // remove any extra zero-terminations + std::string lower(modelName); + StringUtils::ToLower(lower); + if (lower == "system product name" || lower == "to be filled by o.e.m." || lower == "unknown" || + lower == "unidentified") + modelName.clear(); + } + RegCloseKey(hKey); + } +#endif + inited = true; + } + + return modelName; +} + +bool CSysInfo::IsAeroDisabled() +{ +#ifdef TARGET_WINDOWS + BOOL aeroEnabled = FALSE; + HRESULT res = DwmIsCompositionEnabled(&aeroEnabled); + if (SUCCEEDED(res)) + return !aeroEnabled; +#endif + return false; +} + +bool CSysInfo::HasHW3DInterlaced() +{ +#if defined(TARGET_ANDROID) + if (aml_hw3d_present()) + return true; +#endif + return false; +} + +CSysInfo::WindowsVersion CSysInfo::m_WinVer = WindowsVersionUnknown; + +bool CSysInfo::IsWindowsVersion(WindowsVersion ver) +{ + if (ver == WindowsVersionUnknown) + return false; + return GetWindowsVersion() == ver; +} + +bool CSysInfo::IsWindowsVersionAtLeast(WindowsVersion ver) +{ + if (ver == WindowsVersionUnknown) + return false; + return GetWindowsVersion() >= ver; +} + +CSysInfo::WindowsVersion CSysInfo::GetWindowsVersion() +{ +#ifdef TARGET_WINDOWS + if (m_WinVer == WindowsVersionUnknown) + { + OSVERSIONINFOEXW osvi = {}; + if (sysGetVersionExWByRef(osvi)) + { + if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) + m_WinVer = WindowsVersionVista; + else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) + m_WinVer = WindowsVersionWin7; + else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2) + m_WinVer = WindowsVersionWin8; + else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3) + m_WinVer = WindowsVersionWin8_1; + /* Insert checks for new Windows versions here */ + else if ( (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion > 3) || osvi.dwMajorVersion > 6) + m_WinVer = WindowsVersionFuture; + } + } +#endif // TARGET_WINDOWS + return m_WinVer; +} + +int CSysInfo::GetKernelBitness(void) +{ + static int kernelBitness = -1; + if (kernelBitness == -1) + { +#ifdef TARGET_WINDOWS + SYSTEM_INFO si; + GetNativeSystemInfo(&si); + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL || si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM) + kernelBitness = 32; + else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) + kernelBitness = 64; + else + { + BOOL isWow64 = FALSE; + if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) // fallback + kernelBitness = 64; + } +#elif defined(TARGET_DARWIN_IOS) + // Note: OS X return x86 CPU type without CPU_ARCH_ABI64 flag + const NXArchInfo* archInfo = NXGetLocalArchInfo(); + if (archInfo) + kernelBitness = ((archInfo->cputype & CPU_ARCH_ABI64) != 0) ? 64 : 32; +#elif defined(TARGET_POSIX) + struct utsname un; + if (uname(&un) == 0) + { + std::string machine(un.machine); + if (machine == "x86_64" || machine == "amd64" || machine == "arm64" || machine == "aarch64" || machine == "ppc64" || + machine == "ia64" || machine == "mips64") + kernelBitness = 64; + else + kernelBitness = 32; + } +#endif + if (kernelBitness == -1) + kernelBitness = 0; // can't detect + } + + return kernelBitness; +} + +const std::string& CSysInfo::GetKernelCpuFamily(void) +{ + static std::string kernelCpuFamily; + if (kernelCpuFamily.empty()) + { +#ifdef TARGET_WINDOWS + SYSTEM_INFO si; + GetNativeSystemInfo(&si); + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL || + si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) + kernelCpuFamily = "x86"; + else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM) + kernelCpuFamily = "ARM"; +#elif defined(TARGET_DARWIN) + const NXArchInfo* archInfo = NXGetLocalArchInfo(); + if (archInfo) + { + const cpu_type_t cpuType = (archInfo->cputype & ~CPU_ARCH_ABI64); // get CPU family without 64-bit ABI flag + if (cpuType == CPU_TYPE_I386) + kernelCpuFamily = "x86"; + else if (cpuType == CPU_TYPE_ARM) + kernelCpuFamily = "ARM"; + else if (cpuType == CPU_TYPE_POWERPC) + kernelCpuFamily = "PowerPC"; +#ifdef CPU_TYPE_MIPS + else if (cpuType == CPU_TYPE_MIPS) + kernelCpuFamily = "MIPS"; +#endif // CPU_TYPE_MIPS + } +#elif defined(TARGET_POSIX) + struct utsname un; + if (uname(&un) == 0) + { + std::string machine(un.machine); + if (machine.compare(0, 3, "arm", 3) == 0) + kernelCpuFamily = "ARM"; + else if (machine.compare(0, 4, "mips", 4) == 0) + kernelCpuFamily = "MIPS"; + else if (machine.compare(0, 4, "i686", 4) == 0 || machine == "i386" || machine == "amd64" || machine.compare(0, 3, "x86", 3) == 0) + kernelCpuFamily = "x86"; + else if (machine.compare(0, 3, "ppc", 3) == 0 || machine.compare(0, 5, "power", 5) == 0) + kernelCpuFamily = "PowerPC"; + } +#endif + if (kernelCpuFamily.empty()) + kernelCpuFamily = "unknown CPU family"; + } + return kernelCpuFamily; +} + +int CSysInfo::GetXbmcBitness(void) +{ +#if defined (__aarch64__) || defined(__arm64__) || defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(__ppc64__) || defined(__mips64) + return 64; +#elif defined(__thumb__) || defined(_M_ARMT) || defined(__arm__) || defined(_M_ARM) || defined(__mips__) || defined(mips) || defined(__mips) || defined(i386) || \ + defined(__i386) || defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(_X86_) || defined(__powerpc) || \ + defined(__powerpc__) || defined(__ppc__) || defined(_M_PPC) + return 32; +#else + return 0; // Unknown +#endif +} + +bool CSysInfo::HasInternet() +{ + if (m_info.internetState != CSysData::UNKNOWN) + return m_info.internetState == CSysData::CONNECTED; + return (m_info.internetState = CSysInfoJob::GetInternetState()) == CSysData::CONNECTED; +} + +std::string CSysInfo::GetHddSpaceInfo(int drive, bool shortText) +{ + int percent; + return GetHddSpaceInfo( percent, drive, shortText); +} + +std::string CSysInfo::GetHddSpaceInfo(int& percent, int drive, bool shortText) +{ + int total, totalFree, totalUsed, percentFree, percentused; + std::string strRet; + percent = 0; + if (g_sysinfo.GetDiskSpace("", total, totalFree, totalUsed, percentFree, percentused)) + { + if (shortText) + { + switch(drive) + { + case SYSTEM_FREE_SPACE: + percent = percentFree; + break; + case SYSTEM_USED_SPACE: + percent = percentused; + break; + } + } + else + { + switch(drive) + { + case SYSTEM_FREE_SPACE: + strRet = StringUtils::Format("%i MB %s", totalFree, g_localizeStrings.Get(160).c_str()); + break; + case SYSTEM_USED_SPACE: + strRet = StringUtils::Format("%i MB %s", totalUsed, g_localizeStrings.Get(20162).c_str()); + break; + case SYSTEM_TOTAL_SPACE: + strRet = StringUtils::Format("%i MB %s", total, g_localizeStrings.Get(20161).c_str()); + break; + case SYSTEM_FREE_SPACE_PERCENT: + strRet = StringUtils::Format("%i %% %s", percentFree, g_localizeStrings.Get(160).c_str()); + break; + case SYSTEM_USED_SPACE_PERCENT: + strRet = StringUtils::Format("%i %% %s", percentused, g_localizeStrings.Get(20162).c_str()); + break; + } + } + } + else + { + if (shortText) + strRet = "N/A"; + else + strRet = g_localizeStrings.Get(161); + } + return strRet; +} + +std::string CSysInfo::GetUserAgent() +{ + static std::string result; + if (!result.empty()) + return result; + + result = GetAppName() + "/" + (std::string)g_infoManager.GetLabel(SYSTEM_BUILD_VERSION_SHORT) + " ("; +#if defined(TARGET_WINDOWS) + result += GetKernelName() + " " + GetKernelVersion(); + BOOL bIsWow = FALSE; + if (IsWow64Process(GetCurrentProcess(), &bIsWow) && bIsWow) + result.append("; WOW64"); + else + { + SYSTEM_INFO si = {}; + GetSystemInfo(&si); + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) + result.append("; Win64; x64"); + else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) + result.append("; Win64; IA64"); + else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM) + result.append("; ARM"); + } +#elif defined(TARGET_DARWIN) +#if defined(TARGET_DARWIN_IOS) + std::string iDevStr(GetModelName()); // device model name with number of model version + size_t iDevStrDigit = iDevStr.find_first_of("0123456789"); + std::string iDev(iDevStr, 0, iDevStrDigit); // device model name without number + if (iDevStrDigit == 0) + iDev = "unknown"; + result += iDev + "; "; + std::string iOSVerison(GetOsVersion()); + size_t lastDotPos = iOSVerison.rfind('.'); + if (lastDotPos != std::string::npos && iOSVerison.find('.') != lastDotPos + && iOSVerison.find_first_not_of('0', lastDotPos + 1) == std::string::npos) + iOSVerison.erase(lastDotPos); + StringUtils::Replace(iOSVerison, '.', '_'); + if (iDev == "iPad" || iDev == "AppleTV") + result += "CPU OS "; + else + result += "CPU iPhone OS "; + result += iOSVerison + " like Mac OS X"; +#else + result += "Macintosh; "; + std::string cpuFam(GetBuildTargetCpuFamily()); + if (cpuFam == "x86") + result += "Intel "; + else if (cpuFam == "PowerPC") + result += "PPC "; + result += "Mac OS X "; + std::string OSXVersion(GetOsVersion()); + StringUtils::Replace(OSXVersion, '.', '_'); + result += OSXVersion; +#endif +#elif defined(TARGET_ANDROID) + result += "Linux; Android "; + std::string versionStr(GetOsVersion()); + const size_t verLen = versionStr.length(); + if (verLen >= 2 && versionStr.compare(verLen - 2, 2, ".0", 2) == 0) + versionStr.erase(verLen - 2); // remove last ".0" if any + result += versionStr; + std::string deviceInfo(GetModelName()); + + char buildId[PROP_VALUE_MAX]; + int propLen = __system_property_get("ro.build.id", buildId); + if (propLen > 0 && propLen <= PROP_VALUE_MAX) + { + if (!deviceInfo.empty()) + deviceInfo += " "; + deviceInfo += "Build/"; + deviceInfo.append(buildId, propLen); + } + + if (!deviceInfo.empty()) + result += "; " + deviceInfo; +#elif defined(TARGET_POSIX) + result += "X11; "; + struct utsname un; + if (uname(&un) == 0) + { + std::string cpuStr(un.machine); + if (cpuStr == "x86_64" && GetXbmcBitness() == 32) + cpuStr = "i686 (x86_64)"; + result += un.sysname; + result += " "; + result += cpuStr; + } + else + result += "Unknown"; +#else + result += "Unknown"; +#endif + result += ")"; + + if (GetAppName() != "Kodi") + result += " Kodi_Fork_" + GetAppName() + "/1.0"; // default fork number is '1.0', replace it with actual number if necessary + +#ifdef TARGET_LINUX + // Add distribution name + std::string linuxOSName(GetOsName(true)); + if (!linuxOSName.empty()) + result += " " + linuxOSName + "/" + GetOsVersion(); +#endif + +#ifdef TARGET_RASPBERRY_PI + result += " HW_RaspberryPi/1.0"; +#elif defined (TARGET_DARWIN_IOS) + std::string iDevVer; + if (iDevStrDigit == std::string::npos) + iDevVer = "0.0"; + else + iDevVer.assign(iDevStr, iDevStrDigit, std::string::npos); + StringUtils::Replace(iDevVer, ',', '.'); + result += " HW_" + iDev + "/" + iDevVer; +#endif + // add more device IDs here if needed. + // keep only one device ID in result! Form: + // result += " HW_" + "deviceID" + "/" + "1.0"; // '1.0' if device has no version + +#if defined(TARGET_ANDROID) + // Android has no CPU string by default, so add it as additional parameter + struct utsname un1; + if (uname(&un1) == 0) + { + std::string cpuStr(un1.machine); + StringUtils::Replace(cpuStr, ' ', '_'); + result += " Sys_CPU/" + cpuStr; + } +#endif + + result += " App_Bitness/" + StringUtils::Format("%d", GetXbmcBitness()); + + std::string fullVer(g_infoManager.GetLabel(SYSTEM_BUILD_VERSION)); + StringUtils::Replace(fullVer, ' ', '-'); + result += " Version/" + fullVer; + + return result; +} + +bool CSysInfo::IsAppleTV2() +{ +#if defined(TARGET_DARWIN) + return CDarwinUtils::IsAppleTV2(); +#else + return false; +#endif +} + +bool CSysInfo::HasVideoToolBoxDecoder() +{ +#if defined(HAVE_VIDEOTOOLBOXDECODER) + return CDarwinUtils::HasVideoToolboxDecoder(); +#else + return false; +#endif +} + +std::string CSysInfo::GetBuildTargetPlatformName(void) +{ +#if defined(TARGET_DARWIN_OSX) + return "OS X"; +#elif defined(TARGET_DARWIN_IOS_ATV2) + return "iOS ATV2"; +#elif defined(TARGET_DARWIN_IOS) + return "iOS"; +#elif defined(TARGET_FREEBSD) + return "FreeBSD"; +#elif defined(TARGET_ANDROID) + return "Android"; +#elif defined(TARGET_LINUX) + return "Linux"; +#elif defined(TARGET_WINDOWS) +#ifdef NTDDI_VERSION + return "Windows NT"; +#else // !NTDDI_VERSION + return "unknown Win32 platform"; +#endif // !NTDDI_VERSION +#else + return "unknown platform"; +#endif +} + +std::string CSysInfo::GetBuildTargetPlatformVersion(void) +{ +#if defined(TARGET_DARWIN_OSX) + return XSTR_MACRO(__MAC_OS_X_VERSION_MIN_REQUIRED); +#elif defined(TARGET_DARWIN_IOS) + return XSTR_MACRO(__IPHONE_OS_VERSION_MIN_REQUIRED); +#elif defined(TARGET_FREEBSD) + return XSTR_MACRO(__FreeBSD_version); +#elif defined(TARGET_ANDROID) + return "API level " XSTR_MACRO(__ANDROID_API__); +#elif defined(TARGET_LINUX) + return XSTR_MACRO(LINUX_VERSION_CODE); +#elif defined(TARGET_WINDOWS) +#ifdef NTDDI_VERSION + return XSTR_MACRO(NTDDI_VERSION); +#else // !NTDDI_VERSION + return "(unknown Win32 platform)"; +#endif // !NTDDI_VERSION +#else + return "(unknown platform)"; +#endif +} + +std::string CSysInfo::GetBuildTargetPlatformVersionDecoded(void) +{ +#if defined(TARGET_DARWIN_OSX) +#if defined(MAC_OS_X_VERSION_10_10) && __MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10 + if (__MAC_OS_X_VERSION_MIN_REQUIRED % 10) + return StringUtils::Format("version %d.%d", (__MAC_OS_X_VERSION_MIN_REQUIRED / 1000) % 100, (__MAC_OS_X_VERSION_MIN_REQUIRED / 10) % 100); + else + return StringUtils::Format("version %d.%d.%d", (__MAC_OS_X_VERSION_MIN_REQUIRED / 1000) % 100, + (__MAC_OS_X_VERSION_MIN_REQUIRED / 10) % 100, __MAC_OS_X_VERSION_MIN_REQUIRED % 10); +#else // defined(MAC_OS_X_VERSION_10_10) && __MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10 + if (__MAC_OS_X_VERSION_MIN_REQUIRED % 10) + return StringUtils::Format("version %d.%d", (__MAC_OS_X_VERSION_MIN_REQUIRED / 100) % 100, (__MAC_OS_X_VERSION_MIN_REQUIRED / 10) % 10); + else + return StringUtils::Format("version %d.%d.%d", (__MAC_OS_X_VERSION_MIN_REQUIRED / 100) % 100, + (__MAC_OS_X_VERSION_MIN_REQUIRED / 10) % 10, __MAC_OS_X_VERSION_MIN_REQUIRED % 10); +#endif // defined(MAC_OS_X_VERSION_10_10) && __MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10 +#elif defined(TARGET_DARWIN_IOS) + return StringUtils::Format("version %d.%d.%d", (__IPHONE_OS_VERSION_MIN_REQUIRED / 10000) % 100, + (__IPHONE_OS_VERSION_MIN_REQUIRED / 100) % 100, __IPHONE_OS_VERSION_MIN_REQUIRED % 100); +#elif defined(TARGET_FREEBSD) + // FIXME: should works well starting from FreeBSD 8.1 + static const int major = (__FreeBSD_version / 100000) % 100; + static const int minor = (__FreeBSD_version / 1000) % 100; + static const int Rxx = __FreeBSD_version % 1000; + if ((major < 9 && Rxx == 0) || + __FreeBSD_version == 900044 || __FreeBSD_version == 901000) + return StringUtils::Format("version %d.%d-RELEASE", major, minor); + if (Rxx >= 500) + return StringUtils::Format("version %d.%d-STABLE", major, minor); + + return StringUtils::Format("version %d.%d-CURRENT", major, minor); +#elif defined(TARGET_ANDROID) + return "API level " XSTR_MACRO(__ANDROID_API__); +#elif defined(TARGET_LINUX) + return StringUtils::Format("version %d.%d.%d", (LINUX_VERSION_CODE >> 16) & 0xFF , (LINUX_VERSION_CODE >> 8) & 0xFF, LINUX_VERSION_CODE & 0xFF); +#elif defined(TARGET_WINDOWS) +#ifdef NTDDI_VERSION + std::string version(StringUtils::Format("version %d.%d", int(NTDDI_VERSION >> 24) & 0xFF, int(NTDDI_VERSION >> 16) & 0xFF)); + if (SPVER(NTDDI_VERSION)) + version += StringUtils::Format(" SP%d", int(SPVER(NTDDI_VERSION))); + return version; +#else // !NTDDI_VERSION + return "(unknown Win32 platform)"; +#endif // !NTDDI_VERSION +#else + return "(unknown platform)"; +#endif +} + +std::string CSysInfo::GetBuildTargetCpuFamily(void) +{ +#if defined(__thumb__) || defined(_M_ARMT) + return "ARM (Thumb)"; +#elif defined(__arm__) || defined(_M_ARM) || defined (__aarch64__) + return "ARM"; +#elif defined(__mips__) || defined(mips) || defined(__mips) + return "MIPS"; +#elif defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) || \ + defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(_X86_) + return "x86"; +#elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || defined(__ppc__) || defined(__ppc64__) || defined(_M_PPC) + return "PowerPC"; +#else + return "unknown CPU family"; +#endif +} + +std::string CSysInfo::GetUsedCompilerNameAndVer(void) +{ +#if defined(__clang__) +#ifdef __clang_version__ + return "Clang " __clang_version__; +#else // ! __clang_version__ + return "Clang " XSTR_MACRO(__clang_major__) "." XSTR_MACRO(__clang_minor__) "." XSTR_MACRO(__clang_patchlevel__); +#endif //! __clang_version__ +#elif defined (__INTEL_COMPILER) + return "Intel Compiler " XSTR_MACRO(__INTEL_COMPILER); +#elif defined (__GNUC__) + std::string compilerStr; +#ifdef __llvm__ + /* Note: this will not detect GCC + DragonEgg */ + compilerStr = "llvm-gcc "; +#else // __llvm__ + compilerStr = "GCC "; +#endif // !__llvm__ + compilerStr += XSTR_MACRO(__GNUC__) "." XSTR_MACRO(__GNUC_MINOR__) "." XSTR_MACRO(__GNUC_PATCHLEVEL__); + return compilerStr; +#elif defined (_MSC_VER) + return "MSVC " XSTR_MACRO(_MSC_FULL_VER); +#else + return "unknown compiler"; +#endif +} + + +CJob *CSysInfo::GetJob() const +{ + return new CSysInfoJob(); +} + +void CSysInfo::OnJobComplete(unsigned int jobID, bool success, CJob *job) +{ + m_info = ((CSysInfoJob *)job)->GetData(); + CInfoLoader::OnJobComplete(jobID, success, job); +} diff --git a/src/utils/SystemInfo.h b/src/utils/SystemInfo.h new file mode 100644 index 0000000000..0a96625802 --- /dev/null +++ b/src/utils/SystemInfo.h @@ -0,0 +1,157 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "md5.h" +#include "InfoLoader.h" +#include "settings/lib/ISubSettings.h" +#include <string> + +#define KB (1024) // 1 KiloByte (1KB) 1024 Byte (2^10 Byte) +#define MB (1024*KB) // 1 MegaByte (1MB) 1024 KB (2^10 KB) +#define GB (1024*MB) // 1 GigaByte (1GB) 1024 MB (2^10 MB) +#define TB (1024*GB) // 1 TerraByte (1TB) 1024 GB (2^10 GB) + +#define MAX_KNOWN_ATTRIBUTES 46 + + +class CSysData +{ +public: + enum INTERNET_STATE { UNKNOWN, CONNECTED, DISCONNECTED }; + CSysData() + { + Reset(); + }; + + void Reset() + { + internetState = UNKNOWN; + }; + + std::string systemUptime; + std::string systemTotalUptime; + INTERNET_STATE internetState; + std::string videoEncoder; + std::string cpuFrequency; + std::string osVersionInfo; + std::string macAddress; + std::string batteryLevel; +}; + +class CSysInfoJob : public CJob +{ +public: + CSysInfoJob(); + + virtual bool DoWork(); + const CSysData &GetData() const; + + static CSysData::INTERNET_STATE GetInternetState(); +private: + static bool SystemUpTime(int iInputMinutes, int &iMinutes, int &iHours, int &iDays); + static double GetCPUFrequency(); + static std::string GetSystemUpTime(bool bTotalUptime); + static std::string GetCPUFreqInfo(); + static std::string GetMACAddress(); + static std::string GetVideoEncoder(); + static std::string GetBatteryLevel(); + + CSysData m_info; +}; + +class CSysInfo : public CInfoLoader, public ISubSettings +{ +public: + enum WindowsVersion + { + WindowsVersionUnknown = -1, // Undetected, unsupported Windows version or OS in not Windows + WindowsVersionVista, // Windows Vista, Windows Server 2008 + WindowsVersionWin7, // Windows 7, Windows Server 2008 R2 + WindowsVersionWin8, // Windows 8, Windows Server 2012 + WindowsVersionWin8_1, // Windows 8.1 + /* Insert new Windows versions here, when they'll be known */ + WindowsVersionFuture = 100 // Future Windows version, not known to code + }; + + CSysInfo(void); + virtual ~CSysInfo(); + + virtual bool Load(const TiXmlNode *settings); + virtual bool Save(TiXmlNode *settings) const; + + char MD5_Sign[32 + 1]; + + static const std::string& GetAppName(void); // the same name as CCompileInfo::GetAppName(), but const ref to std::string + + static std::string GetKernelName(bool emptyIfUnknown = false); + static std::string GetKernelVersionFull(void); // full version string, including "-generic", "-RELEASE" etc. + static std::string GetKernelVersion(void); // only digits with dots + static std::string GetOsName(bool emptyIfUnknown = false); + static std::string GetOsVersion(void); + static std::string GetOsPrettyNameWithVersion(void); + static std::string GetUserAgent(); + bool HasInternet(); + bool IsAppleTV2(); + bool HasVideoToolBoxDecoder(); + bool IsAeroDisabled(); + bool HasHW3DInterlaced(); + static bool IsWindowsVersion(WindowsVersion ver); + static bool IsWindowsVersionAtLeast(WindowsVersion ver); + static WindowsVersion GetWindowsVersion(); + static int GetKernelBitness(void); + static int GetXbmcBitness(void); + static const std::string& GetKernelCpuFamily(void); + std::string GetCPUModel(); + std::string GetCPUBogoMips(); + std::string GetCPUHardware(); + std::string GetCPURevision(); + std::string GetCPUSerial(); + static std::string GetManufacturerName(void); + static std::string GetModelName(void); + bool GetDiskSpace(const std::string& drive,int& iTotal, int& iTotalFree, int& iTotalUsed, int& iPercentFree, int& iPercentUsed); + std::string GetHddSpaceInfo(int& percent, int drive, bool shortText=false); + std::string GetHddSpaceInfo(int drive, bool shortText=false); + + int GetTotalUptime() const { return m_iSystemTimeTotalUp; } + void SetTotalUptime(int uptime) { m_iSystemTimeTotalUp = uptime; } + + static std::string GetBuildTargetPlatformName(void); + static std::string GetBuildTargetPlatformVersion(void); + static std::string GetBuildTargetPlatformVersionDecoded(void); + static std::string GetBuildTargetCpuFamily(void); + + static std::string GetUsedCompilerNameAndVer(void); + +protected: + virtual CJob *GetJob() const; + virtual std::string TranslateInfo(int info) const; + virtual void OnJobComplete(unsigned int jobID, bool success, CJob *job); + +private: + CSysData m_info; + static WindowsVersion m_WinVer; + int m_iSystemTimeTotalUp; // Uptime in minutes! + void Reset(); +}; + +extern CSysInfo g_sysinfo; + diff --git a/src/utils/TextSearch.cpp b/src/utils/TextSearch.cpp new file mode 100644 index 0000000000..ef0c6ae3aa --- /dev/null +++ b/src/utils/TextSearch.cpp @@ -0,0 +1,165 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "TextSearch.h" + +using namespace std; + +CTextSearch::CTextSearch(const CStdString &strSearchTerms, bool bCaseSensitive /* = false */, TextSearchDefault defaultSearchMode /* = SEARCH_DEFAULT_OR */) +{ + m_bCaseSensitive = bCaseSensitive; + ExtractSearchTerms(strSearchTerms, defaultSearchMode); +} + +CTextSearch::~CTextSearch(void) +{ + m_AND.clear(); + m_OR.clear(); + m_NOT.clear(); +} + +bool CTextSearch::IsValid(void) const +{ + return m_AND.size() > 0 || m_OR.size() > 0 || m_NOT.size() > 0; +} + +bool CTextSearch::Search(const CStdString &strHaystack) const +{ + if (strHaystack.empty() || !IsValid()) + return false; + + CStdString strSearch(strHaystack); + if (!m_bCaseSensitive) + StringUtils::ToLower(strSearch); + + /* check whether any of the NOT terms matches and return false if there's a match */ + for (unsigned int iNotPtr = 0; iNotPtr < m_NOT.size(); iNotPtr++) + { + if (strSearch.find(m_NOT.at(iNotPtr)) != std::string::npos) + return false; + } + + /* check whether at least one of the OR terms matches and return false if there's no match found */ + bool bFound(m_OR.size() == 0); + for (unsigned int iOrPtr = 0; iOrPtr < m_OR.size(); iOrPtr++) + { + if (strSearch.find(m_OR.at(iOrPtr)) != std::string::npos) + { + bFound = true; + break; + } + } + if (!bFound) + return false; + + /* check whether all of the AND terms match and return false if one of them wasn't found */ + for (unsigned int iAndPtr = 0; iAndPtr < m_AND.size(); iAndPtr++) + { + if (strSearch.find(m_AND[iAndPtr]) == std::string::npos) + return false; + } + + /* all ok, return true */ + return true; +} + +void CTextSearch::GetAndCutNextTerm(CStdString &strSearchTerm, CStdString &strNextTerm) +{ + CStdString strFindNext(" "); + + if (StringUtils::EndsWith(strSearchTerm, "\"")) + { + strSearchTerm.erase(0, 1); + strFindNext = "\""; + } + + size_t iNextPos = strSearchTerm.find(strFindNext); + if (iNextPos != std::string::npos) + { + strNextTerm = strSearchTerm.substr(0, iNextPos); + strSearchTerm.erase(0, iNextPos + 1); + } + else + { + strNextTerm = strSearchTerm; + strSearchTerm.clear(); + } +} + +void CTextSearch::ExtractSearchTerms(const CStdString &strSearchTerm, TextSearchDefault defaultSearchMode) +{ + CStdString strParsedSearchTerm(strSearchTerm); + StringUtils::Trim(strParsedSearchTerm); + + if (!m_bCaseSensitive) + StringUtils::ToLower(strParsedSearchTerm); + + bool bNextAND(defaultSearchMode == SEARCH_DEFAULT_AND); + bool bNextOR(defaultSearchMode == SEARCH_DEFAULT_OR); + bool bNextNOT(defaultSearchMode == SEARCH_DEFAULT_NOT); + + while (strParsedSearchTerm.length() > 0) + { + StringUtils::TrimLeft(strParsedSearchTerm); + + if (StringUtils::StartsWith(strParsedSearchTerm, "!") || StringUtils::StartsWithNoCase(strParsedSearchTerm, "not")) + { + CStdString strDummy; + GetAndCutNextTerm(strParsedSearchTerm, strDummy); + bNextNOT = true; + } + else if (StringUtils::StartsWith(strParsedSearchTerm, "+") || StringUtils::StartsWithNoCase(strParsedSearchTerm, "and")) + { + CStdString strDummy; + GetAndCutNextTerm(strParsedSearchTerm, strDummy); + bNextAND = true; + } + else if (StringUtils::StartsWith(strParsedSearchTerm, "|") || StringUtils::StartsWithNoCase(strParsedSearchTerm, "or")) + { + CStdString strDummy; + GetAndCutNextTerm(strParsedSearchTerm, strDummy); + bNextOR = true; + } + else + { + CStdString strTerm; + GetAndCutNextTerm(strParsedSearchTerm, strTerm); + if (strTerm.length() > 0) + { + if (bNextAND) + m_AND.push_back(strTerm); + else if (bNextOR) + m_OR.push_back(strTerm); + else if (bNextNOT) + m_NOT.push_back(strTerm); + } + else + { + break; + } + + bNextAND = (defaultSearchMode == SEARCH_DEFAULT_AND); + bNextOR = (defaultSearchMode == SEARCH_DEFAULT_OR); + bNextNOT = (defaultSearchMode == SEARCH_DEFAULT_NOT); + } + + StringUtils::TrimLeft(strParsedSearchTerm); + } +} diff --git a/src/utils/TextSearch.h b/src/utils/TextSearch.h new file mode 100644 index 0000000000..034bb9fd5f --- /dev/null +++ b/src/utils/TextSearch.h @@ -0,0 +1,49 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <vector> +#include "StringUtils.h" + +typedef enum TextSearchDefault +{ + SEARCH_DEFAULT_AND = 0, + SEARCH_DEFAULT_OR, + SEARCH_DEFAULT_NOT +} TextSearchDefault; + +class CTextSearch +{ +public: + CTextSearch(const CStdString &strSearchTerms, bool bCaseSensitive = false, TextSearchDefault defaultSearchMode = SEARCH_DEFAULT_OR); + virtual ~CTextSearch(void); + + bool Search(const CStdString &strHaystack) const; + bool IsValid(void) const; + +private: + static void GetAndCutNextTerm(CStdString &strSearchTerm, CStdString &strNextTerm); + void ExtractSearchTerms(const CStdString &strSearchTerm, TextSearchDefault defaultSearchMode); + + bool m_bCaseSensitive; + std::vector<CStdString> m_AND; + std::vector<CStdString> m_OR; + std::vector<CStdString> m_NOT; +}; diff --git a/src/utils/TimeSmoother.cpp b/src/utils/TimeSmoother.cpp new file mode 100644 index 0000000000..29143f51a7 --- /dev/null +++ b/src/utils/TimeSmoother.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ + + +#include "TimeSmoother.h" +#include <math.h> +#include <limits> +#include "utils/MathUtils.h" + +using namespace std; + +CTimeSmoother::CTimeSmoother() +: m_diffs(num_diffs), + m_periods(num_periods), + m_period(0), + m_lastFrameTime(0), + m_prevIn(num_stamps), + m_prevOut(num_stamps) +{ +} + +void CTimeSmoother::AddTimeStamp(unsigned int currentTime) +{ + double diff = m_prevIn.size() ? currentTime - m_prevIn.back() : currentTime; + if (diff) + m_diffs.push_back(diff); + + vector<double> bins; + BinData(m_diffs, bins, 0.15, 2); + + if (bins.size() && m_diffs.size() == num_diffs) + { + // have enough data to update our estimate + vector<unsigned int> binMultipliers; + GetGCDMultipliers(bins, binMultipliers, 2); + assert(binMultipliers.size() == bins.size()); + + vector<unsigned int> intRepresentation; + GetIntRepresentation(m_diffs, intRepresentation, bins, binMultipliers); + assert(intRepresentation.size() == m_diffs.size()); + + double period = EstimatePeriod(m_diffs, intRepresentation); + + // update our mean period + if (fabs(period - m_period) > m_period*0.1) + { // more than 10 % out - kill our previous running average + m_periods.clear(); + m_period = 0; + } + if (m_periods.size() < m_periods.capacity()) + m_period = (m_period * m_periods.size() + period) / (m_periods.size() + 1); + else + m_period += (period - m_periods[0]) / m_periods.size(); + m_periods.push_back(period); + } + double frameTime = EstimateFrameTime(currentTime); + m_prevIn.push_back(currentTime); + m_prevOut.push_back(frameTime); +} + +unsigned int CTimeSmoother::GetNextFrameTime(unsigned int currentTime) +{ + if (m_period) + { + double frameTime = EstimateFrameTime(currentTime); + // ensure we jump at least 1 period ahead of the last time we were called + if (frameTime < m_lastFrameTime + m_period) + frameTime = m_lastFrameTime + m_period; + // Return an unsigned int in ms, so wrap into that, and round. + // Don't use MathUtils::round_int as that's restricted to -2^30..2^30 + if (frameTime >= UINT_MAX) + frameTime = fmod(frameTime, UINT_MAX); + m_lastFrameTime = frameTime; + return (unsigned int)floor(frameTime + 0.5); + } + return currentTime; +} + +void CTimeSmoother::BinData(const boost::circular_buffer<double> &data, vector<double> &bins, const double threshold, const unsigned int minbinsize) +{ + if (!data.size()) + return; + + bins.clear(); + vector<unsigned int> counts; + + for (boost::circular_buffer<double>::const_iterator i = data.begin(); i != data.end(); ++i) + { + bool found = false; + for (unsigned int j = 0; j < bins.size(); ++j) + { + if (fabs(*i - bins[j]) < threshold*bins[j]) + { + found = true; + // update our bin mean and count + bins[j] = (bins[j]*counts[j] + *i)/(counts[j]+1); + counts[j]++; + break; + } + } + if (!found) + { + bins.push_back(*i); + counts.push_back(1); + } + } + if (minbinsize) + { + assert(counts.size() == bins.size()); + assert(counts.size()); + // filter out any bins that are not large enough (and any bins that aren't positive) + for (unsigned int j = 0; j < counts.size(); ) + { + if (counts[j] < minbinsize || bins[j] < 0.05) + { + bins.erase(bins.begin() + j); + counts.erase(counts.begin() + j); + } + else + j++; + } + } +} + +void CTimeSmoother::GetConvergent(double value, unsigned int &num, unsigned int &denom, const unsigned int maxnumden) +{ + assert(value >= 1); + + unsigned int old_n = 1, old_d = 0; + num = 0; denom = 1; + + // this while loop would typically be guaranteed to terminate as new_n, new_d are increasing non-negative + // integers as long as f >= 1. This in turn is guaranteed as f may never be zero as long as value > 1 and + // value - f < 1. Given that f = floor(value) this *should* always be true. + // However, as f is unsigned int and thus range restricted, we can not guarantee this, and hence + // break if value - f >= 1. + + // In addition, just to be on the safe side we don't allow the loop to run forever ;) + unsigned int maxLoops = 3 * maxnumden; + while (maxLoops--) + { + unsigned int f = (unsigned int)floor(value); + if (value - f >= 1) + break; // value out of range of unsigned int + unsigned int new_n = f * num + old_n; + unsigned int new_d = f * denom + old_d; + if (min(new_n, new_d) > maxnumden) + break; + old_n = num; old_d = denom; + num = new_n; denom = new_d; + if ((double)f == value) + break; + value = 1/(value - f); + } + // ensure num, denom are positive + assert(num > 0 && denom > 0); +} + +void CTimeSmoother::GetGCDMultipliers(const vector<double> &data, vector<unsigned int> &multipliers, const unsigned int maxminmult) +{ + vector<double>::const_iterator i = std::min_element(data.begin(), data.end()); + + multipliers.clear(); + + vector<unsigned int> num, denom; + for (vector<double>::const_iterator j = data.begin(); j != data.end(); ++j) + { + if (j != i) + { + unsigned int n, d; + GetConvergent(*j / *i, n, d, maxminmult); + num.push_back(n); + denom.push_back(d); + } + else + { + num.push_back(1); + denom.push_back(1); + } + } + vector<unsigned int>::const_iterator k = std::max_element(num.begin(), num.end()); + for (unsigned int i = 0; i < num.size(); ++i) + multipliers.push_back(denom[i] * (*k) / num[i]); +} + +void CTimeSmoother::GetIntRepresentation(const boost::circular_buffer<double> &data, vector<unsigned int> &intData, const vector<double> &bins, const vector<unsigned int> &intBins) +{ + intData.clear(); + for (boost::circular_buffer<double>::const_iterator i = data.begin(); i != data.end(); ++i) + { + double min_r2 = numeric_limits<double>::max(); + unsigned int min_j = 0; + for (unsigned int j = 0; j < bins.size(); ++j) + { + double d = MathUtils::round_int(*i/bins[j]); + double r2 = (*i - bins[j]*d)*(*i - bins[j]*d); + if (r2 < min_r2) + { + min_j = j; + min_r2 = r2; + } + } + intData.push_back(MathUtils::round_int(*i/bins[min_j])*intBins[min_j]); + } +} + +double CTimeSmoother::EstimatePeriod(const boost::circular_buffer<double> &data, const vector<unsigned int> &intData) +{ + double sxy = 0, sxx = 0; + for (unsigned int i = 0; i < data.size(); ++i) + { + sxy += intData[i] * data[i]; + sxx += intData[i] * intData[i]; + } + return sxy/sxx; +} + +double CTimeSmoother::EstimateFrameTime(unsigned int currentTime) +{ + assert(m_prevIn.size() == m_prevOut.size()); + if (m_period) + { + vector<double> outTimes; + for (unsigned int i = 0; i < m_prevIn.size(); ++i) + outTimes.push_back(m_prevOut[i] + m_period * MathUtils::round_int((currentTime - m_prevIn[i]) / m_period)); + sort(outTimes.begin(), outTimes.end()); + double outTime = outTimes[(outTimes.size()-1)/2]; + if (outTime < m_prevOut.back() + m_period) + outTime = m_prevOut.back() + m_period; + return outTime; + } + return currentTime; +} diff --git a/src/utils/TimeSmoother.h b/src/utils/TimeSmoother.h new file mode 100644 index 0000000000..f7f52e265b --- /dev/null +++ b/src/utils/TimeSmoother.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <vector> +#include <boost/circular_buffer.hpp> + +/*! \brief Class to smooth timestamps + + This class is designed to smooth reasonably regular timestamps into more regular timestamps. It was designed + to deal with per-frame timestamps, i.e. timestamps immediately following a back/front buffer flip. + + The main difficultes are: + 1. The timestamps are generally noisy. + 2. We do not know the vsync rate. + 3. We do not know whether or not we've missed flips, either intermittently or due to some cadence (eg 2/3/2/3 + frame durations) + + Primarily we're interested in what the vsync rate is, i.e. what is the duration of a single frame. To do this, + we solve the linear regression + + y_i = b_0 + b_1*x_i + e_i + + where y_i are the time stamps, x_i are the integer frame counts that these timestamps correspond to, b_0 is an offset, + b_1 is the frame duration, and e_i is an error term, assumed to be independent, identically distributed as + Normal(0, \sigma^2) for some fixed \sigma^2. + + The main difficulty is we do not know the predictors x_i's - all we know is that they're an increasing sequence of + integers. Thus, to solve this problem we must estimate both the x_i's, b_0 and b_1. We can eliminate b_0 by operating + on the differences t_i = y_i - y_{i-1} and k_i = x_i - x_{i-1} rather than y_i and x_i directly. Further, if we assume + that the error in the differences is additive gaussian white noise, by the maximum likelihood principle we may estimate + the k_i's and b_1 by minimizing the least squares problem: + + min_{b_1 \in R, k \in Z^M}||t - kb_1||_2^2 + + This is linearly separable, and the cost function can be concentrated to k, yielding + + min_{b_1 \in R}||t - b_1 round(t/b_1)||_2^2 ....(1) + + This allows the period b_1 to be estimated without knowledge of the x_i. The main problem with this is we require a + limit on the range for b_1, as clearly b_1 / j for any positive integer j also minimizes (1). This presents a problem + in the case where we have no knowledge what b_1 is. Furthermore, (1) is typically not smooth, with the concave portion + which contains it's minimum often being very small. Thus, in order to minimize (1) we need to evaluate it over a fairly + fine grid, between pre-defined limits. This is infeasible. + + Instead, we attack the problem by first attempting to estimate the k_i's. We do this by binning the differences t_i + into common categories and then computing the greatest common divisor of those bins. Given the data is noisy, the binning + procedure (BinData) has a tolerance for the bin size. To minimize the influence of timestamps that are out of the ordinary + we then discard any bins with only one value in them, thus leaving only binned values that are common. We then compute + an approximate greatest common divisor by computing the continued fraction expansion of the ratio of pairs of bins, to + produce integer divisors that produce a common divisor. We restrict the size of these integer divisors so that our divisor + is not too small - typically we're wanting to pick up simple cadences such as 1,2,1,2 and 2,3,2,3 but are unlikely to need + to detect 5,4,5,4 etc - at that point the CPU/GPU is limiting the frame rate so much that timing issues aren't likely to be + the largest issue. + + Once we have the GCD we can then compute the integers k_i. Given k_i we can then estimate b_1 using a standard simple + linear regression without intercept: + + t_i = b_1*x_i + e_i + + which can be solved in the standard manner. + + Our procedure is thus as follows: + 1. We collect K differences, where K is enough to ensure we get an estimate of the period, but not too large so that the + computation is unnecessarily long. + 2. We then keep a running average of our period that is fairly long in order to stabilise the period over time. + 3. To allow the running average to adapt quickly to framerate changes, if the newly computed period is much different + from our running average, we start a new running average. + 4. Given our period, we then estimate our noise-free timestamps by rounding to the nearest multiple of the period + from past time points. We use several time points and choose the median such point as our final point. This allows + for deciding whether to round up or round down in cases where it may not be particularly obvious. + 5. Finally, we ensure the timestamps always move forward by the period. + + This works well for the most part. Note that we're estimating a noise-free version of the last timestamp passed in, + we do not claim to be estimating the timestamp of the next frame time. Such an estimate is essentially indeterminant, as + the time to process the frame is non-constant. + */ + +class CTimeSmoother +{ +public: + CTimeSmoother(); + + /*! \brief Add a valid time stamp to the time smoother + This function will add a time stamp to the smoother and use it to update the current average frame rate + and estimate the current cadence. The next frame time can be retrieved using GetNextFrameTime. + \param currentTime the current time stamp to add to the smoother + \sa GetNextFrameTime + */ + void AddTimeStamp(unsigned int currentTime); + + /*! \brief Retreive an estimate of the next frame time, based on the current time + This function uses previously estimated average frame rates and the current cadence to estimate the next + frame time. + \param currentTime the current time stamp to use to estimate the next frame time + \return the estimated time the next frame will be displayed + \sa AddTimeStamp + */ + unsigned int GetNextFrameTime(unsigned int currentTime); + +protected: + /*! \brief Bin data into separate clusters, determined by a given threshold and minimum bin size. + \param data a circular buffer of data points + \param bins the bins to return + \param threshold the threshold to determine whether a data point is close to a given bin as a proportion of bin mean + \param minbinsize the minimum bin size of each bin + */ + static void BinData(const boost::circular_buffer<double> &data, std::vector<double> &bins, const double threshold, const unsigned int minbinsize); + + /*! \brief Given a real value, find a rational convergent + Uses a continued fraction expansion of value to determine the numerator and denominator of a rational convergent + where min(num, denom) does not exceed maxnumdem + \param value real data value. Must be no less than 1. + \param num [out] the numerator + \param denom [out] the denominator + \param maxnumden the maximal value of min(num, denom) + */ + static void GetConvergent(double value, unsigned int &num, unsigned int &denom, const unsigned int maxnumden); + + /*! \brief Given a set of data, find integer multipliers such that data[i] \sim quotient[i] * gcd(data) + Uses rational convergents to data[i]/min(data) to find integer multipliers to the (approximate) greatest common divisor + of the data. + \param data a vector of data + \param multipliers the output multipliers + \param maxminmult the maximal value of multiplier[min(data)] + \sa GetConvergent + */ + static void GetGCDMultipliers(const std::vector<double> &data, std::vector<unsigned int> &multipliers, const unsigned int maxminmult); + + /*! \brief Given a set of bins and integer values associated with each bin, find the integer representation of some data + This allows noisy data to be approximated by a set of clean data, and to compute the integer representation of that data. + \param data the data on which to compute the integer representation + \param intData [out] the integer representation of the data + \param bins the bins to use for approximating the data + \param intBins the integer representation of the bins + */ + static void GetIntRepresentation(const boost::circular_buffer<double> &data, std::vector<unsigned int> &intData, const std::vector<double> &bins, const std::vector<unsigned int> &intBins); + + /*! \brief Given a set of data, and an integer representation of that data, estimate the period of the data + Essentially we solve a linear regression d_i = \theta*z_i, where d_i is the original data, and z_i is the integer + representation of that data. Note that no intercept is included, so this is appropriate for operating on difference data + (such as the difference between timestamps following flipping of buffers during rendering - the integers represent the vsync + sequence). + \param data noisy data to estimate the period of + \param intData an integral representation of the data + \return the period of the data + */ + static double EstimatePeriod(const boost::circular_buffer<double> &data, const std::vector<unsigned int> &intData); + + /*! \brief Compute the next frame time + \param currentTime the current time + \return the next frame time + */ + double EstimateFrameTime(unsigned int currentTime); + +private: + static const unsigned int num_diffs = 10; ///< \brief number of differences to keep for evaluating period + static const unsigned int num_periods = 100; ///< \brief number of previous period estimates to use for the average period + static const unsigned int num_stamps = 3; ///< \brief number of previous time stamps to keep to optimize next time point + + boost::circular_buffer<double> m_diffs; ///< \brief the recently received differences + boost::circular_buffer<double> m_periods; ///< \brief the recently evaluated periods + double m_period; ///< \brief the running average of m_periods + + double m_lastFrameTime; ///< \brief the last frame time + + boost::circular_buffer<double> m_prevIn; ///< \brief the previous timestamps coming in + boost::circular_buffer<double> m_prevOut; ///< \brief the previous timestamps going out +}; diff --git a/src/utils/TimeUtils.cpp b/src/utils/TimeUtils.cpp new file mode 100644 index 0000000000..9f2e1350a8 --- /dev/null +++ b/src/utils/TimeUtils.cpp @@ -0,0 +1,109 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "TimeUtils.h" +#include "XBDateTime.h" +#include "threads/SystemClock.h" + +#if (defined HAVE_CONFIG_H) && (!defined TARGET_WINDOWS) + #include "config.h" +#endif + +#if defined(TARGET_DARWIN) +#include <mach/mach_time.h> +#include <CoreVideo/CVHostTime.h> +#elif defined(TARGET_WINDOWS) +#include <windows.h> +#else +#include <time.h> +#endif + +#include "TimeSmoother.h" + +int64_t CurrentHostCounter(void) +{ +#if defined(TARGET_DARWIN) + return( (int64_t)CVGetCurrentHostTime() ); +#elif defined(TARGET_WINDOWS) + LARGE_INTEGER PerformanceCount; + QueryPerformanceCounter(&PerformanceCount); + return( (int64_t)PerformanceCount.QuadPart ); +#else + struct timespec now; +#ifdef CLOCK_MONOTONIC_RAW + clock_gettime(CLOCK_MONOTONIC_RAW, &now); +#else + clock_gettime(CLOCK_MONOTONIC, &now); +#endif // CLOCK_MONOTONIC_RAW + return( ((int64_t)now.tv_sec * 1000000000L) + now.tv_nsec ); +#endif +} + +int64_t CurrentHostFrequency(void) +{ +#if defined(TARGET_DARWIN) + return( (int64_t)CVGetHostClockFrequency() ); +#elif defined(TARGET_WINDOWS) + LARGE_INTEGER Frequency; + QueryPerformanceFrequency(&Frequency); + return( (int64_t)Frequency.QuadPart ); +#else + return( (int64_t)1000000000L ); +#endif +} + +CTimeSmoother CTimeUtils::frameTimer; +unsigned int CTimeUtils::frameTime = 0; + +void CTimeUtils::UpdateFrameTime(bool flip) +{ + unsigned int currentTime = XbmcThreads::SystemClockMillis(); + if (flip) + frameTimer.AddTimeStamp(currentTime); + frameTime = frameTimer.GetNextFrameTime(currentTime); +} + +unsigned int CTimeUtils::GetFrameTime() +{ + return frameTime; +} + +CDateTime CTimeUtils::GetLocalTime(time_t time) +{ + CDateTime result; + + tm *local; +#ifdef HAVE_LOCALTIME_R + tm res = {}; + local = localtime_r(&time, &res); // Conversion to local time +#else + local = localtime(&time); // Conversion to local time +#endif + /* + * Microsoft implementation of localtime returns NULL if on or before epoch. + * http://msdn.microsoft.com/en-us/library/bf12f0hc(VS.80).aspx + */ + if (local) + result = *local; + else + result = time; // Use the original time as close enough. + + return result; +} diff --git a/src/utils/TimeUtils.h b/src/utils/TimeUtils.h new file mode 100644 index 0000000000..f8796d5c76 --- /dev/null +++ b/src/utils/TimeUtils.h @@ -0,0 +1,43 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <time.h> + +class CDateTime; +class CTimeSmoother; + +int64_t CurrentHostCounter(void); +int64_t CurrentHostFrequency(void); + +class CTimeUtils +{ +public: + static void UpdateFrameTime(bool flip); ///< update the frame time. Not threadsafe + static unsigned int GetFrameTime(); ///< returns the frame time in MS. Not threadsafe + static CDateTime GetLocalTime(time_t time); + +private: + static unsigned int frameTime; + static CTimeSmoother frameTimer; +}; + diff --git a/src/utils/TuxBoxUtil.cpp b/src/utils/TuxBoxUtil.cpp new file mode 100644 index 0000000000..70cc98e813 --- /dev/null +++ b/src/utils/TuxBoxUtil.cpp @@ -0,0 +1,1649 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +// +// GeminiServer +// + +#include "TuxBoxUtil.h" +#include "URIUtils.h" +#include "filesystem/CurlFile.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "Application.h" +#include "ApplicationMessenger.h" +#include "GUIInfoManager.h" +#include "video/VideoInfoTag.h" +#include "guilib/GUIWindowManager.h" +#include "dialogs/GUIDialogOK.h" +#include "dialogs/GUIDialogYesNo.h" +#include "filesystem/File.h" +#include "URL.h" +#include "settings/AdvancedSettings.h" +#include "FileItem.h" +#include "guilib/LocalizeStrings.h" +#include "StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "log.h" + +using namespace XFILE; +using namespace std; + +CTuxBoxUtil g_tuxbox; +CTuxBoxService g_tuxboxService; + +CTuxBoxService::CTuxBoxService() : CThread("TuxBoxService") +{ +} +CTuxBoxService::~CTuxBoxService() +{ +} +CTuxBoxUtil::CTuxBoxUtil(void) +{ + sCurSrvData.requested_audio_channel = 0; + vVideoSubChannel.mode = false; + sZapstream.initialized = false; + sZapstream.available = false; +} +CTuxBoxUtil::~CTuxBoxUtil(void) +{ +} +bool CTuxBoxService::Start() +{ + if(g_advancedSettings.m_iTuxBoxEpgRequestTime != 0) + { + StopThread(); + Create(false); + return true; + } + else + return false; +} +void CTuxBoxService::Stop() +{ + CLog::Log(LOGDEBUG, "%s - Stopping CTuxBoxService thread", __FUNCTION__); + StopThread(); +} +void CTuxBoxService::OnStartup() +{ + CLog::Log(LOGDEBUG, "%s - Starting CTuxBoxService thread", __FUNCTION__); + SetPriority( GetMinPriority() ); +} +void CTuxBoxService::OnExit() +{ + CThread::m_bStop = true; +} +bool CTuxBoxService::IsRunning() +{ + return !CThread::m_bStop; +} +void CTuxBoxService::Process() +{ + std::string strCurrentServiceName = g_tuxbox.sCurSrvData.service_name; + std::string strURL; + + while(!CThread::m_bStop && g_application.m_pPlayer->IsPlaying()) + { + strURL = g_application.CurrentFileItem().GetPath(); + if(!URIUtils::IsTuxBox(strURL)) + break; + + int iRequestTimer = g_advancedSettings.m_iTuxBoxEpgRequestTime *1000; //seconds + Sleep(iRequestTimer); + + CURL url(strURL); + if(g_tuxbox.GetHttpXML(url,"currentservicedata")) + { + CLog::Log(LOGDEBUG, "%s - receive current service data was successful", __FUNCTION__); + if(!strCurrentServiceName.empty()&& + strCurrentServiceName != "NULL" && + !g_tuxbox.sCurSrvData.service_name.empty() && + g_tuxbox.sCurSrvData.service_name != "-" && + !g_tuxbox.vVideoSubChannel.mode) + { + //Detect Channel Change + //We need to detect the channel on the TuxBox Device! + //On changing the channel on the device we will loose the stream and mplayer seems not able to detect it to stop + if (strCurrentServiceName != g_tuxbox.sCurSrvData.service_name && g_application.m_pPlayer->IsPlaying() && !g_tuxbox.sZapstream.available) + { + CLog::Log(LOGDEBUG," - ERROR: Non controlled channel change detected! Stopping current playing stream!"); + CApplicationMessenger::Get().MediaStop(); + break; + } + } + //Update infomanager from tuxbox client + g_infoManager.UpdateFromTuxBox(); + } + else + CLog::Log(LOGDEBUG, "%s - Could not receive current service data", __FUNCTION__); + } +} +bool CTuxBoxUtil::CreateNewItem(const CFileItem& item, CFileItem& item_new) +{ + //Build new Item + item_new.SetLabel(item.GetLabel()); + item_new.SetPath(item.GetPath()); + item_new.SetArt("thumb", item.GetArt("thumb")); + + if(g_tuxbox.GetZapUrl(item.GetPath(), item_new)) + { + if(vVideoSubChannel.mode) + vVideoSubChannel.current_name = item_new.GetLabel()+" ("+vVideoSubChannel.current_name+")"; + return true; + } + else + { + if(sBoxStatus.recording != "1") //Don't Show this Dialog, if the Box is in Recording mode! A previos YN Dialog was send to user! + { + CLog::Log(LOGDEBUG, "%s ---------------------------------------------------------", __FUNCTION__); + CLog::Log(LOGDEBUG, "%s - WARNING: Zaping Failed no Zap Point found!", __FUNCTION__); + CLog::Log(LOGDEBUG, "%s ---------------------------------------------------------", __FUNCTION__); + std::string strText = StringUtils::Format(g_localizeStrings.Get(21334).c_str(), item.GetLabel().c_str()); + CGUIDialogOK::ShowAndGetInput(21331, strText, 21333, 0); + } + } + return false; +} +bool CTuxBoxUtil::ParseBouquets(TiXmlElement *root, CFileItemList &items, CURL &url, std::string strFilter, std::string strChild) +{ + std::string strOptions; + TiXmlElement *pRootElement =root; + TiXmlNode *pNode = NULL; + TiXmlNode *pIt = NULL; + items.m_idepth =1; + // Get Options + strOptions = url.GetOptions(); + + if (!pRootElement) + { + CLog::Log(LOGWARNING, "%s - No %s found", __FUNCTION__, strChild.c_str()); + return false; + } + if (strFilter.empty()) + { + pNode = pRootElement->FirstChild(strChild.c_str()); + if (!pNode) + { + CLog::Log(LOGWARNING, "%s - No %s found", __FUNCTION__,strChild.c_str()); + return false; + } + while(pNode) + { + pIt = pNode->FirstChild("name"); + if (pIt) + { + std::string strItemName = pIt->FirstChild()->Value(); + + pIt = pNode->FirstChild("reference"); + if (pIt) + { + std::string strItemPath = pIt->FirstChild()->Value(); + // add. bouquets to item list! + CFileItemPtr pItem(new CFileItem); + pItem->m_bIsFolder = true; + pItem->SetLabel(strItemName); + { + CURL fileUrl; + fileUrl.SetProtocol("tuxbox"); + fileUrl.SetUserName(url.GetUserName()); + fileUrl.SetPassword(url.GetPassWord()); + fileUrl.SetHostName(url.GetHostName()); + if (url.GetPort() != 0 && url.GetPort() != 80) + fileUrl.SetPort(url.GetPort()); + fileUrl.SetOptions(url.GetOptions()); + fileUrl.SetOption("reference", strItemPath); + pItem->SetPath(fileUrl.Get()); + } + items.Add(pItem); + //DEBUG Log + CLog::Log(LOGDEBUG, "%s - Name: %s", __FUNCTION__,strItemName.c_str()); + CLog::Log(LOGDEBUG, "%s - Adress: %s", __FUNCTION__,pItem->GetPath().c_str()); + } + } + pNode = pNode->NextSibling(strChild.c_str()); + } + } + return true; +} +bool CTuxBoxUtil::ParseBouquetsEnigma2(TiXmlElement *root, CFileItemList &items, CURL &url, std::string& strFilter, std::string& strChild) +{ + TiXmlElement *pRootElement = root; + TiXmlNode *pNode = NULL; + TiXmlNode *pIt = NULL; + items.m_idepth = 1; + + if (!pRootElement) + { + CLog::Log(LOGWARNING, "%s - No %s found", __FUNCTION__, strChild.c_str()); + return false; + } + if (strFilter.empty()) + { + pNode = pRootElement->FirstChildElement("e2bouquet"); + if (!pNode) + { + CLog::Log(LOGWARNING, "%s - No %s found", __FUNCTION__,strChild.c_str()); + return false; + } + while(pNode) + { + CFileItemPtr pItem(new CFileItem); + pIt = pNode->FirstChildElement("e2servicereference"); + std::string strItemPath = pIt->FirstChild()->Value(); + pIt = pNode->FirstChildElement("e2servicename"); + std::string strItemName = pIt->FirstChild()->Value(); + pItem->m_bIsFolder = true; + pItem->SetLabel(strItemName); + { + CURL fileUrl; + fileUrl.SetProtocol("tuxbox"); + fileUrl.SetHostName(url.GetHostName()); + if (url.GetPort() != 0 && url.GetPort() != 80) + fileUrl.SetPort(url.GetPort()); + fileUrl.SetFileName(strItemName + "/"); + pItem->SetPath(fileUrl.Get()); + } + items.Add(pItem); + pNode = pNode->NextSiblingElement("e2bouquet"); + } + } + return true; +} +bool CTuxBoxUtil::ParseChannels(TiXmlElement *root, CFileItemList &items, CURL &url, std::string strFilter, std::string strChild) +{ + TiXmlElement *pRootElement =root; + TiXmlNode *pNode = NULL; + TiXmlNode *pIt = NULL; + TiXmlNode *pIta = NULL; + items.m_idepth =2; + + if (!pRootElement) + { + CLog::Log(LOGWARNING, "%s - No %ss found", __FUNCTION__,strChild.c_str()); + return false; + } + if(!strFilter.empty()) + { + pNode = pRootElement->FirstChild(strChild.c_str()); + if (!pNode) + { + CLog::Log(LOGWARNING, "%s - No %s found", __FUNCTION__,strChild.c_str()); + return false; + } + while(pNode) + { + pIt = pNode->FirstChild("name"); + if (pIt) + { + std::string strItemName = pIt->FirstChild()->Value(); + + pIt = pNode->FirstChild("reference"); + if (strFilter == pIt->FirstChild()->Value()) + { + pIt = pNode->FirstChild("service"); + if (!pIt) + { + CLog::Log(LOGWARNING, "%s - No service found", __FUNCTION__); + return false; + } + while(pIt) + { + pIta = pIt->FirstChild("name"); + if (pIta) + { + strItemName = pIta->FirstChild()->Value(); + + pIta = pIt->FirstChild("reference"); + if (pIta) + { + std::string strItemPath = pIta->FirstChild()->Value(); + // channel listing add. to item list! + CFileItemPtr pbItem(new CFileItem); + pbItem->m_bIsFolder = false; + pbItem->SetLabel(strItemName); + pbItem->SetLabelPreformated(true); + { + CURL fileUrl; + fileUrl.SetProtocol("tuxbox"); + fileUrl.SetUserName(url.GetUserName()); + fileUrl.SetPassword(url.GetPassWord()); + fileUrl.SetHostName(url.GetHostName()); + if (url.GetPort() != 0 && url.GetPort() != 80) + fileUrl.SetPort(url.GetPort()); + fileUrl.SetFileName("cgi-bin/zapTo"); + fileUrl.SetOption("path", strItemPath+".ts"); + pbItem->SetPath(fileUrl.Get()); + } + pbItem->SetArt("thumb", GetPicon(strItemName)); //Set Picon Image + + //DEBUG Log + CLog::Log(LOGDEBUG, "%s - Name: %s", __FUNCTION__,strItemName.c_str()); + CLog::Log(LOGDEBUG, "%s - Adress: %s", __FUNCTION__,pbItem->GetPath().c_str()); + + //add to the list + items.Add(pbItem); + } + } + pIt = pIt->NextSibling("service"); + } + } + } + pNode = pNode->NextSibling(strChild.c_str()); + } + return true; + } + return false; +} +bool CTuxBoxUtil::ParseChannelsEnigma2(TiXmlElement *root, CFileItemList &items, CURL &url, std::string& strFilter, std::string& strChild) +{ + TiXmlElement *pRootElement = root; + TiXmlNode *pNode = NULL; + TiXmlNode *pIt = NULL; + TiXmlNode *pIta = NULL; + TiXmlNode *pItb = NULL; + items.m_idepth = 2; + + if (!pRootElement) + { + CLog::Log(LOGWARNING, "%s - No %ss found", __FUNCTION__,strChild.c_str()); + return false; + } + if(!strFilter.empty()) + { + pNode = pRootElement->FirstChild(strChild.c_str()); + if (!pNode) + { + CLog::Log(LOGWARNING, "%s - No %s found", __FUNCTION__,strChild.c_str()); + return false; + } + while(pNode) + { + pIt = pNode->FirstChildElement("e2servicename"); + std::string bqtName = pIt->FirstChild()->Value(); + pIt = pNode->FirstChildElement("e2servicelist"); + pIta = pIt->FirstChildElement("e2service"); + while(pIta) + { + pItb = pIta->FirstChildElement("e2servicereference"); + std::string strItemPath = pItb->FirstChild()->Value(); + pItb = pIta->FirstChildElement("e2servicename"); + std::string strItemName = pItb->FirstChild()->Value(); + if(bqtName == url.GetShareName()) + { + CFileItemPtr pbItem(new CFileItem); + pbItem->m_bIsFolder = false; + pbItem->SetLabel(strItemName); + { + CURL fileUrl; + fileUrl.SetProtocol("http"); + fileUrl.SetHostName(url.GetHostName()); + fileUrl.SetPort(8001); + fileUrl.SetFileName(strItemPath); + pbItem->SetPath(fileUrl.Get()); + } + pbItem->SetMimeType("video/mpeg2"); + items.Add(pbItem); + CLog::Log(LOGDEBUG, "%s - Name: %s", __FUNCTION__,strItemName.c_str()); + CLog::Log(LOGDEBUG, "%s - Adress: %s", __FUNCTION__,pbItem->GetPath().c_str()); + } + pIta = pIta->NextSiblingElement("e2service"); + } + pNode = pNode->NextSiblingElement("e2bouquet"); + } + } + return true; +} +bool CTuxBoxUtil::ZapToUrl(CURL url, const std::string &pathOption) +{ + // send Zap + //Extract the ZAP to Service String + //Remove the ".ts" + std::string strFilter = pathOption.substr(0, pathOption.size() - 3); + //Get the Service Name + + // Create ZAP URL + CURL urlx; + urlx.SetProtocol("http"); + urlx.SetUserName(url.GetUserName()); + urlx.SetPassword(url.GetPassWord()); + urlx.SetHostName(url.GetHostName()); + if (url.GetPort() != 0 && url.GetPort() != 80) + urlx.SetPort(url.GetPort()); + CURL postUrl(urlx); + postUrl.SetFileName("cgi-bin/zapTo"); + postUrl.SetOption("path", strFilter); + + //Check Recording State! + if(GetHttpXML(urlx,"boxstatus")) + { + if(sBoxStatus.recording == "1") + { + CLog::Log(LOGDEBUG, "%s ---------------------------------------------------------", __FUNCTION__); + CLog::Log(LOGDEBUG, "%s - WARNING: Device is Recording! Record Mode is: %s", __FUNCTION__,sBoxStatus.recording.c_str()); + CLog::Log(LOGDEBUG, "%s ---------------------------------------------------------", __FUNCTION__); + CGUIDialogYesNo* dialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + if (dialog) + { + //Target TuxBox is in Recording mode! Are you sure to stream ?YN + dialog->SetHeading(21331); + dialog->SetLine( 0, 21332); + dialog->SetLine( 1, 21335); + dialog->SetLine( 2, "" ); + dialog->DoModal(); + if (!dialog->IsConfirmed()) + { + //DialogYN = NO -> Return false! + return false; + } + } + } + } + + //Send ZAP Command + CCurlFile http; + if(http.Open(postUrl)) + { + //DEBUG LOG + CLog::Log(LOGDEBUG, "%s - Zapped to: %s", __FUNCTION__,postUrl.Get().c_str()); + + //Request StreamInfo + GetHttpXML(urlx,"streaminfo"); + + //Extract StreamInformations + int iRetry=0; + //PMT must be a valid value to be sure that the ZAP is OK and we can stream! + while(sStrmInfo.pmt == "ffffffffh" && iRetry!=10) //try 10 Times + { + CLog::Log(LOGDEBUG, "%s - Requesting STREAMINFO! TryCount: %i!", __FUNCTION__,iRetry); + GetHttpXML(urlx,"streaminfo"); + iRetry=iRetry+1; + Sleep(200); + } + + // PMT Not Valid? Try Time 10 reached, checking for advancedSettings m_iTuxBoxZapWaitTime + if(sStrmInfo.pmt == "ffffffffh" && g_advancedSettings.m_iTuxBoxZapWaitTime > 0 ) + { + iRetry = 0; + CLog::Log(LOGDEBUG, "%s - Starting TuxBox ZapWaitTimer!", __FUNCTION__); + while(sStrmInfo.pmt == "ffffffffh" && iRetry!=10) //try 10 Times + { + CLog::Log(LOGDEBUG, "%s - Requesting STREAMINFO! TryCount: %i!", __FUNCTION__,iRetry); + GetHttpXML(urlx,"streaminfo"); + iRetry=iRetry+1; + if(sStrmInfo.pmt == "ffffffffh") + { + CLog::Log(LOGERROR, "%s - STREAMINFO ERROR! Could not receive all data, TryCount: %i!", __FUNCTION__,iRetry); + CLog::Log(LOGERROR, "%s - PMT is: %s (not a Valid Value)! Waiting %i sec.", __FUNCTION__,sStrmInfo.pmt.c_str(), g_advancedSettings.m_iTuxBoxZapWaitTime); + Sleep(g_advancedSettings.m_iTuxBoxZapWaitTime*1000); + } + } + } + + //PMT Failed! No StreamInformations availible.. closing stream + if (sStrmInfo.pmt == "ffffffffh") + { + CLog::Log(LOGERROR, "%s-------------------------------------------------------------", __FUNCTION__); + CLog::Log(LOGERROR, "%s - STREAMINFO ERROR! Could not receive all data, TryCount: %i!", __FUNCTION__,iRetry); + CLog::Log(LOGERROR, "%s - PMT is: %s (not a Valid Value)! There is nothing to Stream!", __FUNCTION__,sStrmInfo.pmt.c_str()); + CLog::Log(LOGERROR, "%s - The Stream will stopped!", __FUNCTION__); + CLog::Log(LOGERROR, "%s-------------------------------------------------------------", __FUNCTION__); + return false; + } + //Currentservicedata + GetHttpXML(urlx,"currentservicedata"); + //boxstatus + GetHttpXML(urlx,"boxstatus"); + //boxinfo + GetHttpXML(urlx,"boxinfo"); + //serviceepg + GetHttpXML(urlx,"serviceepg"); + return true; + } + return false; +} +bool CTuxBoxUtil::GetZapUrl(const std::string& strPath, CFileItem &items ) +{ + CURL url(strPath); + std::string strOptions = url.GetOptions(); + if (strOptions.empty()) + return false; + + if (url.HasOption("path")) + { + if(ZapToUrl(url, url.GetOption("path"))) + { + //Check VideoSubChannels + if(GetHttpXML(url,"currentservicedata")) //Update Currentservicedata + { + //Detect VideoSubChannels + std::string strVideoSubChannelName, strVideoSubChannelPID; + if(GetVideoSubChannels(strVideoSubChannelName,strVideoSubChannelPID )) + { + // new videosubchannel selected! settings options to new video zap id + // zap again now to new videosubchannel + if(ZapToUrl(url, strVideoSubChannelPID + ".ts")) + { + vVideoSubChannel.mode = true; + vVideoSubChannel.current_name = strVideoSubChannelName; + } + } + else + vVideoSubChannel.mode= false; + } + + std::string strVideoStream; + std::string strLabel, strLabel2; + std::string strAudioChannelPid; + std::string strAPids; + sAudioChannel sRequestedAudioChannel; + + if (!GetGUIRequestedAudioChannel(sRequestedAudioChannel)) + { + if (g_advancedSettings.m_bTuxBoxSendAllAPids && sCurSrvData.audio_channels.size() > 1) + { + for (vector<sAudioChannel>::iterator sChannel = sCurSrvData.audio_channels.begin(); sChannel!=sCurSrvData.audio_channels.end(); ++sChannel) + { + if (sChannel->pid != sRequestedAudioChannel.pid && sChannel->pid.size() >= 4) + strAPids += "," + sChannel->pid.substr(sChannel->pid.size() - 4); + } + CLog::Log(LOGDEBUG, "%s - Sending all audio pids: %s%s", __FUNCTION__, strAudioChannelPid.c_str(), strAPids.c_str()); + + strVideoStream = StringUtils::Format("0,%s,%s,%s%s", + sStrmInfo.pmt.substr(0, 4).c_str(), + sStrmInfo.vpid.substr(0, 4).c_str(), + sStrmInfo.apid.substr(0, 4).c_str(), + strAPids.c_str()); + } + else + strVideoStream = StringUtils::Format("0,%s,%s,%s", + sStrmInfo.pmt.substr(0, 4).c_str(), + sStrmInfo.vpid.substr(0, 4).c_str(), + sStrmInfo.apid.substr(0, 4).c_str()); + } + else + strVideoStream = StringUtils::Format("0,%s,%s,%s", + sStrmInfo.pmt.substr(0, 4).c_str(), + sStrmInfo.vpid.substr(0, 4).c_str(), + strAudioChannelPid.substr(0, 4).c_str()); + + CURL streamURL; + streamURL.SetProtocol("http"); + streamURL.SetUserName(url.GetUserName()); + streamURL.SetPassword(url.GetPassWord()); + streamURL.SetHostName(url.GetHostName()); + streamURL.SetPort(g_advancedSettings.m_iTuxBoxStreamtsPort); + streamURL.SetFileName(strVideoStream.c_str()); + + if (!g_tuxbox.sZapstream.initialized) + g_tuxbox.InitZapstream(strPath); + + // Use the Zapstream service when available. + if (g_tuxbox.sZapstream.available) + { + sAudioChannel sSelectedAudioChannel; + if (GetRequestedAudioChannel(sSelectedAudioChannel)) + { + if (sSelectedAudioChannel.pid != sStrmInfo.apid) + { + if (SetAudioChannel(strPath, sSelectedAudioChannel)) + CLog::Log(LOGDEBUG, "%s - Zapstream: Requested audio channel is %s, pid %s.", __FUNCTION__, sSelectedAudioChannel.name.c_str(), sSelectedAudioChannel.pid.c_str()); + } + } + streamURL.SetFileName(""); + streamURL.SetPort(g_advancedSettings.m_iTuxBoxZapstreamPort); + } + + if (g_application.m_pPlayer->IsPlaying() && !g_tuxbox.sZapstream.available) + CApplicationMessenger::Get().MediaStop(); + + strLabel = StringUtils::Format("%s: %s %s-%s", + items.GetLabel().c_str(), + sCurSrvData.current_event_date.c_str(), + sCurSrvData.current_event_start.c_str(), + sCurSrvData.current_event_start.c_str()); + strLabel2 = StringUtils::Format("%s", sCurSrvData.current_event_description.c_str()); + + // Set Event details + std::string strGenre, strTitle; + strGenre = StringUtils::Format("%s %s - (%s: %s)", + g_localizeStrings.Get(143).c_str(), sCurSrvData.current_event_description.c_str(), + g_localizeStrings.Get(209).c_str(), sCurSrvData.next_event_description.c_str()); + strTitle = StringUtils::Format("%s", sCurSrvData.current_event_details.c_str()); + int iDuration = atoi(sCurSrvData.current_event_duration.c_str()); + + items.GetVideoInfoTag()->m_genre = StringUtils::Split(strGenre, g_advancedSettings.m_videoItemSeparator); // VIDEOPLAYER_GENRE: current_event_description (Film Name) + items.GetVideoInfoTag()->m_strTitle = strTitle; // VIDEOPLAYER_TITLE: current_event_details (Film beschreibung) + items.GetVideoInfoTag()->m_duration = iDuration; //VIDEOPLAYER_DURATION: current_event_duration (laufzeit in sec.) + + items.SetPath(streamURL.Get()); + items.m_iDriveType = url.GetPort(); // Dirty Hack! But i need to hold the Port ;) + items.SetLabel(items.GetLabel()); // VIDEOPLAYER_DIRECTOR: service_name (Program Name) + items.SetLabel2(sCurSrvData.current_event_description); // current_event_description (Film Name) + items.m_bIsFolder = false; + items.SetMimeType("video/x-mpegts"); + return true; + } + } + return false; +} + +// Notice: Zapstream is a streamts enhancement from PLi development team. +// If you are using a non-PLi based image you might not have Zapstream installed. +bool CTuxBoxUtil::InitZapstream(const std::string& strPath) +{ + CURL url(strPath); + CCurlFile http; + int iTryConnect = 0; + int iTimeout = 2; + + g_tuxbox.sZapstream.initialized = true; + + if (!g_advancedSettings.m_bTuxBoxZapstream) + { + CLog::Log(LOGDEBUG, "%s - Zapstream is disabled in advancedsettings.xml.", __FUNCTION__); + return g_tuxbox.sZapstream.available = false; + } + + url.SetProtocol("http"); + url.SetFileName(""); + url.SetOptions(""); + url.SetPort(g_advancedSettings.m_iTuxBoxZapstreamPort); + + while (iTryConnect < 3) + { + http.SetTimeout(iTimeout); + + if (http.Open(url)) + { + http.Close(); + CHttpHeader h = http.GetHttpHeader(); + std::string strValue = h.GetValue("server"); + + if (strValue.find("zapstream") != std::string::npos) + { + CLog::Log(LOGDEBUG, "%s - Zapstream is available on port %i.", __FUNCTION__, g_advancedSettings.m_iTuxBoxZapstreamPort); + return g_tuxbox.sZapstream.available = true; + } + } + + iTryConnect++; + iTimeout+=5; + } + + CLog::Log(LOGDEBUG, "%s - Zapstream is not available on port %i.", __FUNCTION__, g_advancedSettings.m_iTuxBoxZapstreamPort); + return false; +} +bool CTuxBoxUtil::SetAudioChannel( const std::string& strPath, const AUDIOCHANNEL& sAC ) +{ + CURL url(strPath); + CCurlFile http; + int iTryConnect = 0; + int iTimeout = 2; + + url.SetProtocol("http"); + url.SetFileName("cgi-bin/setAudio"); + url.SetOptions("?channel=1&language=" + sAC.pid); + url.SetPort(80); + + g_tuxbox.sZapstream.initialized = true; + + while (iTryConnect < 3) + { + http.SetTimeout(iTimeout); + + if (http.Open(url)) + { + http.Close(); + return true; + } + + iTryConnect++; + iTimeout+=5; + } + + return false; +} +bool CTuxBoxUtil::GetHttpXML(CURL url,std::string strRequestType) +{ + // Check and Set URL Request Option + if(!strRequestType.empty()) + { + if(strRequestType == "streaminfo") + { + url.SetOptions("xml/streaminfo"); + } + else if(strRequestType == "currentservicedata") + { + url.SetOptions("xml/currentservicedata"); + } + else if(strRequestType == "boxstatus") + { + url.SetOptions("xml/boxstatus"); + } + else if(strRequestType == "boxinfo") + { + url.SetOptions("xml/boxinfo"); + } + else if(strRequestType == "serviceepg") + { + url.SetOptions("xml/serviceepg"); + } + else + { + CLog::Log(LOGERROR, "%s - Request Type is not defined! You requested: %s", __FUNCTION__,strRequestType.c_str()); + return false; + } + } + else + { + CLog::Log(LOGERROR, "%s - strRequestType Request Type is Empty!", __FUNCTION__); + return false; + } + + // Clean Up the URL, so we have a clean request! + url.SetFileName(""); + + //Open + CCurlFile http; + http.SetTimeout(20); + if(http.Open(url)) + { + int size_read = 0; + int size_total = (int)http.GetLength(); + + if(size_total > 0) + { + // read response from server into string buffer + std::string strTmp; + strTmp.reserve(size_total); + char buffer[16384]; + while( (size_read = http.Read( buffer, sizeof(buffer)-1) ) > 0 ) + { + buffer[size_read] = 0; + strTmp += buffer; + } + + // parse returned xml + CXBMCTinyXML doc; + TiXmlElement *XMLRoot=NULL; + StringUtils::Replace(strTmp, "></",">-</"); //FILL EMPTY ELEMENTS WITH "-"! + doc.Parse(strTmp, http.GetServerReportedCharset()); + strTmp.clear(); + + XMLRoot = doc.RootElement(); + std::string strRoot = XMLRoot->Value(); + if( strRoot == "streaminfo") + return StreamInformations(XMLRoot); + if(strRoot == "currentservicedata") + return CurrentServiceData(XMLRoot); + if(strRoot == "boxstatus") + return BoxStatus(XMLRoot); + if(strRoot == "boxinfo") + return BoxInfo(XMLRoot); + if(strRoot == "serviceepg" || + strRoot == "service_epg") + return ServiceEPG(XMLRoot); + + CLog::Log(LOGERROR, "%s - Unable to parse xml", __FUNCTION__); + CLog::Log(LOGERROR, "%s - Request String: %s", __FUNCTION__,strRoot.c_str()); + return false; + } + else + { + CLog::Log(LOGERROR, "%s - http length is invalid!", __FUNCTION__); + return false; + } + } + CLog::Log(LOGERROR, "%s - Open URL Failed! Unable to get XML structure", __FUNCTION__); + return false; +} +bool CTuxBoxUtil::StreamInformations(TiXmlElement *pRootElement) +{ + /* + Sample: + http://192.168.0.110:31339/0,0065,01ff,0200,0201,0203,01ff + + http://getIP:31339/0,pmtpid,vpid,apid,apids,apids,pcrpid; + + vpid,pmtpid,pcrpid,apid --> xml/streaminfo + apids --> /xml/currentservicedata + + apid: is the defined audio stream! + Normal Stereo: http://192.168.0.110:31339/0,0065,01ff,0200,0201,0203,01ff + Normal English: http://192.168.0.110:31339/0,0065,01ff,0201,,,01ff + Normal DD5.1/AC3: http://192.168.0.110:31339/0,0065,01ff,0203,,,01ff + */ + + TiXmlNode *pNode = NULL; + TiXmlNode *pIt = NULL; + if(pRootElement != NULL) + { + pNode = pRootElement->FirstChild("frontend"); + if (pNode) + { + sStrmInfo.frontend = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Frontend: %s", __FUNCTION__, sStrmInfo.frontend.c_str()); + } + pNode = pRootElement->FirstChild("service"); + if (pNode) + { + CLog::Log(LOGDEBUG, "%s - Service", __FUNCTION__); + pIt = pNode->FirstChild("name"); + if (pIt) + { + sStrmInfo.service_name = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Name: %s", __FUNCTION__, sStrmInfo.service_name.c_str()); + } + pIt = pNode->FirstChild("reference"); + if (pIt) + { + sStrmInfo.service_reference = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Reference: %s", __FUNCTION__, sStrmInfo.service_reference.c_str()); + } + } + + pNode = pRootElement->FirstChild("provider"); + if(pNode && pNode->FirstChild()) + { + sStrmInfo.provider= pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Provider: %s", __FUNCTION__, sStrmInfo.provider.c_str()); + } + pNode = pRootElement->FirstChild("vpid"); + if (pNode) + { + sStrmInfo.vpid = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Vpid: %s", __FUNCTION__, sStrmInfo.vpid.c_str()); + } + pNode = pRootElement->FirstChild("apid"); + if (pNode) + { + sStrmInfo.apid = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Apid: %s", __FUNCTION__, sStrmInfo.apid.c_str()); + } + pNode = pRootElement->FirstChild("pcrpid"); + if (pNode) + { + sStrmInfo.pcrpid = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - PcrPid: %s", __FUNCTION__, sStrmInfo.pcrpid.c_str()); + } + pNode = pRootElement->FirstChild("tpid"); + if (pNode) + { + sStrmInfo.tpid = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Tpid: %s", __FUNCTION__, sStrmInfo.tpid.c_str()); + } + pNode = pRootElement->FirstChild("tsid"); + if (pNode) + { + sStrmInfo.tsid = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Tsid: %s", __FUNCTION__, sStrmInfo.tsid.c_str()); + } + pNode = pRootElement->FirstChild("onid"); + if (pNode) + { + sStrmInfo.onid = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Onid: %s", __FUNCTION__, sStrmInfo.onid.c_str()); + } + pNode = pRootElement->FirstChild("sid"); + if (pNode) + { + sStrmInfo.sid = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Sid: %s", __FUNCTION__, sStrmInfo.sid.c_str()); + } + pNode = pRootElement->FirstChild("pmt"); + if (pNode) + { + sStrmInfo.pmt = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Pmt: %s", __FUNCTION__, sStrmInfo.pmt.c_str()); + } + pNode = pRootElement->FirstChild("video_format"); + if (pNode) + { + sStrmInfo.video_format = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Video Format: %s", __FUNCTION__, sStrmInfo.video_format.c_str()); + } + pNode = pRootElement->FirstChild("supported_crypt_systems"); + if (pNode) + { + sStrmInfo.supported_crypt_systems = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Supported Crypt Systems: %s", __FUNCTION__, sStrmInfo.supported_crypt_systems.c_str()); + } + pNode = pRootElement->FirstChild("used_crypt_systems"); + if (pNode) + { + sStrmInfo.used_crypt_systems = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Used Crypt Systems: %s", __FUNCTION__, sStrmInfo.used_crypt_systems.c_str()); + } + pNode = pRootElement->FirstChild("satellite"); + if (pNode) + { + sStrmInfo.satellite = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Satellite: %s", __FUNCTION__, sStrmInfo.satellite.c_str()); + } + pNode = pRootElement->FirstChild("frequency"); + if (pNode) + { + sStrmInfo.frequency = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Frequency: %s", __FUNCTION__, sStrmInfo.frequency.c_str()); + } + pNode = pRootElement->FirstChild("symbol_rate"); + if (pNode) + { + sStrmInfo.symbol_rate = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Symbol Rate: %s", __FUNCTION__, sStrmInfo.symbol_rate.c_str()); + } + pNode = pRootElement->FirstChild("polarisation"); + if (pNode) + { + sStrmInfo.polarisation = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Polarisation: %s", __FUNCTION__, sStrmInfo.polarisation.c_str()); + } + pNode = pRootElement->FirstChild("inversion"); + if (pNode) + { + sStrmInfo.inversion = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Inversion: %s", __FUNCTION__, sStrmInfo.inversion.c_str()); + } + pNode = pRootElement->FirstChild("fec"); + if (pNode) + { + sStrmInfo.fec = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Fec: %s", __FUNCTION__, sStrmInfo.fec.c_str()); + } + pNode = pRootElement->FirstChild("snr"); + if (pNode) + { + sStrmInfo.snr = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Snr: %s", __FUNCTION__, sStrmInfo.snr.c_str()); + } + pNode = pRootElement->FirstChild("agc"); + if (pNode) + { + sStrmInfo.agc = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Agc: %s", __FUNCTION__, sStrmInfo.agc.c_str()); + } + pNode = pRootElement->FirstChild("ber"); + if (pNode) + { + sStrmInfo.ber = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - ber: %s", __FUNCTION__, sStrmInfo.ber.c_str()); + } + pNode = pRootElement->FirstChild("lock"); + if (pNode) + { + sStrmInfo.lock = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Lock: %s", __FUNCTION__, sStrmInfo.lock.c_str()); + } + pNode = pRootElement->FirstChild("sync"); + if (pNode) + { + sStrmInfo.sync = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Sync: %s", __FUNCTION__, sStrmInfo.sync.c_str()); + } + return true; + } + return false; +} +bool CTuxBoxUtil::CurrentServiceData(TiXmlElement *pRootElement) +{ + TiXmlNode *pNode = NULL; + TiXmlNode *pIt = NULL; + TiXmlNode *pVal = NULL; + if(pRootElement) + { + CLog::Log(LOGDEBUG, "%s - Current Service Data", __FUNCTION__); + pNode = pRootElement->FirstChild("service"); + if (pNode) + { + CLog::Log(LOGDEBUG, "%s - Service", __FUNCTION__); + pIt = pNode->FirstChild("name"); + if (pIt) + { + sCurSrvData.service_name = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Service Name: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("reference"); + if (pIt) + { + sCurSrvData.service_reference = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Service Reference: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + } + + pNode = pRootElement->FirstChild("audio_channels"); + if (pNode) + { + CLog::Log(LOGDEBUG, "%s - Audio Channels", __FUNCTION__); + int i = 0; + + pIt = pNode->FirstChild("channel"); + sCurSrvData.audio_channels.clear(); + + while(pIt) + { + sAudioChannel newChannel; + + pVal = pIt->FirstChild("pid"); + if(pVal) + newChannel.pid = pVal->FirstChild()->Value(); + + pVal = pIt->FirstChild("selected"); + if(pVal) + newChannel.selected = pVal->FirstChild()->Value(); + + pVal = pIt->FirstChild("name"); + if(pVal) + newChannel.name = pVal->FirstChild()->Value(); + + CLog::Log(LOGDEBUG, "%s - Audio Channels: Channel %i -> PID: %s Selected: %s Name: %s", __FUNCTION__, i, newChannel.pid.c_str(), newChannel.selected.c_str(), newChannel.name.c_str() ); + + i=i+1; + sCurSrvData.audio_channels.push_back( newChannel ); + pIt = pIt->NextSibling("channel"); + } + } + pNode = pRootElement->FirstChild("audio_track"); + if (pNode) + { + sCurSrvData.audio_track = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Audio Track: %s", __FUNCTION__, pNode->FirstChild()->Value() ); + } + pNode = pRootElement->FirstChild("video_channels"); + if (pNode) + { + CLog::Log(LOGDEBUG, "%s - Video Channels", __FUNCTION__); + pIt = pNode->FirstChild("service"); + if (pIt) + { + vVideoSubChannel.name.clear(); + vVideoSubChannel.reference.clear(); + vVideoSubChannel.selected.clear(); + int i = 0; + while(pIt) + { + pVal = pIt->FirstChild("name"); + if(pVal) + { + vVideoSubChannel.name.push_back(pVal->FirstChild()->Value()); + CLog::Log(LOGDEBUG, "%s - Video Sub Channel %i: Name: %s", __FUNCTION__, i,pVal->FirstChild()->Value()); + } + pVal = pIt->FirstChild("reference"); + if(pVal) + { + vVideoSubChannel.reference.push_back(pVal->FirstChild()->Value()); + CLog::Log(LOGDEBUG, "%s - Video Sub Channel %i: Reference: %s", __FUNCTION__, i,pVal->FirstChild()->Value()); + } + pVal = pIt->FirstChild("selected"); + if(pVal) + { + vVideoSubChannel.selected.push_back(pVal->FirstChild()->Value()); + CLog::Log(LOGDEBUG, "%s - Video Sub Channel %i: Selected: %s", __FUNCTION__, i,pVal->FirstChild()->Value()); + } + pIt = pIt->NextSibling("service"); + i++; + } + } + else + { + vVideoSubChannel.name.clear(); + vVideoSubChannel.reference.clear(); + vVideoSubChannel.selected.clear(); + } + } + pNode = pRootElement->FirstChild("current_event"); + if (pNode) + { + CLog::Log(LOGDEBUG, "%s - Current Event", __FUNCTION__); + pIt = pNode->FirstChild("date"); + if (pIt) + { + sCurSrvData.current_event_date = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Date: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("time"); + if (pIt) + { + sCurSrvData.current_event_time = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Time: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + + pIt = pNode->FirstChild("start"); + if (pIt) + { + sCurSrvData.current_event_start = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Start: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + + pIt = pNode->FirstChild("duration"); + if (pIt) + { + sCurSrvData.current_event_duration = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Duration: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + + pIt = pNode->FirstChild("description"); + if (pIt) + { + sCurSrvData.current_event_description = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Description: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("details"); + if (pIt) + { + sCurSrvData.current_event_details = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Details: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + } + pNode = pRootElement->FirstChild("next_event"); + if (pNode) + { + CLog::Log(LOGDEBUG, "%s - Next Event", __FUNCTION__); + pIt = pNode->FirstChild("date"); + if (pIt) + { + sCurSrvData.next_event_date = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Date: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("time"); + if (pIt) + { + sCurSrvData.next_event_time = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Time: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + + pIt = pNode->FirstChild("start"); + if (pIt) + { + sCurSrvData.next_event_start = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Start: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + + pIt = pNode->FirstChild("duration"); + if (pIt) + { + sCurSrvData.next_event_duration = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Duration: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + + pIt = pNode->FirstChild("description"); + if (pIt) + { + sCurSrvData.next_event_description = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Description: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("details"); + if (pIt) + { + sCurSrvData.next_event_details = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Details: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + } + return true; + } + return false; + +} +bool CTuxBoxUtil::BoxStatus(TiXmlElement *pRootElement) +{ + //Tuxbox Controll Commands + /* + /cgi-bin/admin?command=wakeup + /cgi-bin/admin?command=standby + /cgi-bin/admin?command=shutdown + /cgi-bin/admin?command=reboot + /cgi-bin/admin?command=restart + */ + + TiXmlNode *pNode = NULL; + + if(pRootElement) + { + CLog::Log(LOGDEBUG, "%s - BoxStatus", __FUNCTION__); + pNode = pRootElement->FirstChild("current_time"); + if (pNode) + { + sBoxStatus.current_time = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Current Time: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + pNode = pRootElement->FirstChild("standby"); + if (pNode) + { + sBoxStatus.standby = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Standby: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + pNode = pRootElement->FirstChild("recording"); + if (pNode) + { + sBoxStatus.recording = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Recording: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + pNode = pRootElement->FirstChild("mode"); + if (pNode) + { + sBoxStatus.mode = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Mode: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + pNode = pRootElement->FirstChild("ip"); + if (pNode) + { + if (sBoxStatus.ip != pNode->FirstChild()->Value() ) + { + g_tuxbox.sZapstream.initialized = false; + g_tuxbox.sZapstream.available = false; + } + sBoxStatus.ip = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Ip: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + return true; + } + return false; +} +bool CTuxBoxUtil::BoxInfo(TiXmlElement *pRootElement) +{ + TiXmlNode *pNode = NULL; + TiXmlNode *pIt = NULL; + + if(pRootElement) + { + CLog::Log(LOGDEBUG, "%s - BoxInfo", __FUNCTION__); + pNode = pRootElement->FirstChild("image"); + if (pNode) + { + CLog::Log(LOGDEBUG, "%s - Image", __FUNCTION__); + pIt = pNode->FirstChild("version"); + if (pIt) + { + sBoxInfo.image_version = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Image Version: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("url"); + if (pIt) + { + sBoxInfo.image_url = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Image Url: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("comment"); + if (pIt) + { + sBoxInfo.image_comment = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Image Comment: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("catalog"); + if (pIt) + { + sBoxInfo.image_catalog = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Image Catalog: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + } + pNode = pRootElement->FirstChild("firmware"); + if (pNode) + { + sBoxInfo.firmware = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Firmware: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + pNode = pRootElement->FirstChild("fpfirmware"); + if (pNode) + { + sBoxInfo.fpfirmware = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - FP Firmware: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + pNode = pRootElement->FirstChild("webinterface"); + if (pNode) + { + sBoxInfo.webinterface = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Web Interface: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + pNode = pRootElement->FirstChild("model"); + if (pNode) + { + sBoxInfo.model = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Model: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + pNode = pRootElement->FirstChild("manufacturer"); + if (pNode) + { + sBoxInfo.manufacturer = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Manufacturer: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + pNode = pRootElement->FirstChild("processor"); + if (pNode) + { + sBoxInfo.processor = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Processor: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + pNode = pRootElement->FirstChild("usbstick"); + if (pNode) + { + sBoxInfo.usbstick = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - USB Stick: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + pNode = pRootElement->FirstChild("disk"); + if (pNode) + { + sBoxInfo.disk = pNode->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Disk: %s", __FUNCTION__, pNode->FirstChild()->Value()); + } + return true; + } + return false; +} +bool CTuxBoxUtil::ServiceEPG(TiXmlElement *pRootElement) +{ + TiXmlNode *pNode = NULL; + TiXmlNode *pIt = NULL; + + if(pRootElement) + { + CLog::Log(LOGDEBUG, "%s - Service EPG", __FUNCTION__); + pNode = pRootElement->FirstChild("service"); + if (pNode) + { + CLog::Log(LOGDEBUG, "%s - Service", __FUNCTION__); + pIt = pNode->FirstChild("reference"); + if (pIt) + { + sServiceEPG.service_reference = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Service Reference: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("name"); + if (pIt) + { + sServiceEPG.service_name = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Service Name: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + } + //Todo there is more then 1 event! Create a Event List! + pNode = pRootElement->FirstChild("event"); + if (pNode) + { + CLog::Log(LOGDEBUG, "%s - Event", __FUNCTION__); + pIt = pNode->FirstChild("date"); + if (pIt) + { + sServiceEPG.date = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Date: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("time"); + if (pIt) + { + sServiceEPG.time = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Time: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("duration"); + if (pIt) + { + sServiceEPG.duration = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Duration: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("descritption"); + if (pIt) + { + sServiceEPG.descritption = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Descritption: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("genre"); + if (pIt) + { + sServiceEPG.genre = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Genre: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("genrecategory"); + if (pIt) + { + sServiceEPG.genrecategory = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Genrecategory: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("start"); + if (pIt) + { + sServiceEPG.start = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Start: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + pIt = pNode->FirstChild("details"); + if (pIt) + { + sServiceEPG.details = pIt->FirstChild()->Value(); + CLog::Log(LOGDEBUG, "%s - Details: %s", __FUNCTION__, pIt->FirstChild()->Value()); + } + } + return true; + } + return false; +} +//PopUp and request the AudioChannel +//No PopUp: On 1x detected AudioChannel +bool CTuxBoxUtil::GetGUIRequestedAudioChannel(AUDIOCHANNEL& sRequestedAC) +{ + sRequestedAC = sCurSrvData.audio_channels[0]; + + // Audio Selection is Disabled! Return false to use default values! + if(!g_advancedSettings.m_bTuxBoxAudioChannelSelection) + { + CLog::Log(LOGDEBUG, "%s - Audio Channel Selection is Disabled! Returning False to use the default values!", __FUNCTION__); + return false; + } + + // We have only one Audio Channel return false to use default values! + if(sCurSrvData.audio_channels.size() == 1) + return false; + + // popup the context menu + CContextButtons buttons; + + // add the needed Audio buttons + for (unsigned int i = 0; i < sCurSrvData.audio_channels.size(); ++i) + buttons.Add(i, sCurSrvData.audio_channels[i].name); + + int channel = CGUIDialogContextMenu::ShowAndGetChoice(buttons); + if (channel >= 0) + { + sRequestedAC = sCurSrvData.audio_channels[channel]; + sCurSrvData.requested_audio_channel = channel; + CLog::Log(LOGDEBUG, "%s - Audio channel %s requested.", __FUNCTION__, sRequestedAC.name.c_str()); + return true; + } + return false; +} +bool CTuxBoxUtil::GetRequestedAudioChannel(AUDIOCHANNEL& sRequestedAC) const +{ + sRequestedAC = sCurSrvData.audio_channels[sCurSrvData.requested_audio_channel]; + + return true; +} +bool CTuxBoxUtil::GetVideoSubChannels(std::string& strVideoSubChannelName, std::string& strVideoSubChannelPid) +{ + // no video sub channel return false! + if(vVideoSubChannel.name.size() <= 0 || vVideoSubChannel.reference.size() <= 0) + return false; + + // IsPlaying, Stop it.. + if(g_application.m_pPlayer->IsPlaying()) + CApplicationMessenger::Get().MediaStop(); + + // popup the context menu + CContextButtons buttons; + + // add the needed Audio buttons + for (unsigned int i = 0; i < vVideoSubChannel.name.size(); ++i) + buttons.Add(i, vVideoSubChannel.name[i]); + + // get selected Video Sub Channel name and reference zap + int channel = CGUIDialogContextMenu::ShowAndGetChoice(buttons); + if (channel >= 0) + { + strVideoSubChannelName = vVideoSubChannel.name[channel]; + strVideoSubChannelPid = vVideoSubChannel.reference[channel]; + vVideoSubChannel.name.clear(); + vVideoSubChannel.reference.clear(); + vVideoSubChannel.selected.clear(); + return true; + } + return false; +} +//Input: Service Name (Channel Namne) +//Output: picon url (on ERROR the default icon path will be returned) +std::string CTuxBoxUtil::GetPicon(std::string strServiceName) +{ + if(!g_advancedSettings.m_bTuxBoxPictureIcon) + { + CLog::Log(LOGDEBUG, "%s PictureIcon Detection is Disabled! Using default icon", __FUNCTION__); + return ""; + } + if (strServiceName.empty()) + { + CLog::Log(LOGDEBUG, "%s Service Name is Empty! Can not detect a PictureIcon. Using default icon!", __FUNCTION__); + return ""; + } + else + { + std::string piconXML, piconPath, defaultPng; + piconPath = "special://xbmc/userdata/PictureIcon/Picon/"; + defaultPng = piconPath+"tuxbox.png"; + piconXML = "special://xbmc/userdata/PictureIcon/picon.xml"; + CXBMCTinyXML piconDoc; + + if (!CFile::Exists(piconXML)) + return defaultPng; + + if (!piconDoc.LoadFile(piconXML)) + { + CLog::Log(LOGERROR, "Error loading %s, Line %d\n%s", piconXML.c_str(), piconDoc.ErrorRow(), piconDoc.ErrorDesc()); + return defaultPng; + } + + TiXmlElement *pRootElement = piconDoc.RootElement(); + if (!pRootElement || strcmpi(pRootElement->Value(),"picon") != 0) + { + CLog::Log(LOGERROR, "Error loading %s, no <picon> node", piconXML.c_str()); + return defaultPng; + } + + TiXmlElement* pServices = pRootElement->FirstChildElement("services"); + TiXmlElement* pService; + pService = pServices->FirstChildElement("service"); + while(pService) + { + std::string strName = XMLUtils::GetAttribute(pService, "name"); + std::string strPng = XMLUtils::GetAttribute(pService, "png"); + + if(strName == strServiceName) + { + strPng = piconPath + strPng; + StringUtils::ToLower(strPng); + CLog::Log(LOGDEBUG, "%s %s: Path is: %s", __FUNCTION__,strServiceName.c_str(), strPng.c_str()); + return strPng; + } + pService = pService->NextSiblingElement("service"); + } + return defaultPng; + } +} + +// iMODE: 0 = TV, 1 = Radio, 2 = Data, 3 = Movies, 4 = Root +// SUBMODE: 0 = n/a, 1 = All, 2 = Satellites, 2 = Providers, 4 = Bouquets +std::string CTuxBoxUtil::GetSubMode(int iMode, std::string& strXMLRootString, std::string& strXMLChildString) +{ + //Todo: add a setting: "Don't Use Request mode" to advanced.xml + + // MODE: 0 = TV, 1 = Radio, 2 = Data, 3 = Movies, 4 = Root + // SUBMODE: 0 = n/a, 1 = All, 2 = Satellites, 2 = Providers, 4 = Bouquets + // Default Submode + std::string strSubMode; + + if(iMode <0 || iMode >4) + { + strSubMode = StringUtils::Format("xml/services?mode=0&submode=4"); + strXMLRootString = StringUtils::Format("bouquets"); + strXMLChildString = StringUtils::Format("bouquet"); + return strSubMode; + } + + // popup the context menu + + // FIXME: Localize these + CContextButtons choices; + choices.Add(1, "All"); + choices.Add(2, "Satellites"); + choices.Add(3, "Providers"); + choices.Add(4, "Bouquets"); + + int iSubMode = CGUIDialogContextMenu::ShowAndGetChoice(choices); + if (iSubMode == 1) + { + strXMLRootString = StringUtils::Format("services"); + strXMLChildString = StringUtils::Format("service"); + } + else if (iSubMode == 2) + { + strXMLRootString = StringUtils::Format("satellites"); + strXMLChildString = StringUtils::Format("satellite"); + } + else if (iSubMode == 3) + { + strXMLRootString = StringUtils::Format("providers"); + strXMLChildString = StringUtils::Format("provider"); + } + else // if (iSubMode == 4 || iSubMode < 0) + { + iSubMode = 4; + strXMLRootString = StringUtils::Format("bouquets"); + strXMLChildString = StringUtils::Format("bouquet"); + } + strSubMode = StringUtils::Format("xml/services?mode=%i&submode=%i",iMode,iSubMode); + return strSubMode; +} +//Input: url/path of share/item file/folder +//Output: the detected submode root and child string +std::string CTuxBoxUtil::DetectSubMode(std::string strSubMode, std::string& strXMLRootString, std::string& strXMLChildString) +{ + //strSubMode = "xml/services?mode=0&submode=1" + std::string strFilter; + size_t ipointMode = strSubMode.find("?mode="); + size_t ipointSubMode = strSubMode.find("&submode="); + if (ipointMode != std::string::npos) + strFilter.assign(1, strSubMode.at(ipointMode + 6)); + + if (ipointSubMode != std::string::npos) + { + char v = strSubMode.at(ipointSubMode + 9); + if(v == '1') + { + strXMLRootString = "unknowns"; + strXMLChildString = "unknown"; + } + else if(v == '2') + { + strXMLRootString = "satellites"; + strXMLChildString = "satellite"; + } + else if(v == '3') + { + strXMLRootString = "providers"; + strXMLChildString = "provider"; + } + else if(v == '4') + { + strXMLRootString = "bouquets"; + strXMLChildString = "bouquet"; + } + + } + return strFilter; +} diff --git a/src/utils/TuxBoxUtil.h b/src/utils/TuxBoxUtil.h new file mode 100644 index 0000000000..f6532dd8a0 --- /dev/null +++ b/src/utils/TuxBoxUtil.h @@ -0,0 +1,191 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include "threads/Thread.h" + +class CURL; +class TiXmlElement; +class CFileItem; +class CFileItemList; + +struct STREAMINFO +{ + std::string frontend; + std::string service_name; + std::string service_reference; + std::string provider; + std::string vpid; + std::string apid; + std::string pcrpid; + std::string tpid; + std::string tsid; + std::string onid; + std::string sid; + std::string pmt; + std::string video_format; + std::string supported_crypt_systems; + std::string used_crypt_systems; + std::string satellite; + std::string frequency; + std::string symbol_rate; + std::string polarisation; + std::string inversion; + std::string fec; + std::string snr; + std::string agc; + std::string ber; + std::string lock; + std::string sync; +}; +struct VIDEOSUBCHANNEL +{ + std::vector<std::string> reference; + std::vector<std::string> name; + std::vector<std::string> selected; + std::string current_name; + bool mode; +}; +typedef struct AUDIOCHANNEL +{ + std::string pid; + std::string selected; + std::string name; +} sAudioChannel; +struct CURRENTSERVICEDATA +{ + std::string service_name; + std::string service_reference; + std::vector<AUDIOCHANNEL> audio_channels; + int requested_audio_channel; + std::string audio_track; + std::string current_event_date; + std::string current_event_time; + std::string current_event_start; + std::string current_event_duration; + std::string current_event_description; + std::string current_event_details; + std::string next_event_date; + std::string next_event_time; + std::string next_event_start; + std::string next_event_duration; + std::string next_event_description; + std::string next_event_details; +}; +struct BOXSTATUS +{ + std::string current_time; + std::string standby; + std::string recording; + std::string mode; + std::string ip; +}; +struct BOXSINFO +{ + std::string image_version; + std::string image_url; + std::string image_comment; + std::string image_catalog; + std::string firmware; + std::string fpfirmware; + std::string webinterface; + std::string model; + std::string manufacturer; + std::string processor; + std::string usbstick; + std::string disk; +}; +struct SERVICE_EPG +{ + std::string service_reference; + std::string service_name; + std::string image_comment; + std::string event; + std::string date; + std::string time; + std::string duration; + std::string descritption; + std::string genre; + std::string genrecategory; + std::string start; + std::string details; +}; +struct ZAPSTREAM +{ + bool initialized; + bool available; +}; +class CTuxBoxUtil +{ + public: + STREAMINFO sStrmInfo; + CURRENTSERVICEDATA sCurSrvData; + BOXSTATUS sBoxStatus; + BOXSINFO sBoxInfo; + SERVICE_EPG sServiceEPG; + VIDEOSUBCHANNEL vVideoSubChannel; + ZAPSTREAM sZapstream; + + CTuxBoxUtil(void); + virtual ~CTuxBoxUtil(void); + + bool GetZapUrl(const std::string& strPath, CFileItem &items); + static bool ParseBouquets(TiXmlElement *root, CFileItemList &items, CURL &url, std::string strFilter, std::string strChild); + static bool ParseBouquetsEnigma2(TiXmlElement *root, CFileItemList &items, CURL &url, std::string& strFilter, std::string& strChild); + static bool ParseChannels(TiXmlElement *root, CFileItemList &items, CURL &url, std::string strFilter, std::string strChild); + static bool ParseChannelsEnigma2(TiXmlElement *root, CFileItemList &items, CURL &url, std::string& strFilter, std::string& strChild); + bool ZapToUrl(CURL url, const std::string &pathOption); + bool StreamInformations(TiXmlElement *pRootElement); + bool CurrentServiceData(TiXmlElement *pRootElement); + bool BoxStatus(TiXmlElement *pRootElement); + bool BoxInfo(TiXmlElement *pRootElement); + bool ServiceEPG(TiXmlElement *pRootElement); + bool GetHttpXML(CURL url,std::string strRequestType); + bool GetGUIRequestedAudioChannel(AUDIOCHANNEL& sRequestedAC); + bool GetRequestedAudioChannel(AUDIOCHANNEL& sRequestedAC) const; + bool GetVideoSubChannels(std::string& strVideoSubChannelName, std::string& strVideoSubChannelPid); + bool GetVideoChannels(TiXmlElement *pRootElement); + bool CreateNewItem(const CFileItem& item, CFileItem& item_new); + static bool InitZapstream(const std::string& strPath); + static bool SetAudioChannel(const std::string& strPath, const AUDIOCHANNEL& sAC); + + static std::string GetPicon(std::string strServiceName); + static std::string GetSubMode(int iMode, std::string& strXMLRootString, std::string& strXMLChildString); + static std::string DetectSubMode(std::string strSubMode, std::string& strXMLRootString, std::string& strXMLChildString); +}; +extern CTuxBoxUtil g_tuxbox; + +class CTuxBoxService : public CThread +{ +public: + CTuxBoxService(); + ~CTuxBoxService(); + + bool Start(); + void Stop(); + bool IsRunning(); + + virtual void OnExit(); + virtual void OnStartup(); + virtual void Process(); +}; +extern CTuxBoxService g_tuxboxService; diff --git a/src/utils/URIUtils.cpp b/src/utils/URIUtils.cpp new file mode 100644 index 0000000000..6ab3dbb9b2 --- /dev/null +++ b/src/utils/URIUtils.cpp @@ -0,0 +1,1335 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "network/Network.h" +#include "URIUtils.h" +#include "Application.h" +#include "FileItem.h" +#include "filesystem/MultiPathDirectory.h" +#include "filesystem/MythDirectory.h" +#include "filesystem/SpecialProtocol.h" +#include "filesystem/StackDirectory.h" +#include "network/DNSNameCache.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSettings.h" +#include "URL.h" +#include "StringUtils.h" + +#include <netinet/in.h> +#include <arpa/inet.h> + +using namespace std; +using namespace XFILE; + +bool URIUtils::IsInPath(const CStdString &uri, const CStdString &baseURI) +{ + CStdString uriPath = CSpecialProtocol::TranslatePath(uri); + CStdString basePath = CSpecialProtocol::TranslatePath(baseURI); + + return !basePath.empty() && StringUtils::StartsWith(uriPath, basePath); +} + +/* returns filename extension including period of filename */ +std::string URIUtils::GetExtension(const CURL& url) +{ + return URIUtils::GetExtension(url.GetFileName()); +} + +std::string URIUtils::GetExtension(const std::string& strFileName) +{ + if (IsURL(strFileName)) + { + CURL url(strFileName); + return GetExtension(url.GetFileName()); + } + + size_t period = strFileName.find_last_of("./\\"); + if (period == string::npos || strFileName[period] != '.') + return CStdString(); + + return strFileName.substr(period); +} + +bool URIUtils::HasExtension(const CStdString& strFileName) +{ + if (IsURL(strFileName)) + { + CURL url(strFileName); + return HasExtension(url.GetFileName()); + } + + size_t iPeriod = strFileName.find_last_of("./\\"); + return iPeriod != string::npos && strFileName[iPeriod] == '.'; +} + +bool URIUtils::HasExtension(const CURL& url, const CStdString& strExtensions) +{ + return HasExtension(url.GetFileName(), strExtensions); +} + +bool URIUtils::HasExtension(const CStdString& strFileName, const CStdString& strExtensions) +{ + if (IsURL(strFileName)) + { + CURL url(strFileName); + return HasExtension(url.GetFileName(), strExtensions); + } + + // Search backwards so that '.' can be used as a search terminator. + CStdString::const_reverse_iterator itExtensions = strExtensions.rbegin(); + while (itExtensions != strExtensions.rend()) + { + // Iterate backwards over strFileName untill we hit a '.' or a mismatch + for (CStdString::const_reverse_iterator itFileName = strFileName.rbegin(); + itFileName != strFileName.rend() && itExtensions != strExtensions.rend() && + tolower(*itFileName) == *itExtensions; + ++itFileName, ++itExtensions) + { + if (*itExtensions == '.') + return true; // Match + } + + // No match. Look for more extensions to try. + while (itExtensions != strExtensions.rend() && *itExtensions != '|') + ++itExtensions; + + while (itExtensions != strExtensions.rend() && *itExtensions == '|') + ++itExtensions; + } + + return false; +} + +void URIUtils::RemoveExtension(std::string& strFileName) +{ + if(IsURL(strFileName)) + { + CURL url(strFileName); + strFileName = url.GetFileName(); + RemoveExtension(strFileName); + url.SetFileName(strFileName); + strFileName = url.Get(); + return; + } + + size_t period = strFileName.find_last_of("./\\"); + if (period != string::npos && strFileName[period] == '.') + { + CStdString strExtension = strFileName.substr(period); + StringUtils::ToLower(strExtension); + strExtension += "|"; + + CStdString strFileMask; + strFileMask = g_advancedSettings.m_pictureExtensions; + strFileMask += "|" + g_advancedSettings.m_musicExtensions; + strFileMask += "|" + g_advancedSettings.m_videoExtensions; + strFileMask += "|" + g_advancedSettings.m_subtitlesExtensions; +#if defined(TARGET_DARWIN) + strFileMask += "|.py|.xml|.milk|.xpr|.xbt|.cdg|.app|.applescript|.workflow"; +#else + strFileMask += "|.py|.xml|.milk|.xpr|.xbt|.cdg"; +#endif + strFileMask += "|"; + + if (strFileMask.find(strExtension) != std::string::npos) + strFileName.erase(period); + } +} + +CStdString URIUtils::ReplaceExtension(const CStdString& strFile, + const CStdString& strNewExtension) +{ + if(IsURL(strFile)) + { + CURL url(strFile); + url.SetFileName(ReplaceExtension(url.GetFileName(), strNewExtension)); + return url.Get(); + } + + CStdString strChangedFile; + CStdString strExtension = GetExtension(strFile); + if ( strExtension.size() ) + { + strChangedFile = strFile.substr(0, strFile.size() - strExtension.size()) ; + strChangedFile += strNewExtension; + } + else + { + strChangedFile = strFile; + strChangedFile += strNewExtension; + } + return strChangedFile; +} + +const CStdString URIUtils::GetFileName(const CURL& url) +{ + return GetFileName(url.GetFileName()); +} + +/* returns a filename given an url */ +/* handles both / and \, and options in urls*/ +const CStdString URIUtils::GetFileName(const CStdString& strFileNameAndPath) +{ + if(IsURL(strFileNameAndPath)) + { + CURL url(strFileNameAndPath); + return GetFileName(url.GetFileName()); + } + + /* find the last slash */ + const size_t slash = strFileNameAndPath.find_last_of("/\\"); + return strFileNameAndPath.substr(slash+1); +} + +void URIUtils::Split(const std::string& strFileNameAndPath, + std::string& strPath, std::string& strFileName) +{ + //Splits a full filename in path and file. + //ex. smb://computer/share/directory/filename.ext -> strPath:smb://computer/share/directory/ and strFileName:filename.ext + //Trailing slash will be preserved + strFileName = ""; + strPath = ""; + int i = strFileNameAndPath.size() - 1; + while (i > 0) + { + char ch = strFileNameAndPath[i]; + // Only break on ':' if it's a drive separator for DOS (ie d:foo) + if (ch == '/' || ch == '\\' || (ch == ':' && i == 1)) break; + else i--; + } + if (i == 0) + i--; + + // take left including the directory separator + strPath = strFileNameAndPath.substr(0, i+1); + // everything to the right of the directory separator + strFileName = strFileNameAndPath.substr(i+1); +} + +std::vector<std::string> URIUtils::SplitPath(const CStdString& strPath) +{ + CURL url(strPath); + + // silly CStdString can't take a char in the constructor + CStdString sep(1, url.GetDirectorySeparator()); + + // split the filename portion of the URL up into separate dirs + vector<string> dirs = StringUtils::Split(url.GetFileName(), sep); + + // we start with the root path + CStdString dir = url.GetWithoutFilename(); + + if (!dir.empty()) + dirs.insert(dirs.begin(), dir); + + // we don't need empty token on the end + if (dirs.size() > 1 && dirs.back().empty()) + dirs.erase(dirs.end() - 1); + + return dirs; +} + +void URIUtils::GetCommonPath(std::string& strParent, const std::string& strPath) +{ + // find the common path of parent and path + unsigned int j = 1; + while (j <= min(strParent.size(), strPath.size()) && strnicmp(strParent.c_str(), strPath.c_str(), j) == 0) + j++; + strParent.erase(j - 1); + // they should at least share a / at the end, though for things such as path/cd1 and path/cd2 there won't be + if (!HasSlashAtEnd(strParent)) + { + strParent = GetDirectory(strParent); + AddSlashAtEnd(strParent); + } +} + +bool URIUtils::HasParentInHostname(const CURL& url) +{ + return url.IsProtocol("zip") + || url.IsProtocol("rar") + || url.IsProtocol("apk") + || url.IsProtocol("bluray") + || url.IsProtocol("udf"); +} + +bool URIUtils::HasEncodedHostname(const CURL& url) +{ + return HasParentInHostname(url) + || url.IsProtocol("musicsearch") + || url.IsProtocol( "image"); +} + +bool URIUtils::HasEncodedFilename(const CURL& url) +{ + const std::string prot2 = url.GetTranslatedProtocol(); + + // For now assume only (quasi) http internet streams use URL encoding + return CURL::IsProtocolEqual(prot2, "http") || + CURL::IsProtocolEqual(prot2, "https"); +} + +std::string URIUtils::GetParentPath(const std::string& strPath) +{ + std::string strReturn; + GetParentPath(strPath, strReturn); + return strReturn; +} + +bool URIUtils::GetParentPath(const std::string& strPath, std::string& strParent) +{ + strParent.clear(); + + CURL url(strPath); + std::string strFile = url.GetFileName(); + if ( URIUtils::HasParentInHostname(url) && strFile.empty()) + { + strFile = url.GetHostName(); + return GetParentPath(strFile, strParent); + } + else if (url.IsProtocol("stack")) + { + CStackDirectory dir; + CFileItemList items; + dir.GetDirectory(url, items); + items[0]->m_strDVDLabel = GetDirectory(items[0]->GetPath()); + if (IsProtocol(items[0]->m_strDVDLabel, "rar") || IsProtocol(items[0]->m_strDVDLabel, "zip")) + GetParentPath(items[0]->m_strDVDLabel, strParent); + else + strParent = items[0]->m_strDVDLabel; + for( int i=1;i<items.Size();++i) + { + items[i]->m_strDVDLabel = GetDirectory(items[i]->GetPath()); + if (IsProtocol(items[0]->m_strDVDLabel, "rar") || IsProtocol(items[0]->m_strDVDLabel, "zip")) + items[i]->SetPath(GetParentPath(items[i]->m_strDVDLabel)); + else + items[i]->SetPath(items[i]->m_strDVDLabel); + + GetCommonPath(strParent,items[i]->GetPath()); + } + return true; + } + else if (url.IsProtocol("multipath")) + { + // get the parent path of the first item + return GetParentPath(CMultiPathDirectory::GetFirstPath(strPath), strParent); + } + else if (url.IsProtocol("plugin")) + { + if (!url.GetOptions().empty()) + { + url.SetOptions(""); + strParent = url.Get(); + return true; + } + if (!url.GetFileName().empty()) + { + url.SetFileName(""); + strParent = url.Get(); + return true; + } + if (!url.GetHostName().empty()) + { + url.SetHostName(""); + strParent = url.Get(); + return true; + } + return true; // already at root + } + else if (url.IsProtocol("special")) + { + if (HasSlashAtEnd(strFile)) + strFile.erase(strFile.size() - 1); + if(strFile.rfind('/') == std::string::npos) + return false; + } + else if (strFile.size() == 0) + { + if (url.GetHostName().size() > 0) + { + // we have an share with only server or workgroup name + // set hostname to "" and return true to get back to root + url.SetHostName(""); + strParent = url.Get(); + return true; + } + return false; + } + + if (HasSlashAtEnd(strFile) ) + { + strFile.erase(strFile.size() - 1); + } + + size_t iPos = strFile.rfind('/'); +#ifndef TARGET_POSIX + if (iPos == std::string::npos) + { + iPos = strFile.rfind('\\'); + } +#endif + if (iPos == std::string::npos) + { + url.SetFileName(""); + strParent = url.Get(); + return true; + } + + strFile.erase(iPos); + + AddSlashAtEnd(strFile); + + url.SetFileName(strFile); + strParent = url.Get(); + return true; +} + +std::string URLEncodePath(const std::string& strPath) +{ + vector<string> segments = StringUtils::Split(strPath, "/"); + for (vector<string>::iterator i = segments.begin(); i != segments.end(); ++i) + *i = CURL::Encode(*i); + + return StringUtils::Join(segments, "/"); +} + +std::string URLDecodePath(const std::string& strPath) +{ + vector<string> segments = StringUtils::Split(strPath, "/"); + for (vector<string>::iterator i = segments.begin(); i != segments.end(); ++i) + *i = CURL::Decode(*i); + + return StringUtils::Join(segments, "/"); +} + +std::string URIUtils::ChangeBasePath(const std::string &fromPath, const std::string &fromFile, const std::string &toPath) +{ + std::string toFile = fromFile; + + // Convert back slashes to forward slashes, if required + if (IsDOSPath(fromPath) && !IsDOSPath(toPath)) + StringUtils::Replace(toFile, "\\", "/"); + + // Handle difference in URL encoded vs. not encoded + if ( HasEncodedFilename(CURL(fromPath)) + && !HasEncodedFilename(CURL(toPath)) ) + { + toFile = URLDecodePath(toFile); // Decode path + } + else if (!HasEncodedFilename(CURL(fromPath)) + && HasEncodedFilename(CURL(toPath)) ) + { + toFile = URLEncodePath(toFile); // Encode path + } + + // Convert forward slashes to back slashes, if required + if (!IsDOSPath(fromPath) && IsDOSPath(toPath)) + StringUtils::Replace(toFile, "/", "\\"); + + return AddFileToFolder(toPath, toFile); +} + +CURL URIUtils::SubstitutePath(const CURL& url, bool reverse /* = false */) +{ + const CStdString pathToUrl = url.Get(); + return CURL(SubstitutePath(pathToUrl, reverse)); +} + +CStdString URIUtils::SubstitutePath(const CStdString& strPath, bool reverse /* = false */) +{ + for (CAdvancedSettings::StringMapping::iterator i = g_advancedSettings.m_pathSubstitutions.begin(); + i != g_advancedSettings.m_pathSubstitutions.end(); ++i) + { + CStdString fromPath; + CStdString toPath; + + if (!reverse) + { + fromPath = i->first; // Fake path + toPath = i->second; // Real path + } + else + { + fromPath = i->second; // Real path + toPath = i->first; // Fake path + } + + if (strncmp(strPath.c_str(), fromPath.c_str(), HasSlashAtEnd(fromPath) ? fromPath.size() - 1 : fromPath.size()) == 0) + { + if (strPath.size() > fromPath.size()) + { + CStdString strSubPathAndFileName = strPath.substr(fromPath.size()); + return ChangeBasePath(fromPath, strSubPathAndFileName, toPath); // Fix encoding + slash direction + } + else + { + return toPath; + } + } + } + return strPath; +} + +bool URIUtils::IsProtocol(const std::string& url, const std::string &type) +{ + return StringUtils::StartsWithNoCase(url, type + "://"); +} + +bool URIUtils::PathStarts(const std::string& url, const char *start) +{ + return StringUtils::StartsWith(url, start); +} + +bool URIUtils::PathEquals(const std::string& url, const std::string &start) +{ + return url == start; +} + +bool URIUtils::IsRemote(const CStdString& strFile) +{ + if (IsCDDA(strFile) || IsISO9660(strFile)) + return false; + + if (IsStack(strFile)) + return IsRemote(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsRemote(CSpecialProtocol::TranslatePath(strFile)); + + if(IsMultiPath(strFile)) + { // virtual paths need to be checked separately + vector<std::string> paths; + if (CMultiPathDirectory::GetPaths(strFile, paths)) + { + for (unsigned int i = 0; i < paths.size(); i++) + if (IsRemote(paths[i])) return true; + } + return false; + } + + CURL url(strFile); + if(HasParentInHostname(url)) + return IsRemote(url.GetHostName()); + + if (!url.IsLocal()) + return true; + + return false; +} + +bool URIUtils::IsOnDVD(const CStdString& strFile) +{ +#ifdef TARGET_WINDOWS + if (strFile.size() >= 2 && strFile.substr(1,1) == ":") + return (GetDriveType(strFile.substr(0, 3).c_str()) == DRIVE_CDROM); +#endif + + if (IsProtocol(strFile, "dvd")) + return true; + + if (IsProtocol(strFile, "udf")) + return true; + + if (IsProtocol(strFile, "iso9660")) + return true; + + if (IsProtocol(strFile, "cdda")) + return true; + + return false; +} + +bool URIUtils::IsOnLAN(const CStdString& strPath) +{ + if(IsMultiPath(strPath)) + return IsOnLAN(CMultiPathDirectory::GetFirstPath(strPath)); + + if(IsStack(strPath)) + return IsOnLAN(CStackDirectory::GetFirstStackedFile(strPath)); + + if(IsSpecial(strPath)) + return IsOnLAN(CSpecialProtocol::TranslatePath(strPath)); + + if(IsDAAP(strPath)) + return true; + + if(IsPlugin(strPath)) + return false; + + if(IsTuxBox(strPath)) + return true; + + if(IsUPnP(strPath)) + return true; + + CURL url(strPath); + if (HasParentInHostname(url)) + return IsOnLAN(url.GetHostName()); + + if(!IsRemote(strPath)) + return false; + + CStdString host = url.GetHostName(); + + return IsHostOnLAN(host); +} + +static bool addr_match(uint32_t addr, const char* target, const char* submask) +{ + uint32_t addr2 = ntohl(inet_addr(target)); + uint32_t mask = ntohl(inet_addr(submask)); + return (addr & mask) == (addr2 & mask); +} + +bool URIUtils::IsHostOnLAN(const CStdString& host, bool offLineCheck) +{ + if(host.length() == 0) + return false; + + // assume a hostname without dot's + // is local (smb netbios hostnames) + if(host.find('.') == string::npos) + return true; + + uint32_t address = ntohl(inet_addr(host.c_str())); + if(address == INADDR_NONE) + { + CStdString ip; + if(CDNSNameCache::Lookup(host, ip)) + address = ntohl(inet_addr(ip.c_str())); + } + + if(address != INADDR_NONE) + { + if (offLineCheck) // check if in private range, ref https://en.wikipedia.org/wiki/Private_network + { + if ( + addr_match(address, "192.168.0.0", "255.255.0.0") || + addr_match(address, "10.0.0.0", "255.0.0.0") || + addr_match(address, "172.16.0.0", "255.240.0.0") + ) + return true; + } + // check if we are on the local subnet + if (!g_application.getNetwork().GetFirstConnectedInterface()) + return false; + + if (g_application.getNetwork().HasInterfaceForIP(address)) + return true; + } + + return false; +} + +bool URIUtils::IsMultiPath(const CStdString& strPath) +{ + return IsProtocol(strPath, "multipath"); +} + +bool URIUtils::IsHD(const CStdString& strFileName) +{ + CURL url(strFileName); + + if (IsStack(strFileName)) + return IsHD(CStackDirectory::GetFirstStackedFile(strFileName)); + + if (IsSpecial(strFileName)) + return IsHD(CSpecialProtocol::TranslatePath(strFileName)); + + if (HasParentInHostname(url)) + return IsHD(url.GetHostName()); + + return url.GetProtocol().empty() || url.IsProtocol("file"); +} + +bool URIUtils::IsDVD(const CStdString& strFile) +{ + CStdString strFileLow = strFile; + StringUtils::ToLower(strFileLow); + if (strFileLow.find("video_ts.ifo") != std::string::npos && IsOnDVD(strFile)) + return true; + +#if defined(TARGET_WINDOWS) + if (IsProtocol(strFile, "dvd")) + return true; + + if(strFile.size() < 2 || (strFile.substr(1) != ":\\" && strFile.substr(1) != ":")) + return false; + + if(GetDriveType(strFile.c_str()) == DRIVE_CDROM) + return true; +#else + if (strFileLow == "iso9660://" || strFileLow == "udf://" || strFileLow == "dvd://1" ) + return true; +#endif + + return false; +} + +bool URIUtils::IsStack(const CStdString& strFile) +{ + return IsProtocol(strFile, "stack"); +} + +bool URIUtils::IsRAR(const CStdString& strFile) +{ + CStdString strExtension = GetExtension(strFile); + + if (strExtension.Equals(".001") && !StringUtils::EndsWithNoCase(strFile, ".ts.001")) + return true; + + if (StringUtils::EqualsNoCase(strExtension, ".cbr")) + return true; + + if (StringUtils::EqualsNoCase(strExtension, ".rar")) + return true; + + return false; +} + +bool URIUtils::IsInArchive(const CStdString &strFile) +{ + return IsInZIP(strFile) || IsInRAR(strFile) || IsInAPK(strFile); +} + +bool URIUtils::IsInAPK(const CStdString& strFile) +{ + CURL url(strFile); + + return url.IsProtocol("apk") && !url.GetFileName().empty(); +} + +bool URIUtils::IsInZIP(const CStdString& strFile) +{ + CURL url(strFile); + + return url.IsProtocol("zip") && !url.GetFileName().empty(); +} + +bool URIUtils::IsInRAR(const CStdString& strFile) +{ + CURL url(strFile); + + return url.IsProtocol("rar") && !url.GetFileName().empty(); +} + +bool URIUtils::IsAPK(const CStdString& strFile) +{ + return HasExtension(strFile, ".apk"); +} + +bool URIUtils::IsZIP(const CStdString& strFile) // also checks for comic books! +{ + return HasExtension(strFile, ".zip|.cbz"); +} + +bool URIUtils::IsArchive(const CStdString& strFile) +{ + return HasExtension(strFile, ".zip|.rar|.apk|.cbz|.cbr"); +} + +bool URIUtils::IsSpecial(const CStdString& strFile) +{ + CStdString strFile2(strFile); + + if (IsStack(strFile)) + strFile2 = CStackDirectory::GetFirstStackedFile(strFile); + + return IsProtocol(strFile2, "special"); +} + +bool URIUtils::IsPlugin(const CStdString& strFile) +{ + CURL url(strFile); + return url.IsProtocol("plugin"); +} + +bool URIUtils::IsScript(const CStdString& strFile) +{ + CURL url(strFile); + return url.IsProtocol("script"); +} + +bool URIUtils::IsAddonsPath(const CStdString& strFile) +{ + CURL url(strFile); + return url.IsProtocol("addons"); +} + +bool URIUtils::IsSourcesPath(const CStdString& strPath) +{ + CURL url(strPath); + return url.IsProtocol("sources"); +} + +bool URIUtils::IsCDDA(const CStdString& strFile) +{ + return IsProtocol(strFile, "cdda"); +} + +bool URIUtils::IsISO9660(const CStdString& strFile) +{ + return IsProtocol(strFile, "iso9660"); +} + +bool URIUtils::IsSmb(const CStdString& strFile) +{ + CStdString strFile2(strFile); + + if (IsStack(strFile)) + strFile2 = CStackDirectory::GetFirstStackedFile(strFile); + + return IsProtocol(strFile2, "smb"); +} + +bool URIUtils::IsURL(const CStdString& strFile) +{ + return strFile.find("://") != std::string::npos; +} + +bool URIUtils::IsFTP(const CStdString& strFile) +{ + CStdString strFile2(strFile); + + if (IsStack(strFile)) + strFile2 = CStackDirectory::GetFirstStackedFile(strFile); + + return IsProtocol(strFile2, "ftp") || + IsProtocol(strFile2, "ftps"); +} + +bool URIUtils::IsUDP(const CStdString& strFile) +{ + CStdString strFile2(strFile); + + if (IsStack(strFile)) + strFile2 = CStackDirectory::GetFirstStackedFile(strFile); + + return IsProtocol(strFile2, "udp"); +} + +bool URIUtils::IsTCP(const CStdString& strFile) +{ + CStdString strFile2(strFile); + + if (IsStack(strFile)) + strFile2 = CStackDirectory::GetFirstStackedFile(strFile); + + return IsProtocol(strFile2, "tcp"); +} + +bool URIUtils::IsPVRChannel(const CStdString& strFile) +{ + CStdString strFile2(strFile); + + if (IsStack(strFile)) + strFile2 = CStackDirectory::GetFirstStackedFile(strFile); + + return StringUtils::StartsWithNoCase(strFile2, "pvr://channels"); +} + +bool URIUtils::IsDAV(const CStdString& strFile) +{ + CStdString strFile2(strFile); + + if (IsStack(strFile)) + strFile2 = CStackDirectory::GetFirstStackedFile(strFile); + + return IsProtocol(strFile2, "dav") || + IsProtocol(strFile2, "davs"); +} + +bool URIUtils::IsInternetStream(const std::string &path, bool bStrictCheck /* = false */) +{ + const CURL pathToUrl(path); + return IsInternetStream(pathToUrl, bStrictCheck); +} + +bool URIUtils::IsInternetStream(const CURL& url, bool bStrictCheck /* = false */) +{ + if (url.GetProtocol().empty()) + return false; + + // there's nothing to stop internet streams from being stacked + if (url.IsProtocol("stack")) + return IsInternetStream(CStackDirectory::GetFirstStackedFile(url.Get())); + + // Special case these + if (url.IsProtocol("ftp") || url.IsProtocol("ftps") || + url.IsProtocol("dav") || url.IsProtocol("davs") || + url.IsProtocol("sftp")) + return bStrictCheck; + + std::string protocol = url.GetTranslatedProtocol(); + if (CURL::IsProtocolEqual(protocol, "http") || CURL::IsProtocolEqual(protocol, "https") || + CURL::IsProtocolEqual(protocol, "tcp") || CURL::IsProtocolEqual(protocol, "udp") || + CURL::IsProtocolEqual(protocol, "rtp") || CURL::IsProtocolEqual(protocol, "sdp") || + CURL::IsProtocolEqual(protocol, "mms") || CURL::IsProtocolEqual(protocol, "mmst") || + CURL::IsProtocolEqual(protocol, "mmsh") || CURL::IsProtocolEqual(protocol, "rtsp") || + CURL::IsProtocolEqual(protocol, "rtmp") || CURL::IsProtocolEqual(protocol, "rtmpt") || + CURL::IsProtocolEqual(protocol, "rtmpe") || CURL::IsProtocolEqual(protocol, "rtmpte") || + CURL::IsProtocolEqual(protocol, "rtmps")) + return true; + + return false; +} + +bool URIUtils::IsDAAP(const CStdString& strFile) +{ + return IsProtocol(strFile, "daap"); +} + +bool URIUtils::IsUPnP(const CStdString& strFile) +{ + return IsProtocol(strFile, "upnp"); +} + +bool URIUtils::IsTuxBox(const CStdString& strFile) +{ + return IsProtocol(strFile, "tuxbox"); +} + +bool URIUtils::IsMythTV(const CStdString& strFile) +{ + return IsProtocol(strFile, "myth"); +} + +bool URIUtils::IsHDHomeRun(const CStdString& strFile) +{ + return IsProtocol(strFile, "hdhomerun"); +} + +bool URIUtils::IsSlingbox(const CStdString& strFile) +{ + return IsProtocol(strFile, "sling"); +} + +bool URIUtils::IsVTP(const CStdString& strFile) +{ + return IsProtocol(strFile, "vtp"); +} + +bool URIUtils::IsHTSP(const CStdString& strFile) +{ + return IsProtocol(strFile, "htsp"); +} + +bool URIUtils::IsLiveTV(const CStdString& strFile) +{ + CStdString strFileWithoutSlash(strFile); + RemoveSlashAtEnd(strFileWithoutSlash); + + if(IsTuxBox(strFile) + || IsVTP(strFile) + || IsHDHomeRun(strFile) + || IsSlingbox(strFile) + || IsHTSP(strFile) + || IsProtocol(strFile, "sap") + ||(StringUtils::EndsWithNoCase(strFileWithoutSlash, ".pvr") && !PathStarts(strFileWithoutSlash, "pvr://recordings"))) + return true; + + if (IsMythTV(strFile) && CMythDirectory::IsLiveTV(strFile)) + return true; + + return false; +} + +bool URIUtils::IsPVRRecording(const CStdString& strFile) +{ + CStdString strFileWithoutSlash(strFile); + RemoveSlashAtEnd(strFileWithoutSlash); + + return StringUtils::EndsWithNoCase(strFileWithoutSlash, ".pvr") && + PathStarts(strFile, "pvr://recordings"); +} + +bool URIUtils::IsMusicDb(const CStdString& strFile) +{ + return IsProtocol(strFile, "musicdb"); +} + +bool URIUtils::IsNfs(const CStdString& strFile) +{ + CStdString strFile2(strFile); + + if (IsStack(strFile)) + strFile2 = CStackDirectory::GetFirstStackedFile(strFile); + + return IsProtocol(strFile2, "nfs"); +} + +bool URIUtils::IsAfp(const CStdString& strFile) +{ + CStdString strFile2(strFile); + + if (IsStack(strFile)) + strFile2 = CStackDirectory::GetFirstStackedFile(strFile); + + return IsProtocol(strFile2, "afp"); +} + + +bool URIUtils::IsVideoDb(const CStdString& strFile) +{ + return IsProtocol(strFile, "videodb"); +} + +bool URIUtils::IsBluray(const CStdString& strFile) +{ + return IsProtocol(strFile, "bluray"); +} + +bool URIUtils::IsAndroidApp(const CStdString &path) +{ + return IsProtocol(path, "androidapp"); +} + +bool URIUtils::IsLibraryFolder(const CStdString& strFile) +{ + CURL url(strFile); + return url.IsProtocol("library"); +} + +bool URIUtils::IsLibraryContent(const std::string &strFile) +{ + return (IsProtocol(strFile, "library") || + IsProtocol(strFile, "videodb") || + IsProtocol(strFile, "musicdb") || + StringUtils::EndsWith(strFile, ".xsp")); +} + +bool URIUtils::IsDOSPath(const CStdString &path) +{ + if (path.size() > 1 && path[1] == ':' && isalpha(path[0])) + return true; + + // windows network drives + if (path.size() > 1 && path[0] == '\\' && path[1] == '\\') + return true; + + return false; +} + +void URIUtils::AddSlashAtEnd(std::string& strFolder) +{ + if (IsURL(strFolder)) + { + CURL url(strFolder); + std::string file = url.GetFileName(); + if(!file.empty() && file != strFolder) + { + AddSlashAtEnd(file); + url.SetFileName(file); + strFolder = url.Get(); + } + return; + } + + if (!HasSlashAtEnd(strFolder)) + { + if (IsDOSPath(strFolder)) + strFolder += '\\'; + else + strFolder += '/'; + } +} + +bool URIUtils::HasSlashAtEnd(const std::string& strFile, bool checkURL /* = false */) +{ + if (strFile.empty()) return false; + if (checkURL && IsURL(strFile)) + { + CURL url(strFile); + CStdString file = url.GetFileName(); + return file.empty() || HasSlashAtEnd(file, false); + } + char kar = strFile.c_str()[strFile.size() - 1]; + + if (kar == '/' || kar == '\\') + return true; + + return false; +} + +void URIUtils::RemoveSlashAtEnd(std::string& strFolder) +{ + if (IsURL(strFolder)) + { + CURL url(strFolder); + std::string file = url.GetFileName(); + if (!file.empty() && file != strFolder) + { + RemoveSlashAtEnd(file); + url.SetFileName(file); + strFolder = url.Get(); + return; + } + if(url.GetHostName().empty()) + return; + } + + while (HasSlashAtEnd(strFolder)) + strFolder.erase(strFolder.size()-1, 1); +} + +bool URIUtils::CompareWithoutSlashAtEnd(const CStdString& strPath1, const CStdString& strPath2) +{ + CStdString strc1 = strPath1, strc2 = strPath2; + RemoveSlashAtEnd(strc1); + RemoveSlashAtEnd(strc2); + return strc1.Equals(strc2); +} + + +std::string URIUtils::FixSlashesAndDups(const std::string& path, const char slashCharacter /* = '/' */, const size_t startFrom /*= 0*/) +{ + const size_t len = path.length(); + if (startFrom >= len) + return path; + + std::string result(path, 0, startFrom); + result.reserve(len); + + const char* const str = path.c_str(); + size_t pos = startFrom; + do + { + if (str[pos] == '\\' || str[pos] == '/') + { + result.push_back(slashCharacter); // append one slash + pos++; + // skip any following slashes + while (str[pos] == '\\' || str[pos] == '/') // str is null-terminated, no need to check for buffer overrun + pos++; + } + else + result.push_back(str[pos++]); // append current char and advance pos to next char + + } while (pos < len); + + return result; +} + + +std::string URIUtils::CanonicalizePath(const std::string& path, const char slashCharacter /*= '\\'*/) +{ + assert(slashCharacter == '\\' || slashCharacter == '/'); + + if (path.empty()) + return path; + + const std::string slashStr(1, slashCharacter); + vector<std::string> pathVec, resultVec; + StringUtils::Tokenize(path, pathVec, slashStr); + + for (vector<std::string>::const_iterator it = pathVec.begin(); it != pathVec.end(); ++it) + { + if (*it == ".") + { /* skip - do nothing */ } + else if (*it == ".." && !resultVec.empty() && resultVec.back() != "..") + resultVec.pop_back(); + else + resultVec.push_back(*it); + } + + std::string result; + if (path[0] == slashCharacter) + result.push_back(slashCharacter); // add slash at the begin + + result += StringUtils::Join(resultVec, slashStr); + + if (path[path.length() - 1] == slashCharacter && !result.empty() && result[result.length() - 1] != slashCharacter) + result.push_back(slashCharacter); // add slash at the end if result isn't empty and result isn't "/" + + return result; +} + +CStdString URIUtils::AddFileToFolder(const CStdString& strFolder, + const CStdString& strFile) +{ + if (IsURL(strFolder)) + { + CURL url(strFolder); + if (url.GetFileName() != strFolder) + { + url.SetFileName(AddFileToFolder(url.GetFileName(), strFile)); + return url.Get(); + } + } + + CStdString strResult = strFolder; + if (!strResult.empty()) + AddSlashAtEnd(strResult); + + // Remove any slash at the start of the file + if (strFile.size() && (strFile[0] == '/' || strFile[0] == '\\')) + strResult += strFile.substr(1); + else + strResult += strFile; + + // correct any slash directions + if (!IsDOSPath(strFolder)) + StringUtils::Replace(strResult, '\\', '/'); + else + StringUtils::Replace(strResult, '/', '\\'); + + return strResult; +} + +CStdString URIUtils::GetDirectory(const CStdString &strFilePath) +{ + // Will from a full filename return the directory the file resides in. + // Keeps the final slash at end and possible |option=foo options. + + size_t iPosSlash = strFilePath.find_last_of("/\\"); + if (iPosSlash == string::npos) + return ""; // No slash, so no path (ignore any options) + + size_t iPosBar = strFilePath.rfind('|'); + if (iPosBar == string::npos) + return strFilePath.substr(0, iPosSlash + 1); // Only path + + return strFilePath.substr(0, iPosSlash + 1) + strFilePath.substr(iPosBar); // Path + options +} + +CURL URIUtils::CreateArchivePath(const std::string& type, + const CURL& archiveUrl, + const std::string& pathInArchive, + const std::string& password) +{ + CURL url; + url.SetProtocol(type); + if (!password.empty()) + url.SetUserName(password); + url.SetHostName(archiveUrl.Get()); + + /* NOTE: on posix systems, the replacement of \ with / is incorrect. + Ideally this would not be done. We need to check that the ZipManager + and RarManager code (and elsewhere) doesn't pass in non-posix paths. + */ + std::string strBuffer(pathInArchive); + StringUtils::Replace(strBuffer, '\\', '/'); + StringUtils::TrimLeft(strBuffer, "/"); + url.SetFileName(strBuffer); + + return url; +} + +string URIUtils::GetRealPath(const string &path) +{ + if (path.empty()) + return path; + + CURL url(path); + url.SetHostName(GetRealPath(url.GetHostName())); + url.SetFileName(resolvePath(url.GetFileName())); + + return url.Get(); +} + +std::string URIUtils::resolvePath(const std::string &path) +{ + if (path.empty()) + return path; + + size_t posSlash = path.find('/'); + size_t posBackslash = path.find('\\'); + string delim = posSlash < posBackslash ? "/" : "\\"; + vector<string> parts = StringUtils::Split(path, delim); + vector<string> realParts; + + for (vector<string>::const_iterator part = parts.begin(); part != parts.end(); ++part) + { + if (part->empty() || part->compare(".") == 0) + continue; + + // go one level back up + if (part->compare("..") == 0) + { + if (!realParts.empty()) + realParts.pop_back(); + continue; + } + + realParts.push_back(*part); + } + + CStdString realPath; + // re-add any / or \ at the beginning + for (std::string::const_iterator itPath = path.begin(); itPath != path.end(); ++itPath) + { + if (*itPath != delim.at(0)) + break; + + realPath += delim; + } + // put together the path + realPath += StringUtils::Join(realParts, delim); + // re-add any / or \ at the end + if (path.at(path.size() - 1) == delim.at(0) && realPath.at(realPath.size() - 1) != delim.at(0)) + realPath += delim; + + return realPath; +} + +bool URIUtils::UpdateUrlEncoding(std::string &strFilename) +{ + if (strFilename.empty()) + return false; + + CURL url(strFilename); + // if this is a stack:// URL we need to work with its filename + if (URIUtils::IsStack(strFilename)) + { + vector<string> files; + if (!CStackDirectory::GetPaths(strFilename, files)) + return false; + + for (vector<string>::iterator file = files.begin(); file != files.end(); file++) + UpdateUrlEncoding(*file); + + CStdString stackPath; + if (!CStackDirectory::ConstructStackPath(files, stackPath)) + return false; + + url.Parse(stackPath); + } + // if the protocol has an encoded hostname we need to work with its hostname + else if (URIUtils::HasEncodedHostname(url)) + { + std::string hostname = url.GetHostName(); + UpdateUrlEncoding(hostname); + url.SetHostName(hostname); + } + else + return false; + + std::string newFilename = url.Get(); + if (newFilename == strFilename) + return false; + + strFilename = newFilename; + return true; +} + +bool URIUtils::IsUsingFastSwitch(const CStdString& strFile) +{ + return IsUDP(strFile) || IsTCP(strFile) || IsPVRChannel(strFile); +} diff --git a/src/utils/URIUtils.h b/src/utils/URIUtils.h new file mode 100644 index 0000000000..0094709d0c --- /dev/null +++ b/src/utils/URIUtils.h @@ -0,0 +1,225 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#include "StdString.h" + +class CURL; + +class URIUtils +{ +public: + URIUtils(void); + virtual ~URIUtils(void); + static bool IsInPath(const CStdString &uri, const CStdString &baseURI); + + static CStdString GetDirectory(const CStdString &strFilePath); + + static const CStdString GetFileName(const CURL& url); + static const CStdString GetFileName(const CStdString& strFileNameAndPath); + + static std::string GetExtension(const CURL& url); + static std::string GetExtension(const std::string& strFileName); + + /*! + \brief Check if there is a file extension + \param strFileName Path or URL to check + \return \e true if strFileName have an extension. + \note Returns false when strFileName is empty. + \sa GetExtension + */ + static bool HasExtension(const CStdString& strFileName); + + /*! + \brief Check if filename have any of the listed extensions + \param strFileName Path or URL to check + \param strExtensions List of '.' prefixed lowercase extensions seperated with '|' + \return \e true if strFileName have any one of the extensions. + \note The check is case insensitive for strFileName, but requires + strExtensions to be lowercase. Returns false when strFileName or + strExtensions is empty. + \sa GetExtension + */ + static bool HasExtension(const CStdString& strFileName, const CStdString& strExtensions); + static bool HasExtension(const CURL& url, const CStdString& strExtensions); + + static void RemoveExtension(std::string& strFileName); + static CStdString ReplaceExtension(const CStdString& strFile, + const CStdString& strNewExtension); + static void Split(const std::string& strFileNameAndPath, + std::string& strPath, std::string& strFileName); + static std::vector<std::string> SplitPath(const CStdString& strPath); + + static void GetCommonPath(std::string& strPath, const std::string& strPath2); + static std::string GetParentPath(const std::string& strPath); + static bool GetParentPath(const std::string& strPath, std::string& strParent); + + /* \brief Change the base path of a URL: fromPath/fromFile -> toPath/toFile + Handles changes in path separator and filename URL encoding if necessary to derive toFile. + \param fromPath the base path of the original URL + \param fromFile the filename portion of the original URL + \param toPath the base path of the resulting URL + \return the full path. + */ + static std::string ChangeBasePath(const std::string &fromPath, const std::string &fromFile, const std::string &toPath); + + static CURL SubstitutePath(const CURL& url, bool reverse = false); + static CStdString SubstitutePath(const CStdString& strPath, bool reverse = false); + + /*! \brief Check whether a URL is a given URL scheme. + Comparison is case-insensitve as per RFC1738 + \param url a std::string path. + \param type a lower-case scheme name, e.g. "smb". + \return true if the url is of the given scheme, false otherwise. + \sa PathStarts, PathEquals + */ + static bool IsProtocol(const std::string& url, const std::string& type); + + /*! \brief Check whether a path starts with a given start. + Comparison is case-sensitive. + Use IsProtocol() to compare the protocol portion only. + \param path a std::string path. + \param start the string the start of the path should be compared against. + \return true if the path starts with the given string, false otherwise. + \sa IsProtocol, PathEquals + */ + static bool PathStarts(const std::string& path, const char *start); + + /*! \brief Check whether a path equals another path. + Comparison is case-sensitive. + \param path1 a std::string path. + \param path2 the second path the path should be compared against. + \return true if the paths are equal, false otherwise. + \sa IsProtocol, PathStarts + */ + static bool PathEquals(const std::string& path1, const std::string &path2); + + static bool IsAddonsPath(const CStdString& strFile); + static bool IsSourcesPath(const CStdString& strFile); + static bool IsCDDA(const CStdString& strFile); + static bool IsDAAP(const CStdString& strFile); + static bool IsDAV(const CStdString& strFile); + static bool IsDOSPath(const CStdString &path); + static bool IsDVD(const CStdString& strFile); + static bool IsFTP(const CStdString& strFile); + static bool IsUDP(const CStdString& strFile); + static bool IsTCP(const CStdString& strFile); + static bool IsHD(const CStdString& strFileName); + static bool IsHDHomeRun(const CStdString& strFile); + static bool IsSlingbox(const CStdString& strFile); + static bool IsHTSP(const CStdString& strFile); + static bool IsInArchive(const CStdString& strFile); + static bool IsInRAR(const CStdString& strFile); + static bool IsInternetStream(const std::string& path, bool bStrictCheck = false); + static bool IsInternetStream(const CURL& url, bool bStrictCheck = false); + static bool IsInAPK(const CStdString& strFile); + static bool IsInZIP(const CStdString& strFile); + static bool IsISO9660(const CStdString& strFile); + static bool IsLiveTV(const CStdString& strFile); + static bool IsPVRRecording(const CStdString& strFile); + static bool IsMultiPath(const CStdString& strPath); + static bool IsMusicDb(const CStdString& strFile); + static bool IsMythTV(const CStdString& strFile); + static bool IsNfs(const CStdString& strFile); + static bool IsAfp(const CStdString& strFile); + static bool IsOnDVD(const CStdString& strFile); + static bool IsOnLAN(const CStdString& strFile); + static bool IsHostOnLAN(const CStdString& hostName, bool offLineCheck = false); + static bool IsPlugin(const CStdString& strFile); + static bool IsScript(const CStdString& strFile); + static bool IsRAR(const CStdString& strFile); + static bool IsRemote(const CStdString& strFile); + static bool IsSmb(const CStdString& strFile); + static bool IsSpecial(const CStdString& strFile); + static bool IsStack(const CStdString& strFile); + static bool IsTuxBox(const CStdString& strFile); + static bool IsUPnP(const CStdString& strFile); + static bool IsURL(const CStdString& strFile); + static bool IsVideoDb(const CStdString& strFile); + static bool IsVTP(const CStdString& strFile); + static bool IsAPK(const CStdString& strFile); + static bool IsZIP(const CStdString& strFile); + static bool IsArchive(const CStdString& strFile); + static bool IsBluray(const CStdString& strFile); + static bool IsAndroidApp(const CStdString& strFile); + static bool IsLibraryFolder(const CStdString& strFile); + static bool IsLibraryContent(const std::string& strFile); + static bool IsPVRChannel(const CStdString& strFile); + static bool IsUsingFastSwitch(const CStdString& strFile); + + static void AddSlashAtEnd(std::string& strFolder); + static bool HasSlashAtEnd(const std::string& strFile, bool checkURL = false); + static void RemoveSlashAtEnd(std::string& strFolder); + static bool CompareWithoutSlashAtEnd(const CStdString& strPath1, const CStdString& strPath2); + static std::string FixSlashesAndDups(const std::string& path, const char slashCharacter = '/', const size_t startFrom = 0); + /** + * Convert path to form without duplicated slashes and without relative directories + * Strip duplicated slashes + * Resolve and remove relative directories ("/../" and "/./") + * Will ignore slashes with other direction than specified + * Will not resolve path starting from relative directory + * @warning Don't use with "protocol://path"-style URLs + * @param path string to process + * @param slashCharacter character to use as directory delimiter + * @return transformed path + */ + static std::string CanonicalizePath(const std::string& path, const char slashCharacter = '\\'); + + static CURL CreateArchivePath(const std::string& type, + const CURL& archiveUrl, + const std::string& pathInArchive = "", + const std::string& password = ""); + + static CStdString AddFileToFolder(const CStdString &strFolder, const CStdString &strFile); + + static bool HasParentInHostname(const CURL& url); + static bool HasEncodedHostname(const CURL& url); + static bool HasEncodedFilename(const CURL& url); + + /*! + \brief Cleans up the given path by resolving "." and ".." + and returns it. + + This methods goes through the given path and removes any "." + (as it states "this directory") and resolves any ".." by + removing the previous directory from the path. This is done + for file paths and host names (in case of VFS paths). + + \param path Path to be cleaned up + \return Actual path without any "." or ".." + */ + static std::string GetRealPath(const std::string &path); + + /*! + \brief Updates the URL encoded hostname of the given path + + This method must only be used to update paths encoded with + the old (Eden) URL encoding implementation to the new (Frodo) + URL encoding implementation (which does not URL encode -_.!(). + + \param strFilename Path to update + \return True if the path has been updated/changed otherwise false + */ + static bool UpdateUrlEncoding(std::string &strFilename); + +private: + static std::string resolvePath(const std::string &path); +}; + diff --git a/src/utils/UrlOptions.cpp b/src/utils/UrlOptions.cpp new file mode 100644 index 0000000000..349ce1f916 --- /dev/null +++ b/src/utils/UrlOptions.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <sstream> + +#include "UrlOptions.h" +#include "URL.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +using namespace std; + +CUrlOptions::CUrlOptions() + : m_strLead("") +{ } + +CUrlOptions::CUrlOptions(const std::string &options, const char *strLead /* = "" */) + : m_strLead(strLead) +{ + AddOptions(options); +} + +CUrlOptions::~CUrlOptions() +{ } + +std::string CUrlOptions::GetOptionsString(bool withLeadingSeperator /* = false */) const +{ + std::string options; + for (UrlOptions::const_iterator opt = m_options.begin(); opt != m_options.end(); ++opt) + { + if (opt != m_options.begin()) + options += "&"; + + options += CURL::Encode(opt->first); + if (!opt->second.empty()) + options += "=" + CURL::Encode(opt->second.asString()); + } + + if (withLeadingSeperator && !options.empty()) + { + if (m_strLead.empty()) + options = "?" + options; + else + options = m_strLead + options; + } + + return options; +} + +void CUrlOptions::AddOption(const std::string &key, const char *value) +{ + if (key.empty() || value == NULL) + return; + + return AddOption(key, string(value)); +} + +void CUrlOptions::AddOption(const std::string &key, const std::string &value) +{ + if (key.empty()) + return; + + m_options[key] = value; +} + +void CUrlOptions::AddOption(const std::string &key, int value) +{ + if (key.empty()) + return; + + m_options[key] = value; +} + +void CUrlOptions::AddOption(const std::string &key, float value) +{ + if (key.empty()) + return; + + m_options[key] = value; +} + +void CUrlOptions::AddOption(const std::string &key, double value) +{ + if (key.empty()) + return; + + m_options[key] = value; +} + +void CUrlOptions::AddOption(const std::string &key, bool value) +{ + if (key.empty()) + return; + + m_options[key] = value; +} + +void CUrlOptions::AddOptions(const std::string &options) +{ + if (options.empty()) + return; + + string strOptions = options; + + // if matching the preset leading str, remove from options. + if (!m_strLead.empty() && strOptions.compare(0, m_strLead.length(), m_strLead) == 0) + strOptions.erase(0, m_strLead.length()); + else if (strOptions.at(0) == '?' || strOptions.at(0) == '#' || strOptions.at(0) == ';' || strOptions.at(0) == '|') + { + // remove leading ?, #, ; or | if present + if (!m_strLead.empty()) + CLog::Log(LOGWARNING, "%s: original leading str %s overrided by %c", __FUNCTION__, m_strLead.c_str(), strOptions.at(0)); + m_strLead = strOptions.at(0); + strOptions.erase(0, 1); + } + + // split the options by & and process them one by one + vector<string> optionList = StringUtils::Split(strOptions, "&"); + for (vector<string>::const_iterator option = optionList.begin(); option != optionList.end(); ++option) + { + if (option->empty()) + continue; + + string key, value; + + size_t pos = option->find('='); + key = CURL::Decode(option->substr(0, pos)); + if (pos != string::npos) + value = CURL::Decode(option->substr(pos + 1)); + + // the key cannot be empty + if (!key.empty()) + AddOption(key, value); + } +} + +void CUrlOptions::AddOptions(const CUrlOptions &options) +{ + m_options.insert(options.m_options.begin(), options.m_options.end()); +} + +void CUrlOptions::RemoveOption(const std::string &key) +{ + if (key.empty()) + return; + + UrlOptions::iterator option = m_options.find(key); + if (option != m_options.end()) + m_options.erase(option); +} + +bool CUrlOptions::HasOption(const std::string &key) const +{ + if (key.empty()) + return false; + + return m_options.find(key) != m_options.end(); +} + +bool CUrlOptions::GetOption(const std::string &key, CVariant &value) const +{ + if (key.empty()) + return false; + + UrlOptions::const_iterator option = m_options.find(key); + if (option == m_options.end()) + return false; + + value = option->second; + return true; +} diff --git a/src/utils/UrlOptions.h b/src/utils/UrlOptions.h new file mode 100644 index 0000000000..a86cd1d214 --- /dev/null +++ b/src/utils/UrlOptions.h @@ -0,0 +1,57 @@ +#pragma once +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> +#include <string> + +#include "utils/Variant.h" + +class CUrlOptions +{ +public: + typedef std::map<std::string, CVariant> UrlOptions; + + CUrlOptions(); + CUrlOptions(const std::string &options, const char *strLead = ""); + virtual ~CUrlOptions(); + + virtual void Clear() { m_options.clear(); m_strLead = ""; } + + virtual const UrlOptions& GetOptions() const { return m_options; } + virtual std::string GetOptionsString(bool withLeadingSeperator = false) const; + + virtual void AddOption(const std::string &key, const char *value); + virtual void AddOption(const std::string &key, const std::string &value); + virtual void AddOption(const std::string &key, int value); + virtual void AddOption(const std::string &key, float value); + virtual void AddOption(const std::string &key, double value); + virtual void AddOption(const std::string &key, bool value); + virtual void AddOptions(const std::string &options); + virtual void AddOptions(const CUrlOptions &options); + virtual void RemoveOption(const std::string &key); + + virtual bool HasOption(const std::string &key) const; + virtual bool GetOption(const std::string &key, CVariant &value) const; + +protected: + UrlOptions m_options; + std::string m_strLead; +}; diff --git a/src/utils/Utf8Utils.cpp b/src/utils/Utf8Utils.cpp new file mode 100644 index 0000000000..d9efff510c --- /dev/null +++ b/src/utils/Utf8Utils.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (C) 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "Utf8Utils.h" + + +CUtf8Utils::utf8CheckResult CUtf8Utils::checkStrForUtf8(const std::string& str) +{ + const char* const strC = str.c_str(); + const size_t len = str.length(); + size_t pos = 0; + bool isPlainAscii = true; + + while (pos < len) + { + const size_t chrLen = SizeOfUtf8Char(strC + pos); + if (chrLen == 0) + return hiAscii; // non valid UTF-8 sequence + else if (chrLen > 1) + isPlainAscii = false; + + pos += chrLen; + } + + if (isPlainAscii) + return plainAscii; // only single-byte characters (valid for US-ASCII and for UTF-8) + + return utf8string; // valid UTF-8 with at least one valid UTF-8 multi-byte sequence +} + + + +size_t CUtf8Utils::FindValidUtf8Char(const std::string& str, const size_t startPos /*= 0*/) +{ + const char* strC = str.c_str(); + const size_t len = str.length(); + + size_t pos = startPos; + while (pos < len) + { + if (SizeOfUtf8Char(strC + pos)) + return pos; + + pos++; + } + + return std::string::npos; +} + +size_t CUtf8Utils::RFindValidUtf8Char(const std::string& str, const size_t startPos) +{ + const size_t len = str.length(); + if (!len) + return std::string::npos; + + const char* strC = str.c_str(); + size_t pos = (startPos >= len) ? len - 1 : startPos; + while (pos < len) // pos is unsigned, after zero pos becomes large then len + { + if (SizeOfUtf8Char(strC + pos)) + return pos; + + pos--; + } + + return std::string::npos; +} + +inline size_t CUtf8Utils::SizeOfUtf8Char(const std::string& str, const size_t charStart /*= 0*/) +{ + if (charStart >= str.length()) + return std::string::npos; + + return SizeOfUtf8Char(str.c_str() + charStart); +} + +// must be used only internally in class! +// str must be null-terminated +inline size_t CUtf8Utils::SizeOfUtf8Char(const char* const str) +{ + if (!str) + return 0; + + const unsigned char* const strU = (const unsigned char*)str; + const unsigned char chr = strU[0]; + + /* this is an implementation of http://www.unicode.org/versions/Unicode6.2.0/ch03.pdf#G27506 */ + + /* U+0000 - U+007F in UTF-8 */ + if (chr <= 0x7F) + return 1; + + /* U+0080 - U+07FF in UTF-8 */ /* binary representation and range */ + if (chr >= 0xC2 && chr <= 0xDF /* C2=1100 0010 - DF=1101 1111 */ + // as str is null terminated, + && ((strU[1] & 0xC0) == 0x80)) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */ + return 2; // valid UTF-8 2 bytes sequence + + /* U+0800 - U+0FFF in UTF-8 */ + if (chr == 0xE0 /* E0=1110 0000 */ + && (strU[1] & 0xE0) == 0xA0 /* E0=1110 0000, A0=1010 0000 - BF=1011 1111 */ + && (strU[2] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */ + return 3; // valid UTF-8 3 bytes sequence + + /* U+1000 - U+CFFF in UTF-8 */ + /* skip U+D000 - U+DFFF (handled later) */ + /* U+E000 - U+FFFF in UTF-8 */ + if (((chr >= 0xE1 && chr <= 0xEC) /* E1=1110 0001 - EC=1110 1100 */ + || chr == 0xEE || chr == 0xEF) /* EE=1110 1110 - EF=1110 1111 */ + && (strU[1] & 0xC0) == 0x80 /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */ + && (strU[2] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */ + return 3; // valid UTF-8 3 bytes sequence + + /* U+D000 - U+D7FF in UTF-8 */ + /* note: range U+D800 - U+DFFF is reserved and invalid */ + if (chr == 0xED /* ED=1110 1101 */ + && (strU[1] & 0xE0) == 0x80 /* E0=1110 0000, 80=1000 0000 - 9F=1001 1111 */ + && (strU[2] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */ + return 3; // valid UTF-8 3 bytes sequence + + /* U+10000 - U+3FFFF in UTF-8 */ + if (chr == 0xF0 /* F0=1111 0000 */ + && (strU[1] & 0xE0) == 0x80 /* E0=1110 0000, 80=1000 0000 - 9F=1001 1111 */ + && strU[2] >= 0x90 && strU[2] <= 0xBF /* 90=1001 0000 - BF=1011 1111 */ + && (strU[3] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */ + return 4; // valid UTF-8 4 bytes sequence + + /* U+40000 - U+FFFFF in UTF-8 */ + if (chr >= 0xF1 && chr <= 0xF3 /* F1=1111 0001 - F3=1111 0011 */ + && (strU[1] & 0xC0) == 0x80 /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */ + && (strU[2] & 0xC0) == 0x80 /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */ + && (strU[3] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */ + return 4; // valid UTF-8 4 bytes sequence + + /* U+100000 - U+10FFFF in UTF-8 */ + if (chr == 0xF4 /* F4=1111 0100 */ + && (strU[1] & 0xF0) == 0x80 /* F0=1111 0000, 80=1000 0000 - 8F=1000 1111 */ + && (strU[2] & 0xC0) == 0x80 /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */ + && (strU[3] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */ + return 4; // valid UTF-8 4 bytes sequence + + return 0; // invalid UTF-8 char sequence +} diff --git a/src/utils/Utf8Utils.h b/src/utils/Utf8Utils.h new file mode 100644 index 0000000000..a34518eb1b --- /dev/null +++ b/src/utils/Utf8Utils.h @@ -0,0 +1,55 @@ +#pragma once + +/* + * Copyright (C) 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 + * <http://www.gnu.org/licenses/>. + * + */ + + +#include <string> + +class CUtf8Utils +{ +public: + enum utf8CheckResult + { + plainAscii = -1, // only US-ASCII characters (valid for UTF-8 too) + hiAscii = 0, // non-UTF-8 sequence with high ASCII characters + // (possible single-byte national encoding like WINDOWS-1251, multi-byte encoding like UTF-32 or invalid UTF-8) + utf8string = 1 // valid UTF-8 sequences, but not US-ASCII only + }; + + /** + * Check given string for valid UTF-8 sequences + * @param str string to check + * @return result of check, "plainAscii" for empty string + */ + static utf8CheckResult checkStrForUtf8(const std::string& str); + + static inline bool isValidUtf8(const std::string& str) + { + return checkStrForUtf8(str) != hiAscii; + } + + static size_t FindValidUtf8Char(const std::string& str, const size_t startPos = 0); + static size_t RFindValidUtf8Char(const std::string& str, const size_t startPos); + + static size_t SizeOfUtf8Char(const std::string& str, const size_t charStart = 0); +private: + static size_t SizeOfUtf8Char(const char* const str); +}; diff --git a/src/utils/Variant.cpp b/src/utils/Variant.cpp new file mode 100644 index 0000000000..bb2b493597 --- /dev/null +++ b/src/utils/Variant.cpp @@ -0,0 +1,794 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <string.h> +#include <sstream> + +#include "Variant.h" + +#ifndef strtoll +#ifdef TARGET_WINDOWS +#define strtoll _strtoi64 +#define strtoull _strtoui64 +#define wcstoll _wcstoi64 +#define wcstoull _wcstoui64 +#else // TARGET_WINDOWS +#define strtoll(str, endptr, base) (int64_t)strtod(str, endptr) +#define strtoull(str, endptr, base) (uint64_t)strtod(str, endptr) +#define wcstoll(str, endptr, base) (int64_t)wcstod(str, endptr) +#define wcstoull(str, endptr, base) (uint64_t)wcstod(str, endptr) +#endif // TARGET_WINDOWS +#endif // strtoll + +using namespace std; + +string trimRight(const string &str) +{ + string tmp = str; + // find_last_not_of will return string::npos (which is defined as -1) + // or a value between 0 and size() - 1 => find_last_not_of() + 1 will + // always result in a valid index between 0 and size() + tmp.erase(tmp.find_last_not_of(" \n\r\t") + 1); + + return tmp; +} + +wstring trimRight(const wstring &str) +{ + wstring tmp = str; + // find_last_not_of will return string::npos (which is defined as -1) + // or a value between 0 and size() - 1 => find_last_not_of() + 1 will + // always result in a valid index between 0 and size() + tmp.erase(tmp.find_last_not_of(L" \n\r\t") + 1); + + return tmp; +} + +int64_t str2int64(const string &str, int64_t fallback /* = 0 */) +{ + char *end = NULL; + string tmp = trimRight(str); + int64_t result = strtoll(tmp.c_str(), &end, 0); + if (end == NULL || *end == '\0') + return result; + + return fallback; +} + +int64_t str2int64(const wstring &str, int64_t fallback /* = 0 */) +{ + wchar_t *end = NULL; + wstring tmp = trimRight(str); + int64_t result = wcstoll(tmp.c_str(), &end, 0); + if (end == NULL || *end == '\0') + return result; + + return fallback; +} + +uint64_t str2uint64(const string &str, uint64_t fallback /* = 0 */) +{ + char *end = NULL; + string tmp = trimRight(str); + uint64_t result = strtoull(tmp.c_str(), &end, 0); + if (end == NULL || *end == '\0') + return result; + + return fallback; +} + +uint64_t str2uint64(const wstring &str, uint64_t fallback /* = 0 */) +{ + wchar_t *end = NULL; + wstring tmp = trimRight(str); + uint64_t result = wcstoull(tmp.c_str(), &end, 0); + if (end == NULL || *end == '\0') + return result; + + return fallback; +} + +double str2double(const string &str, double fallback /* = 0.0 */) +{ + char *end = NULL; + string tmp = trimRight(str); + double result = strtod(tmp.c_str(), &end); + if (end == NULL || *end == '\0') + return result; + + return fallback; +} + +double str2double(const wstring &str, double fallback /* = 0.0 */) +{ + wchar_t *end = NULL; + wstring tmp = trimRight(str); + double result = wcstod(tmp.c_str(), &end); + if (end == NULL || *end == '\0') + return result; + + return fallback; +} + +CVariant CVariant::ConstNullVariant = CVariant::VariantTypeConstNull; + +CVariant::CVariant(VariantType type) +{ + m_type = type; + + switch (type) + { + case VariantTypeInteger: + m_data.integer = 0; + break; + case VariantTypeUnsignedInteger: + m_data.unsignedinteger = 0; + break; + case VariantTypeBoolean: + m_data.boolean = false; + break; + case VariantTypeDouble: + m_data.dvalue = 0.0; + break; + case VariantTypeString: + m_data.string = new string(); + break; + case VariantTypeWideString: + m_data.wstring = new wstring(); + break; + case VariantTypeArray: + m_data.array = new VariantArray(); + break; + case VariantTypeObject: + m_data.map = new VariantMap(); + break; + default: + memset(&m_data, 0, sizeof(m_data)); + break; + } +} + +CVariant::CVariant(int integer) +{ + m_type = VariantTypeInteger; + m_data.integer = integer; +} + +CVariant::CVariant(int64_t integer) +{ + m_type = VariantTypeInteger; + m_data.integer = integer; +} + +CVariant::CVariant(unsigned int unsignedinteger) +{ + m_type = VariantTypeUnsignedInteger; + m_data.unsignedinteger = unsignedinteger; +} + +CVariant::CVariant(uint64_t unsignedinteger) +{ + m_type = VariantTypeUnsignedInteger; + m_data.unsignedinteger = unsignedinteger; +} + +CVariant::CVariant(double value) +{ + m_type = VariantTypeDouble; + m_data.dvalue = value; +} + +CVariant::CVariant(float value) +{ + m_type = VariantTypeDouble; + m_data.dvalue = (double)value; +} + +CVariant::CVariant(bool boolean) +{ + m_type = VariantTypeBoolean; + m_data.boolean = boolean; +} + +CVariant::CVariant(const char *str) +{ + m_type = VariantTypeString; + m_data.string = new string(str); +} + +CVariant::CVariant(const char *str, unsigned int length) +{ + m_type = VariantTypeString; + m_data.string = new string(str, length); +} + +CVariant::CVariant(const string &str) +{ + m_type = VariantTypeString; + m_data.string = new string(str); +} + +CVariant::CVariant(const wchar_t *str) +{ + m_type = VariantTypeWideString; + m_data.wstring = new wstring(str); +} + +CVariant::CVariant(const wchar_t *str, unsigned int length) +{ + m_type = VariantTypeWideString; + m_data.wstring = new wstring(str, length); +} + +CVariant::CVariant(const wstring &str) +{ + m_type = VariantTypeWideString; + m_data.wstring = new wstring(str); +} + +CVariant::CVariant(const std::vector<std::string> &strArray) +{ + m_type = VariantTypeArray; + m_data.array = new VariantArray; + m_data.array->reserve(strArray.size()); + for (unsigned int index = 0; index < strArray.size(); index++) + m_data.array->push_back(strArray.at(index)); +} + +CVariant::CVariant(const std::map<std::string, std::string> &strMap) +{ + m_type = VariantTypeObject; + m_data.map = new VariantMap; + for (std::map<std::string, std::string>::const_iterator it = strMap.begin(); it != strMap.end(); ++it) + m_data.map->insert(make_pair(it->first, CVariant(it->second))); +} + +CVariant::CVariant(const std::map<std::string, CVariant> &variantMap) +{ + m_type = VariantTypeObject; + m_data.map = new VariantMap(variantMap.begin(), variantMap.end()); +} + +CVariant::CVariant(const CVariant &variant) +{ + m_type = VariantTypeNull; + *this = variant; +} + +CVariant::~CVariant() +{ + cleanup(); +} + +void CVariant::cleanup() +{ + if (m_type == VariantTypeString) + delete m_data.string; + else if (m_type == VariantTypeWideString) + delete m_data.wstring; + else if (m_type == VariantTypeArray) + delete m_data.array; + else if (m_type == VariantTypeObject) + delete m_data.map; + m_type = VariantTypeNull; +} + +bool CVariant::isInteger() const +{ + return m_type == VariantTypeInteger; +} + +bool CVariant::isUnsignedInteger() const +{ + return m_type == VariantTypeUnsignedInteger; +} + +bool CVariant::isBoolean() const +{ + return m_type == VariantTypeBoolean; +} + +bool CVariant::isDouble() const +{ + return m_type == VariantTypeDouble; +} + +bool CVariant::isString() const +{ + return m_type == VariantTypeString; +} + +bool CVariant::isWideString() const +{ + return m_type == VariantTypeWideString; +} + +bool CVariant::isArray() const +{ + return m_type == VariantTypeArray; +} + +bool CVariant::isObject() const +{ + return m_type == VariantTypeObject; +} + +bool CVariant::isNull() const +{ + return m_type == VariantTypeNull || m_type == VariantTypeConstNull; +} + +CVariant::VariantType CVariant::type() const +{ + return m_type; +} + +int64_t CVariant::asInteger(int64_t fallback) const +{ + switch (m_type) + { + case VariantTypeInteger: + return m_data.integer; + case VariantTypeUnsignedInteger: + return (int64_t)m_data.unsignedinteger; + case VariantTypeDouble: + return (int64_t)m_data.dvalue; + case VariantTypeString: + return str2int64(*m_data.string, fallback); + case VariantTypeWideString: + return str2int64(*m_data.wstring, fallback); + default: + return fallback; + } + + return fallback; +} + +uint64_t CVariant::asUnsignedInteger(uint64_t fallback) const +{ + switch (m_type) + { + case VariantTypeUnsignedInteger: + return m_data.unsignedinteger; + case VariantTypeInteger: + return (uint64_t)m_data.integer; + case VariantTypeDouble: + return (uint64_t)m_data.dvalue; + case VariantTypeString: + return str2uint64(*m_data.string, fallback); + case VariantTypeWideString: + return str2uint64(*m_data.wstring, fallback); + default: + return fallback; + } + + return fallback; +} + +double CVariant::asDouble(double fallback) const +{ + switch (m_type) + { + case VariantTypeDouble: + return m_data.dvalue; + case VariantTypeInteger: + return (double)m_data.integer; + case VariantTypeUnsignedInteger: + return (double)m_data.unsignedinteger; + case VariantTypeString: + return str2double(*m_data.string, fallback); + case VariantTypeWideString: + return str2double(*m_data.wstring, fallback); + default: + return fallback; + } + + return fallback; +} + +float CVariant::asFloat(float fallback) const +{ + switch (m_type) + { + case VariantTypeDouble: + return (float)m_data.dvalue; + case VariantTypeInteger: + return (float)m_data.integer; + case VariantTypeUnsignedInteger: + return (float)m_data.unsignedinteger; + case VariantTypeString: + return (float)str2double(*m_data.string, fallback); + case VariantTypeWideString: + return (float)str2double(*m_data.wstring, fallback); + default: + return fallback; + } + + return fallback; +} + +bool CVariant::asBoolean(bool fallback) const +{ + switch (m_type) + { + case VariantTypeBoolean: + return m_data.boolean; + case VariantTypeInteger: + return (m_data.integer != 0); + case VariantTypeUnsignedInteger: + return (m_data.unsignedinteger != 0); + case VariantTypeDouble: + return (m_data.dvalue != 0); + case VariantTypeString: + if (m_data.string->empty() || m_data.string->compare("0") == 0 || m_data.string->compare("false") == 0) + return false; + return true; + case VariantTypeWideString: + if (m_data.wstring->empty() || m_data.wstring->compare(L"0") == 0 || m_data.wstring->compare(L"false") == 0) + return false; + return true; + default: + return fallback; + } + + return fallback; +} + +std::string CVariant::asString(const std::string &fallback /* = "" */) const +{ + switch (m_type) + { + case VariantTypeString: + return *m_data.string; + case VariantTypeBoolean: + return m_data.boolean ? "true" : "false"; + case VariantTypeInteger: + case VariantTypeUnsignedInteger: + case VariantTypeDouble: + { + std::ostringstream strStream; + if (m_type == VariantTypeInteger) + strStream << m_data.integer; + else if (m_type == VariantTypeUnsignedInteger) + strStream << m_data.unsignedinteger; + else + strStream << m_data.dvalue; + return strStream.str(); + } + default: + return fallback; + } + + return fallback; +} + +std::wstring CVariant::asWideString(const std::wstring &fallback /* = L"" */) const +{ + switch (m_type) + { + case VariantTypeWideString: + return *m_data.wstring; + case VariantTypeBoolean: + return m_data.boolean ? L"true" : L"false"; + case VariantTypeInteger: + case VariantTypeUnsignedInteger: + case VariantTypeDouble: + { + std::wostringstream strStream; + if (m_type == VariantTypeInteger) + strStream << m_data.integer; + else if (m_type == VariantTypeUnsignedInteger) + strStream << m_data.unsignedinteger; + else + strStream << m_data.dvalue; + return strStream.str(); + } + default: + return fallback; + } + + return fallback; +} + +CVariant &CVariant::operator[](const std::string &key) +{ + if (m_type == VariantTypeNull) + { + m_type = VariantTypeObject; + m_data.map = new VariantMap; + } + + if (m_type == VariantTypeObject) + return (*m_data.map)[key]; + else + return ConstNullVariant; +} + +const CVariant &CVariant::operator[](const std::string &key) const +{ + VariantMap::const_iterator it; + if (m_type == VariantTypeObject && (it = m_data.map->find(key)) != m_data.map->end()) + return it->second; + else + return ConstNullVariant; +} + +CVariant &CVariant::operator[](unsigned int position) +{ + if (m_type == VariantTypeArray && size() > position) + return m_data.array->at(position); + else + return ConstNullVariant; +} + +const CVariant &CVariant::operator[](unsigned int position) const +{ + if (m_type == VariantTypeArray && size() > position) + return m_data.array->at(position); + else + return ConstNullVariant; +} + +CVariant &CVariant::operator=(const CVariant &rhs) +{ + if (m_type == VariantTypeConstNull || this == &rhs) + return *this; + + cleanup(); + + m_type = rhs.m_type; + + switch (m_type) + { + case VariantTypeInteger: + m_data.integer = rhs.m_data.integer; + break; + case VariantTypeUnsignedInteger: + m_data.integer = rhs.m_data.unsignedinteger; + break; + case VariantTypeBoolean: + m_data.boolean = rhs.m_data.boolean; + break; + case VariantTypeDouble: + m_data.dvalue = rhs.m_data.dvalue; + break; + case VariantTypeString: + m_data.string = new string(*rhs.m_data.string); + break; + case VariantTypeWideString: + m_data.wstring = new wstring(*rhs.m_data.wstring); + break; + case VariantTypeArray: + m_data.array = new VariantArray(rhs.m_data.array->begin(), rhs.m_data.array->end()); + break; + case VariantTypeObject: + m_data.map = new VariantMap(rhs.m_data.map->begin(), rhs.m_data.map->end()); + break; + default: + break; + } + + return *this; +} + +bool CVariant::operator==(const CVariant &rhs) const +{ + if (m_type == rhs.m_type) + { + switch (m_type) + { + case VariantTypeInteger: + return m_data.integer == rhs.m_data.integer; + case VariantTypeUnsignedInteger: + return m_data.unsignedinteger == rhs.m_data.unsignedinteger; + case VariantTypeBoolean: + return m_data.boolean == rhs.m_data.boolean; + case VariantTypeDouble: + return m_data.dvalue == rhs.m_data.dvalue; + case VariantTypeString: + return *m_data.string == *rhs.m_data.string; + case VariantTypeWideString: + return *m_data.wstring == *rhs.m_data.wstring; + case VariantTypeArray: + return *m_data.array == *rhs.m_data.array; + case VariantTypeObject: + return *m_data.map == *rhs.m_data.map; + default: + break; + } + } + + return false; +} + +void CVariant::push_back(const CVariant &variant) +{ + if (m_type == VariantTypeNull) + { + m_type = VariantTypeArray; + m_data.array = new VariantArray; + } + + if (m_type == VariantTypeArray) + m_data.array->push_back(variant); +} + +void CVariant::append(const CVariant &variant) +{ + push_back(variant); +} + +const char *CVariant::c_str() const +{ + if (m_type == VariantTypeString) + return m_data.string->c_str(); + else + return NULL; +} + +void CVariant::swap(CVariant &rhs) +{ + VariantType temp_type = m_type; + VariantUnion temp_data = m_data; + + m_type = rhs.m_type; + m_data = rhs.m_data; + + rhs.m_type = temp_type; + rhs.m_data = temp_data; +} + +CVariant::iterator_array CVariant::begin_array() +{ + if (m_type == VariantTypeArray) + return m_data.array->begin(); + else + return iterator_array(); +} + +CVariant::const_iterator_array CVariant::begin_array() const +{ + if (m_type == VariantTypeArray) + return m_data.array->begin(); + else + return const_iterator_array(); +} + +CVariant::iterator_array CVariant::end_array() +{ + if (m_type == VariantTypeArray) + return m_data.array->end(); + else + return iterator_array(); +} + +CVariant::const_iterator_array CVariant::end_array() const +{ + if (m_type == VariantTypeArray) + return m_data.array->end(); + else + return const_iterator_array(); +} + +CVariant::iterator_map CVariant::begin_map() +{ + if (m_type == VariantTypeObject) + return m_data.map->begin(); + else + return iterator_map(); +} + +CVariant::const_iterator_map CVariant::begin_map() const +{ + if (m_type == VariantTypeObject) + return m_data.map->begin(); + else + return const_iterator_map(); +} + +CVariant::iterator_map CVariant::end_map() +{ + if (m_type == VariantTypeObject) + return m_data.map->end(); + else + return iterator_map(); +} + +CVariant::const_iterator_map CVariant::end_map() const +{ + if (m_type == VariantTypeObject) + return m_data.map->end(); + else + return const_iterator_map(); +} + +unsigned int CVariant::size() const +{ + if (m_type == VariantTypeObject) + return m_data.map->size(); + else if (m_type == VariantTypeArray) + return m_data.array->size(); + else if (m_type == VariantTypeString) + return m_data.string->size(); + else if (m_type == VariantTypeWideString) + return m_data.wstring->size(); + else + return 0; +} + +bool CVariant::empty() const +{ + if (m_type == VariantTypeObject) + return m_data.map->empty(); + else if (m_type == VariantTypeArray) + return m_data.array->empty(); + else if (m_type == VariantTypeString) + return m_data.string->empty(); + else if (m_type == VariantTypeWideString) + return m_data.wstring->empty(); + else if (m_type == VariantTypeNull) + return true; + + return false; +} + +void CVariant::clear() +{ + if (m_type == VariantTypeObject) + m_data.map->clear(); + else if (m_type == VariantTypeArray) + m_data.array->clear(); + else if (m_type == VariantTypeString) + m_data.string->clear(); + else if (m_type == VariantTypeWideString) + m_data.wstring->clear(); +} + +void CVariant::erase(const std::string &key) +{ + if (m_type == VariantTypeNull) + { + m_type = VariantTypeObject; + m_data.map = new VariantMap; + } + else if (m_type == VariantTypeObject) + m_data.map->erase(key); +} + +void CVariant::erase(unsigned int position) +{ + if (m_type == VariantTypeNull) + { + m_type = VariantTypeArray; + m_data.array = new VariantArray; + } + + if (m_type == VariantTypeArray && position < size()) + m_data.array->erase(m_data.array->begin() + position); +} + +bool CVariant::isMember(const std::string &key) const +{ + if (m_type == VariantTypeObject) + return m_data.map->find(key) != m_data.map->end(); + + return false; +} diff --git a/src/utils/Variant.h b/src/utils/Variant.h new file mode 100644 index 0000000000..2be0d7e606 --- /dev/null +++ b/src/utils/Variant.h @@ -0,0 +1,154 @@ +#pragma once +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ +#include <map> +#include <vector> +#include <string> +#include <stdint.h> +#include <wchar.h> + +int64_t str2int64(const std::string &str, int64_t fallback = 0); +int64_t str2int64(const std::wstring &str, int64_t fallback = 0); +uint64_t str2uint64(const std::string &str, uint64_t fallback = 0); +uint64_t str2uint64(const std::wstring &str, uint64_t fallback = 0); +double str2double(const std::string &str, double fallback = 0.0); +double str2double(const std::wstring &str, double fallback = 0.0); + +class CVariant +{ +public: + enum VariantType + { + VariantTypeInteger, + VariantTypeUnsignedInteger, + VariantTypeBoolean, + VariantTypeString, + VariantTypeWideString, + VariantTypeDouble, + VariantTypeArray, + VariantTypeObject, + VariantTypeNull, + VariantTypeConstNull + }; + + CVariant(VariantType type = VariantTypeNull); + CVariant(int integer); + CVariant(int64_t integer); + CVariant(unsigned int unsignedinteger); + CVariant(uint64_t unsignedinteger); + CVariant(double value); + CVariant(float value); + CVariant(bool boolean); + CVariant(const char *str); + CVariant(const char *str, unsigned int length); + CVariant(const std::string &str); + CVariant(const wchar_t *str); + CVariant(const wchar_t *str, unsigned int length); + CVariant(const std::wstring &str); + CVariant(const std::vector<std::string> &strArray); + CVariant(const std::map<std::string, std::string> &strMap); + CVariant(const std::map<std::string, CVariant> &variantMap); + CVariant(const CVariant &variant); + ~CVariant(); + + bool isInteger() const; + bool isUnsignedInteger() const; + bool isBoolean() const; + bool isString() const; + bool isWideString() const; + bool isDouble() const; + bool isArray() const; + bool isObject() const; + bool isNull() const; + + VariantType type() const; + + int64_t asInteger(int64_t fallback = 0) const; + uint64_t asUnsignedInteger(uint64_t fallback = 0u) const; + bool asBoolean(bool fallback = false) const; + std::string asString(const std::string &fallback = "") const; + std::wstring asWideString(const std::wstring &fallback = L"") const; + double asDouble(double fallback = 0.0) const; + float asFloat(float fallback = 0.0f) const; + + CVariant &operator[](const std::string &key); + const CVariant &operator[](const std::string &key) const; + CVariant &operator[](unsigned int position); + const CVariant &operator[](unsigned int position) const; + + CVariant &operator=(const CVariant &rhs); + bool operator==(const CVariant &rhs) const; + bool operator!=(const CVariant &rhs) const { return !(*this == rhs); } + + void push_back(const CVariant &variant); + void append(const CVariant &variant); + + const char *c_str() const; + + void swap(CVariant &rhs); + +private: + typedef std::vector<CVariant> VariantArray; + typedef std::map<std::string, CVariant> VariantMap; + +public: + typedef VariantArray::iterator iterator_array; + typedef VariantArray::const_iterator const_iterator_array; + + typedef VariantMap::iterator iterator_map; + typedef VariantMap::const_iterator const_iterator_map; + + iterator_array begin_array(); + const_iterator_array begin_array() const; + iterator_array end_array(); + const_iterator_array end_array() const; + + iterator_map begin_map(); + const_iterator_map begin_map() const; + iterator_map end_map(); + const_iterator_map end_map() const; + + unsigned int size() const; + bool empty() const; + void clear(); + void erase(const std::string &key); + void erase(unsigned int position); + + bool isMember(const std::string &key) const; + + static CVariant ConstNullVariant; + +private: + void cleanup(); + union VariantUnion + { + int64_t integer; + uint64_t unsignedinteger; + bool boolean; + double dvalue; + std::string *string; + std::wstring *wstring; + VariantArray *array; + VariantMap *map; + }; + + VariantType m_type; + VariantUnion m_data; +}; diff --git a/src/utils/Vector.cpp b/src/utils/Vector.cpp new file mode 100644 index 0000000000..ba6d046bb6 --- /dev/null +++ b/src/utils/Vector.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <math.h> + +#include "Vector.h" + +CVector::CVector() +{ + reset(); +} + +CVector::CVector(float xCoord, float yCoord) + : x(xCoord), + y(yCoord) +{ } + +void CVector::reset() +{ + x = 0.0f; + y = 0.0f; +} + +const CVector CVector::operator+(const CVector &other) const +{ + return CVector(x + other.x, y + other.y); +} + +const CVector CVector::operator-(const CVector &other) const +{ + return CVector(x - other.x, y - other.y); +} + +CVector& CVector::operator+=(const CVector &other) +{ + x += other.x; + y += other.y; + + return *this; +} + +CVector& CVector::operator-=(const CVector &other) +{ + x -= other.x; + y -= other.y; + + return *this; +} + +float CVector::scalar(const CVector &other) const +{ + return x * other.x + y * other.y; +} + +float CVector::length() const +{ + return sqrt(pow(x, 2) + pow(y, 2)); +} diff --git a/src/utils/Vector.h b/src/utils/Vector.h new file mode 100644 index 0000000000..3c541ca0d7 --- /dev/null +++ b/src/utils/Vector.h @@ -0,0 +1,41 @@ +#pragma once +/* + * Copyright (C) 2012-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 + * <http://www.gnu.org/licenses/>. + * + */ + +class CVector +{ +public: + CVector(); + CVector(float xCoord, float yCoord); + virtual ~CVector() { } + + virtual void reset(); + + const CVector operator+(const CVector &other) const; + const CVector operator-(const CVector &other) const; + CVector& operator+=(const CVector &other); + CVector& operator-=(const CVector &other); + + float scalar(const CVector &other) const; + float length() const; + + float x; + float y; +};
\ No newline at end of file diff --git a/src/utils/Weather.cpp b/src/utils/Weather.cpp new file mode 100644 index 0000000000..a85cb15538 --- /dev/null +++ b/src/utils/Weather.cpp @@ -0,0 +1,501 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#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<std::string> 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<string> words = StringUtils::Split(str, " "); + for (vector<string>::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;i<NUM_DAYS;++i) + { + std::string strDay = StringUtils::Format("Day%i.Title",i); + m_info.forecast[i].m_day = window->GetProperty(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(); + } + } +} + diff --git a/src/utils/Weather.h b/src/utils/Weather.h new file mode 100644 index 0000000000..1ed5eba90a --- /dev/null +++ b/src/utils/Weather.h @@ -0,0 +1,173 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "InfoLoader.h" +#include "settings/lib/ISettingCallback.h" +#include "utils/GlobalsHandling.h" + +#include <map> +#include <string> + +class TiXmlElement; + +#define WEATHER_LABEL_LOCATION 10 +#define WEATHER_IMAGE_CURRENT_ICON 21 +#define WEATHER_LABEL_CURRENT_COND 22 +#define WEATHER_LABEL_CURRENT_TEMP 23 +#define WEATHER_LABEL_CURRENT_FEEL 24 +#define WEATHER_LABEL_CURRENT_UVID 25 +#define WEATHER_LABEL_CURRENT_WIND 26 +#define WEATHER_LABEL_CURRENT_DEWP 27 +#define WEATHER_LABEL_CURRENT_HUMI 28 + +struct day_forecast +{ + std::string m_icon; + std::string m_overview; + std::string m_day; + std::string m_high; + std::string m_low; +}; + +#define NUM_DAYS 7 + +class CWeatherInfo +{ +public: + day_forecast forecast[NUM_DAYS]; + + void Reset() + { + lastUpdateTime.clear(); + currentIcon.clear(); + currentConditions.clear(); + currentTemperature.clear(); + currentFeelsLike.clear(); + currentWind.clear(); + currentHumidity.clear(); + currentUVIndex.clear(); + currentDewPoint.clear(); + + for (int i = 0; i < NUM_DAYS; i++) + { + forecast[i].m_icon.clear(); + forecast[i].m_overview.clear(); + forecast[i].m_day.clear(); + forecast[i].m_high.clear(); + forecast[i].m_low.clear(); + } + }; + + std::string lastUpdateTime; + std::string location; + std::string currentIcon; + std::string currentConditions; + std::string currentTemperature; + std::string currentFeelsLike; + std::string currentUVIndex; + std::string currentWind; + std::string currentDewPoint; + std::string currentHumidity; + std::string busyString; + std::string naIcon; +}; + +class CWeatherJob : public CJob +{ +public: + CWeatherJob(int location); + + virtual bool DoWork(); + + const CWeatherInfo &GetInfo() const; +private: + void LocalizeOverview(std::string &str); + void LocalizeOverviewToken(std::string &str); + void LoadLocalizedToken(); + static int ConvertSpeed(int speed); + + void SetFromProperties(); + + /*! \brief Formats a celcius temperature into a string based on the users locale + \param text the string to format + \param temp the temperature (in degrees celcius). + */ + static void FormatTemperature(std::string &text, int temp); + + struct ci_less : std::binary_function<std::string, std::string, bool> + { + // case-independent (ci) compare_less binary function + struct nocase_compare : public std::binary_function<unsigned char,unsigned char,bool> + { + bool operator() (const unsigned char& c1, const unsigned char& c2) const { + return tolower (c1) < tolower (c2); + } + }; + bool operator() (const std::string & s1, const std::string & s2) const { + return std::lexicographical_compare + (s1.begin (), s1.end (), + s2.begin (), s2.end (), + nocase_compare ()); + } + }; + + std::map<std::string, int, ci_less> m_localizedTokens; + typedef std::map<std::string, int, ci_less>::const_iterator ilocalizedTokens; + + CWeatherInfo m_info; + int m_location; + + static bool m_imagesOkay; +}; + +class CWeather : public CInfoLoader, + public ISettingCallback +{ +public: + CWeather(void); + virtual ~CWeather(void); + static bool GetSearchResults(const std::string &strSearch, std::string &strResult); + + std::string GetLocation(int iLocation); + const std::string &GetLastUpdateTime() const { return m_info.lastUpdateTime; }; + const day_forecast &GetForecast(int day) const; + bool IsFetched(); + void Reset(); + + void SetArea(int iLocation); + int GetArea() const; +protected: + virtual CJob *GetJob() const; + virtual std::string TranslateInfo(int info) const; + virtual std::string BusyInfo(int info) const; + virtual void OnJobComplete(unsigned int jobID, bool success, CJob *job); + + virtual void OnSettingChanged(const CSetting *setting); + virtual void OnSettingAction(const CSetting *setting); + +private: + + CWeatherInfo m_info; +}; + +XBMC_GLOBAL_REF(CWeather, g_weatherManager); +#define g_weatherManager XBMC_GLOBAL_USE(CWeather) diff --git a/src/utils/WindowsShortcut.cpp b/src/utils/WindowsShortcut.cpp new file mode 100644 index 0000000000..1ba63250fb --- /dev/null +++ b/src/utils/WindowsShortcut.cpp @@ -0,0 +1,166 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +// WindowsShortcut.cpp: implementation of the CWindowsShortcut class. +// +////////////////////////////////////////////////////////////////////// + +#include "sc.h" +#include "WindowsShortcut.h" + +#ifdef _DEBUG +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#define new DEBUG_NEW +#endif + + +#define FLAG_SHELLITEMIDLIST 1 +#define FLAG_FILEORDIRECTORY 2 +#define FLAG_DESCRIPTION 4 +#define FLAG_RELATIVEPATH 8 +#define FLAG_WORKINGDIRECTORY 0x10 +#define FLAG_ARGUMENTS 0x20 +#define FLAG_ICON 0x40 + +#define VOLUME_LOCAL 1 +#define VOLUME_NETWORK 2 + +using namespace std; + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CWindowsShortcut::CWindowsShortcut() +{ +} + +CWindowsShortcut::~CWindowsShortcut() +{ +} + +bool CWindowsShortcut::GetShortcut(const string& strFileName, string& strFileOrDir) +{ + strFileOrDir = ""; + if (!IsShortcut(strFileName) ) return false; + + + CFile file; + if (!file.Open(strFileName.c_str(), CFile::typeBinary | CFile::modeRead)) return false; + byte byHeader[2048]; + int iBytesRead = file.Read(byHeader, 2048); + file.Close(); + + DWORD dwFlags = *((DWORD*)(&byHeader[0x14])); + + int iPos = 0x4c; + if (dwFlags & FLAG_SHELLITEMIDLIST) + { + WORD sLength = *((WORD*)(&byHeader[iPos])); + iPos += sLength; + iPos += 2; + } + + // skip File Location Info + DWORD dwLen = 0x1c; + if (dwFlags & FLAG_SHELLITEMIDLIST) + { + dwLen = *((DWORD*)(&byHeader[iPos])); + } + + + DWORD dwVolumeFlags = *((DWORD*)(&byHeader[iPos + 0x8])); + DWORD dwOffsetLocalVolumeInfo = *((DWORD*)(&byHeader[iPos + 0xc])); + DWORD dwOffsetBasePathName = *((DWORD*)(&byHeader[iPos + 0x10])); + DWORD dwOffsetNetworkVolumeInfo = *((DWORD*)(&byHeader[iPos + 0x14])); + DWORD dwOffsetRemainingPathName = *((DWORD*)(&byHeader[iPos + 0x18])); + + + if ((dwVolumeFlags & VOLUME_NETWORK) == 0) return false; + + strFileOrDir = "smb:"; + // share name + iPos += dwOffsetNetworkVolumeInfo + 0x14; + while (iPos < iBytesRead && byHeader[iPos] != 0) + { + if (byHeader[iPos] == '\\') byHeader[iPos] = '/'; + strFileOrDir += (char)byHeader[iPos]; + iPos++; + } + iPos++; + // file/folder name + strFileOrDir += '/'; + while (iPos < iBytesRead && byHeader[iPos] != 0) + { + if (byHeader[iPos] == '\\') byHeader[iPos] = '/'; + strFileOrDir += (char)byHeader[iPos]; + iPos++; + } + return true; +} + +bool CWindowsShortcut::IsShortcut(const string& strFileName) +{ + CFile file; + if (!file.Open(strFileName.c_str(), CFile::typeBinary | CFile::modeRead)) return false; + byte byHeader[0x80]; + int iBytesRead = file.Read(byHeader, 0x80); + file.Close(); + if (iBytesRead < 0x4c) + { + return false; + } + //long integer that is always set to 4Ch + if (byHeader[0] != 0x4c) return false; + if (byHeader[1] != 0x0 ) return false; + if (byHeader[2] != 0x0 ) return false; + if (byHeader[3] != 0x0 ) return false; + + //globally unique identifier GUID of the shell links + if (byHeader[0x04] != 0x01) return false; + if (byHeader[0x05] != 0x14) return false; + if (byHeader[0x06] != 0x02) return false; + if (byHeader[0x07] != 0x00) return false; + if (byHeader[0x08] != 0x00) return false; + if (byHeader[0x09] != 0x00) return false; + if (byHeader[0x0a] != 0x00) return false; + if (byHeader[0x0b] != 0x00) return false; + if (byHeader[0x0c] != 0xc0) return false; + if (byHeader[0x0d] != 0x00) return false; + if (byHeader[0x0e] != 0x00) return false; + if (byHeader[0x0f] != 0x00) return false; + if (byHeader[0x10] != 0x00) return false; + if (byHeader[0x11] != 0x00) return false; + if (byHeader[0x12] != 0x00) return false; + if (byHeader[0x13] != 0x46) return false; + + // 2dwords, always 0 + if (byHeader[0x44] != 0x0 ) return false; + if (byHeader[0x45] != 0x0 ) return false; + if (byHeader[0x46] != 0x0 ) return false; + if (byHeader[0x47] != 0x0 ) return false; + if (byHeader[0x48] != 0x0 ) return false; + if (byHeader[0x49] != 0x0 ) return false; + if (byHeader[0x4a] != 0x0 ) return false; + if (byHeader[0x4b] != 0x0 ) return false; + + return true; +} diff --git a/src/utils/WindowsShortcut.h b/src/utils/WindowsShortcut.h new file mode 100644 index 0000000000..a79e95f3a4 --- /dev/null +++ b/src/utils/WindowsShortcut.h @@ -0,0 +1,43 @@ +// WindowsShortcut.h: interface for the CWindowsShortcut class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_WINDOWSSHORTCUT_H__A905CF83_3C3D_44FF_B3EF_778D70676D2C__INCLUDED_) +#define AFX_WINDOWSSHORTCUT_H__A905CF83_3C3D_44FF_B3EF_778D70676D2C__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> + +class CWindowsShortcut +{ +public: + CWindowsShortcut(); + virtual ~CWindowsShortcut(); + static bool IsShortcut(const string& strFileName); + static bool GetShortcut(const string& strFileName, string& strFileOrDir); +}; + +#endif // !defined(AFX_WINDOWSSHORTCUT_H__A905CF83_3C3D_44FF_B3EF_778D70676D2C__INCLUDED_) diff --git a/src/utils/XBMCTinyXML.cpp b/src/utils/XBMCTinyXML.cpp new file mode 100644 index 0000000000..e7db9f132d --- /dev/null +++ b/src/utils/XBMCTinyXML.cpp @@ -0,0 +1,276 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "XBMCTinyXML.h" +#include "filesystem/File.h" +#include "utils/StringUtils.h" +#include "utils/CharsetConverter.h" +#include "utils/CharsetDetection.h" +#include "utils/Utf8Utils.h" +#include "LangInfo.h" +#include "RegExp.h" +#include "utils/log.h" + +#define MAX_ENTITY_LENGTH 8 // size of largest entity "&#xNNNN;" +#define BUFFER_SIZE 4096 + +CXBMCTinyXML::CXBMCTinyXML() +: TiXmlDocument() +{ +} + +CXBMCTinyXML::CXBMCTinyXML(const char *documentName) +: TiXmlDocument(documentName) +{ +} + +CXBMCTinyXML::CXBMCTinyXML(const std::string& documentName) +: TiXmlDocument(documentName) +{ +} + +CXBMCTinyXML::CXBMCTinyXML(const std::string& documentName, const std::string& documentCharset) +: TiXmlDocument(documentName), m_SuggestedCharset(documentCharset) +{ + StringUtils::ToUpper(m_SuggestedCharset); +} + +bool CXBMCTinyXML::LoadFile(TiXmlEncoding encoding) +{ + return LoadFile(value, encoding); +} + +bool CXBMCTinyXML::LoadFile(const char *_filename, TiXmlEncoding encoding) +{ + return LoadFile(std::string(_filename), encoding); +} + +bool CXBMCTinyXML::LoadFile(const std::string& _filename, TiXmlEncoding encoding) +{ + value = _filename.c_str(); + + XFILE::CFile file; + XFILE::auto_buffer buffer; + + if (file.LoadFile(value, buffer) <= 0) + { + SetError(TIXML_ERROR_OPENING_FILE, NULL, NULL, TIXML_ENCODING_UNKNOWN); + return false; + } + + // Delete the existing data: + Clear(); + location.Clear(); + + std::string data(buffer.get(), buffer.length()); + buffer.clear(); // free memory early + + if (encoding == TIXML_ENCODING_UNKNOWN) + Parse(data, file.GetContentCharset()); + else + Parse(data, encoding); + + if (Error()) + return false; + return true; +} + +bool CXBMCTinyXML::LoadFile(const std::string& _filename, const std::string& documentCharset) +{ + m_SuggestedCharset = documentCharset; + StringUtils::ToUpper(m_SuggestedCharset); + return LoadFile(_filename, TIXML_ENCODING_UNKNOWN); +} + +bool CXBMCTinyXML::LoadFile(FILE *f, TiXmlEncoding encoding) +{ + std::string data; + char buf[BUFFER_SIZE]; + memset(buf, 0, BUFFER_SIZE); + int result; + while ((result = fread(buf, 1, BUFFER_SIZE, f)) > 0) + data.append(buf, result); + return Parse(data, encoding); +} + +bool CXBMCTinyXML::SaveFile(const char *_filename) const +{ + return SaveFile(std::string(_filename)); +} + +bool CXBMCTinyXML::SaveFile(const std::string& filename) const +{ + XFILE::CFile file; + if (file.OpenForWrite(filename, true)) + { + TiXmlPrinter printer; + Accept(&printer); + return file.Write(printer.CStr(), printer.Size()) == printer.Size(); + } + return false; +} + +bool CXBMCTinyXML::Parse(const char *_data, TiXmlEncoding encoding) +{ + return Parse(std::string(_data), encoding); +} + +bool CXBMCTinyXML::Parse(const std::string& data, const std::string& dataCharset) +{ + m_SuggestedCharset = dataCharset; + StringUtils::ToUpper(m_SuggestedCharset); + return Parse(data, TIXML_ENCODING_UNKNOWN); +} + +bool CXBMCTinyXML::Parse(const std::string& data, TiXmlEncoding encoding /*= TIXML_DEFAULT_ENCODING */) +{ + m_UsedCharset.clear(); + if (encoding != TIXML_ENCODING_UNKNOWN) + { // encoding != TIXML_ENCODING_UNKNOWN means "do not use m_SuggestedCharset and charset detection" + m_SuggestedCharset.clear(); + if (encoding == TIXML_ENCODING_UTF8) + m_UsedCharset = "UTF-8"; + + return InternalParse(data, encoding); + } + + if (!m_SuggestedCharset.empty() && TryParse(data, m_SuggestedCharset)) + return true; + + std::string detectedCharset; + if (CCharsetDetection::DetectXmlEncoding(data, detectedCharset) && TryParse(data, detectedCharset)) + { + if (!m_SuggestedCharset.empty()) + CLog::Log(LOGWARNING, "%s: \"%s\" charset was used instead of suggested charset \"%s\" for %s", __FUNCTION__, m_UsedCharset.c_str(), m_SuggestedCharset.c_str(), + (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str())); + + return true; + } + + // check for valid UTF-8 + if (m_SuggestedCharset != "UTF-8" && detectedCharset != "UTF-8" && CUtf8Utils::isValidUtf8(data) && + TryParse(data, "UTF-8")) + { + if (!m_SuggestedCharset.empty()) + CLog::Log(LOGWARNING, "%s: \"%s\" charset was used instead of suggested charset \"%s\" for %s", __FUNCTION__, m_UsedCharset.c_str(), m_SuggestedCharset.c_str(), + (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str())); + else if (!detectedCharset.empty()) + CLog::Log(LOGWARNING, "%s: \"%s\" charset was used instead of detected charset \"%s\" for %s", __FUNCTION__, m_UsedCharset.c_str(), detectedCharset.c_str(), + (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str())); + return true; + } + + // fallback: try user GUI charset + if (TryParse(data, g_langInfo.GetGuiCharSet())) + { + if (!m_SuggestedCharset.empty()) + CLog::Log(LOGWARNING, "%s: \"%s\" charset was used instead of suggested charset \"%s\" for %s", __FUNCTION__, m_UsedCharset.c_str(), m_SuggestedCharset.c_str(), + (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str())); + else if (!detectedCharset.empty()) + CLog::Log(LOGWARNING, "%s: \"%s\" charset was used instead of detected charset \"%s\" for %s", __FUNCTION__, m_UsedCharset.c_str(), detectedCharset.c_str(), + (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str())); + return true; + } + + // can't detect correct data charset, try to process data as is + if (InternalParse(data, TIXML_ENCODING_UNKNOWN)) + { + if (!m_SuggestedCharset.empty()) + CLog::Log(LOGWARNING, "%s: Processed %s as unknown encoding instead of suggested \"%s\"", __FUNCTION__, + (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str()), m_SuggestedCharset.c_str()); + else if (!detectedCharset.empty()) + CLog::Log(LOGWARNING, "%s: Processed %s as unknown encoding instead of detected \"%s\"", __FUNCTION__, + (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str()), detectedCharset.c_str()); + return true; + } + + return false; +} + +bool CXBMCTinyXML::TryParse(const std::string& data, const std::string& tryDataCharset) +{ + if (tryDataCharset == "UTF-8") + InternalParse(data, TIXML_ENCODING_UTF8); // process data without conversion + else if (!tryDataCharset.empty()) + { + std::string converted; + /* some wrong conversions can leave US-ASCII XML header and structure untouched but break non-English data + * so conversion must fail on wrong character and then other encodings will be tried */ + if (!g_charsetConverter.ToUtf8(tryDataCharset, data, converted, true) || converted.empty()) + return false; // can't convert data + + InternalParse(converted, TIXML_ENCODING_UTF8); + } + else + InternalParse(data, TIXML_ENCODING_LEGACY); + + // 'Error()' contains result of last run of 'TiXmlDocument::Parse()' + if (Error()) + { + Clear(); + location.Clear(); + + return false; + } + + m_UsedCharset = tryDataCharset; + return true; +} + +bool CXBMCTinyXML::InternalParse(const std::string& rawdata, TiXmlEncoding encoding /*= TIXML_DEFAULT_ENCODING */) +{ + // Preprocess string, replacing '&' with '& for invalid XML entities + size_t pos = rawdata.find('&'); + if (pos == std::string::npos) + return (TiXmlDocument::Parse(rawdata.c_str(), NULL, encoding) != NULL); // nothing to fix, process data directly + + std::string data(rawdata); + CRegExp re(false, CRegExp::asciiOnly, "^&(amp|lt|gt|quot|apos|#x[a-fA-F0-9]{1,4}|#[0-9]{1,5});.*"); + do + { + if (re.RegFind(data, pos, MAX_ENTITY_LENGTH) < 0) + data.insert(pos + 1, "amp;"); + pos = data.find('&', pos + 1); + } while (pos != std::string::npos); + + return (TiXmlDocument::Parse(data.c_str(), NULL, encoding) != NULL); +} + +bool CXBMCTinyXML::Test() +{ + // scraper results with unescaped & + CXBMCTinyXML doc; + std::string data("<details><url function=\"ParseTMDBRating\" " + "cache=\"tmdb-en-12244.json\">" + "http://api.themoviedb.org/3/movie/12244" + "?api_key=57983e31fb435df4df77afb854740ea9" + "&language=en???</url></details>"); + doc.Parse(data.c_str()); + TiXmlNode *root = doc.RootElement(); + if (root && root->ValueStr() == "details") + { + TiXmlElement *url = root->FirstChildElement("url"); + if (url && url->FirstChild()) + { + return (url->FirstChild()->ValueStr() == "http://api.themoviedb.org/3/movie/12244?api_key=57983e31fb435df4df77afb854740ea9&language=en???"); + } + } + return false; +} diff --git a/src/utils/XBMCTinyXML.h b/src/utils/XBMCTinyXML.h new file mode 100644 index 0000000000..0e875ebab5 --- /dev/null +++ b/src/utils/XBMCTinyXML.h @@ -0,0 +1,77 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#if (defined HAVE_CONFIG_H) && (!defined TARGET_WINDOWS) + #include "config.h" +#endif +#ifdef TARGET_WINDOWS +#define TIXML_USE_STL +#pragma comment(lib, "tinyxmlSTL.lib") +#else +//compile fix for TinyXml < 2.6.0 +#define DOCUMENT TINYXML_DOCUMENT +#define ELEMENT TINYXML_ELEMENT +#define COMMENT TINYXML_COMMENT +#define UNKNOWN TINYXML_UNKNOWN +#define TEXT TINYXML_TEXT +#define DECLARATION TINYXML_DECLARATION +#define TYPECOUNT TINYXML_TYPECOUNT +#endif + +#include <tinyxml.h> +#include <string> + +#undef DOCUMENT +#undef ELEMENT +#undef COMMENT +#undef UNKNOWN +//#undef TEXT +#undef DECLARATION +#undef TYPECOUNT + +class CXBMCTinyXML : public TiXmlDocument +{ +public: + CXBMCTinyXML(); + CXBMCTinyXML(const char*); + CXBMCTinyXML(const std::string& documentName); + CXBMCTinyXML(const std::string& documentName, const std::string& documentCharset); + bool LoadFile(TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); + bool LoadFile(const char*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); + bool LoadFile(const std::string& _filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); + bool LoadFile(const std::string& _filename, const std::string& documentCharset); + bool LoadFile(FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); + bool SaveFile(const char*) const; + bool SaveFile(const std::string& filename) const; + bool Parse(const char*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); + bool Parse(const std::string& data, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); + bool Parse(const std::string& data, const std::string& dataCharset); + inline std::string GetSuggestedCharset(void) const { return m_SuggestedCharset; } + inline std::string GetUsedCharset(void) const { return m_UsedCharset; } + static bool Test(); +protected: + bool TryParse(const std::string& data, const std::string& tryDataCharset); + bool InternalParse(const std::string& rawdata, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); + + std::string m_SuggestedCharset; + std::string m_UsedCharset; +}; diff --git a/src/utils/XMLUtils.cpp b/src/utils/XMLUtils.cpp new file mode 100644 index 0000000000..dc234da225 --- /dev/null +++ b/src/utils/XMLUtils.cpp @@ -0,0 +1,343 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "XMLUtils.h" +#include "URL.h" +#include "StringUtils.h" +#ifdef TARGET_WINDOWS +#include "PlatformDefs.h" //for strcasecmp +#endif + +bool XMLUtils::GetHex(const TiXmlNode* pRootNode, const char* strTag, uint32_t& hexValue) +{ + const TiXmlNode* pNode = pRootNode->FirstChild(strTag ); + if (!pNode || !pNode->FirstChild()) return false; + return sscanf(pNode->FirstChild()->Value(), "%x", (uint32_t*)&hexValue) == 1; +} + + +bool XMLUtils::GetUInt(const TiXmlNode* pRootNode, const char* strTag, uint32_t& uintValue) +{ + const TiXmlNode* pNode = pRootNode->FirstChild(strTag ); + if (!pNode || !pNode->FirstChild()) return false; + uintValue = atol(pNode->FirstChild()->Value()); + return true; +} + +bool XMLUtils::GetUInt(const TiXmlNode* pRootNode, const char* strTag, uint32_t &value, const uint32_t min, const uint32_t max) +{ + if (GetUInt(pRootNode, strTag, value)) + { + if (value < min) value = min; + if (value > max) value = max; + return true; + } + return false; +} + +bool XMLUtils::GetLong(const TiXmlNode* pRootNode, const char* strTag, long& lLongValue) +{ + const TiXmlNode* pNode = pRootNode->FirstChild(strTag ); + if (!pNode || !pNode->FirstChild()) return false; + lLongValue = atol(pNode->FirstChild()->Value()); + return true; +} + +bool XMLUtils::GetInt(const TiXmlNode* pRootNode, const char* strTag, int& iIntValue) +{ + const TiXmlNode* pNode = pRootNode->FirstChild(strTag ); + if (!pNode || !pNode->FirstChild()) return false; + iIntValue = atoi(pNode->FirstChild()->Value()); + return true; +} + +bool XMLUtils::GetInt(const TiXmlNode* pRootNode, const char* strTag, int &value, const int min, const int max) +{ + if (GetInt(pRootNode, strTag, value)) + { + if (value < min) value = min; + if (value > max) value = max; + return true; + } + return false; +} + +bool XMLUtils::GetDouble(const TiXmlNode *root, const char *tag, double &value) +{ + const TiXmlNode* node = root->FirstChild(tag); + if (!node || !node->FirstChild()) return false; + value = atof(node->FirstChild()->Value()); + return true; +} + +bool XMLUtils::GetFloat(const TiXmlNode* pRootNode, const char* strTag, float& value) +{ + const TiXmlNode* pNode = pRootNode->FirstChild(strTag ); + if (!pNode || !pNode->FirstChild()) return false; + value = (float)atof(pNode->FirstChild()->Value()); + return true; +} + +bool XMLUtils::GetFloat(const TiXmlNode* pRootElement, const char *tagName, float& fValue, const float fMin, const float fMax) +{ + if (GetFloat(pRootElement, tagName, fValue)) + { // check range + if (fValue < fMin) fValue = fMin; + if (fValue > fMax) fValue = fMax; + return true; + } + return false; +} + +bool XMLUtils::GetBoolean(const TiXmlNode* pRootNode, const char* strTag, bool& bBoolValue) +{ + const TiXmlNode* pNode = pRootNode->FirstChild(strTag ); + if (!pNode || !pNode->FirstChild()) return false; + std::string strEnabled = pNode->FirstChild()->ValueStr(); + StringUtils::ToLower(strEnabled); + if (strEnabled == "off" || strEnabled == "no" || strEnabled == "disabled" || strEnabled == "false" || strEnabled == "0" ) + bBoolValue = false; + else + { + bBoolValue = true; + if (strEnabled != "on" && strEnabled != "yes" && strEnabled != "enabled" && strEnabled != "true") + return false; // invalid bool switch - it's probably some other string. + } + return true; +} + +bool XMLUtils::GetString(const TiXmlNode* pRootNode, const char* strTag, std::string& strStringValue) +{ + const TiXmlElement* pElement = pRootNode->FirstChildElement(strTag ); + if (!pElement) return false; + const char* encoded = pElement->Attribute("urlencoded"); + const TiXmlNode* pNode = pElement->FirstChild(); + if (pNode != NULL) + { + strStringValue = pNode->ValueStr(); + if (encoded && strcasecmp(encoded,"yes") == 0) + strStringValue = CURL::Decode(strStringValue); + return true; + } + strStringValue.clear(); + return true; +} + +bool XMLUtils::HasChild(const TiXmlNode* pRootNode, const char* strTag) +{ + const TiXmlElement* pElement = pRootNode->FirstChildElement(strTag); + if (!pElement) return false; + const TiXmlNode* pNode = pElement->FirstChild(); + return (pNode != NULL); +} + +bool XMLUtils::GetAdditiveString(const TiXmlNode* pRootNode, const char* strTag, + const std::string& strSeparator, std::string& strStringValue, + bool clear) +{ + std::string strTemp; + const TiXmlElement* node = pRootNode->FirstChildElement(strTag); + bool bResult=false; + if (node && node->FirstChild() && clear) + strStringValue.clear(); + while (node) + { + if (node->FirstChild()) + { + bResult = true; + strTemp = node->FirstChild()->Value(); + const char* clear=node->Attribute("clear"); + if (strStringValue.empty() || (clear && strcasecmp(clear,"true")==0)) + strStringValue = strTemp; + else + strStringValue += strSeparator+strTemp; + } + node = node->NextSiblingElement(strTag); + } + + return bResult; +} + +/*! + Parses the XML for multiple tags of the given name. + Does not clear the array to support chaining. +*/ +bool XMLUtils::GetStringArray(const TiXmlNode* pRootNode, const char* strTag, std::vector<std::string>& arrayValue, bool clear /* = false */, const std::string& separator /* = "" */) +{ + std::string strTemp; + const TiXmlElement* node = pRootNode->FirstChildElement(strTag); + bool bResult=false; + if (node && node->FirstChild() && clear) + arrayValue.clear(); + while (node) + { + if (node->FirstChild()) + { + bResult = true; + strTemp = node->FirstChild()->ValueStr(); + + const char* clearAttr = node->Attribute("clear"); + if (clearAttr && strcasecmp(clearAttr, "true") == 0) + arrayValue.clear(); + + if (strTemp.empty()) + continue; + + if (separator.empty()) + arrayValue.push_back(strTemp); + else + { + std::vector<std::string> tempArray = StringUtils::Split(strTemp, separator); + arrayValue.insert(arrayValue.end(), tempArray.begin(), tempArray.end()); + } + } + node = node->NextSiblingElement(strTag); + } + + return bResult; +} + +bool XMLUtils::GetPath(const TiXmlNode* pRootNode, const char* strTag, std::string& strStringValue) +{ + const TiXmlElement* pElement = pRootNode->FirstChildElement(strTag); + if (!pElement) return false; + + const char* encoded = pElement->Attribute("urlencoded"); + const TiXmlNode* pNode = pElement->FirstChild(); + if (pNode != NULL) + { + strStringValue = pNode->Value(); + if (encoded && strcasecmp(encoded,"yes") == 0) + strStringValue = CURL::Decode(strStringValue); + return true; + } + strStringValue.clear(); + return false; +} + +bool XMLUtils::GetDate(const TiXmlNode* pRootNode, const char* strTag, CDateTime& date) +{ + std::string strDate; + if (GetString(pRootNode, strTag, strDate) && !strDate.empty()) + { + date.SetFromDBDate(strDate); + return true; + } + + return false; +} + +bool XMLUtils::GetDateTime(const TiXmlNode* pRootNode, const char* strTag, CDateTime& dateTime) +{ + std::string strDateTime; + if (GetString(pRootNode, strTag, strDateTime) && !strDateTime.empty()) + { + dateTime.SetFromDBDateTime(strDateTime); + return true; + } + + return false; +} + +std::string XMLUtils::GetAttribute(const TiXmlElement *element, const char *tag) +{ + if (element) + { + const char *attribute = element->Attribute(tag); + if (attribute) + return attribute; + } + return ""; +} + +void XMLUtils::SetAdditiveString(TiXmlNode* pRootNode, const char *strTag, const std::string& strSeparator, const std::string& strValue) +{ + std::vector<std::string> list = StringUtils::Split(strValue, strSeparator); + for (std::vector<std::string>::const_iterator i = list.begin(); i != list.end(); ++i) + SetString(pRootNode, strTag, *i); +} + +void XMLUtils::SetStringArray(TiXmlNode* pRootNode, const char *strTag, const std::vector<std::string>& arrayValue) +{ + for (unsigned int i = 0; i < arrayValue.size(); i++) + SetString(pRootNode, strTag, arrayValue.at(i)); +} + +void XMLUtils::SetString(TiXmlNode* pRootNode, const char *strTag, const std::string& strValue) +{ + TiXmlElement newElement(strTag); + TiXmlNode *pNewNode = pRootNode->InsertEndChild(newElement); + if (pNewNode) + { + TiXmlText value(strValue); + pNewNode->InsertEndChild(value); + } +} + +void XMLUtils::SetInt(TiXmlNode* pRootNode, const char *strTag, int value) +{ + std::string strValue = StringUtils::Format("%i", value); + SetString(pRootNode, strTag, strValue); +} + +void XMLUtils::SetLong(TiXmlNode* pRootNode, const char *strTag, long value) +{ + std::string strValue = StringUtils::Format("%ld", value); + SetString(pRootNode, strTag, strValue); +} + +void XMLUtils::SetFloat(TiXmlNode* pRootNode, const char *strTag, float value) +{ + std::string strValue = StringUtils::Format("%f", value); + SetString(pRootNode, strTag, strValue); +} + +void XMLUtils::SetBoolean(TiXmlNode* pRootNode, const char *strTag, bool value) +{ + SetString(pRootNode, strTag, value ? "true" : "false"); +} + +void XMLUtils::SetHex(TiXmlNode* pRootNode, const char *strTag, uint32_t value) +{ + std::string strValue = StringUtils::Format("%x", value); + SetString(pRootNode, strTag, strValue); +} + +void XMLUtils::SetPath(TiXmlNode* pRootNode, const char *strTag, const std::string& strValue) +{ + TiXmlElement newElement(strTag); + newElement.SetAttribute("pathversion", path_version); + TiXmlNode *pNewNode = pRootNode->InsertEndChild(newElement); + if (pNewNode) + { + TiXmlText value(strValue); + pNewNode->InsertEndChild(value); + } +} + +void XMLUtils::SetDate(TiXmlNode* pRootNode, const char *strTag, const CDateTime& date) +{ + SetString(pRootNode, strTag, date.IsValid() ? date.GetAsDBDate() : ""); +} + +void XMLUtils::SetDateTime(TiXmlNode* pRootNode, const char *strTag, const CDateTime& dateTime) +{ + SetString(pRootNode, strTag, dateTime.IsValid() ? dateTime.GetAsDBDateTime() : ""); +} diff --git a/src/utils/XMLUtils.h b/src/utils/XMLUtils.h new file mode 100644 index 0000000000..70e6446e46 --- /dev/null +++ b/src/utils/XMLUtils.h @@ -0,0 +1,86 @@ +#pragma once + +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <stdint.h> +#include <vector> +#include "utils/XBMCTinyXML.h" + +class CDateTime; + +class XMLUtils +{ +public: + static bool HasChild(const TiXmlNode* pRootNode, const char* strTag); + + static bool GetHex(const TiXmlNode* pRootNode, const char* strTag, uint32_t& dwHexValue); + static bool GetUInt(const TiXmlNode* pRootNode, const char* strTag, uint32_t& dwUIntValue); + static bool GetLong(const TiXmlNode* pRootNode, const char* strTag, long& lLongValue); + static bool GetFloat(const TiXmlNode* pRootNode, const char* strTag, float& value); + static bool GetDouble(const TiXmlNode* pRootNode, const char* strTag, double &value); + static bool GetInt(const TiXmlNode* pRootNode, const char* strTag, int& iIntValue); + static bool GetBoolean(const TiXmlNode* pRootNode, const char* strTag, bool& bBoolValue); + static bool GetString(const TiXmlNode* pRootNode, const char* strTag, std::string& strStringValue); + /*! \brief Get multiple tags, concatenating the values together. + Transforms + <tag>value1</tag> + <tag clear="true">value2</tag> + ... + <tag>valuen</tag> + into value2<sep>...<sep>valuen, appending it to the value string. Note that <value1> is overwritten by the clear="true" tag. + + \param rootNode the parent containing the <tag>'s. + \param tag the <tag> in question. + \param separator the separator to use when concatenating values. + \param value [out] the resulting string. Remains untouched if no <tag> is available, else is appended (or cleared based on the clear parameter). + \param clear if true, clears the string prior to adding tags, if tags are available. Defaults to false. + */ + static bool GetAdditiveString(const TiXmlNode* rootNode, const char* tag, const std::string& separator, std::string& value, bool clear = false); + static bool GetStringArray(const TiXmlNode* rootNode, const char* tag, std::vector<std::string>& arrayValue, bool clear = false, const std::string& separator = ""); + static bool GetPath(const TiXmlNode* pRootNode, const char* strTag, std::string& strStringValue); + static bool GetFloat(const TiXmlNode* pRootNode, const char* strTag, float& value, const float min, const float max); + static bool GetUInt(const TiXmlNode* pRootNode, const char* strTag, uint32_t& dwUIntValue, const uint32_t min, const uint32_t max); + static bool GetInt(const TiXmlNode* pRootNode, const char* strTag, int& iIntValue, const int min, const int max); + static bool GetDate(const TiXmlNode* pRootNode, const char* strTag, CDateTime& date); + static bool GetDateTime(const TiXmlNode* pRootNode, const char* strTag, CDateTime& dateTime); + /*! \brief Fetch a std::string copy of an attribute, if it exists. Cannot distinguish between empty and non-existent attributes. + \param element the element to query. + \param tag the name of the attribute. + \return the attribute, if it exists, else an empty string + */ + static std::string GetAttribute(const TiXmlElement *element, const char *tag); + + static void SetString(TiXmlNode* pRootNode, const char *strTag, const std::string& strValue); + static void SetAdditiveString(TiXmlNode* pRootNode, const char *strTag, const std::string& strSeparator, const std::string& strValue); + static void SetStringArray(TiXmlNode* pRootNode, const char *strTag, const std::vector<std::string>& arrayValue); + static void SetInt(TiXmlNode* pRootNode, const char *strTag, int value); + static void SetFloat(TiXmlNode* pRootNode, const char *strTag, float value); + static void SetBoolean(TiXmlNode* pRootNode, const char *strTag, bool value); + static void SetHex(TiXmlNode* pRootNode, const char *strTag, uint32_t value); + static void SetPath(TiXmlNode* pRootNode, const char *strTag, const std::string& strValue); + static void SetLong(TiXmlNode* pRootNode, const char *strTag, long iValue); + static void SetDate(TiXmlNode* pRootNode, const char *strTag, const CDateTime& date); + static void SetDateTime(TiXmlNode* pRootNode, const char *strTag, const CDateTime& dateTime); + + static const int path_version = 1; +}; + diff --git a/src/utils/XSLTUtils.cpp b/src/utils/XSLTUtils.cpp new file mode 100644 index 0000000000..61b0e9d3dd --- /dev/null +++ b/src/utils/XSLTUtils.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "XSLTUtils.h" +#include "log.h" +#include <libxslt/xslt.h> +#include <libxslt/transform.h> + +#ifdef TARGET_WINDOWS +#pragma comment(lib, "libxslt.lib") +#pragma comment(lib, "libxml2.lib") +#else +#include <iostream> +#endif + +#define TMP_BUF_SIZE 512 +void err(void *ctx, const char *msg, ...) { + char string[TMP_BUF_SIZE]; + va_list arg_ptr; + va_start(arg_ptr, msg); + vsnprintf(string, TMP_BUF_SIZE, msg, arg_ptr); + va_end(arg_ptr); + CLog::Log(LOGDEBUG, "XSLT: %s", string); + return; +} + +XSLTUtils::XSLTUtils() : +m_xmlInput(NULL), m_xmlStylesheet(NULL), m_xsltStylesheet(NULL) +{ + // initialize libxslt + xmlSubstituteEntitiesDefault(1); + xmlLoadExtDtdDefaultValue = 0; + xsltSetGenericErrorFunc(NULL, err); +} + +XSLTUtils::~XSLTUtils() +{ + if (m_xmlInput) + xmlFreeDoc(m_xmlInput); + if (m_xmlOutput) + xmlFreeDoc(m_xmlOutput); + if (m_xsltStylesheet) + xsltFreeStylesheet(m_xsltStylesheet); +} + +bool XSLTUtils::XSLTTransform(std::string& output) +{ + const char *params[16+1]; + params[0] = NULL; + m_xmlOutput = xsltApplyStylesheet(m_xsltStylesheet, m_xmlInput, params); + if (!m_xmlOutput) + { + CLog::Log(LOGDEBUG, "XSLT: xslt transformation failed"); + return false; + } + + xmlChar *xmlResultBuffer = NULL; + int xmlResultLength = 0; + int res = xsltSaveResultToString(&xmlResultBuffer, &xmlResultLength, m_xmlOutput, m_xsltStylesheet); + if (res == -1) + { + xmlFree(xmlResultBuffer); + return false; + } + + output.append((const char *)xmlResultBuffer, xmlResultLength); + xmlFree(xmlResultBuffer); + + return true; +} + +bool XSLTUtils::SetInput(const std::string& input) +{ + m_xmlInput = xmlParseMemory(input.c_str(), input.size()); + if (!m_xmlInput) + return false; + return true; +} + +bool XSLTUtils::SetStylesheet(const std::string& stylesheet) +{ + if (m_xsltStylesheet) { + xsltFreeStylesheet(m_xsltStylesheet); + m_xsltStylesheet = NULL; + } + + m_xmlStylesheet = xmlParseMemory(stylesheet.c_str(), stylesheet.size()); + if (!m_xmlStylesheet) + { + CLog::Log(LOGDEBUG, "could not xmlParseMemory stylesheetdoc"); + return false; + } + + m_xsltStylesheet = xsltParseStylesheetDoc(m_xmlStylesheet); + if (!m_xsltStylesheet) { + CLog::Log(LOGDEBUG, "could not parse stylesheetdoc"); + xmlFree(m_xmlStylesheet); + m_xmlStylesheet = NULL; + return false; + } + + return true; +} diff --git a/src/utils/XSLTUtils.h b/src/utils/XSLTUtils.h new file mode 100644 index 0000000000..18269fb953 --- /dev/null +++ b/src/utils/XSLTUtils.h @@ -0,0 +1,62 @@ +#pragma once + +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <libxslt/xslt.h> +#include <libxslt/xsltutils.h> + +class XSLTUtils +{ +public: + XSLTUtils(); + ~XSLTUtils(); + + /*! \brief Set the input XML for an XSLT transform from a string. + This sets up the XSLT transformer with some input XML from a string in memory. + The input XML should be well formed. + \param input the XML document to be transformed. + */ + bool SetInput(const std::string& input); + + /*! \brief Set the stylesheet (XSL) for an XSLT transform from a string. + This sets up the XSLT transformer with some stylesheet XML from a string in memory. + The input XSL should be well formed. + \param input the XSL document to be transformed. + */ + bool SetStylesheet(const std::string& stylesheet); + + /*! \brief Perform an XSLT transform on an inbound XML document. + This will apply an XSLT transformation on an input XML document, + giving an output XML document, using the specified XSLT document + as the transformer. + \param input the parent containing the <tag>'s. + \param filename the <tag> in question. + */ + bool XSLTTransform(std::string& output); + + +private: + xmlDocPtr m_xmlInput; + xmlDocPtr m_xmlOutput; + xmlDocPtr m_xmlStylesheet; + xsltStylesheetPtr m_xsltStylesheet; +}; diff --git a/src/utils/auto_buffer.cpp b/src/utils/auto_buffer.cpp new file mode 100644 index 0000000000..c54ff73b64 --- /dev/null +++ b/src/utils/auto_buffer.cpp @@ -0,0 +1,95 @@ +/* +* Copyright (C) 2013-2014 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 +* <http://www.gnu.org/licenses/>. +* +*/ + +#include "auto_buffer.h" +#include <new> // for std::bad_alloc +#include <stdlib.h> // for malloc(), realloc() and free() + +using namespace XUTILS; + +auto_buffer::auto_buffer(size_t size) : p(0), s(0) +{ + if (!size) + return; + + p = malloc(size); // "malloc()" instead of "new" allow to use "realloc()" + if (!p) + throw std::bad_alloc(); + s = size; +} + +auto_buffer::~auto_buffer() +{ + free(p); +} + +auto_buffer& auto_buffer::allocate(size_t size) +{ + clear(); + if (size) + { + p = malloc(size); + if (!p) + throw std::bad_alloc(); + s = size; + } + return *this; +} + +auto_buffer& auto_buffer::resize(size_t newSize) +{ + if (!newSize) + return clear(); + + void* newPtr = realloc(p, newSize); + if (!newPtr) + throw std::bad_alloc(); + p = newPtr; + s = newSize; + return *this; +} + +auto_buffer& auto_buffer::clear(void) +{ + free(p); + p = 0; + s = 0; + return *this; +} + +auto_buffer& auto_buffer::attach(void* pointer, size_t size) +{ + clear(); + if ((pointer && size) || (!pointer && !size)) + { + p = pointer; + s = size; + } + return *this; +} + +void* auto_buffer::detach(void) +{ + void* returnPtr = p; + p = 0; + s = 0; + return returnPtr; +} + diff --git a/src/utils/auto_buffer.h b/src/utils/auto_buffer.h new file mode 100644 index 0000000000..43c9be86ec --- /dev/null +++ b/src/utils/auto_buffer.h @@ -0,0 +1,105 @@ +#pragma once +/* +* Copyright (C) 2013-2014 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 +* <http://www.gnu.org/licenses/>. +* +*/ + +#include <stddef.h> // for size_t + +namespace XUTILS +{ + + class auto_buffer + { + public: + /** + * Create buffer with zero size + */ + auto_buffer(void) : p(0), s(0) + {} + /** + * Create buffer with specified size + * @param size of created buffer + */ + explicit auto_buffer(size_t size); + ~auto_buffer(); + + /** + * Allocate specified size for buffer, discarding current buffer content + * @param size of buffer to allocate + * @return reference to itself + */ + auto_buffer& allocate(size_t size); + /** + * Resize current buffer to new size. Buffer will be extended or truncated at the end. + * @param newSize of buffer + * @return reference to itself + */ + auto_buffer& resize(size_t newSize); + /** + * Reset buffer to zero size + * @return reference to itself + */ + auto_buffer& clear(void); + + /** + * Get pointer to buffer content + * @return pointer to buffer content or NULL if buffer is zero size + */ + inline char* get(void) { return static_cast<char*>(p); } + /** + * Get constant pointer to buffer content + * @return constant pointer to buffer content + */ + inline const char* get(void) const { return static_cast<char*>(p); } + /** + * Get size of the buffer + * @return size of the buffer + */ + inline size_t size(void) const { return s; } + /** + * Get size of the buffer + * @return size of the buffer + */ + inline size_t length(void) const { return s; } + + /** + * Attach malloc'ed pointer to the buffer, discarding current buffer content + * Pointer must be acquired by malloc() or realloc(). + * Pointer will be automatically freed on destroy of the buffer. + * @param pointer to attach + * @param size of new memory region pointed by pointer + * @return reference to itself + */ + auto_buffer& attach(void* pointer, size_t size); + /** + * Detach current buffer content from the buffer, reset buffer to zero size + * Caller is responsible to free memory by calling free() for returned pointer + * when pointer in not needed anymore + * @return detached from buffer pointer to content + */ + void* detach(void); + + private: + auto_buffer(const auto_buffer& other); // disallow copy constructor + auto_buffer& operator=(const auto_buffer& other); // disallow assignment + + void* p; + size_t s; + }; +} diff --git a/src/utils/fastmemcpy-arm.S b/src/utils/fastmemcpy-arm.S new file mode 100644 index 0000000000..6cb8b0cfc6 --- /dev/null +++ b/src/utils/fastmemcpy-arm.S @@ -0,0 +1,528 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * All rights reserved. + * + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ +#if defined(__arm__) && !defined(TARGET_ANDROID) && !defined(TARGET_DARWIN_IOS) +#if defined(__ARM_NEON__) + + .text +#ifndef __APPLE__ + .fpu neon + .global fast_memcpy + .type fast_memcpy, %function +#else + .globl _fast_memcpy +#endif + .align 4 + +/* a prefetch distance of 4 cache-lines works best experimentally */ +#define CACHE_LINE_SIZE 64 +#define PREFETCH_DISTANCE (CACHE_LINE_SIZE*4) + +#ifndef __APPLE__ + .fnstart + .save {r0, lr} +fast_memcpy: +#else +_fast_memcpy: +#endif + stmfd sp!, {r0, lr} + + /* start preloading as early as possible */ + pld [r1, #(CACHE_LINE_SIZE*0)] + pld [r1, #(CACHE_LINE_SIZE*1)] + + /* do we have at least 16-bytes to copy (needed for alignment below) */ + cmp r2, #16 + blo 5f + + /* align destination to half cache-line for the write-buffer */ + rsb r3, r0, #0 + ands r3, r3, #0xF + beq 0f + + /* copy up to 15-bytes (count in r3) */ + sub r2, r2, r3 + movs ip, r3, lsl #31 + ldrmib lr, [r1], #1 + strmib lr, [r0], #1 + ldrcsb ip, [r1], #1 + ldrcsb lr, [r1], #1 + strcsb ip, [r0], #1 + strcsb lr, [r0], #1 + movs ip, r3, lsl #29 + bge 1f + // copies 4 bytes, destination 32-bits aligned + vld4.8 {d0[0], d1[0], d2[0], d3[0]}, [r1]! + vst4.8 {d0[0], d1[0], d2[0], d3[0]}, [r0, :32]! +1: bcc 2f + // copies 8 bytes, destination 64-bits aligned + vld1.8 {d0}, [r1]! + vst1.8 {d0}, [r0, :64]! +2: + +0: /* preload immediately the next cache line, which we may need */ + pld [r1, #(CACHE_LINE_SIZE*0)] + pld [r1, #(CACHE_LINE_SIZE*1)] + + /* make sure we have at least 64 bytes to copy */ + subs r2, r2, #64 + blo 2f + + /* preload all the cache lines we need. + * NOTE: the number of pld below depends on PREFETCH_DISTANCE, + * ideally would would increase the distance in the main loop to + * avoid the goofy code below. In practice this doesn't seem to make + * a big difference. + */ + pld [r1, #(CACHE_LINE_SIZE*2)] + pld [r1, #(CACHE_LINE_SIZE*3)] + pld [r1, #(PREFETCH_DISTANCE)] + +1: /* The main loop copies 64 bytes at a time */ + vld1.8 {d0 - d3}, [r1]! + vld1.8 {d4 - d7}, [r1]! + pld [r1, #(PREFETCH_DISTANCE)] + subs r2, r2, #64 + vst1.8 {d0 - d3}, [r0, :128]! + vst1.8 {d4 - d7}, [r0, :128]! + bhs 1b + +2: /* fix-up the remaining count and make sure we have >= 32 bytes left */ + add r2, r2, #64 + subs r2, r2, #32 + blo 4f + +3: /* 32 bytes at a time. These cache lines were already preloaded */ + vld1.8 {d0 - d3}, [r1]! + subs r2, r2, #32 + vst1.8 {d0 - d3}, [r0, :128]! + bhs 3b + +4: /* less than 32 left */ + add r2, r2, #32 + tst r2, #0x10 + beq 5f + // copies 16 bytes, 128-bits aligned + vld1.8 {d0, d1}, [r1]! + vst1.8 {d0, d1}, [r0, :128]! + +5: /* copy up to 15-bytes (count in r2) */ + movs ip, r2, lsl #29 + bcc 1f + vld1.8 {d0}, [r1]! + vst1.8 {d0}, [r0]! +1: bge 2f + vld4.8 {d0[0], d1[0], d2[0], d3[0]}, [r1]! + vst4.8 {d0[0], d1[0], d2[0], d3[0]}, [r0]! +2: movs ip, r2, lsl #31 + ldrmib r3, [r1], #1 + ldrcsb ip, [r1], #1 + ldrcsb lr, [r1], #1 + strmib r3, [r0], #1 + strcsb ip, [r0], #1 + strcsb lr, [r0], #1 + + ldmfd sp!, {r0, lr} + bx lr +#ifndef __APPLE__ + .fnend +#endif + +#else /* __ARM_ARCH__ < 7 */ + + + .text + +#ifndef __APPLE__ + .global fast_memcpy + .type fast_memcpy, %function +#else + .globl _fast_memcpy +#endif + .align 4 + + /* + * Optimized memcpy() for ARM. + * + * note that memcpy() always returns the destination pointer, + * so we have to preserve R0. + */ + +#ifndef __APPLE__ +fast_memcpy: +#else +_fast_memcpy: +#endif + /* The stack must always be 64-bits aligned to be compliant with the + * ARM ABI. Since we have to save R0, we might as well save R4 + * which we can use for better pipelining of the reads below + */ +#ifndef __APPLE__ + .fnstart + .save {r0, r4, lr} +#endif + stmfd sp!, {r0, r4, lr} + /* Making room for r5-r11 which will be spilled later */ + .pad #28 + sub sp, sp, #28 + + // preload the destination because we'll align it to a cache line + // with small writes. Also start the source "pump". + //PLD (r0, #0) + //PLD (r1, #0) + //PLD (r1, #32) + + /* it simplifies things to take care of len<4 early */ + cmp r2, #4 + blo copy_last_3_and_return + + /* compute the offset to align the source + * offset = (4-(src&3))&3 = -src & 3 + */ + rsb r3, r1, #0 + ands r3, r3, #3 + beq src_aligned + + /* align source to 32 bits. We need to insert 2 instructions between + * a ldr[b|h] and str[b|h] because byte and half-word instructions + * stall 2 cycles. + */ + movs r12, r3, lsl #31 + sub r2, r2, r3 /* we know that r3 <= r2 because r2 >= 4 */ + ldrmib r3, [r1], #1 + ldrcsb r4, [r1], #1 + ldrcsb r12,[r1], #1 + strmib r3, [r0], #1 + strcsb r4, [r0], #1 + strcsb r12,[r0], #1 + +src_aligned: + + /* see if src and dst are aligned together (congruent) */ + eor r12, r0, r1 + tst r12, #3 + bne non_congruent + + /* Use post-incriment mode for stm to spill r5-r11 to reserved stack + * frame. Don't update sp. + */ + stmea sp, {r5-r11} + + /* align the destination to a cache-line */ + rsb r3, r0, #0 + ands r3, r3, #0x1C + beq congruent_aligned32 + cmp r3, r2 + andhi r3, r2, #0x1C + + /* conditionnaly copies 0 to 7 words (length in r3) */ + movs r12, r3, lsl #28 + ldmcsia r1!, {r4, r5, r6, r7} /* 16 bytes */ + ldmmiia r1!, {r8, r9} /* 8 bytes */ + stmcsia r0!, {r4, r5, r6, r7} + stmmiia r0!, {r8, r9} + tst r3, #0x4 + ldrne r10,[r1], #4 /* 4 bytes */ + strne r10,[r0], #4 + sub r2, r2, r3 + +congruent_aligned32: + /* + * here source is aligned to 32 bytes. + */ + +cached_aligned32: + subs r2, r2, #32 + blo less_than_32_left + + /* + * We preload a cache-line up to 64 bytes ahead. On the 926, this will + * stall only until the requested world is fetched, but the linefill + * continues in the the background. + * While the linefill is going, we write our previous cache-line + * into the write-buffer (which should have some free space). + * When the linefill is done, the writebuffer will + * start dumping its content into memory + * + * While all this is going, we then load a full cache line into + * 8 registers, this cache line should be in the cache by now + * (or partly in the cache). + * + * This code should work well regardless of the source/dest alignment. + * + */ + + // Align the preload register to a cache-line because the cpu does + // "critical word first" (the first word requested is loaded first). + bic r12, r1, #0x1F + add r12, r12, #64 + +1: ldmia r1!, { r4-r11 } + //PLD (r12, #64) + subs r2, r2, #32 + + // NOTE: if r12 is more than 64 ahead of r1, the following ldrhi + // for ARM9 preload will not be safely guarded by the preceding subs. + // When it is safely guarded the only possibility to have SIGSEGV here + // is because the caller overstates the length. + ldrhi r3, [r12], #32 /* cheap ARM9 preload */ + stmia r0!, { r4-r11 } + bhs 1b + + add r2, r2, #32 + + + + +less_than_32_left: + /* + * less than 32 bytes left at this point (length in r2) + */ + + /* skip all this if there is nothing to do, which should + * be a common case (if not executed the code below takes + * about 16 cycles) + */ + tst r2, #0x1F + beq 1f + + /* conditionnaly copies 0 to 31 bytes */ + movs r12, r2, lsl #28 + ldmcsia r1!, {r4, r5, r6, r7} /* 16 bytes */ + ldmmiia r1!, {r8, r9} /* 8 bytes */ + stmcsia r0!, {r4, r5, r6, r7} + stmmiia r0!, {r8, r9} + movs r12, r2, lsl #30 + ldrcs r3, [r1], #4 /* 4 bytes */ + ldrmih r4, [r1], #2 /* 2 bytes */ + strcs r3, [r0], #4 + strmih r4, [r0], #2 + tst r2, #0x1 + ldrneb r3, [r1] /* last byte */ + strneb r3, [r0] + + /* we're done! restore everything and return */ +1: ldmfd sp!, {r5-r11} + ldmfd sp!, {r0, r4, lr} + bx lr + + /********************************************************************/ + +non_congruent: + /* + * here source is aligned to 4 bytes + * but destination is not. + * + * in the code below r2 is the number of bytes read + * (the number of bytes written is always smaller, because we have + * partial words in the shift queue) + */ + cmp r2, #4 + blo copy_last_3_and_return + + /* Use post-incriment mode for stm to spill r5-r11 to reserved stack + * frame. Don't update sp. + */ + stmea sp, {r5-r11} + + /* compute shifts needed to align src to dest */ + rsb r5, r0, #0 + and r5, r5, #3 /* r5 = # bytes in partial words */ + mov r12, r5, lsl #3 /* r12 = right */ + rsb lr, r12, #32 /* lr = left */ + + /* read the first word */ + ldr r3, [r1], #4 + sub r2, r2, #4 + + /* write a partial word (0 to 3 bytes), such that destination + * becomes aligned to 32 bits (r5 = nb of words to copy for alignment) + */ + movs r5, r5, lsl #31 + strmib r3, [r0], #1 + movmi r3, r3, lsr #8 + strcsb r3, [r0], #1 + movcs r3, r3, lsr #8 + strcsb r3, [r0], #1 + movcs r3, r3, lsr #8 + + cmp r2, #4 + blo partial_word_tail + + /* Align destination to 32 bytes (cache line boundary) */ +1: tst r0, #0x1c + beq 2f + ldr r5, [r1], #4 + sub r2, r2, #4 + orr r4, r3, r5, lsl lr + mov r3, r5, lsr r12 + str r4, [r0], #4 + cmp r2, #4 + bhs 1b + blo partial_word_tail + + /* copy 32 bytes at a time */ +2: subs r2, r2, #32 + blo less_than_thirtytwo + + /* Use immediate mode for the shifts, because there is an extra cycle + * for register shifts, which could account for up to 50% of + * performance hit. + */ + + cmp r12, #24 + beq loop24 + cmp r12, #8 + beq loop8 + +loop16: + ldr r12, [r1], #4 +1: mov r4, r12 + ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} + //PLD (r1, #64) + subs r2, r2, #32 + ldrhs r12, [r1], #4 + orr r3, r3, r4, lsl #16 + mov r4, r4, lsr #16 + orr r4, r4, r5, lsl #16 + mov r5, r5, lsr #16 + orr r5, r5, r6, lsl #16 + mov r6, r6, lsr #16 + orr r6, r6, r7, lsl #16 + mov r7, r7, lsr #16 + orr r7, r7, r8, lsl #16 + mov r8, r8, lsr #16 + orr r8, r8, r9, lsl #16 + mov r9, r9, lsr #16 + orr r9, r9, r10, lsl #16 + mov r10, r10, lsr #16 + orr r10, r10, r11, lsl #16 + stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} + mov r3, r11, lsr #16 + bhs 1b + b less_than_thirtytwo + +loop8: + ldr r12, [r1], #4 +1: mov r4, r12 + ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} + //PLD (r1, #64) + subs r2, r2, #32 + ldrhs r12, [r1], #4 + orr r3, r3, r4, lsl #24 + mov r4, r4, lsr #8 + orr r4, r4, r5, lsl #24 + mov r5, r5, lsr #8 + orr r5, r5, r6, lsl #24 + mov r6, r6, lsr #8 + orr r6, r6, r7, lsl #24 + mov r7, r7, lsr #8 + orr r7, r7, r8, lsl #24 + mov r8, r8, lsr #8 + orr r8, r8, r9, lsl #24 + mov r9, r9, lsr #8 + orr r9, r9, r10, lsl #24 + mov r10, r10, lsr #8 + orr r10, r10, r11, lsl #24 + stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} + mov r3, r11, lsr #8 + bhs 1b + b less_than_thirtytwo + +loop24: + ldr r12, [r1], #4 +1: mov r4, r12 + ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} + //PLD (r1, #64) + subs r2, r2, #32 + ldrhs r12, [r1], #4 + orr r3, r3, r4, lsl #8 + mov r4, r4, lsr #24 + orr r4, r4, r5, lsl #8 + mov r5, r5, lsr #24 + orr r5, r5, r6, lsl #8 + mov r6, r6, lsr #24 + orr r6, r6, r7, lsl #8 + mov r7, r7, lsr #24 + orr r7, r7, r8, lsl #8 + mov r8, r8, lsr #24 + orr r8, r8, r9, lsl #8 + mov r9, r9, lsr #24 + orr r9, r9, r10, lsl #8 + mov r10, r10, lsr #24 + orr r10, r10, r11, lsl #8 + stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} + mov r3, r11, lsr #24 + bhs 1b + + +less_than_thirtytwo: + /* copy the last 0 to 31 bytes of the source */ + rsb r12, lr, #32 /* we corrupted r12, recompute it */ + add r2, r2, #32 + cmp r2, #4 + blo partial_word_tail + +1: ldr r5, [r1], #4 + sub r2, r2, #4 + orr r4, r3, r5, lsl lr + mov r3, r5, lsr r12 + str r4, [r0], #4 + cmp r2, #4 + bhs 1b + +partial_word_tail: + /* we have a partial word in the input buffer */ + movs r5, lr, lsl #(31-3) + strmib r3, [r0], #1 + movmi r3, r3, lsr #8 + strcsb r3, [r0], #1 + movcs r3, r3, lsr #8 + strcsb r3, [r0], #1 + + /* Refill spilled registers from the stack. Don't update sp. */ + ldmfd sp, {r5-r11} + +copy_last_3_and_return: + movs r2, r2, lsl #31 /* copy remaining 0, 1, 2 or 3 bytes */ + ldrmib r2, [r1], #1 + ldrcsb r3, [r1], #1 + ldrcsb r12,[r1] + strmib r2, [r0], #1 + strcsb r3, [r0], #1 + strcsb r12,[r0] + + /* we're done! restore sp and spilled registers and return */ + add sp, sp, #28 + ldmfd sp!, {r0, r4, lr} + bx lr +#ifndef __APPLE__ + .fnend +#endif + +#endif /* __ARM_ARCH__ < 7 */ +#endif + +#if defined(__linux__) && defined(__ELF__) +/* we don't need an executable stack */ +.section .note.GNU-stack,"",%progbits +#endif diff --git a/src/utils/fastmemcpy.c b/src/utils/fastmemcpy.c new file mode 100644 index 0000000000..d2a1d49d54 --- /dev/null +++ b/src/utils/fastmemcpy.c @@ -0,0 +1,396 @@ +/* + * fastmemcpy.h : fast memcpy routines + ***************************************************************************** + * $Id: fastmemcpy.h 13905 2006-01-12 23:10:04Z dionoea $ + * + * Authors: various Linux kernel hackers + * various MPlayer hackers + * Nick Kurshev <nickols_k@mail.ru> + * + * Copyright (C) 2011-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 + * <http://www.gnu.org/licenses/>. + * + */ +#if !defined(TARGET_WINDOWS) && !defined(__ppc__) && !defined(__powerpc__) && !defined(__arm__) +#define HAVE_MMX2 +#define HAVE_SSE + +/* + aclib - advanced C library ;) + This file contains functions which improve and expand standard C-library +*/ +#include <stddef.h> + +#define BLOCK_SIZE 4096 +#define CONFUSION_FACTOR 0 +/*Feel free to fine-tune the above 2, it might be possible to get some speedup with them :)*/ + +/*#define STATISTICS*/ + +#ifndef HAVE_SSE2 +/* + P3 processor has only one SSE decoder so can execute only 1 sse insn per + cpu clock, but it has 3 mmx decoders (include load/store unit) + and executes 3 mmx insns per cpu clock. + P4 processor has some chances, but after reading: + http://www.emulators.com/pentium4.htm + I have doubts. Anyway SSE2 version of this code can be written better. +*/ +#undef HAVE_SSE +#endif + + +/* + This part of code was taken by me from Linux-2.4.3 and slightly modified +for MMX, MMX2, SSE instruction set. I have done it since linux uses page aligned +blocks but mplayer uses weakly ordered data and original sources can not +speedup them. Only using PREFETCHNTA and MOVNTQ together have effect! + +>From IA-32 Intel Architecture Software Developer's Manual Volume 1, + +Order Number 245470: +"10.4.6. Cacheability Control, Prefetch, and Memory Ordering Instructions" + +Data referenced by a program can be temporal (data will be used again) or +non-temporal (data will be referenced once and not reused in the immediate +future). To make efficient use of the processor's caches, it is generally +desirable to cache temporal data and not cache non-temporal data. Overloading +the processor's caches with non-temporal data is sometimes referred to as +"polluting the caches". +The non-temporal data is written to memory with Write-Combining semantics. + +The PREFETCHh instructions permits a program to load data into the processor +at a suggested cache level, so that it is closer to the processors load and +store unit when it is needed. If the data is already present in a level of +the cache hierarchy that is closer to the processor, the PREFETCHh instruction +will not result in any data movement. +But we should you PREFETCHNTA: Non-temporal data fetch data into location +close to the processor, minimizing cache pollution. + +The MOVNTQ (store quadword using non-temporal hint) instruction stores +packed integer data from an MMX register to memory, using a non-temporal hint. +The MOVNTPS (store packed single-precision floating-point values using +non-temporal hint) instruction stores packed floating-point data from an +XMM register to memory, using a non-temporal hint. + +The SFENCE (Store Fence) instruction controls write ordering by creating a +fence for memory store operations. This instruction guarantees that the results +of every store instruction that precedes the store fence in program order is +globally visible before any store instruction that follows the fence. The +SFENCE instruction provides an efficient way of ensuring ordering between +procedures that produce weakly-ordered data and procedures that consume that +data. + +If you have questions please contact with me: Nick Kurshev: nickols_k@mail.ru. +*/ + +/* 3dnow memcpy support from kernel 2.4.2 */ +/* by Pontscho/fresh!mindworkz */ + +#if defined( HAVE_MMX2 ) || defined( HAVE_3DNOW ) || defined( HAVE_MMX ) + +#undef HAVE_MMX1 +#if defined(HAVE_MMX) && !defined(HAVE_MMX2) && !defined(HAVE_3DNOW) && !defined(HAVE_SSE) +/* means: mmx v.1. Note: Since we added alignment of destinition it speedups + of memory copying on PentMMX, Celeron-1 and P2 upto 12% versus + standard (non MMX-optimized) version. + Note: on K6-2+ it speedups memory copying upto 25% and + on K7 and P3 about 500% (5 times). */ +#define HAVE_MMX1 +#endif + + +#undef HAVE_K6_2PLUS +#if !defined( HAVE_MMX2) && defined( HAVE_3DNOW) +#define HAVE_K6_2PLUS +#endif + +/* for small memory blocks (<256 bytes) this version is faster */ +#define small_memcpy(to,from,n)\ +{\ +register unsigned long int dummy;\ +__asm__ __volatile__(\ + "rep; movsb"\ + :"=&D"(to), "=&S"(from), "=&c"(dummy)\ +/* It's most portable way to notify compiler */\ +/* that edi, esi and ecx are clobbered in asm block. */\ +/* Thanks to A'rpi for hint!!! */\ + :"0" (to), "1" (from),"2" (n)\ + : "memory");\ +} + +#ifdef HAVE_SSE +#define MMREG_SIZE 16 +#else +#define MMREG_SIZE 64 /*8*/ +#endif + +/* Small defines (for readability only) ;) */ +#ifdef HAVE_K6_2PLUS +#define PREFETCH "prefetch" +/* On K6 femms is faster of emms. On K7 femms is directly mapped on emms. */ +#define EMMS "femms" +#else +#define PREFETCH "prefetchnta" +#define EMMS "emms" +#endif + +#ifdef HAVE_MMX2 +#define MOVNTQ "movntq" +#else +#define MOVNTQ "movq" +#endif + +#ifdef HAVE_MMX1 +#define MIN_LEN 0x800 /* 2K blocks */ +#else +#define MIN_LEN 0x40 /* 64-byte blocks */ +#endif + +void * fast_memcpy(void * to, const void * from, size_t len) +{ + void *retval; + size_t i; + retval = to; +#ifdef STATISTICS + { + static int freq[33]; + static int t=0; + int i; + for(i=0; len>(1<<i); i++); + freq[i]++; + t++; + if(1024*1024*1024 % t == 0) + for(i=0; i<32; i++) + printf("freq < %8d %4d\n", 1<<i, freq[i]); + } +#endif +#ifndef HAVE_MMX1 + /* PREFETCH has effect even for MOVSB instruction ;) */ + __asm__ __volatile__ ( + PREFETCH" (%0)\n" + PREFETCH" 64(%0)\n" + PREFETCH" 128(%0)\n" + PREFETCH" 192(%0)\n" + PREFETCH" 256(%0)\n" + : : "r" (from) ); +#endif + if(len >= MIN_LEN) + { + register unsigned long int delta; + /* Align destinition to MMREG_SIZE -boundary */ + delta = ((unsigned long int)to)&(MMREG_SIZE-1); + if(delta) + { + delta=MMREG_SIZE-delta; + len -= delta; + small_memcpy(to, from, delta); + } + i = len >> 6; /* len/64 */ + len&=63; + /* + This algorithm is top effective when the code consequently + reads and writes blocks which have size of cache line. + Size of cache line is processor-dependent. + It will, however, be a minimum of 32 bytes on any processors. + It would be better to have a number of instructions which + perform reading and writing to be multiple to a number of + processor's decoders, but it's not always possible. + */ +#ifdef HAVE_SSE /* Only P3 (may be Cyrix3) */ + if(((unsigned long)from) & 15) + /* if SRC is misaligned */ + for(; i>0; i--) + { + __asm__ __volatile__ ( + PREFETCH" 320(%0)\n" + "movups (%0), %%xmm0\n" + "movups 16(%0), %%xmm1\n" + "movups 32(%0), %%xmm2\n" + "movups 48(%0), %%xmm3\n" + "movntps %%xmm0, (%1)\n" + "movntps %%xmm1, 16(%1)\n" + "movntps %%xmm2, 32(%1)\n" + "movntps %%xmm3, 48(%1)\n" + :: "r" (from), "r" (to) : "memory"); + ((const unsigned char *)from)+=64; + ((unsigned char *)to)+=64; + } + else + /* + Only if SRC is aligned on 16-byte boundary. + It allows to use movaps instead of movups, which required data + to be aligned or a general-protection exception (#GP) is generated. + */ + for(; i>0; i--) + { + __asm__ __volatile__ ( + PREFETCH" 320(%0)\n" + "movaps (%0), %%xmm0\n" + "movaps 16(%0), %%xmm1\n" + "movaps 32(%0), %%xmm2\n" + "movaps 48(%0), %%xmm3\n" + "movntps %%xmm0, (%1)\n" + "movntps %%xmm1, 16(%1)\n" + "movntps %%xmm2, 32(%1)\n" + "movntps %%xmm3, 48(%1)\n" + :: "r" (from), "r" (to) : "memory"); + ((const unsigned char *)from)+=64; + ((unsigned char *)to)+=64; + } +#else + /* Align destination at BLOCK_SIZE boundary */ + for(; ((ptrdiff_t)to & (BLOCK_SIZE-1)) && i>0; i--) + { + __asm__ __volatile__ ( +#ifndef HAVE_MMX1 + PREFETCH" 320(%0)\n" +#endif + "movq (%0), %%mm0\n" + "movq 8(%0), %%mm1\n" + "movq 16(%0), %%mm2\n" + "movq 24(%0), %%mm3\n" + "movq 32(%0), %%mm4\n" + "movq 40(%0), %%mm5\n" + "movq 48(%0), %%mm6\n" + "movq 56(%0), %%mm7\n" + MOVNTQ" %%mm0, (%1)\n" + MOVNTQ" %%mm1, 8(%1)\n" + MOVNTQ" %%mm2, 16(%1)\n" + MOVNTQ" %%mm3, 24(%1)\n" + MOVNTQ" %%mm4, 32(%1)\n" + MOVNTQ" %%mm5, 40(%1)\n" + MOVNTQ" %%mm6, 48(%1)\n" + MOVNTQ" %%mm7, 56(%1)\n" + :: "r" (from), "r" (to) : "memory"); + from = (const void *) (((const unsigned char *)from)+64); + to = (void *) (((unsigned char *)to)+64); + } + +/* printf(" %p %p\n", (ptrdiff_t)from&1023, (ptrdiff_t)to&1023); */ + /* Pure Assembly cuz gcc is a bit unpredictable ;) */ +# if 0 + if(i>=BLOCK_SIZE/64) + asm volatile( + "xorl %%eax, %%eax \n\t" + ".balign 16 \n\t" + "1: \n\t" + "movl (%0, %%eax), %%ebx \n\t" + "movl 32(%0, %%eax), %%ebx \n\t" + "movl 64(%0, %%eax), %%ebx \n\t" + "movl 96(%0, %%eax), %%ebx \n\t" + "addl $128, %%eax \n\t" + "cmpl %3, %%eax \n\t" + " jb 1b \n\t" + + "xorl %%eax, %%eax \n\t" + + ".balign 16 \n\t" + "2: \n\t" + "movq (%0, %%eax), %%mm0\n" + "movq 8(%0, %%eax), %%mm1\n" + "movq 16(%0, %%eax), %%mm2\n" + "movq 24(%0, %%eax), %%mm3\n" + "movq 32(%0, %%eax), %%mm4\n" + "movq 40(%0, %%eax), %%mm5\n" + "movq 48(%0, %%eax), %%mm6\n" + "movq 56(%0, %%eax), %%mm7\n" + MOVNTQ" %%mm0, (%1, %%eax)\n" + MOVNTQ" %%mm1, 8(%1, %%eax)\n" + MOVNTQ" %%mm2, 16(%1, %%eax)\n" + MOVNTQ" %%mm3, 24(%1, %%eax)\n" + MOVNTQ" %%mm4, 32(%1, %%eax)\n" + MOVNTQ" %%mm5, 40(%1, %%eax)\n" + MOVNTQ" %%mm6, 48(%1, %%eax)\n" + MOVNTQ" %%mm7, 56(%1, %%eax)\n" + "addl $64, %%eax \n\t" + "cmpl %3, %%eax \n\t" + "jb 2b \n\t" + +#if CONFUSION_FACTOR > 0 + /* a few percent speedup on out of order executing CPUs */ + "movl %5, %%eax \n\t" + "2: \n\t" + "movl (%0), %%ebx \n\t" + "movl (%0), %%ebx \n\t" + "movl (%0), %%ebx \n\t" + "movl (%0), %%ebx \n\t" + "decl %%eax \n\t" + " jnz 2b \n\t" +#endif + + "xorl %%eax, %%eax \n\t" + "addl %3, %0 \n\t" + "addl %3, %1 \n\t" + "subl %4, %2 \n\t" + "cmpl %4, %2 \n\t" + " jae 1b \n\t" + : "+r" (from), "+r" (to), "+r" (i) + : "r" (BLOCK_SIZE), "i" (BLOCK_SIZE/64), "i" (CONFUSION_FACTOR) + : "%eax", "%ebx" + ); +#endif + + for(; i>0; i--) + { + __asm__ __volatile__ ( +#ifndef HAVE_MMX1 + PREFETCH" 320(%0)\n" +#endif + "movq (%0), %%mm0\n" + "movq 8(%0), %%mm1\n" + "movq 16(%0), %%mm2\n" + "movq 24(%0), %%mm3\n" + "movq 32(%0), %%mm4\n" + "movq 40(%0), %%mm5\n" + "movq 48(%0), %%mm6\n" + "movq 56(%0), %%mm7\n" + MOVNTQ" %%mm0, (%1)\n" + MOVNTQ" %%mm1, 8(%1)\n" + MOVNTQ" %%mm2, 16(%1)\n" + MOVNTQ" %%mm3, 24(%1)\n" + MOVNTQ" %%mm4, 32(%1)\n" + MOVNTQ" %%mm5, 40(%1)\n" + MOVNTQ" %%mm6, 48(%1)\n" + MOVNTQ" %%mm7, 56(%1)\n" + :: "r" (from), "r" (to) : "memory"); + from = (const void *) (((const unsigned char *)from)+64); + to = (void *) (((unsigned char *)to)+64); + } + +#endif /* Have SSE */ +#ifdef HAVE_MMX2 + /* since movntq is weakly-ordered, a "sfence" + * is needed to become ordered again. */ + __asm__ __volatile__ ("sfence":::"memory"); +#endif +#ifndef HAVE_SSE + /* enables to use FPU */ + __asm__ __volatile__ (EMMS:::"memory"); +#endif + } + /* + * Now do the tail of the block + */ + if(len) small_memcpy(to, from, len); + return retval; +} + + +#endif /* #if defined( HAVE_MMX2 ) || defined( HAVE_3DNOW ) || defined( HAVE_MMX ) */ + +#endif diff --git a/src/utils/fastmemcpy.h b/src/utils/fastmemcpy.h new file mode 100644 index 0000000000..ce78d491ce --- /dev/null +++ b/src/utils/fastmemcpy.h @@ -0,0 +1,35 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(TARGET_WINDOWS) && !defined(__ppc__) && !defined(__powerpc__) && !defined(TARGET_ANDROID) && !defined(TARGET_DARWIN_IOS) +void * fast_memcpy(void * to, const void * from, size_t len); +//#define fast_memcpy memcpy +#else +#define fast_memcpy memcpy +#endif + +#ifdef __cplusplus +} +#endif diff --git a/src/utils/fft.cpp b/src/utils/fft.cpp new file mode 100644 index 0000000000..8f0ddab696 --- /dev/null +++ b/src/utils/fft.cpp @@ -0,0 +1,176 @@ +/* + * COPYRIGHT + * + * XAnalyser, frequence spectrum analyser for X Window + * Copyright (C) 1998 Arvin Schnell + * + * 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 of the License, 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 + * <http://www.gnu.org/licenses/>. + * + * Contact addresses: + * arvin@informatik.uni-bremen.de + * Arvin Schnell, Am Heidberg 8, 28865 Lilienthal, Germany + * + */ + + +#include <math.h> +#include <algorithm> +#include "fft.h" + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#ifndef M_SQRT2 +#define M_SQRT2 1.4142135623730950488016887242097 +#endif + +// WARNING: +// Whenever you call that method directly, make sure +// that you pass in ptr - 1. Also remember this method +// has a complex result. You most likely want to have a +// realfft only instead +void fft( float data[], int nn, int isign ) +{ + int n = nn << 1; + int i, j, m; + + /* bit reversal section */ + + j = 1; + for ( i = 1; i < n; i += 2 ) + { + if ( j > i ) + { + std::swap( data[j], data[i] ); + std::swap( data[j + 1], data[i + 1] ); + } + m = nn; + while ( m >= 2 && j > m ) + { + j -= m; + m >>= 1; + } + j += m; + } + + /* Daniel-Lanczos section */ + + long double theta, wr, wpr, wpi, wi, wtemp; + float tempr, tempi; + int mmax = 2; + while (n > mmax) + { + int istep = mmax << 1; + theta = isign * ( 2.0 * M_PI / mmax ); + wtemp = sin(0.5 * theta); + wpr = -2.0 * wtemp * wtemp; + wpi = sin( theta ); + wr = 1.0; + wi = 0.0; + for ( m = 1; m < mmax; m += 2 ) + { + for ( i = m; i <= n; i += istep ) + { + j = i + mmax; + if (j >= n || i >= n) + break; + tempr = (float) (wr * data[j] - wi * data[j + 1]); + tempi = (float) (wr * data[j + 1] + wi * data[j]); + data[j] = data[i] - tempr; + data[j + 1] = data[i + 1] - tempi; + data[i] += tempr; + data[i + 1] += tempi; + } + wr = (wtemp = wr) * wpr - wi * wpi + wr; + wi = wi * wpr + wtemp * wpi + wi; + } + mmax = istep; + } +} + +// By JM - packed 2 channel real fft - returns the amplitudes of the fft array +// data[] is a 2n size array, with interleaved channels, and the fft is returned in data[] +// interleaving is preserved. +void twochannelrfft(float data[], int n) +{ + float rep, rem, aip, aim; + int nn = n + n; + int nn1 = nn + 1; + // data is already packed - do the transform + fft( data - 1, n , + 1 ); + + // now repack the array as needed + data[0] = data[0] * data[0]; // only need the amplitude squared + data[1] = data[1] * data[1]; + data[n] = data[n] * data[n]; + data[n + 1] = data[n + 1] * data[n + 1]; + // don't need the last component - this is the constant component? + + for (int j = 2; j < n; j += 2) + { + rep = (float)(0.5 * (data[j] + data[nn - j])); + rem = (float)(0.5 * (data[j] - data[nn - j])); + aip = (float)(0.5 * (data[j + 1] + data[nn1 - j])); + aim = (float)(0.5 * (data[j + 1] - data[nn1 - j])); + /* this works out the complex FT + fft1[j]=rep; + fft1[j+1]=aim; + fft1[nn-j]=rep; + fft1[nn1-j]=-aim; + fft2[j]=aip; + fft2[j+1]=-rem; + fft2[nn-j]=aip; + fft2[nn1-j]=rem; */ + // we just need the amplitudes + data[j] = (float)(2 * (sqr(rep) + sqr(aim))); // was sqrt'd + data[j + 1] = (float)(2 * (sqr(rem) + sqr(aip))); + } +} + +void twochanwithwindow(float data[], int n) +{ + float rep, rem, aip, aim; + int nn = n + n; + int nn1 = nn + 1; + // window the data + float wn; + for (int i = 0; i < nn; i += 2) + { + wn = (float)(0.5 * (1 - cos(M_PI * i / n))); + data[i] *= wn; + data[i + 1] *= wn; + } + // data is already packed - do the transform + fft( data - 1, n , + 1 ); + + // now repack the array as needed + data[0] = data[0] * data[0]; // only need the amplitude squared + data[1] = data[1] * data[1]; + data[n] = data[n] * data[n]; + data[n + 1] = data[n + 1] * data[n + 1]; + // don't need the last component - this is the constant component? + + for (int j = 2; j < n; j += 2) + { + rep = data[j] + data[nn - j]; + rem = data[j] - data[nn - j]; + aip = data[j + 1] + data[nn1 - j]; + aim = data[j + 1] - data[nn1 - j]; + data[j] = (float)(0.5 * (sqr(rep) + sqr(aim))); + data[j + 1] = (float)(0.5 * (sqr(rem) + sqr(aip))); + } +} + diff --git a/src/utils/fft.h b/src/utils/fft.h new file mode 100644 index 0000000000..8a78aa77f8 --- /dev/null +++ b/src/utils/fft.h @@ -0,0 +1,52 @@ +#ifndef fft_hh +#define fft_hh +/* + * COPYRIGHT + * + * XAnalyser, frequence spectrum analyser for X Window + * Copyright (C) 1998 Arvin Schnell + * + * 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 of the License, 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 + * <http://www.gnu.org/licenses/>. + * + * Contact addresses: + * arvin@informatik.uni-bremen.de + * Arvin Schnell, Am Heidberg 8, 28865 Lilienthal, Germany + * + */ +static __inline long double sqr( long double arg ) +{ + return arg * arg; +} + + +static __inline void swap( float &a, float &b ) +{ + float t = a; a = b; b = t; +} + +// (complex) fast fourier transformation +// Based on four1() in Numerical Recipes in C, Page 507-508. + +// The input in data[1..2*nn] is replaced by its fft or inverse fft, depending +// only on isign (+1 for fft, -1 for inverse fft). The number of complex numbers +// n must be a power of 2 (which is not checked). + +void fft( float data[], int nn, int isign ); + +void twochannelrfft(float data[], int n); +void twochanwithwindow(float data[], int n); // test + + +#endif diff --git a/src/utils/fstrcmp.c b/src/utils/fstrcmp.c new file mode 100644 index 0000000000..3b0cc233c1 --- /dev/null +++ b/src/utils/fstrcmp.c @@ -0,0 +1,114 @@ +/* + * Functions to make fuzzy comparisons between strings. + * + * Derived from PHP 5 similar_text() function + * + * The basic algorithm is described in: + * Oliver [1993] and the complexity is O(N**3) with N == length of longest string + + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2010 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rasmus Lerdorf <rasmus@php.net> | + | Stig Sther Bakken <ssb@php.net> | + | Zeev Suraski <zeev@zend.com> | + +----------------------------------------------------------------------+ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include <string.h> + +static int similar_text(const char *str1, const char *str2, int len1, int len2) +{ + int sum; + int pos1 = 0, pos2 = 0; + int max = 0; + + char *p, *q; + char *end1 = (char *)str1 + len1; + char *end2 = (char *)str2 + len2; + int l; + + for (p = (char *)str1; p < end1; p++) + { + for (q = (char *)str2; q < end2; q++) + { + for (l = 0; (p + l < end1) && (q + l < end2) && (p[l] == q[l]); l++) + ; + if (l > max) + { + max = l; + pos1 = p - str1; + pos2 = q - str2; + } + } + } + if ((sum = max)) + { + if (pos1 && pos2) + sum += similar_text(str1, str2, pos1, pos2); + + if ((pos1 + max < len1) && (pos2 + max < len2)) + sum += similar_text(str1 + pos1 + max, str2 + pos2 + max, + len1 - pos1 - max, len2 - pos2 - max); + } + + return sum; +} + +/* NAME + fstrcmp - fuzzy string compare + + SYNOPSIS + double fstrcmp(const char *, const char *, double); + + DESCRIPTION + The fstrcmp function may be used to compare two string for + similarity. It is very useful in reducing "cascade" or + "secondary" errors in compilers or other situations where + symbol tables occur. + + RETURNS + double; 0 if the strings are entirly dissimilar, 1 if the + strings are identical, and a number in between if they are + similar. */ + +double +fstrcmp (const char *string1, const char *string2, double minimum) +{ + int len1, len2, score; + + len1 = (int)strlen(string1); + len2 = (int)strlen(string2); + + /* short-circuit obvious comparisons */ + if (len1 == 0 && len2 == 0) + return 1.0; + if (len1 == 0 || len2 == 0) + return 0.0; + + score = similar_text(string1, string2, len1, len2); + /* The result is + ((number of chars in common) / (average length of the strings)). + This is admittedly biased towards finding that the strings are + similar, however it does produce meaningful results. */ + return ((double)score * 2.0 / (len1 + len2)); +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/utils/fstrcmp.h b/src/utils/fstrcmp.h new file mode 100644 index 0000000000..d848085fd7 --- /dev/null +++ b/src/utils/fstrcmp.h @@ -0,0 +1,36 @@ +#ifndef _FSTRCMP_H +#define _FSTRCMP_H + + /* GNU gettext - internationalization aids + Copyright (C) 1995 Free Software Foundation, Inc. + + This file was written by Peter Miller <pmiller@agso.gov.au> + +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 +<http://www.gnu.org/licenses/>. +*/ +#define PARAMS(proto) proto + +#ifdef __cplusplus +extern "C" +{ +#endif + +double fstrcmp (const char *__s1, const char *__s2, double __minimum); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/src/utils/log.cpp b/src/utils/log.cpp new file mode 100644 index 0000000000..3443f1293d --- /dev/null +++ b/src/utils/log.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2005-2014 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "log.h" +#include "system.h" +#include "threads/SingleLock.h" +#include "threads/Thread.h" +#include "utils/StringUtils.h" +#include "CompileInfo.h" + +static const char* const levelNames[] = +{"DEBUG", "INFO", "NOTICE", "WARNING", "ERROR", "SEVERE", "FATAL", "NONE"}; + +// add 1 to level number to get index of name +static const char* const logLevelNames[] = +{ "LOG_LEVEL_NONE" /*-1*/, "LOG_LEVEL_NORMAL" /*0*/, "LOG_LEVEL_DEBUG" /*1*/, "LOG_LEVEL_DEBUG_FREEMEM" /*2*/ }; + +// s_globals is used as static global with CLog global variables +#define s_globals XBMC_GLOBAL_USE(CLog).m_globalInstance + +CLog::CLog() +{} + +CLog::~CLog() +{} + +void CLog::Close() +{ + CSingleLock waitLock(s_globals.critSec); + s_globals.m_platform.CloseLogFile(); + s_globals.m_repeatLine.clear(); +} + +void CLog::Log(int loglevel, const char *format, ...) +{ + if (IsLogLevelLogged(loglevel)) + { + va_list va; + va_start(va, format); + LogString(loglevel, StringUtils::FormatV(format, va)); + va_end(va); + } +} + +void CLog::LogFunction(int loglevel, const char* functionName, const char* format, ...) +{ + if (IsLogLevelLogged(loglevel)) + { + std::string fNameStr; + if (functionName && functionName[0]) + fNameStr.assign(functionName).append(": "); + va_list va; + va_start(va, format); + LogString(loglevel, fNameStr + StringUtils::FormatV(format, va)); + va_end(va); + } +} + +void CLog::LogString(int logLevel, const std::string& logString) +{ + CSingleLock waitLock(s_globals.critSec); + std::string strData(logString); + StringUtils::TrimRight(strData); + if (!strData.empty()) + { + if (s_globals.m_repeatLogLevel == logLevel && s_globals.m_repeatLine == strData) + { + s_globals.m_repeatCount++; + return; + } + else if (s_globals.m_repeatCount) + { + std::string strData2 = StringUtils::Format("Previous line repeats %d times.", + s_globals.m_repeatCount); + PrintDebugString(strData2); + WriteLogString(s_globals.m_repeatLogLevel, strData2); + s_globals.m_repeatCount = 0; + } + + s_globals.m_repeatLine = strData; + s_globals.m_repeatLogLevel = logLevel; + + PrintDebugString(strData); + + WriteLogString(logLevel, strData); + } +} + +bool CLog::Init(const std::string& path) +{ + CSingleLock waitLock(s_globals.critSec); + + // the log folder location is initialized in the CAdvancedSettings + // constructor and changed in CApplication::Create() + + std::string appName = CCompileInfo::GetAppName(); + StringUtils::ToLower(appName); + return s_globals.m_platform.OpenLogFile(path + appName + ".log", path + appName + ".old.log"); +} + +void CLog::MemDump(char *pData, int length) +{ + Log(LOGDEBUG, "MEM_DUMP: Dumping from %p", pData); + for (int i = 0; i < length; i+=16) + { + std::string strLine = StringUtils::Format("MEM_DUMP: %04x ", i); + char *alpha = pData; + for (int k=0; k < 4 && i + 4*k < length; k++) + { + for (int j=0; j < 4 && i + 4*k + j < length; j++) + { + std::string strFormat = StringUtils::Format(" %02x", (unsigned char)*pData++); + strLine += strFormat; + } + strLine += " "; + } + // pad with spaces + while (strLine.size() < 13*4 + 16) + strLine += " "; + for (int j=0; j < 16 && i + j < length; j++) + { + if (*alpha > 31) + strLine += *alpha; + else + strLine += '.'; + alpha++; + } + Log(LOGDEBUG, "%s", strLine.c_str()); + } +} + +void CLog::SetLogLevel(int level) +{ + CSingleLock waitLock(s_globals.critSec); + if (level >= LOG_LEVEL_NONE && level <= LOG_LEVEL_MAX) + { + s_globals.m_logLevel = level; + CLog::Log(LOGNOTICE, "Log level changed to \"%s\"", logLevelNames[s_globals.m_logLevel + 1]); + } + else + CLog::Log(LOGERROR, "%s: Invalid log level requested: %d", __FUNCTION__, level); +} + +int CLog::GetLogLevel() +{ + return s_globals.m_logLevel; +} + +void CLog::SetExtraLogLevels(int level) +{ + CSingleLock waitLock(s_globals.critSec); + s_globals.m_extraLogLevels = level; +} + +bool CLog::IsLogLevelLogged(int loglevel) +{ + const int extras = (loglevel & ~LOGMASK); + if (extras != 0 && (s_globals.m_extraLogLevels & extras) == 0) + return false; + +#if defined(_DEBUG) || defined(PROFILE) + return true; +#else + if (s_globals.m_logLevel >= LOG_LEVEL_DEBUG) + return true; + if (s_globals.m_logLevel <= LOG_LEVEL_NONE) + return false; + + // "m_logLevel" is "LOG_LEVEL_NORMAL" + return (loglevel & LOGMASK) >= LOGNOTICE; +#endif +} + + +void CLog::PrintDebugString(const std::string& line) +{ +#if defined(_DEBUG) || defined(PROFILE) + s_globals.m_platform.PrintDebugString(line); +#endif // defined(_DEBUG) || defined(PROFILE) +} + +bool CLog::WriteLogString(int logLevel, const std::string& logString) +{ + static const char* prefixFormat = "%02.2d:%02.2d:%02.2d T:%" PRIu64" %7s: "; + + std::string strData(logString); + /* fixup newline alignment, number of spaces should equal prefix length */ + StringUtils::Replace(strData, "\n", "\n "); + + int hour, minute, second; + s_globals.m_platform.GetCurrentLocalTime(hour, minute, second); + + strData = StringUtils::Format(prefixFormat, + hour, + minute, + second, + (uint64_t)CThread::GetCurrentThreadId(), + levelNames[logLevel]) + strData; + + return s_globals.m_platform.WriteStringToLog(strData); +} diff --git a/src/utils/log.h b/src/utils/log.h new file mode 100644 index 0000000000..0e9d51b9ac --- /dev/null +++ b/src/utils/log.h @@ -0,0 +1,86 @@ +#pragma once + +/* + * Copyright (C) 2005-2014 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> + +#if defined(TARGET_POSIX) +#include "posix/PosixInterfaceForCLog.h" +typedef class CPosixInterfaceForCLog PlatformInterfaceForCLog; +#elif defined(TARGET_WINDOWS) +#include "win32/Win32InterfaceForCLog.h" +typedef class CWin32InterfaceForCLog PlatformInterfaceForCLog; +#endif + +#include "commons/ilog.h" +#include "threads/CriticalSection.h" +#include "utils/GlobalsHandling.h" + +#include "utils/params_check_macros.h" + +class CLog +{ +public: + CLog(); + ~CLog(void); + static void Close(); + static void Log(int loglevel, PRINTF_FORMAT_STRING const char *format, ...) PARAM2_PRINTF_FORMAT; + static void LogFunction(int loglevel, IN_OPT_STRING const char* functionName, PRINTF_FORMAT_STRING const char* format, ...) PARAM3_PRINTF_FORMAT; +#define LogF(loglevel,format,...) LogFunction((loglevel),__FUNCTION__,(format),##__VA_ARGS__) + static void MemDump(char *pData, int length); + static bool Init(const std::string& path); + static void PrintDebugString(const std::string& line); // universal interface for printing debug strings + static void SetLogLevel(int level); + static int GetLogLevel(); + static void SetExtraLogLevels(int level); + static bool IsLogLevelLogged(int loglevel); + +protected: + class CLogGlobals + { + public: + CLogGlobals(void) : m_repeatCount(0), m_repeatLogLevel(-1), m_logLevel(LOG_LEVEL_DEBUG), m_extraLogLevels(0) {} + ~CLogGlobals() {} + PlatformInterfaceForCLog m_platform; + int m_repeatCount; + int m_repeatLogLevel; + std::string m_repeatLine; + int m_logLevel; + int m_extraLogLevels; + CCriticalSection critSec; + }; + class CLogGlobals m_globalInstance; // used as static global variable + static void LogString(int logLevel, const std::string& logString); + static bool WriteLogString(int logLevel, const std::string& logString); +}; + + +namespace XbmcUtils +{ + class LogImplementation : public XbmcCommons::ILogger + { + public: + virtual ~LogImplementation() {} + inline virtual void log(int logLevel, IN_STRING const char* message) { CLog::Log(logLevel, "%s", message); } + }; +} + +XBMC_GLOBAL_REF(CLog, g_log); diff --git a/src/utils/md5.cpp b/src/utils/md5.cpp new file mode 100644 index 0000000000..ce9ff6b703 --- /dev/null +++ b/src/utils/md5.cpp @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2009-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "md5.h" +#include "utils/StringUtils.h" + +typedef unsigned char md5byte; + +static void MD5Init(struct MD5Context *context); +static void MD5Update(struct MD5Context *context, md5byte const *buf, unsigned len); +static void MD5Final(unsigned char digest[16], struct MD5Context *context); +static void MD5Transform(uint32_t buf[4], uint32_t const in[16]); + + +XBMC::XBMC_MD5::XBMC_MD5(void) +{ + MD5Init(&m_ctx); +} + +XBMC::XBMC_MD5::~XBMC_MD5(void) +{} + +void XBMC::XBMC_MD5::append(const void *inBuf, size_t inLen) +{ + MD5Update(&m_ctx, (md5byte*)inBuf, inLen); +} + +void XBMC::XBMC_MD5::append(const std::string& str) +{ + append((unsigned char*) str.c_str(), (unsigned int) str.length()); +} + +void XBMC::XBMC_MD5::getDigest(unsigned char digest[16]) +{ + MD5Final(digest, &m_ctx); +} + +std::string XBMC::XBMC_MD5::getDigest() +{ + unsigned char szBuf[16] = {'\0'}; + getDigest(szBuf); + return StringUtils::Format("%02X%02X%02X%02X%02X%02X%02X%02X"\ + "%02X%02X%02X%02X%02X%02X%02X%02X", + szBuf[0], szBuf[1], szBuf[2], + szBuf[3], szBuf[4], szBuf[5], szBuf[6], szBuf[7], szBuf[8], + szBuf[9], szBuf[10], szBuf[11], szBuf[12], szBuf[13], szBuf[14], + szBuf[15]); +} + +std::string XBMC::XBMC_MD5::GetMD5(const std::string &text) +{ + if (text.empty()) + return ""; + XBMC_MD5 state; + state.append(text); + return state.getDigest(); +} + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * Changed so as no longer to depend on Colin Plumb's `usual.h' header + * definitions; now uses stuff from dpkg's config.h. + * - Ian Jackson <ian@chiark.greenend.org.uk>. + * Still in the public domain. + */ + +#include "md5.h" + +#include <sys/types.h> /* for stupid systems */ +#include <string.h> /* for memcpy() */ +#if defined(HAVE_CONFIG_H) && !defined(TARGET_WINDOWS) +#include "../config.h" +#endif + +#ifdef WORDS_BIGENDIAN +void +byteSwap(uint32_t *buf, unsigned words) +{ + md5byte *p = (md5byte *)buf; + + do { + *buf++ = (uint32_t)((unsigned)p[3] << 8 | p[2]) << 16 | + ((unsigned)p[1] << 8 | p[0]); + p += 4; + } while (--words); +} +#else +#define byteSwap(buf,words) +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +static void +MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bytes[0] = 0; + ctx->bytes[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +static void +MD5Update(struct MD5Context *ctx, md5byte const *buf, unsigned len) +{ + uint32_t t; + + /* Update byte count */ + + t = ctx->bytes[0]; + if ((ctx->bytes[0] = t + len) < t) + ctx->bytes[1]++; /* Carry from low to high */ + + t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */ + if (t > len) { + memcpy((md5byte *)ctx->in + 64 - t, buf, len); + return; + } + /* First chunk is an odd size */ + memcpy((md5byte *)ctx->in + 64 - t, buf, t); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += t; + len -= t; + + /* Process data in 64-byte chunks */ + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +static void +MD5Final(md5byte digest[16], struct MD5Context *ctx) +{ + int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */ + md5byte *p = (md5byte *)ctx->in + count; + + /* Set the first char of padding to 0x80. There is always room. */ + *p++ = 0x80; + + /* Bytes of padding needed to make 56 bytes (-8..55) */ + count = 56 - 1 - count; + + if (count < 0) { /* Padding forces an extra block */ + memset(p, 0, count + 8); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + p = (md5byte *)ctx->in; + count = 56; + } + memset(p, 0, count); + byteSwap(ctx->in, 14); + + /* Append length in bits and transform */ + ctx->in[14] = ctx->bytes[0] << 3; + ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; + MD5Transform(ctx->buf, ctx->in); + + byteSwap(ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f,w,x,y,z,in,s) \ + (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void +MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif diff --git a/src/utils/md5.h b/src/utils/md5.h new file mode 100644 index 0000000000..d0c336a4cd --- /dev/null +++ b/src/utils/md5.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009-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 + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef _MD5_H_ +#define _MD5_H_ + +#include <string> +#include <stdint.h> + +struct MD5Context { + uint32_t buf[4]; + uint32_t bytes[2]; + uint32_t in[16]; +}; + +namespace XBMC +{ + class XBMC_MD5 + { + public: + XBMC_MD5(void); + ~XBMC_MD5(void); + void append(const void *inBuf, size_t inLen); + void append(const std::string& str); + void getDigest(unsigned char digest[16]); + std::string getDigest(); + + /*! \brief Get the MD5 digest of the given text + \param text text to compute the MD5 for + \return MD5 digest + */ + static std::string GetMD5(const std::string &text); +private: + MD5Context m_ctx; + }; +} + +#endif diff --git a/src/utils/params_check_macros.h b/src/utils/params_check_macros.h new file mode 100644 index 0000000000..e145fa214c --- /dev/null +++ b/src/utils/params_check_macros.h @@ -0,0 +1,68 @@ +#pragma once +/* +* Copyright (C) 2014 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 +* <http://www.gnu.org/licenses/>. +* +*/ + +// macros for gcc, clang & others +#ifndef PARAM1_PRINTF_FORMAT +#ifdef __GNUC__ +// for use in functions that take printf format string as first parameter and additional printf parameters as second parameter +// for example: int myprintf(const char* format, ...) PARAM1_PRINTF_FORMAT; +#define PARAM1_PRINTF_FORMAT __attribute__((format(printf,1,2))) + +// for use in functions that take printf format string as second parameter and additional printf parameters as third parameter +// for example: bool log_string(int logLevel, const char* format, ...) PARAM2_PRINTF_FORMAT; +// note: all non-static class member functions take pointer to class object as hidden first parameter +// another example: class A { int myprintf(const char* format, ...) PARAM2_PRINTF_FORMAT; }; +#define PARAM2_PRINTF_FORMAT __attribute__((format(printf,2,3))) + +// for use in functions that take printf format string as third parameter and additional printf parameters as fourth parameter +// note: all non-static class member functions take pointer to class object as hidden first parameter +// for example: class A { bool log_string(int logLevel, const char* format, ...) PARAM3_PRINTF_FORMAT; }; +#define PARAM3_PRINTF_FORMAT __attribute__((format(printf,3,4))) +#else // ! __GNUC__ +#define PARAM1_PRINTF_FORMAT +#define PARAM2_PRINTF_FORMAT +#define PARAM3_PRINTF_FORMAT +#endif // ! __GNUC__ +#endif // PARAM1_PRINTF_FORMAT + +// macros for VC +// VC check parameters only when "Code Analysis" is called +#ifndef PRINTF_FORMAT_STRING +#ifdef _MSC_VER +#include <sal.h> + +// for use in any function that take printf format string and parameters +// for example: bool log_string(int logLevel, PRINTF_FORMAT_STRING const char* format, ...); +#define PRINTF_FORMAT_STRING _In_z_ _Printf_format_string_ + +// specify that parameter must be zero-terminated string +// for example: void SetName(IN_STRING const char* newName); +#define IN_STRING _In_z_ + +// specify that parameter must be zero-terminated string or NULL +// for example: bool SetAdditionalName(IN_OPT_STRING const char* addName); +#define IN_OPT_STRING _In_opt_z_ +#else // ! _MSC_VER +#define PRINTF_FORMAT_STRING +#define IN_STRING +#define IN_OPT_STRING +#endif // ! _MSC_VER +#endif // PRINTF_FORMAT_STRING diff --git a/src/utils/posix/PosixInterfaceForCLog.cpp b/src/utils/posix/PosixInterfaceForCLog.cpp new file mode 100644 index 0000000000..3f80594f06 --- /dev/null +++ b/src/utils/posix/PosixInterfaceForCLog.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "PosixInterfaceForCLog.h" +#include <stdio.h> +#include <time.h> + +#if defined(TARGET_DARWIN) +#include "DarwinUtils.h" +#elif defined(TARGET_ANDROID) +#include "android/activity/XBMCApp.h" +#endif // TARGET_ANDROID + +struct FILEWRAP : public FILE +{}; + + +CPosixInterfaceForCLog::CPosixInterfaceForCLog() : + m_file(NULL) +{ } + +CPosixInterfaceForCLog::~CPosixInterfaceForCLog() +{ + if (m_file) + fclose(m_file); +} + +bool CPosixInterfaceForCLog::OpenLogFile(const std::string &logFilename, const std::string &backupOldLogToFilename) +{ + if (m_file) + return false; // file was already opened + + (void)remove(backupOldLogToFilename.c_str()); // if it's failed, try to continue + (void)rename(logFilename.c_str(), backupOldLogToFilename.c_str()); // if it's failed, try to continue + + m_file = (FILEWRAP*)fopen(logFilename.c_str(), "wb"); + if (!m_file) + return false; // error, can't open log file + + static const unsigned char BOM[3] = { 0xEF, 0xBB, 0xBF }; + (void)fwrite(BOM, sizeof(BOM), 1, m_file); // write BOM, ignore possible errors + + return true; +} + +void CPosixInterfaceForCLog::CloseLogFile() +{ + if (m_file) + { + fclose(m_file); + m_file = NULL; + } +} + +bool CPosixInterfaceForCLog::WriteStringToLog(const std::string &logString) +{ + if (!m_file) + return false; + + const bool ret = (fwrite(logString.data(), logString.size(), 1, m_file) == 1) && + (fwrite("\n", 1, 1, m_file) == 1); + (void)fflush(m_file); + + return ret; +} + +void CPosixInterfaceForCLog::PrintDebugString(const std::string &debugString) +{ +#ifdef _DEBUG +#if defined(TARGET_DARWIN) + CDarwinUtils::PrintDebugString(debugString); +#elif defined(TARGET_ANDROID) + //print to adb + CXBMCApp::android_printf("Debug Print: %s", debugString.c_str()); +#endif // TARGET_ANDROID +#endif // _DEBUG +} + +void CPosixInterfaceForCLog::GetCurrentLocalTime(int &hour, int &minute, int &second) +{ + time_t curTime; + struct tm localTime; + if (time(&curTime) != -1 && localtime_r(&curTime, &localTime) != NULL) + { + hour = localTime.tm_hour; + minute = localTime.tm_min; + second = localTime.tm_sec; + } + else + hour = minute = second = 0; +} diff --git a/src/utils/posix/PosixInterfaceForCLog.h b/src/utils/posix/PosixInterfaceForCLog.h new file mode 100644 index 0000000000..bb534427c1 --- /dev/null +++ b/src/utils/posix/PosixInterfaceForCLog.h @@ -0,0 +1,38 @@ +#pragma once +/* + * Copyright (C) 2014 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string> + +struct FILEWRAP; // forward declaration, wrapper for FILE + +class CPosixInterfaceForCLog +{ +public: + CPosixInterfaceForCLog(); + ~CPosixInterfaceForCLog(); + bool OpenLogFile(const std::string& logFilename, const std::string& backupOldLogToFilename); + void CloseLogFile(void); + bool WriteStringToLog(const std::string& logString); + void PrintDebugString(const std::string& debugString); + static void GetCurrentLocalTime(int& hour, int& minute, int& second); +private: + FILEWRAP* m_file; +}; diff --git a/src/utils/test/CXBMCTinyXML-test.xml b/src/utils/test/CXBMCTinyXML-test.xml new file mode 100644 index 0000000000..9444dc8ace --- /dev/null +++ b/src/utils/test/CXBMCTinyXML-test.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<details> + <url function="ParseTMDBRating" cache="tmdb-en-12244.json"> + http://api.themoviedb.org/3/movie/12244?api_key=57983e31fb435df4df77afb854740ea9&language=en??? + </url> +</details> diff --git a/src/utils/test/Makefile b/src/utils/test/Makefile new file mode 100644 index 0000000000..2c38acd74f --- /dev/null +++ b/src/utils/test/Makefile @@ -0,0 +1,60 @@ +SRCS= \ + TestAlarmClock.cpp \ + TestAliasShortcutUtils.cpp \ + TestArchive.cpp \ + TestAsyncFileCopy.cpp \ + TestBase64.cpp \ + TestBitstreamStats.cpp \ + TestCharsetConverter.cpp \ + TestCPUInfo.cpp \ + TestCrc32.cpp \ + TestCryptThreading.cpp \ + TestDatabaseUtils.cpp \ + TestEndianSwap.cpp \ + Testfastmemcpy.cpp \ + Testfft.cpp \ + TestFileOperationJob.cpp \ + TestFileUtils.cpp \ + Testfstrcmp.cpp \ + TestGlobalsHandling.cpp \ + TestHTMLTable.cpp \ + TestHTMLUtil.cpp \ + TestHttpHeader.cpp \ + TestHttpParser.cpp \ + TestHttpResponse.cpp \ + TestJobManager.cpp \ + TestJSONVariantParser.cpp \ + TestJSONVariantWriter.cpp \ + TestLabelFormatter.cpp \ + TestLangCodeExpander.cpp \ + Testlog.cpp \ + TestMathUtils.cpp \ + Testmd5.cpp \ + TestMime.cpp \ + TestPerformanceSample.cpp \ + TestPOUtils.cpp \ + TestRegExp.cpp \ + TestRingBuffer.cpp \ + TestScraperParser.cpp \ + TestScraperUrl.cpp \ + TestSortUtils.cpp \ + TestStdString.cpp \ + TestStopwatch.cpp \ + TestStreamDetails.cpp \ + TestStreamUtils.cpp \ + TestStringUtils.cpp \ + TestSystemInfo.cpp \ + TestTimeSmoother.cpp \ + TestTimeUtils.cpp \ + TestURIUtils.cpp \ + TestUrlOptions.cpp \ + TestVariant.cpp \ + TestXBMCTinyXML.cpp \ + TestXMLUtils.cpp + +LIB=utilsTest.a + +INCLUDES += -I../../../lib/gtest/include + +include ../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/src/utils/test/TestAlarmClock.cpp b/src/utils/test/TestAlarmClock.cpp new file mode 100644 index 0000000000..5cd13799d2 --- /dev/null +++ b/src/utils/test/TestAlarmClock.cpp @@ -0,0 +1,37 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/AlarmClock.h" + +#include "gtest/gtest.h" + +TEST(TestAlarmClock, General) +{ + CAlarmClock a; + EXPECT_FALSE(a.IsRunning()); + EXPECT_FALSE(a.HasAlarm("test")); + a.Start("test", 100.f, "test"); + EXPECT_TRUE(a.IsRunning()); + EXPECT_TRUE(a.HasAlarm("test")); + EXPECT_FALSE(a.HasAlarm("test2")); + EXPECT_NE(0.f, a.GetRemaining("test")); + EXPECT_EQ(0.f, a.GetRemaining("test2")); + a.Stop("test"); +} diff --git a/src/utils/test/TestAliasShortcutUtils.cpp b/src/utils/test/TestAliasShortcutUtils.cpp new file mode 100644 index 0000000000..15dd76d234 --- /dev/null +++ b/src/utils/test/TestAliasShortcutUtils.cpp @@ -0,0 +1,44 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/AliasShortcutUtils.h" + +#include "gtest/gtest.h" + +TEST(TestAliasShortcutUtils, IsAliasShortcut) +{ + std::string a; +#if defined(TARGET_DARWIN_OSX) + /* TODO: Write test case for OSX */ +#else + EXPECT_FALSE(IsAliasShortcut(a)); +#endif +} + +TEST(TestAliasShortcutUtils, TranslateAliasShortcut) +{ + std::string a; + TranslateAliasShortcut(a); +#if defined(TARGET_DARWIN_OSX) + /* TODO: Write test case for OSX */ +#else + EXPECT_STREQ("", a.c_str()); +#endif +} diff --git a/src/utils/test/TestArchive.cpp b/src/utils/test/TestArchive.cpp new file mode 100644 index 0000000000..9d082dcd86 --- /dev/null +++ b/src/utils/test/TestArchive.cpp @@ -0,0 +1,420 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/Archive.h" +#include "utils/Variant.h" +#include "filesystem/File.h" +#include "utils/StdString.h" + +#include "test/TestUtils.h" + +#include "gtest/gtest.h" + +class TestArchive : public testing::Test +{ +protected: + TestArchive() + { + file = XBMC_CREATETEMPFILE(".ar"); + } + ~TestArchive() + { + EXPECT_TRUE(XBMC_DELETETEMPFILE(file)); + } + XFILE::CFile *file; +}; + +TEST_F(TestArchive, IsStoring) +{ + ASSERT_TRUE(file); + CArchive arstore(file, CArchive::store); + EXPECT_TRUE(arstore.IsStoring()); + EXPECT_FALSE(arstore.IsLoading()); + arstore.Close(); +} + +TEST_F(TestArchive, IsLoading) +{ + ASSERT_TRUE(file); + CArchive arload(file, CArchive::load); + EXPECT_TRUE(arload.IsLoading()); + EXPECT_FALSE(arload.IsStoring()); + arload.Close(); +} + +TEST_F(TestArchive, FloatArchive) +{ + ASSERT_TRUE(file); + float float_ref = 1, float_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << float_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> float_var; + arload.Close(); + + EXPECT_EQ(float_ref, float_var); +} + +TEST_F(TestArchive, DoubleArchive) +{ + ASSERT_TRUE(file); + double double_ref = 2, double_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << double_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> double_var; + arload.Close(); + + EXPECT_EQ(double_ref, double_var); +} + +TEST_F(TestArchive, IntegerArchive) +{ + ASSERT_TRUE(file); + int int_ref = 3, int_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << int_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> int_var; + arload.Close(); + + EXPECT_EQ(int_ref, int_var); +} + +TEST_F(TestArchive, UnsignedIntegerArchive) +{ + ASSERT_TRUE(file); + unsigned int unsigned_int_ref = 4, unsigned_int_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << unsigned_int_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> unsigned_int_var; + arload.Close(); + + EXPECT_EQ(unsigned_int_ref, unsigned_int_var); +} + +TEST_F(TestArchive, Int64tArchive) +{ + ASSERT_TRUE(file); + int64_t int64_t_ref = 5, int64_t_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << int64_t_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> int64_t_var; + arload.Close(); + + EXPECT_EQ(int64_t_ref, int64_t_var); +} + +TEST_F(TestArchive, UInt64tArchive) +{ + ASSERT_TRUE(file); + uint64_t uint64_t_ref = 6, uint64_t_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << uint64_t_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> uint64_t_var; + arload.Close(); + + EXPECT_EQ(uint64_t_ref, uint64_t_var); +} + +TEST_F(TestArchive, BoolArchive) +{ + ASSERT_TRUE(file); + bool bool_ref = true, bool_var = false; + + CArchive arstore(file, CArchive::store); + arstore << bool_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> bool_var; + arload.Close(); + + EXPECT_EQ(bool_ref, bool_var); +} + +TEST_F(TestArchive, CharArchive) +{ + ASSERT_TRUE(file); + char char_ref = 'A', char_var = '\0'; + + CArchive arstore(file, CArchive::store); + arstore << char_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> char_var; + arload.Close(); + + EXPECT_EQ(char_ref, char_var); +} + +TEST_F(TestArchive, CStdStringArchive) +{ + ASSERT_TRUE(file); + CStdStringW CStdStringW_ref = L"test CStdStringW", CStdStringW_var = L""; + + CArchive arstore(file, CArchive::store); + arstore << CStdStringW_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> CStdStringW_var; + arload.Close(); + + EXPECT_STREQ(CStdStringW_ref.c_str(), CStdStringW_var.c_str()); +} + +TEST_F(TestArchive, CStdStringWArchive) +{ + ASSERT_TRUE(file); + CStdString CStdString_ref = "test CStdString", CStdString_var = ""; + + CArchive arstore(file, CArchive::store); + arstore << CStdString_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> CStdString_var; + arload.Close(); + + EXPECT_STREQ(CStdString_ref.c_str(), CStdString_var.c_str()); +} + +TEST_F(TestArchive, SYSTEMTIMEArchive) +{ + ASSERT_TRUE(file); + SYSTEMTIME SYSTEMTIME_ref = { 1, 2, 3, 4, 5, 6, 7, 8 }; + SYSTEMTIME SYSTEMTIME_var = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + CArchive arstore(file, CArchive::store); + arstore << SYSTEMTIME_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> SYSTEMTIME_var; + arload.Close(); + + EXPECT_TRUE(!memcmp(&SYSTEMTIME_ref, &SYSTEMTIME_var, sizeof(SYSTEMTIME))); +} + +TEST_F(TestArchive, CVariantArchive) +{ + ASSERT_TRUE(file); + CVariant CVariant_ref((int)1), CVariant_var; + + CArchive arstore(file, CArchive::store); + arstore << CVariant_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> CVariant_var; + arload.Close(); + + EXPECT_TRUE(CVariant_var.isInteger()); + EXPECT_EQ(1, CVariant_var.asInteger()); +} + +TEST_F(TestArchive, CVariantArchiveString) +{ + ASSERT_TRUE(file); + CVariant CVariant_ref("teststring"), CVariant_var; + + CArchive arstore(file, CArchive::store); + arstore << CVariant_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> CVariant_var; + arload.Close(); + + EXPECT_TRUE(CVariant_var.isString()); + EXPECT_STREQ("teststring", CVariant_var.asString().c_str()); +} + +TEST_F(TestArchive, StringVectorArchive) +{ + ASSERT_TRUE(file); + std::vector<std::string> strArray_ref, strArray_var; + strArray_ref.push_back("test strArray_ref 0"); + strArray_ref.push_back("test strArray_ref 1"); + strArray_ref.push_back("test strArray_ref 2"); + strArray_ref.push_back("test strArray_ref 3"); + + CArchive arstore(file, CArchive::store); + arstore << strArray_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> strArray_var; + arload.Close(); + + EXPECT_STREQ("test strArray_ref 0", strArray_var.at(0).c_str()); + EXPECT_STREQ("test strArray_ref 1", strArray_var.at(1).c_str()); + EXPECT_STREQ("test strArray_ref 2", strArray_var.at(2).c_str()); + EXPECT_STREQ("test strArray_ref 3", strArray_var.at(3).c_str()); +} + +TEST_F(TestArchive, IntegerVectorArchive) +{ + ASSERT_TRUE(file); + std::vector<int> iArray_ref, iArray_var; + iArray_ref.push_back(0); + iArray_ref.push_back(1); + iArray_ref.push_back(2); + iArray_ref.push_back(3); + + CArchive arstore(file, CArchive::store); + arstore << iArray_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + arload >> iArray_var; + arload.Close(); + + EXPECT_EQ(0, iArray_var.at(0)); + EXPECT_EQ(1, iArray_var.at(1)); + EXPECT_EQ(2, iArray_var.at(2)); + EXPECT_EQ(3, iArray_var.at(3)); +} + +TEST_F(TestArchive, MultiTypeArchive) +{ + ASSERT_TRUE(file); + float float_ref = 1, float_var = 0; + double double_ref = 2, double_var = 0; + int int_ref = 3, int_var = 0; + unsigned int unsigned_int_ref = 4, unsigned_int_var = 0; + int64_t int64_t_ref = 5, int64_t_var = 0; + uint64_t uint64_t_ref = 6, uint64_t_var = 0; + bool bool_ref = true, bool_var = false; + char char_ref = 'A', char_var = '\0'; + CStdString CStdString_ref = "test CStdString", CStdString_var = ""; + CStdStringW CStdStringW_ref = L"test CStdStringW", CStdStringW_var = L""; + SYSTEMTIME SYSTEMTIME_ref = { 1, 2, 3, 4, 5, 6, 7, 8 }; + SYSTEMTIME SYSTEMTIME_var = { 0, 0, 0, 0, 0, 0, 0, 0 }; + CVariant CVariant_ref((int)1), CVariant_var; + std::vector<std::string> strArray_ref, strArray_var; + strArray_ref.push_back("test strArray_ref 0"); + strArray_ref.push_back("test strArray_ref 1"); + strArray_ref.push_back("test strArray_ref 2"); + strArray_ref.push_back("test strArray_ref 3"); + std::vector<int> iArray_ref, iArray_var; + iArray_ref.push_back(0); + iArray_ref.push_back(1); + iArray_ref.push_back(2); + iArray_ref.push_back(3); + + CArchive arstore(file, CArchive::store); + EXPECT_TRUE(arstore.IsStoring()); + EXPECT_FALSE(arstore.IsLoading()); + arstore << float_ref; + arstore << double_ref; + arstore << int_ref; + arstore << unsigned_int_ref; + arstore << int64_t_ref; + arstore << uint64_t_ref; + arstore << bool_ref; + arstore << char_ref; + arstore << CStdString_ref; + arstore << CStdStringW_ref; + arstore << SYSTEMTIME_ref; + arstore << CVariant_ref; + arstore << strArray_ref; + arstore << iArray_ref; + arstore.Close(); + + ASSERT_TRUE((file->Seek(0, SEEK_SET) == 0)); + CArchive arload(file, CArchive::load); + EXPECT_TRUE(arload.IsLoading()); + EXPECT_FALSE(arload.IsStoring()); + arload >> float_var; + arload >> double_var; + arload >> int_var; + arload >> unsigned_int_var; + arload >> int64_t_var; + arload >> uint64_t_var; + arload >> bool_var; + arload >> char_var; + arload >> CStdString_var; + arload >> CStdStringW_var; + arload >> SYSTEMTIME_var; + arload >> CVariant_var; + arload >> strArray_var; + arload >> iArray_var; + arload.Close(); + + EXPECT_EQ(float_ref, float_var); + EXPECT_EQ(double_ref, double_var); + EXPECT_EQ(int_ref, int_var); + EXPECT_EQ(unsigned_int_ref, unsigned_int_var); + EXPECT_EQ(int64_t_ref, int64_t_var); + EXPECT_EQ(uint64_t_ref, uint64_t_var); + EXPECT_EQ(bool_ref, bool_var); + EXPECT_EQ(char_ref, char_var); + EXPECT_STREQ(CStdString_ref.c_str(), CStdString_var.c_str()); + EXPECT_STREQ(CStdStringW_ref.c_str(), CStdStringW_var.c_str()); + EXPECT_TRUE(!memcmp(&SYSTEMTIME_ref, &SYSTEMTIME_var, sizeof(SYSTEMTIME))); + EXPECT_TRUE(CVariant_var.isInteger()); + EXPECT_STREQ("test strArray_ref 0", strArray_var.at(0).c_str()); + EXPECT_STREQ("test strArray_ref 1", strArray_var.at(1).c_str()); + EXPECT_STREQ("test strArray_ref 2", strArray_var.at(2).c_str()); + EXPECT_STREQ("test strArray_ref 3", strArray_var.at(3).c_str()); + EXPECT_EQ(0, iArray_var.at(0)); + EXPECT_EQ(1, iArray_var.at(1)); + EXPECT_EQ(2, iArray_var.at(2)); + EXPECT_EQ(3, iArray_var.at(3)); +} diff --git a/src/utils/test/TestAsyncFileCopy.cpp b/src/utils/test/TestAsyncFileCopy.cpp new file mode 100644 index 0000000000..62636d88d3 --- /dev/null +++ b/src/utils/test/TestAsyncFileCopy.cpp @@ -0,0 +1,55 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/AsyncFileCopy.h" +#include "filesystem/File.h" + +#include "test/TestUtils.h" + +#include "gtest/gtest.h" + +static const char refdata[] = "\x01\x02\x03\x04\x05\x06\x07\x08" + "\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" + "\x11\x12\x13\x14\x15\x16\x17\x18" + "\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20" + "\x21\x22\x23\x24\x25\x26\x27\x28" + "\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"; + +TEST(TestAsyncFileCopy, General) +{ + CAsyncFileCopy c; + XFILE::CFile *f1, *f2; + char vardata[sizeof(refdata)]; + + ASSERT_TRUE((f1 = XBMC_CREATETEMPFILE(""))); + ASSERT_TRUE((f2 = XBMC_CREATETEMPFILE(".copy"))); + + EXPECT_EQ((int)sizeof(refdata), f1->Write(refdata, sizeof(refdata))); + f1->Close(); + f2->Close(); + EXPECT_TRUE(c.Copy(XBMC_TEMPFILEPATH(f1), XBMC_TEMPFILEPATH(f2), "")); + EXPECT_TRUE(f2->Open(XBMC_TEMPFILEPATH(f2))); + EXPECT_EQ(sizeof(refdata), f2->Read(vardata, sizeof(refdata))); + f2->Close(); + EXPECT_TRUE(!memcmp(vardata, refdata, sizeof(refdata))); + + EXPECT_TRUE(XBMC_DELETETEMPFILE(f1)); + EXPECT_TRUE(XBMC_DELETETEMPFILE(f2)); +} diff --git a/src/utils/test/TestBase64.cpp b/src/utils/test/TestBase64.cpp new file mode 100644 index 0000000000..0ae643159b --- /dev/null +++ b/src/utils/test/TestBase64.cpp @@ -0,0 +1,89 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/Base64.h" + +#include "gtest/gtest.h" + +static const char refdata[] = "\x01\x02\x03\x04\x05\x06\x07\x08" + "\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" + "\x11\x12\x13\x14\x15\x16\x17\x18" + "\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20" + "\x21\x22\x23\x24\x25\x26\x27\x28" + "\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"; + +static const char refbase64data[] = "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY" + "GRobHB0eHyAhIiMkJSYnKCkqKywtLi8w"; + +TEST(TestBase64, Encode_1) +{ + std::string a; + Base64::Encode(refdata, sizeof(refdata) - 1, a); + EXPECT_STREQ(refbase64data, a.c_str()); +} + +TEST(TestBase64, Encode_2) +{ + std::string a; + a = Base64::Encode(refdata, sizeof(refdata) - 1); + EXPECT_STREQ(refbase64data, a.c_str()); +} + +TEST(TestBase64, Encode_3) +{ + std::string a; + Base64::Encode(refdata, a); + EXPECT_STREQ(refbase64data, a.c_str()); +} + +TEST(TestBase64, Encode_4) +{ + std::string a; + a = Base64::Encode(refdata); + EXPECT_STREQ(refbase64data, a.c_str()); +} + +TEST(TestBase64, Decode_1) +{ + std::string a; + Base64::Decode(refbase64data, sizeof(refbase64data) - 1, a); + EXPECT_STREQ(refdata, a.c_str()); +} + +TEST(TestBase64, Decode_2) +{ + std::string a; + a = Base64::Decode(refbase64data, sizeof(refbase64data) - 1); + EXPECT_STREQ(refdata, a.c_str()); +} + +TEST(TestBase64, Decode_3) +{ + std::string a; + Base64::Decode(refbase64data, a); + EXPECT_STREQ(refdata, a.c_str()); +} + +TEST(TestBase64, Decode_4) +{ + std::string a; + a = Base64::Decode(refbase64data); + EXPECT_STREQ(refdata, a.c_str()); +} diff --git a/src/utils/test/TestBitstreamStats.cpp b/src/utils/test/TestBitstreamStats.cpp new file mode 100644 index 0000000000..1e8d90c89c --- /dev/null +++ b/src/utils/test/TestBitstreamStats.cpp @@ -0,0 +1,70 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "threads/Thread.h" +#include "utils/BitstreamStats.h" + +#include "gtest/gtest.h" + +#define BITS (256 * 8) +#define BYTES (256) + +class CTestBitstreamStatsThread : public CThread +{ +public: + CTestBitstreamStatsThread() : + CThread("TestBitstreamStats"){} + +}; + +TEST(TestBitstreamStats, General) +{ + int i; + BitstreamStats a; + CTestBitstreamStatsThread t; + + i = 0; + a.Start(); + EXPECT_EQ(0.0, a.GetBitrate()); + EXPECT_EQ(0.0, a.GetMaxBitrate()); + EXPECT_EQ(-1.0, a.GetMinBitrate()); + while (i <= BITS) + { + a.AddSampleBits(1); + i++; + t.Sleep(1); + } + a.CalculateBitrate(); + EXPECT_GT(a.GetBitrate(), 0.0); + EXPECT_GT(a.GetMaxBitrate(), 0.0); + EXPECT_GT(a.GetMinBitrate(), 0.0); + + i = 0; + while (i <= BYTES) + { + a.AddSampleBytes(1); + t.Sleep(2); + i++; + } + a.CalculateBitrate(); + EXPECT_GT(a.GetBitrate(), 0.0); + EXPECT_GT(a.GetMaxBitrate(), 0.0); + EXPECT_LE(a.GetMinBitrate(), a.GetMaxBitrate()); +} diff --git a/src/utils/test/TestCPUInfo.cpp b/src/utils/test/TestCPUInfo.cpp new file mode 100644 index 0000000000..b43289933b --- /dev/null +++ b/src/utils/test/TestCPUInfo.cpp @@ -0,0 +1,133 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/CPUInfo.h" +#include "Temperature.h" +#include "settings/AdvancedSettings.h" + +#ifdef TARGET_POSIX +#include "../linux/XTimeUtils.h" +#endif + +#include "gtest/gtest.h" + +TEST(TestCPUInfo, getUsedPercentage) +{ + EXPECT_GE(g_cpuInfo.getUsedPercentage(), 0); +} + +TEST(TestCPUInfo, getCPUCount) +{ + EXPECT_GT(g_cpuInfo.getCPUCount(), 0); +} + +TEST(TestCPUInfo, getCPUFrequency) +{ + EXPECT_GE(g_cpuInfo.getCPUFrequency(), 0.f); +} + +namespace +{ +class TemporarySetting +{ +public: + + TemporarySetting(std::string &setting, const char *newValue) : + m_Setting(setting), + m_OldValue(setting) + { + m_Setting = newValue; + } + + ~TemporarySetting() + { + m_Setting = m_OldValue; + } + +private: + + std::string &m_Setting; + std::string m_OldValue; +}; +} + +TEST(TestCPUInfo, getTemperature) +{ + TemporarySetting command(g_advancedSettings.m_cpuTempCmd, "echo '50 c'"); + CTemperature t; + EXPECT_TRUE(g_cpuInfo.getTemperature(t)); + EXPECT_TRUE(t.IsValid()); +} + +TEST(TestCPUInfo, getCPUModel) +{ + std::string s = g_cpuInfo.getCPUModel(); + EXPECT_STRNE("", s.c_str()); +} + +TEST(TestCPUInfo, getCPUBogoMips) +{ + std::string s = g_cpuInfo.getCPUBogoMips(); + EXPECT_STRNE("", s.c_str()); +} + +TEST(TestCPUInfo, getCPUHardware) +{ + std::string s = g_cpuInfo.getCPUHardware(); + EXPECT_STRNE("", s.c_str()); +} + +TEST(TestCPUInfo, getCPURevision) +{ + std::string s = g_cpuInfo.getCPURevision(); + EXPECT_STRNE("", s.c_str()); +} + +TEST(TestCPUInfo, getCPUSerial) +{ + std::string s = g_cpuInfo.getCPUSerial(); + EXPECT_STRNE("", s.c_str()); +} + +TEST(TestCPUInfo, CoreInfo) +{ + ASSERT_TRUE(g_cpuInfo.HasCoreId(0)); + const CoreInfo c = g_cpuInfo.GetCoreInfo(0); + EXPECT_FALSE(c.m_strModel.empty()); +} + +TEST(TestCPUInfo, GetCoresUsageString) +{ + EXPECT_STRNE("", g_cpuInfo.GetCoresUsageString().c_str()); +} + +TEST(TestCPUInfo, GetCPUFeatures) +{ + unsigned int a = g_cpuInfo.GetCPUFeatures(); + (void)a; +} + +TEST(TestCPUInfo, getUsedPercentage_output) +{ + CCPUInfo c; + Sleep(1); /* TODO: Support option from main that sets this parameter */ + int r = c.getUsedPercentage(); + std::cout << "Percentage: " << testing::PrintToString(r) << std::endl; +} diff --git a/src/utils/test/TestCharsetConverter.cpp b/src/utils/test/TestCharsetConverter.cpp new file mode 100644 index 0000000000..aceb0fbee7 --- /dev/null +++ b/src/utils/test/TestCharsetConverter.cpp @@ -0,0 +1,407 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "settings/Settings.h" +#include "utils/CharsetConverter.h" +#include "utils/StdString.h" +#include "utils/Utf8Utils.h" +#include "system.h" + +#include "gtest/gtest.h" + +static const uint16_t refutf16LE1[] = { 0xff54, 0xff45, 0xff53, 0xff54, + 0xff3f, 0xff55, 0xff54, 0xff46, + 0xff11, 0xff16, 0xff2c, 0xff25, + 0xff54, 0xff4f, 0xff57, 0x0 }; + +static const uint16_t refutf16LE2[] = { 0xff54, 0xff45, 0xff53, 0xff54, + 0xff3f, 0xff55, 0xff54, 0xff46, + 0xff18, 0xff34, 0xff4f, 0xff1a, + 0xff3f, 0xff43, 0xff48, 0xff41, + 0xff52, 0xff53, 0xff45, 0xff54, + 0xff3f, 0xff35, 0xff34, 0xff26, + 0xff0d, 0xff11, 0xff16, 0xff2c, + 0xff25, 0xff0c, 0xff3f, 0xff23, + 0xff33, 0xff54, 0xff44, 0xff33, + 0xff54, 0xff52, 0xff49, 0xff4e, + 0xff47, 0xff11, 0xff16, 0x0 }; + +static const char refutf16LE3[] = "T\377E\377S\377T\377?\377S\377T\377" + "R\377I\377N\377G\377#\377H\377A\377" + "R\377S\377E\377T\377\064\377O\377\065" + "\377T\377F\377\030\377"; + +static const uint16_t refutf16LE4[] = { 0xff54, 0xff45, 0xff53, 0xff54, + 0xff3f, 0xff55, 0xff54, 0xff46, + 0xff11, 0xff16, 0xff2c, 0xff25, + 0xff54, 0xff4f, 0xff35, 0xff34, + 0xff26, 0xff18, 0x0 }; + +static const uint32_t refutf32LE1[] = { 0xff54, 0xff45, 0xff53, 0xff54, + 0xff3f, 0xff55, 0xff54, 0xff46, + 0xff18, 0xff34, 0xff4f, 0xff1a, + 0xff3f, 0xff43, 0xff48, 0xff41, + 0xff52, 0xff53, 0xff45, 0xff54, + 0xff3f, 0xff35, 0xff34, 0xff26, + 0xff0d, 0xff13, 0xff12, 0xff2c, + 0xff25, 0xff0c, 0xff3f, 0xff23, + 0xff33, 0xff54, 0xff44, 0xff33, + 0xff54, 0xff52, 0xff49, 0xff4e, + 0xff47, 0xff13, 0xff12, 0xff3f, +#ifdef TARGET_DARWIN + 0x0 }; +#else + 0x1f42d, 0x1f42e, 0x0 }; +#endif + +static const uint16_t refutf16BE[] = { 0x54ff, 0x45ff, 0x53ff, 0x54ff, + 0x3fff, 0x55ff, 0x54ff, 0x46ff, + 0x11ff, 0x16ff, 0x22ff, 0x25ff, + 0x54ff, 0x4fff, 0x35ff, 0x34ff, + 0x26ff, 0x18ff, 0x0}; + +static const uint16_t refucs2[] = { 0xff54, 0xff45, 0xff53, 0xff54, + 0xff3f, 0xff55, 0xff43, 0xff53, + 0xff12, 0xff54, 0xff4f, 0xff35, + 0xff34, 0xff26, 0xff18, 0x0 }; + +class TestCharsetConverter : public testing::Test +{ +protected: + TestCharsetConverter() + { + /* Add default settings for locale. + * Settings here are taken from CGUISettings::Initialize() + */ + /* TODO + CSettingsCategory *loc = CSettings::Get().AddCategory(7, "locale", 14090); + CSettings::Get().AddString(loc, "locale.language",248,"english", + SPIN_CONTROL_TEXT); + CSettings::Get().AddString(loc, "locale.country", 20026, "USA", + SPIN_CONTROL_TEXT); + CSettings::Get().AddString(loc, "locale.charset", 14091, "DEFAULT", + SPIN_CONTROL_TEXT); // charset is set by the + // language file + + // Add default settings for subtitles + CSettingsCategory *sub = CSettings::Get().AddCategory(5, "subtitles", 287); + CSettings::Get().AddString(sub, "subtitles.charset", 735, "DEFAULT", + SPIN_CONTROL_TEXT); + */ + + g_charsetConverter.reset(); + g_charsetConverter.clear(); + } + + ~TestCharsetConverter() + { + CSettings::Get().Unload(); + } + + CStdStringA refstra1, refstra2, varstra1; + CStdStringW refstrw1, varstrw1; + CStdString16 refstr16_1, varstr16_1; + CStdString32 refstr32_1, varstr32_1; + CStdString refstr1; +}; + +TEST_F(TestCharsetConverter, utf8ToW) +{ + refstra1 = "test utf8ToW"; + refstrw1 = L"test utf8ToW"; + varstrw1.clear(); + g_charsetConverter.utf8ToW(refstra1, varstrw1, true, false, NULL); + EXPECT_STREQ(refstrw1.c_str(), varstrw1.c_str()); +} + +TEST_F(TestCharsetConverter, utf16LEtoW) +{ + refstrw1 = L"test_utf16LEtow"; + /* TODO: Should be able to use '=' operator instead of assign() */ + refstr16_1.assign(refutf16LE1); + varstrw1.clear(); + g_charsetConverter.utf16LEtoW(refstr16_1, varstrw1); + EXPECT_STREQ(refstrw1.c_str(), varstrw1.c_str()); +} + +TEST_F(TestCharsetConverter, subtitleCharsetToUtf8) +{ + refstra1 = "test subtitleCharsetToW"; + varstra1.clear(); + g_charsetConverter.subtitleCharsetToUtf8(refstra1, varstra1); + + /* Assign refstra1 to refstrw1 so that we can compare */ + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, utf8ToStringCharset_1) +{ + refstra1 = "test utf8ToStringCharset"; + varstra1.clear(); + g_charsetConverter.utf8ToStringCharset(refstra1, varstra1); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, utf8ToStringCharset_2) +{ + refstra1 = "test utf8ToStringCharset"; + varstra1 = "test utf8ToStringCharset"; + g_charsetConverter.utf8ToStringCharset(varstra1); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, utf8ToSystem) +{ + refstra1 = "test utf8ToSystem"; + varstra1 = "test utf8ToSystem"; + g_charsetConverter.utf8ToSystem(varstra1); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, utf8To_ASCII) +{ + refstra1 = "test utf8To: charset ASCII, CStdStringA"; + varstra1.clear(); + g_charsetConverter.utf8To("ASCII", refstra1, varstra1); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, utf8To_UTF16LE) +{ + refstra1 = "test_utf8To:_charset_UTF-16LE,_" + "CStdString16"; + refstr16_1.assign(refutf16LE2); + varstr16_1.clear(); + g_charsetConverter.utf8To("UTF-16LE", refstra1, varstr16_1); + EXPECT_TRUE(!memcmp(refstr16_1.c_str(), varstr16_1.c_str(), + refstr16_1.length() * sizeof(uint16_t))); +} + +TEST_F(TestCharsetConverter, utf8To_UTF32LE) +{ + refstra1 = "test_utf8To:_charset_UTF-32LE,_" +#ifdef TARGET_DARWIN +/* OSX has it's own 'special' utf-8 charset which we use (see UTF8_SOURCE in CharsetConverter.cpp) + which is basically NFD (decomposed) utf-8. The trouble is, it fails on the COW FACE and MOUSE FACE + characters for some reason (possibly anything over 0x100000, or maybe there's a decomposed form of these + that I couldn't find???) If UTF8_SOURCE is switched to UTF-8 then this test would pass as-is, but then + some filenames stored in utf8-mac wouldn't display correctly in the UI. */ + "CStdString32_"; +#else + "CStdString32_🐭🐮"; +#endif + refstr32_1.assign(refutf32LE1); + varstr32_1.clear(); + g_charsetConverter.utf8To("UTF-32LE", refstra1, varstr32_1); + EXPECT_TRUE(!memcmp(refstr32_1.c_str(), varstr32_1.c_str(), + sizeof(refutf32LE1))); +} + +TEST_F(TestCharsetConverter, stringCharsetToUtf8) +{ + refstra1 = "test_stringCharsetToUtf8"; + varstra1.clear(); + g_charsetConverter.ToUtf8("UTF-16LE", refutf16LE3, varstra1); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, isValidUtf8_1) +{ + varstra1.clear(); + g_charsetConverter.ToUtf8("UTF-16LE", refutf16LE3, varstra1); + EXPECT_TRUE(CUtf8Utils::isValidUtf8(varstra1.c_str())); +} + +TEST_F(TestCharsetConverter, isValidUtf8_2) +{ + refstr1 = refutf16LE3; + EXPECT_FALSE(CUtf8Utils::isValidUtf8(refstr1)); +} + +TEST_F(TestCharsetConverter, isValidUtf8_3) +{ + varstra1.clear(); + g_charsetConverter.ToUtf8("UTF-16LE", refutf16LE3, varstra1); + EXPECT_TRUE(CUtf8Utils::isValidUtf8(varstra1.c_str())); +} + +TEST_F(TestCharsetConverter, isValidUtf8_4) +{ + EXPECT_FALSE(CUtf8Utils::isValidUtf8(refutf16LE3)); +} + +/* TODO: Resolve correct input/output for this function */ +// TEST_F(TestCharsetConverter, ucs2CharsetToStringCharset) +// { +// void ucs2CharsetToStringCharset(const CStdStringW& strSource, +// CStdStringA& strDest, bool swap = false); +// } + +TEST_F(TestCharsetConverter, wToUTF8) +{ + refstrw1 = L"test_wToUTF8"; + refstra1 = "test_wToUTF8"; + varstra1.clear(); + g_charsetConverter.wToUTF8(refstrw1, varstra1); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, utf16BEtoUTF8) +{ + refstr16_1.assign(refutf16BE); + refstra1 = "test_utf16BEtoUTF8"; + varstra1.clear(); + g_charsetConverter.utf16BEtoUTF8(refstr16_1, varstra1); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, utf16LEtoUTF8) +{ + refstr16_1.assign(refutf16LE4); + refstra1 = "test_utf16LEtoUTF8"; + varstra1.clear(); + g_charsetConverter.utf16LEtoUTF8(refstr16_1, varstra1); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, ucs2ToUTF8) +{ + refstr16_1.assign(refucs2); + refstra1 = "test_ucs2toUTF8"; + varstra1.clear(); + g_charsetConverter.ucs2ToUTF8(refstr16_1, varstra1); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, utf8logicalToVisualBiDi) +{ + refstra1 = "test_utf8logicalToVisualBiDi"; + refstra2 = "test_utf8logicalToVisualBiDi"; + varstra1.clear(); + g_charsetConverter.utf8logicalToVisualBiDi(refstra1, varstra1); + EXPECT_STREQ(refstra2.c_str(), varstra1.c_str()); +} + +/* TODO: Resolve correct input/output for this function */ +// TEST_F(TestCharsetConverter, utf32ToStringCharset) +// { +// void utf32ToStringCharset(const unsigned long* strSource, CStdStringA& strDest); +// } + +TEST_F(TestCharsetConverter, getCharsetLabels) +{ + std::vector<CStdString> reflabels; + reflabels.push_back("Western Europe (ISO)"); + reflabels.push_back("Central Europe (ISO)"); + reflabels.push_back("South Europe (ISO)"); + reflabels.push_back("Baltic (ISO)"); + reflabels.push_back("Cyrillic (ISO)"); + reflabels.push_back("Arabic (ISO)"); + reflabels.push_back("Greek (ISO)"); + reflabels.push_back("Hebrew (ISO)"); + reflabels.push_back("Turkish (ISO)"); + reflabels.push_back("Central Europe (Windows)"); + reflabels.push_back("Cyrillic (Windows)"); + reflabels.push_back("Western Europe (Windows)"); + reflabels.push_back("Greek (Windows)"); + reflabels.push_back("Turkish (Windows)"); + reflabels.push_back("Hebrew (Windows)"); + reflabels.push_back("Arabic (Windows)"); + reflabels.push_back("Baltic (Windows)"); + reflabels.push_back("Vietnamesse (Windows)"); + reflabels.push_back("Thai (Windows)"); + reflabels.push_back("Chinese Traditional (Big5)"); + reflabels.push_back("Chinese Simplified (GBK)"); + reflabels.push_back("Japanese (Shift-JIS)"); + reflabels.push_back("Korean"); + reflabels.push_back("Hong Kong (Big5-HKSCS)"); + + std::vector<std::string> varlabels = g_charsetConverter.getCharsetLabels(); + ASSERT_EQ(reflabels.size(), varlabels.size()); + + std::vector<std::string>::iterator it; + for (it = varlabels.begin(); it < varlabels.end(); ++it) + { + EXPECT_STREQ((reflabels.at(it - varlabels.begin())).c_str(), (*it).c_str()); + } +} + +TEST_F(TestCharsetConverter, getCharsetLabelByName) +{ + CStdString varstr = + g_charsetConverter.getCharsetLabelByName("ISO-8859-1"); + EXPECT_STREQ("Western Europe (ISO)", varstr.c_str()); + varstr.clear(); + varstr = g_charsetConverter.getCharsetLabelByName("Bogus"); + EXPECT_STREQ("", varstr.c_str()); +} + +TEST_F(TestCharsetConverter, getCharsetNameByLabel) +{ + CStdString varstr = + g_charsetConverter.getCharsetNameByLabel("Western Europe (ISO)"); + EXPECT_STREQ("ISO-8859-1", varstr.c_str()); + varstr.clear(); + varstr = g_charsetConverter.getCharsetNameByLabel("Bogus"); + EXPECT_STREQ("", varstr.c_str()); +} + +TEST_F(TestCharsetConverter, unknownToUTF8_1) +{ + refstra1 = "test_unknownToUTF8"; + varstra1 = "test_unknownToUTF8"; + g_charsetConverter.unknownToUTF8(varstra1); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, unknownToUTF8_2) +{ + refstra1 = "test_unknownToUTF8"; + varstra1.clear(); + g_charsetConverter.unknownToUTF8(refstra1, varstra1); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} + +TEST_F(TestCharsetConverter, toW) +{ + refstra1 = "test_toW:_charset_UTF-16LE"; + refstrw1 = L"\xBDEF\xEF94\x85BD\xBDEF\xEF93\x94BD\xBCEF\xEFBF" + L"\x94BD\xBDEF\xEF8F\xB7BC\xBCEF\xEF9A\xBFBC\xBDEF" + L"\xEF83\x88BD\xBDEF\xEF81\x92BD\xBDEF\xEF93\x85BD" + L"\xBDEF\xEF94\xBFBC\xBCEF\xEFB5\xB4BC\xBCEF\xEFA6" + L"\x8DBC\xBCEF\xEF91\x96BC\xBCEF\xEFAC\xA5BC"; + varstrw1.clear(); + g_charsetConverter.toW(refstra1, varstrw1, "UTF-16LE"); + EXPECT_STREQ(refstrw1.c_str(), varstrw1.c_str()); +} + +TEST_F(TestCharsetConverter, fromW) +{ + refstrw1 = L"\xBDEF\xEF94\x85BD\xBDEF\xEF93\x94BD\xBCEF\xEFBF" + L"\x86BD\xBDEF\xEF92\x8FBD\xBDEF\xEF8D\xB7BC\xBCEF" + L"\xEF9A\xBFBC\xBDEF\xEF83\x88BD\xBDEF\xEF81\x92BD" + L"\xBDEF\xEF93\x85BD\xBDEF\xEF94\xBFBC\xBCEF\xEFB5" + L"\xB4BC\xBCEF\xEFA6\x8DBC\xBCEF\xEF91\x96BC\xBCEF" + L"\xEFAC\xA5BC"; + refstra1 = "test_fromW:_charset_UTF-16LE"; + varstra1.clear(); + g_charsetConverter.fromW(refstrw1, varstra1, "UTF-16LE"); + EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); +} diff --git a/src/utils/test/TestCrc32.cpp b/src/utils/test/TestCrc32.cpp new file mode 100644 index 0000000000..4396a94771 --- /dev/null +++ b/src/utils/test/TestCrc32.cpp @@ -0,0 +1,67 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/Crc32.h" + +#include "gtest/gtest.h" + +static const char refdata[] = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "01234567890!@#$%^&*()"; + +TEST(TestCrc32, Compute_1) +{ + Crc32 a; + uint32_t varcrc; + a.Compute(refdata, sizeof(refdata) - 1); + varcrc = a; + EXPECT_EQ(0xa4eb60e3, varcrc); +} + +TEST(TestCrc32, Compute_2) +{ + Crc32 a; + uint32_t varcrc; + std::string s = refdata; + a.Compute(s); + varcrc = a; + EXPECT_EQ(0xa4eb60e3, varcrc); +} + +TEST(TestCrc32, ComputeFromLowerCase) +{ + Crc32 a; + uint32_t varcrc; + std::string s = refdata; + a.ComputeFromLowerCase(s); + varcrc = a; + EXPECT_EQ((uint32_t)0x7f045b3e, varcrc); +} + +TEST(TestCrc32, Reset) +{ + Crc32 a; + uint32_t varcrc; + std::string s = refdata; + a.ComputeFromLowerCase(s); + a.Reset(); + varcrc = a; + EXPECT_EQ(0xffffffff, varcrc); +} diff --git a/src/utils/test/TestCryptThreading.cpp b/src/utils/test/TestCryptThreading.cpp new file mode 100644 index 0000000000..75f784d8cc --- /dev/null +++ b/src/utils/test/TestCryptThreading.cpp @@ -0,0 +1,29 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/CryptThreading.h" + +#include "gtest/gtest.h" + +TEST(TestCryptThreadingInitializer, General) +{ + std::cout << "g_cryptThreadingInitializer address: " << + testing::PrintToString(&g_cryptThreadingInitializer) << "\n"; +} diff --git a/src/utils/test/TestDatabaseUtils.cpp b/src/utils/test/TestDatabaseUtils.cpp new file mode 100644 index 0000000000..5d846772f3 --- /dev/null +++ b/src/utils/test/TestDatabaseUtils.cpp @@ -0,0 +1,1312 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/DatabaseUtils.h" +#include "video/VideoDatabase.h" +#include "music/MusicDatabase.h" +#include "dbwrappers/qry_dat.h" +#include "utils/Variant.h" +#include "utils/StringUtils.h" + +#include "gtest/gtest.h" + +class TestDatabaseUtilsHelper +{ +public: + TestDatabaseUtilsHelper() + { + album_idAlbum = CMusicDatabase::album_idAlbum; + album_strAlbum = CMusicDatabase::album_strAlbum; + album_strArtists = CMusicDatabase::album_strArtists; + album_strGenres = CMusicDatabase::album_strGenres; + album_iYear = CMusicDatabase::album_iYear; + album_strMoods = CMusicDatabase::album_strMoods; + album_strStyles = CMusicDatabase::album_strStyles; + album_strThemes = CMusicDatabase::album_strThemes; + album_strReview = CMusicDatabase::album_strReview; + album_strLabel = CMusicDatabase::album_strLabel; + album_strType = CMusicDatabase::album_strType; + album_iRating = CMusicDatabase::album_iRating; + + song_idSong = CMusicDatabase::song_idSong; + song_strTitle = CMusicDatabase::song_strTitle; + song_iTrack = CMusicDatabase::song_iTrack; + song_iDuration = CMusicDatabase::song_iDuration; + song_iYear = CMusicDatabase::song_iYear; + song_strFileName = CMusicDatabase::song_strFileName; + song_iTimesPlayed = CMusicDatabase::song_iTimesPlayed; + song_iStartOffset = CMusicDatabase::song_iStartOffset; + song_iEndOffset = CMusicDatabase::song_iEndOffset; + song_lastplayed = CMusicDatabase::song_lastplayed; + song_rating = CMusicDatabase::song_rating; + song_comment = CMusicDatabase::song_comment; + song_strAlbum = CMusicDatabase::song_strAlbum; + song_strPath = CMusicDatabase::song_strPath; + song_strGenres = CMusicDatabase::song_strGenres; + song_strArtists = CMusicDatabase::song_strArtists; + } + + int album_idAlbum; + int album_strAlbum; + int album_strArtists; + int album_strGenres; + int album_iYear; + int album_strMoods; + int album_strStyles; + int album_strThemes; + int album_strReview; + int album_strLabel; + int album_strType; + int album_iRating; + + int song_idSong; + int song_strTitle; + int song_iTrack; + int song_iDuration; + int song_iYear; + int song_strFileName; + int song_iTimesPlayed; + int song_iStartOffset; + int song_iEndOffset; + int song_lastplayed; + int song_rating; + int song_comment; + int song_strAlbum; + int song_strPath; + int song_strGenres; + int song_strArtists; +}; + +TEST(TestDatabaseUtils, GetField_None) +{ + std::string refstr, varstr; + + refstr = ""; + varstr = DatabaseUtils::GetField(FieldNone, MediaTypeNone, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + varstr = DatabaseUtils::GetField(FieldNone, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestDatabaseUtils, GetField_MediaTypeAlbum) +{ + std::string refstr, varstr; + + refstr = "albumview.idAlbum"; + varstr = DatabaseUtils::GetField(FieldId, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strAlbum"; + varstr = DatabaseUtils::GetField(FieldAlbum, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strArtists"; + varstr = DatabaseUtils::GetField(FieldArtist, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strArtists"; + varstr = DatabaseUtils::GetField(FieldAlbumArtist, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strGenre"; + varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.iYear"; + varstr = DatabaseUtils::GetField(FieldYear, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strMoods"; + varstr = DatabaseUtils::GetField(FieldMoods, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strStyles"; + varstr = DatabaseUtils::GetField(FieldStyles, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strThemes"; + varstr = DatabaseUtils::GetField(FieldThemes, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strReview"; + varstr = DatabaseUtils::GetField(FieldReview, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strLabel"; + varstr = DatabaseUtils::GetField(FieldMusicLabel, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strType"; + varstr = DatabaseUtils::GetField(FieldAlbumType, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.iRating"; + varstr = DatabaseUtils::GetField(FieldRating, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.idalbum"; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeAlbum, + DatabaseQueryPartOrderBy); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = ""; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strAlbum"; + varstr = DatabaseUtils::GetField(FieldAlbum, MediaTypeAlbum, + DatabaseQueryPartWhere); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + varstr = DatabaseUtils::GetField(FieldAlbum, MediaTypeAlbum, + DatabaseQueryPartOrderBy); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestDatabaseUtils, GetField_MediaTypeSong) +{ + std::string refstr, varstr; + + refstr = "songview.idSong"; + varstr = DatabaseUtils::GetField(FieldId, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.strTitle"; + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.iTrack"; + varstr = DatabaseUtils::GetField(FieldTrackNumber, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.iDuration"; + varstr = DatabaseUtils::GetField(FieldTime, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.iYear"; + varstr = DatabaseUtils::GetField(FieldYear, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.strFilename"; + varstr = DatabaseUtils::GetField(FieldFilename, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.iTimesPlayed"; + varstr = DatabaseUtils::GetField(FieldPlaycount, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.iStartOffset"; + varstr = DatabaseUtils::GetField(FieldStartOffset, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.iEndOffset"; + varstr = DatabaseUtils::GetField(FieldEndOffset, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.lastPlayed"; + varstr = DatabaseUtils::GetField(FieldLastPlayed, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.rating"; + varstr = DatabaseUtils::GetField(FieldRating, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.comment"; + varstr = DatabaseUtils::GetField(FieldComment, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.strAlbum"; + varstr = DatabaseUtils::GetField(FieldAlbum, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.strArtists"; + varstr = DatabaseUtils::GetField(FieldArtist, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.strArtists"; + varstr = DatabaseUtils::GetField(FieldAlbumArtist, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.strGenre"; + varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.idSong"; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeSong, + DatabaseQueryPartOrderBy); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = ""; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeSong, + DatabaseQueryPartWhere); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeSong, + DatabaseQueryPartOrderBy); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestDatabaseUtils, GetField_MediaTypeMusicVideo) +{ + CStdString refstr, varstr; + + refstr = "musicvideoview.idMVideo"; + varstr = DatabaseUtils::GetField(FieldId, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_TITLE); + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_RUNTIME); + varstr = DatabaseUtils::GetField(FieldTime, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_DIRECTOR); + varstr = DatabaseUtils::GetField(FieldDirector, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_STUDIOS); + varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_YEAR); + varstr = DatabaseUtils::GetField(FieldYear, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_PLOT); + varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_ALBUM); + varstr = DatabaseUtils::GetField(FieldAlbum, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_ARTIST); + varstr = DatabaseUtils::GetField(FieldArtist, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_GENRE); + varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideoview.c%02d",VIDEODB_ID_MUSICVIDEO_TRACK); + varstr = DatabaseUtils::GetField(FieldTrackNumber, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideoview.strFilename"; + varstr = DatabaseUtils::GetField(FieldFilename, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideoview.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideoview.playCount"; + varstr = DatabaseUtils::GetField(FieldPlaycount, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideoview.lastPlayed"; + varstr = DatabaseUtils::GetField(FieldLastPlayed, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideoview.dateAdded"; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = ""; + varstr = DatabaseUtils::GetField(FieldVideoResolution, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideoview.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMusicVideo, + DatabaseQueryPartWhere); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideoview.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMusicVideo, + DatabaseQueryPartOrderBy); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestDatabaseUtils, GetField_MediaTypeMovie) +{ + CStdString refstr, varstr; + + refstr = "movieview.idMovie"; + varstr = DatabaseUtils::GetField(FieldId, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_TITLE); + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("CASE WHEN length(movieview.c%02d) > 0 THEN movieview.c%02d " + "ELSE movieview.c%02d END", VIDEODB_ID_SORTTITLE, + VIDEODB_ID_SORTTITLE, VIDEODB_ID_TITLE, VIDEODB_ID_TITLE); + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeMovie, + DatabaseQueryPartOrderBy); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_PLOT); + varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_PLOTOUTLINE); + varstr = DatabaseUtils::GetField(FieldPlotOutline, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_TAGLINE); + varstr = DatabaseUtils::GetField(FieldTagline, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_VOTES); + varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_RATING); + varstr = DatabaseUtils::GetField(FieldRating, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("CAST(movieview.c%02d as DECIMAL(5,3))", VIDEODB_ID_RATING); + varstr = DatabaseUtils::GetField(FieldRating, MediaTypeMovie, + DatabaseQueryPartOrderBy); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_CREDITS); + varstr = DatabaseUtils::GetField(FieldWriter, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_YEAR); + varstr = DatabaseUtils::GetField(FieldYear, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_SORTTITLE); + varstr = DatabaseUtils::GetField(FieldSortTitle, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_RUNTIME); + varstr = DatabaseUtils::GetField(FieldTime, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_MPAA); + varstr = DatabaseUtils::GetField(FieldMPAA, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_TOP250); + varstr = DatabaseUtils::GetField(FieldTop250, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_GENRE); + varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_DIRECTOR); + varstr = DatabaseUtils::GetField(FieldDirector, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_STUDIOS); + varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_TRAILER); + varstr = DatabaseUtils::GetField(FieldTrailer, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movieview.c%02d", VIDEODB_ID_COUNTRY); + varstr = DatabaseUtils::GetField(FieldCountry, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movieview.strFilename"; + varstr = DatabaseUtils::GetField(FieldFilename, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movieview.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movieview.playCount"; + varstr = DatabaseUtils::GetField(FieldPlaycount, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movieview.lastPlayed"; + varstr = DatabaseUtils::GetField(FieldLastPlayed, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movieview.dateAdded"; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = ""; + varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestDatabaseUtils, GetField_MediaTypeTvShow) +{ + CStdString refstr, varstr; + + refstr = "tvshowview.idShow"; + varstr = DatabaseUtils::GetField(FieldId, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("CASE WHEN length(tvshowview.c%02d) > 0 THEN tvshowview.c%02d " + "ELSE tvshowview.c%02d END", VIDEODB_ID_TV_SORTTITLE, + VIDEODB_ID_TV_SORTTITLE, VIDEODB_ID_TV_TITLE); + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeTvShow, + DatabaseQueryPartOrderBy); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_TITLE); + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_PLOT); + varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_STATUS); + varstr = DatabaseUtils::GetField(FieldTvShowStatus, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_VOTES); + varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_RATING); + varstr = DatabaseUtils::GetField(FieldRating, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_PREMIERED); + varstr = DatabaseUtils::GetField(FieldYear, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_GENRE); + varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_MPAA); + varstr = DatabaseUtils::GetField(FieldMPAA, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_STUDIOS); + varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshowview.c%02d", VIDEODB_ID_TV_SORTTITLE); + varstr = DatabaseUtils::GetField(FieldSortTitle, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshowview.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshowview.dateAdded"; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshowview.totalSeasons"; + varstr = DatabaseUtils::GetField(FieldSeason, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshowview.totalCount"; + varstr = DatabaseUtils::GetField(FieldNumberOfEpisodes, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshowview.watchedcount"; + varstr = DatabaseUtils::GetField(FieldNumberOfWatchedEpisodes, + MediaTypeTvShow, DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = ""; + varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestDatabaseUtils, GetField_MediaTypeEpisode) +{ + CStdString refstr, varstr; + + refstr = "episodeview.idEpisode"; + varstr = DatabaseUtils::GetField(FieldId, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_TITLE); + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_PLOT); + varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_VOTES); + varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_RATING); + varstr = DatabaseUtils::GetField(FieldRating, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_CREDITS); + varstr = DatabaseUtils::GetField(FieldWriter, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_AIRED); + varstr = DatabaseUtils::GetField(FieldAirDate, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_RUNTIME); + varstr = DatabaseUtils::GetField(FieldTime, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_DIRECTOR); + varstr = DatabaseUtils::GetField(FieldDirector, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_SEASON); + varstr = DatabaseUtils::GetField(FieldSeason, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episodeview.c%02d", VIDEODB_ID_EPISODE_EPISODE); + varstr = DatabaseUtils::GetField(FieldEpisodeNumber, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episodeview.strFilename"; + varstr = DatabaseUtils::GetField(FieldFilename, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episodeview.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episodeview.playCount"; + varstr = DatabaseUtils::GetField(FieldPlaycount, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episodeview.lastPlayed"; + varstr = DatabaseUtils::GetField(FieldLastPlayed, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episodeview.dateAdded"; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episodeview.strTitle"; + varstr = DatabaseUtils::GetField(FieldTvShowTitle, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episodeview.premiered"; + varstr = DatabaseUtils::GetField(FieldYear, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episodeview.mpaa"; + varstr = DatabaseUtils::GetField(FieldMPAA, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episodeview.strStudio"; + varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = ""; + varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestDatabaseUtils, GetField_FieldRandom) +{ + std::string refstr, varstr; + + refstr = ""; + varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = ""; + varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeEpisode, + DatabaseQueryPartWhere); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "RANDOM()"; + varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeEpisode, + DatabaseQueryPartOrderBy); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestDatabaseUtils, GetFieldIndex_None) +{ + int refindex, varindex; + + refindex = -1; + varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeNone); + EXPECT_EQ(refindex, varindex); + + varindex = DatabaseUtils::GetFieldIndex(FieldNone, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); +} + +/* TODO: Should enums in CMusicDatabase be made public instead? */ +TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeAlbum) +{ + int refindex, varindex; + TestDatabaseUtilsHelper a; + + refindex = a.album_idAlbum; + varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_strAlbum; + varindex = DatabaseUtils::GetFieldIndex(FieldAlbum, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_strArtists; + varindex = DatabaseUtils::GetFieldIndex(FieldArtist, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_strArtists; + varindex = DatabaseUtils::GetFieldIndex(FieldAlbumArtist, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_strGenres; + varindex = DatabaseUtils::GetFieldIndex(FieldGenre, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_iYear; + varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_strMoods; + varindex = DatabaseUtils::GetFieldIndex(FieldMoods, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_strStyles; + varindex = DatabaseUtils::GetFieldIndex(FieldStyles, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_strThemes; + varindex = DatabaseUtils::GetFieldIndex(FieldThemes, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_strReview; + varindex = DatabaseUtils::GetFieldIndex(FieldReview, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_strLabel; + varindex = DatabaseUtils::GetFieldIndex(FieldMusicLabel, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_strType; + varindex = DatabaseUtils::GetFieldIndex(FieldAlbumType, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_iRating; + varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = -1; + varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); +} + +TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeSong) +{ + int refindex, varindex; + TestDatabaseUtilsHelper a; + + refindex = a.song_idSong; + varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_strTitle; + varindex = DatabaseUtils::GetFieldIndex(FieldTitle, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_iTrack; + varindex = DatabaseUtils::GetFieldIndex(FieldTrackNumber, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_iDuration; + varindex = DatabaseUtils::GetFieldIndex(FieldTime, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_iYear; + varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_strFileName; + varindex = DatabaseUtils::GetFieldIndex(FieldFilename, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_iTimesPlayed; + varindex = DatabaseUtils::GetFieldIndex(FieldPlaycount, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_iStartOffset; + varindex = DatabaseUtils::GetFieldIndex(FieldStartOffset, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_iEndOffset; + varindex = DatabaseUtils::GetFieldIndex(FieldEndOffset, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_lastplayed; + varindex = DatabaseUtils::GetFieldIndex(FieldLastPlayed, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_rating; + varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_comment; + varindex = DatabaseUtils::GetFieldIndex(FieldComment, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_strAlbum; + varindex = DatabaseUtils::GetFieldIndex(FieldAlbum, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_strPath; + varindex = DatabaseUtils::GetFieldIndex(FieldPath, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_strArtists; + varindex = DatabaseUtils::GetFieldIndex(FieldArtist, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_strGenres; + varindex = DatabaseUtils::GetFieldIndex(FieldGenre, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = -1; + varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeSong); + EXPECT_EQ(refindex, varindex); +} + +TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeMusicVideo) +{ + int refindex, varindex; + + refindex = 0; + varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_MUSICVIDEO_TITLE + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldTitle, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_MUSICVIDEO_RUNTIME + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldTime, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_MUSICVIDEO_DIRECTOR + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldDirector, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_MUSICVIDEO_STUDIOS + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldStudio, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_MUSICVIDEO_YEAR + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_MUSICVIDEO_PLOT + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldPlot, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_MUSICVIDEO_ALBUM + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldAlbum, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_MUSICVIDEO_ARTIST + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldArtist, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_MUSICVIDEO_GENRE + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldGenre, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_MUSICVIDEO_TRACK + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldTrackNumber, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MUSICVIDEO_FILE; + varindex = DatabaseUtils::GetFieldIndex(FieldFilename, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MUSICVIDEO_PATH; + varindex = DatabaseUtils::GetFieldIndex(FieldPath, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MUSICVIDEO_PLAYCOUNT; + varindex = DatabaseUtils::GetFieldIndex(FieldPlaycount, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MUSICVIDEO_LASTPLAYED; + varindex = DatabaseUtils::GetFieldIndex(FieldLastPlayed, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MUSICVIDEO_DATEADDED; + varindex = DatabaseUtils::GetFieldIndex(FieldDateAdded, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = -1; + varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); +} + +TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeMovie) +{ + int refindex, varindex; + + refindex = 0; + varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TITLE + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldTitle, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_SORTTITLE + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldSortTitle, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_PLOT + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldPlot, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_PLOTOUTLINE + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldPlotOutline, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TAGLINE + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldTagline, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_VOTES + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldVotes, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_RATING + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_CREDITS + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldWriter, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_YEAR + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_RUNTIME + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldTime, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_MPAA + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldMPAA, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TOP250 + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldTop250, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_GENRE + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldGenre, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_DIRECTOR + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldDirector, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_STUDIOS + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldStudio, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TRAILER + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldTrailer, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_COUNTRY + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldCountry, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MOVIE_FILE + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldFilename, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MOVIE_PATH; + varindex = DatabaseUtils::GetFieldIndex(FieldPath, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MOVIE_PLAYCOUNT; + varindex = DatabaseUtils::GetFieldIndex(FieldPlaycount, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MOVIE_LASTPLAYED; + varindex = DatabaseUtils::GetFieldIndex(FieldLastPlayed, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MOVIE_DATEADDED; + varindex = DatabaseUtils::GetFieldIndex(FieldDateAdded, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = -1; + varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); +} + +TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeTvShow) +{ + int refindex, varindex; + + refindex = 0; + varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TV_TITLE + 1; + varindex = DatabaseUtils::GetFieldIndex(FieldTitle, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TV_SORTTITLE + 1; + varindex = DatabaseUtils::GetFieldIndex(FieldSortTitle, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TV_PLOT + 1; + varindex = DatabaseUtils::GetFieldIndex(FieldPlot, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TV_STATUS + 1; + varindex = DatabaseUtils::GetFieldIndex(FieldTvShowStatus, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TV_VOTES + 1; + varindex = DatabaseUtils::GetFieldIndex(FieldVotes, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TV_RATING + 1; + varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TV_PREMIERED + 1; + varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TV_GENRE + 1; + varindex = DatabaseUtils::GetFieldIndex(FieldGenre, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TV_MPAA + 1; + varindex = DatabaseUtils::GetFieldIndex(FieldMPAA, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_TV_STUDIOS + 1; + varindex = DatabaseUtils::GetFieldIndex(FieldStudio, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_TVSHOW_PATH; + varindex = DatabaseUtils::GetFieldIndex(FieldPath, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_TVSHOW_DATEADDED; + varindex = DatabaseUtils::GetFieldIndex(FieldDateAdded, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_TVSHOW_NUM_EPISODES; + varindex = DatabaseUtils::GetFieldIndex(FieldNumberOfEpisodes, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_TVSHOW_NUM_WATCHED; + varindex = DatabaseUtils::GetFieldIndex(FieldNumberOfWatchedEpisodes, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_TVSHOW_NUM_SEASONS; + varindex = DatabaseUtils::GetFieldIndex(FieldSeason, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = -1; + varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); +} + +TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeEpisode) +{ + int refindex, varindex; + + refindex = 0; + varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_EPISODE_TITLE + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldTitle, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_EPISODE_PLOT + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldPlot, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_EPISODE_VOTES + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldVotes, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_EPISODE_RATING + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_EPISODE_CREDITS + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldWriter, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_EPISODE_AIRED + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldAirDate, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_EPISODE_RUNTIME + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldTime, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_EPISODE_DIRECTOR + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldDirector, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_EPISODE_SEASON + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldSeason, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_ID_EPISODE_EPISODE + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldEpisodeNumber, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_EPISODE_FILE; + varindex = DatabaseUtils::GetFieldIndex(FieldFilename, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_EPISODE_PATH; + varindex = DatabaseUtils::GetFieldIndex(FieldPath, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_EPISODE_PLAYCOUNT; + varindex = DatabaseUtils::GetFieldIndex(FieldPlaycount, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_EPISODE_LASTPLAYED; + varindex = DatabaseUtils::GetFieldIndex(FieldLastPlayed, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_EPISODE_DATEADDED; + varindex = DatabaseUtils::GetFieldIndex(FieldDateAdded, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_EPISODE_TVSHOW_NAME; + varindex = DatabaseUtils::GetFieldIndex(FieldTvShowTitle, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_EPISODE_TVSHOW_STUDIO; + varindex = DatabaseUtils::GetFieldIndex(FieldStudio, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_EPISODE_TVSHOW_AIRED; + varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_EPISODE_TVSHOW_MPAA; + varindex = DatabaseUtils::GetFieldIndex(FieldMPAA, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = -1; + varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); +} + +TEST(TestDatabaseUtils, GetSelectFields) +{ + Fields fields; + FieldList fieldlist; + + EXPECT_FALSE(DatabaseUtils::GetSelectFields(fields, MediaTypeAlbum, + fieldlist)); + + fields.insert(FieldId); + fields.insert(FieldGenre); + fields.insert(FieldAlbum); + fields.insert(FieldArtist); + fields.insert(FieldTitle); + EXPECT_FALSE(DatabaseUtils::GetSelectFields(fields, MediaTypeNone, + fieldlist)); + EXPECT_TRUE(DatabaseUtils::GetSelectFields(fields, MediaTypeAlbum, + fieldlist)); + EXPECT_FALSE(fieldlist.empty()); +} + +TEST(TestDatabaseUtils, GetFieldValue) +{ + CVariant v_null, v_string; + dbiplus::field_value f_null, f_string("test"); + + f_null.set_isNull(); + EXPECT_TRUE(DatabaseUtils::GetFieldValue(f_null, v_null)); + EXPECT_TRUE(v_null.isNull()); + + EXPECT_TRUE(DatabaseUtils::GetFieldValue(f_string, v_string)); + EXPECT_FALSE(v_string.isNull()); + EXPECT_TRUE(v_string.isString()); +} + +/* TODO: Need some way to test this function */ +// TEST(TestDatabaseUtils, GetDatabaseResults) +// { +// static bool GetDatabaseResults(MediaType mediaType, const FieldList &fields, +// const std::auto_ptr<dbiplus::Dataset> &dataset, +// DatabaseResults &results); +// } + +TEST(TestDatabaseUtils, BuildLimitClause) +{ + std::string a = DatabaseUtils::BuildLimitClause(100); + EXPECT_STREQ(" LIMIT 100", a.c_str()); +} + +// class DatabaseUtils +// { +// public: +// +// +// static std::string BuildLimitClause(int end, int start = 0); +// }; diff --git a/src/utils/test/TestEndianSwap.cpp b/src/utils/test/TestEndianSwap.cpp new file mode 100644 index 0000000000..bbb16441cb --- /dev/null +++ b/src/utils/test/TestEndianSwap.cpp @@ -0,0 +1,145 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/EndianSwap.h" + +#include "gtest/gtest.h" + +TEST(TestEndianSwap, Endian_Swap16) +{ + uint16_t ref, var; + ref = 0x00FF; + var = Endian_Swap16(0xFF00); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_Swap32) +{ + uint32_t ref, var; + ref = 0x00FF00FF; + var = Endian_Swap32(0xFF00FF00); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_Swap64) +{ + uint64_t ref, var; + ref = UINT64_C(0x00FF00FF00FF00FF); + var = Endian_Swap64(UINT64_C(0xFF00FF00FF00FF00)); + EXPECT_EQ(ref, var); +} + +#ifndef WORDS_BIGENDIAN +TEST(TestEndianSwap, Endian_SwapLE16) +{ + uint16_t ref, var; + ref = 0x00FF; + var = Endian_SwapLE16(0x00FF); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_SwapLE32) +{ + uint32_t ref, var; + ref = 0x00FF00FF; + var = Endian_SwapLE32(0x00FF00FF); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_SwapLE64) +{ + uint64_t ref, var; + ref = UINT64_C(0x00FF00FF00FF00FF); + var = Endian_SwapLE64(UINT64_C(0x00FF00FF00FF00FF)); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_SwapBE16) +{ + uint16_t ref, var; + ref = 0x00FF; + var = Endian_SwapBE16(0xFF00); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_SwapBE32) +{ + uint32_t ref, var; + ref = 0x00FF00FF; + var = Endian_SwapBE32(0xFF00FF00); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_SwapBE64) +{ + uint64_t ref, var; + ref = UINT64_C(0x00FF00FF00FF00FF); + var = Endian_SwapBE64(UINT64_C(0xFF00FF00FF00FF00)); + EXPECT_EQ(ref, var); +} +#else +TEST(TestEndianSwap, Endian_SwapLE16) +{ + uint16_t ref, var; + ref = 0x00FF; + var = Endian_SwapLE16(0xFF00); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_SwapLE32) +{ + uint32_t ref, var; + ref = 0x00FF00FF; + var = Endian_SwapLE32(0xFF00FF00); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_SwapLE64) +{ + uint64_t ref, var; + ref = UINT64_C(0x00FF00FF00FF00FF); + var = Endian_SwapLE64(UINT64_C(0xFF00FF00FF00FF00)); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_SwapBE16) +{ + uint16_t ref, var; + ref = 0x00FF; + var = Endian_SwapBE16(0x00FF); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_SwapBE32) +{ + uint32_t ref, var; + ref = 0x00FF00FF; + var = Endian_SwapBE32(0x00FF00FF); + EXPECT_EQ(ref, var); +} + +TEST(TestEndianSwap, Endian_SwapBE64) +{ + uint64_t ref, var; + ref = UINT64_C(0x00FF00FF00FF00FF); + var = Endian_SwapBE64(UINT64_C(0x00FF00FF00FF00FF)); + EXPECT_EQ(ref, var); +} +#endif diff --git a/src/utils/test/TestFileOperationJob.cpp b/src/utils/test/TestFileOperationJob.cpp new file mode 100644 index 0000000000..47a12e02e9 --- /dev/null +++ b/src/utils/test/TestFileOperationJob.cpp @@ -0,0 +1,301 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/FileOperationJob.h" +#include "filesystem/File.h" +#include "filesystem/Directory.h" +#include "utils/URIUtils.h" + +#include "test/TestUtils.h" + +#include "gtest/gtest.h" + +TEST(TestFileOperationJob, ActionCopy) +{ + XFILE::CFile *tmpfile; + CStdString tmpfilepath, destfile; + CFileItemList items; + CFileOperationJob job; + + ASSERT_TRUE((tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + tmpfile->Close(); + + CFileItemPtr item(new CFileItem(tmpfilepath)); + item->SetPath(tmpfilepath); + item->m_bIsFolder = false; + item->Select(true); + items.Add(item); + + CStdString destpath = URIUtils::GetDirectory(tmpfilepath); + destpath = URIUtils::AddFileToFolder(destpath, "copy"); + destfile = URIUtils::AddFileToFolder(destpath, URIUtils::GetFileName(tmpfilepath)); + ASSERT_FALSE(XFILE::CFile::Exists(destfile)); + + job.SetFileOperation(CFileOperationJob::ActionCopy, items, destpath); + EXPECT_EQ(CFileOperationJob::ActionCopy, job.GetAction()); + + EXPECT_TRUE(job.DoWork()); + EXPECT_TRUE(XFILE::CFile::Exists(tmpfilepath)); + EXPECT_TRUE(XFILE::CFile::Exists(destfile)); + + EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile)); + EXPECT_TRUE(XFILE::CFile::Delete(destfile)); + EXPECT_TRUE(XFILE::CDirectory::Remove(destpath)); +} + +TEST(TestFileOperationJob, ActionMove) +{ + XFILE::CFile *tmpfile; + CStdString tmpfilepath, destfile; + CFileItemList items; + CFileOperationJob job; + + ASSERT_TRUE((tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + tmpfile->Close(); + + CFileItemPtr item(new CFileItem(tmpfilepath)); + item->SetPath(tmpfilepath); + item->m_bIsFolder = false; + item->Select(true); + items.Add(item); + + CStdString destpath = URIUtils::GetDirectory(tmpfilepath); + destpath = URIUtils::AddFileToFolder(destpath, "move"); + destfile = URIUtils::AddFileToFolder(destpath, URIUtils::GetFileName(tmpfilepath)); + ASSERT_FALSE(XFILE::CFile::Exists(destfile)); + ASSERT_TRUE(XFILE::CDirectory::Create(destpath)); + + job.SetFileOperation(CFileOperationJob::ActionMove, items, destpath); + EXPECT_EQ(CFileOperationJob::ActionMove, job.GetAction()); + + EXPECT_TRUE(job.DoWork()); + EXPECT_FALSE(XFILE::CFile::Exists(tmpfilepath)); + EXPECT_TRUE(XFILE::CFile::Exists(destfile)); + + EXPECT_TRUE(XFILE::CFile::Delete(destfile)); + EXPECT_TRUE(XFILE::CDirectory::Remove(destpath)); +} + +TEST(TestFileOperationJob, ActionDelete) +{ + XFILE::CFile *tmpfile; + CStdString tmpfilepath, destfile; + CFileItemList items; + CFileOperationJob job; + + ASSERT_TRUE((tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + tmpfile->Close(); + + CFileItemPtr item(new CFileItem(tmpfilepath)); + item->SetPath(tmpfilepath); + item->m_bIsFolder = false; + item->Select(true); + items.Add(item); + + CStdString destpath = URIUtils::GetDirectory(tmpfilepath); + destpath = URIUtils::AddFileToFolder(destpath, "delete"); + destfile = URIUtils::AddFileToFolder(destpath, URIUtils::GetFileName(tmpfilepath)); + ASSERT_FALSE(XFILE::CFile::Exists(destfile)); + + job.SetFileOperation(CFileOperationJob::ActionCopy, items, destpath); + EXPECT_EQ(CFileOperationJob::ActionCopy, job.GetAction()); + + EXPECT_TRUE(job.DoWork()); + EXPECT_TRUE(XFILE::CFile::Exists(tmpfilepath)); + EXPECT_TRUE(XFILE::CFile::Exists(destfile)); + + job.SetFileOperation(CFileOperationJob::ActionDelete, items, ""); + EXPECT_EQ(CFileOperationJob::ActionDelete, job.GetAction()); + + EXPECT_TRUE(job.DoWork()); + EXPECT_FALSE(XFILE::CFile::Exists(tmpfilepath)); + + items.Clear(); + CFileItemPtr item2(new CFileItem(destfile)); + item2->SetPath(destfile); + item2->m_bIsFolder = false; + item2->Select(true); + items.Add(item2); + + job.SetFileOperation(CFileOperationJob::ActionDelete, items, ""); + EXPECT_EQ(CFileOperationJob::ActionDelete, job.GetAction()); + + EXPECT_TRUE(job.DoWork()); + EXPECT_FALSE(XFILE::CFile::Exists(destfile)); + EXPECT_TRUE(XFILE::CDirectory::Remove(destpath)); +} + +TEST(TestFileOperationJob, ActionReplace) +{ + XFILE::CFile *tmpfile; + CStdString tmpfilepath, destfile; + CFileItemList items; + CFileOperationJob job; + + ASSERT_TRUE((tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + tmpfile->Close(); + + CFileItemPtr item(new CFileItem(tmpfilepath)); + item->SetPath(tmpfilepath); + item->m_bIsFolder = false; + item->Select(true); + items.Add(item); + + CStdString destpath = URIUtils::GetDirectory(tmpfilepath); + destpath = URIUtils::AddFileToFolder(destpath, "replace"); + destfile = URIUtils::AddFileToFolder(destpath, URIUtils::GetFileName(tmpfilepath)); + ASSERT_FALSE(XFILE::CFile::Exists(destfile)); + + job.SetFileOperation(CFileOperationJob::ActionCopy, items, destpath); + EXPECT_EQ(CFileOperationJob::ActionCopy, job.GetAction()); + + EXPECT_TRUE(job.DoWork()); + EXPECT_TRUE(XFILE::CFile::Exists(tmpfilepath)); + EXPECT_TRUE(XFILE::CFile::Exists(destfile)); + + job.SetFileOperation(CFileOperationJob::ActionReplace, items, destpath); + EXPECT_EQ(CFileOperationJob::ActionReplace, job.GetAction()); + + EXPECT_TRUE(job.DoWork()); + EXPECT_TRUE(XFILE::CFile::Exists(destfile)); + + EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile)); + EXPECT_TRUE(XFILE::CFile::Delete(destfile)); + EXPECT_TRUE(XFILE::CDirectory::Remove(destpath)); +} + +TEST(TestFileOperationJob, ActionCreateFolder) +{ + XFILE::CFile *tmpfile; + CStdString tmpfilepath, destpath; + CFileItemList items; + CFileOperationJob job; + + ASSERT_TRUE((tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + + CStdString tmpfiledirectory = + CXBMCTestUtils::Instance().TempFileDirectory(tmpfile); + + tmpfile->Close(); + + destpath = tmpfilepath; + destpath += ".createfolder"; + ASSERT_FALSE(XFILE::CFile::Exists(destpath)); + + CFileItemPtr item(new CFileItem(destpath)); + item->SetPath(destpath); + item->m_bIsFolder = true; + item->Select(true); + items.Add(item); + + job.SetFileOperation(CFileOperationJob::ActionCreateFolder, items, tmpfiledirectory); + EXPECT_EQ(CFileOperationJob::ActionCreateFolder, job.GetAction()); + + EXPECT_TRUE(job.DoWork()); + EXPECT_TRUE(XFILE::CDirectory::Exists(destpath)); + + EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile)); + EXPECT_TRUE(XFILE::CDirectory::Remove(destpath)); +} + +// This test will fail until ActionDeleteFolder has a proper implementation +TEST(TestFileOperationJob, ActionDeleteFolder) +{ + XFILE::CFile *tmpfile; + CStdString tmpfilepath, destpath; + CFileItemList items; + CFileOperationJob job; + + ASSERT_TRUE((tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + + CStdString tmpfiledirectory = + CXBMCTestUtils::Instance().TempFileDirectory(tmpfile); + + tmpfile->Close(); + + destpath = tmpfilepath; + destpath += ".deletefolder"; + ASSERT_FALSE(XFILE::CFile::Exists(destpath)); + + CFileItemPtr item(new CFileItem(destpath)); + item->SetPath(destpath); + item->m_bIsFolder = true; + item->Select(true); + items.Add(item); + + job.SetFileOperation(CFileOperationJob::ActionCreateFolder, items, tmpfiledirectory); + EXPECT_EQ(CFileOperationJob::ActionCreateFolder, job.GetAction()); + + EXPECT_TRUE(job.DoWork()); + EXPECT_TRUE(XFILE::CDirectory::Exists(destpath)); + + job.SetFileOperation(CFileOperationJob::ActionDeleteFolder, items, tmpfiledirectory); + EXPECT_EQ(CFileOperationJob::ActionDeleteFolder, job.GetAction()); + + EXPECT_TRUE(job.DoWork()); + EXPECT_FALSE(XFILE::CDirectory::Exists(destpath)); + + EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile)); +} + +TEST(TestFileOperationJob, GetFunctions) +{ + XFILE::CFile *tmpfile; + CStdString tmpfilepath, destfile; + CFileItemList items; + CFileOperationJob job; + + ASSERT_TRUE((tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + tmpfile->Close(); + + CFileItemPtr item(new CFileItem(tmpfilepath)); + item->SetPath(tmpfilepath); + item->m_bIsFolder = false; + item->Select(true); + items.Add(item); + + CStdString destpath = URIUtils::GetDirectory(tmpfilepath); + destpath = URIUtils::AddFileToFolder(destpath, "getfunctions"); + destfile = URIUtils::AddFileToFolder(destpath, URIUtils::GetFileName(tmpfilepath)); + ASSERT_FALSE(XFILE::CFile::Exists(destfile)); + + job.SetFileOperation(CFileOperationJob::ActionCopy, items, destpath); + EXPECT_EQ(CFileOperationJob::ActionCopy, job.GetAction()); + + EXPECT_TRUE(job.DoWork()); + EXPECT_TRUE(XFILE::CFile::Exists(tmpfilepath)); + EXPECT_TRUE(XFILE::CFile::Exists(destfile)); + + std::cout << "GetAverageSpeed(): " << job.GetAverageSpeed() << std::endl; + std::cout << "GetCurrentOperation(): " << job.GetCurrentOperation() << std::endl; + std::cout << "GetCurrentFile(): " << job.GetCurrentFile() << std::endl; + EXPECT_FALSE(job.GetItems().IsEmpty()); + + EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile)); + EXPECT_TRUE(XFILE::CFile::Delete(destfile)); + EXPECT_TRUE(XFILE::CDirectory::Remove(destpath)); +} diff --git a/src/utils/test/TestFileUtils.cpp b/src/utils/test/TestFileUtils.cpp new file mode 100644 index 0000000000..45f9682892 --- /dev/null +++ b/src/utils/test/TestFileUtils.cpp @@ -0,0 +1,53 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/FileUtils.h" +#include "filesystem/File.h" + +#include "test/TestUtils.h" + +#include "gtest/gtest.h" + +TEST(TestFileUtils, DeleteItem_CFileItemPtr) +{ + XFILE::CFile *tmpfile; + CStdString tmpfilepath; + + ASSERT_TRUE((tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + + CFileItemPtr item(new CFileItem(tmpfilepath)); + item->SetPath(tmpfilepath); + item->m_bIsFolder = false; + item->Select(true); + + EXPECT_TRUE(CFileUtils::DeleteItem(item)); +} + +TEST(TestFileUtils, DeleteItem_CStdString) +{ + XFILE::CFile *tmpfile; + + ASSERT_TRUE((tmpfile = XBMC_CREATETEMPFILE(""))); + EXPECT_TRUE(CFileUtils::DeleteItem(XBMC_TEMPFILEPATH(tmpfile))); +} + +/* Executing RenameFile() requires input from the user */ +// static bool RenameFile(const CStdString &strFile); diff --git a/src/utils/test/TestGlobalsHandling.cpp b/src/utils/test/TestGlobalsHandling.cpp new file mode 100644 index 0000000000..fc00763fd2 --- /dev/null +++ b/src/utils/test/TestGlobalsHandling.cpp @@ -0,0 +1,37 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/test/TestGlobalsHandlingPattern1.h" + +#include "gtest/gtest.h" + +using namespace xbmcutil; +using namespace test; + +bool TestGlobalPattern1::ctorCalled = false; +bool TestGlobalPattern1::dtorCalled = false; + +TEST(TestGlobal, Pattern1) +{ + EXPECT_TRUE(TestGlobalPattern1::ctorCalled); + { + boost::shared_ptr<TestGlobalPattern1> ptr = g_testGlobalPattern1Ref; + } +} diff --git a/src/utils/test/TestGlobalsHandlingPattern1.h b/src/utils/test/TestGlobalsHandlingPattern1.h new file mode 100644 index 0000000000..55482dcf9a --- /dev/null +++ b/src/utils/test/TestGlobalsHandlingPattern1.h @@ -0,0 +1,52 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include "utils/GlobalsHandling.h" + +#include <iostream> + +namespace xbmcutil +{ + namespace test + { + class TestGlobalPattern1 + { + public: + static bool ctorCalled; + static bool dtorCalled; + + int somethingToAccess; + + TestGlobalPattern1() : somethingToAccess(0) { ctorCalled = true; } + ~TestGlobalPattern1() + { + std::cout << "Clean shutdown of TestGlobalPattern1" << std::endl << std::flush; + dtorCalled = true; + } + + void beHappy() { if (somethingToAccess) throw somethingToAccess; } + }; + } +} + +XBMC_GLOBAL_REF(xbmcutil::test::TestGlobalPattern1,g_testGlobalPattern1); +#define g_testGlobalPattern1 XBMC_GLOBAL_USE(xbmcutil::test::TestGlobalPattern1) diff --git a/src/utils/test/TestHTMLTable.cpp b/src/utils/test/TestHTMLTable.cpp new file mode 100644 index 0000000000..a13ff2b68f --- /dev/null +++ b/src/utils/test/TestHTMLTable.cpp @@ -0,0 +1,84 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/HTMLTable.h" + +#include "gtest/gtest.h" + +// class CHTMLRow +// { +// public: +// CHTMLRow(void); +// virtual ~CHTMLRow(void); +// int GetColumns() const; +// const std::string& GetColumValue(int iColumn) const; +// void Parse(const std::string& strTableRow); +// +// protected: +// std::vector<std::string> m_vecColums; +// }; +// +// class CHTMLTable +// { +// public: +// CHTMLTable(void); +// virtual ~CHTMLTable(void); +// void Parse(const std::string& strHTML); +// int GetRows() const; +// const CHTMLRow& GetRow(int iRow) const; +// protected: +// std::vector<CHTMLRow> m_vecRows; +// }; + +TEST(TestHTMLTable, General) +{ + HTML::CHTMLTable table; + HTML::CHTMLRow row1, row2; + std::string str; + str = "<table>\n" + " <tr>\n" + " <td>r1c1</td>\n" + " <td>r1c2</td>\n" + " </tr>\n" + " <tr>\n" + " <td>r2c1</td>\n" + " <td>r2c2</td>\n" + " </tr>\n" + " <tr>\n" + " <td>r3c1</td>\n" + " <td>r3c2</td>\n" + " </tr>\n" + "</table>\n"; + table.Parse(str); + EXPECT_EQ(3, table.GetRows()); + + row1 = table.GetRow(0); + EXPECT_EQ(2, row1.GetColumns()); + EXPECT_STREQ("r1c1", row1.GetColumValue(0).c_str()); + + str = "<tr>\n" + " <td>new row1 column1</td>\n" + " <td>new row1 column2</td>\n" + " <td>new row1 column3</td>\n" + "</tr>\n"; + row2.Parse(str); + EXPECT_EQ(3, row2.GetColumns()); + EXPECT_STREQ("new row1 column2", row2.GetColumValue(1).c_str()); +} diff --git a/src/utils/test/TestHTMLUtil.cpp b/src/utils/test/TestHTMLUtil.cpp new file mode 100644 index 0000000000..a7032ae2a4 --- /dev/null +++ b/src/utils/test/TestHTMLUtil.cpp @@ -0,0 +1,103 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/HTMLUtil.h" + +#include "gtest/gtest.h" + +TEST(TestHTMLUtil, FindTag) +{ + HTML::CHTMLUtil util; + std::string str, found; + str = "<!DOCTYPE html>\n" + "<html>\n" + " <head class=\"someclass\">\n" + " <body>\n" + " <p>blah blah blah</p>\n" + " </body>\n" + " </head>\n" + "</html>\n"; + EXPECT_EQ(54, util.FindTag(str, "<body", found)); + EXPECT_STREQ("<body>", found.c_str()); + + found.clear(); + EXPECT_EQ(-1, util.FindTag(str, "<span", found)); + EXPECT_STREQ("", found.c_str()); +} + +TEST(TestHTMLUtil, FindClosingTag) +{ + HTML::CHTMLUtil util; + std::string str, found; + str = "<!DOCTYPE html>\n" + "<html>\n" + " <head class=\"someclass\">\n" + " <body>\n" + " <p>blah blah blah</p>\n" + " </body>\n" + " </head>\n" + "</html>\n"; + /* Need to set position past '<body>' tag */ + EXPECT_EQ(93, util.FindClosingTag(str, "body", found, 61)); + EXPECT_STREQ("</body>", found.c_str()); +} + +TEST(TestHTMLUtil, getValueOfTag) +{ + HTML::CHTMLUtil util; + std::string str, value; + str = "<p>blah blah blah</p>"; + util.getValueOfTag(str, value); + EXPECT_STREQ("blah blah blah", value.c_str()); +} + +TEST(TestHTMLUtil, getAttributeOfTag) +{ + HTML::CHTMLUtil util; + std::string str, tag, value; + str = "<head class=\"someclass\"></head>\n"; + util.getAttributeOfTag(str, "class", value); + EXPECT_STREQ("\"someclass", value.c_str()); +} + +TEST(TestHTMLUtil, RemoveTags) +{ + std::string str; + str = "<!DOCTYPE html>\n" + "<html>\n" + " <head class=\"someclass\">\n" + " <body>\n" + " <p>blah blah blah</p>\n" + " </body>\n" + " </head>\n" + "</html>\n"; + HTML::CHTMLUtil::RemoveTags(str); + EXPECT_STREQ("\n\n \n \n blah blah blah\n \n \n\n", + str.c_str()); +} + +TEST(TestHTMLUtil, ConvertHTMLToW) +{ + std::wstring inw, refstrw, varstrw; + inw = L"å&€"; + refstrw = L"å&€"; + HTML::CHTMLUtil::ConvertHTMLToW(inw, varstrw); + EXPECT_STREQ(refstrw.c_str(), varstrw.c_str()); +} diff --git a/src/utils/test/TestHttpHeader.cpp b/src/utils/test/TestHttpHeader.cpp new file mode 100644 index 0000000000..170282685f --- /dev/null +++ b/src/utils/test/TestHttpHeader.cpp @@ -0,0 +1,515 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <string.h> +#include "utils/HttpHeader.h" +#include "gtest/gtest.h" + +#define CHECK_CNT_TYPE_NAME "Content-Type" +#define CHECK_CONTENT_TYPE_HTML "text/html" +#define CHECK_CONTENT_TYPE_HTML_CHRS "text/html; charset=WINDOWS-1251" +#define CHECK_CONTENT_TYPE_XML_CHRS "text/xml; charset=uTf-8" +#define CHECK_CONTENT_TYPE_TEXT "text/plain" +#define CHECK_DATE_NAME "Date" +#define CHECK_DATE_VALUE1 "Thu, 09 Jan 2014 17:58:30 GMT" +#define CHECK_DATE_VALUE2 "Thu, 09 Jan 2014 20:21:20 GMT" +#define CHECK_DATE_VALUE3 "Thu, 09 Jan 2014 20:25:02 GMT" +#define CHECK_PROT_LINE_200 "HTTP/1.1 200 OK" +#define CHECK_PROT_LINE_301 "HTTP/1.1 301 Moved Permanently" + +#define CHECK_HEADER_SMPL CHECK_PROT_LINE_200 "\r\n" \ + CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML "\r\n" \ + "\r\n" + +#define CHECK_HEADER_L1 CHECK_PROT_LINE_200 "\r\n" \ + "Server: nginx/1.4.4\r\n" \ + CHECK_DATE_NAME ": " CHECK_DATE_VALUE1 "\r\n" \ + CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML_CHRS "\r\n" \ + "Transfer-Encoding: chunked\r\n" \ + "Connection: close\r\n" \ + "Set-Cookie: PHPSESSID=90857d437518db8f0944ca012761048a; path=/; domain=example.com\r\n" \ + "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" \ + "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" \ + "Pragma: no-cache\r\n" \ + "Set-Cookie: user_country=ot; expires=Thu, 09-Jan-2014 18:58:30 GMT; path=/; domain=.example.com\r\n" \ + "\r\n" + +#define CHECK_HEADER_R CHECK_PROT_LINE_301 "\r\n" \ + "Server: nginx/1.4.4\r\n" \ + CHECK_DATE_NAME ": " CHECK_DATE_VALUE2 "\r\n" \ + CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML "\r\n" \ + "Content-Length: 150\r\n" \ + "Connection: close\r\n" \ + "Location: http://www.Example.Com\r\n" \ + "\r\n" + +#define CHECK_HEADER_L2 CHECK_PROT_LINE_200 "\r\n" \ + CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\n" \ + "Server: Apache/2.4.7 (Unix) mod_wsgi/3.4 Python/2.7.5 OpenSSL/1.0.1e\r\n" \ + "Last-Modified: Thu, 09 Jan 2014 20:10:28 GMT\r\n" \ + "ETag: \"9a97-4ef8f335ebd10\"\r\n" \ + "Accept-Ranges: bytes\r\n" \ + "Content-Length: 33355\r\n" \ + "Vary: Accept-Encoding\r\n" \ + "Cache-Control: max-age=3600\r\n" \ + "Expires: Thu, 09 Jan 2014 21:25:02 GMT\r\n" \ + "Connection: close\r\n" \ + CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_XML_CHRS "\r\n" \ + "\r\n" + +// local helper function: replace substrings +std::string strReplc(const std::string& str, const std::string& from, const std::string& to) +{ + std::string result; + size_t prevPos = 0; + size_t pos; + const size_t len = str.length(); + + do + { + pos = str.find(from, prevPos); + result.append(str, prevPos, pos - prevPos); + if (pos >= len) + break; + result.append(to); + prevPos = pos + from.length(); + } while (true); + + return result; +} + +TEST(TestHttpHeader, General) +{ + /* check freshly created object */ + CHttpHeader testHdr; + EXPECT_TRUE(testHdr.GetHeader().empty()) << "Newly created object is not empty"; + EXPECT_TRUE(testHdr.GetProtoLine().empty()) << "Newly created object has non-empty protocol line"; + EXPECT_TRUE(testHdr.GetMimeType().empty()) << "Newly created object has non-empty MIME-type"; + EXPECT_TRUE(testHdr.GetCharset().empty()) << "Newly created object has non-empty charset"; + EXPECT_TRUE(testHdr.GetValue("foo").empty()) << "Newly created object has some parameter"; + EXPECT_TRUE(testHdr.GetValues("bar").empty()) << "Newly created object has some parameters"; + EXPECT_FALSE(testHdr.IsHeaderDone()) << "Newly created object has \"parsing finished\" state"; + + /* check general functions in simple case */ + testHdr.Parse(CHECK_HEADER_SMPL); + EXPECT_FALSE(testHdr.GetHeader().empty()) << "Parsed header is empty"; + EXPECT_FALSE(testHdr.GetProtoLine().empty()) << "Parsed header has empty protocol line"; + EXPECT_FALSE(testHdr.GetMimeType().empty()) << "Parsed header has empty MIME-type"; + EXPECT_FALSE(testHdr.GetValue(CHECK_CNT_TYPE_NAME).empty()) << "Parsed header has empty \"" CHECK_CNT_TYPE_NAME "\" parameter"; + EXPECT_FALSE(testHdr.GetValues(CHECK_CNT_TYPE_NAME).empty()) << "Parsed header has no \"" CHECK_CNT_TYPE_NAME "\" parameters"; + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Parsed header has \"parsing not finished\" state"; + + /* check clearing of object */ + testHdr.Clear(); + EXPECT_TRUE(testHdr.GetHeader().empty()) << "Cleared object is not empty"; + EXPECT_TRUE(testHdr.GetProtoLine().empty()) << "Cleared object has non-empty protocol line"; + EXPECT_TRUE(testHdr.GetMimeType().empty()) << "Cleared object has non-empty MIME-type"; + EXPECT_TRUE(testHdr.GetCharset().empty()) << "Cleared object has non-empty charset"; + EXPECT_TRUE(testHdr.GetValue(CHECK_CNT_TYPE_NAME).empty()) << "Cleared object has some parameter"; + EXPECT_TRUE(testHdr.GetValues(CHECK_CNT_TYPE_NAME).empty()) << "Cleared object has some parameters"; + EXPECT_FALSE(testHdr.IsHeaderDone()) << "Cleared object has \"parsing finished\" state"; + + /* check general functions after object clearing */ + testHdr.Parse(CHECK_HEADER_R); + EXPECT_FALSE(testHdr.GetHeader().empty()) << "Parsed header is empty"; + EXPECT_FALSE(testHdr.GetProtoLine().empty()) << "Parsed header has empty protocol line"; + EXPECT_FALSE(testHdr.GetMimeType().empty()) << "Parsed header has empty MIME-type"; + EXPECT_FALSE(testHdr.GetValue(CHECK_CNT_TYPE_NAME).empty()) << "Parsed header has empty \"" CHECK_CNT_TYPE_NAME "\" parameter"; + EXPECT_FALSE(testHdr.GetValues(CHECK_CNT_TYPE_NAME).empty()) << "Parsed header has no \"" CHECK_CNT_TYPE_NAME "\" parameters"; + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Parsed header has \"parsing not finished\" state"; +} + +TEST(TestHttpHeader, Parse) +{ + CHttpHeader testHdr; + + /* check parsing line-by-line */ + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n"); + EXPECT_FALSE(testHdr.IsHeaderDone()) << "Not completed header has \"parsing finished\" state"; + testHdr.Parse(CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML "\r\n"); + EXPECT_FALSE(testHdr.IsHeaderDone()) << "Not completed header has \"parsing finished\" state"; + testHdr.Parse("\r\n"); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ(CHECK_PROT_LINE_200, testHdr.GetProtoLine().c_str()) << "Wrong protocol line"; + EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\""; + + /* check autoclearing when new header is parsed */ + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n"); + EXPECT_FALSE(testHdr.IsHeaderDone()) << "Not completed header has \"parsing finished\" state"; + EXPECT_TRUE(testHdr.GetValues(CHECK_CNT_TYPE_NAME).empty()) << "Cleared header has some parameters"; + testHdr.Clear(); + EXPECT_TRUE(testHdr.GetHeader().empty()) << "Cleared object is not empty"; + + /* general check parsing */ + testHdr.Parse(CHECK_HEADER_SMPL); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STRCASEEQ(CHECK_HEADER_SMPL, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header"; + testHdr.Parse(CHECK_HEADER_L1); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STRCASEEQ(CHECK_HEADER_L1, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header"; + EXPECT_STREQ("Thu, 09 Jan 2014 17:58:30 GMT", testHdr.GetValue("Date").c_str()); // case-sensitive match of value + testHdr.Parse(CHECK_HEADER_L2); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STRCASEEQ(CHECK_HEADER_L2, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header"; + EXPECT_STREQ("Thu, 09 Jan 2014 20:10:28 GMT", testHdr.GetValue("Last-Modified").c_str()); // case-sensitive match of value + testHdr.Parse(CHECK_HEADER_R); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STRCASEEQ(CHECK_HEADER_R, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header"; + EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("Location").c_str()); // case-sensitive match of value + + /* check support for '\n' line endings */ + testHdr.Parse(strReplc(CHECK_HEADER_SMPL, "\r\n", "\n")); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STRCASEEQ(CHECK_HEADER_SMPL, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header"; + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + testHdr.Parse(strReplc(CHECK_HEADER_L1, "\r\n", "\n")); + EXPECT_STRCASEEQ(CHECK_HEADER_L1, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header"; + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + testHdr.Parse(strReplc(CHECK_HEADER_L2, "\r\n", "\n")); + EXPECT_STRCASEEQ(CHECK_HEADER_L2, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header"; + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + testHdr.Parse(CHECK_PROT_LINE_200 "\n" CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML "\r\n"); // mixed "\n" and "\r\n" + testHdr.Parse("\n"); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STRCASEEQ(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML "\r\n\r\n", testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header"; + EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\""; + + /* check trimming of whitespaces for parameter name and value */ + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML "\r\n\r\n"); + EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\""; + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML " \r\n\r\n"); + EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\""; + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME ":" CHECK_CONTENT_TYPE_HTML " \r\n\r\n"); + EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\""; + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME ":\t" CHECK_CONTENT_TYPE_HTML " \t \r\n\r\n"); + EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\""; + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME ":\t " CHECK_CONTENT_TYPE_HTML " \t \r\n\r\n"); + EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\""; + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME "\t:" CHECK_CONTENT_TYPE_HTML " \t \r\n\r\n"); + EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\""; + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME " \t : " CHECK_CONTENT_TYPE_HTML " \t \r\n\r\n"); + EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\""; +} + +TEST(TestHttpHeader, Parse_Multiline) +{ + CHttpHeader testHdr; + + /* Check multiline parameter parsing line-by-line */ + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n"); + testHdr.Parse(CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\n"); + testHdr.Parse("X-Comment: This\r\n"); // between singleline parameters + testHdr.Parse(" is\r\n"); + testHdr.Parse(" multi\r\n"); + testHdr.Parse(" line\r\n"); + testHdr.Parse(" value\r\n"); + testHdr.Parse(CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_TEXT "\r\n"); + testHdr.Parse("\r\n"); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n"); + testHdr.Parse("X-Comment: This\r\n"); // first parameter + testHdr.Parse(" is\r\n"); + testHdr.Parse(" multi\r\n"); + testHdr.Parse(" line\r\n"); + testHdr.Parse(" value\r\n"); + testHdr.Parse(CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_TEXT "\r\n"); + testHdr.Parse("\r\n"); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n"); + testHdr.Parse(CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\n"); + testHdr.Parse("X-Comment: This\r\n"); // last parameter + testHdr.Parse(" is\r\n"); + testHdr.Parse(" multi\r\n"); + testHdr.Parse(" line\r\n"); + testHdr.Parse(" value\r\n"); + testHdr.Parse("\r\n"); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n"); + testHdr.Parse("X-Comment: This\r\n"); // the only parameter + testHdr.Parse(" is\r\n"); + testHdr.Parse(" multi\r\n"); + testHdr.Parse(" line\r\n"); + testHdr.Parse(" value\r\n"); + testHdr.Parse("\r\n"); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n"); + testHdr.Parse("X-Comment: This\n"); // the only parameter with mixed ending style + testHdr.Parse(" is\r\n"); + testHdr.Parse(" multi\n"); + testHdr.Parse(" line\r\n"); + testHdr.Parse(" value\n"); + testHdr.Parse("\r\n"); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + /* Check multiline parameter parsing as one line */ + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\nX-Comment: This\r\n is\r\n multi\r\n line\r\n value\r\n" \ + CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_TEXT "\r\n\r\n"); // between singleline parameters + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\nX-Comment: This\r\n is\r\n multi\r\n line\r\n value\r\n" \ + CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_TEXT "\r\n\r\n"); // first parameter + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\nX-Comment: This\r\n is\r\n multi\r\n line\r\n value\r\n\r\n"); // last parameter + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\nX-Comment: This\r\n is\r\n multi\r\n line\r\n value\r\n\r\n"); // the only parameter + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\nX-Comment: This\n is\r\n multi\r\n line\n value\r\n\n"); // the only parameter with mixed ending style + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + /* Check multiline parameter parsing as mixed one/many lines */ + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\nX-Comment: This\n is\r\n multi\r\n"); + testHdr.Parse(" line\n value\r\n\n"); // the only parameter with mixed ending style + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + /* Check parsing of multiline parameter with ':' in value */ + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\nX-Comment: This\r\n is:\r\n mul:ti\r\n"); + testHdr.Parse(" :line\r\n valu:e\r\n\n"); // the only parameter + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("This is: mul:ti :line valu:e", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value"; + + /* Check multiline parameter parsing with trimming */ + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n"); + testHdr.Parse(CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\n"); + testHdr.Parse("Server: Apache/2.4.7 (Unix)\r\n"); // last parameter, line-by-line parsing + testHdr.Parse(" mod_wsgi/3.4 \r\n"); + testHdr.Parse("\tPython/2.7.5\r\n"); + testHdr.Parse("\t \t \tOpenSSL/1.0.1e\r\n"); + testHdr.Parse("\r\n"); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_GE(strlen("Apache/2.4.7 (Unix) mod_wsgi/3.4 \tPython/2.7.5\t \t \tOpenSSL/1.0.1e"), testHdr.GetValue("Server").length()) << "Length of miltiline value is greater than length of original string"; + EXPECT_LE(strlen("Apache/2.4.7 (Unix) mod_wsgi/3.4 Python/2.7.5 OpenSSL/1.0.1e"), testHdr.GetValue("Server").length()) << "Length of miltiline value is less than length of trimmed original string"; + EXPECT_STREQ("Apache/2.4.7(Unix)mod_wsgi/3.4Python/2.7.5OpenSSL/1.0.1e", strReplc(strReplc(testHdr.GetValue("Server"), " ", ""), "\t", "").c_str()) << "Multiline value with removed whitespaces does not match original string with removed whitespaces"; + + testHdr.Clear(); + testHdr.Parse(CHECK_PROT_LINE_200 "\r\n"); + testHdr.Parse(CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\n"); + testHdr.Parse("Server: Apache/2.4.7 (Unix)\r\n mod_wsgi/3.4 \n"); // last parameter, mixed line-by-line/one line parsing, mixed line ending + testHdr.Parse("\tPython/2.7.5\n\t \t \tOpenSSL/1.0.1e\r\n"); + testHdr.Parse("\r\n"); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_GE(strlen("Apache/2.4.7 (Unix) mod_wsgi/3.4 \tPython/2.7.5\t \t \tOpenSSL/1.0.1e"), testHdr.GetValue("Server").length()) << "Length of miltiline value is greater than length of original string"; + EXPECT_LE(strlen("Apache/2.4.7 (Unix) mod_wsgi/3.4 Python/2.7.5 OpenSSL/1.0.1e"), testHdr.GetValue("Server").length()) << "Length of miltiline value is less than length of trimmed original string"; + EXPECT_STREQ("Apache/2.4.7(Unix)mod_wsgi/3.4Python/2.7.5OpenSSL/1.0.1e", strReplc(strReplc(testHdr.GetValue("Server"), " ", ""), "\t", "").c_str()) << "Multiline value with removed whitespaces does not match original string with removed whitespaces"; +} + +TEST(TestHttpHeader, GetValue) +{ + CHttpHeader testHdr; + + /* Check that all parameters values can be retrieved */ + testHdr.Parse(CHECK_HEADER_R); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("nginx/1.4.4", testHdr.GetValue("Server").c_str()) << "Wrong parameter value"; + EXPECT_STREQ(CHECK_DATE_VALUE2, testHdr.GetValue(CHECK_DATE_NAME).c_str()) << "Wrong parameter value"; + EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong parameter value"; + EXPECT_STREQ("150", testHdr.GetValue("Content-Length").c_str()) << "Wrong parameter value"; + EXPECT_STREQ("close", testHdr.GetValue("Connection").c_str()) << "Wrong parameter value"; + EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("Location").c_str()) << "Wrong parameter value"; + EXPECT_TRUE(testHdr.GetValue("foo").empty()) << "Some value is returned for non-existed parameter"; + + /* Check that all parameters values can be retrieved in random order */ + testHdr.Parse(CHECK_HEADER_R); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state"; + EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("Location").c_str()) << "Wrong parameter value"; + EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong parameter value"; + EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("Location").c_str()) << "Wrong parameter value"; + EXPECT_STREQ("close", testHdr.GetValue("Connection").c_str()) << "Wrong parameter value"; + EXPECT_STREQ("nginx/1.4.4", testHdr.GetValue("Server").c_str()) << "Wrong parameter value"; + EXPECT_STREQ("150", testHdr.GetValue("Content-Length").c_str()) << "Wrong parameter value"; + EXPECT_STREQ(CHECK_DATE_VALUE2, testHdr.GetValue(CHECK_DATE_NAME).c_str()) << "Wrong parameter value"; + EXPECT_STREQ("nginx/1.4.4", testHdr.GetValue("Server").c_str()) << "Wrong parameter value"; + EXPECT_TRUE(testHdr.GetValue("foo").empty()) << "Some value is returned for non-existed parameter"; + + /* Check that parameters name is case-insensitive and value is case-sensitive*/ + EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("location").c_str()) << "Wrong parameter value for lowercase name"; + EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("LOCATION").c_str()) << "Wrong parameter value for UPPERCASE name"; + EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("LoCAtIOn").c_str()) << "Wrong parameter value for MiXEdcASe name"; + + /* Check value of last added parameter with the same name is returned */ + testHdr.Parse(CHECK_HEADER_L1); + EXPECT_STREQ("close", testHdr.GetValue("Connection").c_str()) << "Wrong parameter value"; + EXPECT_STREQ("user_country=ot; expires=Thu, 09-Jan-2014 18:58:30 GMT; path=/; domain=.example.com", testHdr.GetValue("Set-Cookie").c_str()) << "Wrong parameter value"; + EXPECT_STREQ("user_country=ot; expires=Thu, 09-Jan-2014 18:58:30 GMT; path=/; domain=.example.com", testHdr.GetValue("set-cookie").c_str()) << "Wrong parameter value for lowercase name"; +} + +TEST(TestHttpHeader, GetValues) +{ + CHttpHeader testHdr; + + /* Check that all parameter values can be retrieved and order of values is correct */ + testHdr.Parse(CHECK_HEADER_L1); + EXPECT_EQ(1, testHdr.GetValues("Server").size()) << "Wrong number of values for parameter \"Server\""; + EXPECT_STREQ("nginx/1.4.4", testHdr.GetValues("Server")[0].c_str()) << "Wrong parameter value"; + EXPECT_EQ(2, testHdr.GetValues("Set-Cookie").size()) << "Wrong number of values for parameter \"Set-Cookie\""; + EXPECT_STREQ("PHPSESSID=90857d437518db8f0944ca012761048a; path=/; domain=example.com", testHdr.GetValues("Set-Cookie")[0].c_str()) << "Wrong parameter value"; + EXPECT_STREQ("user_country=ot; expires=Thu, 09-Jan-2014 18:58:30 GMT; path=/; domain=.example.com", testHdr.GetValues("Set-Cookie")[1].c_str()) << "Wrong parameter value"; + EXPECT_TRUE(testHdr.GetValues("foo").empty()) << "Some values are returned for non-existed parameter"; +} + +TEST(TestHttpHeader, AddParam) +{ + CHttpHeader testHdr; + + /* General functionality */ + testHdr.AddParam("server", "Microsoft-IIS/8.0"); + EXPECT_STREQ("Microsoft-IIS/8.0", testHdr.GetValue("Server").c_str()) << "Wrong parameter value"; + + /* Interfere with parsing */ + EXPECT_FALSE(testHdr.IsHeaderDone()) << "\"AddParam\" set \"parsing finished\" state"; + testHdr.Parse(CHECK_PROT_LINE_200 "\r\nServer: nginx/1.4.4\r\n\r\n"); + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Parsed header has \"parsing not finished\" state"; + EXPECT_STREQ("nginx/1.4.4", testHdr.GetValue("Server").c_str()) << "Wrong parameter value"; + testHdr.AddParam("server", "Apache/2.4.7"); + EXPECT_STREQ("Apache/2.4.7", testHdr.GetValue("Server").c_str()) << "Wrong parameter value"; + EXPECT_EQ(3, testHdr.GetValues("Server").size()) << "Wrong number of values for parameter \"Server\""; + + /* Multiple values */ + testHdr.AddParam("X-foo", "bar1"); + testHdr.AddParam("x-foo", "bar2"); + testHdr.AddParam("x-fOO", "bar3"); + EXPECT_EQ(3, testHdr.GetValues("X-FOO").size()) << "Wrong number of values for parameter \"X-foo\""; + EXPECT_STREQ("bar1", testHdr.GetValues("X-FOo")[0].c_str()) << "Wrong parameter value"; + EXPECT_STREQ("bar2", testHdr.GetValues("X-fOo")[1].c_str()) << "Wrong parameter value"; + EXPECT_STREQ("bar3", testHdr.GetValues("x-fOo")[2].c_str()) << "Wrong parameter value"; + EXPECT_STREQ("bar3", testHdr.GetValue("x-foo").c_str()) << "Wrong parameter value"; + + /* Overwrite value */ + EXPECT_TRUE(testHdr.IsHeaderDone()) << "Parsed header has \"parsing not finished\" state"; + testHdr.AddParam("x-fOO", "superbar", true); + EXPECT_EQ(1, testHdr.GetValues("X-FoO").size()) << "Wrong number of values for parameter \"X-foo\""; + EXPECT_STREQ("superbar", testHdr.GetValue("x-foo").c_str()) << "Wrong parameter value"; + + /* Check name trimming */ + testHdr.AddParam("\tx-fOO\t ", "bar"); + EXPECT_EQ(2, testHdr.GetValues("X-FoO").size()) << "Wrong number of values for parameter \"X-foo\""; + EXPECT_STREQ("bar", testHdr.GetValue("x-foo").c_str()) << "Wrong parameter value"; + testHdr.AddParam(" SerVer \t ", "fakeSrv", true); + EXPECT_EQ(1, testHdr.GetValues("serveR").size()) << "Wrong number of values for parameter \"Server\""; + EXPECT_STREQ("fakeSrv", testHdr.GetValue("Server").c_str()) << "Wrong parameter value"; + + /* Check value trimming */ + testHdr.AddParam("X-TestParam", " testValue1"); + EXPECT_STREQ("testValue1", testHdr.GetValue("X-TestParam").c_str()) << "Wrong parameter value"; + testHdr.AddParam("X-TestParam", "\ttestValue2 and more \t "); + EXPECT_STREQ("testValue2 and more", testHdr.GetValue("X-TestParam").c_str()) << "Wrong parameter value"; + + /* Empty name or value */ + testHdr.Clear(); + testHdr.AddParam("X-TestParam", " "); + EXPECT_TRUE(testHdr.GetHeader().empty()) << "Parameter with empty value was added"; + testHdr.AddParam("\t\t", "value"); + EXPECT_TRUE(testHdr.GetHeader().empty()); + testHdr.AddParam(" ", "\t"); + EXPECT_TRUE(testHdr.GetHeader().empty()); +} + +TEST(TestHttpHeader, GetMimeType) +{ + CHttpHeader testHdr; + + /* General functionality */ + EXPECT_TRUE(testHdr.GetMimeType().empty()) << "Newly created object has non-empty MIME-type"; + testHdr.Parse(CHECK_PROT_LINE_200 "\r\nServer: nginx/1.4.4\r\n\r\n"); + EXPECT_TRUE(testHdr.GetMimeType().empty()) << "Non-empty MIME-type for header without MIME-type"; + testHdr.Parse(CHECK_HEADER_SMPL); + EXPECT_STREQ("text/html", testHdr.GetMimeType().c_str()) << "Wrong MIME-type"; + testHdr.Parse(CHECK_HEADER_L1); + EXPECT_STREQ("text/html", testHdr.GetMimeType().c_str()) << "Wrong MIME-type"; + testHdr.Parse(CHECK_HEADER_L2); + EXPECT_STREQ("text/xml", testHdr.GetMimeType().c_str()) << "Wrong MIME-type"; + testHdr.Parse(CHECK_HEADER_R); + EXPECT_STREQ("text/html", testHdr.GetMimeType().c_str()) << "Wrong MIME-type"; + + /* Overwrite by AddParam */ + testHdr.AddParam(CHECK_CNT_TYPE_NAME, CHECK_CONTENT_TYPE_TEXT); + EXPECT_STREQ(CHECK_CONTENT_TYPE_TEXT, testHdr.GetMimeType().c_str()) << "MIME-type was not overwritten by \"AddParam\""; + + /* Correct trimming */ + testHdr.AddParam(CHECK_CNT_TYPE_NAME, " "CHECK_CONTENT_TYPE_TEXT " \t ;foo=bar"); + EXPECT_STREQ(CHECK_CONTENT_TYPE_TEXT, testHdr.GetMimeType().c_str()) << "MIME-type is not trimmed correctly"; +} + + +TEST(TestHttpHeader, GetCharset) +{ + CHttpHeader testHdr; + + /* General functionality */ + EXPECT_TRUE(testHdr.GetCharset().empty()) << "Newly created object has non-empty charset"; + testHdr.Parse(CHECK_PROT_LINE_200 "\r\nServer: nginx/1.4.4\r\n\r\n"); + EXPECT_TRUE(testHdr.GetCharset().empty()) << "Non-empty charset for header without charset"; + testHdr.Parse(CHECK_HEADER_SMPL); + EXPECT_TRUE(testHdr.GetCharset().empty()) << "Non-empty charset for header without charset"; + testHdr.Parse(CHECK_HEADER_L1); + EXPECT_STREQ("WINDOWS-1251", testHdr.GetCharset().c_str()) << "Wrong charset value"; + testHdr.Parse(CHECK_HEADER_L2); + EXPECT_STREQ("UTF-8", testHdr.GetCharset().c_str()) << "Wrong charset value"; + + /* Overwrite by AddParam */ + testHdr.AddParam(CHECK_CNT_TYPE_NAME, CHECK_CONTENT_TYPE_TEXT "; charset=WINDOWS-1252"); + EXPECT_STREQ("WINDOWS-1252", testHdr.GetCharset().c_str()) << "Charset was not overwritten by \"AddParam\""; + + /* Correct trimming */ + testHdr.AddParam(CHECK_CNT_TYPE_NAME, "text/plain;charset=WINDOWS-1251"); + EXPECT_STREQ("WINDOWS-1251", testHdr.GetCharset().c_str()) << "Wrong charset value"; + testHdr.AddParam(CHECK_CNT_TYPE_NAME, "text/plain ;\tcharset=US-AScII\t"); + EXPECT_STREQ("US-ASCII", testHdr.GetCharset().c_str()) << "Wrong charset value"; + testHdr.AddParam(CHECK_CNT_TYPE_NAME, "text/html ; \tcharset=\"uTF-8\"\t"); + EXPECT_STREQ("UTF-8", testHdr.GetCharset().c_str()) << "Wrong charset value"; + testHdr.AddParam(CHECK_CNT_TYPE_NAME, " \ttext/xml\t;\tcharset=uTF-16 "); + EXPECT_STREQ("UTF-16", testHdr.GetCharset().c_str()) << "Wrong charset value"; +} diff --git a/src/utils/test/TestHttpParser.cpp b/src/utils/test/TestHttpParser.cpp new file mode 100644 index 0000000000..75977f617b --- /dev/null +++ b/src/utils/test/TestHttpParser.cpp @@ -0,0 +1,62 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/HttpParser.h" +#include "utils/StdString.h" + +#include "gtest/gtest.h" + +TEST(TestHttpParser, General) +{ + HttpParser a; + CStdString str = "POST /path/script.cgi HTTP/1.0\r\n" + "From: amejia@xbmc.org\r\n" + "User-Agent: XBMC/snapshot (compatible; MSIE 5.5; Windows NT" + " 4.0)\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: 35\r\n" + "\r\n" + "home=amejia&favorite+flavor=orange\r\n"; + CStdString refstr, varstr; + + EXPECT_EQ(a.Done, a.addBytes(str.c_str(), str.length())); + + refstr = "POST"; + varstr = a.getMethod(); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "/path/script.cgi"; + varstr = a.getUri(); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = ""; + varstr = a.getQueryString(); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "home=amejia&favorite+flavor=orange\r\n"; + varstr = a.getBody(); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "application/x-www-form-urlencoded"; + varstr = a.getValue("content-type"); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + EXPECT_EQ((unsigned)35, a.getContentLength()); +} diff --git a/src/utils/test/TestHttpResponse.cpp b/src/utils/test/TestHttpResponse.cpp new file mode 100644 index 0000000000..d0fadeb649 --- /dev/null +++ b/src/utils/test/TestHttpResponse.cpp @@ -0,0 +1,59 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/HttpResponse.h" + +#include "gtest/gtest.h" + +TEST(TestHttpResponse, General) +{ + CHttpResponse a(HTTP::POST, HTTP::OK); + char *buf = new char(100); + std::string response, content, refstr; + unsigned int size; + memset(buf, 0, sizeof(*buf)); + + a.AddHeader("date", "Sun, 01 Jul 2012 00:00:00 -0400"); + a.AddHeader("content-type", "text/html"); + content = "<html>\r\n" + " <body>\r\n" + " <h1>XBMC TestHttpResponse Page</h1>\r\n" + " <p>blah blah blah</p>\r\n" + " </body>\r\n" + "</html>\r\n"; + a.SetContent(content.c_str(), content.length()); + + size = a.Create(buf); + EXPECT_EQ((unsigned int)210, size); + + response = buf; + refstr = "HTTP/1.1 200 OK\r\n" + "date: Sun, 01 Jul 2012 00:00:00 -0400\r\n" + "content-type: text/html\r\n" + "Content-Length: 106\r\n" + "\r\n" + "<html>\r\n" + " <body>\r\n" + " <h1>XBMC TestHttpResponse Page</h1>\r\n" + " <p>blah blah blah</p>\r\n" + " </body>\r\n" + "</html>\r\n"; + EXPECT_STREQ(refstr.c_str(), response.c_str()); +} diff --git a/src/utils/test/TestJSONVariantParser.cpp b/src/utils/test/TestJSONVariantParser.cpp new file mode 100644 index 0000000000..48d12cee37 --- /dev/null +++ b/src/utils/test/TestJSONVariantParser.cpp @@ -0,0 +1,33 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/JSONVariantParser.h" + +#include "gtest/gtest.h" + +TEST(TestJSONVariantParser, Parse) +{ + CVariant variant; + unsigned char buf[100]; + + memset(buf, 0, sizeof(buf)); + variant = CJSONVariantParser::Parse(buf, sizeof(buf)); + EXPECT_TRUE(variant.isNull()); +} diff --git a/src/utils/test/TestJSONVariantWriter.cpp b/src/utils/test/TestJSONVariantWriter.cpp new file mode 100644 index 0000000000..cab124e74f --- /dev/null +++ b/src/utils/test/TestJSONVariantWriter.cpp @@ -0,0 +1,32 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/JSONVariantWriter.h" + +#include "gtest/gtest.h" + +TEST(TestJSONVariantWriter, Write) +{ + CVariant variant; + std::string str; + + str = CJSONVariantWriter::Write(variant, false); + EXPECT_STREQ("null\n", str.c_str()); +} diff --git a/src/utils/test/TestJobManager.cpp b/src/utils/test/TestJobManager.cpp new file mode 100644 index 0000000000..073dcea270 --- /dev/null +++ b/src/utils/test/TestJobManager.cpp @@ -0,0 +1,179 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/JobManager.h" +#include "settings/Settings.h" +#include "utils/SystemInfo.h" + +#include "gtest/gtest.h" + +/* CSysInfoJob::GetInternetState() will test for network connectivity. */ +class TestJobManager : public testing::Test +{ +protected: + TestJobManager() + { + /* TODO + CSettingsCategory* net = CSettings::Get().AddCategory(4, "network", 798); + CSettings::Get().AddBool(net, "network.usehttpproxy", 708, false); + CSettings::Get().AddString(net, "network.httpproxyserver", 706, "", + EDIT_CONTROL_INPUT); + CSettings::Get().AddString(net, "network.httpproxyport", 730, "8080", + EDIT_CONTROL_NUMBER_INPUT, false, 707); + CSettings::Get().AddString(net, "network.httpproxyusername", 1048, "", + EDIT_CONTROL_INPUT); + CSettings::Get().AddString(net, "network.httpproxypassword", 733, "", + EDIT_CONTROL_HIDDEN_INPUT,true,733); + CSettings::Get().AddInt(net, "network.bandwidth", 14041, 0, 0, 512, 100*1024, + SPIN_CONTROL_INT_PLUS, 14048, 351); + */ + } + + ~TestJobManager() + { + /* Always cancel jobs test completion */ + CJobManager::GetInstance().CancelJobs(); + CJobManager::GetInstance().Restart(); + CSettings::Get().Unload(); + } +}; + +TEST_F(TestJobManager, AddJob) +{ + CJob* job = new CSysInfoJob(); + CJobManager::GetInstance().AddJob(job, NULL); +} + +TEST_F(TestJobManager, CancelJob) +{ + unsigned int id; + CJob* job = new CSysInfoJob(); + id = CJobManager::GetInstance().AddJob(job, NULL); + CJobManager::GetInstance().CancelJob(id); +} + +namespace +{ +struct JobControlPackage +{ + JobControlPackage() : + ready (false) + { + // We're not ready to wait yet + jobCreatedMutex.lock(); + } + + ~JobControlPackage() + { + jobCreatedMutex.unlock(); + } + + bool ready; + XbmcThreads::ConditionVariable jobCreatedCond; + CCriticalSection jobCreatedMutex; +}; + +class BroadcastingJob : + public CJob +{ +public: + + BroadcastingJob(JobControlPackage &package) : + m_package(package), + m_finish(false) + { + } + + void FinishAndStopBlocking() + { + CSingleLock lock(m_blockMutex); + + m_finish = true; + m_block.notifyAll(); + } + + const char * GetType() const + { + return "BroadcastingJob"; + } + + bool DoWork() + { + { + CSingleLock lock(m_package.jobCreatedMutex); + + m_package.ready = true; + m_package.jobCreatedCond.notifyAll(); + } + + CSingleLock blockLock(m_blockMutex); + + // Block until we're told to go away + while (!m_finish) + m_block.wait(m_blockMutex); + return true; + } + +private: + + JobControlPackage &m_package; + + XbmcThreads::ConditionVariable m_block; + CCriticalSection m_blockMutex; + bool m_finish; +}; + +BroadcastingJob * +WaitForJobToStartProcessing(CJob::PRIORITY priority, JobControlPackage &package) +{ + BroadcastingJob* job = new BroadcastingJob(package); + CJobManager::GetInstance().AddJob(job, NULL, priority); + + // We're now ready to wait, wait and then unblock once ready + while (!package.ready) + package.jobCreatedCond.wait(package.jobCreatedMutex); + + return job; +} +} + +TEST_F(TestJobManager, PauseLowPriorityJob) +{ + JobControlPackage package; + BroadcastingJob *job (WaitForJobToStartProcessing(CJob::PRIORITY_LOW_PAUSABLE, package)); + + EXPECT_TRUE(CJobManager::GetInstance().IsProcessing(CJob::PRIORITY_LOW_PAUSABLE)); + CJobManager::GetInstance().PauseJobs(); + EXPECT_FALSE(CJobManager::GetInstance().IsProcessing(CJob::PRIORITY_LOW_PAUSABLE)); + CJobManager::GetInstance().UnPauseJobs(); + EXPECT_TRUE(CJobManager::GetInstance().IsProcessing(CJob::PRIORITY_LOW_PAUSABLE)); + + job->FinishAndStopBlocking(); +} + +TEST_F(TestJobManager, IsProcessing) +{ + JobControlPackage package; + BroadcastingJob *job (WaitForJobToStartProcessing(CJob::PRIORITY_LOW_PAUSABLE, package)); + + EXPECT_EQ(0, CJobManager::GetInstance().IsProcessing("")); + + job->FinishAndStopBlocking(); +} diff --git a/src/utils/test/TestLabelFormatter.cpp b/src/utils/test/TestLabelFormatter.cpp new file mode 100644 index 0000000000..d05d0238e1 --- /dev/null +++ b/src/utils/test/TestLabelFormatter.cpp @@ -0,0 +1,91 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/LabelFormatter.h" +#include "filesystem/File.h" +#include "settings/Settings.h" +#include "FileItem.h" + +#include "test/TestUtils.h" + +#include "gtest/gtest.h" + +/* Set default settings used by CLabelFormatter. */ +class TestLabelFormatter : public testing::Test +{ +protected: + TestLabelFormatter() + { + /* TODO + CSettingsCategory* fl = CSettings::Get().AddCategory(7, "filelists", 14081); + CSettings::Get().AddBool(fl, "filelists.showparentdiritems", 13306, true); + CSettings::Get().AddBool(fl, "filelists.showextensions", 497, true); + CSettings::Get().AddBool(fl, "filelists.ignorethewhensorting", 13399, true); + CSettings::Get().AddBool(fl, "filelists.allowfiledeletion", 14071, false); + CSettings::Get().AddBool(fl, "filelists.showaddsourcebuttons", 21382, true); + CSettings::Get().AddBool(fl, "filelists.showhidden", 21330, false); + */ + } + + ~TestLabelFormatter() + { + CSettings::Get().Unload(); + } +}; + +TEST_F(TestLabelFormatter, FormatLabel) +{ + XFILE::CFile *tmpfile; + std::string tmpfilepath, destpath; + LABEL_MASKS labelMasks; + CLabelFormatter formatter("", labelMasks.m_strLabel2File); + + ASSERT_TRUE((tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + + CFileItemPtr item(new CFileItem(tmpfilepath)); + item->SetPath(tmpfilepath); + item->m_bIsFolder = false; + item->Select(true); + + formatter.FormatLabel(item.get()); + + EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile)); +} + +TEST_F(TestLabelFormatter, FormatLabel2) +{ + XFILE::CFile *tmpfile; + std::string tmpfilepath, destpath; + LABEL_MASKS labelMasks; + CLabelFormatter formatter("", labelMasks.m_strLabel2File); + + ASSERT_TRUE((tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + + CFileItemPtr item(new CFileItem(tmpfilepath)); + item->SetPath(tmpfilepath); + item->m_bIsFolder = false; + item->Select(true); + + formatter.FormatLabel2(item.get()); + + EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile)); +} diff --git a/src/utils/test/TestLangCodeExpander.cpp b/src/utils/test/TestLangCodeExpander.cpp new file mode 100644 index 0000000000..a6c2db9557 --- /dev/null +++ b/src/utils/test/TestLangCodeExpander.cpp @@ -0,0 +1,41 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/LangCodeExpander.h" + +#include "gtest/gtest.h" + +TEST(TestLangCodeExpander, ConvertTwoToThreeCharCode) +{ + std::string refstr, varstr; + + refstr = "eng"; + g_LangCodeExpander.ConvertTwoToThreeCharCode(varstr, "en"); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestLangCodeExpander, ConvertToThreeCharCode) +{ + std::string refstr, varstr; + + refstr = "eng"; + g_LangCodeExpander.ConvertToThreeCharCode(varstr, "en"); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} diff --git a/src/utils/test/TestMathUtils.cpp b/src/utils/test/TestMathUtils.cpp new file mode 100644 index 0000000000..009c4fdf1a --- /dev/null +++ b/src/utils/test/TestMathUtils.cpp @@ -0,0 +1,71 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/MathUtils.h" + +#include "gtest/gtest.h" + +TEST(TestMathUtils, round_int) +{ + int refval, varval, i; + + for (i = -8; i < 8; ++i) + { + double d = 0.25*i; + refval = (i < 0) ? (i - 1) / 4 : (i + 2) / 4; + varval = MathUtils::round_int(d); + EXPECT_EQ(refval, varval); + } +} + +TEST(TestMathUtils, truncate_int) +{ + int refval, varval, i; + + for (i = -8; i < 8; ++i) + { + double d = 0.25*i; + refval = i / 4; + varval = MathUtils::truncate_int(d); + EXPECT_EQ(refval, varval); + } +} + +TEST(TestMathUtils, abs) +{ + int64_t refval, varval; + + refval = 5; + varval = MathUtils::abs(-5); + EXPECT_EQ(refval, varval); +} + +TEST(TestMathUtils, bitcount) +{ + unsigned refval, varval; + + refval = 10; + varval = MathUtils::bitcount(0x03FF); + EXPECT_EQ(refval, varval); + + refval = 8; + varval = MathUtils::bitcount(0x2AD5); + EXPECT_EQ(refval, varval); +} diff --git a/src/utils/test/TestMime.cpp b/src/utils/test/TestMime.cpp new file mode 100644 index 0000000000..34cef46a26 --- /dev/null +++ b/src/utils/test/TestMime.cpp @@ -0,0 +1,41 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/Mime.h" +#include "FileItem.h" + +#include "gtest/gtest.h" + +TEST(TestMime, GetMimeType_string) +{ + EXPECT_STREQ("video/avi", CMime::GetMimeType("avi").c_str()); + EXPECT_STRNE("video/x-msvideo", CMime::GetMimeType("avi").c_str()); + EXPECT_STRNE("video/avi", CMime::GetMimeType("xvid").c_str()); +} + +TEST(TestMime, GetMimeType_CFileItem) +{ + std::string refstr, varstr; + CFileItem item("testfile.mp4", false); + + refstr = "video/mp4"; + varstr = CMime::GetMimeType(item); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} diff --git a/src/utils/test/TestPOUtils.cpp b/src/utils/test/TestPOUtils.cpp new file mode 100644 index 0000000000..e28830d65d --- /dev/null +++ b/src/utils/test/TestPOUtils.cpp @@ -0,0 +1,59 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/POUtils.h" + +#include "test/TestUtils.h" + +#include "gtest/gtest.h" + +TEST(TestPOUtils, General) +{ + CPODocument a; + + EXPECT_TRUE(a.LoadFile(XBMC_REF_FILE_PATH("/language/Spanish/strings.po"))); + + EXPECT_TRUE(a.GetNextEntry()); + EXPECT_EQ(ID_FOUND, a.GetEntryType()); + EXPECT_EQ((uint32_t)0, a.GetEntryID()); + a.ParseEntry(false); + EXPECT_STREQ("", a.GetMsgctxt().c_str()); + EXPECT_STREQ("Programs", a.GetMsgid().c_str()); + EXPECT_STREQ("Programas", a.GetMsgstr().c_str()); + EXPECT_STREQ("", a.GetPlurMsgstr(0).c_str()); + + EXPECT_TRUE(a.GetNextEntry()); + EXPECT_EQ(ID_FOUND, a.GetEntryType()); + EXPECT_EQ((uint32_t)1, a.GetEntryID()); + a.ParseEntry(false); + EXPECT_STREQ("", a.GetMsgctxt().c_str()); + EXPECT_STREQ("Pictures", a.GetMsgid().c_str()); + EXPECT_STREQ("Imágenes", a.GetMsgstr().c_str()); + EXPECT_STREQ("", a.GetPlurMsgstr(0).c_str()); + + EXPECT_TRUE(a.GetNextEntry()); + EXPECT_EQ(ID_FOUND, a.GetEntryType()); + EXPECT_EQ((uint32_t)2, a.GetEntryID()); + a.ParseEntry(false); + EXPECT_STREQ("", a.GetMsgctxt().c_str()); + EXPECT_STREQ("Music", a.GetMsgid().c_str()); + EXPECT_STREQ("Música", a.GetMsgstr().c_str()); + EXPECT_STREQ("", a.GetPlurMsgstr(0).c_str()); +} diff --git a/src/utils/test/TestPerformanceSample.cpp b/src/utils/test/TestPerformanceSample.cpp new file mode 100644 index 0000000000..be13685fff --- /dev/null +++ b/src/utils/test/TestPerformanceSample.cpp @@ -0,0 +1,53 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/PerformanceSample.h" + +#include "gtest/gtest.h" + +class MyCPerformanceSample : public CPerformanceSample +{ +public: + MyCPerformanceSample(const std::string &statName, bool bCheckWhenDone = true) + : CPerformanceSample(statName, bCheckWhenDone) + {} + std::string getStatName() { return m_statName; } + bool getCheckWhenDown() { return m_bCheckWhenDone; } + int64_t getStart() { return m_tmStart; } + int64_t getFreq() { return m_tmFreq; } +}; + +TEST(TestPerformanceSample, General) +{ + MyCPerformanceSample a("test"); + + a.Reset(); + a.CheckPoint(); + + EXPECT_STREQ("test", a.getStatName().c_str()); + EXPECT_TRUE(a.getCheckWhenDown()); + EXPECT_GT(a.getStart(), (int64_t)0); + EXPECT_GT(a.getFreq(), (int64_t)0); + + std::cout << "Estimated Error: " << + testing::PrintToString(a.GetEstimatedError()) << std::endl; + std::cout << "Start: " << testing::PrintToString(a.getStart()) << std::endl; + std::cout << "Frequency: " << testing::PrintToString(a.getFreq()) << std::endl; +} diff --git a/src/utils/test/TestRegExp.cpp b/src/utils/test/TestRegExp.cpp new file mode 100644 index 0000000000..e4231afa29 --- /dev/null +++ b/src/utils/test/TestRegExp.cpp @@ -0,0 +1,183 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +/* TODO: gtest/gtest.h needs to come in before utils/RegExp.h. + * Investigate why. + */ +#include "gtest/gtest.h" + +#include "utils/RegExp.h" +#include "utils/log.h" +#include "utils/StdString.h" +#include "filesystem/File.h" +#include "filesystem/SpecialProtocol.h" +#include "utils/StringUtils.h" +#include "CompileInfo.h" + +TEST(TestRegExp, RegFind) +{ + CRegExp regex; + + EXPECT_TRUE(regex.RegComp("^Test.*")); + EXPECT_EQ(0, regex.RegFind("Test string.")); + + EXPECT_TRUE(regex.RegComp("^string.*")); + EXPECT_EQ(-1, regex.RegFind("Test string.")); +} + +TEST(TestRegExp, GetReplaceString) +{ + CRegExp regex; + + EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\.")); + EXPECT_EQ(0, regex.RegFind("Test string.")); + EXPECT_STREQ("string", regex.GetReplaceString("\\2").c_str()); +} + +TEST(TestRegExp, GetFindLen) +{ + CRegExp regex; + + EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\.")); + EXPECT_EQ(0, regex.RegFind("Test string.")); + EXPECT_EQ(12, regex.GetFindLen()); +} + +TEST(TestRegExp, GetSubCount) +{ + CRegExp regex; + + EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\.")); + EXPECT_EQ(0, regex.RegFind("Test string.")); + EXPECT_EQ(2, regex.GetSubCount()); +} + +TEST(TestRegExp, GetSubStart) +{ + CRegExp regex; + + EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\.")); + EXPECT_EQ(0, regex.RegFind("Test string.")); + EXPECT_EQ(0, regex.GetSubStart(0)); + EXPECT_EQ(0, regex.GetSubStart(1)); + EXPECT_EQ(5, regex.GetSubStart(2)); +} + +TEST(TestRegExp, GetCaptureTotal) +{ + CRegExp regex; + + EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\.")); + EXPECT_EQ(0, regex.RegFind("Test string.")); + EXPECT_EQ(2, regex.GetCaptureTotal()); +} + +TEST(TestRegExp, GetMatch) +{ + CRegExp regex; + + EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\.")); + EXPECT_EQ(0, regex.RegFind("Test string.")); + EXPECT_STREQ("Test string.", regex.GetMatch(0).c_str()); + EXPECT_STREQ("Test", regex.GetMatch(1).c_str()); + EXPECT_STREQ("string", regex.GetMatch(2).c_str()); +} + +TEST(TestRegExp, GetPattern) +{ + CRegExp regex; + + EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\.")); + EXPECT_STREQ("^(Test)\\s*(.*)\\.", regex.GetPattern().c_str()); +} + +TEST(TestRegExp, GetNamedSubPattern) +{ + CRegExp regex; + std::string match; + + EXPECT_TRUE(regex.RegComp("^(?<first>Test)\\s*(?<second>.*)\\.")); + EXPECT_EQ(0, regex.RegFind("Test string.")); + EXPECT_TRUE(regex.GetNamedSubPattern("first", match)); + EXPECT_STREQ("Test", match.c_str()); + EXPECT_TRUE(regex.GetNamedSubPattern("second", match)); + EXPECT_STREQ("string", match.c_str()); +} + +TEST(TestRegExp, operatorEqual) +{ + CRegExp regex, regexcopy; + std::string match; + + EXPECT_TRUE(regex.RegComp("^(?<first>Test)\\s*(?<second>.*)\\.")); + regexcopy = regex; + EXPECT_EQ(0, regexcopy.RegFind("Test string.")); + EXPECT_TRUE(regexcopy.GetNamedSubPattern("first", match)); + EXPECT_STREQ("Test", match.c_str()); + EXPECT_TRUE(regexcopy.GetNamedSubPattern("second", match)); + EXPECT_STREQ("string", match.c_str()); +} + +class TestRegExpLog : public testing::Test +{ +protected: + TestRegExpLog(){} + ~TestRegExpLog() + { + CLog::Close(); + } +}; + +TEST_F(TestRegExpLog, DumpOvector) +{ + CRegExp regex; + CStdString logfile, logstring; + char buf[100]; + unsigned int bytesread; + XFILE::CFile file; + + std::string appName = CCompileInfo::GetAppName(); + StringUtils::ToLower(appName); + logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log"; + EXPECT_TRUE(CLog::Init(CSpecialProtocol::TranslatePath("special://temp/").c_str())); + EXPECT_TRUE(XFILE::CFile::Exists(logfile)); + + EXPECT_TRUE(regex.RegComp("^(?<first>Test)\\s*(?<second>.*)\\.")); + EXPECT_EQ(0, regex.RegFind("Test string.")); + regex.DumpOvector(LOGDEBUG); + CLog::Close(); + + EXPECT_TRUE(file.Open(logfile)); + while ((bytesread = file.Read(buf, sizeof(buf) - 1)) > 0) + { + buf[bytesread] = '\0'; + logstring.append(buf); + } + file.Close(); + EXPECT_FALSE(logstring.empty()); + + EXPECT_STREQ("\xEF\xBB\xBF", logstring.substr(0, 3).c_str()); + + EXPECT_TRUE(regex.RegComp(".*DEBUG: regexp ovector=\\{\\[0,12\\],\\[0,4\\]," + "\\[5,11\\]\\}.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + + EXPECT_TRUE(XFILE::CFile::Delete(logfile)); +} diff --git a/src/utils/test/TestRingBuffer.cpp b/src/utils/test/TestRingBuffer.cpp new file mode 100644 index 0000000000..37a14b9935 --- /dev/null +++ b/src/utils/test/TestRingBuffer.cpp @@ -0,0 +1,45 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/RingBuffer.h" + +#include "gtest/gtest.h" + +TEST(TestRingBuffer, General) +{ + CRingBuffer a; + char data[20]; + unsigned int i; + + EXPECT_TRUE(a.Create(20)); + EXPECT_EQ((unsigned int)20, a.getSize()); + memset(data, 0, sizeof(data)); + for (i = 0; i < a.getSize(); i++) + EXPECT_TRUE(a.WriteData(data, 1)); + a.Clear(); + + memcpy(data, "0123456789", sizeof("0123456789")); + EXPECT_TRUE(a.WriteData(data, sizeof("0123456789"))); + EXPECT_STREQ("0123456789", a.getBuffer()); + + memset(data, 0, sizeof(data)); + EXPECT_TRUE(a.ReadData(data, 5)); + EXPECT_STREQ("01234", data); +} diff --git a/src/utils/test/TestScraperParser.cpp b/src/utils/test/TestScraperParser.cpp new file mode 100644 index 0000000000..de2592d7a6 --- /dev/null +++ b/src/utils/test/TestScraperParser.cpp @@ -0,0 +1,39 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/ScraperParser.h" + +#include "test/TestUtils.h" + +#include "gtest/gtest.h" + +TEST(TestScraperParser, General) +{ + CScraperParser a; + + a.Clear(); + EXPECT_TRUE( + a.Load(XBMC_REF_FILE_PATH("/addons/metadata.themoviedb.org/tmdb.xml"))); + + EXPECT_STREQ( + XBMC_REF_FILE_PATH("/addons/metadata.themoviedb.org/tmdb.xml").c_str(), + a.GetFilename().c_str()); + EXPECT_STREQ("UTF-8", a.GetSearchStringEncoding().c_str()); +} diff --git a/src/utils/test/TestScraperUrl.cpp b/src/utils/test/TestScraperUrl.cpp new file mode 100644 index 0000000000..8f49060094 --- /dev/null +++ b/src/utils/test/TestScraperUrl.cpp @@ -0,0 +1,45 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/ScraperUrl.h" + +#include "gtest/gtest.h" + +TEST(TestScraperUrl, General) +{ + CScraperUrl a; + std::string xmlstring; + + xmlstring = "<data spoof=\"blah\" gzip=\"yes\">\n" + " <someurl>\n" + " </someurl>\n" + " <someotherurl>\n" + " </someotherurl>\n" + "</data>\n"; + EXPECT_TRUE(a.ParseString(xmlstring)); + + EXPECT_STREQ("blah", a.GetFirstThumb().m_spoof.c_str()); + EXPECT_STREQ("someurl", a.GetFirstThumb().m_url.c_str()); + EXPECT_STREQ("", a.GetFirstThumb().m_cache.c_str()); + EXPECT_EQ(CScraperUrl::URL_TYPE_GENERAL, a.GetFirstThumb().m_type); + EXPECT_FALSE(a.GetFirstThumb().m_post); + EXPECT_TRUE(a.GetFirstThumb().m_isgz); + EXPECT_EQ(-1, a.GetFirstThumb().m_season); +} diff --git a/src/utils/test/TestSortUtils.cpp b/src/utils/test/TestSortUtils.cpp new file mode 100644 index 0000000000..659ff527b6 --- /dev/null +++ b/src/utils/test/TestSortUtils.cpp @@ -0,0 +1,133 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/SortUtils.h" +#include "utils/Variant.h" + +#include "gtest/gtest.h" + +TEST(TestSortUtils, Sort_SortBy) +{ + SortItems items; + + CVariant variant1("M Artist"); + SortItemPtr item1(new SortItem()); + (*item1)[FieldArtist] = variant1; + CVariant variant2("B Artist"); + SortItemPtr item2(new SortItem()); + (*item2)[FieldArtist] = variant2; + CVariant variant3("R Artist"); + SortItemPtr item3(new SortItem()); + (*item3)[FieldArtist] = variant3; + CVariant variant4("R Artist"); + SortItemPtr item4(new SortItem()); + (*item4)[FieldArtist] = variant4; + CVariant variant5("I Artist"); + SortItemPtr item5(new SortItem()); + (*item5)[FieldArtist] = variant5; + CVariant variant6("A Artist"); + SortItemPtr item6(new SortItem()); + (*item6)[FieldArtist] = variant6; + CVariant variant7("G Artist"); + SortItemPtr item7(new SortItem()); + (*item7)[FieldArtist] = variant7; + + items.push_back(item1); + items.push_back(item2); + items.push_back(item3); + items.push_back(item4); + items.push_back(item5); + items.push_back(item6); + items.push_back(item7); + + SortUtils::Sort(SortByArtist, SortOrderAscending, SortAttributeNone, items); + + EXPECT_STREQ("A Artist", (*items.at(0))[FieldArtist].asString().c_str()); + EXPECT_STREQ("B Artist", (*items.at(1))[FieldArtist].asString().c_str()); + EXPECT_STREQ("G Artist", (*items.at(2))[FieldArtist].asString().c_str()); + EXPECT_STREQ("I Artist", (*items.at(3))[FieldArtist].asString().c_str()); + EXPECT_STREQ("M Artist", (*items.at(4))[FieldArtist].asString().c_str()); + EXPECT_STREQ("R Artist", (*items.at(5))[FieldArtist].asString().c_str()); + EXPECT_STREQ("R Artist", (*items.at(6))[FieldArtist].asString().c_str()); +} + +TEST(TestSortUtils, Sort_SortDescription) +{ + SortItems items; + + CVariant variant1("M Artist"); + SortItemPtr item1(new SortItem()); + (*item1)[FieldArtist] = variant1; + CVariant variant2("B Artist"); + SortItemPtr item2(new SortItem()); + (*item2)[FieldArtist] = variant2; + CVariant variant3("R Artist"); + SortItemPtr item3(new SortItem()); + (*item3)[FieldArtist] = variant3; + CVariant variant4("R Artist"); + SortItemPtr item4(new SortItem()); + (*item4)[FieldArtist] = variant4; + CVariant variant5("I Artist"); + SortItemPtr item5(new SortItem()); + (*item5)[FieldArtist] = variant5; + CVariant variant6("A Artist"); + SortItemPtr item6(new SortItem()); + (*item6)[FieldArtist] = variant6; + CVariant variant7("G Artist"); + SortItemPtr item7(new SortItem()); + (*item7)[FieldArtist] = variant7; + + items.push_back(item1); + items.push_back(item2); + items.push_back(item3); + items.push_back(item4); + items.push_back(item5); + items.push_back(item6); + items.push_back(item7); + + SortDescription desc; + desc.sortBy = SortByArtist; + SortUtils::Sort(desc, items); + + EXPECT_STREQ("A Artist", (*items.at(0))[FieldArtist].asString().c_str()); + EXPECT_STREQ("B Artist", (*items.at(1))[FieldArtist].asString().c_str()); + EXPECT_STREQ("G Artist", (*items.at(2))[FieldArtist].asString().c_str()); + EXPECT_STREQ("I Artist", (*items.at(3))[FieldArtist].asString().c_str()); + EXPECT_STREQ("M Artist", (*items.at(4))[FieldArtist].asString().c_str()); + EXPECT_STREQ("R Artist", (*items.at(5))[FieldArtist].asString().c_str()); + EXPECT_STREQ("R Artist", (*items.at(6))[FieldArtist].asString().c_str()); +} + +TEST(TestSortUtils, GetFieldsForSorting) +{ + Fields fields; + + fields = SortUtils::GetFieldsForSorting(SortByArtist); + Fields::iterator it; + it = fields.find(FieldAlbum); + EXPECT_EQ(FieldAlbum, *it); + it = fields.find(FieldArtist); + EXPECT_EQ(FieldArtist, *it); + it = fields.find(FieldYear); + EXPECT_EQ(FieldYear, *it); + it = fields.find(FieldTrackNumber); + EXPECT_EQ(FieldTrackNumber, *it); + EXPECT_EQ((unsigned int)4, fields.size()); +} diff --git a/src/utils/test/TestStdString.cpp b/src/utils/test/TestStdString.cpp new file mode 100644 index 0000000000..c613db28b3 --- /dev/null +++ b/src/utils/test/TestStdString.cpp @@ -0,0 +1,50 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/StdString.h" + +#include "gtest/gtest.h" + +TEST(TestStdString, CStdString) +{ + CStdString ref, var; + + ref = "CStdString test"; + var = ref; + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST(TestStdString, CStdStringA) +{ + CStdStringA ref, var; + + ref = "CStdStringA test"; + var = ref; + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST(TestStdString, CStdStringW) +{ + CStdStringW ref, var; + + ref = L"CStdStringW test"; + var = ref; + EXPECT_STREQ(ref.c_str(), var.c_str()); +} diff --git a/src/utils/test/TestStopwatch.cpp b/src/utils/test/TestStopwatch.cpp new file mode 100644 index 0000000000..4f8a607b91 --- /dev/null +++ b/src/utils/test/TestStopwatch.cpp @@ -0,0 +1,118 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/Stopwatch.h" +#include "threads/Thread.h" + +#include "gtest/gtest.h" + +class CTestStopWatchThread : public CThread +{ +public: + CTestStopWatchThread() : + CThread("TestStopWatch"){} +}; + +TEST(TestStopWatch, Start) +{ + CStopWatch a; + CTestStopWatchThread thread; + + EXPECT_FALSE(a.IsRunning()); + EXPECT_EQ(0.0f, a.GetElapsedSeconds()); + EXPECT_EQ(0.0f, a.GetElapsedMilliseconds()); + + std::cout << "Calling Start()" << std::endl; + a.Start(); + thread.Sleep(1000); + EXPECT_TRUE(a.IsRunning()); + std::cout << "Elapsed Seconds: " << a.GetElapsedSeconds() << std::endl; + std::cout << "Elapsed Milliseconds: " << a.GetElapsedMilliseconds() << std::endl; +} + +TEST(TestStopWatch, Stop) +{ + CStopWatch a; + CTestStopWatchThread thread; + + EXPECT_FALSE(a.IsRunning()); + EXPECT_EQ(0.0f, a.GetElapsedSeconds()); + EXPECT_EQ(0.0f, a.GetElapsedMilliseconds()); + + std::cout << "Calling Start()" << std::endl; + a.Start(); + thread.Sleep(1000); + EXPECT_TRUE(a.IsRunning()); + std::cout << "Elapsed Seconds: " << a.GetElapsedSeconds() << std::endl; + std::cout << "Elapsed Milliseconds: " << a.GetElapsedMilliseconds() << std::endl; + + a.Stop(); + EXPECT_FALSE(a.IsRunning()); + EXPECT_GT(a.GetElapsedSeconds(), 0.0f); + EXPECT_GT(a.GetElapsedMilliseconds(), 0.0f); +} + +TEST(TestStopWatch, StartZero) +{ + CStopWatch a; + CTestStopWatchThread thread; + + EXPECT_FALSE(a.IsRunning()); + EXPECT_EQ(0.0f, a.GetElapsedSeconds()); + EXPECT_EQ(0.0f, a.GetElapsedMilliseconds()); + + std::cout << "Calling StartZero()" << std::endl; + a.StartZero(); + thread.Sleep(1000); + EXPECT_TRUE(a.IsRunning()); + std::cout << "Elapsed Seconds: " << a.GetElapsedSeconds() << std::endl; + std::cout << "Elapsed Milliseconds: " << a.GetElapsedMilliseconds() << std::endl; + + std::cout << "Calling StartZero()" << std::endl; + a.StartZero(); + thread.Sleep(1000); + EXPECT_TRUE(a.IsRunning()); + std::cout << "Elapsed Seconds: " << a.GetElapsedSeconds() << std::endl; + std::cout << "Elapsed Milliseconds: " << a.GetElapsedMilliseconds() << std::endl; +} + +TEST(TestStopWatch, Reset) +{ + CStopWatch a; + CTestStopWatchThread thread; + + EXPECT_FALSE(a.IsRunning()); + EXPECT_EQ(0.0f, a.GetElapsedSeconds()); + EXPECT_EQ(0.0f, a.GetElapsedMilliseconds()); + + std::cout << "Calling StartZero()" << std::endl; + a.StartZero(); + thread.Sleep(1000); + EXPECT_TRUE(a.IsRunning()); + std::cout << "Elapsed Seconds: " << a.GetElapsedSeconds() << std::endl; + std::cout << "Elapsed Milliseconds: " << a.GetElapsedMilliseconds() << std::endl; + + std::cout << "Calling Reset()" << std::endl; + a.Reset(); + thread.Sleep(1000); + EXPECT_TRUE(a.IsRunning()); + std::cout << "Elapsed Seconds: " << a.GetElapsedSeconds() << std::endl; + std::cout << "Elapsed Milliseconds: " << a.GetElapsedMilliseconds() << std::endl; +} diff --git a/src/utils/test/TestStreamDetails.cpp b/src/utils/test/TestStreamDetails.cpp new file mode 100644 index 0000000000..1ebf688ae6 --- /dev/null +++ b/src/utils/test/TestStreamDetails.cpp @@ -0,0 +1,87 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/StreamDetails.h" + +#include "gtest/gtest.h" + +TEST(TestStreamDetails, General) +{ + CStreamDetails a; + CStreamDetailVideo *video = new CStreamDetailVideo(); + CStreamDetailAudio *audio = new CStreamDetailAudio(); + CStreamDetailSubtitle *subtitle = new CStreamDetailSubtitle(); + + video->m_iWidth = 1920; + video->m_iHeight = 1080; + video->m_fAspect = 2.39f; + video->m_iDuration = 30; + video->m_strCodec = "h264"; + video->m_strStereoMode = "left_right"; + + audio->m_iChannels = 2; + audio->m_strCodec = "aac"; + audio->m_strLanguage = "eng"; + + subtitle->m_strLanguage = "eng"; + + a.AddStream(video); + a.AddStream(audio); + + EXPECT_TRUE(a.HasItems()); + + EXPECT_EQ(1, a.GetStreamCount(CStreamDetail::VIDEO)); + EXPECT_EQ(1, a.GetVideoStreamCount()); + EXPECT_STREQ("", a.GetVideoCodec().c_str()); + EXPECT_EQ(0.0f, a.GetVideoAspect()); + EXPECT_EQ(0, a.GetVideoWidth()); + EXPECT_EQ(0, a.GetVideoHeight()); + EXPECT_EQ(0, a.GetVideoDuration()); + EXPECT_STREQ("", a.GetStereoMode().c_str()); + + EXPECT_EQ(1, a.GetStreamCount(CStreamDetail::AUDIO)); + EXPECT_EQ(1, a.GetAudioStreamCount()); + + EXPECT_EQ(0, a.GetStreamCount(CStreamDetail::SUBTITLE)); + EXPECT_EQ(0, a.GetSubtitleStreamCount()); + + a.AddStream(subtitle); + EXPECT_EQ(1, a.GetStreamCount(CStreamDetail::SUBTITLE)); + EXPECT_EQ(1, a.GetSubtitleStreamCount()); + + a.DetermineBestStreams(); + EXPECT_STREQ("h264", a.GetVideoCodec().c_str()); + EXPECT_EQ(2.39f, a.GetVideoAspect()); + EXPECT_EQ(1920, a.GetVideoWidth()); + EXPECT_EQ(1080, a.GetVideoHeight()); + EXPECT_EQ(30, a.GetVideoDuration()); + EXPECT_STREQ("left_right", a.GetStereoMode().c_str()); +} + +TEST(TestStreamDetails, VideoDimsToResolutionDescription) +{ + EXPECT_STREQ("1080", + CStreamDetails::VideoDimsToResolutionDescription(1920, 1080).c_str()); +} + +TEST(TestStreamDetails, VideoAspectToAspectDescription) +{ + EXPECT_STREQ("2.40", CStreamDetails::VideoAspectToAspectDescription(2.39f).c_str()); +} diff --git a/src/utils/test/TestStreamUtils.cpp b/src/utils/test/TestStreamUtils.cpp new file mode 100644 index 0000000000..026e4c4948 --- /dev/null +++ b/src/utils/test/TestStreamUtils.cpp @@ -0,0 +1,35 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/StreamUtils.h" + +#include "gtest/gtest.h" + +TEST(TestStreamUtils, General) +{ + EXPECT_EQ(0, StreamUtils::GetCodecPriority("")); + EXPECT_EQ(1, StreamUtils::GetCodecPriority("ac3")); + EXPECT_EQ(2, StreamUtils::GetCodecPriority("dca")); + EXPECT_EQ(3, StreamUtils::GetCodecPriority("eac3")); + EXPECT_EQ(4, StreamUtils::GetCodecPriority("dtshd_hra")); + EXPECT_EQ(5, StreamUtils::GetCodecPriority("dtshd_ma")); + EXPECT_EQ(6, StreamUtils::GetCodecPriority("truehd")); + EXPECT_EQ(7, StreamUtils::GetCodecPriority("flac")); +} diff --git a/src/utils/test/TestStringUtils.cpp b/src/utils/test/TestStringUtils.cpp new file mode 100644 index 0000000000..f9e25831c0 --- /dev/null +++ b/src/utils/test/TestStringUtils.cpp @@ -0,0 +1,480 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/StringUtils.h" + +#include "gtest/gtest.h" + +TEST(TestStringUtils, Format) +{ + std::string refstr = "test 25 2.7 ff FF"; + + std::string varstr = StringUtils::Format("%s %d %.1f %x %02X", "test", 25, 2.743f, 0x00ff, 0x00ff); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + varstr = StringUtils::Format("", "test", 25, 2.743f, 0x00ff, 0x00ff); + EXPECT_STREQ("", varstr.c_str()); +} + +TEST(TestStringUtils, ToUpper) +{ + std::string refstr = "TEST"; + + std::string varstr = "TeSt"; + StringUtils::ToUpper(varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestStringUtils, ToLower) +{ + std::string refstr = "test"; + + std::string varstr = "TeSt"; + StringUtils::ToLower(varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestStringUtils, EqualsNoCase) +{ + std::string refstr = "TeSt"; + + EXPECT_TRUE(StringUtils::EqualsNoCase(refstr, "TeSt")); + EXPECT_TRUE(StringUtils::EqualsNoCase(refstr, "tEsT")); +} + +TEST(TestStringUtils, Left) +{ + std::string refstr, varstr; + std::string origstr = "test"; + + refstr = ""; + varstr = StringUtils::Left(origstr, 0); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "te"; + varstr = StringUtils::Left(origstr, 2); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "test"; + varstr = StringUtils::Left(origstr, 10); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestStringUtils, Mid) +{ + std::string refstr, varstr; + std::string origstr = "test"; + + refstr = ""; + varstr = StringUtils::Mid(origstr, 0, 0); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "te"; + varstr = StringUtils::Mid(origstr, 0, 2); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "test"; + varstr = StringUtils::Mid(origstr, 0, 10); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "st"; + varstr = StringUtils::Mid(origstr, 2); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "st"; + varstr = StringUtils::Mid(origstr, 2, 2); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "es"; + varstr = StringUtils::Mid(origstr, 1, 2); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestStringUtils, Right) +{ + std::string refstr, varstr; + std::string origstr = "test"; + + refstr = ""; + varstr = StringUtils::Right(origstr, 0); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "st"; + varstr = StringUtils::Right(origstr, 2); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "test"; + varstr = StringUtils::Right(origstr, 10); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestStringUtils, Trim) +{ + std::string refstr = "test test"; + + std::string varstr = " test test "; + StringUtils::Trim(varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestStringUtils, TrimLeft) +{ + std::string refstr = "test test "; + + std::string varstr = " test test "; + StringUtils::TrimLeft(varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestStringUtils, TrimRight) +{ + std::string refstr = " test test"; + + std::string varstr = " test test "; + StringUtils::TrimRight(varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestStringUtils, Replace) +{ + std::string refstr = "text text"; + + std::string varstr = "test test"; + EXPECT_EQ(StringUtils::Replace(varstr, 's', 'x'), 2); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + EXPECT_EQ(StringUtils::Replace(varstr, 's', 'x'), 0); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + varstr = "test test"; + EXPECT_EQ(StringUtils::Replace(varstr, "s", "x"), 2); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + EXPECT_EQ(StringUtils::Replace(varstr, "s", "x"), 0); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestStringUtils, StartsWith) +{ + std::string refstr = "test"; + + EXPECT_FALSE(StringUtils::StartsWithNoCase(refstr, "x")); + + EXPECT_TRUE(StringUtils::StartsWith(refstr, "te")); + EXPECT_TRUE(StringUtils::StartsWith(refstr, "test")); + EXPECT_FALSE(StringUtils::StartsWith(refstr, "Te")); + + EXPECT_TRUE(StringUtils::StartsWithNoCase(refstr, "Te")); + EXPECT_TRUE(StringUtils::StartsWithNoCase(refstr, "TesT")); +} + +TEST(TestStringUtils, EndsWith) +{ + std::string refstr = "test"; + + EXPECT_FALSE(StringUtils::EndsWithNoCase(refstr, "x")); + + EXPECT_TRUE(StringUtils::EndsWith(refstr, "st")); + EXPECT_TRUE(StringUtils::EndsWith(refstr, "test")); + EXPECT_FALSE(StringUtils::EndsWith(refstr, "sT")); + + EXPECT_TRUE(StringUtils::EndsWithNoCase(refstr, "sT")); + EXPECT_TRUE(StringUtils::EndsWithNoCase(refstr, "TesT")); +} + +TEST(TestStringUtils, Join) +{ + CStdString refstr, varstr; + std::vector<std::string> strarray; + + strarray.push_back("a"); + strarray.push_back("b"); + strarray.push_back("c"); + strarray.push_back("de"); + strarray.push_back(","); + strarray.push_back("fg"); + strarray.push_back(","); + refstr = "a,b,c,de,,,fg,,"; + varstr = StringUtils::Join(strarray, ","); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestStringUtils, Split) +{ + std::vector<std::string> varresults; + + // test overload with string as delimiter + varresults = StringUtils::Split("g,h,ij,k,lm,,n", ","); + EXPECT_STREQ("g", varresults.at(0).c_str()); + EXPECT_STREQ("h", varresults.at(1).c_str()); + EXPECT_STREQ("ij", varresults.at(2).c_str()); + EXPECT_STREQ("k", varresults.at(3).c_str()); + EXPECT_STREQ("lm", varresults.at(4).c_str()); + EXPECT_STREQ("", varresults.at(5).c_str()); + EXPECT_STREQ("n", varresults.at(6).c_str()); + + EXPECT_TRUE(StringUtils::Split("", "|").empty()); + + EXPECT_EQ(4, StringUtils::Split("a bc d ef ghi ", " ", 4).size()); + EXPECT_STREQ("d ef ghi ", StringUtils::Split("a bc d ef ghi ", " ", 4).at(3).c_str()) << "Last part must include rest of the input string"; + EXPECT_EQ(7, StringUtils::Split("a bc d ef ghi ", " ").size()) << "Result must be 7 strings including two empty strings"; + EXPECT_STREQ("bc", StringUtils::Split("a bc d ef ghi ", " ").at(1).c_str()); + EXPECT_STREQ("", StringUtils::Split("a bc d ef ghi ", " ").at(2).c_str()); + EXPECT_STREQ("", StringUtils::Split("a bc d ef ghi ", " ").at(6).c_str()); + + EXPECT_EQ(2, StringUtils::Split("a bc d ef ghi ", " ").size()); + EXPECT_EQ(2, StringUtils::Split("a bc d ef ghi ", " ", 10).size()); + EXPECT_STREQ("a bc", StringUtils::Split("a bc d ef ghi ", " ", 10).at(0).c_str()); + + EXPECT_EQ(1, StringUtils::Split("a bc d ef ghi ", " z").size()); + EXPECT_STREQ("a bc d ef ghi ", StringUtils::Split("a bc d ef ghi ", " z").at(0).c_str()); + + EXPECT_EQ(1, StringUtils::Split("a bc d ef ghi ", "").size()); + EXPECT_STREQ("a bc d ef ghi ", StringUtils::Split("a bc d ef ghi ", "").at(0).c_str()); + + // test overload with char as delimiter + EXPECT_EQ(4, StringUtils::Split("a bc d ef ghi ", ' ', 4).size()); + EXPECT_STREQ("d ef ghi ", StringUtils::Split("a bc d ef ghi ", ' ', 4).at(3).c_str()); + EXPECT_EQ(7, StringUtils::Split("a bc d ef ghi ", ' ').size()) << "Result must be 7 strings including two empty strings"; + EXPECT_STREQ("bc", StringUtils::Split("a bc d ef ghi ", ' ').at(1).c_str()); + EXPECT_STREQ("", StringUtils::Split("a bc d ef ghi ", ' ').at(2).c_str()); + EXPECT_STREQ("", StringUtils::Split("a bc d ef ghi ", ' ').at(6).c_str()); + + EXPECT_EQ(1, StringUtils::Split("a bc d ef ghi ", 'z').size()); + EXPECT_STREQ("a bc d ef ghi ", StringUtils::Split("a bc d ef ghi ", 'z').at(0).c_str()); + + EXPECT_EQ(1, StringUtils::Split("a bc d ef ghi ", "").size()); + EXPECT_STREQ("a bc d ef ghi ", StringUtils::Split("a bc d ef ghi ", 'z').at(0).c_str()); +} + +TEST(TestStringUtils, FindNumber) +{ + EXPECT_EQ(3, StringUtils::FindNumber("aabcaadeaa", "aa")); + EXPECT_EQ(1, StringUtils::FindNumber("aabcaadeaa", "b")); +} + +TEST(TestStringUtils, AlphaNumericCompare) +{ + int64_t ref, var; + + ref = 0; + var = StringUtils::AlphaNumericCompare(L"123abc", L"abc123"); + EXPECT_LT(var, ref); +} + +TEST(TestStringUtils, TimeStringToSeconds) +{ + EXPECT_EQ(77455, StringUtils::TimeStringToSeconds("21:30:55")); + EXPECT_EQ(7*60, StringUtils::TimeStringToSeconds("7 min")); + EXPECT_EQ(7*60, StringUtils::TimeStringToSeconds("7 min\t")); + EXPECT_EQ(154*60, StringUtils::TimeStringToSeconds(" 154 min")); + EXPECT_EQ(1*60+1, StringUtils::TimeStringToSeconds("1:01")); + EXPECT_EQ(4*60+3, StringUtils::TimeStringToSeconds("4:03")); + EXPECT_EQ(2*3600+4*60+3, StringUtils::TimeStringToSeconds("2:04:03")); + EXPECT_EQ(2*3600+4*60+3, StringUtils::TimeStringToSeconds(" 2:4:3")); + EXPECT_EQ(2*3600+4*60+3, StringUtils::TimeStringToSeconds(" \t\t 02:04:03 \n ")); + EXPECT_EQ(1*3600+5*60+2, StringUtils::TimeStringToSeconds("01:05:02:04:03 \n ")); + EXPECT_EQ(0, StringUtils::TimeStringToSeconds("blah")); + EXPECT_EQ(0, StringUtils::TimeStringToSeconds("ля-ля")); +} + +TEST(TestStringUtils, RemoveCRLF) +{ + CStdString refstr, varstr; + + refstr = "test\r\nstring\nblah blah"; + varstr = "test\r\nstring\nblah blah\n"; + StringUtils::RemoveCRLF(varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestStringUtils, utf8_strlen) +{ + int ref, var; + + ref = 9; + var = StringUtils::utf8_strlen("test_UTF8"); + EXPECT_EQ(ref, var); +} + +TEST(TestStringUtils, SecondsToTimeString) +{ + CStdString ref, var; + + ref = "21:30:55"; + var = StringUtils::SecondsToTimeString(77455); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST(TestStringUtils, IsNaturalNumber) +{ + EXPECT_TRUE(StringUtils::IsNaturalNumber("10")); + EXPECT_TRUE(StringUtils::IsNaturalNumber(" 10")); + EXPECT_TRUE(StringUtils::IsNaturalNumber("0")); + EXPECT_FALSE(StringUtils::IsNaturalNumber(" 1 0")); + EXPECT_FALSE(StringUtils::IsNaturalNumber("1.0")); + EXPECT_FALSE(StringUtils::IsNaturalNumber("1.1")); + EXPECT_FALSE(StringUtils::IsNaturalNumber("0x1")); + EXPECT_FALSE(StringUtils::IsNaturalNumber("blah")); + EXPECT_FALSE(StringUtils::IsNaturalNumber("120 h")); + EXPECT_FALSE(StringUtils::IsNaturalNumber(" ")); + EXPECT_FALSE(StringUtils::IsNaturalNumber("")); +} + +TEST(TestStringUtils, IsInteger) +{ + EXPECT_TRUE(StringUtils::IsInteger("10")); + EXPECT_TRUE(StringUtils::IsInteger(" -10")); + EXPECT_TRUE(StringUtils::IsInteger("0")); + EXPECT_FALSE(StringUtils::IsInteger(" 1 0")); + EXPECT_FALSE(StringUtils::IsInteger("1.0")); + EXPECT_FALSE(StringUtils::IsInteger("1.1")); + EXPECT_FALSE(StringUtils::IsInteger("0x1")); + EXPECT_FALSE(StringUtils::IsInteger("blah")); + EXPECT_FALSE(StringUtils::IsInteger("120 h")); + EXPECT_FALSE(StringUtils::IsInteger(" ")); + EXPECT_FALSE(StringUtils::IsInteger("")); +} + +TEST(TestStringUtils, SizeToString) +{ + CStdString ref, var; + + ref = "2.00 GB"; + var = StringUtils::SizeToString(2147483647); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST(TestStringUtils, EmptyString) +{ + EXPECT_STREQ("", StringUtils::EmptyString.c_str()); +} + +TEST(TestStringUtils, FindWords) +{ + int ref, var; + + ref = 5; + var = StringUtils::FindWords("test string", "string"); + EXPECT_EQ(ref, var); + var = StringUtils::FindWords("12345string", "string"); + EXPECT_EQ(ref, var); + var = StringUtils::FindWords("apple2012", "2012"); + EXPECT_EQ(ref, var); + ref = -1; + var = StringUtils::FindWords("12345string", "ring"); + EXPECT_EQ(ref, var); + var = StringUtils::FindWords("12345string", "345"); + EXPECT_EQ(ref, var); + var = StringUtils::FindWords("apple2012", "e2012"); + EXPECT_EQ(ref, var); + var = StringUtils::FindWords("apple2012", "12"); + EXPECT_EQ(ref, var); +} + +TEST(TestStringUtils, FindWords_NonAscii) +{ + int ref, var; + + ref = 6; + var = StringUtils::FindWords("我的视频", "视频"); + EXPECT_EQ(ref, var); + var = StringUtils::FindWords("我的视频", "视"); + EXPECT_EQ(ref, var); + var = StringUtils::FindWords("Apple ple", "ple"); + EXPECT_EQ(ref, var); + ref = 7; + var = StringUtils::FindWords("Äpfel.pfel", "pfel"); + EXPECT_EQ(ref, var); +} + +TEST(TestStringUtils, FindEndBracket) +{ + int ref, var; + + ref = 11; + var = StringUtils::FindEndBracket("atest testbb test", 'a', 'b'); + EXPECT_EQ(ref, var); +} + +TEST(TestStringUtils, DateStringToYYYYMMDD) +{ + int ref, var; + + ref = 20120706; + var = StringUtils::DateStringToYYYYMMDD("2012-07-06"); + EXPECT_EQ(ref, var); +} + +TEST(TestStringUtils, WordToDigits) +{ + std::string ref, var; + + ref = "8378 787464"; + var = "test string"; + StringUtils::WordToDigits(var); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST(TestStringUtils, CreateUUID) +{ + std::cout << "CreateUUID(): " << StringUtils::CreateUUID() << std::endl; +} + +TEST(TestStringUtils, ValidateUUID) +{ + EXPECT_TRUE(StringUtils::ValidateUUID(StringUtils::CreateUUID())); +} + +TEST(TestStringUtils, CompareFuzzy) +{ + double ref, var; + + ref = 6.25f; + var = StringUtils::CompareFuzzy("test string", "string test"); + EXPECT_EQ(ref, var); +} + +TEST(TestStringUtils, FindBestMatch) +{ + double refdouble, vardouble; + int refint, varint; + std::vector<std::string> strarray; + + refint = 3; + refdouble = 0.5625f; + strarray.push_back(""); + strarray.push_back("a"); + strarray.push_back("e"); + strarray.push_back("es"); + strarray.push_back("t"); + varint = StringUtils::FindBestMatch("test", strarray, vardouble); + EXPECT_EQ(refint, varint); + EXPECT_EQ(refdouble, vardouble); +} + +TEST(TestStringUtils, Paramify) +{ + const char *input = "some, very \\ odd \"string\""; + const char *ref = "\"some, very \\\\ odd \\\"string\\\"\""; + + std::string result = StringUtils::Paramify(input); + EXPECT_STREQ(ref, result.c_str()); +} diff --git a/src/utils/test/TestSystemInfo.cpp b/src/utils/test/TestSystemInfo.cpp new file mode 100644 index 0000000000..f19d4d0c7d --- /dev/null +++ b/src/utils/test/TestSystemInfo.cpp @@ -0,0 +1,349 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/SystemInfo.h" +#include "settings/Settings.h" +#include "GUIInfoManager.h" + +#include "gtest/gtest.h" + +class TestSystemInfo : public testing::Test +{ +protected: + TestSystemInfo() + { + } + ~TestSystemInfo() + { + } +}; + +TEST_F(TestSystemInfo, Print_System_Info) +{ + std::cout << "'GetKernelName(false)': \"" << g_sysinfo.GetKernelName(true) << "\"\n"; + std::cout << "'GetKernelVersion()': \"" << g_sysinfo.GetKernelVersion() << "\"\n"; + std::cout << "'GetKernelVersionFull()': \"" << g_sysinfo.GetKernelVersionFull() << "\"\n"; + std::cout << "'GetOsPrettyNameWithVersion()': \"" << g_sysinfo.GetOsPrettyNameWithVersion() << "\"\n"; + std::cout << "'GetOsName(false)': \"" << g_sysinfo.GetOsName(false) << "\"\n"; + std::cout << "'GetOsVersion()': \"" << g_sysinfo.GetOsVersion() << "\"\n"; + std::cout << "'GetKernelCpuFamily()': \"" << g_sysinfo.GetKernelCpuFamily() << "\"\n"; + std::cout << "'GetKernelBitness()': \"" << g_sysinfo.GetKernelBitness() << "\"\n"; + std::cout << "'GetBuildTargetPlatformName()': \"" << g_sysinfo.GetBuildTargetPlatformName() << "\"\n"; + std::cout << "'GetBuildTargetPlatformVersionDecoded()': \"" << g_sysinfo.GetBuildTargetPlatformVersionDecoded() << "\"\n"; + std::cout << "'GetBuildTargetPlatformVersion()': \"" << g_sysinfo.GetBuildTargetPlatformVersion() << "\"\n"; + std::cout << "'GetBuildTargetCpuFamily()': \"" << g_sysinfo.GetBuildTargetCpuFamily() << "\"\n"; + std::cout << "'GetXbmcBitness()': \"" << g_sysinfo.GetXbmcBitness() << "\"\n"; + std::cout << "'GetUsedCompilerNameAndVer()': \"" << g_sysinfo.GetUsedCompilerNameAndVer() << "\"\n"; + std::cout << "'GetManufacturerName()': \"" << g_sysinfo.GetManufacturerName() << "\"\n"; + std::cout << "'GetModelName()': \"" << g_sysinfo.GetModelName() << "\"\n"; + std::cout << "'IsAppleTV2()': \"" << g_sysinfo.IsAppleTV2() << "\"\n"; + std::cout << "'GetUserAgent()': \"" << g_sysinfo.GetUserAgent() << "\"\n"; +} + +TEST_F(TestSystemInfo, GetKernelName) +{ + EXPECT_FALSE(g_sysinfo.GetKernelName(true).empty()) << "'GetKernelName(true)' must not return empty kernel name"; + EXPECT_FALSE(g_sysinfo.GetKernelName(false).empty()) << "'GetKernelName(false)' must not return empty kernel name"; + EXPECT_STRCASENE("Unknown kernel", g_sysinfo.GetKernelName(true).c_str()) << "'GetKernelName(true)' must not return 'Unknown kernel'"; + EXPECT_STRCASENE("Unknown kernel", g_sysinfo.GetKernelName(false).c_str()) << "'GetKernelName(false)' must not return 'Unknown kernel'"; +#ifndef TARGET_DARWIN + EXPECT_EQ(g_sysinfo.GetBuildTargetPlatformName(), g_sysinfo.GetKernelName(true)) << "'GetKernelName(true)' must match GetBuildTargetPlatformName()"; + EXPECT_EQ(g_sysinfo.GetBuildTargetPlatformName(), g_sysinfo.GetKernelName(false)) << "'GetKernelName(false)' must match GetBuildTargetPlatformName()"; +#endif // !TARGET_DARWIN +#if defined(TARGET_WINDOWS) + EXPECT_NE(std::string::npos, g_sysinfo.GetKernelName(true).find("Windows")) << "'GetKernelName(true)' must contain 'Windows'"; + EXPECT_NE(std::string::npos, g_sysinfo.GetKernelName(false).find("Windows")) << "'GetKernelName(false)' must contain 'Windows'"; +#elif defined(TARGET_FREEBSD) + EXPECT_STREQ("FreeBSD", g_sysinfo.GetKernelName(true).c_str()) << "'GetKernelName(true)' must return 'FreeBSD'"; + EXPECT_STREQ("FreeBSD", g_sysinfo.GetKernelName(false).c_str()) << "'GetKernelName(false)' must return 'FreeBSD'"; +#elif defined(TARGET_DARWIN) + EXPECT_STREQ("Darwin", g_sysinfo.GetKernelName(true).c_str()) << "'GetKernelName(true)' must return 'Darwin'"; + EXPECT_STREQ("Darwin", g_sysinfo.GetKernelName(false).c_str()) << "'GetKernelName(false)' must return 'Darwin'"; +#elif defined(TARGET_LINUX) + EXPECT_STREQ("Linux", g_sysinfo.GetKernelName(true).c_str()) << "'GetKernelName(true)' must return 'Linux'"; + EXPECT_STREQ("Linux", g_sysinfo.GetKernelName(false).c_str()) << "'GetKernelName(false)' must return 'Linux'"; +#endif +} + +TEST_F(TestSystemInfo, GetKernelVersionFull) +{ + EXPECT_FALSE(g_sysinfo.GetKernelVersionFull().empty()) << "'GetKernelVersionFull()' must not return empty string"; + EXPECT_STRNE("0.0.0", g_sysinfo.GetKernelVersionFull().c_str()) << "'GetKernelVersionFull()' must not return '0.0.0'"; + EXPECT_STRNE("0.0", g_sysinfo.GetKernelVersionFull().c_str()) << "'GetKernelVersionFull()' must not return '0.0'"; + EXPECT_EQ(0, g_sysinfo.GetKernelVersionFull().find_first_of("0123456789")) << "'GetKernelVersionFull()' must not return version not starting from digit"; +} + +TEST_F(TestSystemInfo, GetKernelVersion) +{ + EXPECT_FALSE(g_sysinfo.GetKernelVersion().empty()) << "'GetKernelVersion()' must not return empty string"; + EXPECT_STRNE("0.0.0", g_sysinfo.GetKernelVersion().c_str()) << "'GetKernelVersion()' must not return '0.0.0'"; + EXPECT_STRNE("0.0", g_sysinfo.GetKernelVersion().c_str()) << "'GetKernelVersion()' must not return '0.0'"; + EXPECT_EQ(0, g_sysinfo.GetKernelVersion().find_first_of("0123456789")) << "'GetKernelVersion()' must not return version not starting from digit"; + EXPECT_EQ(std::string::npos, g_sysinfo.GetKernelVersion().find_first_not_of("0123456789.")) << "'GetKernelVersion()' must not return version with not only digits and dots"; +} + +TEST_F(TestSystemInfo, GetOsName) +{ + EXPECT_FALSE(g_sysinfo.GetOsName(true).empty()) << "'GetOsName(true)' must not return empty OS name"; + EXPECT_FALSE(g_sysinfo.GetOsName(false).empty()) << "'GetOsName(false)' must not return empty OS name"; + EXPECT_STRCASENE("Unknown OS", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must not return 'Unknown OS'"; + EXPECT_STRCASENE("Unknown OS", g_sysinfo.GetOsName(false).c_str()) << "'GetOsName(false)' must not return 'Unknown OS'"; +#if defined(TARGET_WINDOWS) + EXPECT_NE(std::string::npos, g_sysinfo.GetOsName(true).find("Windows")) << "'GetOsName(true)' must contain 'Windows'"; + EXPECT_NE(std::string::npos, g_sysinfo.GetOsName(false).find("Windows")) << "'GetOsName(false)' must contain 'Windows'"; +#elif defined(TARGET_FREEBSD) + EXPECT_STREQ("FreeBSD", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must return 'FreeBSD'"; + EXPECT_STREQ("FreeBSD", g_sysinfo.GetOsName(false).c_str()) << "'GetOsName(false)' must return 'FreeBSD'"; +#elif defined(TARGET_DARWIN_IOS) + EXPECT_STREQ("iOS", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must return 'iOS'"; + EXPECT_STREQ("iOS", g_sysinfo.GetOsName(false).c_str()) << "'GetOsName(false)' must return 'iOS'"; +#elif defined(TARGET_DARWIN_OSX) + EXPECT_STREQ("OS X", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must return 'OS X'"; + EXPECT_STREQ("OS X", g_sysinfo.GetOsName(false).c_str()) << "'GetOsName(false)' must return 'OS X'"; +#elif defined(TARGET_ANDROID) + EXPECT_STREQ("Android", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must return 'Android'"; + EXPECT_STREQ("Android", g_sysinfo.GetOsName(false).c_str()) << "'GetOsName(false)' must return 'Android'"; +#endif +#ifdef TARGET_DARWIN + EXPECT_EQ(g_sysinfo.GetBuildTargetPlatformName(), g_sysinfo.GetOsName(true)) << "'GetOsName(true)' must match GetBuildTargetPlatformName()"; + EXPECT_EQ(g_sysinfo.GetBuildTargetPlatformName(), g_sysinfo.GetOsName(false)) << "'GetOsName(false)' must match GetBuildTargetPlatformName()"; +#endif // TARGET_DARWIN +} + +TEST_F(TestSystemInfo, GetOsVersion) +{ + EXPECT_FALSE(g_sysinfo.GetOsVersion().empty()) << "'GetOsVersion()' must not return empty string"; + EXPECT_STRNE("0.0.0", g_sysinfo.GetOsVersion().c_str()) << "'GetOsVersion()' must not return '0.0.0'"; + EXPECT_STRNE("0.0", g_sysinfo.GetOsVersion().c_str()) << "'GetOsVersion()' must not return '0.0'"; + EXPECT_EQ(0, g_sysinfo.GetOsVersion().find_first_of("0123456789")) << "'GetOsVersion()' must not return version not starting from digit"; + EXPECT_EQ(std::string::npos, g_sysinfo.GetOsVersion().find_first_not_of("0123456789.")) << "'GetOsVersion()' must not return version with not only digits and dots"; +} + +TEST_F(TestSystemInfo, GetOsPrettyNameWithVersion) +{ + EXPECT_FALSE(g_sysinfo.GetOsPrettyNameWithVersion().empty()) << "'GetOsPrettyNameWithVersion()' must not return empty string"; + EXPECT_EQ(std::string::npos, g_sysinfo.GetOsPrettyNameWithVersion().find("Unknown")) << "'GetOsPrettyNameWithVersion()' must not contain 'Unknown'"; + EXPECT_EQ(std::string::npos, g_sysinfo.GetOsPrettyNameWithVersion().find("unknown")) << "'GetOsPrettyNameWithVersion()' must not contain 'unknown'"; +#ifdef TARGET_WINDOWS + EXPECT_NE(std::string::npos, g_sysinfo.GetOsPrettyNameWithVersion().find("Windows")) << "'GetOsPrettyNameWithVersion()' must contain 'Windows'"; +#else // ! TARGET_WINDOWS + EXPECT_NE(std::string::npos, g_sysinfo.GetOsPrettyNameWithVersion().find(g_sysinfo.GetOsVersion())) << "'GetOsPrettyNameWithVersion()' must contain OS version"; +#endif // ! TARGET_WINDOWS +} + +TEST_F(TestSystemInfo, GetManufacturerName) +{ + EXPECT_STRCASENE("unknown", g_sysinfo.GetManufacturerName().c_str()) << "'GetManufacturerName()' must return empty string instead of 'Unknown'"; +} + +TEST_F(TestSystemInfo, GetModelName) +{ + EXPECT_STRCASENE("unknown", g_sysinfo.GetModelName().c_str()) << "'GetModelName()' must return empty string instead of 'Unknown'"; +} + +#ifndef TARGET_WINDOWS +TEST_F(TestSystemInfo, IsAeroDisabled) +{ + EXPECT_FALSE(g_sysinfo.IsAeroDisabled()) << "'IsAeroDisabled()' must return 'false'"; +} +#endif // ! TARGET_WINDOWS + +TEST_F(TestSystemInfo, IsWindowsVersion) +{ + EXPECT_FALSE(g_sysinfo.IsWindowsVersion(CSysInfo::WindowsVersionUnknown)) << "'IsWindowsVersion()' must return 'false' for 'WindowsVersionUnknown'"; +#ifndef TARGET_WINDOWS + EXPECT_FALSE(g_sysinfo.IsWindowsVersion(CSysInfo::WindowsVersionWin7)) << "'IsWindowsVersion()' must return 'false'"; +#endif // ! TARGET_WINDOWS +} + +TEST_F(TestSystemInfo, IsWindowsVersionAtLeast) +{ + EXPECT_FALSE(g_sysinfo.IsWindowsVersionAtLeast(CSysInfo::WindowsVersionUnknown)) << "'IsWindowsVersionAtLeast()' must return 'false' for 'WindowsVersionUnknown'"; + EXPECT_FALSE(g_sysinfo.IsWindowsVersionAtLeast(CSysInfo::WindowsVersionFuture)) << "'IsWindowsVersionAtLeast()' must return 'false' for 'WindowsVersionFuture'"; +#ifndef TARGET_WINDOWS + EXPECT_FALSE(g_sysinfo.IsWindowsVersion(CSysInfo::WindowsVersionWin7)) << "'IsWindowsVersionAtLeast()' must return 'false'"; +#endif // ! TARGET_WINDOWS +} + +TEST_F(TestSystemInfo, GetWindowsVersion) +{ +#ifdef TARGET_WINDOWS + EXPECT_NE(CSysInfo::WindowsVersionUnknown, g_sysinfo.GetWindowsVersion()) << "'GetWindowsVersion()' must not return 'WindowsVersionUnknown'"; + EXPECT_NE(CSysInfo::WindowsVersionFuture, g_sysinfo.GetWindowsVersion()) << "'GetWindowsVersion()' must not return 'WindowsVersionFuture'"; +#else // ! TARGET_WINDOWS + EXPECT_EQ(CSysInfo::WindowsVersionUnknown, g_sysinfo.GetWindowsVersion()) << "'GetWindowsVersion()' must return 'WindowsVersionUnknown'"; +#endif // ! TARGET_WINDOWS +} + +TEST_F(TestSystemInfo, GetKernelBitness) +{ + EXPECT_TRUE(g_sysinfo.GetKernelBitness() == 32 || g_sysinfo.GetKernelBitness() == 64) << "'GetKernelBitness()' must return '32' or '64', but not '" << g_sysinfo.GetKernelBitness() << "'"; + EXPECT_LE(g_sysinfo.GetXbmcBitness(), g_sysinfo.GetKernelBitness()) << "'GetKernelBitness()' must be greater or equal to 'GetXbmcBitness()'"; +} + +TEST_F(TestSystemInfo, GetKernelCpuFamily) +{ + EXPECT_STRNE("unknown CPU family", g_sysinfo.GetKernelCpuFamily().c_str()) << "'GetKernelCpuFamily()' must not return 'unknown CPU family'"; +#if defined(__thumb__) || defined(_M_ARMT) || defined(__arm__) || defined(_M_ARM) || defined (__aarch64__) + EXPECT_STREQ("ARM", g_sysinfo.GetKernelCpuFamily().c_str()) << "'GetKernelCpuFamily()' must return 'ARM'"; +#else // ! ARM + EXPECT_EQ(g_sysinfo.GetBuildTargetCpuFamily(), g_sysinfo.GetKernelCpuFamily()) << "'GetKernelCpuFamily()' must match 'GetBuildTargetCpuFamily()'"; +#endif // ! ARM +} + +TEST_F(TestSystemInfo, GetXbmcBitness) +{ + EXPECT_TRUE(g_sysinfo.GetXbmcBitness() == 32 || g_sysinfo.GetXbmcBitness() == 64) << "'GetXbmcBitness()' must return '32' or '64', but not '" << g_sysinfo.GetXbmcBitness() << "'"; + EXPECT_GE(g_sysinfo.GetKernelBitness(), g_sysinfo.GetXbmcBitness()) << "'GetXbmcBitness()' must be not greater than 'GetKernelBitness()'"; +} + +TEST_F(TestSystemInfo, GetUserAgent) +{ + EXPECT_STREQ(g_sysinfo.GetAppName().c_str(), g_sysinfo.GetUserAgent().substr(0, g_sysinfo.GetAppName().size()).c_str()) << "'GetUserAgent()' string must start with app name'"; + EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find('(')) << "'GetUserAgent()' must contain brackets around second parameter"; + EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find(')')) << "'GetUserAgent()' must contain brackets around second parameter"; + EXPECT_EQ(g_sysinfo.GetUserAgent().find(' '), g_sysinfo.GetUserAgent().find(" (")) << "Second parameter in 'GetUserAgent()' string must be in brackets"; + EXPECT_EQ(g_sysinfo.GetUserAgent().find(" (") + 1, g_sysinfo.GetUserAgent().find('(')) << "'GetUserAgent()' string must not contain any opening brackets before second parameter"; + EXPECT_GT(g_sysinfo.GetUserAgent().find(')'), g_sysinfo.GetUserAgent().find('(')) << "'GetUserAgent()' string must not contain any closing brackets before second parameter"; + EXPECT_EQ(g_sysinfo.GetUserAgent().find(") "), g_sysinfo.GetUserAgent().find(')')) << "'GetUserAgent()' string must not contain any closing brackets before end of second parameter"; +#if defined(TARGET_WINDOWS) + EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(Windows")) << "Second parameter in 'GetUserAgent()' string must start from `Windows`"; + EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find("Windows")) << "'GetUserAgent()' must contain 'Windows'"; +#elif defined(TARGET_DARWIN_IOS) + EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find("like Mac OS X")) << "'GetUserAgent()' must contain ' like Mac OS X'"; +#ifdef TARGET_DARWIN_IOS_ATV2 + EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find("CPU OS ")) << "'GetUserAgent()' must contain 'CPU OS '"; +#else // ! TARGET_DARWIN_IOS_ATV2 + EXPECT_TRUE(g_sysinfo.GetUserAgent().find("CPU OS ") != std::string::npos || g_sysinfo.GetUserAgent().find("CPU iPhone OS ") != std::string::npos) << "'GetUserAgent()' must contain 'CPU OS ' or 'CPU iPhone OS '"; +#endif // ! TARGET_DARWIN_IOS_ATV2 +#elif defined(TARGET_DARWIN_OSX) + EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(Macintosh; ")) << "Second parameter in 'GetUserAgent()' string must start from 'Macintosh; '"; +#elif defined(TARGET_ANDROID) + EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(Linux; Android ")) << "Second parameter in 'GetUserAgent()' string must start from 'Linux; Android '"; +#elif defined(TARGET_POSIX) + EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(X11; ")) << "Second parameter in 'GetUserAgent()' string must start from 'X11; '"; +#if defined(TARGET_FREEBSD) + EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(X11; FreeBSD ")) << "Second parameter in 'GetUserAgent()' string must start from 'X11; FreeBSD '"; +#elif defined(TARGET_LINUX) + EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(X11; Linux ")) << "Second parameter in 'GetUserAgent()' string must start from 'X11; Linux '"; +#endif // defined(TARGET_LINUX) +#endif // defined(TARGET_POSIX) + +#ifdef TARGET_RASPBERRY_PI + EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find(" XBMC_HW_RaspberryPi/")) << "'GetUserAgent()' must contain ' XBMC_HW_RaspberryPi/'"; +#endif // TARGET_RASPBERRY_PI + + EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find(" App_Bitness/")) << "'GetUserAgent()' must contain ' App_Bitness/'"; + EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find(" Version/")) << "'GetUserAgent()' must contain ' Version/'"; +} + +TEST_F(TestSystemInfo, IsAppleTV2) +{ +#ifdef TARGET_DARWIN_IOS_ATV2 + EXPECT_TRUE(g_sysinfo.IsAppleTV2()) << "'IsAppleTV2()' must return 'true'"; +#else + EXPECT_FALSE(g_sysinfo.IsAppleTV2()) << "'IsAppleTV2()' must return 'false'"; +#endif +} + +// FIXME: TARGET_DARWIN_IOS_ATV2? +#ifndef TARGET_DARWIN +TEST_F(TestSystemInfo, HasVideoToolBoxDecoder) +{ + EXPECT_FALSE(g_sysinfo.HasVideoToolBoxDecoder()) << "'HasVideoToolBoxDecoder()' must return 'false'"; +} +#endif + +TEST_F(TestSystemInfo, GetBuildTargetPlatformName) +{ + EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformName().find("Unknown")) << "'GetBuildTargetPlatformName()' must not contain 'Unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformName() << "'"; + EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformName().find("unknown")) << "'GetBuildTargetPlatformName()' must not contain 'unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformName() << "'"; +} + +TEST_F(TestSystemInfo, GetBuildTargetPlatformVersion) +{ + EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformVersion().find("Unknown")) << "'GetBuildTargetPlatformVersion()' must not contain 'Unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformVersion() << "'"; + EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformVersion().find("unknown")) << "'GetBuildTargetPlatformVersion()' must not contain 'unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformVersion() << "'"; +} + +TEST_F(TestSystemInfo, GetBuildTargetPlatformVersionDecoded) +{ + EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformVersionDecoded().find("Unknown")) << "'GetBuildTargetPlatformVersionDecoded()' must not contain 'Unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformVersion() << "'"; + EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformVersionDecoded().find("unknown")) << "'GetBuildTargetPlatformVersionDecoded()' must not contain 'unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformVersion() << "'"; +#ifdef TARGET_ANDROID + EXPECT_STREQ("API level ", g_sysinfo.GetBuildTargetPlatformVersionDecoded().substr(0, 10).c_str()) << "'GetBuildTargetPlatformVersionDecoded()' must start from 'API level '"; +#else + EXPECT_STREQ("version ", g_sysinfo.GetBuildTargetPlatformVersionDecoded().substr(0, 8).c_str()) << "'GetBuildTargetPlatformVersionDecoded()' must start from 'version'"; +#endif +} + +TEST_F(TestSystemInfo, GetBuildTargetCpuFamily) +{ + EXPECT_STRNE("unknown CPU family", g_sysinfo.GetBuildTargetCpuFamily().c_str()) << "'GetBuildTargetCpuFamily()' must not return 'unknown CPU family'"; +#if defined(__thumb__) || defined(_M_ARMT) || defined(__arm__) || defined(_M_ARM) || defined (__aarch64__) + EXPECT_STREQ("ARM", g_sysinfo.GetBuildTargetCpuFamily().substr(0, 3).c_str()) << "'GetKernelCpuFamily()' string must start from 'ARM'"; +#else // ! ARM + EXPECT_EQ(g_sysinfo.GetKernelCpuFamily(), g_sysinfo.GetBuildTargetCpuFamily()) << "'GetBuildTargetCpuFamily()' must match 'GetKernelCpuFamily()'"; +#endif // ! ARM +} + +TEST_F(TestSystemInfo, GetUsedCompilerNameAndVer) +{ + EXPECT_STRNE("unknown compiler", g_sysinfo.GetUsedCompilerNameAndVer().c_str()) << "'GetUsedCompilerNameAndVer()' must not return 'unknown compiler'"; +} + +TEST_F(TestSystemInfo, GetDiskSpace) +{ + int iTotal, iTotalFree, iTotalUsed, iPercentFree, iPercentUsed; + + iTotal = iTotalFree = iTotalUsed = iPercentFree = iPercentUsed = 0; + EXPECT_TRUE(g_sysinfo.GetDiskSpace("*", iTotal, iTotalFree, iTotalUsed, iPercentFree, iPercentUsed)) << "'GetDiskSpace()' return 'false' for disk '*'"; + EXPECT_NE(0, iTotal) << "'GetDiskSpace()' return zero total space for disk '*'"; + EXPECT_EQ(iTotal, iTotalFree + iTotalUsed) << "'GetDiskSpace()' return 'TotalFree + TotalUsed' not equal to 'Total' for disk '*'"; + EXPECT_EQ(100, iPercentFree + iPercentUsed) << "'GetDiskSpace()' return 'PercentFree + PercentUsed' not equal to '100' for disk '*'"; + + iTotal = iTotalFree = iTotalUsed = iPercentFree = iPercentUsed = 0; + EXPECT_TRUE(g_sysinfo.GetDiskSpace("", iTotal, iTotalFree, iTotalUsed, iPercentFree, iPercentUsed)) << "'GetDiskSpace()' return 'false' for disk ''"; + EXPECT_NE(0, iTotal) << "'GetDiskSpace()' return zero total space for disk ''"; + EXPECT_EQ(iTotal, iTotalFree + iTotalUsed) << "'GetDiskSpace()' return 'TotalFree + TotalUsed' not equal to 'Total' for disk ''"; + EXPECT_EQ(100, iPercentFree + iPercentUsed) << "'GetDiskSpace()' return 'PercentFree + PercentUsed' not equal to '100' for disk ''"; + +#ifdef TARGET_WINDOWS + char sysDrive[300]; + DWORD res = GetEnvironmentVariableA("SystemDrive", sysDrive, sizeof(sysDrive) / sizeof(char)); + std::string sysDriveLtr; + if (res != 0 && res <= sizeof(sysDrive) / sizeof(char)) + sysDriveLtr.assign(sysDrive, 1); + else + sysDriveLtr = "C"; // fallback + + iTotal = iTotalFree = iTotalUsed = iPercentFree = iPercentUsed = 0; + EXPECT_TRUE(g_sysinfo.GetDiskSpace(sysDriveLtr, iTotal, iTotalFree, iTotalUsed, iPercentFree, iPercentUsed)) << "'GetDiskSpace()' return 'false' for disk '" << sysDriveLtr << ":'"; + EXPECT_NE(0, iTotal) << "'GetDiskSpace()' return zero total space for disk '" << sysDriveLtr << ":'"; + EXPECT_EQ(iTotal, iTotalFree + iTotalUsed) << "'GetDiskSpace()' return 'TotalFree + TotalUsed' not equal to 'Total' for disk '" << sysDriveLtr << ":'"; + EXPECT_EQ(100, iPercentFree + iPercentUsed) << "'GetDiskSpace()' return 'PercentFree + PercentUsed' not equal to '100' for disk '" << sysDriveLtr << ":'"; +#elif defined(TARGET_POSIX) + iTotal = iTotalFree = iTotalUsed = iPercentFree = iPercentUsed = 0; + EXPECT_TRUE(g_sysinfo.GetDiskSpace("/", iTotal, iTotalFree, iTotalUsed, iPercentFree, iPercentUsed)) << "'GetDiskSpace()' return 'false' for directory '/'"; + EXPECT_NE(0, iTotal) << "'GetDiskSpace()' return zero total space for directory '/'"; + EXPECT_EQ(iTotal, iTotalFree + iTotalUsed) << "'GetDiskSpace()' return 'TotalFree + TotalUsed' not equal to 'Total' for directory '/'"; + EXPECT_EQ(100, iPercentFree + iPercentUsed) << "'GetDiskSpace()' return 'PercentFree + PercentUsed' not equal to '100' for directory '/'"; +#endif +} diff --git a/src/utils/test/TestTimeSmoother.cpp b/src/utils/test/TestTimeSmoother.cpp new file mode 100644 index 0000000000..c59e62f3e5 --- /dev/null +++ b/src/utils/test/TestTimeSmoother.cpp @@ -0,0 +1,41 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/TimeSmoother.h" +#include "threads/SystemClock.h" + +#include "gtest/gtest.h" + +TEST(TestTimeSmoother, General) +{ + CTimeSmoother a; + + unsigned int currentTime, frameTime; + currentTime = XbmcThreads::SystemClockMillis(); + std::cout << "currentTime: " << testing::PrintToString(currentTime) << "\n"; + frameTime = a.GetNextFrameTime(currentTime); + std::cout << "frameTime: " << testing::PrintToString(frameTime) << "\n"; + + std::cout << "Calling AddTimeStamp()\n"; + a.AddTimeStamp(currentTime); + std::cout << "currentTime: " << testing::PrintToString(currentTime) << "\n"; + frameTime = a.GetNextFrameTime(currentTime); + std::cout << "frameTime: " << testing::PrintToString(frameTime) << "\n"; +} diff --git a/src/utils/test/TestTimeUtils.cpp b/src/utils/test/TestTimeUtils.cpp new file mode 100644 index 0000000000..f4ed08cc4f --- /dev/null +++ b/src/utils/test/TestTimeUtils.cpp @@ -0,0 +1,63 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/TimeUtils.h" +#include "XBDateTime.h" + +#include "gtest/gtest.h" + +TEST(TestTimeUtils, CurrentHostCounter) +{ + std::cout << "CurrentHostCounter(): " << + testing::PrintToString(CurrentHostCounter()) << std::endl; +} + +TEST(TestTimeUtils, CurrentHostFrequency) +{ + std::cout << "CurrentHostFrequency(): " << + testing::PrintToString(CurrentHostFrequency()) << std::endl; +} + +TEST(TestTimeUtils, GetFrameTime) +{ + std::cout << "GetFrameTime(): " << + testing::PrintToString(CTimeUtils::GetFrameTime()) << std::endl; + + std::cout << "Calling UpdateFrameTime()" << std::endl; + CTimeUtils::UpdateFrameTime(true); + std::cout << "GetFrameTime(): " << + testing::PrintToString(CTimeUtils::GetFrameTime()) << std::endl; +} + +TEST(TestTimeUtils, GetLocalTime) +{ + CDateTime cdatetime, cdatetime2; + time_t timer; + + time(&timer); + + cdatetime = CTimeUtils::GetLocalTime(timer); + std::cout << "cdatetime.GetAsLocalizedDateTime(): " << + cdatetime.GetAsLocalizedDateTime() << std::endl; + + cdatetime2 = timer; + std::cout << "time: " << + cdatetime2.GetAsLocalizedDateTime() << std::endl; +} diff --git a/src/utils/test/TestURIUtils.cpp b/src/utils/test/TestURIUtils.cpp new file mode 100644 index 0000000000..cd6dbec802 --- /dev/null +++ b/src/utils/test/TestURIUtils.cpp @@ -0,0 +1,622 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/URIUtils.h" +#include "settings/AdvancedSettings.h" +#include "filesystem/MultiPathDirectory.h" +#include "URL.h" + +#include "gtest/gtest.h" + +using namespace XFILE; + +class TestURIUtils : public testing::Test +{ +protected: + TestURIUtils(){} + ~TestURIUtils() + { + g_advancedSettings.m_pathSubstitutions.clear(); + } +}; + +TEST_F(TestURIUtils, IsInPath) +{ + EXPECT_TRUE(URIUtils::IsInPath("/path/to/movie.avi", "/path/to/")); + EXPECT_FALSE(URIUtils::IsInPath("/path/to/movie.avi", "/path/2/")); +} + +TEST_F(TestURIUtils, GetDirectory) +{ + EXPECT_STREQ("/path/to/", URIUtils::GetDirectory("/path/to/movie.avi")); + EXPECT_STREQ("/path/to/", URIUtils::GetDirectory("/path/to/")); + EXPECT_STREQ("/path/to/|option=foo", URIUtils::GetDirectory("/path/to/movie.avi|option=foo")); + EXPECT_STREQ("/path/to/|option=foo", URIUtils::GetDirectory("/path/to/|option=foo")); + EXPECT_STREQ("", URIUtils::GetDirectory("movie.avi")); + EXPECT_STREQ("", URIUtils::GetDirectory("movie.avi|option=foo")); + EXPECT_STREQ("", URIUtils::GetDirectory("")); + + // Make sure it works when assigning to the same str as the reference parameter + CStdString var = "/path/to/movie.avi|option=foo"; + var = URIUtils::GetDirectory(var); + EXPECT_STREQ("/path/to/|option=foo", var); +} + +TEST_F(TestURIUtils, GetExtension) +{ + EXPECT_STREQ(".avi", + URIUtils::GetExtension("/path/to/movie.avi").c_str()); +} + +TEST_F(TestURIUtils, HasExtension) +{ + EXPECT_TRUE (URIUtils::HasExtension("/path/to/movie.AvI")); + EXPECT_FALSE(URIUtils::HasExtension("/path/to/movie")); + EXPECT_FALSE(URIUtils::HasExtension("/path/.to/movie")); + EXPECT_FALSE(URIUtils::HasExtension("")); + + EXPECT_TRUE (URIUtils::HasExtension("/path/to/movie.AvI", ".avi")); + EXPECT_FALSE(URIUtils::HasExtension("/path/to/movie.AvI", ".mkv")); + EXPECT_FALSE(URIUtils::HasExtension("/path/.avi/movie", ".avi")); + EXPECT_FALSE(URIUtils::HasExtension("", ".avi")); + + EXPECT_TRUE (URIUtils::HasExtension("/path/movie.AvI", ".avi|.mkv|.mp4")); + EXPECT_TRUE (URIUtils::HasExtension("/path/movie.AvI", ".mkv|.avi|.mp4")); + EXPECT_FALSE(URIUtils::HasExtension("/path/movie.AvI", ".mpg|.mkv|.mp4")); + EXPECT_FALSE(URIUtils::HasExtension("/path.mkv/movie.AvI", ".mpg|.mkv|.mp4")); + EXPECT_FALSE(URIUtils::HasExtension("", ".avi|.mkv|.mp4")); +} + +TEST_F(TestURIUtils, GetFileName) +{ + EXPECT_STREQ("movie.avi", + URIUtils::GetFileName("/path/to/movie.avi").c_str()); +} + +TEST_F(TestURIUtils, RemoveExtension) +{ + CStdString ref, var; + + /* NOTE: CSettings need to be set to find other extensions. */ + ref = "/path/to/file"; + var = "/path/to/file.xml"; + URIUtils::RemoveExtension(var); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST_F(TestURIUtils, ReplaceExtension) +{ + CStdString ref, var; + + ref = "/path/to/file.xsd"; + var = URIUtils::ReplaceExtension("/path/to/file.xml", ".xsd"); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST_F(TestURIUtils, Split) +{ + CStdString refpath, reffile, varpath, varfile; + + refpath = "/path/to/"; + reffile = "movie.avi"; + URIUtils::Split("/path/to/movie.avi", varpath, varfile); + EXPECT_STREQ(refpath.c_str(), varpath.c_str()); + EXPECT_STREQ(reffile.c_str(), varfile.c_str()); +} + +TEST_F(TestURIUtils, SplitPath) +{ + std::vector<std::string> strarray; + + strarray = URIUtils::SplitPath("http://www.test.com/path/to/movie.avi"); + + EXPECT_STREQ("http://www.test.com/", strarray.at(0).c_str()); + EXPECT_STREQ("path", strarray.at(1).c_str()); + EXPECT_STREQ("to", strarray.at(2).c_str()); + EXPECT_STREQ("movie.avi", strarray.at(3).c_str()); +} + +TEST_F(TestURIUtils, SplitPathLocal) +{ +#ifndef TARGET_LINUX + const char *path = "C:\\path\\to\\movie.avi"; +#else + const char *path = "/path/to/movie.avi"; +#endif + std::vector<std::string> strarray; + + strarray = URIUtils::SplitPath(path); + +#ifndef TARGET_LINUX + EXPECT_STREQ("C:", strarray.at(0).c_str()); +#else + EXPECT_STREQ("", strarray.at(0).c_str()); +#endif + EXPECT_STREQ("path", strarray.at(1).c_str()); + EXPECT_STREQ("to", strarray.at(2).c_str()); + EXPECT_STREQ("movie.avi", strarray.at(3).c_str()); +} + +TEST_F(TestURIUtils, GetCommonPath) +{ + CStdString ref, var; + + ref = "/path/"; + var = "/path/2/movie.avi"; + URIUtils::GetCommonPath(var, "/path/to/movie.avi"); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST_F(TestURIUtils, GetParentPath) +{ + CStdString ref, var; + + ref = "/path/to/"; + var = URIUtils::GetParentPath("/path/to/movie.avi"); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + var.clear(); + EXPECT_TRUE(URIUtils::GetParentPath("/path/to/movie.avi", var)); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST_F(TestURIUtils, SubstitutePath) +{ + CStdString from, to, ref, var; + + from = "C:\\My Videos"; + to = "https://myserver/some%20other%20path"; + g_advancedSettings.m_pathSubstitutions.push_back(std::make_pair(from, to)); + + from = "/this/path1"; + to = "/some/other/path2"; + g_advancedSettings.m_pathSubstitutions.push_back(std::make_pair(from, to)); + + from = "davs://otherserver/my%20music%20path"; + to = "D:\\Local Music\\MP3 Collection"; + g_advancedSettings.m_pathSubstitutions.push_back(std::make_pair(from, to)); + + ref = "https://myserver/some%20other%20path/sub%20dir/movie%20name.avi"; + var = URIUtils::SubstitutePath("C:\\My Videos\\sub dir\\movie name.avi"); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + ref = "C:\\My Videos\\sub dir\\movie name.avi"; + var = URIUtils::SubstitutePath("https://myserver/some%20other%20path/sub%20dir/movie%20name.avi", true); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + ref = "D:\\Local Music\\MP3 Collection\\Phil Collins\\Some CD\\01 - Two Hearts.mp3"; + var = URIUtils::SubstitutePath("davs://otherserver/my%20music%20path/Phil%20Collins/Some%20CD/01%20-%20Two%20Hearts.mp3"); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + ref = "davs://otherserver/my%20music%20path/Phil%20Collins/Some%20CD/01%20-%20Two%20Hearts.mp3"; + var = URIUtils::SubstitutePath("D:\\Local Music\\MP3 Collection\\Phil Collins\\Some CD\\01 - Two Hearts.mp3", true); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + ref = "/some/other/path2/to/movie.avi"; + var = URIUtils::SubstitutePath("/this/path1/to/movie.avi"); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + ref = "/this/path1/to/movie.avi"; + var = URIUtils::SubstitutePath("/some/other/path2/to/movie.avi", true); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + ref = "/no/translation path/"; + var = URIUtils::SubstitutePath(ref); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + ref = "/no/translation path/"; + var = URIUtils::SubstitutePath(ref, true); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + ref = "c:\\no\\translation path"; + var = URIUtils::SubstitutePath(ref); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + ref = "c:\\no\\translation path"; + var = URIUtils::SubstitutePath(ref, true); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST_F(TestURIUtils, IsAddonsPath) +{ + EXPECT_TRUE(URIUtils::IsAddonsPath("addons://path/to/addons")); +} + +TEST_F(TestURIUtils, IsSourcesPath) +{ + EXPECT_TRUE(URIUtils::IsSourcesPath("sources://path/to/sources")); +} + +TEST_F(TestURIUtils, IsCDDA) +{ + EXPECT_TRUE(URIUtils::IsCDDA("cdda://path/to/cdda")); +} + +TEST_F(TestURIUtils, IsDAAP) +{ + EXPECT_TRUE(URIUtils::IsDAAP("daap://path/to/daap")); +} + +TEST_F(TestURIUtils, IsDOSPath) +{ + EXPECT_TRUE(URIUtils::IsDOSPath("C://path/to/dosfile")); +} + +TEST_F(TestURIUtils, IsDVD) +{ + EXPECT_TRUE(URIUtils::IsDVD("dvd://path/in/video_ts.ifo")); +#if defined(TARGET_WINDOWS) + EXPECT_TRUE(URIUtils::IsDVD("dvd://path/in/file")); +#else + EXPECT_TRUE(URIUtils::IsDVD("iso9660://path/in/video_ts.ifo")); + EXPECT_TRUE(URIUtils::IsDVD("udf://path/in/video_ts.ifo")); + EXPECT_TRUE(URIUtils::IsDVD("dvd://1")); +#endif +} + +TEST_F(TestURIUtils, IsFTP) +{ + EXPECT_TRUE(URIUtils::IsFTP("ftp://path/in/ftp")); +} + +TEST_F(TestURIUtils, IsHD) +{ + EXPECT_TRUE(URIUtils::IsHD("/path/to/file")); + EXPECT_TRUE(URIUtils::IsHD("file:///path/to/file")); + EXPECT_TRUE(URIUtils::IsHD("special://path/to/file")); + EXPECT_TRUE(URIUtils::IsHD("stack://path/to/file")); + EXPECT_TRUE(URIUtils::IsHD("zip://path/to/file")); + EXPECT_TRUE(URIUtils::IsHD("rar://path/to/file")); +} + +TEST_F(TestURIUtils, IsHDHomeRun) +{ + EXPECT_TRUE(URIUtils::IsHDHomeRun("hdhomerun://path/to/file")); +} + +TEST_F(TestURIUtils, IsSlingbox) +{ + EXPECT_TRUE(URIUtils::IsSlingbox("sling://path/to/file")); +} + +TEST_F(TestURIUtils, IsHTSP) +{ + EXPECT_TRUE(URIUtils::IsHTSP("htsp://path/to/file")); +} + +TEST_F(TestURIUtils, IsInArchive) +{ + EXPECT_TRUE(URIUtils::IsInArchive("zip://path/to/file")); + EXPECT_TRUE(URIUtils::IsInArchive("rar://path/to/file")); +} + +TEST_F(TestURIUtils, IsInRAR) +{ + EXPECT_TRUE(URIUtils::IsInRAR("rar://path/to/file")); +} + +TEST_F(TestURIUtils, IsInternetStream) +{ + CURL url1("http://path/to/file"); + CURL url2("https://path/to/file"); + EXPECT_TRUE(URIUtils::IsInternetStream(url1)); + EXPECT_TRUE(URIUtils::IsInternetStream(url2)); +} + +TEST_F(TestURIUtils, IsInZIP) +{ + EXPECT_TRUE(URIUtils::IsInZIP("zip://path/to/file")); +} + +TEST_F(TestURIUtils, IsISO9660) +{ + EXPECT_TRUE(URIUtils::IsISO9660("iso9660://path/to/file")); +} + +TEST_F(TestURIUtils, IsLiveTV) +{ + EXPECT_TRUE(URIUtils::IsLiveTV("tuxbox://path/to/file")); + EXPECT_TRUE(URIUtils::IsLiveTV("vtp://path/to/file")); + EXPECT_TRUE(URIUtils::IsLiveTV("hdhomerun://path/to/file")); + EXPECT_TRUE(URIUtils::IsLiveTV("sling://path/to/file")); + EXPECT_TRUE(URIUtils::IsLiveTV("htsp://path/to/file")); + EXPECT_TRUE(URIUtils::IsLiveTV("sap://path/to/file")); + EXPECT_TRUE(URIUtils::IsLiveTV("myth://path/channels/")); +} + +TEST_F(TestURIUtils, IsMultiPath) +{ + EXPECT_TRUE(URIUtils::IsMultiPath("multipath://path/to/file")); +} + +TEST_F(TestURIUtils, IsMusicDb) +{ + EXPECT_TRUE(URIUtils::IsMusicDb("musicdb://path/to/file")); +} + +TEST_F(TestURIUtils, IsMythTV) +{ + EXPECT_TRUE(URIUtils::IsMythTV("myth://path/to/file")); +} + +TEST_F(TestURIUtils, IsNfs) +{ + EXPECT_TRUE(URIUtils::IsNfs("nfs://path/to/file")); + EXPECT_TRUE(URIUtils::IsNfs("stack://nfs://path/to/file")); +} + +TEST_F(TestURIUtils, IsAfp) +{ + EXPECT_TRUE(URIUtils::IsAfp("afp://path/to/file")); + EXPECT_TRUE(URIUtils::IsAfp("stack://afp://path/to/file")); +} + +TEST_F(TestURIUtils, IsOnDVD) +{ + EXPECT_TRUE(URIUtils::IsOnDVD("dvd://path/to/file")); + EXPECT_TRUE(URIUtils::IsOnDVD("udf://path/to/file")); + EXPECT_TRUE(URIUtils::IsOnDVD("iso9660://path/to/file")); + EXPECT_TRUE(URIUtils::IsOnDVD("cdda://path/to/file")); +} + +TEST_F(TestURIUtils, IsOnLAN) +{ + std::vector<std::string> multiVec; + multiVec.push_back("daap://path/to/file"); + EXPECT_TRUE(URIUtils::IsOnLAN(CMultiPathDirectory::ConstructMultiPath(multiVec))); + EXPECT_TRUE(URIUtils::IsOnLAN("stack://daap://path/to/file")); + EXPECT_TRUE(URIUtils::IsOnLAN("daap://path/to/file")); + EXPECT_FALSE(URIUtils::IsOnLAN("plugin://path/to/file")); + EXPECT_TRUE(URIUtils::IsOnLAN("tuxbox://path/to/file")); + EXPECT_TRUE(URIUtils::IsOnLAN("upnp://path/to/file")); +} + +TEST_F(TestURIUtils, IsPlugin) +{ + EXPECT_TRUE(URIUtils::IsPlugin("plugin://path/to/file")); +} + +TEST_F(TestURIUtils, IsScript) +{ + EXPECT_TRUE(URIUtils::IsScript("script://path/to/file")); +} + +TEST_F(TestURIUtils, IsRAR) +{ + EXPECT_TRUE(URIUtils::IsRAR("/path/to/rarfile.rar")); + EXPECT_TRUE(URIUtils::IsRAR("/path/to/rarfile.cbr")); + EXPECT_FALSE(URIUtils::IsRAR("/path/to/file")); + EXPECT_FALSE(URIUtils::IsRAR("rar://path/to/file")); +} + +TEST_F(TestURIUtils, IsRemote) +{ + EXPECT_TRUE(URIUtils::IsRemote("http://path/to/file")); + EXPECT_TRUE(URIUtils::IsRemote("https://path/to/file")); +} + +TEST_F(TestURIUtils, IsSmb) +{ + EXPECT_TRUE(URIUtils::IsSmb("smb://path/to/file")); + EXPECT_TRUE(URIUtils::IsSmb("stack://smb://path/to/file")); +} + +TEST_F(TestURIUtils, IsSpecial) +{ + EXPECT_TRUE(URIUtils::IsSpecial("special://path/to/file")); + EXPECT_TRUE(URIUtils::IsSpecial("stack://special://path/to/file")); +} + +TEST_F(TestURIUtils, IsStack) +{ + EXPECT_TRUE(URIUtils::IsStack("stack://path/to/file")); +} + +TEST_F(TestURIUtils, IsTuxBox) +{ + EXPECT_TRUE(URIUtils::IsTuxBox("tuxbox://path/to/file")); +} + +TEST_F(TestURIUtils, IsUPnP) +{ + EXPECT_TRUE(URIUtils::IsUPnP("upnp://path/to/file")); +} + +TEST_F(TestURIUtils, IsURL) +{ + EXPECT_TRUE(URIUtils::IsURL("someprotocol://path/to/file")); + EXPECT_FALSE(URIUtils::IsURL("/path/to/file")); +} + +TEST_F(TestURIUtils, IsVideoDb) +{ + EXPECT_TRUE(URIUtils::IsVideoDb("videodb://path/to/file")); +} + +TEST_F(TestURIUtils, IsVTP) +{ + EXPECT_TRUE(URIUtils::IsVTP("vtp://path/to/file")); +} + +TEST_F(TestURIUtils, IsZIP) +{ + EXPECT_TRUE(URIUtils::IsZIP("/path/to/zipfile.zip")); + EXPECT_TRUE(URIUtils::IsZIP("/path/to/zipfile.cbz")); + EXPECT_FALSE(URIUtils::IsZIP("/path/to/file")); + EXPECT_FALSE(URIUtils::IsZIP("zip://path/to/file")); +} + +TEST_F(TestURIUtils, IsBluray) +{ + EXPECT_TRUE(URIUtils::IsBluray("bluray://path/to/file")); +} + +TEST_F(TestURIUtils, AddSlashAtEnd) +{ + CStdString ref, var; + + ref = "bluray://path/to/file/"; + var = "bluray://path/to/file/"; + URIUtils::AddSlashAtEnd(var); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST_F(TestURIUtils, HasSlashAtEnd) +{ + EXPECT_TRUE(URIUtils::HasSlashAtEnd("bluray://path/to/file/")); + EXPECT_FALSE(URIUtils::HasSlashAtEnd("bluray://path/to/file")); +} + +TEST_F(TestURIUtils, RemoveSlashAtEnd) +{ + CStdString ref, var; + + ref = "bluray://path/to/file"; + var = "bluray://path/to/file/"; + URIUtils::RemoveSlashAtEnd(var); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST_F(TestURIUtils, CreateArchivePath) +{ + CStdString ref, var; + + ref = "zip://%2fpath%2fto%2f/file"; + var = URIUtils::CreateArchivePath("zip", CURL("/path/to/"), "file").Get(); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST_F(TestURIUtils, AddFileToFolder) +{ + CStdString ref = "/path/to/file"; + CStdString var = URIUtils::AddFileToFolder("/path/to", "file"); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST_F(TestURIUtils, HasParentInHostname) +{ + EXPECT_TRUE(URIUtils::HasParentInHostname(CURL("zip://"))); + EXPECT_TRUE(URIUtils::HasParentInHostname(CURL("rar://"))); + EXPECT_TRUE(URIUtils::HasParentInHostname(CURL("bluray://"))); +} + +TEST_F(TestURIUtils, HasEncodedHostname) +{ + EXPECT_TRUE(URIUtils::HasEncodedHostname(CURL("zip://"))); + EXPECT_TRUE(URIUtils::HasEncodedHostname(CURL("rar://"))); + EXPECT_TRUE(URIUtils::HasEncodedHostname(CURL("bluray://"))); + EXPECT_TRUE(URIUtils::HasEncodedHostname(CURL("musicsearch://"))); +} + +TEST_F(TestURIUtils, HasEncodedFilename) +{ + EXPECT_TRUE(URIUtils::HasEncodedFilename(CURL("shout://"))); + EXPECT_TRUE(URIUtils::HasEncodedFilename(CURL("daap://"))); + EXPECT_TRUE(URIUtils::HasEncodedFilename(CURL("dav://"))); + EXPECT_TRUE(URIUtils::HasEncodedFilename(CURL("tuxbox://"))); + EXPECT_TRUE(URIUtils::HasEncodedFilename(CURL("rss://"))); + EXPECT_TRUE(URIUtils::HasEncodedFilename(CURL("davs://"))); +} + +TEST_F(TestURIUtils, GetRealPath) +{ + std::string ref; + + ref = "/path/to/file/"; + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath(ref).c_str()); + + ref = "path/to/file"; + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("../path/to/file").c_str()); + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("./path/to/file").c_str()); + + ref = "/path/to/file"; + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath(ref).c_str()); + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("/path/to/./file").c_str()); + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("/./path/to/./file").c_str()); + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("/path/to/some/../file").c_str()); + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("/../path/to/some/../file").c_str()); + + ref = "/path/to"; + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("/path/to/some/../file/..").c_str()); + +#ifdef TARGET_WINDOWS + ref = "\\\\path\\to\\file\\"; + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath(ref).c_str()); + + ref = "path\\to\\file"; + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("..\\path\\to\\file").c_str()); + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath(".\\path\\to\\file").c_str()); + + ref = "\\\\path\\to\\file"; + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath(ref).c_str()); + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("\\\\path\\to\\.\\file").c_str()); + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("\\\\.\\path/to\\.\\file").c_str()); + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("\\\\path\\to\\some\\..\\file").c_str()); + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("\\\\..\\path\\to\\some\\..\\file").c_str()); + + ref = "\\\\path\\to"; + EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("\\\\path\\to\\some\\..\\file\\..").c_str()); +#endif + + // test rar/zip paths + ref = "rar://%2fpath%2fto%2frar/subpath/to/file"; + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath(ref).c_str()); + + // test rar/zip paths + ref = "rar://%2fpath%2fto%2frar/subpath/to/file"; + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("rar://%2fpath%2fto%2frar/../subpath/to/file").c_str()); + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("rar://%2fpath%2fto%2frar/./subpath/to/file").c_str()); + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("rar://%2fpath%2fto%2frar/subpath/to/./file").c_str()); + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("rar://%2fpath%2fto%2frar/subpath/to/some/../file").c_str()); + + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("rar://%2fpath%2fto%2f.%2frar/subpath/to/file").c_str()); + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("rar://%2fpath%2fto%2fsome%2f..%2frar/subpath/to/file").c_str()); + + // test rar/zip path in rar/zip path + ref ="zip://rar%3A%2F%2F%252Fpath%252Fto%252Frar%2Fpath%2Fto%2Fzip/subpath/to/file"; + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://rar%3A%2F%2F%252Fpath%252Fto%252Fsome%252F..%252Frar%2Fpath%2Fto%2Fsome%2F..%2Fzip/subpath/to/some/../file").c_str()); +} + +TEST_F(TestURIUtils, UpdateUrlEncoding) +{ + std::string oldUrl = "stack://rar://%2fpath%2fto%2farchive%2fsome%2darchive%2dfile%2eCD1%2erar/video.avi , rar://%2fpath%2fto%2farchive%2fsome%2darchive%2dfile%2eCD2%2erar/video.avi"; + std::string newUrl = "stack://rar://%2fpath%2fto%2farchive%2fsome-archive-file.CD1.rar/video.avi , rar://%2fpath%2fto%2farchive%2fsome-archive-file.CD2.rar/video.avi"; + + EXPECT_TRUE(URIUtils::UpdateUrlEncoding(oldUrl)); + EXPECT_STRCASEEQ(newUrl.c_str(), oldUrl.c_str()); + + oldUrl = "rar://%2fpath%2fto%2farchive%2fsome%2darchive%2efile%2erar/video.avi"; + newUrl = "rar://%2fpath%2fto%2farchive%2fsome-archive.file.rar/video.avi"; + + EXPECT_TRUE(URIUtils::UpdateUrlEncoding(oldUrl)); + EXPECT_STRCASEEQ(newUrl.c_str(), oldUrl.c_str()); + + oldUrl = "/path/to/some/long%2dnamed%2efile"; + newUrl = "/path/to/some/long%2dnamed%2efile"; + + EXPECT_FALSE(URIUtils::UpdateUrlEncoding(oldUrl)); + EXPECT_STRCASEEQ(newUrl.c_str(), oldUrl.c_str()); + + oldUrl = "/path/to/some/long-named.file"; + newUrl = "/path/to/some/long-named.file"; + + EXPECT_FALSE(URIUtils::UpdateUrlEncoding(oldUrl)); + EXPECT_STRCASEEQ(newUrl.c_str(), oldUrl.c_str()); +} diff --git a/src/utils/test/TestUrlOptions.cpp b/src/utils/test/TestUrlOptions.cpp new file mode 100644 index 0000000000..ade5855b95 --- /dev/null +++ b/src/utils/test/TestUrlOptions.cpp @@ -0,0 +1,204 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/UrlOptions.h" + +#include "gtest/gtest.h" + +TEST(TestUrlOptions, Clear) +{ + const char *key = "foo"; + + CUrlOptions urlOptions; + urlOptions.AddOption(key, "bar"); + EXPECT_TRUE(urlOptions.HasOption(key)); + + urlOptions.Clear(); + EXPECT_FALSE(urlOptions.HasOption(key)); +} + +TEST(TestUrlOptions, AddOption) +{ + const char *keyChar = "char"; + const char *keyString = "string"; + const char *keyEmpty = "empty"; + const char *keyInt = "int"; + const char *keyFloat = "float"; + const char *keyDouble = "double"; + const char *keyBool = "bool"; + + const char *valueChar = "valueChar"; + const std::string valueString = "valueString"; + const char *valueEmpty = ""; + int valueInt = 1; + float valueFloat = 1.0f; + double valueDouble = 1.0; + bool valueBool = true; + + CVariant variantValue; + + CUrlOptions urlOptions; + urlOptions.AddOption(keyChar, valueChar); + { + CVariant variantValue; + EXPECT_TRUE(urlOptions.GetOption(keyChar, variantValue)); + EXPECT_TRUE(variantValue.isString()); + EXPECT_STREQ(valueChar, variantValue.asString().c_str()); + } + + urlOptions.AddOption(keyString, valueString); + { + CVariant variantValue; + EXPECT_TRUE(urlOptions.GetOption(keyString, variantValue)); + EXPECT_TRUE(variantValue.isString()); + EXPECT_STREQ(valueString.c_str(), variantValue.asString().c_str()); + } + + urlOptions.AddOption(keyEmpty, valueEmpty); + { + CVariant variantValue; + EXPECT_TRUE(urlOptions.GetOption(keyEmpty, variantValue)); + EXPECT_TRUE(variantValue.isString()); + EXPECT_STREQ(valueEmpty, variantValue.asString().c_str()); + } + + urlOptions.AddOption(keyInt, valueInt); + { + CVariant variantValue; + EXPECT_TRUE(urlOptions.GetOption(keyInt, variantValue)); + EXPECT_TRUE(variantValue.isInteger()); + EXPECT_EQ(valueInt, (int)variantValue.asInteger()); + } + + urlOptions.AddOption(keyFloat, valueFloat); + { + CVariant variantValue; + EXPECT_TRUE(urlOptions.GetOption(keyFloat, variantValue)); + EXPECT_TRUE(variantValue.isDouble()); + EXPECT_EQ(valueFloat, variantValue.asFloat()); + } + + urlOptions.AddOption(keyDouble, valueDouble); + { + CVariant variantValue; + EXPECT_TRUE(urlOptions.GetOption(keyDouble, variantValue)); + EXPECT_TRUE(variantValue.isDouble()); + EXPECT_EQ(valueDouble, variantValue.asDouble()); + } + + urlOptions.AddOption(keyBool, valueBool); + { + CVariant variantValue; + EXPECT_TRUE(urlOptions.GetOption(keyBool, variantValue)); + EXPECT_TRUE(variantValue.isBoolean()); + EXPECT_EQ(valueBool, variantValue.asBoolean()); + } +} + +TEST(TestUrlOptions, AddOptions) +{ + std::string ref = "foo=bar&key=value"; + + CUrlOptions urlOptions(ref); + { + CVariant value; + EXPECT_TRUE(urlOptions.GetOption("foo", value)); + EXPECT_TRUE(value.isString()); + EXPECT_STREQ("bar", value.asString().c_str()); + } + { + CVariant value; + EXPECT_TRUE(urlOptions.GetOption("key", value)); + EXPECT_TRUE(value.isString()); + EXPECT_STREQ("value", value.asString().c_str()); + } + + ref = "foo=bar&key"; + urlOptions.Clear(); + urlOptions.AddOptions(ref); + { + CVariant value; + EXPECT_TRUE(urlOptions.GetOption("foo", value)); + EXPECT_TRUE(value.isString()); + EXPECT_STREQ("bar", value.asString().c_str()); + } + { + CVariant value; + EXPECT_TRUE(urlOptions.GetOption("key", value)); + EXPECT_TRUE(value.isString()); + EXPECT_TRUE(value.empty()); + } +} + +TEST(TestUrlOptions, RemoveOption) +{ + const char *key = "foo"; + + CUrlOptions urlOptions; + urlOptions.AddOption(key, "bar"); + EXPECT_TRUE(urlOptions.HasOption(key)); + + urlOptions.RemoveOption(key); + EXPECT_FALSE(urlOptions.HasOption(key)); +} + +TEST(TestUrlOptions, HasOption) +{ + const char *key = "foo"; + + CUrlOptions urlOptions; + urlOptions.AddOption(key, "bar"); + EXPECT_TRUE(urlOptions.HasOption(key)); + EXPECT_FALSE(urlOptions.HasOption("bar")); +} + +TEST(TestUrlOptions, GetOptions) +{ + const char *key1 = "foo"; + const char *key2 = "key"; + const char *value1 = "bar"; + const char *value2 = "value"; + + CUrlOptions urlOptions; + urlOptions.AddOption(key1, value1); + urlOptions.AddOption(key2, value2); + const CUrlOptions::UrlOptions &options = urlOptions.GetOptions(); + EXPECT_FALSE(options.empty()); + EXPECT_EQ(2, options.size()); + + CUrlOptions::UrlOptions::const_iterator it1 = options.find(key1); + EXPECT_TRUE(it1 != options.end()); + CUrlOptions::UrlOptions::const_iterator it2 = options.find(key2); + EXPECT_TRUE(it2 != options.end()); + EXPECT_FALSE(options.find("wrong") != options.end()); + EXPECT_TRUE(it1->second.isString()); + EXPECT_TRUE(it2->second.isString()); + EXPECT_STREQ(value1, it1->second.asString().c_str()); + EXPECT_STREQ(value2, it2->second.asString().c_str()); +} + +TEST(TestUrlOptions, GetOptionsString) +{ + const char *ref = "foo=bar&key"; + + CUrlOptions urlOptions(ref); + std::string value = urlOptions.GetOptionsString(); + EXPECT_STREQ(ref, value.c_str()); +} diff --git a/src/utils/test/TestVariant.cpp b/src/utils/test/TestVariant.cpp new file mode 100644 index 0000000000..45ee687758 --- /dev/null +++ b/src/utils/test/TestVariant.cpp @@ -0,0 +1,350 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/Variant.h" + +#include "gtest/gtest.h" + +TEST(TestVariant, VariantTypeInteger) +{ + CVariant a((int)0), b((int64_t)1); + + EXPECT_TRUE(a.isInteger()); + EXPECT_EQ(CVariant::VariantTypeInteger, a.type()); + EXPECT_TRUE(b.isInteger()); + EXPECT_EQ(CVariant::VariantTypeInteger, b.type()); + + EXPECT_EQ((int64_t)1, b.asInteger()); +} + +TEST(TestVariant, VariantTypeUnsignedInteger) +{ + CVariant a((unsigned int)0), b((uint64_t)1); + + EXPECT_TRUE(a.isUnsignedInteger()); + EXPECT_EQ(CVariant::VariantTypeUnsignedInteger, a.type()); + EXPECT_TRUE(b.isUnsignedInteger()); + EXPECT_EQ(CVariant::VariantTypeUnsignedInteger, b.type()); + + EXPECT_EQ((uint64_t)1, b.asUnsignedInteger()); +} + +TEST(TestVariant, VariantTypeBoolean) +{ + CVariant a(true); + + EXPECT_TRUE(a.isBoolean()); + EXPECT_EQ(CVariant::VariantTypeBoolean, a.type()); + + EXPECT_TRUE(a.asBoolean()); +} + +TEST(TestVariant, VariantTypeString) +{ + CVariant a("VariantTypeString"); + CVariant b("VariantTypeString2", sizeof("VariantTypeString2") - 1); + std::string str("VariantTypeString3"); + CVariant c(str); + + EXPECT_TRUE(a.isString()); + EXPECT_EQ(CVariant::VariantTypeString, a.type()); + EXPECT_TRUE(b.isString()); + EXPECT_EQ(CVariant::VariantTypeString, b.type()); + EXPECT_TRUE(c.isString()); + EXPECT_EQ(CVariant::VariantTypeString, c.type()); + + EXPECT_STREQ("VariantTypeString", a.asString().c_str()); + EXPECT_STREQ("VariantTypeString2", b.asString().c_str()); + EXPECT_STREQ("VariantTypeString3", c.asString().c_str()); +} + +TEST(TestVariant, VariantTypeWideString) +{ + CVariant a(L"VariantTypeWideString"); + CVariant b(L"VariantTypeWideString2", sizeof(L"VariantTypeWideString2") - 1); + std::wstring str(L"VariantTypeWideString3"); + CVariant c(str); + + EXPECT_TRUE(a.isWideString()); + EXPECT_EQ(CVariant::VariantTypeWideString, a.type()); + EXPECT_TRUE(b.isWideString()); + EXPECT_EQ(CVariant::VariantTypeWideString, b.type()); + EXPECT_TRUE(c.isWideString()); + EXPECT_EQ(CVariant::VariantTypeWideString, c.type()); + + EXPECT_STREQ(L"VariantTypeWideString", a.asWideString().c_str()); + EXPECT_STREQ(L"VariantTypeWideString2", b.asWideString().c_str()); + EXPECT_STREQ(L"VariantTypeWideString3", c.asWideString().c_str()); +} + +TEST(TestVariant, VariantTypeDouble) +{ + CVariant a((float)0.0f), b((double)0.1f); + + EXPECT_TRUE(a.isDouble()); + EXPECT_EQ(CVariant::VariantTypeDouble, a.type()); + EXPECT_TRUE(b.isDouble()); + EXPECT_EQ(CVariant::VariantTypeDouble, b.type()); + + EXPECT_EQ((float)0.0f, a.asDouble()); + EXPECT_EQ((double)0.1f, b.asDouble()); +} + +TEST(TestVariant, VariantTypeArray) +{ + std::vector<std::string> strarray; + strarray.push_back("string1"); + strarray.push_back("string2"); + strarray.push_back("string3"); + strarray.push_back("string4"); + CVariant a(strarray); + + EXPECT_TRUE(a.isArray()); + EXPECT_EQ(CVariant::VariantTypeArray, a.type()); +} + +TEST(TestVariant, VariantTypeObject) +{ + CVariant a; + a["key"] = "value"; + + EXPECT_TRUE(a.isObject()); + EXPECT_EQ(CVariant::VariantTypeObject, a.type()); +} + +TEST(TestVariant, VariantTypeNull) +{ + CVariant a; + + EXPECT_TRUE(a.isNull()); + EXPECT_EQ(CVariant::VariantTypeNull, a.type()); +} + +TEST(TestVariant, VariantFromMap) +{ + std::map<std::string, std::string> strMap; + strMap["key"] = "value"; + CVariant a = strMap; + + EXPECT_TRUE(a.isObject()); + EXPECT_TRUE(a.size() == 1); + EXPECT_EQ(CVariant::VariantTypeObject, a.type()); + EXPECT_TRUE(a.isMember("key")); + EXPECT_TRUE(a["key"].isString()); + EXPECT_STREQ(a["key"].asString().c_str(), "value"); + + std::map<std::string, CVariant> variantMap; + variantMap["key"] = CVariant("value"); + CVariant b = variantMap; + + EXPECT_TRUE(b.isObject()); + EXPECT_TRUE(b.size() == 1); + EXPECT_EQ(CVariant::VariantTypeObject, b.type()); + EXPECT_TRUE(b.isMember("key")); + EXPECT_TRUE(b["key"].isString()); + EXPECT_STREQ(b["key"].asString().c_str(), "value"); +} + +TEST(TestVariant, operatorTest) +{ + std::vector<std::string> strarray; + strarray.push_back("string1"); + CVariant a, b, c(strarray), d; + a["key"] = "value"; + b = a; + c[0] = "value2"; + d = c; + + EXPECT_TRUE(a.isObject()); + EXPECT_EQ(CVariant::VariantTypeObject, a.type()); + EXPECT_TRUE(b.isObject()); + EXPECT_EQ(CVariant::VariantTypeObject, b.type()); + EXPECT_TRUE(c.isArray()); + EXPECT_EQ(CVariant::VariantTypeArray, c.type()); + EXPECT_TRUE(d.isArray()); + EXPECT_EQ(CVariant::VariantTypeArray, d.type()); + + EXPECT_TRUE(a == b); + EXPECT_TRUE(c == d); + EXPECT_FALSE(a == d); + + EXPECT_STREQ("value", a["key"].asString().c_str()); + EXPECT_STREQ("value2", c[0].asString().c_str()); +} + +TEST(TestVariant, push_back) +{ + CVariant a, b("variant1"), c("variant2"), d("variant3"); + a.push_back(b); + a.push_back(c); + a.push_back(d); + + EXPECT_TRUE(a.isArray()); + EXPECT_EQ(CVariant::VariantTypeArray, a.type()); + EXPECT_STREQ("variant1", a[0].asString().c_str()); + EXPECT_STREQ("variant2", a[1].asString().c_str()); + EXPECT_STREQ("variant3", a[2].asString().c_str()); +} + +TEST(TestVariant, append) +{ + CVariant a, b("variant1"), c("variant2"), d("variant3"); + a.append(b); + a.append(c); + a.append(d); + + EXPECT_TRUE(a.isArray()); + EXPECT_EQ(CVariant::VariantTypeArray, a.type()); + EXPECT_STREQ("variant1", a[0].asString().c_str()); + EXPECT_STREQ("variant2", a[1].asString().c_str()); + EXPECT_STREQ("variant3", a[2].asString().c_str()); +} + +TEST(TestVariant, c_str) +{ + CVariant a("variant"); + + EXPECT_STREQ("variant", a.c_str()); +} + +TEST(TestVariant, swap) +{ + CVariant a((int)0), b("variant"); + + EXPECT_TRUE(a.isInteger()); + EXPECT_TRUE(b.isString()); + + a.swap(b); + EXPECT_TRUE(b.isInteger()); + EXPECT_TRUE(a.isString()); +} + +TEST(TestVariant, interator_array) +{ + std::vector<std::string> strarray; + strarray.push_back("string"); + strarray.push_back("string"); + strarray.push_back("string"); + strarray.push_back("string"); + CVariant a(strarray); + + EXPECT_TRUE(a.isArray()); + EXPECT_EQ(CVariant::VariantTypeArray, a.type()); + + CVariant::iterator_array it; + for (it = a.begin_array(); it < a.end_array(); it++) + { + EXPECT_STREQ("string", it->c_str()); + } + + CVariant::const_iterator_array const_it; + for (const_it = a.begin_array(); const_it < a.end_array(); const_it++) + { + EXPECT_STREQ("string", const_it->c_str()); + } +} + +TEST(TestVariant, iterator_map) +{ + CVariant a; + a["key1"] = "string"; + a["key2"] = "string"; + a["key3"] = "string"; + a["key4"] = "string"; + + EXPECT_TRUE(a.isObject()); + EXPECT_EQ(CVariant::VariantTypeObject, a.type()); + + CVariant::iterator_map it; + for (it = a.begin_map(); it != a.end_map(); it++) + { + EXPECT_STREQ("string", it->second.c_str()); + } + + CVariant::const_iterator_map const_it; + for (const_it = a.begin_map(); const_it != a.end_map(); const_it++) + { + EXPECT_STREQ("string", const_it->second.c_str()); + } +} + +TEST(TestVariant, size) +{ + std::vector<std::string> strarray; + strarray.push_back("string"); + strarray.push_back("string"); + strarray.push_back("string"); + strarray.push_back("string"); + CVariant a(strarray); + + EXPECT_EQ((unsigned int)4, a.size()); +} + +TEST(TestVariant, empty) +{ + std::vector<std::string> strarray; + CVariant a(strarray); + + EXPECT_TRUE(a.empty()); +} + +TEST(TestVariant, clear) +{ + std::vector<std::string> strarray; + strarray.push_back("string"); + strarray.push_back("string"); + strarray.push_back("string"); + strarray.push_back("string"); + CVariant a(strarray); + + EXPECT_FALSE(a.empty()); + a.clear(); + EXPECT_TRUE(a.empty()); +} + +TEST(TestVariant, erase) +{ + std::vector<std::string> strarray; + strarray.push_back("string1"); + strarray.push_back("string2"); + strarray.push_back("string3"); + strarray.push_back("string4"); + CVariant a, b(strarray); + a["key1"] = "string1"; + a["key2"] = "string2"; + a["key3"] = "string3"; + a["key4"] = "string4"; + + EXPECT_STREQ("string2", a["key2"].c_str()); + EXPECT_STREQ("string2", b[1].c_str()); + a.erase("key2"); + b.erase(1); + EXPECT_FALSE(a["key2"].c_str()); + EXPECT_STREQ("string3", b[1].c_str()); +} + +TEST(TestVariant, isMember) +{ + CVariant a; + a["key1"] = "string1"; + + EXPECT_TRUE(a.isMember("key1")); + EXPECT_FALSE(a.isMember("key2")); +} diff --git a/src/utils/test/TestXBMCTinyXML.cpp b/src/utils/test/TestXBMCTinyXML.cpp new file mode 100644 index 0000000000..0072d1b4f3 --- /dev/null +++ b/src/utils/test/TestXBMCTinyXML.cpp @@ -0,0 +1,70 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/XBMCTinyXML.h" +#include "utils/StringUtils.h" +#include "test/TestUtils.h" + +#include "gtest/gtest.h" + +TEST(TestXBMCTinyXML, ParseFromString) +{ + bool retval = false; + // scraper results with unescaped & + CXBMCTinyXML doc; + std::string data("<details><url function=\"ParseTMDBRating\" " + "cache=\"tmdb-en-12244.json\">" + "http://api.themoviedb.org/3/movie/12244" + "?api_key=57983e31fb435df4df77afb854740ea9" + "&language=en???</url></details>"); + doc.Parse(data.c_str()); + TiXmlNode *root = doc.RootElement(); + if (root && root->ValueStr() == "details") + { + TiXmlElement *url = root->FirstChildElement("url"); + if (url && url->FirstChild()) + { + retval = (url->FirstChild()->ValueStr() == "http://api.themoviedb.org/3/movie/12244?api_key=57983e31fb435df4df77afb854740ea9&language=en???"); + } + } + EXPECT_TRUE(retval); +} + +TEST(TestXBMCTinyXML, ParseFromFileHandle) +{ + bool retval = false; + // scraper results with unescaped & + CXBMCTinyXML doc; + FILE *f = fopen(XBMC_REF_FILE_PATH("/xbmc/utils/test/CXBMCTinyXML-test.xml").c_str(), "r"); + ASSERT_TRUE(f); + doc.LoadFile(f); + fclose(f); + TiXmlNode *root = doc.RootElement(); + if (root && root->ValueStr() == "details") + { + TiXmlElement *url = root->FirstChildElement("url"); + if (url && url->FirstChild()) + { + std::string str = url->FirstChild()->ValueStr(); + retval = (StringUtils::Trim(str) == "http://api.themoviedb.org/3/movie/12244?api_key=57983e31fb435df4df77afb854740ea9&language=en???"); + } + } + EXPECT_TRUE(retval); +} diff --git a/src/utils/test/TestXMLUtils.cpp b/src/utils/test/TestXMLUtils.cpp new file mode 100644 index 0000000000..7170c425dc --- /dev/null +++ b/src/utils/test/TestXMLUtils.cpp @@ -0,0 +1,368 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/XMLUtils.h" +#include "utils/StringUtils.h" +#include "XBDateTime.h" + +#include "gtest/gtest.h" + +TEST(TestXMLUtils, GetHex) +{ + CXBMCTinyXML a; + uint32_t ref, val; + + a.Parse("<root><node>0xFF</node></root>"); + EXPECT_TRUE(XMLUtils::GetHex(a.RootElement(), "node", val)); + + ref = 0xFF; + EXPECT_EQ(ref, val); +} + +TEST(TestXMLUtils, GetUInt) +{ + CXBMCTinyXML a; + uint32_t ref, val; + + a.Parse("<root><node>1000</node></root>"); + EXPECT_TRUE(XMLUtils::GetUInt(a.RootElement(), "node", val)); + + ref = 1000; + EXPECT_EQ(ref, val); +} + +TEST(TestXMLUtils, GetLong) +{ + CXBMCTinyXML a; + long ref, val; + + a.Parse("<root><node>1000</node></root>"); + EXPECT_TRUE(XMLUtils::GetLong(a.RootElement(), "node", val)); + + ref = 1000; + EXPECT_EQ(ref, val); +} + +TEST(TestXMLUtils, GetFloat) +{ + CXBMCTinyXML a; + float ref, val; + + a.Parse("<root><node>1000.1f</node></root>"); + EXPECT_TRUE(XMLUtils::GetFloat(a.RootElement(), "node", val)); + EXPECT_TRUE(XMLUtils::GetFloat(a.RootElement(), "node", val, 1000.0f, + 1000.2f)); + ref = 1000.1f; + EXPECT_EQ(ref, val); +} + +TEST(TestXMLUtils, GetDouble) +{ + CXBMCTinyXML a; + double val; + std::string refstr, valstr; + + a.Parse("<root><node>1000.1f</node></root>"); + EXPECT_TRUE(XMLUtils::GetDouble(a.RootElement(), "node", val)); + + refstr = "1000.100000"; + valstr = StringUtils::Format("%f", val); + EXPECT_STREQ(refstr.c_str(), valstr.c_str()); +} + +TEST(TestXMLUtils, GetInt) +{ + CXBMCTinyXML a; + int ref, val; + + a.Parse("<root><node>1000</node></root>"); + EXPECT_TRUE(XMLUtils::GetInt(a.RootElement(), "node", val)); + EXPECT_TRUE(XMLUtils::GetInt(a.RootElement(), "node", val, 999, 1001)); + + ref = 1000; + EXPECT_EQ(ref, val); +} + +TEST(TestXMLUtils, GetBoolean) +{ + CXBMCTinyXML a; + bool ref, val; + + a.Parse("<root><node>true</node></root>"); + EXPECT_TRUE(XMLUtils::GetBoolean(a.RootElement(), "node", val)); + + ref = true; + EXPECT_EQ(ref, val); +} + +TEST(TestXMLUtils, GetString) +{ + CXBMCTinyXML a; + std::string ref, val; + + a.Parse("<root><node>some string</node></root>"); + EXPECT_TRUE(XMLUtils::GetString(a.RootElement(), "node", val)); + + ref = "some string"; + EXPECT_STREQ(ref.c_str(), val.c_str()); +} + +TEST(TestXMLUtils, GetAdditiveString) +{ + CXBMCTinyXML a, b; + std::string ref, val; + + a.Parse("<root>\n" + " <node>some string1</node>\n" + " <node>some string2</node>\n" + " <node>some string3</node>\n" + " <node>some string4</node>\n" + " <node>some string5</node>\n" + "</root>\n"); + EXPECT_TRUE(XMLUtils::GetAdditiveString(a.RootElement(), "node", ",", val)); + + ref = "some string1,some string2,some string3,some string4,some string5"; + EXPECT_STREQ(ref.c_str(), val.c_str()); + + val.clear(); + b.Parse("<root>\n" + " <node>some string1</node>\n" + " <node>some string2</node>\n" + " <node clear=\"true\">some string3</node>\n" + " <node>some string4</node>\n" + " <node>some string5</node>\n" + "</root>\n"); + EXPECT_TRUE(XMLUtils::GetAdditiveString(b.RootElement(), "node", ",", val)); + + ref = "some string3,some string4,some string5"; + EXPECT_STREQ(ref.c_str(), val.c_str()); +} + +TEST(TestXMLUtils, GetStringArray) +{ + CXBMCTinyXML a; + std::vector<std::string> strarray; + + a.Parse("<root>\n" + " <node>some string1</node>\n" + " <node>some string2</node>\n" + " <node>some string3</node>\n" + " <node>some string4</node>\n" + " <node>some string5</node>\n" + "</root>\n"); + EXPECT_TRUE(XMLUtils::GetStringArray(a.RootElement(), "node", strarray)); + + EXPECT_STREQ("some string1", strarray.at(0).c_str()); + EXPECT_STREQ("some string2", strarray.at(1).c_str()); + EXPECT_STREQ("some string3", strarray.at(2).c_str()); + EXPECT_STREQ("some string4", strarray.at(3).c_str()); + EXPECT_STREQ("some string5", strarray.at(4).c_str()); +} + +TEST(TestXMLUtils, GetPath) +{ + CXBMCTinyXML a, b; + std::string ref, val; + + a.Parse("<root><node urlencoded=\"yes\">special://xbmc/</node></root>"); + EXPECT_TRUE(XMLUtils::GetPath(a.RootElement(), "node", val)); + + ref = "special://xbmc/"; + EXPECT_STREQ(ref.c_str(), val.c_str()); + + val.clear(); + b.Parse("<root><node>special://xbmcbin/</node></root>"); + EXPECT_TRUE(XMLUtils::GetPath(b.RootElement(), "node", val)); + + ref = "special://xbmcbin/"; + EXPECT_STREQ(ref.c_str(), val.c_str()); +} + +TEST(TestXMLUtils, GetDate) +{ + CXBMCTinyXML a; + CDateTime ref, val; + + a.Parse("<root><node>2012-07-08</node></root>"); + EXPECT_TRUE(XMLUtils::GetDate(a.RootElement(), "node", val)); + ref.SetDate(2012, 7, 8); + EXPECT_TRUE(ref == val); +} + +TEST(TestXMLUtils, GetDateTime) +{ + CXBMCTinyXML a; + CDateTime ref, val; + + a.Parse("<root><node>2012-07-08 01:02:03</node></root>"); + EXPECT_TRUE(XMLUtils::GetDateTime(a.RootElement(), "node", val)); + ref.SetDateTime(2012, 7, 8, 1, 2, 3); + EXPECT_TRUE(ref == val); +} + +TEST(TestXMLUtils, SetString) +{ + CXBMCTinyXML a; + std::string ref, val; + + a.Parse("<root></root>"); + XMLUtils::SetString(a.RootElement(), "node", "some string"); + EXPECT_TRUE(XMLUtils::GetString(a.RootElement(), "node", val)); + + ref = "some string"; + EXPECT_STREQ(ref.c_str(), val.c_str()); +} + +TEST(TestXMLUtils, SetAdditiveString) +{ + CXBMCTinyXML a; + std::string ref, val; + + a.Parse("<root></root>"); + XMLUtils::SetAdditiveString(a.RootElement(), "node", ",", + "some string1,some string2,some string3,some string4,some string5"); + EXPECT_TRUE(XMLUtils::GetAdditiveString(a.RootElement(), "node", ",", val)); + + ref = "some string1,some string2,some string3,some string4,some string5"; + EXPECT_STREQ(ref.c_str(), val.c_str()); +} + +TEST(TestXMLUtils, SetStringArray) +{ + CXBMCTinyXML a; + std::vector<std::string> strarray; + strarray.push_back("some string1"); + strarray.push_back("some string2"); + strarray.push_back("some string3"); + strarray.push_back("some string4"); + strarray.push_back("some string5"); + + a.Parse("<root></root>"); + XMLUtils::SetStringArray(a.RootElement(), "node", strarray); + EXPECT_TRUE(XMLUtils::GetStringArray(a.RootElement(), "node", strarray)); + + EXPECT_STREQ("some string1", strarray.at(0).c_str()); + EXPECT_STREQ("some string2", strarray.at(1).c_str()); + EXPECT_STREQ("some string3", strarray.at(2).c_str()); + EXPECT_STREQ("some string4", strarray.at(3).c_str()); + EXPECT_STREQ("some string5", strarray.at(4).c_str()); +} + +TEST(TestXMLUtils, SetInt) +{ + CXBMCTinyXML a; + int ref, val; + + a.Parse("<root></root>"); + XMLUtils::SetInt(a.RootElement(), "node", 1000); + EXPECT_TRUE(XMLUtils::GetInt(a.RootElement(), "node", val)); + + ref = 1000; + EXPECT_EQ(ref, val); +} + +TEST(TestXMLUtils, SetFloat) +{ + CXBMCTinyXML a; + float ref, val; + + a.Parse("<root></root>"); + XMLUtils::SetFloat(a.RootElement(), "node", 1000.1f); + EXPECT_TRUE(XMLUtils::GetFloat(a.RootElement(), "node", val)); + + ref = 1000.1f; + EXPECT_EQ(ref, val); +} + +TEST(TestXMLUtils, SetBoolean) +{ + CXBMCTinyXML a; + bool ref, val; + + a.Parse("<root></root>"); + XMLUtils::SetBoolean(a.RootElement(), "node", true); + EXPECT_TRUE(XMLUtils::GetBoolean(a.RootElement(), "node", val)); + + ref = true; + EXPECT_EQ(ref, val); +} + +TEST(TestXMLUtils, SetHex) +{ + CXBMCTinyXML a; + uint32_t ref, val; + + a.Parse("<root></root>"); + XMLUtils::SetHex(a.RootElement(), "node", 0xFF); + EXPECT_TRUE(XMLUtils::GetHex(a.RootElement(), "node", val)); + + ref = 0xFF; + EXPECT_EQ(ref, val); +} + +TEST(TestXMLUtils, SetPath) +{ + CXBMCTinyXML a; + std::string ref, val; + + a.Parse("<root></root>"); + XMLUtils::SetPath(a.RootElement(), "node", "special://xbmc/"); + EXPECT_TRUE(XMLUtils::GetPath(a.RootElement(), "node", val)); + + ref = "special://xbmc/"; + EXPECT_STREQ(ref.c_str(), val.c_str()); +} + +TEST(TestXMLUtils, SetLong) +{ + CXBMCTinyXML a; + long ref, val; + + a.Parse("<root></root>"); + XMLUtils::SetLong(a.RootElement(), "node", 1000); + EXPECT_TRUE(XMLUtils::GetLong(a.RootElement(), "node", val)); + + ref = 1000; + EXPECT_EQ(ref, val); +} + +TEST(TestXMLUtils, SetDate) +{ + CXBMCTinyXML a; + CDateTime ref, val; + + a.Parse("<root></root>"); + ref.SetDate(2012, 7, 8); + XMLUtils::SetDate(a.RootElement(), "node", ref); + EXPECT_TRUE(XMLUtils::GetDate(a.RootElement(), "node", val)); + EXPECT_TRUE(ref == val); +} + +TEST(TestXMLUtils, SetDateTime) +{ + CXBMCTinyXML a; + CDateTime ref, val; + + a.Parse("<root></root>"); + ref.SetDateTime(2012, 7, 8, 1, 2, 3); + XMLUtils::SetDateTime(a.RootElement(), "node", ref); + EXPECT_TRUE(XMLUtils::GetDateTime(a.RootElement(), "node", val)); + EXPECT_TRUE(ref == val); +} diff --git a/src/utils/test/Testfastmemcpy.cpp b/src/utils/test/Testfastmemcpy.cpp new file mode 100644 index 0000000000..3299f732d5 --- /dev/null +++ b/src/utils/test/Testfastmemcpy.cpp @@ -0,0 +1,39 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <stddef.h> // TODO: This should go in fastmemcpy.h instead. +#include "utils/fastmemcpy.h" + +#include "gtest/gtest.h" + +static const char refdata[] = "\x01\x02\x03\x04\x05\x06\x07\x08" + "\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" + "\x11\x12\x13\x14\x15\x16\x17\x18" + "\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20" + "\x21\x22\x23\x24\x25\x26\x27\x28" + "\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"; + +TEST(Testfastmemcpy, General) +{ + char vardata[sizeof(refdata)]; + memset(vardata, 0, sizeof(vardata)); + EXPECT_TRUE(fast_memcpy(vardata, refdata, sizeof(refdata))); + EXPECT_TRUE(!memcmp(refdata, vardata, sizeof(refdata))); +} diff --git a/src/utils/test/Testfft.cpp b/src/utils/test/Testfft.cpp new file mode 100644 index 0000000000..25bd324f3f --- /dev/null +++ b/src/utils/test/Testfft.cpp @@ -0,0 +1,301 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/fft.h" +#include "utils/StdString.h" +#include "utils/StringUtils.h" + +#include "gtest/gtest.h" + +/* refdata[] below was generated using the following Python script. + +import math +import wave +import struct +import sys + +if __name__=='__main__': + # http://stackoverflow.com/questions/3637350/how-to-write-stereo-wav-files-in-python + # http://www.sonicspot.com/guide/wavefiles.html + freq=440.0 + data_size=128 + fname="test.wav" + frate=11025.0 + amp=64000.0 + nchannels=1 + sampwidth=2 + framerate=int(frate) + nframes=data_size + comptype="NONE" + compname="not compressed" + data=[math.sin(2*math.pi*freq*(x/frate)) + for x in range(data_size)] + count = 0 + sys.stdout.write("static const float refdata[] = {\n") + for v in data: + sys.stdout.write(str(v) + "f,") + count += 1 + if count % 4 == 0: + sys.stdout.write("\n") + else: + sys.stdout.write(" ") + sys.stdout.write("};\n") +*/ + +static const float refdata[] = { +0.0f, 0.248137847944f, 0.480754541017f, 0.683299780871f, +0.843104254616f, 0.950172107096f, 0.997806186976f, 0.983026957126f, +0.906758866265f, 0.773772524197f, 0.592386297618f, 0.373945991846f, +0.132115164631f, -0.117979536667f, -0.360694554958f, -0.580847936259f, +-0.764668969029f, -0.900659549526f, -0.980313394334f, -0.998648112931f, +-0.954516858881f, -0.850680065687f, -0.693632780202f, -0.493198393981f, +-0.261914184895f, -0.0142471037071f, 0.234311141425f, 0.46821309956f, +0.672828078337f, 0.835357301588f, 0.945634479622f, 0.996761716077f, +0.985540975016f, 0.912674119782f, 0.782719011074f, 0.603804410325f, +0.387121521337f, 0.146223974503f, -0.103819960006f, -0.347369900587f, +-0.569191668423f, -0.755410193505f, -0.894377407666f, -0.977400837465f, +-0.999287323041f, -0.958667853037f, -0.858083196987f, -0.703824978831f, +-0.505542132473f, -0.275637355817f, -0.0284913153908f, 0.220436872025f, +0.455576615403f, 0.662219798253f, 0.827440779159f, 0.940904897555f, +0.995514912254f, 0.987854937681f, 0.918404109336f, 0.791506613611f, +0.615099956615f, 0.400218468928f, 0.160303102331f, -0.0896393089034f, +-0.333974733507f, -0.557419860219f, -0.745998077066f, -0.887913715899f, +-0.974289877741f, -0.999723687553f, -0.96262424695f, -0.865312145751f, +-0.71387430784f, -0.517783250833f, -0.289304575039f, -0.0427297436149f, +0.206517856086f, 0.442847653627f, 0.651477093995f, 0.819356294308f, +0.935984320952f, 0.994066028594f, 0.989968375411f, 0.923947671797f, +0.800133548011f, 0.626270643602f, 0.413234176067f, 0.17434969019f, +-0.0754404618937f, -0.320511772808f, -0.545534901211f, -0.736434530278f, +-0.881269786291f, -0.970981146657f, -0.999957117888f, -0.966385237512f, +-0.872365444572f, -0.723778727314f, -0.529919264233f, -0.302913068248f, +-0.056959498117f, 0.192556919032f, 0.430028798089f, 0.64060214623f, +0.811105488104f, 0.930873748644f, 0.992415359208f, 0.991880859198f, +0.929303681875f, 0.80859806309f, 0.637314203745f, 0.42616600069f, +0.188360886759f, -0.0612263012046f, -0.306983751339f, -0.533539203927f, +-0.72672149445f, -0.874446967495f, -0.967475315852f, -0.999987566662f, +-0.969950061278f, -0.879241661701f, -0.733536226752f, -0.541947709182f, +-0.316460073053f, -0.0711776903954f, 0.178556894799f, 0.417122650891f +}; + +#define REFDATA_NUMELEMENTS 128 /*(sizeof(refdata)/sizeof(float))*/ + +/* All reference data below were generated by using the following C++ code. + + fprintf(stdout, "static const float reffftdata[] = {\n"); + for (i = 0; i < REFDATA_NUMELEMENTS; i++) + { + fprintf(stdout, "%.6ff,", vardata[i]); + if ((i + 1) % 4 == 0) + fprintf(stdout, "\n"); + else + fprintf(stdout, " "); + } + fprintf(stdout, "};\n"); + +*/ + +static const float reffftdata[] = { +0.000000f, 0.449505f, 0.120648f, 0.233138f, +0.392939f, 0.043552f, 0.833054f, -0.148657f, +1.675773f, -0.424927f, 3.999525f, -3.602860f, +48.666092f, 0.123507f, -6.779173f, -0.179830f, +-3.556417f, -0.323241f, -2.528245f, -0.424007f, +-1.999831f, -0.503748f, -1.662579f, -0.569499f, +-1.417722f, -0.624251f, -1.224108f, -0.669501f, +-1.061725f, -0.706093f, -0.919814f, -0.734565f, +-0.792209f, -0.755297f, -0.675225f, -0.768604f, +-0.566617f, -0.774772f, -0.465016f, -0.774090f, +-0.369611f, -0.766866f, -0.279953f, -0.753430f, +-0.195832f, -0.734147f, -0.117191f, -0.709420f, +-0.044083f, -0.679685f, 0.023383f, -0.645416f, +0.085060f, -0.607121f, 0.140793f, -0.565342f, +0.190426f, -0.520648f, 0.233821f, -0.473637f, +0.270868f, -0.424926f, 0.301492f, -0.375151f, +0.325655f, -0.324962f, 0.343369f, -0.275019f, +0.354691f, -0.225984f, 0.359731f, -0.178525f, +0.358652f, -0.133307f, 0.351671f, -0.090984f, +0.339060f, -0.052212f, 0.321141f, -0.017629f, +0.298293f, 0.012125f, 0.270944f, 0.036423f, +0.239573f, 0.054633f, 0.204705f, 0.066113f, +0.166914f, 0.070200f, 0.126814f, 0.066174f, +0.085068f, 0.053236f, 0.042377f, 0.030439f, +-0.000509f, -0.003392f, -0.042792f, -0.049815f, +-0.083613f, -0.111011f, -0.122037f, -0.190212f, +-0.157020f, -0.292521f, -0.187344f, -0.426526f, +-0.211503f, -0.607937f, -0.227426f, -0.868800f, +-0.231823f, -1.285804f, -0.218194f, -2.098434f, +-0.168627f, -4.622009f, 0.009162f, 38.381283f, +-2.935104f, 3.676884f, -0.513415f, 1.829207f, +-0.334315f, 1.124193f, -0.207913f, 0.725023f, +}; + +static const float reffftinversedata[] = { +0.000000f, 0.449505f, 0.120648f, 0.725023f, +-0.066419f, 1.124193f, -0.207913f, 1.829207f, +-0.334315f, 3.676884f, -0.513415f, 38.381283f, +-2.935104f, -4.622009f, 0.009162f, -2.098434f, +-0.168627f, -1.285804f, -0.218194f, -0.868800f, +-0.231823f, -0.607937f, -0.227426f, -0.426526f, +-0.211503f, -0.292521f, -0.187344f, -0.190212f, +-0.157020f, -0.111011f, -0.122037f, -0.049815f, +-0.083613f, -0.003392f, -0.042792f, 0.030439f, +-0.000509f, 0.053236f, 0.042377f, 0.066174f, +0.085068f, 0.070200f, 0.126814f, 0.066113f, +0.166914f, 0.054633f, 0.204705f, 0.036423f, +0.239573f, 0.012125f, 0.270944f, -0.017629f, +0.298293f, -0.052212f, 0.321141f, -0.090984f, +0.339060f, -0.133307f, 0.351671f, -0.178525f, +0.358652f, -0.225984f, 0.359731f, -0.275019f, +0.354691f, -0.324962f, 0.343369f, -0.375151f, +0.325655f, -0.424926f, 0.301492f, -0.473637f, +0.270868f, -0.520648f, 0.233821f, -0.565342f, +0.190426f, -0.607121f, 0.140793f, -0.645416f, +0.085060f, -0.679685f, 0.023383f, -0.709420f, +-0.044083f, -0.734147f, -0.117191f, -0.753430f, +-0.195832f, -0.766866f, -0.279953f, -0.774090f, +-0.369611f, -0.774772f, -0.465016f, -0.768604f, +-0.566617f, -0.755297f, -0.675225f, -0.734565f, +-0.792209f, -0.706093f, -0.919814f, -0.669501f, +-1.061725f, -0.624251f, -1.224108f, -0.569499f, +-1.417722f, -0.503748f, -1.662579f, -0.424007f, +-1.999831f, -0.323241f, -2.528245f, -0.179830f, +-3.556417f, 0.123507f, -6.779173f, -3.602860f, +48.666092f, -0.424927f, 3.999525f, -0.148657f, +1.675773f, 0.043552f, 0.833054f, 0.233138f, +}; + +static const float reftwochannelrfftdata[] = { +0.014556f, 0.202055f, 0.174283f, 0.564540f, +0.779294f, 1.223621f, 2.855724f, 3.432353f, +14.488905f, 15.470927f, 1926.995483f, 1936.110718f, +34.176479f, 33.159004f, 8.778496f, 8.333804f, +4.234725f, 3.962682f, 2.589059f, 2.398601f, +1.791486f, 1.647755f, 1.337403f, 1.223514f, +1.051121f, 0.957675f, 0.857526f, 0.778798f, +0.719788f, 0.652054f, 0.617972f, 0.558680f, +0.540455f, 0.487790f, 0.480051f, 0.432682f, +0.432111f, 0.389031f, 0.393506f, 0.353940f, +0.362065f, 0.325402f, 0.336244f, 0.301994f, +0.314917f, 0.282679f, 0.297250f, 0.266693f, +0.282615f, 0.253461f, 0.270538f, 0.242548f, +0.260654f, 0.233622f, 0.252682f, 0.226430f, +0.246418f, 0.220773f, 0.241694f, 0.216511f, +0.238397f, 0.213538f, 0.236449f, 0.211782f, +0.117902f, 0.105600f, -0.357960f, -0.308602f, +-0.372784f, -0.292395f, -0.387933f, -0.276250f, +-0.403502f, -0.260078f, -0.419604f, -0.243792f, +-0.436362f, -0.227290f, -0.453916f, -0.210478f, +-0.472433f, -0.193249f, -0.492110f, -0.175478f, +-0.513182f, -0.157028f, -0.535943f, -0.137741f, +-0.560756f, -0.117426f, -0.588082f, -0.095855f, +-0.618519f, -0.072741f, -0.652857f, -0.047725f, +-0.692169f, -0.020336f, -0.737947f, 0.010054f, +-0.792340f, 0.044317f, -0.858569f, 0.083711f, +-0.941691f, 0.130146f, -1.050159f, 0.186711f, +-1.199277f, 0.258840f, -1.419963f, 0.357229f, +-1.785721f, 0.506807f, -2.525070f, 0.783990f, +-4.890463f, 1.604234f, 36.070564f, -11.902861f, +3.081995f, -0.935849f, 1.359818f, -0.300774f, +0.721637f, -0.007045f, 0.368046f, 0.218320f, +}; + +static const float reftwochanwithwindowdata[] = { +0.000078f, 0.000219f, 0.000856f, 0.001174f, +0.007080f, 0.007566f, 0.107324f, 0.108530f, +90.137039f, 90.161346f, 504.375732f, 504.333801f, +173.107437f, 173.125870f, 0.244316f, 0.243784f, +0.012957f, 0.012861f, 0.001967f, 0.001938f, +0.000483f, 0.000471f, 0.000157f, 0.000152f, +0.000062f, 0.000059f, 0.000028f, 0.000026f, +0.000014f, 0.000013f, 0.000007f, 0.000007f, +0.000004f, 0.000004f, 0.000003f, 0.000002f, +0.000002f, 0.000001f, 0.000001f, 0.000001f, +0.000001f, 0.000001f, 0.000000f, 0.000000f, +0.000000f, 0.000000f, 0.000000f, 0.000000f, +0.000000f, 0.000000f, 0.000000f, 0.000000f, +0.000000f, 0.000000f, 0.000000f, 0.000000f, +0.000000f, 0.000000f, 0.000000f, 0.000000f, +0.000000f, 0.000000f, 0.000000f, 0.000000f, +0.000000f, 0.000000f, 0.000058f, 0.000038f, +0.000081f, 0.000016f, 0.000106f, -0.000007f, +0.000133f, -0.000029f, 0.000164f, -0.000052f, +0.000199f, -0.000077f, 0.000241f, -0.000105f, +0.000290f, -0.000135f, 0.000349f, -0.000169f, +0.000422f, -0.000210f, 0.000513f, -0.000257f, +0.000628f, -0.000314f, 0.000778f, -0.000385f, +0.000975f, -0.000475f, 0.001243f, -0.000593f, +0.001616f, -0.000750f, 0.002154f, -0.000969f, +0.002959f, -0.001283f, 0.004224f, -0.001760f, +0.006336f, -0.002532f, 0.010161f, -0.003890f, +0.017893f, -0.006565f, 0.036268f, -0.012797f, +0.093398f, -0.031901f, 0.406511f, -0.135766f, +-10.831606f, 3.581835f, 18.487400f, -6.118526f, +-7.816598f, 2.582984f, -0.270999f, 0.085336f, +-0.071148f, 0.017091f, -0.026548f, -0.001455f, +}; + +TEST(Testfft, fft) +{ + int i; + float vardata[REFDATA_NUMELEMENTS]; + float res; + + memcpy(vardata, refdata, sizeof(refdata)); + fft(vardata - 1, REFDATA_NUMELEMENTS/2, 1); + // let's see if it's okay enough + fft(vardata -1, REFDATA_NUMELEMENTS/2, -1); + for (i = 0; i < REFDATA_NUMELEMENTS; i++) + { + res = vardata[i] / (REFDATA_NUMELEMENTS / 2); + EXPECT_NEAR(res, refdata[i], 0.000001); + } +} + +TEST(Testfft, twochannelrfft) +{ + int i; + float vardata[REFDATA_NUMELEMENTS]; + CStdString refstr, varstr; + + memcpy(vardata, refdata, sizeof(refdata)); + twochannelrfft(vardata, REFDATA_NUMELEMENTS/2); + for (i = 0; i < REFDATA_NUMELEMENTS; i++) + { + refstr = StringUtils::Format("%.6f", reftwochannelrfftdata[i]); + varstr = StringUtils::Format("%.6f", vardata[i]); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + } +} + +TEST(Testfft, twochanwithwindow) +{ + int i; + float vardata[REFDATA_NUMELEMENTS]; + CStdString refstr, varstr; + + memcpy(vardata, refdata, sizeof(refdata)); + twochanwithwindow(vardata, REFDATA_NUMELEMENTS/2); + for (i = 0; i < REFDATA_NUMELEMENTS; i++) + { + refstr = StringUtils::Format("%.6f", reftwochanwithwindowdata[i]); + varstr = StringUtils::Format("%.6f", vardata[i]); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + } +} diff --git a/src/utils/test/Testfstrcmp.cpp b/src/utils/test/Testfstrcmp.cpp new file mode 100644 index 0000000000..be4605f8d8 --- /dev/null +++ b/src/utils/test/Testfstrcmp.cpp @@ -0,0 +1,47 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/fstrcmp.h" +#include "utils/StdString.h" +#include "utils/StringUtils.h" + +#include "gtest/gtest.h" + +TEST(Testfstrcmp, General) +{ + CStdString refstr, varstr, refresult, varresult; + refstr = "Testfstrcmp test string"; + varstr = refstr; + + /* NOTE: Third parameter is not used at all in fstrcmp. */ + refresult = "1.000000"; + varresult = StringUtils::Format("%.6f", fstrcmp(refstr.c_str(), varstr.c_str(), 0.0)); + EXPECT_STREQ(refresult.c_str(), varresult.c_str()); + + varstr = "Testfstrcmp_test_string"; + refresult = "0.913043"; + varresult = StringUtils::Format("%.6f", fstrcmp(refstr.c_str(), varstr.c_str(), 0.0)); + EXPECT_STREQ(refresult.c_str(), varresult.c_str()); + + varstr = ""; + refresult = "0.000000"; + varresult = StringUtils::Format("%.6f", fstrcmp(refstr.c_str(), varstr.c_str(), 0.0)); + EXPECT_STREQ(refresult.c_str(), varresult.c_str()); +} diff --git a/src/utils/test/Testlog.cpp b/src/utils/test/Testlog.cpp new file mode 100644 index 0000000000..656201f959 --- /dev/null +++ b/src/utils/test/Testlog.cpp @@ -0,0 +1,153 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/log.h" +#include "utils/RegExp.h" +#include "filesystem/File.h" +#include "filesystem/SpecialProtocol.h" +#include "utils/StdString.h" +#include "utils/StringUtils.h" +#include "CompileInfo.h" + +#include "test/TestUtils.h" + +#include "gtest/gtest.h" + +class Testlog : public testing::Test +{ +protected: + Testlog(){} + ~Testlog() + { + CLog::Close(); + } +}; + +TEST_F(Testlog, Log) +{ + CStdString logfile, logstring; + char buf[100]; + unsigned int bytesread; + XFILE::CFile file; + CRegExp regex; + + std::string appName = CCompileInfo::GetAppName(); + StringUtils::ToLower(appName); + logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log"; + EXPECT_TRUE(CLog::Init(CSpecialProtocol::TranslatePath("special://temp/").c_str())); + EXPECT_TRUE(XFILE::CFile::Exists(logfile)); + + CLog::Log(LOGDEBUG, "debug log message"); + CLog::Log(LOGINFO, "info log message"); + CLog::Log(LOGNOTICE, "notice log message"); + CLog::Log(LOGWARNING, "warning log message"); + CLog::Log(LOGERROR, "error log message"); + CLog::Log(LOGSEVERE, "severe log message"); + CLog::Log(LOGFATAL, "fatal log message"); + CLog::Log(LOGNONE, "none type log message"); + CLog::Close(); + + EXPECT_TRUE(file.Open(logfile)); + while ((bytesread = file.Read(buf, sizeof(buf) - 1)) > 0) + { + buf[bytesread] = '\0'; + logstring.append(buf); + } + file.Close(); + EXPECT_FALSE(logstring.empty()); + + EXPECT_STREQ("\xEF\xBB\xBF", logstring.substr(0, 3).c_str()); + + EXPECT_TRUE(regex.RegComp(".*DEBUG: debug log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*INFO: info log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*NOTICE: notice log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*WARNING: warning log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*ERROR: error log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*SEVERE: severe log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*FATAL: fatal log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*NONE: none type log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + + EXPECT_TRUE(XFILE::CFile::Delete(logfile)); +} + +TEST_F(Testlog, MemDump) +{ + CStdString logfile, logstring; + char buf[100]; + unsigned int bytesread; + XFILE::CFile file; + CRegExp regex; + char refdata[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + + std::string appName = CCompileInfo::GetAppName(); + StringUtils::ToLower(appName); + logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log"; + EXPECT_TRUE(CLog::Init(CSpecialProtocol::TranslatePath("special://temp/").c_str())); + EXPECT_TRUE(XFILE::CFile::Exists(logfile)); + + CLog::MemDump(refdata, sizeof(refdata)); + CLog::Close(); + + EXPECT_TRUE(file.Open(logfile)); + while ((bytesread = file.Read(buf, sizeof(buf) - 1)) > 0) + { + buf[bytesread] = '\0'; + logstring.append(buf); + } + file.Close(); + EXPECT_FALSE(logstring.empty()); + + EXPECT_STREQ("\xEF\xBB\xBF", logstring.substr(0, 3).c_str()); + + EXPECT_TRUE(regex.RegComp(".*DEBUG: MEM_DUMP: Dumping from.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*DEBUG: MEM_DUMP: 0000 30 31 32 33.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*73 74 75 76 ghijklmnopqrstuv.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + + EXPECT_TRUE(XFILE::CFile::Delete(logfile)); +} + +TEST_F(Testlog, SetLogLevel) +{ + CStdString logfile; + + std::string appName = CCompileInfo::GetAppName(); + StringUtils::ToLower(appName); + logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log"; + EXPECT_TRUE(CLog::Init(CSpecialProtocol::TranslatePath("special://temp/").c_str())); + EXPECT_TRUE(XFILE::CFile::Exists(logfile)); + + EXPECT_EQ(LOG_LEVEL_DEBUG, CLog::GetLogLevel()); + CLog::SetLogLevel(LOG_LEVEL_MAX); + EXPECT_EQ(LOG_LEVEL_MAX, CLog::GetLogLevel()); + + CLog::Close(); + EXPECT_TRUE(XFILE::CFile::Delete(logfile)); +} diff --git a/src/utils/test/Testmd5.cpp b/src/utils/test/Testmd5.cpp new file mode 100644 index 0000000000..d19ecc749d --- /dev/null +++ b/src/utils/test/Testmd5.cpp @@ -0,0 +1,56 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/md5.h" + +#include "gtest/gtest.h" + +TEST(Testmd5, ZeroLengthString) +{ + XBMC::XBMC_MD5 a; + std::string refdigest, vardigest; + + refdigest = "D41D8CD98F00B204E9800998ECF8427E"; + a.append(""); + vardigest = a.getDigest(); + EXPECT_STREQ(refdigest.c_str(), vardigest.c_str()); +} + +TEST(Testmd5, String1) +{ + XBMC::XBMC_MD5 a; + std::string refdigest, vardigest; + + refdigest = "9E107D9D372BB6826BD81D3542A419D6"; + a.append("The quick brown fox jumps over the lazy dog"); + vardigest = a.getDigest(); + EXPECT_STREQ(refdigest.c_str(), vardigest.c_str()); +} + +TEST(Testmd5, String2) +{ + XBMC::XBMC_MD5 a; + std::string refdigest, vardigest; + + refdigest = "E4D909C290D0FB1CA068FFADDF22CBD0"; + a.append("The quick brown fox jumps over the lazy dog."); + vardigest = a.getDigest(); + EXPECT_STREQ(refdigest.c_str(), vardigest.c_str()); +} diff --git a/src/utils/uXstrings.h b/src/utils/uXstrings.h new file mode 100644 index 0000000000..0b2e12c303 --- /dev/null +++ b/src/utils/uXstrings.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 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 + * <http://www.gnu.org/licenses/>. + * + */ + +/** @file utils/uXstrings.h + * Declarations of std::u16string and std::u32string for systems without those declarations + */ +#pragma once + +#include <string> + +#ifndef TARGET_WINDOWS +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#if !defined(HAVE_STD__U16STRING) || !defined(HAVE_STD__U32STRING) +#if defined(HAVE_STDINT_H) +#include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +#include <inttypes.h> +#endif // defined(HAVE_INTTYPES_H) + +#ifndef HAVE_STD__U16STRING +#ifndef HAVE_CHAR16_T +typedef uint_least16_t char16_t; +#endif // HAVE_CHAR16_T +namespace std +{ + typedef basic_string<char16_t> u16string; +} +#endif // HAVE_STD__U16STRING + +#ifndef HAVE_STD__U32STRING +#ifndef HAVE_CHAR32_T +typedef uint_least32_t char32_t; +#endif // HAVE_CHAR32_T +namespace std +{ + typedef basic_string<char32_t> u32string; +} +#endif // HAVE_STD__U32STRING + +#endif // !defined(HAVE_STD__U16STRING) || !defined(HAVE_STD__U32STRING) +#endif // TARGET_WINDOWS diff --git a/src/utils/win32/Win32InterfaceForCLog.cpp b/src/utils/win32/Win32InterfaceForCLog.cpp new file mode 100644 index 0000000000..6daf40f9ed --- /dev/null +++ b/src/utils/win32/Win32InterfaceForCLog.cpp @@ -0,0 +1,120 @@ +/* +* Copyright (C) 2014 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 +* <http://www.gnu.org/licenses/>. +* +*/ + +#ifndef TARGET_WINDOWS +#error This file is for win32 platforms only +#endif //!TARGET_WINDOWS + +#include "Win32InterfaceForCLog.h" +#include "win32/WIN32Util.h" +#include "utils/StringUtils.h" +#include "utils/auto_buffer.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif // WIN32_LEAN_AND_MEAN +#include <Windows.h> + +CWin32InterfaceForCLog::CWin32InterfaceForCLog() : + m_hFile(INVALID_HANDLE_VALUE) +{ } + +CWin32InterfaceForCLog::~CWin32InterfaceForCLog() +{ + if (m_hFile != INVALID_HANDLE_VALUE) + CloseHandle(m_hFile); +} + +bool CWin32InterfaceForCLog::OpenLogFile(const std::string& logFilename, const std::string& backupOldLogToFilename) +{ + if (m_hFile != INVALID_HANDLE_VALUE) + return false; // file was already opened + + std::wstring strLogFileW(CWIN32Util::ConvertPathToWin32Form(CWIN32Util::SmbToUnc(logFilename))); + std::wstring strLogFileOldW(CWIN32Util::ConvertPathToWin32Form(CWIN32Util::SmbToUnc(backupOldLogToFilename))); + + if (strLogFileW.empty()) + return false; + + if (!strLogFileOldW.empty()) + { + (void)DeleteFileW(strLogFileOldW.c_str()); // if it's failed, try to continue + (void)MoveFileW(strLogFileW.c_str(), strLogFileOldW.c_str()); // if it's failed, try to continue + } + + m_hFile = CreateFileW(strLogFileW.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (m_hFile == INVALID_HANDLE_VALUE) + return false; + + static const unsigned char BOM[3] = { 0xEF, 0xBB, 0xBF }; + DWORD written; + (void)WriteFile(m_hFile, BOM, sizeof(BOM), &written, NULL); // write BOM, ignore possible errors + + return true; +} + +void CWin32InterfaceForCLog::CloseLogFile(void) +{ + if (m_hFile != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hFile); + m_hFile = INVALID_HANDLE_VALUE; + } +} + +bool CWin32InterfaceForCLog::WriteStringToLog(const std::string& logString) +{ + if (m_hFile == INVALID_HANDLE_VALUE) + return false; + + std::string strData(logString); + StringUtils::Replace(strData, "\n", "\r\n"); + strData += "\r\n"; + + DWORD written; + const bool ret = (WriteFile(m_hFile, strData.c_str(), strData.length(), &written, NULL) != 0) && written == strData.length(); + (void)FlushFileBuffers(m_hFile); + + return ret; +} + +void CWin32InterfaceForCLog::PrintDebugString(const std::string& debugString) +{ +#ifdef _DEBUG + ::OutputDebugStringW(L"Debug Print: "); + int bufSize = MultiByteToWideChar(CP_UTF8, 0, debugString.c_str(), debugString.length(), NULL, 0); + XUTILS::auto_buffer buf(sizeof(wchar_t) * (bufSize + 1)); // '+1' for extra safety + if (MultiByteToWideChar(CP_UTF8, 0, debugString.c_str(), debugString.length(), (wchar_t*)buf.get(), buf.size() / sizeof(wchar_t)) == bufSize) + ::OutputDebugStringW(std::wstring((wchar_t*)buf.get(), bufSize).c_str()); + else + ::OutputDebugStringA(debugString.c_str()); + ::OutputDebugStringW(L"\n"); +#endif // _DEBUG +} + +void CWin32InterfaceForCLog::GetCurrentLocalTime(int& hour, int& minute, int& second) +{ + SYSTEMTIME time; + GetLocalTime(&time); + hour = time.wHour; + minute = time.wMinute; + second = time.wSecond; +} diff --git a/src/utils/win32/Win32InterfaceForCLog.h b/src/utils/win32/Win32InterfaceForCLog.h new file mode 100644 index 0000000000..a2909d3b52 --- /dev/null +++ b/src/utils/win32/Win32InterfaceForCLog.h @@ -0,0 +1,38 @@ +#pragma once +/* +* Copyright (C) 2014 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 +* <http://www.gnu.org/licenses/>. +* +*/ + +#include <string> + +typedef void* HANDLE; // forward declaration, to avoid inclusion of whole Windows.h + +class CWin32InterfaceForCLog +{ +public: + CWin32InterfaceForCLog(); + ~CWin32InterfaceForCLog(); + bool OpenLogFile(const std::string& logFilename, const std::string& backupOldLogToFilename); + void CloseLogFile(void); + bool WriteStringToLog(const std::string& logString); + void PrintDebugString(const std::string& debugString); + static void GetCurrentLocalTime(int& hour, int& minute, int& second); +private: + HANDLE m_hFile; +}; diff --git a/src/utils/win32/Win32Log.cpp b/src/utils/win32/Win32Log.cpp new file mode 100644 index 0000000000..ef5264ca67 --- /dev/null +++ b/src/utils/win32/Win32Log.cpp @@ -0,0 +1,65 @@ +/* +* Copyright (C) 2014 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 +* <http://www.gnu.org/licenses/>. +* +*/ + +#include "Win32Log.h" +#include "utils/StringUtils.h" +#include "utils/CharsetConverter.h" + +void CWin32Log::LogW(int loglevel, const wchar_t* format, ...) +{ + if (IsLogLevelLogged(loglevel)) + { + va_list va; + va_start(va, format); + std::wstring strDataW(StringUtils::FormatV(format, va)); + va_end(va); + if (!strDataW.empty()) + { + std::string strDataUtf8; + if (g_charsetConverter.wToUTF8(strDataW, strDataUtf8, false) && !strDataUtf8.empty()) + LogString(loglevel, strDataUtf8); + else + PrintDebugString(__FUNCTION__ ": Can't convert log wide string to UTF-8"); + } + } +} + +void CWin32Log::LogFunctionW(int loglevel, const char* functionName, const wchar_t* format, ...) +{ + if (IsLogLevelLogged(loglevel)) + { + va_list va; + va_start(va, format); + std::wstring strDataW(StringUtils::FormatV(format, va)); + va_end(va); + if (!strDataW.empty()) + { + std::string funcNameStr; + if (functionName && functionName[0]) + funcNameStr.assign(functionName).append(": "); + + std::string strDataUtf8; + if (g_charsetConverter.wToUTF8(strDataW, strDataUtf8, false) && !strDataUtf8.empty()) + LogString(loglevel, funcNameStr + strDataUtf8); + else + PrintDebugString(__FUNCTION__ ": Can't convert log wide string to UTF-8"); + } + } +} diff --git a/src/utils/win32/Win32Log.h b/src/utils/win32/Win32Log.h new file mode 100644 index 0000000000..53ee0fedae --- /dev/null +++ b/src/utils/win32/Win32Log.h @@ -0,0 +1,40 @@ +#pragma once + +/* +* Copyright (C) 2014 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 +* <http://www.gnu.org/licenses/>. +* +*/ + +#include "utils/log.h" + +#ifndef TARGET_WINDOWS +#error This file is for Win32 platfrom only +#endif // TARGET_WINDOWS + + +// CLog version for Win32 with additional widestring logging capabilities +class CWin32Log : public CLog +{ +public: + static void LogW(int loglevel, PRINTF_FORMAT_STRING const wchar_t *format, ...); + static void LogFunctionW(int loglevel, IN_OPT_STRING const char* functionName, PRINTF_FORMAT_STRING const wchar_t* format, ...); +#define LogFW(loglevel,format,...) LogFunctionW((loglevel),__FUNCTION__,(format),##__VA_ARGS__) +}; + +// substitute CWin32Log instead of CLog for Win32 +#define CLog CWin32Log |