diff options
Diffstat (limited to 'xbmc/utils')
260 files changed, 50731 insertions, 0 deletions
diff --git a/xbmc/utils/ActorProtocol.cpp b/xbmc/utils/ActorProtocol.cpp new file mode 100644 index 0000000..8798853 --- /dev/null +++ b/xbmc/utils/ActorProtocol.cpp @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * See LICENSES/README.md for more information. + */ + +#include "ActorProtocol.h" + +#include "threads/Event.h" + +#include <cstring> +#include <mutex> + +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; + + payloadObj.reset(); + + // delete event in case of sync message + delete event; + + origin.ReturnMessage(this); +} + +bool Message::Reply(int sig, void *data /* = NULL*/, size_t 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 > sizeof(msg->buffer)) + 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; + + std::unique_lock<CCriticalSection> lock(criticalSection); + + if (!freeMessageQueue.empty()) + { + msg = freeMessageQueue.front(); + freeMessageQueue.pop(); + } + else + msg = new Message(*this); + + msg->isSync = false; + msg->isSyncFini = false; + msg->isSyncTimeout = false; + msg->event = NULL; + msg->data = NULL; + msg->payloadSize = 0; + msg->replyMessage = NULL; + + return msg; +} + +void Protocol::ReturnMessage(Message *msg) +{ + std::unique_lock<CCriticalSection> lock(criticalSection); + + freeMessageQueue.push(msg); +} + +bool Protocol::SendOutMessage(int signal, + const void* data /* = NULL */, + size_t size /* = 0 */, + Message* outMsg /* = NULL */) +{ + Message *msg; + if (outMsg) + msg = outMsg; + else + msg = GetMessage(); + + msg->signal = signal; + msg->isOut = true; + + if (data) + { + if (size > sizeof(msg->buffer)) + msg->data = new uint8_t[size]; + else + msg->data = msg->buffer; + memcpy(msg->data, data, size); + } + + { + std::unique_lock<CCriticalSection> lock(criticalSection); + outMessages.push(msg); + } + if (containerOutEvent) + containerOutEvent->Set(); + + return true; +} + +bool Protocol::SendOutMessage(int signal, CPayloadWrapBase *payload, Message *outMsg) +{ + Message *msg; + if (outMsg) + msg = outMsg; + else + msg = GetMessage(); + + msg->signal = signal; + msg->isOut = true; + + msg->payloadObj.reset(payload); + + { + std::unique_lock<CCriticalSection> lock(criticalSection); + outMessages.push(msg); + } + if (containerOutEvent) + containerOutEvent->Set(); + + return true; +} + +bool Protocol::SendInMessage(int signal, + const void* data /* = NULL */, + size_t size /* = 0 */, + Message* outMsg /* = NULL */) +{ + Message *msg; + if (outMsg) + msg = outMsg; + else + msg = GetMessage(); + + msg->signal = signal; + msg->isOut = false; + + if (data) + { + if (size > sizeof(msg->data)) + msg->data = new uint8_t[size]; + else + msg->data = msg->buffer; + memcpy(msg->data, data, size); + } + + { + std::unique_lock<CCriticalSection> lock(criticalSection); + inMessages.push(msg); + } + if (containerInEvent) + containerInEvent->Set(); + + return true; +} + +bool Protocol::SendInMessage(int signal, CPayloadWrapBase *payload, Message *outMsg) +{ + Message *msg; + if (outMsg) + msg = outMsg; + else + msg = GetMessage(); + + msg->signal = signal; + msg->isOut = false; + + msg->payloadObj.reset(payload); + + { + std::unique_lock<CCriticalSection> lock(criticalSection); + inMessages.push(msg); + } + if (containerInEvent) + containerInEvent->Set(); + + return true; +} + +bool Protocol::SendOutMessageSync( + int signal, Message** retMsg, int timeout, const void* data /* = NULL */, size_t 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->Wait(std::chrono::milliseconds(timeout))) + { + const std::unique_lock<CCriticalSection> lock(criticalSection); + if (msg->replyMessage) + *retMsg = msg->replyMessage; + else + { + *retMsg = NULL; + msg->isSyncTimeout = true; + } + } + else + *retMsg = msg->replyMessage; + + msg->Release(); + + if (*retMsg) + return true; + else + return false; +} + +bool Protocol::SendOutMessageSync(int signal, Message **retMsg, int timeout, CPayloadWrapBase *payload) +{ + Message *msg = GetMessage(); + msg->isOut = true; + msg->isSync = true; + msg->event = new CEvent; + msg->event->Reset(); + SendOutMessage(signal, payload, msg); + + if (!msg->event->Wait(std::chrono::milliseconds(timeout))) + { + const std::unique_lock<CCriticalSection> lock(criticalSection); + if (msg->replyMessage) + *retMsg = msg->replyMessage; + else + { + *retMsg = NULL; + msg->isSyncTimeout = true; + } + } + else + *retMsg = msg->replyMessage; + + msg->Release(); + + if (*retMsg) + return true; + else + return false; +} + +bool Protocol::ReceiveOutMessage(Message **msg) +{ + std::unique_lock<CCriticalSection> lock(criticalSection); + + if (outMessages.empty() || outDefered) + return false; + + *msg = outMessages.front(); + outMessages.pop(); + + return true; +} + +bool Protocol::ReceiveInMessage(Message **msg) +{ + std::unique_lock<CCriticalSection> 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; + + std::unique_lock<CCriticalSection> 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; + + std::unique_lock<CCriticalSection> 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/xbmc/utils/ActorProtocol.h b/xbmc/utils/ActorProtocol.h new file mode 100644 index 0000000..77f19b9 --- /dev/null +++ b/xbmc/utils/ActorProtocol.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/CriticalSection.h" + +#include <cstddef> +#include <memory> +#include <queue> +#include <string> +#include <utility> + +class CEvent; + +namespace Actor +{ + +class CPayloadWrapBase +{ +public: + virtual ~CPayloadWrapBase() = default; +}; + +template<typename Payload> +class CPayloadWrap : public CPayloadWrapBase +{ +public: + ~CPayloadWrap() override = default; + CPayloadWrap(Payload* data) { m_pPayload.reset(data); } + CPayloadWrap(Payload& data) { m_pPayload.reset(new Payload(data)); } + Payload* GetPlayload() { return m_pPayload.get(); } + +protected: + std::unique_ptr<Payload> m_pPayload; +}; + +class Protocol; + +class Message +{ + friend class Protocol; + + static constexpr size_t MSG_INTERNAL_BUFFER_SIZE = 32; + +public: + int signal; + bool isSync = false; + bool isSyncFini; + bool isOut; + bool isSyncTimeout; + size_t payloadSize; + uint8_t buffer[MSG_INTERNAL_BUFFER_SIZE]; + uint8_t *data = nullptr; + std::unique_ptr<CPayloadWrapBase> payloadObj; + Message *replyMessage = nullptr; + Protocol &origin; + CEvent *event = nullptr; + + void Release(); + bool Reply(int sig, void *data = nullptr, size_t size = 0); + +private: + explicit Message(Protocol &_origin) noexcept + :origin(_origin) {} +}; + +class Protocol +{ +public: + Protocol(std::string name, CEvent* inEvent, CEvent* outEvent) + : portName(std::move(name)), containerInEvent(inEvent), containerOutEvent(outEvent) + { + } + Protocol(std::string name) : Protocol(std::move(name), nullptr, nullptr) {} + ~Protocol(); + Message *GetMessage(); + void ReturnMessage(Message *msg); + bool SendOutMessage(int signal, + const void* data = nullptr, + size_t size = 0, + Message* outMsg = nullptr); + bool SendOutMessage(int signal, CPayloadWrapBase *payload, Message *outMsg = nullptr); + bool SendInMessage(int signal, + const void* data = nullptr, + size_t size = 0, + Message* outMsg = nullptr); + bool SendInMessage(int signal, CPayloadWrapBase *payload, Message *outMsg = nullptr); + bool SendOutMessageSync( + int signal, Message** retMsg, int timeout, const void* data = nullptr, size_t size = 0); + bool SendOutMessageSync(int signal, Message **retMsg, int timeout, CPayloadWrapBase *payload); + 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 = false, outDefered = false; +}; + +} diff --git a/xbmc/utils/AlarmClock.cpp b/xbmc/utils/AlarmClock.cpp new file mode 100644 index 0000000..f37f828 --- /dev/null +++ b/xbmc/utils/AlarmClock.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "AlarmClock.h" + +#include "ServiceBroker.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "events/EventLog.h" +#include "events/NotificationEvent.h" +#include "guilib/LocalizeStrings.h" +#include "log.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/StringUtils.h" + +#include <mutex> +#include <utility> + +using namespace std::chrono_literals; + +CAlarmClock::CAlarmClock() : CThread("AlarmClock") +{ +} + +CAlarmClock::~CAlarmClock() = default; + +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 = static_cast<double>(n_secs); + event.m_strCommand = strCommand; + event.m_loop = bLoop; + if (!m_bIsRunning) + { + StopThread(); + Create(); + m_bIsRunning = true; + } + + uint32_t labelAlarmClock; + uint32_t labelStarted; + if (StringUtils::EqualsNoCase(strName, "shutdowntimer")) + { + labelAlarmClock = 20144; + labelStarted = 20146; + } + else + { + labelAlarmClock = 13208; + labelStarted = 13210; + } + + EventPtr alarmClockActivity(new CNotificationEvent( + labelAlarmClock, + StringUtils::Format(g_localizeStrings.Get(labelStarted), static_cast<int>(event.m_fSecs) / 60, + static_cast<int>(event.m_fSecs) % 60))); + + auto eventLog = CServiceBroker::GetEventLog(); + if (eventLog) + { + if (bSilent) + eventLog->Add(alarmClockActivity); + else + eventLog->AddWithNotification(alarmClockActivity); + } + + event.watch.StartZero(); + std::unique_lock<CCriticalSection> lock(m_events); + m_event.insert(make_pair(lowerName,event)); + CLog::Log(LOGDEBUG, "started alarm with name: {}", lowerName); +} + +void CAlarmClock::Stop(const std::string& strName, bool bSilent /* false */) +{ + std::unique_lock<CCriticalSection> lock(m_events); + + std::string lowerName(strName); + StringUtils::ToLower(lowerName); // lookup as lowercase only + std::map<std::string,SAlarmClockEvent>::iterator iter = m_event.find(lowerName); + + if (iter == m_event.end()) + return; + + uint32_t labelAlarmClock; + if (StringUtils::EqualsNoCase(strName, "shutdowntimer")) + labelAlarmClock = 20144; + else + labelAlarmClock = 13208; + + std::string strMessage; + float elapsed = 0.f; + + if (iter->second.watch.IsRunning()) + elapsed = iter->second.watch.GetElapsedSeconds(); + + if (elapsed > static_cast<float>(iter->second.m_fSecs)) + strMessage = g_localizeStrings.Get(13211); + else + { + float remaining = static_cast<float>(iter->second.m_fSecs) - elapsed; + strMessage = StringUtils::Format(g_localizeStrings.Get(13212), static_cast<int>(remaining) / 60, + static_cast<int>(remaining) % 60); + } + + if (iter->second.m_strCommand.empty() || static_cast<float>(iter->second.m_fSecs) > elapsed) + { + EventPtr alarmClockActivity(new CNotificationEvent(labelAlarmClock, strMessage)); + auto eventLog = CServiceBroker::GetEventLog(); + if (eventLog) + { + if (bSilent) + eventLog->Add(alarmClockActivity); + else + eventLog->AddWithNotification(alarmClockActivity); + } + } + else + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, + 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; + { + std::unique_lock<CCriticalSection> lock(m_events); + for (std::map<std::string,SAlarmClockEvent>::iterator iter=m_event.begin();iter != m_event.end(); ++iter) + if (iter->second.watch.IsRunning() && + iter->second.watch.GetElapsedSeconds() >= static_cast<float>(iter->second.m_fSecs)) + { + Stop(iter->first); + if ((iter = m_event.find(strLast)) == m_event.end()) + break; + } + else + strLast = iter->first; + } + CThread::Sleep(100ms); + } +} + diff --git a/xbmc/utils/AlarmClock.h b/xbmc/utils/AlarmClock.h new file mode 100644 index 0000000..392ba02 --- /dev/null +++ b/xbmc/utils/AlarmClock.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#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() override; + 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 {}",strName); + 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 - static_cast<double>(iter->second.watch.IsRunning() + ? iter->second.watch.GetElapsedSeconds() + : 0.f); + } + + return 0.0; + } + + void Stop(const std::string& strName, bool bSilent = false); + void Process() override; +private: + std::map<std::string,SAlarmClockEvent> m_event; + CCriticalSection m_events; + + bool m_bIsRunning = false; +}; + +extern CAlarmClock g_alarmClock; + diff --git a/xbmc/utils/AliasShortcutUtils.cpp b/xbmc/utils/AliasShortcutUtils.cpp new file mode 100644 index 0000000..6eed427 --- /dev/null +++ b/xbmc/utils/AliasShortcutUtils.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2009-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#if defined(TARGET_DARWIN_OSX) +#include "utils/URIUtils.h" +#include "platform/darwin/DarwinUtils.h" +#elif defined(TARGET_POSIX) +#else +#endif + +#include "AliasShortcutUtils.h" +#include "utils/log.h" + +bool IsAliasShortcut(const std::string& path, bool isdirectory) +{ + 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"))//! @todo - check if this is still needed with the new API + { + rtn = CDarwinUtils::IsAliasShortcut(path, isdirectory); + } +#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) + CDarwinUtils::TranslateAliasShortcut(path); +#elif defined(TARGET_POSIX) + // Linux does not use alias or shortcut methods +#elif defined(TARGET_WINDOWS_STORE) + // Win10 does not use alias or shortcut methods + CLog::Log(LOGDEBUG, "{} is not implemented", __FUNCTION__); +#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/xbmc/utils/AliasShortcutUtils.h b/xbmc/utils/AliasShortcutUtils.h new file mode 100644 index 0000000..524c8f0 --- /dev/null +++ b/xbmc/utils/AliasShortcutUtils.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2009-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +bool IsAliasShortcut(const std::string& path, bool isdirectory); +void TranslateAliasShortcut(std::string &path); diff --git a/xbmc/utils/Archive.cpp b/xbmc/utils/Archive.cpp new file mode 100644 index 0000000..4f69929 --- /dev/null +++ b/xbmc/utils/Archive.cpp @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Archive.h" + +#include "IArchivable.h" +#include "filesystem/File.h" +#include "utils/Variant.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <cstdint> +#include <cstring> +#include <stdexcept> + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wlong-long" +#endif + +using namespace XFILE; + +//arbitrarily chosen, should be plenty big enough for our strings +//without causing random bad things happening +//not very bad, just tiny bad +#define MAX_STRING_SIZE 100*1024*1024 + +CArchive::CArchive(CFile* pFile, int mode) +{ + m_pFile = pFile; + m_iMode = mode; + + m_pBuffer = std::unique_ptr<uint8_t[]>(new uint8_t[CARCHIVE_BUFFER_MAX]); + memset(m_pBuffer.get(), 0, CARCHIVE_BUFFER_MAX); + if (mode == load) + { + m_BufferPos = m_pBuffer.get() + CARCHIVE_BUFFER_MAX; + m_BufferRemain = 0; + } + else + { + m_BufferPos = m_pBuffer.get(); + m_BufferRemain = CARCHIVE_BUFFER_MAX; + } +} + +CArchive::~CArchive() +{ + FlushBuffer(); +} + +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) +{ + auto size = static_cast<uint32_t>(str.size()); + if (size > MAX_STRING_SIZE) + throw std::out_of_range("String too large, over 100MB"); + + *this << size; + + return streamout(str.data(), size * sizeof(char)); +} + +CArchive& CArchive::operator<<(const std::wstring& wstr) +{ + if (wstr.size() > MAX_STRING_SIZE) + throw std::out_of_range("String too large, over 100MB"); + + auto size = static_cast<uint32_t>(wstr.size()); + + *this << size; + + return streamout(wstr.data(), size * sizeof(wchar_t)); +} + +CArchive& CArchive::operator<<(const KODI::TIME::SystemTime& time) +{ + return streamout(&time, sizeof(KODI::TIME::SystemTime)); +} + +CArchive& CArchive::operator<<(IArchivable& obj) +{ + obj.Archive(*this); + + return *this; +} + +CArchive& CArchive::operator<<(const CVariant& variant) +{ + *this << static_cast<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 (auto i = variant.begin_array(); i != variant.end_array(); ++i) + *this << *i; + break; + case CVariant::VariantTypeObject: + *this << variant.size(); + for (auto 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) +{ + if (std::numeric_limits<uint32_t>::max() < strArray.size()) + throw std::out_of_range("Array too large, over 2^32 in size"); + + *this << static_cast<uint32_t>(strArray.size()); + + for (auto&& item : strArray) + *this << item; + + return *this; +} + +CArchive& CArchive::operator<<(const std::vector<int>& iArray) +{ + if (std::numeric_limits<uint32_t>::max() < iArray.size()) + throw std::out_of_range("Array too large, over 2^32 in size"); + + *this << static_cast<uint32_t>(iArray.size()); + + for (auto&& item : iArray) + *this << item; + + return *this; +} + +CArchive& CArchive::operator>>(std::string& str) +{ + uint32_t iLength = 0; + *this >> iLength; + + if (iLength > MAX_STRING_SIZE) + throw std::out_of_range("String too large, over 100MB"); + + auto s = std::unique_ptr<char[]>(new char[iLength]); + streamin(s.get(), iLength * sizeof(char)); + str.assign(s.get(), iLength); + + return *this; +} + +CArchive& CArchive::operator>>(std::wstring& wstr) +{ + uint32_t iLength = 0; + *this >> iLength; + + if (iLength > MAX_STRING_SIZE) + throw std::out_of_range("String too large, over 100MB"); + + auto p = std::unique_ptr<wchar_t[]>(new wchar_t[iLength]); + streamin(p.get(), iLength * sizeof(wchar_t)); + wstr.assign(p.get(), iLength); + + return *this; +} + +CArchive& CArchive::operator>>(KODI::TIME::SystemTime& time) +{ + return streamin(&time, sizeof(KODI::TIME::SystemTime)); +} + +CArchive& CArchive::operator>>(IArchivable& obj) +{ + obj.Archive(*this); + + return *this; +} + +CArchive& CArchive::operator>>(CVariant& variant) +{ + int type; + *this >> type; + variant = CVariant(static_cast<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) +{ + uint32_t size; + *this >> size; + strArray.clear(); + for (uint32_t index = 0; index < size; index++) + { + std::string str; + *this >> str; + strArray.push_back(std::move(str)); + } + + return *this; +} + +CArchive& CArchive::operator>>(std::vector<int>& iArray) +{ + uint32_t size; + *this >> size; + iArray.clear(); + for (uint32_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.get()) + { + if (m_pFile->Write(m_pBuffer.get(), m_BufferPos - m_pBuffer.get()) != m_BufferPos - m_pBuffer.get()) + CLog::Log(LOGERROR, "{}: Error flushing buffer", __FUNCTION__); + else + { + m_BufferPos = m_pBuffer.get(); + m_BufferRemain = CARCHIVE_BUFFER_MAX; + } + } +} + +CArchive &CArchive::streamout_bufferwrap(const uint8_t *ptr, size_t size) +{ + do + { + auto 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) + { + auto read = m_pFile->Read(m_pBuffer.get(), CARCHIVE_BUFFER_MAX); + if (read > 0) + { + m_BufferRemain = read; + m_BufferPos = m_pBuffer.get(); + } + } +} + +CArchive &CArchive::streamin_bufferwrap(uint8_t *ptr, size_t size) +{ + auto orig_ptr = ptr; + auto orig_size = size; + do + { + if (m_BufferRemain == 0) + { + FillBuffer(); + if (m_BufferRemain < CARCHIVE_BUFFER_MAX && m_BufferRemain < size) + { + CLog::Log(LOGERROR, "{}: can't stream in: requested {} bytes, was read {} bytes", + __FUNCTION__, static_cast<unsigned long>(orig_size), + static_cast<unsigned long>(ptr - orig_ptr + m_BufferRemain)); + + memset(orig_ptr, 0, orig_size); + return *this; + } + } + auto 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/xbmc/utils/Archive.h b/xbmc/utils/Archive.h new file mode 100644 index 0000000..a1af0c3 --- /dev/null +++ b/xbmc/utils/Archive.h @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <cstring> +#include <memory> +#include <string> +#include <vector> + +#define CARCHIVE_BUFFER_MAX 4096 + +namespace XFILE +{ + class CFile; +} +class CVariant; +class IArchivable; +namespace KODI::TIME +{ +struct SystemTime; +} + +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 KODI::TIME::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>>(KODI::TIME::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) + { + auto ptr = static_cast<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; + } + + return streamout_bufferwrap(ptr, size); + } + + inline CArchive &streamin(void *dataPtr, size_t size) + { + auto ptr = static_cast<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; + } + + return streamin_bufferwrap(ptr, size); + } + + XFILE::CFile* m_pFile; //non-owning + int m_iMode; + std::unique_ptr<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/xbmc/utils/Base64.cpp b/xbmc/utils/Base64.cpp new file mode 100644 index 0000000..6b41519 --- /dev/null +++ b/xbmc/utils/Base64.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Base64.h" + +#define PADDING '=' + +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 == std::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/xbmc/utils/Base64.h b/xbmc/utils/Base64.h new file mode 100644 index 0000000..4b645ee --- /dev/null +++ b/xbmc/utils/Base64.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#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/xbmc/utils/BitstreamConverter.cpp b/xbmc/utils/BitstreamConverter.cpp new file mode 100644 index 0000000..f2224a5 --- /dev/null +++ b/xbmc/utils/BitstreamConverter.cpp @@ -0,0 +1,1237 @@ +/* + * Copyright (C) 2010-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/log.h" + +#include <assert.h> + +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif + +#include "BitstreamConverter.h" +#include "BitstreamReader.h" +#include "BitstreamWriter.h" + +#include <algorithm> + +enum { + AVC_NAL_SLICE=1, + AVC_NAL_DPA, + AVC_NAL_DPB, + AVC_NAL_DPC, + AVC_NAL_IDR_SLICE, + AVC_NAL_SEI, + AVC_NAL_SPS, + AVC_NAL_PPS, + AVC_NAL_AUD, + AVC_NAL_END_SEQUENCE, + AVC_NAL_END_STREAM, + AVC_NAL_FILLER_DATA, + AVC_NAL_SPS_EXT, + AVC_NAL_AUXILIARY_SLICE=19 +}; + +enum +{ + HEVC_NAL_TRAIL_N = 0, + HEVC_NAL_TRAIL_R = 1, + HEVC_NAL_TSA_N = 2, + HEVC_NAL_TSA_R = 3, + HEVC_NAL_STSA_N = 4, + HEVC_NAL_STSA_R = 5, + HEVC_NAL_RADL_N = 6, + HEVC_NAL_RADL_R = 7, + HEVC_NAL_RASL_N = 8, + HEVC_NAL_RASL_R = 9, + HEVC_NAL_BLA_W_LP = 16, + HEVC_NAL_BLA_W_RADL = 17, + HEVC_NAL_BLA_N_LP = 18, + HEVC_NAL_IDR_W_RADL = 19, + HEVC_NAL_IDR_N_LP = 20, + HEVC_NAL_CRA_NUT = 21, + HEVC_NAL_VPS = 32, + HEVC_NAL_SPS = 33, + HEVC_NAL_PPS = 34, + HEVC_NAL_AUD = 35, + HEVC_NAL_EOS_NUT = 36, + HEVC_NAL_EOB_NUT = 37, + HEVC_NAL_FD_NUT = 38, + HEVC_NAL_SEI_PREFIX = 39, + HEVC_NAL_SEI_SUFFIX = 40, + HEVC_NAL_UNSPEC62 = 62, // Dolby Vision RPU + HEVC_NAL_UNSPEC63 = 63 // Dolby Vision EL +}; + +enum { + SEI_BUFFERING_PERIOD = 0, + SEI_PIC_TIMING, + SEI_PAN_SCAN_RECT, + SEI_FILLER_PAYLOAD, + SEI_USER_DATA_REGISTERED_ITU_T_T35, + SEI_USER_DATA_UNREGISTERED, + SEI_RECOVERY_POINT, + SEI_DEC_REF_PIC_MARKING_REPETITION, + SEI_SPARE_PIC, + SEI_SCENE_INFO, + SEI_SUB_SEQ_INFO, + SEI_SUB_SEQ_LAYER_CHARACTERISTICS, + SEI_SUB_SEQ_CHARACTERISTICS, + SEI_FULL_FRAME_FREEZE, + SEI_FULL_FRAME_FREEZE_RELEASE, + SEI_FULL_FRAME_SNAPSHOT, + SEI_PROGRESSIVE_REFINEMENT_SEGMENT_START, + SEI_PROGRESSIVE_REFINEMENT_SEGMENT_END, + SEI_MOTION_CONSTRAINED_SLICE_GROUP_SET, + SEI_FILM_GRAIN_CHARACTERISTICS, + SEI_DEBLOCKING_FILTER_DISPLAY_PREFERENCE, + SEI_STEREO_VIDEO_INFO, + SEI_POST_FILTER_HINTS, + SEI_TONE_MAPPING +}; + +/* + * GStreamer h264 parser + * Copyright (C) 2005 Michal Benes <michal.benes@itonis.tv> + * (C) 2008 Wim Taymans <wim.taymans@gmail.com> + * gsth264parse.c + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * See LICENSES/README.md for more information. + */ +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 = static_cast<uint32_t>(bs->cache >> shift); + else + res = static_cast<uint32_t>(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 < 31) + 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; +} + +static bool has_sei_recovery_point(const uint8_t *p, const uint8_t *end) +{ + int pt(0), ps(0), offset(1); + + do + { + pt = 0; + do { + pt += p[offset]; + } while (p[offset++] == 0xFF); + + ps = 0; + do { + ps += p[offset]; + } while (p[offset++] == 0xFF); + + if (pt == SEI_RECOVERY_POINT) + { + nal_bitstream bs; + nal_bs_init(&bs, p + offset, ps); + return nal_bs_read_ue(&bs) >= 0; + } + offset += ps; + } while (p + offset < end && p[offset] != 0x80); + + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// +CBitstreamParser::CBitstreamParser() = default; + +void CBitstreamParser::Close() +{ +} + +bool CBitstreamParser::CanStartDecode(const uint8_t *buf, int buf_size) +{ + if (!buf) + return false; + + bool rtn = false; + uint32_t state = -1; + const uint8_t *buf_begin, *buf_end = buf + buf_size; + + for (; rtn == false;) + { + buf = find_start_code(buf, buf_end, &state); + if (buf >= buf_end) + { + break; + } + + switch (state & 0x1f) + { + case AVC_NAL_SLICE: + break; + case AVC_NAL_IDR_SLICE: + rtn = true; + break; + case AVC_NAL_SEI: + buf_begin = buf - 1; + buf = find_start_code(buf, buf_end, &state) - 4; + if (has_sei_recovery_point(buf_begin, buf)) + rtn = true; + break; + case AVC_NAL_SPS: + rtn = true; + break; + case AVC_NAL_PPS: + break; + default: + break; + } + } + + 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; + m_start_decode = true; +} + +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 = BitstreamConvertInitAVC(m_extradata, m_extrasize); + return true; + } + else + CLog::Log(LOGINFO, "CBitstreamConverter::Open Invalid avcC"); + } + 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 reformatting 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(LOGINFO, "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; + case AV_CODEC_ID_HEVC: + if (in_extrasize < 23 || in_extradata == NULL) + { + CLog::Log(LOGERROR, "CBitstreamConverter::Open hvcC data too small or missing"); + return false; + } + // valid hvcC data (bitstream) always starts with the value 1 (version) + if(m_to_annexb) + { + /** + * It seems the extradata is encoded as hvcC format. + * Temporarily, we support configurationVersion==0 until 14496-15 3rd + * is finalized. When finalized, configurationVersion will be 1 and we + * can recognize hvcC by checking if extradata[0]==1 or not. + */ + + if (in_extradata[0] || in_extradata[1] || in_extradata[2] > 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 = BitstreamConvertInitHEVC(m_extradata, m_extrasize); + return true; + } + else + CLog::Log(LOGINFO, "CBitstreamConverter::Open Invalid hvcC"); + } + else + { + // valid hvcC 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"); + //! @todo convert annexb to bitstream format + return false; + } + else + { + CLog::Log(LOGINFO, "CBitstreamConverter::Open invalid hvcC atom data"); + return false; + } + } + else + { + if ((in_extradata[4] & 0x3) == 2) + { + 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] |= 0x03; + m_convert_3byteTo4byteNALSize = true; + } + } + // valid hvcC 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 || + m_codec == AV_CODEC_ID_HEVC) + { + 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_wb32(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; +} + +void CBitstreamConverter::ResetStartDecode(void) +{ + m_start_decode = false; +} + +bool CBitstreamConverter::CanStartDecode() const +{ + return m_start_decode; +} + +bool CBitstreamConverter::BitstreamConvertInitAVC(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 - AV_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 + AV_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, AV_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::BitstreamConvertInitHEVC(void *in_extradata, int in_extrasize) +{ + m_sps_pps_size = 0; + m_sps_pps_context.sps_pps_data = NULL; + + // nothing to filter + if (!in_extradata || in_extrasize < 23) + return false; + + uint16_t unit_nb, unit_size; + uint32_t total_size = 0; + uint8_t *out = NULL, array_nb, nal_type, sps_seen = 0, pps_seen = 0; + const uint8_t *extradata = (uint8_t*)in_extradata + 21; + static const uint8_t nalu_header[4] = {0, 0, 0, 1}; + + // retrieve length coded size + m_sps_pps_context.length_size = (*extradata++ & 0x3) + 1; + + array_nb = *extradata++; + while (array_nb--) + { + nal_type = *extradata++ & 0x3f; + unit_nb = extradata[0] << 8 | extradata[1]; + extradata += 2; + + if (nal_type == HEVC_NAL_SPS && unit_nb) + { + sps_seen = 1; + } + else if (nal_type == HEVC_NAL_PPS && unit_nb) + { + pps_seen = 1; + } + while (unit_nb--) + { + void *tmp; + + unit_size = extradata[0] << 8 | extradata[1]; + extradata += 2; + if (nal_type != HEVC_NAL_SPS && + nal_type != HEVC_NAL_PPS && + nal_type != HEVC_NAL_VPS) + { + extradata += unit_size; + continue; + } + total_size += unit_size + 4; + + if (total_size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE || + (extradata + unit_size) > ((uint8_t*)in_extradata + in_extrasize)) + { + av_free(out); + return false; + } + tmp = av_realloc(out, total_size + AV_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, unit_size); + extradata += unit_size; + } + } + + if (out) + memset(out + total_size, 0, AV_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::IsIDR(uint8_t unit_type) +{ + switch (m_codec) + { + case AV_CODEC_ID_H264: + return unit_type == AVC_NAL_IDR_SLICE; + case AV_CODEC_ID_HEVC: + return unit_type == HEVC_NAL_IDR_W_RADL || + unit_type == HEVC_NAL_IDR_N_LP || + unit_type == HEVC_NAL_CRA_NUT; + default: + return false; + } +} + +bool CBitstreamConverter::IsSlice(uint8_t unit_type) +{ + switch (m_codec) + { + case AV_CODEC_ID_H264: + return unit_type == AVC_NAL_SLICE; + case AV_CODEC_ID_HEVC: + return unit_type == HEVC_NAL_TRAIL_R || + unit_type == HEVC_NAL_TRAIL_N || + unit_type == HEVC_NAL_TSA_N || + unit_type == HEVC_NAL_TSA_R || + unit_type == HEVC_NAL_STSA_N || + unit_type == HEVC_NAL_STSA_R || + unit_type == HEVC_NAL_BLA_W_LP || + unit_type == HEVC_NAL_BLA_W_RADL || + unit_type == HEVC_NAL_BLA_N_LP || + unit_type == HEVC_NAL_CRA_NUT || + unit_type == HEVC_NAL_RADL_N || + unit_type == HEVC_NAL_RADL_R || + unit_type == HEVC_NAL_RASL_N || + unit_type == HEVC_NAL_RASL_R; + default: + return false; + } +} + +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, nal_sps, nal_pps, nal_sei; + int32_t nal_size; + uint32_t cumul_size = 0; + const uint8_t *buf_end = buf + buf_size; + + switch (m_codec) + { + case AV_CODEC_ID_H264: + nal_sps = AVC_NAL_SPS; + nal_pps = AVC_NAL_PPS; + nal_sei = AVC_NAL_SEI; + break; + case AV_CODEC_ID_HEVC: + nal_sps = HEVC_NAL_SPS; + nal_pps = HEVC_NAL_PPS; + nal_sei = HEVC_NAL_SEI_PREFIX; + break; + default: + return false; + } + + 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; + if (m_codec == AV_CODEC_ID_H264) + { + unit_type = *buf & 0x1f; + } + else + { + unit_type = (*buf >> 1) & 0x3f; + } + + 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 == nal_sps || unit_type == nal_pps)) + m_sps_pps_context.idr_sps_pps_seen = 1; + + if (!m_start_decode && (unit_type == nal_sps || IsIDR(unit_type) || (unit_type == nal_sei && has_sei_recovery_point(buf, buf + nal_size)))) + m_start_decode = true; + + // prepend only to the first access unit of an IDR picture, if no sps/pps already present + if (m_sps_pps_context.first_idr && IsIDR(unit_type) && !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, unit_type); + m_sps_pps_context.first_idr = 0; + } + else + { + BitstreamAllocAndCopy(poutbuf, poutbuf_size, NULL, 0, buf, nal_size, unit_type); + if (!m_sps_pps_context.first_idr && IsSlice(unit_type)) + { + 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, + uint8_t nal_type) +{ + // 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; + + // According to x265, this type is always encoded with four-sized header + // https://bitbucket.org/multicoreware/x265_git/src/4bf31dc15fb6d1f93d12ecf21fad5e695f0db5c0/source/encoder/nal.cpp#lines-100 + if (nal_type == HEVC_NAL_UNSPEC62) + nal_header_size = 4; + + *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 if (nal_header_size == 4) + { + (*poutbuf + offset + sps_pps_size)[0] = 0; + (*poutbuf + offset + sps_pps_size)[1] = 0; + (*poutbuf + offset + sps_pps_size)[2] = 0; + (*poutbuf + offset + sps_pps_size)[3] = 1; + } + else + { + (*poutbuf + offset + sps_pps_size)[0] = 0; + (*poutbuf + offset + sps_pps_size)[1] = 0; + (*poutbuf + offset + sps_pps_size)[2] = 1; + } +} + +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; +} + +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; +} + +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 = std::min<uint32_t>(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; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// +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.0f; + break; + default: + case 0x02: + ratio = 4.0f/3; + break; + case 0x03: + ratio = 16.0f/9; + break; + case 0x04: + ratio = 2.21f; + 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 + uint32_t fpsrate = sequence->fps_rate; + uint32_t fpsscale = sequence->fps_scale; + uint32_t rate_info = nal_bs_read(&bs, 4); + + switch(rate_info) + { + default: + case 0x01: + fpsrate = 24000; + fpsscale = 1001; + break; + case 0x02: + fpsrate = 24000; + fpsscale = 1000; + break; + case 0x03: + fpsrate = 25000; + fpsscale = 1000; + break; + case 0x04: + fpsrate = 30000; + fpsscale = 1001; + break; + case 0x05: + fpsrate = 30000; + fpsscale = 1000; + break; + case 0x06: + fpsrate = 50000; + fpsscale = 1000; + break; + case 0x07: + fpsrate = 60000; + fpsscale = 1001; + break; + case 0x08: + fpsrate = 60000; + fpsscale = 1000; + break; + } + + if (fpsscale != sequence->fps_scale || fpsrate != sequence->fps_rate) + { + changed = true; + sequence->fps_rate = fpsrate; + sequence->fps_scale = fpsscale; + } + } + nal_start = nal_end; + } + + return changed; +} + diff --git a/xbmc/utils/BitstreamConverter.h b/xbmc/utils/BitstreamConverter.h new file mode 100644 index 0000000..3c57e14 --- /dev/null +++ b/xbmc/utils/BitstreamConverter.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2010-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stdint.h> + +extern "C" { +#include <libavutil/avutil.h> +#include <libavformat/avformat.h> +#include <libavfilter/avfilter.h> +#include <libavcodec/avcodec.h> +} + +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; + uint32_t fps_rate; + uint32_t fps_scale; + 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() = default; + + static bool Open() { return true; } + static void Close(); + static bool CanStartDecode(const uint8_t *buf, int buf_size); +}; + +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; + void ResetStartDecode(void); + bool CanStartDecode() const; + + static bool mpeg2_sequence_header(const uint8_t *data, const uint32_t size, mpeg2_sequence *sequence); + +protected: + static int avc_parse_nal_units(AVIOContext *pb, const uint8_t *buf_in, int size); + static int avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size); + int isom_write_avcc(AVIOContext *pb, const uint8_t *data, int len); + // bitstream to bytestream (Annex B) conversion support. + bool IsIDR(uint8_t unit_type); + bool IsSlice(uint8_t unit_type); + bool BitstreamConvertInitAVC(void *in_extradata, int in_extrasize); + bool BitstreamConvertInitHEVC(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, + uint8_t nal_type); + + 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; + bool m_start_decode; +}; diff --git a/xbmc/utils/BitstreamReader.cpp b/xbmc/utils/BitstreamReader.cpp new file mode 100644 index 0000000..6f2a7ff --- /dev/null +++ b/xbmc/utils/BitstreamReader.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "BitstreamReader.h" + +CBitstreamReader::CBitstreamReader(const uint8_t *buf, int len) + : buffer(buf) + , start(buf) + , offbits(0) + , length(len) + , oflow(0) +{ +} + +uint32_t CBitstreamReader::ReadBits(int nbits) +{ + uint32_t ret = GetBits(nbits); + + offbits += nbits; + buffer += offbits / 8; + offbits %= 8; + + return ret; +} + +void CBitstreamReader::SkipBits(int nbits) +{ + offbits += nbits; + buffer += offbits / 8; + offbits %= 8; + + if (buffer > (start + length)) + oflow = 1; +} + +uint32_t CBitstreamReader::GetBits(int nbits) +{ + int i, nbytes; + uint32_t ret = 0; + const uint8_t *buf; + + buf = buffer; + nbytes = (offbits + nbits) / 8; + + if (((offbits + nbits) % 8) > 0) + nbytes++; + + if ((buf + nbytes) > (start + length)) + { + oflow = 1; + return 0; + } + for (i = 0; i<nbytes; i++) + ret += buf[i] << ((nbytes - i - 1) * 8); + + i = (4 - nbytes) * 8 + offbits; + + ret = ((ret << i) >> i) >> ((nbytes * 8) - nbits - offbits); + + return ret; +} + +const uint8_t* find_start_code(const uint8_t *p, const uint8_t *end, uint32_t *state) +{ + 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 = (p < end)? p - 4 : end - 4; + *state = BS_RB32(p); + + return p + 4; +} diff --git a/xbmc/utils/BitstreamReader.h b/xbmc/utils/BitstreamReader.h new file mode 100644 index 0000000..89fa2b2 --- /dev/null +++ b/xbmc/utils/BitstreamReader.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stdint.h> + +class CBitstreamReader +{ +public: + CBitstreamReader(const uint8_t *buf, int len); + uint32_t ReadBits(int nbits); + void SkipBits(int nbits); + uint32_t GetBits(int nbits); + +private: + const uint8_t *buffer, *start; + int offbits, length, oflow; +}; + +const uint8_t* find_start_code(const uint8_t *p, const uint8_t *end, uint32_t *state); + +//////////////////////////////////////////////////////////////////////////////////////////// +//! @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 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * See LICENSES/README.md for more information. + */ +constexpr uint32_t BS_RB24(const uint8_t* x) +{ + return (x[0] << 16) | (x[1] << 8) | x[2]; +} + +constexpr uint32_t BS_RB32(const uint8_t* x) +{ + return (x[1] << 24) | (x[1] << 16) | (x[2] << 8) | x[3]; +} + diff --git a/xbmc/utils/BitstreamStats.cpp b/xbmc/utils/BitstreamStats.cpp new file mode 100644 index 0000000..a35a757 --- /dev/null +++ b/xbmc/utils/BitstreamStats.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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(); +} + +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/xbmc/utils/BitstreamStats.h b/xbmc/utils/BitstreamStats.h new file mode 100644 index 0000000..13413c6 --- /dev/null +++ b/xbmc/utils/BitstreamStats.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stdint.h> + +class BitstreamStats final +{ +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. + explicit BitstreamStats(unsigned int nEstimatedBitrate=(10240*8) /*10Kbit*/); + + 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; +}; + diff --git a/xbmc/utils/BitstreamWriter.cpp b/xbmc/utils/BitstreamWriter.cpp new file mode 100644 index 0000000..43c0788 --- /dev/null +++ b/xbmc/utils/BitstreamWriter.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "BitstreamWriter.h" + +CBitstreamWriter::CBitstreamWriter(uint8_t *buffer, unsigned int buffer_size, int writer_le) + : writer_le(writer_le) + , bit_buf(0) + , bit_left(32) + , buf(buffer) + , buf_ptr(buf) +{ +} + +void CBitstreamWriter::WriteBits(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 (writer_le) + { + WriteBits(16, lo); + WriteBits(16, hi); + } + else + { + WriteBits(16, hi); + WriteBits(16, lo); + } + return; + } + + bit_buf = this->bit_buf; + bit_left = this->bit_left; + + if (writer_le) + { + bit_buf |= value << (32 - bit_left); + if (n >= bit_left) { + BS_WL32(buf_ptr, bit_buf); + 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(buf_ptr, bit_buf); + buf_ptr += 4; + bit_left += 32 - n; + bit_buf = value; + } + } + + this->bit_buf = bit_buf; + this->bit_left = bit_left; +} + +void CBitstreamWriter::SkipBits(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. + bit_left -= n; + buf_ptr -= 4 * (bit_left >> 5); + bit_left &= 31; +} + +void CBitstreamWriter::FlushBits() +{ + if (!writer_le) + { + if (bit_left < 32) + bit_buf <<= bit_left; + } + while (bit_left < 32) + { + + if (writer_le) + { + *buf_ptr++ = bit_buf; + bit_buf >>= 8; + } + else + { + *buf_ptr++ = bit_buf >> 24; + bit_buf <<= 8; + } + bit_left += 8; + } + bit_left = 32; + bit_buf = 0; +} diff --git a/xbmc/utils/BitstreamWriter.h b/xbmc/utils/BitstreamWriter.h new file mode 100644 index 0000000..91257b2 --- /dev/null +++ b/xbmc/utils/BitstreamWriter.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stdint.h> + +class CBitstreamWriter +{ +public: + CBitstreamWriter(uint8_t *buffer, unsigned int buffer_size, int writer_le); + void WriteBits(int n, unsigned int value); + void SkipBits(int n); + void FlushBits(); + +private: + int writer_le; + uint32_t bit_buf; + int bit_left; + uint8_t *buf, *buf_ptr; +}; + +//////////////////////////////////////////////////////////////////////////////////////////// +//! @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 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * See LICENSES/README.md for more information. + */ +#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; } diff --git a/xbmc/utils/BooleanLogic.cpp b/xbmc/utils/BooleanLogic.cpp new file mode 100644 index 0000000..7ccc547 --- /dev/null +++ b/xbmc/utils/BooleanLogic.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "BooleanLogic.h" + +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.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 \"{}\"", strNegated); + return false; + } + } + + return true; +} + +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 <{}> definition", tag); + 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 <{}> definition", tag); + return false; + } + + m_values.push_back(value); + } + else if (operationNode->Type() == TiXmlNode::TINYXML_ELEMENT) + CLog::Log(LOGDEBUG, "CBooleanLogicOperation: unknown <{}> definition encountered", tag); + } + + 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/xbmc/utils/BooleanLogic.h b/xbmc/utils/BooleanLogic.h new file mode 100644 index 0000000..0355134 --- /dev/null +++ b/xbmc/utils/BooleanLogic.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/IXmlDeserializable.h" + +#include <memory> +#include <string> +#include <vector> + +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) + { } + ~CBooleanLogicValue() override = default; + + bool Deserialize(const TiXmlNode *node) override; + + 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 std::shared_ptr<CBooleanLogicValue> CBooleanLogicValuePtr; +typedef std::vector<CBooleanLogicValuePtr> CBooleanLogicValues; + +class CBooleanLogicOperation; +typedef std::shared_ptr<CBooleanLogicOperation> CBooleanLogicOperationPtr; +typedef std::vector<CBooleanLogicOperationPtr> CBooleanLogicOperations; + +class CBooleanLogicOperation : public IXmlDeserializable +{ +public: + explicit CBooleanLogicOperation(BooleanLogicOperation op = BooleanLogicOperationAnd) + : m_operation(op) + { } + ~CBooleanLogicOperation() override = default; + + bool Deserialize(const TiXmlNode *node) override; + + 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 +{ +protected: + /* make sure nobody deletes a pointer to this class */ + ~CBooleanLogic() override = default; + +public: + bool Deserialize(const TiXmlNode *node) override; + + const CBooleanLogicOperationPtr& Get() const { return m_operation; } + CBooleanLogicOperationPtr Get() { return m_operation; } + +protected: + CBooleanLogicOperationPtr m_operation; +}; diff --git a/xbmc/utils/BufferObject.cpp b/xbmc/utils/BufferObject.cpp new file mode 100644 index 0000000..1bf338e --- /dev/null +++ b/xbmc/utils/BufferObject.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "BufferObject.h" + +#include "BufferObjectFactory.h" +#include "utils/log.h" + +#if defined(HAVE_LINUX_DMA_BUF) +#include <linux/dma-buf.h> +#include <sys/ioctl.h> +#endif + +std::unique_ptr<CBufferObject> CBufferObject::GetBufferObject(bool needsCreateBySize) +{ + return CBufferObjectFactory::CreateBufferObject(needsCreateBySize); +} + +int CBufferObject::GetFd() +{ + return m_fd; +} + +uint32_t CBufferObject::GetStride() +{ + return m_stride; +} + +uint64_t CBufferObject::GetModifier() +{ + return 0; // linear +} + +void CBufferObject::SyncStart() +{ +#if defined(HAVE_LINUX_DMA_BUF) + struct dma_buf_sync sync; + sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_RW; + + int ret = ioctl(m_fd, DMA_BUF_IOCTL_SYNC, &sync); + if (ret < 0) + CLog::LogF(LOGERROR, "ioctl DMA_BUF_IOCTL_SYNC failed, ret={} errno={}", ret, strerror(errno)); +#endif +} + +void CBufferObject::SyncEnd() +{ +#if defined(HAVE_LINUX_DMA_BUF) + struct dma_buf_sync sync; + sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_RW; + + int ret = ioctl(m_fd, DMA_BUF_IOCTL_SYNC, &sync); + if (ret < 0) + CLog::LogF(LOGERROR, "ioctl DMA_BUF_IOCTL_SYNC failed, ret={} errno={}", ret, strerror(errno)); +#endif +} diff --git a/xbmc/utils/BufferObject.h b/xbmc/utils/BufferObject.h new file mode 100644 index 0000000..4603d9b --- /dev/null +++ b/xbmc/utils/BufferObject.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IBufferObject.h" + +#include <memory> +#include <stdint.h> + +/** + * @brief base class for using the IBufferObject interface. Derived classes + * should be based on this class. + * + */ +class CBufferObject : public IBufferObject +{ +public: + /** + * @brief Get a BufferObject from CBufferObjectFactory + * + * @return std::unique_ptr<CBufferObject> + */ + static std::unique_ptr<CBufferObject> GetBufferObject(bool needsCreateBySize); + + virtual bool CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) override = 0; + bool CreateBufferObject(uint64_t size) override { return false; } + + int GetFd() override; + uint32_t GetStride() override; + uint64_t GetModifier() override; + + void SyncStart() override; + void SyncEnd() override; + +protected: + int m_fd{-1}; + uint32_t m_stride{0}; +}; diff --git a/xbmc/utils/BufferObjectFactory.cpp b/xbmc/utils/BufferObjectFactory.cpp new file mode 100644 index 0000000..13ada4b --- /dev/null +++ b/xbmc/utils/BufferObjectFactory.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "BufferObjectFactory.h" + +std::list<std::function<std::unique_ptr<CBufferObject>()>> CBufferObjectFactory::m_bufferObjects; + +std::unique_ptr<CBufferObject> CBufferObjectFactory::CreateBufferObject(bool needsCreateBySize) +{ + for (const auto& bufferObject : m_bufferObjects) + { + auto bo = bufferObject(); + + if (needsCreateBySize) + { + if (!bo->CreateBufferObject(1)) + continue; + + bo->DestroyBufferObject(); + } + + return bo; + } + + return nullptr; +} + +void CBufferObjectFactory::RegisterBufferObject( + const std::function<std::unique_ptr<CBufferObject>()>& createFunc) +{ + m_bufferObjects.emplace_front(createFunc); +} + +void CBufferObjectFactory::ClearBufferObjects() +{ + m_bufferObjects.clear(); +} diff --git a/xbmc/utils/BufferObjectFactory.h b/xbmc/utils/BufferObjectFactory.h new file mode 100644 index 0000000..1420129 --- /dev/null +++ b/xbmc/utils/BufferObjectFactory.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "BufferObject.h" + +#include <functional> +#include <list> +#include <memory> + +/** + * @brief Factory that provides CBufferObject registration and creation + * + */ +class CBufferObjectFactory +{ +public: + /** + * @brief Create a CBufferObject from the registered BufferObject types. + * In the future this may include some criteria for selecting a specific + * CBufferObject derived type. Currently it returns the CBufferObject + * implementation that was registered last. + * + * @return std::unique_ptr<CBufferObject> + */ + static std::unique_ptr<CBufferObject> CreateBufferObject(bool needsCreateBySize); + + /** + * @brief Registers a CBufferObject class to class to the factory. + * + */ + static void RegisterBufferObject(const std::function<std::unique_ptr<CBufferObject>()>&); + + /** + * @brief Clears the list of registered CBufferObject types + * + */ + static void ClearBufferObjects(); + +protected: + static std::list<std::function<std::unique_ptr<CBufferObject>()>> m_bufferObjects; +}; diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt new file mode 100644 index 0000000..8e13289 --- /dev/null +++ b/xbmc/utils/CMakeLists.txt @@ -0,0 +1,248 @@ +set(SOURCES ActorProtocol.cpp + AlarmClock.cpp + AliasShortcutUtils.cpp + Archive.cpp + Base64.cpp + BitstreamConverter.cpp + BitstreamReader.cpp + BitstreamStats.cpp + BitstreamWriter.cpp + BooleanLogic.cpp + CharArrayParser.cpp + CharsetConverter.cpp + CharsetDetection.cpp + ColorUtils.cpp + ContentUtils.cpp + CPUInfo.cpp + Crc32.cpp + CSSUtils.cpp + DatabaseUtils.cpp + Digest.cpp + DiscsUtils.cpp + EndianSwap.cpp + EmbeddedArt.cpp + ExecString.cpp + FileExtensionProvider.cpp + Fanart.cpp + FileOperationJob.cpp + FileUtils.cpp + FontUtils.cpp + GroupUtils.cpp + HTMLUtil.cpp + HttpHeader.cpp + HttpParser.cpp + HttpRangeUtils.cpp + HttpResponse.cpp + InfoLoader.cpp + JobManager.cpp + JSONVariantParser.cpp + JSONVariantWriter.cpp + LabelFormatter.cpp + LangCodeExpander.cpp + LegacyPathTranslation.cpp + Locale.cpp + log.cpp + Mime.cpp + MovingSpeed.cpp + Observer.cpp + POUtils.cpp + PlayerUtils.cpp + RecentlyAddedJob.cpp + RegExp.cpp + rfft.cpp + RingBuffer.cpp + RssManager.cpp + RssReader.cpp + ProgressJob.cpp + SaveFileStateJob.cpp + ScraperParser.cpp + ScraperUrl.cpp + Screenshot.cpp + SortUtils.cpp + Speed.cpp + StreamDetails.cpp + StreamUtils.cpp + StringUtils.cpp + StringValidation.cpp + SystemInfo.cpp + Temperature.cpp + TextSearch.cpp + TimeUtils.cpp + URIUtils.cpp + UrlOptions.cpp + Utf8Utils.cpp + Variant.cpp + VC1BitstreamParser.cpp + Vector.cpp + XBMCTinyXML.cpp + XMLUtils.cpp) + +set(HEADERS ActorProtocol.h + AlarmClock.h + AliasShortcutUtils.h + Archive.h + Base64.h + BitstreamConverter.h + BitstreamReader.h + BitstreamStats.h + BitstreamWriter.h + BooleanLogic.h + CharArrayParser.h + CharsetConverter.h + CharsetDetection.h + CPUInfo.h + ColorUtils.h + ComponentContainer.h + ContentUtils.h + Crc32.h + CSSUtils.h + DatabaseUtils.h + Digest.h + DiscsUtils.h + EndianSwap.h + EventStream.h + EventStreamDetail.h + ExecString.h + FileExtensionProvider.h + Fanart.h + FileOperationJob.h + FileUtils.h + FontUtils.h + Geometry.h + GlobalsHandling.h + GroupUtils.h + HDRCapabilities.h + HTMLUtil.h + HttpHeader.h + HttpParser.h + HttpRangeUtils.h + HttpResponse.h + IArchivable.h + IBufferObject.h + ILocalizer.h + InfoLoader.h + IPlatformLog.h + IRssObserver.h + IScreenshotSurface.h + ISerializable.h + ISortable.h + IXmlDeserializable.h + Job.h + JobManager.h + JSONVariantParser.h + JSONVariantWriter.h + LabelFormatter.h + LangCodeExpander.h + LegacyPathTranslation.h + Locale.h + log.h + logtypes.h + Map.h + MathUtils.h + MemUtils.h + Mime.h + MovingSpeed.h + Observer.h + params_check_macros.h + POUtils.h + PlayerUtils.h + ProgressJob.h + RecentlyAddedJob.h + RegExp.h + rfft.h + RingBuffer.h + RssManager.h + RssReader.h + SaveFileStateJob.h + ScopeGuard.h + ScraperParser.h + ScraperUrl.h + Screenshot.h + SortUtils.h + Speed.h + Stopwatch.h + StreamDetails.h + StreamUtils.h + StringUtils.h + StringValidation.h + SystemInfo.h + Temperature.h + TextSearch.h + TimeFormat.h + TimeUtils.h + TransformMatrix.h + URIUtils.h + UrlOptions.h + Utf8Utils.h + Variant.h + VC1BitstreamParser.h + Vector.h + XBMCTinyXML.h + XMLUtils.h + XTimeUtils.h) + +if(XSLT_FOUND) + list(APPEND SOURCES XSLTUtils.cpp) + list(APPEND HEADERS XSLTUtils.h) +endif() +if(EGL_FOUND) + list(APPEND SOURCES EGLUtils.cpp + EGLFence.cpp) + list(APPEND HEADERS EGLUtils.h + EGLFence.h) +endif() + +# The large map trips the clang optimizer +if(CMAKE_CXX_COMPILER_ID STREQUAL Clang) + set_source_files_properties(Mime.cpp PROPERTIES COMPILE_FLAGS -O0) +endif() + +if(OPENGL_FOUND OR OPENGLES_FOUND) + list(APPEND SOURCES GLUtils.cpp) + list(APPEND HEADERS GLUtils.h) +endif() + +if("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC) + list(APPEND SOURCES BufferObject.cpp + BufferObjectFactory.cpp) + list(APPEND HEADERS BufferObject.h + BufferObjectFactory.h) + + if("gbm" IN_LIST CORE_PLATFORM_NAME_LC) + list(APPEND SOURCES DumbBufferObject.cpp) + list(APPEND SOURCES DumbBufferObject.h) + endif() + + if(HAVE_LINUX_MEMFD AND HAVE_LINUX_UDMABUF) + list(APPEND SOURCES UDMABufferObject.cpp) + list(APPEND HEADERS UDMABufferObject.h) + endif() + + if(HAVE_LINUX_DMA_HEAP) + list(APPEND SOURCES DMAHeapBufferObject.cpp) + list(APPEND HEADERS DMAHeapBufferObject.h) + endif() + + if(GBM_HAS_BO_MAP AND "gbm" IN_LIST CORE_PLATFORM_NAME_LC) + list(APPEND SOURCES GBMBufferObject.cpp) + list(APPEND HEADERS GBMBufferObject.h) + endif() + + if(EGL_FOUND) + list(APPEND SOURCES EGLImage.cpp) + list(APPEND HEADERS EGLImage.h) + endif() + + if(LIBDRM_FOUND) + list(APPEND SOURCES DRMHelpers.cpp) + list(APPEND HEADERS DRMHelpers.h) + endif() +endif() + +core_add_library(utils) + +if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore) + if(HAVE_SSE2) + target_compile_options(${CORE_LIBRARY} PRIVATE -msse2) + endif() +endif() diff --git a/xbmc/utils/CPUInfo.cpp b/xbmc/utils/CPUInfo.cpp new file mode 100644 index 0000000..9f43c10 --- /dev/null +++ b/xbmc/utils/CPUInfo.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "CPUInfo.h" + +#include "utils/StringUtils.h" + +bool CCPUInfo::HasCoreId(int coreId) const +{ + for (const auto& core : m_cores) + { + if (core.m_id == coreId) + return true; + } + + return false; +} + +const CoreInfo CCPUInfo::GetCoreInfo(int coreId) +{ + CoreInfo coreInfo; + + for (auto& core : m_cores) + { + if (core.m_id == coreId) + coreInfo = core; + } + + return coreInfo; +} + +std::string CCPUInfo::GetCoresUsageString() +{ + std::string strCores; + + if (SupportsCPUUsage()) + { + GetUsedPercentage(); // must call it to recalculate pct values + + if (!m_cores.empty()) + { + for (const auto& core : m_cores) + { + if (!strCores.empty()) + strCores += ' '; + if (core.m_usagePercent < 10.0) + strCores += StringUtils::Format("#{}: {:1.1f}%", core.m_id, core.m_usagePercent); + else + strCores += StringUtils::Format("#{}: {:3.0f}%", core.m_id, core.m_usagePercent); + } + } + else + { + strCores += StringUtils::Format("{:3.0f}%", static_cast<double>(m_lastUsedPercentage)); + } + } + + return strCores; +} diff --git a/xbmc/utils/CPUInfo.h b/xbmc/utils/CPUInfo.h new file mode 100644 index 0000000..e11384e --- /dev/null +++ b/xbmc/utils/CPUInfo.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/SystemClock.h" +#include "utils/Temperature.h" + +#include <chrono> +#include <memory> +#include <string> +#include <vector> + +enum CpuFeature +{ + CPU_FEATURE_MMX = 1 << 0, + CPU_FEATURE_MMX2 = 1 << 1, + CPU_FEATURE_SSE = 1 << 2, + CPU_FEATURE_SSE2 = 1 << 3, + CPU_FEATURE_SSE3 = 1 << 4, + CPU_FEATURE_SSSE3 = 1 << 5, + CPU_FEATURE_SSE4 = 1 << 6, + CPU_FEATURE_SSE42 = 1 << 7, + CPU_FEATURE_3DNOW = 1 << 8, + CPU_FEATURE_3DNOWEXT = 1 << 9, + CPU_FEATURE_ALTIVEC = 1 << 10, + CPU_FEATURE_NEON = 1 << 11, +}; + +struct CoreInfo +{ + int m_id = 0; + double m_usagePercent = 0.0; + std::size_t m_activeTime = 0; + std::size_t m_idleTime = 0; + std::size_t m_totalTime = 0; +}; + +class CCPUInfo +{ +public: + // Defines to help with calls to CPUID + const unsigned int CPUID_INFOTYPE_MANUFACTURER = 0x00000000; + const unsigned int CPUID_INFOTYPE_STANDARD = 0x00000001; + const unsigned int CPUID_INFOTYPE_EXTENDED_IMPLEMENTED = 0x80000000; + const unsigned int CPUID_INFOTYPE_EXTENDED = 0x80000001; + const unsigned int CPUID_INFOTYPE_PROCESSOR_1 = 0x80000002; + const unsigned int CPUID_INFOTYPE_PROCESSOR_2 = 0x80000003; + const unsigned int CPUID_INFOTYPE_PROCESSOR_3 = 0x80000004; + + // Standard Features + // Bitmasks for the values returned by a call to cpuid with eax=0x00000001 + const unsigned int CPUID_00000001_ECX_SSE3 = (1 << 0); + const unsigned int CPUID_00000001_ECX_SSSE3 = (1 << 9); + const unsigned int CPUID_00000001_ECX_SSE4 = (1 << 19); + const unsigned int CPUID_00000001_ECX_SSE42 = (1 << 20); + + const unsigned int CPUID_00000001_EDX_MMX = (1 << 23); + const unsigned int CPUID_00000001_EDX_SSE = (1 << 25); + const unsigned int CPUID_00000001_EDX_SSE2 = (1 << 26); + + // Extended Features + // Bitmasks for the values returned by a call to cpuid with eax=0x80000001 + const unsigned int CPUID_80000001_EDX_MMX2 = (1 << 22); + const unsigned int CPUID_80000001_EDX_MMX = (1 << 23); + const unsigned int CPUID_80000001_EDX_3DNOWEXT = (1 << 30); + const unsigned int CPUID_80000001_EDX_3DNOW = (1U << 31); + + // In milliseconds + const std::chrono::milliseconds MINIMUM_TIME_BETWEEN_READS{500}; + + static std::shared_ptr<CCPUInfo> GetCPUInfo(); + + virtual bool SupportsCPUUsage() const { return true; } + + virtual int GetUsedPercentage() = 0; + virtual float GetCPUFrequency() = 0; + virtual bool GetTemperature(CTemperature& temperature) = 0; + + bool HasCoreId(int coreId) const; + const CoreInfo GetCoreInfo(int coreId); + std::string GetCoresUsageString(); + + unsigned int GetCPUFeatures() const { return m_cpuFeatures; } + int GetCPUCount() const { return m_cpuCount; } + std::string GetCPUModel() { return m_cpuModel; } + std::string GetCPUBogoMips() { return m_cpuBogoMips; } + std::string GetCPUSoC() { return m_cpuSoC; } + std::string GetCPUHardware() { return m_cpuHardware; } + std::string GetCPURevision() { return m_cpuRevision; } + std::string GetCPUSerial() { return m_cpuSerial; } + +protected: + CCPUInfo() = default; + virtual ~CCPUInfo() = default; + + int m_lastUsedPercentage; + XbmcThreads::EndTime<> m_nextUsedReadTime; + std::string m_cpuVendor; + std::string m_cpuModel; + std::string m_cpuBogoMips; + std::string m_cpuSoC; + std::string m_cpuHardware; + std::string m_cpuRevision; + std::string m_cpuSerial; + + double m_usagePercent{0.0}; + std::size_t m_activeTime{0}; + std::size_t m_idleTime{0}; + std::size_t m_totalTime{0}; + + int m_cpuCount; + unsigned int m_cpuFeatures; + + std::vector<CoreInfo> m_cores; +}; diff --git a/xbmc/utils/CSSUtils.cpp b/xbmc/utils/CSSUtils.cpp new file mode 100644 index 0000000..329d70c --- /dev/null +++ b/xbmc/utils/CSSUtils.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "CSSUtils.h" + +#include <cstdint> +#include <string> + +namespace +{ +// https://www.w3.org/TR/css-syntax-3/#hex-digit +bool isHexDigit(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +// https://www.w3.org/TR/css-syntax-3/#hex-digit +uint32_t convertHexDigit(char c) +{ + if (c >= '0' && c <= '9') + { + return c - '0'; + } + else if (c >= 'A' && c <= 'F') + { + return 10 + c - 'A'; + } + else + { + return 10 + c - 'a'; + } +} + +// https://infra.spec.whatwg.org/#surrogate +bool isSurrogateCodePoint(uint32_t c) +{ + return c >= 0xD800 && c <= 0xDFFF; +} + +// https://www.w3.org/TR/css-syntax-3/#maximum-allowed-code-point +bool isGreaterThanMaximumAllowedCodePoint(uint32_t c) +{ + return c > 0x10FFFF; +} + +// https://www.w3.org/TR/css-syntax-3/#consume-escaped-code-point +std::string escapeStringChunk(std::string& str, size_t& pos) +{ + if (str.size() < pos + 1) + return ""; + + uint32_t codePoint = convertHexDigit(str[pos + 1]); + + if (str.size() >= pos + 2) + pos += 2; + else + return ""; + + int numDigits = 1; + while (numDigits < 6 && isHexDigit(str[pos])) + { + codePoint = 16 * codePoint + convertHexDigit(str[pos]); + if (str.size() >= pos + 1) + { + pos += 1; + numDigits += 1; + } + else + break; + } + + std::string result; + + // Convert code point to UTF-8 bytes + if (codePoint == 0 || isSurrogateCodePoint(codePoint) || + isGreaterThanMaximumAllowedCodePoint(codePoint)) + { + result += u8"\uFFFD"; + } + else if (codePoint < 0x80) + { + // 1-byte UTF-8: 0xxxxxxx + result += static_cast<char>(codePoint); + } + else if (codePoint < 0x800) + { + // 2-byte UTF-8: 110xxxxx 10xxxxxx + uint32_t x1 = codePoint >> 6; // 6 = num of x's in 2nd byte + uint32_t x2 = codePoint - (x1 << 6); // 6 = num of x's in 2nd byte + uint32_t b1 = (6 << 5) + x1; // 6 = 0b110 ; 5 = num of x's in 1st byte + uint32_t b2 = (2 << 6) + x2; // 2 = 0b10 ; 6 = num of x's in 2nd byte + result += static_cast<char>(b1); + result += static_cast<char>(b2); + } + else if (codePoint < 0x10000) + { + // 3-byte UTF-8: 1110xxxx 10xxxxxx 10xxxxxx + uint32_t y1 = codePoint >> 6; + uint32_t x3 = codePoint - (y1 << 6); + uint32_t x1 = y1 >> 6; + uint32_t x2 = y1 - (x1 << 6); + uint32_t b1 = (14 << 4) + x1; + uint32_t b2 = (2 << 6) + x2; + uint32_t b3 = (2 << 6) + x3; + result += static_cast<char>(b1); + result += static_cast<char>(b2); + result += static_cast<char>(b3); + } + else + { + // 4-byte UTF-8: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + uint32_t y2 = codePoint >> 6; + uint32_t x4 = codePoint - (y2 << 6); + uint32_t y1 = y2 >> 6; + uint32_t x3 = y2 - (y1 << 6); + uint32_t x1 = y1 >> 6; + uint32_t x2 = y1 - (x1 << 6); + uint32_t b1 = (30 << 3) + x1; + uint32_t b2 = (2 << 6) + x2; + uint32_t b3 = (2 << 6) + x3; + uint32_t b4 = (2 << 6) + x4; + result += static_cast<char>(b1); + result += static_cast<char>(b2); + result += static_cast<char>(b3); + result += static_cast<char>(b4); + } + + return result; +} + +} // unnamed namespace + +void UTILS::CSS::Escape(std::string& str) +{ + std::string result; + + for (size_t pos = 0; pos < str.size(); pos++) + { + if (str[pos] == '\\') + result += escapeStringChunk(str, pos); + else + result += str[pos]; + } + + str = result; +} diff --git a/xbmc/utils/CSSUtils.h b/xbmc/utils/CSSUtils.h new file mode 100644 index 0000000..9d610bb --- /dev/null +++ b/xbmc/utils/CSSUtils.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +namespace UTILS +{ +namespace CSS +{ + +/*! \brief Escape a CSS string + * \param str the string to be escaped + */ +void Escape(std::string& str); + +} // namespace CSS +} // namespace UTILS diff --git a/xbmc/utils/CharArrayParser.cpp b/xbmc/utils/CharArrayParser.cpp new file mode 100644 index 0000000..5aeec20 --- /dev/null +++ b/xbmc/utils/CharArrayParser.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "CharArrayParser.h" + +#include "utils/log.h" + +#include <cstdint> +#include <cstring> + +void CCharArrayParser::Reset() +{ + m_limit = 0; + m_position = 0; +} + +void CCharArrayParser::Reset(const char* data, int limit) +{ + m_data = data; + m_limit = limit; + m_position = 0; +} + +int CCharArrayParser::CharsLeft() +{ + return m_limit - m_position; +} + +int CCharArrayParser::GetPosition() +{ + return m_position; +} + +bool CCharArrayParser::SetPosition(int position) +{ + if (position >= 0 && position <= m_limit) + m_position = position; + else + { + CLog::Log(LOGERROR, "{} - Position out of range", __FUNCTION__); + return false; + } + return true; +} + +bool CCharArrayParser::SkipChars(int nChars) +{ + return SetPosition(m_position + nChars); +} + +uint8_t CCharArrayParser::ReadNextUnsignedChar() +{ + m_position++; + if (!m_data) + { + CLog::Log(LOGERROR, "{} - No data to read", __FUNCTION__); + return 0; + } + if (m_position > m_limit) + CLog::Log(LOGERROR, "{} - Position out of range", __FUNCTION__); + return static_cast<uint8_t>(m_data[m_position - 1]) & 0xFF; +} + +uint16_t CCharArrayParser::ReadNextUnsignedShort() +{ + if (!m_data) + { + CLog::Log(LOGERROR, "{} - No data to read", __FUNCTION__); + return 0; + } + m_position += 2; + if (m_position > m_limit) + CLog::Log(LOGERROR, "{} - Position out of range", __FUNCTION__); + return (static_cast<uint16_t>(m_data[m_position - 2]) & 0xFF) << 8 | + (static_cast<uint16_t>(m_data[m_position - 1]) & 0xFF); +} + +uint32_t CCharArrayParser::ReadNextUnsignedInt() +{ + if (!m_data) + { + CLog::Log(LOGERROR, "{} - No data to read", __FUNCTION__); + return 0; + } + m_position += 4; + if (m_position > m_limit) + CLog::Log(LOGERROR, "{} - Position out of range", __FUNCTION__); + return (static_cast<uint32_t>(m_data[m_position - 4]) & 0xFF) << 24 | + (static_cast<uint32_t>(m_data[m_position - 3]) & 0xFF) << 16 | + (static_cast<uint32_t>(m_data[m_position - 2]) & 0xFF) << 8 | + (static_cast<uint32_t>(m_data[m_position - 1]) & 0xFF); +} + +std::string CCharArrayParser::ReadNextString(int length) +{ + if (!m_data) + { + CLog::Log(LOGERROR, "{} - No data to read", __FUNCTION__); + return ""; + } + std::string str(m_data + m_position, length); + m_position += length; + if (m_position > m_limit) + CLog::Log(LOGERROR, "{} - Position out of range", __FUNCTION__); + return str; +} + +bool CCharArrayParser::ReadNextArray(int length, char* data) +{ + if (!m_data) + { + CLog::Log(LOGERROR, "{} - No data to read", __FUNCTION__); + return false; + } + if (m_position + length > m_limit) + { + CLog::Log(LOGERROR, "{} - Position out of range", __FUNCTION__); + return false; + } + std::strncpy(data, m_data + m_position, length); + data[length] = '\0'; + return true; +} + +bool CCharArrayParser::ReadNextLine(std::string& line) +{ + if (!m_data) + { + CLog::Log(LOGERROR, "{} - No data to read", __FUNCTION__); + return false; + } + if (CharsLeft() == 0) + { + line.clear(); + return false; + } + + int lineLimit = m_position; + while (lineLimit < m_limit && !(m_data[lineLimit] == '\n' || m_data[lineLimit] == '\r')) + { + lineLimit++; + } + + if (lineLimit - m_position >= 3 && m_data[m_position] == '\xEF' && + m_data[m_position + 1] == '\xBB' && m_data[m_position + 2] == '\xBF') + { + // There's a UTF-8 byte order mark at the start of the line. Discard it. + m_position += 3; + } + + line.assign(m_data + m_position, lineLimit - m_position); + m_position = lineLimit; + + if (m_data[m_position] == '\r') + { + m_position++; + } + if (m_data[m_position] == '\n') + { + m_position++; + } + + return true; +} diff --git a/xbmc/utils/CharArrayParser.h b/xbmc/utils/CharArrayParser.h new file mode 100644 index 0000000..3430946 --- /dev/null +++ b/xbmc/utils/CharArrayParser.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <cstdint> +#include <string> + +/*! + *\brief Wraps a char array, providing a set of methods for parsing data from it. + */ +class CCharArrayParser +{ +public: + CCharArrayParser() = default; + ~CCharArrayParser() = default; + + /*! + * \brief Sets the position and limit to zero + */ + void Reset(); + + /*! + * \brief Updates the instance to wrap the specified data and resets the position to zero + * \param data The data + * \param limit The limit of length of the data + */ + void Reset(const char* data, int limit); + + /*! + * \brief Return the number of chars yet to be read + */ + int CharsLeft(); + + /*! + * \brief Returns the current offset in the array + */ + int GetPosition(); + + /*! + * \brief Set the reading offset in the array + * \param position The new offset position + * \return True if success, otherwise false + */ + bool SetPosition(int position); + + /*! + * \brief Skip a specified number of chars + * \param nChars The number of chars + * \return True if success, otherwise false + */ + bool SkipChars(int nChars); + + /*! + * \brief Reads the next unsigned char (it is assumed that the caller has + * already checked the availability of the data for its length) + * \return The unsigned char value + */ + uint8_t ReadNextUnsignedChar(); + + /*! + * \brief Reads the next two chars as unsigned short value (it is assumed + * that the caller has already checked the availability of the data for its length) + * \return The unsigned short value + */ + uint16_t ReadNextUnsignedShort(); + + /*! + * \brief Reads the next four chars as unsigned int value (it is assumed + * that the caller has already checked the availability of the data for its length) + * \return The unsigned int value + */ + uint32_t ReadNextUnsignedInt(); + + /*! + * \brief Reads the next string of specified length (it is assumed that + * the caller has already checked the availability of the data for its length) + * \param length The length to be read + * \return The string value + */ + std::string ReadNextString(int length); + + /*! + * \brief Reads the next chars array of specified length (it is assumed that + * the caller has already checked the availability of the data for its length) + * \param length The length to be read + * \param data[OUT] The data read + * \return True if success, otherwise false + */ + bool ReadNextArray(int length, char* data); + + /*! + * \brief Reads a line of text. + * A line is considered to be terminated by any one of a carriage return ('\\r'), + * a line feed ('\\n'), or a carriage return followed by a line feed ('\\r\\n'), + * this method discards leading UTF-8 byte order marks, if present. + * \param line [OUT] The line read without line-termination characters + * \return True if read, otherwise false if the end of the data has already + * been reached + */ + bool ReadNextLine(std::string& line); + + /*! + * \brief Get the current data + * \return The char pointer to the current data + */ + const char* GetData() { return m_data; }; + +private: + const char* m_data{nullptr}; + int m_position{0}; + int m_limit{0}; +}; diff --git a/xbmc/utils/CharsetConverter.cpp b/xbmc/utils/CharsetConverter.cpp new file mode 100644 index 0000000..89976ee --- /dev/null +++ b/xbmc/utils/CharsetConverter.cpp @@ -0,0 +1,910 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "CharsetConverter.h" + +#include "LangInfo.h" +#include "guilib/LocalizeStrings.h" +#include "log.h" +#include "settings/Settings.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "utils/StringUtils.h" +#include "utils/Utf8Utils.h" + +#include <algorithm> +#include <mutex> + +#include <fribidi.h> +#include <iconv.h> + +#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 +#elif defined(TARGET_FREEBSD) + #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 +#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 */, +}; + +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(std::unique_lock<CCriticalSection>& 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() +{ + std::unique_lock<CCriticalSection> lock(*this); + if (m_iconv != NO_ICONV) + iconv_close(m_iconv); + lock.unlock(); // ensure unlocking before final destruction +} + +iconv_t CConverterType::GetConverter(std::unique_lock<CCriticalSection>& converterLock) +{ + // ensure that this unique instance is locked externally + if (converterLock.mutex() != 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, "{}: iconv_open() for \"{}\" -> \"{}\" failed, errno = {} ({})", + __FUNCTION__, m_sourceCharset, m_targetCharset, errno, strerror(errno)); + } + + return m_iconv; +} + +void CConverterType::Reset(void) +{ + std::unique_lock<CCriticalSection> 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*/) +{ + std::unique_lock<CCriticalSection> 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 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, + MacintoshToUtf8, + 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, + int* visualToLogicalMap = nullptr); + static bool isBidiDirectionRTL(const std::string& stringSrc); + + 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; + +// clang-format off +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), + /* MacintoshToUtf8 */ CConverterType("macintosh", "UTF-8", CCharsetConverter::m_Utf8CharMaxSize) +}; +// clang-format on + +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]; + std::unique_lock<CCriticalSection> 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, "{}: iconv_open() for \"{}\" -> \"{}\" failed, errno = {} ({})", + __FUNCTION__, sourceCharset, targetCharset, 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; + explicit 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(LOGFATAL, "{}: 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(true) + { + //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(LOGFATAL, "{} realloc failed with errno={}({})", __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, "{}: iconv() failed, errno={} ({})", __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, "{} failed cleanup errno={}({})", __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*/, + int* visualToLogicalMap /*= nullptr*/) +{ + 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 + std::unique_lock<CCriticalSection> 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(LOGFATAL, "{}: 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(reinterpret_cast<const FriBidiChar*>(stringSrc.c_str() + lineStart), + lineLen, &baseCopy, visual, nullptr, + !visualToLogicalMap ? nullptr : visualToLogicalMap + lineStart, nullptr)) + { + // Removes bidirectional marks + const int newLen = fribidi_remove_bidi_marks( + visual, lineLen, nullptr, !visualToLogicalMap ? nullptr : visualToLogicalMap + lineStart, + nullptr); + 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(); +} + +bool CCharsetConverter::CInnerConverter::isBidiDirectionRTL(const std::string& str) +{ + std::u32string converted; + if (!CInnerConverter::stdConvert(Utf8ToUtf32, str, converted, true)) + return false; + + int lineLen = static_cast<int>(str.size()); + FriBidiCharType* charTypes = new FriBidiCharType[lineLen]; + fribidi_get_bidi_types(reinterpret_cast<const FriBidiChar*>(converted.c_str()), + (FriBidiStrIndex)lineLen, charTypes); + FriBidiCharType charType = fribidi_get_par_direction(charTypes, (FriBidiStrIndex)lineLen); + delete[] charTypes; + return charType == FRIBIDI_PAR_RTL; +} + +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", "Vietnamese (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() = default; + +void CCharsetConverter::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + const std::string& settingId = setting->GetId(); + if (settingId == CSettings::SETTING_LOCALE_CHARSET) + resetUserCharset(); + else if (settingId == CSettings::SETTING_SUBTITLES_CHARSET) + resetSubtitleCharset(); +} + +void CCharsetConverter::clear() +{ +} + +std::vector<std::string> CCharsetConverter::getCharsetLabels() +{ + std::vector<std::string> lab; + for(SCharsetMapping* c = g_charsets; c->charset; c++) + lab.emplace_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 (CConverterType& conversion : CInnerConverter::m_stdConversion) + conversion.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(); +} + +void CCharsetConverter::resetSubtitleCharset(void) +{ + CInnerConverter::m_stdConversion[SubtitleCharsetToUtf8].Reset(); +} + +void CCharsetConverter::reinitCharsetsFromSettings(void) +{ + resetUserCharset(); // this will also reinit Subtitle 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*/, + int* visualToLogicalMap /*= nullptr*/) +{ + return CInnerConverter::logicalToVisualBiDi( + logicalStringSrc, visualStringDst, forceLTRReadingOrder ? FRIBIDI_TYPE_LTR : FRIBIDI_TYPE_PDF, + failOnBadString, visualToLogicalMap); +} + +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::utf16BEtoUTF8(const std::string& 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::MacintoshToUTF8(const std::string& macStringSrc, std::string& utf8StringDst) +{ + return CInnerConverter::stdConvert(MacintoshToUtf8, macStringSrc, utf8StringDst); +} + +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); +} + +bool CCharsetConverter::utf8IsRTLBidiDirection(const std::string& utf8String) +{ + return CInnerConverter::isBidiDirectionRTL(utf8String); +} + +void CCharsetConverter::SettingOptionsCharsetsFiller(const SettingConstPtr& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + std::vector<std::string> vecCharsets = g_charsetConverter.getCharsetLabels(); + sort(vecCharsets.begin(), vecCharsets.end(), sortstringbyname()); + + list.emplace_back(g_localizeStrings.Get(13278), "DEFAULT"); // "Default" + for (int i = 0; i < (int) vecCharsets.size(); ++i) + list.emplace_back(vecCharsets[i], g_charsetConverter.getCharsetNameByLabel(vecCharsets[i])); +} diff --git a/xbmc/utils/CharsetConverter.h b/xbmc/utils/CharsetConverter.h new file mode 100644 index 0000000..ae956c3 --- /dev/null +++ b/xbmc/utils/CharsetConverter.h @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/ISettingCallback.h" +#include "utils/GlobalsHandling.h" + +#include <string> +#include <utility> +#include <vector> + +class CSetting; +struct StringSettingOption; + +class CCharsetConverter : public ISettingCallback +{ +public: + CCharsetConverter(); + + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + 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 + * @param visualToLogicalMap is output mapping of positions in the visual string to the logical string + * @return true on success, false otherwise + */ + static bool utf32logicalToVisualBiDi(const std::u32string& logicalStringSrc, + std::u32string& visualStringDst, + bool forceLTRReadingOrder = false, + bool failOnBadString = false, + int* visualToLogicalMap = nullptr); + /** + * 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); + + /*! + * \brief Convert UTF-16BE (u16string) string to UTF-8 string. + * No RTL visual-logical transformation is performed. + * \param utf16StringSrc Is source UTF-16BE u16string string to convert + * \param utf8StringDst Is output UTF-8 string, empty on any error + * \return True on successful conversion, false on any error + */ + static bool utf16BEtoUTF8(const std::u16string& utf16StringSrc, std::string& utf8StringDst); + + /*! + * \brief Convert UTF-16BE (string) string to UTF-8 string. + * No RTL visual-logical transformation is performed. + * \param utf16StringSrc Is source UTF-16BE string to convert + * \param utf8StringDst Is output UTF-8 string, empty on any error + * \return True on successful conversion, false on any error + */ + static bool utf16BEtoUTF8(const std::string& utf16StringSrc, std::string& utf8StringDst); + + static bool utf16LEtoUTF8(const std::u16string& utf16StringSrc, std::string& utf8StringDst); + static bool ucs2ToUTF8(const std::u16string& ucs2StringSrc, std::string& utf8StringDst); + + /*! + * \brief Convert Macintosh (string) string to UTF-8 string. + * No RTL visual-logical transformation is performed. + * \param macStringSrc Is source Macintosh string to convert + * \param utf8StringDst Is output UTF-8 string, empty on any error + * \return True on successful conversion, false on any error + */ + static bool MacintoshToUTF8(const std::string& macStringSrc, std::string& utf8StringDst); + + static bool utf8logicalToVisualBiDi(const std::string& utf8StringSrc, std::string& utf8StringDst, bool failOnBadString = false); + + /** + * Check if a string has RTL direction. + * @param utf8StringSrc the string + * @return true if the string is RTL, otherwise false + */ + static bool utf8IsRTLBidiDirection(const std::string& utf8String); + + 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 std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + +private: + static void resetUserCharset(void); + static void resetSubtitleCharset(void); + + static const int m_Utf8CharMinSize, m_Utf8CharMaxSize; + class CInnerConverter; +}; + +XBMC_GLOBAL_REF(CCharsetConverter,g_charsetConverter); +#define g_charsetConverter XBMC_GLOBAL_USE(CCharsetConverter) diff --git a/xbmc/utils/CharsetDetection.cpp b/xbmc/utils/CharsetDetection.cpp new file mode 100644 index 0000000..965af0a --- /dev/null +++ b/xbmc/utils/CharsetDetection.cpp @@ -0,0 +1,642 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "CharsetDetection.h" + +#include "LangInfo.h" +#include "utils/CharsetConverter.h" +#include "utils/StringUtils.h" +#include "utils/Utf8Utils.h" +#include "utils/log.h" + +#include <algorithm> + +/* 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, "{}: Can't correctly convert to UTF-8 charset, converting as \"{}\"", + __FUNCTION__, usedHtmlCharset); + 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, "{}: Can't correctly convert to UTF-8 charset, converting as \"{}\"", + __FUNCTION__, usedCharset); + 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(const 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/xbmc/utils/CharsetDetection.h b/xbmc/utils/CharsetDetection.h new file mode 100644 index 0000000..d1b9ba9 --- /dev/null +++ b/xbmc/utils/CharsetDetection.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#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(const 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/xbmc/utils/ColorUtils.cpp b/xbmc/utils/ColorUtils.cpp new file mode 100644 index 0000000..d153bc4 --- /dev/null +++ b/xbmc/utils/ColorUtils.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ColorUtils.h" + +#include "StringUtils.h" + +#include <algorithm> +#include <cmath> +#include <cstdio> + +using namespace UTILS::COLOR; + +namespace +{ + +void GetHSLValues(ColorInfo& colorInfo) +{ + double r = (colorInfo.colorARGB & 0x00FF0000) >> 16; + double g = (colorInfo.colorARGB & 0x0000FF00) >> 8; + double b = (colorInfo.colorARGB & 0x000000FF); + r /= 255; + g /= 255; + b /= 255; + const double& maxVal = std::max<double>({r, g, b}); + const double& minVal = std::min<double>({r, g, b}); + double h = 0; + double s = 0; + double l = (minVal + maxVal) / 2; + double d = maxVal - minVal; + + if (d == 0) + { + h = s = 0; // achromatic + } + else + { + s = l > 0.5 ? d / (2 - maxVal - minVal) : d / (maxVal + minVal); + if (maxVal == r) + { + h = (g - b) / d + (g < b ? 6 : 0); + } + else if (maxVal == g) + { + h = (b - r) / d + 2; + } + else if (maxVal == b) + { + h = (r - g) / d + 4; + } + h /= 6; + } + + colorInfo.hue = h; + colorInfo.saturation = s; + colorInfo.lightness = l; +} + +} // unnamed namespace + +Color UTILS::COLOR::ChangeOpacity(const Color argb, const float opacity) +{ + int newAlpha = static_cast<int>(std::ceil(((argb >> 24) & 0xff) * opacity)); + return (argb & 0x00FFFFFF) | (newAlpha << 24); +}; + +Color UTILS::COLOR::ConvertToRGBA(const Color argb) +{ + return ((argb & 0x00FF0000) << 8) | //RR______ + ((argb & 0x0000FF00) << 8) | //__GG____ + ((argb & 0x000000FF) << 8) | //____BB__ + ((argb & 0xFF000000) >> 24); //______AA +} + +Color UTILS::COLOR::ConvertToARGB(const Color rgba) +{ + return ((rgba & 0x000000FF) << 24) | //AA_____ + ((rgba & 0xFF000000) >> 8) | //__RR____ + ((rgba & 0x00FF0000) >> 8) | //____GG__ + ((rgba & 0x0000FF00) >> 8); //______BB +} + +Color UTILS::COLOR::ConvertToBGR(const Color argb) +{ + return (argb & 0x00FF0000) >> 16 | //____RR + (argb & 0x0000FF00) | //__GG__ + (argb & 0x000000FF) << 16; //BB____ +} + +Color UTILS::COLOR::ConvertHexToColor(const std::string& hexColor) +{ + Color value = 0; + std::sscanf(hexColor.c_str(), "%x", &value); + return value; +} + +Color UTILS::COLOR::ConvertIntToRGB(int r, int g, int b) +{ + return ((r & 0xff) << 16) + ((g & 0xff) << 8) + (b & 0xff); +} + +ColorInfo UTILS::COLOR::MakeColorInfo(const Color& argb) +{ + ColorInfo colorInfo; + colorInfo.colorARGB = argb; + GetHSLValues(colorInfo); + return colorInfo; +} + +ColorInfo UTILS::COLOR::MakeColorInfo(const std::string& hexColor) +{ + ColorInfo colorInfo; + colorInfo.colorARGB = ConvertHexToColor(hexColor); + GetHSLValues(colorInfo); + return colorInfo; +} + +bool UTILS::COLOR::comparePairColorInfo(const std::pair<std::string, ColorInfo>& a, + const std::pair<std::string, ColorInfo>& b) +{ + if (a.second.hue == b.second.hue) + { + if (a.second.saturation == b.second.saturation) + return (a.second.lightness < b.second.lightness); + else + return (a.second.saturation < b.second.saturation); + } + else + return (a.second.hue < b.second.hue); +} + +ColorFloats UTILS::COLOR::ConvertToFloats(const Color argb) +{ + ColorFloats c; + c.alpha = static_cast<float>((argb >> 24) & 0xFF) * (1.0f / 255.0f); + c.red = static_cast<float>((argb >> 16) & 0xFF) * (1.0f / 255.0f); + c.green = static_cast<float>((argb >> 8) & 0xFF) * (1.0f / 255.0f); + c.blue = static_cast<float>(argb & 0xFF) * (1.0f / 255.0f); + return c; +} + +std::string UTILS::COLOR::ConvertoToHexRGB(const Color argb) +{ + return StringUtils::Format("{:06X}", argb & ~0xFF000000); +} diff --git a/xbmc/utils/ColorUtils.h b/xbmc/utils/ColorUtils.h new file mode 100644 index 0000000..f157835 --- /dev/null +++ b/xbmc/utils/ColorUtils.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/Map.h" + +#include <cstdint> +#include <string> +#include <utility> +#include <vector> + +namespace UTILS +{ +namespace COLOR +{ + +typedef uint32_t Color; + +// Custom colors + +constexpr Color NONE = 0x00000000; +constexpr Color LIMITED_BLACK = 0xFF101010; + +// W3C HTML color list + +constexpr Color WHITE = 0xFFFFFFFF; +constexpr Color SILVER = 0xFFC0C0C0; +constexpr Color GRAY = 0xFF808080; +constexpr Color BLACK = 0xFF000000; +constexpr Color RED = 0xFFFF0000; +constexpr Color MAROON = 0xFF800000; +constexpr Color YELLOW = 0xFFFFFF00; +constexpr Color OLIVE = 0xFF808000; +constexpr Color LIME = 0xFF00FF00; +constexpr Color GREEN = 0xFF008000; +constexpr Color AQUA = 0xFF00FFFF; +constexpr Color TEAL = 0xFF008080; +constexpr Color BLUE = 0xFF0000FF; +constexpr Color NAVY = 0xFF000080; +constexpr Color FUCHSIA = 0xFFFF00FF; +constexpr Color PURPLE = 0xFF800080; + +struct ColorInfo +{ + Color colorARGB; + double hue; + double saturation; + double lightness; +}; + +struct ColorFloats +{ + float red; + float green; + float blue; + float alpha; +}; + +//! \brief W3C HTML 16 basic color list +constexpr auto HTML_BASIC_COLORS = make_map<std::string_view, Color>({{"white", WHITE}, + {"silver", SILVER}, + {"gray", GRAY}, + {"black", BLACK}, + {"red", RED}, + {"maroon", MAROON}, + {"yellow", YELLOW}, + {"olive", OLIVE}, + {"lime", LIME}, + {"green", GREEN}, + {"aqua", AQUA}, + {"teal", TEAL}, + {"blue", BLUE}, + {"navy", NAVY}, + {"fuchsia", FUCHSIA}, + {"purple", PURPLE}}); + +/*! + * \brief Change the opacity of a given ARGB color + * \param color The original color + * \param opacity The opacity value as a float + * \return the original color with the changed opacity/alpha value + */ +Color ChangeOpacity(const Color argb, const float opacity); + +/*! + * \brief Convert given ARGB color to RGBA color value + * \param color The original color + * \return the original color converted to RGBA value + */ +Color ConvertToRGBA(const Color argb); + +/*! + * \brief Convert given RGBA color to ARGB color value + * \param color The original color + * \return the original color converted to ARGB value + */ +Color ConvertToARGB(const Color rgba); + +/*! + * \brief Convert given ARGB color to BGR color value + * \param color The original color + * \return the original color converted to BGR value +*/ +Color ConvertToBGR(const Color argb); + +/*! + * \brief Convert given hex value to Color value + * \param hexColor The original hex color + * \return the original hex color converted to Color value + */ +Color ConvertHexToColor(const std::string& hexColor); + +/*! + * \brief Convert given RGB int values to RGB color value + * \param r The red value + * \param g The green value + * \param b The blue value + * \return the color as RGB value + */ +Color ConvertIntToRGB(int r, int g, int b); + +/*! + * \brief Create a ColorInfo from an ARGB Color to + * get additional information of the color + * and allow to be sorted with a color comparer + * \param argb The original ARGB color + * \return the ColorInfo + */ +ColorInfo MakeColorInfo(const Color& argb); + +/*! + * \brief Create a ColorInfo from an HEX color value to + * get additional information of the color + * and allow to be sorted with a color comparer + * \param hexColor The original ARGB color + * \return the ColorInfo + */ +ColorInfo MakeColorInfo(const std::string& hexColor); + +/*! + * \brief Comparer for pair string/ColorInfo to sort colors in a hue scale + */ +bool comparePairColorInfo(const std::pair<std::string, ColorInfo>& a, + const std::pair<std::string, ColorInfo>& b); + +/*! + * \brief Convert given ARGB color to ColorFloats + * \param color The original color + * \return the original color converted to ColorFloats + */ +ColorFloats ConvertToFloats(const Color argb); + +/*! + * \brief Convert given ARGB color to hex RGB color value + * \param color The original color + * \return The original color converted to hex RGB + */ +std::string ConvertoToHexRGB(const Color argb); + +} // namespace COLOR +} // namespace UTILS diff --git a/xbmc/utils/ComponentContainer.h b/xbmc/utils/ComponentContainer.h new file mode 100644 index 0000000..3aa826a --- /dev/null +++ b/xbmc/utils/ComponentContainer.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/CriticalSection.h" + +#include <cstddef> +#include <memory> +#include <mutex> +#include <stdexcept> +#include <typeindex> +#include <unordered_map> +#include <utility> + +//! \brief A generic container for components. +//! \details A component has to be derived from the BaseType. +//! Only a single instance of each derived type can be registered. +//! Intended use is through inheritance. +template<class BaseType> +class CComponentContainer +{ +public: + //! \brief Obtain a component. + template<class T> + std::shared_ptr<T> GetComponent() + { + return std::const_pointer_cast<T>(std::as_const(*this).template GetComponent<T>()); + } + + //! \brief Obtain a component. + template<class T> + std::shared_ptr<const T> GetComponent() const + { + std::unique_lock<CCriticalSection> lock(m_critSection); + const auto it = m_components.find(std::type_index(typeid(T))); + if (it != m_components.end()) + return std::static_pointer_cast<const T>((*it).second); + + throw std::logic_error("ComponentContainer: Attempt to obtain non-existent component"); + } + + //! \brief Returns number of registered components. + std::size_t size() const { return m_components.size(); } + +protected: + //! \brief Register a new component instance. + void RegisterComponent(const std::shared_ptr<BaseType>& component) + { + if (!component) + return; + + // Note: Extra var needed to avoid clang warning + // "Expression with side effects will be evaluated despite being used as an operand to 'typeid'" + // https://stackoverflow.com/questions/46494928/clang-warning-on-expression-side-effects + const auto& componentRef = *component; + + std::unique_lock<CCriticalSection> lock(m_critSection); + m_components.insert({std::type_index(typeid(componentRef)), component}); + } + + //! \brief Deregister a component. + void DeregisterComponent(const std::type_info& typeInfo) + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_components.erase(typeInfo); + } + +private: + mutable CCriticalSection m_critSection; //!< Critical section for map updates + std::unordered_map<std::type_index, std::shared_ptr<BaseType>> + m_components; //!< Map of components +}; diff --git a/xbmc/utils/ContentUtils.cpp b/xbmc/utils/ContentUtils.cpp new file mode 100644 index 0000000..b0ebc67 --- /dev/null +++ b/xbmc/utils/ContentUtils.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ContentUtils.h" + +#include "FileItem.h" +#include "utils/StringUtils.h" +#include "video/VideoInfoTag.h" + +namespace +{ +bool HasPreferredArtType(const CFileItem& item) +{ + return item.HasVideoInfoTag() && (item.GetVideoInfoTag()->m_type == MediaTypeMovie || + item.GetVideoInfoTag()->m_type == MediaTypeTvShow || + item.GetVideoInfoTag()->m_type == MediaTypeSeason || + item.GetVideoInfoTag()->m_type == MediaTypeVideoCollection); +} + +std::string GetPreferredArtType(const MediaType& type) +{ + if (type == MediaTypeMovie || type == MediaTypeTvShow || type == MediaTypeSeason || + type == MediaTypeVideoCollection) + { + return "poster"; + } + return "thumb"; +} +} // namespace + +const std::string ContentUtils::GetPreferredArtImage(const CFileItem& item) +{ + if (HasPreferredArtType(item)) + { + auto preferredArtType = GetPreferredArtType(item.GetVideoInfoTag()->m_type); + if (item.HasArt(preferredArtType)) + { + return item.GetArt(preferredArtType); + } + } + return item.GetArt("thumb"); +} + +std::unique_ptr<CFileItem> ContentUtils::GeneratePlayableTrailerItem(const CFileItem& item, + const std::string& label) +{ + std::unique_ptr<CFileItem> trailerItem = std::make_unique<CFileItem>(); + trailerItem->SetPath(item.GetVideoInfoTag()->m_strTrailer); + CVideoInfoTag* videoInfoTag = trailerItem->GetVideoInfoTag(); + *videoInfoTag = *item.GetVideoInfoTag(); + videoInfoTag->m_streamDetails.Reset(); + videoInfoTag->m_strTitle = StringUtils::Format("{} ({})", videoInfoTag->m_strTitle, label); + trailerItem->SetArt(item.GetArt()); + videoInfoTag->m_iDbId = -1; + videoInfoTag->m_iFileId = -1; + return trailerItem; +} diff --git a/xbmc/utils/ContentUtils.h b/xbmc/utils/ContentUtils.h new file mode 100644 index 0000000..b9037a7 --- /dev/null +++ b/xbmc/utils/ContentUtils.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "media/MediaType.h" + +#include <memory> +#include <string> + +class CFileItem; + +class ContentUtils +{ +public: + /*! \brief Gets the preferred art image for a given item (depending on its media type). Provided a CFileItem + with many art types on its art map, this method will return a default preferred image (content dependent) for situations where the + the client expects a single image to represent the item. For instance, for movies and shows this will return the poster, while for + episodes it will return the thumb. + \param item The CFileItem to process + \return the preferred art image + */ + static const std::string GetPreferredArtImage(const CFileItem& item); + + /*! \brief Gets a trailer playable file item for a given item. Essentially this creates a new item which contains + the original item infotag and has the playable path and label changed. + \param item The CFileItem to process + \param label The label for the new item + \return a pointer to the trailer item + */ + static std::unique_ptr<CFileItem> GeneratePlayableTrailerItem(const CFileItem& item, + const std::string& label); +}; diff --git a/xbmc/utils/Crc32.cpp b/xbmc/utils/Crc32.cpp new file mode 100644 index 0000000..e10f9c9 --- /dev/null +++ b/xbmc/utils/Crc32.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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]; +} + +uint32_t Crc32::Compute(const std::string& strValue) +{ + Crc32 crc; + crc.Compute(strValue.c_str(), strValue.size()); + return crc; +} + +uint32_t Crc32::ComputeFromLowerCase(const std::string& strValue) +{ + std::string strLower = strValue; + StringUtils::ToLower(strLower); + return Compute(strLower); +} + diff --git a/xbmc/utils/Crc32.h b/xbmc/utils/Crc32.h new file mode 100644 index 0000000..f4a3588 --- /dev/null +++ b/xbmc/utils/Crc32.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stdint.h> +#include <string> + +class Crc32 +{ +public: + Crc32(); + void Reset(); + void Compute(const char* buffer, size_t count); + static uint32_t Compute(const std::string& strValue); + static uint32_t ComputeFromLowerCase(const std::string& strValue); + + operator uint32_t () const + { + return m_crc; + } + +private: + uint32_t m_crc; +}; + diff --git a/xbmc/utils/DMAHeapBufferObject.cpp b/xbmc/utils/DMAHeapBufferObject.cpp new file mode 100644 index 0000000..ad05aa8 --- /dev/null +++ b/xbmc/utils/DMAHeapBufferObject.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DMAHeapBufferObject.h" + +#include "ServiceBroker.h" +#include "utils/BufferObjectFactory.h" +#include "utils/log.h" + +#include <array> + +#include <drm_fourcc.h> +#include <fcntl.h> +#include <linux/dma-heap.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <unistd.h> + +namespace +{ + +std::array<const char*, 3> DMA_HEAP_PATHS = { + "/dev/dma_heap/reserved", + "/dev/dma_heap/linux,cma", + "/dev/dma_heap/system", +}; + +static const char* DMA_HEAP_PATH; + +} // namespace + +std::unique_ptr<CBufferObject> CDMAHeapBufferObject::Create() +{ + return std::make_unique<CDMAHeapBufferObject>(); +} + +void CDMAHeapBufferObject::Register() +{ + for (auto path : DMA_HEAP_PATHS) + { + int fd = open(path, O_RDWR); + if (fd < 0) + { + CLog::Log(LOGDEBUG, "CDMAHeapBufferObject::{} unable to open {}: {}", __FUNCTION__, path, + strerror(errno)); + continue; + } + + close(fd); + DMA_HEAP_PATH = path; + break; + } + + if (!DMA_HEAP_PATH) + return; + + CLog::Log(LOGDEBUG, "CDMAHeapBufferObject::{} - using {}", __FUNCTION__, DMA_HEAP_PATH); + + CBufferObjectFactory::RegisterBufferObject(CDMAHeapBufferObject::Create); +} + +CDMAHeapBufferObject::~CDMAHeapBufferObject() +{ + ReleaseMemory(); + DestroyBufferObject(); + + close(m_dmaheapfd); + m_dmaheapfd = -1; +} + +bool CDMAHeapBufferObject::CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) +{ + if (m_fd >= 0) + return true; + + uint32_t bpp{1}; + + switch (format) + { + case DRM_FORMAT_ARGB8888: + bpp = 4; + break; + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_RGB565: + bpp = 2; + break; + default: + throw std::runtime_error("CDMAHeapBufferObject: pixel format not implemented"); + } + + m_stride = width * bpp; + + return CreateBufferObject(width * height * bpp); +} + +bool CDMAHeapBufferObject::CreateBufferObject(uint64_t size) +{ + m_size = size; + + if (m_dmaheapfd < 0) + { + m_dmaheapfd = open(DMA_HEAP_PATH, O_RDWR); + if (m_dmaheapfd < 0) + { + CLog::LogF(LOGERROR, "failed to open {}:", DMA_HEAP_PATH, strerror(errno)); + return false; + } + } + + struct dma_heap_allocation_data allocData{}; + allocData.len = m_size; + allocData.fd_flags = (O_CLOEXEC | O_RDWR); + allocData.heap_flags = 0; + + int ret = ioctl(m_dmaheapfd, DMA_HEAP_IOCTL_ALLOC, &allocData); + if (ret < 0) + { + CLog::Log(LOGERROR, "CDMAHeapBufferObject::{} - ioctl DMA_HEAP_IOCTL_ALLOC failed, errno={}", + __FUNCTION__, strerror(errno)); + return false; + } + + m_fd = allocData.fd; + m_size = allocData.len; + + if (m_fd < 0 || m_size <= 0) + { + CLog::Log(LOGERROR, "CDMAHeapBufferObject::{} - invalid allocation data: fd={} len={}", + __FUNCTION__, m_fd, m_size); + return false; + } + + return true; +} + +void CDMAHeapBufferObject::DestroyBufferObject() +{ + if (m_fd < 0) + return; + + int ret = close(m_fd); + if (ret < 0) + CLog::Log(LOGERROR, "CDMAHeapBufferObject::{} - close failed, errno={}", __FUNCTION__, + strerror(errno)); + + m_fd = -1; + m_stride = 0; + m_size = 0; +} + +uint8_t* CDMAHeapBufferObject::GetMemory() +{ + if (m_fd < 0) + return nullptr; + + if (m_map) + { + CLog::Log(LOGDEBUG, "CDMAHeapBufferObject::{} - already mapped fd={} map={}", __FUNCTION__, + m_fd, fmt::ptr(m_map)); + return m_map; + } + + m_map = static_cast<uint8_t*>(mmap(nullptr, m_size, PROT_WRITE, MAP_SHARED, m_fd, 0)); + if (m_map == MAP_FAILED) + { + CLog::Log(LOGERROR, "CDMAHeapBufferObject::{} - mmap failed, errno={}", __FUNCTION__, + strerror(errno)); + return nullptr; + } + + return m_map; +} + +void CDMAHeapBufferObject::ReleaseMemory() +{ + if (!m_map) + return; + + int ret = munmap(m_map, m_size); + if (ret < 0) + CLog::Log(LOGERROR, "CDMAHeapBufferObject::{} - munmap failed, errno={}", __FUNCTION__, + strerror(errno)); + + m_map = nullptr; +} diff --git a/xbmc/utils/DMAHeapBufferObject.h b/xbmc/utils/DMAHeapBufferObject.h new file mode 100644 index 0000000..eb7a6fe --- /dev/null +++ b/xbmc/utils/DMAHeapBufferObject.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/BufferObject.h" + +#include <memory> +#include <stdint.h> + +class CDMAHeapBufferObject : public CBufferObject +{ +public: + CDMAHeapBufferObject() = default; + virtual ~CDMAHeapBufferObject() override; + + // Registration + static std::unique_ptr<CBufferObject> Create(); + static void Register(); + + // IBufferObject overrides via CBufferObject + bool CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) override; + bool CreateBufferObject(uint64_t size) override; + void DestroyBufferObject() override; + uint8_t* GetMemory() override; + void ReleaseMemory() override; + std::string GetName() const override { return "CDMAHeapBufferObject"; } + +private: + int m_dmaheapfd{-1}; + uint64_t m_size{0}; + uint8_t* m_map{nullptr}; +}; diff --git a/xbmc/utils/DRMHelpers.cpp b/xbmc/utils/DRMHelpers.cpp new file mode 100644 index 0000000..0b4b4e4 --- /dev/null +++ b/xbmc/utils/DRMHelpers.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DRMHelpers.h" + +#include "utils/StringUtils.h" + +#include <sstream> + +#include <drm_fourcc.h> +#include <xf86drm.h> + +namespace DRMHELPERS +{ + +std::string FourCCToString(uint32_t fourcc) +{ + std::stringstream ss; + ss << static_cast<char>((fourcc & 0x000000FF)); + ss << static_cast<char>((fourcc & 0x0000FF00) >> 8); + ss << static_cast<char>((fourcc & 0x00FF0000) >> 16); + ss << static_cast<char>((fourcc & 0xFF000000) >> 24); + + return ss.str(); +} + +std::string ModifierToString(uint64_t modifier) +{ +#if defined(HAVE_DRM_MODIFIER_NAME) + std::string modifierVendorStr{"UNKNOWN_VENDOR"}; + + const char* vendorName = drmGetFormatModifierVendor(modifier); + if (vendorName) + modifierVendorStr = std::string(vendorName); + + free(const_cast<char*>(vendorName)); + + std::string modifierNameStr{"UNKNOWN_MODIFIER"}; + + const char* modifierName = drmGetFormatModifierName(modifier); + if (modifierName) + modifierNameStr = std::string(modifierName); + + free(const_cast<char*>(modifierName)); + + if (modifier == DRM_FORMAT_MOD_LINEAR) + return modifierNameStr; + + return modifierVendorStr + "_" + modifierNameStr; +#else + return StringUtils::Format("{:#x}", modifier); +#endif +} + +} // namespace DRMHELPERS diff --git a/xbmc/utils/DRMHelpers.h b/xbmc/utils/DRMHelpers.h new file mode 100644 index 0000000..dcc7f50 --- /dev/null +++ b/xbmc/utils/DRMHelpers.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <cstdint> +#include <string> + +namespace DRMHELPERS +{ + +std::string FourCCToString(uint32_t fourcc); + +std::string ModifierToString(uint64_t modifier); + +} // namespace DRMHELPERS diff --git a/xbmc/utils/DatabaseUtils.cpp b/xbmc/utils/DatabaseUtils.cpp new file mode 100644 index 0000000..7397516 --- /dev/null +++ b/xbmc/utils/DatabaseUtils.cpp @@ -0,0 +1,797 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DatabaseUtils.h" + +#include "dbwrappers/dataset.h" +#include "music/MusicDatabase.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" + +#include <sstream> + +MediaType DatabaseUtils::MediaTypeFromVideoContentType(VideoDbContentType videoContentType) +{ + switch (videoContentType) + { + case VideoDbContentType::MOVIES: + return MediaTypeMovie; + + case VideoDbContentType::MOVIE_SETS: + return MediaTypeVideoCollection; + + case VideoDbContentType::TVSHOWS: + return MediaTypeTvShow; + + case VideoDbContentType::EPISODES: + return MediaTypeEpisode; + + case VideoDbContentType::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.strGenres"; + else if (field == FieldYear) + return "albumview.strReleaseDate"; + else if (field == FieldOrigYear || field == FieldOrigDate) + return "albumview.strOrigReleaseDate"; + 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 == FieldCompilation) return "albumview.bCompilation"; + else if (field == FieldRating) return "albumview.fRating"; + else if (field == FieldVotes) return "albumview.iVotes"; + else if (field == FieldUserRating) return "albumview.iUserrating"; + else if (field == FieldDateAdded) return "albumview.dateAdded"; + else if (field == FieldDateNew) return "albumview.dateNew"; + else if (field == FieldDateModified) return "albumview.dateModified"; + else if (field == FieldPlaycount) return "albumview.iTimesPlayed"; + else if (field == FieldLastPlayed) return "albumview.lastPlayed"; + else if (field == FieldTotalDiscs) + return "albumview.iDiscTotal"; + else if (field == FieldAlbumStatus) + return "albumview.strReleaseStatus"; + else if (field == FieldAlbumDuration) + return "albumview.iAlbumDuration"; + } + 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.strReleaseDate"; + else if (field == FieldOrigYear || field == FieldOrigDate) + return "songview.strOrigReleaseDate"; + 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 == FieldVotes) return "songview.votes"; + else if (field == FieldUserRating) return "songview.userrating"; + else if (field == FieldComment) return "songview.comment"; + else if (field == FieldMoods) return "songview.mood"; + 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.strGenres"; + else if (field == FieldDateAdded) return "songview.dateAdded"; + else if (field == FieldDateNew) return "songview.dateNew"; + else if (field == FieldDateModified) return "songview.dateModified"; + + else if (field == FieldDiscTitle) + return "songview.strDiscSubtitle"; + else if (field == FieldBPM) + return "songview.iBPM"; + else if (field == FieldMusicBitRate) + return "songview.iBitRate"; + else if (field == FieldSampleRate) + return "songview.iSampleRate"; + else if (field == FieldNoOfChannels) + return "songview.iChannels"; + } + else if (mediaType == MediaTypeArtist) + { + if (field == FieldId) return "artistview.idArtist"; + else if (field == FieldArtistSort) return "artistview.strSortName"; + else if (field == FieldArtist) return "artistview.strArtist"; + else if (field == FieldArtistType) return "artistview.strType"; + else if (field == FieldGender) return "artistview.strGender"; + else if (field == FieldDisambiguation) return "artistview.strDisambiguation"; + 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 (field == FieldDateAdded) return "artistview.dateAdded"; + else if (field == FieldDateNew) return "artistview.dateNew"; + else if (field == FieldDateModified) return "artistview.dateModified"; + } + else if (mediaType == MediaTypeMusicVideo) + { + std::string result; + if (field == FieldId) return "musicvideo_view.idMVideo"; + else if (field == FieldTitle) + result = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_TITLE); + else if (field == FieldTime) + result = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_RUNTIME); + else if (field == FieldDirector) + result = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_DIRECTOR); + else if (field == FieldStudio) + result = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_STUDIOS); + else if (field == FieldYear) return "musicvideo_view.premiered"; + else if (field == FieldPlot) + result = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_PLOT); + else if (field == FieldAlbum) + result = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_ALBUM); + else if (field == FieldArtist) + result = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_ARTIST); + else if (field == FieldGenre) + result = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_GENRE); + else if (field == FieldTrackNumber) + result = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_TRACK); + else if (field == FieldFilename) return "musicvideo_view.strFilename"; + else if (field == FieldPath) return "musicvideo_view.strPath"; + else if (field == FieldPlaycount) return "musicvideo_view.playCount"; + else if (field == FieldLastPlayed) return "musicvideo_view.lastPlayed"; + else if (field == FieldDateAdded) return "musicvideo_view.dateAdded"; + else if (field == FieldUserRating) return "musicvideo_view.userrating"; + + if (!result.empty()) + return result; + } + else if (mediaType == MediaTypeMovie) + { + std::string result; + if (field == FieldId) return "movie_view.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(movie_view.c{:02}) > 0 THEN " + "movie_view.c{:02} ELSE movie_view.c{:02} END", + VIDEODB_ID_SORTTITLE, VIDEODB_ID_SORTTITLE, VIDEODB_ID_TITLE); + else + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_TITLE); + } + else if (field == FieldPlot) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_PLOT); + else if (field == FieldPlotOutline) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_PLOTOUTLINE); + else if (field == FieldTagline) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_TAGLINE); + else if (field == FieldVotes) return "movie_view.votes"; + else if (field == FieldRating) return "movie_view.rating"; + else if (field == FieldWriter) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_CREDITS); + else if (field == FieldYear) return "movie_view.premiered"; + else if (field == FieldSortTitle) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_SORTTITLE); + else if (field == FieldOriginalTitle) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_ORIGINALTITLE); + else if (field == FieldTime) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_RUNTIME); + else if (field == FieldMPAA) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_MPAA); + else if (field == FieldTop250) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_TOP250); + else if (field == FieldSet) return "movie_view.strSet"; + else if (field == FieldGenre) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_GENRE); + else if (field == FieldDirector) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_DIRECTOR); + else if (field == FieldStudio) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_STUDIOS); + else if (field == FieldTrailer) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_TRAILER); + else if (field == FieldCountry) + result = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_COUNTRY); + else if (field == FieldFilename) return "movie_view.strFilename"; + else if (field == FieldPath) return "movie_view.strPath"; + else if (field == FieldPlaycount) return "movie_view.playCount"; + else if (field == FieldLastPlayed) return "movie_view.lastPlayed"; + else if (field == FieldDateAdded) return "movie_view.dateAdded"; + else if (field == FieldUserRating) return "movie_view.userrating"; + + if (!result.empty()) + return result; + } + else if (mediaType == MediaTypeTvShow) + { + std::string result; + if (field == FieldId) return "tvshow_view.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(tvshow_view.c{:02}) > 0 THEN " + "tvshow_view.c{:02} ELSE tvshow_view.c{:02} END", + VIDEODB_ID_TV_SORTTITLE, VIDEODB_ID_TV_SORTTITLE, + VIDEODB_ID_TV_TITLE); + else + result = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_TITLE); + } + else if (field == FieldPlot) + result = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_PLOT); + else if (field == FieldTvShowStatus) + result = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_STATUS); + else if (field == FieldVotes) return "tvshow_view.votes"; + else if (field == FieldRating) return "tvshow_view.rating"; + else if (field == FieldYear) + result = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_PREMIERED); + else if (field == FieldGenre) + result = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_GENRE); + else if (field == FieldMPAA) + result = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_MPAA); + else if (field == FieldStudio) + result = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_STUDIOS); + else if (field == FieldSortTitle) + result = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_SORTTITLE); + else if (field == FieldOriginalTitle) + result = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_ORIGINALTITLE); + else if (field == FieldPath) return "tvshow_view.strPath"; + else if (field == FieldDateAdded) return "tvshow_view.dateAdded"; + else if (field == FieldLastPlayed) return "tvshow_view.lastPlayed"; + else if (field == FieldSeason) return "tvshow_view.totalSeasons"; + else if (field == FieldNumberOfEpisodes) return "tvshow_view.totalCount"; + else if (field == FieldNumberOfWatchedEpisodes) return "tvshow_view.watchedcount"; + else if (field == FieldUserRating) return "tvshow_view.userrating"; + + if (!result.empty()) + return result; + } + else if (mediaType == MediaTypeEpisode) + { + std::string result; + if (field == FieldId) return "episode_view.idEpisode"; + else if (field == FieldTitle) + result = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_TITLE); + else if (field == FieldPlot) + result = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_PLOT); + else if (field == FieldVotes) return "episode_view.votes"; + else if (field == FieldRating) return "episode_view.rating"; + else if (field == FieldWriter) + result = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_CREDITS); + else if (field == FieldAirDate) + result = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_AIRED); + else if (field == FieldTime) + result = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_RUNTIME); + else if (field == FieldDirector) + result = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_DIRECTOR); + else if (field == FieldSeason) + result = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_SEASON); + else if (field == FieldEpisodeNumber) + result = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_EPISODE); + else if (field == FieldUniqueId) + result = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_IDENT_ID); + else if (field == FieldEpisodeNumberSpecialSort) + result = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_SORTEPISODE); + else if (field == FieldSeasonSpecialSort) + result = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_SORTSEASON); + else if (field == FieldFilename) return "episode_view.strFilename"; + else if (field == FieldPath) return "episode_view.strPath"; + else if (field == FieldPlaycount) return "episode_view.playCount"; + else if (field == FieldLastPlayed) return "episode_view.lastPlayed"; + else if (field == FieldDateAdded) return "episode_view.dateAdded"; + else if (field == FieldTvShowTitle) return "episode_view.strTitle"; + else if (field == FieldYear) return "episode_view.premiered"; + else if (field == FieldMPAA) return "episode_view.mpaa"; + else if (field == FieldStudio) return "episode_view.strStudio"; + else if (field == FieldUserRating) return "episode_view.userrating"; + + 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 {}", *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::unique_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 {}", + resultSet.record_header[fieldIndex].name); + + if (value.first == FieldYear && + (mediaType == MediaTypeTvShow || mediaType == MediaTypeEpisode || + mediaType == MediaTypeMovie)) + { + 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 */) +{ + return " LIMIT " + BuildLimitClauseOnly(end, start); +} + +std::string DatabaseUtils::BuildLimitClauseOnly(int end, int start /* = 0 */) +{ + std::ostringstream sql; + if (start > 0) + { + if (end > 0) + { + end = end - start; + if (end < 0) + end = 0; + } + + sql << start << "," << end; + } + else + sql << end; + + return sql.str(); +} + +size_t DatabaseUtils::GetLimitCount(int end, int start) +{ + if (start > 0) + { + if (end - start < 0) + return 0; + else + return static_cast<size_t>(end - start); + } + else if (end > 0) + return static_cast<size_t>(end); + return 0; +} + +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_strReleaseDate; + 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_fRating; + else if (field == FieldVotes) return CMusicDatabase::album_iVotes; + else if (field == FieldUserRating) return CMusicDatabase::album_iUserrating; + else if (field == FieldPlaycount) return CMusicDatabase::album_iTimesPlayed; + else if (field == FieldLastPlayed) return CMusicDatabase::album_dtLastPlayed; + else if (field == FieldDateAdded) return CMusicDatabase::album_dateAdded; + else if (field == FieldDateNew) return CMusicDatabase::album_dateNew; + else if (field == FieldDateModified) return CMusicDatabase::album_dateModified; + else if (field == FieldTotalDiscs) + return CMusicDatabase::album_iTotalDiscs; + else if (field == FieldOrigYear || field == FieldOrigDate) + return CMusicDatabase::album_strOrigReleaseDate; + else if (field == FieldAlbumStatus) + return CMusicDatabase::album_strReleaseStatus; + else if (field == FieldAlbumDuration) + return CMusicDatabase::album_iAlbumDuration; + } + 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_strReleaseDate; + 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 == FieldUserRating) return CMusicDatabase::song_userrating; + else if (field == FieldVotes) return CMusicDatabase::song_votes; + else if (field == FieldComment) return CMusicDatabase::song_comment; + else if (field == FieldMoods) return CMusicDatabase::song_mood; + 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 (field == FieldDateAdded) return CMusicDatabase::song_dateAdded; + else if (field == FieldDateNew) return CMusicDatabase::song_dateNew; + else if (field == FieldDateModified) return CMusicDatabase::song_dateModified; + else if (field == FieldBPM) + return CMusicDatabase::song_iBPM; + else if (field == FieldMusicBitRate) + return CMusicDatabase::song_iBitRate; + else if (field == FieldSampleRate) + return CMusicDatabase::song_iSampleRate; + else if (field == FieldNoOfChannels) + return CMusicDatabase::song_iChannels; + } + else if (mediaType == MediaTypeArtist) + { + if (field == FieldId) return CMusicDatabase::artist_idArtist; + else if (field == FieldArtist) return CMusicDatabase::artist_strArtist; + else if (field == FieldArtistSort) return CMusicDatabase::artist_strSortName; + else if (field == FieldArtistType) return CMusicDatabase::artist_strType; + else if (field == FieldGender) return CMusicDatabase::artist_strGender; + else if (field == FieldDisambiguation) return CMusicDatabase::artist_strDisambiguation; + 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 (field == FieldDateAdded) return CMusicDatabase::artist_dateAdded; + else if (field == FieldDateNew) return CMusicDatabase::artist_dateNew; + else if (field == FieldDateModified) return CMusicDatabase::artist_dateModified; + } + 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) return VIDEODB_DETAILS_MUSICVIDEO_PREMIERED; + 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; + else if (field == FieldUserRating) return VIDEODB_DETAILS_MUSICVIDEO_USER_RATING; + + 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 == FieldOriginalTitle) index = VIDEODB_ID_ORIGINALTITLE; + 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) return VIDEODB_DETAILS_MOVIE_VOTES; + else if (field == FieldRating) return VIDEODB_DETAILS_MOVIE_RATING; + else if (field == FieldWriter) index = VIDEODB_ID_CREDITS; + else if (field == FieldYear) return VIDEODB_DETAILS_MOVIE_PREMIERED; + 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; + else if (field == FieldUserRating) return VIDEODB_DETAILS_MOVIE_USER_RATING; + + 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 == FieldOriginalTitle) index = VIDEODB_ID_TV_ORIGINALTITLE; + else if (field == FieldPlot) index = VIDEODB_ID_TV_PLOT; + else if (field == FieldTvShowStatus) index = VIDEODB_ID_TV_STATUS; + else if (field == FieldVotes) return VIDEODB_DETAILS_TVSHOW_VOTES; + else if (field == FieldRating) return VIDEODB_DETAILS_TVSHOW_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; + else if (field == FieldUserRating) return VIDEODB_DETAILS_TVSHOW_USER_RATING; + + 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) return VIDEODB_DETAILS_EPISODE_VOTES; + else if (field == FieldRating) return VIDEODB_DETAILS_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_IDENT_ID; + 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; + else if (field == FieldUserRating) return VIDEODB_DETAILS_EPISODE_USER_RATING; + + 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/xbmc/utils/DatabaseUtils.h b/xbmc/utils/DatabaseUtils.h new file mode 100644 index 0000000..c9bf6a6 --- /dev/null +++ b/xbmc/utils/DatabaseUtils.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "media/MediaType.h" + +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +class CVariant; +enum class VideoDbContentType; + +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, + FieldAudioCount, + FieldSubtitleCount, + + FieldInstallDate, + FieldLastUpdated, + FieldLastUsed, + + // fields retrievable from the database + FieldId, + FieldGenre, + FieldAlbum, + FieldDiscTitle, + FieldIsBoxset, + FieldTotalDiscs, + FieldOrigYear, + FieldOrigDate, + FieldArtist, + FieldArtistSort, + FieldAlbumArtist, + FieldTitle, + FieldSortTitle, + FieldOriginalTitle, + FieldYear, + FieldTime, + FieldTrackNumber, + FieldFilename, + FieldPath, + FieldPlaycount, + FieldLastPlayed, + FieldInProgress, + FieldRating, + FieldComment, + FieldRole, + FieldDateAdded, + FieldDateModified, + FieldDateNew, + 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, + FieldCompilation, + FieldSource, + FieldTrailer, + FieldVideoResolution, + FieldVideoAspectRatio, + FieldVideoCodec, + FieldAudioChannels, + FieldAudioCodec, + FieldAudioLanguage, + FieldSubtitleLanguage, + FieldProductionCode, + FieldTag, + FieldChannelName, + FieldChannelNumber, + FieldInstruments, + FieldBiography, + FieldArtistType, + FieldGender, + FieldDisambiguation, + FieldBorn, + FieldBandFormed, + FieldDisbanded, + FieldDied, + FieldStereoMode, + FieldUserRating, + FieldRelevance, // Used for actors' appearances + FieldClientChannelOrder, + FieldBPM, + FieldMusicBitRate, + FieldSampleRate, + FieldNoOfChannels, + FieldAlbumStatus, + FieldAlbumDuration, + FieldHdrType, + FieldProvider, + FieldUserPreference, + 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(VideoDbContentType 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::unique_ptr<dbiplus::Dataset> &dataset, DatabaseResults &results); + + static std::string BuildLimitClause(int end, int start = 0); + static std::string BuildLimitClauseOnly(int end, int start = 0); + static size_t GetLimitCount(int end, int start); + +private: + static int GetField(Field field, const MediaType &mediaType, bool asIndex); +}; diff --git a/xbmc/utils/Digest.cpp b/xbmc/utils/Digest.cpp new file mode 100644 index 0000000..445a755 --- /dev/null +++ b/xbmc/utils/Digest.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Digest.h" + +#include "StringUtils.h" + +#include <array> +#include <stdexcept> + +#include <openssl/evp.h> + +namespace KODI +{ +namespace UTILITY +{ + +namespace +{ + +EVP_MD const * TypeToEVPMD(CDigest::Type type) +{ + switch (type) + { + case CDigest::Type::MD5: + return EVP_md5(); + case CDigest::Type::SHA1: + return EVP_sha1(); + case CDigest::Type::SHA256: + return EVP_sha256(); + case CDigest::Type::SHA512: + return EVP_sha512(); + default: + throw std::invalid_argument("Unknown digest type"); + } +} + +} + +std::ostream& operator<<(std::ostream& os, TypedDigest const& digest) +{ + return os << "{" << CDigest::TypeToString(digest.type) << "}" << digest.value; +} + +std::string CDigest::TypeToString(Type type) +{ + switch (type) + { + case Type::MD5: + return "md5"; + case Type::SHA1: + return "sha1"; + case Type::SHA256: + return "sha256"; + case Type::SHA512: + return "sha512"; + case Type::INVALID: + return "invalid"; + default: + throw std::invalid_argument("Unknown digest type"); + } +} + +CDigest::Type CDigest::TypeFromString(std::string const& type) +{ + std::string typeLower{type}; + StringUtils::ToLower(typeLower); + if (type == "md5") + { + return Type::MD5; + } + else if (type == "sha1") + { + return Type::SHA1; + } + else if (type == "sha256") + { + return Type::SHA256; + } + else if (type == "sha512") + { + return Type::SHA512; + } + else + { + throw std::invalid_argument(std::string("Unknown digest type \"") + type + "\""); + } +} + +void CDigest::MdCtxDeleter::operator()(EVP_MD_CTX* context) +{ + EVP_MD_CTX_destroy(context); +} + +CDigest::CDigest(Type type) +: m_context{EVP_MD_CTX_create()}, m_md(TypeToEVPMD(type)) +{ + if (1 != EVP_DigestInit_ex(m_context.get(), m_md, nullptr)) + { + throw std::runtime_error("EVP_DigestInit_ex failed"); + } +} + +void CDigest::Update(std::string const& data) +{ + Update(data.c_str(), data.size()); +} + +void CDigest::Update(void const* data, std::size_t size) +{ + if (m_finalized) + { + throw std::logic_error("Finalized digest cannot be updated any more"); + } + + if (1 != EVP_DigestUpdate(m_context.get(), data, size)) + { + throw std::runtime_error("EVP_DigestUpdate failed"); + } +} + +std::string CDigest::FinalizeRaw() +{ + if (m_finalized) + { + throw std::logic_error("Digest can only be finalized once"); + } + + m_finalized = true; + + std::array<unsigned char, 64> digest; + std::size_t size = EVP_MD_size(m_md); + if (size > digest.size()) + { + throw std::runtime_error("Digest unexpectedly long"); + } + if (1 != EVP_DigestFinal_ex(m_context.get(), digest.data(), nullptr)) + { + throw std::runtime_error("EVP_DigestFinal_ex failed"); + } + return {reinterpret_cast<char*> (digest.data()), size}; +} + +std::string CDigest::Finalize() +{ + return StringUtils::ToHexadecimal(FinalizeRaw()); +} + +std::string CDigest::Calculate(Type type, std::string const& data) +{ + CDigest digest{type}; + digest.Update(data); + return digest.Finalize(); +} + +std::string CDigest::Calculate(Type type, void const* data, std::size_t size) +{ + CDigest digest{type}; + digest.Update(data, size); + return digest.Finalize(); +} + +} +} diff --git a/xbmc/utils/Digest.h b/xbmc/utils/Digest.h new file mode 100644 index 0000000..6452857 --- /dev/null +++ b/xbmc/utils/Digest.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "StringUtils.h" + +#include <iostream> +#include <memory> +#include <stdexcept> +#include <string> + +#include <openssl/evp.h> + +namespace KODI +{ +namespace UTILITY +{ + +/** + * Utility class for calculating message digests/hashes, currently using OpenSSL + */ +class CDigest +{ +public: + enum class Type + { + MD5, + SHA1, + SHA256, + SHA512, + INVALID + }; + + /** + * Convert type enumeration value to lower-case string representation + */ + static std::string TypeToString(Type type); + /** + * Convert digest type string representation to enumeration value + */ + static Type TypeFromString(std::string const& type); + + /** + * Create a digest calculation object + */ + CDigest(Type type); + /** + * Update digest with data + * + * Cannot be called after \ref Finalize has been called + */ + void Update(std::string const& data); + /** + * Update digest with data + * + * Cannot be called after \ref Finalize has been called + */ + void Update(void const* data, std::size_t size); + /** + * Finalize and return the digest + * + * The digest object cannot be used any more after this function + * has been called. + * + * \return digest value as string in lower-case hexadecimal notation + */ + std::string Finalize(); + /** + * Finalize and return the digest + * + * The digest object cannot be used any more after this + * function has been called. + * + * \return digest value as binary std::string + */ + std::string FinalizeRaw(); + + /** + * Calculate message digest + */ + static std::string Calculate(Type type, std::string const& data); + /** + * Calculate message digest + */ + static std::string Calculate(Type type, void const* data, std::size_t size); + +private: + struct MdCtxDeleter + { + void operator()(EVP_MD_CTX* context); + }; + + bool m_finalized{false}; + std::unique_ptr<EVP_MD_CTX, MdCtxDeleter> m_context; + EVP_MD const* m_md; +}; + +struct TypedDigest +{ + CDigest::Type type{CDigest::Type::INVALID}; + std::string value; + + TypedDigest() = default; + + TypedDigest(CDigest::Type type, std::string const& value) + : type(type), value(value) + {} + + bool Empty() const + { + return (type == CDigest::Type::INVALID || value.empty()); + } +}; + +inline bool operator==(TypedDigest const& left, TypedDigest const& right) +{ + if (left.type != right.type) + { + throw std::logic_error("Cannot compare digests of different type"); + } + return StringUtils::EqualsNoCase(left.value, right.value); +} + +inline bool operator!=(TypedDigest const& left, TypedDigest const& right) +{ + return !(left == right); +} + +std::ostream& operator<<(std::ostream& os, TypedDigest const& digest); + +} +} diff --git a/xbmc/utils/DiscsUtils.cpp b/xbmc/utils/DiscsUtils.cpp new file mode 100644 index 0000000..87d06a3 --- /dev/null +++ b/xbmc/utils/DiscsUtils.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DiscsUtils.h" + +#include "FileItem.h" +//! @todo it's wrong to include videoplayer scoped files, refactor +// dvd inputstream so they can be used by other components. Or just use libdvdnav directly. +#include "cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.h" +#ifdef HAVE_LIBBLURAY +//! @todo it's wrong to include vfs scoped files in a utils class, refactor +// to use libbluray directly. +#include "filesystem/BlurayDirectory.h" +#endif + +bool UTILS::DISCS::GetDiscInfo(UTILS::DISCS::DiscInfo& info, const std::string& mediaPath) +{ + // try to probe as a DVD + info = ProbeDVDDiscInfo(mediaPath); + if (!info.empty()) + return true; + + // try to probe as Blu-ray + info = ProbeBlurayDiscInfo(mediaPath); + if (!info.empty()) + return true; + + return false; +} + +UTILS::DISCS::DiscInfo UTILS::DISCS::ProbeDVDDiscInfo(const std::string& mediaPath) +{ + DiscInfo info; + CFileItem item{mediaPath, false}; + CDVDInputStreamNavigator dvdNavigator{nullptr, item}; + if (dvdNavigator.Open()) + { + info.type = DiscType::DVD; + info.name = dvdNavigator.GetDVDTitleString(); + // fallback to DVD volume id + if (info.name.empty()) + { + info.name = dvdNavigator.GetDVDVolIdString(); + } + info.serial = dvdNavigator.GetDVDSerialString(); + } + return info; +} + +UTILS::DISCS::DiscInfo UTILS::DISCS::ProbeBlurayDiscInfo(const std::string& mediaPath) +{ + DiscInfo info; +#ifdef HAVE_LIBBLURAY + XFILE::CBlurayDirectory bdDir; + if (!bdDir.InitializeBluray(mediaPath)) + return info; + + info.type = DiscType::BLURAY; + info.name = bdDir.GetBlurayTitle(); + info.serial = bdDir.GetBlurayID(); +#endif + return info; +} diff --git a/xbmc/utils/DiscsUtils.h b/xbmc/utils/DiscsUtils.h new file mode 100644 index 0000000..08752ea --- /dev/null +++ b/xbmc/utils/DiscsUtils.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +namespace UTILS +{ +namespace DISCS +{ + +/*! \brief Abstracts a disc type +*/ +enum class DiscType +{ + UNKNOWN, ///< the default value, the application doesn't know what the device is + DVD, ///< dvd disc + BLURAY ///< blu-ray disc +}; + +/*! \brief Abstracts the info the app knows about a disc (type, name, serial) +*/ +struct DiscInfo +{ + /*! \brief The disc type, \sa DiscType */ + DiscType type{DiscType::UNKNOWN}; + /*! \brief The disc serial number */ + std::string serial; + /*! \brief The disc label (equivalent to the mount point label) */ + std::string name; + + /*! \brief Check if the info is empty (e.g. after probing) + \return true if the info is empty, false otherwise + */ + bool empty() { return (type == DiscType::UNKNOWN && name.empty() && serial.empty()); } + + /*! \brief Clears all the DiscInfo members + */ + void clear() + { + type = DiscType::UNKNOWN; + name.clear(); + serial.clear(); + } +}; + +/*! \brief Try to obtain the disc info (type, name, serial) of a given media path + \param[in, out] info The disc info struct + \param mediaPath The disc mediapath (e.g. /dev/cdrom, D\://, etc) + \return true if getting the disc info was successfull +*/ +bool GetDiscInfo(DiscInfo& info, const std::string& mediaPath); + +/*! \brief Try to probe the provided media path as a DVD + \param mediaPath The disc mediapath (e.g. /dev/cdrom, D\://, etc) + \return the DiscInfo for the given media path (might be an empty struct) +*/ +DiscInfo ProbeDVDDiscInfo(const std::string& mediaPath); + +/*! \brief Try to probe the provided media path as a Bluray + \param mediaPath The disc mediapath (e.g. /dev/cdrom, D\://, etc) + \return the DiscInfo for the given media path (might be an empty struct) +*/ +DiscInfo ProbeBlurayDiscInfo(const std::string& mediaPath); + +} // namespace DISCS +} // namespace UTILS diff --git a/xbmc/utils/DumbBufferObject.cpp b/xbmc/utils/DumbBufferObject.cpp new file mode 100644 index 0000000..51e518a --- /dev/null +++ b/xbmc/utils/DumbBufferObject.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DumbBufferObject.h" + +#include "ServiceBroker.h" +#include "utils/BufferObjectFactory.h" +#include "utils/log.h" +#include "windowing/gbm/WinSystemGbm.h" +#include "windowing/gbm/WinSystemGbmEGLContext.h" + +#include <drm_fourcc.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <unistd.h> + +#include "PlatformDefs.h" + +using namespace KODI::WINDOWING::GBM; + +std::unique_ptr<CBufferObject> CDumbBufferObject::Create() +{ + return std::make_unique<CDumbBufferObject>(); +} + +void CDumbBufferObject::Register() +{ + CBufferObjectFactory::RegisterBufferObject(CDumbBufferObject::Create); +} + +CDumbBufferObject::CDumbBufferObject() +{ + auto winSystem = static_cast<CWinSystemGbmEGLContext*>(CServiceBroker::GetWinSystem()); + + m_device = winSystem->GetDrm()->GetFileDescriptor(); +} + +CDumbBufferObject::~CDumbBufferObject() +{ + ReleaseMemory(); + DestroyBufferObject(); +} + +bool CDumbBufferObject::CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) +{ + if (m_fd >= 0) + return true; + + uint32_t bpp; + + switch (format) + { + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_RGB565: + bpp = 16; + break; + case DRM_FORMAT_ARGB8888: + bpp = 32; + break; + default: + throw std::runtime_error("CDumbBufferObject: pixel format not implemented"); + } + + struct drm_mode_create_dumb create_dumb{}; + create_dumb.height = height; + create_dumb.width = width; + create_dumb.bpp = bpp; + + int ret = drmIoctl(m_device, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); + if (ret < 0) + { + CLog::Log(LOGERROR, "CDumbBufferObject::{} - ioctl DRM_IOCTL_MODE_CREATE_DUMB failed, errno={}", + __FUNCTION__, strerror(errno)); + return false; + } + + m_size = create_dumb.size; + m_stride = create_dumb.pitch; + + ret = drmPrimeHandleToFD(m_device, create_dumb.handle, 0, &m_fd); + if (ret < 0) + { + CLog::Log(LOGERROR, "CDumbBufferObject::{} - failed to get fd from prime handle, errno={}", + __FUNCTION__, strerror(errno)); + return false; + } + + return true; +} + +void CDumbBufferObject::DestroyBufferObject() +{ + if (m_fd < 0) + return; + + int ret = close(m_fd); + if (ret < 0) + CLog::Log(LOGERROR, "CDumbBufferObject::{} - close failed, errno={}", __FUNCTION__, + strerror(errno)); + + m_fd = -1; + m_stride = 0; + m_size = 0; +} + +uint8_t* CDumbBufferObject::GetMemory() +{ + if (m_fd < 0) + return nullptr; + + if (m_map) + { + CLog::Log(LOGDEBUG, "CDumbBufferObject::{} - already mapped fd={} map={}", __FUNCTION__, m_fd, + fmt::ptr(m_map)); + return m_map; + } + + uint32_t handle; + int ret = drmPrimeFDToHandle(m_device, m_fd, &handle); + if (ret < 0) + { + CLog::Log(LOGERROR, "CDumbBufferObject::{} - failed to get handle from prime fd, errno={}", + __FUNCTION__, strerror(errno)); + return nullptr; + } + + struct drm_mode_map_dumb map_dumb{}; + map_dumb.handle = handle; + + ret = drmIoctl(m_device, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); + if (ret < 0) + { + CLog::Log(LOGERROR, "CDumbBufferObject::{} - ioctl DRM_IOCTL_MODE_MAP_DUMB failed, errno={}", + __FUNCTION__, strerror(errno)); + return nullptr; + } + + m_offset = map_dumb.offset; + + m_map = static_cast<uint8_t*>(mmap(nullptr, m_size, PROT_WRITE, MAP_SHARED, m_device, m_offset)); + if (m_map == MAP_FAILED) + { + CLog::Log(LOGERROR, "CDumbBufferObject::{} - mmap failed, errno={}", __FUNCTION__, + strerror(errno)); + return nullptr; + } + + return m_map; +} + +void CDumbBufferObject::ReleaseMemory() +{ + if (!m_map) + return; + + int ret = munmap(m_map, m_size); + if (ret < 0) + CLog::Log(LOGERROR, "CDumbBufferObject::{} - munmap failed, errno={}", __FUNCTION__, + strerror(errno)); + + m_map = nullptr; + m_offset = 0; +} diff --git a/xbmc/utils/DumbBufferObject.h b/xbmc/utils/DumbBufferObject.h new file mode 100644 index 0000000..2dec611 --- /dev/null +++ b/xbmc/utils/DumbBufferObject.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/BufferObject.h" + +#include <memory> +#include <stdint.h> + +class CDumbBufferObject : public CBufferObject +{ +public: + CDumbBufferObject(); + virtual ~CDumbBufferObject() override; + + // Registration + static std::unique_ptr<CBufferObject> Create(); + static void Register(); + + // IBufferObject overrides via CBufferObject + bool CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) override; + void DestroyBufferObject() override; + uint8_t* GetMemory() override; + void ReleaseMemory() override; + std::string GetName() const override { return "CDumbBufferObject"; } + +private: + int m_device{-1}; + uint64_t m_size{0}; + uint64_t m_offset{0}; + uint8_t* m_map{nullptr}; +}; diff --git a/xbmc/utils/EGLFence.cpp b/xbmc/utils/EGLFence.cpp new file mode 100644 index 0000000..369c40a --- /dev/null +++ b/xbmc/utils/EGLFence.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "EGLFence.h" + +#include "EGLUtils.h" +#include "utils/log.h" + +using namespace KODI::UTILS::EGL; + +CEGLFence::CEGLFence(EGLDisplay display) : + m_display(display) +{ + m_eglCreateSyncKHR = CEGLUtils::GetRequiredProcAddress<PFNEGLCREATESYNCKHRPROC>("eglCreateSyncKHR"); + m_eglDestroySyncKHR = CEGLUtils::GetRequiredProcAddress<PFNEGLDESTROYSYNCKHRPROC>("eglDestroySyncKHR"); + m_eglGetSyncAttribKHR = CEGLUtils::GetRequiredProcAddress<PFNEGLGETSYNCATTRIBKHRPROC>("eglGetSyncAttribKHR"); +} + +void CEGLFence::CreateFence() +{ + m_fence = m_eglCreateSyncKHR(m_display, EGL_SYNC_FENCE_KHR, nullptr); + if (m_fence == EGL_NO_SYNC_KHR) + { + CEGLUtils::Log(LOGERROR, "failed to create egl sync fence"); + throw std::runtime_error("failed to create egl sync fence"); + } +} + +void CEGLFence::DestroyFence() +{ + if (m_fence == EGL_NO_SYNC_KHR) + { + return; + } + + if (m_eglDestroySyncKHR(m_display, m_fence) != EGL_TRUE) + { + CEGLUtils::Log(LOGERROR, "failed to destroy egl sync fence"); + } + + m_fence = EGL_NO_SYNC_KHR; +} + +bool CEGLFence::IsSignaled() +{ + // fence has been destroyed so return true immediately so buffer can be used + if (m_fence == EGL_NO_SYNC_KHR) + { + return true; + } + + EGLint status = EGL_UNSIGNALED_KHR; + if (m_eglGetSyncAttribKHR(m_display, m_fence, EGL_SYNC_STATUS_KHR, &status) != EGL_TRUE) + { + CEGLUtils::Log(LOGERROR, "failed to query egl sync fence"); + return false; + } + + if (status == EGL_SIGNALED_KHR) + { + return true; + } + + return false; +} diff --git a/xbmc/utils/EGLFence.h b/xbmc/utils/EGLFence.h new file mode 100644 index 0000000..bd96444 --- /dev/null +++ b/xbmc/utils/EGLFence.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "system_egl.h" + +#include <EGL/eglext.h> + +namespace KODI +{ +namespace UTILS +{ +namespace EGL +{ + +class CEGLFence +{ +public: + explicit CEGLFence(EGLDisplay display); + CEGLFence(CEGLFence const& other) = delete; + CEGLFence& operator=(CEGLFence const& other) = delete; + + void CreateFence(); + void DestroyFence(); + bool IsSignaled(); + +private: + EGLDisplay m_display{nullptr}; + EGLSyncKHR m_fence{nullptr}; + + PFNEGLCREATESYNCKHRPROC m_eglCreateSyncKHR{nullptr}; + PFNEGLDESTROYSYNCKHRPROC m_eglDestroySyncKHR{nullptr}; + PFNEGLGETSYNCATTRIBKHRPROC m_eglGetSyncAttribKHR{nullptr}; +}; + +} +} +} diff --git a/xbmc/utils/EGLImage.cpp b/xbmc/utils/EGLImage.cpp new file mode 100644 index 0000000..24f9708 --- /dev/null +++ b/xbmc/utils/EGLImage.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "EGLImage.h" + +#include "ServiceBroker.h" +#include "utils/DRMHelpers.h" +#include "utils/EGLUtils.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <map> + +namespace +{ + const EGLint eglDmabufPlaneFdAttr[CEGLImage::MAX_NUM_PLANES] = + { + EGL_DMA_BUF_PLANE0_FD_EXT, + EGL_DMA_BUF_PLANE1_FD_EXT, + EGL_DMA_BUF_PLANE2_FD_EXT, + }; + + const EGLint eglDmabufPlaneOffsetAttr[CEGLImage::MAX_NUM_PLANES] = + { + EGL_DMA_BUF_PLANE0_OFFSET_EXT, + EGL_DMA_BUF_PLANE1_OFFSET_EXT, + EGL_DMA_BUF_PLANE2_OFFSET_EXT, + }; + + const EGLint eglDmabufPlanePitchAttr[CEGLImage::MAX_NUM_PLANES] = + { + EGL_DMA_BUF_PLANE0_PITCH_EXT, + EGL_DMA_BUF_PLANE1_PITCH_EXT, + EGL_DMA_BUF_PLANE2_PITCH_EXT, + }; + +#if defined(EGL_EXT_image_dma_buf_import_modifiers) + const EGLint eglDmabufPlaneModifierLoAttr[CEGLImage::MAX_NUM_PLANES] = + { + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, + }; + + const EGLint eglDmabufPlaneModifierHiAttr[CEGLImage::MAX_NUM_PLANES] = + { + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, + }; +#endif + +#define X(VAL) std::make_pair(VAL, #VAL) +std::map<EGLint, const char*> eglAttributes = +{ + X(EGL_WIDTH), + X(EGL_HEIGHT), + + // please keep attributes in accordance to: + // https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import.txt + X(EGL_LINUX_DRM_FOURCC_EXT), + X(EGL_DMA_BUF_PLANE0_FD_EXT), + X(EGL_DMA_BUF_PLANE0_OFFSET_EXT), + X(EGL_DMA_BUF_PLANE0_PITCH_EXT), + X(EGL_DMA_BUF_PLANE1_FD_EXT), + X(EGL_DMA_BUF_PLANE1_OFFSET_EXT), + X(EGL_DMA_BUF_PLANE1_PITCH_EXT), + X(EGL_DMA_BUF_PLANE2_FD_EXT), + X(EGL_DMA_BUF_PLANE2_OFFSET_EXT), + X(EGL_DMA_BUF_PLANE2_PITCH_EXT), + X(EGL_YUV_COLOR_SPACE_HINT_EXT), + X(EGL_SAMPLE_RANGE_HINT_EXT), + X(EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT), + X(EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT), + X(EGL_ITU_REC601_EXT), + X(EGL_ITU_REC709_EXT), + X(EGL_ITU_REC2020_EXT), + X(EGL_YUV_FULL_RANGE_EXT), + X(EGL_YUV_NARROW_RANGE_EXT), + X(EGL_YUV_CHROMA_SITING_0_EXT), + X(EGL_YUV_CHROMA_SITING_0_5_EXT), + +#if defined(EGL_EXT_image_dma_buf_import_modifiers) + // please keep attributes in accordance to: + // https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt + X(EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT), + X(EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT), + X(EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT), + X(EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT), + X(EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT), + X(EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT), + X(EGL_DMA_BUF_PLANE3_FD_EXT), + X(EGL_DMA_BUF_PLANE3_OFFSET_EXT), + X(EGL_DMA_BUF_PLANE3_PITCH_EXT), + X(EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT), + X(EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT), +#endif +}; + +} // namespace + +CEGLImage::CEGLImage(EGLDisplay display) : + m_display(display) +{ + m_eglCreateImageKHR = CEGLUtils::GetRequiredProcAddress<PFNEGLCREATEIMAGEKHRPROC>("eglCreateImageKHR"); + m_eglDestroyImageKHR = CEGLUtils::GetRequiredProcAddress<PFNEGLDESTROYIMAGEKHRPROC>("eglDestroyImageKHR"); + m_glEGLImageTargetTexture2DOES = CEGLUtils::GetRequiredProcAddress<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>("glEGLImageTargetTexture2DOES"); +} + +bool CEGLImage::CreateImage(EglAttrs imageAttrs) +{ + CEGLAttributes<22> attribs; + attribs.Add({{EGL_WIDTH, imageAttrs.width}, + {EGL_HEIGHT, imageAttrs.height}, + {EGL_LINUX_DRM_FOURCC_EXT, static_cast<EGLint>(imageAttrs.format)}}); + + if (imageAttrs.colorSpace != 0 && imageAttrs.colorRange != 0) + { + attribs.Add({{EGL_YUV_COLOR_SPACE_HINT_EXT, imageAttrs.colorSpace}, + {EGL_SAMPLE_RANGE_HINT_EXT, imageAttrs.colorRange}, + {EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_EXT}, + {EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_EXT}}); + } + + for (int i = 0; i < MAX_NUM_PLANES; i++) + { + if (imageAttrs.planes[i].fd != 0) + { + attribs.Add({{eglDmabufPlaneFdAttr[i], imageAttrs.planes[i].fd}, + {eglDmabufPlaneOffsetAttr[i], imageAttrs.planes[i].offset}, + {eglDmabufPlanePitchAttr[i], imageAttrs.planes[i].pitch}}); + +#if defined(EGL_EXT_image_dma_buf_import_modifiers) + if (imageAttrs.planes[i].modifier != DRM_FORMAT_MOD_INVALID && imageAttrs.planes[i].modifier != DRM_FORMAT_MOD_LINEAR) + attribs.Add({{eglDmabufPlaneModifierLoAttr[i], static_cast<EGLint>(imageAttrs.planes[i].modifier & 0xFFFFFFFF)}, + {eglDmabufPlaneModifierHiAttr[i], static_cast<EGLint>(imageAttrs.planes[i].modifier >> 32)}}); +#endif + } + } + + m_image = m_eglCreateImageKHR(m_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.Get()); + + if (!m_image || CServiceBroker::GetLogging().CanLogComponent(LOGVIDEO)) + { + const EGLint* attrs = attribs.Get(); + + std::string eglString; + + for (int i = 0; i < (attribs.Size()); i += 2) + { + std::string keyStr; + std::string valueStr; + + auto eglAttrKey = eglAttributes.find(attrs[i]); + if (eglAttrKey != eglAttributes.end()) + { + keyStr = eglAttrKey->second; + } + else + { + keyStr = std::to_string(attrs[i]); + } + + auto eglAttrValue = eglAttributes.find(attrs[i + 1]); + if (eglAttrValue != eglAttributes.end()) + { + valueStr = eglAttrValue->second; + } + else + { + if (eglAttrKey != eglAttributes.end() && eglAttrKey->first == EGL_LINUX_DRM_FOURCC_EXT) + valueStr = DRMHELPERS::FourCCToString(attrs[i + 1]); + else + valueStr = std::to_string(attrs[i + 1]); + } + + eglString.append(StringUtils::Format("{}: {}\n", keyStr, valueStr)); + } + + CLog::Log(LOGDEBUG, "CEGLImage::{} - attributes:\n{}", __FUNCTION__, eglString); + } + + if (!m_image) + { + CLog::Log(LOGERROR, "CEGLImage::{} - failed to import buffer into EGL image: {:#4x}", + __FUNCTION__, eglGetError()); + return false; + } + + return true; +} + +void CEGLImage::UploadImage(GLenum textureTarget) +{ + m_glEGLImageTargetTexture2DOES(textureTarget, m_image); +} + +void CEGLImage::DestroyImage() +{ + m_eglDestroyImageKHR(m_display, m_image); +} + +#if defined(EGL_EXT_image_dma_buf_import_modifiers) +bool CEGLImage::SupportsFormat(uint32_t format) +{ + auto eglQueryDmaBufFormatsEXT = + CEGLUtils::GetRequiredProcAddress<PFNEGLQUERYDMABUFFORMATSEXTPROC>( + "eglQueryDmaBufFormatsEXT"); + + EGLint numFormats; + if (eglQueryDmaBufFormatsEXT(m_display, 0, nullptr, &numFormats) != EGL_TRUE) + { + CLog::Log(LOGERROR, + "CEGLImage::{} - failed to query the max number of EGL dma-buf formats: {:#4x}", + __FUNCTION__, eglGetError()); + return false; + } + + std::vector<EGLint> formats(numFormats); + if (eglQueryDmaBufFormatsEXT(m_display, numFormats, formats.data(), &numFormats) != EGL_TRUE) + { + CLog::Log(LOGERROR, "CEGLImage::{} - failed to query EGL dma-buf formats: {:#4x}", __FUNCTION__, + eglGetError()); + return false; + } + + auto foundFormat = std::find(formats.begin(), formats.end(), format); + if (foundFormat == formats.end() || CServiceBroker::GetLogging().CanLogComponent(LOGVIDEO)) + { + std::string formatStr; + for (const auto& supportedFormat : formats) + formatStr.append("\n" + DRMHELPERS::FourCCToString(supportedFormat)); + + CLog::Log(LOGDEBUG, "CEGLImage::{} - supported formats:{}", __FUNCTION__, formatStr); + } + + if (foundFormat != formats.end()) + { + CLog::Log(LOGDEBUG, LOGVIDEO, "CEGLImage::{} - supported format: {}", __FUNCTION__, + DRMHELPERS::FourCCToString(format)); + return true; + } + + CLog::Log(LOGERROR, "CEGLImage::{} - format not supported: {}", __FUNCTION__, + DRMHELPERS::FourCCToString(format)); + + return false; +} + +bool CEGLImage::SupportsFormatAndModifier(uint32_t format, uint64_t modifier) +{ + if (!SupportsFormat(format)) + return false; + + if (modifier == DRM_FORMAT_MOD_LINEAR) + return true; + + /* + * Some broadcom modifiers have parameters encoded which need to be + * masked out before comparing with reported modifiers. + */ + if (modifier >> 56 == DRM_FORMAT_MOD_VENDOR_BROADCOM) + modifier = fourcc_mod_broadcom_mod(modifier); + + auto eglQueryDmaBufModifiersEXT = + CEGLUtils::GetRequiredProcAddress<PFNEGLQUERYDMABUFMODIFIERSEXTPROC>( + "eglQueryDmaBufModifiersEXT"); + + EGLint numFormats; + if (eglQueryDmaBufModifiersEXT(m_display, format, 0, nullptr, nullptr, &numFormats) != EGL_TRUE) + { + CLog::Log(LOGERROR, + "CEGLImage::{} - failed to query the max number of EGL dma-buf format modifiers for " + "format: {} - {:#4x}", + __FUNCTION__, DRMHELPERS::FourCCToString(format), eglGetError()); + return false; + } + + std::vector<EGLuint64KHR> modifiers(numFormats); + + if (eglQueryDmaBufModifiersEXT(m_display, format, numFormats, modifiers.data(), nullptr, + &numFormats) != EGL_TRUE) + { + CLog::Log( + LOGERROR, + "CEGLImage::{} - failed to query EGL dma-buf format modifiers for format: {} - {:#4x}", + __FUNCTION__, DRMHELPERS::FourCCToString(format), eglGetError()); + return false; + } + + auto foundModifier = std::find(modifiers.begin(), modifiers.end(), modifier); + if (foundModifier == modifiers.end() || CServiceBroker::GetLogging().CanLogComponent(LOGVIDEO)) + { + std::string modifierStr; + for (const auto& supportedModifier : modifiers) + modifierStr.append("\n" + DRMHELPERS::ModifierToString(supportedModifier)); + + CLog::Log(LOGDEBUG, "CEGLImage::{} - supported modifiers:{}", __FUNCTION__, modifierStr); + } + + if (foundModifier != modifiers.end()) + { + CLog::Log(LOGDEBUG, LOGVIDEO, "CEGLImage::{} - supported modifier: {}", __FUNCTION__, + DRMHELPERS::ModifierToString(modifier)); + return true; + } + + CLog::Log(LOGERROR, "CEGLImage::{} - modifier ({}) not supported for format ({})", __FUNCTION__, + DRMHELPERS::ModifierToString(modifier), DRMHELPERS::FourCCToString(format)); + + return false; +} +#endif diff --git a/xbmc/utils/EGLImage.h b/xbmc/utils/EGLImage.h new file mode 100644 index 0000000..5e86155 --- /dev/null +++ b/xbmc/utils/EGLImage.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "system_egl.h" + +#include <array> + +#include <EGL/eglext.h> +#include <drm_fourcc.h> + +#include "system_gl.h" + +class CEGLImage +{ +public: + static const int MAX_NUM_PLANES{3}; + + struct EglPlane + { + int fd{0}; + int offset{0}; + int pitch{0}; + uint64_t modifier{DRM_FORMAT_MOD_INVALID}; + }; + + struct EglAttrs + { + int width{0}; + int height{0}; + uint32_t format{0}; + int colorSpace{0}; + int colorRange{0}; + std::array<EglPlane, MAX_NUM_PLANES> planes; + }; + + explicit CEGLImage(EGLDisplay display); + + CEGLImage(CEGLImage const& other) = delete; + CEGLImage& operator=(CEGLImage const& other) = delete; + + bool CreateImage(EglAttrs imageAttrs); + void UploadImage(GLenum textureTarget); + void DestroyImage(); + +#if defined(EGL_EXT_image_dma_buf_import_modifiers) + bool SupportsFormatAndModifier(uint32_t format, uint64_t modifier); + +private: + bool SupportsFormat(uint32_t format); +#else +private: +#endif + EGLDisplay m_display{nullptr}; + EGLImageKHR m_image{nullptr}; + + PFNEGLCREATEIMAGEKHRPROC m_eglCreateImageKHR{nullptr}; + PFNEGLDESTROYIMAGEKHRPROC m_eglDestroyImageKHR{nullptr}; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_glEGLImageTargetTexture2DOES{nullptr}; +}; diff --git a/xbmc/utils/EGLUtils.cpp b/xbmc/utils/EGLUtils.cpp new file mode 100644 index 0000000..7c5e709 --- /dev/null +++ b/xbmc/utils/EGLUtils.cpp @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "EGLUtils.h" + +#include "ServiceBroker.h" +#include "StringUtils.h" +#include "guilib/IDirtyRegionSolver.h" +#include "log.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" + +#include <map> + +#include <EGL/eglext.h> + +namespace +{ + +#define X(VAL) std::make_pair(VAL, #VAL) +std::map<EGLint, const char*> eglAttributes = +{ + // please keep attributes in accordance to: + // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglGetConfigAttrib.xhtml + X(EGL_ALPHA_SIZE), + X(EGL_ALPHA_MASK_SIZE), + X(EGL_BIND_TO_TEXTURE_RGB), + X(EGL_BIND_TO_TEXTURE_RGBA), + X(EGL_BLUE_SIZE), + X(EGL_BUFFER_SIZE), + X(EGL_COLOR_BUFFER_TYPE), + X(EGL_CONFIG_CAVEAT), + X(EGL_CONFIG_ID), + X(EGL_CONFORMANT), + X(EGL_DEPTH_SIZE), + X(EGL_GREEN_SIZE), + X(EGL_LEVEL), + X(EGL_LUMINANCE_SIZE), + X(EGL_MAX_PBUFFER_WIDTH), + X(EGL_MAX_PBUFFER_HEIGHT), + X(EGL_MAX_PBUFFER_PIXELS), + X(EGL_MAX_SWAP_INTERVAL), + X(EGL_MIN_SWAP_INTERVAL), + X(EGL_NATIVE_RENDERABLE), + X(EGL_NATIVE_VISUAL_ID), + X(EGL_NATIVE_VISUAL_TYPE), + X(EGL_RED_SIZE), + X(EGL_RENDERABLE_TYPE), + X(EGL_SAMPLE_BUFFERS), + X(EGL_SAMPLES), + X(EGL_STENCIL_SIZE), + X(EGL_SURFACE_TYPE), + X(EGL_TRANSPARENT_TYPE), + X(EGL_TRANSPARENT_RED_VALUE), + X(EGL_TRANSPARENT_GREEN_VALUE), + X(EGL_TRANSPARENT_BLUE_VALUE) +}; + +std::map<EGLenum, const char*> eglErrors = +{ + // please keep errors in accordance to: + // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglGetError.xhtml + X(EGL_SUCCESS), + X(EGL_NOT_INITIALIZED), + X(EGL_BAD_ACCESS), + X(EGL_BAD_ALLOC), + X(EGL_BAD_ATTRIBUTE), + X(EGL_BAD_CONFIG), + X(EGL_BAD_CONTEXT), + X(EGL_BAD_CURRENT_SURFACE), + X(EGL_BAD_DISPLAY), + X(EGL_BAD_MATCH), + X(EGL_BAD_NATIVE_PIXMAP), + X(EGL_BAD_NATIVE_WINDOW), + X(EGL_BAD_PARAMETER), + X(EGL_BAD_SURFACE), + X(EGL_CONTEXT_LOST), +}; + +std::map<EGLint, const char*> eglErrorType = +{ + X(EGL_DEBUG_MSG_CRITICAL_KHR), + X(EGL_DEBUG_MSG_ERROR_KHR), + X(EGL_DEBUG_MSG_WARN_KHR), + X(EGL_DEBUG_MSG_INFO_KHR), +}; +#undef X + +} // namespace + +void EglErrorCallback(EGLenum error, + const char* command, + EGLint messageType, + EGLLabelKHR threadLabel, + EGLLabelKHR objectLabel, + const char* message) +{ + std::string errorStr; + std::string typeStr; + + auto eglError = eglErrors.find(error); + if (eglError != eglErrors.end()) + { + errorStr = eglError->second; + } + + auto eglType = eglErrorType.find(messageType); + if (eglType != eglErrorType.end()) + { + typeStr = eglType->second; + } + + CLog::Log(LOGDEBUG, "EGL Debugging:\nError: {}\nCommand: {}\nType: {}\nMessage: {}", errorStr, command, typeStr, message); +} + +std::set<std::string> CEGLUtils::GetClientExtensions() +{ + const char* extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (!extensions) + { + return {}; + } + std::set<std::string> result; + StringUtils::SplitTo(std::inserter(result, result.begin()), extensions, " "); + return result; +} + +std::set<std::string> CEGLUtils::GetExtensions(EGLDisplay eglDisplay) +{ + const char* extensions = eglQueryString(eglDisplay, EGL_EXTENSIONS); + if (!extensions) + { + throw std::runtime_error("Could not query EGL for extensions"); + } + std::set<std::string> result; + StringUtils::SplitTo(std::inserter(result, result.begin()), extensions, " "); + return result; +} + +bool CEGLUtils::HasExtension(EGLDisplay eglDisplay, const std::string& name) +{ + auto exts = GetExtensions(eglDisplay); + return (exts.find(name) != exts.end()); +} + +bool CEGLUtils::HasClientExtension(const std::string& name) +{ + auto exts = GetClientExtensions(); + return (exts.find(name) != exts.end()); +} + +void CEGLUtils::Log(int logLevel, const std::string& what) +{ + EGLenum error = eglGetError(); + std::string errorStr = StringUtils::Format("{:#04X}", error); + + auto eglError = eglErrors.find(error); + if (eglError != eglErrors.end()) + { + errorStr = eglError->second; + } + + CLog::Log(logLevel, "{} ({})", what, errorStr); +} + +CEGLContextUtils::CEGLContextUtils(EGLenum platform, std::string const& platformExtension) +: m_platform{platform} +{ + if (CEGLUtils::HasClientExtension("EGL_KHR_debug")) + { + auto eglDebugMessageControl = CEGLUtils::GetRequiredProcAddress<PFNEGLDEBUGMESSAGECONTROLKHRPROC>("eglDebugMessageControlKHR"); + + EGLAttrib eglDebugAttribs[] = {EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, + EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, + EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, + EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, + EGL_NONE}; + + eglDebugMessageControl(EglErrorCallback, eglDebugAttribs); + } + + m_platformSupported = CEGLUtils::HasClientExtension("EGL_EXT_platform_base") && CEGLUtils::HasClientExtension(platformExtension); +} + +bool CEGLContextUtils::IsPlatformSupported() const +{ + return m_platformSupported; +} + +CEGLContextUtils::~CEGLContextUtils() +{ + Destroy(); +} + +bool CEGLContextUtils::CreateDisplay(EGLNativeDisplayType nativeDisplay) +{ + if (m_eglDisplay != EGL_NO_DISPLAY) + { + throw std::logic_error("Do not call CreateDisplay when display has already been created"); + } + + m_eglDisplay = eglGetDisplay(nativeDisplay); + if (m_eglDisplay == EGL_NO_DISPLAY) + { + CEGLUtils::Log(LOGERROR, "failed to get EGL display"); + return false; + } + + return true; +} + +bool CEGLContextUtils::CreatePlatformDisplay(void* nativeDisplay, EGLNativeDisplayType nativeDisplayLegacy) +{ + if (m_eglDisplay != EGL_NO_DISPLAY) + { + throw std::logic_error("Do not call CreateDisplay when display has already been created"); + } + +#if defined(EGL_EXT_platform_base) + if (IsPlatformSupported()) + { + // Theoretically it is possible to use eglGetDisplay() and eglCreateWindowSurface, + // but then the EGL library basically has to guess which platform we want + // if it supports multiple which is usually the case - + // it's better and safer to make it explicit + + auto getPlatformDisplayEXT = CEGLUtils::GetRequiredProcAddress<PFNEGLGETPLATFORMDISPLAYEXTPROC>("eglGetPlatformDisplayEXT"); + m_eglDisplay = getPlatformDisplayEXT(m_platform, nativeDisplay, nullptr); + + if (m_eglDisplay == EGL_NO_DISPLAY) + { + CEGLUtils::Log(LOGERROR, "failed to get platform display"); + return false; + } + } +#endif + + if (m_eglDisplay == EGL_NO_DISPLAY) + { + return CreateDisplay(nativeDisplayLegacy); + } + + return true; +} + +bool CEGLContextUtils::InitializeDisplay(EGLint renderingApi) +{ + if (!eglInitialize(m_eglDisplay, nullptr, nullptr)) + { + CEGLUtils::Log(LOGERROR, "failed to initialize EGL display"); + Destroy(); + return false; + } + + const char* value; + value = eglQueryString(m_eglDisplay, EGL_VERSION); + CLog::Log(LOGINFO, "EGL_VERSION = {}", value ? value : "NULL"); + + value = eglQueryString(m_eglDisplay, EGL_VENDOR); + CLog::Log(LOGINFO, "EGL_VENDOR = {}", value ? value : "NULL"); + + value = eglQueryString(m_eglDisplay, EGL_EXTENSIONS); + CLog::Log(LOGINFO, "EGL_EXTENSIONS = {}", value ? value : "NULL"); + + value = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + CLog::Log(LOGINFO, "EGL_CLIENT_EXTENSIONS = {}", value ? value : "NULL"); + + if (eglBindAPI(renderingApi) != EGL_TRUE) + { + CEGLUtils::Log(LOGERROR, "failed to bind EGL API"); + Destroy(); + return false; + } + + return true; +} + +bool CEGLContextUtils::ChooseConfig(EGLint renderableType, EGLint visualId, bool hdr) +{ + EGLint numMatched{0}; + + if (m_eglDisplay == EGL_NO_DISPLAY) + { + throw std::logic_error("Choosing an EGLConfig requires an EGL display"); + } + + EGLint surfaceType = EGL_WINDOW_BIT; + // for the non-trivial dirty region modes, we need the EGL buffer to be preserved across updates + int guiAlgorithmDirtyRegions = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiAlgorithmDirtyRegions; + if (guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_COST_REDUCTION || + guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_UNION) + surfaceType |= EGL_SWAP_BEHAVIOR_PRESERVED_BIT; + + CEGLAttributesVec attribs; + attribs.Add({{EGL_RED_SIZE, 8}, + {EGL_GREEN_SIZE, 8}, + {EGL_BLUE_SIZE, 8}, + {EGL_ALPHA_SIZE, 2}, + {EGL_DEPTH_SIZE, 16}, + {EGL_STENCIL_SIZE, 0}, + {EGL_SAMPLE_BUFFERS, 0}, + {EGL_SAMPLES, 0}, + {EGL_SURFACE_TYPE, surfaceType}, + {EGL_RENDERABLE_TYPE, renderableType}}); + + EGLConfig* currentConfig(hdr ? &m_eglHDRConfig : &m_eglConfig); + + if (hdr) +#if EGL_EXT_pixel_format_float + attribs.Add({{EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT}}); +#else + return false; +#endif + + const char* errorMsg = nullptr; + + if (eglChooseConfig(m_eglDisplay, attribs.Get(), nullptr, 0, &numMatched) != EGL_TRUE) + errorMsg = "failed to query number of EGL configs"; + + std::vector<EGLConfig> eglConfigs(numMatched); + if (eglChooseConfig(m_eglDisplay, attribs.Get(), eglConfigs.data(), numMatched, &numMatched) != EGL_TRUE) + errorMsg = "failed to find EGL configs with appropriate attributes"; + + if (errorMsg) + { + if (!hdr) + { + CEGLUtils::Log(LOGERROR, errorMsg); + Destroy(); + } + else + CEGLUtils::Log(LOGINFO, errorMsg); + return false; + } + + EGLint id{0}; + for (const auto &eglConfig: eglConfigs) + { + *currentConfig = eglConfig; + + if (visualId == 0) + break; + + if (eglGetConfigAttrib(m_eglDisplay, *currentConfig, EGL_NATIVE_VISUAL_ID, &id) != EGL_TRUE) + CEGLUtils::Log(LOGERROR, "failed to query EGL attribute EGL_NATIVE_VISUAL_ID"); + + if (visualId == id) + break; + } + + if (visualId != 0 && visualId != id) + { + CLog::Log(LOGDEBUG, "failed to find EGL config with EGL_NATIVE_VISUAL_ID={}", visualId); + return false; + } + + CLog::Log(LOGDEBUG, "EGL {}Config Attributes:", hdr ? "HDR " : ""); + + for (const auto &eglAttribute : eglAttributes) + { + EGLint value{0}; + if (eglGetConfigAttrib(m_eglDisplay, *currentConfig, eglAttribute.first, &value) != EGL_TRUE) + CEGLUtils::Log(LOGERROR, + StringUtils::Format("failed to query EGL attribute {}", eglAttribute.second)); + + // we only need to print the hex value if it's an actual EGL define + CLog::Log(LOGDEBUG, " {}: {}", eglAttribute.second, + (value >= 0x3000 && value <= 0x3200) ? StringUtils::Format("{:#04x}", value) + : std::to_string(value)); + } + + return true; +} + +EGLint CEGLContextUtils::GetConfigAttrib(EGLint attribute) const +{ + EGLint value{0}; + if (eglGetConfigAttrib(m_eglDisplay, m_eglConfig, attribute, &value) != EGL_TRUE) + CEGLUtils::Log(LOGERROR, "failed to query EGL attribute"); + return value; +} + +bool CEGLContextUtils::CreateContext(CEGLAttributesVec contextAttribs) +{ + if (m_eglContext != EGL_NO_CONTEXT) + { + throw std::logic_error("Do not call CreateContext when context has already been created"); + } + + EGLConfig eglConfig{m_eglConfig}; + + if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_KHR_no_config_context")) + eglConfig = EGL_NO_CONFIG_KHR; + + if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_IMG_context_priority")) + contextAttribs.Add({{EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG}}); + + if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_KHR_create_context") && + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_openGlDebugging) + { + contextAttribs.Add({{EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR}}); + } + + m_eglContext = eglCreateContext(m_eglDisplay, eglConfig, + EGL_NO_CONTEXT, contextAttribs.Get()); + + if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_IMG_context_priority")) + { + EGLint value{EGL_CONTEXT_PRIORITY_MEDIUM_IMG}; + + if (eglQueryContext(m_eglDisplay, m_eglContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value) != EGL_TRUE) + CEGLUtils::Log(LOGERROR, "failed to query EGL context attribute EGL_CONTEXT_PRIORITY_LEVEL_IMG"); + + if (value != EGL_CONTEXT_PRIORITY_HIGH_IMG) + CLog::Log(LOGDEBUG, "Failed to obtain a high priority EGL context"); + } + + if (m_eglContext == EGL_NO_CONTEXT) + { + // This is expected to fail under some circumstances, so log as debug + CLog::Log(LOGDEBUG, "Failed to create EGL context (EGL error {})", eglGetError()); + return false; + } + + return true; +} + +bool CEGLContextUtils::BindContext() +{ + if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE || m_eglContext == EGL_NO_CONTEXT) + { + throw std::logic_error("Activating an EGLContext requires display, surface, and context"); + } + + if (eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext) != EGL_TRUE) + { + CLog::Log(LOGERROR, "Failed to make context current {} {} {}", fmt::ptr(m_eglDisplay), + fmt::ptr(m_eglSurface), fmt::ptr(m_eglContext)); + return false; + } + + return true; +} + +void CEGLContextUtils::SurfaceAttrib() +{ + if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE) + { + throw std::logic_error("Setting surface attributes requires a surface"); + } + + // for the non-trivial dirty region modes, we need the EGL buffer to be preserved across updates + int guiAlgorithmDirtyRegions = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiAlgorithmDirtyRegions; + if (guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_COST_REDUCTION || + guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_UNION) + { + if (eglSurfaceAttrib(m_eglDisplay, m_eglSurface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED) != EGL_TRUE) + { + CEGLUtils::Log(LOGERROR, "failed to set EGL_BUFFER_PRESERVED swap behavior"); + } + } +} + +void CEGLContextUtils::SurfaceAttrib(EGLint attribute, EGLint value) +{ + if (eglSurfaceAttrib(m_eglDisplay, m_eglSurface, attribute, value) != EGL_TRUE) + { + CEGLUtils::Log(LOGERROR, "failed to set EGL_BUFFER_PRESERVED swap behavior"); + } +} + +bool CEGLContextUtils::CreateSurface(EGLNativeWindowType nativeWindow, EGLint HDRcolorSpace /* = EGL_NONE */) +{ + if (m_eglDisplay == EGL_NO_DISPLAY) + { + throw std::logic_error("Creating a surface requires a display"); + } + if (m_eglSurface != EGL_NO_SURFACE) + { + throw std::logic_error("Do not call CreateSurface when surface has already been created"); + } + + CEGLAttributesVec attribs; + EGLConfig config = m_eglConfig; + +#ifdef EGL_GL_COLORSPACE + if (HDRcolorSpace != EGL_NONE) + { + attribs.Add({{EGL_GL_COLORSPACE, HDRcolorSpace}}); + config = m_eglHDRConfig; + } +#endif + + m_eglSurface = eglCreateWindowSurface(m_eglDisplay, config, nativeWindow, attribs.Get()); + + if (m_eglSurface == EGL_NO_SURFACE) + { + CEGLUtils::Log(LOGERROR, "failed to create window surface"); + return false; + } + + SurfaceAttrib(); + + return true; +} + +bool CEGLContextUtils::CreatePlatformSurface(void* nativeWindow, EGLNativeWindowType nativeWindowLegacy) +{ + if (m_eglDisplay == EGL_NO_DISPLAY) + { + throw std::logic_error("Creating a surface requires a display"); + } + if (m_eglSurface != EGL_NO_SURFACE) + { + throw std::logic_error("Do not call CreateSurface when surface has already been created"); + } + +#if defined(EGL_EXT_platform_base) + if (IsPlatformSupported()) + { + auto createPlatformWindowSurfaceEXT = CEGLUtils::GetRequiredProcAddress<PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC>("eglCreatePlatformWindowSurfaceEXT"); + m_eglSurface = createPlatformWindowSurfaceEXT(m_eglDisplay, m_eglConfig, nativeWindow, nullptr); + + if (m_eglSurface == EGL_NO_SURFACE) + { + CEGLUtils::Log(LOGERROR, "failed to create platform window surface"); + return false; + } + } +#endif + + if (m_eglSurface == EGL_NO_SURFACE) + { + return CreateSurface(nativeWindowLegacy); + } + + SurfaceAttrib(); + + return true; +} + +void CEGLContextUtils::Destroy() +{ + DestroyContext(); + DestroySurface(); + + if (m_eglDisplay != EGL_NO_DISPLAY) + { + eglTerminate(m_eglDisplay); + m_eglDisplay = EGL_NO_DISPLAY; + } +} + +void CEGLContextUtils::DestroyContext() +{ + if (m_eglContext != EGL_NO_CONTEXT) + { + eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(m_eglDisplay, m_eglContext); + m_eglContext = EGL_NO_CONTEXT; + } +} + +void CEGLContextUtils::DestroySurface() +{ + if (m_eglSurface != EGL_NO_SURFACE) + { + eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(m_eglDisplay, m_eglSurface); + m_eglSurface = EGL_NO_SURFACE; + } +} + + +bool CEGLContextUtils::SetVSync(bool enable) +{ + if (m_eglDisplay == EGL_NO_DISPLAY) + { + return false; + } + + return (eglSwapInterval(m_eglDisplay, enable) == EGL_TRUE); +} + +bool CEGLContextUtils::TrySwapBuffers() +{ + if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE) + { + return false; + } + + return (eglSwapBuffers(m_eglDisplay, m_eglSurface) == EGL_TRUE); +} diff --git a/xbmc/utils/EGLUtils.h b/xbmc/utils/EGLUtils.h new file mode 100644 index 0000000..2db1628 --- /dev/null +++ b/xbmc/utils/EGLUtils.h @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <array> +#include <set> +#include <stdexcept> +#include <string> +#include <vector> + +#include "system_egl.h" + +class CEGLUtils +{ +public: + static std::set<std::string> GetClientExtensions(); + static std::set<std::string> GetExtensions(EGLDisplay eglDisplay); + static bool HasExtension(EGLDisplay eglDisplay, std::string const & name); + static bool HasClientExtension(std::string const& name); + static void Log(int logLevel, std::string const& what); + template<typename T> + static T GetRequiredProcAddress(const char * procname) + { + T p = reinterpret_cast<T>(eglGetProcAddress(procname)); + if (!p) + { + throw std::runtime_error(std::string("Could not get EGL function \"") + procname + "\" - maybe a required extension is not supported?"); + } + return p; + } + +private: + CEGLUtils(); +}; + +/** + * Convenience wrapper for heap-allocated EGL attribute arrays + * + * The wrapper makes sure that the key/value pairs are always written in actual + * pairs and that the array is always terminated with EGL_NONE. + */ +class CEGLAttributesVec +{ +public: + struct EGLAttribute + { + EGLint key; + EGLint value; + }; + + /** + * Add multiple attributes + * + * The array is automatically terminated with EGL_NONE + */ + void Add(std::initializer_list<EGLAttribute> const& attributes) + { + for (auto const& attribute : attributes) + { + m_attributes.insert(m_attributes.begin(), attribute.value); + m_attributes.insert(m_attributes.begin(), attribute.key); + } + } + + /** + * Add one attribute + * + * The array is automatically terminated with EGL_NONE + */ + void Add(EGLAttribute const& attribute) + { + Add({attribute}); + } + + EGLint const * Get() const + { + return m_attributes.data(); + } + +private: + std::vector<EGLint> m_attributes{EGL_NONE}; +}; + +/** + * Convenience wrapper for stack-allocated EGL attribute arrays + * + * The wrapper makes sure that the key/value pairs are always written in actual + * pairs, that the array is always terminated with EGL_NONE, and that the bounds + * of the array are not exceeded (checked on runtime). + * + * \tparam AttributeCount maximum number of attributes that can be added. + * Determines the size of the storage array. + */ +template<std::size_t AttributeCount> +class CEGLAttributes +{ +public: + struct EGLAttribute + { + EGLint key; + EGLint value; + }; + + CEGLAttributes() + { + m_attributes[0] = EGL_NONE; + } + + /** + * Add multiple attributes + * + * The array is automatically terminated with EGL_NONE + * + * \throws std::out_of_range if more than AttributeCount attributes are added + * in total + */ + void Add(std::initializer_list<EGLAttribute> const& attributes) + { + if (m_writePosition + attributes.size() * 2 + 1 > m_attributes.size()) + { + throw std::out_of_range("CEGLAttributes::Add"); + } + + for (auto const& attribute : attributes) + { + m_attributes[m_writePosition++] = attribute.key; + m_attributes[m_writePosition++] = attribute.value; + } + m_attributes[m_writePosition] = EGL_NONE; + } + + /** + * Add one attribute + * + * The array is automatically terminated with EGL_NONE + * + * \throws std::out_of_range if more than AttributeCount attributes are added + * in total + */ + void Add(EGLAttribute const& attribute) + { + Add({attribute}); + } + + EGLint const * Get() const + { + return m_attributes.data(); + } + + int Size() const + { + return m_writePosition; + } + +private: + std::array<EGLint, AttributeCount * 2 + 1> m_attributes; + int m_writePosition{}; +}; + +class CEGLContextUtils final +{ +public: + CEGLContextUtils() = default; + /** + * \param platform platform as constant from an extension building on EGL_EXT_platform_base + */ + CEGLContextUtils(EGLenum platform, std::string const& platformExtension); + ~CEGLContextUtils(); + + bool CreateDisplay(EGLNativeDisplayType nativeDisplay); + /** + * Create EGLDisplay with EGL_EXT_platform_base + * + * Falls back to \ref CreateDisplay (with nativeDisplayLegacy) on failure. + * The native displays to use with the platform-based and the legacy approach + * may be defined to have different types and/or semantics, so this function takes + * both as separate parameters. + * + * \param nativeDisplay native display to use with eglGetPlatformDisplayEXT + * \param nativeDisplayLegacy native display to use with eglGetDisplay + */ + bool CreatePlatformDisplay(void* nativeDisplay, EGLNativeDisplayType nativeDisplayLegacy); + + void SurfaceAttrib(EGLint attribute, EGLint value); + bool CreateSurface(EGLNativeWindowType nativeWindow, EGLint HDRcolorSpace = EGL_NONE); + bool CreatePlatformSurface(void* nativeWindow, EGLNativeWindowType nativeWindowLegacy); + bool InitializeDisplay(EGLint renderingApi); + bool ChooseConfig(EGLint renderableType, EGLint visualId = 0, bool hdr = false); + bool CreateContext(CEGLAttributesVec contextAttribs); + bool BindContext(); + void Destroy(); + void DestroySurface(); + void DestroyContext(); + bool SetVSync(bool enable); + bool TrySwapBuffers(); + bool IsPlatformSupported() const; + EGLint GetConfigAttrib(EGLint attribute) const; + + EGLDisplay GetEGLDisplay() const + { + return m_eglDisplay; + } + EGLSurface GetEGLSurface() const + { + return m_eglSurface; + } + EGLContext GetEGLContext() const + { + return m_eglContext; + } + EGLConfig GetEGLConfig() const + { + return m_eglConfig; + } + +private: + void SurfaceAttrib(); + + EGLenum m_platform{EGL_NONE}; + bool m_platformSupported{false}; + + EGLDisplay m_eglDisplay{EGL_NO_DISPLAY}; + EGLSurface m_eglSurface{EGL_NO_SURFACE}; + EGLContext m_eglContext{EGL_NO_CONTEXT}; + EGLConfig m_eglConfig{}, m_eglHDRConfig{}; +}; diff --git a/xbmc/utils/EmbeddedArt.cpp b/xbmc/utils/EmbeddedArt.cpp new file mode 100644 index 0000000..3af2259 --- /dev/null +++ b/xbmc/utils/EmbeddedArt.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "EmbeddedArt.h" + +#include "Archive.h" + +EmbeddedArtInfo::EmbeddedArtInfo(size_t size, + const std::string &mime, const std::string& type) +{ + Set(size, mime, type); +} + +void EmbeddedArtInfo::Set(size_t size, const std::string &mime, const std::string& type) +{ + m_size = size; + m_mime = mime; + m_type = type; +} + +void EmbeddedArtInfo::Clear() +{ + m_mime.clear(); + m_size = 0; +} + +bool EmbeddedArtInfo::Empty() const +{ + return m_size == 0; +} + +bool EmbeddedArtInfo::Matches(const EmbeddedArtInfo &right) const +{ + return (m_size == right.m_size && + m_mime == right.m_mime && + m_type == right.m_type); +} + +void EmbeddedArtInfo::Archive(CArchive &ar) +{ + if (ar.IsStoring()) + { + ar << m_size; + ar << m_mime; + ar << m_type; + } + else + { + ar >> m_size; + ar >> m_mime; + ar >> m_type; + } +} + +EmbeddedArt::EmbeddedArt(const uint8_t *data, size_t size, + const std::string &mime, const std::string& type) +{ + Set(data, size, mime, type); +} + +void EmbeddedArt::Set(const uint8_t *data, size_t size, + const std::string &mime, const std::string& type) +{ + EmbeddedArtInfo::Set(size, mime, type); + m_data.resize(size); + m_data.assign(data, data+size); +} diff --git a/xbmc/utils/EmbeddedArt.h b/xbmc/utils/EmbeddedArt.h new file mode 100644 index 0000000..b752bac --- /dev/null +++ b/xbmc/utils/EmbeddedArt.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IArchivable.h" + +#include <stdint.h> +#include <string> +#include <vector> + +class EmbeddedArtInfo : public IArchivable +{ +public: + EmbeddedArtInfo() = default; + EmbeddedArtInfo(size_t size, const std::string &mime, const std::string& type = ""); + virtual ~EmbeddedArtInfo() = default; + + // implementation of IArchivable + void Archive(CArchive& ar) override; + + void Set(size_t size, const std::string &mime, const std::string& type = ""); + void Clear(); + bool Empty() const; + bool Matches(const EmbeddedArtInfo &right) const; + void SetType(const std::string& type) { m_type = type; } + + size_t m_size = 0; + std::string m_mime; + std::string m_type; +}; + +class EmbeddedArt : public EmbeddedArtInfo +{ +public: + EmbeddedArt() = default; + EmbeddedArt(const uint8_t *data, size_t size, + const std::string &mime, const std::string& type = ""); + + void Set(const uint8_t *data, size_t size, + const std::string &mime, const std::string& type = ""); + + std::vector<uint8_t> m_data; +}; diff --git a/xbmc/utils/EndianSwap.cpp b/xbmc/utils/EndianSwap.cpp new file mode 100644 index 0000000..3f645d9 --- /dev/null +++ b/xbmc/utils/EndianSwap.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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/xbmc/utils/EndianSwap.h b/xbmc/utils/EndianSwap.h new file mode 100644 index 0000000..0b02689 --- /dev/null +++ b/xbmc/utils/EndianSwap.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + + /* Endian_SwapXX functions taken from SDL (SDL_endian.h) */ + +#ifdef TARGET_POSIX +#include <inttypes.h> +#elif 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 + diff --git a/xbmc/utils/EventStream.h b/xbmc/utils/EventStream.h new file mode 100644 index 0000000..6fcb651 --- /dev/null +++ b/xbmc/utils/EventStream.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "EventStreamDetail.h" +#include "JobManager.h" +#include "threads/CriticalSection.h" + +#include <algorithm> +#include <memory> +#include <mutex> +#include <vector> + + +template<typename Event> +class CEventStream +{ +public: + + template<typename A> + void Subscribe(A* owner, void (A::*fn)(const Event&)) + { + auto subscription = std::make_shared<detail::CSubscription<Event, A>>(owner, fn); + std::unique_lock<CCriticalSection> lock(m_criticalSection); + m_subscriptions.emplace_back(std::move(subscription)); + } + + template<typename A> + void Unsubscribe(A* obj) + { + std::vector<std::shared_ptr<detail::ISubscription<Event>>> toCancel; + { + std::unique_lock<CCriticalSection> lock(m_criticalSection); + auto it = m_subscriptions.begin(); + while (it != m_subscriptions.end()) + { + if ((*it)->IsOwnedBy(obj)) + { + toCancel.push_back(*it); + it = m_subscriptions.erase(it); + } + else + { + ++it; + } + } + } + for (auto& subscription : toCancel) + subscription->Cancel(); + } + +protected: + std::vector<std::shared_ptr<detail::ISubscription<Event>>> m_subscriptions; + CCriticalSection m_criticalSection; +}; + + +template<typename Event> +class CEventSource : public CEventStream<Event> +{ +public: + explicit CEventSource() : m_queue(false, 1, CJob::PRIORITY_HIGH) {} + + template<typename A> + void Publish(A event) + { + std::unique_lock<CCriticalSection> lock(this->m_criticalSection); + auto& subscriptions = this->m_subscriptions; + auto task = [subscriptions, event](){ + for (auto& s: subscriptions) + s->HandleEvent(event); + }; + lock.unlock(); + m_queue.Submit(std::move(task)); + } + +private: + CJobQueue m_queue; +}; + +template<typename Event> +class CBlockingEventSource : public CEventStream<Event> +{ +public: + template<typename A> + void HandleEvent(A event) + { + std::unique_lock<CCriticalSection> lock(this->m_criticalSection); + for (const auto& subscription : this->m_subscriptions) + { + subscription->HandleEvent(event); + } + } +}; diff --git a/xbmc/utils/EventStreamDetail.h b/xbmc/utils/EventStreamDetail.h new file mode 100644 index 0000000..31cd0b5 --- /dev/null +++ b/xbmc/utils/EventStreamDetail.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/CriticalSection.h" + +#include <mutex> + +namespace detail +{ + +template<typename Event> +class ISubscription +{ +public: + virtual void HandleEvent(const Event& event) = 0; + virtual void Cancel() = 0; + virtual bool IsOwnedBy(void* obj) = 0; + virtual ~ISubscription() = default; +}; + +template<typename Event, typename Owner> +class CSubscription : public ISubscription<Event> +{ +public: + typedef void (Owner::*Fn)(const Event&); + CSubscription(Owner* owner, Fn fn); + void HandleEvent(const Event& event) override; + void Cancel() override; + bool IsOwnedBy(void *obj) override; + +private: + Owner* m_owner; + Fn m_eventHandler; + CCriticalSection m_criticalSection; +}; + +template<typename Event, typename Owner> +CSubscription<Event, Owner>::CSubscription(Owner* owner, Fn fn) + : m_owner(owner), m_eventHandler(fn) +{} + +template<typename Event, typename Owner> +bool CSubscription<Event, Owner>::IsOwnedBy(void* obj) +{ + std::unique_lock<CCriticalSection> lock(m_criticalSection); + return obj != nullptr && obj == m_owner; +} + +template<typename Event, typename Owner> +void CSubscription<Event, Owner>::Cancel() +{ + std::unique_lock<CCriticalSection> lock(m_criticalSection); + m_owner = nullptr; +} + +template<typename Event, typename Owner> +void CSubscription<Event, Owner>::HandleEvent(const Event& event) +{ + std::unique_lock<CCriticalSection> lock(m_criticalSection); + if (m_owner) + (m_owner->*m_eventHandler)(event); +} +} diff --git a/xbmc/utils/ExecString.cpp b/xbmc/utils/ExecString.cpp new file mode 100644 index 0000000..37650e0 --- /dev/null +++ b/xbmc/utils/ExecString.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ExecString.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "music/tags/MusicInfoTag.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoInfoTag.h" + +CExecString::CExecString(const std::string& execString) +{ + m_valid = Parse(execString); +} + +CExecString::CExecString(const std::string& function, const std::vector<std::string>& params) + : m_function(function), m_params(params) +{ + m_valid = !m_function.empty(); + + if (m_valid) + SetExecString(); +} + +CExecString::CExecString(const CFileItem& item, const std::string& contextWindow) +{ + m_valid = Parse(item, contextWindow); +} + +namespace +{ +void SplitParams(const std::string& paramString, std::vector<std::string>& parameters) +{ + bool inQuotes = false; + bool lastEscaped = false; // only every second character can be escaped + int inFunction = 0; + size_t whiteSpacePos = 0; + std::string parameter; + parameters.clear(); + for (size_t pos = 0; pos < paramString.size(); pos++) + { + char ch = paramString[pos]; + bool escaped = (pos > 0 && paramString[pos - 1] == '\\' && !lastEscaped); + lastEscaped = escaped; + if (inQuotes) + { // if we're in a quote, we accept everything until the closing quote + if (ch == '"' && !escaped) + { // finished a quote - no need to add the end quote to our string + inQuotes = false; + } + } + else + { // not in a quote, so check if we should be starting one + if (ch == '"' && !escaped) + { // start of quote - no need to add the quote to our string + inQuotes = true; + } + if (inFunction && ch == ')') + { // end of a function + inFunction--; + } + if (ch == '(') + { // start of function + inFunction++; + } + if (!inFunction && ch == ',') + { // not in a function, so a comma signifies the end of this parameter + if (whiteSpacePos) + parameter = parameter.substr(0, whiteSpacePos); + // trim off start and end quotes + if (parameter.length() > 1 && parameter[0] == '"' && + parameter[parameter.length() - 1] == '"') + parameter = parameter.substr(1, parameter.length() - 2); + else if (parameter.length() > 3 && parameter[parameter.length() - 1] == '"') + { + // check name="value" style param. + size_t quotaPos = parameter.find('"'); + if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=') + { + parameter.erase(parameter.length() - 1); + parameter.erase(quotaPos); + } + } + parameters.push_back(parameter); + parameter.clear(); + whiteSpacePos = 0; + continue; + } + } + if ((ch == '"' || ch == '\\') && escaped) + { // escaped quote or backslash + parameter[parameter.size() - 1] = ch; + continue; + } + // whitespace handling - we skip any whitespace at the left or right of an unquoted parameter + if (ch == ' ' && !inQuotes) + { + if (parameter.empty()) // skip whitespace on left + continue; + if (!whiteSpacePos) // make a note of where whitespace starts on the right + whiteSpacePos = parameter.size(); + } + else + whiteSpacePos = 0; + parameter += ch; + } + if (inFunction || inQuotes) + CLog::Log(LOGWARNING, "{}({}) - end of string while searching for ) or \"", __FUNCTION__, + paramString); + if (whiteSpacePos) + parameter.erase(whiteSpacePos); + // trim off start and end quotes + if (parameter.size() > 1 && parameter[0] == '"' && parameter[parameter.size() - 1] == '"') + parameter = parameter.substr(1, parameter.size() - 2); + else if (parameter.size() > 3 && parameter[parameter.size() - 1] == '"') + { + // check name="value" style param. + size_t quotaPos = parameter.find('"'); + if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=') + { + parameter.erase(parameter.length() - 1); + parameter.erase(quotaPos); + } + } + if (!parameter.empty() || parameters.size()) + parameters.push_back(parameter); +} + +void SplitExecFunction(const std::string& execString, + std::string& function, + std::vector<std::string>& parameters) +{ + std::string paramString; + + size_t iPos = execString.find('('); + size_t iPos2 = execString.rfind(')'); + if (iPos != std::string::npos && iPos2 != std::string::npos) + { + paramString = execString.substr(iPos + 1, iPos2 - iPos - 1); + function = execString.substr(0, iPos); + } + else + function = execString; + + // remove any whitespace, and the standard prefix (if it exists) + StringUtils::Trim(function); + + SplitParams(paramString, parameters); +} +} // namespace + +bool CExecString::Parse(const std::string& execString) +{ + m_execString = execString; + SplitExecFunction(m_execString, m_function, m_params); + + // Keep original function case in execstring, lowercase it in function + StringUtils::ToLower(m_function); + return true; +} + +bool CExecString::Parse(const CFileItem& item, const std::string& contextWindow) +{ + if (item.IsFavourite()) + { + const CURL url(item.GetPath()); + Parse(CURL::Decode(url.GetHostName())); + } + else if (item.m_bIsFolder && + (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders || + !(item.IsSmartPlayList() || item.IsPlayList()))) + { + if (!contextWindow.empty()) + Build("ActivateWindow", {contextWindow, StringUtils::Paramify(item.GetPath()), "return"}); + } + else if (item.IsScript() && item.GetPath().size() > 9) // script://<foo> + Build("RunScript", {StringUtils::Paramify(item.GetPath().substr(9))}); + else if (item.IsAddonsPath() && item.GetPath().size() > 9) // addons://<foo> + { + const CURL url(item.GetPath()); + if (url.GetHostName() == "install") + Build("InstallFromZip", {}); + else if (url.GetHostName() == "check_for_updates") + Build("UpdateAddonRepos", {"showProgress"}); + else + Build("RunAddon", {StringUtils::Paramify(url.GetFileName())}); + } + else if (item.IsAndroidApp() && item.GetPath().size() > 26) // androidapp://sources/apps/<foo> + Build("StartAndroidActivity", {StringUtils::Paramify(item.GetPath().substr(26))}); + else // assume a media file + { + if (item.IsVideoDb() && item.HasVideoInfoTag()) + BuildPlayMedia(item, StringUtils::Paramify(item.GetVideoInfoTag()->m_strFileNameAndPath)); + else if (item.IsMusicDb() && item.HasMusicInfoTag()) + BuildPlayMedia(item, StringUtils::Paramify(item.GetMusicInfoTag()->GetURL())); + else if (item.IsPicture()) + Build("ShowPicture", {StringUtils::Paramify(item.GetPath())}); + else + { + // Everything else will be treated as PlayMedia for item's path + BuildPlayMedia(item, StringUtils::Paramify(item.GetPath())); + } + } + return true; +} + +void CExecString::Build(const std::string& function, const std::vector<std::string>& params) +{ + m_function = function; + m_params = params; + SetExecString(); +} + +void CExecString::BuildPlayMedia(const CFileItem& item, const std::string& target) +{ + std::vector<std::string> params{target}; + + if (item.HasProperty("playlist_type_hint")) + params.emplace_back("playlist_type_hint=" + item.GetProperty("playlist_type_hint").asString()); + + Build("PlayMedia", params); +} + +void CExecString::SetExecString() +{ + if (m_params.empty()) + m_execString = m_function; + else + m_execString = StringUtils::Format("{}({})", m_function, StringUtils::Join(m_params, ",")); + + // Keep original function case in execstring, lowercase it in function + StringUtils::ToLower(m_function); +} diff --git a/xbmc/utils/ExecString.h b/xbmc/utils/ExecString.h new file mode 100644 index 0000000..fda234a --- /dev/null +++ b/xbmc/utils/ExecString.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> +#include <vector> + +class CFileItem; + +class CExecString +{ +public: + CExecString() = default; + explicit CExecString(const std::string& execString); + CExecString(const std::string& function, const std::vector<std::string>& params); + CExecString(const CFileItem& item, const std::string& contextWindow); + + virtual ~CExecString() = default; + + std::string GetExecString() const { return m_execString; } + + bool IsValid() const { return m_valid; } + + std::string GetFunction() const { return m_function; } + std::vector<std::string> GetParams() const { return m_params; } + +private: + bool Parse(const std::string& execString); + bool Parse(const CFileItem& item, const std::string& contextWindow); + + void Build(const std::string& function, const std::vector<std::string>& params); + void BuildPlayMedia(const CFileItem& item, const std::string& target); + + void SetExecString(); + + bool m_valid{false}; + std::string m_function; + std::vector<std::string> m_params; + std::string m_execString; +}; diff --git a/xbmc/utils/Fanart.cpp b/xbmc/utils/Fanart.cpp new file mode 100644 index 0000000..5e385ee --- /dev/null +++ b/xbmc/utils/Fanart.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Fanart.h" + +#include "StringUtils.h" +#include "URIUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" + +#include <algorithm> +#include <functional> + +const unsigned int CFanart::max_fanart_colors=3; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// CFanart Functions +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +CFanart::CFanart() = default; + +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("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; +} + +void CFanart::AddFanart(const std::string& image, const std::string& preview, const std::string& colors) +{ + SFanartData info; + info.strPreview = preview; + info.strImage = image; + ParseColors(colors, info.strColors); + m_fanart.push_back(std::move(info)); +} + +void CFanart::Clear() +{ + m_fanart.clear(); + m_xml.clear(); +} + +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")); + } + 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.empty() || + 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 separated "FFFFFFFF,DDDDDDDD,AAAAAAAA" + // 1: The TVDB RGB Int Triplets, pipe separate 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 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{:2x}{:2x}{:2x}", std::stol(strTriplets[0]), + std::stol(strTriplets[1]), std::stol(strTriplets[2])); + } + } + } + else + { // assume is our format + colorsOut = colorsIn; + } + return true; +} diff --git a/xbmc/utils/Fanart.h b/xbmc/utils/Fanart.h new file mode 100644 index 0000000..c47d2df --- /dev/null +++ b/xbmc/utils/Fanart.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +// Fanart.h +////////////////////////////////////////////////////////////////////// + +#include <string> +#include <vector> + +/// +/// /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 necessity. + /// \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; + /// Adds an image to internal fanart data + void AddFanart(const std::string& image, const std::string& preview, const std::string& colors); + /// Clear all internal fanart data + void Clear(); + /// + /// 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 formatted 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 separate with leading/trailing pipes "|68,69,59|69,70,58|78,78,68|" + /// * XBMC ARGB Hexadecimal string comma separated "FFFFFFFF,DDDDDDDD,AAAAAAAA" + /// + /// \param colorsIn: string containing colors in some format to be converted + /// \param colorsOut: XBMC ARGB Hexadecimal string comma separated "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 strColors; + std::string strPreview; + }; + + /// + /// std::vector that stores all our fanart data + std::vector<SFanartData> m_fanart; +}; + diff --git a/xbmc/utils/FileExtensionProvider.cpp b/xbmc/utils/FileExtensionProvider.cpp new file mode 100644 index 0000000..79ce46c --- /dev/null +++ b/xbmc/utils/FileExtensionProvider.cpp @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FileExtensionProvider.h" + +#include "ServiceBroker.h" +#include "addons/AddonEvents.h" +#include "addons/AddonManager.h" +#include "addons/AudioDecoder.h" +#include "addons/ExtsMimeSupportList.h" +#include "addons/ImageDecoder.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/URIUtils.h" + +#include <string> +#include <vector> + +using namespace ADDON; +using namespace KODI::ADDONS; + +const std::vector<AddonType> ADDON_TYPES = {AddonType::VFS, AddonType::IMAGEDECODER, + AddonType::AUDIODECODER}; + +CFileExtensionProvider::CFileExtensionProvider(ADDON::CAddonMgr& addonManager) + : m_advancedSettings(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()), + m_addonManager(addonManager) +{ + SetAddonExtensions(); + + m_addonManager.Events().Subscribe(this, &CFileExtensionProvider::OnAddonEvent); +} + +CFileExtensionProvider::~CFileExtensionProvider() +{ + m_addonManager.Events().Unsubscribe(this); + + m_advancedSettings.reset(); + m_addonExtensions.clear(); +} + +std::string CFileExtensionProvider::GetDiscStubExtensions() const +{ + return m_advancedSettings->m_discStubExtensions; +} + +std::string CFileExtensionProvider::GetMusicExtensions() const +{ + std::string extensions(m_advancedSettings->m_musicExtensions); + extensions += '|' + GetAddonExtensions(AddonType::VFS); + extensions += '|' + GetAddonExtensions(AddonType::AUDIODECODER); + + return extensions; +} + +std::string CFileExtensionProvider::GetPictureExtensions() const +{ + std::string extensions(m_advancedSettings->m_pictureExtensions); + extensions += '|' + GetAddonExtensions(AddonType::VFS); + extensions += '|' + GetAddonExtensions(AddonType::IMAGEDECODER); + + return extensions; +} + +std::string CFileExtensionProvider::GetSubtitleExtensions() const +{ + std::string extensions(m_advancedSettings->m_subtitlesExtensions); + extensions += '|' + GetAddonExtensions(AddonType::VFS); + + return extensions; +} + +std::string CFileExtensionProvider::GetVideoExtensions() const +{ + std::string extensions(m_advancedSettings->m_videoExtensions); + if (!extensions.empty()) + extensions += '|'; + extensions += GetAddonExtensions(AddonType::VFS); + + return extensions; +} + +std::string CFileExtensionProvider::GetFileFolderExtensions() const +{ + std::string extensions(GetAddonFileFolderExtensions(AddonType::VFS)); + if (!extensions.empty()) + extensions += '|'; + extensions += GetAddonFileFolderExtensions(AddonType::AUDIODECODER); + + return extensions; +} + +bool CFileExtensionProvider::CanOperateExtension(const std::string& path) const +{ + /*! + * @todo Improve this function to support all cases and not only audio decoder. + */ + + // Get file extensions to find addon related to it. + std::string strExtension = URIUtils::GetExtension(path); + StringUtils::ToLower(strExtension); + if (!strExtension.empty() && CServiceBroker::IsAddonInterfaceUp()) + { + std::vector<std::unique_ptr<KODI::ADDONS::IAddonSupportCheck>> supportList; + + auto addonInfos = CServiceBroker::GetExtsMimeSupportList().GetExtensionSupportedAddonInfos( + strExtension, CExtsMimeSupportList::FilterSelect::all); + for (const auto& addonInfo : addonInfos) + { + switch (addonInfo.first) + { + case AddonType::AUDIODECODER: + supportList.emplace_back(new CAudioDecoder(addonInfo.second)); + break; + case AddonType::IMAGEDECODER: + supportList.emplace_back(new CImageDecoder(addonInfo.second, "")); + break; + default: + break; + } + } + + /*! + * We expect that other addons can support the file, and return true if + * list empty. + * + * @todo Check addons can also be types in conflict with Kodi's + * supported parts! + * + * @warning This part is really big ugly at the moment and as soon as possible + * add about other addons where works with extensions!!! + * Due to @ref GetFileFolderExtensions() call from outside place before here, becomes + * it usable in this way, as there limited to AudioDecoder and VFS addons. + */ + if (supportList.empty()) + { + return true; + } + + /*! + * Check all found addons about support of asked file. + */ + for (const auto& addon : supportList) + { + if (addon->SupportsFile(path)) + return true; + } + } + + /*! + * If no file extensions present, mark it as not supported. + */ + return false; +} + +std::string CFileExtensionProvider::GetAddonExtensions(AddonType type) const +{ + auto it = m_addonExtensions.find(type); + if (it != m_addonExtensions.end()) + return it->second; + + return ""; +} + +std::string CFileExtensionProvider::GetAddonFileFolderExtensions(AddonType type) const +{ + auto it = m_addonFileFolderExtensions.find(type); + if (it != m_addonFileFolderExtensions.end()) + return it->second; + + return ""; +} + +void CFileExtensionProvider::SetAddonExtensions() +{ + for (auto const type : ADDON_TYPES) + { + SetAddonExtensions(type); + } +} + +void CFileExtensionProvider::SetAddonExtensions(AddonType type) +{ + std::vector<std::string> extensions; + std::vector<std::string> fileFolderExtensions; + + if (type == AddonType::AUDIODECODER || type == AddonType::IMAGEDECODER) + { + auto addonInfos = CServiceBroker::GetExtsMimeSupportList().GetSupportedAddonInfos( + CExtsMimeSupportList::FilterSelect::all); + for (const auto& addonInfo : addonInfos) + { + if (addonInfo.m_addonType != type) + continue; + + for (const auto& ext : addonInfo.m_supportedExtensions) + { + extensions.push_back(ext.first); + if (addonInfo.m_hasTracks) + fileFolderExtensions.push_back(ext.first); + } + } + } + else if (type == AddonType::VFS) + { + std::vector<AddonInfoPtr> addonInfos; + m_addonManager.GetAddonInfos(addonInfos, true, type); + for (const auto& addonInfo : addonInfos) + { + std::string ext = addonInfo->Type(type)->GetValue("@extensions").asString(); + if (!ext.empty()) + { + extensions.push_back(ext); + if (addonInfo->Type(type)->GetValue("@filedirectories").asBoolean()) + fileFolderExtensions.push_back(ext); + + if (addonInfo->Type(type)->GetValue("@encodedhostname").asBoolean()) + { + std::string prot = addonInfo->Type(type)->GetValue("@protocols").asString(); + auto prots = StringUtils::Split(prot, "|"); + for (const std::string& it : prots) + m_encoded.push_back(it); + } + } + } + } + + m_addonExtensions[type] = StringUtils::Join(extensions, "|"); + m_addonFileFolderExtensions[type] = StringUtils::Join(fileFolderExtensions, "|"); +} + +void CFileExtensionProvider::OnAddonEvent(const AddonEvent& event) +{ + if (typeid(event) == typeid(AddonEvents::Enabled) || + typeid(event) == typeid(AddonEvents::Disabled) || + typeid(event) == typeid(AddonEvents::ReInstalled)) + { + for (auto &type : ADDON_TYPES) + { + if (m_addonManager.HasType(event.addonId, type)) + { + SetAddonExtensions(type); + break; + } + } + } + else if (typeid(event) == typeid(AddonEvents::UnInstalled)) + { + SetAddonExtensions(); + } +} + +bool CFileExtensionProvider::EncodedHostName(const std::string& protocol) const +{ + return std::find(m_encoded.begin(),m_encoded.end(),protocol) != m_encoded.end(); +} diff --git a/xbmc/utils/FileExtensionProvider.h b/xbmc/utils/FileExtensionProvider.h new file mode 100644 index 0000000..586f155 --- /dev/null +++ b/xbmc/utils/FileExtensionProvider.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <map> +#include <memory> +#include <string> +#include <vector> + +namespace ADDON +{ +enum class AddonType; +class CAddonMgr; +struct AddonEvent; +} + +class CAdvancedSettings; + +class CFileExtensionProvider +{ +public: + CFileExtensionProvider(ADDON::CAddonMgr& addonManager); + ~CFileExtensionProvider(); + + /*! + * @brief Returns a list of picture extensions + */ + std::string GetPictureExtensions() const; + + /*! + * @brief Returns a list of music extensions + */ + std::string GetMusicExtensions() const; + + /*! + * @brief Returns a list of video extensions + */ + std::string GetVideoExtensions() const; + + /*! + * @brief Returns a list of subtitle extensions + */ + std::string GetSubtitleExtensions() const; + + /*! + * @brief Returns a list of disc stub extensions + */ + std::string GetDiscStubExtensions() const; + + /*! + * @brief Returns a file folder extensions + */ + std::string GetFileFolderExtensions() const; + + /*! + * @brief Returns whether a url protocol from add-ons use encoded hostnames + */ + bool EncodedHostName(const std::string& protocol) const; + + /*! + * @brief Returns true if related provider can operate related file + * + * @note Thought for cases e.g. by ISO, where can be a video or also a SACD. + */ + bool CanOperateExtension(const std::string& path) const; + +private: + std::string GetAddonExtensions(ADDON::AddonType type) const; + std::string GetAddonFileFolderExtensions(ADDON::AddonType type) const; + void SetAddonExtensions(); + void SetAddonExtensions(ADDON::AddonType type); + + void OnAddonEvent(const ADDON::AddonEvent& event); + + // Construction properties + std::shared_ptr<CAdvancedSettings> m_advancedSettings; + ADDON::CAddonMgr &m_addonManager; + + // File extension properties + std::map<ADDON::AddonType, std::string> m_addonExtensions; + std::map<ADDON::AddonType, std::string> m_addonFileFolderExtensions; + + // Protocols from add-ons with encoded host names + std::vector<std::string> m_encoded; +}; diff --git a/xbmc/utils/FileOperationJob.cpp b/xbmc/utils/FileOperationJob.cpp new file mode 100644 index 0000000..7313a18 --- /dev/null +++ b/xbmc/utils/FileOperationJob.cpp @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FileOperationJob.h" + +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "dialogs/GUIDialogExtendedProgressBar.h" +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "filesystem/FileDirectoryFactory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +using namespace XFILE; + +CFileOperationJob::CFileOperationJob() + : m_items(), + m_strDestFile(), + m_avgSpeed(), + m_currentOperation(), + m_currentFile() +{ } + +CFileOperationJob::CFileOperationJob(FileAction action, CFileItemList & items, + const std::string& strDestFile, + bool displayProgress /* = false */, + int heading /* = 0 */, int line /* = 0 */) + : m_action(action), + m_items(), + m_strDestFile(strDestFile), + m_avgSpeed(), + m_currentOperation(), + m_currentFile(), + m_displayProgress(displayProgress), + m_heading(heading), + m_line(line) +{ + SetFileOperation(action, items, strDestFile); +} + +void CFileOperationJob::SetFileOperation(FileAction action, + const CFileItemList& items, + const std::string& 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 && GetProgressDialog() == NULL) + { + CGUIDialogExtendedProgressBar* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogExtendedProgressBar>(WINDOW_DIALOG_EXT_PROGRESS); + SetProgressBar(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); + + MarkFinished(); + + return success; +} + +bool CFileOperationJob::DoProcessFile(FileAction action, const std::string& strFileA, const std::string& 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 std::string& strPath, const std::string& 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; + } + + CFileItemList 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); + } + + if (!DoProcess(action, items, strDestFile, fileOperations, totalTime)) + { + CLog::Log(LOGERROR, "FileManager: error while processing folder: {}", strPath); + return false; + } + + if (action == ActionMove) + { + fileOperations.push_back(CFileOperation(ActionDeleteFolder, strPath, "", 1)); + totalTime += 1.0; + } + + return true; +} + +bool CFileOperationJob::DoProcess(FileAction action, + const CFileItemList& items, + const std::string& strDestFile, + FileOperationList& fileOperations, + double& totalTime) +{ + for (int iItem = 0; iItem < items.Size(); ++iItem) + { + CFileItemPtr pItem = items[iItem]; + if (pItem->IsSelected()) + { + std::string strNoSlash = pItem->GetPath(); + URIUtils::RemoveSlashAtEnd(strNoSlash); + std::string 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); + } + + std::string 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 std::string &strFileA, const std::string &strFileB, int64_t time) + : m_action(action), + m_strFileA(strFileA), + m_strFileB(strFileB), + m_time(time) +{ } + +struct DataHolder +{ + CFileOperationJob *base; + double current; + double opWeight; +}; + +std::string CFileOperationJob::GetActionString(FileAction action) +{ + std::string 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 int)current, 100)) + return false; + + base->SetText(base->GetCurrentFile()); + + DataHolder data = {base, current, opWeight}; + + switch (m_action) + { + case ActionCopy: + case ActionReplace: + bResult = CFile::Copy(m_strFileA, m_strFileB, this, &data); + break; + + case ActionMove: + 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: + bResult = CFile::Delete(m_strFileA); + break; + + case ActionDeleteFolder: + bResult = CDirectory::Remove(m_strFileA); + break; + + case ActionCreateFolder: + bResult = CDirectory::Create(m_strFileA); + break; + + default: + CLog::Log(LOGERROR, "FileManager: unknown operation"); + bResult = false; + break; + } + + current += (double)m_time * opWeight; + + return bResult; +} + +inline bool CFileOperationJob::CanBeRenamed(const std::string &strFileA, const std::string &strFileB) +{ +#ifndef TARGET_POSIX + if (strFileA[1] == ':' && strFileA[0] == strFileB[0]) + return true; +#else + if (URIUtils::IsHD(strFileA) && URIUtils::IsHD(strFileB)) + return true; + else if (URIUtils::IsSmb(strFileA) && URIUtils::IsSmb(strFileB)) { + CURL smbFileA(strFileA), smbFileB(strFileB); + return smbFileA.GetHostName() == smbFileB.GetHostName() && + smbFileA.GetShareName() == smbFileB.GetShareName(); + } +#endif + return false; +} + +bool CFileOperationJob::CFileOperation::OnFileCallback(void* pContext, int ipercent, float avgSpeed) +{ + DataHolder *data = static_cast<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); + + std::string line; + line = + StringUtils::Format("{} ({})", data->base->GetCurrentFile(), data->base->GetAverageSpeed()); + data->base->SetText(line); + return !data->base->ShouldCancel((unsigned)current, 100); +} + +bool CFileOperationJob::operator==(const CJob* job) const +{ + if (strcmp(job->GetType(), GetType()) != 0) + return false; + + const CFileOperationJob* rjob = dynamic_cast<const CFileOperationJob*>(job); + if (rjob == NULL) + return false; + + if (GetAction() != rjob->GetAction() || + m_strDestFile != rjob->m_strDestFile || + m_items.Size() != rjob->m_items.Size()) + return false; + + 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; +} diff --git a/xbmc/utils/FileOperationJob.h b/xbmc/utils/FileOperationJob.h new file mode 100644 index 0000000..7284c62 --- /dev/null +++ b/xbmc/utils/FileOperationJob.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItem.h" +#include "filesystem/IFileTypes.h" +#include "utils/ProgressJob.h" + +#include <string> +#include <vector> + +class CFileOperationJob : public CProgressJob +{ +public: + enum FileAction + { + ActionCopy = 1, + ActionMove, + ActionDelete, + ActionReplace, ///< Copy, emptying any existing destination directories first + ActionCreateFolder, + ActionDeleteFolder, + }; + + CFileOperationJob(); + CFileOperationJob(FileAction action, CFileItemList & items, + const std::string& strDestFile, + bool displayProgress = false, + int errorHeading = 0, int errorLine = 0); + + static std::string GetActionString(FileAction action); + + // implementations of CJob + bool DoWork() override; + const char* GetType() const override { return m_displayProgress ? "filemanager" : ""; } + bool operator==(const CJob *job) const override; + + void SetFileOperation(FileAction action, + const CFileItemList& items, + const std::string& strDestFile); + + const std::string &GetAverageSpeed() const { return m_avgSpeed; } + const std::string &GetCurrentOperation() const { return m_currentOperation; } + const std::string &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 std::string &strFileA, const std::string &strFileB, int64_t time); + + bool OnFileCallback(void* pContext, int ipercent, float avgSpeed) override; + + bool ExecuteOperation(CFileOperationJob *base, double ¤t, double opWeight); + + private: + FileAction m_action; + std::string m_strFileA, m_strFileB; + int64_t m_time; + }; + friend class CFileOperation; + + typedef std::vector<CFileOperation> FileOperationList; + bool DoProcess(FileAction action, + const CFileItemList& items, + const std::string& strDestFile, + FileOperationList& fileOperations, + double& totalTime); + bool DoProcessFolder(FileAction action, const std::string& strPath, const std::string& strDestFile, FileOperationList &fileOperations, double &totalTime); + bool DoProcessFile(FileAction action, const std::string& strFileA, const std::string& strFileB, FileOperationList &fileOperations, double &totalTime); + + static inline bool CanBeRenamed(const std::string &strFileA, const std::string &strFileB); + + FileAction m_action = ActionCopy; + CFileItemList m_items; + std::string m_strDestFile; + std::string m_avgSpeed, m_currentOperation, m_currentFile; + bool m_displayProgress = false; + int m_heading = 0; + int m_line = 0; +}; diff --git a/xbmc/utils/FileUtils.cpp b/xbmc/utils/FileUtils.cpp new file mode 100644 index 0000000..b39f75a --- /dev/null +++ b/xbmc/utils/FileUtils.cpp @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2010-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FileUtils.h" + +#include "CompileInfo.h" +#include "FileOperationJob.h" +#include "ServiceBroker.h" +#include "StringUtils.h" +#include "URIUtils.h" +#include "URL.h" +#include "Util.h" +#include "filesystem/File.h" +#include "filesystem/MultiPathDirectory.h" +#include "filesystem/SpecialProtocol.h" +#include "filesystem/StackDirectory.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/LocalizeStrings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#if defined(TARGET_WINDOWS) +#include "platform/win32/WIN32Util.h" +#include "utils/CharsetConverter.h" +#endif + +#include <vector> + +using namespace XFILE; + +bool CFileUtils::DeleteItem(const std::string &strPath) +{ + CFileItemPtr item(new CFileItem(strPath)); + item->SetPath(strPath); + item->m_bIsFolder = URIUtils::HasSlashAtEnd(strPath); + item->Select(true); + return DeleteItem(item); +} + +bool CFileUtils::DeleteItem(const std::shared_ptr<CFileItem>& item) +{ + if (!item || item->IsParentFolder()) + 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 std::string &strFile) +{ + std::string strFileAndPath(strFile); + URIUtils::RemoveSlashAtEnd(strFileAndPath); + std::string strFileName = URIUtils::GetFileName(strFileAndPath); + std::string strPath = URIUtils::GetDirectory(strFileAndPath); + if (CGUIKeyboardFactory::ShowAndGetInput(strFileName, CVariant{g_localizeStrings.Get(16013)}, false)) + { + strPath = URIUtils::AddFileToFolder(strPath, strFileName); + CLog::Log(LOGINFO, "FileUtils: rename {}->{}", strFileAndPath, strPath); + if (URIUtils::IsMultiPath(strFileAndPath)) + { // special case for multipath renames - rename all the paths. + std::vector<std::string> paths; + CMultiPathDirectory::GetPaths(strFileAndPath, paths); + bool success = false; + for (unsigned int i = 0; i < paths.size(); ++i) + { + std::string 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 std::string &strPath) +{ + std::string SourceNames[] = { "programs", "files", "video", "music", "pictures" }; + + std::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, "library://music")) + 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 = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH); + URIUtils::RemoveSlashAtEnd(strPlaylistsPath); + if (StringUtils::StartsWithNoCase(realPath, strPlaylistsPath)) + return true; + } + bool isSource; + // Check manually added sources (held in sources.xml) + for (const std::string& sourceName : SourceNames) + { + VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources(sourceName); + int sourceIndex = CUtil::GetMatchingSource(realPath, *sources, isSource); + if (sourceIndex >= 0 && sourceIndex < static_cast<int>(sources->size()) && + sources->at(sourceIndex).m_iHasLock != LOCK_STATE_LOCKED && + sources->at(sourceIndex).m_allowSharing) + return true; + } + // Check auto-mounted sources + VECSOURCES sources; + CServiceBroker::GetMediaManager().GetRemovableDrives( + sources); // Sources returned always have m_allowsharing = true + //! @todo Make sharing of auto-mounted sources user configurable + int sourceIndex = CUtil::GetMatchingSource(realPath, sources, isSource); + if (sourceIndex >= 0 && sourceIndex < static_cast<int>(sources.size()) && + sources.at(sourceIndex).m_iHasLock != LOCK_STATE_LOCKED && + sources.at(sourceIndex).m_allowSharing) + return true; + + return false; +} + +CDateTime CFileUtils::GetModificationDate(const std::string& strFileNameAndPath, + const bool& bUseLatestDate) +{ + if (bUseLatestDate) + return GetModificationDate(1, strFileNameAndPath); + else + return GetModificationDate(0, strFileNameAndPath); +} + +CDateTime CFileUtils::GetModificationDate(const int& code, const std::string& strFileNameAndPath) +{ + CDateTime dateAdded; + if (strFileNameAndPath.empty()) + { + CLog::Log(LOGDEBUG, "{} empty strFileNameAndPath variable", __FUNCTION__); + return dateAdded; + } + + try + { + std::string file = strFileNameAndPath; + if (URIUtils::IsStack(strFileNameAndPath)) + file = CStackDirectory::GetFirstStackedFile(strFileNameAndPath); + + if (URIUtils::IsInArchive(file)) + file = CURL(file).GetHostName(); + + // Try to get ctime (creation on Windows, metadata change on Linux) and mtime (modification) + struct __stat64 buffer; + if (CFile::Stat(file, &buffer) == 0 && (buffer.st_mtime != 0 || buffer.st_ctime != 0)) + { + time_t now = time(NULL); + time_t addedTime; + // Prefer the modification time if it's valid, fallback to ctime + if (code == 0) + { + if (buffer.st_mtime != 0 && static_cast<time_t>(buffer.st_mtime) <= now) + addedTime = static_cast<time_t>(buffer.st_mtime); + else + addedTime = static_cast<time_t>(buffer.st_ctime); + } + // Use the later of the ctime and mtime + else if (code == 1) + { + addedTime = + std::max(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime)); + // if the newer of the two dates is in the future, we try it with the older one + if (addedTime > now) + addedTime = + std::min(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime)); + } + // Prefer the earliest of ctime and mtime, fallback to other + else + { + addedTime = + std::min(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime)); + // if the older of the two dates is invalid, we try it with the newer one + if (addedTime == 0) + addedTime = + std::max(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime)); + } + + + // make sure the datetime does is not in the future + if (addedTime <= now) + { + struct tm* time; +#ifdef HAVE_LOCALTIME_R + struct tm result = {}; + time = localtime_r(&addedTime, &result); +#else + time = localtime(&addedTime); +#endif + if (time) + dateAdded = *time; + } + } + } + catch (...) + { + CLog::Log(LOGERROR, "{} unable to extract modification date for file ({})", __FUNCTION__, + strFileNameAndPath); + } + return dateAdded; +} + +bool CFileUtils::CheckFileAccessAllowed(const std::string &filePath) +{ + // DENY access to paths matching + const std::vector<std::string> blacklist = { + "passwords.xml", + "sources.xml", + "guisettings.xml", + "advancedsettings.xml", + "server.key", + "/.ssh/", + }; + // ALLOW kodi paths + std::vector<std::string> whitelist = { + CSpecialProtocol::TranslatePath("special://home"), + CSpecialProtocol::TranslatePath("special://xbmc"), + CSpecialProtocol::TranslatePath("special://musicartistsinfo"), + }; + + auto kodiExtraWhitelist = CCompileInfo::GetWebserverExtraWhitelist(); + whitelist.insert(whitelist.end(), kodiExtraWhitelist.begin(), kodiExtraWhitelist.end()); + + // image urls come in the form of image://... sometimes with a / appended at the end + // and can be embedded in a music or video file image://music@... + // strip this off to get the real file path + bool isImage = false; + std::string decodePath = CURL::Decode(filePath); + size_t pos = decodePath.find("image://"); + if (pos != std::string::npos) + { + isImage = true; + decodePath.erase(pos, 8); + URIUtils::RemoveSlashAtEnd(decodePath); + if (StringUtils::StartsWith(decodePath, "music@") || StringUtils::StartsWith(decodePath, "video@")) + decodePath.erase(pos, 6); + } + + // check blacklist + for (const auto &b : blacklist) + { + if (decodePath.find(b) != std::string::npos) + { + CLog::Log(LOGERROR, "{} denied access to {}", __FUNCTION__, decodePath); + return false; + } + } + +#if defined(TARGET_POSIX) + std::string whiteEntry; + char *fullpath = realpath(decodePath.c_str(), nullptr); + + // if this is a locally existing file, check access permissions + if (fullpath) + { + const std::string realPath = fullpath; + free(fullpath); + + // check whitelist + for (const auto &w : whitelist) + { + char *realtemp = realpath(w.c_str(), nullptr); + if (realtemp) + { + whiteEntry = realtemp; + free(realtemp); + } + if (StringUtils::StartsWith(realPath, whiteEntry)) + return true; + } + // check sources with realPath + return CFileUtils::RemoteAccessAllowed(realPath); + } +#elif defined(TARGET_WINDOWS) + CURL url(decodePath); + if (url.GetProtocol().empty()) + { + std::wstring decodePathW; + g_charsetConverter.utf8ToW(decodePath, decodePathW, false); + CWIN32Util::AddExtraLongPathPrefix(decodePathW); + DWORD bufSize = GetFullPathNameW(decodePathW.c_str(), 0, nullptr, nullptr); + if (bufSize > 0) + { + std::wstring fullpathW; + fullpathW.resize(bufSize); + if (GetFullPathNameW(decodePathW.c_str(), bufSize, const_cast<wchar_t*>(fullpathW.c_str()), nullptr) <= bufSize - 1) + { + CWIN32Util::RemoveExtraLongPathPrefix(fullpathW); + std::string fullpath; + g_charsetConverter.wToUTF8(fullpathW, fullpath, false); + for (const std::string& whiteEntry : whitelist) + { + if (StringUtils::StartsWith(fullpath, whiteEntry)) + return true; + } + return CFileUtils::RemoteAccessAllowed(fullpath); + } + } + } +#endif + // if it isn't a local file, it must be a vfs entry + if (! isImage) + return CFileUtils::RemoteAccessAllowed(decodePath); + return true; +} + +bool CFileUtils::Exists(const std::string& strFileName, bool bUseCache) +{ + return CFile::Exists(strFileName, bUseCache); +} diff --git a/xbmc/utils/FileUtils.h b/xbmc/utils/FileUtils.h new file mode 100644 index 0000000..667d4b3 --- /dev/null +++ b/xbmc/utils/FileUtils.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <string> + +class CFileItem; +class CDateTime; + +class CFileUtils +{ +public: + static bool CheckFileAccessAllowed(const std::string &filePath); + static bool DeleteItem(const std::shared_ptr<CFileItem>& item); + static bool DeleteItem(const std::string &strPath); + static bool Exists(const std::string& strFileName, bool bUseCache = true); + static bool RenameFile(const std::string &strFile); + static bool RemoteAccessAllowed(const std::string &strPath); + static unsigned int LoadFile(const std::string &filename, void* &outputBuffer); + /*! \brief Get the modified date of a file if its invalid it returns the creation date - this behavior changes when you set bUseLatestDate + \param strFileNameAndPath path to the file + \param bUseLatestDate use the newer datetime of the files mtime and ctime + \return Returns the file date, can return a invalid date if problems occur + */ + static CDateTime GetModificationDate(const std::string& strFileNameAndPath, const bool& bUseLatestDate); + static CDateTime GetModificationDate(const int& code, const std::string& strFileNameAndPath); +}; diff --git a/xbmc/utils/FontUtils.cpp b/xbmc/utils/FontUtils.cpp new file mode 100644 index 0000000..4abf510 --- /dev/null +++ b/xbmc/utils/FontUtils.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FontUtils.h" + +#include "FileItem.h" +#include "StringUtils.h" +#include "URIUtils.h" +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "filesystem/SpecialProtocol.h" +#include "utils/CharsetConverter.h" +#include "utils/log.h" + +#include <ft2build.h> + +#include FT_FREETYPE_H +#include FT_SFNT_NAMES_H +#include FT_TRUETYPE_IDS_H + +using namespace XFILE; + +namespace +{ +// \brief Get font family from SFNT table entries +std::string GetFamilyNameFromSfnt(FT_Face face) +{ + std::string familyName; + + for (FT_UInt index = 0; index < FT_Get_Sfnt_Name_Count(face); ++index) + { + FT_SfntName name; + if (FT_Get_Sfnt_Name(face, index, &name) != 0) + { + CLog::LogF(LOGWARNING, "Failed to get SFNT name at index {}", index); + continue; + } + + // Get the unicode font family name (format-specific interface) + // In properties there may be one or more font family names encoded for each platform. + // NOTE: we give preference to MS/APPLE platform data, then fallback to MAC + // because has been found some fonts that provide names not convertible MAC text to UTF8 + if (name.name_id == TT_NAME_ID_FONT_FAMILY) + { + const std::string nameEnc{reinterpret_cast<const char*>(name.string), name.string_len}; + + if (name.platform_id == TT_PLATFORM_MICROSOFT || + name.platform_id == TT_PLATFORM_APPLE_UNICODE) + { + if (name.language_id != TT_MAC_LANGID_ENGLISH && + name.language_id != TT_MS_LANGID_ENGLISH_UNITED_STATES && + name.language_id != TT_MS_LANGID_ENGLISH_UNITED_KINGDOM) + continue; + + if (CCharsetConverter::utf16BEtoUTF8(nameEnc, familyName)) + break; // Stop here to prefer the name given with this platform + else + CLog::LogF(LOGERROR, "Failed to convert the font name string encoded as \"UTF-16BE\""); + } + else if (name.platform_id == TT_PLATFORM_MACINTOSH && familyName.empty()) + { + if (name.language_id != TT_MAC_LANGID_ENGLISH || name.encoding_id != TT_MAC_ID_ROMAN) + continue; + + if (!CCharsetConverter::MacintoshToUTF8(nameEnc, familyName)) + CLog::LogF(LOGERROR, "Failed to convert the font name string encoded as \"macintosh\""); + } + else + { + CLog::LogF(LOGERROR, "Unsupported font SFNT name platform \"{}\"", name.platform_id); + } + } + } + return familyName; +} +} // unnamed namespace + +std::string UTILS::FONT::GetFontFamily(std::vector<uint8_t>& buffer) +{ + FT_Library m_library{nullptr}; + FT_Init_FreeType(&m_library); + if (!m_library) + { + CLog::LogF(LOGERROR, "Unable to initialize freetype library"); + return ""; + } + + // Load the font face + FT_Face face; + std::string familyName; + if (FT_New_Memory_Face(m_library, reinterpret_cast<const FT_Byte*>(buffer.data()), buffer.size(), + 0, &face) == 0) + { + familyName = GetFamilyNameFromSfnt(face); + if (familyName.empty()) + { + CLog::LogF(LOGWARNING, "Failed to get the unicode family name for \"{}\", fallback to ASCII", + face->family_name); + // ASCII font family name may differ from the unicode one, use this as fallback only + familyName = std::string{face->family_name}; + if (familyName.empty()) + CLog::LogF(LOGERROR, "Family name missing in the font"); + } + } + else + { + CLog::LogF(LOGERROR, "Failed to process font memory buffer"); + } + + FT_Done_Face(face); + FT_Done_FreeType(m_library); + return familyName; +} + +std::string UTILS::FONT::GetFontFamily(const std::string& filepath) +{ + std::vector<uint8_t> buffer; + if (filepath.empty()) + return ""; + if (XFILE::CFile().LoadFile(filepath, buffer) <= 0) + { + CLog::LogF(LOGERROR, "Failed to load file {}", filepath); + return ""; + } + return GetFontFamily(buffer); +} + +bool UTILS::FONT::IsSupportedFontExtension(const std::string& filepath) +{ + return URIUtils::HasExtension(filepath, UTILS::FONT::SUPPORTED_EXTENSIONS_MASK); +} + +void UTILS::FONT::ClearTemporaryFonts() +{ + if (!CDirectory::Exists(UTILS::FONT::FONTPATH::TEMP)) + return; + + CFileItemList items; + CDirectory::GetDirectory(UTILS::FONT::FONTPATH::TEMP, items, + UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, + DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_BYPASS_CACHE | DIR_FLAG_GET_HIDDEN); + for (const auto& item : items) + { + if (item->m_bIsFolder) + continue; + + CFile::Delete(item->GetPath()); + } +} + +std::string UTILS::FONT::FONTPATH::GetSystemFontPath(const std::string& filename) +{ + std::string fontPath = URIUtils::AddFileToFolder( + CSpecialProtocol::TranslatePath(UTILS::FONT::FONTPATH::SYSTEM), filename); + if (XFILE::CFile::Exists(fontPath)) + { + return CSpecialProtocol::TranslatePath(fontPath); + } + + CLog::LogF(LOGERROR, "Could not find application system font {}", filename); + return ""; +} diff --git a/xbmc/utils/FontUtils.h b/xbmc/utils/FontUtils.h new file mode 100644 index 0000000..d4b92dd --- /dev/null +++ b/xbmc/utils/FontUtils.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stdint.h> +#include <string> +#include <vector> + +namespace UTILS +{ +namespace FONT +{ +constexpr const char* SUPPORTED_EXTENSIONS_MASK = ".ttf|.otf"; + +// The default application font +constexpr const char* FONT_DEFAULT_FILENAME = "arial.ttf"; + +namespace FONTPATH +{ +// Directory where Kodi bundled fonts files are located +constexpr const char* SYSTEM = "special://xbmc/media/Fonts/"; +// Directory where user defined fonts are located +constexpr const char* USER = "special://home/media/Fonts/"; +// Temporary font path (where MKV fonts are extracted and temporarily stored) +constexpr const char* TEMP = "special://temp/fonts/"; + +/*! + * \brief Provided a font filename returns the complete path for the font in + * the system font folder (if it exists) or an empty string otherwise + * \param filename The font file name + * \return The path for the font or an empty string if the path does not exist + */ +std::string GetSystemFontPath(const std::string& filename); +}; // namespace FONTPATH + +/*! + * \brief Get the font family name from a font file + * \param buffer The font data + * \return The font family name, otherwise empty if fails + */ +std::string GetFontFamily(std::vector<uint8_t>& buffer); + +/*! + * \brief Get the font family name from a font file + * \param filepath The path where read the font data + * \return The font family name, otherwise empty if fails + */ +std::string GetFontFamily(const std::string& filepath); + +/*! + * \brief Check if a filename have a supported font extension. + * \param filepath The font file path + * \return True if it has a supported extension, otherwise false + */ +bool IsSupportedFontExtension(const std::string& filepath); + +/*! + * \brief Removes all temporary fonts, e.g.those extract from MKV containers + * that are only available during playback + */ +void ClearTemporaryFonts(); + +} // namespace FONT +} // namespace UTILS diff --git a/xbmc/utils/GBMBufferObject.cpp b/xbmc/utils/GBMBufferObject.cpp new file mode 100644 index 0000000..90c4017 --- /dev/null +++ b/xbmc/utils/GBMBufferObject.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GBMBufferObject.h" + +#include "BufferObjectFactory.h" +#include "ServiceBroker.h" +#include "windowing/gbm/WinSystemGbmEGLContext.h" + +#include <gbm.h> +#include <unistd.h> + +using namespace KODI::WINDOWING::GBM; + +std::unique_ptr<CBufferObject> CGBMBufferObject::Create() +{ + return std::make_unique<CGBMBufferObject>(); +} + +void CGBMBufferObject::Register() +{ + CBufferObjectFactory::RegisterBufferObject(CGBMBufferObject::Create); +} + +CGBMBufferObject::CGBMBufferObject() +{ + m_device = + static_cast<CWinSystemGbmEGLContext*>(CServiceBroker::GetWinSystem())->GetGBMDevice()->Get(); +} + +CGBMBufferObject::~CGBMBufferObject() +{ + ReleaseMemory(); + DestroyBufferObject(); +} + +bool CGBMBufferObject::CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) +{ + if (m_fd >= 0) + return true; + + m_width = width; + m_height = height; + + m_bo = gbm_bo_create(m_device, m_width, m_height, format, GBM_BO_USE_LINEAR); + + if (!m_bo) + return false; + + m_fd = gbm_bo_get_fd(m_bo); + + return true; +} + +void CGBMBufferObject::DestroyBufferObject() +{ + close(m_fd); + + if (m_bo) + gbm_bo_destroy(m_bo); + + m_bo = nullptr; + m_fd = -1; +} + +uint8_t* CGBMBufferObject::GetMemory() +{ + if (m_bo) + { + m_map = static_cast<uint8_t*>(gbm_bo_map(m_bo, 0, 0, m_width, m_height, GBM_BO_TRANSFER_WRITE, &m_stride, &m_map_data)); + if (m_map) + return m_map; + } + + return nullptr; +} + +void CGBMBufferObject::ReleaseMemory() +{ + if (m_bo && m_map) + { + gbm_bo_unmap(m_bo, m_map_data); + m_map_data = nullptr; + m_map = nullptr; + } +} + +uint64_t CGBMBufferObject::GetModifier() +{ +#if defined(HAS_GBM_MODIFIERS) + return gbm_bo_get_modifier(m_bo); +#else + return 0; +#endif +} diff --git a/xbmc/utils/GBMBufferObject.h b/xbmc/utils/GBMBufferObject.h new file mode 100644 index 0000000..ae8de58 --- /dev/null +++ b/xbmc/utils/GBMBufferObject.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/BufferObject.h" + +#include <memory> +#include <stdint.h> + +struct gbm_bo; +struct gbm_device; + +class CGBMBufferObject : public CBufferObject +{ +public: + CGBMBufferObject(); + ~CGBMBufferObject() override; + + // Registration + static std::unique_ptr<CBufferObject> Create(); + static void Register(); + + // IBufferObject overrides via CBufferObject + bool CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) override; + void DestroyBufferObject() override; + uint8_t* GetMemory() override; + void ReleaseMemory() override; + std::string GetName() const override { return "CGBMBufferObject"; } + + // CBufferObject overrides + uint64_t GetModifier() override; + +private: + gbm_device* m_device{nullptr}; + gbm_bo* m_bo{nullptr}; + + uint32_t m_width{0}; + uint32_t m_height{0}; + + uint8_t* m_map{nullptr}; + void* m_map_data{nullptr}; +}; diff --git a/xbmc/utils/GLUtils.cpp b/xbmc/utils/GLUtils.cpp new file mode 100644 index 0000000..df8921e --- /dev/null +++ b/xbmc/utils/GLUtils.cpp @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GLUtils.h" + +#include "ServiceBroker.h" +#include "log.h" +#include "rendering/MatrixGL.h" +#include "rendering/RenderSystem.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" + +#include <map> +#include <stdexcept> +#include <utility> + +namespace +{ + +#define X(VAL) std::make_pair(VAL, #VAL) +std::map<GLenum, const char*> glErrors = +{ + // please keep attributes in accordance to: + // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetError.xhtml + X(GL_NO_ERROR), + X(GL_INVALID_ENUM), + X(GL_INVALID_VALUE), + X(GL_INVALID_OPERATION), + X(GL_INVALID_FRAMEBUFFER_OPERATION), + X(GL_OUT_OF_MEMORY), +#if defined(HAS_GL) + X(GL_STACK_UNDERFLOW), + X(GL_STACK_OVERFLOW), +#endif +}; + +std::map<GLenum, const char*> glErrorSource = { +#if defined(HAS_GLES) && defined(TARGET_LINUX) + X(GL_DEBUG_SOURCE_API_KHR), + X(GL_DEBUG_SOURCE_WINDOW_SYSTEM_KHR), + X(GL_DEBUG_SOURCE_SHADER_COMPILER_KHR), + X(GL_DEBUG_SOURCE_THIRD_PARTY_KHR), + X(GL_DEBUG_SOURCE_APPLICATION_KHR), + X(GL_DEBUG_SOURCE_OTHER_KHR), +#endif +}; + +std::map<GLenum, const char*> glErrorType = { +#if defined(HAS_GLES) && defined(TARGET_LINUX) + X(GL_DEBUG_TYPE_ERROR_KHR), + X(GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR), + X(GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR), + X(GL_DEBUG_TYPE_PORTABILITY_KHR), + X(GL_DEBUG_TYPE_PERFORMANCE_KHR), + X(GL_DEBUG_TYPE_OTHER_KHR), + X(GL_DEBUG_TYPE_MARKER_KHR), +#endif +}; + +std::map<GLenum, const char*> glErrorSeverity = { +#if defined(HAS_GLES) && defined(TARGET_LINUX) + X(GL_DEBUG_SEVERITY_HIGH_KHR), + X(GL_DEBUG_SEVERITY_MEDIUM_KHR), + X(GL_DEBUG_SEVERITY_LOW_KHR), + X(GL_DEBUG_SEVERITY_NOTIFICATION_KHR), +#endif +}; +#undef X + +} // namespace + +void KODI::UTILS::GL::GlErrorCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) +{ + std::string sourceStr; + std::string typeStr; + std::string severityStr; + + auto glSource = glErrorSource.find(source); + if (glSource != glErrorSource.end()) + { + sourceStr = glSource->second; + } + + auto glType = glErrorType.find(type); + if (glType != glErrorType.end()) + { + typeStr = glType->second; + } + + auto glSeverity = glErrorSeverity.find(severity); + if (glSeverity != glErrorSeverity.end()) + { + severityStr = glSeverity->second; + } + + CLog::Log(LOGDEBUG, "OpenGL(ES) Debugging:\nSource: {}\nType: {}\nSeverity: {}\nID: {}\nMessage: {}", sourceStr, typeStr, severityStr, id, message); +} + +static void PrintMatrix(const GLfloat* matrix, const std::string& matrixName) +{ + CLog::Log(LOGDEBUG, "{}:\n{:> 10.3f} {:> 10.3f} {:> 10.3f} {:> 10.3f}\n{:> 10.3f} {:> 10.3f} {:> 10.3f} {:> 10.3f}\n{:> 10.3f} {:> 10.3f} {:> 10.3f} {:> 10.3f}\n{:> 10.3f} {:> 10.3f} {:> 10.3f} {:> 10.3f}", + matrixName, + matrix[0], matrix[1], matrix[2], matrix[3], + matrix[4], matrix[5], matrix[6], matrix[7], + matrix[8], matrix[9], matrix[10], matrix[11], + matrix[12], matrix[13], matrix[14], matrix[15]); +} + +void _VerifyGLState(const char* szfile, const char* szfunction, int lineno) +{ + GLenum err = glGetError(); + if (err == GL_NO_ERROR) + { + return; + } + + auto error = glErrors.find(err); + if (error != glErrors.end()) + { + CLog::Log(LOGERROR, "GL(ES) ERROR: {}", error->second); + } + + if (szfile && szfunction) + { + CLog::Log(LOGERROR, "In file: {} function: {} line: {}", szfile, szfunction, lineno); + } + + GLboolean scissors; + glGetBooleanv(GL_SCISSOR_TEST, &scissors); + CLog::Log(LOGDEBUG, "Scissor test enabled: {}", scissors == GL_TRUE ? "True" : "False"); + + GLfloat matrix[16]; + glGetFloatv(GL_SCISSOR_BOX, matrix); + CLog::Log(LOGDEBUG, "Scissor box: {}, {}, {}, {}", matrix[0], matrix[1], matrix[2], matrix[3]); + + glGetFloatv(GL_VIEWPORT, matrix); + CLog::Log(LOGDEBUG, "Viewport: {}, {}, {}, {}", matrix[0], matrix[1], matrix[2], matrix[3]); + + PrintMatrix(glMatrixProject.Get(), "Projection Matrix"); + PrintMatrix(glMatrixModview.Get(), "Modelview Matrix"); +} + +void LogGraphicsInfo() +{ +#if defined(HAS_GL) || defined(HAS_GLES) + const char* s; + + s = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); + if (s) + CLog::Log(LOGINFO, "GL_VENDOR = {}", s); + else + CLog::Log(LOGINFO, "GL_VENDOR = NULL"); + + s = reinterpret_cast<const char*>(glGetString(GL_RENDERER)); + if (s) + CLog::Log(LOGINFO, "GL_RENDERER = {}", s); + else + CLog::Log(LOGINFO, "GL_RENDERER = NULL"); + + s = reinterpret_cast<const char*>(glGetString(GL_VERSION)); + if (s) + CLog::Log(LOGINFO, "GL_VERSION = {}", s); + else + CLog::Log(LOGINFO, "GL_VERSION = NULL"); + + s = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION)); + if (s) + CLog::Log(LOGINFO, "GL_SHADING_LANGUAGE_VERSION = {}", s); + else + CLog::Log(LOGINFO, "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 (CServiceBroker::GetRenderSystem()->IsExtSupported("GL_NVX_gpu_memory_info")) + { + GLint mem = 0; + + glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &mem); + CLog::Log(LOGINFO, "GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX = {}", mem); + + //this seems to be the amount of ram on the videocard + glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &mem); + CLog::Log(LOGINFO, "GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX = {}", mem); + } + + std::string extensions; +#if defined(HAS_GL) + unsigned int renderVersionMajor, renderVersionMinor; + CServiceBroker::GetRenderSystem()->GetRenderVersion(renderVersionMajor, renderVersionMinor); + if (renderVersionMajor > 3 || + (renderVersionMajor == 3 && renderVersionMinor >= 2)) + { + GLint n; + glGetIntegerv(GL_NUM_EXTENSIONS, &n); + if (n > 0) + { + GLint i; + for (i = 0; i < n; i++) + { + extensions += (const char*)glGetStringi(GL_EXTENSIONS, i); + extensions += " "; + } + } + } + else +#endif + { + extensions += (const char*) glGetString(GL_EXTENSIONS); + } + + if (!extensions.empty()) + CLog::Log(LOGINFO, "GL_EXTENSIONS = {}", extensions); + else + CLog::Log(LOGINFO, "GL_EXTENSIONS = NULL"); + + +#else /* !HAS_GL */ + CLog::Log(LOGINFO, "Please define LogGraphicsInfo for your chosen graphics library"); +#endif /* !HAS_GL */ +} + +int KODI::UTILS::GL::glFormatElementByteCount(GLenum format) +{ + switch (format) + { +#ifdef HAS_GL + case GL_BGRA: + return 4; + case GL_RED: + return 1; + case GL_GREEN: + return 1; + case GL_RG: + return 2; + case GL_BGR: + return 3; +#endif + case GL_RGBA: + return 4; + 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 {}", format); + return 1; + } +} + +uint8_t KODI::UTILS::GL::GetChannelFromARGB(const KODI::UTILS::GL::ColorChannel colorChannel, + const uint32_t argb) +{ + switch (colorChannel) + { + case KODI::UTILS::GL::ColorChannel::A: + return (argb >> 24) & 0xFF; + case KODI::UTILS::GL::ColorChannel::R: + return (argb >> 16) & 0xFF; + case KODI::UTILS::GL::ColorChannel::G: + return (argb >> 8) & 0xFF; + case KODI::UTILS::GL::ColorChannel::B: + return (argb >> 0) & 0xFF; + default: + throw std::runtime_error("KODI::UTILS::GL::GetChannelFromARGB: ColorChannel not handled"); + }; +} diff --git a/xbmc/utils/GLUtils.h b/xbmc/utils/GLUtils.h new file mode 100644 index 0000000..5c36030 --- /dev/null +++ b/xbmc/utils/GLUtils.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +// 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_gl.h" + +namespace KODI +{ +namespace UTILS +{ +namespace GL +{ +void GlErrorCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam); + +int glFormatElementByteCount(GLenum format); + +enum class ColorChannel +{ + A, + R, + G, + B, +}; + +uint8_t GetChannelFromARGB(const ColorChannel colorChannel, const uint32_t argb); +} +} +} + +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(); diff --git a/xbmc/utils/Geometry.h b/xbmc/utils/Geometry.h new file mode 100644 index 0000000..878905b --- /dev/null +++ b/xbmc/utils/Geometry.h @@ -0,0 +1,484 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#ifdef __GNUC__ +// under gcc, inline will only take place if optimizations are applied (-O). this will force inline even with optimizations. +#define XBMC_FORCE_INLINE __attribute__((always_inline)) +#else +#define XBMC_FORCE_INLINE +#endif + +#include <algorithm> +#include <stdexcept> +#include <vector> + +template <typename T> class CPointGen +{ +public: + typedef CPointGen<T> this_type; + + CPointGen() noexcept = default; + + constexpr CPointGen(T a, T b) + : x{a}, y{b} + {} + + template<class U> explicit constexpr CPointGen(const CPointGen<U>& rhs) + : x{static_cast<T> (rhs.x)}, y{static_cast<T> (rhs.y)} + {} + + constexpr this_type operator+(const this_type &point) const + { + return {x + point.x, y + point.y}; + }; + + this_type& operator+=(const this_type &point) + { + x += point.x; + y += point.y; + return *this; + }; + + constexpr this_type operator-(const this_type &point) const + { + return {x - point.x, y - point.y}; + }; + + this_type& operator-=(const this_type &point) + { + x -= point.x; + y -= point.y; + return *this; + }; + + constexpr this_type operator*(T factor) const + { + return {x * factor, y * factor}; + } + + this_type& operator*=(T factor) + { + x *= factor; + y *= factor; + return *this; + } + + constexpr this_type operator/(T factor) const + { + return {x / factor, y / factor}; + } + + this_type& operator/=(T factor) + { + x /= factor; + y /= factor; + return *this; + } + + T x{}, y{}; +}; + +template<typename T> +constexpr bool operator==(const CPointGen<T> &point1, const CPointGen<T> &point2) noexcept +{ + return (point1.x == point2.x && point1.y == point2.y); +} + +template<typename T> +constexpr bool operator!=(const CPointGen<T> &point1, const CPointGen<T> &point2) noexcept +{ + return !(point1 == point2); +} + +using CPoint = CPointGen<float>; +using CPointInt = CPointGen<int>; + + +/** + * Generic two-dimensional size representation + * + * Class invariant: width and height are both non-negative + * Throws std::out_of_range if invariant would be violated. The class + * is exception-safe. If modification would violate the invariant, the size + * is not changed. + */ +template <typename T> class CSizeGen +{ + T m_w{}, m_h{}; + + void CheckSet(T width, T height) + { + if (width < 0) + { + throw std::out_of_range("Size may not have negative width"); + } + if (height < 0) + { + throw std::out_of_range("Size may not have negative height"); + } + m_w = width; + m_h = height; + } + +public: + typedef CSizeGen<T> this_type; + + CSizeGen() noexcept = default; + + CSizeGen(T width, T height) + { + CheckSet(width, height); + } + + T Width() const + { + return m_w; + } + + T Height() const + { + return m_h; + } + + void SetWidth(T width) + { + CheckSet(width, m_h); + } + + void SetHeight(T height) + { + CheckSet(m_w, height); + } + + void Set(T width, T height) + { + CheckSet(width, height); + } + + bool IsZero() const + { + return (m_w == static_cast<T> (0) && m_h == static_cast<T> (0)); + } + + T Area() const + { + return m_w * m_h; + } + + CPointGen<T> ToPoint() const + { + return {m_w, m_h}; + } + + template<class U> explicit CSizeGen<T>(const CSizeGen<U>& rhs) + { + CheckSet(static_cast<T> (rhs.m_w), static_cast<T> (rhs.m_h)); + } + + this_type operator+(const this_type& size) const + { + return {m_w + size.m_w, m_h + size.m_h}; + }; + + this_type& operator+=(const this_type& size) + { + CheckSet(m_w + size.m_w, m_h + size.m_h); + return *this; + }; + + this_type operator-(const this_type& size) const + { + return {m_w - size.m_w, m_h - size.m_h}; + }; + + this_type& operator-=(const this_type& size) + { + CheckSet(m_w - size.m_w, m_h - size.m_h); + return *this; + }; + + this_type operator*(T factor) const + { + return {m_w * factor, m_h * factor}; + } + + this_type& operator*=(T factor) + { + CheckSet(m_w * factor, m_h * factor); + return *this; + } + + this_type operator/(T factor) const + { + return {m_w / factor, m_h / factor}; + } + + this_type& operator/=(T factor) + { + CheckSet(m_w / factor, m_h / factor); + return *this; + } +}; + +template<typename T> +inline bool operator==(const CSizeGen<T>& size1, const CSizeGen<T>& size2) noexcept +{ + return (size1.Width() == size2.Width() && size1.Height() == size2.Height()); +} + +template<typename T> +inline bool operator!=(const CSizeGen<T>& size1, const CSizeGen<T>& size2) noexcept +{ + return !(size1 == size2); +} + +using CSize = CSizeGen<float>; +using CSizeInt = CSizeGen<int>; + + +template <typename T> class CRectGen +{ +public: + typedef CRectGen<T> this_type; + typedef CPointGen<T> point_type; + typedef CSizeGen<T> size_type; + + CRectGen() noexcept = default; + + constexpr CRectGen(T left, T top, T right, T bottom) + : x1{left}, y1{top}, x2{right}, y2{bottom} + {} + + constexpr CRectGen(const point_type &p1, const point_type &p2) + : x1{p1.x}, y1{p1.y}, x2{p2.x}, y2{p2.y} + {} + + constexpr CRectGen(const point_type &origin, const size_type &size) + : x1{origin.x}, y1{origin.y}, x2{x1 + size.Width()}, y2{y1 + size.Height()} + {} + + template<class U> explicit constexpr CRectGen(const CRectGen<U>& rhs) + : x1{static_cast<T> (rhs.x1)}, y1{static_cast<T> (rhs.y1)}, x2{static_cast<T> (rhs.x2)}, y2{static_cast<T> (rhs.y2)} + {} + + void SetRect(T left, T top, T right, T bottom) + { + x1 = left; + y1 = top; + x2 = right; + y2 = bottom; + } + + constexpr bool PtInRect(const point_type &point) const + { + return (x1 <= point.x && point.x <= x2 && y1 <= point.y && point.y <= y2); + }; + + this_type& operator-=(const point_type &point) XBMC_FORCE_INLINE + { + x1 -= point.x; + y1 -= point.y; + x2 -= point.x; + y2 -= point.y; + return *this; + }; + + constexpr this_type operator-(const point_type &point) const + { + return {x1 - point.x, y1 - point.y, x2 - point.x, y2 - point.y}; + } + + this_type& operator+=(const point_type &point) XBMC_FORCE_INLINE + { + x1 += point.x; + y1 += point.y; + x2 += point.x; + y2 += point.y; + return *this; + }; + + constexpr this_type operator+(const point_type &point) const + { + return {x1 + point.x, y1 + point.y, x2 + point.x, y2 + point.y}; + } + + this_type& operator-=(const size_type &size) + { + x2 -= size.Width(); + y2 -= size.Height(); + return *this; + }; + + constexpr this_type operator-(const size_type &size) const + { + return {x1, y1, x2 - size.Width(), y2 - size.Height()}; + } + + this_type& operator+=(const size_type &size) + { + x2 += size.Width(); + y2 += size.Height(); + return *this; + }; + + constexpr this_type operator+(const size_type &size) const + { + return {x1, y1, x2 + size.Width(), y2 + size.Height()}; + } + + this_type& Intersect(const this_type &rect) + { + x1 = clamp_range(x1, rect.x1, rect.x2); + x2 = clamp_range(x2, rect.x1, rect.x2); + y1 = clamp_range(y1, rect.y1, rect.y2); + y2 = clamp_range(y2, rect.y1, rect.y2); + return *this; + }; + + this_type& Union(const this_type &rect) + { + if (IsEmpty()) + *this = rect; + else if (!rect.IsEmpty()) + { + x1 = std::min(x1,rect.x1); + y1 = std::min(y1,rect.y1); + + x2 = std::max(x2,rect.x2); + y2 = std::max(y2,rect.y2); + } + + return *this; + }; + + constexpr bool IsEmpty() const XBMC_FORCE_INLINE + { + return (x2 - x1) * (y2 - y1) == 0; + }; + + constexpr point_type P1() const XBMC_FORCE_INLINE + { + return {x1, y1}; + } + + constexpr point_type P2() const XBMC_FORCE_INLINE + { + return {x2, y2}; + } + + constexpr T Width() const XBMC_FORCE_INLINE + { + return x2 - x1; + }; + + constexpr T Height() const XBMC_FORCE_INLINE + { + return y2 - y1; + }; + + constexpr T Area() const XBMC_FORCE_INLINE + { + return Width() * Height(); + }; + + size_type ToSize() const + { + return {Width(), Height()}; + }; + + std::vector<this_type> SubtractRect(this_type splitterRect) + { + std::vector<this_type> newRectanglesList; + this_type intersection = splitterRect.Intersect(*this); + + if (!intersection.IsEmpty()) + { + this_type add; + + // add rect above intersection if not empty + add = this_type(x1, y1, x2, intersection.y1); + if (!add.IsEmpty()) + newRectanglesList.push_back(add); + + // add rect below intersection if not empty + add = this_type(x1, intersection.y2, x2, y2); + if (!add.IsEmpty()) + newRectanglesList.push_back(add); + + // add rect left intersection if not empty + add = this_type(x1, intersection.y1, intersection.x1, intersection.y2); + if (!add.IsEmpty()) + newRectanglesList.push_back(add); + + // add rect right intersection if not empty + add = this_type(intersection.x2, intersection.y1, x2, intersection.y2); + if (!add.IsEmpty()) + newRectanglesList.push_back(add); + } + else + { + newRectanglesList.push_back(*this); + } + + return newRectanglesList; + } + + std::vector<this_type> SubtractRects(std::vector<this_type> intersectionList) + { + std::vector<this_type> fragmentsList; + fragmentsList.push_back(*this); + + for (typename std::vector<this_type>::iterator splitter = intersectionList.begin(); splitter != intersectionList.end(); ++splitter) + { + typename std::vector<this_type> toAddList; + + for (typename std::vector<this_type>::iterator fragment = fragmentsList.begin(); fragment != fragmentsList.end(); ++fragment) + { + std::vector<this_type> newFragmentsList = fragment->SubtractRect(*splitter); + toAddList.insert(toAddList.end(), newFragmentsList.begin(), newFragmentsList.end()); + } + + fragmentsList.clear(); + fragmentsList.insert(fragmentsList.end(), toAddList.begin(), toAddList.end()); + } + + return fragmentsList; + } + + void GetQuad(point_type (&points)[4]) + { + points[0] = { x1, y1 }; + points[1] = { x2, y1 }; + points[2] = { x2, y2 }; + points[3] = { x1, y2 }; + } + + T x1{}, y1{}, x2{}, y2{}; +private: + static constexpr T clamp_range(T x, T l, T h) XBMC_FORCE_INLINE + { + return (x > h) ? h : ((x < l) ? l : x); + } +}; + +template<typename T> +constexpr bool operator==(const CRectGen<T> &rect1, const CRectGen<T> &rect2) noexcept +{ + return (rect1.x1 == rect2.x1 && rect1.y1 == rect2.y1 && rect1.x2 == rect2.x2 && rect1.y2 == rect2.y2); +} + +template<typename T> +constexpr bool operator!=(const CRectGen<T> &rect1, const CRectGen<T> &rect2) noexcept +{ + return !(rect1 == rect2); +} + +using CRect = CRectGen<float>; +using CRectInt = CRectGen<int>; diff --git a/xbmc/utils/GlobalsHandling.h b/xbmc/utils/GlobalsHandling.h new file mode 100644 index 0000000..a51cc08 --- /dev/null +++ b/xbmc/utils/GlobalsHandling.h @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> + +/** + * 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 possibility + * 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 std::shared_ptr to access the global variable. + * + * This would be the preferred means since it is (very slightly) more efficient 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 std::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 std::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 deterministically 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<std::shared_ptr<T> > instance; + + /** + * See 'getQuick' below. + */ + static T* quick; + public: + + /** + * Retrieve an instance of the singleton using a shared pointer for + * reference counting. + */ + inline static std::shared_ptr<T> getInstance() + { + if (!instance.guarded) + { + if (!quick) + quick = new T; + instance.guarded = new std::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<std::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: explicit 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 std::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())) diff --git a/xbmc/utils/GroupUtils.cpp b/xbmc/utils/GroupUtils.cpp new file mode 100644 index 0000000..a51399f --- /dev/null +++ b/xbmc/utils/GroupUtils.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GroupUtils.h" + +#include "FileItem.h" +#include "filesystem/MultiPathDirectory.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "video/VideoDbUrl.h" +#include "video/VideoInfoTag.h" + +#include <map> +#include <set> + +using SetMap = std::map<int, std::set<CFileItemPtr> >; + +bool GroupUtils::Group(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItems, GroupAttribute groupAttributes /* = GroupAttributeNone */) +{ + CFileItemList ungroupedItems; + return Group(groupBy, baseDir, items, groupedItems, ungroupedItems, groupAttributes); +} + +bool GroupUtils::Group(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItems, CFileItemList &ungroupedItems, 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 ungrouped = true; + const CFileItemPtr item = items.Get(index); + + // group by sets + if ((groupBy & GroupBySet) && + item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_set.id > 0) + { + ungrouped = false; + setMap[item->GetVideoInfoTag()->m_set.id].insert(item); + } + + if (ungrouped) + ungroupedItems.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 add it to the ungrouped items + if (set->second.size() == 1 && (groupAttributes & GroupAttributeIgnoreSingleItems)) + { + ungroupedItems.Add(*set->second.begin()); + continue; + } + + CFileItemPtr pItem(new CFileItem((*set->second.begin())->GetVideoInfoTag()->m_set.title)); + pItem->GetVideoInfoTag()->m_iDbId = set->first; + pItem->GetVideoInfoTag()->m_type = MediaTypeVideoCollection; + + std::string basePath = StringUtils::Format("videodb://movies/sets/{}/", set->first); + CVideoDbUrl videoUrl; + if (!videoUrl.FromString(basePath)) + pItem->SetPath(basePath); + else + { + videoUrl.AddOptions((*set->second.begin())->GetURL().GetOptions()); + pItem->SetPath(videoUrl.ToString()); + } + pItem->m_bIsFolder = true; + + CVideoInfoTag* setInfo = pItem->GetVideoInfoTag(); + setInfo->m_strPath = pItem->GetPath(); + setInfo->m_strTitle = pItem->GetLabel(); + setInfo->m_strPlot = (*set->second.begin())->GetVideoInfoTag()->m_set.overview; + + int ratings = 0; + float totalRatings = 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->GetRating().rating > 0.0f) + { + ratings++; + totalRatings += movieInfo->GetRating().rating; + } + + // handle year + if (movieInfo->GetYear() > setInfo->GetYear()) + setInfo->SetYear(movieInfo->GetYear()); + + // 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->SetPlayCount(setInfo->GetPlayCount() + movieInfo->GetPlayCount()); + if (movieInfo->GetPlayCount() > 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 > 0) + pItem->GetVideoInfoTag()->SetRating(totalRatings / ratings); + + setInfo->SetPlayCount(iWatched >= static_cast<int>(set->second.size()) ? (setInfo->GetPlayCount() / 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->GetPlayCount() > 0); + + groupedItems.Add(pItem); + } + } + + return true; +} + +bool GroupUtils::GroupAndMix(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItemsMixed, GroupAttribute groupAttributes /* = GroupAttributeNone */) +{ + CFileItemList ungroupedItems; + if (!Group(groupBy, baseDir, items, groupedItemsMixed, ungroupedItems, groupAttributes)) + return false; + + // add all the ungrouped items as well + groupedItemsMixed.Append(ungroupedItems); + + return true; +} diff --git a/xbmc/utils/GroupUtils.h b/xbmc/utils/GroupUtils.h new file mode 100644 index 0000000..2ea7083 --- /dev/null +++ b/xbmc/utils/GroupUtils.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +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 Group(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItems, CFileItemList &ungroupedItems, GroupAttribute groupAttributes = GroupAttributeNone); + static bool GroupAndMix(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItemsMixed, GroupAttribute groupAttributes = GroupAttributeNone); +}; diff --git a/xbmc/utils/HDRCapabilities.h b/xbmc/utils/HDRCapabilities.h new file mode 100644 index 0000000..4802b78 --- /dev/null +++ b/xbmc/utils/HDRCapabilities.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class CHDRCapabilities +{ +public: + CHDRCapabilities() = default; + ~CHDRCapabilities() = default; + + bool SupportsHDR10() const { return m_hdr10; } + bool SupportsHLG() const { return m_hlg; } + bool SupportsHDR10Plus() const { return m_hdr10_plus; } + bool SupportsDolbyVision() const { return m_dolby_vision; } + + void SetHDR10() { m_hdr10 = true; } + void SetHLG() { m_hlg = true; } + void SetHDR10Plus() { m_hdr10_plus = true; } + void SetDolbyVision() { m_dolby_vision = true; } + +private: + bool m_hdr10 = false; + bool m_hlg = false; + bool m_hdr10_plus = false; + bool m_dolby_vision = false; +}; diff --git a/xbmc/utils/HTMLUtil.cpp b/xbmc/utils/HTMLUtil.cpp new file mode 100644 index 0000000..8687ffe --- /dev/null +++ b/xbmc/utils/HTMLUtil.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "HTMLUtil.h" + +#include "utils/StringUtils.h" + +#include <wctype.h> + +using namespace HTML; + +CHTMLUtil::CHTMLUtil(void) = default; + +CHTMLUtil::~CHTMLUtil(void) = default; + +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.empty()) + { + 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"&#{};", num); + else + num = StringUtils::Format(L"&#x{};", num); + + StringUtils::Replace(strStripped, num,std::wstring(1,val)); + iPos = strStripped.find(L"&#", iStart); + } +} + diff --git a/xbmc/utils/HTMLUtil.h b/xbmc/utils/HTMLUtil.h new file mode 100644 index 0000000..5295a9c --- /dev/null +++ b/xbmc/utils/HTMLUtil.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +namespace HTML +{ +class CHTMLUtil +{ +public: + CHTMLUtil(void); + virtual ~CHTMLUtil(void); + static void RemoveTags(std::string& strHTML); + static void ConvertHTMLToW(const std::wstring& strHTML, std::wstring& strStripped); +}; +} diff --git a/xbmc/utils/HttpHeader.cpp b/xbmc/utils/HttpHeader.cpp new file mode 100644 index 0000000..ad73bb2 --- /dev/null +++ b/xbmc/utils/HttpHeader.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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() = default; + +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=' + size_t 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/xbmc/utils/HttpHeader.h b/xbmc/utils/HttpHeader.h new file mode 100644 index 0000000..6d8b543 --- /dev/null +++ b/xbmc/utils/HttpHeader.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> +#include <utility> +#include <vector> + +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/xbmc/utils/HttpParser.cpp b/xbmc/utils/HttpParser.cpp new file mode 100644 index 0000000..9276d4c --- /dev/null +++ b/xbmc/utils/HttpParser.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + * + * 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 + */ + +#include "HttpParser.h" + +HttpParser::~HttpParser() = default; + +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 (const FSM& f : fsm) { + if ( f.curState == _state && + ( c == f.c || f.c == ANY ) ) { + + nextState = f.nextState; + + if ( f.actions & LOWER ) { + _data[i] = tolower( _data[i] ); + } + + if ( f.actions & NULLIFY ) { + _data[i] = 0; + } + + if ( f.actions & SET_HEADER_START ) { + _headerStart = i; + } + + if ( f.actions & SET_KEY ) { + _keyIndex = i; + } + + if ( f.actions & SET_VALUE ) { + _valueIndex = i; + } + + if ( f.actions & SET_CONTENT_START ) { + _contentStart = i + 1; + } + + if ( f.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/xbmc/utils/HttpParser.h b/xbmc/utils/HttpParser.h new file mode 100644 index 0000000..47a3a9f --- /dev/null +++ b/xbmc/utils/HttpParser.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + * + * 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 + */ + +#pragma once + +#include <stdlib.h> +#include <string.h> +#include <string> +#include <vector> + +// 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(); + + 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 = 0; + unsigned _parsedTo = 0 ; + int _state = 0 ; + unsigned _keyIndex = 0; + unsigned _valueIndex = 0; + unsigned _contentLength = 0; + unsigned _contentStart = 0; + unsigned _uriIndex = 0; + + 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 = Incomplete ; +}; + diff --git a/xbmc/utils/HttpRangeUtils.cpp b/xbmc/utils/HttpRangeUtils.cpp new file mode 100644 index 0000000..18ad32b --- /dev/null +++ b/xbmc/utils/HttpRangeUtils.cpp @@ -0,0 +1,424 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include <algorithm> + +#include "HttpRangeUtils.h" +#include "Util.h" +#ifdef HAS_WEB_SERVER +#include "network/httprequesthandler/IHTTPRequestHandler.h" +#endif // HAS_WEB_SERVER +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <inttypes.h> + +#define HEADER_NEWLINE "\r\n" +#define HEADER_SEPARATOR HEADER_NEWLINE HEADER_NEWLINE +#define HEADER_BOUNDARY "--" + +#define HEADER_CONTENT_RANGE_VALUE "{}" +#define HEADER_CONTENT_RANGE_VALUE_UNKNOWN "*" +#define HEADER_CONTENT_RANGE_FORMAT_BYTES "bytes " HEADER_CONTENT_RANGE_VALUE "-" HEADER_CONTENT_RANGE_VALUE "/" +#define CONTENT_RANGE_FORMAT_TOTAL HEADER_CONTENT_RANGE_FORMAT_BYTES HEADER_CONTENT_RANGE_VALUE +#define CONTENT_RANGE_FORMAT_TOTAL_UNKNOWN HEADER_CONTENT_RANGE_FORMAT_BYTES HEADER_CONTENT_RANGE_VALUE_UNKNOWN + +CHttpRange::CHttpRange(uint64_t firstPosition, uint64_t lastPosition) + : m_first(firstPosition), + m_last(lastPosition) +{ } + +bool CHttpRange::operator<(const CHttpRange &other) const +{ + return (m_first < other.m_first) || + (m_first == other.m_first && m_last < other.m_last); +} + +bool CHttpRange::operator==(const CHttpRange &other) const +{ + return m_first == other.m_first && m_last == other.m_last; +} + +bool CHttpRange::operator!=(const CHttpRange &other) const +{ + return !(*this == other); +} + +uint64_t CHttpRange::GetLength() const +{ + if (!IsValid()) + return 0; + + return m_last - m_first + 1; +} + +void CHttpRange::SetLength(uint64_t length) +{ + m_last = m_first + length - 1; +} + +bool CHttpRange::IsValid() const +{ + return m_last >= m_first; +} + +CHttpResponseRange::CHttpResponseRange() + : CHttpRange(), + m_data(NULL) +{ } + +CHttpResponseRange::CHttpResponseRange(uint64_t firstPosition, uint64_t lastPosition) + : CHttpRange(firstPosition, lastPosition), + m_data(NULL) +{ } + +CHttpResponseRange::CHttpResponseRange(const void* data, uint64_t firstPosition, uint64_t lastPosition) + : CHttpRange(firstPosition, lastPosition), + m_data(data) +{ } + +CHttpResponseRange::CHttpResponseRange(const void* data, uint64_t length) + : CHttpRange(0, length - 1), + m_data(data) +{ } + +bool CHttpResponseRange::operator==(const CHttpResponseRange &other) const +{ + if (!CHttpRange::operator==(other)) + return false; + + return m_data == other.m_data; +} + +bool CHttpResponseRange::operator!=(const CHttpResponseRange &other) const +{ + return !(*this == other); +} + +void CHttpResponseRange::SetData(const void* data, uint64_t length) +{ + if (length == 0) + return; + + m_first = 0; + + SetData(data); + SetLength(length); +} + +void CHttpResponseRange::SetData(const void* data, uint64_t firstPosition, uint64_t lastPosition) +{ + SetData(data); + SetFirstPosition(firstPosition); + SetLastPosition(lastPosition); +} + +bool CHttpResponseRange::IsValid() const +{ + if (!CHttpRange::IsValid()) + return false; + + return m_data != NULL; +} + +CHttpRanges::CHttpRanges() +: m_ranges() +{ } + +CHttpRanges::CHttpRanges(const HttpRanges& httpRanges) +: m_ranges(httpRanges) +{ + SortAndCleanup(); +} + +bool CHttpRanges::Get(size_t index, CHttpRange& range) const +{ + if (index >= Size()) + return false; + + range = m_ranges.at(index); + return true; +} + +bool CHttpRanges::GetFirst(CHttpRange& range) const +{ + if (m_ranges.empty()) + return false; + + range = m_ranges.front(); + return true; +} + +bool CHttpRanges::GetLast(CHttpRange& range) const +{ + if (m_ranges.empty()) + return false; + + range = m_ranges.back(); + return true; +} + +bool CHttpRanges::GetFirstPosition(uint64_t& position) const +{ + if (m_ranges.empty()) + return false; + + position = m_ranges.front().GetFirstPosition(); + return true; +} + +bool CHttpRanges::GetLastPosition(uint64_t& position) const +{ + if (m_ranges.empty()) + return false; + + position = m_ranges.back().GetLastPosition(); + return true; +} + +uint64_t CHttpRanges::GetLength() const +{ + uint64_t length = 0; + for (HttpRanges::const_iterator range = m_ranges.begin(); range != m_ranges.end(); ++range) + length += range->GetLength(); + + return length; +} + +bool CHttpRanges::GetTotalRange(CHttpRange& range) const +{ + if (m_ranges.empty()) + return false; + + uint64_t firstPosition, lastPosition; + if (!GetFirstPosition(firstPosition) || !GetLastPosition(lastPosition)) + return false; + + range.SetFirstPosition(firstPosition); + range.SetLastPosition(lastPosition); + + return range.IsValid(); +} + +void CHttpRanges::Add(const CHttpRange& range) +{ + if (!range.IsValid()) + return; + + m_ranges.push_back(range); + + SortAndCleanup(); +} + +void CHttpRanges::Remove(size_t index) +{ + if (index >= Size()) + return; + + m_ranges.erase(m_ranges.begin() + index); +} + +void CHttpRanges::Clear() +{ + m_ranges.clear(); +} + +bool CHttpRanges::Parse(const std::string& header) +{ + return Parse(header, std::numeric_limits<uint64_t>::max()); +} + +bool CHttpRanges::Parse(const std::string& header, uint64_t totalLength) +{ + m_ranges.clear(); + + if (header.empty() || totalLength == 0 || !StringUtils::StartsWithNoCase(header, "bytes=")) + return false; + + uint64_t lastPossiblePosition = totalLength - 1; + + // remove "bytes=" from the beginning + std::string rangesValue = header.substr(6); + + // split the value of the "Range" header by "," + std::vector<std::string> rangeValues = StringUtils::Split(rangesValue, ","); + + for (std::vector<std::string>::const_iterator range = rangeValues.begin(); range != rangeValues.end(); ++range) + { + // there must be a "-" in the range definition + if (range->find("-") == std::string::npos) + return false; + + std::vector<std::string> positions = StringUtils::Split(*range, "-"); + if (positions.size() != 2) + return false; + + bool hasStart = false; + uint64_t start = 0; + bool hasEnd = false; + uint64_t end = 0; + + // parse the start and end positions + if (!positions.front().empty()) + { + if (!StringUtils::IsNaturalNumber(positions.front())) + return false; + + start = str2uint64(positions.front(), 0); + hasStart = true; + } + if (!positions.back().empty()) + { + if (!StringUtils::IsNaturalNumber(positions.back())) + return false; + + end = str2uint64(positions.back(), 0); + hasEnd = true; + } + + // nothing defined at all + if (!hasStart && !hasEnd) + return false; + + // make sure that the end position makes sense + if (hasEnd) + end = std::min(end, lastPossiblePosition); + + if (!hasStart && hasEnd) + { + // the range is defined as the number of bytes from the end + start = totalLength - end; + end = lastPossiblePosition; + } + else if (hasStart && !hasEnd) + end = lastPossiblePosition; + + // make sure the start position makes sense + if (start > lastPossiblePosition) + return false; + + // make sure that the start position is smaller or equal to the end position + if (end < start) + return false; + + m_ranges.push_back(CHttpRange(start, end)); + } + + if (m_ranges.empty()) + return false; + + SortAndCleanup(); + return !m_ranges.empty(); +} + +void CHttpRanges::SortAndCleanup() +{ + // sort the ranges by their first position + std::sort(m_ranges.begin(), m_ranges.end()); + + // check for overlapping ranges + for (HttpRanges::iterator range = m_ranges.begin() + 1; range != m_ranges.end();) + { + HttpRanges::iterator previous = range - 1; + + // check if the current and the previous range overlap + if (previous->GetLastPosition() + 1 >= range->GetFirstPosition()) + { + // combine the previous and the current ranges by setting the last position of the previous range + // to the last position of the current range + previous->SetLastPosition(range->GetLastPosition()); + + // then remove the current range which is not needed anymore + range = m_ranges.erase(range); + } + else + ++range; + } +} + +std::string HttpRangeUtils::GenerateContentRangeHeaderValue(const CHttpRange* range) +{ + if (range == NULL) + return ""; + + return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL, range->GetFirstPosition(), range->GetLastPosition(), range->GetLength()); +} + +std::string HttpRangeUtils::GenerateContentRangeHeaderValue(uint64_t start, uint64_t end, uint64_t total) +{ + if (total > 0) + return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL, start, end, total); + + return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL_UNKNOWN, start, end); +} + +#ifdef HAS_WEB_SERVER + +std::string HttpRangeUtils::GenerateMultipartBoundary() +{ + static char chars[] = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + // create a string of length 30 to 40 and pre-fill it with "-" + size_t count = static_cast<size_t>(CUtil::GetRandomNumber()) % 11 + 30; + std::string boundary(count, '-'); + + for (size_t i = static_cast<size_t>(CUtil::GetRandomNumber()) % 5 + 8; i < count; i++) + boundary.replace(i, 1, 1, chars[static_cast<size_t>(CUtil::GetRandomNumber()) % 64]); + + return boundary; +} + +std::string HttpRangeUtils::GenerateMultipartBoundaryContentType(const std::string& multipartBoundary) +{ + if (multipartBoundary.empty()) + return ""; + + return "multipart/byteranges; boundary=" + multipartBoundary; +} + +std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType) +{ + if (multipartBoundary.empty()) + return ""; + + std::string boundaryWithHeader = HEADER_BOUNDARY + multipartBoundary + HEADER_NEWLINE; + if (!contentType.empty()) + boundaryWithHeader += MHD_HTTP_HEADER_CONTENT_TYPE ": " + contentType + HEADER_NEWLINE; + + return boundaryWithHeader; +} + +std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType, const CHttpRange* range) +{ + if (multipartBoundary.empty() || range == NULL) + return ""; + + return GenerateMultipartBoundaryWithHeader(GenerateMultipartBoundaryWithHeader(multipartBoundary, contentType), range); +} + +std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundaryWithContentType, const CHttpRange* range) +{ + if (multipartBoundaryWithContentType.empty() || range == NULL) + return ""; + + std::string boundaryWithHeader = multipartBoundaryWithContentType; + boundaryWithHeader += MHD_HTTP_HEADER_CONTENT_RANGE ": " + GenerateContentRangeHeaderValue(range); + boundaryWithHeader += HEADER_SEPARATOR; + + return boundaryWithHeader; +} + +std::string HttpRangeUtils::GenerateMultipartBoundaryEnd(const std::string& multipartBoundary) +{ + if (multipartBoundary.empty()) + return ""; + + return HEADER_NEWLINE HEADER_BOUNDARY + multipartBoundary + HEADER_BOUNDARY; +} + +#endif // HAS_WEB_SERVER diff --git a/xbmc/utils/HttpRangeUtils.h b/xbmc/utils/HttpRangeUtils.h new file mode 100644 index 0000000..7e0b66d --- /dev/null +++ b/xbmc/utils/HttpRangeUtils.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stdint.h> +#include <string> +#include <vector> + +class CHttpRange +{ +public: + CHttpRange() = default; + CHttpRange(uint64_t firstPosition, uint64_t lastPosition); + virtual ~CHttpRange() = default; + + bool operator<(const CHttpRange &other) const; + bool operator==(const CHttpRange &other) const; + bool operator!=(const CHttpRange &other) const; + + virtual uint64_t GetFirstPosition() const { return m_first; } + virtual void SetFirstPosition(uint64_t firstPosition) { m_first = firstPosition; } + virtual uint64_t GetLastPosition() const { return m_last; } + virtual void SetLastPosition(uint64_t lastPosition) { m_last = lastPosition; } + + virtual uint64_t GetLength() const; + virtual void SetLength(uint64_t length); + + virtual bool IsValid() const; + +protected: + uint64_t m_first = 1; + uint64_t m_last = 0; +}; + +typedef std::vector<CHttpRange> HttpRanges; + +class CHttpResponseRange : public CHttpRange +{ +public: + CHttpResponseRange(); + CHttpResponseRange(uint64_t firstPosition, uint64_t lastPosition); + CHttpResponseRange(const void* data, uint64_t firstPosition, uint64_t lastPosition); + CHttpResponseRange(const void* data, uint64_t length); + ~CHttpResponseRange() override = default; + + bool operator==(const CHttpResponseRange &other) const; + bool operator!=(const CHttpResponseRange &other) const; + + const void* GetData() const { return m_data; } + void SetData(const void* data) { m_data = data; } + void SetData(const void* data, uint64_t length); + void SetData(const void* data, uint64_t firstPosition, uint64_t lastPosition); + + bool IsValid() const override; + +protected: + const void* m_data; +}; + +typedef std::vector<CHttpResponseRange> HttpResponseRanges; + +class CHttpRanges final +{ +public: + CHttpRanges(); + explicit CHttpRanges(const HttpRanges& httpRanges); + + const HttpRanges& Get() const { return m_ranges; } + bool Get(size_t index, CHttpRange& range) const; + bool GetFirst(CHttpRange& range) const; + bool GetLast(CHttpRange& range) const; + size_t Size() const { return m_ranges.size(); } + bool IsEmpty() const { return m_ranges.empty(); } + + bool GetFirstPosition(uint64_t& position) const; + bool GetLastPosition(uint64_t& position) const; + uint64_t GetLength() const; + + bool GetTotalRange(CHttpRange& range) const; + + void Add(const CHttpRange& range); + void Remove(size_t index); + void Clear(); + + HttpRanges::const_iterator Begin() const { return m_ranges.begin(); } + HttpRanges::const_iterator End() const { return m_ranges.end(); } + + bool Parse(const std::string& header); + bool Parse(const std::string& header, uint64_t totalLength); + +protected: + void SortAndCleanup(); + + HttpRanges m_ranges; +}; + +class HttpRangeUtils +{ +public: + /*! + * \brief Generates a valid Content-Range HTTP header value for the given HTTP + * range definition. + * + * \param range HTTP range definition used to generate the Content-Range HTTP header + * \return Content-Range HTTP header value + */ + static std::string GenerateContentRangeHeaderValue(const CHttpRange* range); + + /*! + * \brief Generates a valid Content-Range HTTP header value for the given HTTP + * range properties. + * + * \param start Start position of the HTTP range + * \param end Last/End position of the HTTP range + * \param total Total length of original content (not just the range) + * \return Content-Range HTTP header value + */ + static std::string GenerateContentRangeHeaderValue(uint64_t start, uint64_t end, uint64_t total); + +#ifdef HAS_WEB_SERVER + /*! + * \brief Generates a multipart boundary that can be used in ranged HTTP + * responses. + * + * \return Multipart boundary that can be used in ranged HTTP responses + */ + static std::string GenerateMultipartBoundary(); + + /*! + * \brief Generates the multipart/byteranges Content-Type HTTP header value + * containing the given multipart boundary for a ranged HTTP response. + * + * \param multipartBoundary Multipart boundary to be used in the ranged HTTP response + * \return multipart/byteranges Content-Type HTTP header value + */ + static std::string GenerateMultipartBoundaryContentType(const std::string& multipartBoundary); + + /*! + * \brief Generates a multipart boundary including the Content-Type HTTP + * header value with the (actual) given content type of the original + * content. + * + * \param multipartBoundary Multipart boundary to be used in the ranged HTTP response + * \param contentType (Actual) Content type of the original content + * \return Multipart boundary (including the Content-Type HTTP header) value that can be used in ranged HTTP responses + */ + static std::string GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType); + + /*! + * \brief Generates a multipart boundary including the Content-Type HTTP + * header value with the (actual) given content type of the original + * content and the Content-Range HTTP header value for the given range. + * + * \param multipartBoundary Multipart boundary to be used in the ranged HTTP response + * \param contentType (Actual) Content type of the original content + * \param range HTTP range definition used to generate the Content-Range HTTP header + * \return Multipart boundary (including the Content-Type and Content-Range HTTP headers) value that can be used in ranged HTTP responses + */ + static std::string GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType, const CHttpRange* range); + + /*! + * \brief Generates a multipart boundary including the Content-Type HTTP + * header value with the (actual) given content type of the original + * content and the Content-Range HTTP header value for the given range. + * + * \param multipartBoundaryWithContentType Multipart boundary (already including the Content-Type HTTP header value) to be used in the ranged HTTP response + * \param range HTTP range definition used to generate the Content-Range HTTP header + * \return Multipart boundary (including the Content-Type and Content-Range HTTP headers) value that can be used in ranged HTTP responses + */ + static std::string GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundaryWithContentType, const CHttpRange* range); + + /*! + * \brief Generates a multipart boundary end that can be used in ranged HTTP + * responses. + * + * \param multipartBoundary Multipart boundary to be used in the ranged HTTP response + * \return Multipart boundary end that can be used in a ranged HTTP response + */ + static std::string GenerateMultipartBoundaryEnd(const std::string& multipartBoundary); +#endif // HAS_WEB_SERVER +}; diff --git a/xbmc/utils/HttpResponse.cpp b/xbmc/utils/HttpResponse.cpp new file mode 100644 index 0000000..33c7c27 --- /dev/null +++ b/xbmc/utils/HttpResponse.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "HttpResponse.h" + +#include <stdio.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.emplace_back(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; +} + +std::string CHttpResponse::Create() +{ + 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); + + return m_buffer; +} + +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/xbmc/utils/HttpResponse.h b/xbmc/utils/HttpResponse.h new file mode 100644 index 0000000..50fc739 --- /dev/null +++ b/xbmc/utils/HttpResponse.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <map> +#include <string> +#include <utility> +#include <vector> + +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); + + std::string Create(); + +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/xbmc/utils/IArchivable.h b/xbmc/utils/IArchivable.h new file mode 100644 index 0000000..c481e7b --- /dev/null +++ b/xbmc/utils/IArchivable.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class CArchive; + +class IArchivable +{ +protected: + /* make sure nobody deletes a pointer to this class */ + virtual ~IArchivable() = default; + +public: + virtual void Archive(CArchive& ar) = 0; +}; + diff --git a/xbmc/utils/IBufferObject.h b/xbmc/utils/IBufferObject.h new file mode 100644 index 0000000..4588aff --- /dev/null +++ b/xbmc/utils/IBufferObject.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stdint.h> +#include <string> + +/** + * @brief Interface to describe CBufferObjects. + * + * BufferObjects are used to abstract various memory types and present them + * with a generic interface. Typically on a posix system exists the concept + * of Direct Memory Access (DMA) which allows various systems to interact + * with a memory location directly without having to copy the data into + * userspace (ie. from kernel space). These DMA buffers are presented as + * file descriptors (fds) which can be passed around to gain access to + * the buffers memory location. + * + * In order to write to these buffer types typically the memory location has + * to be mapped into userspace via a call to mmap (or similar). This presents + * userspace with a memory location that can be initially written to (ie. by + * using memcpy or similar). Depending on the underlying implementation a + * stride might be specified if the memory type requires padding to a certain + * size (such as page size). The stride must be used when copying into the buffer. + * + * Some memory implementation may provide special memory layouts in which case + * modifiers are provided that describe tiling or compression. This should be + * transparent to the caller as the data copied into a BufferObject will likely + * be linear. The modifier will be needed when presenting the buffer via DRM or + * EGL even if it is linear. + * + */ +class IBufferObject +{ +public: + virtual ~IBufferObject() = default; + + /** + * @brief Create a BufferObject based on the format, width, and height of the desired buffer + * + * @param format framebuffer pixel formats are described using the fourcc codes defined in + * https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_fourcc.h + * @param width width of the requested buffer. + * @param height height of the requested buffer. + * @return true BufferObject creation was successful. + * @return false BufferObject creation was unsuccessful. + */ + virtual bool CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) = 0; + + /** + * @brief Create a BufferObject based only on the size of the desired buffer. Not all + * CBufferObject implementations may support this. This method is required for + * use with the CAddonVideoCodec as it only knows the decoded buffer size. + * + * @param size of the requested buffer. + * @return true BufferObject creation was successful. + * @return false BufferObject creation was unsuccessful. + */ + virtual bool CreateBufferObject(uint64_t size) = 0; + + /** + * @brief Destroy a BufferObject. + * + */ + virtual void DestroyBufferObject() = 0; + + /** + * @brief Get the Memory location of the BufferObject. This method needs to be + * called to be able to copy data into the BufferObject. + * + * @return uint8_t* pointer to the memory location of the BufferObject. + */ + virtual uint8_t *GetMemory() = 0; + + /** + * @brief Release the mapped memory of the BufferObject. After calling this the memory + * location pointed to by GetMemory() will be invalid. + * + */ + virtual void ReleaseMemory() = 0; + + /** + * @brief Get the File Descriptor of the BufferObject. The fd is guaranteed to be + * available after calling CreateBufferObject(). + * + * @return int fd for the BufferObject. Invalid if -1. + */ + virtual int GetFd() = 0; + + /** + * @brief Get the Stride of the BufferObject. The stride is guaranteed to be + * available after calling GetMemory(). + * + * @return uint32_t stride of the BufferObject. + */ + virtual uint32_t GetStride() = 0; + + /** + * @brief Get the Modifier of the BufferObject. Format Modifiers further describe + * the buffer's format such as for tiling or compression. + * see https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_fourcc.h + * + * @return uint64_t modifier of the BufferObject. 0 means the layout is linear (default). + */ + virtual uint64_t GetModifier() = 0; + + /** + * @brief Must be called before reading/writing data to the BufferObject. + * + */ + virtual void SyncStart() = 0; + + /** + * @brief Must be called after reading/writing data to the BufferObject. + * + */ + virtual void SyncEnd() = 0; + + /** + * @brief Get the Name of the BufferObject type in use + * + * @return std::string name of the BufferObject type in use + */ + virtual std::string GetName() const = 0; +}; diff --git a/xbmc/utils/ILocalizer.h b/xbmc/utils/ILocalizer.h new file mode 100644 index 0000000..a81f7ce --- /dev/null +++ b/xbmc/utils/ILocalizer.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <cstdint> +#include <string> + +class ILocalizer +{ +public: + virtual ~ILocalizer() = default; + + virtual std::string Localize(std::uint32_t code) const = 0; + +protected: + ILocalizer() = default; +}; diff --git a/xbmc/utils/IPlatformLog.h b/xbmc/utils/IPlatformLog.h new file mode 100644 index 0000000..6ccf98d --- /dev/null +++ b/xbmc/utils/IPlatformLog.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <mutex> +#include <string> + +#ifdef TARGET_WINDOWS +using spdlog_filename_t = std::wstring; +#else +using spdlog_filename_t = std::string; +#endif + +namespace spdlog +{ +namespace sinks +{ +template<typename Mutex> +class dist_sink; +} +} // namespace spdlog + +class IPlatformLog +{ +public: + virtual ~IPlatformLog() = default; + + static std::unique_ptr<IPlatformLog> CreatePlatformLog(); + + virtual spdlog_filename_t GetLogFilename(const std::string& filename) const = 0; + virtual void AddSinks( + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const = 0; +}; diff --git a/xbmc/utils/IRssObserver.h b/xbmc/utils/IRssObserver.h new file mode 100644 index 0000000..fae240c --- /dev/null +++ b/xbmc/utils/IRssObserver.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <vector> + +typedef uint32_t character_t; +typedef std::vector<character_t> vecText; + +class IRssObserver +{ +protected: + /* make sure nobody deletes a pointer to this class */ + ~IRssObserver() = default; + +public: + virtual void OnFeedUpdate(const vecText &feed) = 0; + virtual void OnFeedRelease() = 0; +}; diff --git a/xbmc/utils/IScreenshotSurface.h b/xbmc/utils/IScreenshotSurface.h new file mode 100644 index 0000000..ba3fa6a --- /dev/null +++ b/xbmc/utils/IScreenshotSurface.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class IScreenshotSurface +{ +public: + virtual ~IScreenshotSurface() = default; + virtual bool Capture() { return false; } + virtual void CaptureVideo(bool blendToBuffer) {} + + int GetWidth() const { return m_width; } + int GetHeight() const { return m_height; } + int GetStride() const { return m_stride; } + unsigned char* GetBuffer() const { return m_buffer; } + void ReleaseBuffer() + { + if (m_buffer) + { + delete m_buffer; + m_buffer = nullptr; + } + }; + +protected: + int m_width{0}; + int m_height{0}; + int m_stride{0}; + unsigned char* m_buffer{nullptr}; +}; diff --git a/xbmc/utils/ISerializable.h b/xbmc/utils/ISerializable.h new file mode 100644 index 0000000..12f0fba --- /dev/null +++ b/xbmc/utils/ISerializable.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class CVariant; + +class ISerializable +{ +protected: + /* make sure nobody deletes a pointer to this class */ + ~ISerializable() = default; + + public: + virtual void Serialize(CVariant& value) const = 0; +}; diff --git a/xbmc/utils/ISortable.h b/xbmc/utils/ISortable.h new file mode 100644 index 0000000..ea4a0d3 --- /dev/null +++ b/xbmc/utils/ISortable.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SortUtils.h" + +#include <map> + +class ISortable +{ +protected: + /* make sure nobody deletes a pointer to this class */ + ~ISortable() = default; + +public: + virtual void ToSortable(SortItem& sortable, Field field) const = 0; +}; diff --git a/xbmc/utils/IXmlDeserializable.h b/xbmc/utils/IXmlDeserializable.h new file mode 100644 index 0000000..edeec25 --- /dev/null +++ b/xbmc/utils/IXmlDeserializable.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class TiXmlNode; + +class IXmlDeserializable +{ +public: + virtual ~IXmlDeserializable() = default; + + virtual bool Deserialize(const TiXmlNode *node) = 0; +}; diff --git a/xbmc/utils/InfoLoader.cpp b/xbmc/utils/InfoLoader.cpp new file mode 100644 index 0000000..be4697c --- /dev/null +++ b/xbmc/utils/InfoLoader.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "InfoLoader.h" + +#include "JobManager.h" +#include "ServiceBroker.h" +#include "TimeUtils.h" +#include "guilib/LocalizeStrings.h" + +CInfoLoader::CInfoLoader(unsigned int timeToRefresh) +{ + m_refreshTime = 0; + m_timeToRefresh = timeToRefresh; + m_busy = false; +} + +CInfoLoader::~CInfoLoader() = default; + +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; + CServiceBroker::GetJobManager()->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/xbmc/utils/InfoLoader.h b/xbmc/utils/InfoLoader.h new file mode 100644 index 0000000..720f0d7 --- /dev/null +++ b/xbmc/utils/InfoLoader.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Job.h" + +#include <string> + +class CInfoLoader : public IJobCallback +{ +public: + explicit CInfoLoader(unsigned int timeToRefresh = 5 * 60 * 1000); + ~CInfoLoader() override; + + std::string GetInfo(int info); + void Refresh(); + + void OnJobComplete(unsigned int jobID, bool success, CJob *job) override; +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/xbmc/utils/JSONVariantParser.cpp b/xbmc/utils/JSONVariantParser.cpp new file mode 100644 index 0000000..493abfd --- /dev/null +++ b/xbmc/utils/JSONVariantParser.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "JSONVariantParser.h" + +#include <rapidjson/reader.h> + +class CJSONVariantParserHandler +{ +public: + explicit CJSONVariantParserHandler(CVariant& parsedObject); + + bool Null(); + bool Bool(bool b); + bool Int(int i); + bool Uint(unsigned u); + bool Int64(int64_t i); + bool Uint64(uint64_t u); + bool Double(double d); + bool RawNumber(const char* str, rapidjson::SizeType length, bool copy); + bool String(const char* str, rapidjson::SizeType length, bool copy); + bool StartObject(); + bool Key(const char* str, rapidjson::SizeType length, bool copy); + bool EndObject(rapidjson::SizeType memberCount); + bool StartArray(); + bool EndArray(rapidjson::SizeType elementCount); + +private: + template <typename... TArgs> + bool Primitive(TArgs... args) + { + PushObject(CVariant(std::forward<TArgs>(args)...)); + PopObject(); + + return true; + } + + void PushObject(const CVariant& variant); + void PopObject(); + + CVariant& m_parsedObject; + std::vector<CVariant *> m_parse; + std::string m_key; + CVariant m_root; + + enum class PARSE_STATUS + { + Variable, + Array, + Object + }; + PARSE_STATUS m_status; +}; + +CJSONVariantParserHandler::CJSONVariantParserHandler(CVariant& parsedObject) + : m_parsedObject(parsedObject), + m_parse(), + m_key(), + m_status(PARSE_STATUS::Variable) +{ } + +bool CJSONVariantParserHandler::Null() +{ + PushObject(CVariant::ConstNullVariant); + PopObject(); + + return true; +} + +bool CJSONVariantParserHandler::Bool(bool b) +{ + return Primitive(b); +} + +bool CJSONVariantParserHandler::Int(int i) +{ + return Primitive(i); +} + +bool CJSONVariantParserHandler::Uint(unsigned u) +{ + return Primitive(u); +} + +bool CJSONVariantParserHandler::Int64(int64_t i) +{ + return Primitive(i); +} + +bool CJSONVariantParserHandler::Uint64(uint64_t u) +{ + return Primitive(u); +} + +bool CJSONVariantParserHandler::Double(double d) +{ + return Primitive(d); +} + +bool CJSONVariantParserHandler::RawNumber(const char* str, rapidjson::SizeType length, bool copy) +{ + return Primitive(str, length); +} + +bool CJSONVariantParserHandler::String(const char* str, rapidjson::SizeType length, bool copy) +{ + return Primitive(str, length); +} + +bool CJSONVariantParserHandler::StartObject() +{ + PushObject(CVariant::VariantTypeObject); + + return true; +} + +bool CJSONVariantParserHandler::Key(const char* str, rapidjson::SizeType length, bool copy) +{ + m_key = std::string(str, 0, length); + + return true; +} + +bool CJSONVariantParserHandler::EndObject(rapidjson::SizeType memberCount) +{ + PopObject(); + + return true; +} + +bool CJSONVariantParserHandler::StartArray() +{ + PushObject(CVariant::VariantTypeArray); + + return true; +} + +bool CJSONVariantParserHandler::EndArray(rapidjson::SizeType elementCount) +{ + PopObject(); + + return true; +} + +void CJSONVariantParserHandler::PushObject(const CVariant& variant) +{ + if (m_status == PARSE_STATUS::Object) + { + (*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 == PARSE_STATUS::Array) + { + CVariant *temp = m_parse[m_parse.size() - 1]; + temp->push_back(variant); + m_parse.push_back(&(*temp)[temp->size() - 1]); + } + else if (m_parse.empty()) + { + m_root = variant; + m_parse.push_back(&m_root); + } + + if (variant.isObject()) + m_status = PARSE_STATUS::Object; + else if (variant.isArray()) + m_status = PARSE_STATUS::Array; + else + m_status = PARSE_STATUS::Variable; +} + +void CJSONVariantParserHandler::PopObject() +{ + CVariant *variant = m_parse[m_parse.size() - 1]; + m_parse.pop_back(); + + if (!m_parse.empty()) + { + variant = m_parse[m_parse.size() - 1]; + if (variant->isObject()) + m_status = PARSE_STATUS::Object; + else if (variant->isArray()) + m_status = PARSE_STATUS::Array; + else + m_status = PARSE_STATUS::Variable; + } + else + { + m_parsedObject = *variant; + m_status = PARSE_STATUS::Variable; + } +} + +bool CJSONVariantParser::Parse(const char* json, CVariant& data) +{ + if (json == nullptr) + return false; + + rapidjson::Reader reader; + rapidjson::StringStream stringStream(json); + + CJSONVariantParserHandler handler(data); + // use kParseIterativeFlag to eliminate possible stack overflow + // from json parsing via reentrant calls + if (reader.Parse<rapidjson::kParseIterativeFlag>(stringStream, handler)) + return true; + + return false; +} + +bool CJSONVariantParser::Parse(const std::string& json, CVariant& data) +{ + return Parse(json.c_str(), data); +} diff --git a/xbmc/utils/JSONVariantParser.h b/xbmc/utils/JSONVariantParser.h new file mode 100644 index 0000000..17cfb61 --- /dev/null +++ b/xbmc/utils/JSONVariantParser.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/Variant.h" + +#include <string> + +class CJSONVariantParser +{ +public: + CJSONVariantParser() = delete; + + static bool Parse(const char* json, CVariant& data); + static bool Parse(const std::string& json, CVariant& data); +}; diff --git a/xbmc/utils/JSONVariantWriter.cpp b/xbmc/utils/JSONVariantWriter.cpp new file mode 100644 index 0000000..b48a8ae --- /dev/null +++ b/xbmc/utils/JSONVariantWriter.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "JSONVariantWriter.h" + +#include "utils/Variant.h" + +#include <rapidjson/prettywriter.h> +#include <rapidjson/stringbuffer.h> +#include <rapidjson/writer.h> + +template<class TWriter> +bool InternalWrite(TWriter& writer, const CVariant &value) +{ + switch (value.type()) + { + case CVariant::VariantTypeInteger: + return writer.Int64(value.asInteger()); + + case CVariant::VariantTypeUnsignedInteger: + return writer.Uint64(value.asUnsignedInteger()); + + case CVariant::VariantTypeDouble: + return writer.Double(value.asDouble()); + + case CVariant::VariantTypeBoolean: + return writer.Bool(value.asBoolean()); + + case CVariant::VariantTypeString: + return writer.String(value.c_str(), value.size()); + + case CVariant::VariantTypeArray: + if (!writer.StartArray()) + return false; + + for (CVariant::const_iterator_array itr = value.begin_array(); itr != value.end_array(); ++itr) + { + if (!InternalWrite(writer, *itr)) + return false; + } + + return writer.EndArray(value.size()); + + case CVariant::VariantTypeObject: + if (!writer.StartObject()) + return false; + + for (CVariant::const_iterator_map itr = value.begin_map(); itr != value.end_map(); ++itr) + { + if (!writer.Key(itr->first.c_str()) || + !InternalWrite(writer, itr->second)) + return false; + } + + return writer.EndObject(value.size()); + + case CVariant::VariantTypeConstNull: + case CVariant::VariantTypeNull: + default: + return writer.Null(); + } + + return false; +} + +bool CJSONVariantWriter::Write(const CVariant &value, std::string& output, bool compact) +{ + rapidjson::StringBuffer stringBuffer; + if (compact) + { + rapidjson::Writer<rapidjson::StringBuffer> writer(stringBuffer); + + if (!InternalWrite(writer, value) || !writer.IsComplete()) + return false; + } + else + { + rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(stringBuffer); + writer.SetIndent('\t', 1); + + if (!InternalWrite(writer, value) || !writer.IsComplete()) + return false; + } + + output = stringBuffer.GetString(); + return true; +} diff --git a/xbmc/utils/JSONVariantWriter.h b/xbmc/utils/JSONVariantWriter.h new file mode 100644 index 0000000..e1f5bd6 --- /dev/null +++ b/xbmc/utils/JSONVariantWriter.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +class CVariant; + +class CJSONVariantWriter +{ +public: + CJSONVariantWriter() = delete; + + static bool Write(const CVariant &value, std::string& output, bool compact); +}; diff --git a/xbmc/utils/Job.h b/xbmc/utils/Job.h new file mode 100644 index 0000000..b4d3f7f --- /dev/null +++ b/xbmc/utils/Job.h @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +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, abort and completion notification of jobs. + Clients of small jobs wishing to perform actions on job completion or abort should implement the + IJobCallback::OnJobComplete() and/or IJobCallback::OnJobAbort() 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() = default; + + /*! + \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 used when a job will be aborted. + + OnJobAbort is called whenever a job gets aborted before or while being executed. + Job's DoWork method will not be called, OnJobComplete will not be called. The job instance will + be destroyed by the caller after calling this function. + + \param jobID the unique id of the job (as retrieved from CJobManager::AddJob) + \param job the job that has been aborted. + \sa CJobManager and CJob + */ + virtual void OnJobAbort(unsigned int jobID, CJob* job) {} + + /*! + \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 asynchronously. + + 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, + PRIORITY_DEDICATED, // will create a new worker if no worker is available at queue time + }; + CJob() { m_callback = NULL; } + + /*! + \brief Destructor for job objects. + + Jobs are destroyed by the CJobManager after the OnJobComplete() or OnJobAbort() 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() = default; + + /*! + \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() + */ + virtual bool ShouldCancel(unsigned int progress, unsigned int total) const; +private: + friend class CJobManager; + CJobManager *m_callback; +}; diff --git a/xbmc/utils/JobManager.cpp b/xbmc/utils/JobManager.cpp new file mode 100644 index 0000000..a1ca34b --- /dev/null +++ b/xbmc/utils/JobManager.cpp @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "JobManager.h" + +#include "ServiceBroker.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <functional> +#include <mutex> +#include <stdexcept> + +using namespace std::chrono_literals; + +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() +{ + m_jobManager->RemoveWorker(this); + if(!IsAutoDelete()) + StopThread(); +} + +void CJobWorker::Process() +{ + SetPriority(ThreadPriority::LOWEST); + while (true) + { + // request an item from our manager (this call is blocking) + CJob* job = m_jobManager->GetNextJob(); + if (!job) + break; + + bool success = false; + try + { + success = job->DoWork(); + } + catch (...) + { + CLog::Log(LOGERROR, "{} error processing job {}", __FUNCTION__, job->GetType()); + } + m_jobManager->OnJobComplete(success, job); + } +} + +void CJobQueue::CJobPointer::CancelJob() +{ + CServiceBroker::GetJobManager()->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) +{ + OnJobNotify(job); +} + +void CJobQueue::OnJobAbort(unsigned int jobID, CJob* job) +{ + OnJobNotify(job); +} + +void CJobQueue::CancelJob(const CJob *job) +{ + std::unique_lock<CCriticalSection> 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); + } +} + +bool CJobQueue::AddJob(CJob *job) +{ + std::unique_lock<CCriticalSection> 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 false; + } + + if (m_lifo) + m_jobQueue.push_back(CJobPointer(job)); + else + m_jobQueue.push_front(CJobPointer(job)); + QueueNextJob(); + + return true; +} + +void CJobQueue::OnJobNotify(CJob* job) +{ + std::unique_lock<CCriticalSection> lock(m_section); + + // check if this job is in our processing list + const auto it = std::find(m_processing.begin(), m_processing.end(), job); + if (it != m_processing.end()) + m_processing.erase(it); + // request a new job be queued + QueueNextJob(); +} + +void CJobQueue::QueueNextJob() +{ + std::unique_lock<CCriticalSection> lock(m_section); + while (m_jobQueue.size() && m_processing.size() < m_jobsAtOnce) + { + CJobPointer &job = m_jobQueue.back(); + job.m_id = CServiceBroker::GetJobManager()->AddJob(job.m_job, this, m_priority); + if (job.m_id > 0) + { + m_processing.emplace_back(job); + m_jobQueue.pop_back(); + return; + } + m_jobQueue.pop_back(); + } +} + +void CJobQueue::CancelJobs() +{ + std::unique_lock<CCriticalSection> lock(m_section); + for_each(m_processing.begin(), m_processing.end(), [](CJobPointer& jp) { jp.CancelJob(); }); + for_each(m_jobQueue.begin(), m_jobQueue.end(), [](CJobPointer& jp) { jp.FreeJob(); }); + m_jobQueue.clear(); + m_processing.clear(); +} + +bool CJobQueue::IsProcessing() const +{ + return CServiceBroker::GetJobManager()->m_running && + (!m_processing.empty() || !m_jobQueue.empty()); +} + +bool CJobQueue::QueueEmpty() const +{ + std::unique_lock<CCriticalSection> lock(m_section); + return m_jobQueue.empty(); +} + +CJobManager::CJobManager() +{ + m_jobCounter = 0; + m_running = true; + m_pauseJobs = false; +} + +void CJobManager::Restart() +{ + std::unique_lock<CCriticalSection> lock(m_section); + + if (m_running) + throw std::logic_error("CJobManager already running"); + m_running = true; +} + +void CJobManager::CancelJobs() +{ + std::unique_lock<CCriticalSection> lock(m_section); + m_running = false; + + // clear any pending jobs + for (unsigned int priority = CJob::PRIORITY_LOW_PAUSABLE; priority <= CJob::PRIORITY_DEDICATED; ++priority) + { + std::for_each(m_jobQueue[priority].begin(), m_jobQueue[priority].end(), [](CWorkItem& wi) { + if (wi.m_callback) + wi.m_callback->OnJobAbort(wi.m_id, wi.m_job); + wi.FreeJob(); + }); + m_jobQueue[priority].clear(); + } + + // cancel any callbacks on jobs still processing + std::for_each(m_processing.begin(), m_processing.end(), [](CWorkItem& wi) { + if (wi.m_callback) + wi.m_callback->OnJobAbort(wi.m_id, wi.m_job); + wi.Cancel(); + }); + + // tell our workers to finish + while (m_workers.size()) + { + lock.unlock(); + m_jobEvent.Set(); + std::this_thread::yield(); // yield after setting the event to give the workers some time to die + lock.lock(); + } +} + +unsigned int CJobManager::AddJob(CJob *job, IJobCallback *callback, CJob::PRIORITY priority) +{ + std::unique_lock<CCriticalSection> lock(m_section); + + if (!m_running) + { + delete job; + 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) +{ + std::unique_lock<CCriticalSection> lock(m_section); + + // check whether we have this job in the queue + for (unsigned int priority = CJob::PRIORITY_LOW_PAUSABLE; priority <= CJob::PRIORITY_DEDICATED; ++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) +{ + std::unique_lock<CCriticalSection> 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() +{ + std::unique_lock<CCriticalSection> lock(m_section); + for (int priority = CJob::PRIORITY_DEDICATED; 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() +{ + std::unique_lock<CCriticalSection> lock(m_section); + m_pauseJobs = true; +} + +void CJobManager::UnPauseJobs() +{ + std::unique_lock<CCriticalSection> lock(m_section); + m_pauseJobs = false; +} + +bool CJobManager::IsProcessing(const CJob::PRIORITY &priority) const +{ + std::unique_lock<CCriticalSection> 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; + std::unique_lock<CCriticalSection> 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() +{ + std::unique_lock<CCriticalSection> 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.unlock(); + bool newJob = m_jobEvent.Wait(30000ms); + lock.lock(); + if (!newJob) + break; + } + // ensure no jobs have come in during the period after + // timeout and before we held the lock + return PopJob(); +} + +bool CJobManager::OnJobProgress(unsigned int progress, unsigned int total, const CJob *job) const +{ + std::unique_lock<CCriticalSection> 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.unlock(); // 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) +{ + std::unique_lock<CCriticalSection> 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.unlock(); + try + { + if (item.m_callback) + item.m_callback->OnJobComplete(item.m_id, success, item.m_job); + } + catch (...) + { + CLog::Log(LOGERROR, "{} error processing job {}", __FUNCTION__, item.m_job->GetType()); + } + lock.lock(); + Processing::iterator j = find(m_processing.begin(), m_processing.end(), job); + if (j != m_processing.end()) + m_processing.erase(j); + lock.unlock(); + item.FreeJob(); + } +} + +void CJobManager::RemoveWorker(const CJobWorker *worker) +{ + std::unique_lock<CCriticalSection> 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; + if (priority == CJob::PRIORITY_DEDICATED) + return 10000; // A large number.. + return max_workers - (CJob::PRIORITY_HIGH - priority); +} diff --git a/xbmc/utils/JobManager.h b/xbmc/utils/JobManager.h new file mode 100644 index 0000000..545e5a1 --- /dev/null +++ b/xbmc/utils/JobManager.h @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Job.h" +#include "threads/CriticalSection.h" +#include "threads/Thread.h" + +#include <queue> +#include <string> +#include <vector> + +class CJobManager; + +class CJobWorker : public CThread +{ +public: + explicit CJobWorker(CJobManager *manager); + ~CJobWorker() override; + + void Process() override; +private: + CJobManager *m_jobManager; +}; + +template<typename F> +class CLambdaJob : public CJob +{ +public: + CLambdaJob(F&& f) : m_f(std::forward<F>(f)) {} + bool DoWork() override + { + m_f(); + return true; + } + bool operator==(const CJob *job) const override + { + return this == job; + }; +private: + F m_f; +}; + +/*! + \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: + explicit 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 + */ + ~CJobQueue() override; + + /*! + \brief Add a job to the queue + On completion of the job, destruction of the job queue or in case the job could not be added successfully, the CJob object will be destroyed. + \param job a pointer to the job to add. The job should be subclassed from CJob. + \return True if the job was added successfully, false otherwise. + In case of failure, the passed CJob object will be deleted before returning from this method. + \sa CJob + */ + bool AddJob(CJob *job); + + /*! + \brief Add a function f to this job queue + */ + template<typename F> + void Submit(F&& f) + { + AddJob(new CLambdaJob<F>(std::forward<F>(f))); + } + + /*! + \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 Check whether the queue is processing a job + */ + bool IsProcessing() const; + + /*! + \brief The callback used when a job completes. + + CJobQueue implementation will cleanup the internal processing queue and then queue the next + job at the job manager, if any. + + \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. + \sa CJobManager, IJobCallback and CJob + */ + void OnJobComplete(unsigned int jobID, bool success, CJob *job) override; + + /*! + \brief The callback used when a job will be aborted. + + CJobQueue implementation will cleanup the internal processing queue and then queue the next + job at the job manager, if any. + + \param jobID the unique id of the job (as retrieved from CJobManager::AddJob) + \param job the job that has been aborted. + \sa CJobManager, IJobCallback and CJob + */ + void OnJobAbort(unsigned int jobID, CJob* job) override; + +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 OnJobNotify(CJob* job); + 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; + mutable 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 CServiceBroker::GetJobManager(). 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 final +{ + 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: + CJobManager(); + + /*! + \brief Add a job to the threaded job manager. + On completion or abort of the job or in case the job could not be added successfully, the CJob object will be destroyed. + \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 On success, a unique identifier for this job, to be used with other interaction, 0 otherwise. + In case of failure, the passed CJob object will be deleted before returning from this method. + \sa CJob, IJobCallback, CancelJob() + */ + unsigned int AddJob(CJob *job, IJobCallback *callback, CJob::PRIORITY priority = CJob::PRIORITY_LOW); + + /*! + \brief Add a function f to this job manager for asynchronously execution. + */ + template<typename F> + void Submit(F&& f, CJob::PRIORITY priority = CJob::PRIORITY_LOW) + { + AddJob(new CLambdaJob<F>(std::forward<F>(f)), nullptr, priority); + } + + /*! + \brief Add a function f to this job manager for asynchronously execution. + */ + template<typename F> + void Submit(F&& f, IJobCallback *callback, CJob::PRIORITY priority = CJob::PRIORITY_LOW) + { + AddJob(new CLambdaJob<F>(std::forward<F>(f)), callback, priority); + } + + /*! + \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; + friend class CJobQueue; + + /*! + \brief Get a new job to process. Blocks until a new job is available, or a timeout has occurred. + \sa CJob + */ + CJob* GetNextJob(); + + /*! + \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: + CJobManager(const CJobManager&) = delete; + CJobManager const& operator=(CJobManager const&) = delete; + + /*! \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_DEDICATED + 1]; + bool m_pauseJobs; + Processing m_processing; + Workers m_workers; + + mutable CCriticalSection m_section; + CEvent m_jobEvent; + bool m_running; +}; diff --git a/xbmc/utils/LabelFormatter.cpp b/xbmc/utils/LabelFormatter.cpp new file mode 100644 index 0000000..d66cc63 --- /dev/null +++ b/xbmc/utils/LabelFormatter.cpp @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LabelFormatter.h" + +#include "FileItem.h" +#include "RegExp.h" +#include "ServiceBroker.h" +#include "StringUtils.h" +#include "URIUtils.h" +#include "Util.h" +#include "Variant.h" +#include "addons/IAddon.h" +#include "guilib/LocalizeStrings.h" +#include "music/tags/MusicInfoTag.h" +#include "pictures/PictureInfoTag.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "video/VideoInfoTag.h" + +#include <cassert> +#include <cstdlib> +#include <inttypes.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 + * %b - Total number of discs + * %c - Relevance - Used for actors' appearances + * %d - Date and Time + * %e - Original release date + * %f - bpm + * %p - Last Played + * %r - User Rating + * *t - Date Taken (suitable for Pictures) + */ + +#define MASK_CHARS "NSATBGYFLDIJRCKMEPHZOQUVXWabcdefiprstuv" + +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 = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_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}", music->GetTrackNumber()); + if (movie&& movie->m_iTrack > 0) + value = StringUtils::Format("{:02}", movie->m_iTrack); + break; + case 'S': + if (music && music->GetDiscNumber() > 0) + value = StringUtils::Format("{:02}", music->GetDiscNumber()); + break; + case 'A': + if (music && music->GetArtistString().size()) + value = music->GetArtistString(); + if (movie && movie->m_artist.size()) + value = StringUtils::Join(movie->m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->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(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); + if (movie && movie->m_genre.size()) + value = StringUtils::Join(movie->m_genre, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->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->HasYear()) + value = std::to_string(movie->GetYear()); + } + 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) || 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.f) + value = StringUtils::Format("{:.1f}", music->GetRating()); + else if (movie && movie->GetRating().rating != 0.f) + value = StringUtils::Format("{:.1f}", movie->GetRating().rating); + break; + case 'C': // programs count + value = std::to_string(item->m_iprogramCount); + break; + case 'c': // relevance + value = std::to_string(movie->m_relevance); + break; + case 'K': + value = item->m_strTitle; + break; + case 'M': + if (movie && movie->m_iEpisode > 0) + value = StringUtils::Format("{} {}", movie->m_iEpisode, + g_localizeStrings.Get(movie->m_iEpisode == 1 ? 20452 : 20453)); + break; + case 'E': + if (movie && movie->m_iEpisode > 0) + { // episode number + if (movie->m_iSeason == 0) + value = StringUtils::Format("S{:02}", movie->m_iEpisode); + else + value = StringUtils::Format("{:02}", 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}", movie->m_iEpisode); + else + value = StringUtils::Format("{}x{:02}", movie->m_iSeason, movie->m_iEpisode); + } + break; + case 'O': + if (movie) + {// MPAA Rating + value = movie->m_strMPAARating; + } + break; + case 'U': + if (movie && !movie->m_studio.empty()) + {// Studios + value = StringUtils::Join(movie ->m_studio, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator); + } + break; + case 'V': // Playcount + if (music) + value = std::to_string(music->GetPlayCount()); + if (movie) + value = std::to_string(movie->GetPlayCount()); + break; + case 'X': // Bitrate + if( !item->m_bIsFolder && item->m_dwSize != 0 ) + value = StringUtils::Format("{} kbps", item->m_dwSize); + break; + case 'W': // Listeners + if( !item->m_bIsFolder && music && music->GetListeners() != 0 ) + value = + StringUtils::Format("{} {}", music->GetListeners(), + g_localizeStrings.Get(music->GetListeners() == 1 ? 20454 : 20455)); + break; + case 'a': // Date Added + if (movie && movie->m_dateAdded.IsValid()) + value = movie->m_dateAdded.GetAsLocalizedDate(); + if (music && music->GetDateAdded().IsValid()) + value = music->GetDateAdded().GetAsLocalizedDate(); + break; + case 'b': // Total number of discs + if (music) + value = std::to_string(music->GetTotalDiscs()); + break; + case 'e': // Original release date + if (music) + { + value = music->GetOriginalDate(); + if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bMusicLibraryUseISODates) + value = StringUtils::ISODateToLocalizedDate(value); + } + break; + case 'd': // date and time + if (item->m_dateTime.IsValid()) + value = item->m_dateTime.GetAsLocalizedDateTime(); + break; + case 'p': // Last played + if (movie && movie->m_lastPlayed.IsValid()) + value = movie->m_lastPlayed.GetAsLocalizedDate(); + if (music && music->GetLastPlayed().IsValid()) + value = music->GetLastPlayed().GetAsLocalizedDate(); + break; + case 'r': // userrating + if (movie && movie->m_iUserRating != 0) + value = std::to_string(movie->m_iUserRating); + if (music && music->GetUserrating() != 0) + value = std::to_string(music->GetUserrating()); + break; + case 't': // Date Taken + if (pic && pic->GetDateTimeTaken().IsValid()) + value = pic->GetDateTimeTaken().GetAsLocalizedDate(); + break; + case 's': // Addon status + if (item->HasProperty("Addon.Status")) + value = item->GetProperty("Addon.Status").asString(); + break; + case 'i': // Install date + if (item->HasAddonInfo() && item->GetAddonInfo()->InstallDate().IsValid()) + value = item->GetAddonInfo()->InstallDate().GetAsLocalizedDate(); + break; + case 'u': // Last used + if (item->HasAddonInfo() && item->GetAddonInfo()->LastUsed().IsValid()) + value = item->GetAddonInfo()->LastUsed().GetAsLocalizedDate(); + break; + case 'v': // Last updated + if (item->HasAddonInfo() && item->GetAddonInfo()->LastUpdated().IsValid()) + value = item->GetAddonInfo()->LastUpdated().GetAsLocalizedDate(); + break; + case 'f': // BPM + if (music) + value = std::to_string(music->GetBPM()); + 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].emplace_back("", 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].emplace_back(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; + case 'r': // userrating + tag->SetUserrating(value[0]); + break; + case 'b': // total discs + tag->SetTotalDiscs(atol(value.c_str())); + break; + } +} + diff --git a/xbmc/utils/LabelFormatter.h b/xbmc/utils/LabelFormatter.h new file mode 100644 index 0000000..a10eae6 --- /dev/null +++ b/xbmc/utils/LabelFormatter.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#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/xbmc/utils/LangCodeExpander.cpp b/xbmc/utils/LangCodeExpander.cpp new file mode 100644 index 0000000..f683905 --- /dev/null +++ b/xbmc/utils/LangCodeExpander.cpp @@ -0,0 +1,1792 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LangCodeExpander.h" + +#include "LangInfo.h" +#include "utils/RegExp.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" + +#include <algorithm> +#include <array> + +#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 std::array<struct LCENTRY, 186> g_iso639_1; +extern const std::array<struct LCENTRY, 540> g_iso639_2; + +struct ISO639 +{ + const char* iso639_1; + const char* iso639_2b; + const char* iso639_2t; + const char* win_id; +}; + +struct ISO3166_1 +{ + const char* alpha2; + const char* alpha3; +}; + +// declared as extern to allow forward declaration +extern const std::array<ISO639, 190> LanguageCodes; +extern const std::array<ISO3166_1, 245> RegionCodes; + +CLangCodeExpander::CLangCodeExpander() = default; + +CLangCodeExpander::~CLangCodeExpander() = default; + +void CLangCodeExpander::Clear() +{ + m_mapUser.clear(); +} + +void CLangCodeExpander::LoadUserCodes(const TiXmlElement* pRootElement) +{ + if (pRootElement != NULL) + { + m_mapUser.clear(); + + std::string sShort, sLong; + + const TiXmlNode* pLangCode = pRootElement->FirstChild("code"); + while (pLangCode != NULL) + { + const TiXmlNode* pShort = pLangCode->FirstChildElement("short"); + const TiXmlNode* pLong = pLangCode->FirstChildElement("long"); + if (pShort != NULL && pLong != NULL) + { + sShort = pShort->FirstChild()->Value(); + sLong = pLong->FirstChild()->Value(); + StringUtils::ToLower(sShort); + + m_mapUser[sShort] = sLong; + } + + pLangCode = pLangCode->NextSibling(); + } + } +} + +bool CLangCodeExpander::Lookup(const std::string& code, std::string& desc) +{ + if (LookupInUserMap(code, desc)) + return true; + + if (LookupInISO639Tables(code, desc)) + return true; + + if (LookupInLangAddons(code, desc)) + return true; + + // Language code with subtag is supported only with language addons + // or with user defined map, then if not found we fallback by obtaining + // the primary code description only and appending the remaining + int iSplit = code.find('-'); + if (iSplit > 0) + { + std::string primaryTagDesc; + const bool hasPrimaryTagDesc = Lookup(code.substr(0, iSplit), primaryTagDesc); + std::string subtagCode = code.substr(iSplit + 1); + if (hasPrimaryTagDesc) + { + if (primaryTagDesc.length() > 0) + desc = primaryTagDesc; + else + desc = code.substr(0, iSplit); + + if (subtagCode.length() > 0) + desc += " - " + subtagCode; + + return true; + } + } + + return false; +} + +bool CLangCodeExpander::Lookup(const int code, std::string& desc) +{ + char lang[3]; + lang[2] = 0; + lang[1] = (code & 0xFF); + lang[0] = (code >> 8) & 0xFF; + + return Lookup(lang, desc); +} + +bool CLangCodeExpander::ConvertISO6391ToISO6392B(const std::string& strISO6391, + std::string& strISO6392B, + bool checkWin32Locales /*= false*/) +{ + // not a 2 char code + if (strISO6391.length() != 2) + return false; + + std::string strISO6391Lower(strISO6391); + StringUtils::ToLower(strISO6391Lower); + StringUtils::Trim(strISO6391Lower); + + for (const auto& codes : LanguageCodes) + { + if (strISO6391Lower == codes.iso639_1) + { + if (checkWin32Locales && codes.win_id) + { + strISO6392B = codes.win_id; + return true; + } + + strISO6392B = codes.iso639_2b; + return true; + } + } + + return false; +} + +bool CLangCodeExpander::ConvertToISO6392B(const std::string& strCharCode, + std::string& strISO6392B, + bool checkWin32Locales /* = false */) +{ + + //first search in the user defined map + if (LookupUserCode(strCharCode, strISO6392B)) + return true; + + if (strCharCode.size() == 2) + return g_LangCodeExpander.ConvertISO6391ToISO6392B(strCharCode, strISO6392B, checkWin32Locales); + + if (strCharCode.size() == 3) + { + std::string charCode(strCharCode); + StringUtils::ToLower(charCode); + for (const auto& codes : LanguageCodes) + { + if (charCode == codes.iso639_2b || + (checkWin32Locales && codes.win_id != NULL && charCode == codes.win_id)) + { + strISO6392B = charCode; + return true; + } + } + + for (const auto& codes : RegionCodes) + { + if (charCode == codes.alpha3) + { + strISO6392B = charCode; + return true; + } + } + } + else if (strCharCode.size() > 3) + { + for (const auto& codes : g_iso639_2) + { + if (StringUtils::EqualsNoCase(strCharCode, codes.name)) + { + strISO6392B = CodeToString(codes.code); + return true; + } + } + + // Try search on language addons + strISO6392B = g_langInfo.ConvertEnglishNameToAddonLocale(strCharCode); + if (!strISO6392B.empty()) + return true; + } + return false; +} + +bool CLangCodeExpander::ConvertToISO6392T(const std::string& strCharCode, + std::string& strISO6392T, + bool checkWin32Locales /* = false */) +{ + if (!ConvertToISO6392B(strCharCode, strISO6392T, checkWin32Locales)) + return false; + + for (const auto& codes : LanguageCodes) + { + if (strISO6392T == codes.iso639_2b || + (checkWin32Locales && codes.win_id != NULL && strISO6392T == codes.win_id)) + { + if (codes.iso639_2t != nullptr) + strISO6392T = codes.iso639_2t; + return true; + } + } + return false; +} + + +bool CLangCodeExpander::LookupUserCode(const std::string& desc, std::string& userCode) +{ + for (STRINGLOOKUPTABLE::const_iterator it = m_mapUser.begin(); it != m_mapUser.end(); ++it) + { + if (StringUtils::EqualsNoCase(desc, it->first) || StringUtils::EqualsNoCase(desc, it->second)) + { + userCode = it->first; + return true; + } + } + return false; +} + +#ifdef TARGET_WINDOWS +bool CLangCodeExpander::ConvertISO31661Alpha2ToISO31661Alpha3(const std::string& strISO31661Alpha2, + std::string& strISO31661Alpha3) +{ + if (strISO31661Alpha2.length() != 2) + return false; + + std::string strLower(strISO31661Alpha2); + StringUtils::ToLower(strLower); + StringUtils::Trim(strLower); + for (const auto& codes : RegionCodes) + { + if (strLower == codes.alpha2) + { + strISO31661Alpha3 = codes.alpha3; + return true; + } + } + + return true; +} + +bool CLangCodeExpander::ConvertWindowsLanguageCodeToISO6392B( + const std::string& strWindowsLanguageCode, std::string& strISO6392B) +{ + if (strWindowsLanguageCode.length() != 3) + return false; + + std::string strLower(strWindowsLanguageCode); + StringUtils::ToLower(strLower); + for (const auto& codes : LanguageCodes) + { + if ((codes.win_id && strLower == codes.win_id) || strLower == codes.iso639_2b) + { + strISO6392B = codes.iso639_2b; + return true; + } + } + + return false; +} +#endif + +bool CLangCodeExpander::ConvertToISO6391(const std::string& lang, std::string& code) +{ + if (lang.empty()) + return false; + + //first search in the user defined map + if (LookupUserCode(lang, code)) + return true; + + if (lang.length() == 2) + { + std::string tmp; + if (Lookup(lang, tmp)) + { + code = lang; + return true; + } + } + else if (lang.length() == 3) + { + std::string lower(lang); + StringUtils::ToLower(lower); + for (const auto& codes : LanguageCodes) + { + if (lower == codes.iso639_2b || (codes.win_id && lower == codes.win_id)) + { + code = codes.iso639_1; + return true; + } + } + + for (const auto& codes : RegionCodes) + { + if (lower == codes.alpha3) + { + code = codes.alpha2; + return true; + } + } + } + + // check if lang is full language name + std::string tmp; + if (ReverseLookup(lang, tmp)) + { + if (tmp.length() == 2) + { + code = tmp; + return true; + } + + if (tmp.length() == 3) + { + // there's only an iso639-2 code that is identical to the language name, e.g. Yao + if (StringUtils::EqualsNoCase(tmp, lang)) + return false; + + return ConvertToISO6391(tmp, code); + } + } + + return false; +} + +bool CLangCodeExpander::ReverseLookup(const std::string& desc, std::string& code) +{ + if (desc.empty()) + return false; + + std::string descTmp(desc); + StringUtils::Trim(descTmp); + + // First find to user-defined languages + for (STRINGLOOKUPTABLE::const_iterator it = m_mapUser.begin(); it != m_mapUser.end(); ++it) + { + if (StringUtils::EqualsNoCase(descTmp, it->second)) + { + code = it->first; + return true; + } + } + + for (const auto& codes : g_iso639_1) + { + if (StringUtils::EqualsNoCase(descTmp, codes.name)) + { + code = CodeToString(codes.code); + return true; + } + } + + for (const auto& codes : g_iso639_2) + { + if (StringUtils::EqualsNoCase(descTmp, codes.name)) + { + code = CodeToString(codes.code); + return true; + } + } + + // Find on language addons + code = g_langInfo.ConvertEnglishNameToAddonLocale(descTmp); + if (!code.empty()) + return true; + + return false; +} + +bool CLangCodeExpander::LookupInUserMap(const std::string& code, std::string& desc) +{ + if (code.empty()) + return false; + + // make sure we convert to lowercase before trying to find it + std::string sCode(code); + StringUtils::ToLower(sCode); + StringUtils::Trim(sCode); + + STRINGLOOKUPTABLE::iterator it = m_mapUser.find(sCode); + if (it != m_mapUser.end()) + { + desc = it->second; + return true; + } + + return false; +} + +bool CLangCodeExpander::LookupInLangAddons(const std::string& code, std::string& desc) +{ + if (code.empty()) + return false; + + std::string sCode{code}; + StringUtils::Trim(sCode); + StringUtils::ToLower(sCode); + StringUtils::Replace(sCode, '-', '_'); + + desc = g_langInfo.GetEnglishLanguageName(sCode); + return !desc.empty(); +} + +bool CLangCodeExpander::LookupInISO639Tables(const std::string& code, std::string& desc) +{ + 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 (const auto& codes : g_iso639_1) + { + if (codes.code == longcode) + { + desc = codes.name; + return true; + } + } + } + else if (sCode.length() == 3) + { + longcode = MAKECODE('\0', sCode[0], sCode[1], sCode[2]); + for (const auto& codes : g_iso639_2) + { + if (codes.code == longcode) + { + desc = codes.name; + return true; + } + } + } + return false; +} + +std::string CLangCodeExpander::CodeToString(long code) +{ + std::string ret; + for (unsigned int j = 0; j < 4; j++) + { + char c = (char)code & 0xFF; + if (c == '\0') + break; + + ret.insert(0, 1, c); + code >>= 8; + } + return ret; +} + +bool CLangCodeExpander::CompareFullLanguageNames(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; + + code1 = lang1; + if (!ReverseLookup(lang2, code2)) + return false; + + 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 */, + LANG_LIST list /* = LANG_LIST::DEFAULT */) +{ + std::map<std::string, std::string> langMap; + + if (format == CLangCodeExpander::ISO_639_2) + std::transform(g_iso639_2.begin(), g_iso639_2.end(), std::inserter(langMap, langMap.end()), + [](const LCENTRY& e) { return std::make_pair(CodeToString(e.code), e.name); }); + else + std::transform(g_iso639_1.begin(), g_iso639_1.end(), std::inserter(langMap, langMap.end()), + [](const LCENTRY& e) { return std::make_pair(CodeToString(e.code), e.name); }); + + if (list == LANG_LIST::INCLUDE_ADDONS || list == LANG_LIST::INCLUDE_ADDONS_USERDEFINED) + { + g_langInfo.GetAddonsLanguageCodes(langMap); + } + + // User-defined languages can override existing ones + if (list == LANG_LIST::INCLUDE_USERDEFINED || list == LANG_LIST::INCLUDE_ADDONS_USERDEFINED) + { + for (const auto& value : m_mapUser) + { + langMap[value.first] = value.second; + } + } + + // Sort by name and remove duplicates + std::set<std::string, sortstringbyname> languages; + for (const auto& lang : langMap) + { + languages.insert(lang.second); + } + + return std::vector<std::string>(languages.begin(), languages.end()); +} + +bool CLangCodeExpander::CompareISO639Codes(const std::string& code1, const std::string& code2) +{ + if (StringUtils::EqualsNoCase(code1, code2)) + return true; + + std::string expandedLang1; + if (!Lookup(code1, expandedLang1)) + return false; + + std::string expandedLang2; + if (!Lookup(code2, expandedLang2)) + return false; + + return StringUtils::EqualsNoCase(expandedLang1, expandedLang2); +} + +std::string CLangCodeExpander::ConvertToISO6392B(const std::string& lang) +{ + if (lang.empty()) + return lang; + + std::string two, three; + if (ConvertToISO6391(lang, two)) + { + if (ConvertToISO6392B(two, three)) + return three; + } + + return lang; +} + +std::string CLangCodeExpander::ConvertToISO6392T(const std::string& lang) +{ + if (lang.empty()) + return lang; + + std::string two, three; + if (ConvertToISO6391(lang, two)) + { + if (ConvertToISO6392T(two, three)) + return three; + } + + return lang; +} + +std::string CLangCodeExpander::FindLanguageCodeWithSubtag(const std::string& str) +{ + CRegExp regLangCode; + if (regLangCode.RegComp( + "(?:^|\\s|\\()(([A-Za-z]{2,3})-([A-Za-z]{2}|[0-9]{3}|[A-Za-z]{4}))(?:$|\\s|\\))") && + regLangCode.RegFind(str) >= 0) + { + return regLangCode.GetMatch(1); + } + return ""; +} + +// clang-format off +const std::array<struct LCENTRY, 186> g_iso639_1 = {{ + {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'), "Norwegian Bokm\xC3\xA5l"}, + {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"}, + // pb = unofficial language code for Brazilian Portuguese + {MAKECODE('\0', '\0', 'p', 'b'), "Portuguese (Brazil)"}, + {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"}, +}}; +// clang-format on + +// clang-format off +const std::array<struct LCENTRY, 540> g_iso639_2 = {{ + {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', 's', 'w'), "German, Swiss German"}, + {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"}, + // pob = unofficial language code for Brazilian Portuguese + {MAKECODE('\0', 'p', 'o', 'b'), "Portuguese (Brazil)"}, + {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"}, +}}; +// clang-format on + +// clang-format off +const std::array<ISO639, 190> LanguageCodes = {{ + {"aa", "aar", NULL, NULL}, + {"ab", "abk", NULL, NULL}, + {"af", "afr", NULL, NULL}, + {"ak", "aka", NULL, NULL}, + {"am", "amh", NULL, NULL}, + {"ar", "ara", NULL, NULL}, + {"an", "arg", NULL, NULL}, + {"as", "asm", NULL, NULL}, + {"av", "ava", NULL, NULL}, + {"ae", "ave", NULL, NULL}, + {"ay", "aym", NULL, NULL}, + {"az", "aze", NULL, NULL}, + {"ba", "bak", NULL, NULL}, + {"bm", "bam", NULL, NULL}, + {"be", "bel", NULL, NULL}, + {"bn", "ben", NULL, NULL}, + {"bh", "bih", NULL, NULL}, + {"bi", "bis", NULL, NULL}, + {"bo", "tib", NULL, "bod"}, + {"bs", "bos", NULL, NULL}, + {"br", "bre", NULL, NULL}, + {"bg", "bul", NULL, NULL}, + {"ca", "cat", NULL, NULL}, + {"cs", "cze", "ces", "ces"}, + {"ch", "cha", NULL, NULL}, + {"ce", "che", NULL, NULL}, + {"cu", "chu", NULL, NULL}, + {"cv", "chv", NULL, NULL}, + {"kw", "cor", NULL, NULL}, + {"co", "cos", NULL, NULL}, + {"cr", "cre", NULL, NULL}, + {"cy", "wel", NULL, "cym"}, + {"da", "dan", NULL, NULL}, + {"de", "ger", "deu", "deu"}, + {"dv", "div", NULL, NULL}, + {"dz", "dzo", NULL, NULL}, + {"el", "gre", "ell", "ell"}, + {"en", "eng", NULL, NULL}, + {"eo", "epo", NULL, NULL}, + {"et", "est", NULL, NULL}, + {"eu", "baq", NULL, "eus"}, + {"ee", "ewe", NULL, NULL}, + {"fo", "fao", NULL, NULL}, + {"fa", "per", NULL, "fas"}, + {"fj", "fij", NULL, NULL}, + {"fi", "fin", NULL, NULL}, + {"fr", "fre", "fra", "fra"}, + {"fy", "fry", NULL, NULL}, + {"ff", "ful", NULL, NULL}, + {"gd", "gla", NULL, NULL}, + {"ga", "gle", NULL, NULL}, + {"gl", "glg", NULL, NULL}, + {"gv", "glv", NULL, NULL}, + {"gn", "grn", NULL, NULL}, + {"gu", "guj", NULL, NULL}, + {"ht", "hat", NULL, NULL}, + {"ha", "hau", NULL, NULL}, + {"he", "heb", NULL, NULL}, + {"hz", "her", NULL, NULL}, + {"hi", "hin", NULL, NULL}, + {"ho", "hmo", NULL, NULL}, + {"hr", "hrv", NULL, NULL}, + {"hu", "hun", NULL, NULL}, + {"hy", "arm", NULL, "hye"}, + {"ig", "ibo", NULL, NULL}, + {"io", "ido", NULL, NULL}, + {"ii", "iii", NULL, NULL}, + {"iu", "iku", NULL, NULL}, + {"ie", "ile", NULL, NULL}, + {"ia", "ina", NULL, NULL}, + {"id", "ind", NULL, NULL}, + {"ik", "ipk", NULL, NULL}, + {"is", "ice", "isl", "isl"}, + {"it", "ita", NULL, NULL}, + {"jv", "jav", NULL, NULL}, + {"ja", "jpn", NULL, NULL}, + {"kl", "kal", NULL, NULL}, + {"kn", "kan", NULL, NULL}, + {"ks", "kas", NULL, NULL}, + {"ka", "geo", NULL, "kat"}, + {"kr", "kau", NULL, NULL}, + {"kk", "kaz", NULL, NULL}, + {"km", "khm", NULL, NULL}, + {"ki", "kik", NULL, NULL}, + {"rw", "kin", NULL, NULL}, + {"ky", "kir", NULL, NULL}, + {"kv", "kom", NULL, NULL}, + {"kg", "kon", NULL, NULL}, + {"ko", "kor", NULL, NULL}, + {"kj", "kua", NULL, NULL}, + {"ku", "kur", NULL, NULL}, + {"lo", "lao", NULL, NULL}, + {"la", "lat", NULL, NULL}, + {"lv", "lav", NULL, NULL}, + {"li", "lim", NULL, NULL}, + {"ln", "lin", NULL, NULL}, + {"lt", "lit", NULL, NULL}, + {"lb", "ltz", NULL, NULL}, + {"lu", "lub", NULL, NULL}, + {"lg", "lug", NULL, NULL}, + {"mk", "mac", NULL, "mdk"}, + {"mh", "mah", NULL, NULL}, + {"ml", "mal", NULL, NULL}, + {"mi", "mao", NULL, "mri"}, + {"mr", "mar", NULL, NULL}, + {"ms", "may", NULL, "msa"}, + {"mg", "mlg", NULL, NULL}, + {"mt", "mlt", NULL, NULL}, + {"mn", "mon", NULL, NULL}, + {"my", "bur", NULL, "mya"}, + {"na", "nau", NULL, NULL}, + {"nv", "nav", NULL, NULL}, + {"nr", "nbl", NULL, NULL}, + {"nd", "nde", NULL, NULL}, + {"ng", "ndo", NULL, NULL}, + {"ne", "nep", NULL, NULL}, + {"nl", "dut", "nld", "nld"}, + {"nn", "nno", NULL, NULL}, + {"nb", "nob", NULL, NULL}, + {"no", "nor", NULL, NULL}, + {"ny", "nya", NULL, NULL}, + {"oc", "oci", NULL, NULL}, + {"oj", "oji", NULL, NULL}, + {"or", "ori", NULL, NULL}, + {"om", "orm", NULL, NULL}, + {"os", "oss", NULL, NULL}, + {"pa", "pan", NULL, NULL}, + // pb / pob = unofficial language code for Brazilian Portuguese + {"pb", "pob", NULL, NULL}, + {"pi", "pli", NULL, NULL}, + {"pl", "pol", "plk", NULL}, + {"pt", "por", "ptg", NULL}, + {"ps", "pus", NULL, NULL}, + {"qu", "que", NULL, NULL}, + {"rm", "roh", NULL, NULL}, + {"ro", "rum", "ron", "ron"}, + {"rn", "run", NULL, NULL}, + {"ru", "rus", NULL, NULL}, + {"sh", "scr", NULL, NULL}, + {"sg", "sag", NULL, NULL}, + {"sa", "san", NULL, NULL}, + {"si", "sin", NULL, NULL}, + {"sk", "slo", "sky", "slk"}, + {"sl", "slv", NULL, NULL}, + {"se", "sme", NULL, NULL}, + {"sm", "smo", NULL, NULL}, + {"sn", "sna", NULL, NULL}, + {"sd", "snd", NULL, NULL}, + {"so", "som", NULL, NULL}, + {"st", "sot", NULL, NULL}, + {"es", "spa", "esp", NULL}, + {"sq", "alb", NULL, "sqi"}, + {"sc", "srd", NULL, NULL}, + {"sr", "srp", NULL, NULL}, + {"ss", "ssw", NULL, NULL}, + {"su", "sun", NULL, NULL}, + {"sw", "swa", NULL, NULL}, + {"sv", "swe", "sve", NULL}, + {"ty", "tah", NULL, NULL}, + {"ta", "tam", NULL, NULL}, + {"tt", "tat", NULL, NULL}, + {"te", "tel", NULL, NULL}, + {"tg", "tgk", NULL, NULL}, + {"tl", "tgl", NULL, NULL}, + {"th", "tha", NULL, NULL}, + {"ti", "tir", NULL, NULL}, + {"to", "ton", NULL, NULL}, + {"tn", "tsn", NULL, NULL}, + {"ts", "tso", NULL, NULL}, + {"tk", "tuk", NULL, NULL}, + {"tr", "tur", "trk", NULL}, + {"tw", "twi", NULL, NULL}, + {"ug", "uig", NULL, NULL}, + {"uk", "ukr", NULL, NULL}, + {"ur", "urd", NULL, NULL}, + {"uz", "uzb", NULL, NULL}, + {"ve", "ven", NULL, NULL}, + {"vi", "vie", NULL, NULL}, + {"vo", "vol", NULL, NULL}, + {"wa", "wln", NULL, NULL}, + {"wo", "wol", NULL, NULL}, + {"xh", "xho", NULL, NULL}, + {"yi", "yid", NULL, NULL}, + {"yo", "yor", NULL, NULL}, + {"za", "zha", NULL, NULL}, + {"zh", "chi", "zho", "zho"}, + {"zu", "zul", NULL, NULL}, + {"zv", "und", NULL, NULL}, // Kodi intern mapping for missing "Undetermined" iso639-1 code + {"zx", "zxx", NULL, + NULL}, // Kodi intern mapping for missing "No linguistic content" iso639-1 code + {"zy", "mis", NULL, + NULL}, // Kodi intern mapping for missing "Miscellaneous languages" iso639-1 code + {"zz", "mul", NULL, NULL} // Kodi intern mapping for missing "Multiple languages" iso639-1 code +}}; +// clang-format on + +// Based on ISO 3166 +// clang-format off +const std::array<ISO3166_1, 245> RegionCodes = {{ + {"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"}, + {"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"} +}}; +// clang-format on diff --git a/xbmc/utils/LangCodeExpander.h b/xbmc/utils/LangCodeExpander.h new file mode 100644 index 0000000..dc9e5dc --- /dev/null +++ b/xbmc/utils/LangCodeExpander.h @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <map> +#include <string> +#include <vector> + +class TiXmlElement; + +class CLangCodeExpander +{ +public: + CLangCodeExpander(); + ~CLangCodeExpander(); + + enum LANGFORMATS + { + ISO_639_1, + ISO_639_2, + ENGLISH_NAME + }; + + enum class LANG_LIST + { + // Standard ISO + DEFAULT, + // Standard ISO + Language addons + INCLUDE_ADDONS, + // Standard ISO + User defined + // (User defined can override language name of existing codes) + INCLUDE_USERDEFINED, + // Standard ISO + Language addons + User defined + // (User defined can override language name of existing codes) + INCLUDE_ADDONS_USERDEFINED, + }; + + void LoadUserCodes(const TiXmlElement* pRootElement); + void Clear(); + + bool Lookup(const std::string& code, std::string& desc); + bool Lookup(const int code, std::string& desc); + + /** \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 CompareFullLanguageNames(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 CompareISO639Codes(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. + * \return true if the conversion succeeded, false otherwise. + */ + bool ConvertToISO6391(const std::string& lang, std::string& code); + + /** \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/B code. + * \param[in] lang The language that should be converted. + * \return The 3-Char ISO 639-2/B code of lang if that code exists, lang otherwise. + */ + std::string ConvertToISO6392B(const std::string& lang); + + /** \brief Converts a language given as 2-Char (ISO 639-1) to a 3-Char (ISO 639-2/T) code. + * \param[in] strISO6391 The language that should be converted. + * \param[out] strISO6392B The 3-Char (ISO 639-2/B) language code of the given language strISO6391. + * \param[in] checkWin32Locales Whether to also check WIN32 specific language codes. + * \return true if the conversion succeeded, false otherwise. + */ + static bool ConvertISO6391ToISO6392B(const std::string& strISO6391, std::string& strISO6392B, bool checkWin32Locales = false); + + /** \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] strCharCode The language that should be converted. + * \param[out] strISO6392B The 3-Char (ISO 639-2/B) language code of the given language strISO6391. + * \param[in] checkWin32Locales Whether to also check WIN32 specific language codes. + * \return true if the conversion succeeded, false otherwise. + */ + bool ConvertToISO6392B(const std::string& strCharCode, std::string& strISO6392B, bool checkWin32Locales = false); + + /** \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] strCharCode The language that should be converted. + * \param[out] strISO6392T The 3-Char (ISO 639-2/T) language code of the given language strISO6391. + * \param[in] checkWin32Locales Whether to also check WIN32 specific language codes. + * \return true if the conversion succeeded, false otherwise. + */ + bool ConvertToISO6392T(const std::string& strCharCode, std::string& strISO6392T, bool checkWin32Locales = false); + + /** \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); + + /* + * \brief Find a language code with subtag (e.g. zh-tw, zh-Hans) in to a string. + * This function find a limited set of IETF BCP47 specs, so: + * language tag + region subtag, or, language tag + script subtag. + * The language code can be found also if wrapped with round brackets. + * \param str The string where find the language code. + * \return The language code found in the string, otherwise empty string + */ + static std::string FindLanguageCodeWithSubtag(const std::string& str); + +#ifdef TARGET_WINDOWS + static bool ConvertISO31661Alpha2ToISO31661Alpha3(const std::string& strISO31661Alpha2, std::string& strISO31661Alpha3); + static bool ConvertWindowsLanguageCodeToISO6392B(const std::string& strWindowsLanguageCode, std::string& strISO6392B); +#endif + + /* + * \brief Get the list of language names. + * \param format [OPT] The format type. + * \param list [OPT] The type of language list to retrieve. + * \return The languages + */ + std::vector<std::string> GetLanguageNames(LANGFORMATS format = ISO_639_1, + LANG_LIST list = LANG_LIST::DEFAULT); + +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). + * \return The string representation of the given language code code. + */ + static std::string CodeToString(long code); + + static bool LookupInISO639Tables(const std::string& code, std::string& desc); + + /* + * \brief Looks up the language description for given language code + * in to the installed language addons. + * \param[in] code The language code for which description is looked for. + * \param[out] desc The english language name. + * \return true if the language description was found, false otherwise. + */ + static bool LookupInLangAddons(const std::string& code, std::string& desc); + + bool LookupInUserMap(const std::string& code, std::string& desc); + + /** \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); + + + /** \brief Looks up the user defined code of the given code or language name. + * \param[in] desc The language code or name that should be converted. + * \param[out] userCode The user defined language code of the given language desc. + * \return true if desc was found, false otherwise. + */ + bool LookupUserCode(const std::string& desc, std::string &userCode); + + typedef std::map<std::string, std::string> STRINGLOOKUPTABLE; + STRINGLOOKUPTABLE m_mapUser; +}; + +extern CLangCodeExpander g_LangCodeExpander; diff --git a/xbmc/utils/LegacyPathTranslation.cpp b/xbmc/utils/LegacyPathTranslation.cpp new file mode 100644 index 0000000..0069339 --- /dev/null +++ b/xbmc/utils/LegacyPathTranslation.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LegacyPathTranslation.h" + +#include "URL.h" +#include "utils/StringUtils.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/xbmc/utils/LegacyPathTranslation.h b/xbmc/utils/LegacyPathTranslation.h new file mode 100644 index 0000000..ba6450b --- /dev/null +++ b/xbmc/utils/LegacyPathTranslation.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#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/xbmc/utils/Literals.h b/xbmc/utils/Literals.h new file mode 100644 index 0000000..ce567d5 --- /dev/null +++ b/xbmc/utils/Literals.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +constexpr unsigned long long int operator"" _kib (unsigned long long int val) +{ + return val * 1024ull; +} + +constexpr unsigned long long int operator"" _kb (unsigned long long int val) +{ + return val * 1000ull; +} + +constexpr unsigned long long int operator"" _mib (unsigned long long int val) +{ + return val * 1024ull * 1024ull; +} + +constexpr unsigned long long int operator"" _mb (unsigned long long int val) +{ + return val * 1000ull * 1000ull; +} diff --git a/xbmc/utils/Locale.cpp b/xbmc/utils/Locale.cpp new file mode 100644 index 0000000..ff63ed4 --- /dev/null +++ b/xbmc/utils/Locale.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Locale.h" + +#include "utils/StringUtils.h" + +const CLocale CLocale::Empty; + +CLocale::CLocale() + : m_language(), + m_territory(), + m_codeset(), + m_modifier() +{ } + +CLocale::CLocale(const std::string& language) + : m_language(), + m_territory(), + m_codeset(), + m_modifier() +{ + m_valid = ParseLocale(language, m_language, m_territory, m_codeset, m_modifier); +} + +CLocale::CLocale(const std::string& language, const std::string& territory) + : m_language(language), + m_territory(territory), + m_codeset(), + m_modifier() +{ + Initialize(); +} + +CLocale::CLocale(const std::string& language, const std::string& territory, const std::string& codeset) + : m_language(language), + m_territory(territory), + m_codeset(codeset), + m_modifier() +{ + Initialize(); +} + +CLocale::CLocale(const std::string& language, const std::string& territory, const std::string& codeset, const std::string& modifier) + : m_language(language), + m_territory(territory), + m_codeset(codeset), + m_modifier(modifier) +{ + Initialize(); +} + +CLocale::~CLocale() = default; + +CLocale CLocale::FromString(const std::string& locale) +{ + return CLocale(locale); +} + +bool CLocale::operator==(const CLocale& other) const +{ + if (!m_valid && !other.m_valid) + return true; + + return m_valid == other.m_valid && + StringUtils::EqualsNoCase(m_language, other.m_language) && + StringUtils::EqualsNoCase(m_territory, other.m_territory) && + StringUtils::EqualsNoCase(m_codeset, other.m_codeset) && + StringUtils::EqualsNoCase(m_modifier, other.m_modifier); +} + +std::string CLocale::ToString() const +{ + if (!m_valid) + return ""; + + std::string locale = ToShortString(); + + if (!m_codeset.empty()) + locale += "." + m_codeset; + + if (!m_modifier.empty()) + locale += "@" + m_modifier; + + return locale; +} + +std::string CLocale::ToStringLC() const +{ + if (!m_valid) + return ""; + + std::string locale = ToString(); + StringUtils::ToLower(locale); + + return locale; +} + +std::string CLocale::ToShortString() const +{ + if (!m_valid) + return ""; + + std::string locale = m_language; + + if (!m_territory.empty()) + locale += "_" + m_territory; + + return locale; +} + +std::string CLocale::ToShortStringLC() const +{ + if (!m_valid) + return ""; + + std::string locale = ToShortString(); + StringUtils::ToLower(locale); + + return locale; +} + +bool CLocale::Equals(const std::string& locale) const +{ + CLocale other = FromString(locale); + + return *this == other; +} + +bool CLocale::Matches(const std::string& locale) const +{ + CLocale other = FromString(locale); + + if (!m_valid && !other.m_valid) + return true; + if (!m_valid || !other.m_valid) + return false; + + if (!StringUtils::EqualsNoCase(m_language, other.m_language)) + return false; + if (!m_territory.empty() && !other.m_territory.empty() && !StringUtils::EqualsNoCase(m_territory, other.m_territory)) + return false; + if (!m_codeset.empty() && !other.m_codeset.empty() && !StringUtils::EqualsNoCase(m_codeset, other.m_codeset)) + return false; + if (!m_modifier.empty() && !other.m_modifier.empty() && !StringUtils::EqualsNoCase(m_modifier, other.m_modifier)) + return false; + + return true; +} + +std::string CLocale::FindBestMatch(const std::set<std::string>& locales) const +{ + std::string bestMatch = ""; + int bestMatchRank = -1; + + for (auto const& locale : locales) + { + // check if there is an exact match + if (Equals(locale)) + return locale; + + int matchRank = GetMatchRank(locale); + if (matchRank > bestMatchRank) + { + bestMatchRank = matchRank; + bestMatch = locale; + } + } + + return bestMatch; +} + +std::string CLocale::FindBestMatch(const std::unordered_map<std::string, std::string>& locales) const +{ + std::string bestMatch = ""; + int bestMatchRank = -1; + + for (auto const& locale : locales) + { + // check if there is an exact match + if (Equals(locale.first)) + return locale.first; + + int matchRank = GetMatchRank(locale.first); + if (matchRank > bestMatchRank) + { + bestMatchRank = matchRank; + bestMatch = locale.first; + } + } + + return bestMatch; +} + +bool CLocale::CheckValidity(const std::string& language, const std::string& territory, const std::string& codeset, const std::string& modifier) +{ + static_cast<void>(territory); + static_cast<void>(codeset); + static_cast<void>(modifier); + + return !language.empty(); +} + +bool CLocale::ParseLocale(const std::string &locale, std::string &language, std::string &territory, std::string &codeset, std::string &modifier) +{ + if (locale.empty()) + return false; + + language.clear(); + territory.clear(); + codeset.clear(); + modifier.clear(); + + // the format for a locale is [language[_territory][.codeset][@modifier]] + std::string tmp = locale; + + // look for the modifier after @ + size_t pos = tmp.find('@'); + if (pos != std::string::npos) + { + modifier = tmp.substr(pos + 1); + tmp = tmp.substr(0, pos); + } + + // look for the codeset after . + pos = tmp.find('.'); + if (pos != std::string::npos) + { + codeset = tmp.substr(pos + 1); + tmp = tmp.substr(0, pos); + } + + // look for the codeset after _ + pos = tmp.find('_'); + if (pos != std::string::npos) + { + territory = tmp.substr(pos + 1); + StringUtils::ToUpper(territory); + tmp = tmp.substr(0, pos); + } + + // what remains is the language + language = tmp; + StringUtils::ToLower(language); + + return CheckValidity(language, territory, codeset, modifier); +} + +void CLocale::Initialize() +{ + m_valid = CheckValidity(m_language, m_territory, m_codeset, m_modifier); + if (m_valid) + { + StringUtils::ToLower(m_language); + StringUtils::ToUpper(m_territory); + } +} + +int CLocale::GetMatchRank(const std::string& locale) const +{ + CLocale other = FromString(locale); + + // both locales must be valid and match in language + if (!m_valid || !other.m_valid || + !StringUtils::EqualsNoCase(m_language, other.m_language)) + return -1; + + int rank = 0; + // matching in territory is considered more important than matching in + // codeset and/or modifier + if (!m_territory.empty() && !other.m_territory.empty() && StringUtils::EqualsNoCase(m_territory, other.m_territory)) + rank += 3; + if (!m_codeset.empty() && !other.m_codeset.empty() && StringUtils::EqualsNoCase(m_codeset, other.m_codeset)) + rank += 1; + if (!m_modifier.empty() && !other.m_modifier.empty() && StringUtils::EqualsNoCase(m_modifier, other.m_modifier)) + rank += 1; + + return rank; +} diff --git a/xbmc/utils/Locale.h b/xbmc/utils/Locale.h new file mode 100644 index 0000000..4f68af8 --- /dev/null +++ b/xbmc/utils/Locale.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <set> +#include <string> +#include <unordered_map> + +/*! + \brief Class representing a full locale of the form `[language[_territory][.codeset][@modifier]]`. + */ +class CLocale +{ +public: + CLocale(); + explicit CLocale(const std::string& language); + CLocale(const std::string& language, const std::string& territory); + CLocale(const std::string& language, const std::string& territory, const std::string& codeset); + CLocale(const std::string& language, const std::string& territory, const std::string& codeset, const std::string& modifier); + ~CLocale(); + + /*! + \brief Empty (and invalid) CLocale instance. + */ + static const CLocale Empty; + + /*! + \brief Parses the given string representation and turns it into a locale. + + \param locale String representation of a locale + */ + static CLocale FromString(const std::string& locale); + + bool operator==(const CLocale& other) const; + inline bool operator!=(const CLocale& other) const { return !(*this == other); } + + /*! + \brief Whether the locale is valid or not. + + \details A locale is considered valid if at least the language code is set. + */ + bool IsValid() const { return m_valid; } + + /*! + \brief Returns the (lower-case) ISO 639-1 language code of the locale. + */ + const std::string& GetLanguageCode() const { return m_language; } + /*! + \brief Returns the (upper-case) ISO 3166-1 Alpha-2 territory code of the locale. + */ + const std::string& GetTerritoryCode() const { return m_territory; } + /*! + \brief Returns the codeset of the locale. + */ + const std::string& GetCodeset() const { return m_codeset; } + /*! + \brief Returns the modifier of the locale. + */ + const std::string& GetModifier() const { return m_modifier; } + + /*! + \brief Returns the full string representation of the locale. + + \details The format of the string representation is + `[language[_territory][.codeset][@modifier]]` where the language is + represented as a (lower-case) two character ISO 639-1 code and the territory + is represented as a (upper-case) two character ISO 3166-1 Alpha-2 code. + */ + std::string ToString() const; + /*! + \brief Returns the full string representation of the locale in lowercase. + + \details The format of the string representation is + `language[_territory][.codeset][@modifier]]` where the language is + represented as a two character ISO 639-1 code and the territory is + represented as a two character ISO 3166-1 Alpha-2 code. + */ + std::string ToStringLC() const; + /*! + \brief Returns the short string representation of the locale. + + \details The format of the short string representation is + `[language[_territory]` where the language is represented as a (lower-case) + two character ISO 639-1 code and the territory is represented as a + (upper-case) two character ISO 3166-1 Alpha-2 code. + */ + std::string ToShortString() const; + /*! + \brief Returns the short string representation of the locale in lowercase. + + \details The format of the short string representation is + `[language[_territory]` where the language is represented as a two character + ISO 639-1 code and the territory is represented as a two character + ISO 3166-1 Alpha-2 code. + */ + std::string ToShortStringLC() const; + + /*! + \brief Checks if the given string representation of a locale exactly matches + the locale. + + \param locale String representation of a locale + \return True if the string representation matches the locale, false otherwise. + */ + bool Equals(const std::string& locale) const; + + /*! + \brief Checks if the given string representation of a locale partly matches + the locale. + + \details Partial matching means that every available locale part needs to + match the same locale part of the other locale if present. + + \param locale String representation of a locale + \return True if the string representation matches the locale, false otherwise. + */ + bool Matches(const std::string& locale) const; + + /*! + \brief Tries to find the locale in the given list that matches this locale + best. + + \param locales List of string representations of locales + \return Best matching locale from the given list or empty string. + */ + std::string FindBestMatch(const std::set<std::string>& locales) const; + + /*! + \brief Tries to find the locale in the given list that matches this locale + best. + + \param locales Map list of string representations of locales with first as + locale identifier + \return Best matching locale from the given list or empty string. + + \remark Used from \ref CAddonInfo::GetTranslatedText to prevent copy from map + to set. + */ + std::string FindBestMatch(const std::unordered_map<std::string, std::string>& locales) const; + +private: + static bool CheckValidity(const std::string& language, const std::string& territory, const std::string& codeset, const std::string& modifier); + static bool ParseLocale(const std::string &locale, std::string &language, std::string &territory, std::string &codeset, std::string &modifier); + + void Initialize(); + + int GetMatchRank(const std::string& locale) const; + + bool m_valid = false; + std::string m_language; + std::string m_territory; + std::string m_codeset; + std::string m_modifier; +}; + diff --git a/xbmc/utils/Map.h b/xbmc/utils/Map.h new file mode 100644 index 0000000..17af545 --- /dev/null +++ b/xbmc/utils/Map.h @@ -0,0 +1,102 @@ + +/* + * Copyright (C) 2005-2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <algorithm> +#include <array> +#include <stdexcept> + +/*! + * \brief This class is designed to implement a constexpr version of std::map. + * The standard library std::map doesn't allow constexpr (and it + * doesn't look like it will be implemented in the future). This class + * utilizes std::array and std::pair as they allow constexpr. + * + * When using this class you should use the helper make_map instead of + * constructing this class directly. For example: + * constexpr auto myMap = make_map<int, std::string_view>({{1, "one"}}); + * + * This class is useful for mapping enum values to strings that can be + * compile time checked. This also helps with heap usage. + * + * Lookups have linear complexity, so should not be used for "big" maps. + */ +template<typename Key, typename Value, size_t Size> +class CMap +{ +public: + template<typename Iterable> + constexpr CMap(Iterable begin, Iterable end) + { + size_t index = 0; + while (begin != end) + { + // c++17 doesn't have constexpr assignment operator for std::pair + auto& first = m_map[index].first; + auto& second = m_map[index].second; + ++index; + + first = std::move(begin->first); + second = std::move(begin->second); + ++begin; + + //! @todo: c++20 can use constexpr assignment operator instead + // auto& p = data[index]; + // ++index; + + // p = std::move(*begin); + // ++begin; + // + } + } + + ~CMap() = default; + + constexpr const Value& at(const Key& key) const + { + const auto it = find(key); + if (it != m_map.cend()) + { + return it->second; + } + else + { + throw std::range_error("Not Found"); + } + } + + constexpr auto find(const Key& key) const + { + return std::find_if(m_map.cbegin(), m_map.cend(), + [&key](const auto& pair) { return pair.first == key; }); + } + + constexpr size_t size() const { return Size; } + + constexpr auto cbegin() const { return m_map.cbegin(); } + constexpr auto cend() const { return m_map.cend(); } + +private: + CMap() = delete; + + std::array<std::pair<Key, Value>, Size> m_map; +}; + +/*! + * \brief Use this helper when wanting to use CMap. This is needed to allow + * deducing the size of the map from the initializer list without + * needing to explicitly give the size of the map (similar to std::map). + * + */ +template<typename Key, typename Value, std::size_t Size> +constexpr auto make_map(std::pair<Key, Value>(&&m)[Size]) -> CMap<Key, Value, Size> +{ + return CMap<Key, Value, Size>(std::begin(m), std::end(m)); +} diff --git a/xbmc/utils/MathUtils.h b/xbmc/utils/MathUtils.h new file mode 100644 index 0000000..2b1dbcc --- /dev/null +++ b/xbmc/utils/MathUtils.h @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <assert.h> +#include <climits> +#include <cmath> +#include <stdint.h> +#include <type_traits> + +#if defined(HAVE_SSE2) && defined(__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. + +// clang-format off +#if defined(__aarch64__) || \ + defined(__alpha__) || \ + defined(__arc__) || \ + defined(__arm__) || \ + defined(__loongarch__) || \ + defined(_M_ARM) || \ + defined(__mips__) || \ + defined(__or1k__) || \ + defined(__powerpc__) || \ + defined(__ppc__) || \ + defined(__riscv) || \ + defined(__SH4__) || \ + defined(__s390x__) || \ + defined(__sparc__) || \ + defined(__xtensa__) +#define DISABLE_MATHUTILS_ASM_ROUND_INT +#endif +// clang-format on + +/*! \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) (INT_MIN / 2)) - 1.0); + assert(x < static_cast<double>((int) (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 + 2147483648.5)) - 0x80000000; + +#else + const float round_to_nearest = 0.5f; + int i; +#if defined(HAVE_SSE2) && defined(__SSE2__) + const float round_dn_to_nearest = 0.4999999f; + i = (x > 0) ? _mm_cvttsd_si32(_mm_set_sd(x + static_cast<double>(round_to_nearest))) + : _mm_cvttsd_si32(_mm_set_sd(x - static_cast<double>(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 static_cast<int>(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); + } + + /** + * Compare two floating-point numbers for equality and regard them + * as equal if their difference is below a given threshold. + * + * It is usually not useful to compare float numbers for equality with + * the standard operator== since very close numbers might have different + * representations. + */ + template<typename FloatT> + inline bool FloatEquals(FloatT f1, FloatT f2, FloatT maxDelta) + { + return (std::abs(f2 - f1) < maxDelta); + } + + /*! + * \brief Round a floating point number to nearest multiple + * \param value The value to round + * \param multiple The multiple + * \return The rounded value + */ + template<typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true> + inline T RoundF(const T value, const T multiple) + { + if (multiple == 0) + return value; + + return static_cast<T>(std::round(static_cast<double>(value) / static_cast<double>(multiple)) * + static_cast<double>(multiple)); + } + +#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/xbmc/utils/MemUtils.h b/xbmc/utils/MemUtils.h new file mode 100644 index 0000000..0266908 --- /dev/null +++ b/xbmc/utils/MemUtils.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <cstdint> +#include <memory> + +namespace KODI +{ +namespace MEMORY +{ +struct MemoryStatus +{ + unsigned int memoryLoad; + + uint64_t totalPhys; + uint64_t availPhys; +}; + +void* AlignedMalloc(size_t s, size_t alignTo); +void AlignedFree(void* p); +void GetMemoryStatus(MemoryStatus* buffer); +} +} diff --git a/xbmc/utils/Mime.cpp b/xbmc/utils/Mime.cpp new file mode 100644 index 0000000..576e499 --- /dev/null +++ b/xbmc/utils/Mime.cpp @@ -0,0 +1,700 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Mime.h" + +#include "FileItem.h" +#include "URIUtils.h" +#include "URL.h" +#include "filesystem/CurlFile.h" +#include "music/tags/MusicInfoTag.h" +#include "utils/StringUtils.h" +#include "video/VideoInfoTag.h" + +#include <algorithm> + +const std::map<std::string, std::string> CMime::m_mimetypes = { + {{"3dm", "x-world/x-3dmf"}, + {"3dmf", "x-world/x-3dmf"}, + {"3fr", "image/3fr"}, + {"a", "application/octet-stream"}, + {"aab", "application/x-authorware-bin"}, + {"aam", "application/x-authorware-map"}, + {"aas", "application/x-authorware-seg"}, + {"abc", "text/vnd.abc"}, + {"acgi", "text/html"}, + {"afl", "video/animaflex"}, + {"ai", "application/postscript"}, + {"aif", "audio/aiff"}, + {"aifc", "audio/x-aiff"}, + {"aiff", "audio/aiff"}, + {"aim", "application/x-aim"}, + {"aip", "text/x-audiosoft-intra"}, + {"ani", "application/x-navi-animation"}, + {"aos", "application/x-nokia-9000-communicator-add-on-software"}, + {"apng", "image/apng"}, + {"aps", "application/mime"}, + {"arc", "application/octet-stream"}, + {"arj", "application/arj"}, + {"art", "image/x-jg"}, + {"arw", "image/arw"}, + {"asf", "video/x-ms-asf"}, + {"asm", "text/x-asm"}, + {"asp", "text/asp"}, + {"asx", "video/x-ms-asf"}, + {"au", "audio/basic"}, + {"avi", "video/avi"}, + {"avs", "video/avs-video"}, + {"bcpio", "application/x-bcpio"}, + {"bin", "application/octet-stream"}, + {"bm", "image/bmp"}, + {"bmp", "image/bmp"}, + {"boo", "application/book"}, + {"book", "application/book"}, + {"boz", "application/x-bzip2"}, + {"bsh", "application/x-bsh"}, + {"bz", "application/x-bzip"}, + {"bz2", "application/x-bzip2"}, + {"c", "text/plain"}, + {"c++", "text/plain"}, + {"cat", "application/vnd.ms-pki.seccat"}, + {"cc", "text/plain"}, + {"ccad", "application/clariscad"}, + {"cco", "application/x-cocoa"}, + {"cdf", "application/cdf"}, + {"cer", "application/pkix-cert"}, + {"cer", "application/x-x509-ca-cert"}, + {"cha", "application/x-chat"}, + {"chat", "application/x-chat"}, + {"class", "application/java"}, + {"com", "application/octet-stream"}, + {"conf", "text/plain"}, + {"cpio", "application/x-cpio"}, + {"cpp", "text/x-c"}, + {"cpt", "application/x-cpt"}, + {"crl", "application/pkcs-crl"}, + {"crt", "application/pkix-cert"}, + {"cr2", "image/cr2"}, + {"crw", "image/crw"}, + {"csh", "application/x-csh"}, + {"css", "text/css"}, + {"cxx", "text/plain"}, + {"dcr", "application/x-director"}, + {"deepv", "application/x-deepv"}, + {"def", "text/plain"}, + {"der", "application/x-x509-ca-cert"}, + {"dif", "video/x-dv"}, + {"dir", "application/x-director"}, + {"dl", "video/dl"}, + {"divx", "video/x-msvideo"}, + {"dng", "image/dng"}, + {"doc", "application/msword"}, + {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {"dot", "application/msword"}, + {"dp", "application/commonground"}, + {"drw", "application/drafting"}, + {"dump", "application/octet-stream"}, + {"dv", "video/x-dv"}, + {"dvi", "application/x-dvi"}, + {"dwf", "model/vnd.dwf"}, + {"dwg", "image/vnd.dwg"}, + {"dxf", "image/vnd.dwg"}, + {"dxr", "application/x-director"}, + {"el", "text/x-script.elisp"}, + {"elc", "application/x-elc"}, + {"env", "application/x-envoy"}, + {"eps", "application/postscript"}, + {"erf", "image/erf"}, + {"es", "application/x-esrehber"}, + {"etx", "text/x-setext"}, + {"evy", "application/envoy"}, + {"exe", "application/octet-stream"}, + {"f", "text/x-fortran"}, + {"f77", "text/x-fortran"}, + {"f90", "text/x-fortran"}, + {"fdf", "application/vnd.fdf"}, + {"fif", "image/fif"}, + {"flac", "audio/flac"}, + {"fli", "video/fli"}, + {"flo", "image/florian"}, + {"flv", "video/x-flv"}, + {"flx", "text/vnd.fmi.flexstor"}, + {"fmf", "video/x-atomic3d-feature"}, + {"for", "text/plain"}, + {"for", "text/x-fortran"}, + {"fpx", "image/vnd.fpx"}, + {"frl", "application/freeloader"}, + {"funk", "audio/make"}, + {"g", "text/plain"}, + {"g3", "image/g3fax"}, + {"gif", "image/gif"}, + {"gl", "video/x-gl"}, + {"gsd", "audio/x-gsm"}, + {"gsm", "audio/x-gsm"}, + {"gsp", "application/x-gsp"}, + {"gss", "application/x-gss"}, + {"gtar", "application/x-gtar"}, + {"gz", "application/x-compressed"}, + {"gzip", "application/x-gzip"}, + {"h", "text/plain"}, + {"hdf", "application/x-hdf"}, + {"heic", "image/heic"}, + {"heif", "image/heif"}, + {"help", "application/x-helpfile"}, + {"hgl", "application/vnd.hp-hpgl"}, + {"hh", "text/plain"}, + {"hlb", "text/x-script"}, + {"hlp", "application/hlp"}, + {"hpg", "application/vnd.hp-hpgl"}, + {"hpgl", "application/vnd.hp-hpgl"}, + {"hqx", "application/binhex"}, + {"hta", "application/hta"}, + {"htc", "text/x-component"}, + {"htm", "text/html"}, + {"html", "text/html"}, + {"htmls", "text/html"}, + {"htt", "text/webviewhtml"}, + {"htx", "text/html"}, + {"ice", "x-conference/x-cooltalk"}, + {"ico", "image/x-icon"}, + {"idc", "text/plain"}, + {"ief", "image/ief"}, + {"iefs", "image/ief"}, + {"iges", "application/iges"}, + {"igs", "application/iges"}, + {"ima", "application/x-ima"}, + {"imap", "application/x-httpd-imap"}, + {"inf", "application/inf"}, + {"ins", "application/x-internet-signup"}, + {"ip", "application/x-ip2"}, + {"isu", "video/x-isvideo"}, + {"it", "audio/it"}, + {"iv", "application/x-inventor"}, + {"ivr", "i-world/i-vrml"}, + {"ivy", "application/x-livescreen"}, + {"jam", "audio/x-jam"}, + {"jav", "text/x-java-source"}, + {"java", "text/x-java-source"}, + {"jcm", "application/x-java-commerce"}, + {"jfif", "image/jpeg"}, + {"jp2", "image/jp2"}, + {"jfif-tbnl", "image/jpeg"}, + {"jpe", "image/jpeg"}, + {"jpeg", "image/jpeg"}, + {"jpg", "image/jpeg"}, + {"jps", "image/x-jps"}, + {"js", "application/javascript"}, + {"json", "application/json"}, + {"jut", "image/jutvision"}, + {"kar", "music/x-karaoke"}, + {"kdc", "image/kdc"}, + {"ksh", "text/x-script.ksh"}, + {"la", "audio/nspaudio"}, + {"lam", "audio/x-liveaudio"}, + {"latex", "application/x-latex"}, + {"lha", "application/lha"}, + {"lhx", "application/octet-stream"}, + {"list", "text/plain"}, + {"lma", "audio/nspaudio"}, + {"log", "text/plain"}, + {"lsp", "application/x-lisp"}, + {"lst", "text/plain"}, + {"lsx", "text/x-la-asf"}, + {"ltx", "application/x-latex"}, + {"lzh", "application/x-lzh"}, + {"lzx", "application/lzx"}, + {"m", "text/x-m"}, + {"m1v", "video/mpeg"}, + {"m2a", "audio/mpeg"}, + {"m2v", "video/mpeg"}, + {"m3u", "audio/x-mpegurl"}, + {"man", "application/x-troff-man"}, + {"map", "application/x-navimap"}, + {"mar", "text/plain"}, + {"mbd", "application/mbedlet"}, + {"mc$", "application/x-magic-cap-package-1.0"}, + {"mcd", "application/x-mathcad"}, + {"mcf", "text/mcf"}, + {"mcp", "application/netmc"}, + {"mdc", "image/mdc"}, + {"me", "application/x-troff-me"}, + {"mef", "image/mef"}, + {"mht", "message/rfc822"}, + {"mhtml", "message/rfc822"}, + {"mid", "audio/midi"}, + {"midi", "audio/midi"}, + {"mif", "application/x-mif"}, + {"mime", "message/rfc822"}, + {"mjf", "audio/x-vnd.audioexplosion.mjuicemediafile"}, + {"mjpg", "video/x-motion-jpeg"}, + {"mka", "audio/x-matroska"}, + {"mkv", "video/x-matroska"}, + {"mk3d", "video/x-matroska-3d"}, + {"mm", "application/x-meme"}, + {"mme", "application/base64"}, + {"mod", "audio/mod"}, + {"moov", "video/quicktime"}, + {"mov", "video/quicktime"}, + {"movie", "video/x-sgi-movie"}, + {"mos", "image/mos"}, + {"mp2", "audio/mpeg"}, + {"mp3", "audio/mpeg3"}, + {"mp4", "video/mp4"}, + {"mpa", "audio/mpeg"}, + {"mpc", "application/x-project"}, + {"mpe", "video/mpeg"}, + {"mpeg", "video/mpeg"}, + {"mpg", "video/mpeg"}, + {"mpga", "audio/mpeg"}, + {"mpp", "application/vnd.ms-project"}, + {"mpt", "application/x-project"}, + {"mpv", "application/x-project"}, + {"mpx", "application/x-project"}, + {"mrc", "application/marc"}, + {"mrw", "image/mrw"}, + {"ms", "application/x-troff-ms"}, + {"mv", "video/x-sgi-movie"}, + {"my", "audio/make"}, + {"mzz", "application/x-vnd.audioexplosion.mzz"}, + {"nap", "image/naplps"}, + {"naplps", "image/naplps"}, + {"nc", "application/x-netcdf"}, + {"ncm", "application/vnd.nokia.configuration-message"}, + {"nef", "image/nef"}, + {"nfo", "text/xml"}, + {"nif", "image/x-niff"}, + {"niff", "image/x-niff"}, + {"nix", "application/x-mix-transfer"}, + {"nrw", "image/nrw"}, + {"nsc", "application/x-conference"}, + {"nvd", "application/x-navidoc"}, + {"o", "application/octet-stream"}, + {"oda", "application/oda"}, + {"ogg", "audio/ogg"}, + {"omc", "application/x-omc"}, + {"omcd", "application/x-omcdatamaker"}, + {"omcr", "application/x-omcregerator"}, + {"orf", "image/orf"}, + {"p", "text/x-pascal"}, + {"p10", "application/pkcs10"}, + {"p12", "application/pkcs-12"}, + {"p7a", "application/x-pkcs7-signature"}, + {"p7c", "application/pkcs7-mime"}, + {"p7m", "application/pkcs7-mime"}, + {"p7r", "application/x-pkcs7-certreqresp"}, + {"p7s", "application/pkcs7-signature"}, + {"part", "application/pro_eng"}, + {"pas", "text/pascal"}, + {"pbm", "image/x-portable-bitmap"}, + {"pcl", "application/vnd.hp-pcl"}, + {"pct", "image/x-pict"}, + {"pcx", "image/x-pcx"}, + {"pdb", "chemical/x-pdb"}, + {"pdf", "application/pdf"}, + {"pef", "image/pef"}, + {"pfunk", "audio/make.my.funk"}, + {"pgm", "image/x-portable-greymap"}, + {"pic", "image/pict"}, + {"pict", "image/pict"}, + {"pkg", "application/x-newton-compatible-pkg"}, + {"pko", "application/vnd.ms-pki.pko"}, + {"pl", "text/x-script.perl"}, + {"plx", "application/x-pixclscript"}, + {"pm", "text/x-script.perl-module"}, + {"pm4", "application/x-pagemaker"}, + {"pm5", "application/x-pagemaker"}, + {"png", "image/png"}, + {"pnm", "application/x-portable-anymap"}, + {"pot", "application/vnd.ms-powerpoint"}, + {"pov", "model/x-pov"}, + {"ppa", "application/vnd.ms-powerpoint"}, + {"ppm", "image/x-portable-pixmap"}, + {"pps", "application/mspowerpoint"}, + {"ppt", "application/mspowerpoint"}, + {"ppz", "application/mspowerpoint"}, + {"pre", "application/x-freelance"}, + {"prt", "application/pro_eng"}, + {"ps", "application/postscript"}, + {"psd", "application/octet-stream"}, + {"pvu", "paleovu/x-pv"}, + {"pwz", "application/vnd.ms-powerpoint"}, + {"py", "text/x-script.python"}, + {"pyc", "application/x-bytecode.python"}, + {"qcp", "audio/vnd.qcelp"}, + {"qd3", "x-world/x-3dmf"}, + {"qd3d", "x-world/x-3dmf"}, + {"qif", "image/x-quicktime"}, + {"qt", "video/quicktime"}, + {"qtc", "video/x-qtc"}, + {"qti", "image/x-quicktime"}, + {"qtif", "image/x-quicktime"}, + {"ra", "audio/x-realaudio"}, + {"raf", "image/raf"}, + {"ram", "audio/x-pn-realaudio"}, + {"ras", "image/cmu-raster"}, + {"rast", "image/cmu-raster"}, + {"raw", "image/raw"}, + {"rexx", "text/x-script.rexx"}, + {"rf", "image/vnd.rn-realflash"}, + {"rgb", "image/x-rgb"}, + {"rm", "audio/x-pn-realaudio"}, + {"rmi", "audio/mid"}, + {"rmm", "audio/x-pn-realaudio"}, + {"rmp", "audio/x-pn-realaudio"}, + {"rng", "application/ringing-tones"}, + {"rnx", "application/vnd.rn-realplayer"}, + {"roff", "application/x-troff"}, + {"rp", "image/vnd.rn-realpix"}, + {"rpm", "audio/x-pn-realaudio-plugin"}, + {"rt", "text/richtext"}, + {"rtf", "text/richtext"}, + {"rtx", "text/richtext"}, + {"rv", "video/vnd.rn-realvideo"}, + {"rw2", "image/rw2"}, + {"s", "text/x-asm"}, + {"s3m", "audio/s3m"}, + {"saveme", "application/octet-stream"}, + {"sbk", "application/x-tbook"}, + {"scm", "video/x-scm"}, + {"sdml", "text/plain"}, + {"sdp", "application/sdp"}, + {"sdr", "application/sounder"}, + {"sea", "application/sea"}, + {"set", "application/set"}, + {"sgm", "text/sgml"}, + {"sgml", "text/sgml"}, + {"sh", "text/x-script.sh"}, + {"shar", "application/x-bsh"}, + {"shtml", "text/x-server-parsed-html"}, + {"sid", "audio/x-psid"}, + {"sit", "application/x-stuffit"}, + {"skd", "application/x-koan"}, + {"skm", "application/x-koan"}, + {"skp", "application/x-koan"}, + {"skt", "application/x-koan"}, + {"sl", "application/x-seelogo"}, + {"smi", "application/smil"}, + {"smil", "application/smil"}, + {"snd", "audio/basic"}, + {"sol", "application/solids"}, + {"spc", "text/x-speech"}, + {"spl", "application/futuresplash"}, + {"spr", "application/x-sprite"}, + {"sprite", "application/x-sprite"}, + {"src", "application/x-wais-source"}, + {"srw", "image/srw"}, + {"ssi", "text/x-server-parsed-html"}, + {"ssm", "application/streamingmedia"}, + {"sst", "application/vnd.ms-pki.certstore"}, + {"step", "application/step"}, + {"stl", "application/sla"}, + {"stp", "application/step"}, + {"sup", "application/x-pgs"}, + {"sv4cpio", "application/x-sv4cpio"}, + {"sv4crc", "application/x-sv4crc"}, + {"svf", "image/vnd.dwg"}, + {"svg", "image/svg+xml"}, + {"svr", "application/x-world"}, + {"swf", "application/x-shockwave-flash"}, + {"t", "application/x-troff"}, + {"talk", "text/x-speech"}, + {"tar", "application/x-tar"}, + {"tbk", "application/toolbook"}, + {"tcl", "text/x-script.tcl"}, + {"tcsh", "text/x-script.tcsh"}, + {"tex", "application/x-tex"}, + {"texi", "application/x-texinfo"}, + {"texinfo", "application/x-texinfo"}, + {"text", "text/plain"}, + {"tgz", "application/x-compressed"}, + {"tif", "image/tiff"}, + {"tiff", "image/tiff"}, + {"tr", "application/x-troff"}, + {"ts", "video/mp2t"}, + {"tsi", "audio/tsp-audio"}, + {"tsp", "audio/tsplayer"}, + {"tsv", "text/tab-separated-values"}, + {"turbot", "image/florian"}, + {"txt", "text/plain"}, + {"uil", "text/x-uil"}, + {"uni", "text/uri-list"}, + {"unis", "text/uri-list"}, + {"unv", "application/i-deas"}, + {"uri", "text/uri-list"}, + {"uris", "text/uri-list"}, + {"ustar", "application/x-ustar"}, + {"uu", "text/x-uuencode"}, + {"uue", "text/x-uuencode"}, + {"vcd", "application/x-cdlink"}, + {"vcs", "text/x-vcalendar"}, + {"vda", "application/vda"}, + {"vdo", "video/vdo"}, + {"vew", "application/groupwise"}, + {"viv", "video/vivo"}, + {"vivo", "video/vivo"}, + {"vmd", "application/vocaltec-media-desc"}, + {"vmf", "application/vocaltec-media-file"}, + {"voc", "audio/voc"}, + {"vos", "video/vosaic"}, + {"vox", "audio/voxware"}, + {"vqe", "audio/x-twinvq-plugin"}, + {"vqf", "audio/x-twinvq"}, + {"vql", "audio/x-twinvq-plugin"}, + {"vrml", "application/x-vrml"}, + {"vrt", "x-world/x-vrt"}, + {"vsd", "application/x-visio"}, + {"vst", "application/x-visio"}, + {"vsw", "application/x-visio"}, + {"vtt", "text/vtt"}, + {"w60", "application/wordperfect6.0"}, + {"w61", "application/wordperfect6.1"}, + {"w6w", "application/msword"}, + {"wav", "audio/wav"}, + {"wb1", "application/x-qpro"}, + {"wbmp", "image/vnd.wap.wbmp"}, + {"web", "application/vnd.xara"}, + {"webp", "image/webp"}, + {"wiz", "application/msword"}, + {"wk1", "application/x-123"}, + {"wma", "audio/x-ms-wma"}, + {"wmf", "windows/metafile"}, + {"wml", "text/vnd.wap.wml"}, + {"wmlc", "application/vnd.wap.wmlc"}, + {"wmls", "text/vnd.wap.wmlscript"}, + {"wmlsc", "application/vnd.wap.wmlscriptc"}, + {"wmv", "video/x-ms-wmv"}, + {"word", "application/msword"}, + {"wp", "application/wordperfect"}, + {"wp5", "application/wordperfect"}, + {"wp6", "application/wordperfect"}, + {"wpd", "application/wordperfect"}, + {"wq1", "application/x-lotus"}, + {"wri", "application/mswrite"}, + {"wrl", "model/vrml"}, + {"wrz", "model/vrml"}, + {"wsc", "text/scriplet"}, + {"wsrc", "application/x-wais-source"}, + {"wtk", "application/x-wintalk"}, + {"x3f", "image/x3f"}, + {"xbm", "image/xbm"}, + {"xdr", "video/x-amt-demorun"}, + {"xgz", "xgl/drawing"}, + {"xif", "image/vnd.xiff"}, + {"xl", "application/excel"}, + {"xla", "application/excel"}, + {"xlb", "application/excel"}, + {"xlc", "application/excel"}, + {"xld", "application/excel"}, + {"xlk", "application/excel"}, + {"xll", "application/excel"}, + {"xlm", "application/excel"}, + {"xls", "application/excel"}, + {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {"xlt", "application/excel"}, + {"xlv", "application/excel"}, + {"xlw", "application/excel"}, + {"xm", "audio/xm"}, + {"xml", "text/xml"}, + {"xmz", "xgl/movie"}, + {"xpix", "application/x-vnd.ls-xpix"}, + {"xpm", "image/xpm"}, + {"x-png", "image/png"}, + {"xspf", "application/xspf+xml"}, + {"xsr", "video/x-amt-showrun"}, + {"xvid", "video/x-msvideo"}, + {"xwd", "image/x-xwd"}, + {"xyz", "chemical/x-pdb"}, + {"z", "application/x-compressed"}, + {"zip", "application/zip"}, + {"zoo", "application/octet-stream"}, + {"zsh", "text/x-script.zsh"}}}; + +std::string CMime::GetMimeType(const std::string &extension) +{ + if (extension.empty()) + return ""; + + std::string ext = extension; + size_t posNotPoint = ext.find_first_not_of('.'); + if (posNotPoint != std::string::npos && posNotPoint > 0) + ext = extension.substr(posNotPoint); + transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + + std::map<std::string, std::string>::const_iterator it = m_mimetypes.find(ext); + if (it != m_mimetypes.end()) + return it->second; + + return ""; +} + +std::string CMime::GetMimeType(const CFileItem &item) +{ + std::string path = item.GetDynPath(); + 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)); +} + +std::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; + + std::string 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/xbmc/utils/Mime.h b/xbmc/utils/Mime.h new file mode 100644 index 0000000..d3554b9 --- /dev/null +++ b/xbmc/utils/Mime.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <map> +#include <string> + +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); + +private: + static bool parseMimeType(const std::string& mimeType, std::string& type, std::string& subtype); + + static const std::map<std::string, std::string> m_mimetypes; +}; diff --git a/xbmc/utils/MovingSpeed.cpp b/xbmc/utils/MovingSpeed.cpp new file mode 100644 index 0000000..1e4269e --- /dev/null +++ b/xbmc/utils/MovingSpeed.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "MovingSpeed.h" + +#include "utils/MathUtils.h" +#include "utils/TimeUtils.h" +#include "utils/log.h" + +void UTILS::MOVING_SPEED::CMovingSpeed::AddEventConfig(uint32_t eventId, + float acceleration, + float maxVelocity, + uint32_t resetTimeout) +{ + EventCfg eventCfg{acceleration, maxVelocity, resetTimeout}; + m_eventsData.emplace(eventId, EventData{eventCfg}); +} + +void UTILS::MOVING_SPEED::CMovingSpeed::AddEventConfig(uint32_t eventId, EventCfg event) +{ + m_eventsData.emplace(eventId, EventData{event}); +} + +void UTILS::MOVING_SPEED::CMovingSpeed::AddEventMapConfig(MapEventConfig& configs) +{ + for (auto& cfg : configs) + { + AddEventConfig(static_cast<uint32_t>(cfg.first), cfg.second); + } +} + +void UTILS::MOVING_SPEED::CMovingSpeed::Reset() +{ + m_currentEventId = 0; + for (auto& eventPair : m_eventsData) + { + Reset(eventPair.first); + } +} + +void UTILS::MOVING_SPEED::CMovingSpeed::Reset(uint32_t eventId) +{ + auto mapIt = m_eventsData.find(eventId); + if (mapIt == m_eventsData.end()) + { + CLog::LogF(LOGWARNING, "Cannot reset Event ID {} configuration", eventId); + } + else + { + EventData& event = mapIt->second; + event.m_currentVelocity = 1.0f; + event.m_lastFrameTime = 0; + } +} + +float UTILS::MOVING_SPEED::CMovingSpeed::GetUpdatedDistance(uint32_t eventId) +{ + auto mapEventIt = m_eventsData.find(eventId); + + if (mapEventIt == m_eventsData.end()) + { + CLog::LogF(LOGDEBUG, "No event set for event ID {}", eventId); + return 0; + } + + EventData& eventData = mapEventIt->second; + EventCfg& eventCfg = eventData.m_config; + + uint32_t currentFrameTime{CTimeUtils::GetFrameTime()}; + uint32_t deltaFrameTime{currentFrameTime - eventData.m_lastFrameTime}; + float distance = (eventCfg.m_delta == 0.0f) ? 1.0f : eventCfg.m_delta; + + if (eventData.m_lastFrameTime != 0 && deltaFrameTime > eventCfg.m_resetTimeout) + { + // If the delta time exceed the timeout then reset values + Reset(eventId); + } + else if (m_currentEventId != eventId) + { + // If the event id is changed then reset values + Reset(eventId); + } + else if (eventData.m_lastFrameTime != 0) + { + // Calculate the new speed based on time so as not to depend on the frame rate + eventData.m_currentVelocity += + eventCfg.m_acceleration * (static_cast<float>(deltaFrameTime) / 1000); + + if (eventCfg.m_maxVelocity > 0 && eventData.m_currentVelocity > eventCfg.m_maxVelocity) + eventData.m_currentVelocity = eventCfg.m_maxVelocity; + + distance = eventData.m_currentVelocity * (static_cast<float>(deltaFrameTime) / 1000); + if (eventCfg.m_delta > 0.0f) + distance = MathUtils::RoundF(distance, eventCfg.m_delta); + } + + m_currentEventId = eventId; + eventData.m_lastFrameTime = currentFrameTime; + return distance; +} + +UTILS::MOVING_SPEED::EventType UTILS::MOVING_SPEED::ParseEventType(std::string_view eventType) +{ + if (eventType == "up") + return EventType::UP; + else if (eventType == "down") + return EventType::DOWN; + else if (eventType == "left") + return EventType::LEFT; + else if (eventType == "right") + return EventType::RIGHT; + else + { + CLog::LogF(LOGERROR, "Unsupported event type \"{}\"", eventType); + return EventType::NONE; + } +} diff --git a/xbmc/utils/MovingSpeed.h b/xbmc/utils/MovingSpeed.h new file mode 100644 index 0000000..70214a6 --- /dev/null +++ b/xbmc/utils/MovingSpeed.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <map> +#include <stdint.h> +#include <string_view> + +namespace UTILS +{ +namespace MOVING_SPEED +{ + +struct EventCfg +{ + /*! + * \param acceleration Acceleration in pixels per second (px/sec) + * \param maxVelocity Max movement speed, it depends from acceleration value, + * a suitable value could be (acceleration value)*3. Set 0 to disable it + * \param resetTimeout Resets acceleration speed if idle for specified millisecs + */ + EventCfg(float acceleration, float maxVelocity, uint32_t resetTimeout) + : m_acceleration{acceleration}, m_maxVelocity{maxVelocity}, m_resetTimeout{resetTimeout} + { + } + + /*! + * \param acceleration Acceleration in pixels per second (px/sec) + * \param maxVelocity Max movement speed, it depends from acceleration value, + * a suitable value could be (acceleration value)*3. Set 0 to disable it + * \param resetTimeout Resets acceleration speed if idle for specified millisecs + * \param delta Specify the minimal increment step, and the result of distance + value will be rounded by delta. Set 0 to disable it + */ + EventCfg(float acceleration, float maxVelocity, uint32_t resetTimeout, float delta) + : m_acceleration{acceleration}, + m_maxVelocity{maxVelocity}, + m_resetTimeout{resetTimeout}, + m_delta{delta} + { + } + + float m_acceleration; + float m_maxVelocity; + uint32_t m_resetTimeout; + float m_delta{0}; +}; + +enum class EventType +{ + NONE = 0, + UP, + DOWN, + LEFT, + RIGHT +}; + +typedef std::map<EventType, EventCfg> MapEventConfig; + +/*! + * \brief Class to calculate the velocity for a motion effect. + * To ensure it works, the GetUpdatedDistance method must be called at each + * input received (e.g. continuous key press of same key on the keyboard). + * The motion effect will stop at the event ID change (different key pressed). + */ +class CMovingSpeed +{ +public: + /*! + * \brief Add the configuration for an event + * \param eventId The id for the event, must be unique + * \param acceleration Acceleration in pixels per second (px/sec) + * \param maxVelocity Max movement speed, it depends from acceleration value, + * a suitable value could be (acceleration value)*3. Set 0 to disable it + * \param resetTimeout Resets acceleration speed if idle for specified millisecs + */ + void AddEventConfig(uint32_t eventId, + float acceleration, + float maxVelocity, + uint32_t resetTimeout); + + /*! + * \brief Add the configuration for an event + * \param eventId The id for the event, must be unique + * \param event The event configuration + */ + void AddEventConfig(uint32_t eventId, EventCfg event); + + /*! + * \brief Add a map of events configuration + * \param configs The map of events configuration where key value is event id, + */ + void AddEventMapConfig(MapEventConfig& configs); + + /*! + * \brief Reset stored velocity to all events + * \param event The event configuration + */ + void Reset(); + + /*! + * \brief Reset stored velocity for a specific event + * \param event The event ID + */ + void Reset(uint32_t eventId); + + /*! + * \brief Get the updated distance based on acceleration speed + * \param eventId The id for the event to handle + * \return The distance + */ + float GetUpdatedDistance(uint32_t eventId); + + /*! + * \brief Get the updated distance based on acceleration speed + * \param eventType The event type to handle + * \return The distance + */ + float GetUpdatedDistance(EventType eventType) + { + return GetUpdatedDistance(static_cast<uint32_t>(eventType)); + } + +private: + struct EventData + { + EventData(EventCfg config) : m_config{config} {} + + EventCfg m_config; + float m_currentVelocity{1.0f}; + uint32_t m_lastFrameTime{0}; + }; + + uint32_t m_currentEventId{0}; + std::map<uint32_t, EventData> m_eventsData; +}; + +/*! + * \brief Parse a string event type to enum EventType. + * \param eventType The event type as string + * \return The EventType if has success, otherwise EventType::NONE + */ +EventType ParseEventType(std::string_view eventType); + +} // namespace MOVING_SPEED +} // namespace UTILS diff --git a/xbmc/utils/Observer.cpp b/xbmc/utils/Observer.cpp new file mode 100644 index 0000000..5c60a4b --- /dev/null +++ b/xbmc/utils/Observer.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + + +#include "Observer.h" + +#include <algorithm> +#include <mutex> + +Observable &Observable::operator=(const Observable &observable) +{ + std::unique_lock<CCriticalSection> lock(m_obsCritSection); + + m_bObservableChanged = static_cast<bool>(observable.m_bObservableChanged); + m_observers = observable.m_observers; + + return *this; +} + +bool Observable::IsObserving(const Observer &obs) const +{ + std::unique_lock<CCriticalSection> lock(m_obsCritSection); + return std::find(m_observers.begin(), m_observers.end(), &obs) != m_observers.end(); +} + +void Observable::RegisterObserver(Observer *obs) +{ + std::unique_lock<CCriticalSection> lock(m_obsCritSection); + if (!IsObserving(*obs)) + { + m_observers.push_back(obs); + } +} + +void Observable::UnregisterObserver(Observer *obs) +{ + std::unique_lock<CCriticalSection> lock(m_obsCritSection); + auto iter = std::remove(m_observers.begin(), m_observers.end(), obs); + if (iter != m_observers.end()) + m_observers.erase(iter); +} + +void Observable::NotifyObservers(const ObservableMessage message /* = ObservableMessageNone */) +{ + // Make sure the set/compare is atomic + // so we don't clobber the variable in a race condition + auto bNotify = m_bObservableChanged.exchange(false); + + if (bNotify) + SendMessage(message); +} + +void Observable::SetChanged(bool SetTo) +{ + m_bObservableChanged = SetTo; +} + +void Observable::SendMessage(const ObservableMessage message) +{ + std::unique_lock<CCriticalSection> lock(m_obsCritSection); + + for (auto& observer : m_observers) + { + observer->Notify(*this, message); + } +} diff --git a/xbmc/utils/Observer.h b/xbmc/utils/Observer.h new file mode 100644 index 0000000..49c9b3c --- /dev/null +++ b/xbmc/utils/Observer.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/CriticalSection.h" + +#include <atomic> +#include <vector> + +class Observable; +class ObservableMessageJob; + +typedef enum +{ + ObservableMessageNone, + ObservableMessagePeripheralsChanged, + ObservableMessageSettingsChanged, + ObservableMessageButtonMapsChanged, + // Used for example when the subtitle alignment position change + ObservableMessagePositionChanged, + ObservableMessageGamePortsChanged, + ObservableMessageGameAgentsChanged, +} ObservableMessage; + +class Observer +{ +public: + Observer() = default; + virtual ~Observer() = default; + /*! + * @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; +}; + +class Observable +{ + friend class ObservableMessageJob; + +public: + Observable() = default; + virtual ~Observable() = default; + Observable& operator=(const Observable& observable); + + /*! + * @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. + */ + void SendMessage(const ObservableMessage message); + + std::atomic<bool> m_bObservableChanged{false}; /*!< true when the observable is marked as changed, false otherwise */ + std::vector<Observer *> m_observers; /*!< all observers */ + mutable CCriticalSection m_obsCritSection; /*!< mutex */ +}; diff --git a/xbmc/utils/POUtils.cpp b/xbmc/utils/POUtils.cpp new file mode 100644 index 0000000..830336d --- /dev/null +++ b/xbmc/utils/POUtils.cpp @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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() = default; + +bool CPODocument::LoadFile(const std::string &pofilename) +{ + CURL poFileUrl(pofilename); + if (!XFILE::CFile::Exists(poFileUrl)) + return false; + + XFILE::CFile file; + std::vector<uint8_t> buf; + if (file.LoadFile(poFileUrl, buf) < 18) // at least a size of a minimalistic header + { + CLog::Log(LOGERROR, "{}: can't load file \"{}\" or file is too small", __FUNCTION__, + pofilename); + return false; + } + + m_strBuffer = '\n'; + m_strBuffer.append(reinterpret_cast<char*>(buf.data()), 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: {}", pofilename); + 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: {}", + m_Entry.Content); + 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.empty()) + { + CLog::Log(LOGERROR, + "POParser: msgstr[] plural lines have zero valid strings. " + "Failed entry: {}", + m_Entry.Content); + m_Entry.msgStrPlural.resize(1); // Put 1 element with an empty string into the vector + } +} + +const std::string& CPODocument::GetPlurMsgstr(size_t plural) const +{ + if (m_Entry.msgStrPlural.size() < plural+1) + { + CLog::Log(LOGERROR, + "POParser: msgstr[{}] plural field requested, but not found in PO file. " + "Failed entry: {}", + static_cast<int>(plural), m_Entry.Content); + 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: {}", + m_Entry.Content); + 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: {}", + m_Entry.Content); + 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: {}", m_Entry.Content); + 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: {}", + filename); + else + CLog::Log(LOGDEBUG, + "POParser: PO file has Win Style Line Endings. " + "Converted in memory to Linux LF for file: {}", + filename); + + 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/xbmc/utils/POUtils.h b/xbmc/utils/POUtils.h new file mode 100644 index 0000000..1752b79 --- /dev/null +++ b/xbmc/utils/POUtils.h @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stdint.h> +#include <string> +#include <vector> + +typedef 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. +} POIdType; + +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/xbmc/utils/PlayerUtils.cpp b/xbmc/utils/PlayerUtils.cpp new file mode 100644 index 0000000..3fd6847 --- /dev/null +++ b/xbmc/utils/PlayerUtils.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PlayerUtils.h" + +#include "FileItem.h" +#include "music/MusicUtils.h" +#include "utils/Variant.h" +#include "video/VideoUtils.h" + +bool CPlayerUtils::IsItemPlayable(const CFileItem& itemIn) +{ + const CFileItem item(itemIn.GetItemToPlay()); + + // General + if (item.IsParentFolder()) + return false; + + // Plugins + if (item.IsPlugin() && item.GetProperty("isplayable").asBoolean()) + return true; + + // Music + if (MUSIC_UTILS::IsItemPlayable(item)) + return true; + + // Movies / TV Shows / Music Videos + if (VIDEO_UTILS::IsItemPlayable(item)) + return true; + + //! @todo add more types on demand. + + return false; +} diff --git a/xbmc/utils/PlayerUtils.h b/xbmc/utils/PlayerUtils.h new file mode 100644 index 0000000..f62d891 --- /dev/null +++ b/xbmc/utils/PlayerUtils.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class CFileItem; + +class CPlayerUtils +{ +public: + static bool IsItemPlayable(const CFileItem& item); +}; diff --git a/xbmc/utils/ProgressJob.cpp b/xbmc/utils/ProgressJob.cpp new file mode 100644 index 0000000..6ef1f24 --- /dev/null +++ b/xbmc/utils/ProgressJob.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ProgressJob.h" + +#include "ServiceBroker.h" +#include "dialogs/GUIDialogExtendedProgressBar.h" +#include "dialogs/GUIDialogProgress.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "utils/Variant.h" + +#include <math.h> + +CProgressJob::CProgressJob() + : m_progress(NULL), + m_progressDialog(NULL) +{ } + +CProgressJob::CProgressJob(CGUIDialogProgressBarHandle* progressBar) + : m_progress(progressBar), + m_progressDialog(NULL) +{ } + +CProgressJob::~CProgressJob() +{ + MarkFinished(); + + m_progress = NULL; + m_progressDialog = NULL; +} + +bool CProgressJob::ShouldCancel(unsigned int progress, unsigned int total) const +{ + if (IsCancelled()) + return true; + + SetProgress(progress, total); + + return CJob::ShouldCancel(progress, total); +} + +bool CProgressJob::DoModal() +{ + m_progress = NULL; + + // get a progress dialog if we don't already have one + if (m_progressDialog == NULL) + { + m_progressDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + + if (m_progressDialog == NULL) + return false; + } + + m_modal = true; + + // do the work + bool result = DoWork(); + + // mark the progress dialog as finished (will close it) + MarkFinished(); + m_modal = false; + + return result; +} + +void CProgressJob::SetProgressIndicators(CGUIDialogProgressBarHandle* progressBar, CGUIDialogProgress* progressDialog, bool updateProgress /* = true */, bool updateInformation /* = true */) +{ + SetProgressBar(progressBar); + SetProgressDialog(progressDialog); + SetUpdateProgress(updateProgress); + SetUpdateInformation(updateInformation); + + // disable auto-closing + SetAutoClose(false); +} + +void CProgressJob::ShowProgressDialog() const +{ + if (!IsModal() || m_progressDialog == NULL || + m_progressDialog->IsDialogRunning()) + return; + + // show the progress dialog as a modal dialog with a progress bar + m_progressDialog->Open(); + m_progressDialog->ShowProgressBar(true); +} + +void CProgressJob::SetTitle(const std::string &title) +{ + if (!m_updateInformation) + return; + + if (m_progress != NULL) + m_progress->SetTitle(title); + else if (m_progressDialog != NULL) + { + m_progressDialog->SetHeading(CVariant{title}); + + ShowProgressDialog(); + } +} + +void CProgressJob::SetText(const std::string &text) +{ + if (!m_updateInformation) + return; + + if (m_progress != NULL) + m_progress->SetText(text); + else if (m_progressDialog != NULL) + { + m_progressDialog->SetText(CVariant{text}); + + ShowProgressDialog(); + } +} + +void CProgressJob::SetProgress(float percentage) const +{ + if (!m_updateProgress) + return; + + if (m_progress != NULL) + m_progress->SetPercentage(percentage); + else if (m_progressDialog != NULL) + { + ShowProgressDialog(); + + int iPercentage = static_cast<int>(ceil(percentage)); + // only change and update the progress bar if its percentage value changed + // (this can have a huge impact on performance if it's called a lot) + if (iPercentage != m_progressDialog->GetPercentage()) + { + m_progressDialog->SetPercentage(iPercentage); + m_progressDialog->Progress(); + } + } +} + +void CProgressJob::SetProgress(int currentStep, int totalSteps) const +{ + if (!m_updateProgress) + return; + + if (m_progress != NULL) + m_progress->SetProgress(currentStep, totalSteps); + else if (m_progressDialog != NULL) + SetProgress((static_cast<float>(currentStep) * 100.0f) / totalSteps); +} + +void CProgressJob::MarkFinished() +{ + if (m_progress != NULL) + { + if (m_updateProgress) + { + m_progress->MarkFinished(); + // We don't own this pointer and it will be deleted after it's marked finished + // just set it to nullptr so we don't try to use it again + m_progress = nullptr; + } + } + else if (m_progressDialog != NULL && m_autoClose) + m_progressDialog->Close(); +} + +bool CProgressJob::IsCancelled() const +{ + if (m_progressDialog != NULL) + return m_progressDialog->IsCanceled(); + + return false; +} + +bool CProgressJob::HasProgressIndicator() const +{ + return m_progress != nullptr || m_progressDialog != nullptr; +} diff --git a/xbmc/utils/ProgressJob.h b/xbmc/utils/ProgressJob.h new file mode 100644 index 0000000..f1117aa --- /dev/null +++ b/xbmc/utils/ProgressJob.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/Job.h" + +#include <string> + +class CGUIDialogProgress; +class CGUIDialogProgressBarHandle; + +/*! + \brief Basic implementation of a CJob with a progress bar to indicate the + progress of the job being processed. + */ +class CProgressJob : public CJob +{ +public: + ~CProgressJob() override; + + // implementation of CJob + const char *GetType() const override { return "ProgressJob"; } + bool operator==(const CJob* job) const override { return false; } + bool ShouldCancel(unsigned int progress, unsigned int total) const override; + + /*! + \brief Executes the job showing a modal progress dialog. + */ + bool DoModal(); + + /*! + \brief Sets the given progress indicators to be used during execution of + the job. + + \details This automatically disables auto-closing the given progress + indicators once the job has been finished. + + \param progressBar Progress bar handle to be used. + \param progressDialog Progress dialog to be used. + \param updateProgress (optional) Whether to show progress updates. + \param updateInformation (optional) Whether to show progress information. + */ + void SetProgressIndicators(CGUIDialogProgressBarHandle* progressBar, CGUIDialogProgress* progressDialog, bool updateProgress = true, bool updateInformation = true); + + bool HasProgressIndicator() const; + +protected: + CProgressJob(); + explicit CProgressJob(CGUIDialogProgressBarHandle* progressBar); + + /*! + \brief Whether the job is being run modally or in the background. + */ + bool IsModal() const { return m_modal; } + + /*! + \brief Returns the progress bar indicating the progress of the job. + */ + CGUIDialogProgressBarHandle* GetProgressBar() const { return m_progress; } + + /*! + \brief Sets the progress bar indicating the progress of the job. + */ + void SetProgressBar(CGUIDialogProgressBarHandle* progress) { m_progress = progress; } + + /*! + \brief Returns the progress dialog indicating the progress of the job. + */ + CGUIDialogProgress* GetProgressDialog() const { return m_progressDialog; } + + /*! + \brief Sets the progress bar indicating the progress of the job. + */ + void SetProgressDialog(CGUIDialogProgress* progressDialog) { m_progressDialog = progressDialog; } + + /*! + \brief Whether to automatically close the progress indicator in MarkFinished(). + */ + bool GetAutoClose() { return m_autoClose; } + + /*! + \brief Set whether to automatically close the progress indicator in MarkFinished(). + */ + void SetAutoClose(bool autoClose) { m_autoClose = autoClose; } + + /*! + \brief Whether to update the progress bar or not. + */ + bool GetUpdateProgress() { return m_updateProgress; } + + /*! + \brief Set whether to update the progress bar or not. + */ + void SetUpdateProgress(bool updateProgress) { m_updateProgress = updateProgress; } + + /*! + \brief Whether to update the progress information or not. + */ + bool GetUpdateInformation() { return m_updateInformation; } + + /*! + \brief Set whether to update the progress information or not. + */ + void SetUpdateInformation(bool updateInformation) { m_updateInformation = updateInformation; } + + /*! + \brief Makes sure that the modal dialog is being shown. + */ + void ShowProgressDialog() const; + + /*! + \brief Sets the given title as the title of the progress bar. + + \param[in] title Title to be set + */ + void SetTitle(const std::string &title); + + /*! + \brief Sets the given text as the description of the progress bar. + + \param[in] text Text to be set + */ + void SetText(const std::string &text); + + /*! + \brief Sets the progress of the progress bar to the given value in percentage. + + \param[in] percentage Percentage to be set as the current progress + */ + void SetProgress(float percentage) const; + + /*! + \brief Sets the progress of the progress bar to the given value. + + \param[in] currentStep Current step being processed + \param[in] totalSteps Total steps to be processed + */ + void SetProgress(int currentStep, int totalSteps) const; + + /*! + \brief Marks the progress as finished by setting it to 100%. + */ + void MarkFinished(); + + /*! + \brief Checks if the progress dialog has been cancelled. + */ + bool IsCancelled() const; + +private: + bool m_modal = false; + bool m_autoClose = true; + bool m_updateProgress = true; + bool m_updateInformation = true; + mutable CGUIDialogProgressBarHandle* m_progress; + mutable CGUIDialogProgress* m_progressDialog; +}; diff --git a/xbmc/utils/Random.h b/xbmc/utils/Random.h new file mode 100644 index 0000000..ac2a073 --- /dev/null +++ b/xbmc/utils/Random.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <algorithm> +#include <random> + +namespace KODI +{ +namespace UTILS +{ +template<class TIterator> +void RandomShuffle(TIterator begin, TIterator end) +{ + std::random_device rd; + std::mt19937 mt(rd()); + std::shuffle(begin, end, mt); +} +} +} diff --git a/xbmc/utils/RecentlyAddedJob.cpp b/xbmc/utils/RecentlyAddedJob.cpp new file mode 100644 index 0000000..fca1f6d --- /dev/null +++ b/xbmc/utils/RecentlyAddedJob.cpp @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RecentlyAddedJob.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindow.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/WindowIDs.h" +#include "music/MusicDatabase.h" +#include "music/MusicDbUrl.h" +#include "music/MusicThumbLoader.h" +#include "music/tags/MusicInfoTag.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/VideoInfoTag.h" +#include "video/VideoThumbLoader.h" + +#if defined(TARGET_DARWIN_TVOS) +#include "platform/darwin/tvos/TVOSTopShelf.h" +#endif + +#define NUM_ITEMS 10 + +CRecentlyAddedJob::CRecentlyAddedJob(int flag) +{ + m_flag = flag; +} + +bool CRecentlyAddedJob::UpdateVideo() +{ + auto home = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_HOME); + + if ( home == nullptr ) + 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) + { + auto item = items.Get(i); + std::string value = std::to_string(i + 1); + std::string strRating = + StringUtils::Format("{:.1f}", item->GetVideoInfoTag()->GetRating().rating); + + home->SetProperty("LatestMovie." + value + ".Title" , item->GetLabel()); + home->SetProperty("LatestMovie." + value + ".Rating" , strRating); + home->SetProperty("LatestMovie." + value + ".Year" , item->GetVideoInfoTag()->GetYear()); + 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")); + home->SetProperty("LatestMovie." + value + ".Poster" , item->GetArt("poster")); + } + } + for (; i < NUM_ITEMS; ++i) + { + std::string value = std::to_string(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" , ""); + home->SetProperty("LatestMovie." + value + ".Poster" , ""); + } + + i = 0; + CFileItemList TVShowItems; + + if (videodatabase.GetRecentlyAddedEpisodesNav("videodb://recentlyaddedepisodes/", TVShowItems, NUM_ITEMS)) + { + for (; i < TVShowItems.Size(); ++i) + { + auto item = TVShowItems.Get(i); + int EpisodeSeason = item->GetVideoInfoTag()->m_iSeason; + int EpisodeNumber = item->GetVideoInfoTag()->m_iEpisode; + std::string EpisodeNo = StringUtils::Format("s{:02}e{:02}", EpisodeSeason, EpisodeNumber); + std::string value = std::to_string(i + 1); + std::string strRating = + StringUtils::Format("{:.1f}", item->GetVideoInfoTag()->GetRating().rating); + + 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) + { + std::string value = std::to_string(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" , ""); + } + +#if defined(TARGET_DARWIN_TVOS) + // Add recently added Movies and TvShows items on tvOS Kodi TopShelf + CTVOSTopShelf::GetInstance().SetTopShelfItems(items, TVOSTopShelfItemsCategory::MOVIES); + CTVOSTopShelf::GetInstance().SetTopShelfItems(TVShowItems, TVOSTopShelfItemsCategory::TV_SHOWS); +#endif + + i = 0; + CFileItemList MusicVideoItems; + + if (videodatabase.GetRecentlyAddedMusicVideosNav("videodb://recentlyaddedmusicvideos/", MusicVideoItems, NUM_ITEMS)) + { + for (; i < MusicVideoItems.Size(); ++i) + { + auto item = MusicVideoItems.Get(i); + std::string value = std::to_string(i + 1); + + home->SetProperty("LatestMusicVideo." + value + ".Title" , item->GetLabel()); + home->SetProperty("LatestMusicVideo." + value + ".Year" , item->GetVideoInfoTag()->GetYear()); + 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, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->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) + { + std::string value = std::to_string(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() +{ + auto home = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_HOME); + + if ( home == nullptr ) + 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)) + { + int idAlbum = -1; + std::string strAlbumThumb; + std::string strAlbumFanart; + for (; i < musicItems.Size(); ++i) + { + auto item = musicItems.Get(i); + std::string value = std::to_string(i + 1); + + std::string strRating; + std::string strAlbum = item->GetMusicInfoTag()->GetAlbum(); + std::string strArtist = item->GetMusicInfoTag()->GetArtistString(); + + 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 = std::to_string(item->GetMusicInfoTag()->GetUserrating()); + + 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) + { + std::string value = std::to_string(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)) + { + size_t j = 0; + for (; j < albums.size(); ++j) + { + auto& album=albums[j]; + std::string value = std::to_string(j + 1); + std::string strThumb; + std::string strFanart; + bool artfound = false; + std::vector<ArtForThumbLoader> art; + // Get album thumb and fanart for first album artist + artfound = musicdatabase.GetArtForItem(-1, album.idAlbum, -1, true, art); + if (artfound) + { + for (const auto& artitem : art) + { + if (artitem.mediaType == MediaTypeAlbum && artitem.artType == "thumb") + strThumb = artitem.url; + else if (artitem.mediaType == MediaTypeArtist && artitem.artType == "fanart") + strFanart = artitem.url; + } + } + + std::string strDBpath = StringUtils::Format("musicdb://albums/{}/", album.idAlbum); + + home->SetProperty("LatestAlbum." + value + ".Title" , album.strAlbum); + home->SetProperty("LatestAlbum." + value + ".Year" , album.strReleaseDate); + home->SetProperty("LatestAlbum." + value + ".Artist" , album.GetAlbumArtistString()); + home->SetProperty("LatestAlbum." + value + ".Rating" , album.fRating); + home->SetProperty("LatestAlbum." + value + ".Path" , strDBpath); + home->SetProperty("LatestAlbum." + value + ".Thumb" , strThumb); + home->SetProperty("LatestAlbum." + value + ".Fanart" , strFanart); + } + i = j; + } + for (; i < NUM_ITEMS; ++i) + { + std::string value = std::to_string(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() +{ + auto home = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_HOME); + + if ( home == nullptr ) + return false; + + CLog::Log(LOGDEBUG, "CRecentlyAddedJob::UpdateTotal() - Running RecentlyAdded home screen update"); + + CVideoDatabase videodatabase; + CMusicDatabase musicdatabase; + + musicdatabase.Open(); + + CMusicDbUrl musicUrl; + musicUrl.FromString("musicdb://artists/"); + musicUrl.AddOption("albumartistsonly", !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS)); + + CFileItemList items; + CDatabase::Filter filter; + musicdatabase.GetArtistsByWhere(musicUrl.ToString(), filter, items, SortDescription(), true); + int MusArtistTotals = 0; + if (items.Size() == 1 && items.Get(0)->HasProperty("total")) + MusArtistTotals = static_cast<int>(items.Get(0)->GetProperty("total").asInteger()); + + int MusSongTotals = atoi(musicdatabase.GetSingleValue("songview" , "count(1)").c_str()); + int MusAlbumTotals = atoi(musicdatabase.GetSingleValue("songview" , "count(distinct strAlbum)").c_str()); + musicdatabase.Close(); + + videodatabase.Open(); + int tvShowCount = atoi(videodatabase.GetSingleValue("tvshow_view" , "count(1)").c_str()); + int movieTotals = atoi(videodatabase.GetSingleValue("movie_view" , "count(1)").c_str()); + int movieWatched = atoi(videodatabase.GetSingleValue("movie_view" , "count(playCount)").c_str()); + int MusVidTotals = atoi(videodatabase.GetSingleValue("musicvideo_view" , "count(1)").c_str()); + int MusVidWatched = atoi(videodatabase.GetSingleValue("musicvideo_view" , "count(playCount)").c_str()); + int EpWatched = atoi(videodatabase.GetSingleValue("tvshow_view" , "sum(watchedcount)").c_str()); + int EpCount = atoi(videodatabase.GetSingleValue("tvshow_view" , "sum(totalcount)").c_str()); + int TvShowsWatched = atoi(videodatabase.GetSingleValue("tvshow_view" , "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/xbmc/utils/RecentlyAddedJob.h b/xbmc/utils/RecentlyAddedJob.h new file mode 100644 index 0000000..f61b60b --- /dev/null +++ b/xbmc/utils/RecentlyAddedJob.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Job.h" + +enum ERecentlyAddedFlag +{ + Audio = 0x1, + Video = 0x2, + Totals = 0x4 +}; + +class CRecentlyAddedJob : public CJob +{ +public: + explicit CRecentlyAddedJob(int flag); + static bool UpdateVideo(); + static bool UpdateMusic(); + static bool UpdateTotal(); + bool DoWork() override; +private: + int m_flag; +}; diff --git a/xbmc/utils/RegExp.cpp b/xbmc/utils/RegExp.cpp new file mode 100644 index 0000000..9667b64 --- /dev/null +++ b/xbmc/utils/RegExp.cpp @@ -0,0 +1,651 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RegExp.h" + +#include "log.h" +#include "utils/StringUtils.h" +#include "utils/Utf8Utils.h" + +#include <algorithm> +#include <stdlib.h> +#include <string.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(LOGFATAL, "{}: 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: {}. Compilation failed at offset {} in expression '{}'", 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, "{}: PCRE error \"{}\" 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, "{}: 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, "{}: 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: \"{}\"", + m_subject.substr(startPos)); + 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: {}, position: {}. Text before bad " + "char: \"{}\"", + m_iOvector[1], m_iOvector[0], + m_subject.substr(startPos, m_iOvector[0] - startPos + 1)); + else + CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character, error code: {}, position: {}", + 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: {}", 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; + + std::string str = "{"; + int size = GetSubCount(); // past the subpatterns is junk + for (int i = 0; i <= size; i++) + { + std::string t = StringUtils::Format("[{},{}]", m_iOvector[(i * 2)], m_iOvector[(i * 2) + 1]); + if (i != size) + t += ","; + str += t; + } + str += "}"; + CLog::Log(iLog, "regexp ovector={}", 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(LOGINFO, + "Consider installing PCRE lib version 8.10 or later with enabled Unicode properties " + "and UTF-8 support. Your PCRE lib version: {}", + PCRE::pcre_version()); +#if PCRE_UCP == 0 + CLog::Log(LOGINFO, "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/xbmc/utils/RegExp.h b/xbmc/utils/RegExp.h new file mode 100644 index 0000000..53f6019 --- /dev/null +++ b/xbmc/utils/RegExp.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +//! @todo - move to std::regex (after switching to gcc 4.9 or higher) and get rid of CRegExp + +#include <string> +#include <vector> + +/* make sure stdlib.h is included before including pcre.h inside the + namespace; this works around stdlib.h definitions also living in + the PCRE namespace */ +#include <stdlib.h> + +namespace PCRE { +struct real_pcre_jit_stack; // forward declaration for PCRE without JIT +typedef struct real_pcre_jit_stack pcre_jit_stack; +#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; + diff --git a/xbmc/utils/RingBuffer.cpp b/xbmc/utils/RingBuffer.cpp new file mode 100644 index 0000000..454f1f9 --- /dev/null +++ b/xbmc/utils/RingBuffer.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2010-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RingBuffer.h" + +#include <algorithm> +#include <cstdlib> +#include <cstring> +#include <mutex> + +/* 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) +{ + std::unique_lock<CCriticalSection> 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() +{ + std::unique_lock<CCriticalSection> 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() +{ + std::unique_lock<CCriticalSection> 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) +{ + std::unique_lock<CCriticalSection> 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) +{ + std::unique_lock<CCriticalSection> 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) +{ + std::unique_lock<CCriticalSection> 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) +{ + std::unique_lock<CCriticalSection> 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) +{ + std::unique_lock<CCriticalSection> 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() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_size; +} + +unsigned int CRingBuffer::getReadPtr() const +{ + return m_readPtr; +} + +unsigned int CRingBuffer::getWritePtr() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_writePtr; +} + +unsigned int CRingBuffer::getMaxReadSize() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_fillCount; +} + +unsigned int CRingBuffer::getMaxWriteSize() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_size - m_fillCount; +} diff --git a/xbmc/utils/RingBuffer.h b/xbmc/utils/RingBuffer.h new file mode 100644 index 0000000..8cdb971 --- /dev/null +++ b/xbmc/utils/RingBuffer.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2010-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#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/xbmc/utils/RssManager.cpp b/xbmc/utils/RssManager.cpp new file mode 100644 index 0000000..b8d350c --- /dev/null +++ b/xbmc/utils/RssManager.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RssManager.h" + +#include "ServiceBroker.h" +#include "addons/AddonInstaller.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonType.h" +#include "interfaces/builtins/Builtins.h" +#include "profiles/ProfileManager.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "utils/FileUtils.h" +#include "utils/RssReader.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <mutex> +#include <utility> + +using namespace KODI::MESSAGING; + + +CRssManager::CRssManager() +{ + m_bActive = false; +} + +CRssManager::~CRssManager() +{ + Stop(); +} + +CRssManager& CRssManager::GetInstance() +{ + static CRssManager sRssManager; + return sRssManager; +} + +void CRssManager::OnSettingsLoaded() +{ + Load(); +} + +void CRssManager::OnSettingsUnloaded() +{ + Clear(); +} + +void CRssManager::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + const std::string &settingId = setting->GetId(); + if (settingId == CSettings::SETTING_LOOKANDFEEL_RSSEDIT) + { + ADDON::AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon("script.rss.editor", addon, + ADDON::OnlyEnabled::CHOICE_YES)) + { + if (!ADDON::CAddonInstaller::GetInstance().InstallModal( + "script.rss.editor", addon, ADDON::InstallModalPrompt::CHOICE_YES)) + return; + } + CBuiltins::GetInstance().Execute("RunScript(script.rss.editor)"); + } +} + +void CRssManager::Start() + { + m_bActive = true; +} + +void CRssManager::Stop() +{ + std::unique_lock<CCriticalSection> 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() +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + std::unique_lock<CCriticalSection> lock(m_critical); + + std::string rssXML = profileManager->GetUserDataItem("RssFeeds.xml"); + if (!CFileUtils::Exists(rssXML)) + return false; + + CXBMCTinyXML rssDoc; + if (!rssDoc.LoadFile(rssXML)) + { + CLog::Log(LOGERROR, "CRssManager: error loading {}, Line {}\n{}", rssXML, 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 {}, no <rssfeeds> node", rssXML); + 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 && + StringUtils::CompareNoCase(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? + std::string strUrl = pFeed->FirstChild()->ValueStr(); + set.url.push_back(strUrl); + set.interval.push_back(iInterval); + } + pFeed = pFeed->NextSiblingElement("feed"); + } + + m_mapRssUrls.insert(std::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() +{ + std::unique_lock<CCriticalSection> 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) +{ + std::unique_lock<CCriticalSection> 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/xbmc/utils/RssManager.h b/xbmc/utils/RssManager.h new file mode 100644 index 0000000..2b807d7 --- /dev/null +++ b/xbmc/utils/RssManager.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/ISettingCallback.h" +#include "settings/lib/ISettingsHandler.h" +#include "threads/CriticalSection.h" + +#include <map> +#include <string> +#include <vector> + +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& GetInstance(); + + void OnSettingsLoaded() override; + void OnSettingsUnloaded() override; + + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + + 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() override; + +private: + CRssManager(const CRssManager&) = delete; + CRssManager& operator=(const CRssManager&) = delete; + 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/xbmc/utils/RssReader.cpp b/xbmc/utils/RssReader.cpp new file mode 100644 index 0000000..0b227b6 --- /dev/null +++ b/xbmc/utils/RssReader.cpp @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RssReader.h" + +#include "CharsetConverter.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "filesystem/CurlFile.h" +#include "filesystem/File.h" +#include "guilib/GUIRSSControl.h" +#include "guilib/LocalizeStrings.h" +#include "log.h" +#include "network/Network.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "threads/SystemClock.h" +#include "utils/HTMLUtil.h" +#include "utils/XTimeUtils.h" + +#include <mutex> + +#define RSS_COLOR_BODY 0 +#define RSS_COLOR_HEADLINE 1 +#define RSS_COLOR_CHANNEL 2 + +using namespace XFILE; +using namespace std::chrono_literals; + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CRssReader::CRssReader() : CThread("RSSReader") +{ + m_pObserver = NULL; + m_spacesBetweenFeeds = 0; + m_bIsRunning = false; + m_savedScrollPixelPos = 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 std::vector<std::string>& aUrls, const std::vector<int> ×, int spacesBetweenFeeds, bool rtl) +{ + std::unique_lock<CCriticalSection> 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); + KODI::TIME::SystemTime* time = new KODI::TIME::SystemTime; + KODI::TIME::GetLocalTime(time); + m_vecTimeStamps.push_back(time); + } +} + +void CRssReader::requestRefresh() +{ + m_requestRefresh = true; +} + +void CRssReader::AddToQueue(int iAdd) +{ + std::unique_lock<CCriticalSection> lock(m_critical); + if (iAdd < (int)m_vecUrls.size()) + m_vecQueue.push_back(iAdd); + if (!m_bIsRunning) + { + StopThread(); + m_bIsRunning = true; + CThread::Create(false); + } +} + +void CRssReader::OnExit() +{ + m_bIsRunning = false; +} + +int CRssReader::GetQueueSize() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + return m_vecQueue.size(); +} + +void CRssReader::Process() +{ + while (GetQueueSize()) + { + std::unique_lock<CCriticalSection> 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(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_userAgent); + http.SetTimeout(2); + std::string strXML; + std::string strUrl = m_vecUrls[iFeed]; + lock.unlock(); + + int nRetries = 3; + CURL url(strUrl); + std::string fileCharset; + + // we wait for the network to come up + if ((url.IsProtocol("http") || url.IsProtocol("https")) && + !CServiceBroker::GetNetwork().IsAvailable()) + { + CLog::Log(LOGWARNING, "RSS: No network connection"); + strXML = "<rss><item><title>"+g_localizeStrings.Get(15301)+"</title></item></rss>"; + } + else + { + XbmcThreads::EndTime<> timeout(15s); + while (!m_bStop && nRetries > 0) + { + if (timeout.IsTimePast()) + { + CLog::Log(LOGERROR, "Timeout while retrieving rss feed: {}", strUrl); + break; + } + nRetries--; + + if (!url.IsProtocol("http") && !url.IsProtocol("https")) + { + CFile file; + std::vector<uint8_t> buffer; + if (file.LoadFile(strUrl, buffer) > 0) + { + strXML.assign(reinterpret_cast<char*>(buffer.data()), buffer.size()); + break; + } + } + else + { + if (http.Get(strUrl, strXML)) + { + fileCharset = http.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET); + CLog::Log(LOGDEBUG, "Got rss feed: {}", strUrl); + break; + } + else if (nRetries > 0) + CThread::Sleep(5000ms); // Network problems? Retry, but not immediately. + else + CLog::Log(LOGERROR, "Unable to obtain rss feed: {}", strUrl); + } + } + 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: {}", strUrl); + } + } + 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] = static_cast<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"); + std::map<std::string, std::wstring> mTagElements; + typedef std::pair<std::string, std::wstring> StrPair; + std::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 != nullptr) + { + TiXmlNode* childNode = itemNode->FirstChild(); + mTagElements.clear(); + while (childNode != nullptr) + { + 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) + { + std::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: {}", m_xml.GetUsedCharset()); + + 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.empty()) + { + std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext()); + if (m_pObserver) // need to check again when locked to make sure observer wasnt removed + m_pObserver->OnFeedUpdate(feed); + } +} + +void CRssReader::CheckForUpdates() +{ + KODI::TIME::SystemTime time; + KODI::TIME::GetLocalTime(&time); + + for (unsigned int i = 0;i < m_vecUpdateTimes.size(); ++i ) + { + if (m_requestRefresh || ((time.day * 24 * 60) + (time.hour * 60) + time.minute) - + ((m_vecTimeStamps[i]->day * 24 * 60) + + (m_vecTimeStamps[i]->hour * 60) + m_vecTimeStamps[i]->minute) > + m_vecUpdateTimes[i]) + { + CLog::Log(LOGDEBUG, "Updating RSS"); + KODI::TIME::GetLocalTime(m_vecTimeStamps[i]); + AddToQueue(i); + } + } + + m_requestRefresh = false; +} diff --git a/xbmc/utils/RssReader.h b/xbmc/utils/RssReader.h new file mode 100644 index 0000000..ae9d2f7 --- /dev/null +++ b/xbmc/utils/RssReader.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/CriticalSection.h" +#include "threads/Thread.h" +#include "utils/IRssObserver.h" +#include "utils/XBMCTinyXML.h" + +#include <list> +#include <string> +#include <vector> + +namespace KODI::TIME +{ +struct SystemTime; +} + +class CRssReader : public CThread +{ +public: + CRssReader(); + ~CRssReader() override; + + 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(); + float m_savedScrollPixelPos; + +private: + void Process() override; + bool Parse(int iFeed); + void GetNewsItems(TiXmlElement* channelXmlNode, int iFeed); + void AddString(std::wstring aString, int aColour, int iFeed); + void UpdateFeed(); + void OnExit() override; + int GetQueueSize(); + + IRssObserver* m_pObserver; + + std::vector<std::wstring> m_strFeed; + std::vector<std::wstring> m_strColors; + std::vector<KODI::TIME::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/xbmc/utils/SaveFileStateJob.cpp b/xbmc/utils/SaveFileStateJob.cpp new file mode 100644 index 0000000..f7da601 --- /dev/null +++ b/xbmc/utils/SaveFileStateJob.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2010-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SaveFileStateJob.h" + +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "StringUtils.h" +#include "URIUtils.h" +#include "URL.h" +#include "Util.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationStackHelper.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "interfaces/AnnouncementManager.h" +#include "log.h" +#include "music/MusicDatabase.h" +#include "music/tags/MusicInfoTag.h" +#include "network/upnp/UPnP.h" +#include "utils/Variant.h" +#include "video/Bookmark.h" +#include "video/VideoDatabase.h" + +void CSaveFileState::DoWork(CFileItem& item, + CBookmark& bookmark, + bool updatePlayCount) +{ + std::string progressTrackingFile = item.GetPath(); + + if (item.HasVideoInfoTag() && StringUtils::StartsWith(item.GetVideoInfoTag()->m_strFileNameAndPath, "removable://")) + progressTrackingFile = item.GetVideoInfoTag()->m_strFileNameAndPath; // this variable contains removable:// suffixed by disc label+uniqueid or is empty if label not uniquely identified + else if (item.HasVideoInfoTag() && item.IsVideoDb()) + progressTrackingFile = item.GetVideoInfoTag()->m_strFileNameAndPath; // we need the file url of the video db item to create the bookmark + else if (item.HasProperty("original_listitem_url")) + { + // only use original_listitem_url for Python, UPnP and Bluray sources + std::string original = item.GetProperty("original_listitem_url").asString(); + if (URIUtils::IsPlugin(original) || URIUtils::IsUPnP(original) || URIUtils::IsBluray(item.GetPath())) + progressTrackingFile = original; + } + + if (!progressTrackingFile.empty()) + { +#ifdef HAS_UPNP + // checks if UPnP server of this file is available and supports updating + if (URIUtils::IsUPnP(progressTrackingFile) + && UPNP::CUPnP::SaveFileState(item, bookmark, updatePlayCount)) + { + return; + } +#endif + if (item.IsVideo()) + { + std::string redactPath = CURL::GetRedacted(progressTrackingFile); + CLog::Log(LOGDEBUG, "{} - Saving file state for video item {}", __FUNCTION__, redactPath); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + { + CLog::Log(LOGWARNING, "{} - Unable to open video database. Can not save file state!", + __FUNCTION__); + } + else + { + videodatabase.BeginTransaction(); + + if (URIUtils::IsPlugin(progressTrackingFile) && !(item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId >= 0)) + { + // FileItem from plugin can lack information, make sure all needed fields are set + CVideoInfoTag *tag = item.GetVideoInfoTag(); + CStreamDetails streams = tag->m_streamDetails; + if (videodatabase.LoadVideoInfo(progressTrackingFile, *tag)) + { + item.SetPath(progressTrackingFile); + item.ClearProperty("original_listitem_url"); + tag->m_streamDetails = streams; + } + } + + bool updateListing = false; + // No resume & watched status for livetv + if (!item.IsLiveTV()) + { + if (updatePlayCount) + { + // no watched for not yet finished pvr recordings + if (!item.IsInProgressPVRRecording()) + { + CLog::Log(LOGDEBUG, "{} - Marking video item {} as watched", __FUNCTION__, + redactPath); + + // consider this item as played + const CDateTime newLastPlayed = videodatabase.IncrementPlayCount(item); + + item.SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, true); + updateListing = true; + + if (item.HasVideoInfoTag()) + { + item.GetVideoInfoTag()->IncrementPlayCount(); + + if (newLastPlayed.IsValid()) + item.GetVideoInfoTag()->m_lastPlayed = newLastPlayed; + + CVariant data; + data["id"] = item.GetVideoInfoTag()->m_iDbId; + data["type"] = item.GetVideoInfoTag()->m_type; + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, + "OnUpdate", data); + } + } + } + else + { + const CDateTime newLastPlayed = videodatabase.UpdateLastPlayed(item); + + if (item.HasVideoInfoTag() && newLastPlayed.IsValid()) + item.GetVideoInfoTag()->m_lastPlayed = newLastPlayed; + } + + if (!item.HasVideoInfoTag() || + item.GetVideoInfoTag()->GetResumePoint().timeInSeconds != bookmark.timeInSeconds) + { + if (bookmark.timeInSeconds <= 0.0) + videodatabase.ClearBookMarksOfFile(progressTrackingFile, CBookmark::RESUME); + else + videodatabase.AddBookMarkToFile(progressTrackingFile, bookmark, CBookmark::RESUME); + if (item.HasVideoInfoTag()) + item.GetVideoInfoTag()->SetResumePoint(bookmark); + + // UPnP announce resume point changes to clients + // however not if playcount is modified as that already announces + if (item.HasVideoInfoTag() && !updatePlayCount) + { + CVariant data; + data["id"] = item.GetVideoInfoTag()->m_iDbId; + data["type"] = item.GetVideoInfoTag()->m_type; + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, + "OnUpdate", data); + } + + updateListing = true; + } + } + + if (item.HasVideoInfoTag() && item.GetVideoInfoTag()->HasStreamDetails()) + { + CFileItem dbItem(item); + + // Check whether the item's db streamdetails need updating + if (!videodatabase.GetStreamDetails(dbItem) || + dbItem.GetVideoInfoTag()->m_streamDetails != item.GetVideoInfoTag()->m_streamDetails) + { + videodatabase.SetStreamDetailsForFile(item.GetVideoInfoTag()->m_streamDetails, progressTrackingFile); + updateListing = true; + } + } + + videodatabase.CommitTransaction(); + + if (updateListing) + { + CUtil::DeleteVideoDatabaseDirectoryCache(); + CFileItemPtr msgItem(new CFileItem(item)); + if (item.HasProperty("original_listitem_url")) + msgItem->SetPath(item.GetProperty("original_listitem_url").asString()); + + // Could be part of an ISO stack. In this case the bookmark is saved onto the part. + // In order to properly update the list, we need to refresh the stack's resume point + const auto& components = CServiceBroker::GetAppComponents(); + const auto stackHelper = components.GetComponent<CApplicationStackHelper>(); + if (stackHelper->HasRegisteredStack(item) && + stackHelper->GetRegisteredStackTotalTimeMs(item) == 0) + videodatabase.GetResumePoint(*(msgItem->GetVideoInfoTag())); + + CGUIMessage message(GUI_MSG_NOTIFY_ALL, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(), 0, GUI_MSG_UPDATE_ITEM, 0, msgItem); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); + } + + videodatabase.Close(); + } + } + + if (item.IsAudio()) + { + std::string redactPath = CURL::GetRedacted(progressTrackingFile); + CLog::Log(LOGDEBUG, "{} - Saving file state for audio item {}", __FUNCTION__, redactPath); + + CMusicDatabase musicdatabase; + if (updatePlayCount) + { + if (!musicdatabase.Open()) + { + CLog::Log(LOGWARNING, "{} - Unable to open music database. Can not save file state!", + __FUNCTION__); + } + else + { + // consider this item as played + CLog::Log(LOGDEBUG, "{} - Marking audio item {} as listened", __FUNCTION__, redactPath); + + musicdatabase.IncrementPlayCount(item); + musicdatabase.Close(); + + // UPnP announce resume point changes to clients + // however not if playcount is modified as that already announces + if (item.IsMusicDb()) + { + CVariant data; + data["id"] = item.GetMusicInfoTag()->GetDatabaseId(); + data["type"] = item.GetMusicInfoTag()->GetType(); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::AudioLibrary, + "OnUpdate", data); + } + } + } + + if (item.IsAudioBook()) + { + musicdatabase.Open(); + musicdatabase.SetResumeBookmarkForAudioBook( + item, item.GetStartOffset() + CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds)); + musicdatabase.Close(); + } + } + } +} diff --git a/xbmc/utils/SaveFileStateJob.h b/xbmc/utils/SaveFileStateJob.h new file mode 100644 index 0000000..b7bb0cc --- /dev/null +++ b/xbmc/utils/SaveFileStateJob.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2010-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class CBookmark; +class CFileItem; + +class CSaveFileState +{ +public: + static void DoWork(CFileItem& item, + CBookmark& bookmark, + bool updatePlayCount); +}; + diff --git a/xbmc/utils/ScopeGuard.h b/xbmc/utils/ScopeGuard.h new file mode 100644 index 0000000..2f731fb --- /dev/null +++ b/xbmc/utils/ScopeGuard.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <functional> + +namespace KODI +{ +namespace UTILS +{ + +/*! \class CScopeGuard + \brief Generic scopeguard designed to handle any type of handle + + This is not necessary but recommended to cut down on some typing + using CSocketHandle = CScopeGuard<SOCKET, INVALID_SOCKET, closesocket>; + + CSocketHandle sh(closesocket, open(thingy)); + */ +template<typename Handle, Handle invalid, typename Deleter> +class CScopeGuard +{ + +public: + + CScopeGuard(std::function<Deleter> del, Handle handle = invalid) + : m_handle{handle} + , m_deleter{del} + { }; + + ~CScopeGuard() noexcept + { + reset(); + } + + operator Handle() const + { + return m_handle; + } + + operator bool() const + { + return m_handle != invalid; + } + + /*! \brief attach a new handle to this instance, if there's + already a handle it will be closed. + + \param[in] handle The handle to manage + */ + void attach(Handle handle) + { + reset(); + + m_handle = handle; + } + + /*! \brief release the managed handle so that it won't be auto closed + + \return The handle being managed by the guard + */ + Handle release() + { + Handle h = m_handle; + m_handle = invalid; + return h; + } + + /*! \brief reset the instance, closing any managed handle and setting it to invalid + */ + void reset() + { + if (m_handle != invalid) + { + m_deleter(m_handle); + m_handle = invalid; + } + } + + //Disallow default construction and copying + CScopeGuard() = delete; + CScopeGuard(const CScopeGuard& rhs) = delete; + CScopeGuard& operator= (const CScopeGuard& rhs) = delete; + + //Allow moving + CScopeGuard(CScopeGuard&& rhs) noexcept + : m_handle{std::move(rhs.m_handle)}, m_deleter{std::move(rhs.m_deleter)} + { + // Bring moved-from object into released state so destructor will not do anything + rhs.release(); + } + CScopeGuard& operator=(CScopeGuard&& rhs) noexcept + { + attach(rhs.release()); + m_deleter = std::move(rhs.m_deleter); + return *this; + } + +private: + Handle m_handle; + std::function<Deleter> m_deleter; +}; + +} +} diff --git a/xbmc/utils/ScraperParser.cpp b/xbmc/utils/ScraperParser.cpp new file mode 100644 index 0000000..b6f85b9 --- /dev/null +++ b/xbmc/utils/ScraperParser.cpp @@ -0,0 +1,615 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ScraperParser.h" + +#include "guilib/LocalizeStrings.h" +#include "RegExp.h" +#include "HTMLUtil.h" +#include "addons/Scraper.h" +#include "URL.h" +#include "utils/StringUtils.h" +#include "log.h" +#include "CharsetConverter.h" +#ifdef HAVE_LIBXSLT +#include "utils/XSLTUtils.h" +#endif +#include "utils/XMLUtils.h" +#include <sstream> +#include <cstring> + +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 + 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 = g_localizeStrings.GetAddonString(m_scraper->ID(), 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 (StringUtils::CompareNoCase(sensitive, "yes") == 0) + bInsensitive=false; // match case sensitive + + CRegExp::utf8Mode eUtf8 = CRegExp::autoUtf8; + const char* const strUtf8 = pExpression->Attribute("utf8"); + if (strUtf8) + { + if (StringUtils::CompareNoCase(strUtf8, "yes") == 0) + eUtf8 = CRegExp::forceUtf8; + else if (StringUtils::CompareNoCase(strUtf8, "no") == 0) + eUtf8 = CRegExp::asciiOnly; + else if (StringUtils::CompareNoCase(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 (StringUtils::CompareNoCase(szRepeat, "yes") == 0) + bRepeat = true; + + const char* szClear = pExpression->Attribute("clear"); + if (szClear) + if (StringUtils::CompareNoCase(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.empty())) + { + if (!bAppend) + { + dest = ""; + bAppend = true; + } + std::string strCurOutput=strOutput; + + if (iOptional > -1) // check that required param is there + { + char temp[12]; + 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); + 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) +{ +#ifdef HAVE_LIBXSLT + 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); + } +#endif +} + +TiXmlElement *FirstChildScraperElement(TiXmlElement *element) +{ + for (TiXmlElement *child = element->FirstChildElement(); child; child = child->NextSiblingElement()) + { +#ifdef HAVE_LIBXSLT + if (child->ValueStr() == "XSLT") + return child; +#endif + if (child->ValueStr() == "RegExp") + return child; + } + return NULL; +} + +TiXmlElement *NextSiblingScraperElement(TiXmlElement *element) +{ + for (TiXmlElement *next = element->NextSiblingElement(); next; next = next->NextSiblingElement()) + { +#ifdef HAVE_LIBXSLT + if (next->ValueStr() == "XSLT") + return next; +#endif + if (next->ValueStr() == "RegExp") + 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) + { +#ifdef HAVE_LIBXSLT + if (pReg->ValueStr() == "XSLT") + ParseXSLT(strInput, m_param[iDest - 1], pReg, bAppend); + else +#endif + 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, "{}: Could not find scraper function {}", __FUNCTION__, strTag); + 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 || StringUtils::CompareNoCase(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{};", szReplace); + 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 = std::to_string(std::stol(szHexValue, 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 (std::string& param : m_param) + param.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) + { + std::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/xbmc/utils/ScraperParser.h b/xbmc/utils/ScraperParser.h new file mode 100644 index 0000000..ec1cfd2 --- /dev/null +++ b/xbmc/utils/ScraperParser.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#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 destination 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; +}; + diff --git a/xbmc/utils/ScraperUrl.cpp b/xbmc/utils/ScraperUrl.cpp new file mode 100644 index 0000000..f131b16 --- /dev/null +++ b/xbmc/utils/ScraperUrl.cpp @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ScraperUrl.h" + +#include "CharsetConverter.h" +#include "ServiceBroker.h" +#include "URIUtils.h" +#include "URL.h" +#include "XMLUtils.h" +#include "filesystem/CurlFile.h" +#include "filesystem/ZipFile.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/CharsetDetection.h" +#include "utils/Mime.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <algorithm> +#include <cstring> +#include <sstream> + +CScraperUrl::CScraperUrl() : m_relevance(0.0), m_parsed(false) +{ +} + +CScraperUrl::CScraperUrl(const std::string& strUrl) : CScraperUrl() +{ + ParseFromData(strUrl); +} + +CScraperUrl::CScraperUrl(const TiXmlElement* element) : CScraperUrl() +{ + ParseAndAppendUrl(element); +} + +CScraperUrl::~CScraperUrl() = default; + +void CScraperUrl::Clear() +{ + m_urls.clear(); + m_data.clear(); + m_relevance = 0.0; + m_parsed = false; +} + +void CScraperUrl::SetData(std::string data) +{ + m_data = std::move(data); + m_parsed = false; +} + +const CScraperUrl::SUrlEntry CScraperUrl::GetFirstUrlByType(const std::string& type) const +{ + const auto url = std::find_if(m_urls.begin(), m_urls.end(), [type](const SUrlEntry& url) { + return url.m_type == UrlType::General && (type.empty() || url.m_aspect == type); + }); + if (url != m_urls.end()) + return *url; + + return SUrlEntry(); +} + +const CScraperUrl::SUrlEntry CScraperUrl::GetSeasonUrl(int season, const std::string& type) const +{ + const auto url = std::find_if(m_urls.begin(), m_urls.end(), [season, type](const SUrlEntry& url) { + return url.m_type == UrlType::Season && url.m_season == season && + (type.empty() || type == "thumb" || url.m_aspect == type); + }); + if (url != m_urls.end()) + return *url; + + return SUrlEntry(); +} + +unsigned int CScraperUrl::GetMaxSeasonUrl() const +{ + unsigned int maxSeason = 0; + for (const auto& url : m_urls) + { + if (url.m_type == UrlType::Season && url.m_season > 0 && + static_cast<unsigned int>(url.m_season) > maxSeason) + maxSeason = url.m_season; + } + return maxSeason; +} + +std::string CScraperUrl::GetFirstThumbUrl() const +{ + if (m_urls.empty()) + return {}; + + return GetThumbUrl(m_urls.front()); +} + +void CScraperUrl::GetThumbUrls(std::vector<std::string>& thumbs, + const std::string& type, + int season, + bool unique) const +{ + for (const auto& url : m_urls) + { + if (url.m_aspect == type || type.empty() || url.m_aspect.empty()) + { + if ((url.m_type == CScraperUrl::UrlType::General && season == -1) || + (url.m_type == CScraperUrl::UrlType::Season && url.m_season == season)) + { + std::string thumbUrl = GetThumbUrl(url); + if (!unique || std::find(thumbs.begin(), thumbs.end(), thumbUrl) == thumbs.end()) + thumbs.push_back(thumbUrl); + } + } + } +} + +bool CScraperUrl::Parse() +{ + if (m_parsed) + return true; + + auto dataToParse = m_data; + m_data.clear(); + return ParseFromData(dataToParse); +} + +bool CScraperUrl::ParseFromData(const std::string& data) +{ + if (data.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(data, TIXML_ENCODING_UTF8); + + auto pElement = doc.RootElement(); + if (pElement == nullptr) + { + m_urls.emplace_back(data); + m_data = data; + } + else + { + while (pElement != nullptr) + { + ParseAndAppendUrl(pElement); + pElement = pElement->NextSiblingElement(pElement->Value()); + } + } + + m_parsed = true; + return true; +} + +bool CScraperUrl::ParseAndAppendUrl(const TiXmlElement* element) +{ + if (element == nullptr || element->FirstChild() == nullptr || + element->FirstChild()->Value() == nullptr) + return false; + + bool wasEmpty = m_data.empty(); + + std::stringstream stream; + stream << *element; + m_data += stream.str(); + + SUrlEntry url(element->FirstChild()->ValueStr()); + url.m_spoof = XMLUtils::GetAttribute(element, "spoof"); + + const char* szPost = element->Attribute("post"); + if (szPost && StringUtils::CompareNoCase(szPost, "yes") == 0) + url.m_post = true; + else + url.m_post = false; + + const char* szIsGz = element->Attribute("gzip"); + if (szIsGz && StringUtils::CompareNoCase(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"); + if (szType && StringUtils::CompareNoCase(szType, "season") == 0) + { + url.m_type = UrlType::Season; + const char* szSeason = element->Attribute("season"); + if (szSeason) + url.m_season = atoi(szSeason); + } + + url.m_aspect = XMLUtils::GetAttribute(element, "aspect"); + url.m_preview = XMLUtils::GetAttribute(element, "preview"); + + m_urls.push_back(url); + + if (wasEmpty) + m_parsed = true; + + return true; +} + +// XML format is of strUrls is: +// <TAG><url>...</url>...</TAG> (parsed by ParseElement) or <url>...</url> (ditto) +bool CScraperUrl::ParseAndAppendUrlsFromEpisodeGuide(const std::string& episodeGuide) +{ + if (episodeGuide.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(episodeGuide, TIXML_ENCODING_UTF8); + if (doc.RootElement() == nullptr) + return false; + + bool wasEmpty = m_data.empty(); + + TiXmlHandle docHandle(&doc); + auto link = docHandle.FirstChild("episodeguide").Element(); + if (link->FirstChildElement("url")) + { + for (link = link->FirstChildElement("url"); link; link = link->NextSiblingElement("url")) + ParseAndAppendUrl(link); + } + else if (link->FirstChild() && link->FirstChild()->Value()) + ParseAndAppendUrl(link); + + if (wasEmpty) + m_parsed = true; + + return true; +} + +void CScraperUrl::AddParsedUrl(const std::string& url, + const std::string& aspect, + const std::string& preview, + const std::string& referrer, + const std::string& cache, + bool post, + bool isgz, + int season) +{ + bool wasEmpty = m_data.empty(); + + TiXmlElement thumb("thumb"); + thumb.SetAttribute("spoof", referrer); + thumb.SetAttribute("cache", cache); + if (post) + thumb.SetAttribute("post", "yes"); + if (isgz) + thumb.SetAttribute("gzip", "yes"); + if (season >= 0) + { + thumb.SetAttribute("season", std::to_string(season)); + thumb.SetAttribute("type", "season"); + } + thumb.SetAttribute("aspect", aspect); + thumb.SetAttribute("preview", preview); + TiXmlText text(url); + thumb.InsertEndChild(text); + + m_data << thumb; + + SUrlEntry nUrl(url); + nUrl.m_spoof = referrer; + nUrl.m_post = post; + nUrl.m_isgz = isgz; + nUrl.m_cache = cache; + nUrl.m_preview = preview; + if (season >= 0) + { + nUrl.m_type = UrlType::Season; + nUrl.m_season = season; + } + nUrl.m_aspect = aspect; + + m_urls.push_back(nUrl); + + if (wasEmpty) + m_parsed = 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); +} + +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_cache.empty()) + { + strCachePath = URIUtils::AddFileToFolder( + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cachePath, "scrapers", + cacheContext, scrURL.m_cache); + if (XFILE::CFile::Exists(strCachePath)) + { + XFILE::CFile file; + std::vector<uint8_t> buffer; + if (file.LoadFile(strCachePath, buffer) > 0) + { + strHTML.assign(reinterpret_cast<char*>(buffer.data()), buffer.size()); + return true; + } + } + } + + auto 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; + + const auto mimeType = http.GetProperty(XFILE::FILE_PROPERTY_MIME_TYPE); + 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; + auto iSize = file.UnpackFromMemory( + strBuffer, strHTML, scrURL.m_isgz); // FIXME: use FileTypeGZip instead of scrURL.m_isgz? + if (iSize > 0) + { + strHTML = strBuffer; + CLog::Log(LOGDEBUG, "{}: Archive \"{}\" was unpacked in memory", __FUNCTION__, scrURL.m_url); + } + else + CLog::Log(LOGWARNING, "{}: \"{}\" looks like archive but cannot be unpacked", __FUNCTION__, + scrURL.m_url); + } + + const auto reportedCharset = http.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET); + if (ftype == CMime::FileTypeHtml) + { + std::string realHtmlCharset, converted; + if (!CCharsetDetection::ConvertHtmlToUtf8(strHTML, converted, reportedCharset, realHtmlCharset)) + CLog::Log(LOGWARNING, + "{}: Can't find precise charset for HTML \"{}\", using \"{}\" as fallback", + __FUNCTION__, scrURL.m_url, realHtmlCharset); + else + CLog::Log(LOGDEBUG, "{}: Using \"{}\" charset for HTML \"{}\"", __FUNCTION__, realHtmlCharset, + scrURL.m_url); + + strHTML = converted; + } + else if (ftype == CMime::FileTypeXml) + { + CXBMCTinyXML xmlDoc; + xmlDoc.Parse(strHTML, reportedCharset); + + const auto realXmlCharset = xmlDoc.GetUsedCharset(); + if (!realXmlCharset.empty()) + { + CLog::Log(LOGDEBUG, "{}: Using \"{}\" charset for XML \"{}\"", __FUNCTION__, realXmlCharset, + scrURL.m_url); + std::string converted; + g_charsetConverter.ToUtf8(realXmlCharset, strHTML, converted); + strHTML = converted; + } + } + else if (ftype == CMime::FileTypePlainText || + StringUtils::EqualsNoCase(mimeType.substr(0, 5), "text/")) + { + std::string realTextCharset; + std::string converted; + CCharsetDetection::ConvertPlainTextToUtf8(strHTML, converted, reportedCharset, realTextCharset); + strHTML = converted; + if (reportedCharset != realTextCharset) + CLog::Log(LOGWARNING, + "{}: Using \"{}\" charset for plain text \"{}\" instead of server reported \"{}\" " + "charset", + __FUNCTION__, realTextCharset, scrURL.m_url, reportedCharset); + else + CLog::Log(LOGDEBUG, "{}: Using \"{}\" charset for plain text \"{}\"", __FUNCTION__, + realTextCharset, scrURL.m_url); + } + else if (!reportedCharset.empty()) + { + CLog::Log(LOGDEBUG, "{}: Using \"{}\" charset for \"{}\"", __FUNCTION__, reportedCharset, + scrURL.m_url); + if (reportedCharset != "UTF-8") + { + std::string converted; + g_charsetConverter.ToUtf8(reportedCharset, strHTML, converted); + strHTML = converted; + } + } + else + CLog::Log(LOGDEBUG, "{}: Using content of \"{}\" as binary or text with \"UTF-8\" charset", + __FUNCTION__, scrURL.m_url); + + if (!scrURL.m_cache.empty()) + { + const auto strCachePath = URIUtils::AddFileToFolder( + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cachePath, "scrapers", + cacheContext, scrURL.m_cache); + XFILE::CFile file; + if (!file.OpenForWrite(strCachePath, true) || + file.Write(strHTML.data(), strHTML.size()) != static_cast<ssize_t>(strHTML.size())) + return false; + } + return true; +} diff --git a/xbmc/utils/ScraperUrl.h b/xbmc/utils/ScraperUrl.h new file mode 100644 index 0000000..9ff416a --- /dev/null +++ b/xbmc/utils/ScraperUrl.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <map> +#include <string> +#include <vector> + +class TiXmlElement; +namespace XFILE +{ +class CCurlFile; +} + +class CScraperUrl +{ +public: + enum class UrlType + { + General = 1, + Season = 2 + }; + + struct SUrlEntry + { + explicit SUrlEntry(std::string url = "") + : m_url(std::move(url)), m_type(UrlType::General), m_post(false), m_isgz(false), m_season(-1) + { + } + + std::string m_spoof; + std::string m_url; + std::string m_cache; + std::string m_aspect; + std::string m_preview; + UrlType m_type; + bool m_post; + bool m_isgz; + int m_season; + }; + + CScraperUrl(); + explicit CScraperUrl(const std::string& strUrl); + explicit CScraperUrl(const TiXmlElement* element); + ~CScraperUrl(); + + void Clear(); + + bool HasData() const { return !m_data.empty(); } + const std::string& GetData() const { return m_data; } + void SetData(std::string data); + + const std::string& GetTitle() const { return m_title; } + void SetTitle(std::string title) { m_title = std::move(title); } + + const std::string& GetId() const { return m_id; } + void SetId(std::string id) { m_id = std::move(id); } + + double GetRelevance() const { return m_relevance; } + void SetRelevance(double relevance) { m_relevance = relevance; } + + bool HasUrls() const { return !m_urls.empty(); } + const std::vector<SUrlEntry>& GetUrls() const { return m_urls; } + void SetUrls(std::vector<SUrlEntry> urls) { m_urls = std::move(urls); } + void AppendUrl(SUrlEntry url) { m_urls.push_back(std::move(url)); } + + const SUrlEntry GetFirstUrlByType(const std::string& type = "") const; + const SUrlEntry GetSeasonUrl(int season, const std::string& type = "") const; + unsigned int GetMaxSeasonUrl() const; + + std::string GetFirstThumbUrl() const; + + /*! \brief fetch the full URLs (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) + \param unique avoid adding duplicate URLs when adding to a thumbs vector with existing items + */ + void GetThumbUrls(std::vector<std::string>& thumbs, + const std::string& type = "", + int season = -1, + bool unique = false) const; + + bool Parse(); + bool ParseFromData(const std::string& data); // copies by intention + bool ParseAndAppendUrl(const TiXmlElement* element); + bool ParseAndAppendUrlsFromEpisodeGuide(const std::string& episodeGuide); // copies by intention + void AddParsedUrl(const std::string& url, + const std::string& aspect = "", + const std::string& preview = "", + const std::string& referrer = "", + const std::string& cache = "", + bool post = false, + bool isgz = false, + int season = -1); + + /*! \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); + + static bool Get(const SUrlEntry& scrURL, + std::string& strHTML, + XFILE::CCurlFile& http, + const std::string& cacheContext); + + // ATTENTION: this member MUST NOT be used directly except from databases + std::string m_data; + +private: + std::string m_title; + std::string m_id; + double m_relevance; + std::vector<SUrlEntry> m_urls; + bool m_parsed; +}; diff --git a/xbmc/utils/Screenshot.cpp b/xbmc/utils/Screenshot.cpp new file mode 100644 index 0000000..25ecbac --- /dev/null +++ b/xbmc/utils/Screenshot.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Screenshot.h" + +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "filesystem/File.h" +#include "guilib/LocalizeStrings.h" +#include "pictures/Picture.h" +#include "settings/SettingPath.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/windows/GUIControlSettings.h" +#include "utils/JobManager.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +using namespace XFILE; + +std::vector<std::function<std::unique_ptr<IScreenshotSurface>()>> CScreenShot::m_screenShotSurfaces; + +void CScreenShot::Register(const std::function<std::unique_ptr<IScreenshotSurface>()>& createFunc) +{ + m_screenShotSurfaces.emplace_back(createFunc); +} + +void CScreenShot::TakeScreenshot(const std::string& filename, bool sync) +{ + auto surface = m_screenShotSurfaces.back()(); + + if (!surface) + { + CLog::Log(LOGERROR, "failed to create screenshot surface"); + return; + } + + if (!surface->Capture()) + { + CLog::Log(LOGERROR, "Screenshot {} failed", CURL::GetRedacted(filename)); + return; + } + + surface->CaptureVideo(true); + + CLog::Log(LOGDEBUG, "Saving screenshot {}", CURL::GetRedacted(filename)); + + //set alpha byte to 0xFF + for (int y = 0; y < surface->GetHeight(); y++) + { + unsigned char* alphaptr = surface->GetBuffer() - 1 + y * surface->GetStride(); + for (int x = 0; x < surface->GetWidth(); 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->GetBuffer(), surface->GetWidth(), surface->GetHeight(), surface->GetStride(), filename)) + CLog::Log(LOGERROR, "Unable to write screenshot {}", CURL::GetRedacted(filename)); + + surface->ReleaseBuffer(); + } + else + { + //make sure the file exists to avoid concurrency issues + XFILE::CFile file; + if (file.OpenForWrite(filename)) + file.Close(); + else + CLog::Log(LOGERROR, "Unable to create file {}", CURL::GetRedacted(filename)); + + //write .png file asynchronous with CThumbnailWriter, prevents stalling of the render thread + //buffer is deleted from CThumbnailWriter + CThumbnailWriter* thumbnailwriter = new CThumbnailWriter(surface->GetBuffer(), surface->GetWidth(), surface->GetHeight(), surface->GetStride(), filename); + CServiceBroker::GetJobManager()->AddJob(thumbnailwriter, nullptr); + } +} + +void CScreenShot::TakeScreenshot() +{ + std::shared_ptr<CSettingPath> screenshotSetting = std::static_pointer_cast<CSettingPath>(CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(CSettings::SETTING_DEBUG_SCREENSHOTPATH)); + if (!screenshotSetting) + return; + + std::string strDir = screenshotSetting->GetValue(); + if (strDir.empty()) + { + if (!CGUIControlButtonSetting::GetPath(screenshotSetting, &g_localizeStrings)) + return; + + strDir = screenshotSetting->GetValue(); + } + + URIUtils::RemoveSlashAtEnd(strDir); + + if (!strDir.empty()) + { + std::string file = + CUtil::GetNextFilename(URIUtils::AddFileToFolder(strDir, "screenshot{:05}.png"), 65535); + + if (!file.empty()) + { + TakeScreenshot(file, false); + } + else + { + CLog::Log(LOGWARNING, "Too many screen shots or invalid folder"); + } + } +} diff --git a/xbmc/utils/Screenshot.h b/xbmc/utils/Screenshot.h new file mode 100644 index 0000000..6c44558 --- /dev/null +++ b/xbmc/utils/Screenshot.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IScreenshotSurface.h" + +#include <functional> +#include <memory> +#include <string> +#include <vector> + +class CScreenShot +{ +public: + static void Register(const std::function<std::unique_ptr<IScreenshotSurface>()>& createFunc); + + static void TakeScreenshot(); + static void TakeScreenshot(const std::string &filename, bool sync); + +private: + static std::vector<std::function<std::unique_ptr<IScreenshotSurface>()>> m_screenShotSurfaces; +}; diff --git a/xbmc/utils/SortUtils.cpp b/xbmc/utils/SortUtils.cpp new file mode 100644 index 0000000..b6b2c21 --- /dev/null +++ b/xbmc/utils/SortUtils.cpp @@ -0,0 +1,1385 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SortUtils.h" + +#include "LangInfo.h" +#include "URL.h" +#include "Util.h" +#include "utils/CharsetConverter.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <algorithm> +#include <inttypes.h> + +std::string ArrayToString(SortAttribute attributes, const CVariant &variant, const std::string &separator = " / ") +{ + std::vector<std::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, separator); + } + else if (variant.isString()) + { + if (attributes & SortAttributeIgnoreArticle) + return SortUtils::RemoveArticles(variant.asString()); + else + return variant.asString(); + } + + return ""; +} + +std::string ByLabel(SortAttribute attributes, const SortItem &values) +{ + if (attributes & SortAttributeIgnoreArticle) + return SortUtils::RemoveArticles(values.at(FieldLabel).asString()); + + return values.at(FieldLabel).asString(); +} + +std::string ByFile(SortAttribute attributes, const SortItem &values) +{ + CURL url(values.at(FieldPath).asString()); + + return StringUtils::Format("{} {}", url.GetFileNameWithoutPath(), + values.at(FieldStartOffset).asInteger()); +} + +std::string ByPath(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", values.at(FieldPath).asString(), + values.at(FieldStartOffset).asInteger()); +} + +std::string ByLastPlayed(SortAttribute attributes, const SortItem &values) +{ + if (attributes & SortAttributeIgnoreLabel) + return values.at(FieldLastPlayed).asString(); + + return StringUtils::Format("{} {}", values.at(FieldLastPlayed).asString(), + ByLabel(attributes, values)); +} + +std::string ByPlaycount(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", (int)values.at(FieldPlaycount).asInteger(), + ByLabel(attributes, values)); +} + +std::string ByDate(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldDate).asString() + " " + ByLabel(attributes, values); +} + +std::string ByDateAdded(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", values.at(FieldDateAdded).asString(), + (int)values.at(FieldId).asInteger()); +} + +std::string BySize(SortAttribute attributes, const SortItem &values) +{ + return std::to_string(values.at(FieldSize).asInteger()); +} + +std::string ByDriveType(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", (int)values.at(FieldDriveType).asInteger(), + ByLabel(attributes, values)); +} + +std::string ByTitle(SortAttribute attributes, const SortItem &values) +{ + if (attributes & SortAttributeIgnoreArticle) + return SortUtils::RemoveArticles(values.at(FieldTitle).asString()); + + return values.at(FieldTitle).asString(); +} + +std::string ByAlbum(SortAttribute attributes, const SortItem &values) +{ + std::string album = values.at(FieldAlbum).asString(); + if (attributes & SortAttributeIgnoreArticle) + album = SortUtils::RemoveArticles(album); + + std::string label = + StringUtils::Format("{} {}", album, ArrayToString(attributes, values.at(FieldArtist))); + + const CVariant &track = values.at(FieldTrackNumber); + if (!track.isNull()) + label += StringUtils::Format(" {}", (int)track.asInteger()); + + return label; +} + +std::string ByAlbumType(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldAlbumType).asString() + " " + ByLabel(attributes, values); +} + +std::string ByArtist(SortAttribute attributes, const SortItem &values) +{ + std::string label; + if (attributes & SortAttributeUseArtistSortName) + { + const CVariant &artistsort = values.at(FieldArtistSort); + if (!artistsort.isNull()) + label = artistsort.asString(); + } + if (label.empty()) + label = ArrayToString(attributes, values.at(FieldArtist)); + + 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(" {}", (int)track.asInteger()); + + return label; +} + +std::string ByArtistThenYear(SortAttribute attributes, const SortItem &values) +{ + std::string label; + if (attributes & SortAttributeUseArtistSortName) + { + const CVariant &artistsort = values.at(FieldArtistSort); + if (!artistsort.isNull()) + label = artistsort.asString(); + } + if (label.empty()) + label = ArrayToString(attributes, values.at(FieldArtist)); + + const CVariant &year = values.at(FieldYear); + if (!year.isNull()) + label += StringUtils::Format(" {}", static_cast<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(" {}", (int)track.asInteger()); + + return label; +} + +std::string ByTrackNumber(SortAttribute attributes, const SortItem &values) +{ + return std::to_string((int)values.at(FieldTrackNumber).asInteger()); +} + +std::string ByTotalDiscs(SortAttribute attributes, const SortItem& values) +{ + return StringUtils::Format("{} {}", static_cast<int>(values.at(FieldTotalDiscs).asInteger()), + ByLabel(attributes, values)); +} +std::string ByTime(SortAttribute attributes, const SortItem &values) +{ + std::string label; + const CVariant &time = values.at(FieldTime); + if (time.isInteger()) + label = std::to_string((int)time.asInteger()); + else + label = time.asString(); + return label; +} + +std::string ByProgramCount(SortAttribute attributes, const SortItem &values) +{ + return std::to_string((int)values.at(FieldProgramCount).asInteger()); +} + +std::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); +} + +std::string ByGenre(SortAttribute attributes, const SortItem &values) +{ + return ArrayToString(attributes, values.at(FieldGenre)); +} + +std::string ByCountry(SortAttribute attributes, const SortItem &values) +{ + return ArrayToString(attributes, values.at(FieldCountry)); +} + +std::string ByYear(SortAttribute attributes, const SortItem &values) +{ + std::string label; + const CVariant &airDate = values.at(FieldAirDate); + if (!airDate.isNull() && !airDate.asString().empty()) + label = airDate.asString() + " "; + + label += std::to_string((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(" {}", (int)track.asInteger()); + + label += " " + ByLabel(attributes, values); + + return label; +} + +std::string ByOrigDate(SortAttribute attributes, const SortItem& values) +{ + std::string label; + label = values.at(FieldOrigDate).asString(); + + 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(" {}", static_cast<int>(track.asInteger())); + + label += " " + ByLabel(attributes, values); + + return label; +} + +std::string BySortTitle(SortAttribute attributes, const SortItem &values) +{ + std::string title = values.at(FieldSortTitle).asString(); + if (title.empty()) + title = values.at(FieldTitle).asString(); + + if (attributes & SortAttributeIgnoreArticle) + title = SortUtils::RemoveArticles(title); + + return title; +} + +std::string ByOriginalTitle(SortAttribute attributes, const SortItem& values) +{ + + std::string title = values.at(FieldOriginalTitle).asString(); + if (title.empty()) + title = values.at(FieldSortTitle).asString(); + + if (title.empty()) + title = values.at(FieldTitle).asString(); + + if (attributes & SortAttributeIgnoreArticle) + title = SortUtils::RemoveArticles(title); + + return title; +} + +std::string ByRating(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{:f} {}", values.at(FieldRating).asFloat(), + ByLabel(attributes, values)); +} + +std::string ByUserRating(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", static_cast<int>(values.at(FieldUserRating).asInteger()), + ByLabel(attributes, values)); +} + +std::string ByVotes(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", (int)values.at(FieldVotes).asInteger(), + ByLabel(attributes, values)); +} + +std::string ByTop250(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", (int)values.at(FieldTop250).asInteger(), + ByLabel(attributes, values)); +} + +std::string ByMPAA(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldMPAA).asString() + " " + ByLabel(attributes, values); +} + +std::string ByStudio(SortAttribute attributes, const SortItem &values) +{ + return ArrayToString(attributes, values.at(FieldStudio)); +} + +std::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("{} {}", num, title); +} + +std::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("{} {}", season, ByLabel(attributes, values)); +} + +std::string ByNumberOfEpisodes(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", (int)values.at(FieldNumberOfEpisodes).asInteger(), + ByLabel(attributes, values)); +} + +std::string ByNumberOfWatchedEpisodes(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", (int)values.at(FieldNumberOfWatchedEpisodes).asInteger(), + ByLabel(attributes, values)); +} + +std::string ByTvShowStatus(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldTvShowStatus).asString() + " " + ByLabel(attributes, values); +} + +std::string ByTvShowTitle(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldTvShowTitle).asString() + " " + ByLabel(attributes, values); +} + +std::string ByProductionCode(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldProductionCode).asString(); +} + +std::string ByVideoResolution(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", (int)values.at(FieldVideoResolution).asInteger(), + ByLabel(attributes, values)); +} + +std::string ByVideoCodec(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", values.at(FieldVideoCodec).asString(), + ByLabel(attributes, values)); +} + +std::string ByVideoAspectRatio(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{:.3f} {}", values.at(FieldVideoAspectRatio).asFloat(), + ByLabel(attributes, values)); +} + +std::string ByAudioChannels(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", (int)values.at(FieldAudioChannels).asInteger(), + ByLabel(attributes, values)); +} + +std::string ByAudioCodec(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", values.at(FieldAudioCodec).asString(), + ByLabel(attributes, values)); +} + +std::string ByAudioLanguage(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", values.at(FieldAudioLanguage).asString(), + ByLabel(attributes, values)); +} + +std::string BySubtitleLanguage(SortAttribute attributes, const SortItem &values) +{ + return StringUtils::Format("{} {}", values.at(FieldSubtitleLanguage).asString(), + ByLabel(attributes, values)); +} + +std::string ByBitrate(SortAttribute attributes, const SortItem &values) +{ + return std::to_string(values.at(FieldBitrate).asInteger()); +} + +std::string ByListeners(SortAttribute attributes, const SortItem &values) +{ + return std::to_string(values.at(FieldListeners).asInteger()); +} + +std::string ByRandom(SortAttribute attributes, const SortItem &values) +{ + return std::to_string(CUtil::GetRandomNumber()); +} + +std::string ByChannel(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldChannelName).asString(); +} + +std::string ByChannelNumber(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldChannelNumber).asString(); +} + +std::string ByClientChannelOrder(SortAttribute attributes, const SortItem& values) +{ + return values.at(FieldClientChannelOrder).asString(); +} + +std::string ByProvider(SortAttribute attributes, const SortItem& values) +{ + return values.at(FieldProvider).asString(); +} + +std::string ByUserPreference(SortAttribute attributes, const SortItem& values) +{ + return values.at(FieldUserPreference).asString(); +} + +std::string ByDateTaken(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldDateTaken).asString(); +} + +std::string ByRelevance(SortAttribute attributes, const SortItem &values) +{ + return std::to_string((int)values.at(FieldRelevance).asInteger()); +} + +std::string ByInstallDate(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldInstallDate).asString(); +} + +std::string ByLastUpdated(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldLastUpdated).asString(); +} + +std::string ByLastUsed(SortAttribute attributes, const SortItem &values) +{ + return values.at(FieldLastUsed).asString(); +} + +std::string ByBPM(SortAttribute attributes, const SortItem& values) +{ + return StringUtils::Format("{} {}", static_cast<int>(values.at(FieldBPM).asInteger()), + ByLabel(attributes, values)); +} + +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) + { + 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); +} + +// clang-format off +std::map<SortBy, SortUtils::SortPreparator> fillPreparators() +{ + std::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[SortByArtistThenYear] = ByArtistThenYear; + preparators[SortByAlbum] = ByAlbum; + preparators[SortByAlbumType] = ByAlbumType; + preparators[SortByGenre] = ByGenre; + preparators[SortByCountry] = ByCountry; + preparators[SortByYear] = ByYear; + preparators[SortByRating] = ByRating; + preparators[SortByUserRating] = ByUserRating; + 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[SortByClientChannelOrder] = ByClientChannelOrder; + preparators[SortByProvider] = ByProvider; + preparators[SortByUserPreference] = ByUserPreference; + preparators[SortByDateTaken] = ByDateTaken; + preparators[SortByRelevance] = ByRelevance; + preparators[SortByInstallDate] = ByInstallDate; + preparators[SortByLastUpdated] = ByLastUpdated; + preparators[SortByLastUsed] = ByLastUsed; + preparators[SortByTotalDiscs] = ByTotalDiscs; + preparators[SortByOrigDate] = ByOrigDate; + preparators[SortByBPM] = ByBPM; + preparators[SortByOriginalTitle] = ByOriginalTitle; + + return preparators; +} +// clang-format on + +std::map<SortBy, Fields> fillSortingFields() +{ + std::map<SortBy, Fields> sortingFields; + + sortingFields.insert(std::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(FieldArtistSort); + sortingFields[SortByArtist].insert(FieldYear); + sortingFields[SortByArtist].insert(FieldAlbum); + sortingFields[SortByArtist].insert(FieldTrackNumber); + sortingFields[SortByArtistThenYear].insert(FieldArtist); + sortingFields[SortByArtistThenYear].insert(FieldArtistSort); + sortingFields[SortByArtistThenYear].insert(FieldYear); + sortingFields[SortByArtistThenYear].insert(FieldOrigDate); + sortingFields[SortByArtistThenYear].insert(FieldAlbum); + sortingFields[SortByArtistThenYear].insert(FieldTrackNumber); + sortingFields[SortByAlbum].insert(FieldAlbum); + sortingFields[SortByAlbum].insert(FieldArtist); + sortingFields[SortByAlbum].insert(FieldArtistSort); + 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[SortByYear].insert(FieldOrigDate); + sortingFields[SortByRating].insert(FieldRating); + sortingFields[SortByUserRating].insert(FieldUserRating); + 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[SortByClientChannelOrder].insert(FieldClientChannelOrder); + sortingFields[SortByProvider].insert(FieldProvider); + sortingFields[SortByUserPreference].insert(FieldUserPreference); + sortingFields[SortByDateTaken].insert(FieldDateTaken); + sortingFields[SortByRelevance].insert(FieldRelevance); + sortingFields[SortByInstallDate].insert(FieldInstallDate); + sortingFields[SortByLastUpdated].insert(FieldLastUpdated); + sortingFields[SortByLastUsed].insert(FieldLastUsed); + sortingFields[SortByTotalDiscs].insert(FieldTotalDiscs); + sortingFields[SortByOrigDate].insert(FieldOrigDate); + sortingFields[SortByOrigDate].insert(FieldAlbum); + sortingFields[SortByOrigDate].insert(FieldTrackNumber); + sortingFields[SortByBPM].insert(FieldBPM); + sortingFields[SortByOriginalTitle].insert(FieldOriginalTitle); + sortingFields[SortByOriginalTitle].insert(FieldTitle); + sortingFields[SortByOriginalTitle].insert(FieldSortTitle); + sortingFields.insert(std::pair<SortBy, Fields>(SortByRandom, Fields())); + + return sortingFields; +} + +std::map<SortBy, SortUtils::SortPreparator> SortUtils::m_preparators = fillPreparators(); +std::map<SortBy, Fields> SortUtils::m_sortingFields = fillSortingFields(); + +void SortUtils::GetFieldsForSQLSort(const MediaType& mediaType, + SortBy sortMethod, + FieldList& fields) +{ + fields.clear(); + if (mediaType == MediaTypeNone) + return; + + if (mediaType == MediaTypeAlbum) + { + if (sortMethod == SortByLabel || sortMethod == SortByAlbum || sortMethod == SortByTitle) + { + fields.emplace_back(FieldAlbum); + fields.emplace_back(FieldArtist); + } + else if (sortMethod == SortByAlbumType) + { + fields.emplace_back(FieldAlbumType); + fields.emplace_back(FieldAlbum); + fields.emplace_back(FieldArtist); + } + else if (sortMethod == SortByArtist) + { + fields.emplace_back(FieldArtist); + fields.emplace_back(FieldAlbum); + } + else if (sortMethod == SortByArtistThenYear) + { + fields.emplace_back(FieldArtist); + fields.emplace_back(FieldYear); + fields.emplace_back(FieldAlbum); + } + else if (sortMethod == SortByYear) + { + fields.emplace_back(FieldYear); + fields.emplace_back(FieldAlbum); + } + else if (sortMethod == SortByGenre) + { + fields.emplace_back(FieldGenre); + fields.emplace_back(FieldAlbum); + } + else if (sortMethod == SortByDateAdded) + fields.emplace_back(FieldDateAdded); + else if (sortMethod == SortByPlaycount) + { + fields.emplace_back(FieldPlaycount); + fields.emplace_back(FieldAlbum); + } + else if (sortMethod == SortByLastPlayed) + { + fields.emplace_back(FieldLastPlayed); + fields.emplace_back(FieldAlbum); + } + else if (sortMethod == SortByRating) + { + fields.emplace_back(FieldRating); + fields.emplace_back(FieldAlbum); + } + else if (sortMethod == SortByVotes) + { + fields.emplace_back(FieldVotes); + fields.emplace_back(FieldAlbum); + } + else if (sortMethod == SortByUserRating) + { + fields.emplace_back(FieldUserRating); + fields.emplace_back(FieldAlbum); + } + else if (sortMethod == SortByTotalDiscs) + { + fields.emplace_back(FieldTotalDiscs); + fields.emplace_back(FieldAlbum); + } + else if (sortMethod == SortByOrigDate) + { + fields.emplace_back(FieldOrigDate); + fields.emplace_back(FieldAlbum); + } + } + else if (mediaType == MediaTypeSong) + { + if (sortMethod == SortByLabel || sortMethod == SortByTrackNumber) + fields.emplace_back(FieldTrackNumber); + else if (sortMethod == SortByTitle) + fields.emplace_back(FieldTitle); + else if (sortMethod == SortByAlbum) + { + fields.emplace_back(FieldAlbum); + fields.emplace_back(FieldAlbumArtist); + fields.emplace_back(FieldTrackNumber); + } + else if (sortMethod == SortByArtist) + { + fields.emplace_back(FieldArtist); + fields.emplace_back(FieldAlbum); + fields.emplace_back(FieldTrackNumber); + } + else if (sortMethod == SortByArtistThenYear) + { + fields.emplace_back(FieldArtist); + fields.emplace_back(FieldYear); + fields.emplace_back(FieldAlbum); + fields.emplace_back(FieldTrackNumber); + } + else if (sortMethod == SortByYear) + { + fields.emplace_back(FieldYear); + fields.emplace_back(FieldAlbum); + fields.emplace_back(FieldTrackNumber); + } + else if (sortMethod == SortByGenre) + { + fields.emplace_back(FieldGenre); + fields.emplace_back(FieldAlbum); + } + else if (sortMethod == SortByDateAdded) + fields.emplace_back(FieldDateAdded); + else if (sortMethod == SortByPlaycount) + { + fields.emplace_back(FieldPlaycount); + fields.emplace_back(FieldTrackNumber); + } + else if (sortMethod == SortByLastPlayed) + { + fields.emplace_back(FieldLastPlayed); + fields.emplace_back(FieldTrackNumber); + } + else if (sortMethod == SortByRating) + { + fields.emplace_back(FieldRating); + fields.emplace_back(FieldTrackNumber); + } + else if (sortMethod == SortByVotes) + { + fields.emplace_back(FieldVotes); + fields.emplace_back(FieldTrackNumber); + } + else if (sortMethod == SortByUserRating) + { + fields.emplace_back(FieldUserRating); + fields.emplace_back(FieldTrackNumber); + } + else if (sortMethod == SortByFile) + { + fields.emplace_back(FieldPath); + fields.emplace_back(FieldFilename); + fields.emplace_back(FieldStartOffset); + } + else if (sortMethod == SortByTime) + fields.emplace_back(FieldTime); + else if (sortMethod == SortByAlbumType) + { + fields.emplace_back(FieldAlbumType); + fields.emplace_back(FieldAlbum); + fields.emplace_back(FieldTrackNumber); + } + else if (sortMethod == SortByOrigDate) + { + fields.emplace_back(FieldOrigDate); + fields.emplace_back(FieldAlbum); + fields.emplace_back(FieldTrackNumber); + } + else if (sortMethod == SortByBPM) + fields.emplace_back(FieldBPM); + } + else if (mediaType == MediaTypeArtist) + { + if (sortMethod == SortByLabel || sortMethod == SortByTitle || sortMethod == SortByArtist) + fields.emplace_back(FieldArtist); + else if (sortMethod == SortByGenre) + fields.emplace_back(FieldGenre); + else if (sortMethod == SortByDateAdded) + fields.emplace_back(FieldDateAdded); + } + + // Add sort by id to define order when other fields same or sort none + fields.emplace_back(FieldId); + return; +} + + +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(std::pair<Field, CVariant>(*field, CVariant::ConstNullVariant)); + } + + std::wstring sortLabel; + g_charsetConverter.utf8ToW(preparator(attributes, *item), sortLabel, false); + item->insert(std::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(std::pair<Field, CVariant>(*field, CVariant::ConstNullVariant)); + } + + std::wstring sortLabel; + g_charsetConverter.utf8ToW(preparator(attributes, **item), sortLabel, false); + (*item)->insert(std::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::unique_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) +{ + std::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) +{ + std::map<SortBy, Fields>::const_iterator it = m_sortingFields.find(sortBy); + if (it != m_sortingFields.end()) + return it->second; + + return m_sortingFields[SortByNone]; +} + +std::string SortUtils::RemoveArticles(const std::string &label) +{ + std::set<std::string> sortTokens = g_langInfo.GetSortTokens(); + for (std::set<std::string>::const_iterator token = sortTokens.begin(); token != sortTokens.end(); ++token) + { + if (token->size() < label.size() && StringUtils::StartsWithNoCase(label, *token)) + return label.substr(token->size()); + } + + return label; +} + +typedef struct +{ + SortBy sort; + SORT_METHOD old; + SortAttribute flags; + int label; +} sort_map; + +// clang-format off +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 }, + { SortByArtistThenYear, SORT_METHOD_ARTIST_AND_YEAR, SortAttributeNone, 578 }, + { 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 }, + { SortByUserRating, SORT_METHOD_SONG_USER_RATING, SortAttributeIgnoreFolders, 38018 }, + { SortByUserRating, SORT_METHOD_VIDEO_USER_RATING, SortAttributeIgnoreFolders, 38018 }, + { SortBySortTitle, SORT_METHOD_VIDEO_SORT_TITLE, SortAttributeIgnoreFolders, 171 }, + { SortBySortTitle, SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE, (SortAttribute)(SortAttributeIgnoreFolders | SortAttributeIgnoreArticle), 171 }, + { SortByOriginalTitle, SORT_METHOD_VIDEO_ORIGINAL_TITLE, SortAttributeIgnoreFolders, 20376 }, + { SortByOriginalTitle, SORT_METHOD_VIDEO_ORIGINAL_TITLE_IGNORE_THE, (SortAttribute)(SortAttributeIgnoreFolders | SortAttributeIgnoreArticle), 20376 }, + { 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 }, + { SortByChannel, SORT_METHOD_CLIENT_CHANNEL_ORDER, SortAttributeNone, 19315 }, + { SortByProvider, SORT_METHOD_PROVIDER, SortAttributeNone, 19348 }, + { SortByUserPreference, SORT_METHOD_USER_PREFERENCE, SortAttributeNone, 19349 }, + { SortByDateTaken, SORT_METHOD_DATE_TAKEN, SortAttributeIgnoreFolders, 577 }, + { SortByNone, SORT_METHOD_NONE, SortAttributeNone, 16018 }, + { SortByTotalDiscs, SORT_METHOD_TOTAL_DISCS, SortAttributeNone, 38077 }, + { SortByOrigDate, SORT_METHOD_ORIG_DATE, SortAttributeNone, 38079 }, + { SortByBPM, SORT_METHOD_BPM, SortAttributeNone, 38080 }, + + // 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 } +}; +// clang-format on + +SORT_METHOD SortUtils::TranslateOldSortMethod(SortBy sortBy, bool ignoreArticle) +{ + for (const sort_map& t : table) + { + if (t.sort == sortBy) + { + if (ignoreArticle == ((t.flags & SortAttributeIgnoreArticle) == SortAttributeIgnoreArticle)) + return t.old; + } + } + for (const sort_map& t : table) + { + if (t.sort == sortBy) + return t.old; + } + return SORT_METHOD_NONE; +} + +SortDescription SortUtils::TranslateOldSortMethod(SORT_METHOD sortBy) +{ + SortDescription description; + for (const sort_map& t : table) + { + if (t.old == sortBy) + { + description.sortBy = t.sort; + description.sortAttributes = t.flags; + break; + } + } + return description; +} + +int SortUtils::GetSortLabel(SortBy sortBy) +{ + for (const sort_map& t : table) + { + if (t.sort == sortBy) + return t.label; + } + return 16018; // None +} + +template<typename T> +T TypeFromString(const std::map<std::string, T>& typeMap, const std::string& name, const T& defaultType) +{ + auto it = typeMap.find(name); + if (it == typeMap.end()) + return defaultType; + + return it->second; +} + +template<typename T> +const std::string& TypeToString(const std::map<std::string, T>& typeMap, const T& value) +{ + auto it = std::find_if(typeMap.begin(), typeMap.end(), + [&value](const std::pair<std::string, T>& pair) + { + return pair.second == value; + }); + + if (it == typeMap.end()) + return StringUtils::Empty; + + return it->first; +} + +/** + * @brief Sort methods to translate string values to enum values. + * + * @warning On string changes, edit __SortBy__ enumerator to have strings right + * for documentation! + */ +const std::map<std::string, SortBy> sortMethods = { + {"label", SortByLabel}, + {"date", SortByDate}, + {"size", SortBySize}, + {"file", SortByFile}, + {"path", SortByPath}, + {"drivetype", SortByDriveType}, + {"title", SortByTitle}, + {"track", SortByTrackNumber}, + {"time", SortByTime}, + {"artist", SortByArtist}, + {"artistyear", SortByArtistThenYear}, + {"album", SortByAlbum}, + {"albumtype", SortByAlbumType}, + {"genre", SortByGenre}, + {"country", SortByCountry}, + {"year", SortByYear}, + {"rating", SortByRating}, + {"votes", SortByVotes}, + {"top250", SortByTop250}, + {"programcount", SortByProgramCount}, + {"playlist", SortByPlaylistOrder}, + {"episode", SortByEpisodeNumber}, + {"season", SortBySeason}, + {"totalepisodes", SortByNumberOfEpisodes}, + {"watchedepisodes", SortByNumberOfWatchedEpisodes}, + {"tvshowstatus", SortByTvShowStatus}, + {"tvshowtitle", SortByTvShowTitle}, + {"sorttitle", SortBySortTitle}, + {"productioncode", SortByProductionCode}, + {"mpaa", SortByMPAA}, + {"videoresolution", SortByVideoResolution}, + {"videocodec", SortByVideoCodec}, + {"videoaspectratio", SortByVideoAspectRatio}, + {"audiochannels", SortByAudioChannels}, + {"audiocodec", SortByAudioCodec}, + {"audiolanguage", SortByAudioLanguage}, + {"subtitlelanguage", SortBySubtitleLanguage}, + {"studio", SortByStudio}, + {"dateadded", SortByDateAdded}, + {"lastplayed", SortByLastPlayed}, + {"playcount", SortByPlaycount}, + {"listeners", SortByListeners}, + {"bitrate", SortByBitrate}, + {"random", SortByRandom}, + {"channel", SortByChannel}, + {"channelnumber", SortByChannelNumber}, + {"clientchannelorder", SortByClientChannelOrder}, + {"provider", SortByProvider}, + {"userpreference", SortByUserPreference}, + {"datetaken", SortByDateTaken}, + {"userrating", SortByUserRating}, + {"installdate", SortByInstallDate}, + {"lastupdated", SortByLastUpdated}, + {"lastused", SortByLastUsed}, + {"totaldiscs", SortByTotalDiscs}, + {"originaldate", SortByOrigDate}, + {"bpm", SortByBPM}, + {"originaltitle", SortByOriginalTitle}, +}; + +SortBy SortUtils::SortMethodFromString(const std::string& sortMethod) +{ + return TypeFromString<SortBy>(sortMethods, sortMethod, SortByNone); +} + +const std::string& SortUtils::SortMethodToString(SortBy sortMethod) +{ + return TypeToString<SortBy>(sortMethods, sortMethod); +} + +const std::map<std::string, SortOrder> sortOrders = { + { "ascending", SortOrderAscending }, + { "descending", SortOrderDescending } +}; + +SortOrder SortUtils::SortOrderFromString(const std::string& sortOrder) +{ + return TypeFromString<SortOrder>(sortOrders, sortOrder, SortOrderNone); +} + +const std::string& SortUtils::SortOrderToString(SortOrder sortOrder) +{ + return TypeToString<SortOrder>(sortOrders, sortOrder); +} diff --git a/xbmc/utils/SortUtils.h b/xbmc/utils/SortUtils.h new file mode 100644 index 0000000..19a7d07 --- /dev/null +++ b/xbmc/utils/SortUtils.h @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DatabaseUtils.h" +#include "LabelFormatter.h" +#include "SortFileItem.h" + +#include <map> +#include <memory> +#include <string> +#include <vector> + +typedef enum { + SortOrderNone = 0, + SortOrderAscending, + SortOrderDescending +} SortOrder; + +typedef enum { + SortAttributeNone = 0x0, + SortAttributeIgnoreArticle = 0x1, + SortAttributeIgnoreFolders = 0x2, + SortAttributeUseArtistSortName = 0x4, + SortAttributeIgnoreLabel = 0x8 +} SortAttribute; + +typedef enum { + SortSpecialNone = 0, + SortSpecialOnTop = 1, + SortSpecialOnBottom = 2 +} SortSpecial; + +/// +/// \defgroup List_of_sort_methods List of sort methods +/// \addtogroup List_of_sort_methods +/// +/// \brief These ID's can be used with the \ref built_in_functions_6 "Container.SetSortMethod(id)" function +/// \note The on field named part with <b>String</b> shows the string used on +/// GUI to set this sort type. +/// +///@{ +typedef enum +{ + /// __0__ : + SortByNone = 0, + /// __1__ : Sort by Name <em>(String: <b><c>Label</c></b>)</em> + SortByLabel, + /// __2__ : Sort by Date <em>(String: <b><c>Date</c></b>)</em> + SortByDate, + /// __3__ : Sort by Size <em>(String: <b><c>Size</c></b>)</em> + SortBySize, + /// __4__ : Sort by filename <em>(String: <b><c>File</c></b>)</em> + SortByFile, + /// __5__ : Sort by path <em>(String: <b><c>Path</c></b>)</em> + SortByPath, + /// __6__ : Sort by drive type <em>(String: <b><c>DriveType</c></b>)</em> + SortByDriveType, + /// __7__ : Sort by title <em>(String: <b><c>Title</c></b>)</em> + SortByTitle, + /// __8__ : Sort by track number <em>(String: <b><c>TrackNumber</c></b>)</em> + SortByTrackNumber, + /// __9__ : Sort by time <em>(String: <b><c>Time</c></b>)</em> + SortByTime, + /// __10__ : Sort by artist <em>(String: <b><c>Artist</c></b>)</em> + SortByArtist, + /// __11__ : Sort by first artist then year <em>(String: <b><c>ArtistYear</c></b>)</em> + SortByArtistThenYear, + /// __12__ : Sort by album <em>(String: <b><c>Album</c></b>)</em> + SortByAlbum, + /// __13__ : Sort by album type <em>(String: <b><c>AlbumType</c></b>)</em> + SortByAlbumType, + /// __14__ : Sort by genre <em>(String: <b><c>Genre</c></b>)</em> + SortByGenre, + /// __15__ : Sort by country <em>(String: <b><c>Country</c></b>)</em> + SortByCountry, + /// __16__ : Sort by year <em>(String: <b><c>Year</c></b>)</em> + SortByYear, + /// __17__ : Sort by rating <em>(String: <b><c>Rating</c></b>)</em> + SortByRating, + /// __18__ : Sort by user rating <em>(String: <b><c>UserRating</c></b>)</em> + SortByUserRating, + /// __19__ : Sort by votes <em>(String: <b><c>Votes</c></b>)</em> + SortByVotes, + /// __20__ : Sort by top 250 <em>(String: <b><c>Top250</c></b>)</em> + SortByTop250, + /// __21__ : Sort by program count <em>(String: <b><c>ProgramCount</c></b>)</em> + SortByProgramCount, + /// __22__ : Sort by playlist order <em>(String: <b><c>Playlist</c></b>)</em> + SortByPlaylistOrder, + /// __23__ : Sort by episode number <em>(String: <b><c>Episode</c></b>)</em> + SortByEpisodeNumber, + /// __24__ : Sort by season <em>(String: <b><c>Season</c></b>)</em> + SortBySeason, + /// __25__ : Sort by number of episodes <em>(String: <b><c>TotalEpisodes</c></b>)</em> + SortByNumberOfEpisodes, + /// __26__ : Sort by number of watched episodes <em>(String: <b><c>WatchedEpisodes</c></b>)</em> + SortByNumberOfWatchedEpisodes, + /// __27__ : Sort by TV show status <em>(String: <b><c>TvShowStatus</c></b>)</em> + SortByTvShowStatus, + /// __28__ : Sort by TV show title <em>(String: <b><c>TvShowTitle</c></b>)</em> + SortByTvShowTitle, + /// __29__ : Sort by sort title <em>(String: <b><c>SortTitle</c></b>)</em> + SortBySortTitle, + /// __30__ : Sort by production code <em>(String: <b><c>ProductionCode</c></b>)</em> + SortByProductionCode, + /// __31__ : Sort by MPAA <em>(String: <b><c>MPAA</c></b>)</em> + SortByMPAA, + /// __32__ : Sort by video resolution <em>(String: <b><c>VideoResolution</c></b>)</em> + SortByVideoResolution, + /// __33__ : Sort by video codec <em>(String: <b><c>VideoCodec</c></b>)</em> + SortByVideoCodec, + /// __34__ : Sort by video aspect ratio <em>(String: <b><c>VideoAspectRatio</c></b>)</em> + SortByVideoAspectRatio, + /// __35__ : Sort by audio channels <em>(String: <b><c>AudioChannels</c></b>)</em> + SortByAudioChannels, + /// __36__ : Sort by audio codec <em>(String: <b><c>AudioCodec</c></b>)</em> + SortByAudioCodec, + /// __37__ : Sort by audio language <em>(String: <b><c>AudioLanguage</c></b>)</em> + SortByAudioLanguage, + /// __38__ : Sort by subtitle language <em>(String: <b><c>SubtitleLanguage</c></b>)</em> + SortBySubtitleLanguage, + /// __39__ : Sort by studio <em>(String: <b><c>Studio</c></b>)</em> + SortByStudio, + /// __40__ : Sort by date added <em>(String: <b><c>DateAdded</c></b>)</em> + SortByDateAdded, + /// __41__ : Sort by last played <em>(String: <b><c>LastPlayed</c></b>)</em> + SortByLastPlayed, + /// __42__ : Sort by playcount <em>(String: <b><c>PlayCount</c></b>)</em> + SortByPlaycount, + /// __43__ : Sort by listener <em>(String: <b><c>Listeners</c></b>)</em> + SortByListeners, + /// __44__ : Sort by bitrate <em>(String: <b><c>Bitrate</c></b>)</em> + SortByBitrate, + /// __45__ : Sort by random <em>(String: <b><c>Random</c></b>)</em> + SortByRandom, + /// __46__ : Sort by channel <em>(String: <b><c>Channel</c></b>)</em> + SortByChannel, + /// __47__ : Sort by channel number <em>(String: <b><c>ChannelNumber</c></b>)</em> + SortByChannelNumber, + /// __48__ : Sort by date taken <em>(String: <b><c>DateTaken</c></b>)</em> + SortByDateTaken, + /// __49__ : Sort by relevance + SortByRelevance, + /// __50__ : Sort by installation date <en>(String: <b><c>installdate</c></b>)</em> + SortByInstallDate, + /// __51__ : Sort by last updated <en>(String: <b><c>lastupdated</c></b>)</em> + SortByLastUpdated, + /// __52__ : Sort by last used <em>(String: <b><c>lastused</c></b>)</em> + SortByLastUsed, + /// __53__ : Sort by client channel order <em>(String: <b><c>ClientChannelOrder</c></b>)</em> + SortByClientChannelOrder, + /// __54__ : Sort by total number of discs <em>(String: <b><c>totaldiscs</c></b>)</em> + SortByTotalDiscs, + /// __55__ : Sort by original release date <em>(String: <b><c>Originaldate</c></b>)</em> + SortByOrigDate, + /// __56__ : Sort by BPM <em>(String: <b><c>bpm</c></b>)</em> + SortByBPM, + /// __57__ : Sort by original title <em>(String: <b><c>OriginalTitle</c></b>)</em> + SortByOriginalTitle, + /// __58__ : Sort by provider <em>(String: <b><c>Provider</c></b>)</em> + /// @skinning_v20 <b>SortByProvider</b> New sort method added. + SortByProvider, + /// __59__ : Sort by user preference <em>(String: <b><c>UserPreference</c></b>)</em> + /// @skinning_v20 <b>SortByUserPreference</b> New sort method added. + SortByUserPreference, +} SortBy; +///@} + +typedef struct SortDescription { + SortBy sortBy = SortByNone; + SortOrder sortOrder = SortOrderAscending; + SortAttribute sortAttributes = SortAttributeNone; + int limitStart = 0; + int limitEnd = -1; +} SortDescription; + +typedef struct GUIViewSortDetails +{ + SortDescription m_sortDescription; + int m_buttonLabel; + LABEL_MASKS m_labelMasks; +} GUIViewSortDetails; + +typedef DatabaseResult SortItem; +typedef std::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); + + static SortBy SortMethodFromString(const std::string& sortMethod); + static const std::string& SortMethodToString(SortBy sortMethod); + static SortOrder SortOrderFromString(const std::string& sortOrder); + static const std::string& SortOrderToString(SortOrder sortOrder); + + /*! \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::unique_ptr<dbiplus::Dataset> &dataset, DatabaseResults &results); + + static void GetFieldsForSQLSort(const MediaType& mediaType, SortBy sortMethod, FieldList& fields); + 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/xbmc/utils/Speed.cpp b/xbmc/utils/Speed.cpp new file mode 100644 index 0000000..d1326c7 --- /dev/null +++ b/xbmc/utils/Speed.cpp @@ -0,0 +1,582 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Speed.h" + +#include "utils/Archive.h" +#include "utils/StringUtils.h" + +#include <assert.h> + +CSpeed::CSpeed() +{ + m_value = 0.0; + m_valid = false; +} + +CSpeed::CSpeed(const CSpeed& speed) +{ + m_value = speed.m_value; + m_valid = speed.m_valid; +} + +CSpeed::CSpeed(double value) +{ + m_value = value; + m_valid = true; +} + +bool CSpeed::operator >(const CSpeed& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + if (!IsValid() || !right.IsValid()) + return false; + + if (this == &right) + return false; + + return (m_value > right.m_value); +} + +bool CSpeed::operator >=(const CSpeed& right) const +{ + return operator >(right) || operator ==(right); +} + +bool CSpeed::operator <(const CSpeed& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + if (!IsValid() || !right.IsValid()) + return false; + + if (this == &right) + return false; + + return (m_value < right.m_value); +} + +bool CSpeed::operator <=(const CSpeed& right) const +{ + return operator <(right) || operator ==(right); +} + +bool CSpeed::operator ==(const CSpeed& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + if (!IsValid() || !right.IsValid()) + return false; + + if (this == &right) + return true; + + return (m_value == right.m_value); +} + +bool CSpeed::operator !=(const CSpeed& right) const +{ + return !operator ==(right.m_value); +} + +CSpeed& CSpeed::operator =(const CSpeed& right) +{ + m_valid = right.m_valid; + m_value = right.m_value; + return *this; +} + +const CSpeed& CSpeed::operator +=(const CSpeed& right) +{ + assert(IsValid()); + assert(right.IsValid()); + + m_value += right.m_value; + return *this; +} + +const CSpeed& CSpeed::operator -=(const CSpeed& right) +{ + assert(IsValid()); + assert(right.IsValid()); + + m_value -= right.m_value; + return *this; +} + +const CSpeed& CSpeed::operator *=(const CSpeed& right) +{ + assert(IsValid()); + assert(right.IsValid()); + + m_value *= right.m_value; + return *this; +} + +const CSpeed& CSpeed::operator /=(const CSpeed& right) +{ + assert(IsValid()); + assert(right.IsValid()); + + m_value /= right.m_value; + return *this; +} + +CSpeed CSpeed::operator +(const CSpeed& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + CSpeed temp(*this); + + if (!IsValid() || !right.IsValid()) + temp.SetValid(false); + else + temp.m_value += right.m_value; + + return temp; +} + +CSpeed CSpeed::operator -(const CSpeed& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + CSpeed temp(*this); + if (!IsValid() || !right.IsValid()) + temp.SetValid(false); + else + temp.m_value -= right.m_value; + + return temp; +} + +CSpeed CSpeed::operator *(const CSpeed& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + CSpeed temp(*this); + if (!IsValid() || !right.IsValid()) + temp.SetValid(false); + else + temp.m_value *= right.m_value; + return temp; +} + +CSpeed CSpeed::operator /(const CSpeed& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + CSpeed temp(*this); + if (!IsValid() || !right.IsValid()) + temp.SetValid(false); + else + temp.m_value /= right.m_value; + return temp; +} + +CSpeed& CSpeed::operator ++() +{ + assert(IsValid()); + + m_value++; + return *this; +} + +CSpeed& CSpeed::operator --() +{ + assert(IsValid()); + + m_value--; + return *this; +} + +CSpeed CSpeed::operator ++(int) +{ + assert(IsValid()); + + CSpeed temp(*this); + m_value++; + return temp; +} + +CSpeed CSpeed::operator --(int) +{ + assert(IsValid()); + + CSpeed temp(*this); + m_value--; + return temp; +} + +bool CSpeed::operator >(double right) const +{ + assert(IsValid()); + + if (!IsValid()) + return false; + + return (m_value > right); +} + +bool CSpeed::operator >=(double right) const +{ + return operator >(right) || operator ==(right); +} + +bool CSpeed::operator <(double right) const +{ + assert(IsValid()); + + if (!IsValid()) + return false; + + return (m_value < right); +} + +bool CSpeed::operator <=(double right) const +{ + return operator <(right) || operator ==(right); +} + +bool CSpeed::operator ==(double right) const +{ + if (!IsValid()) + return false; + + return (m_value == right); +} + +bool CSpeed::operator !=(double right) const +{ + return !operator ==(right); +} + +const CSpeed& CSpeed::operator +=(double right) +{ + assert(IsValid()); + + m_value += right; + return *this; +} + +const CSpeed& CSpeed::operator -=(double right) +{ + assert(IsValid()); + + m_value -= right; + return *this; +} + +const CSpeed& CSpeed::operator *=(double right) +{ + assert(IsValid()); + + m_value *= right; + return *this; +} + +const CSpeed& CSpeed::operator /=(double right) +{ + assert(IsValid()); + + m_value /= right; + return *this; +} + +CSpeed CSpeed::operator +(double right) const +{ + assert(IsValid()); + + CSpeed temp(*this); + temp.m_value += right; + return temp; +} + +CSpeed CSpeed::operator -(double right) const +{ + assert(IsValid()); + + CSpeed temp(*this); + temp.m_value -= right; + return temp; +} + +CSpeed CSpeed::operator *(double right) const +{ + assert(IsValid()); + + CSpeed temp(*this); + temp.m_value *= right; + return temp; +} + +CSpeed CSpeed::operator /(double right) const +{ + assert(IsValid()); + + CSpeed temp(*this); + temp.m_value /= right; + return temp; +} + +CSpeed CSpeed::CreateFromKilometresPerHour(double value) +{ + return CSpeed(value / 3.6); +} + +CSpeed CSpeed::CreateFromMetresPerMinute(double value) +{ + return CSpeed(value / 60.0); +} + +CSpeed CSpeed::CreateFromMetresPerSecond(double value) +{ + return CSpeed(value); +} + +CSpeed CSpeed::CreateFromFeetPerHour(double value) +{ + return CreateFromFeetPerMinute(value / 60.0); +} + +CSpeed CSpeed::CreateFromFeetPerMinute(double value) +{ + return CreateFromFeetPerSecond(value / 60.0); +} + +CSpeed CSpeed::CreateFromFeetPerSecond(double value) +{ + return CSpeed(value / 3.280839895); +} + +CSpeed CSpeed::CreateFromMilesPerHour(double value) +{ + return CSpeed(value / 2.236936292); +} + +CSpeed CSpeed::CreateFromKnots(double value) +{ + return CSpeed(value / 1.943846172); +} + +CSpeed CSpeed::CreateFromBeaufort(unsigned int value) +{ + if (value == 0) + return CSpeed(0.15); + if (value == 1) + return CSpeed(0.9); + if (value == 2) + return CSpeed(2.4); + if (value == 3) + return CSpeed(4.4); + if (value == 4) + return CSpeed(6.75); + if (value == 5) + return CSpeed(9.4); + if (value == 6) + return CSpeed(12.35); + if (value == 7) + return CSpeed(15.55); + if (value == 8) + return CSpeed(18.95); + if (value == 9) + return CSpeed(22.6); + if (value == 10) + return CSpeed(26.45); + if (value == 11) + return CSpeed(30.5); + + return CSpeed(32.6); +} + +CSpeed CSpeed::CreateFromInchPerSecond(double value) +{ + return CSpeed(value / 39.37007874); +} + +CSpeed CSpeed::CreateFromYardPerSecond(double value) +{ + return CSpeed(value / 1.093613298); +} + +CSpeed CSpeed::CreateFromFurlongPerFortnight(double value) +{ + return CSpeed(value / 6012.885613871); +} + +void CSpeed::Archive(CArchive& ar) +{ + if (ar.IsStoring()) + { + ar << m_value; + ar << m_valid; + } + else + { + ar >> m_value; + ar >> m_valid; + } +} + +bool CSpeed::IsValid() const +{ + return m_valid; +} + +double CSpeed::ToKilometresPerHour() const +{ + return m_value * 3.6; +} + +double CSpeed::ToMetresPerMinute() const +{ + return m_value * 60.0; +} + +double CSpeed::ToMetresPerSecond() const +{ + return m_value; +} + +double CSpeed::ToFeetPerHour() const +{ + return ToFeetPerMinute() * 60.0; +} + +double CSpeed::ToFeetPerMinute() const +{ + return ToFeetPerSecond() * 60.0; +} + +double CSpeed::ToFeetPerSecond() const +{ + return m_value * 3.280839895; +} + +double CSpeed::ToMilesPerHour() const +{ + return m_value * 2.236936292; +} + +double CSpeed::ToKnots() const +{ + return m_value * 1.943846172; +} + +double CSpeed::ToBeaufort() const +{ + if (m_value < 0.3) + return 0; + if (m_value >= 0.3 && m_value < 1.5) + return 1; + if (m_value >= 1.5 && m_value < 3.3) + return 2; + if (m_value >= 3.3 && m_value < 5.5) + return 3; + if (m_value >= 5.5 && m_value < 8.0) + return 4; + if (m_value >= 8.0 && m_value < 10.8) + return 5; + if (m_value >= 10.8 && m_value < 13.9) + return 6; + if (m_value >= 13.9 && m_value < 17.2) + return 7; + if (m_value >= 17.2 && m_value < 20.7) + return 8; + if (m_value >= 20.7 && m_value < 24.5) + return 9; + if (m_value >= 24.5 && m_value < 28.4) + return 10; + if (m_value >= 28.4 && m_value < 32.6) + return 11; + + return 12; +} + +double CSpeed::ToInchPerSecond() const +{ + return m_value * 39.37007874; +} + +double CSpeed::ToYardPerSecond() const +{ + return m_value * 1.093613298; +} + +double CSpeed::ToFurlongPerFortnight() const +{ + return m_value * 6012.885613871; +} + +double CSpeed::To(Unit speedUnit) const +{ + if (!IsValid()) + return 0; + + double value = 0.0; + + switch (speedUnit) + { + case UnitKilometresPerHour: + value = ToKilometresPerHour(); + break; + case UnitMetresPerMinute: + value = ToMetresPerMinute(); + break; + case UnitMetresPerSecond: + value = ToMetresPerSecond(); + break; + case UnitFeetPerHour: + value = ToFeetPerHour(); + break; + case UnitFeetPerMinute: + value = ToFeetPerMinute(); + break; + case UnitFeetPerSecond: + value = ToFeetPerSecond(); + break; + case UnitMilesPerHour: + value = ToMilesPerHour(); + break; + case UnitKnots: + value = ToKnots(); + break; + case UnitBeaufort: + value = ToBeaufort(); + break; + case UnitInchPerSecond: + value = ToInchPerSecond(); + break; + case UnitYardPerSecond: + value = ToYardPerSecond(); + break; + case UnitFurlongPerFortnight: + value = ToFurlongPerFortnight(); + break; + default: + assert(false); + break; + } + return value; +} + +// Returns temperature as localized string +std::string CSpeed::ToString(Unit speedUnit) const +{ + if (!IsValid()) + return ""; + + return StringUtils::Format("{:2.0f}", To(speedUnit)); +} diff --git a/xbmc/utils/Speed.h b/xbmc/utils/Speed.h new file mode 100644 index 0000000..8ad5d05 --- /dev/null +++ b/xbmc/utils/Speed.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/IArchivable.h" + +#include <string> + +class CSpeed : public IArchivable +{ +public: + CSpeed(); + CSpeed(const CSpeed& speed); + + typedef enum Unit + { + UnitKilometresPerHour = 0, + UnitMetresPerMinute, + UnitMetresPerSecond, + UnitFeetPerHour, + UnitFeetPerMinute, + UnitFeetPerSecond, + UnitMilesPerHour, + UnitKnots, + UnitBeaufort, + UnitInchPerSecond, + UnitYardPerSecond, + UnitFurlongPerFortnight + } Unit; + + static CSpeed CreateFromKilometresPerHour(double value); + static CSpeed CreateFromMetresPerMinute(double value); + static CSpeed CreateFromMetresPerSecond(double value); + static CSpeed CreateFromFeetPerHour(double value); + static CSpeed CreateFromFeetPerMinute(double value); + static CSpeed CreateFromFeetPerSecond(double value); + static CSpeed CreateFromMilesPerHour(double value); + static CSpeed CreateFromKnots(double value); + static CSpeed CreateFromBeaufort(unsigned int value); + static CSpeed CreateFromInchPerSecond(double value); + static CSpeed CreateFromYardPerSecond(double value); + static CSpeed CreateFromFurlongPerFortnight(double value); + + bool operator >(const CSpeed& right) const; + bool operator >=(const CSpeed& right) const; + bool operator <(const CSpeed& right) const; + bool operator <=(const CSpeed& right) const; + bool operator ==(const CSpeed& right) const; + bool operator !=(const CSpeed& right) const; + + CSpeed& operator =(const CSpeed& right); + const CSpeed& operator +=(const CSpeed& right); + const CSpeed& operator -=(const CSpeed& right); + const CSpeed& operator *=(const CSpeed& right); + const CSpeed& operator /=(const CSpeed& right); + CSpeed operator +(const CSpeed& right) const; + CSpeed operator -(const CSpeed& right) const; + CSpeed operator *(const CSpeed& right) const; + CSpeed operator /(const CSpeed& right) const; + + bool operator >(double right) const; + bool operator >=(double right) const; + bool operator <(double right) const; + bool operator <=(double right) const; + bool operator ==(double right) const; + bool operator !=(double right) const; + + const CSpeed& operator +=(double right); + const CSpeed& operator -=(double right); + const CSpeed& operator *=(double right); + const CSpeed& operator /=(double right); + CSpeed operator +(double right) const; + CSpeed operator -(double right) const; + CSpeed operator *(double right) const; + CSpeed operator /(double right) const; + + CSpeed& operator ++(); + CSpeed& operator --(); + CSpeed operator ++(int); + CSpeed operator --(int); + + void Archive(CArchive& ar) override; + + bool IsValid() const; + + double ToKilometresPerHour() const; + double ToMetresPerMinute() const; + double ToMetresPerSecond() const; + double ToFeetPerHour() const; + double ToFeetPerMinute() const; + double ToFeetPerSecond() const; + double ToMilesPerHour() const; + double ToKnots() const; + double ToBeaufort() const; + double ToInchPerSecond() const; + double ToYardPerSecond() const; + double ToFurlongPerFortnight() const; + + double To(Unit speedUnit) const; + std::string ToString(Unit speedUnit) const; + +protected: + explicit CSpeed(double value); + + void SetValid(bool valid) { m_valid = valid; } + + double m_value; // we store in m/s + bool m_valid; +}; + diff --git a/xbmc/utils/Stopwatch.h b/xbmc/utils/Stopwatch.h new file mode 100644 index 0000000..9ec2983 --- /dev/null +++ b/xbmc/utils/Stopwatch.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <chrono> +#include <stdint.h> + +class CStopWatch +{ +public: + CStopWatch() = default; + ~CStopWatch() = default; + + /*! + \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 = std::chrono::steady_clock::now(); + 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 = std::chrono::steady_clock::now(); + m_isRunning = false; + } + } + + /*! + \brief Set the start time such that time elapsed is now zero. + */ + void Reset() + { + if (m_isRunning) + m_startTick = std::chrono::steady_clock::now(); + 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 + { + std::chrono::duration<float> elapsed; + + if (m_isRunning) + elapsed = std::chrono::steady_clock::now() - m_startTick; + else + elapsed = m_stopTick - m_startTick; + + return elapsed.count(); + } + + /*! + \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 + { + std::chrono::duration<float, std::milli> elapsed; + + if (m_isRunning) + elapsed = std::chrono::steady_clock::now() - m_startTick; + else + elapsed = m_stopTick - m_startTick; + + return elapsed.count(); + } + +private: + std::chrono::time_point<std::chrono::steady_clock> m_startTick; + std::chrono::time_point<std::chrono::steady_clock> m_stopTick; + bool m_isRunning = false; +}; diff --git a/xbmc/utils/StreamDetails.cpp b/xbmc/utils/StreamDetails.cpp new file mode 100644 index 0000000..90f5b4a --- /dev/null +++ b/xbmc/utils/StreamDetails.cpp @@ -0,0 +1,674 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "StreamDetails.h" + +#include "LangInfo.h" +#include "StreamUtils.h" +#include "utils/Archive.h" +#include "utils/LangCodeExpander.h" +#include "utils/Variant.h" + +#include <math.h> + +const float VIDEOASPECT_EPSILON = 0.025f; + +CStreamDetailVideo::CStreamDetailVideo() : + CStreamDetail(CStreamDetail::VIDEO) +{ +} + +CStreamDetailVideo::CStreamDetailVideo(const VideoStreamInfo &info, int duration) : + CStreamDetail(CStreamDetail::VIDEO), + m_iWidth(info.width), + m_iHeight(info.height), + m_fAspect(info.videoAspectRatio), + m_iDuration(duration), + m_strCodec(info.codecName), + m_strStereoMode(info.stereoMode), + m_strLanguage(info.language), + m_strHdrType(CStreamDetails::HdrTypeToString(info.hdrType)) +{ +} + +void CStreamDetailVideo::Archive(CArchive& ar) +{ + if (ar.IsStoring()) + { + ar << m_strCodec; + ar << m_fAspect; + ar << m_iHeight; + ar << m_iWidth; + ar << m_iDuration; + ar << m_strStereoMode; + ar << m_strLanguage; + ar << m_strHdrType; + } + else + { + ar >> m_strCodec; + ar >> m_fAspect; + ar >> m_iHeight; + ar >> m_iWidth; + ar >> m_iDuration; + ar >> m_strStereoMode; + ar >> m_strLanguage; + ar >> m_strHdrType; + } +} +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; + value["language"] = m_strLanguage; + value["hdrtype"] = m_strHdrType; +} + +bool CStreamDetailVideo::IsWorseThan(const CStreamDetail &that) const +{ + if (that.m_eType != CStreamDetail::VIDEO) + return true; + + // Best video stream is that with the most pixels + const auto& sdv = static_cast<const CStreamDetailVideo&>(that); + return (sdv.m_iWidth * sdv.m_iHeight) > (m_iWidth * m_iHeight); +} + +CStreamDetailAudio::CStreamDetailAudio() : + CStreamDetail(CStreamDetail::AUDIO) +{ +} + +CStreamDetailAudio::CStreamDetailAudio(const AudioStreamInfo &info) : + CStreamDetail(CStreamDetail::AUDIO), + m_iChannels(info.channels), + m_strCodec(info.codecName), + m_strLanguage(info.language) +{ +} + +void CStreamDetailAudio::Archive(CArchive& 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(const CStreamDetail &that) const +{ + if (that.m_eType != CStreamDetail::AUDIO) + return true; + + const auto& sda = static_cast<const 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) +{ +} + +CStreamDetailSubtitle::CStreamDetailSubtitle(const SubtitleStreamInfo &info) : + CStreamDetail(CStreamDetail::SUBTITLE), + m_strLanguage(info.language) +{ +} + +void CStreamDetailSubtitle::Archive(CArchive& ar) +{ + if (ar.IsStoring()) + { + ar << m_strLanguage; + } + else + { + ar >> m_strLanguage; + } +} +void CStreamDetailSubtitle::Serialize(CVariant& value) const +{ + value["language"] = m_strLanguage; +} + +bool CStreamDetailSubtitle::IsWorseThan(const CStreamDetail &that) const +{ + if (that.m_eType != CStreamDetail::SUBTITLE) + return true; + + if (g_LangCodeExpander.CompareISO639Codes(m_strLanguage, static_cast<const 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.CompareISO639Codes(static_cast<const 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(); + for (const auto &iter : that.m_vecItems) + { + switch (iter->m_eType) + { + case CStreamDetail::VIDEO: + AddStream(new CStreamDetailVideo(static_cast<const CStreamDetailVideo&>(*iter))); + break; + case CStreamDetail::AUDIO: + AddStream(new CStreamDetailAudio(static_cast<const CStreamDetailAudio&>(*iter))); + break; + case CStreamDetail::SUBTITLE: + AddStream(new CStreamDetailSubtitle(static_cast<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; +} + +std::string CStreamDetails::GetVideoLanguage(int idx) const +{ + const CStreamDetailVideo* item = + dynamic_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx)); + if (item) + return item->m_strLanguage; + else + return ""; +} + +int CStreamDetails::GetStreamCount(CStreamDetail::StreamType type) const +{ + int retVal = 0; + for (const auto &iter : m_vecItems) + 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) +{ + m_pBestVideo = nullptr; + m_pBestAudio = nullptr; + m_pBestSubtitle = nullptr; + *this = that; +} + +void CStreamDetails::AddStream(CStreamDetail *item) +{ + item->m_pParent = this; + m_vecItems.emplace_back(item); +} + +void CStreamDetails::Reset(void) +{ + m_pBestVideo = nullptr; + m_pBestAudio = nullptr; + m_pBestSubtitle = nullptr; + + 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; + } + } + + for (const auto &iter : m_vecItems) + if (iter->m_eType == type) + { + idx--; + if (idx < 1) + return iter.get(); + } + + return NULL; +} + +std::string CStreamDetails::GetVideoCodec(int idx) const +{ + const CStreamDetailVideo* item = + dynamic_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx)); + if (item) + return item->m_strCodec; + else + return ""; +} + +float CStreamDetails::GetVideoAspect(int idx) const +{ + const CStreamDetailVideo* item = + dynamic_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx)); + if (item) + return item->m_fAspect; + else + return 0.0; +} + +int CStreamDetails::GetVideoWidth(int idx) const +{ + const CStreamDetailVideo* item = + dynamic_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx)); + if (item) + return item->m_iWidth; + else + return 0; +} + +int CStreamDetails::GetVideoHeight(int idx) const +{ + const CStreamDetailVideo* item = + dynamic_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx)); + if (item) + return item->m_iHeight; + else + return 0; +} + +std::string CStreamDetails::GetVideoHdrType( int idx) const +{ + const CStreamDetailVideo* item = + dynamic_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx)); + if (item) + return item->m_strHdrType; + else + return ""; +} + +int CStreamDetails::GetVideoDuration(int idx) const +{ + const CStreamDetailVideo* item = + dynamic_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx)); + if (item) + return item->m_iDuration; + else + return 0; +} + +void CStreamDetails::SetVideoDuration(int idx, const int duration) +{ + CStreamDetailVideo* item = const_cast<CStreamDetailVideo*>( + dynamic_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx))); + if (item) + item->m_iDuration = duration; +} + +std::string CStreamDetails::GetStereoMode(int idx) const +{ + const CStreamDetailVideo* item = + dynamic_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx)); + if (item) + return item->m_strStereoMode; + else + return ""; +} + +std::string CStreamDetails::GetAudioCodec(int idx) const +{ + const CStreamDetailAudio* item = + dynamic_cast<const CStreamDetailAudio*>(GetNthStream(CStreamDetail::AUDIO, idx)); + if (item) + return item->m_strCodec; + else + return ""; +} + +std::string CStreamDetails::GetAudioLanguage(int idx) const +{ + const CStreamDetailAudio* item = + dynamic_cast<const CStreamDetailAudio*>(GetNthStream(CStreamDetail::AUDIO, idx)); + if (item) + return item->m_strLanguage; + else + return ""; +} + +int CStreamDetails::GetAudioChannels(int idx) const +{ + const CStreamDetailAudio* item = + dynamic_cast<const CStreamDetailAudio*>(GetNthStream(CStreamDetail::AUDIO, idx)); + if (item) + return item->m_iChannels; + else + return -1; +} + +std::string CStreamDetails::GetSubtitleLanguage(int idx) const +{ + const CStreamDetailSubtitle* item = + dynamic_cast<const 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(); + + for (auto &iter : m_vecItems) + { + // 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); + + CVariant v; + for (const auto &iter : m_vecItems) + { + 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; + + for (const auto &iter : m_vecItems) + { + const CStreamDetail **champion; + switch (iter->m_eType) + { + case CStreamDetail::VIDEO: + champion = (const CStreamDetail **)&m_pBestVideo; + break; + case CStreamDetail::AUDIO: + champion = (const CStreamDetail **)&m_pBestAudio; + break; + case CStreamDetail::SUBTITLE: + champion = (const CStreamDetail **)&m_pBestSubtitle; + break; + default: + champion = NULL; + } /* switch type */ + + if (!champion) + continue; + + if ((*champion == NULL) || (*champion)->IsWorseThan(*iter)) + *champion = iter.get(); + } /* 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 <= 962) + return "720"; + // 1920x1080 + else if (iWidth <= 1920 && iHeight <= 1440) + return "1080"; + // 4K + else if (iWidth <= 4096 && iHeight <= 3072) + return "4K"; + // 8K + else if (iWidth <= 8192 && iHeight <= 6144) + return "8K"; + 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.0909f) // sqrt(1.00*1.19) + return "1.00"; + else if (fAspect < 1.2581f) // sqrt(1.19*1.33) + return "1.19"; + else 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 < 1.9235f) // sqrt(1.85*2.00) + return "1.85"; + else if (fAspect < 2.0976f) // sqrt(2.00*2.20) + return "2.00"; + 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"; +} + +bool CStreamDetails::SetStreams(const VideoStreamInfo& videoInfo, int videoDuration, const AudioStreamInfo& audioInfo, const SubtitleStreamInfo& subtitleInfo) +{ + if (!videoInfo.valid && !audioInfo.valid && !subtitleInfo.valid) + return false; + Reset(); + if (videoInfo.valid) + AddStream(new CStreamDetailVideo(videoInfo, videoDuration)); + if (audioInfo.valid) + AddStream(new CStreamDetailAudio(audioInfo)); + if (subtitleInfo.valid) + AddStream(new CStreamDetailSubtitle(subtitleInfo)); + DetermineBestStreams(); + return true; +} + +std::string CStreamDetails::HdrTypeToString(StreamHdrType hdrType) +{ + switch (hdrType) + { + case StreamHdrType::HDR_TYPE_DOLBYVISION: + return "dolbyvision"; + case StreamHdrType::HDR_TYPE_HDR10: + return "hdr10"; + case StreamHdrType::HDR_TYPE_HLG: + return "hlg"; + case StreamHdrType::HDR_TYPE_NONE: + default: + return ""; + } +} diff --git a/xbmc/utils/StreamDetails.h b/xbmc/utils/StreamDetails.h new file mode 100644 index 0000000..bc3aacd --- /dev/null +++ b/xbmc/utils/StreamDetails.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "ISerializable.h" +#include "cores/VideoPlayer/Interface/StreamInfo.h" +#include "utils/IArchivable.h" + +#include <memory> +#include <string> +#include <vector> + +class CStreamDetails; +class CVariant; +struct VideoStreamInfo; +struct AudioStreamInfo; +struct SubtitleStreamInfo; + +class CStreamDetail : public IArchivable, public ISerializable +{ +public: + enum StreamType { + VIDEO, + AUDIO, + SUBTITLE + }; + + explicit CStreamDetail(StreamType type) : m_eType(type), m_pParent(NULL) {} + virtual ~CStreamDetail() = default; + virtual bool IsWorseThan(const CStreamDetail &that) const = 0; + + const StreamType m_eType; + +protected: + CStreamDetails *m_pParent; + friend class CStreamDetails; +}; + +class CStreamDetailVideo final : public CStreamDetail +{ +public: + CStreamDetailVideo(); + CStreamDetailVideo(const VideoStreamInfo &info, int duration = 0); + void Archive(CArchive& ar) override; + void Serialize(CVariant& value) const override; + bool IsWorseThan(const CStreamDetail &that) const override; + + int m_iWidth = 0; + int m_iHeight = 0; + float m_fAspect = 0.0; + int m_iDuration = 0; + std::string m_strCodec; + std::string m_strStereoMode; + std::string m_strLanguage; + std::string m_strHdrType; +}; + +class CStreamDetailAudio final : public CStreamDetail +{ +public: + CStreamDetailAudio(); + CStreamDetailAudio(const AudioStreamInfo &info); + void Archive(CArchive& ar) override; + void Serialize(CVariant& value) const override; + bool IsWorseThan(const CStreamDetail &that) const override; + + int m_iChannels = -1; + std::string m_strCodec; + std::string m_strLanguage; +}; + +class CStreamDetailSubtitle final : public CStreamDetail +{ +public: + CStreamDetailSubtitle(); + CStreamDetailSubtitle(const SubtitleStreamInfo &info); + CStreamDetailSubtitle(const CStreamDetailSubtitle&) = default; + CStreamDetailSubtitle& operator=(const CStreamDetailSubtitle &that); + void Archive(CArchive& ar) override; + void Serialize(CVariant& value) const override; + bool IsWorseThan(const CStreamDetail &that) const override; + + std::string m_strLanguage; +}; + +class CStreamDetails final : public IArchivable, public ISerializable +{ +public: + CStreamDetails() { Reset(); } + CStreamDetails(const CStreamDetails &that); + 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; + static std::string HdrTypeToString(StreamHdrType hdrType); + 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; + std::string GetVideoHdrType (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 GetVideoLanguage(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); + + void Archive(CArchive& ar) override; + void Serialize(CVariant& value) const override; + + bool SetStreams(const VideoStreamInfo& videoInfo, int videoDuration, const AudioStreamInfo& audioInfo, const SubtitleStreamInfo& subtitleInfo); +private: + CStreamDetail *NewStream(CStreamDetail::StreamType type); + std::vector<std::unique_ptr<CStreamDetail>> m_vecItems; + const CStreamDetailVideo *m_pBestVideo; + const CStreamDetailAudio *m_pBestAudio; + const CStreamDetailSubtitle *m_pBestSubtitle; +}; diff --git a/xbmc/utils/StreamUtils.cpp b/xbmc/utils/StreamUtils.cpp new file mode 100644 index 0000000..bd34fec --- /dev/null +++ b/xbmc/utils/StreamUtils.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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/xbmc/utils/StreamUtils.h b/xbmc/utils/StreamUtils.h new file mode 100644 index 0000000..3d16e8a --- /dev/null +++ b/xbmc/utils/StreamUtils.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <cstdint> +#include <string> + +static constexpr int MP4_BOX_HEADER_SIZE = 8; + +class StreamUtils +{ +public: + static int GetCodecPriority(const std::string& codec); + + /*! + * \brief Make a FourCC code as unsigned integer value + * \param c1 The first FourCC char + * \param c2 The second FourCC char + * \param c3 The third FourCC char + * \param c4 The fourth FourCC char + * \return The FourCC as unsigned integer value + */ + static constexpr uint32_t MakeFourCC(char c1, char c2, char c3, char c4) + { + return ((static_cast<uint32_t>(c1) << 24) | (static_cast<uint32_t>(c2) << 16) | + (static_cast<uint32_t>(c3) << 8) | (static_cast<uint32_t>(c4))); + } +}; diff --git a/xbmc/utils/StringUtils.cpp b/xbmc/utils/StringUtils.cpp new file mode 100644 index 0000000..7429223 --- /dev/null +++ b/xbmc/utils/StringUtils.cpp @@ -0,0 +1,1900 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +//----------------------------------------------------------------------- +// +// File: StringUtils.cpp +// +// Purpose: ATL split string utility +// Author: Paul J. Weiss +// +// Modified to use J O'Leary's std::string class by kraqh3d +// +//------------------------------------------------------------------------ + +#ifdef HAVE_NEW_CROSSGUID +#include <crossguid/guid.hpp> +#else +#include <guid.h> +#endif + +#if defined(TARGET_ANDROID) +#include <androidjni/JNIThreading.h> +#endif + +#include "CharsetConverter.h" +#include "LangInfo.h" +#include "StringUtils.h" +#include "XBDateTime.h" + +#include <algorithm> +#include <array> +#include <assert.h> +#include <functional> +#include <inttypes.h> +#include <iomanip> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <fstrcmp.h> +#include <memory.h> + +// don't move or std functions end up in PCRE namespace +// clang-format off +#include "utils/RegExp.h" +// clang-format on + +#define FORMAT_BLOCK_SIZE 512 // # of bytes for initial allocation for printf + +namespace +{ +/*! + * \brief Converts a string to a number of a specified type, by using istringstream. + * \param str The string to convert + * \param fallback [OPT] The number to return when the conversion fails + * \return The converted number, otherwise fallback if conversion fails + */ +template<typename T> +T NumberFromSS(std::string_view str, T fallback) noexcept +{ + std::istringstream iss{str.data()}; + T result{fallback}; + iss >> result; + return result; +} +} // unnamed namespace + +static constexpr 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 std::string StringUtils::Empty = ""; + +// 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 constexpr 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 +}; + + +std::string StringUtils::FormatV(const char *fmt, va_list args) +{ + if (!fmt || !fmt[0]) + return ""; + + int size = FORMAT_BLOCK_SIZE; + va_list argCopy; + + while (true) + { + 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 +} + +std::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 (true) + { + 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 (*(const wchar_t*)a < *(const wchar_t*)b) + return -1; + else if (*(const wchar_t*)a > *(const 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; +} + +template<typename Str, typename Fn> +void transformString(const Str& input, Str& output, Fn fn) +{ + std::transform(input.begin(), input.end(), output.begin(), fn); +} + +std::string StringUtils::ToUpper(const std::string& str) +{ + std::string result(str.size(), '\0'); + transformString(str, result, ::toupper); + return result; +} + +std::wstring StringUtils::ToUpper(const std::wstring& str) +{ + std::wstring result(str.size(), '\0'); + transformString(str, result, toupperUnicode); + return result; +} + +void StringUtils::ToUpper(std::string &str) +{ + transformString(str, str, ::toupper); +} + +void StringUtils::ToUpper(std::wstring &str) +{ + transformString(str, str, toupperUnicode); +} + +std::string StringUtils::ToLower(const std::string& str) +{ + std::string result(str.size(), '\0'); + transformString(str, result, ::tolower); + return result; +} + +std::wstring StringUtils::ToLower(const std::wstring& str) +{ + std::wstring result(str.size(), '\0'); + transformString(str, result, tolowerUnicode); + return result; +} + +void StringUtils::ToLower(std::string &str) +{ + transformString(str, str, ::tolower); +} + +void StringUtils::ToLower(std::wstring &str) +{ + transformString(str, str, tolowerUnicode); +} + +void StringUtils::ToCapitalize(std::string &str) +{ + std::wstring wstr; + g_charsetConverter.utf8ToW(str, wstr); + ToCapitalize(wstr); + g_charsetConverter.wToUTF8(wstr, str); +} + +void StringUtils::ToCapitalize(std::wstring &str) +{ + const std::locale& loc = g_langInfo.GetSystemLocale(); + bool isFirstLetter = true; + for (std::wstring::iterator it = str.begin(); it < str.end(); ++it) + { + // capitalize after spaces and punctuation characters (except apostrophes) + if (std::isspace(*it, loc) || (std::ispunct(*it, loc) && *it != '\'')) + isFirstLetter = true; + else if (isFirstLetter) + { + *it = std::toupper(*it, loc); + isFirstLetter = false; + } + } +} + +bool StringUtils::EqualsNoCase(const std::string &str1, const std::string &str2) +{ + // before we do the char-by-char comparison, first compare sizes of both strings. + // This led to a 33% improvement in benchmarking on average. (size() just returns a member of std::string) + if (str1.size() != str2.size()) + return false; + 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, size_t n /* = 0 */) +{ + return CompareNoCase(str1.c_str(), str2.c_str(), n); +} + +int StringUtils::CompareNoCase(const char* s1, const char* s2, size_t n /* = 0 */) +{ + char c2; // we need only one char outside the loop + size_t index = 0; + do + { + const char c1 = *s1++; // const local variable should help compiler to optimize + c2 = *s2++; + index++; + 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' && + index != n); // At this point, we know c1 == c2, so there's no need to test them both. + return 0; +} + +std::string StringUtils::Left(const std::string &str, size_t count) +{ + count = std::max((size_t)0, std::min(count, str.size())); + return str.substr(0, count); +} + +std::string StringUtils::Mid(const std::string &str, size_t first, size_t count /* = string::npos */) +{ + if (first + count > str.size()) + count = str.size() - first; + + if (first > str.size()) + return std::string(); + + assert(first + count <= str.size()); + + return str.substr(first, count); +} + +std::string StringUtils::Right(const std::string &str, size_t count) +{ + count = std::max((size_t)0, std::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(), + std::find_if(str.begin(), str.end(), [](char s) { return isspace_c(s) == 0; })); + 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(std::find_if(str.rbegin(), str.rend(), [](char s) { return isspace_c(s) == 0; }).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; +} + +int StringUtils::ReturnDigits(const std::string& str) +{ + std::stringstream ss; + for (const auto& character : str) + { + if (isdigit(character)) + ss << character; + } + return atoi(ss.str().c_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(std::string &str, char oldChar, char newChar) +{ + int replacedChars = 0; + for (std::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)) != std::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)) != std::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::vector<std::string> StringUtils::Split(const std::string& input, const std::string& delimiter, unsigned int iMaxStrings) +{ + std::vector<std::string> result; + SplitTo(std::back_inserter(result), input, delimiter, iMaxStrings); + return result; +} + +std::vector<std::string> StringUtils::Split(const std::string& input, const char delimiter, size_t iMaxStrings) +{ + std::vector<std::string> result; + SplitTo(std::back_inserter(result), input, delimiter, iMaxStrings); + return result; +} + +std::vector<std::string> StringUtils::Split(const std::string& input, const std::vector<std::string>& delimiters) +{ + std::vector<std::string> result; + SplitTo(std::back_inserter(result), input, delimiters); + return result; +} + +std::vector<std::string> StringUtils::SplitMulti(const std::vector<std::string>& input, + const std::vector<std::string>& delimiters, + size_t iMaxStrings /* = 0 */) +{ + if (input.empty()) + return std::vector<std::string>(); + + std::vector<std::string> results(input); + + if (delimiters.empty() || (iMaxStrings > 0 && iMaxStrings <= input.size())) + return results; + + std::vector<std::string> strings1; + if (iMaxStrings == 0) + { + for (size_t di = 0; di < delimiters.size(); di++) + { + for (size_t i = 0; i < results.size(); i++) + { + std::vector<std::string> substrings = StringUtils::Split(results[i], delimiters[di]); + for (size_t j = 0; j < substrings.size(); j++) + strings1.push_back(substrings[j]); + } + results = strings1; + strings1.clear(); + } + return results; + } + + // Control the number of strings input is split into, keeping the original strings. + // Note iMaxStrings > input.size() + int64_t iNew = iMaxStrings - results.size(); + for (size_t di = 0; di < delimiters.size(); di++) + { + for (size_t i = 0; i < results.size(); i++) + { + if (iNew > 0) + { + std::vector<std::string> substrings = StringUtils::Split(results[i], delimiters[di], iNew + 1); + iNew = iNew - substrings.size() + 1; + for (size_t j = 0; j < substrings.size(); j++) + strings1.push_back(substrings[j]); + } + else + strings1.push_back(results[i]); + } + results = strings1; + iNew = iMaxStrings - results.size(); + strings1.clear(); + if ((iNew <= 0)) + break; //Stop trying any more delimiters + } + return results; +} + +// returns the number of occurrences of strFind in strInput. +int StringUtils::FindNumber(const std::string& strInput, const std::string &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; +} + +// Plane maps for MySQL utf8_general_ci (now known as utf8mb3_general_ci) collation +// Derived from https://github.com/MariaDB/server/blob/10.5/strings/ctype-utf8.c + +// clang-format off +static const uint16_t plane00[] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, + 0x0060, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x039C, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00C6, 0x0043, 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049, + 0x00D0, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x00D7, 0x00D8, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00DE, 0x0053, + 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00C6, 0x0043, 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049, + 0x00D0, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x00F7, 0x00D8, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00DE, 0x0059 +}; + +static const uint16_t plane01[] = { + 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0043, 0x0043, 0x0043, 0x0043, 0x0043, 0x0043, 0x0043, 0x0043, 0x0044, 0x0044, + 0x0110, 0x0110, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0047, 0x0047, 0x0047, 0x0047, + 0x0047, 0x0047, 0x0047, 0x0047, 0x0048, 0x0048, 0x0126, 0x0126, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, + 0x0049, 0x0049, 0x0132, 0x0132, 0x004A, 0x004A, 0x004B, 0x004B, 0x0138, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x013F, + 0x013F, 0x0141, 0x0141, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x0149, 0x014A, 0x014A, 0x004F, 0x004F, 0x004F, 0x004F, + 0x004F, 0x004F, 0x0152, 0x0152, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, + 0x0053, 0x0053, 0x0054, 0x0054, 0x0054, 0x0054, 0x0166, 0x0166, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, + 0x0055, 0x0055, 0x0055, 0x0055, 0x0057, 0x0057, 0x0059, 0x0059, 0x0059, 0x005A, 0x005A, 0x005A, 0x005A, 0x005A, 0x005A, 0x0053, + 0x0180, 0x0181, 0x0182, 0x0182, 0x0184, 0x0184, 0x0186, 0x0187, 0x0187, 0x0189, 0x018A, 0x018B, 0x018B, 0x018D, 0x018E, 0x018F, + 0x0190, 0x0191, 0x0191, 0x0193, 0x0194, 0x01F6, 0x0196, 0x0197, 0x0198, 0x0198, 0x019A, 0x019B, 0x019C, 0x019D, 0x019E, 0x019F, + 0x004F, 0x004F, 0x01A2, 0x01A2, 0x01A4, 0x01A4, 0x01A6, 0x01A7, 0x01A7, 0x01A9, 0x01AA, 0x01AB, 0x01AC, 0x01AC, 0x01AE, 0x0055, + 0x0055, 0x01B1, 0x01B2, 0x01B3, 0x01B3, 0x01B5, 0x01B5, 0x01B7, 0x01B8, 0x01B8, 0x01BA, 0x01BB, 0x01BC, 0x01BC, 0x01BE, 0x01F7, + 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x01C4, 0x01C4, 0x01C4, 0x01C7, 0x01C7, 0x01C7, 0x01CA, 0x01CA, 0x01CA, 0x0041, 0x0041, 0x0049, + 0x0049, 0x004F, 0x004F, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x018E, 0x0041, 0x0041, + 0x0041, 0x0041, 0x00C6, 0x00C6, 0x01E4, 0x01E4, 0x0047, 0x0047, 0x004B, 0x004B, 0x004F, 0x004F, 0x004F, 0x004F, 0x01B7, 0x01B7, + 0x004A, 0x01F1, 0x01F1, 0x01F1, 0x0047, 0x0047, 0x01F6, 0x01F7, 0x004E, 0x004E, 0x0041, 0x0041, 0x00C6, 0x00C6, 0x00D8, 0x00D8 +}; + +static const uint16_t plane02[] = { + 0x0041, 0x0041, 0x0041, 0x0041, 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049, 0x004F, 0x004F, 0x004F, 0x004F, + 0x0052, 0x0052, 0x0052, 0x0052, 0x0055, 0x0055, 0x0055, 0x0055, 0x0053, 0x0053, 0x0054, 0x0054, 0x021C, 0x021C, 0x0048, 0x0048, + 0x0220, 0x0221, 0x0222, 0x0222, 0x0224, 0x0224, 0x0041, 0x0041, 0x0045, 0x0045, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, + 0x004F, 0x004F, 0x0059, 0x0059, 0x0234, 0x0235, 0x0236, 0x0237, 0x0238, 0x0239, 0x023A, 0x023B, 0x023C, 0x023D, 0x023E, 0x023F, + 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024A, 0x024B, 0x024C, 0x024D, 0x024E, 0x024F, + 0x0250, 0x0251, 0x0252, 0x0181, 0x0186, 0x0255, 0x0189, 0x018A, 0x0258, 0x018F, 0x025A, 0x0190, 0x025C, 0x025D, 0x025E, 0x025F, + 0x0193, 0x0261, 0x0262, 0x0194, 0x0264, 0x0265, 0x0266, 0x0267, 0x0197, 0x0196, 0x026A, 0x026B, 0x026C, 0x026D, 0x026E, 0x019C, + 0x0270, 0x0271, 0x019D, 0x0273, 0x0274, 0x019F, 0x0276, 0x0277, 0x0278, 0x0279, 0x027A, 0x027B, 0x027C, 0x027D, 0x027E, 0x027F, + 0x01A6, 0x0281, 0x0282, 0x01A9, 0x0284, 0x0285, 0x0286, 0x0287, 0x01AE, 0x0289, 0x01B1, 0x01B2, 0x028C, 0x028D, 0x028E, 0x028F, + 0x0290, 0x0291, 0x01B7, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297, 0x0298, 0x0299, 0x029A, 0x029B, 0x029C, 0x029D, 0x029E, 0x029F, + 0x02A0, 0x02A1, 0x02A2, 0x02A3, 0x02A4, 0x02A5, 0x02A6, 0x02A7, 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC, 0x02AD, 0x02AE, 0x02AF, + 0x02B0, 0x02B1, 0x02B2, 0x02B3, 0x02B4, 0x02B5, 0x02B6, 0x02B7, 0x02B8, 0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF, + 0x02C0, 0x02C1, 0x02C2, 0x02C3, 0x02C4, 0x02C5, 0x02C6, 0x02C7, 0x02C8, 0x02C9, 0x02CA, 0x02CB, 0x02CC, 0x02CD, 0x02CE, 0x02CF, + 0x02D0, 0x02D1, 0x02D2, 0x02D3, 0x02D4, 0x02D5, 0x02D6, 0x02D7, 0x02D8, 0x02D9, 0x02DA, 0x02DB, 0x02DC, 0x02DD, 0x02DE, 0x02DF, + 0x02E0, 0x02E1, 0x02E2, 0x02E3, 0x02E4, 0x02E5, 0x02E6, 0x02E7, 0x02E8, 0x02E9, 0x02EA, 0x02EB, 0x02EC, 0x02ED, 0x02EE, 0x02EF, + 0x02F0, 0x02F1, 0x02F2, 0x02F3, 0x02F4, 0x02F5, 0x02F6, 0x02F7, 0x02F8, 0x02F9, 0x02FA, 0x02FB, 0x02FC, 0x02FD, 0x02FE, 0x02FF +}; + +static const uint16_t plane03[] = { + 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F, + 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F, + 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F, + 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F, + 0x0340, 0x0341, 0x0342, 0x0343, 0x0344, 0x0399, 0x0346, 0x0347, 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F, + 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F, + 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F, + 0x0370, 0x0371, 0x0372, 0x0373, 0x0374, 0x0375, 0x0376, 0x0377, 0x0378, 0x0379, 0x037A, 0x037B, 0x037C, 0x037D, 0x037E, 0x037F, + 0x0380, 0x0381, 0x0382, 0x0383, 0x0384, 0x0385, 0x0391, 0x0387, 0x0395, 0x0397, 0x0399, 0x038B, 0x039F, 0x038D, 0x03A5, 0x03A9, + 0x0399, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, + 0x03A0, 0x03A1, 0x03A2, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x0399, 0x03A5, 0x0391, 0x0395, 0x0397, 0x0399, + 0x03A5, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, + 0x03A0, 0x03A1, 0x03A3, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x0399, 0x03A5, 0x039F, 0x03A5, 0x03A9, 0x03CF, + 0x0392, 0x0398, 0x03D2, 0x03D2, 0x03D2, 0x03A6, 0x03A0, 0x03D7, 0x03D8, 0x03D9, 0x03DA, 0x03DA, 0x03DC, 0x03DC, 0x03DE, 0x03DE, + 0x03E0, 0x03E0, 0x03E2, 0x03E2, 0x03E4, 0x03E4, 0x03E6, 0x03E6, 0x03E8, 0x03E8, 0x03EA, 0x03EA, 0x03EC, 0x03EC, 0x03EE, 0x03EE, + 0x039A, 0x03A1, 0x03A3, 0x03F3, 0x03F4, 0x03F5, 0x03F6, 0x03F7, 0x03F8, 0x03F9, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF +}; + +static const uint16_t plane04[] = { + 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406, 0x0408, 0x0409, 0x040A, 0x040B, 0x041A, 0x0418, 0x0423, 0x040F, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406, 0x0408, 0x0409, 0x040A, 0x040B, 0x041A, 0x0418, 0x0423, 0x040F, + 0x0460, 0x0460, 0x0462, 0x0462, 0x0464, 0x0464, 0x0466, 0x0466, 0x0468, 0x0468, 0x046A, 0x046A, 0x046C, 0x046C, 0x046E, 0x046E, + 0x0470, 0x0470, 0x0472, 0x0472, 0x0474, 0x0474, 0x0474, 0x0474, 0x0478, 0x0478, 0x047A, 0x047A, 0x047C, 0x047C, 0x047E, 0x047E, + 0x0480, 0x0480, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048C, 0x048E, 0x048E, + 0x0490, 0x0490, 0x0492, 0x0492, 0x0494, 0x0494, 0x0496, 0x0496, 0x0498, 0x0498, 0x049A, 0x049A, 0x049C, 0x049C, 0x049E, 0x049E, + 0x04A0, 0x04A0, 0x04A2, 0x04A2, 0x04A4, 0x04A4, 0x04A6, 0x04A6, 0x04A8, 0x04A8, 0x04AA, 0x04AA, 0x04AC, 0x04AC, 0x04AE, 0x04AE, + 0x04B0, 0x04B0, 0x04B2, 0x04B2, 0x04B4, 0x04B4, 0x04B6, 0x04B6, 0x04B8, 0x04B8, 0x04BA, 0x04BA, 0x04BC, 0x04BC, 0x04BE, 0x04BE, + 0x04C0, 0x0416, 0x0416, 0x04C3, 0x04C3, 0x04C5, 0x04C6, 0x04C7, 0x04C7, 0x04C9, 0x04CA, 0x04CB, 0x04CB, 0x04CD, 0x04CE, 0x04CF, + 0x0410, 0x0410, 0x0410, 0x0410, 0x04D4, 0x04D4, 0x0415, 0x0415, 0x04D8, 0x04D8, 0x04D8, 0x04D8, 0x0416, 0x0416, 0x0417, 0x0417, + 0x04E0, 0x04E0, 0x0418, 0x0418, 0x0418, 0x0418, 0x041E, 0x041E, 0x04E8, 0x04E8, 0x04E8, 0x04E8, 0x042D, 0x042D, 0x0423, 0x0423, + 0x0423, 0x0423, 0x0423, 0x0423, 0x0427, 0x0427, 0x04F6, 0x04F7, 0x042B, 0x042B, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF +}; + +static const uint16_t plane05[] = { + 0x0500, 0x0501, 0x0502, 0x0503, 0x0504, 0x0505, 0x0506, 0x0507, 0x0508, 0x0509, 0x050A, 0x050B, 0x050C, 0x050D, 0x050E, 0x050F, + 0x0510, 0x0511, 0x0512, 0x0513, 0x0514, 0x0515, 0x0516, 0x0517, 0x0518, 0x0519, 0x051A, 0x051B, 0x051C, 0x051D, 0x051E, 0x051F, + 0x0520, 0x0521, 0x0522, 0x0523, 0x0524, 0x0525, 0x0526, 0x0527, 0x0528, 0x0529, 0x052A, 0x052B, 0x052C, 0x052D, 0x052E, 0x052F, + 0x0530, 0x0531, 0x0532, 0x0533, 0x0534, 0x0535, 0x0536, 0x0537, 0x0538, 0x0539, 0x053A, 0x053B, 0x053C, 0x053D, 0x053E, 0x053F, + 0x0540, 0x0541, 0x0542, 0x0543, 0x0544, 0x0545, 0x0546, 0x0547, 0x0548, 0x0549, 0x054A, 0x054B, 0x054C, 0x054D, 0x054E, 0x054F, + 0x0550, 0x0551, 0x0552, 0x0553, 0x0554, 0x0555, 0x0556, 0x0557, 0x0558, 0x0559, 0x055A, 0x055B, 0x055C, 0x055D, 0x055E, 0x055F, + 0x0560, 0x0531, 0x0532, 0x0533, 0x0534, 0x0535, 0x0536, 0x0537, 0x0538, 0x0539, 0x053A, 0x053B, 0x053C, 0x053D, 0x053E, 0x053F, + 0x0540, 0x0541, 0x0542, 0x0543, 0x0544, 0x0545, 0x0546, 0x0547, 0x0548, 0x0549, 0x054A, 0x054B, 0x054C, 0x054D, 0x054E, 0x054F, + 0x0550, 0x0551, 0x0552, 0x0553, 0x0554, 0x0555, 0x0556, 0x0587, 0x0588, 0x0589, 0x058A, 0x058B, 0x058C, 0x058D, 0x058E, 0x058F, + 0x0590, 0x0591, 0x0592, 0x0593, 0x0594, 0x0595, 0x0596, 0x0597, 0x0598, 0x0599, 0x059A, 0x059B, 0x059C, 0x059D, 0x059E, 0x059F, + 0x05A0, 0x05A1, 0x05A2, 0x05A3, 0x05A4, 0x05A5, 0x05A6, 0x05A7, 0x05A8, 0x05A9, 0x05AA, 0x05AB, 0x05AC, 0x05AD, 0x05AE, 0x05AF, + 0x05B0, 0x05B1, 0x05B2, 0x05B3, 0x05B4, 0x05B5, 0x05B6, 0x05B7, 0x05B8, 0x05B9, 0x05BA, 0x05BB, 0x05BC, 0x05BD, 0x05BE, 0x05BF, + 0x05C0, 0x05C1, 0x05C2, 0x05C3, 0x05C4, 0x05C5, 0x05C6, 0x05C7, 0x05C8, 0x05C9, 0x05CA, 0x05CB, 0x05CC, 0x05CD, 0x05CE, 0x05CF, + 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, + 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x05EB, 0x05EC, 0x05ED, 0x05EE, 0x05EF, + 0x05F0, 0x05F1, 0x05F2, 0x05F3, 0x05F4, 0x05F5, 0x05F6, 0x05F7, 0x05F8, 0x05F9, 0x05FA, 0x05FB, 0x05FC, 0x05FD, 0x05FE, 0x05FF +}; + +static const uint16_t plane1E[] = { + 0x0041, 0x0041, 0x0042, 0x0042, 0x0042, 0x0042, 0x0042, 0x0042, 0x0043, 0x0043, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, + 0x0044, 0x0044, 0x0044, 0x0044, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0046, 0x0046, + 0x0047, 0x0047, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0049, 0x0049, 0x0049, 0x0049, + 0x004B, 0x004B, 0x004B, 0x004B, 0x004B, 0x004B, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x004D, 0x004D, + 0x004D, 0x004D, 0x004D, 0x004D, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, + 0x004F, 0x004F, 0x004F, 0x004F, 0x0050, 0x0050, 0x0050, 0x0050, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, + 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0054, 0x0054, 0x0054, 0x0054, 0x0054, 0x0054, + 0x0054, 0x0054, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0056, 0x0056, 0x0056, 0x0056, + 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0058, 0x0058, 0x0058, 0x0058, 0x0059, 0x0059, + 0x005A, 0x005A, 0x005A, 0x005A, 0x005A, 0x005A, 0x0048, 0x0054, 0x0057, 0x0059, 0x1E9A, 0x0053, 0x1E9C, 0x1E9D, 0x1E9E, 0x1E9F, + 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, + 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, + 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049, 0x004F, 0x004F, 0x004F, 0x004F, + 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, + 0x004F, 0x004F, 0x004F, 0x004F, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, + 0x0055, 0x0055, 0x0059, 0x0059, 0x0059, 0x0059, 0x0059, 0x0059, 0x0059, 0x0059, 0x1EFA, 0x1EFB, 0x1EFC, 0x1EFD, 0x1EFE, 0x1EFF +}; + +static const uint16_t plane1F[] = { + 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, + 0x0395, 0x0395, 0x0395, 0x0395, 0x0395, 0x0395, 0x1F16, 0x1F17, 0x0395, 0x0395, 0x0395, 0x0395, 0x0395, 0x0395, 0x1F1E, 0x1F1F, + 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, + 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, + 0x039F, 0x039F, 0x039F, 0x039F, 0x039F, 0x039F, 0x1F46, 0x1F47, 0x039F, 0x039F, 0x039F, 0x039F, 0x039F, 0x039F, 0x1F4E, 0x1F4F, + 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x1F58, 0x03A5, 0x1F5A, 0x03A5, 0x1F5C, 0x03A5, 0x1F5E, 0x03A5, + 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, + 0x0391, 0x1FBB, 0x0395, 0x1FC9, 0x0397, 0x1FCB, 0x0399, 0x1FDB, 0x039F, 0x1FF9, 0x03A5, 0x1FEB, 0x03A9, 0x1FFB, 0x1F7E, 0x1F7F, + 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, + 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, + 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, + 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x1FB5, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x1FBB, 0x0391, 0x1FBD, 0x0399, 0x1FBF, + 0x1FC0, 0x1FC1, 0x0397, 0x0397, 0x0397, 0x1FC5, 0x0397, 0x0397, 0x0395, 0x1FC9, 0x0397, 0x1FCB, 0x0397, 0x1FCD, 0x1FCE, 0x1FCF, + 0x0399, 0x0399, 0x0399, 0x1FD3, 0x1FD4, 0x1FD5, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x1FDB, 0x1FDC, 0x1FDD, 0x1FDE, 0x1FDF, + 0x03A5, 0x03A5, 0x03A5, 0x1FE3, 0x03A1, 0x03A1, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x1FEB, 0x03A1, 0x1FED, 0x1FEE, 0x1FEF, + 0x1FF0, 0x1FF1, 0x03A9, 0x03A9, 0x03A9, 0x1FF5, 0x03A9, 0x03A9, 0x039F, 0x1FF9, 0x03A9, 0x1FFB, 0x03A9, 0x1FFD, 0x1FFE, 0x1FFF +}; + +static const uint16_t plane21[] = { + 0x2100, 0x2101, 0x2102, 0x2103, 0x2104, 0x2105, 0x2106, 0x2107, 0x2108, 0x2109, 0x210A, 0x210B, 0x210C, 0x210D, 0x210E, 0x210F, + 0x2110, 0x2111, 0x2112, 0x2113, 0x2114, 0x2115, 0x2116, 0x2117, 0x2118, 0x2119, 0x211A, 0x211B, 0x211C, 0x211D, 0x211E, 0x211F, + 0x2120, 0x2121, 0x2122, 0x2123, 0x2124, 0x2125, 0x2126, 0x2127, 0x2128, 0x2129, 0x212A, 0x212B, 0x212C, 0x212D, 0x212E, 0x212F, + 0x2130, 0x2131, 0x2132, 0x2133, 0x2134, 0x2135, 0x2136, 0x2137, 0x2138, 0x2139, 0x213A, 0x213B, 0x213C, 0x213D, 0x213E, 0x213F, + 0x2140, 0x2141, 0x2142, 0x2143, 0x2144, 0x2145, 0x2146, 0x2147, 0x2148, 0x2149, 0x214A, 0x214B, 0x214C, 0x214D, 0x214E, 0x214F, + 0x2150, 0x2151, 0x2152, 0x2153, 0x2154, 0x2155, 0x2156, 0x2157, 0x2158, 0x2159, 0x215A, 0x215B, 0x215C, 0x215D, 0x215E, 0x215F, + 0x2160, 0x2161, 0x2162, 0x2163, 0x2164, 0x2165, 0x2166, 0x2167, 0x2168, 0x2169, 0x216A, 0x216B, 0x216C, 0x216D, 0x216E, 0x216F, + 0x2160, 0x2161, 0x2162, 0x2163, 0x2164, 0x2165, 0x2166, 0x2167, 0x2168, 0x2169, 0x216A, 0x216B, 0x216C, 0x216D, 0x216E, 0x216F, + 0x2180, 0x2181, 0x2182, 0x2183, 0x2184, 0x2185, 0x2186, 0x2187, 0x2188, 0x2189, 0x218A, 0x218B, 0x218C, 0x218D, 0x218E, 0x218F, + 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199, 0x219A, 0x219B, 0x219C, 0x219D, 0x219E, 0x219F, + 0x21A0, 0x21A1, 0x21A2, 0x21A3, 0x21A4, 0x21A5, 0x21A6, 0x21A7, 0x21A8, 0x21A9, 0x21AA, 0x21AB, 0x21AC, 0x21AD, 0x21AE, 0x21AF, + 0x21B0, 0x21B1, 0x21B2, 0x21B3, 0x21B4, 0x21B5, 0x21B6, 0x21B7, 0x21B8, 0x21B9, 0x21BA, 0x21BB, 0x21BC, 0x21BD, 0x21BE, 0x21BF, + 0x21C0, 0x21C1, 0x21C2, 0x21C3, 0x21C4, 0x21C5, 0x21C6, 0x21C7, 0x21C8, 0x21C9, 0x21CA, 0x21CB, 0x21CC, 0x21CD, 0x21CE, 0x21CF, + 0x21D0, 0x21D1, 0x21D2, 0x21D3, 0x21D4, 0x21D5, 0x21D6, 0x21D7, 0x21D8, 0x21D9, 0x21DA, 0x21DB, 0x21DC, 0x21DD, 0x21DE, 0x21DF, + 0x21E0, 0x21E1, 0x21E2, 0x21E3, 0x21E4, 0x21E5, 0x21E6, 0x21E7, 0x21E8, 0x21E9, 0x21EA, 0x21EB, 0x21EC, 0x21ED, 0x21EE, 0x21EF, + 0x21F0, 0x21F1, 0x21F2, 0x21F3, 0x21F4, 0x21F5, 0x21F6, 0x21F7, 0x21F8, 0x21F9, 0x21FA, 0x21FB, 0x21FC, 0x21FD, 0x21FE, 0x21FF +}; + +static const uint16_t plane24[] = { + 0x2400, 0x2401, 0x2402, 0x2403, 0x2404, 0x2405, 0x2406, 0x2407, 0x2408, 0x2409, 0x240A, 0x240B, 0x240C, 0x240D, 0x240E, 0x240F, + 0x2410, 0x2411, 0x2412, 0x2413, 0x2414, 0x2415, 0x2416, 0x2417, 0x2418, 0x2419, 0x241A, 0x241B, 0x241C, 0x241D, 0x241E, 0x241F, + 0x2420, 0x2421, 0x2422, 0x2423, 0x2424, 0x2425, 0x2426, 0x2427, 0x2428, 0x2429, 0x242A, 0x242B, 0x242C, 0x242D, 0x242E, 0x242F, + 0x2430, 0x2431, 0x2432, 0x2433, 0x2434, 0x2435, 0x2436, 0x2437, 0x2438, 0x2439, 0x243A, 0x243B, 0x243C, 0x243D, 0x243E, 0x243F, + 0x2440, 0x2441, 0x2442, 0x2443, 0x2444, 0x2445, 0x2446, 0x2447, 0x2448, 0x2449, 0x244A, 0x244B, 0x244C, 0x244D, 0x244E, 0x244F, + 0x2450, 0x2451, 0x2452, 0x2453, 0x2454, 0x2455, 0x2456, 0x2457, 0x2458, 0x2459, 0x245A, 0x245B, 0x245C, 0x245D, 0x245E, 0x245F, + 0x2460, 0x2461, 0x2462, 0x2463, 0x2464, 0x2465, 0x2466, 0x2467, 0x2468, 0x2469, 0x246A, 0x246B, 0x246C, 0x246D, 0x246E, 0x246F, + 0x2470, 0x2471, 0x2472, 0x2473, 0x2474, 0x2475, 0x2476, 0x2477, 0x2478, 0x2479, 0x247A, 0x247B, 0x247C, 0x247D, 0x247E, 0x247F, + 0x2480, 0x2481, 0x2482, 0x2483, 0x2484, 0x2485, 0x2486, 0x2487, 0x2488, 0x2489, 0x248A, 0x248B, 0x248C, 0x248D, 0x248E, 0x248F, + 0x2490, 0x2491, 0x2492, 0x2493, 0x2494, 0x2495, 0x2496, 0x2497, 0x2498, 0x2499, 0x249A, 0x249B, 0x249C, 0x249D, 0x249E, 0x249F, + 0x24A0, 0x24A1, 0x24A2, 0x24A3, 0x24A4, 0x24A5, 0x24A6, 0x24A7, 0x24A8, 0x24A9, 0x24AA, 0x24AB, 0x24AC, 0x24AD, 0x24AE, 0x24AF, + 0x24B0, 0x24B1, 0x24B2, 0x24B3, 0x24B4, 0x24B5, 0x24B6, 0x24B7, 0x24B8, 0x24B9, 0x24BA, 0x24BB, 0x24BC, 0x24BD, 0x24BE, 0x24BF, + 0x24C0, 0x24C1, 0x24C2, 0x24C3, 0x24C4, 0x24C5, 0x24C6, 0x24C7, 0x24C8, 0x24C9, 0x24CA, 0x24CB, 0x24CC, 0x24CD, 0x24CE, 0x24CF, + 0x24B6, 0x24B7, 0x24B8, 0x24B9, 0x24BA, 0x24BB, 0x24BC, 0x24BD, 0x24BE, 0x24BF, 0x24C0, 0x24C1, 0x24C2, 0x24C3, 0x24C4, 0x24C5, + 0x24C6, 0x24C7, 0x24C8, 0x24C9, 0x24CA, 0x24CB, 0x24CC, 0x24CD, 0x24CE, 0x24CF, 0x24EA, 0x24EB, 0x24EC, 0x24ED, 0x24EE, 0x24EF, + 0x24F0, 0x24F1, 0x24F2, 0x24F3, 0x24F4, 0x24F5, 0x24F6, 0x24F7, 0x24F8, 0x24F9, 0x24FA, 0x24FB, 0x24FC, 0x24FD, 0x24FE, 0x24FF +}; + +static const uint16_t planeFF[] = { + 0xFF00, 0xFF01, 0xFF02, 0xFF03, 0xFF04, 0xFF05, 0xFF06, 0xFF07, 0xFF08, 0xFF09, 0xFF0A, 0xFF0B, 0xFF0C, 0xFF0D, 0xFF0E, 0xFF0F, + 0xFF10, 0xFF11, 0xFF12, 0xFF13, 0xFF14, 0xFF15, 0xFF16, 0xFF17, 0xFF18, 0xFF19, 0xFF1A, 0xFF1B, 0xFF1C, 0xFF1D, 0xFF1E, 0xFF1F, + 0xFF20, 0xFF21, 0xFF22, 0xFF23, 0xFF24, 0xFF25, 0xFF26, 0xFF27, 0xFF28, 0xFF29, 0xFF2A, 0xFF2B, 0xFF2C, 0xFF2D, 0xFF2E, 0xFF2F, + 0xFF30, 0xFF31, 0xFF32, 0xFF33, 0xFF34, 0xFF35, 0xFF36, 0xFF37, 0xFF38, 0xFF39, 0xFF3A, 0xFF3B, 0xFF3C, 0xFF3D, 0xFF3E, 0xFF3F, + 0xFF40, 0xFF21, 0xFF22, 0xFF23, 0xFF24, 0xFF25, 0xFF26, 0xFF27, 0xFF28, 0xFF29, 0xFF2A, 0xFF2B, 0xFF2C, 0xFF2D, 0xFF2E, 0xFF2F, + 0xFF30, 0xFF31, 0xFF32, 0xFF33, 0xFF34, 0xFF35, 0xFF36, 0xFF37, 0xFF38, 0xFF39, 0xFF3A, 0xFF5B, 0xFF5C, 0xFF5D, 0xFF5E, 0xFF5F, + 0xFF60, 0xFF61, 0xFF62, 0xFF63, 0xFF64, 0xFF65, 0xFF66, 0xFF67, 0xFF68, 0xFF69, 0xFF6A, 0xFF6B, 0xFF6C, 0xFF6D, 0xFF6E, 0xFF6F, + 0xFF70, 0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF76, 0xFF77, 0xFF78, 0xFF79, 0xFF7A, 0xFF7B, 0xFF7C, 0xFF7D, 0xFF7E, 0xFF7F, + 0xFF80, 0xFF81, 0xFF82, 0xFF83, 0xFF84, 0xFF85, 0xFF86, 0xFF87, 0xFF88, 0xFF89, 0xFF8A, 0xFF8B, 0xFF8C, 0xFF8D, 0xFF8E, 0xFF8F, + 0xFF90, 0xFF91, 0xFF92, 0xFF93, 0xFF94, 0xFF95, 0xFF96, 0xFF97, 0xFF98, 0xFF99, 0xFF9A, 0xFF9B, 0xFF9C, 0xFF9D, 0xFF9E, 0xFF9F, + 0xFFA0, 0xFFA1, 0xFFA2, 0xFFA3, 0xFFA4, 0xFFA5, 0xFFA6, 0xFFA7, 0xFFA8, 0xFFA9, 0xFFAA, 0xFFAB, 0xFFAC, 0xFFAD, 0xFFAE, 0xFFAF, + 0xFFB0, 0xFFB1, 0xFFB2, 0xFFB3, 0xFFB4, 0xFFB5, 0xFFB6, 0xFFB7, 0xFFB8, 0xFFB9, 0xFFBA, 0xFFBB, 0xFFBC, 0xFFBD, 0xFFBE, 0xFFBF, + 0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC4, 0xFFC5, 0xFFC6, 0xFFC7, 0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF, + 0xFFD0, 0xFFD1, 0xFFD2, 0xFFD3, 0xFFD4, 0xFFD5, 0xFFD6, 0xFFD7, 0xFFD8, 0xFFD9, 0xFFDA, 0xFFDB, 0xFFDC, 0xFFDD, 0xFFDE, 0xFFDF, + 0xFFE0, 0xFFE1, 0xFFE2, 0xFFE3, 0xFFE4, 0xFFE5, 0xFFE6, 0xFFE7, 0xFFE8, 0xFFE9, 0xFFEA, 0xFFEB, 0xFFEC, 0xFFED, 0xFFEE, 0xFFEF, + 0xFFF0, 0xFFF1, 0xFFF2, 0xFFF3, 0xFFF4, 0xFFF5, 0xFFF6, 0xFFF7, 0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF +}; + +static const uint16_t* const planemap[256] = { + plane00, plane01, plane02, plane03, plane04, plane05, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, plane1E, plane1F, NULL, + plane21, NULL, NULL, plane24, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, planeFF +}; +// clang-format on + +static wchar_t GetCollationWeight(const wchar_t& r) +{ + // Lookup the "weight" of a UTF8 char, equivalent lowercase ascii letter, in the plane map, + // the character comparison value used by using "accent folding" collation utf8_general_ci + // in MySQL (AKA utf8mb3_general_ci in MariaDB 10) + auto index = r >> 8; + if (index > 255) + return 0xFFFD; + auto plane = planemap[index]; + if (plane == nullptr) + return r; + return static_cast<wchar_t>(plane[r & 0xFF]); +} + +// Compares separately the numeric and alphabetic parts of a wide string. +// returns negative if left < right, positive if left > right +// and 0 if they are identical. +// See also the equivalent StringUtils::AlphaNumericCollation() for UFT8 data +int64_t StringUtils::AlphaNumericCompare(const wchar_t* left, const wchar_t* right) +{ + const wchar_t *l = left; + const wchar_t *r = right; + const wchar_t *ld, *rd; + wchar_t lc, rc; + int64_t lnum, rnum; + bool lsym, rsym; + 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 = *ld++ - L'0'; + while (*ld >= L'0' && *ld <= L'9' && ld < l + 15) + { // compare only up to 15 digits + lnum *= 10; + lnum += *ld++ - L'0'; + } + rd = r; + rnum = *rd++ - L'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; + } + + lc = *l; + rc = *r; + // Put ascii punctuation and symbols e.g. !#$&()*+,-./:;<=>?@[\]^_ `{|}~ above the other + // alphanumeric ascii, rather than some being mixed between the numbers and letters, and + // above all other unicode letters, symbols and punctuation. + // (Locale collation of these chars varies across platforms) + lsym = (lc >= 32 && lc < L'0') || (lc > L'9' && lc < L'A') || (lc > L'Z' && lc < L'a') || + (lc > L'z' && lc < 128); + rsym = (rc >= 32 && rc < L'0') || (rc > L'9' && rc < L'A') || (rc > L'Z' && rc < L'a') || + (rc > L'z' && rc < 128); + if (lsym && !rsym) + return -1; + if (!lsym && rsym) + return 1; + if (lsym && rsym) + { + if (lc != rc) + return static_cast<int64_t>(lc) - static_cast<int64_t>(rc); + else + { // Same symbol advance to next wchar + l++; + r++; + continue; + } + } + if (!g_langInfo.UseLocaleCollation()) + { + // Apply case sensitive accent folding collation to non-ascii chars. + // This mimics utf8_general_ci collation, and provides simple collation of LATIN-1 chars + // for any platformthat doesn't have a language specific collate facet implemented + if (lc > 128) + lc = GetCollationWeight(lc); + if (rc > 128) + rc = GetCollationWeight(rc); + } + // Do case less comparison, convert ascii upper case to lower case + if (lc >= L'A' && lc <= L'Z') + lc += L'a' - L'A'; + if (rc >= L'A' && rc <= L'Z') + rc += L'a' - L'A'; + + if (lc != rc) + { + if (!g_langInfo.UseLocaleCollation()) + { + // Compare unicode (having applied accent folding collation to non-ascii chars). + int i = wcsncmp(&lc, &rc, 1); + return i; + } + else + { + // Fetch collation facet from locale to do comparison of wide char although on some + // platforms this is not language specific but just compares unicode + const std::collate<wchar_t>& coll = + std::use_facet<std::collate<wchar_t>>(g_langInfo.GetSystemLocale()); + int cmp_res = coll.compare(&lc, &lc + 1, &rc, &rc + 1); + if (cmp_res != 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 +} + +/* + Convert the UTF8 character to which z points into a 31-bit Unicode point. + Return how many bytes (0 to 3) of UTF8 data encode the character. + This only works right if z points to a well-formed UTF8 string. + Byte-0 Byte-1 Byte-2 Byte-3 Value + 0xxxxxxx 00000000 00000000 0xxxxxxx + 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx + 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx + 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx +*/ +static uint32_t UTF8ToUnicode(const unsigned char* z, int nKey, unsigned char& bytes) +{ + // Lookup table used decode the first byte of a multi-byte UTF8 character + // clang-format off + static const unsigned char utf8Trans1[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, + }; + // clang-format on + + uint32_t c; + bytes = 0; + c = z[0]; + if (c >= 0xc0) + { + c = utf8Trans1[c - 0xc0]; + int index = 1; + while (index < nKey && (z[index] & 0xc0) == 0x80) + { + c = (c << 6) + (0x3f & z[index]); + index++; + } + if (c < 0x80 || (c & 0xFFFFF800) == 0xD800 || (c & 0xFFFFFFFE) == 0xFFFE) + c = 0xFFFD; + bytes = static_cast<unsigned char>(index - 1); + } + return c; +} + +/* + SQLite collating function, see sqlite3_create_collation + The equivalent of AlphaNumericCompare() but for comparing UTF8 encoded data + + This only processes enough data to find a difference, and avoids expensive data conversions. + When sorting in memory item data is converted once to wstring in advance prior to sorting, the + SQLite callback function can not do that kind of preparation. Instead, in order to use + AlphaNumericCompare(), it would have to repeatedly convert the full input data to wstring for + every pair comparison made. That approach was found to be 10 times slower than using this + separate routine. +*/ +int StringUtils::AlphaNumericCollation(int nKey1, const void* pKey1, int nKey2, const void* pKey2) +{ + // Get exact matches of shorter text to start of larger test fast + int n = std::min(nKey1, nKey2); + int r = memcmp(pKey1, pKey2, n); + if (r == 0) + return nKey1 - nKey2; + + //Not a binary match, so process character at a time + const unsigned char* zA = static_cast<const unsigned char*>(pKey1); + const unsigned char* zB = static_cast<const unsigned char*>(pKey2); + wchar_t lc, rc; + unsigned char bytes; + int64_t lnum, rnum; + bool lsym, rsym; + int ld, rd; + int i = 0; + int j = 0; + // Looping Unicode point at a time through potentially 1 to 4 multi-byte encoded UTF8 data + while (i < nKey1 && j < nKey2) + { + // Check if we have numerical values, compare only up to 15 digits + if (isdigit(zA[i]) && isdigit(zB[j])) + { + lnum = zA[i] - '0'; + ld = i + 1; + while (ld < nKey1 && isdigit(zA[ld]) && ld < i + 15) + { + lnum *= 10; + lnum += zA[ld] - '0'; + ld++; + } + rnum = zB[j] - '0'; + rd = j + 1; + while (rd < nKey2 && isdigit(zB[rd]) && rd < j + 15) + { + rnum *= 10; + rnum += zB[rd] - '0'; + rd++; + } + // do we have numbers? + if (lnum != rnum) + { // yes - and they're different! + return static_cast<int>(lnum - rnum); + } + // Advance to after digits + i = ld; + j = rd; + continue; + } + // Put ascii punctuation and symbols e.g. !#$&()*+,-./:;<=>?@[\]^_ `{|}~ before the other + // alphanumeric ascii, rather than some being mixed between the numbers and letters, and + // above all other unicode letters, symbols and punctuation. + // (Locale collation of these chars varies across platforms) + lsym = (zA[i] >= 32 && zA[i] < '0') || (zA[i] > '9' && zA[i] < 'A') || + (zA[i] > 'Z' && zA[i] < 'a') || (zA[i] > 'z' && zA[i] < 128); + rsym = (zB[j] >= 32 && zB[j] < '0') || (zB[j] > '9' && zB[j] < 'A') || + (zB[j] > 'Z' && zB[j] < 'a') || (zB[j] > 'z' && zB[j] < 128); + if (lsym && !rsym) + return -1; + if (!lsym && rsym) + return 1; + if (lsym && rsym) + { + if (zA[i] != zB[j]) + return static_cast<int>(zA[i]) - static_cast<int>(zB[j]); + else + { // Same symbol advance to next + i++; + j++; + continue; + } + } + //Decode single (1 to 4 bytes) UTF8 character to Unicode + lc = UTF8ToUnicode(&zA[i], nKey1 - i, bytes); + i += bytes; + rc = UTF8ToUnicode(&zB[j], nKey2 - j, bytes); + j += bytes; + if (!g_langInfo.UseLocaleCollation()) + { + // Apply case sensitive accent folding collation to non-ascii chars. + // This mimics utf8_general_ci collation, and provides simple collation of LATIN-1 chars + // for any platform that doesn't have a language specific collate facet implemented + if (lc > 128) + lc = GetCollationWeight(lc); + if (rc > 128) + rc = GetCollationWeight(rc); + } + // Caseless comparison so convert ascii upper case to lower case + if (lc >= 'A' && lc <= 'Z') + lc += 'a' - 'A'; + if (rc >= 'A' && rc <= 'Z') + rc += 'a' - 'A'; + + if (lc != rc) + { + if (!g_langInfo.UseLocaleCollation() || (lc <= 128 && rc <= 128)) + // Compare unicode (having applied accent folding collation to non-ascii chars). + return static_cast<int>(lc) - static_cast<int>(rc); + else + { + // Fetch collation facet from locale to do comparison of wide char although on some + // platforms this is not language specific but just compares unicode + const std::collate<wchar_t>& coll = + std::use_facet<std::collate<wchar_t>>(g_langInfo.GetSystemLocale()); + int cmp_res = coll.compare(&lc, &lc + 1, &rc, &rc + 1); + if (cmp_res != 0) + return cmp_res; + } + } + i++; + j++; + } + // Compared characters of shortest are the same as longest, length determines order + return (nKey1 - nKey2); +} + +int StringUtils::DateStringToYYYYMMDD(const std::string &dateString) +{ + std::vector<std::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; +} + +std::string StringUtils::ISODateToLocalizedDate(const std::string& strIsoDate) +{ + // Convert ISO8601 date strings YYYY, YYYY-MM, or YYYY-MM-DD to (partial) localized date strings + CDateTime date; + std::string formattedDate = strIsoDate; + if (formattedDate.size() == 10) + { + date.SetFromDBDate(strIsoDate); + formattedDate = date.GetAsLocalizedDate(); + } + else if (formattedDate.size() == 7) + { + std::string strFormat = date.GetAsLocalizedDate(false); + std::string tempdate; + // find which date separator we are using. Can be -./ + size_t pos = strFormat.find_first_of("-./"); + if (pos != std::string::npos) + { + bool yearFirst = strFormat.find("1601") == 0; // true if year comes first + std::string sep = strFormat.substr(pos, 1); + if (yearFirst) + { // build formatted date with year first, then separator and month + tempdate = formattedDate.substr(0, 4); + tempdate += sep; + tempdate += formattedDate.substr(5, 2); + } + else + { + tempdate = formattedDate.substr(5, 2); + tempdate += sep; + tempdate += formattedDate.substr(0, 4); + } + formattedDate = tempdate; + } + // return either just the year or the locally formatted version of the ISO date + } + return formattedDate; +} + +long StringUtils::TimeStringToSeconds(const std::string &timeString) +{ + std::string strCopy(timeString); + StringUtils::Trim(strCopy); + if(StringUtils::EndsWithNoCase(strCopy, " min")) + { + // this is imdb format of "XXX min" + return 60 * atoi(strCopy.c_str()); + } + else + { + std::vector<std::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; + } +} + +std::string StringUtils::SecondsToTimeString(long lSeconds, TIME_FORMAT format) +{ + bool isNegative = lSeconds < 0; + lSeconds = std::abs(lSeconds); + + std::string strHMS; + if (format == TIME_FORMAT_SECS) + strHMS = std::to_string(lSeconds); + else if (format == TIME_FORMAT_MINS) + strHMS = std::to_string(lrintf(static_cast<float>(lSeconds) / 60.0f)); + else if (format == TIME_FORMAT_HOURS) + strHMS = std::to_string(lrintf(static_cast<float>(lSeconds) / 3600.0f)); + else if (format & TIME_FORMAT_M) + strHMS += std::to_string(lSeconds % 3600 / 60); + else + { + 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; + if (format & TIME_FORMAT_HH) + strHMS += StringUtils::Format("{:02}", hh); + else if (format & TIME_FORMAT_H) + strHMS += std::to_string(hh); + if (format & TIME_FORMAT_MM) + strHMS += StringUtils::Format(strHMS.empty() ? "{:02}" : ":{:02}", mm); + if (format & TIME_FORMAT_SS) + strHMS += StringUtils::Format(strHMS.empty() ? "{:02}" : ":{:02}", ss); + } + + if (isNegative) + strHMS = "-" + strHMS; + + return strHMS; +} + +bool StringUtils::IsNaturalNumber(const std::string& 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 std::string& 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"); +} + +std::string StringUtils::SizeToString(int64_t size) +{ + std::string strLabel; + constexpr std::array<char, 9> prefixes = {' ', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'}; + unsigned int i = 0; + double s = (double)size; + while (i < prefixes.size() && s >= 1000.0) + { + s /= 1024.0; + i++; + } + + if (!i) + strLabel = StringUtils::Format("{:.2f} B", s); + else if (i == prefixes.size()) + { + if (s >= 1000.0) + strLabel = StringUtils::Format(">999.99 {}B", prefixes[i - 1]); + else + strLabel = StringUtils::Format("{:.2f} {}B", s, prefixes[i - 1]); + } + else if (s >= 100.0) + strLabel = StringUtils::Format("{:.1f} {}B", s, prefixes[i]); + else + strLabel = StringUtils::Format("{:.2f} {}B", s, prefixes[i]); + + return strLabel; +} + +std::string StringUtils::BinaryStringToString(const std::string& in) +{ + std::string out; + out.reserve(in.size() / 2); + for (const char *cur = in.c_str(), *end = cur + in.size(); cur != end; ++cur) { + if (*cur == '\\') { + ++cur; + if (cur == end) { + break; + } + if (isdigit(*cur)) { + char* end; + unsigned long num = strtol(cur, &end, 10); + cur = end - 1; + out.push_back(num); + continue; + } + } + out.push_back(*cur); + } + return out; +} + +std::string StringUtils::ToHexadecimal(const std::string& in) +{ + std::ostringstream ss; + ss << std::hex; + for (unsigned char ch : in) { + ss << std::setw(2) << std::setfill('0') << static_cast<unsigned long> (ch); + } + return ss.str(); +} + +// 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! + const unsigned char *s = (const unsigned char *)str; + do + { + // start with a compare + const unsigned char *c = s; + const unsigned char *w = (const 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 std::string::npos; +} + +// assumes it is called from after the first open bracket is found +int StringUtils::FindEndBracket(const std::string &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)std::string::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 + } + } +} + +std::string StringUtils::CreateUUID() +{ +#ifdef HAVE_NEW_CROSSGUID +#ifdef TARGET_ANDROID + JNIEnv* env = xbmc_jnienv(); + return xg::newGuid(env).str(); +#else + return xg::newGuid().str(); +#endif /* TARGET_ANDROID */ +#else + static GuidGenerator guidGenerator; + auto guid = guidGenerator.newGuid(); + + std::stringstream strGuid; strGuid << guid; + return strGuid.str(); +#endif +} + +bool StringUtils::ValidateUUID(const std::string &uuid) +{ + CRegExp guidRE; + guidRE.RegComp(ADDON_GUID_RE); + return (guidRE.RegFind(uuid.c_str()) == 0); +} + +double StringUtils::CompareFuzzy(const std::string &left, const std::string &right) +{ + return (0.5 + fstrcmp(left.c_str(), right.c_str()) * (left.length() + right.length())) / 2.0; +} + +int StringUtils::FindBestMatch(const std::string &str, const std::vector<std::string> &strings, double &matchscore) +{ + int best = -1; + matchscore = 0; + + int i = 0; + for (std::vector<std::string>::const_iterator it = strings.begin(); it != strings.end(); ++it, i++) + { + int maxlength = std::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 std::string &str, const std::vector<std::string> &keywords) +{ + for (std::vector<std::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::string StringUtils::DeParamify(const std::string& param) +{ + std::string result = param; + + // remove double quotes around the whole string + if (StringUtils::StartsWith(result, "\"") && StringUtils::EndsWith(result, "\"")) + { + result.erase(0, 1); + result.pop_back(); + + // unescape double quotes + StringUtils::Replace(result, "\\\"", "\""); + + // unescape backspaces + StringUtils::Replace(result, "\\\\", "\\"); + } + + 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); + } +} + +uint32_t StringUtils::ToUint32(std::string_view str, uint32_t fallback /* = 0 */) noexcept +{ + return NumberFromSS(str, fallback); +} + +uint64_t StringUtils::ToUint64(std::string_view str, uint64_t fallback /* = 0 */) noexcept +{ + return NumberFromSS(str, fallback); +} + +float StringUtils::ToFloat(std::string_view str, float fallback /* = 0.0f */) noexcept +{ + return NumberFromSS(str, fallback); +} + +std::string StringUtils::FormatFileSize(uint64_t bytes) +{ + const std::array<std::string, 6> units{{"B", "kB", "MB", "GB", "TB", "PB"}}; + if (bytes < 1000) + return Format("{}B", bytes); + + size_t i = 0; + double value = static_cast<double>(bytes); + while (i + 1 < units.size() && value >= 999.5) + { + ++i; + value /= 1024.0; + } + unsigned int decimals = value < 9.995 ? 2 : (value < 99.95 ? 1 : 0); + return Format("{:.{}f}{}", value, decimals, units[i]); +} + +const std::locale& StringUtils::GetOriginalLocale() noexcept +{ + return g_langInfo.GetOriginalLocale(); +} + +std::string StringUtils::CreateFromCString(const char* cstr) +{ + return cstr != nullptr ? std::string(cstr) : std::string(); +} diff --git a/xbmc/utils/StringUtils.h b/xbmc/utils/StringUtils.h new file mode 100644 index 0000000..29d9985 --- /dev/null +++ b/xbmc/utils/StringUtils.h @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +//----------------------------------------------------------------------- +// +// File: StringUtils.h +// +// Purpose: ATL split string utility +// Author: Paul J. Weiss +// +// Modified to support J O'Leary's std::string class by kraqh3d +// +//------------------------------------------------------------------------ + +#include <stdarg.h> +#include <stdint.h> +#include <string> +#include <vector> +#include <sstream> +#include <locale> + +// workaround for broken [[deprecated]] in coverity +#if defined(__COVERITY__) +#undef FMT_DEPRECATED +#define FMT_DEPRECATED +#endif +#include "utils/TimeFormat.h" +#include "utils/params_check_macros.h" + +#include <fmt/format.h> +#if FMT_VERSION >= 80000 +#include <fmt/xchar.h> +#endif + +/*! \brief C-processor Token stringification + +The following macros can be used to stringify definitions to +C style strings. + +Example: + +#define foo 4 +DEF_TO_STR_NAME(foo) // outputs "foo" +DEF_TO_STR_VALUE(foo) // outputs "4" + +*/ + +#define DEF_TO_STR_NAME(x) #x +#define DEF_TO_STR_VALUE(x) DEF_TO_STR_NAME(x) + +template<typename T, std::enable_if_t<!std::is_enum<T>::value, int> = 0> +constexpr auto&& EnumToInt(T&& arg) noexcept +{ + return arg; +} +template<typename T, std::enable_if_t<std::is_enum<T>::value, int> = 0> +constexpr auto EnumToInt(T&& arg) noexcept +{ + return static_cast<int>(arg); +} + +class StringUtils +{ +public: + /*! \brief Get a formatted string similar to sprintf + + \param fmt Format of the resulting string + \param ... variable number of value type arguments + \return Formatted string + */ + template<typename... Args> + static std::string Format(const std::string& fmt, Args&&... args) + { + // coverity[fun_call_w_exception : FALSE] + return ::fmt::format(fmt, EnumToInt(std::forward<Args>(args))...); + } + template<typename... Args> + static std::wstring Format(const std::wstring& fmt, Args&&... args) + { + // coverity[fun_call_w_exception : FALSE] + return ::fmt::format(fmt, EnumToInt(std::forward<Args>(args))...); + } + + static std::string FormatV(PRINTF_FORMAT_STRING const char *fmt, va_list args); + static std::wstring FormatV(PRINTF_FORMAT_STRING const wchar_t *fmt, va_list args); + static std::string ToUpper(const std::string& str); + static std::wstring ToUpper(const std::wstring& str); + static void ToUpper(std::string &str); + static void ToUpper(std::wstring &str); + static std::string ToLower(const std::string& str); + static std::wstring ToLower(const std::wstring& str); + static void ToLower(std::string &str); + static void ToLower(std::wstring &str); + static void ToCapitalize(std::string &str); + static void ToCapitalize(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, size_t n = 0); + static int CompareNoCase(const char* s1, const char* s2, size_t n = 0); + static int ReturnDigits(const std::string &str); + 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); + + template<typename CONTAINER> + static std::string Join(const CONTAINER &strings, const std::string& delimiter) + { + std::string result; + for (const auto& str : strings) + result += str + delimiter; + + if (!result.empty()) + result.erase(result.size() - delimiter.size()); + return result; + } + + /*! \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 std::vector<std::string> Split(const std::string& input, const std::vector<std::string> &delimiters); + /*! \brief Splits the given input string using the given delimiter into separate strings. + + If the given input string is empty nothing will be put into the target iterator. + + \param d_first the beginning of the destination range + \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 + \return output iterator to the element in the destination range, one past the last element + * that was put there + */ + template<typename OutputIt> + static OutputIt SplitTo(OutputIt d_first, const std::string& input, const std::string& delimiter, unsigned int iMaxStrings = 0) + { + OutputIt dest = d_first; + + if (input.empty()) + return dest; + if (delimiter.empty()) + { + *d_first++ = input; + return dest; + } + + const size_t delimLen = delimiter.length(); + size_t nextDelim; + size_t textPos = 0; + do + { + if (--iMaxStrings == 0) + { + *dest++ = input.substr(textPos); + break; + } + nextDelim = input.find(delimiter, textPos); + *dest++ = input.substr(textPos, nextDelim - textPos); + textPos = nextDelim + delimLen; + } while (nextDelim != std::string::npos); + + return dest; + } + template<typename OutputIt> + static OutputIt SplitTo(OutputIt d_first, const std::string& input, const char delimiter, size_t iMaxStrings = 0) + { + return SplitTo(d_first, input, std::string(1, delimiter), iMaxStrings); + } + template<typename OutputIt> + static OutputIt SplitTo(OutputIt d_first, const std::string& input, const std::vector<std::string> &delimiters) + { + OutputIt dest = d_first; + if (input.empty()) + return dest; + + if (delimiters.empty()) + { + *dest++ = input; + return dest; + } + std::string str = input; + for (size_t di = 1; di < delimiters.size(); di++) + StringUtils::Replace(str, delimiters[di], delimiters[0]); + return SplitTo(dest, str, delimiters[0]); + } + + /*! \brief Splits the given input strings using the given delimiters into further separate strings. + + If the given input string vector is empty the result will be an empty array (not + an array containing an empty string). + + Delimiter strings are applied in order, so once the (optional) maximum number of + items is produced no other delimiters are applied. This produces different results + to applying all delimiters at once e.g. "a/b#c/d" becomes "a", "b#c", "d" rather + than "a", "b", "c/d" + + \param input Input vector of strings each to be split + \param delimiters Delimiter strings to be used to split the input strings + \param iMaxStrings (optional) Maximum number of resulting split strings + */ + static std::vector<std::string> SplitMulti(const std::vector<std::string>& input, + const std::vector<std::string>& delimiters, + size_t iMaxStrings = 0); + static int FindNumber(const std::string& strInput, const std::string &strFind); + static int64_t AlphaNumericCompare(const wchar_t *left, const wchar_t *right); + static int AlphaNumericCollation(int nKey1, const void* pKey1, int nKey2, const void* pKey2); + static long TimeStringToSeconds(const std::string &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 std::string 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 std::string& 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 std::string& 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 std::string SizeToString(int64_t size); + static const std::string Empty; + static size_t FindWords(const char *str, const char *wordLowerCase); + static int FindEndBracket(const std::string &str, char opener, char closer, int startPos = 0); + static int DateStringToYYYYMMDD(const std::string &dateString); + static std::string ISODateToLocalizedDate (const std::string& strIsoDate); + static void WordToDigits(std::string &word); + static std::string CreateUUID(); + static bool ValidateUUID(const std::string &uuid); // NB only validates syntax + static double CompareFuzzy(const std::string &left, const std::string &right); + static int FindBestMatch(const std::string &str, const std::vector<std::string> &strings, double &matchscore); + static bool ContainsKeyword(const std::string &str, const std::vector<std::string> &keywords); + + /*! \brief Convert the string of binary chars to the actual string. + + Convert the string representation of binary chars to the actual string. + For example \1\2\3 is converted to a string with binary char \1, \2 and \3 + + \param param String to convert + \return Converted string + */ + static std::string BinaryStringToString(const std::string& in); + /** + * Convert each character in the string to its hexadecimal + * representation and return the concatenated result + * + * example: "abc\n" -> "6162630a" + */ + static std::string ToHexadecimal(const std::string& in); + /*! \brief Format the string with locale separators. + + Format the string with locale separators. + For example 10000.57 in en-us is '10,000.57' but in italian is '10.000,57' + + \param param String to format + \return Formatted string + */ + template<typename T> + static std::string FormatNumber(T num) + { + std::stringstream ss; +// ifdef is needed because when you set _ITERATOR_DEBUG_LEVEL=0 and you use custom numpunct you will get runtime error in debug mode +// for more info https://connect.microsoft.com/VisualStudio/feedback/details/2655363 +#if !(defined(_DEBUG) && defined(TARGET_WINDOWS)) + ss.imbue(GetOriginalLocale()); +#endif + ss.precision(1); + ss << std::fixed << num; + return ss.str(); + } + + /*! \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 Unescapes the given string. + + Unescapes backslashes and double-quotes and removes double-quotes around the whole string. + + \param param String to unescape/deparamify + \return Unescaped/Deparamified string + */ + static std::string DeParamify(const std::string& param); + + /*! \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); + + /*! + * \brief Converts a string to a unsigned int number. + * \param str The string to convert + * \param fallback [OPT] The number to return when the conversion fails + * \return The converted number, otherwise fallback if conversion fails + */ + static uint32_t ToUint32(std::string_view str, uint32_t fallback = 0) noexcept; + + /*! + * \brief Converts a string to a unsigned long long number. + * \param str The string to convert + * \param fallback [OPT] The number to return when the conversion fails + * \return The converted number, otherwise fallback if conversion fails + */ + static uint64_t ToUint64(std::string_view str, uint64_t fallback = 0) noexcept; + + /*! + * \brief Converts a string to a float number. + * \param str The string to convert + * \param fallback [OPT] The number to return when the conversion fails + * \return The converted number, otherwise fallback if conversion fails + */ + static float ToFloat(std::string_view str, float fallback = 0.0f) noexcept; + + /*! + * Returns bytes in a human readable format using the smallest unit that will fit `bytes` in at + * most three digits. The number of decimals are adjusted with significance such that 'small' + * numbers will have more decimals than larger ones. + * + * For example: 1024 bytes will be formatted as "1.00kB", 10240 bytes as "10.0kB" and + * 102400 bytes as "100kB". See TestStringUtils for more examples. + */ + static std::string FormatFileSize(uint64_t bytes); + + /*! \brief Converts a cstring pointer (const char*) to a std::string. + In case nullptr is passed the result is an empty string. + \param cstr the const pointer to char + \return the resulting std::string or "" + */ + static std::string CreateFromCString(const char* cstr); + +private: + /*! + * Wrapper for CLangInfo::GetOriginalLocale() which allows us to + * avoid including LangInfo.h from this header. + */ + static const std::locale& GetOriginalLocale() noexcept; +}; + +struct sortstringbyname +{ + bool operator()(const std::string& strItem1, const std::string& strItem2) const + { + return StringUtils::CompareNoCase(strItem1, strItem2) < 0; + } +}; diff --git a/xbmc/utils/StringValidation.cpp b/xbmc/utils/StringValidation.cpp new file mode 100644 index 0000000..386bfb9 --- /dev/null +++ b/xbmc/utils/StringValidation.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "StringValidation.h" + +#include "utils/StringUtils.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/xbmc/utils/StringValidation.h b/xbmc/utils/StringValidation.h new file mode 100644 index 0000000..34d54e8 --- /dev/null +++ b/xbmc/utils/StringValidation.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#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() = default; +}; diff --git a/xbmc/utils/SystemInfo.cpp b/xbmc/utils/SystemInfo.cpp new file mode 100644 index 0000000..e85c415 --- /dev/null +++ b/xbmc/utils/SystemInfo.cpp @@ -0,0 +1,1469 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include <limits.h> + +#include "SystemInfo.h" +#ifndef TARGET_POSIX +#include <conio.h> +#else +#include <sys/utsname.h> +#endif +#include "CompileInfo.h" +#include "ServiceBroker.h" +#include "filesystem/CurlFile.h" +#include "filesystem/File.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/guiinfo/GUIInfoLabels.h" +#include "network/Network.h" +#include "platform/Filesystem.h" +#include "rendering/RenderSystem.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/CPUInfo.h" +#include "utils/log.h" + +#ifdef TARGET_WINDOWS +#include <dwmapi.h> +#include "utils/CharsetConverter.h" +#include <VersionHelpers.h> + +#ifdef TARGET_WINDOWS_STORE +#include <winrt/Windows.Security.ExchangeActiveSyncProvisioning.h> +#include <winrt/Windows.System.Profile.h> + +using namespace winrt::Windows::ApplicationModel; +using namespace winrt::Windows::Security::ExchangeActiveSyncProvisioning; +using namespace winrt::Windows::System; +using namespace winrt::Windows::System::Profile; +#endif +#include <wincrypt.h> +#include "platform/win32/CharsetConverter.h" +#endif +#if defined(TARGET_DARWIN) +#include "platform/darwin/DarwinUtils.h" +#endif +#include "powermanagement/PowerManager.h" +#include "utils/StringUtils.h" +#include "utils/XMLUtils.h" +#if defined(TARGET_ANDROID) +#include <androidjni/Build.h> +#include <androidjni/Context.h> +#include <androidjni/PackageManager.h> +#endif + +/* Platform identification */ +#if defined(TARGET_DARWIN) +#include <Availability.h> +#include <mach-o/arch.h> +#include <sys/sysctl.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 "platform/linux/SysfsPath.h" + +#include <linux/version.h> +#endif + +#include <system_error> + +/* Expand macro before stringify */ +#define STR_MACRO(x) #x +#define XSTR_MACRO(x) STR_MACRO(x) + +namespace +{ +auto startTime = std::chrono::steady_clock::now(); +} + +using namespace XFILE; + +#ifdef TARGET_WINDOWS_DESKTOP +static bool sysGetVersionExWByRef(OSVERSIONINFOEXW& 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; +} + +static bool appendWindows10NameVersion(std::string& osNameVer) +{ + wchar_t versionW[32] = {}; + DWORD len = sizeof(versionW); + bool obtained = false; + if (ERROR_SUCCESS == RegGetValueW(HKEY_LOCAL_MACHINE, REG_CURRENT_VERSION, L"DisplayVersion", + RRF_RT_REG_SZ, nullptr, &versionW, &len)) + { + obtained = true; + } + else if (ERROR_SUCCESS == RegGetValueW(HKEY_LOCAL_MACHINE, REG_CURRENT_VERSION, L"ReleaseId", + RRF_RT_REG_SZ, nullptr, &versionW, &len)) + { + obtained = true; + } + if (obtained) + osNameVer.append(StringUtils::Format(" {}", KODI::PLATFORM::WINDOWS::FromW(versionW))); + + return obtained; +} +#endif // TARGET_WINDOWS_DESKTOP + +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) +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 ""; + } + command += " 2>/dev/null"; + 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 && !TARGET_ANDROID + +CSysInfo g_sysinfo; + +CSysInfoJob::CSysInfoJob() = default; + +bool CSysInfoJob::DoWork() +{ + m_info.systemUptime = GetSystemUpTime(false); + m_info.systemTotalUptime = GetSystemUpTime(true); + m_info.internetState = GetInternetState(); + m_info.videoEncoder = GetVideoEncoder(); + m_info.cpuFrequency = + StringUtils::Format("{:4.0f} MHz", CServiceBroker::GetCPUInfo()->GetCPUFrequency()); + 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; +} + +CSysData::INTERNET_STATE CSysInfoJob::GetInternetState() +{ + // Internet connection state! + XFILE::CCurlFile http; + if (http.IsInternet()) + return CSysData::CONNECTED; + return CSysData::DISCONNECTED; +} + +std::string CSysInfoJob::GetMACAddress() +{ + CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); + if (iface) + return iface->GetMacAddress(); + + return ""; +} + +std::string CSysInfoJob::GetVideoEncoder() +{ + return "GPU: " + CServiceBroker::GetRenderSystem()->GetRenderRenderer(); +} + +std::string CSysInfoJob::GetBatteryLevel() +{ + return StringUtils::Format("{}%", CServiceBroker::GetPowerManager().BatteryLevel()); +} + +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; + + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast<std::chrono::minutes>(now - startTime); + + if(bTotalUptime) + { + //Total Uptime + iInputMinutes = g_sysinfo.GetTotalUptime() + duration.count(); + } + else + { + //Current UpTime + iInputMinutes = duration.count(); + } + + SystemUpTime(iInputMinutes,iMinutes, iHours, iDays); + if (iDays > 0) + { + strSystemUptime = + StringUtils::Format("{} {}, {} {}, {} {}", iDays, g_localizeStrings.Get(12393), iHours, + g_localizeStrings.Get(12392), iMinutes, g_localizeStrings.Get(12391)); + } + else if (iDays == 0 && iHours >= 1 ) + { + strSystemUptime = StringUtils::Format("{} {}, {} {}", iHours, g_localizeStrings.Get(12392), + iMinutes, g_localizeStrings.Get(12391)); + } + else if (iDays == 0 && iHours == 0 && iMinutes >= 0) + { + strSystemUptime = StringUtils::Format("{} {}", iMinutes, g_localizeStrings.Get(12391)); + } + 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() = default; + +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(std::string drive,int& iTotal, int& iTotalFree, int& iTotalUsed, int& iPercentFree, int& iPercentUsed) +{ + using namespace KODI::PLATFORM::FILESYSTEM; + + space_info total = {}; + std::error_code ec; + + // None of this makes sense but the idea of total space + // makes no sense on any system really. + // Return space for / or for C: as it's correct in a sense + // and not much worse than trying to count a total for different + // drives/mounts + if (drive.empty() || drive == "*") + { +#if defined(TARGET_WINDOWS) + drive = "C"; +#elif defined(TARGET_POSIX) + drive = "/"; +#endif + } + +#ifdef TARGET_WINDOWS_DESKTOP + using KODI::PLATFORM::WINDOWS::ToW; + UINT uidriveType = GetDriveType(ToW(drive + ":\\").c_str()); + if (uidriveType != DRIVE_UNKNOWN && uidriveType != DRIVE_NO_ROOT_DIR) + total = space(drive + ":\\", ec); +#elif defined(TARGET_POSIX) + total = space(drive, ec); +#endif + if (ec.value() != 0) + return false; + + iTotal = static_cast<int>(total.capacity / MB); + iTotalFree = static_cast<int>(total.free / MB); + iTotalUsed = iTotal - iTotalFree; + if (total.capacity > 0) + iPercentUsed = static_cast<int>(100.0f * (total.capacity - total.free) / total.capacity + 0.5f); + else + iPercentUsed = 0; + + iPercentFree = 100 - iPercentUsed; + + return true; +} + +std::string CSysInfo::GetKernelName(bool emptyIfUnknown /*= false*/) +{ + static std::string kernelName; + if (kernelName.empty()) + { +#if defined(TARGET_WINDOWS_DESKTOP) + OSVERSIONINFOEXW osvi = {}; + if (sysGetVersionExWByRef(osvi) && osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) + kernelName = "Windows NT"; +#elif defined(TARGET_WINDOWS_STORE) + auto e = EasClientDeviceInformation(); + auto os = e.OperatingSystem(); + g_charsetConverter.wToUTF8(std::wstring(os.c_str()), kernelName); +#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_DESKTOP) + OSVERSIONINFOEXW osvi = {}; + DWORD dwBuildRevision = 0; + DWORD len = sizeof(DWORD); + + if (sysGetVersionExWByRef(osvi)) + kernelVersionFull = StringUtils::Format("{}.{}.{}", osvi.dwMajorVersion, osvi.dwMinorVersion, + osvi.dwBuildNumber); + // get UBR (updates build revision) + if (ERROR_SUCCESS == RegGetValueW(HKEY_LOCAL_MACHINE, REG_CURRENT_VERSION, L"UBR", + RRF_RT_REG_DWORD, nullptr, &dwBuildRevision, &len)) + { + kernelVersionFull += StringUtils::Format(".{}", dwBuildRevision); + } + +#elif defined(TARGET_WINDOWS_STORE) + // get the system version number + auto sv = AnalyticsInfo::VersionInfo().DeviceFamilyVersion(); + wchar_t* end; + unsigned long long v = wcstoull(sv.c_str(), &end, 10); + unsigned long long v1 = (v & 0xFFFF000000000000L) >> 48; + unsigned long long v2 = (v & 0x0000FFFF00000000L) >> 32; + unsigned long long v3 = (v & 0x00000000FFFF0000L) >> 16; + unsigned long long v4 = (v & 0x000000000000FFFFL); + kernelVersionFull = StringUtils::Format("{}.{}.{}", v1, v2, v3); + if (v4) + kernelVersionFull += StringUtils::Format(".{}", v4); + +#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_TVOS) + osName = "tvOS"; +#elif defined(TARGET_DARWIN_OSX) + osName = "macOS"; +#elif defined (TARGET_ANDROID) + if (CJNIContext::GetPackageManager().hasSystemFeature("android.software.leanback")) + osName = "Android TV"; + else + 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) + osVersion = CDarwinUtils::GetVersionString(); +#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_DESKTOP) + OSVERSIONINFOEXW osvi = {}; + + osNameVer = "Windows "; + if (sysGetVersionExWByRef(osvi)) + { + switch (GetWindowsVersion()) + { + 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 WindowsVersionWin10: + case WindowsVersionWin10_1709: + case WindowsVersionWin10_1803: + case WindowsVersionWin10_1809: + case WindowsVersionWin10_1903: + case WindowsVersionWin10_1909: + case WindowsVersionWin10_2004: + case WindowsVersionWin10_Future: + osNameVer.append("10"); + appendWindows10NameVersion(osNameVer); + break; + case WindowsVersionWin11: + osNameVer.append("11"); + appendWindows10NameVersion(osNameVer); + 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{}", osvi.wServicePackMajor)); + if (osvi.wServicePackMinor > 0) + { + osNameVer.append(StringUtils::Format(".{}", osvi.wServicePackMinor)); + } + } + } + else + osNameVer.append(" unknown"); +#elif defined(TARGET_WINDOWS_STORE) + osNameVer = GetKernelName() + " " + GetOsVersion(); +#elif defined(TARGET_FREEBSD) + osNameVer = GetOsName() + " " + GetOsVersion(); +#elif defined(TARGET_DARWIN) + osNameVer = StringUtils::Format("{} {} ({})", GetOsName(), GetOsVersion(), + CDarwinUtils::GetOSVersionString()); +#elif defined(TARGET_ANDROID) + osNameVer = + GetOsName() + " " + GetOsVersion() + " API level " + std::to_string(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_STORE) + auto eas = EasClientDeviceInformation(); + auto manufacturer = eas.SystemManufacturer(); + g_charsetConverter.wToUTF8(std::wstring(manufacturer.c_str()), manufName); +#elif defined(TARGET_LINUX) + + auto cpuInfo = CServiceBroker::GetCPUInfo(); + manufName = cpuInfo->GetCPUSoC(); + +#elif defined(TARGET_WINDOWS) + // We just don't care, might be useful on embedded +#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_EMBEDDED) + modelName = CDarwinUtils::getIosPlatformString(); +#elif defined(TARGET_DARWIN_OSX) + size_t nameLen = 0; // 'nameLen' should include terminating null + if (sysctlbyname("hw.model", NULL, &nameLen, NULL, 0) == 0 && nameLen > 1) + { + std::vector<char> buf(nameLen); + if (sysctlbyname("hw.model", buf.data(), &nameLen, NULL, 0) == 0 && nameLen == buf.size()) + modelName.assign(buf.data(), + nameLen - 1); // assign exactly 'nameLen-1' characters to 'modelName' + } +#elif defined(TARGET_WINDOWS_STORE) + auto eas = EasClientDeviceInformation(); + auto manufacturer = eas.SystemProductName(); + g_charsetConverter.wToUTF8(std::wstring(manufacturer.c_str()), modelName); +#elif defined(TARGET_LINUX) + auto cpuInfo = CServiceBroker::GetCPUInfo(); + modelName = cpuInfo->GetCPUHardware(); +#elif defined(TARGET_WINDOWS) + // We just don't care, might be useful on embedded +#endif + inited = true; + } + + return modelName; +} + +bool CSysInfo::IsAeroDisabled() +{ +#ifdef TARGET_WINDOWS_STORE + return true; // need to review https://msdn.microsoft.com/en-us/library/windows/desktop/aa969518(v=vs.85).aspx +#elif defined(TARGET_WINDOWS) + BOOL aeroEnabled = FALSE; + HRESULT res = DwmIsCompositionEnabled(&aeroEnabled); + if (SUCCEEDED(res)) + return !aeroEnabled; +#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_DESKTOP + if (m_WinVer == WindowsVersionUnknown) + { + OSVERSIONINFOEXW osvi = {}; + if (sysGetVersionExWByRef(osvi)) + { + 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; + else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber < 16299) + m_WinVer = WindowsVersionWin10; + else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 16299) + m_WinVer = WindowsVersionWin10_1709; + else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 17134) + m_WinVer = WindowsVersionWin10_1803; + else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 17763) + m_WinVer = WindowsVersionWin10_1809; + else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 18362) + m_WinVer = WindowsVersionWin10_1903; + else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 18363) + m_WinVer = WindowsVersionWin10_1909; + else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 19041) + m_WinVer = WindowsVersionWin10_2004; + else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= 22000) + m_WinVer = WindowsVersionWin11; + else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber > 19041) + m_WinVer = WindowsVersionWin10_Future; + /* Insert checks for new Windows versions here */ + else if ( (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion > 3) || osvi.dwMajorVersion > 10) + m_WinVer = WindowsVersionFuture; + } + } +#elif defined(TARGET_WINDOWS_STORE) + m_WinVer = WindowsVersionWin10; +#endif // TARGET_WINDOWS + return m_WinVer; +} + +int CSysInfo::GetKernelBitness(void) +{ + static int kernelBitness = -1; + if (kernelBitness == -1) + { +#ifdef TARGET_WINDOWS_STORE + Package package = Package::Current(); + auto arch = package.Id().Architecture(); + switch (arch) + { + case ProcessorArchitecture::X86: + kernelBitness = 32; + break; + case ProcessorArchitecture::X64: + kernelBitness = 64; + break; + case ProcessorArchitecture::Arm: + kernelBitness = 32; + break; + case ProcessorArchitecture::Unknown: // not sure what to do here. guess 32 for now + case ProcessorArchitecture::Neutral: + kernelBitness = 32; + break; + } +#elif defined(TARGET_WINDOWS_DESKTOP) + 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_EMBEDDED) + // 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 == "ppc64el" || machine == "ppc64le" || machine == "ia64" || + machine == "mips64" || machine == "s390x" || machine == "riscv64") + 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"; +#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 || machine.compare(0, 7, "aarch64", 7) == 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, 4, "s390", 4) == 0) + kernelCpuFamily = "s390"; + else if (machine.compare(0, 3, "ppc", 3) == 0 || machine.compare(0, 5, "power", 5) == 0) + kernelCpuFamily = "PowerPC"; + else if (machine.compare(0, 5, "riscv", 5) == 0) + kernelCpuFamily = "RISC-V"; + } +#endif + if (kernelCpuFamily.empty()) + kernelCpuFamily = "unknown CPU family"; + } + return kernelCpuFamily; +} + +int CSysInfo::GetXbmcBitness(void) +{ + return static_cast<int>(sizeof(void*) * 8); +} + +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("{} MB {}", totalFree, g_localizeStrings.Get(160)); + break; + case SYSTEM_USED_SPACE: + strRet = StringUtils::Format("{} MB {}", totalUsed, g_localizeStrings.Get(20162)); + break; + case SYSTEM_TOTAL_SPACE: + strRet = StringUtils::Format("{} MB {}", total, g_localizeStrings.Get(20161)); + break; + case SYSTEM_FREE_SPACE_PERCENT: + strRet = StringUtils::Format("{} % {}", percentFree, g_localizeStrings.Get(160)); + break; + case SYSTEM_USED_SPACE_PERCENT: + strRet = StringUtils::Format("{} % {}", percentused, g_localizeStrings.Get(20162)); + break; + } + } + } + else + { + if (shortText) + strRet = g_localizeStrings.Get(10006); // N/A + else + strRet = g_localizeStrings.Get(10005); // Not available + } + return strRet; +} + +std::string CSysInfo::GetUserAgent() +{ + static std::string result; + if (!result.empty()) + return result; + + result = GetAppName() + "/" + CSysInfo::GetVersionShort() + " ("; +#if defined(TARGET_WINDOWS) + result += GetKernelName() + " " + GetKernelVersion(); +#ifndef TARGET_WINDOWS_STORE + BOOL bIsWow = FALSE; + if (IsWow64Process(GetCurrentProcess(), &bIsWow) && bIsWow) + result.append("; WOW64"); + else +#endif + { + 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_EMBEDDED) + 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 iOSVersion(GetOsVersion()); + size_t lastDotPos = iOSVersion.rfind('.'); + if (lastDotPos != std::string::npos && iOSVersion.find('.') != lastDotPos + && iOSVersion.find_first_not_of('0', lastDotPos + 1) == std::string::npos) + iOSVersion.erase(lastDotPos); + StringUtils::Replace(iOSVersion, '.', '_'); + if (iDev == "AppleTV") + { + // check if it's ATV4 (AppleTV5,3) or later + auto modelMajorNumberEndPos = iDevStr.find_first_of(',', iDevStrDigit); + std::string s{iDevStr, iDevStrDigit, modelMajorNumberEndPos - iDevStrDigit}; + if (stoi(s) >= 5) + result += "CPU TVOS"; + else + result += "CPU OS"; + } + else if (iDev == "iPad") + result += "CPU OS"; + else + result += "CPU iPhone OS "; + result += iOSVersion + " like Mac OS X"; +#else + result += "Macintosh; "; + std::string cpuFam(GetBuildTargetCpuFamily()); + if (cpuFam == "x86") + result += "Intel "; + 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 on 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 + +#if 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/" + std::to_string(GetXbmcBitness()); + + std::string fullVer(CSysInfo::GetVersion()); + StringUtils::Replace(fullVer, ' ', '-'); + result += " Version/" + fullVer; + + return result; +} + +std::string CSysInfo::GetDeviceName() +{ + std::string friendlyName = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SERVICES_DEVICENAME); + if (StringUtils::EqualsNoCase(friendlyName, CCompileInfo::GetAppName())) + { + std::string hostname("[unknown]"); + CServiceBroker::GetNetwork().GetHostName(hostname); + return StringUtils::Format("{} ({})", friendlyName, hostname); + } + + return friendlyName; +} + +// Version string MUST NOT contain spaces. It is used +// in the HTTP request user agent. +std::string CSysInfo::GetVersionShort() +{ + if (strlen(CCompileInfo::GetSuffix()) == 0) + return StringUtils::Format("{}.{}", CCompileInfo::GetMajor(), CCompileInfo::GetMinor()); + else + return StringUtils::Format("{}.{}-{}", CCompileInfo::GetMajor(), CCompileInfo::GetMinor(), + CCompileInfo::GetSuffix()); +} + +std::string CSysInfo::GetVersion() +{ + return GetVersionShort() + " (" + CCompileInfo::GetVersionCode() + ")" + + " Git:" + CCompileInfo::GetSCMID(); +} + +std::string CSysInfo::GetVersionCode() +{ + return CCompileInfo::GetVersionCode(); +} + +std::string CSysInfo::GetVersionGit() +{ + return CCompileInfo::GetSCMID(); +} + +std::string CSysInfo::GetBuildDate() +{ + return CCompileInfo::GetBuildDate(); +} + +std::string CSysInfo::GetBuildTargetPlatformName(void) +{ +#if defined(TARGET_DARWIN_OSX) + return "macOS"; +#elif defined(TARGET_DARWIN_IOS) + return "iOS"; +#elif defined(TARGET_DARWIN_TVOS) + return "tvOS"; +#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_DARWIN_TVOS) + return XSTR_MACRO(__TV_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 (__MAC_OS_X_VERSION_MIN_REQUIRED % 100) + return StringUtils::Format("version {}.{}.{}", __MAC_OS_X_VERSION_MIN_REQUIRED / 10000, + (__MAC_OS_X_VERSION_MIN_REQUIRED / 100) % 100, + __MAC_OS_X_VERSION_MIN_REQUIRED % 100); + else + return StringUtils::Format("version {}.{}", __MAC_OS_X_VERSION_MIN_REQUIRED / 10000, + (__MAC_OS_X_VERSION_MIN_REQUIRED / 100) % 100); +#elif defined(TARGET_DARWIN_EMBEDDED) + std::string versionStr = GetBuildTargetPlatformVersion(); + static const int major = (std::stoi(versionStr) / 10000) % 100; + static const int minor = (std::stoi(versionStr) / 100) % 100; + static const int rev = std::stoi(versionStr) % 100; + return StringUtils::Format("version {}.{}.{}", major, minor, rev); +#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)) + return StringUtils::Format("version {}.{}-RELEASE", major, minor); + if (Rxx >= 500) + return StringUtils::Format("version {}.{}-STABLE", major, minor); + + return StringUtils::Format("version {}.{}-CURRENT", major, minor); +#elif defined(TARGET_ANDROID) + return "API level " XSTR_MACRO(__ANDROID_API__); +#elif defined(TARGET_LINUX) + return StringUtils::Format("version {}.{}.{}", (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 {}.{}", int(NTDDI_VERSION >> 24) & 0xFF, + int(NTDDI_VERSION >> 16) & 0xFF)); + if (SPVER(NTDDI_VERSION)) + version += StringUtils::Format(" SP{}", 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(__s390x__) + return "s390"; +#elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || defined(__ppc__) || defined(__ppc64__) || defined(_M_PPC) + return "PowerPC"; +#elif defined(__riscv) + return "RISC-V"; +#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 +} + +std::string CSysInfo::GetPrivacyPolicy() +{ + if (m_privacyPolicy.empty()) + { + CFile file; + std::vector<uint8_t> buf; + if (file.LoadFile("special://xbmc/privacy-policy.txt", buf) > 0) + { + m_privacyPolicy = std::string(reinterpret_cast<char*>(buf.data()), buf.size()); + } + else + m_privacyPolicy = g_localizeStrings.Get(19055); + } + return m_privacyPolicy; +} + +CSysInfo::WindowsDeviceFamily CSysInfo::GetWindowsDeviceFamily() +{ +#ifdef TARGET_WINDOWS_STORE + auto familyName = AnalyticsInfo::VersionInfo().DeviceFamily(); + if (familyName == L"Windows.Desktop") + return WindowsDeviceFamily::Desktop; + else if (familyName == L"Windows.Mobile") + return WindowsDeviceFamily::Mobile; + else if (familyName == L"Windows.Universal") + return WindowsDeviceFamily::IoT; + else if (familyName == L"Windows.Team") + return WindowsDeviceFamily::Surface; + else if (familyName == L"Windows.Xbox") + return WindowsDeviceFamily::Xbox; + else + return WindowsDeviceFamily::Other; +#endif // TARGET_WINDOWS_STORE + return WindowsDeviceFamily::Desktop; +} + +CJob *CSysInfo::GetJob() const +{ + return new CSysInfoJob(); +} + +void CSysInfo::OnJobComplete(unsigned int jobID, bool success, CJob *job) +{ + m_info = static_cast<CSysInfoJob*>(job)->GetData(); + CInfoLoader::OnJobComplete(jobID, success, job); +} diff --git a/xbmc/utils/SystemInfo.h b/xbmc/utils/SystemInfo.h new file mode 100644 index 0000000..0facf9d --- /dev/null +++ b/xbmc/utils/SystemInfo.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "InfoLoader.h" +#include "settings/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 + +#define REG_CURRENT_VERSION L"Software\\Microsoft\\Windows NT\\CurrentVersion" + + +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(); + + bool DoWork() override; + const CSysData &GetData() const; + + static CSysData::INTERNET_STATE GetInternetState(); +private: + static bool SystemUpTime(int iInputMinutes, int &iMinutes, int &iHours, int &iDays); + static std::string GetSystemUpTime(bool bTotalUptime); + 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 + WindowsVersionWin7, // Windows 7, Windows Server 2008 R2 + WindowsVersionWin8, // Windows 8, Windows Server 2012 + WindowsVersionWin8_1, // Windows 8.1 + WindowsVersionWin10, // Windows 10 + WindowsVersionWin10_1709, // Windows 10 1709 (FCU) + WindowsVersionWin10_1803, // Windows 10 1803 + WindowsVersionWin10_1809, // Windows 10 1809 + WindowsVersionWin10_1903, // Windows 10 1903 + WindowsVersionWin10_1909, // Windows 10 1909 + WindowsVersionWin10_2004, // Windows 10 2004 + WindowsVersionWin10_Future, // Windows 10 future build + WindowsVersionWin11, // Windows 11 + /* Insert new Windows versions here, when they'll be known */ + WindowsVersionFuture = 100 // Future Windows version, not known to code + }; + enum WindowsDeviceFamily + { + Mobile = 1, + Desktop = 2, + IoT = 3, + Xbox = 4, + Surface = 5, + Other = 100 + }; + + CSysInfo(void); + ~CSysInfo() override; + + bool Load(const TiXmlNode *settings) override; + bool Save(TiXmlNode *settings) const override; + + 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(); + static std::string GetDeviceName(); + static std::string GetVersion(); + static std::string GetVersionShort(); + static std::string GetVersionCode(); + static std::string GetVersionGit(); + static std::string GetBuildDate(); + + bool HasInternet(); + bool IsAeroDisabled(); + 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); + static std::string GetManufacturerName(void); + static std::string GetModelName(void); + bool GetDiskSpace(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); + std::string GetPrivacyPolicy(); + + static WindowsDeviceFamily GetWindowsDeviceFamily(); + +protected: + CJob *GetJob() const override; + std::string TranslateInfo(int info) const override; + void OnJobComplete(unsigned int jobID, bool success, CJob *job) override; + +private: + CSysData m_info; + std::string m_privacyPolicy; + static WindowsVersion m_WinVer; + int m_iSystemTimeTotalUp; // Uptime in minutes! + void Reset(); +}; + +extern CSysInfo g_sysinfo; + diff --git a/xbmc/utils/Temperature.cpp b/xbmc/utils/Temperature.cpp new file mode 100644 index 0000000..15aad3a --- /dev/null +++ b/xbmc/utils/Temperature.cpp @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Temperature.h" + +#include "utils/Archive.h" +#include "utils/StringUtils.h" + +#include <assert.h> + +CTemperature::CTemperature() +{ + m_value = 0.0; + m_valid=false; +} + +CTemperature::CTemperature(const CTemperature& temperature) +{ + m_value=temperature.m_value; + m_valid=temperature.m_valid; +} + +CTemperature::CTemperature(double value) +{ + m_value=value; + m_valid=true; +} + +bool CTemperature::operator >(const CTemperature& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + if (!IsValid() || !right.IsValid()) + return false; + + if (this==&right) + return false; + + return (m_value>right.m_value); +} + +bool CTemperature::operator >=(const CTemperature& right) const +{ + return operator >(right) || operator ==(right); +} + +bool CTemperature::operator <(const CTemperature& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + if (!IsValid() || !right.IsValid()) + return false; + + if (this==&right) + return false; + + return (m_value<right.m_value); +} + +bool CTemperature::operator <=(const CTemperature& right) const +{ + return operator <(right) || operator ==(right); +} + +bool CTemperature::operator ==(const CTemperature& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + if (!IsValid() || !right.IsValid()) + return false; + + if (this==&right) + return true; + + return (m_value==right.m_value); +} + +bool CTemperature::operator !=(const CTemperature& right) const +{ + return !operator ==(right.m_value); +} + +CTemperature& CTemperature::operator =(const CTemperature& right) +{ + m_valid=right.m_valid; + m_value=right.m_value; + return *this; +} + +const CTemperature& CTemperature::operator +=(const CTemperature& right) +{ + assert(IsValid()); + assert(right.IsValid()); + + m_value+=right.m_value; + return *this; +} + +const CTemperature& CTemperature::operator -=(const CTemperature& right) +{ + assert(IsValid()); + assert(right.IsValid()); + + m_value-=right.m_value; + return *this; +} + +const CTemperature& CTemperature::operator *=(const CTemperature& right) +{ + assert(IsValid()); + assert(right.IsValid()); + + m_value*=right.m_value; + return *this; +} + +const CTemperature& CTemperature::operator /=(const CTemperature& right) +{ + assert(IsValid()); + assert(right.IsValid()); + + m_value/=right.m_value; + return *this; +} + +CTemperature CTemperature::operator +(const CTemperature& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + CTemperature temp(*this); + + if (!IsValid() || !right.IsValid()) + temp.SetValid(false); + else + temp.m_value+=right.m_value; + + return temp; +} + +CTemperature CTemperature::operator -(const CTemperature& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + CTemperature temp(*this); + if (!IsValid() || !right.IsValid()) + temp.SetValid(false); + else + temp.m_value-=right.m_value; + + return temp; +} + +CTemperature CTemperature::operator *(const CTemperature& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + CTemperature temp(*this); + if (!IsValid() || !right.IsValid()) + temp.SetValid(false); + else + temp.m_value*=right.m_value; + return temp; +} + +CTemperature CTemperature::operator /(const CTemperature& right) const +{ + assert(IsValid()); + assert(right.IsValid()); + + CTemperature temp(*this); + if (!IsValid() || !right.IsValid()) + temp.SetValid(false); + else + temp.m_value/=right.m_value; + return temp; +} + +CTemperature& CTemperature::operator ++() +{ + assert(IsValid()); + + m_value++; + return *this; +} + +CTemperature& CTemperature::operator --() +{ + assert(IsValid()); + + m_value--; + return *this; +} + +CTemperature CTemperature::operator ++(int) +{ + assert(IsValid()); + + CTemperature temp(*this); + m_value++; + return temp; +} + +CTemperature CTemperature::operator --(int) +{ + assert(IsValid()); + + CTemperature temp(*this); + m_value--; + return temp; +} + +bool CTemperature::operator >(double right) const +{ + assert(IsValid()); + + if (!IsValid()) + return false; + + return (m_value>right); +} + +bool CTemperature::operator >=(double right) const +{ + return operator >(right) || operator ==(right); +} + +bool CTemperature::operator <(double right) const +{ + assert(IsValid()); + + if (!IsValid()) + return false; + + return (m_value<right); +} + +bool CTemperature::operator <=(double right) const +{ + return operator <(right) || operator ==(right); +} + +bool CTemperature::operator ==(double right) const +{ + if (!IsValid()) + return false; + + return (m_value==right); +} + +bool CTemperature::operator !=(double right) const +{ + return !operator ==(right); +} + +const CTemperature& CTemperature::operator +=(double right) +{ + assert(IsValid()); + + m_value+=right; + return *this; +} + +const CTemperature& CTemperature::operator -=(double right) +{ + assert(IsValid()); + + m_value-=right; + return *this; +} + +const CTemperature& CTemperature::operator *=(double right) +{ + assert(IsValid()); + + m_value*=right; + return *this; +} + +const CTemperature& CTemperature::operator /=(double right) +{ + assert(IsValid()); + + m_value/=right; + return *this; +} + +CTemperature CTemperature::operator +(double right) const +{ + assert(IsValid()); + + CTemperature temp(*this); + temp.m_value+=right; + return temp; +} + +CTemperature CTemperature::operator -(double right) const +{ + assert(IsValid()); + + CTemperature temp(*this); + temp.m_value-=right; + return temp; +} + +CTemperature CTemperature::operator *(double right) const +{ + assert(IsValid()); + + CTemperature temp(*this); + temp.m_value*=right; + return temp; +} + +CTemperature CTemperature::operator /(double right) const +{ + assert(IsValid()); + + CTemperature temp(*this); + temp.m_value/=right; + return temp; +} + +CTemperature CTemperature::CreateFromFahrenheit(double value) +{ + return CTemperature(value); +} + +CTemperature CTemperature::CreateFromReaumur(double value) +{ + return CTemperature(value * 2.25 + 32.0); +} + +CTemperature CTemperature::CreateFromRankine(double value) +{ + return CTemperature(value - 459.67); +} + +CTemperature CTemperature::CreateFromRomer(double value) +{ + return CTemperature((value - 7.5) * 24.0 / 7.0 + 32.0); +} + +CTemperature CTemperature::CreateFromDelisle(double value) +{ + CTemperature temp(212.0 - value * 1.2); + return temp; +} + +CTemperature CTemperature::CreateFromNewton(double value) +{ + return CTemperature(value * 60.0 / 11.0 + 32.0); +} + +CTemperature CTemperature::CreateFromCelsius(double value) +{ + return CTemperature(value * 1.8 + 32.0); +} + +CTemperature CTemperature::CreateFromKelvin(double value) +{ + return CTemperature((value - 273.15) * 1.8 + 32.0); +} + +void CTemperature::Archive(CArchive& ar) +{ + if (ar.IsStoring()) + { + ar<<m_value; + ar<<m_valid; + } + else + { + ar>>m_value; + ar>>m_valid; + } +} + +bool CTemperature::IsValid() const +{ + return m_valid; +} + +double CTemperature::ToFahrenheit() const +{ + return m_value; +} + +double CTemperature::ToKelvin() const +{ + return (m_value + 459.67) / 1.8; +} + +double CTemperature::ToCelsius() const +{ + return (m_value - 32.0) / 1.8; +} + +double CTemperature::ToReaumur() const +{ + return (m_value - 32.0) / 2.25; +} + +double CTemperature::ToRankine() const +{ + return m_value + 459.67; +} + +double CTemperature::ToRomer() const +{ + return (m_value - 32.0) * 7.0 / 24.0 + 7.5; +} + +double CTemperature::ToDelisle() const +{ + return (212.0 - m_value) * 5.0 / 6.0; +} + +double CTemperature::ToNewton() const +{ + return (m_value - 32.0) * 11.0 / 60.0; +} + +double CTemperature::To(Unit temperatureUnit) const +{ + if (!IsValid()) + return 0; + + double value = 0.0; + + switch (temperatureUnit) + { + case UnitFahrenheit: + value=ToFahrenheit(); + break; + case UnitKelvin: + value=ToKelvin(); + break; + case UnitCelsius: + value=ToCelsius(); + break; + case UnitReaumur: + value=ToReaumur(); + break; + case UnitRankine: + value=ToRankine(); + break; + case UnitRomer: + value=ToRomer(); + break; + case UnitDelisle: + value=ToDelisle(); + break; + case UnitNewton: + value=ToNewton(); + break; + default: + assert(false); + break; + } + return value; +} + +// Returns temperature as localized string +std::string CTemperature::ToString(Unit temperatureUnit) const +{ + if (!IsValid()) + return ""; + + return StringUtils::Format("{:2.0f}", To(temperatureUnit)); +} diff --git a/xbmc/utils/Temperature.h b/xbmc/utils/Temperature.h new file mode 100644 index 0000000..9d2a019 --- /dev/null +++ b/xbmc/utils/Temperature.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/IArchivable.h" + +#include <string> + +class CTemperature : public IArchivable +{ +public: + CTemperature(); + CTemperature(const CTemperature& temperature); + + typedef enum Unit + { + UnitFahrenheit = 0, + UnitKelvin, + UnitCelsius, + UnitReaumur, + UnitRankine, + UnitRomer, + UnitDelisle, + UnitNewton + } Unit; + + static CTemperature CreateFromFahrenheit(double value); + static CTemperature CreateFromKelvin(double value); + static CTemperature CreateFromCelsius(double value); + static CTemperature CreateFromReaumur(double value); + static CTemperature CreateFromRankine(double value); + static CTemperature CreateFromRomer(double value); + static CTemperature CreateFromDelisle(double value); + static CTemperature CreateFromNewton(double value); + + bool operator >(const CTemperature& right) const; + bool operator >=(const CTemperature& right) const; + bool operator <(const CTemperature& right) const; + bool operator <=(const CTemperature& right) const; + bool operator ==(const CTemperature& right) const; + bool operator !=(const CTemperature& right) const; + + CTemperature& operator =(const CTemperature& right); + const CTemperature& operator +=(const CTemperature& right); + const CTemperature& operator -=(const CTemperature& right); + const CTemperature& operator *=(const CTemperature& right); + const CTemperature& operator /=(const CTemperature& right); + CTemperature operator +(const CTemperature& right) const; + CTemperature operator -(const CTemperature& right) const; + CTemperature operator *(const CTemperature& right) const; + CTemperature operator /(const CTemperature& right) const; + + bool operator >(double right) const; + bool operator >=(double right) const; + bool operator <(double right) const; + bool operator <=(double right) const; + bool operator ==(double right) const; + bool operator !=(double right) const; + + const CTemperature& operator +=(double right); + const CTemperature& operator -=(double right); + const CTemperature& operator *=(double right); + const CTemperature& operator /=(double right); + CTemperature operator +(double right) const; + CTemperature operator -(double right) const; + CTemperature operator *(double right) const; + CTemperature operator /(double right) const; + + CTemperature& operator ++(); + CTemperature& operator --(); + CTemperature operator ++(int); + CTemperature operator --(int); + + void Archive(CArchive& ar) override; + + bool IsValid() const; + void SetValid(bool valid) { m_valid = valid; } + + double ToFahrenheit() const; + double ToKelvin() const; + double ToCelsius() const; + double ToReaumur() const; + double ToRankine() const; + double ToRomer() const; + double ToDelisle() const; + double ToNewton() const; + + double To(Unit temperatureUnit) const; + std::string ToString(Unit temperatureUnit) const; + +protected: + explicit CTemperature(double value); + + double m_value; // we store as fahrenheit + bool m_valid; +}; + diff --git a/xbmc/utils/TextSearch.cpp b/xbmc/utils/TextSearch.cpp new file mode 100644 index 0000000..1ada61d --- /dev/null +++ b/xbmc/utils/TextSearch.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "TextSearch.h" + +#include "StringUtils.h" + +CTextSearch::CTextSearch(const std::string &strSearchTerms, bool bCaseSensitive /* = false */, TextSearchDefault defaultSearchMode /* = SEARCH_DEFAULT_OR */) +{ + m_bCaseSensitive = bCaseSensitive; + ExtractSearchTerms(strSearchTerms, defaultSearchMode); +} + +bool CTextSearch::IsValid(void) const +{ + return m_AND.size() > 0 || m_OR.size() > 0 || m_NOT.size() > 0; +} + +bool CTextSearch::Search(const std::string &strHaystack) const +{ + if (strHaystack.empty() || !IsValid()) + return false; + + std::string 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.empty()); + 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(std::string &strSearchTerm, std::string &strNextTerm) +{ + std::string 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 std::string &strSearchTerm, TextSearchDefault defaultSearchMode) +{ + std::string 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")) + { + std::string strDummy; + GetAndCutNextTerm(strParsedSearchTerm, strDummy); + bNextNOT = true; + } + else if (StringUtils::StartsWith(strParsedSearchTerm, "+") || StringUtils::StartsWithNoCase(strParsedSearchTerm, "and")) + { + std::string strDummy; + GetAndCutNextTerm(strParsedSearchTerm, strDummy); + bNextAND = true; + } + else if (StringUtils::StartsWith(strParsedSearchTerm, "|") || StringUtils::StartsWithNoCase(strParsedSearchTerm, "or")) + { + std::string strDummy; + GetAndCutNextTerm(strParsedSearchTerm, strDummy); + bNextOR = true; + } + else + { + std::string 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/xbmc/utils/TextSearch.h b/xbmc/utils/TextSearch.h new file mode 100644 index 0000000..f2d1fdb --- /dev/null +++ b/xbmc/utils/TextSearch.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> +#include <vector> + +typedef enum TextSearchDefault +{ + SEARCH_DEFAULT_AND = 0, + SEARCH_DEFAULT_OR, + SEARCH_DEFAULT_NOT +} TextSearchDefault; + +class CTextSearch final +{ +public: + CTextSearch(const std::string &strSearchTerms, bool bCaseSensitive = false, TextSearchDefault defaultSearchMode = SEARCH_DEFAULT_OR); + + bool Search(const std::string &strHaystack) const; + bool IsValid(void) const; + +private: + static void GetAndCutNextTerm(std::string &strSearchTerm, std::string &strNextTerm); + void ExtractSearchTerms(const std::string &strSearchTerm, TextSearchDefault defaultSearchMode); + + bool m_bCaseSensitive; + std::vector<std::string> m_AND; + std::vector<std::string> m_OR; + std::vector<std::string> m_NOT; +}; diff --git a/xbmc/utils/TimeFormat.h b/xbmc/utils/TimeFormat.h new file mode 100644 index 0000000..595f532 --- /dev/null +++ b/xbmc/utils/TimeFormat.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +/*! \brief TIME_FORMAT enum/bitmask used for formatting time strings + Note the use of bitmasking, e.g. + TIME_FORMAT_HH_MM_SS = TIME_FORMAT_HH | TIME_FORMAT_MM | TIME_FORMAT_SS + \sa StringUtils::SecondsToTimeString + \note For InfoLabels use the equivalent value listed (bold) + on the description of each enum value. + \note<b>Example:</b> 3661 seconds => h=1, hh=01, m=1, mm=01, ss=01, hours=1, mins=61, secs=3661 + <p><hr> + @skinning_v18 **[Infolabels Updated]** Added <b>secs</b>, <b>mins</b>, <b>hours</b> (total time) and **m** as possible formats for + InfoLabels that support the definition of a time format. Examples are: + - \link Player_SeekOffset_format `Player.SeekOffset(format)`\endlink + - \link Player_TimeRemaining_format `Player.TimeRemaining(format)`\endlink + - \link Player_Time_format `Player.Time(format)`\endlink + - \link Player_Duration_format `Player.Duration(format)`\endlink + - \link Player_FinishTime_format `Player.FinishTime(format)`\endlink + - \link Player_StartTime_format `Player.StartTime(format)` \endlink + - \link Player_SeekNumeric_format `Player.SeekNumeric(format)`\endlink + - \link ListItem_Duration_format `ListItem.Duration(format)`\endlink + - \link PVR_EpgEventDuration_format `PVR.EpgEventDuration(format)`\endlink + - \link PVR_EpgEventElapsedTime_format `PVR.EpgEventElapsedTime(format)`\endlink + - \link PVR_EpgEventRemainingTime_format `PVR.EpgEventRemainingTime(format)`\endlink + - \link PVR_EpgEventSeekTime_format `PVR.EpgEventSeekTime(format)`\endlink + - \link PVR_EpgEventFinishTime_format `PVR.EpgEventFinishTime(format)`\endlink + - \link PVR_TimeShiftStart_format `PVR.TimeShiftStart(format)`\endlink + - \link PVR_TimeShiftEnd_format `PVR.TimeShiftEnd(format)`\endlink + - \link PVR_TimeShiftCur_format `PVR.TimeShiftCur(format)`\endlink + - \link PVR_TimeShiftOffset_format `PVR.TimeShiftOffset(format)`\endlink + - \link PVR_TimeshiftProgressDuration_format `PVR.TimeshiftProgressDuration(format)`\endlink + - \link PVR_TimeshiftProgressEndTime `PVR.TimeshiftProgressEndTime`\endlink + - \link PVR_TimeshiftProgressEndTime_format `PVR.TimeshiftProgressEndTime(format)`\endlink + - \link ListItem_NextDuration_format `ListItem.NextDuration(format)` \endlink + <p> + */ +enum TIME_FORMAT +{ + TIME_FORMAT_GUESS = 0, ///< usually used as the fallback value if the format value is empty + TIME_FORMAT_SS = 1, ///< <b>ss</b> - seconds only + TIME_FORMAT_MM = 2, ///< <b>mm</b> - minutes only (2-digit) + TIME_FORMAT_MM_SS = 3, ///< <b>mm:ss</b> - minutes and seconds + TIME_FORMAT_HH = 4, ///< <b>hh</b> - hours only (2-digit) + TIME_FORMAT_HH_SS = 5, ///< <b>hh:ss</b> - hours and seconds (this is not particularly useful) + TIME_FORMAT_HH_MM = 6, ///< <b>hh:mm</b> - hours and minutes + TIME_FORMAT_HH_MM_SS = 7, ///< <b>hh:mm:ss</b> - hours, minutes and seconds + TIME_FORMAT_XX = 8, ///< <b>xx</b> - returns AM/PM for a 12-hour clock + TIME_FORMAT_HH_MM_XX = + 14, ///< <b>hh:mm xx</b> - returns hours and minutes in a 12-hour clock format (AM/PM) + TIME_FORMAT_HH_MM_SS_XX = + 15, ///< <b>hh:mm:ss xx</b> - returns hours (2-digit), minutes and seconds in a 12-hour clock format (AM/PM) + TIME_FORMAT_H = 16, ///< <b>h</b> - hours only (1-digit) + TIME_FORMAT_H_MM_SS = 19, ///< <b>hh:mm:ss</b> - hours, minutes and seconds + TIME_FORMAT_H_MM_SS_XX = + 27, ///< <b>hh:mm:ss xx</b> - returns hours (1-digit), minutes and seconds in a 12-hour clock format (AM/PM) + TIME_FORMAT_SECS = 32, ///< <b>secs</b> - total time in seconds + TIME_FORMAT_MINS = 64, ///< <b>mins</b> - total time in minutes + TIME_FORMAT_HOURS = 128, ///< <b>hours</b> - total time in hours + TIME_FORMAT_M = 256 ///< <b>m</b> - minutes only (1-digit) +}; diff --git a/xbmc/utils/TimeUtils.cpp b/xbmc/utils/TimeUtils.cpp new file mode 100644 index 0000000..95c5069 --- /dev/null +++ b/xbmc/utils/TimeUtils.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "TimeUtils.h" +#include "XBDateTime.h" +#include "windowing/GraphicContext.h" + +#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 + +namespace +{ +auto startTime = std::chrono::steady_clock::now(); +} + +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; +#if defined(CLOCK_MONOTONIC_RAW) && !defined(TARGET_ANDROID) + clock_gettime(CLOCK_MONOTONIC_RAW, &now); +#else + clock_gettime(CLOCK_MONOTONIC, &now); +#endif // CLOCK_MONOTONIC_RAW && !TARGET_ANDROID + 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 +} + +unsigned int CTimeUtils::frameTime = 0; + +void CTimeUtils::UpdateFrameTime(bool flip) +{ + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - startTime); + + unsigned int currentTime = duration.count(); + unsigned int last = frameTime; + while (frameTime < currentTime) + { + frameTime += (unsigned int)(1000 / CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS()); + // observe wrap around + if (frameTime < last) + break; + } +} + +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; +} + +std::string CTimeUtils::WithoutSeconds(const std::string& hhmmss) +{ + return hhmmss.substr(0, 5); +} diff --git a/xbmc/utils/TimeUtils.h b/xbmc/utils/TimeUtils.h new file mode 100644 index 0000000..d7740d7 --- /dev/null +++ b/xbmc/utils/TimeUtils.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stdint.h> +#include <string> +#include <time.h> + +class CDateTime; + +int64_t CurrentHostCounter(void); +int64_t CurrentHostFrequency(void); + +class CTimeUtils +{ +public: + + /*! + * @brief Update the time frame + * @note Not threadsafe + */ + static void UpdateFrameTime(bool flip); + + /*! + * @brief Returns the frame time in MS + * @note Not threadsafe + */ + static unsigned int GetFrameTime(); + static CDateTime GetLocalTime(time_t time); + + /*! + * @brief Returns a time string without seconds, i.e: HH:MM + * @param hhmmss Time string in the format HH:MM:SS + */ + static std::string WithoutSeconds(const std::string& hhmmss); + +private: + static unsigned int frameTime; +}; + diff --git a/xbmc/utils/TransformMatrix.h b/xbmc/utils/TransformMatrix.h new file mode 100644 index 0000000..a9bf8fd --- /dev/null +++ b/xbmc/utils/TransformMatrix.h @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/ColorUtils.h" + +#include <algorithm> +#include <math.h> +#include <memory> +#include <string.h> + +#ifdef __GNUC__ +// under gcc, inline will only take place if optimizations are applied (-O). this will force inline even with optimizations. +#define XBMC_FORCE_INLINE __attribute__((always_inline)) +#else +#define XBMC_FORCE_INLINE +#endif + +class TransformMatrix +{ +public: + TransformMatrix() + { + Reset(); + }; + void Reset() + { + m[0][0] = 1.0f; m[0][1] = m[0][2] = m[0][3] = 0.0f; + m[1][0] = m[1][2] = m[1][3] = 0.0f; m[1][1] = 1.0f; + m[2][0] = m[2][1] = m[2][3] = 0.0f; m[2][2] = 1.0f; + alpha = red = green = blue = 1.0f; + identity = true; + }; + static TransformMatrix CreateTranslation(float transX, float transY, float transZ = 0) + { + TransformMatrix translation; + translation.SetTranslation(transX, transY, transZ); + return translation; + } + void SetTranslation(float transX, float transY, float transZ) + { + m[0][1] = m[0][2] = 0.0f; m[0][0] = 1.0f; m[0][3] = transX; + m[1][0] = m[1][2] = 0.0f; m[1][1] = 1.0f; m[1][3] = transY; + m[2][0] = m[2][1] = 0.0f; m[2][2] = 1.0f; m[2][3] = transZ; + alpha = red = green = blue = 1.0f; + identity = (transX == 0 && transY == 0 && transZ == 0); + } + static TransformMatrix CreateScaler(float scaleX, float scaleY, float scaleZ = 1.0f) + { + TransformMatrix scaler; + scaler.m[0][0] = scaleX; + scaler.m[1][1] = scaleY; + scaler.m[2][2] = scaleZ; + scaler.identity = (scaleX == 1 && scaleY == 1 && scaleZ == 1); + return scaler; + }; + void SetScaler(float scaleX, float scaleY, float centerX, float centerY) + { + // Trans(centerX,centerY,centerZ)*Scale(scaleX,scaleY,scaleZ)*Trans(-centerX,-centerY,-centerZ) + float centerZ = 0.0f, scaleZ = 1.0f; + m[0][0] = scaleX; m[0][1] = 0.0f; m[0][2] = 0.0f; m[0][3] = centerX*(1-scaleX); + m[1][0] = 0.0f; m[1][1] = scaleY; m[1][2] = 0.0f; m[1][3] = centerY*(1-scaleY); + m[2][0] = 0.0f; m[2][1] = 0.0f; m[2][2] = scaleZ; m[2][3] = centerZ*(1-scaleZ); + alpha = red = green = blue = 1.0f; + identity = (scaleX == 1 && scaleY == 1); + }; + void SetXRotation(float angle, float y, float z, float ar = 1.0f) + { // angle about the X axis, centered at y,z where our coordinate system has aspect ratio ar. + // Trans(0,y,z)*Scale(1,1/ar,1)*RotateX(angle)*Scale(ar,1,1)*Trans(0,-y,-z); + float c = cos(angle); float s = sin(angle); + m[0][0] = ar; m[0][1] = 0.0f; m[0][2] = 0.0f; m[0][3] = 0.0f; + m[1][0] = 0.0f; m[1][1] = c/ar; m[1][2] = -s/ar; m[1][3] = (-y*c+s*z)/ar + y; + m[2][0] = 0.0f; m[2][1] = s; m[2][2] = c; m[2][3] = (-y*s-c*z) + z; + alpha = red = green = blue = 1.0f; + identity = (angle == 0); + } + void SetYRotation(float angle, float x, float z, float ar = 1.0f) + { // angle about the Y axis, centered at x,z where our coordinate system has aspect ratio ar. + // Trans(x,0,z)*Scale(1/ar,1,1)*RotateY(angle)*Scale(ar,1,1)*Trans(-x,0,-z); + float c = cos(angle); float s = sin(angle); + m[0][0] = c; m[0][1] = 0.0f; m[0][2] = -s/ar; m[0][3] = -x*c + s*z/ar + x; + m[1][0] = 0.0f; m[1][1] = 1.0f; m[1][2] = 0.0f; m[1][3] = 0.0f; + m[2][0] = ar*s; m[2][1] = 0.0f; m[2][2] = c; m[2][3] = -ar*x*s - c*z + z; + alpha = red = green = blue = 1.0f; + identity = (angle == 0); + } + static TransformMatrix CreateZRotation(float angle, float x, float y, float ar = 1.0f) + { // angle about the Z axis, centered at x,y where our coordinate system has aspect ratio ar. + // Trans(x,y,0)*Scale(1/ar,1,1)*RotateZ(angle)*Scale(ar,1,1)*Trans(-x,-y,0) + TransformMatrix rot; + rot.SetZRotation(angle, x, y, ar); + return rot; + } + void SetZRotation(float angle, float x, float y, float ar = 1.0f) + { // angle about the Z axis, centered at x,y where our coordinate system has aspect ratio ar. + // Trans(x,y,0)*Scale(1/ar,1,1)*RotateZ(angle)*Scale(ar,1,1)*Trans(-x,-y,0) + float c = cos(angle); float s = sin(angle); + m[0][0] = c; m[0][1] = -s/ar; m[0][2] = 0.0f; m[0][3] = -x*c + s*y/ar + x; + m[1][0] = s*ar; m[1][1] = c; m[1][2] = 0.0f; m[1][3] = -ar*x*s - c*y + y; + m[2][0] = 0.0f; m[2][1] = 0.0f; m[2][2] = 1.0f; m[2][3] = 0.0f; + alpha = red = green = blue = 1.0f; + identity = (angle == 0); + } + static TransformMatrix CreateFader(float a) + { + TransformMatrix fader; + fader.SetFader(a); + return fader; + } + static TransformMatrix CreateFader(float a, float r, float g, float b) + { + TransformMatrix fader; + fader.SetFader(a, r, g, b); + return fader; + } + void SetFader(float a) + { + m[0][0] = 1.0f; m[0][1] = 0.0f; m[0][2] = 0.0f; m[0][3] = 0.0f; + m[1][0] = 0.0f; m[1][1] = 1.0f; m[1][2] = 0.0f; m[1][3] = 0.0f; + m[2][0] = 0.0f; m[2][1] = 0.0f; m[2][2] = 1.0f; m[2][3] = 0.0f; + alpha = a; + red = green = blue = 1.0f; + identity = (a == 1.0f); + } + + void SetFader(float a, float r, float g, float b) + { + m[0][0] = 1.0f; m[0][1] = 0.0f; m[0][2] = 0.0f; m[0][3] = 0.0f; + m[1][0] = 0.0f; m[1][1] = 1.0f; m[1][2] = 0.0f; m[1][3] = 0.0f; + m[2][0] = 0.0f; m[2][1] = 0.0f; m[2][2] = 1.0f; m[2][3] = 0.0f; + alpha = a; + red = r; + green = g; + blue = b; + identity = ((a == 1.0f) && (r == 1.0f) && (g == 1.0f) && (b == 1.0f)); + } + + // multiplication operators + const TransformMatrix &operator *=(const TransformMatrix &right) + { + if (right.identity) + return *this; + if (identity) + { + *this = right; + return *this; + } + float t00 = m[0][0] * right.m[0][0] + m[0][1] * right.m[1][0] + m[0][2] * right.m[2][0]; + float t01 = m[0][0] * right.m[0][1] + m[0][1] * right.m[1][1] + m[0][2] * right.m[2][1]; + float t02 = m[0][0] * right.m[0][2] + m[0][1] * right.m[1][2] + m[0][2] * right.m[2][2]; + m[0][3] = m[0][0] * right.m[0][3] + m[0][1] * right.m[1][3] + m[0][2] * right.m[2][3] + m[0][3]; + m[0][0] = t00; m[0][1] = t01; m[0][2] = t02; + t00 = m[1][0] * right.m[0][0] + m[1][1] * right.m[1][0] + m[1][2] * right.m[2][0]; + t01 = m[1][0] * right.m[0][1] + m[1][1] * right.m[1][1] + m[1][2] * right.m[2][1]; + t02 = m[1][0] * right.m[0][2] + m[1][1] * right.m[1][2] + m[1][2] * right.m[2][2]; + m[1][3] = m[1][0] * right.m[0][3] + m[1][1] * right.m[1][3] + m[1][2] * right.m[2][3] + m[1][3]; + m[1][0] = t00; m[1][1] = t01; m[1][2] = t02; + t00 = m[2][0] * right.m[0][0] + m[2][1] * right.m[1][0] + m[2][2] * right.m[2][0]; + t01 = m[2][0] * right.m[0][1] + m[2][1] * right.m[1][1] + m[2][2] * right.m[2][1]; + t02 = m[2][0] * right.m[0][2] + m[2][1] * right.m[1][2] + m[2][2] * right.m[2][2]; + m[2][3] = m[2][0] * right.m[0][3] + m[2][1] * right.m[1][3] + m[2][2] * right.m[2][3] + m[2][3]; + m[2][0] = t00; m[2][1] = t01; m[2][2] = t02; + alpha *= right.alpha; + red *= right.red; + green *= right.green; + blue *= right.blue; + identity = false; + return *this; + } + + TransformMatrix operator *(const TransformMatrix &right) const + { + if (right.identity) + return *this; + if (identity) + return right; + TransformMatrix result; + result.m[0][0] = m[0][0] * right.m[0][0] + m[0][1] * right.m[1][0] + m[0][2] * right.m[2][0]; + result.m[0][1] = m[0][0] * right.m[0][1] + m[0][1] * right.m[1][1] + m[0][2] * right.m[2][1]; + result.m[0][2] = m[0][0] * right.m[0][2] + m[0][1] * right.m[1][2] + m[0][2] * right.m[2][2]; + result.m[0][3] = m[0][0] * right.m[0][3] + m[0][1] * right.m[1][3] + m[0][2] * right.m[2][3] + m[0][3]; + result.m[1][0] = m[1][0] * right.m[0][0] + m[1][1] * right.m[1][0] + m[1][2] * right.m[2][0]; + result.m[1][1] = m[1][0] * right.m[0][1] + m[1][1] * right.m[1][1] + m[1][2] * right.m[2][1]; + result.m[1][2] = m[1][0] * right.m[0][2] + m[1][1] * right.m[1][2] + m[1][2] * right.m[2][2]; + result.m[1][3] = m[1][0] * right.m[0][3] + m[1][1] * right.m[1][3] + m[1][2] * right.m[2][3] + m[1][3]; + result.m[2][0] = m[2][0] * right.m[0][0] + m[2][1] * right.m[1][0] + m[2][2] * right.m[2][0]; + result.m[2][1] = m[2][0] * right.m[0][1] + m[2][1] * right.m[1][1] + m[2][2] * right.m[2][1]; + result.m[2][2] = m[2][0] * right.m[0][2] + m[2][1] * right.m[1][2] + m[2][2] * right.m[2][2]; + result.m[2][3] = m[2][0] * right.m[0][3] + m[2][1] * right.m[1][3] + m[2][2] * right.m[2][3] + m[2][3]; + result.alpha = alpha * right.alpha; + result.red = red * right.red; + result.green = green * right.green; + result.blue = blue * right.blue; + result.identity = false; + return result; + } + + inline void TransformPosition(float &x, float &y, float &z) const XBMC_FORCE_INLINE + { + float newX = m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3]; + float newY = m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3]; + z = m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3]; + y = newY; + x = newX; + } + + inline void TransformPositionUnscaled(float &x, float &y, float &z) const XBMC_FORCE_INLINE + { + float n; + // calculate the norm of the transformed (but not translated) vectors involved + n = sqrt(m[0][0]*m[0][0] + m[0][1]*m[0][1] + m[0][2]*m[0][2]); + float newX = (m[0][0] * x + m[0][1] * y + m[0][2] * z)/n + m[0][3]; + n = sqrt(m[1][0]*m[1][0] + m[1][1]*m[1][1] + m[1][2]*m[1][2]); + float newY = (m[1][0] * x + m[1][1] * y + m[1][2] * z)/n + m[1][3]; + n = sqrt(m[2][0]*m[2][0] + m[2][1]*m[2][1] + m[2][2]*m[2][2]); + float newZ = (m[2][0] * x + m[2][1] * y + m[2][2] * z)/n + m[2][3]; + z = newZ; + y = newY; + x = newX; + } + + inline void InverseTransformPosition(float &x, float &y) const XBMC_FORCE_INLINE + { // used for mouse - no way to find z + x -= m[0][3]; y -= m[1][3]; + float detM = m[0][0]*m[1][1] - m[0][1]*m[1][0]; + float newX = (m[1][1] * x - m[0][1] * y)/detM; + y = (-m[1][0] * x + m[0][0] * y)/detM; + x = newX; + } + + inline float TransformXCoord(float x, float y, float z) const XBMC_FORCE_INLINE + { + return m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3]; + } + + inline float TransformYCoord(float x, float y, float z) const XBMC_FORCE_INLINE + { + return m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3]; + } + + inline float TransformZCoord(float x, float y, float z) const XBMC_FORCE_INLINE + { + return m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3]; + } + + inline UTILS::COLOR::Color TransformAlpha(UTILS::COLOR::Color color) const XBMC_FORCE_INLINE + { + return static_cast<UTILS::COLOR::Color>(color * alpha); + } + + inline UTILS::COLOR::Color TransformColor(UTILS::COLOR::Color color) const XBMC_FORCE_INLINE + { + UTILS::COLOR::Color a = static_cast<UTILS::COLOR::Color>(((color >> 24) & 0xff) * alpha); + UTILS::COLOR::Color r = static_cast<UTILS::COLOR::Color>(((color >> 16) & 0xff) * red); + UTILS::COLOR::Color g = static_cast<UTILS::COLOR::Color>(((color >> 8) & 0xff) * green); + UTILS::COLOR::Color b = static_cast<UTILS::COLOR::Color>(((color)&0xff) * blue); + if (a > 255) + a = 255; + if (r > 255) + r = 255; + if (g > 255) + g = 255; + if (b > 255) + b = 255; + + return ((a << 24) & 0xff000000) | ((r << 16) & 0xff0000) | ((g << 8) & 0xff00) | (b & 0xff); + } + + float m[3][4]; + float alpha; + float red; + float green; + float blue; + bool identity; +}; + +inline bool operator==(const TransformMatrix &a, const TransformMatrix &b) +{ + bool comparison = + a.alpha == b.alpha && a.red == b.red && a.green == b.green && a.blue == b.blue && + ((a.identity && b.identity) || + (!a.identity && !b.identity && + std::equal(&a.m[0][0], &a.m[0][0] + sizeof(a.m) / sizeof(a.m[0][0]), &b.m[0][0]))); + return comparison; +} + +inline bool operator!=(const TransformMatrix &a, const TransformMatrix &b) +{ + return !operator==(a, b); +} diff --git a/xbmc/utils/UDMABufferObject.cpp b/xbmc/utils/UDMABufferObject.cpp new file mode 100644 index 0000000..2b8336b --- /dev/null +++ b/xbmc/utils/UDMABufferObject.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "UDMABufferObject.h" + +#include "utils/BufferObjectFactory.h" +#include "utils/log.h" + +#include <drm_fourcc.h> +#include <fcntl.h> +#include <linux/udmabuf.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <unistd.h> + +#include "PlatformDefs.h" + +namespace +{ + +const auto PAGESIZE = getpagesize(); + +int RoundUp(int num, int factor) +{ + return num + factor - 1 - (num - 1) % factor; +} + +} // namespace + +std::unique_ptr<CBufferObject> CUDMABufferObject::Create() +{ + return std::make_unique<CUDMABufferObject>(); +} + +void CUDMABufferObject::Register() +{ + int fd = open("/dev/udmabuf", O_RDWR); + if (fd < 0) + { + CLog::Log(LOGDEBUG, "CUDMABufferObject::{} - unable to open /dev/udmabuf: {}", __FUNCTION__, + strerror(errno)); + return; + } + + close(fd); + + CBufferObjectFactory::RegisterBufferObject(CUDMABufferObject::Create); +} + +CUDMABufferObject::~CUDMABufferObject() +{ + ReleaseMemory(); + DestroyBufferObject(); + + int ret = close(m_udmafd); + if (ret < 0) + CLog::Log(LOGERROR, "CUDMABufferObject::{} - close /dev/udmabuf failed, errno={}", __FUNCTION__, + strerror(errno)); + + m_udmafd = -1; +} + +bool CUDMABufferObject::CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) +{ + if (m_fd >= 0) + return true; + + uint32_t bpp{1}; + + switch (format) + { + case DRM_FORMAT_ARGB8888: + bpp = 4; + break; + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_RGB565: + bpp = 2; + break; + default: + throw std::runtime_error("CUDMABufferObject: pixel format not implemented"); + } + + m_stride = width * bpp; + + return CreateBufferObject(width * height * bpp); +} + +bool CUDMABufferObject::CreateBufferObject(uint64_t size) +{ + // Must be rounded to the system page size + m_size = RoundUp(size, PAGESIZE); + + m_memfd = memfd_create("kodi", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (m_memfd < 0) + { + CLog::Log(LOGERROR, "CUDMABufferObject::{} - memfd_create failed: {}", __FUNCTION__, + strerror(errno)); + return false; + } + + if (ftruncate(m_memfd, m_size) < 0) + { + CLog::Log(LOGERROR, "CUDMABufferObject::{} - ftruncate failed: {}", __FUNCTION__, + strerror(errno)); + return false; + } + + if (fcntl(m_memfd, F_ADD_SEALS, F_SEAL_SHRINK) < 0) + { + CLog::Log(LOGERROR, "CUDMABufferObject::{} - fcntl failed: {}", __FUNCTION__, strerror(errno)); + close(m_memfd); + return false; + } + + if (m_udmafd < 0) + { + m_udmafd = open("/dev/udmabuf", O_RDWR); + if (m_udmafd < 0) + { + CLog::Log(LOGERROR, "CUDMABufferObject::{} - unable to open /dev/udmabuf: {}", __FUNCTION__, + strerror(errno)); + close(m_memfd); + return false; + } + } + + struct udmabuf_create_item create{}; + create.memfd = static_cast<uint32_t>(m_memfd); + create.offset = 0; + create.size = m_size; + + m_fd = ioctl(m_udmafd, UDMABUF_CREATE, &create); + if (m_fd < 0) + { + CLog::Log(LOGERROR, "CUDMABufferObject::{} - ioctl UDMABUF_CREATE failed: {}", __FUNCTION__, + strerror(errno)); + close(m_memfd); + return false; + } + + return true; +} + +void CUDMABufferObject::DestroyBufferObject() +{ + if (m_fd < 0) + return; + + int ret = close(m_fd); + if (ret < 0) + CLog::Log(LOGERROR, "CUDMABufferObject::{} - close fd failed, errno={}", __FUNCTION__, + strerror(errno)); + + ret = close(m_memfd); + if (ret < 0) + CLog::Log(LOGERROR, "CUDMABufferObject::{} - close memfd failed, errno={}", __FUNCTION__, + strerror(errno)); + + m_memfd = -1; + m_fd = -1; + m_stride = 0; + m_size = 0; +} + +uint8_t* CUDMABufferObject::GetMemory() +{ + if (m_fd < 0) + return nullptr; + + if (m_map) + { + CLog::Log(LOGDEBUG, "CUDMABufferObject::{} - already mapped fd={} map={}", __FUNCTION__, m_fd, + fmt::ptr(m_map)); + return m_map; + } + + m_map = static_cast<uint8_t*>(mmap(nullptr, m_size, PROT_WRITE, MAP_SHARED, m_memfd, 0)); + if (m_map == MAP_FAILED) + { + CLog::Log(LOGERROR, "CUDMABufferObject::{} - mmap failed, errno={}", __FUNCTION__, + strerror(errno)); + return nullptr; + } + + return m_map; +} + +void CUDMABufferObject::ReleaseMemory() +{ + if (!m_map) + return; + + int ret = munmap(m_map, m_size); + if (ret < 0) + CLog::Log(LOGERROR, "CUDMABufferObject::{} - munmap failed, errno={}", __FUNCTION__, + strerror(errno)); + + m_map = nullptr; +} diff --git a/xbmc/utils/UDMABufferObject.h b/xbmc/utils/UDMABufferObject.h new file mode 100644 index 0000000..a842560 --- /dev/null +++ b/xbmc/utils/UDMABufferObject.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/BufferObject.h" + +#include <memory> +#include <stdint.h> + +class CUDMABufferObject : public CBufferObject +{ +public: + CUDMABufferObject() = default; + virtual ~CUDMABufferObject() override; + + // Registration + static std::unique_ptr<CBufferObject> Create(); + static void Register(); + + // IBufferObject overrides via CBufferObject + bool CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) override; + bool CreateBufferObject(uint64_t size) override; + void DestroyBufferObject() override; + uint8_t* GetMemory() override; + void ReleaseMemory() override; + std::string GetName() const override { return "CUDMABufferObject"; } + +private: + int m_memfd{-1}; + int m_udmafd{-1}; + uint64_t m_size{0}; + uint8_t* m_map{nullptr}; +}; diff --git a/xbmc/utils/URIUtils.cpp b/xbmc/utils/URIUtils.cpp new file mode 100644 index 0000000..b2b9b23 --- /dev/null +++ b/xbmc/utils/URIUtils.cpp @@ -0,0 +1,1493 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "network/Network.h" +#include "URIUtils.h" +#include "FileItem.h" +#include "filesystem/MultiPathDirectory.h" +#include "filesystem/SpecialProtocol.h" +#include "filesystem/StackDirectory.h" +#include "network/DNSNameCache.h" +#include "pvr/channels/PVRChannelsPath.h" +#include "settings/AdvancedSettings.h" +#include "URL.h" +#include "utils/FileExtensionProvider.h" +#include "ServiceBroker.h" +#include "StringUtils.h" +#include "utils/log.h" + +#if defined(TARGET_WINDOWS) +#include "platform/win32/CharsetConverter.h" +#endif + +#include <algorithm> +#include <cassert> +#include <netinet/in.h> +#include <arpa/inet.h> + +using namespace PVR; +using namespace XFILE; + +const CAdvancedSettings* URIUtils::m_advancedSettings = nullptr; + +void URIUtils::RegisterAdvancedSettings(const CAdvancedSettings& advancedSettings) +{ + m_advancedSettings = &advancedSettings; +} + +void URIUtils::UnregisterAdvancedSettings() +{ + m_advancedSettings = nullptr; +} + +/* 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 == std::string::npos || strFileName[period] != '.') + return std::string(); + + return strFileName.substr(period); +} + +bool URIUtils::HasPluginPath(const CFileItem& item) +{ + return IsPlugin(item.GetPath()) || IsPlugin(item.GetDynPath()); +} + +bool URIUtils::HasExtension(const std::string& strFileName) +{ + if (IsURL(strFileName)) + { + CURL url(strFileName); + return HasExtension(url.GetFileName()); + } + + size_t iPeriod = strFileName.find_last_of("./\\"); + return iPeriod != std::string::npos && strFileName[iPeriod] == '.'; +} + +bool URIUtils::HasExtension(const CURL& url, const std::string& strExtensions) +{ + return HasExtension(url.GetFileName(), strExtensions); +} + +bool URIUtils::HasExtension(const std::string& strFileName, const std::string& strExtensions) +{ + if (IsURL(strFileName)) + { + const CURL url(strFileName); + return HasExtension(url.GetFileName(), strExtensions); + } + + const size_t pos = strFileName.find_last_of("./\\"); + if (pos == std::string::npos || strFileName[pos] != '.') + return false; + + const std::string extensionLower = StringUtils::ToLower(strFileName.substr(pos)); + + const std::vector<std::string> extensionsLower = + StringUtils::Split(StringUtils::ToLower(strExtensions), '|'); + + for (const auto& ext : extensionsLower) + { + if (StringUtils::EndsWith(ext, extensionLower)) + return true; + } + + 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 != std::string::npos && strFileName[period] == '.') + { + std::string strExtension = strFileName.substr(period); + StringUtils::ToLower(strExtension); + strExtension += "|"; + + std::string strFileMask; + strFileMask = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(); + strFileMask += "|" + CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(); + strFileMask += "|" + CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(); + strFileMask += "|" + CServiceBroker::GetFileExtensionProvider().GetSubtitleExtensions(); +#if defined(TARGET_DARWIN) + strFileMask += "|.py|.xml|.milk|.xbt|.cdg|.app|.applescript|.workflow"; +#else + strFileMask += "|.py|.xml|.milk|.xbt|.cdg"; +#endif + strFileMask += "|"; + + if (strFileMask.find(strExtension) != std::string::npos) + strFileName.erase(period); + } +} + +std::string URIUtils::ReplaceExtension(const std::string& strFile, + const std::string& strNewExtension) +{ + if(IsURL(strFile)) + { + CURL url(strFile); + url.SetFileName(ReplaceExtension(url.GetFileName(), strNewExtension)); + return url.Get(); + } + + std::string strChangedFile; + std::string strExtension = GetExtension(strFile); + if ( strExtension.size() ) + { + strChangedFile = strFile.substr(0, strFile.size() - strExtension.size()) ; + strChangedFile += strNewExtension; + } + else + { + strChangedFile = strFile; + strChangedFile += strNewExtension; + } + return strChangedFile; +} + +std::string URIUtils::GetFileName(const CURL& url) +{ + return GetFileName(url.GetFileName()); +} + +/* returns a filename given an url */ +/* handles both / and \, and options in urls*/ +std::string URIUtils::GetFileName(const std::string& 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); + + // if actual uri, ignore options + if (IsURL(strFileNameAndPath)) + { + i = strFileName.size() - 1; + while (i > 0) + { + char ch = strFileName[i]; + if (ch == '?' || ch == '|') break; + else i--; + } + if (i > 0) + strFileName = strFileName.substr(0, i); + } +} + +std::vector<std::string> URIUtils::SplitPath(const std::string& strPath) +{ + CURL url(strPath); + + // silly std::string can't take a char in the constructor + std::string sep(1, url.GetDirectorySeparator()); + + // split the filename portion of the URL up into separate dirs + std::vector<std::string> dirs = StringUtils::Split(url.GetFileName(), sep); + + // we start with the root path + std::string 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 <= std::min(strParent.size(), strPath.size()) && + StringUtils::CompareNoCase(strParent, strPath, 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("apk") || url.IsProtocol("bluray") || + url.IsProtocol("udf") || url.IsProtocol("iso9660") || url.IsProtocol("xbt") || + (CServiceBroker::IsAddonInterfaceUp() && + CServiceBroker::GetFileExtensionProvider().EncodedHostName(url.GetProtocol())); +} + +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; + if (!dir.GetDirectory(url, items)) + return false; + CURL url2(GetDirectory(items[0]->GetPath())); + if (HasParentInHostname(url2)) + GetParentPath(url2.Get(), strParent); + else + strParent = url2.Get(); + for( int i=1;i<items.Size();++i) + { + items[i]->m_strDVDLabel = GetDirectory(items[i]->GetPath()); + if (HasParentInHostname(url2)) + 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()) + { + //! @todo Make a new python call to get the plugin content type and remove this temporary hack + // When a plugin provides multiple types, it has "plugin://addon.id/?content_type=xxx" root URL + if (url.GetFileName().empty() && url.HasOption("content_type") && url.GetOptions().find('&') == std::string::npos) + url.SetHostName(""); + // + 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.empty()) + { + if (!url.GetHostName().empty()) + { + // 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 URIUtils::GetBasePath(const std::string& strPath) +{ + std::string strCheck(strPath); + if (IsStack(strPath)) + strCheck = CStackDirectory::GetFirstStackedFile(strPath); + + std::string strDirectory = GetDirectory(strCheck); + if (IsInRAR(strCheck)) + { + std::string strPath=strDirectory; + GetParentPath(strPath, strDirectory); + } + if (IsStack(strPath)) + { + strCheck = strDirectory; + RemoveSlashAtEnd(strCheck); + if (GetFileName(strCheck).size() == 3 && StringUtils::StartsWithNoCase(GetFileName(strCheck), "cd")) + strDirectory = GetDirectory(strCheck); + } + return strDirectory; +} + +std::string URLEncodePath(const std::string& strPath) +{ + std::vector<std::string> segments = StringUtils::Split(strPath, "/"); + for (std::vector<std::string>::iterator i = segments.begin(); i != segments.end(); ++i) + *i = CURL::Encode(*i); + + return StringUtils::Join(segments, "/"); +} + +std::string URLDecodePath(const std::string& strPath) +{ + std::vector<std::string> segments = StringUtils::Split(strPath, "/"); + for (std::vector<std::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, const bool &bAddPath /* = true */) +{ + 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, "/", "\\"); + + if (bAddPath) + return AddFileToFolder(toPath, toFile); + + return toFile; +} + +CURL URIUtils::SubstitutePath(const CURL& url, bool reverse /* = false */) +{ + const std::string pathToUrl = url.Get(); + return CURL(SubstitutePath(pathToUrl, reverse)); +} + +std::string URIUtils::SubstitutePath(const std::string& strPath, bool reverse /* = false */) +{ + if (!m_advancedSettings) + { + // path substitution not needed / not working during Kodi bootstrap. + return strPath; + } + + for (const auto& pathPair : m_advancedSettings->m_pathSubstitutions) + { + const std::string fromPath = reverse ? pathPair.second : pathPair.first; + std::string toPath = reverse ? pathPair.first : pathPair.second; + + if (strncmp(strPath.c_str(), fromPath.c_str(), HasSlashAtEnd(fromPath) ? fromPath.size() - 1 : fromPath.size()) == 0) + { + if (strPath.size() > fromPath.size()) + { + std::string 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::PathHasParent(std::string path, std::string parent, bool translate /* = false */) +{ + if (translate) + { + path = CSpecialProtocol::TranslatePath(path); + parent = CSpecialProtocol::TranslatePath(parent); + } + + if (parent.empty()) + return false; + + if (path == parent) + return true; + + // Make sure parent has a trailing slash + AddSlashAtEnd(parent); + + return StringUtils::StartsWith(path, parent); +} + +bool URIUtils::PathEquals(std::string path1, std::string path2, bool ignoreTrailingSlash /* = false */, bool ignoreURLOptions /* = false */) +{ + if (ignoreURLOptions) + { + path1 = CURL(path1).GetWithoutOptions(); + path2 = CURL(path2).GetWithoutOptions(); + } + + if (ignoreTrailingSlash) + { + RemoveSlashAtEnd(path1); + RemoveSlashAtEnd(path2); + } + + return (path1 == path2); +} + +bool URIUtils::IsRemote(const std::string& 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 + std::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 (IsAddonsPath(strFile)) + return false; + + if (IsSourcesPath(strFile)) + return false; + + if (IsVideoDb(strFile) || IsMusicDb(strFile)) + return false; + + if (IsLibraryFolder(strFile)) + return false; + + if (IsPlugin(strFile)) + return false; + + if (IsAndroidApp(strFile)) + return false; + + if (!url.IsLocal()) + return true; + + return false; +} + +bool URIUtils::IsOnDVD(const std::string& strFile) +{ + if (IsProtocol(strFile, "dvd")) + return true; + + if (IsProtocol(strFile, "udf")) + return true; + + if (IsProtocol(strFile, "iso9660")) + return true; + + if (IsProtocol(strFile, "cdda")) + return true; + +#if defined(TARGET_WINDOWS_STORE) + CLog::Log(LOGDEBUG, "{} is not implemented", __FUNCTION__); +#elif defined(TARGET_WINDOWS_DESKTOP) + using KODI::PLATFORM::WINDOWS::ToW; + if (strFile.size() >= 2 && strFile.substr(1, 1) == ":") + return (GetDriveType(ToW(strFile.substr(0, 3)).c_str()) == DRIVE_CDROM); +#endif + return false; +} + +bool URIUtils::IsOnLAN(const std::string& 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(IsPlugin(strPath)) + return false; + + if(IsUPnP(strPath)) + return true; + + CURL url(strPath); + if (HasParentInHostname(url)) + return IsOnLAN(url.GetHostName()); + + if(!IsRemote(strPath)) + return false; + + const std::string& 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 std::string& host, bool offLineCheck) +{ + if(host.length() == 0) + return false; + + // assume a hostname without dot's + // is local (smb netbios hostnames) + if(host.find('.') == std::string::npos) + return true; + + uint32_t address = ntohl(inet_addr(host.c_str())); + if(address == INADDR_NONE) + { + std::string 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 (!CServiceBroker::GetNetwork().GetFirstConnectedInterface()) + return false; + + if (CServiceBroker::GetNetwork().HasInterfaceForIP(address)) + return true; + } + + return false; +} + +bool URIUtils::IsMultiPath(const std::string& strPath) +{ + return IsProtocol(strPath, "multipath"); +} + +bool URIUtils::IsHD(const std::string& 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") || url.IsProtocol("win-lib"); +} + +bool URIUtils::IsDVD(const std::string& strFile) +{ + std::string 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; + +#ifndef TARGET_WINDOWS_STORE + if(GetDriveType(KODI::PLATFORM::WINDOWS::ToW(strFile).c_str()) == DRIVE_CDROM) + return true; +#endif +#else + if (strFileLow == "iso9660://" || strFileLow == "udf://" || strFileLow == "dvd://1" ) + return true; +#endif + + return false; +} + +bool URIUtils::IsStack(const std::string& strFile) +{ + return IsProtocol(strFile, "stack"); +} + +bool URIUtils::IsFavourite(const std::string& strFile) +{ + return IsProtocol(strFile, "favourites"); +} + +bool URIUtils::IsRAR(const std::string& strFile) +{ + std::string strExtension = GetExtension(strFile); + + if (strExtension == ".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 std::string &strFile) +{ + CURL url(strFile); + + bool archiveProto = url.IsProtocol("archive") && !url.GetFileName().empty(); + return archiveProto || IsInZIP(strFile) || IsInRAR(strFile) || IsInAPK(strFile); +} + +bool URIUtils::IsInAPK(const std::string& strFile) +{ + CURL url(strFile); + + return url.IsProtocol("apk") && !url.GetFileName().empty(); +} + +bool URIUtils::IsInZIP(const std::string& strFile) +{ + CURL url(strFile); + + if (url.GetFileName().empty()) + return false; + + if (url.IsProtocol("archive")) + return IsZIP(url.GetHostName()); + + return url.IsProtocol("zip"); +} + +bool URIUtils::IsInRAR(const std::string& strFile) +{ + CURL url(strFile); + + if (url.GetFileName().empty()) + return false; + + if (url.IsProtocol("archive")) + return IsRAR(url.GetHostName()); + + return url.IsProtocol("rar"); +} + +bool URIUtils::IsAPK(const std::string& strFile) +{ + return HasExtension(strFile, ".apk"); +} + +bool URIUtils::IsZIP(const std::string& strFile) // also checks for comic books! +{ + return HasExtension(strFile, ".zip|.cbz"); +} + +bool URIUtils::IsArchive(const std::string& strFile) +{ + return HasExtension(strFile, ".zip|.rar|.apk|.cbz|.cbr"); +} + +bool URIUtils::IsSpecial(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsSpecial(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "special"); +} + +bool URIUtils::IsPlugin(const std::string& strFile) +{ + CURL url(strFile); + return url.IsProtocol("plugin"); +} + +bool URIUtils::IsScript(const std::string& strFile) +{ + CURL url(strFile); + return url.IsProtocol("script"); +} + +bool URIUtils::IsAddonsPath(const std::string& strFile) +{ + CURL url(strFile); + return url.IsProtocol("addons"); +} + +bool URIUtils::IsSourcesPath(const std::string& strPath) +{ + CURL url(strPath); + return url.IsProtocol("sources"); +} + +bool URIUtils::IsCDDA(const std::string& strFile) +{ + return IsProtocol(strFile, "cdda"); +} + +bool URIUtils::IsISO9660(const std::string& strFile) +{ + return IsProtocol(strFile, "iso9660"); +} + +bool URIUtils::IsSmb(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsSmb(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsSmb(CSpecialProtocol::TranslatePath(strFile)); + + CURL url(strFile); + if (HasParentInHostname(url)) + return IsSmb(url.GetHostName()); + + return IsProtocol(strFile, "smb"); +} + +bool URIUtils::IsURL(const std::string& strFile) +{ + return strFile.find("://") != std::string::npos; +} + +bool URIUtils::IsFTP(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsFTP(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsFTP(CSpecialProtocol::TranslatePath(strFile)); + + CURL url(strFile); + if (HasParentInHostname(url)) + return IsFTP(url.GetHostName()); + + return IsProtocol(strFile, "ftp") || + IsProtocol(strFile, "ftps"); +} + +bool URIUtils::IsHTTP(const std::string& strFile, bool bTranslate /* = false */) +{ + if (IsStack(strFile)) + return IsHTTP(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsHTTP(CSpecialProtocol::TranslatePath(strFile)); + + CURL url(strFile); + if (HasParentInHostname(url)) + return IsHTTP(url.GetHostName()); + + const std::string strProtocol = (bTranslate ? url.GetTranslatedProtocol() : url.GetProtocol()); + + return (strProtocol == "http" || strProtocol == "https"); +} + +bool URIUtils::IsUDP(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsUDP(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "udp"); +} + +bool URIUtils::IsTCP(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsTCP(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "tcp"); +} + +bool URIUtils::IsPVR(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsPVR(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "pvr"); +} + +bool URIUtils::IsPVRChannel(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsPVRChannel(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "pvr") && CPVRChannelsPath(strFile).IsChannel(); +} + +bool URIUtils::IsPVRChannelGroup(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsPVRChannelGroup(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "pvr") && CPVRChannelsPath(strFile).IsChannelGroup(); +} + +bool URIUtils::IsPVRGuideItem(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsPVRGuideItem(CStackDirectory::GetFirstStackedFile(strFile)); + + return StringUtils::StartsWithNoCase(strFile, "pvr://guide"); +} + +bool URIUtils::IsDAV(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsDAV(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsDAV(CSpecialProtocol::TranslatePath(strFile)); + + CURL url(strFile); + if (HasParentInHostname(url)) + return IsDAV(url.GetHostName()); + + return IsProtocol(strFile, "dav") || + IsProtocol(strFile, "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()), bStrictCheck); + + // Only consider "streamed" filesystems internet streams when being strict + if (bStrictCheck && IsStreamedFilesystem(url.Get())) + return true; + + // Check for true internetstreams + const std::string& protocol = url.GetProtocol(); + 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") || CURL::IsProtocolEqual(protocol, "shout") || + CURL::IsProtocolEqual(protocol, "rss") || CURL::IsProtocolEqual(protocol, "rsss")) + return true; + + return false; +} + +bool URIUtils::IsStreamedFilesystem(const std::string& strPath) +{ + CURL url(strPath); + + if (url.GetProtocol().empty()) + return false; + + if (url.IsProtocol("stack")) + return IsStreamedFilesystem(CStackDirectory::GetFirstStackedFile(strPath)); + + if (IsUPnP(strPath) || IsFTP(strPath) || IsHTTP(strPath, true)) + return true; + + //! @todo sftp/ssh special case has to be handled by vfs addon + if (url.IsProtocol("sftp") || url.IsProtocol("ssh")) + return true; + + return false; +} + +bool URIUtils::IsNetworkFilesystem(const std::string& strPath) +{ + CURL url(strPath); + + if (url.GetProtocol().empty()) + return false; + + if (url.IsProtocol("stack")) + return IsNetworkFilesystem(CStackDirectory::GetFirstStackedFile(strPath)); + + if (IsStreamedFilesystem(strPath)) + return true; + + if (IsSmb(strPath) || IsNfs(strPath)) + return true; + + return false; +} + +bool URIUtils::IsUPnP(const std::string& strFile) +{ + return IsProtocol(strFile, "upnp"); +} + +bool URIUtils::IsLiveTV(const std::string& strFile) +{ + std::string strFileWithoutSlash(strFile); + RemoveSlashAtEnd(strFileWithoutSlash); + + if (StringUtils::EndsWithNoCase(strFileWithoutSlash, ".pvr") && + !StringUtils::StartsWith(strFileWithoutSlash, "pvr://recordings")) + return true; + + return false; +} + +bool URIUtils::IsPVRRecording(const std::string& strFile) +{ + std::string strFileWithoutSlash(strFile); + RemoveSlashAtEnd(strFileWithoutSlash); + + return StringUtils::EndsWithNoCase(strFileWithoutSlash, ".pvr") && + StringUtils::StartsWith(strFile, "pvr://recordings"); +} + +bool URIUtils::IsPVRRecordingFileOrFolder(const std::string& strFile) +{ + return StringUtils::StartsWith(strFile, "pvr://recordings"); +} + +bool URIUtils::IsPVRTVRecordingFileOrFolder(const std::string& strFile) +{ + return StringUtils::StartsWith(strFile, "pvr://recordings/tv"); +} + +bool URIUtils::IsPVRRadioRecordingFileOrFolder(const std::string& strFile) +{ + return StringUtils::StartsWith(strFile, "pvr://recordings/radio"); +} + +bool URIUtils::IsMusicDb(const std::string& strFile) +{ + return IsProtocol(strFile, "musicdb"); +} + +bool URIUtils::IsNfs(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsNfs(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsNfs(CSpecialProtocol::TranslatePath(strFile)); + + CURL url(strFile); + if (HasParentInHostname(url)) + return IsNfs(url.GetHostName()); + + return IsProtocol(strFile, "nfs"); +} + +bool URIUtils::IsVideoDb(const std::string& strFile) +{ + return IsProtocol(strFile, "videodb"); +} + +bool URIUtils::IsBluray(const std::string& strFile) +{ + return IsProtocol(strFile, "bluray"); +} + +bool URIUtils::IsAndroidApp(const std::string &path) +{ + return IsProtocol(path, "androidapp"); +} + +bool URIUtils::IsLibraryFolder(const std::string& 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 std::string &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; +} + +std::string URIUtils::AppendSlash(std::string strFolder) +{ + AddSlashAtEnd(strFolder); + return strFolder; +} + +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); + const std::string& 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) +{ + // performance optimization. pvr guide items are mass objects, uri never has a slash at end, and this method is quite expensive... + if (IsPVRGuideItem(strFolder)) + return; + + 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 std::string& strPath1, const std::string& strPath2) +{ + std::string strc1 = strPath1, strc2 = strPath2; + RemoveSlashAtEnd(strc1); + RemoveSlashAtEnd(strc2); + return StringUtils::EqualsNoCase(strc1, 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); + std::vector<std::string> pathVec, resultVec; + StringUtils::Tokenize(path, pathVec, slashStr); + + for (std::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; +} + +std::string URIUtils::AddFileToFolder(const std::string& strFolder, + const std::string& strFile) +{ + if (IsURL(strFolder)) + { + CURL url(strFolder); + if (url.GetFileName() != strFolder) + { + url.SetFileName(AddFileToFolder(url.GetFileName(), strFile)); + return url.Get(); + } + } + + std::string 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; +} + +std::string URIUtils::GetDirectory(const std::string &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 == std::string::npos) + return ""; // No slash, so no path (ignore any options) + + size_t iPosBar = strFilePath.rfind('|'); + if (iPosBar == std::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 + 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; +} + +std::string URIUtils::GetRealPath(const std::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('\\'); + std::string delim = posSlash < posBackslash ? "/" : "\\"; + std::vector<std::string> parts = StringUtils::Split(path, delim); + std::vector<std::string> realParts; + + for (std::vector<std::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); + } + + std::string 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.size() > 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)) + { + std::vector<std::string> files; + if (!CStackDirectory::GetPaths(strFilename, files)) + return false; + + for (std::vector<std::string>::iterator file = files.begin(); file != files.end(); ++file) + UpdateUrlEncoding(*file); + + std::string 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; +} diff --git a/xbmc/utils/URIUtils.h b/xbmc/utils/URIUtils.h new file mode 100644 index 0000000..85ba81b --- /dev/null +++ b/xbmc/utils/URIUtils.h @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> +#include <vector> + +class CURL; +class CAdvancedSettings; +class CFileItem; + +class URIUtils +{ +public: + static void RegisterAdvancedSettings(const CAdvancedSettings& advancedSettings); + static void UnregisterAdvancedSettings(); + + static std::string GetDirectory(const std::string &strFilePath); + + static std::string GetFileName(const CURL& url); + static std::string GetFileName(const std::string& strFileNameAndPath); + + static std::string GetExtension(const CURL& url); + static std::string GetExtension(const std::string& strFileName); + + + /*! \brief Check if the CFileItem has a plugin path. + \param item The CFileItem. + \return true if there is a plugin path, false otherwise. + */ + static bool HasPluginPath(const CFileItem& item); + + /*! + \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 std::string& 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 separated 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 std::string& strFileName, const std::string& strExtensions); + static bool HasExtension(const CURL& url, const std::string& strExtensions); + + static void RemoveExtension(std::string& strFileName); + static std::string ReplaceExtension(const std::string& strFile, + const std::string& strNewExtension); + static void Split(const std::string& strFileNameAndPath, + std::string& strPath, std::string& strFileName); + static std::vector<std::string> SplitPath(const std::string& strPath); + + static void GetCommonPath(std::string& strParent, const std::string& strPath); + static std::string GetParentPath(const std::string& strPath); + static bool GetParentPath(const std::string& strPath, std::string& strParent); + + /*! \brief Retrieve the base path, accounting for stacks and files in rars. + \param strPath path. + \return the folder that contains the item. + */ + static std::string GetBasePath(const std::string& strPath); + + /* \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, const bool &bAddPath = true); + + static CURL SubstitutePath(const CURL& url, bool reverse = false); + static std::string SubstitutePath(const std::string& strPath, bool reverse = false); + + /*! \brief Check whether a URL is a given URL scheme. + Comparison is case-insensitive 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 PathHasParent, PathEquals + */ + static bool IsProtocol(const std::string& url, const std::string& type); + + /*! \brief Check whether a path has a given parent. + Comparison is case-sensitive. + Use IsProtocol() to compare the protocol portion only. + \param path a std::string path. + \param parent the string the parent of the path should be compared against. + \param translate whether to translate any special paths into real paths + \return true if the path has the given parent string, false otherwise. + \sa IsProtocol, PathEquals + */ + static bool PathHasParent(std::string path, std::string parent, bool translate = false); + + /*! \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. + \param ignoreTrailingSlash ignore any trailing slashes in both paths + \return true if the paths are equal, false otherwise. + \sa IsProtocol, PathHasParent + */ + static bool PathEquals(std::string path1, std::string path2, bool ignoreTrailingSlash = false, bool ignoreURLOptions = false); + + static bool IsAddonsPath(const std::string& strFile); + static bool IsSourcesPath(const std::string& strFile); + static bool IsCDDA(const std::string& strFile); + static bool IsDAV(const std::string& strFile); + static bool IsDOSPath(const std::string &path); + static bool IsDVD(const std::string& strFile); + static bool IsFTP(const std::string& strFile); + static bool IsHTTP(const std::string& strFile, bool bTranslate = false); + static bool IsUDP(const std::string& strFile); + static bool IsTCP(const std::string& strFile); + static bool IsHD(const std::string& strFileName); + static bool IsInArchive(const std::string& strFile); + static bool IsInRAR(const std::string& strFile); + static bool IsInternetStream(const std::string& path, bool bStrictCheck = false); + static bool IsInternetStream(const CURL& url, bool bStrictCheck = false); + static bool IsStreamedFilesystem(const std::string& strPath); + static bool IsNetworkFilesystem(const std::string& strPath); + static bool IsInAPK(const std::string& strFile); + static bool IsInZIP(const std::string& strFile); + static bool IsISO9660(const std::string& strFile); + static bool IsLiveTV(const std::string& strFile); + static bool IsPVRRecording(const std::string& strFile); + static bool IsPVRRecordingFileOrFolder(const std::string& strFile); + static bool IsPVRTVRecordingFileOrFolder(const std::string& strFile); + static bool IsPVRRadioRecordingFileOrFolder(const std::string& strFile); + static bool IsMultiPath(const std::string& strPath); + static bool IsMusicDb(const std::string& strFile); + static bool IsNfs(const std::string& strFile); + static bool IsOnDVD(const std::string& strFile); + static bool IsOnLAN(const std::string& strFile); + static bool IsHostOnLAN(const std::string& hostName, bool offLineCheck = false); + static bool IsPlugin(const std::string& strFile); + static bool IsScript(const std::string& strFile); + static bool IsRAR(const std::string& strFile); + static bool IsRemote(const std::string& strFile); + static bool IsSmb(const std::string& strFile); + static bool IsSpecial(const std::string& strFile); + static bool IsStack(const std::string& strFile); + static bool IsFavourite(const std::string& strFile); + static bool IsUPnP(const std::string& strFile); + static bool IsURL(const std::string& strFile); + static bool IsVideoDb(const std::string& strFile); + static bool IsAPK(const std::string& strFile); + static bool IsZIP(const std::string& strFile); + static bool IsArchive(const std::string& strFile); + static bool IsBluray(const std::string& strFile); + static bool IsAndroidApp(const std::string& strFile); + static bool IsLibraryFolder(const std::string& strFile); + static bool IsLibraryContent(const std::string& strFile); + static bool IsPVR(const std::string& strFile); + static bool IsPVRChannel(const std::string& strFile); + static bool IsPVRChannelGroup(const std::string& strFile); + static bool IsPVRGuideItem(const std::string& strFile); + + static std::string AppendSlash(std::string strFolder); + 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 std::string& strPath1, const std::string& 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 std::string AddFileToFolder(const std::string& strFolder, const std::string& strFile); + template <typename... T> + static std::string AddFileToFolder(const std::string& strFolder, const std::string& strFile, T... args) + { + auto newPath = AddFileToFolder(strFolder, strFile); + return AddFileToFolder(newPath, args...); + } + + 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); + + static const CAdvancedSettings* m_advancedSettings; +}; + diff --git a/xbmc/utils/UrlOptions.cpp b/xbmc/utils/UrlOptions.cpp new file mode 100644 index 0000000..389d08d --- /dev/null +++ b/xbmc/utils/UrlOptions.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "UrlOptions.h" + +#include "URL.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +CUrlOptions::CUrlOptions() = default; + +CUrlOptions::CUrlOptions(const std::string &options, const char *strLead /* = "" */) + : m_strLead(strLead) +{ + AddOptions(options); +} + +CUrlOptions::~CUrlOptions() = default; + +std::string CUrlOptions::GetOptionsString(bool withLeadingSeparator /* = false */) const +{ + std::string options; + for (const auto &opt : m_options) + { + if (!options.empty()) + options += "&"; + + options += CURL::Encode(opt.first); + if (!opt.second.empty()) + options += "=" + CURL::Encode(opt.second.asString()); + } + + if (withLeadingSeparator && !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, std::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; + + std::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, "{}: original leading str {} overridden by {}", __FUNCTION__, m_strLead, + strOptions.at(0)); + m_strLead = strOptions.at(0); + strOptions.erase(0, 1); + } + + // split the options by & and process them one by one + for (const auto &option : StringUtils::Split(strOptions, "&")) + { + if (option.empty()) + continue; + + std::string key, value; + + size_t pos = option.find('='); + key = CURL::Decode(option.substr(0, pos)); + if (pos != std::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; + + auto 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; + + auto option = m_options.find(key); + if (option == m_options.end()) + return false; + + value = option->second; + return true; +} diff --git a/xbmc/utils/UrlOptions.h b/xbmc/utils/UrlOptions.h new file mode 100644 index 0000000..1fa7ac6 --- /dev/null +++ b/xbmc/utils/UrlOptions.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/Variant.h" + +#include <map> +#include <string> + +class CUrlOptions +{ +public: + typedef std::map<std::string, CVariant> UrlOptions; + + CUrlOptions(); + CUrlOptions(const std::string &options, const char *strLead = ""); + virtual ~CUrlOptions(); + + void Clear() { m_options.clear(); m_strLead.clear(); } + + const UrlOptions& GetOptions() const { return m_options; } + std::string GetOptionsString(bool withLeadingSeparator = 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); + + bool HasOption(const std::string &key) const; + bool GetOption(const std::string &key, CVariant &value) const; + +protected: + UrlOptions m_options; + std::string m_strLead; +}; diff --git a/xbmc/utils/Utf8Utils.cpp b/xbmc/utils/Utf8Utils.cpp new file mode 100644 index 0000000..a45002a --- /dev/null +++ b/xbmc/utils/Utf8Utils.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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/xbmc/utils/Utf8Utils.h b/xbmc/utils/Utf8Utils.h new file mode 100644 index 0000000..a29f64a --- /dev/null +++ b/xbmc/utils/Utf8Utils.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#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/xbmc/utils/VC1BitstreamParser.cpp b/xbmc/utils/VC1BitstreamParser.cpp new file mode 100644 index 0000000..8ac1b6e --- /dev/null +++ b/xbmc/utils/VC1BitstreamParser.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "VC1BitstreamParser.h" + +#include "BitstreamReader.h" + +enum +{ + VC1_PROFILE_SIMPLE, + VC1_PROFILE_MAIN, + VC1_PROFILE_RESERVED, + VC1_PROFILE_ADVANCED, + VC1_PROFILE_NOPROFILE +}; + +enum +{ + VC1_END_OF_SEQ = 0x0A, + VC1_SLICE = 0x0B, + VC1_FIELD = 0x0C, + VC1_FRAME = 0x0D, + VC1_ENTRYPOINT = 0x0E, + VC1_SEQUENCE = 0x0F, + VC1_SLICE_USER = 0x1B, + VC1_FIELD_USER = 0x1C, + VC1_FRAME_USER = 0x1D, + VC1_ENTRY_POINT_USER = 0x1E, + VC1_SEQUENCE_USER = 0x1F +}; + +enum +{ + VC1_FRAME_PROGRESSIVE = 0x0, + VC1_FRAME_INTERLACE = 0x10, + VC1_FIELD_INTERLACE = 0x11 +}; + +CVC1BitstreamParser::CVC1BitstreamParser() +{ + Reset(); +} + +void CVC1BitstreamParser::Reset() +{ + m_Profile = VC1_PROFILE_NOPROFILE; +} + +bool CVC1BitstreamParser::IsRecoveryPoint(const uint8_t *buf, int buf_size) +{ + return vc1_parse_frame(buf, buf + buf_size, true); +}; + +bool CVC1BitstreamParser::IsIFrame(const uint8_t *buf, int buf_size) +{ + return vc1_parse_frame(buf, buf + buf_size, false); +}; + +bool CVC1BitstreamParser::vc1_parse_frame(const uint8_t *buf, const uint8_t *buf_end, bool sequence_only) +{ + uint32_t state = -1; + for (;;) + { + buf = find_start_code(buf, buf_end, &state); + if (buf >= buf_end) + break; + if (buf[-1] == VC1_SEQUENCE) + { + if (m_Profile != VC1_PROFILE_NOPROFILE) + return false; + CBitstreamReader br(buf, buf_end - buf); + // Read the profile + m_Profile = static_cast<uint8_t>(br.ReadBits(2)); + if (m_Profile == VC1_PROFILE_ADVANCED) + { + br.SkipBits(39); + m_AdvInterlace = br.ReadBits(1); + } + else + { + br.SkipBits(22); + + m_SimpleSkipBits = 2; + if (br.ReadBits(1)) //rangered + ++m_SimpleSkipBits; + + m_MaxBFrames = br.ReadBits(3); + + br.SkipBits(2); // quantizer + if (br.ReadBits(1)) //finterpflag + ++m_SimpleSkipBits; + } + if (sequence_only) + return true; + } + else if (buf[-1] == VC1_FRAME) + { + CBitstreamReader br(buf, buf_end - buf); + + if (sequence_only) + return false; + if (m_Profile == VC1_PROFILE_ADVANCED) + { + uint8_t fcm; + if (m_AdvInterlace) { + fcm = br.ReadBits(1); + if (fcm) + fcm = br.ReadBits(1) + 1; + } + else + fcm = VC1_FRAME_PROGRESSIVE; + if (fcm == VC1_FIELD_INTERLACE) { + uint8_t pic = br.ReadBits(3); + return pic == 0x00 || pic == 0x01; + } + else + { + uint8_t pic(0); + while (pic < 4 && br.ReadBits(1))++pic; + return pic == 2; + } + return false; + } + else if (m_Profile != VC1_PROFILE_NOPROFILE) + { + br.SkipBits(m_SimpleSkipBits); // quantizer + uint8_t pic(br.ReadBits(1)); + if (m_MaxBFrames) { + if (!pic) { + pic = br.ReadBits(1); + return pic != 0; + } + else + return false; + } + else + return pic != 0; + } + else + break; + } + } + return false; +} diff --git a/xbmc/utils/VC1BitstreamParser.h b/xbmc/utils/VC1BitstreamParser.h new file mode 100644 index 0000000..882160c --- /dev/null +++ b/xbmc/utils/VC1BitstreamParser.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <stdint.h> + +class CVC1BitstreamParser +{ +public: + CVC1BitstreamParser(); + ~CVC1BitstreamParser() = default; + + void Reset(); + + inline bool IsRecoveryPoint(const uint8_t *buf, int buf_size); + inline bool IsIFrame(const uint8_t *buf, int buf_size); + +protected: + bool vc1_parse_frame(const uint8_t *buf, const uint8_t *buf_end, bool sequenceOnly); +private: + uint8_t m_Profile; + uint8_t m_MaxBFrames; + uint8_t m_SimpleSkipBits; + uint8_t m_AdvInterlace; +}; diff --git a/xbmc/utils/Variant.cpp b/xbmc/utils/Variant.cpp new file mode 100644 index 0000000..a9327c9 --- /dev/null +++ b/xbmc/utils/Variant.cpp @@ -0,0 +1,885 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Variant.h" + +#include <stdlib.h> +#include <string.h> +#include <utility> + +#ifndef strtoll +#ifdef TARGET_WINDOWS +#define strtoll _strtoi64 +#define strtoull _strtoui64 +#define wcstoll _wcstoi64 +#define wcstoull _wcstoui64 +#else // TARGET_WINDOWS +#if !defined(TARGET_DARWIN) +#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 +#endif // TARGET_WINDOWS +#endif // strtoll + +std::string trimRight(const std::string &str) +{ + std::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; +} + +std::wstring trimRight(const std::wstring &str) +{ + std::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 std::string &str, int64_t fallback /* = 0 */) +{ + char *end = NULL; + std::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 std::wstring &str, int64_t fallback /* = 0 */) +{ + wchar_t *end = NULL; + std::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 std::string &str, uint64_t fallback /* = 0 */) +{ + char *end = NULL; + std::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 std::wstring &str, uint64_t fallback /* = 0 */) +{ + wchar_t *end = NULL; + std::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 std::string &str, double fallback /* = 0.0 */) +{ + char *end = NULL; + std::string tmp = trimRight(str); + double result = strtod(tmp.c_str(), &end); + if (end == NULL || *end == '\0') + return result; + + return fallback; +} + +double str2double(const std::wstring &str, double fallback /* = 0.0 */) +{ + wchar_t *end = NULL; + std::wstring tmp = trimRight(str); + double result = wcstod(tmp.c_str(), &end); + if (end == NULL || *end == '\0') + return result; + + return fallback; +} + +CVariant::CVariant() + : CVariant(VariantTypeNull) +{ +} + +CVariant CVariant::ConstNullVariant = CVariant::VariantTypeConstNull; +CVariant::VariantArray CVariant::EMPTY_ARRAY; +CVariant::VariantMap CVariant::EMPTY_MAP; + +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 std::string(); + break; + case VariantTypeWideString: + m_data.wstring = new std::wstring(); + break; + case VariantTypeArray: + m_data.array = new VariantArray(); + break; + case VariantTypeObject: + m_data.map = new VariantMap(); + break; + default: +#ifndef TARGET_WINDOWS_STORE // this corrupts the heap in Win10 UWP version + memset(&m_data, 0, sizeof(m_data)); +#endif + 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 std::string(str); +} + +CVariant::CVariant(const char *str, unsigned int length) +{ + m_type = VariantTypeString; + m_data.string = new std::string(str, length); +} + +CVariant::CVariant(const std::string &str) +{ + m_type = VariantTypeString; + m_data.string = new std::string(str); +} + +CVariant::CVariant(std::string &&str) +{ + m_type = VariantTypeString; + m_data.string = new std::string(std::move(str)); +} + +CVariant::CVariant(const wchar_t *str) +{ + m_type = VariantTypeWideString; + m_data.wstring = new std::wstring(str); +} + +CVariant::CVariant(const wchar_t *str, unsigned int length) +{ + m_type = VariantTypeWideString; + m_data.wstring = new std::wstring(str, length); +} + +CVariant::CVariant(const std::wstring &str) +{ + m_type = VariantTypeWideString; + m_data.wstring = new std::wstring(str); +} + +CVariant::CVariant(std::wstring &&str) +{ + m_type = VariantTypeWideString; + m_data.wstring = new std::wstring(std::move(str)); +} + +CVariant::CVariant(const std::vector<std::string> &strArray) +{ + m_type = VariantTypeArray; + m_data.array = new VariantArray; + m_data.array->reserve(strArray.size()); + for (const auto& item : strArray) + m_data.array->push_back(CVariant(item)); +} + +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(CVariant&& rhs) noexcept +{ + //Set this so that operator= don't try and run cleanup + //when we're not initialized. + m_type = VariantTypeNull; + + *this = std::move(rhs); +} + +CVariant::~CVariant() +{ + cleanup(); +} + +void CVariant::cleanup() +{ + switch (m_type) + { + case VariantTypeString: + delete m_data.string; + m_data.string = nullptr; + break; + + case VariantTypeWideString: + delete m_data.wstring; + m_data.wstring = nullptr; + break; + + case VariantTypeArray: + delete m_data.array; + m_data.array = nullptr; + break; + + case VariantTypeObject: + delete m_data.map; + m_data.map = nullptr; + break; + default: + break; + } + m_type = VariantTypeNull; +} + +bool CVariant::isInteger() const +{ + return isSignedInteger() || isUnsignedInteger(); +} + +bool CVariant::isSignedInteger() 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; +} + +int32_t CVariant::asInteger32(int32_t fallback) const +{ + return static_cast<int32_t>(asInteger(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; +} + +uint32_t CVariant::asUnsignedInteger32(uint32_t fallback) const +{ + return static_cast<uint32_t>(asUnsignedInteger(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, static_cast<double>(fallback)); + case VariantTypeWideString: + return (float)str2double(*m_data.wstring, static_cast<double>(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: + return std::to_string(m_data.integer); + case VariantTypeUnsignedInteger: + return std::to_string(m_data.unsignedinteger); + case VariantTypeDouble: + return std::to_string(m_data.dvalue); + 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: + return std::to_wstring(m_data.integer); + case VariantTypeUnsignedInteger: + return std::to_wstring(m_data.unsignedinteger); + case VariantTypeDouble: + return std::to_wstring(m_data.dvalue); + 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.unsignedinteger = 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 std::string(*rhs.m_data.string); + break; + case VariantTypeWideString: + m_data.wstring = new std::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; +} + +CVariant& CVariant::operator=(CVariant&& rhs) noexcept +{ + if (m_type == VariantTypeConstNull || this == &rhs) + return *this; + + //Make sure that if we're moved into we don't leak any pointers + if (m_type != VariantTypeNull) + cleanup(); + + m_type = rhs.m_type; + m_data = rhs.m_data; + + //Should be enough to just set m_type here + //but better safe than sorry, could probably lead to coverity warnings + if (rhs.m_type == VariantTypeString) + rhs.m_data.string = nullptr; + else if (rhs.m_type == VariantTypeWideString) + rhs.m_data.wstring = nullptr; + else if (rhs.m_type == VariantTypeArray) + rhs.m_data.array = nullptr; + else if (rhs.m_type == VariantTypeObject) + rhs.m_data.map = nullptr; + + rhs.m_type = VariantTypeNull; + + 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::reserve(size_t length) +{ + if (m_type == VariantTypeNull) + { + m_type = VariantTypeArray; + m_data.array = new VariantArray; + } + if (m_type == VariantTypeArray) + m_data.array->reserve(length); +} + +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::push_back(CVariant &&variant) +{ + if (m_type == VariantTypeNull) + { + m_type = VariantTypeArray; + m_data.array = new VariantArray; + } + + if (m_type == VariantTypeArray) + m_data.array->push_back(std::move(variant)); +} + +void CVariant::append(const CVariant &variant) +{ + push_back(variant); +} + +void CVariant::append(CVariant&& variant) +{ + push_back(std::move(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 EMPTY_ARRAY.begin(); +} + +CVariant::const_iterator_array CVariant::begin_array() const +{ + if (m_type == VariantTypeArray) + return m_data.array->begin(); + else + return EMPTY_ARRAY.begin(); +} + +CVariant::iterator_array CVariant::end_array() +{ + if (m_type == VariantTypeArray) + return m_data.array->end(); + else + return EMPTY_ARRAY.end(); +} + +CVariant::const_iterator_array CVariant::end_array() const +{ + if (m_type == VariantTypeArray) + return m_data.array->end(); + else + return EMPTY_ARRAY.end(); +} + +CVariant::iterator_map CVariant::begin_map() +{ + if (m_type == VariantTypeObject) + return m_data.map->begin(); + else + return EMPTY_MAP.begin(); +} + +CVariant::const_iterator_map CVariant::begin_map() const +{ + if (m_type == VariantTypeObject) + return m_data.map->begin(); + else + return EMPTY_MAP.begin(); +} + +CVariant::iterator_map CVariant::end_map() +{ + if (m_type == VariantTypeObject) + return m_data.map->end(); + else + return EMPTY_MAP.end(); +} + +CVariant::const_iterator_map CVariant::end_map() const +{ + if (m_type == VariantTypeObject) + return m_data.map->end(); + else + return EMPTY_MAP.end(); +} + +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/xbmc/utils/Variant.h b/xbmc/utils/Variant.h new file mode 100644 index 0000000..9d48a3d --- /dev/null +++ b/xbmc/utils/Variant.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <map> +#include <stdint.h> +#include <string> +#include <vector> +#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); + +#ifdef TARGET_WINDOWS_STORE +#pragma pack(push) +#pragma pack(8) +#endif + +class CVariant +{ +public: + enum VariantType + { + VariantTypeInteger, + VariantTypeUnsignedInteger, + VariantTypeBoolean, + VariantTypeString, + VariantTypeWideString, + VariantTypeDouble, + VariantTypeArray, + VariantTypeObject, + VariantTypeNull, + VariantTypeConstNull + }; + + CVariant(); + CVariant(VariantType type); + 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(std::string &&str); + CVariant(const wchar_t *str); + CVariant(const wchar_t *str, unsigned int length); + CVariant(const std::wstring &str); + CVariant(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(CVariant&& rhs) noexcept; + ~CVariant(); + + + + bool isInteger() const; + bool isSignedInteger() 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; + int32_t asInteger32(int32_t fallback = 0) const; + uint64_t asUnsignedInteger(uint64_t fallback = 0u) const; + uint32_t asUnsignedInteger32(uint32_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); + CVariant& operator=(CVariant&& rhs) noexcept; + bool operator==(const CVariant &rhs) const; + bool operator!=(const CVariant &rhs) const { return !(*this == rhs); } + + void reserve(size_t length); + void push_back(const CVariant &variant); + void push_back(CVariant &&variant); + void append(const CVariant &variant); + void append(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; + + static VariantArray EMPTY_ARRAY; + static VariantMap EMPTY_MAP; +}; + +#ifdef TARGET_WINDOWS_STORE +#pragma pack(pop) +#endif + diff --git a/xbmc/utils/Vector.cpp b/xbmc/utils/Vector.cpp new file mode 100644 index 0000000..1f3a2fd --- /dev/null +++ b/xbmc/utils/Vector.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Vector.h" + +#include <math.h> + +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::length() const +{ + return sqrt(pow(x, 2) + pow(y, 2)); +} diff --git a/xbmc/utils/Vector.h b/xbmc/utils/Vector.h new file mode 100644 index 0000000..f427ec9 --- /dev/null +++ b/xbmc/utils/Vector.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class CVector +{ +public: + CVector() = default; + constexpr CVector(float xCoord, float yCoord):x(xCoord), y(yCoord) {} + + constexpr CVector operator+(const CVector &other) const + { + return CVector(x + other.x, y + other.y); + } + + constexpr CVector operator-(const CVector &other) const + { + return CVector(x - other.x, y - other.y); + } + + CVector& operator+=(const CVector &other); + CVector& operator-=(const CVector &other); + + constexpr float scalar(const CVector &other) const + { + return x * other.x + y * other.y; + } + + float length() const; + + float x = 0; + float y = 0; +}; diff --git a/xbmc/utils/XBMCTinyXML.cpp b/xbmc/utils/XBMCTinyXML.cpp new file mode 100644 index 0000000..612ddf2 --- /dev/null +++ b/xbmc/utils/XBMCTinyXML.cpp @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "XBMCTinyXML.h" + +#include "LangInfo.h" +#include "RegExp.h" +#include "filesystem/File.h" +#include "utils/CharsetConverter.h" +#include "utils/CharsetDetection.h" +#include "utils/StringUtils.h" +#include "utils/Utf8Utils.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; + std::vector<uint8_t> 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(reinterpret_cast<char*>(buffer.data()), buffer.size()); + buffer.clear(); // free memory early + + if (encoding == TIXML_ENCODING_UNKNOWN) + Parse(data, file.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET)); + 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] = {}; + 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); + bool suc = file.Write(printer.CStr(), printer.Size()) == static_cast<ssize_t>(printer.Size()); + if (suc) + file.Flush(); + + return suc; + } + return false; +} + +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, + "{}: \"{}\" charset was used instead of suggested charset \"{}\" for {}", + __FUNCTION__, m_UsedCharset, m_SuggestedCharset, + (value.empty() ? "XML data" : ("file \"" + value + "\""))); + + 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, + "{}: \"{}\" charset was used instead of suggested charset \"{}\" for {}", + __FUNCTION__, m_UsedCharset, m_SuggestedCharset, + (value.empty() ? "XML data" : ("file \"" + value + "\""))); + else if (!detectedCharset.empty()) + CLog::Log(LOGWARNING, "{}: \"{}\" charset was used instead of detected charset \"{}\" for {}", + __FUNCTION__, m_UsedCharset, detectedCharset, + (value.empty() ? "XML data" : ("file \"" + value + "\""))); + return true; + } + + // fallback: try user GUI charset + if (TryParse(data, g_langInfo.GetGuiCharSet())) + { + if (!m_SuggestedCharset.empty()) + CLog::Log(LOGWARNING, + "{}: \"{}\" charset was used instead of suggested charset \"{}\" for {}", + __FUNCTION__, m_UsedCharset, m_SuggestedCharset, + (value.empty() ? "XML data" : ("file \"" + value + "\""))); + else if (!detectedCharset.empty()) + CLog::Log(LOGWARNING, "{}: \"{}\" charset was used instead of detected charset \"{}\" for {}", + __FUNCTION__, m_UsedCharset, detectedCharset, + (value.empty() ? "XML data" : ("file \"" + value + "\""))); + 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, "{}: Processed {} as unknown encoding instead of suggested \"{}\"", + __FUNCTION__, (value.empty() ? "XML data" : ("file \"" + value + "\"")), + m_SuggestedCharset); + else if (!detectedCharset.empty()) + CLog::Log(LOGWARNING, "{}: Processed {} as unknown encoding instead of detected \"{}\"", + __FUNCTION__, (value.empty() ? "XML data" : ("file \"" + value + "\"")), + detectedCharset); + 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, TIXML_DEFAULT_ENCODING); + 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/xbmc/utils/XBMCTinyXML.h b/xbmc/utils/XBMCTinyXML.h new file mode 100644 index 0000000..2f4e188 --- /dev/null +++ b/xbmc/utils/XBMCTinyXML.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#ifndef TARGET_WINDOWS +//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(); + explicit CXBMCTinyXML(const char*); + explicit 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 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: + using TiXmlDocument::Parse; + 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/xbmc/utils/XMLUtils.cpp b/xbmc/utils/XMLUtils.cpp new file mode 100644 index 0000000..8e8603e --- /dev/null +++ b/xbmc/utils/XMLUtils.cpp @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "XMLUtils.h" + +#include "StringUtils.h" +#include "URL.h" +#include "XBDateTime.h" + +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", &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 && StringUtils::CompareNoCase(encoded, "yes") == 0) + strStringValue = CURL::Decode(strStringValue); + return true; + } + strStringValue.clear(); + return true; +} + +std::string XMLUtils::GetString(const TiXmlNode* pRootNode, const char* strTag) +{ + std::string temp; + GetString(pRootNode, strTag, temp); + return temp; +} + +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 && StringUtils::CompareNoCase(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 && StringUtils::CompareNoCase(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 && StringUtils::CompareNoCase(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)); +} + +TiXmlNode* 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); + } + return pNewNode; +} + +TiXmlNode* XMLUtils::SetInt(TiXmlNode* pRootNode, const char *strTag, int value) +{ + std::string strValue = std::to_string(value); + return SetString(pRootNode, strTag, strValue); +} + +void XMLUtils::SetLong(TiXmlNode* pRootNode, const char *strTag, long value) +{ + std::string strValue = std::to_string(value); + SetString(pRootNode, strTag, strValue); +} + +TiXmlNode* XMLUtils::SetFloat(TiXmlNode* pRootNode, const char *strTag, float value) +{ + std::string strValue = StringUtils::Format("{:f}", value); + return SetString(pRootNode, strTag, strValue); +} + +TiXmlNode* XMLUtils::SetDouble(TiXmlNode* pRootNode, const char* strTag, double value) +{ + std::string strValue = StringUtils::Format("{:f}", value); + return 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/xbmc/utils/XMLUtils.h b/xbmc/utils/XMLUtils.h new file mode 100644 index 0000000..fcd23bd --- /dev/null +++ b/xbmc/utils/XMLUtils.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/XBMCTinyXML.h" + +#include <stdint.h> +#include <string> +#include <vector> + +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); + + /*! \brief Get a string value from the xml tag + If the specified tag isn't found strStringvalue is not modified and will contain whatever + value it had before the method call. + + \param[in] pRootNode the xml node that contains the tag + \param[in] strTag the xml tag to read from + \param[in,out] strStringValue where to store the read string + \return true on success, false if the tag isn't found + */ + static bool GetString(const TiXmlNode* pRootNode, const char* strTag, std::string& strStringValue); + + /*! \brief Get a string value from the xml tag + + \param[in] pRootNode the xml node that contains the tag + \param[in] strTag the tag to read from + + \return the value in the specified tag or an empty string if the tag isn't found + */ + static std::string GetString(const TiXmlNode* pRootNode, const char* strTag); + /*! \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 TiXmlNode* 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 TiXmlNode* SetInt(TiXmlNode* pRootNode, const char *strTag, int value); + static TiXmlNode* SetFloat(TiXmlNode* pRootNode, const char *strTag, float value); + static TiXmlNode* SetDouble(TiXmlNode* pRootNode, const char* strTag, double 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/xbmc/utils/XSLTUtils.cpp b/xbmc/utils/XSLTUtils.cpp new file mode 100644 index 0000000..ba069ba --- /dev/null +++ b/xbmc/utils/XSLTUtils.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "XSLTUtils.h" +#include "log.h" +#include <libxslt/xslt.h> +#include <libxslt/transform.h> + +#ifndef TARGET_WINDOWS +#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: {}", string); +} + +XSLTUtils::XSLTUtils() +{ + // 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/xbmc/utils/XSLTUtils.h b/xbmc/utils/XSLTUtils.h new file mode 100644 index 0000000..78221b9 --- /dev/null +++ b/xbmc/utils/XSLTUtils.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#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 = nullptr; + xmlDocPtr m_xmlOutput = nullptr; + xmlDocPtr m_xmlStylesheet = nullptr; + xsltStylesheetPtr m_xsltStylesheet = nullptr; +}; diff --git a/xbmc/utils/XTimeUtils.h b/xbmc/utils/XTimeUtils.h new file mode 100644 index 0000000..91cda40 --- /dev/null +++ b/xbmc/utils/XTimeUtils.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <chrono> +#include <string> +#include <thread> + +#if !defined(TARGET_WINDOWS) +#include "PlatformDefs.h" +#else +// This is needed, a forward declaration of FILETIME +// breaks everything +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <Windows.h> +#endif + +namespace KODI +{ +namespace TIME +{ +struct SystemTime +{ + unsigned short year; + unsigned short month; + unsigned short dayOfWeek; + unsigned short day; + unsigned short hour; + unsigned short minute; + unsigned short second; + unsigned short milliseconds; +}; + +struct TimeZoneInformation +{ + long bias; + std::string standardName; + SystemTime standardDate; + long standardBias; + std::string daylightName; + SystemTime daylightDate; + long daylightBias; +}; + +constexpr int KODI_TIME_ZONE_ID_INVALID{-1}; +constexpr int KODI_TIME_ZONE_ID_UNKNOWN{0}; +constexpr int KODI_TIME_ZONE_ID_STANDARD{1}; +constexpr int KODI_TIME_ZONE_ID_DAYLIGHT{2}; + +struct FileTime +{ + unsigned int lowDateTime; + unsigned int highDateTime; +}; + +void GetLocalTime(SystemTime* systemTime); +uint32_t GetTimeZoneInformation(TimeZoneInformation* timeZoneInformation); + +template<typename Rep, typename Period> +void Sleep(std::chrono::duration<Rep, Period> duration) +{ + if (duration == std::chrono::duration<Rep, Period>::zero()) + { + std::this_thread::yield(); + return; + } + + std::this_thread::sleep_for(duration); +} + +int FileTimeToLocalFileTime(const FileTime* fileTime, FileTime* localFileTime); +int SystemTimeToFileTime(const SystemTime* systemTime, FileTime* fileTime); +long CompareFileTime(const FileTime* fileTime1, const FileTime* fileTime2); +int FileTimeToSystemTime(const FileTime* fileTime, SystemTime* systemTime); +int LocalFileTimeToFileTime(const FileTime* LocalFileTime, FileTime* fileTime); + +int FileTimeToTimeT(const FileTime* localFileTime, time_t* pTimeT); +int TimeTToFileTime(time_t timeT, FileTime* localFileTime); +} // namespace TIME +} // namespace KODI diff --git a/xbmc/utils/log.cpp b/xbmc/utils/log.cpp new file mode 100644 index 0000000..da5771e --- /dev/null +++ b/xbmc/utils/log.cpp @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "log.h" + +#include "CompileInfo.h" +#include "ServiceBroker.h" +#include "filesystem/File.h" +#include "guilib/LocalizeStrings.h" +#include "settings/SettingUtils.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingsManager.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + +#include <cstring> +#include <set> + +#include <spdlog/sinks/basic_file_sink.h> +#include <spdlog/sinks/dist_sink.h> +#include <spdlog/sinks/dup_filter_sink.h> + +namespace +{ +static constexpr unsigned char Utf8Bom[3] = {0xEF, 0xBB, 0xBF}; +static const std::string LogFileExtension = ".log"; +static const std::string LogPattern = "%Y-%m-%d %T.%e T:%-5t %7l <%n>: %v"; +} // namespace + +CLog::CLog() + : m_platform(IPlatformLog::CreatePlatformLog()), + m_sinks(std::make_shared<spdlog::sinks::dist_sink_mt>()), + m_defaultLogger(CreateLogger("general")), + m_logLevel(LOG_LEVEL_DEBUG), + m_componentLogEnabled(false), + m_componentLogLevels(0) +{ + // add platform-specific debug sinks + m_platform->AddSinks(m_sinks); + + // register the default logger with spdlog + spdlog::set_default_logger(m_defaultLogger); + + // set the formatting pattern globally + spdlog::set_pattern(LogPattern); + + // flush on debug logs + spdlog::flush_on(spdlog::level::debug); + + // set the log level + SetLogLevel(m_logLevel); +} + +CLog::~CLog() +{ + spdlog::drop("general"); +} + +void CLog::OnSettingsLoaded() +{ + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + m_componentLogEnabled = settings->GetBool(CSettings::SETTING_DEBUG_EXTRALOGGING); + SetComponentLogLevel(settings->GetList(CSettings::SETTING_DEBUG_SETEXTRALOGLEVEL)); +} + +void CLog::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + const std::string& settingId = setting->GetId(); + if (settingId == CSettings::SETTING_DEBUG_EXTRALOGGING) + m_componentLogEnabled = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + else if (settingId == CSettings::SETTING_DEBUG_SETEXTRALOGLEVEL) + SetComponentLogLevel( + CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(setting))); +} + +void CLog::Initialize(const std::string& path) +{ + if (m_fileSink != nullptr) + return; + + // register setting callbacks + auto settingsManager = + CServiceBroker::GetSettingsComponent()->GetSettings()->GetSettingsManager(); + settingsManager->RegisterSettingOptionsFiller("loggingcomponents", + SettingOptionsLoggingComponentsFiller); + settingsManager->RegisterSettingsHandler(this); + std::set<std::string> settingSet; + settingSet.insert(CSettings::SETTING_DEBUG_EXTRALOGGING); + settingSet.insert(CSettings::SETTING_DEBUG_SETEXTRALOGLEVEL); + settingsManager->RegisterCallback(this, settingSet); + + if (path.empty()) + return; + + // put together the path to the log file(s) + std::string appName = CCompileInfo::GetAppName(); + StringUtils::ToLower(appName); + const std::string filePathBase = URIUtils::AddFileToFolder(path, appName); + const std::string filePath = filePathBase + LogFileExtension; + const std::string oldFilePath = filePathBase + ".old" + LogFileExtension; + + // handle old.log by deleting an existing old.log and renaming the last log to old.log + XFILE::CFile::Delete(oldFilePath); + XFILE::CFile::Rename(filePath, oldFilePath); + + // write UTF-8 BOM + { + XFILE::CFile file; + if (file.OpenForWrite(filePath, true)) + file.Write(Utf8Bom, sizeof(Utf8Bom)); + } + + // create the file sink within a duplicate filter sink + auto duplicateFilterSink = + std::make_shared<spdlog::sinks::dup_filter_sink_st>(std::chrono::seconds(10)); + auto basicFileSink = std::make_shared<spdlog::sinks::basic_file_sink_st>( + m_platform->GetLogFilename(filePath), false); + basicFileSink->set_pattern(LogPattern); + duplicateFilterSink->add_sink(basicFileSink); + m_fileSink = duplicateFilterSink; + + // add it to the existing sinks + m_sinks->add_sink(m_fileSink); +} + +void CLog::UnregisterFromSettings() +{ + // unregister setting callbacks + auto settingsManager = + CServiceBroker::GetSettingsComponent()->GetSettings()->GetSettingsManager(); + settingsManager->UnregisterSettingOptionsFiller("loggingcomponents"); + settingsManager->UnregisterSettingsHandler(this); + settingsManager->UnregisterCallback(this); +} + +void CLog::Deinitialize() +{ + if (m_fileSink == nullptr) + return; + + // flush all loggers + spdlog::apply_all([](const std::shared_ptr<spdlog::logger>& logger) { logger->flush(); }); + + // flush the file sink + m_fileSink->flush(); + + // remove and destroy the file sink + m_sinks->remove_sink(m_fileSink); + m_fileSink.reset(); +} + +void CLog::SetLogLevel(int level) +{ + if (level < LOG_LEVEL_NONE || level > LOG_LEVEL_MAX) + return; + + m_logLevel = level; + + auto spdLevel = spdlog::level::info; + if (level <= LOG_LEVEL_NONE) + spdLevel = spdlog::level::off; + else if (level >= LOG_LEVEL_DEBUG) + spdLevel = spdlog::level::trace; + + if (m_defaultLogger != nullptr && m_defaultLogger->level() == spdLevel) + return; + + spdlog::set_level(spdLevel); + FormatAndLogInternal(spdlog::level::info, "Log level changed to \"{}\"", + spdlog::level::to_string_view(spdLevel)); +} + +bool CLog::IsLogLevelLogged(int loglevel) +{ + if (m_logLevel >= LOG_LEVEL_DEBUG) + return true; + if (m_logLevel <= LOG_LEVEL_NONE) + return false; + + return (loglevel & LOGMASK) >= LOGINFO; +} + +bool CLog::CanLogComponent(uint32_t component) const +{ + if (!m_componentLogEnabled || component == 0) + return false; + + return ((m_componentLogLevels & component) == component); +} + +void CLog::SettingOptionsLoggingComponentsFiller(const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + list.emplace_back(g_localizeStrings.Get(669), LOGSAMBA); + list.emplace_back(g_localizeStrings.Get(670), LOGCURL); + list.emplace_back(g_localizeStrings.Get(672), LOGFFMPEG); + list.emplace_back(g_localizeStrings.Get(675), LOGJSONRPC); + list.emplace_back(g_localizeStrings.Get(676), LOGAUDIO); + list.emplace_back(g_localizeStrings.Get(680), LOGVIDEO); + list.emplace_back(g_localizeStrings.Get(683), LOGAVTIMING); + list.emplace_back(g_localizeStrings.Get(684), LOGWINDOWING); + list.emplace_back(g_localizeStrings.Get(685), LOGPVR); + list.emplace_back(g_localizeStrings.Get(686), LOGEPG); + list.emplace_back(g_localizeStrings.Get(39117), LOGANNOUNCE); +#ifdef HAS_DBUS + list.emplace_back(g_localizeStrings.Get(674), LOGDBUS); +#endif +#ifdef HAS_WEB_SERVER + list.emplace_back(g_localizeStrings.Get(681), LOGWEBSERVER); +#endif +#ifdef HAS_AIRTUNES + list.emplace_back(g_localizeStrings.Get(677), LOGAIRTUNES); +#endif +#ifdef HAS_UPNP + list.emplace_back(g_localizeStrings.Get(678), LOGUPNP); +#endif +#ifdef HAVE_LIBCEC + list.emplace_back(g_localizeStrings.Get(679), LOGCEC); +#endif + list.emplace_back(g_localizeStrings.Get(682), LOGDATABASE); +#if defined(HAS_FILESYSTEM_SMB) + list.emplace_back(g_localizeStrings.Get(37050), LOGWSDISCOVERY); +#endif +} + +Logger CLog::GetLogger(const std::string& loggerName) +{ + auto logger = spdlog::get(loggerName); + if (logger == nullptr) + logger = CreateLogger(loggerName); + + return logger; +} + +CLog& CLog::GetInstance() +{ + return CServiceBroker::GetLogging(); +} + +spdlog::level::level_enum CLog::MapLogLevel(int level) +{ + switch (level) + { + case LOGDEBUG: + return spdlog::level::debug; + case LOGINFO: + return spdlog::level::info; + case LOGWARNING: + return spdlog::level::warn; + case LOGERROR: + return spdlog::level::err; + case LOGFATAL: + return spdlog::level::critical; + case LOGNONE: + return spdlog::level::off; + + default: + break; + } + + return spdlog::level::info; +} + +Logger CLog::CreateLogger(const std::string& loggerName) +{ + // create the logger + auto logger = std::make_shared<spdlog::logger>(loggerName, m_sinks); + + // initialize the logger + spdlog::initialize_logger(logger); + + return logger; +} + +void CLog::SetComponentLogLevel(const std::vector<CVariant>& components) +{ + m_componentLogLevels = 0; + for (const auto& component : components) + { + if (!component.isInteger()) + continue; + + m_componentLogLevels |= static_cast<uint32_t>(component.asInteger()); + } +} + +void CLog::FormatLineBreaks(std::string& message) +{ + StringUtils::Replace(message, "\n", "\n "); +} diff --git a/xbmc/utils/log.h b/xbmc/utils/log.h new file mode 100644 index 0000000..1c42c88 --- /dev/null +++ b/xbmc/utils/log.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +// spdlog specific defines +// clang-format off +#include <string_view> +#define SPDLOG_LEVEL_NAMES \ +{ \ + std::string_view{"TRACE"}, \ + std::string_view{"DEBUG"}, \ + std::string_view{"INFO"}, \ + std::string_view{"WARNING"}, \ + std::string_view{"ERROR"}, \ + std::string_view{"FATAL"}, \ + std::string_view{"OFF"} \ +}; +// clang-format on + +#include "commons/ilog.h" +#include "settings/lib/ISettingCallback.h" +#include "settings/lib/ISettingsHandler.h" +#include "settings/lib/SettingDefinitions.h" +#include "utils/IPlatformLog.h" +#include "utils/logtypes.h" + +#include <string> +#include <vector> + +#include <spdlog/spdlog.h> + +namespace spdlog +{ +namespace sinks +{ +class sink; + +template<typename Mutex> +class dist_sink; +} // namespace sinks +} // namespace spdlog + +#if FMT_VERSION >= 100000 +using fmt::enums::format_as; + +namespace fmt +{ +template<typename T, typename Char> +struct formatter<std::atomic<T>, Char> : formatter<T, Char> +{ +}; +} // namespace fmt +#endif + +class CLog : public ISettingsHandler, public ISettingCallback +{ +public: + CLog(); + ~CLog(); + + // implementation of ISettingsHandler + void OnSettingsLoaded() override; + + // implementation of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + void Initialize(const std::string& path); + void UnregisterFromSettings(); + void Deinitialize(); + + void SetLogLevel(int level); + int GetLogLevel() { return m_logLevel; } + bool IsLogLevelLogged(int loglevel); + + bool CanLogComponent(uint32_t component) const; + static void SettingOptionsLoggingComponentsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + + Logger GetLogger(const std::string& loggerName); + + template<typename... Args> + static inline void Log(int level, const std::string_view& format, Args&&... args) + { + Log(MapLogLevel(level), format, std::forward<Args>(args)...); + } + + template<typename... Args> + static inline void Log(int level, + uint32_t component, + const std::string_view& format, + Args&&... args) + { + if (!GetInstance().CanLogComponent(component)) + return; + + Log(level, format, std::forward<Args>(args)...); + } + + template<typename... Args> + static inline void Log(spdlog::level::level_enum level, + const std::string_view& format, + Args&&... args) + { + GetInstance().FormatAndLogInternal(level, format, std::forward<Args>(args)...); + } + + template<typename... Args> + static inline void Log(spdlog::level::level_enum level, + uint32_t component, + const std::string_view& format, + Args&&... args) + { + if (!GetInstance().CanLogComponent(component)) + return; + + Log(level, format, std::forward<Args>(args)...); + } + +#define LogF(level, format, ...) Log((level), ("{}: " format), __FUNCTION__, ##__VA_ARGS__) +#define LogFC(level, component, format, ...) \ + Log((level), (component), ("{}: " format), __FUNCTION__, ##__VA_ARGS__) + +private: + static CLog& GetInstance(); + + static spdlog::level::level_enum MapLogLevel(int level); + + template<typename... Args> + inline void FormatAndLogInternal(spdlog::level::level_enum level, + const std::string_view& format, + Args&&... args) + { + auto message = fmt::format(format, std::forward<Args>(args)...); + + // fixup newline alignment, number of spaces should equal prefix length + FormatLineBreaks(message); + + m_defaultLogger->log(level, message); + } + + Logger CreateLogger(const std::string& loggerName); + + void SetComponentLogLevel(const std::vector<CVariant>& components); + + void FormatLineBreaks(std::string& message); + + std::unique_ptr<IPlatformLog> m_platform; + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> m_sinks; + Logger m_defaultLogger; + + std::shared_ptr<spdlog::sinks::sink> m_fileSink; + + int m_logLevel; + + bool m_componentLogEnabled; + uint32_t m_componentLogLevels; +}; diff --git a/xbmc/utils/logtypes.h b/xbmc/utils/logtypes.h new file mode 100644 index 0000000..f41aa7e --- /dev/null +++ b/xbmc/utils/logtypes.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> + +namespace spdlog +{ +class logger; +} + +using Logger = std::shared_ptr<spdlog::logger>; diff --git a/xbmc/utils/params_check_macros.h b/xbmc/utils/params_check_macros.h new file mode 100644 index 0000000..550e229 --- /dev/null +++ b/xbmc/utils/params_check_macros.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +// 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 +#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* functionName, const char* format, ...) PARAM3_PRINTF_FORMAT; } +#define PARAM3_PRINTF_FORMAT __attribute__((format(printf,3,4))) + +// for use in functions that take printf format string as fourth parameter and additional printf parameters as fifth 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* functionName, int component, const char* format, ...) PARAM4_PRINTF_FORMAT; } +#define PARAM4_PRINTF_FORMAT __attribute__((format(printf,4,5))) +#else // ! __GNUC__ +#define PARAM1_PRINTF_FORMAT +#define PARAM2_PRINTF_FORMAT +#define PARAM3_PRINTF_FORMAT +#define PARAM4_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/xbmc/utils/rfft.cpp b/xbmc/utils/rfft.cpp new file mode 100644 index 0000000..e0ae383 --- /dev/null +++ b/xbmc/utils/rfft.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "rfft.h" + +#if defined(TARGET_WINDOWS) && !defined(_USE_MATH_DEFINES) +#define _USE_MATH_DEFINES +#endif +#include <math.h> + +RFFT::RFFT(int size, bool windowed) : + m_size(size), m_windowed(windowed) +{ + m_cfg = kiss_fftr_alloc(m_size,0,nullptr,nullptr); +} + +RFFT::~RFFT() +{ + // we don' use kiss_fftr_free here because + // its hardcoded to free and doesn't pay attention + // to SIMD (which might be used during kiss_fftr_alloc + //in the C'tor). + KISS_FFT_FREE(m_cfg); +} + +void RFFT::calc(const float* input, float* output) +{ + // temporary buffers + std::vector<kiss_fft_scalar> linput(m_size), rinput(m_size); + std::vector<kiss_fft_cpx> loutput(m_size), routput(m_size); + + for (size_t i=0;i<m_size;++i) + { + linput[i] = input[2*i]; + rinput[i] = input[2*i+1]; + } + + if (m_windowed) + { + hann(linput); + hann(rinput); + } + + // transform channels + kiss_fftr(m_cfg, &linput[0], &loutput[0]); + kiss_fftr(m_cfg, &rinput[0], &routput[0]); + + auto&& filter = [&](kiss_fft_cpx& data) + { + return static_cast<double>(sqrt(data.r * data.r + data.i * data.i)) * 2.0 / m_size * + (m_windowed ? sqrt(8.0 / 3.0) : 1.0); + }; + + // interleave while taking magnitudes and normalizing + for (size_t i=0;i<m_size/2;++i) + { + output[2*i] = filter(loutput[i]); + output[2*i+1] = filter(routput[i]); + } +} + +#include <iostream> + +void RFFT::hann(std::vector<kiss_fft_scalar>& data) +{ + for (size_t i=0;i<data.size();++i) + data[i] *= 0.5f * (1.0f - cos(2.0f * static_cast<float>(M_PI) * i / (data.size() - 1))); +} diff --git a/xbmc/utils/rfft.h b/xbmc/utils/rfft.h new file mode 100644 index 0000000..ce54d63 --- /dev/null +++ b/xbmc/utils/rfft.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <vector> + +#include <kissfft/kiss_fftr.h> + +//! \brief Class performing a RFFT of interleaved stereo data. +class RFFT +{ +public: + //! \brief The constructor creates a RFFT plan. + //! \brief size Length of time data for a single channel. + //! \brief windowed Whether or not to apply a Hann window to data. + RFFT(int size, bool windowed=false); + + //! \brief Free the RFFT plan + ~RFFT(); + + //! \brief Calculate FFTs + //! \param input Input data of size 2*m_size + //! \param output Output data of size m_size. + void calc(const float* input, float* output); +protected: + //! \brief Apply a Hann window to a buffer. + //! \param data Vector with data to apply window to. + static void hann(std::vector<kiss_fft_scalar>& data); + + size_t m_size; //!< Size for a single channel. + bool m_windowed; //!< Whether or not a Hann window is applied. + kiss_fftr_cfg m_cfg; //!< FFT plan +}; diff --git a/xbmc/utils/test/CMakeLists.txt b/xbmc/utils/test/CMakeLists.txt new file mode 100644 index 0000000..a5ba095 --- /dev/null +++ b/xbmc/utils/test/CMakeLists.txt @@ -0,0 +1,51 @@ +set(SOURCES TestAlarmClock.cpp + TestAliasShortcutUtils.cpp + TestArchive.cpp + TestBase64.cpp + TestBitstreamStats.cpp + TestCharsetConverter.cpp + TestCPUInfo.cpp + TestComponentContainer.cpp + TestCrc32.cpp + TestDatabaseUtils.cpp + TestDigest.cpp + TestEndianSwap.cpp + TestExecString.cpp + TestFileOperationJob.cpp + TestFileUtils.cpp + TestGlobalsHandling.cpp + TestHTMLUtil.cpp + TestHttpHeader.cpp + TestHttpParser.cpp + TestHttpRangeUtils.cpp + TestHttpResponse.cpp + TestJobManager.cpp + TestJSONVariantParser.cpp + TestJSONVariantWriter.cpp + TestLabelFormatter.cpp + TestLangCodeExpander.cpp + TestLocale.cpp + Testlog.cpp + TestMathUtils.cpp + TestMime.cpp + TestPOUtils.cpp + TestRegExp.cpp + Testrfft.cpp + TestRingBuffer.cpp + TestScraperParser.cpp + TestScraperUrl.cpp + TestSortUtils.cpp + TestStopwatch.cpp + TestStreamDetails.cpp + TestStreamUtils.cpp + TestStringUtils.cpp + TestSystemInfo.cpp + TestURIUtils.cpp + TestUrlOptions.cpp + TestVariant.cpp + TestXBMCTinyXML.cpp + TestXMLUtils.cpp) + +set(HEADERS TestGlobalsHandlingPattern1.h) + +core_add_test_library(utils_test) diff --git a/xbmc/utils/test/CXBMCTinyXML-test.xml b/xbmc/utils/test/CXBMCTinyXML-test.xml new file mode 100644 index 0000000..9444dc8 --- /dev/null +++ b/xbmc/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/xbmc/utils/test/TestAlarmClock.cpp b/xbmc/utils/test/TestAlarmClock.cpp new file mode 100644 index 0000000..75ea84a --- /dev/null +++ b/xbmc/utils/test/TestAlarmClock.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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/xbmc/utils/test/TestAliasShortcutUtils.cpp b/xbmc/utils/test/TestAliasShortcutUtils.cpp new file mode 100644 index 0000000..d36fd41 --- /dev/null +++ b/xbmc/utils/test/TestAliasShortcutUtils.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/AliasShortcutUtils.h" +#include "filesystem/File.h" +#include "test/TestUtils.h" + +#if defined(TARGET_DARWIN_OSX) +#include "platform/darwin/DarwinUtils.h" +#endif +#include <gtest/gtest.h> + +TEST(TestAliasShortcutUtils, IsAliasShortcut) +{ + XFILE::CFile *tmpFile = XBMC_CREATETEMPFILE("noaliastest"); + std::string noalias = XBMC_TEMPFILEPATH(tmpFile); + +#if defined(TARGET_DARWIN_OSX) + XFILE::CFile *aliasDestFile = XBMC_CREATETEMPFILE("aliastest"); + std::string alias = XBMC_TEMPFILEPATH(aliasDestFile); + + //we only need the path here so delete the alias file + //which will be recreated as shortcut later: + XBMC_DELETETEMPFILE(aliasDestFile); + + // create alias from a pointing to /Volumes + CDarwinUtils::CreateAliasShortcut(alias, "/Volumes"); + EXPECT_TRUE(IsAliasShortcut(alias, true)); + XFILE::CFile::Delete(alias); + + // volumes is not a shortcut but a dir + EXPECT_FALSE(IsAliasShortcut("/Volumes", true)); +#endif + + // a regular file is not a shortcut + EXPECT_FALSE(IsAliasShortcut(noalias, false)); + XBMC_DELETETEMPFILE(tmpFile); + + // empty string is not an alias + std::string emptyString; + EXPECT_FALSE(IsAliasShortcut(emptyString, false)); + + // non-existent file is no alias + std::string nonExistingFile="/IDontExistsNormally/somefile.txt"; + EXPECT_FALSE(IsAliasShortcut(nonExistingFile, false)); +} + +TEST(TestAliasShortcutUtils, TranslateAliasShortcut) +{ + XFILE::CFile *tmpFile = XBMC_CREATETEMPFILE("noaliastest"); + std::string noalias = XBMC_TEMPFILEPATH(tmpFile); + std::string noaliastemp = noalias; + +#if defined(TARGET_DARWIN_OSX) + XFILE::CFile *aliasDestFile = XBMC_CREATETEMPFILE("aliastest"); + std::string alias = XBMC_TEMPFILEPATH(aliasDestFile); + + //we only need the path here so delete the alias file + //which will be recreated as shortcut later: + XBMC_DELETETEMPFILE(aliasDestFile); + + // create alias from a pointing to /Volumes + CDarwinUtils::CreateAliasShortcut(alias, "/Volumes"); + + // resolve the shortcut + TranslateAliasShortcut(alias); + EXPECT_STREQ("/Volumes", alias.c_str()); + XFILE::CFile::Delete(alias); +#endif + + // translating a non-shortcut url should result in no change... + TranslateAliasShortcut(noaliastemp); + EXPECT_STREQ(noaliastemp.c_str(), noalias.c_str()); + XBMC_DELETETEMPFILE(tmpFile); + + //translate empty should stay empty + std::string emptyString; + TranslateAliasShortcut(emptyString); + EXPECT_STREQ("", emptyString.c_str()); + + // translate non-existent file should result in no change... + std::string nonExistingFile="/IDontExistsNormally/somefile.txt"; + std::string resolvedNonExistingFile=nonExistingFile; + TranslateAliasShortcut(resolvedNonExistingFile); + EXPECT_STREQ(resolvedNonExistingFile.c_str(), nonExistingFile.c_str()); +} diff --git a/xbmc/utils/test/TestArchive.cpp b/xbmc/utils/test/TestArchive.cpp new file mode 100644 index 0000000..90628ea --- /dev/null +++ b/xbmc/utils/test/TestArchive.cpp @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#if defined(TARGET_WINDOWS) +# include <windows.h> +#endif + +#include "filesystem/File.h" +#include "test/TestUtils.h" +#include "utils/Archive.h" +#include "utils/Variant.h" +#include "utils/XTimeUtils.h" + +#include <gtest/gtest.h> + +class TestArchive : public testing::Test +{ +protected: + TestArchive() + { + file = XBMC_CREATETEMPFILE(".ar"); + } + ~TestArchive() override + { + EXPECT_TRUE(XBMC_DELETETEMPFILE(file)); + } + XFILE::CFile *file; +}; + +TEST_F(TestArchive, IsStoring) +{ + ASSERT_NE(nullptr, file); + CArchive arstore(file, CArchive::store); + EXPECT_TRUE(arstore.IsStoring()); + EXPECT_FALSE(arstore.IsLoading()); + arstore.Close(); +} + +TEST_F(TestArchive, IsLoading) +{ + ASSERT_NE(nullptr, file); + CArchive arload(file, CArchive::load); + EXPECT_TRUE(arload.IsLoading()); + EXPECT_FALSE(arload.IsStoring()); + arload.Close(); +} + +TEST_F(TestArchive, FloatArchive) +{ + ASSERT_NE(nullptr, file); + float float_ref = 1, float_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << float_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + CArchive arload(file, CArchive::load); + arload >> float_var; + arload.Close(); + + EXPECT_EQ(float_ref, float_var); +} + +TEST_F(TestArchive, DoubleArchive) +{ + ASSERT_NE(nullptr, file); + double double_ref = 2, double_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << double_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + CArchive arload(file, CArchive::load); + arload >> double_var; + arload.Close(); + + EXPECT_EQ(double_ref, double_var); +} + +TEST_F(TestArchive, IntegerArchive) +{ + ASSERT_NE(nullptr, file); + int int_ref = 3, int_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << int_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + CArchive arload(file, CArchive::load); + arload >> int_var; + arload.Close(); + + EXPECT_EQ(int_ref, int_var); +} + +TEST_F(TestArchive, UnsignedIntegerArchive) +{ + ASSERT_NE(nullptr, file); + unsigned int unsigned_int_ref = 4, unsigned_int_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << unsigned_int_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + CArchive arload(file, CArchive::load); + arload >> unsigned_int_var; + arload.Close(); + + EXPECT_EQ(unsigned_int_ref, unsigned_int_var); +} + +TEST_F(TestArchive, Int64tArchive) +{ + ASSERT_NE(nullptr, file); + int64_t int64_t_ref = 5, int64_t_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << int64_t_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + CArchive arload(file, CArchive::load); + arload >> int64_t_var; + arload.Close(); + + EXPECT_EQ(int64_t_ref, int64_t_var); +} + +TEST_F(TestArchive, UInt64tArchive) +{ + ASSERT_NE(nullptr, file); + uint64_t uint64_t_ref = 6, uint64_t_var = 0; + + CArchive arstore(file, CArchive::store); + arstore << uint64_t_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + CArchive arload(file, CArchive::load); + arload >> uint64_t_var; + arload.Close(); + + EXPECT_EQ(uint64_t_ref, uint64_t_var); +} + +TEST_F(TestArchive, BoolArchive) +{ + ASSERT_NE(nullptr, file); + bool bool_ref = true, bool_var = false; + + CArchive arstore(file, CArchive::store); + arstore << bool_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + CArchive arload(file, CArchive::load); + arload >> bool_var; + arload.Close(); + + EXPECT_EQ(bool_ref, bool_var); +} + +TEST_F(TestArchive, CharArchive) +{ + ASSERT_NE(nullptr, file); + char char_ref = 'A', char_var = '\0'; + + CArchive arstore(file, CArchive::store); + arstore << char_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + CArchive arload(file, CArchive::load); + arload >> char_var; + arload.Close(); + + EXPECT_EQ(char_ref, char_var); +} + +TEST_F(TestArchive, WStringArchive) +{ + ASSERT_NE(nullptr, file); + std::wstring wstring_ref = L"test wstring", wstring_var; + + CArchive arstore(file, CArchive::store); + arstore << wstring_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + CArchive arload(file, CArchive::load); + arload >> wstring_var; + arload.Close(); + + EXPECT_STREQ(wstring_ref.c_str(), wstring_var.c_str()); +} + +TEST_F(TestArchive, StringArchive) +{ + ASSERT_NE(nullptr, file); + std::string string_ref = "test string", string_var; + + CArchive arstore(file, CArchive::store); + arstore << string_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + CArchive arload(file, CArchive::load); + arload >> string_var; + arload.Close(); + + EXPECT_STREQ(string_ref.c_str(), string_var.c_str()); +} + +TEST_F(TestArchive, SystemTimeArchive) +{ + ASSERT_NE(nullptr, file); + KODI::TIME::SystemTime SystemTime_ref = {1, 2, 3, 4, 5, 6, 7, 8}; + KODI::TIME::SystemTime SystemTime_var = {0, 0, 0, 0, 0, 0, 0, 0}; + + CArchive arstore(file, CArchive::store); + arstore << SystemTime_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + CArchive arload(file, CArchive::load); + arload >> SystemTime_var; + arload.Close(); + + EXPECT_TRUE(!memcmp(&SystemTime_ref, &SystemTime_var, sizeof(KODI::TIME::SystemTime))); +} + +TEST_F(TestArchive, CVariantArchive) +{ + ASSERT_NE(nullptr, file); + CVariant CVariant_ref((int)1), CVariant_var; + + CArchive arstore(file, CArchive::store); + arstore << CVariant_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + 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_NE(nullptr, file); + CVariant CVariant_ref("teststring"), CVariant_var; + + CArchive arstore(file, CArchive::store); + arstore << CVariant_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + 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_NE(nullptr, file); + std::vector<std::string> strArray_ref, strArray_var; + strArray_ref.emplace_back("test strArray_ref 0"); + strArray_ref.emplace_back("test strArray_ref 1"); + strArray_ref.emplace_back("test strArray_ref 2"); + strArray_ref.emplace_back("test strArray_ref 3"); + + CArchive arstore(file, CArchive::store); + arstore << strArray_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + 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_NE(nullptr, 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_EQ(0, file->Seek(0, SEEK_SET)); + 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_NE(nullptr, 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'; + std::string string_ref = "test string", string_var; + std::wstring wstring_ref = L"test wstring", wstring_var; + KODI::TIME::SystemTime SystemTime_ref = {1, 2, 3, 4, 5, 6, 7, 8}; + KODI::TIME::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.emplace_back("test strArray_ref 0"); + strArray_ref.emplace_back("test strArray_ref 1"); + strArray_ref.emplace_back("test strArray_ref 2"); + strArray_ref.emplace_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 << string_ref; + arstore << wstring_ref; + arstore << SystemTime_ref; + arstore << CVariant_ref; + arstore << strArray_ref; + arstore << iArray_ref; + arstore.Close(); + + ASSERT_EQ(0, file->Seek(0, SEEK_SET)); + 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 >> string_var; + arload >> wstring_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(string_ref.c_str(), string_var.c_str()); + EXPECT_STREQ(wstring_ref.c_str(), wstring_var.c_str()); + EXPECT_TRUE(!memcmp(&SystemTime_ref, &SystemTime_var, sizeof(KODI::TIME::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/xbmc/utils/test/TestBase64.cpp b/xbmc/utils/test/TestBase64.cpp new file mode 100644 index 0000000..8416378 --- /dev/null +++ b/xbmc/utils/test/TestBase64.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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/xbmc/utils/test/TestBitstreamStats.cpp b/xbmc/utils/test/TestBitstreamStats.cpp new file mode 100644 index 0000000..200a633 --- /dev/null +++ b/xbmc/utils/test/TestBitstreamStats.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "threads/Thread.h" +#include "utils/BitstreamStats.h" + +#include <gtest/gtest.h> + +using namespace std::chrono_literals; + +#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(1ms); + } + 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(2ms); + i++; + } + a.CalculateBitrate(); + EXPECT_GT(a.GetBitrate(), 0.0); + EXPECT_GT(a.GetMaxBitrate(), 0.0); + EXPECT_LE(a.GetMinBitrate(), a.GetMaxBitrate()); +} diff --git a/xbmc/utils/test/TestCPUInfo.cpp b/xbmc/utils/test/TestCPUInfo.cpp new file mode 100644 index 0000000..bd9572a --- /dev/null +++ b/xbmc/utils/test/TestCPUInfo.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#if defined(TARGET_WINDOWS) +# include <windows.h> +#endif + +#include "ServiceBroker.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/CPUInfo.h" +#include "utils/Temperature.h" +#include "utils/XTimeUtils.h" + +#include <gtest/gtest.h> + +struct TestCPUInfo : public ::testing::Test +{ + TestCPUInfo() { CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo()); } + + ~TestCPUInfo() { CServiceBroker::UnregisterCPUInfo(); } +}; + +TEST_F(TestCPUInfo, GetUsedPercentage) +{ + EXPECT_GE(CServiceBroker::GetCPUInfo()->GetUsedPercentage(), 0); +} + +TEST_F(TestCPUInfo, GetCPUCount) +{ + EXPECT_GT(CServiceBroker::GetCPUInfo()->GetCPUCount(), 0); +} + +TEST_F(TestCPUInfo, GetCPUFrequency) +{ + EXPECT_GE(CServiceBroker::GetCPUInfo()->GetCPUFrequency(), 0.f); +} + +#if defined(TARGET_WINDOWS) +TEST_F(TestCPUInfo, DISABLED_GetTemperature) +#else +TEST_F(TestCPUInfo, GetTemperature) +#endif +{ + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cpuTempCmd = "echo '50 c'"; + CTemperature t; + EXPECT_TRUE(CServiceBroker::GetCPUInfo()->GetTemperature(t)); + EXPECT_TRUE(t.IsValid()); +} + +TEST_F(TestCPUInfo, CoreInfo) +{ + ASSERT_TRUE(CServiceBroker::GetCPUInfo()->HasCoreId(0)); + const CoreInfo c = CServiceBroker::GetCPUInfo()->GetCoreInfo(0); + EXPECT_TRUE(c.m_id == 0); +} + +TEST_F(TestCPUInfo, GetCoresUsageString) +{ + EXPECT_STRNE("", CServiceBroker::GetCPUInfo()->GetCoresUsageString().c_str()); +} + +TEST_F(TestCPUInfo, GetCPUFeatures) +{ + unsigned int a = CServiceBroker::GetCPUInfo()->GetCPUFeatures(); + (void)a; +} diff --git a/xbmc/utils/test/TestCharsetConverter.cpp b/xbmc/utils/test/TestCharsetConverter.cpp new file mode 100644 index 0000000..f8736b7 --- /dev/null +++ b/xbmc/utils/test/TestCharsetConverter.cpp @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ServiceBroker.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/CharsetConverter.h" +#include "utils/Utf8Utils.h" + +#include <gtest/gtest.h> + +#if 0 +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 }; +#endif + +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"; + +#if 0 +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 }; +#endif + +class TestCharsetConverter : public testing::Test +{ +protected: + TestCharsetConverter() + { + /* Add default settings for locale. + * Settings here are taken from CGUISettings::Initialize() + */ + /* + //! @todo implement + CSettingsCategory *loc = CServiceBroker::GetSettingsComponent()->GetSettings()->AddCategory(7, "locale", 14090); + CServiceBroker::GetSettingsComponent()->GetSettings()->AddString(loc, CSettings::SETTING_LOCALE_LANGUAGE,248,"english", + SPIN_CONTROL_TEXT); + CServiceBroker::GetSettingsComponent()->GetSettings()->AddString(loc, CSettings::SETTING_LOCALE_COUNTRY, 20026, "USA", + SPIN_CONTROL_TEXT); + CServiceBroker::GetSettingsComponent()->GetSettings()->AddString(loc, CSettings::SETTING_LOCALE_CHARSET, 14091, "DEFAULT", + SPIN_CONTROL_TEXT); // charset is set by the + // language file + + // Add default settings for subtitles + CSettingsCategory *sub = CServiceBroker::GetSettingsComponent()->GetSettings()->AddCategory(5, "subtitles", 287); + CServiceBroker::GetSettingsComponent()->GetSettings()->AddString(sub, CSettings::SETTING_SUBTITLES_CHARSET, 735, "DEFAULT", + SPIN_CONTROL_TEXT); + */ + g_charsetConverter.reset(); + g_charsetConverter.clear(); + } + + ~TestCharsetConverter() override + { + CServiceBroker::GetSettingsComponent()->GetSettings()->Unload(); + } + + std::string refstra1, refstra2, varstra1; + std::wstring refstrw1, varstrw1; + std::string refstr1; +}; + +TEST_F(TestCharsetConverter, utf8ToW) +{ + refstra1 = "test utf8ToW"; + refstrw1 = L"test utf8ToW"; + varstrw1.clear(); + g_charsetConverter.utf8ToW(refstra1, varstrw1, true, false, false); + 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() +// std::wstring refstr16_1; +// 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, std::string"; + 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 its 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 std::wstring& strSource, +// std::string& strDest, bool swap = false); +// } + +TEST_F(TestCharsetConverter, wToUTF8) +{ + refstrw1 = L"test_wToUTF8"; + refstra1 = u8"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, std::string& strDest); +// } + +TEST_F(TestCharsetConverter, getCharsetLabels) +{ + std::vector<std::string> reflabels; + reflabels.emplace_back("Western Europe (ISO)"); + reflabels.emplace_back("Central Europe (ISO)"); + reflabels.emplace_back("South Europe (ISO)"); + reflabels.emplace_back("Baltic (ISO)"); + reflabels.emplace_back("Cyrillic (ISO)"); + reflabels.emplace_back("Arabic (ISO)"); + reflabels.emplace_back("Greek (ISO)"); + reflabels.emplace_back("Hebrew (ISO)"); + reflabels.emplace_back("Turkish (ISO)"); + reflabels.emplace_back("Central Europe (Windows)"); + reflabels.emplace_back("Cyrillic (Windows)"); + reflabels.emplace_back("Western Europe (Windows)"); + reflabels.emplace_back("Greek (Windows)"); + reflabels.emplace_back("Turkish (Windows)"); + reflabels.emplace_back("Hebrew (Windows)"); + reflabels.emplace_back("Arabic (Windows)"); + reflabels.emplace_back("Baltic (Windows)"); + reflabels.emplace_back("Vietnamese (Windows)"); + reflabels.emplace_back("Thai (Windows)"); + reflabels.emplace_back("Chinese Traditional (Big5)"); + reflabels.emplace_back("Chinese Simplified (GBK)"); + reflabels.emplace_back("Japanese (Shift-JIS)"); + reflabels.emplace_back("Korean"); + reflabels.emplace_back("Hong Kong (Big5-HKSCS)"); + + std::vector<std::string> varlabels = g_charsetConverter.getCharsetLabels(); + ASSERT_EQ(reflabels.size(), varlabels.size()); + + size_t pos = 0; + for (const auto& it : varlabels) + { + EXPECT_STREQ((reflabels.at(pos++)).c_str(), it.c_str()); + } +} + +TEST_F(TestCharsetConverter, getCharsetLabelByName) +{ + std::string 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) +{ + std::string 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/xbmc/utils/test/TestComponentContainer.cpp b/xbmc/utils/test/TestComponentContainer.cpp new file mode 100644 index 0000000..d7246be --- /dev/null +++ b/xbmc/utils/test/TestComponentContainer.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/ComponentContainer.h" + +#include <utility> + +#include <gtest/gtest.h> + +class BaseTestType +{ +public: + virtual ~BaseTestType() = default; +}; + +struct DerivedType1 : public BaseTestType +{ + int a = 1; +}; + +struct DerivedType2 : public BaseTestType +{ + int a = 2; +}; + +struct DerivedType3 : public BaseTestType +{ + int a = 3; +}; + +class TestContainer : public CComponentContainer<BaseTestType> +{ + FRIEND_TEST(TestComponentContainer, Generic); +}; + +TEST(TestComponentContainer, Generic) +{ + TestContainer container; + + // check that we can register types + container.RegisterComponent(std::make_shared<DerivedType1>()); + EXPECT_EQ(container.size(), 1u); + container.RegisterComponent(std::make_shared<DerivedType2>()); + EXPECT_EQ(container.size(), 2u); + + // check that trying to register a component twice does nothing + container.RegisterComponent(std::make_shared<DerivedType2>()); + EXPECT_EQ(container.size(), 2u); + + // check that first component is valid + const auto t1 = container.GetComponent<DerivedType1>(); + EXPECT_TRUE(t1 != nullptr); + EXPECT_EQ(t1->a, 1); + + // check that second component is valid + const auto t2 = container.GetComponent<DerivedType2>(); + EXPECT_TRUE(t2 != nullptr); + EXPECT_EQ(t2->a, 2); + + // check that third component is not there + EXPECT_THROW(container.GetComponent<DerivedType3>(), std::logic_error); + + // check that component instance is constant + const auto t4 = container.GetComponent<DerivedType1>(); + EXPECT_EQ(t1.get(), t4.get()); + + // check we can call the const overload for GetComponent + // and that the returned type is const + const auto t5 = const_cast<const TestContainer&>(container).GetComponent<DerivedType1>(); + EXPECT_TRUE(std::is_const_v<typename decltype(t5)::element_type>); +} diff --git a/xbmc/utils/test/TestCrc32.cpp b/xbmc/utils/test/TestCrc32.cpp new file mode 100644 index 0000000..99a2dd5 --- /dev/null +++ b/xbmc/utils/test/TestCrc32.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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) +{ + uint32_t varcrc; + std::string s = refdata; + varcrc = Crc32::Compute(s); + EXPECT_EQ(0xa4eb60e3, varcrc); +} + +TEST(TestCrc32, ComputeFromLowerCase) +{ + std::string s = refdata; + uint32_t varcrc = Crc32::ComputeFromLowerCase(s); + EXPECT_EQ((uint32_t)0x7f045b3e, varcrc); +} + +TEST(TestCrc32, Reset) +{ + Crc32 a; + uint32_t varcrc; + std::string s = refdata; + a.Compute(s.c_str(), s.length()); + a.Reset(); + varcrc = a; + EXPECT_EQ(0xffffffff, varcrc); +} diff --git a/xbmc/utils/test/TestDatabaseUtils.cpp b/xbmc/utils/test/TestDatabaseUtils.cpp new file mode 100644 index 0000000..ddb986c --- /dev/null +++ b/xbmc/utils/test/TestDatabaseUtils.cpp @@ -0,0 +1,1377 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "dbwrappers/qry_dat.h" +#include "music/MusicDatabase.h" +#include "utils/DatabaseUtils.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "video/VideoDatabase.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_strMoods = CMusicDatabase::album_strMoods; + album_strReleaseDate = CMusicDatabase::album_strReleaseDate; + album_strOrigReleaseDate = CMusicDatabase::album_strOrigReleaseDate; + 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_fRating = CMusicDatabase::album_fRating; + album_iVotes = CMusicDatabase::album_iVotes; + album_iUserrating = CMusicDatabase::album_iUserrating; + album_dtDateAdded = CMusicDatabase::album_dateAdded; + + song_idSong = CMusicDatabase::song_idSong; + song_strTitle = CMusicDatabase::song_strTitle; + song_iTrack = CMusicDatabase::song_iTrack; + song_iDuration = CMusicDatabase::song_iDuration; + song_strReleaseDate = CMusicDatabase::song_strReleaseDate; + song_strOrigReleaseDate = CMusicDatabase::song_strOrigReleaseDate; + 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_votes = CMusicDatabase::song_votes; + song_userrating = CMusicDatabase::song_userrating; + 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_strMoods; + int album_strReleaseDate; + int album_strOrigReleaseDate; + int album_strStyles; + int album_strThemes; + int album_strReview; + int album_strLabel; + int album_strType; + int album_fRating; + int album_iVotes; + int album_iUserrating; + int album_dtDateAdded; + + int song_idSong; + int song_strTitle; + int song_iTrack; + int song_iDuration; + int song_strReleaseDate; + int song_strOrigReleaseDate; + int song_strFileName; + int song_iTimesPlayed; + int song_iStartOffset; + int song_iEndOffset; + int song_lastplayed; + int song_rating; + int song_votes; + int song_userrating; + 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.strGenres"; + varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.strReleaseDate"; + varstr = DatabaseUtils::GetField(FieldYear, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + +refstr = "albumview.strOrigReleaseDate"; + varstr = DatabaseUtils::GetField(FieldOrigYear, 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.fRating"; + varstr = DatabaseUtils::GetField(FieldRating, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.iVotes"; + varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.iUserrating"; + varstr = DatabaseUtils::GetField(FieldUserRating, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "albumview.dateAdded"; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeAlbum, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = ""; + varstr = DatabaseUtils::GetField(FieldNone, 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.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.votes"; + varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.userrating"; + varstr = DatabaseUtils::GetField(FieldUserRating, 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.strReleaseDate"; + varstr = DatabaseUtils::GetField(FieldYear, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.strOrigReleaseDate"; + varstr = DatabaseUtils::GetField(FieldOrigYear, 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.strGenres"; + varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeSong, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "songview.dateAdded"; + 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) +{ + std::string refstr, varstr; + + refstr = "musicvideo_view.idMVideo"; + varstr = DatabaseUtils::GetField(FieldId, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_TITLE); + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_RUNTIME); + varstr = DatabaseUtils::GetField(FieldTime, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_DIRECTOR); + varstr = DatabaseUtils::GetField(FieldDirector, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_STUDIOS); + varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_PLOT); + varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_ALBUM); + varstr = DatabaseUtils::GetField(FieldAlbum, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_ARTIST); + varstr = DatabaseUtils::GetField(FieldArtist, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_GENRE); + varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("musicvideo_view.c{:02}", VIDEODB_ID_MUSICVIDEO_TRACK); + varstr = DatabaseUtils::GetField(FieldTrackNumber, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideo_view.strFilename"; + varstr = DatabaseUtils::GetField(FieldFilename, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideo_view.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideo_view.playCount"; + varstr = DatabaseUtils::GetField(FieldPlaycount, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideo_view.lastPlayed"; + varstr = DatabaseUtils::GetField(FieldLastPlayed, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideo_view.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 = "musicvideo_view.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMusicVideo, + DatabaseQueryPartWhere); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideo_view.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMusicVideo, + DatabaseQueryPartOrderBy); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "musicvideo_view.userrating"; + varstr = DatabaseUtils::GetField(FieldUserRating, MediaTypeMusicVideo, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestDatabaseUtils, GetField_MediaTypeMovie) +{ + std::string refstr, varstr; + + refstr = "movie_view.idMovie"; + varstr = DatabaseUtils::GetField(FieldId, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_TITLE); + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("CASE WHEN length(movie_view.c{:02}) > 0 THEN movie_view.c{:02} " + "ELSE movie_view.c{:02} END", + VIDEODB_ID_SORTTITLE, VIDEODB_ID_SORTTITLE, VIDEODB_ID_TITLE); + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeMovie, + DatabaseQueryPartOrderBy); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_PLOT); + varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_PLOTOUTLINE); + varstr = DatabaseUtils::GetField(FieldPlotOutline, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_TAGLINE); + varstr = DatabaseUtils::GetField(FieldTagline, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movie_view.votes"; + varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movie_view.rating"; + varstr = DatabaseUtils::GetField(FieldRating, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_CREDITS); + varstr = DatabaseUtils::GetField(FieldWriter, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_SORTTITLE); + varstr = DatabaseUtils::GetField(FieldSortTitle, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_RUNTIME); + varstr = DatabaseUtils::GetField(FieldTime, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_MPAA); + varstr = DatabaseUtils::GetField(FieldMPAA, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_TOP250); + varstr = DatabaseUtils::GetField(FieldTop250, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_GENRE); + varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_DIRECTOR); + varstr = DatabaseUtils::GetField(FieldDirector, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_STUDIOS); + varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_TRAILER); + varstr = DatabaseUtils::GetField(FieldTrailer, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("movie_view.c{:02}", VIDEODB_ID_COUNTRY); + varstr = DatabaseUtils::GetField(FieldCountry, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movie_view.strFilename"; + varstr = DatabaseUtils::GetField(FieldFilename, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movie_view.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movie_view.playCount"; + varstr = DatabaseUtils::GetField(FieldPlaycount, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movie_view.lastPlayed"; + varstr = DatabaseUtils::GetField(FieldLastPlayed, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movie_view.dateAdded"; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeMovie, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "movie_view.userrating"; + varstr = DatabaseUtils::GetField(FieldUserRating, 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) +{ + std::string refstr, varstr; + + refstr = "tvshow_view.idShow"; + varstr = DatabaseUtils::GetField(FieldId, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = + StringUtils::Format("CASE WHEN length(tvshow_view.c{:02}) > 0 THEN tvshow_view.c{:02} " + "ELSE tvshow_view.c{:02} 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("tvshow_view.c{:02}", VIDEODB_ID_TV_TITLE); + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_PLOT); + varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_STATUS); + varstr = DatabaseUtils::GetField(FieldTvShowStatus, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshow_view.votes"; + varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshow_view.rating"; + varstr = DatabaseUtils::GetField(FieldRating, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_PREMIERED); + varstr = DatabaseUtils::GetField(FieldYear, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_GENRE); + varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_MPAA); + varstr = DatabaseUtils::GetField(FieldMPAA, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_STUDIOS); + varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("tvshow_view.c{:02}", VIDEODB_ID_TV_SORTTITLE); + varstr = DatabaseUtils::GetField(FieldSortTitle, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshow_view.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshow_view.dateAdded"; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshow_view.totalSeasons"; + varstr = DatabaseUtils::GetField(FieldSeason, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshow_view.totalCount"; + varstr = DatabaseUtils::GetField(FieldNumberOfEpisodes, MediaTypeTvShow, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshow_view.watchedcount"; + varstr = DatabaseUtils::GetField(FieldNumberOfWatchedEpisodes, + MediaTypeTvShow, DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "tvshow_view.userrating"; + varstr = DatabaseUtils::GetField(FieldUserRating, 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) +{ + std::string refstr, varstr; + + refstr = "episode_view.idEpisode"; + varstr = DatabaseUtils::GetField(FieldId, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_TITLE); + varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_PLOT); + varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.votes"; + varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.rating"; + varstr = DatabaseUtils::GetField(FieldRating, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_CREDITS); + varstr = DatabaseUtils::GetField(FieldWriter, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_AIRED); + varstr = DatabaseUtils::GetField(FieldAirDate, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_RUNTIME); + varstr = DatabaseUtils::GetField(FieldTime, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_DIRECTOR); + varstr = DatabaseUtils::GetField(FieldDirector, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_SEASON); + varstr = DatabaseUtils::GetField(FieldSeason, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = StringUtils::Format("episode_view.c{:02}", VIDEODB_ID_EPISODE_EPISODE); + varstr = DatabaseUtils::GetField(FieldEpisodeNumber, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.strFilename"; + varstr = DatabaseUtils::GetField(FieldFilename, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.strPath"; + varstr = DatabaseUtils::GetField(FieldPath, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.playCount"; + varstr = DatabaseUtils::GetField(FieldPlaycount, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.lastPlayed"; + varstr = DatabaseUtils::GetField(FieldLastPlayed, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.dateAdded"; + varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.strTitle"; + varstr = DatabaseUtils::GetField(FieldTvShowTitle, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.premiered"; + varstr = DatabaseUtils::GetField(FieldYear, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.mpaa"; + varstr = DatabaseUtils::GetField(FieldMPAA, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.strStudio"; + varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeEpisode, + DatabaseQueryPartSelect); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "episode_view.userrating"; + varstr = DatabaseUtils::GetField(FieldUserRating, 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_strReleaseDate; + varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_strOrigReleaseDate; + varindex = DatabaseUtils::GetFieldIndex(FieldOrigYear, 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_fRating; + varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeAlbum); + EXPECT_EQ(refindex, varindex); + + refindex = a.album_dtDateAdded; + varindex = DatabaseUtils::GetFieldIndex(FieldDateAdded, 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_strReleaseDate; + 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_votes; + varindex = DatabaseUtils::GetFieldIndex(FieldVotes, MediaTypeSong); + EXPECT_EQ(refindex, varindex); + + refindex = a.song_userrating; + varindex = DatabaseUtils::GetFieldIndex(FieldUserRating, 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_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 = VIDEODB_DETAILS_MUSICVIDEO_USER_RATING; + varindex = DatabaseUtils::GetFieldIndex(FieldUserRating, MediaTypeMusicVideo); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MUSICVIDEO_PREMIERED; + varindex = DatabaseUtils::GetFieldIndex(FieldYear, 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_CREDITS + 2; + varindex = DatabaseUtils::GetFieldIndex(FieldWriter, 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 = VIDEODB_DETAILS_MOVIE_USER_RATING; + varindex = DatabaseUtils::GetFieldIndex(FieldUserRating, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MOVIE_VOTES; + varindex = DatabaseUtils::GetFieldIndex(FieldVotes, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MOVIE_RATING; + varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeMovie); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_MOVIE_PREMIERED; + varindex = DatabaseUtils::GetFieldIndex(FieldYear, 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_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 = VIDEODB_DETAILS_TVSHOW_USER_RATING; + varindex = DatabaseUtils::GetFieldIndex(FieldUserRating, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_TVSHOW_VOTES; + varindex = DatabaseUtils::GetFieldIndex(FieldVotes, MediaTypeTvShow); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_TVSHOW_RATING; + varindex = DatabaseUtils::GetFieldIndex(FieldRating, 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_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 = VIDEODB_DETAILS_EPISODE_USER_RATING; + varindex = DatabaseUtils::GetFieldIndex(FieldUserRating, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_EPISODE_VOTES; + varindex = DatabaseUtils::GetFieldIndex(FieldVotes, MediaTypeEpisode); + EXPECT_EQ(refindex, varindex); + + refindex = VIDEODB_DETAILS_EPISODE_RATING; + varindex = DatabaseUtils::GetFieldIndex(FieldRating, 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::unique_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/xbmc/utils/test/TestDigest.cpp b/xbmc/utils/test/TestDigest.cpp new file mode 100644 index 0000000..96d0529 --- /dev/null +++ b/xbmc/utils/test/TestDigest.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/Digest.h" + +#include <gtest/gtest.h> + +using KODI::UTILITY::CDigest; +using KODI::UTILITY::TypedDigest; + +TEST(TestDigest, Digest_Empty) +{ + EXPECT_STREQ(CDigest::Calculate(CDigest::Type::MD5, "").c_str(), "d41d8cd98f00b204e9800998ecf8427e"); + EXPECT_STREQ(CDigest::Calculate(CDigest::Type::MD5, nullptr, 0).c_str(), "d41d8cd98f00b204e9800998ecf8427e"); + { + CDigest digest{CDigest::Type::MD5}; + EXPECT_STREQ(digest.Finalize().c_str(), "d41d8cd98f00b204e9800998ecf8427e"); + } + { + CDigest digest{CDigest::Type::MD5}; + digest.Update(""); + digest.Update(nullptr, 0); + EXPECT_STREQ(digest.Finalize().c_str(), "d41d8cd98f00b204e9800998ecf8427e"); + } +} + +TEST(TestDigest, Digest_Basic) +{ + EXPECT_STREQ(CDigest::Calculate(CDigest::Type::MD5, "asdf").c_str(), "912ec803b2ce49e4a541068d495ab570"); + EXPECT_STREQ(CDigest::Calculate(CDigest::Type::MD5, "asdf", 4).c_str(), "912ec803b2ce49e4a541068d495ab570"); + { + CDigest digest{CDigest::Type::MD5}; + digest.Update("as"); + digest.Update("df", 2); + EXPECT_STREQ(digest.Finalize().c_str(), "912ec803b2ce49e4a541068d495ab570"); + } +} + +TEST(TestDigest, Digest_SHA1) +{ + EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA1, "").c_str(), "da39a3ee5e6b4b0d3255bfef95601890afd80709"); + EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA1, "asdf").c_str(), "3da541559918a808c2402bba5012f6c60b27661c"); +} + +TEST(TestDigest, Digest_SHA256) +{ + EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA256, "").c_str(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA256, "asdf").c_str(), "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"); +} + +TEST(TestDigest, Digest_SHA512) +{ + EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA512, "").c_str(), "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"); + EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA512, "asdf").c_str(), "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"); +} + +TEST(TestDigest, TypedDigest_Empty) +{ + TypedDigest t1, t2; + EXPECT_EQ(t1, t2); + EXPECT_EQ(t1.type, CDigest::Type::INVALID); + EXPECT_EQ(t1.value, ""); + EXPECT_TRUE(t1.Empty()); + t1.type = CDigest::Type::SHA1; + EXPECT_TRUE(t1.Empty()); +} + +TEST(TestDigest, TypedDigest_SameType) +{ + TypedDigest t1{CDigest::Type::SHA1, "da39a3ee5e6b4b0d3255bfef95601890afd80709"}; + TypedDigest t2{CDigest::Type::SHA1, "da39a3ee5e6b4b0d3255bfef95601890afd80708"}; + EXPECT_NE(t1, t2); + EXPECT_FALSE(t1.Empty()); +} + +TEST(TestDigest, TypedDigest_CompareCase) +{ + TypedDigest t1{CDigest::Type::SHA1, "da39a3ee5e6b4b0d3255bfef95601890afd80708"}; + TypedDigest t2{CDigest::Type::SHA1, "da39A3EE5e6b4b0d3255bfef95601890afd80708"}; + EXPECT_EQ(t1, t2); +} + +TEST(TestDigest, TypedDigest_DifferingType) +{ + TypedDigest t1{CDigest::Type::SHA1, "da39a3ee5e6b4b0d3255bfef95601890afd80709"}; + TypedDigest t2{CDigest::Type::SHA256, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}; + // Silence "unused expression" warning + bool a; + EXPECT_THROW(a = (t1 == t2), std::logic_error); + // Silence "unused variable" warning + (void)a; + EXPECT_THROW(a = (t1 != t2), std::logic_error); + (void)a; +} diff --git a/xbmc/utils/test/TestEndianSwap.cpp b/xbmc/utils/test/TestEndianSwap.cpp new file mode 100644 index 0000000..70d3cf0 --- /dev/null +++ b/xbmc/utils/test/TestEndianSwap.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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/xbmc/utils/test/TestExecString.cpp b/xbmc/utils/test/TestExecString.cpp new file mode 100644 index 0000000..4577b87 --- /dev/null +++ b/xbmc/utils/test/TestExecString.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FileItem.h" +#include "utils/ExecString.h" + +#include <gtest/gtest.h> + +TEST(TestExecString, ctor_1) +{ + { + const CExecString exec("ActivateWindow(Video, \"C:\\test\\foo\")"); + EXPECT_EQ(exec.IsValid(), true); + EXPECT_EQ(exec.GetFunction(), "activatewindow"); + EXPECT_EQ(exec.GetParams().size(), 2U); + EXPECT_EQ(exec.GetParams()[0], "Video"); + EXPECT_EQ(exec.GetParams()[1], "C:\\test\\foo"); + EXPECT_EQ(exec.GetExecString(), "ActivateWindow(Video, \"C:\\test\\foo\")"); + } + { + const CExecString exec("ActivateWindow(Video, \"C:\\test\\foo\\\")"); + EXPECT_EQ(exec.IsValid(), true); + EXPECT_EQ(exec.GetFunction(), "activatewindow"); + EXPECT_EQ(exec.GetParams().size(), 2U); + EXPECT_EQ(exec.GetParams()[0], "Video"); + EXPECT_EQ(exec.GetParams()[1], "C:\\test\\foo"); + EXPECT_EQ(exec.GetExecString(), "ActivateWindow(Video, \"C:\\test\\foo\\\")"); + } + { + const CExecString exec("ActivateWindow(Video, \"C:\\\\test\\\\foo\\\\\")"); + EXPECT_EQ(exec.IsValid(), true); + EXPECT_EQ(exec.GetFunction(), "activatewindow"); + EXPECT_EQ(exec.GetParams().size(), 2U); + EXPECT_EQ(exec.GetParams()[0], "Video"); + EXPECT_EQ(exec.GetParams()[1], "C:\\test\\foo\\"); + EXPECT_EQ(exec.GetExecString(), "ActivateWindow(Video, \"C:\\\\test\\\\foo\\\\\")"); + } + { + const CExecString exec("ActivateWindow(Video, \"C:\\\\\\\\test\\\\\\foo\\\\\")"); + EXPECT_EQ(exec.IsValid(), true); + EXPECT_EQ(exec.GetFunction(), "activatewindow"); + EXPECT_EQ(exec.GetParams().size(), 2U); + EXPECT_EQ(exec.GetParams()[0], "Video"); + EXPECT_EQ(exec.GetParams()[1], "C:\\\\test\\\\foo\\"); + EXPECT_EQ(exec.GetExecString(), "ActivateWindow(Video, \"C:\\\\\\\\test\\\\\\foo\\\\\")"); + } + { + const CExecString exec("SetProperty(Foo,\"\")"); + EXPECT_EQ(exec.IsValid(), true); + EXPECT_EQ(exec.GetFunction(), "setproperty"); + EXPECT_EQ(exec.GetParams().size(), 2U); + EXPECT_EQ(exec.GetParams()[0], "Foo"); + EXPECT_EQ(exec.GetParams()[1], ""); + EXPECT_EQ(exec.GetExecString(), "SetProperty(Foo,\"\")"); + } + { + const CExecString exec("SetProperty(foo,ba(\"ba black )\",sheep))"); + EXPECT_EQ(exec.IsValid(), true); + EXPECT_EQ(exec.GetFunction(), "setproperty"); + EXPECT_EQ(exec.GetParams().size(), 2U); + EXPECT_EQ(exec.GetParams()[0], "foo"); + EXPECT_EQ(exec.GetParams()[1], "ba(\"ba black )\",sheep)"); + EXPECT_EQ(exec.GetExecString(), "SetProperty(foo,ba(\"ba black )\",sheep))"); + } +} + +TEST(TestExecString, ctor_2) +{ + { + const CExecString exec("ActivateWindow", {"Video", "C:\\test\\foo"}); + EXPECT_EQ(exec.IsValid(), true); + EXPECT_EQ(exec.GetFunction(), "activatewindow"); + EXPECT_EQ(exec.GetParams().size(), 2U); + EXPECT_EQ(exec.GetParams()[0], "Video"); + EXPECT_EQ(exec.GetParams()[1], "C:\\test\\foo"); + EXPECT_EQ(exec.GetExecString(), "ActivateWindow(Video,C:\\test\\foo)"); + } +} + +TEST(TestExecString, ctor_3) +{ + { + const CFileItem item("C:\\test\\foo", true); + const CExecString exec(item, "Video"); + EXPECT_EQ(exec.IsValid(), true); + EXPECT_EQ(exec.GetFunction(), "activatewindow"); + EXPECT_EQ(exec.GetParams().size(), 3U); + EXPECT_EQ(exec.GetParams()[0], "Video"); + EXPECT_EQ(exec.GetParams()[1], "\"C:\\\\test\\\\foo\\\\\""); + EXPECT_EQ(exec.GetParams()[2], "return"); + EXPECT_EQ(exec.GetExecString(), "ActivateWindow(Video,\"C:\\\\test\\\\foo\\\\\",return)"); + } + { + const CFileItem item("C:\\test\\foo\\", true); + const CExecString exec(item, "Video"); + EXPECT_EQ(exec.IsValid(), true); + EXPECT_EQ(exec.GetFunction(), "activatewindow"); + EXPECT_EQ(exec.GetParams().size(), 3U); + EXPECT_EQ(exec.GetParams()[0], "Video"); + EXPECT_EQ(exec.GetParams()[1], "\"C:\\\\test\\\\foo\\\\\""); + EXPECT_EQ(exec.GetParams()[2], "return"); + EXPECT_EQ(exec.GetExecString(), "ActivateWindow(Video,\"C:\\\\test\\\\foo\\\\\",return)"); + } + { + const CFileItem item("C:\\test\\foo", false); + const CExecString exec(item, "Video"); + EXPECT_EQ(exec.IsValid(), true); + EXPECT_EQ(exec.GetFunction(), "playmedia"); + EXPECT_EQ(exec.GetParams().size(), 1U); + EXPECT_EQ(exec.GetParams()[0], "\"C:\\\\test\\\\foo\""); + EXPECT_EQ(exec.GetExecString(), "PlayMedia(\"C:\\\\test\\\\foo\")"); + } +} diff --git a/xbmc/utils/test/TestFileOperationJob.cpp b/xbmc/utils/test/TestFileOperationJob.cpp new file mode 100644 index 0000000..1df243e --- /dev/null +++ b/xbmc/utils/test/TestFileOperationJob.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "test/TestUtils.h" +#include "utils/FileOperationJob.h" +#include "utils/URIUtils.h" + +#include <gtest/gtest.h> + +TEST(TestFileOperationJob, ActionCopy) +{ + XFILE::CFile *tmpfile; + std::string tmpfilepath, destfile; + CFileItemList items; + CFileOperationJob job; + + ASSERT_NE(nullptr, (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); + + std::string 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; + std::string tmpfilepath, destfile; + CFileItemList items; + CFileOperationJob job; + + ASSERT_NE(nullptr, (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); + + std::string 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)); + + EXPECT_FALSE(XBMC_DELETETEMPFILE(tmpfile)); +} + +TEST(TestFileOperationJob, ActionDelete) +{ + XFILE::CFile *tmpfile; + std::string tmpfilepath, destfile; + CFileItemList items; + CFileOperationJob job; + + ASSERT_NE(nullptr, (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); + + std::string 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)); + + EXPECT_FALSE(XBMC_DELETETEMPFILE(tmpfile)); +} + +TEST(TestFileOperationJob, ActionReplace) +{ + XFILE::CFile *tmpfile; + std::string tmpfilepath, destfile; + CFileItemList items; + CFileOperationJob job; + + ASSERT_NE(nullptr, (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); + + std::string 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; + std::string tmpfilepath, destpath; + CFileItemList items; + CFileOperationJob job; + + ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + + std::string 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; + std::string tmpfilepath, destpath; + CFileItemList items; + CFileOperationJob job; + + ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + + std::string 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; + std::string tmpfilepath, destfile; + CFileItemList items; + CFileOperationJob job; + + ASSERT_NE(nullptr, (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); + + std::string 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/xbmc/utils/test/TestFileUtils.cpp b/xbmc/utils/test/TestFileUtils.cpp new file mode 100644 index 0000000..bb9b8b6 --- /dev/null +++ b/xbmc/utils/test/TestFileUtils.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FileItem.h" +#include "filesystem/File.h" +#include "test/TestUtils.h" +#include "utils/FileUtils.h" + +#include <gtest/gtest.h> + +TEST(TestFileUtils, DeleteItem_CFileItemPtr) +{ + XFILE::CFile *tmpfile; + std::string tmpfilepath; + + ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfilepath = XBMC_TEMPFILEPATH(tmpfile); + + CFileItemPtr item(new CFileItem(tmpfilepath)); + item->SetPath(tmpfilepath); + item->m_bIsFolder = false; + item->Select(true); + tmpfile->Close(); //Close tmpfile before we try to delete it + EXPECT_TRUE(CFileUtils::DeleteItem(item)); + EXPECT_FALSE(XBMC_DELETETEMPFILE(tmpfile)); +} + +TEST(TestFileUtils, DeleteItemString) +{ + XFILE::CFile *tmpfile; + + ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE(""))); + tmpfile->Close(); //Close tmpfile before we try to delete it + EXPECT_TRUE(CFileUtils::DeleteItem(XBMC_TEMPFILEPATH(tmpfile))); + EXPECT_FALSE(XBMC_DELETETEMPFILE(tmpfile)); +} + +/* Executing RenameFile() requires input from the user */ +// static bool RenameFile(const std::string &strFile); diff --git a/xbmc/utils/test/TestGlobalsHandling.cpp b/xbmc/utils/test/TestGlobalsHandling.cpp new file mode 100644 index 0000000..5b8d26a --- /dev/null +++ b/xbmc/utils/test/TestGlobalsHandling.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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); + { + std::shared_ptr<TestGlobalPattern1> ptr = g_testGlobalPattern1Ref; + } +} diff --git a/xbmc/utils/test/TestGlobalsHandlingPattern1.h b/xbmc/utils/test/TestGlobalsHandlingPattern1.h new file mode 100644 index 0000000..92088b8 --- /dev/null +++ b/xbmc/utils/test/TestGlobalsHandlingPattern1.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/GlobalsHandling.h" + +#include <iostream> + +namespace xbmcutil +{ + namespace test + { + class TestGlobalPattern1 + { + public: + static bool ctorCalled; + static bool dtorCalled; + + int somethingToAccess = 0; + + TestGlobalPattern1() { 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/xbmc/utils/test/TestHTMLUtil.cpp b/xbmc/utils/test/TestHTMLUtil.cpp new file mode 100644 index 0000000..7d0e515 --- /dev/null +++ b/xbmc/utils/test/TestHTMLUtil.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/HTMLUtil.h" + +#include <gtest/gtest.h> + +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"\u00e5&\u20ac"; + HTML::CHTMLUtil::ConvertHTMLToW(inw, varstrw); + EXPECT_STREQ(refstrw.c_str(), varstrw.c_str()); +} diff --git a/xbmc/utils/test/TestHttpHeader.cpp b/xbmc/utils/test/TestHttpHeader.cpp new file mode 100644 index 0000000..1aeecc7 --- /dev/null +++ b/xbmc/utils/test/TestHttpHeader.cpp @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/HttpHeader.h" + +#include <string.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 strReplace(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(strReplace(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(strReplace(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(strReplace(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", strReplace(strReplace(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", strReplace(strReplace(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(1U, 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(2U, 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(3U, 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(3U, 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(1U, 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(2U, 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(1U, 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/xbmc/utils/test/TestHttpParser.cpp b/xbmc/utils/test/TestHttpParser.cpp new file mode 100644 index 0000000..1eb2932 --- /dev/null +++ b/xbmc/utils/test/TestHttpParser.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/HttpParser.h" + +#include <gtest/gtest.h> + +TEST(TestHttpParser, General) +{ + HttpParser a; + std::string 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"; + std::string 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/xbmc/utils/test/TestHttpRangeUtils.cpp b/xbmc/utils/test/TestHttpRangeUtils.cpp new file mode 100644 index 0000000..f988f10 --- /dev/null +++ b/xbmc/utils/test/TestHttpRangeUtils.cpp @@ -0,0 +1,887 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/HttpRangeUtils.h" + +#include <gtest/gtest.h> + +#define RANGES_START "bytes=" + +static const uint64_t DefaultFirstPosition = 1; +static const uint64_t DefaultLastPosition = 0; +static const uint64_t DefaultLength = 0; +static const void* DefaultData = NULL; + +TEST(TestHttpRange, FirstPosition) +{ + const uint64_t expectedFirstPosition = 25; + + CHttpRange range; + EXPECT_EQ(DefaultFirstPosition, range.GetFirstPosition()); + + range.SetFirstPosition(expectedFirstPosition); + EXPECT_EQ(expectedFirstPosition, range.GetFirstPosition()); +} + +TEST(TestHttpRange, LastPosition) +{ + const uint64_t expectedLastPosition = 25; + + CHttpRange range; + EXPECT_EQ(DefaultLastPosition, range.GetLastPosition()); + + range.SetLastPosition(expectedLastPosition); + EXPECT_EQ(expectedLastPosition, range.GetLastPosition()); +} + +TEST(TestHttpRange, Length) +{ + const uint64_t expectedFirstPosition = 10; + const uint64_t expectedLastPosition = 25; + const uint64_t expectedLength = expectedLastPosition - expectedFirstPosition + 1; + + CHttpRange range; + EXPECT_EQ(DefaultLength, range.GetLength()); + + range.SetFirstPosition(expectedFirstPosition); + range.SetLastPosition(expectedLastPosition); + EXPECT_EQ(expectedLength, range.GetLength()); + + CHttpRange range_length; + range.SetFirstPosition(expectedFirstPosition); + range.SetLength(expectedLength); + EXPECT_EQ(expectedLastPosition, range.GetLastPosition()); + EXPECT_EQ(expectedLength, range.GetLength()); +} + +TEST(TestHttpRange, IsValid) +{ + const uint64_t validFirstPosition = 10; + const uint64_t validLastPosition = 25; + const uint64_t invalidLastPosition = 5; + + CHttpRange range; + EXPECT_FALSE(range.IsValid()); + + range.SetFirstPosition(validFirstPosition); + EXPECT_FALSE(range.IsValid()); + + range.SetLastPosition(invalidLastPosition); + EXPECT_FALSE(range.IsValid()); + + range.SetLastPosition(validLastPosition); + EXPECT_TRUE(range.IsValid()); +} + +TEST(TestHttpRange, Ctor) +{ + const uint64_t validFirstPosition = 10; + const uint64_t validLastPosition = 25; + const uint64_t invalidLastPosition = 5; + const uint64_t validLength = validLastPosition - validFirstPosition + 1; + + CHttpRange range_invalid(validFirstPosition, invalidLastPosition); + EXPECT_EQ(validFirstPosition, range_invalid.GetFirstPosition()); + EXPECT_EQ(invalidLastPosition, range_invalid.GetLastPosition()); + EXPECT_EQ(DefaultLength, range_invalid.GetLength()); + EXPECT_FALSE(range_invalid.IsValid()); + + CHttpRange range_valid(validFirstPosition, validLastPosition); + EXPECT_EQ(validFirstPosition, range_valid.GetFirstPosition()); + EXPECT_EQ(validLastPosition, range_valid.GetLastPosition()); + EXPECT_EQ(validLength, range_valid.GetLength()); + EXPECT_TRUE(range_valid.IsValid()); +} + +TEST(TestHttpResponseRange, SetData) +{ + const uint64_t validFirstPosition = 1; + const uint64_t validLastPosition = 2; + const uint64_t validLength = validLastPosition - validFirstPosition + 1; + const char* validData = "test"; + const void* invalidData = DefaultData; + const size_t validDataLength = strlen(validData); + const size_t invalidDataLength = 1; + + CHttpResponseRange range; + EXPECT_EQ(DefaultData, range.GetData()); + EXPECT_FALSE(range.IsValid()); + + range.SetData(invalidData); + EXPECT_EQ(invalidData, range.GetData()); + EXPECT_FALSE(range.IsValid()); + + range.SetData(validData); + EXPECT_EQ(validData, range.GetData()); + EXPECT_FALSE(range.IsValid()); + + range.SetData(invalidData, 0); + EXPECT_EQ(validData, range.GetData()); + EXPECT_FALSE(range.IsValid()); + + range.SetData(invalidData, invalidDataLength); + EXPECT_EQ(invalidData, range.GetData()); + EXPECT_FALSE(range.IsValid()); + + range.SetData(validData, validDataLength); + EXPECT_EQ(validData, range.GetData()); + EXPECT_EQ(0U, range.GetFirstPosition()); + EXPECT_EQ(validDataLength - 1, range.GetLastPosition()); + EXPECT_EQ(validDataLength, range.GetLength()); + EXPECT_TRUE(range.IsValid()); + + range.SetData(invalidData, 0, 0); + EXPECT_EQ(invalidData, range.GetData()); + EXPECT_FALSE(range.IsValid()); + + range.SetData(validData, validFirstPosition, validLastPosition); + EXPECT_EQ(validData, range.GetData()); + EXPECT_EQ(validFirstPosition, range.GetFirstPosition()); + EXPECT_EQ(validLastPosition, range.GetLastPosition()); + EXPECT_EQ(validLength, range.GetLength()); + EXPECT_TRUE(range.IsValid()); +} + +TEST(TestHttpRanges, Ctor) +{ + CHttpRange range; + uint64_t position; + + CHttpRanges ranges_empty; + + EXPECT_EQ(0U, ranges_empty.Size()); + EXPECT_TRUE(ranges_empty.Get().empty()); + + EXPECT_FALSE(ranges_empty.Get(0, range)); + EXPECT_FALSE(ranges_empty.GetFirst(range)); + EXPECT_FALSE(ranges_empty.GetLast(range)); + + EXPECT_FALSE(ranges_empty.GetFirstPosition(position)); + EXPECT_FALSE(ranges_empty.GetLastPosition(position)); + EXPECT_EQ(0U, ranges_empty.GetLength()); + EXPECT_FALSE(ranges_empty.GetTotalRange(range)); +} + +TEST(TestHttpRanges, GetAll) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + + HttpRanges ranges_raw; + ranges_raw.push_back(range_0); + ranges_raw.push_back(range_1); + ranges_raw.push_back(range_2); + + CHttpRanges ranges(ranges_raw); + + const HttpRanges& ranges_raw_get = ranges.Get(); + ASSERT_EQ(ranges_raw.size(), ranges_raw_get.size()); + + for (size_t i = 0; i < ranges_raw.size(); ++i) + EXPECT_EQ(ranges_raw.at(i), ranges_raw_get.at(i)); +} + +TEST(TestHttpRanges, GetIndex) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + + HttpRanges ranges_raw; + ranges_raw.push_back(range_0); + ranges_raw.push_back(range_1); + ranges_raw.push_back(range_2); + + CHttpRanges ranges(ranges_raw); + + CHttpRange range; + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range_0, range); + + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range_1, range); + + EXPECT_TRUE(ranges.Get(2, range)); + EXPECT_EQ(range_2, range); + + EXPECT_FALSE(ranges.Get(3, range)); +} + +TEST(TestHttpRanges, GetFirst) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + + HttpRanges ranges_raw; + ranges_raw.push_back(range_0); + ranges_raw.push_back(range_1); + ranges_raw.push_back(range_2); + + CHttpRanges ranges(ranges_raw); + + CHttpRange range; + EXPECT_TRUE(ranges.GetFirst(range)); + EXPECT_EQ(range_0, range); +} + +TEST(TestHttpRanges, GetLast) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + + HttpRanges ranges_raw; + ranges_raw.push_back(range_0); + ranges_raw.push_back(range_1); + ranges_raw.push_back(range_2); + + CHttpRanges ranges(ranges_raw); + + CHttpRange range; + EXPECT_TRUE(ranges.GetLast(range)); + EXPECT_EQ(range_2, range); +} + +TEST(TestHttpRanges, Size) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + + HttpRanges ranges_raw; + ranges_raw.push_back(range_0); + ranges_raw.push_back(range_1); + ranges_raw.push_back(range_2); + + CHttpRanges ranges_empty; + EXPECT_EQ(0U, ranges_empty.Size()); + + CHttpRanges ranges(ranges_raw); + EXPECT_EQ(ranges_raw.size(), ranges.Size()); +} + +TEST(TestHttpRanges, GetFirstPosition) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + + HttpRanges ranges_raw; + ranges_raw.push_back(range_0); + ranges_raw.push_back(range_1); + ranges_raw.push_back(range_2); + + CHttpRanges ranges(ranges_raw); + + uint64_t position; + EXPECT_TRUE(ranges.GetFirstPosition(position)); + EXPECT_EQ(range_0.GetFirstPosition(), position); +} + +TEST(TestHttpRanges, GetLastPosition) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + + HttpRanges ranges_raw; + ranges_raw.push_back(range_0); + ranges_raw.push_back(range_1); + ranges_raw.push_back(range_2); + + CHttpRanges ranges(ranges_raw); + + uint64_t position; + EXPECT_TRUE(ranges.GetLastPosition(position)); + EXPECT_EQ(range_2.GetLastPosition(), position); +} + +TEST(TestHttpRanges, GetLength) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + const uint64_t expectedLength = range_0.GetLength() + range_1.GetLength() + range_2.GetLength(); + + HttpRanges ranges_raw; + ranges_raw.push_back(range_0); + ranges_raw.push_back(range_1); + ranges_raw.push_back(range_2); + + CHttpRanges ranges(ranges_raw); + + EXPECT_EQ(expectedLength, ranges.GetLength()); +} + +TEST(TestHttpRanges, GetTotalRange) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + CHttpRange range_total_expected(range_0.GetFirstPosition(), range_2.GetLastPosition()); + + HttpRanges ranges_raw; + ranges_raw.push_back(range_0); + ranges_raw.push_back(range_1); + ranges_raw.push_back(range_2); + + CHttpRanges ranges(ranges_raw); + + CHttpRange range_total; + EXPECT_TRUE(ranges.GetTotalRange(range_total)); + EXPECT_EQ(range_total_expected, range_total); +} + +TEST(TestHttpRanges, Add) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + + CHttpRanges ranges; + CHttpRange range; + + ranges.Add(range_0); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.GetFirst(range)); + EXPECT_EQ(range_0, range); + EXPECT_TRUE(ranges.GetLast(range)); + EXPECT_EQ(range_0, range); + + ranges.Add(range_1); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.GetFirst(range)); + EXPECT_EQ(range_0, range); + EXPECT_TRUE(ranges.GetLast(range)); + EXPECT_EQ(range_1, range); + + ranges.Add(range_2); + EXPECT_EQ(3U, ranges.Size()); + EXPECT_TRUE(ranges.GetFirst(range)); + EXPECT_EQ(range_0, range); + EXPECT_TRUE(ranges.GetLast(range)); + EXPECT_EQ(range_2, range); +} + +TEST(TestHttpRanges, Remove) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + + HttpRanges ranges_raw; + ranges_raw.push_back(range_0); + ranges_raw.push_back(range_1); + ranges_raw.push_back(range_2); + + CHttpRanges ranges(ranges_raw); + + CHttpRange range; + EXPECT_EQ(3U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range_1, range); + EXPECT_TRUE(ranges.Get(2, range)); + EXPECT_EQ(range_2, range); + + // remove non-existing range + ranges.Remove(ranges.Size()); + EXPECT_EQ(3U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range_1, range); + EXPECT_TRUE(ranges.Get(2, range)); + EXPECT_EQ(range_2, range); + + // remove first range + ranges.Remove(0); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range_1, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range_2, range); + + // remove last range + ranges.Remove(1); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range_1, range); + + // remove remaining range + ranges.Remove(0); + EXPECT_EQ(0U, ranges.Size()); +} + +TEST(TestHttpRanges, Clear) +{ + CHttpRange range_0(0, 2); + CHttpRange range_1(4, 6); + CHttpRange range_2(8, 10); + + HttpRanges ranges_raw; + ranges_raw.push_back(range_0); + ranges_raw.push_back(range_1); + ranges_raw.push_back(range_2); + + CHttpRanges ranges(ranges_raw); + + CHttpRange range; + EXPECT_EQ(3U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range_1, range); + EXPECT_TRUE(ranges.Get(2, range)); + EXPECT_EQ(range_2, range); + + ranges.Clear(); + EXPECT_EQ(0U, ranges.Size()); +} + +TEST(TestHttpRanges, ParseInvalid) +{ + CHttpRanges ranges; + + // combinations of invalid string and invalid total length + EXPECT_FALSE(ranges.Parse("")); + EXPECT_FALSE(ranges.Parse("", 0)); + EXPECT_FALSE(ranges.Parse("", 1)); + EXPECT_FALSE(ranges.Parse("test", 0)); + EXPECT_FALSE(ranges.Parse(RANGES_START, 0)); + + // empty range definition + EXPECT_FALSE(ranges.Parse(RANGES_START)); + EXPECT_FALSE(ranges.Parse(RANGES_START "-")); + + // bad characters in range definition + EXPECT_FALSE(ranges.Parse(RANGES_START "a")); + EXPECT_FALSE(ranges.Parse(RANGES_START "1a")); + EXPECT_FALSE(ranges.Parse(RANGES_START "1-a")); + EXPECT_FALSE(ranges.Parse(RANGES_START "a-a")); + EXPECT_FALSE(ranges.Parse(RANGES_START "a-1")); + EXPECT_FALSE(ranges.Parse(RANGES_START "--")); + EXPECT_FALSE(ranges.Parse(RANGES_START "1--")); + EXPECT_FALSE(ranges.Parse(RANGES_START "1--2")); + EXPECT_FALSE(ranges.Parse(RANGES_START "--2")); + + // combination of valid and empty range definitions + EXPECT_FALSE(ranges.Parse(RANGES_START "0-1,")); + EXPECT_FALSE(ranges.Parse(RANGES_START ",0-1")); + + // too big start position + EXPECT_FALSE(ranges.Parse(RANGES_START "10-11", 5)); + + // end position smaller than start position + EXPECT_FALSE(ranges.Parse(RANGES_START "1-0")); +} + +TEST(TestHttpRanges, ParseStartOnly) +{ + const uint64_t totalLength = 5; + const CHttpRange range0_(0, totalLength - 1); + const CHttpRange range2_(2, totalLength - 1); + + CHttpRange range; + + CHttpRanges ranges_all; + EXPECT_TRUE(ranges_all.Parse(RANGES_START "0-", totalLength)); + EXPECT_EQ(1U, ranges_all.Size()); + EXPECT_TRUE(ranges_all.Get(0, range)); + EXPECT_EQ(range0_, range); + + CHttpRanges ranges_some; + EXPECT_TRUE(ranges_some.Parse(RANGES_START "2-", totalLength)); + EXPECT_EQ(1U, ranges_some.Size()); + EXPECT_TRUE(ranges_some.Get(0, range)); + EXPECT_EQ(range2_, range); +} + +TEST(TestHttpRanges, ParseFromEnd) +{ + const uint64_t totalLength = 5; + const CHttpRange range_1(totalLength - 1, totalLength - 1); + const CHttpRange range_3(totalLength - 3, totalLength - 1); + + CHttpRange range; + + CHttpRanges ranges_1; + EXPECT_TRUE(ranges_1.Parse(RANGES_START "-1", totalLength)); + EXPECT_EQ(1U, ranges_1.Size()); + EXPECT_TRUE(ranges_1.Get(0, range)); + EXPECT_EQ(range_1, range); + + CHttpRanges ranges_3; + EXPECT_TRUE(ranges_3.Parse(RANGES_START "-3", totalLength)); + EXPECT_EQ(1U, ranges_3.Size()); + EXPECT_TRUE(ranges_3.Get(0, range)); + EXPECT_EQ(range_3, range); +} + +TEST(TestHttpRanges, ParseSingle) +{ + const uint64_t totalLength = 5; + const CHttpRange range0_0(0, 0); + const CHttpRange range0_1(0, 1); + const CHttpRange range0_5(0, totalLength - 1); + const CHttpRange range1_1(1, 1); + const CHttpRange range1_3(1, 3); + const CHttpRange range3_4(3, 4); + const CHttpRange range4_4(4, 4); + + CHttpRange range; + + CHttpRanges ranges; + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_0, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-1", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_1, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-5", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_5, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "1-1", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range1_1, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "1-3", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range1_3, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "3-4", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range3_4, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "4-4", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range4_4, range); +} + +TEST(TestHttpRanges, ParseMulti) +{ + const uint64_t totalLength = 6; + const CHttpRange range0_0(0, 0); + const CHttpRange range0_1(0, 1); + const CHttpRange range1_3(1, 3); + const CHttpRange range2_2(2, 2); + const CHttpRange range4_5(4, 5); + const CHttpRange range5_5(5, 5); + + CHttpRange range; + + CHttpRanges ranges; + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-2", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range2_2, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-2,4-5", totalLength)); + EXPECT_EQ(3U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range2_2, range); + EXPECT_TRUE(ranges.Get(2, range)); + EXPECT_EQ(range4_5, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,5-5", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_1, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range5_5, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "1-3,5-5", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range1_3, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range5_5, range); +} + +TEST(TestHttpRanges, ParseOrderedNotOverlapping) +{ + const uint64_t totalLength = 5; + const CHttpRange range0_0(0, 0); + const CHttpRange range0_1(0, 1); + const CHttpRange range2_2(2, 2); + const CHttpRange range2_(2, totalLength - 1); + const CHttpRange range_1(totalLength - 1, totalLength - 1); + + CHttpRange range; + + CHttpRanges ranges; + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,-1", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range_1, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-2,-1", totalLength)); + EXPECT_EQ(3U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range2_2, range); + EXPECT_TRUE(ranges.Get(2, range)); + EXPECT_EQ(range_1, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range2_, range); +} + +TEST(TestHttpRanges, ParseOrderedBackToBack) +{ + const uint64_t totalLength = 5; + const CHttpRange range0_1(0, 1); + const CHttpRange range0_2(0, 2); + const CHttpRange range1_2(1, 2); + const CHttpRange range0_3(0, 3); + const CHttpRange range4_4(4, 4); + const CHttpRange range0_4(0, 4); + const CHttpRange range3_4(3, 4); + + CHttpRange range; + + CHttpRanges ranges; + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,1-1", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_1, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,1-1,2-2", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_2, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,1-1,2-2,3-3", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_3, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,1-1,2-2,3-3,4-4", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_4, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,1-1,3-3,4-4", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_1, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range3_4, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "1-1,2-2,4-4", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range1_2, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range4_4, range); +} + +TEST(TestHttpRanges, ParseOrderedOverlapping) +{ + const uint64_t totalLength = 5; + const CHttpRange range0_0(0, 0); + const CHttpRange range0_1(0, 1); + const CHttpRange range0_2(0, 2); + const CHttpRange range0_3(0, 3); + const CHttpRange range0_4(0, 4); + const CHttpRange range2_4(2, 4); + + CHttpRange range; + + CHttpRanges ranges; + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,0-1", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_1, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,0-1,0-2", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_2, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,0-1,1-2", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_2, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,0-2,1-3", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_3, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,1-2,2-3,3-4", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_4, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-3,2-4,4-4", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range2_4, range); +} + +TEST(TestHttpRanges, ParseUnorderedNotOverlapping) +{ + const uint64_t totalLength = 5; + const CHttpRange range0_0(0, 0); + const CHttpRange range0_1(0, 1); + const CHttpRange range2_2(2, 2); + const CHttpRange range2_(2, totalLength - 1); + const CHttpRange range_1(totalLength - 1, totalLength - 1); + + CHttpRange range; + + CHttpRanges ranges; + EXPECT_TRUE(ranges.Parse(RANGES_START "-1,0-0", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range_1, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "2-2,-1,0-0", totalLength)); + EXPECT_EQ(3U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range2_2, range); + EXPECT_TRUE(ranges.Get(2, range)); + EXPECT_EQ(range_1, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "2-,0-0", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range2_, range); +} + +TEST(TestHttpRanges, ParseUnorderedBackToBack) +{ + const uint64_t totalLength = 5; + const CHttpRange range0_0(0, 0); + const CHttpRange range1_1(1, 1); + const CHttpRange range0_1(0, 1); + const CHttpRange range2_2(2, 2); + const CHttpRange range0_2(0, 2); + const CHttpRange range1_2(1, 2); + const CHttpRange range3_3(3, 3); + const CHttpRange range0_3(0, 3); + const CHttpRange range4_4(4, 4); + const CHttpRange range0_4(0, 4); + const CHttpRange range3_4(3, 4); + + CHttpRange range; + + CHttpRanges ranges; + EXPECT_TRUE(ranges.Parse(RANGES_START "1-1,0-0", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_1, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "1-1,0-0,2-2", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_2, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "2-2,1-1,3-3,0-0", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_3, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,1-1,0-0,2-2,3-3", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_4, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "3-3,0-0,4-4,1-1", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_1, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range3_4, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,1-1,2-2", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range1_2, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range4_4, range); +} + +TEST(TestHttpRanges, ParseUnorderedOverlapping) +{ + const uint64_t totalLength = 5; + const CHttpRange range0_0(0, 0); + const CHttpRange range0_1(0, 1); + const CHttpRange range0_2(0, 2); + const CHttpRange range0_3(0, 3); + const CHttpRange range0_4(0, 4); + const CHttpRange range2_4(2, 4); + + CHttpRange range; + + CHttpRanges ranges; + EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,0-0", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_1, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-2,0-0,0-1", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_2, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,1-2,0-0", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_2, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "0-2,0-0,1-3", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_3, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "2-3,1-2,0-1,3-4", totalLength)); + EXPECT_EQ(1U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_4, range); + + EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,0-0,2-4,2-3", totalLength)); + EXPECT_EQ(2U, ranges.Size()); + EXPECT_TRUE(ranges.Get(0, range)); + EXPECT_EQ(range0_0, range); + EXPECT_TRUE(ranges.Get(1, range)); + EXPECT_EQ(range2_4, range); +} diff --git a/xbmc/utils/test/TestHttpResponse.cpp b/xbmc/utils/test/TestHttpResponse.cpp new file mode 100644 index 0000000..1f66285 --- /dev/null +++ b/xbmc/utils/test/TestHttpResponse.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/HttpResponse.h" + +#include <gtest/gtest.h> + +TEST(TestHttpResponse, General) +{ + CHttpResponse a(HTTP::POST, HTTP::OK); + std::string response, content, refstr; + + 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()); + + response = a.Create();; + EXPECT_EQ((unsigned int)210, response.size()); + + 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/xbmc/utils/test/TestJSONVariantParser.cpp b/xbmc/utils/test/TestJSONVariantParser.cpp new file mode 100644 index 0000000..b8556b0 --- /dev/null +++ b/xbmc/utils/test/TestJSONVariantParser.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/JSONVariantParser.h" +#include "utils/Variant.h" + +#include <gtest/gtest.h> + +TEST(TestJSONVariantParser, CannotParseNullptr) +{ + CVariant variant; + ASSERT_FALSE(CJSONVariantParser::Parse(nullptr, variant)); +} + +TEST(TestJSONVariantParser, CannotParseEmptyString) +{ + CVariant variant; + ASSERT_FALSE(CJSONVariantParser::Parse("", variant)); + ASSERT_FALSE(CJSONVariantParser::Parse(std::string(), variant)); +} + +TEST(TestJSONVariantParser, CannotParseInvalidJson) +{ + CVariant variant; + ASSERT_FALSE(CJSONVariantParser::Parse("{", variant)); + ASSERT_FALSE(CJSONVariantParser::Parse("}", variant)); + ASSERT_FALSE(CJSONVariantParser::Parse("[", variant)); + ASSERT_FALSE(CJSONVariantParser::Parse("]", variant)); + ASSERT_FALSE(CJSONVariantParser::Parse("foo", variant)); +} + +TEST(TestJSONVariantParser, CanParseNull) +{ + CVariant variant; + ASSERT_TRUE(CJSONVariantParser::Parse("null", variant)); + ASSERT_TRUE(variant.isNull()); +} + +TEST(TestJSONVariantParser, CanParseBoolean) +{ + CVariant variant; + ASSERT_TRUE(CJSONVariantParser::Parse("true", variant)); + ASSERT_TRUE(variant.isBoolean()); + ASSERT_TRUE(variant.asBoolean()); + + ASSERT_TRUE(CJSONVariantParser::Parse("false", variant)); + ASSERT_TRUE(variant.isBoolean()); + ASSERT_FALSE(variant.asBoolean()); +} + +TEST(TestJSONVariantParser, CanParseSignedInteger) +{ + CVariant variant; + ASSERT_TRUE(CJSONVariantParser::Parse("-1", variant)); + ASSERT_TRUE(variant.isInteger()); + ASSERT_EQ(-1, variant.asInteger()); +} + +TEST(TestJSONVariantParser, CanParseUnsignedInteger) +{ + CVariant variant; + ASSERT_TRUE(CJSONVariantParser::Parse("0", variant)); + ASSERT_TRUE(variant.isUnsignedInteger()); + ASSERT_EQ(0U, variant.asUnsignedInteger()); + + ASSERT_TRUE(CJSONVariantParser::Parse("1", variant)); + ASSERT_TRUE(variant.isUnsignedInteger()); + ASSERT_EQ(1U, variant.asUnsignedInteger()); +} + +TEST(TestJSONVariantParser, CanParseSignedInteger64) +{ + CVariant variant; + ASSERT_TRUE(CJSONVariantParser::Parse("-4294967296", variant)); + ASSERT_TRUE(variant.isInteger()); + ASSERT_EQ(-4294967296, variant.asInteger()); +} + +TEST(TestJSONVariantParser, CanParseUnsignedInteger64) +{ + CVariant variant; + ASSERT_TRUE(CJSONVariantParser::Parse("4294967296", variant)); + ASSERT_TRUE(variant.isUnsignedInteger()); + ASSERT_EQ(4294967296U, variant.asUnsignedInteger()); +} + +TEST(TestJSONVariantParser, CanParseDouble) +{ + CVariant variant; + ASSERT_TRUE(CJSONVariantParser::Parse("0.0", variant)); + ASSERT_TRUE(variant.isDouble()); + ASSERT_EQ(0.0, variant.asDouble()); + + ASSERT_TRUE(CJSONVariantParser::Parse("1.0", variant)); + ASSERT_TRUE(variant.isDouble()); + ASSERT_EQ(1.0, variant.asDouble()); + + ASSERT_TRUE(CJSONVariantParser::Parse("-1.0", variant)); + ASSERT_TRUE(variant.isDouble()); + ASSERT_EQ(-1.0, variant.asDouble()); +} + +TEST(TestJSONVariantParser, CanParseString) +{ + CVariant variant; + ASSERT_TRUE(CJSONVariantParser::Parse("\"\"", variant)); + ASSERT_TRUE(variant.isString()); + ASSERT_TRUE(variant.empty()); + + ASSERT_TRUE(CJSONVariantParser::Parse("\"foo\"", variant)); + ASSERT_TRUE(variant.isString()); + ASSERT_STREQ("foo", variant.asString().c_str()); + + ASSERT_TRUE(CJSONVariantParser::Parse("\"foo bar\"", variant)); + ASSERT_TRUE(variant.isString()); + ASSERT_STREQ("foo bar", variant.asString().c_str()); +} + +TEST(TestJSONVariantParser, CanParseObject) +{ + CVariant variant; + ASSERT_TRUE(CJSONVariantParser::Parse("{}", variant)); + ASSERT_TRUE(variant.isObject()); + ASSERT_TRUE(variant.empty()); + + variant.clear(); + ASSERT_TRUE(CJSONVariantParser::Parse("{ \"foo\": \"bar\" }", variant)); + ASSERT_TRUE(variant.isObject()); + ASSERT_TRUE(variant.isMember("foo")); + ASSERT_TRUE(variant["foo"].isString()); + ASSERT_STREQ("bar", variant["foo"].asString().c_str()); + + variant.clear(); + ASSERT_TRUE(CJSONVariantParser::Parse("{ \"foo\": \"bar\", \"bar\": true }", variant)); + ASSERT_TRUE(variant.isObject()); + ASSERT_TRUE(variant.isMember("foo")); + ASSERT_TRUE(variant["foo"].isString()); + ASSERT_STREQ("bar", variant["foo"].asString().c_str()); + ASSERT_TRUE(variant.isMember("bar")); + ASSERT_TRUE(variant["bar"].isBoolean()); + ASSERT_TRUE(variant["bar"].asBoolean()); + + variant.clear(); + ASSERT_TRUE(CJSONVariantParser::Parse("{ \"foo\": { \"sub-foo\": \"bar\" } }", variant)); + ASSERT_TRUE(variant.isObject()); + ASSERT_TRUE(variant.isMember("foo")); + ASSERT_TRUE(variant["foo"].isObject()); + ASSERT_TRUE(variant["foo"].isMember("sub-foo")); + ASSERT_TRUE(variant["foo"]["sub-foo"].isString()); + ASSERT_STREQ("bar", variant["foo"]["sub-foo"].asString().c_str()); +} + +TEST(TestJSONVariantParser, CanParseArray) +{ + CVariant variant; + ASSERT_TRUE(CJSONVariantParser::Parse("[]", variant)); + ASSERT_TRUE(variant.isArray()); + ASSERT_TRUE(variant.empty()); + + variant.clear(); + ASSERT_TRUE(CJSONVariantParser::Parse("[ true ]", variant)); + ASSERT_TRUE(variant.isArray()); + ASSERT_EQ(1U, variant.size()); + ASSERT_TRUE(variant[0].isBoolean()); + ASSERT_TRUE(variant[0].asBoolean()); + + variant.clear(); + ASSERT_TRUE(CJSONVariantParser::Parse("[ true, \"foo\" ]", variant)); + ASSERT_TRUE(variant.isArray()); + ASSERT_EQ(2U, variant.size()); + ASSERT_TRUE(variant[0].isBoolean()); + ASSERT_TRUE(variant[0].asBoolean()); + ASSERT_TRUE(variant[1].isString()); + ASSERT_STREQ("foo", variant[1].asString().c_str()); + + variant.clear(); + ASSERT_TRUE(CJSONVariantParser::Parse("[ { \"foo\": \"bar\" } ]", variant)); + ASSERT_TRUE(variant.isArray()); + ASSERT_EQ(1U, variant.size()); + ASSERT_TRUE(variant[0].isObject()); + ASSERT_TRUE(variant[0].isMember("foo")); + ASSERT_TRUE(variant[0]["foo"].isString()); + ASSERT_STREQ("bar", variant[0]["foo"].asString().c_str()); +} diff --git a/xbmc/utils/test/TestJSONVariantWriter.cpp b/xbmc/utils/test/TestJSONVariantWriter.cpp new file mode 100644 index 0000000..0772a4d --- /dev/null +++ b/xbmc/utils/test/TestJSONVariantWriter.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/JSONVariantWriter.h" +#include "utils/Variant.h" + +#include <gtest/gtest.h> + +TEST(TestJSONVariantWriter, CanWriteNull) +{ + CVariant variant; + std::string str; + + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("null", str.c_str()); +} + +TEST(TestJSONVariantWriter, CanWriteBoolean) +{ + CVariant variant(true); + std::string str; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("true", str.c_str()); + + variant = false; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("false", str.c_str()); +} + +TEST(TestJSONVariantWriter, CanWriteSignedInteger) +{ + CVariant variant(-1); + std::string str; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("-1", str.c_str()); +} + +TEST(TestJSONVariantWriter, CanWriteUnsignedInteger) +{ + CVariant variant(0); + std::string str; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("0", str.c_str()); + + variant = 1; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("1", str.c_str()); +} + +TEST(TestJSONVariantWriter, CanWriteSignedInteger64) +{ + CVariant variant(static_cast<int64_t>(-4294967296LL)); + std::string str; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("-4294967296", str.c_str()); +} + +TEST(TestJSONVariantWriter, CanWriteUnsignedInteger64) +{ + CVariant variant(static_cast<int64_t>(4294967296LL)); + std::string str; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("4294967296", str.c_str()); +} + +TEST(TestJSONVariantWriter, CanWriteDouble) +{ + CVariant variant(0.0); + std::string str; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("0.0", str.c_str()); + + variant = 1.0; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("1.0", str.c_str()); + + variant = -1.0; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("-1.0", str.c_str()); +} + +TEST(TestJSONVariantWriter, CanWriteString) +{ + CVariant variant(""); + std::string str; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("\"\"", str.c_str()); + + variant = "foo"; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("\"foo\"", str.c_str()); + + variant = "foo bar"; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("\"foo bar\"", str.c_str()); +} + +TEST(TestJSONVariantWriter, CanWriteObject) +{ + CVariant variant(CVariant::VariantTypeObject); + std::string str; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("{}", str.c_str()); + + variant.clear(); + variant["foo"] = "bar"; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("{\n\t\"foo\": \"bar\"\n}", str.c_str()); + + variant.clear(); + variant["foo"] = "bar"; + variant["bar"] = true; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("{\n\t\"bar\": true,\n\t\"foo\": \"bar\"\n}", str.c_str()); + + variant.clear(); + variant["foo"]["sub-foo"] = "bar"; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("{\n\t\"foo\": {\n\t\t\"sub-foo\": \"bar\"\n\t}\n}", str.c_str()); +} + +TEST(TestJSONVariantWriter, CanWriteArray) +{ + CVariant variant(CVariant::VariantTypeArray); + std::string str; + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("[]", str.c_str()); + + variant.clear(); + variant.push_back(true); + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("[\n\ttrue\n]", str.c_str()); + + variant.clear(); + variant.push_back(true); + variant.push_back("foo"); + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("[\n\ttrue,\n\t\"foo\"\n]", str.c_str()); + + variant.clear(); + CVariant obj(CVariant::VariantTypeObject); + obj["foo"] = "bar"; + variant.push_back(obj); + ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false)); + ASSERT_STREQ("[\n\t{\n\t\t\"foo\": \"bar\"\n\t}\n]", str.c_str()); +} diff --git a/xbmc/utils/test/TestJobManager.cpp b/xbmc/utils/test/TestJobManager.cpp new file mode 100644 index 0000000..86f0af9 --- /dev/null +++ b/xbmc/utils/test/TestJobManager.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ServiceBroker.h" +#include "test/MtTestUtils.h" +#include "utils/Job.h" +#include "utils/JobManager.h" +#include "utils/XTimeUtils.h" + +#include <atomic> +#include <mutex> + +#include <gtest/gtest.h> + +using namespace ConditionPoll; + +struct Flags +{ + std::atomic<bool> lingerAtWork{true}; + std::atomic<bool> started{false}; + std::atomic<bool> finished{false}; + std::atomic<bool> wasCanceled{false}; +}; + +class DummyJob : public CJob +{ + Flags* m_flags; +public: + inline DummyJob(Flags* flags) : m_flags(flags) + { + } + + bool DoWork() override + { + m_flags->started = true; + while (m_flags->lingerAtWork) + std::this_thread::yield(); + + if (ShouldCancel(0,0)) + m_flags->wasCanceled = true; + + m_flags->finished = true; + return true; + } +}; + +class ReallyDumbJob : public CJob +{ + Flags* m_flags; +public: + inline ReallyDumbJob(Flags* flags) : m_flags(flags) {} + + bool DoWork() override + { + m_flags->finished = true; + return true; + } +}; + +class TestJobManager : public testing::Test +{ +protected: + TestJobManager() { CServiceBroker::RegisterJobManager(std::make_shared<CJobManager>()); } + + ~TestJobManager() override + { + /* Always cancel jobs test completion */ + CServiceBroker::GetJobManager()->CancelJobs(); + CServiceBroker::GetJobManager()->Restart(); + CServiceBroker::UnregisterJobManager(); + } +}; + +TEST_F(TestJobManager, AddJob) +{ + Flags* flags = new Flags(); + ReallyDumbJob* job = new ReallyDumbJob(flags); + CServiceBroker::GetJobManager()->AddJob(job, nullptr); + ASSERT_TRUE(poll([flags]() -> bool { return flags->finished; })); + delete flags; +} + +TEST_F(TestJobManager, CancelJob) +{ + unsigned int id; + Flags* flags = new Flags(); + DummyJob* job = new DummyJob(flags); + id = CServiceBroker::GetJobManager()->AddJob(job, nullptr); + + // wait for the worker thread to be entered + ASSERT_TRUE(poll([flags]() -> bool { return flags->started; })); + + // cancel the job + CServiceBroker::GetJobManager()->CancelJob(id); + + // let the worker thread continue + flags->lingerAtWork = false; + + // make sure the job finished. + ASSERT_TRUE(poll([flags]() -> bool { return flags->finished; })); + + // ... and that it was canceled. + EXPECT_TRUE(flags->wasCanceled); + delete flags; +} + +namespace +{ +struct JobControlPackage +{ + JobControlPackage() + { + // We're not ready to wait yet + jobCreatedMutex.lock(); + } + + ~JobControlPackage() + { + jobCreatedMutex.unlock(); + } + + bool ready = false; + XbmcThreads::ConditionVariable jobCreatedCond; + CCriticalSection jobCreatedMutex; +}; + +class BroadcastingJob : + public CJob +{ +public: + + BroadcastingJob(JobControlPackage &package) : + m_package(package), + m_finish(false) + { + } + + void FinishAndStopBlocking() + { + std::unique_lock<CCriticalSection> lock(m_blockMutex); + + m_finish = true; + m_block.notifyAll(); + } + + const char * GetType() const override + { + return "BroadcastingJob"; + } + + bool DoWork() override + { + { + std::unique_lock<CCriticalSection> lock(m_package.jobCreatedMutex); + + m_package.ready = true; + m_package.jobCreatedCond.notifyAll(); + } + + std::unique_lock<CCriticalSection> 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); + CServiceBroker::GetJobManager()->AddJob(job, nullptr, 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(CServiceBroker::GetJobManager()->IsProcessing(CJob::PRIORITY_LOW_PAUSABLE)); + CServiceBroker::GetJobManager()->PauseJobs(); + EXPECT_FALSE(CServiceBroker::GetJobManager()->IsProcessing(CJob::PRIORITY_LOW_PAUSABLE)); + CServiceBroker::GetJobManager()->UnPauseJobs(); + EXPECT_TRUE(CServiceBroker::GetJobManager()->IsProcessing(CJob::PRIORITY_LOW_PAUSABLE)); + + job->FinishAndStopBlocking(); +} + +TEST_F(TestJobManager, IsProcessing) +{ + JobControlPackage package; + BroadcastingJob *job (WaitForJobToStartProcessing(CJob::PRIORITY_LOW_PAUSABLE, package)); + + EXPECT_EQ(0, CServiceBroker::GetJobManager()->IsProcessing("")); + + job->FinishAndStopBlocking(); +} diff --git a/xbmc/utils/test/TestLabelFormatter.cpp b/xbmc/utils/test/TestLabelFormatter.cpp new file mode 100644 index 0000000..633e89a --- /dev/null +++ b/xbmc/utils/test/TestLabelFormatter.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "filesystem/File.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "test/TestUtils.h" +#include "utils/LabelFormatter.h" + +#include <gtest/gtest.h> + +/* Set default settings used by CLabelFormatter. */ +class TestLabelFormatter : public testing::Test +{ +protected: + TestLabelFormatter() = default; + + ~TestLabelFormatter() override + { + CServiceBroker::GetSettingsComponent()->GetSettings()->Unload(); + } +}; + +TEST_F(TestLabelFormatter, FormatLabel) +{ + XFILE::CFile *tmpfile; + std::string tmpfilepath; + LABEL_MASKS labelMasks; + CLabelFormatter formatter("", labelMasks.m_strLabel2File); + + ASSERT_NE(nullptr, (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; + LABEL_MASKS labelMasks; + CLabelFormatter formatter("", labelMasks.m_strLabel2File); + + ASSERT_NE(nullptr, (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/xbmc/utils/test/TestLangCodeExpander.cpp b/xbmc/utils/test/TestLangCodeExpander.cpp new file mode 100644 index 0000000..7a6dde1 --- /dev/null +++ b/xbmc/utils/test/TestLangCodeExpander.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/LangCodeExpander.h" + +#include <gtest/gtest.h> + +TEST(TestLangCodeExpander, ConvertISO6391ToISO6392B) +{ + std::string refstr, varstr; + + refstr = "eng"; + g_LangCodeExpander.ConvertISO6391ToISO6392B("en", varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} + +TEST(TestLangCodeExpander, ConvertToISO6392B) +{ + std::string refstr, varstr; + + refstr = "eng"; + g_LangCodeExpander.ConvertToISO6392B("en", varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); +} diff --git a/xbmc/utils/test/TestLocale.cpp b/xbmc/utils/test/TestLocale.cpp new file mode 100644 index 0000000..f5193ed --- /dev/null +++ b/xbmc/utils/test/TestLocale.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/Locale.h" +#include "utils/StringUtils.h" + +#include <gtest/gtest.h> + +static const std::string TerritorySeparator = "_"; +static const std::string CodesetSeparator = "."; +static const std::string ModifierSeparator = "@"; + +static const std::string LanguageCodeEnglish = "en"; +static const std::string TerritoryCodeBritain = "GB"; +static const std::string CodesetUtf8 = "UTF-8"; +static const std::string ModifierLatin = "latin"; + +TEST(TestLocale, DefaultLocale) +{ + CLocale locale; + ASSERT_FALSE(locale.IsValid()); + ASSERT_STREQ("", locale.GetLanguageCode().c_str()); + ASSERT_STREQ("", locale.GetTerritoryCode().c_str()); + ASSERT_STREQ("", locale.GetCodeset().c_str()); + ASSERT_STREQ("", locale.GetModifier().c_str()); + ASSERT_STREQ("", locale.ToString().c_str()); +} + +TEST(TestLocale, LanguageLocale) +{ + CLocale locale(LanguageCodeEnglish); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str()); + ASSERT_STREQ("", locale.GetTerritoryCode().c_str()); + ASSERT_STREQ("", locale.GetCodeset().c_str()); + ASSERT_STREQ("", locale.GetModifier().c_str()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToString().c_str()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToStringLC().c_str()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortString().c_str()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortStringLC().c_str()); +} + +TEST(TestLocale, LanguageTerritoryLocale) +{ + const std::string strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain; + std::string strLocaleLC = strLocale; + StringUtils::ToLower(strLocaleLC); + + CLocale locale(LanguageCodeEnglish, TerritoryCodeBritain); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str()); + ASSERT_STREQ(TerritoryCodeBritain.c_str(), locale.GetTerritoryCode().c_str()); + ASSERT_STREQ("", locale.GetCodeset().c_str()); + ASSERT_STREQ("", locale.GetModifier().c_str()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str()); + ASSERT_STREQ(strLocale.c_str(), locale.ToShortString().c_str()); + ASSERT_STREQ(strLocaleLC.c_str(), locale.ToShortStringLC().c_str()); +} + +TEST(TestLocale, LanguageCodesetLocale) +{ + const std::string strLocale = LanguageCodeEnglish + CodesetSeparator + CodesetUtf8; + std::string strLocaleLC = strLocale; + StringUtils::ToLower(strLocaleLC); + + CLocale locale(LanguageCodeEnglish, "", CodesetUtf8); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str()); + ASSERT_STREQ("", locale.GetTerritoryCode().c_str()); + ASSERT_STREQ(CodesetUtf8.c_str(), locale.GetCodeset().c_str()); + ASSERT_STREQ("", locale.GetModifier().c_str()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortString().c_str()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortStringLC().c_str()); +} + +TEST(TestLocale, LanguageModifierLocale) +{ + const std::string strLocale = LanguageCodeEnglish + ModifierSeparator + ModifierLatin; + std::string strLocaleLC = strLocale; + StringUtils::ToLower(strLocaleLC); + + CLocale locale(LanguageCodeEnglish, "", "", ModifierLatin); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str()); + ASSERT_STREQ("", locale.GetTerritoryCode().c_str()); + ASSERT_STREQ("", locale.GetCodeset().c_str()); + ASSERT_STREQ(ModifierLatin.c_str(), locale.GetModifier().c_str()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortString().c_str()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortStringLC().c_str()); +} + +TEST(TestLocale, LanguageTerritoryCodesetLocale) +{ + const std::string strLocaleShort = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain; + std::string strLocaleShortLC = strLocaleShort; + StringUtils::ToLower(strLocaleShortLC); + const std::string strLocale = strLocaleShort + CodesetSeparator + CodesetUtf8; + std::string strLocaleLC = strLocale; + StringUtils::ToLower(strLocaleLC); + + CLocale locale(LanguageCodeEnglish, TerritoryCodeBritain, CodesetUtf8); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str()); + ASSERT_STREQ(TerritoryCodeBritain.c_str(), locale.GetTerritoryCode().c_str()); + ASSERT_STREQ(CodesetUtf8.c_str(), locale.GetCodeset().c_str()); + ASSERT_STREQ("", locale.GetModifier().c_str()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str()); + ASSERT_STREQ(strLocaleShort.c_str(), locale.ToShortString().c_str()); + ASSERT_STREQ(strLocaleShortLC.c_str(), locale.ToShortStringLC().c_str()); +} + +TEST(TestLocale, LanguageTerritoryModifierLocale) +{ + const std::string strLocaleShort = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain; + std::string strLocaleShortLC = strLocaleShort; + StringUtils::ToLower(strLocaleShortLC); + const std::string strLocale = strLocaleShort + ModifierSeparator + ModifierLatin; + std::string strLocaleLC = strLocale; + StringUtils::ToLower(strLocaleLC); + + CLocale locale(LanguageCodeEnglish, TerritoryCodeBritain, "", ModifierLatin); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str()); + ASSERT_STREQ(TerritoryCodeBritain.c_str(), locale.GetTerritoryCode().c_str()); + ASSERT_STREQ("", locale.GetCodeset().c_str()); + ASSERT_STREQ(ModifierLatin.c_str(), locale.GetModifier().c_str()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str()); + ASSERT_STREQ(strLocaleShort.c_str(), locale.ToShortString().c_str()); + ASSERT_STREQ(strLocaleShortLC.c_str(), locale.ToShortStringLC().c_str()); +} + +TEST(TestLocale, LanguageTerritoryCodesetModifierLocale) +{ + const std::string strLocaleShort = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain; + std::string strLocaleShortLC = strLocaleShort; + StringUtils::ToLower(strLocaleShortLC); + const std::string strLocale = strLocaleShort + CodesetSeparator + CodesetUtf8 + ModifierSeparator + ModifierLatin; + std::string strLocaleLC = strLocale; + StringUtils::ToLower(strLocaleLC); + + CLocale locale(LanguageCodeEnglish, TerritoryCodeBritain, CodesetUtf8, ModifierLatin); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str()); + ASSERT_STREQ(TerritoryCodeBritain.c_str(), locale.GetTerritoryCode().c_str()); + ASSERT_STREQ(CodesetUtf8.c_str(), locale.GetCodeset().c_str()); + ASSERT_STREQ(ModifierLatin.c_str(), locale.GetModifier().c_str()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str()); + ASSERT_STREQ(strLocaleShort.c_str(), locale.ToShortString().c_str()); + ASSERT_STREQ(strLocaleShortLC.c_str(), locale.ToShortStringLC().c_str()); +} + +TEST(TestLocale, FullStringLocale) +{ + const std::string strLocaleShort = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain; + std::string strLocaleShortLC = strLocaleShort; + StringUtils::ToLower(strLocaleShortLC); + const std::string strLocale = strLocaleShort + CodesetSeparator + CodesetUtf8 + ModifierSeparator + ModifierLatin; + std::string strLocaleLC = strLocale; + StringUtils::ToLower(strLocaleLC); + + CLocale locale(strLocale); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str()); + ASSERT_STREQ(TerritoryCodeBritain.c_str(), locale.GetTerritoryCode().c_str()); + ASSERT_STREQ(CodesetUtf8.c_str(), locale.GetCodeset().c_str()); + ASSERT_STREQ(ModifierLatin.c_str(), locale.GetModifier().c_str()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str()); + ASSERT_STREQ(strLocaleShort.c_str(), locale.ToShortString().c_str()); + ASSERT_STREQ(strLocaleShortLC.c_str(), locale.ToShortStringLC().c_str()); +} + +TEST(TestLocale, FromString) +{ + std::string strLocale = ""; + CLocale locale = CLocale::FromString(strLocale); + ASSERT_FALSE(locale.IsValid()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + + strLocale = LanguageCodeEnglish; + locale = CLocale::FromString(strLocale); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + + strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain; + locale = CLocale::FromString(strLocale); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + + strLocale = LanguageCodeEnglish + CodesetSeparator + CodesetUtf8; + locale = CLocale::FromString(strLocale); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + + strLocale = LanguageCodeEnglish + ModifierSeparator + ModifierLatin; + locale = CLocale::FromString(strLocale); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + + strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + CodesetSeparator + CodesetUtf8; + locale = CLocale::FromString(strLocale); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + + strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + ModifierSeparator + ModifierLatin; + locale = CLocale::FromString(strLocale); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); + + strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + CodesetSeparator + CodesetUtf8 + ModifierSeparator + ModifierLatin; + locale = CLocale::FromString(strLocale); + ASSERT_TRUE(locale.IsValid()); + ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str()); +} + +TEST(TestLocale, EmptyLocale) +{ + ASSERT_FALSE(CLocale::Empty.IsValid()); + ASSERT_STREQ("", CLocale::Empty.GetLanguageCode().c_str()); + ASSERT_STREQ("", CLocale::Empty.GetTerritoryCode().c_str()); + ASSERT_STREQ("", CLocale::Empty.GetCodeset().c_str()); + ASSERT_STREQ("", CLocale::Empty.GetModifier().c_str()); + ASSERT_STREQ("", CLocale::Empty.ToString().c_str()); +} + +TEST(TestLocale, Equals) +{ + std::string strLocale = ""; + CLocale locale; + ASSERT_TRUE(locale.Equals(strLocale)); + + locale = CLocale(LanguageCodeEnglish); + strLocale = LanguageCodeEnglish; + ASSERT_TRUE(locale.Equals(strLocale)); + + locale = CLocale(LanguageCodeEnglish, TerritoryCodeBritain); + strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain; + ASSERT_TRUE(locale.Equals(strLocale)); + + locale = CLocale(LanguageCodeEnglish, "", CodesetUtf8); + strLocale = LanguageCodeEnglish + CodesetSeparator + CodesetUtf8; + ASSERT_TRUE(locale.Equals(strLocale)); + + locale = CLocale(LanguageCodeEnglish, "", "", ModifierLatin); + strLocale = LanguageCodeEnglish + ModifierSeparator + ModifierLatin; + ASSERT_TRUE(locale.Equals(strLocale)); + + locale = CLocale(LanguageCodeEnglish, TerritoryCodeBritain, CodesetUtf8); + strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + CodesetSeparator + CodesetUtf8; + ASSERT_TRUE(locale.Equals(strLocale)); + + locale = CLocale(LanguageCodeEnglish, TerritoryCodeBritain, "", ModifierLatin); + strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + ModifierSeparator + ModifierLatin; + ASSERT_TRUE(locale.Equals(strLocale)); + + locale = CLocale(LanguageCodeEnglish, TerritoryCodeBritain, CodesetUtf8, ModifierLatin); + strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + CodesetSeparator + CodesetUtf8 + ModifierSeparator + ModifierLatin; + ASSERT_TRUE(locale.Equals(strLocale)); +} diff --git a/xbmc/utils/test/TestMathUtils.cpp b/xbmc/utils/test/TestMathUtils.cpp new file mode 100644 index 0000000..d60cc3f --- /dev/null +++ b/xbmc/utils/test/TestMathUtils.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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/xbmc/utils/test/TestMime.cpp b/xbmc/utils/test/TestMime.cpp new file mode 100644 index 0000000..7ef82c3 --- /dev/null +++ b/xbmc/utils/test/TestMime.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FileItem.h" +#include "utils/Mime.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/xbmc/utils/test/TestPOUtils.cpp b/xbmc/utils/test/TestPOUtils.cpp new file mode 100644 index 0000000..5808c31 --- /dev/null +++ b/xbmc/utils/test/TestPOUtils.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "test/TestUtils.h" +#include "utils/POUtils.h" + +#include <gtest/gtest.h> + + +TEST(TestPOUtils, General) +{ + CPODocument a; + + EXPECT_TRUE(a.LoadFile(XBMC_REF_FILE_PATH("xbmc/utils/test/data/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/xbmc/utils/test/TestRegExp.cpp b/xbmc/utils/test/TestRegExp.cpp new file mode 100644 index 0000000..1cd3939 --- /dev/null +++ b/xbmc/utils/test/TestRegExp.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +/** @todo gtest/gtest.h needs to come in before utils/RegExp.h. + * Investigate why. + */ +#include "CompileInfo.h" +#include "ServiceBroker.h" +#include "filesystem/File.h" +#include "filesystem/SpecialProtocol.h" +#include "utils/RegExp.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <gtest/gtest.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() = default; + ~TestRegExpLog() override { CServiceBroker::GetLogging().Deinitialize(); } +}; + +TEST_F(TestRegExpLog, DumpOvector) +{ + CRegExp regex; + std::string logfile, logstring; + char buf[100]; + ssize_t bytesread; + XFILE::CFile file; + + std::string appName = CCompileInfo::GetAppName(); + StringUtils::ToLower(appName); + logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log"; + CServiceBroker::GetLogging().Initialize( + 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); + CServiceBroker::GetLogging().Deinitialize(); + + 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) <general>: regexp ovector=\\{\\[0,12\\],\\[0,4\\]," + "\\[5,11\\]\\}.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + + EXPECT_TRUE(XFILE::CFile::Delete(logfile)); +} diff --git a/xbmc/utils/test/TestRingBuffer.cpp b/xbmc/utils/test/TestRingBuffer.cpp new file mode 100644 index 0000000..e2fd2d5 --- /dev/null +++ b/xbmc/utils/test/TestRingBuffer.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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/xbmc/utils/test/TestScraperParser.cpp b/xbmc/utils/test/TestScraperParser.cpp new file mode 100644 index 0000000..4ff4b06 --- /dev/null +++ b/xbmc/utils/test/TestScraperParser.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "test/TestUtils.h" +#include "utils/ScraperParser.h" + +#include <gtest/gtest.h> + +TEST(TestScraperParser, General) +{ + CScraperParser a; + + a.Clear(); + EXPECT_TRUE(a.Load(XBMC_REF_FILE_PATH("/addons/metadata.local/local.xml"))); + + EXPECT_STREQ(XBMC_REF_FILE_PATH("/addons/metadata.local/local.xml").c_str(), + a.GetFilename().c_str()); + EXPECT_STREQ("UTF-8", a.GetSearchStringEncoding().c_str()); +} diff --git a/xbmc/utils/test/TestScraperUrl.cpp b/xbmc/utils/test/TestScraperUrl.cpp new file mode 100644 index 0000000..1feb181 --- /dev/null +++ b/xbmc/utils/test/TestScraperUrl.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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.ParseFromData(xmlstring)); + + const auto url = a.GetFirstUrlByType(); + EXPECT_STREQ("blah", url.m_spoof.c_str()); + EXPECT_STREQ("someurl", url.m_url.c_str()); + EXPECT_STREQ("", url.m_cache.c_str()); + EXPECT_EQ(CScraperUrl::UrlType::General, url.m_type); + EXPECT_FALSE(url.m_post); + EXPECT_TRUE(url.m_isgz); + EXPECT_EQ(-1, url.m_season); +} diff --git a/xbmc/utils/test/TestSortUtils.cpp b/xbmc/utils/test/TestSortUtils.cpp new file mode 100644 index 0000000..dac3c62 --- /dev/null +++ b/xbmc/utils/test/TestSortUtils.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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(FieldArtistSort); + EXPECT_EQ(FieldArtistSort, *it); + it = fields.find(FieldYear); + EXPECT_EQ(FieldYear, *it); + it = fields.find(FieldTrackNumber); + EXPECT_EQ(FieldTrackNumber, *it); + EXPECT_EQ((unsigned int)5, fields.size()); +} diff --git a/xbmc/utils/test/TestStopwatch.cpp b/xbmc/utils/test/TestStopwatch.cpp new file mode 100644 index 0000000..82f555d --- /dev/null +++ b/xbmc/utils/test/TestStopwatch.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "threads/Thread.h" +#include "utils/Stopwatch.h" + +#include <gtest/gtest.h> + +using namespace std::chrono_literals; + +class CTestStopWatchThread : public CThread +{ +public: + CTestStopWatchThread() : + CThread("TestStopWatch"){} +}; + +TEST(TestStopWatch, Initialization) +{ + CStopWatch a; + EXPECT_FALSE(a.IsRunning()); + EXPECT_EQ(0.0f, a.GetElapsedSeconds()); + EXPECT_EQ(0.0f, a.GetElapsedMilliseconds()); +} + +TEST(TestStopWatch, Start) +{ + CStopWatch a; + a.Start(); + EXPECT_TRUE(a.IsRunning()); +} + +TEST(TestStopWatch, Stop) +{ + CStopWatch a; + a.Start(); + a.Stop(); + EXPECT_FALSE(a.IsRunning()); +} + +TEST(TestStopWatch, ElapsedTime) +{ + CStopWatch a; + CTestStopWatchThread thread; + a.Start(); + thread.Sleep(1ms); + EXPECT_GT(a.GetElapsedSeconds(), 0.0f); + EXPECT_GT(a.GetElapsedMilliseconds(), 0.0f); +} + +TEST(TestStopWatch, Reset) +{ + CStopWatch a; + CTestStopWatchThread thread; + a.StartZero(); + thread.Sleep(2ms); + EXPECT_GT(a.GetElapsedMilliseconds(), 1); + thread.Sleep(3ms); + a.Reset(); + EXPECT_LT(a.GetElapsedMilliseconds(), 5); +} diff --git a/xbmc/utils/test/TestStreamDetails.cpp b/xbmc/utils/test/TestStreamDetails.cpp new file mode 100644 index 0000000..7842eee --- /dev/null +++ b/xbmc/utils/test/TestStreamDetails.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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"; + video->m_strLanguage = "eng"; + + 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/xbmc/utils/test/TestStreamUtils.cpp b/xbmc/utils/test/TestStreamUtils.cpp new file mode 100644 index 0000000..e23f958 --- /dev/null +++ b/xbmc/utils/test/TestStreamUtils.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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/xbmc/utils/test/TestStringUtils.cpp b/xbmc/utils/test/TestStringUtils.cpp new file mode 100644 index 0000000..82a78b1 --- /dev/null +++ b/xbmc/utils/test/TestStringUtils.cpp @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/StringUtils.h" + +#include <algorithm> + +#include <gtest/gtest.h> +enum class ECG +{ + A, + B +}; + +enum EG +{ + C, + D +}; + +namespace test_enum +{ +enum class ECN +{ + A = 1, + B +}; +enum EN +{ + C = 1, + D +}; +} +TEST(TestStringUtils, Format) +{ + std::string refstr = "test 25 2.7 ff FF"; + + std::string varstr = + StringUtils::Format("{} {} {:.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, FormatEnum) +{ + const char* zero = "0"; + const char* one = "1"; + + std::string varstr = StringUtils::Format("{}", ECG::A); + EXPECT_STREQ(zero, varstr.c_str()); + + varstr = StringUtils::Format("{}", EG::C); + EXPECT_STREQ(zero, varstr.c_str()); + + varstr = StringUtils::Format("{}", test_enum::ECN::A); + EXPECT_STREQ(one, varstr.c_str()); + + varstr = StringUtils::Format("{}", test_enum::EN::C); + EXPECT_STREQ(one, varstr.c_str()); +} + +TEST(TestStringUtils, FormatEnumWidth) +{ + const char* one = "01"; + + std::string varstr = StringUtils::Format("{:02d}", ECG::B); + EXPECT_STREQ(one, varstr.c_str()); + + varstr = StringUtils::Format("{:02}", EG::D); + EXPECT_STREQ(one, 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, ToCapitalize) +{ + std::string refstr = "Test"; + std::string varstr = "test"; + StringUtils::ToCapitalize(varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "Just A Test"; + varstr = "just a test"; + StringUtils::ToCapitalize(varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "Test -1;2:3, String For Case"; + varstr = "test -1;2:3, string for Case"; + StringUtils::ToCapitalize(varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = " JuST Another\t\tTEst:\nWoRKs "; + varstr = " juST another\t\ttEst:\nwoRKs "; + StringUtils::ToCapitalize(varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "N.Y.P.D"; + varstr = "n.y.p.d"; + StringUtils::ToCapitalize(varstr); + EXPECT_STREQ(refstr.c_str(), varstr.c_str()); + + refstr = "N-Y-P-D"; + varstr = "n-y-p-d"; + StringUtils::ToCapitalize(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) +{ + std::string refstr, varstr; + std::vector<std::string> strarray; + + strarray.emplace_back("a"); + strarray.emplace_back("b"); + strarray.emplace_back("c"); + strarray.emplace_back("de"); + strarray.emplace_back(","); + strarray.emplace_back("fg"); + strarray.emplace_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(4U, 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(7U, 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(2U, StringUtils::Split("a bc d ef ghi ", " ").size()); + EXPECT_EQ(2U, 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(1U, 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(1U, 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(4U, 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(7U, 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(1U, 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(1U, 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) +{ + std::string 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) +{ + size_t ref, var; + + ref = 9; + var = StringUtils::utf8_strlen("test_UTF8"); + EXPECT_EQ(ref, var); +} + +TEST(TestStringUtils, SecondsToTimeString) +{ + std::string 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) +{ + std::string ref, var; + + ref = "2.00 GB"; + var = StringUtils::SizeToString(2147483647); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + ref = "0.00 B"; + var = StringUtils::SizeToString(0); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST(TestStringUtils, EmptyString) +{ + EXPECT_STREQ("", StringUtils::Empty.c_str()); +} + +TEST(TestStringUtils, FindWords) +{ + size_t 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) +{ + size_t 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.25; + 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.5625; + strarray.emplace_back(""); + strarray.emplace_back("a"); + strarray.emplace_back("e"); + strarray.emplace_back("es"); + strarray.emplace_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()); +} + +TEST(TestStringUtils, sortstringbyname) +{ + std::vector<std::string> strarray; + strarray.emplace_back("B"); + strarray.emplace_back("c"); + strarray.emplace_back("a"); + std::sort(strarray.begin(), strarray.end(), sortstringbyname()); + + EXPECT_STREQ("a", strarray[0].c_str()); + EXPECT_STREQ("B", strarray[1].c_str()); + EXPECT_STREQ("c", strarray[2].c_str()); +} + +TEST(TestStringUtils, FileSizeFormat) +{ + EXPECT_STREQ("0B", StringUtils::FormatFileSize(0).c_str()); + + EXPECT_STREQ("999B", StringUtils::FormatFileSize(999).c_str()); + EXPECT_STREQ("0.98kB", StringUtils::FormatFileSize(1000).c_str()); + + EXPECT_STREQ("1.00kB", StringUtils::FormatFileSize(1024).c_str()); + EXPECT_STREQ("9.99kB", StringUtils::FormatFileSize(10229).c_str()); + + EXPECT_STREQ("10.1kB", StringUtils::FormatFileSize(10387).c_str()); + EXPECT_STREQ("99.9kB", StringUtils::FormatFileSize(102297).c_str()); + + EXPECT_STREQ("100kB", StringUtils::FormatFileSize(102400).c_str()); + EXPECT_STREQ("999kB", StringUtils::FormatFileSize(1023431).c_str()); + + EXPECT_STREQ("0.98MB", StringUtils::FormatFileSize(1023897).c_str()); + EXPECT_STREQ("0.98MB", StringUtils::FormatFileSize(1024000).c_str()); + + //Last unit should overflow the 3 digit limit + EXPECT_STREQ("5432PB", StringUtils::FormatFileSize(6115888293969133568).c_str()); +} + +TEST(TestStringUtils, ToHexadecimal) +{ + EXPECT_STREQ("", StringUtils::ToHexadecimal("").c_str()); + EXPECT_STREQ("616263", StringUtils::ToHexadecimal("abc").c_str()); + std::string a{"a\0b\n", 4}; + EXPECT_STREQ("6100620a", StringUtils::ToHexadecimal(a).c_str()); + std::string nul{"\0", 1}; + EXPECT_STREQ("00", StringUtils::ToHexadecimal(nul).c_str()); + std::string ff{"\xFF", 1}; + EXPECT_STREQ("ff", StringUtils::ToHexadecimal(ff).c_str()); +} diff --git a/xbmc/utils/test/TestSystemInfo.cpp b/xbmc/utils/test/TestSystemInfo.cpp new file mode 100644 index 0000000..d14a474 --- /dev/null +++ b/xbmc/utils/test/TestSystemInfo.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIInfoManager.h" +#include "ServiceBroker.h" +#include "settings/Settings.h" +#include "utils/CPUInfo.h" +#include "utils/SystemInfo.h" +#if defined(TARGET_WINDOWS) +#include "platform/win32/CharsetConverter.h" +#endif + +#include <gtest/gtest.h> + +#include "PlatformDefs.h" + +class TestSystemInfo : public testing::Test +{ +protected: + TestSystemInfo() { CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo()); } + ~TestSystemInfo() { CServiceBroker::UnregisterCPUInfo(); } +}; + +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 << "'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(0U, 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(0U, 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_TVOS) + EXPECT_STREQ("tvOS", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must return 'tvOS'"; + EXPECT_STREQ("tvOS", g_sysinfo.GetOsName(false).c_str()) + << "'GetOsName(false)' must return 'tvOS'"; +#elif defined(TARGET_DARWIN_OSX) + EXPECT_STREQ("macOS", g_sysinfo.GetOsName(true).c_str()) + << "'GetOsName(true)' must return 'macOS'"; + EXPECT_STREQ("macOS", g_sysinfo.GetOsName(false).c_str()) + << "'GetOsName(false)' must return 'macOS'"; +#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, DISABLED_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(0U, 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'"; + 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 '"; +#elif defined(TARGET_DARWIN_TVOS) + EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find("like Mac OS X")) + << "'GetUserAgent()' must contain ' like Mac OS X'"; + EXPECT_TRUE(g_sysinfo.GetUserAgent().find("CPU TVOS ") != std::string::npos) + << "'GetUserAgent()' must contain 'CPU TVOS '"; +#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) + + 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, 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 + using KODI::PLATFORM::WINDOWS::FromW; + wchar_t sysDrive[300]; + DWORD res = GetEnvironmentVariableW(L"SystemDrive", sysDrive, sizeof(sysDrive) / sizeof(wchar_t)); + std::string sysDriveLtr; + if (res != 0 && res <= sizeof(sysDrive) / sizeof(wchar_t)) + sysDriveLtr.assign(FromW(sysDrive), 0, 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/xbmc/utils/test/TestURIUtils.cpp b/xbmc/utils/test/TestURIUtils.cpp new file mode 100644 index 0000000..7122fe9 --- /dev/null +++ b/xbmc/utils/test/TestURIUtils.cpp @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ServiceBroker.h" +#include "URL.h" +#include "filesystem/MultiPathDirectory.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/URIUtils.h" + +#include <utility> + +#include <gtest/gtest.h> + +using namespace XFILE; + +class TestURIUtils : public testing::Test +{ +protected: + TestURIUtils() = default; + ~TestURIUtils() override + { + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pathSubstitutions.clear(); + } +}; + +TEST_F(TestURIUtils, PathHasParent) +{ + EXPECT_TRUE(URIUtils::PathHasParent("/path/to/movie.avi", "/path/to/")); + EXPECT_FALSE(URIUtils::PathHasParent("/path/to/movie.avi", "/path/2/")); +} + +TEST_F(TestURIUtils, GetDirectory) +{ + EXPECT_STREQ("/path/to/", URIUtils::GetDirectory("/path/to/movie.avi").c_str()); + EXPECT_STREQ("/path/to/", URIUtils::GetDirectory("/path/to/").c_str()); + EXPECT_STREQ("/path/to/|option=foo", URIUtils::GetDirectory("/path/to/movie.avi|option=foo").c_str()); + EXPECT_STREQ("/path/to/|option=foo", URIUtils::GetDirectory("/path/to/|option=foo").c_str()); + EXPECT_STREQ("", URIUtils::GetDirectory("movie.avi").c_str()); + EXPECT_STREQ("", URIUtils::GetDirectory("movie.avi|option=foo").c_str()); + EXPECT_STREQ("", URIUtils::GetDirectory("").c_str()); + + // Make sure it works when assigning to the same str as the reference parameter + std::string var = "/path/to/movie.avi|option=foo"; + var = URIUtils::GetDirectory(var); + EXPECT_STREQ("/path/to/|option=foo", var.c_str()); +} + +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) +{ + std::string 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) +{ + std::string 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) +{ + std::string 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()); + + std::string varpathOptional, varfileOptional; + + refpath = "/path/to/"; + reffile = "movie?movie.avi"; + URIUtils::Split("/path/to/movie?movie.avi", varpathOptional, varfileOptional); + EXPECT_STREQ(refpath.c_str(), varpathOptional.c_str()); + EXPECT_STREQ(reffile.c_str(), varfileOptional.c_str()); + + refpath = "file:///path/to/"; + reffile = "movie.avi"; + URIUtils::Split("file:///path/to/movie.avi?showinfo=true", varpathOptional, varfileOptional); + EXPECT_STREQ(refpath.c_str(), varpathOptional.c_str()); + EXPECT_STREQ(reffile.c_str(), varfileOptional.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) +{ + std::string 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) +{ + std::string 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) +{ + std::string from, to, ref, var; + + from = "C:\\My Videos"; + to = "https://myserver/some%20other%20path"; + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pathSubstitutions.push_back(std::make_pair(from, to)); + + from = "/this/path1"; + to = "/some/other/path2"; + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pathSubstitutions.push_back(std::make_pair(from, to)); + + from = "davs://otherserver/my%20music%20path"; + to = "D:\\Local Music\\MP3 Collection"; + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->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, 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")); +} + +TEST_F(TestURIUtils, IsInArchive) +{ + EXPECT_TRUE(URIUtils::IsInArchive("zip://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("whatever://path/to/file.pvr")); +} + +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, IsNfs) +{ + EXPECT_TRUE(URIUtils::IsNfs("nfs://path/to/file")); + EXPECT_TRUE(URIUtils::IsNfs("stack://nfs://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.emplace_back("smb://path/to/file"); + EXPECT_TRUE(URIUtils::IsOnLAN(CMultiPathDirectory::ConstructMultiPath(multiVec))); + EXPECT_TRUE(URIUtils::IsOnLAN("stack://smb://path/to/file")); + EXPECT_TRUE(URIUtils::IsOnLAN("smb://path/to/file")); + EXPECT_FALSE(URIUtils::IsOnLAN("plugin://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")); + EXPECT_FALSE(URIUtils::IsRemote("addons://user/")); + EXPECT_FALSE(URIUtils::IsRemote("sources://video/")); + EXPECT_FALSE(URIUtils::IsRemote("videodb://movies/titles")); + EXPECT_FALSE(URIUtils::IsRemote("musicdb://genres/")); + EXPECT_FALSE(URIUtils::IsRemote("library://video/")); + EXPECT_FALSE(URIUtils::IsRemote("androidapp://app")); + EXPECT_FALSE(URIUtils::IsRemote("plugin://plugin.video.id")); +} + +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, 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, 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) +{ + std::string 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) +{ + std::string 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) +{ + std::string 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) +{ + std::string ref = "/path/to/file"; + std::string var = URIUtils::AddFileToFolder("/path/to", "file"); + EXPECT_STREQ(ref.c_str(), var.c_str()); + + ref = "/path/to/file/and/more"; + var = URIUtils::AddFileToFolder("/path", "to", "file", "and", "more"); + EXPECT_STREQ(ref.c_str(), var.c_str()); +} + +TEST_F(TestURIUtils, HasParentInHostname) +{ + EXPECT_TRUE(URIUtils::HasParentInHostname(CURL("zip://"))); + EXPECT_TRUE(URIUtils::HasParentInHostname(CURL("bluray://"))); +} + +TEST_F(TestURIUtils, HasEncodedHostname) +{ + EXPECT_TRUE(URIUtils::HasEncodedHostname(CURL("zip://"))); + 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("dav://"))); + 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 = "zip://%2fpath%2fto%2fzip/subpath/to/file"; + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath(ref).c_str()); + + // test rar/zip paths + ref = "zip://%2fpath%2fto%2fzip/subpath/to/file"; + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2fzip/../subpath/to/file").c_str()); + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2fzip/./subpath/to/file").c_str()); + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2fzip/subpath/to/./file").c_str()); + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2fzip/subpath/to/some/../file").c_str()); + + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2f.%2fzip/subpath/to/file").c_str()); + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2fsome%2f..%2fzip/subpath/to/file").c_str()); + + // test zip/zip path + ref ="zip://zip%3a%2f%2f%252Fpath%252Fto%252Fzip%2fpath%2fto%2fzip/subpath/to/file"; + EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://zip%3a%2f%2f%252Fpath%252Fto%252Fsome%252F..%252Fzip%2fpath%2fto%2fsome%2f..%2fzip/subpath/to/some/../file").c_str()); +} + +TEST_F(TestURIUtils, UpdateUrlEncoding) +{ + std::string oldUrl = "stack://zip://%2fpath%2fto%2farchive%2fsome%2darchive%2dfile%2eCD1%2ezip/video.avi , zip://%2fpath%2fto%2farchive%2fsome%2darchive%2dfile%2eCD2%2ezip/video.avi"; + std::string newUrl = "stack://zip://%2fpath%2fto%2farchive%2fsome-archive-file.CD1.zip/video.avi , zip://%2fpath%2fto%2farchive%2fsome-archive-file.CD2.zip/video.avi"; + + EXPECT_TRUE(URIUtils::UpdateUrlEncoding(oldUrl)); + EXPECT_STRCASEEQ(newUrl.c_str(), oldUrl.c_str()); + + oldUrl = "zip://%2fpath%2fto%2farchive%2fsome%2darchive%2efile%2ezip/video.avi"; + newUrl = "zip://%2fpath%2fto%2farchive%2fsome-archive.file.zip/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/xbmc/utils/test/TestUrlOptions.cpp b/xbmc/utils/test/TestUrlOptions.cpp new file mode 100644 index 0000000..f684fe5 --- /dev/null +++ b/xbmc/utils/test/TestUrlOptions.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/UrlOptions.h" +#include "utils/Variant.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(2U, 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/xbmc/utils/test/TestVariant.cpp b/xbmc/utils/test/TestVariant.cpp new file mode 100644 index 0000000..3c96cd0 --- /dev/null +++ b/xbmc/utils/test/TestVariant.cpp @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#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.emplace_back("string1"); + strarray.emplace_back("string2"); + strarray.emplace_back("string3"); + strarray.emplace_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.emplace_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, iterator_array) +{ + std::vector<std::string> strarray; + strarray.emplace_back("string"); + strarray.emplace_back("string"); + strarray.emplace_back("string"); + strarray.emplace_back("string"); + CVariant a(strarray); + + EXPECT_TRUE(a.isArray()); + EXPECT_EQ(CVariant::VariantTypeArray, a.type()); + + for (auto it = a.begin_array(); it != a.end_array(); it++) + { + EXPECT_STREQ("string", it->c_str()); + } + + for (auto 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()); + + for (auto it = a.begin_map(); it != a.end_map(); it++) + { + EXPECT_STREQ("string", it->second.c_str()); + } + + for (auto 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.emplace_back("string"); + strarray.emplace_back("string"); + strarray.emplace_back("string"); + strarray.emplace_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.emplace_back("string"); + strarray.emplace_back("string"); + strarray.emplace_back("string"); + strarray.emplace_back("string"); + CVariant a(strarray); + + EXPECT_FALSE(a.empty()); + a.clear(); + EXPECT_TRUE(a.empty()); +} + +TEST(TestVariant, erase) +{ + std::vector<std::string> strarray; + strarray.emplace_back("string1"); + strarray.emplace_back("string2"); + strarray.emplace_back("string3"); + strarray.emplace_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/xbmc/utils/test/TestXBMCTinyXML.cpp b/xbmc/utils/test/TestXBMCTinyXML.cpp new file mode 100644 index 0000000..b3f84eb --- /dev/null +++ b/xbmc/utils/test/TestXBMCTinyXML.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "test/TestUtils.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.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); + 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_NE(nullptr, 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/xbmc/utils/test/TestXMLUtils.cpp b/xbmc/utils/test/TestXMLUtils.cpp new file mode 100644 index 0000000..ba4c87c --- /dev/null +++ b/xbmc/utils/test/TestXMLUtils.cpp @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "XBDateTime.h" +#include "utils/StringUtils.h" +#include "utils/XMLUtils.h" + +#include <gtest/gtest.h> + +TEST(TestXMLUtils, GetHex) +{ + CXBMCTinyXML a; + uint32_t ref, val; + + a.Parse(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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.emplace_back("some string1"); + strarray.emplace_back("some string2"); + strarray.emplace_back("some string3"); + strarray.emplace_back("some string4"); + strarray.emplace_back("some string5"); + + a.Parse(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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(std::string("<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/xbmc/utils/test/Testlog.cpp b/xbmc/utils/test/Testlog.cpp new file mode 100644 index 0000000..a700d2a --- /dev/null +++ b/xbmc/utils/test/Testlog.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "CompileInfo.h" +#include "ServiceBroker.h" +#include "filesystem/File.h" +#include "filesystem/SpecialProtocol.h" +#include "test/TestUtils.h" +#include "utils/RegExp.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <stdlib.h> + +#include <gtest/gtest.h> + +class Testlog : public testing::Test +{ +protected: + Testlog() = default; + ~Testlog() override { CServiceBroker::GetLogging().Deinitialize(); } +}; + +TEST_F(Testlog, Log) +{ + std::string logfile, logstring; + char buf[100]; + ssize_t bytesread; + XFILE::CFile file; + CRegExp regex; + + std::string appName = CCompileInfo::GetAppName(); + StringUtils::ToLower(appName); + logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log"; + CServiceBroker::GetLogging().Initialize( + 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(LOGWARNING, "warning log message"); + CLog::Log(LOGERROR, "error log message"); + CLog::Log(LOGFATAL, "fatal log message"); + CLog::Log(LOGNONE, "none type log message"); + CServiceBroker::GetLogging().Deinitialize(); + + 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) <general>: debug log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*(info|INFO) <general>: info log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*(warning|WARNING) <general>: warning log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*(error|ERROR) <general>: error log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*(critical|CRITICAL|fatal|FATAL) <general>: fatal log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + EXPECT_TRUE(regex.RegComp(".*(off|OFF) <general>: none type log message.*")); + EXPECT_GE(regex.RegFind(logstring), 0); + + EXPECT_TRUE(XFILE::CFile::Delete(logfile)); +} + +TEST_F(Testlog, SetLogLevel) +{ + std::string logfile; + + std::string appName = CCompileInfo::GetAppName(); + StringUtils::ToLower(appName); + logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log"; + CServiceBroker::GetLogging().Initialize( + CSpecialProtocol::TranslatePath("special://temp/").c_str()); + EXPECT_TRUE(XFILE::CFile::Exists(logfile)); + + EXPECT_EQ(LOG_LEVEL_DEBUG, CServiceBroker::GetLogging().GetLogLevel()); + CServiceBroker::GetLogging().SetLogLevel(LOG_LEVEL_MAX); + EXPECT_EQ(LOG_LEVEL_MAX, CServiceBroker::GetLogging().GetLogLevel()); + + CServiceBroker::GetLogging().Deinitialize(); + EXPECT_TRUE(XFILE::CFile::Delete(logfile)); +} diff --git a/xbmc/utils/test/Testrfft.cpp b/xbmc/utils/test/Testrfft.cpp new file mode 100644 index 0000000..a6c859d --- /dev/null +++ b/xbmc/utils/test/Testrfft.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/rfft.h" + +#include <gtest/gtest.h> + +#if defined(TARGET_WINDOWS) && !defined(_USE_MATH_DEFINES) +#define _USE_MATH_DEFINES +#endif + +#include <math.h> + + +TEST(TestRFFT, SimpleSignal) +{ + const int size = 32; + const int freq1 = 5; + const int freq2[] = {1,7}; + std::vector<float> input(2*size); + std::vector<float> output(size); + for (size_t i=0;i<size;++i) + { + input[2*i] = cos(freq1*2.0*M_PI*i/size); + input[2*i+1] = cos(freq2[0]*2.0*M_PI*i/size)+cos(freq2[1]*2.0*M_PI*i/size); + } + RFFT transform(size, false); + + transform.calc(&input[0], &output[0]); + + for (int i=0;i<size/2;++i) + { + EXPECT_NEAR(output[2*i],(i==freq1?1.0:0.0), 1e-7); + EXPECT_NEAR(output[2*i+1], ((i==freq2[0]||i==freq2[1])?1.0:0.0), 1e-7); + } +} diff --git a/xbmc/utils/test/data/language/Spanish/strings.po b/xbmc/utils/test/data/language/Spanish/strings.po new file mode 100644 index 0000000..8ee8d02 --- /dev/null +++ b/xbmc/utils/test/data/language/Spanish/strings.po @@ -0,0 +1,26 @@ +# Kodi Media Center language file +msgid "" +msgstr "" +"Project-Id-Version: XBMC Main\n" +"Report-Msgid-Bugs-To: http://trac.xbmc.org/\n" +"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Kodi Translation Team\n" +"Language-Team: Spanish (http://www.transifex.com/projects/p/xbmc-main/language/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgctxt "#0" +msgid "Programs" +msgstr "Programas" + +msgctxt "#1" +msgid "Pictures" +msgstr "Imágenes" + +msgctxt "#2" +msgid "Music" +msgstr "Música" |