summaryrefslogtreecommitdiffstats
path: root/xbmc/utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/utils
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/utils')
-rw-r--r--xbmc/utils/ActorProtocol.cpp376
-rw-r--r--xbmc/utils/ActorProtocol.h117
-rw-r--r--xbmc/utils/AlarmClock.cpp161
-rw-r--r--xbmc/utils/AlarmClock.h69
-rw-r--r--xbmc/utils/AliasShortcutUtils.cpp93
-rw-r--r--xbmc/utils/AliasShortcutUtils.h14
-rw-r--r--xbmc/utils/Archive.cpp463
-rw-r--r--xbmc/utils/Archive.h185
-rw-r--r--xbmc/utils/Base64.cpp128
-rw-r--r--xbmc/utils/Base64.h27
-rw-r--r--xbmc/utils/BitstreamConverter.cpp1237
-rw-r--r--xbmc/utils/BitstreamConverter.h144
-rw-r--r--xbmc/utils/BitstreamReader.cpp96
-rw-r--r--xbmc/utils/BitstreamReader.h49
-rw-r--r--xbmc/utils/BitstreamStats.cpp70
-rw-r--r--xbmc/utils/BitstreamStats.h40
-rw-r--r--xbmc/utils/BitstreamWriter.cpp113
-rw-r--r--xbmc/utils/BitstreamWriter.h50
-rw-r--r--xbmc/utils/BooleanLogic.cpp122
-rw-r--r--xbmc/utils/BooleanLogic.h90
-rw-r--r--xbmc/utils/BufferObject.cpp61
-rw-r--r--xbmc/utils/BufferObject.h44
-rw-r--r--xbmc/utils/BufferObjectFactory.cpp42
-rw-r--r--xbmc/utils/BufferObjectFactory.h48
-rw-r--r--xbmc/utils/CMakeLists.txt248
-rw-r--r--xbmc/utils/CPUInfo.cpp64
-rw-r--r--xbmc/utils/CPUInfo.h121
-rw-r--r--xbmc/utils/CSSUtils.cpp151
-rw-r--r--xbmc/utils/CSSUtils.h24
-rw-r--r--xbmc/utils/CharArrayParser.cpp169
-rw-r--r--xbmc/utils/CharArrayParser.h118
-rw-r--r--xbmc/utils/CharsetConverter.cpp910
-rw-r--r--xbmc/utils/CharsetConverter.h207
-rw-r--r--xbmc/utils/CharsetDetection.cpp642
-rw-r--r--xbmc/utils/CharsetDetection.h94
-rw-r--r--xbmc/utils/ColorUtils.cpp150
-rw-r--r--xbmc/utils/ColorUtils.h167
-rw-r--r--xbmc/utils/ComponentContainer.h78
-rw-r--r--xbmc/utils/ContentUtils.cpp62
-rw-r--r--xbmc/utils/ContentUtils.h38
-rw-r--r--xbmc/utils/Crc32.cpp110
-rw-r--r--xbmc/utils/Crc32.h31
-rw-r--r--xbmc/utils/DMAHeapBufferObject.cpp190
-rw-r--r--xbmc/utils/DMAHeapBufferObject.h38
-rw-r--r--xbmc/utils/DRMHelpers.cpp60
-rw-r--r--xbmc/utils/DRMHelpers.h21
-rw-r--r--xbmc/utils/DatabaseUtils.cpp797
-rw-r--r--xbmc/utils/DatabaseUtils.h187
-rw-r--r--xbmc/utils/Digest.cpp169
-rw-r--r--xbmc/utils/Digest.h138
-rw-r--r--xbmc/utils/DiscsUtils.cpp68
-rw-r--r--xbmc/utils/DiscsUtils.h73
-rw-r--r--xbmc/utils/DumbBufferObject.cpp168
-rw-r--r--xbmc/utils/DumbBufferObject.h38
-rw-r--r--xbmc/utils/EGLFence.cpp70
-rw-r--r--xbmc/utils/EGLFence.h44
-rw-r--r--xbmc/utils/EGLImage.cpp318
-rw-r--r--xbmc/utils/EGLImage.h66
-rw-r--r--xbmc/utils/EGLUtils.cpp598
-rw-r--r--xbmc/utils/EGLUtils.h232
-rw-r--r--xbmc/utils/EmbeddedArt.cpp72
-rw-r--r--xbmc/utils/EmbeddedArt.h49
-rw-r--r--xbmc/utils/EndianSwap.cpp30
-rw-r--r--xbmc/utils/EndianSwap.h90
-rw-r--r--xbmc/utils/EventStream.h100
-rw-r--r--xbmc/utils/EventStreamDetail.h70
-rw-r--r--xbmc/utils/ExecString.cpp244
-rw-r--r--xbmc/utils/ExecString.h46
-rw-r--r--xbmc/utils/Fanart.cpp176
-rw-r--r--xbmc/utils/Fanart.h107
-rw-r--r--xbmc/utils/FileExtensionProvider.cpp263
-rw-r--r--xbmc/utils/FileExtensionProvider.h91
-rw-r--r--xbmc/utils/FileOperationJob.cpp358
-rw-r--r--xbmc/utils/FileOperationJob.h91
-rw-r--r--xbmc/utils/FileUtils.cpp361
-rw-r--r--xbmc/utils/FileUtils.h34
-rw-r--r--xbmc/utils/FontUtils.cpp167
-rw-r--r--xbmc/utils/FontUtils.h70
-rw-r--r--xbmc/utils/GBMBufferObject.cpp100
-rw-r--r--xbmc/utils/GBMBufferObject.h48
-rw-r--r--xbmc/utils/GLUtils.cpp280
-rw-r--r--xbmc/utils/GLUtils.h54
-rw-r--r--xbmc/utils/Geometry.h484
-rw-r--r--xbmc/utils/GlobalsHandling.h202
-rw-r--r--xbmc/utils/GroupUtils.cpp157
-rw-r--r--xbmc/utils/GroupUtils.h32
-rw-r--r--xbmc/utils/HDRCapabilities.h32
-rw-r--r--xbmc/utils/HTMLUtil.cpp229
-rw-r--r--xbmc/utils/HTMLUtil.h23
-rw-r--r--xbmc/utils/HttpHeader.cpp239
-rw-r--r--xbmc/utils/HttpHeader.h53
-rw-r--r--xbmc/utils/HttpParser.cpp236
-rw-r--r--xbmc/utils/HttpParser.h98
-rw-r--r--xbmc/utils/HttpRangeUtils.cpp424
-rw-r--r--xbmc/utils/HttpRangeUtils.h187
-rw-r--r--xbmc/utils/HttpResponse.cpp166
-rw-r--r--xbmc/utils/HttpResponse.h125
-rw-r--r--xbmc/utils/IArchivable.h22
-rw-r--r--xbmc/utils/IBufferObject.h131
-rw-r--r--xbmc/utils/ILocalizer.h23
-rw-r--r--xbmc/utils/IPlatformLog.h40
-rw-r--r--xbmc/utils/IRssObserver.h25
-rw-r--r--xbmc/utils/IScreenshotSurface.h36
-rw-r--r--xbmc/utils/ISerializable.h21
-rw-r--r--xbmc/utils/ISortable.h23
-rw-r--r--xbmc/utils/IXmlDeserializable.h19
-rw-r--r--xbmc/utils/InfoLoader.cpp60
-rw-r--r--xbmc/utils/InfoLoader.h33
-rw-r--r--xbmc/utils/JSONVariantParser.cpp219
-rw-r--r--xbmc/utils/JSONVariantParser.h22
-rw-r--r--xbmc/utils/JSONVariantWriter.cpp92
-rw-r--r--xbmc/utils/JSONVariantWriter.h21
-rw-r--r--xbmc/utils/Job.h179
-rw-r--r--xbmc/utils/JobManager.cpp440
-rw-r--r--xbmc/utils/JobManager.h381
-rw-r--r--xbmc/utils/LabelFormatter.cpp479
-rw-r--r--xbmc/utils/LabelFormatter.h76
-rw-r--r--xbmc/utils/LangCodeExpander.cpp1792
-rw-r--r--xbmc/utils/LangCodeExpander.h185
-rw-r--r--xbmc/utils/LegacyPathTranslation.cpp105
-rw-r--r--xbmc/utils/LegacyPathTranslation.h47
-rw-r--r--xbmc/utils/Literals.h29
-rw-r--r--xbmc/utils/Locale.cpp284
-rw-r--r--xbmc/utils/Locale.h161
-rw-r--r--xbmc/utils/Map.h102
-rw-r--r--xbmc/utils/MathUtils.h239
-rw-r--r--xbmc/utils/MemUtils.h30
-rw-r--r--xbmc/utils/Mime.cpp700
-rw-r--r--xbmc/utils/Mime.h46
-rw-r--r--xbmc/utils/MovingSpeed.cpp122
-rw-r--r--xbmc/utils/MovingSpeed.h152
-rw-r--r--xbmc/utils/Observer.cpp71
-rw-r--r--xbmc/utils/Observer.h95
-rw-r--r--xbmc/utils/POUtils.cpp313
-rw-r--r--xbmc/utils/POUtils.h162
-rw-r--r--xbmc/utils/PlayerUtils.cpp39
-rw-r--r--xbmc/utils/PlayerUtils.h17
-rw-r--r--xbmc/utils/ProgressJob.cpp185
-rw-r--r--xbmc/utils/ProgressJob.h163
-rw-r--r--xbmc/utils/Random.h26
-rw-r--r--xbmc/utils/RecentlyAddedJob.cpp396
-rw-r--r--xbmc/utils/RecentlyAddedJob.h30
-rw-r--r--xbmc/utils/RegExp.cpp651
-rw-r--r--xbmc/utils/RegExp.h165
-rw-r--r--xbmc/utils/RingBuffer.cpp245
-rw-r--r--xbmc/utils/RingBuffer.h40
-rw-r--r--xbmc/utils/RssManager.cpp199
-rw-r--r--xbmc/utils/RssManager.h68
-rw-r--r--xbmc/utils/RssReader.cpp415
-rw-r--r--xbmc/utils/RssReader.h67
-rw-r--r--xbmc/utils/SaveFileStateJob.cpp235
-rw-r--r--xbmc/utils/SaveFileStateJob.h21
-rw-r--r--xbmc/utils/ScopeGuard.h111
-rw-r--r--xbmc/utils/ScraperParser.cpp615
-rw-r--r--xbmc/utils/ScraperParser.h78
-rw-r--r--xbmc/utils/ScraperUrl.cpp434
-rw-r--r--xbmc/utils/ScraperUrl.h123
-rw-r--r--xbmc/utils/Screenshot.cpp117
-rw-r--r--xbmc/utils/Screenshot.h28
-rw-r--r--xbmc/utils/SortUtils.cpp1385
-rw-r--r--xbmc/utils/SortUtils.h233
-rw-r--r--xbmc/utils/Speed.cpp582
-rw-r--r--xbmc/utils/Speed.h116
-rw-r--r--xbmc/utils/Stopwatch.h111
-rw-r--r--xbmc/utils/StreamDetails.cpp674
-rw-r--r--xbmc/utils/StreamDetails.h142
-rw-r--r--xbmc/utils/StreamUtils.cpp32
-rw-r--r--xbmc/utils/StreamUtils.h34
-rw-r--r--xbmc/utils/StringUtils.cpp1900
-rw-r--r--xbmc/utils/StringUtils.h434
-rw-r--r--xbmc/utils/StringValidation.cpp49
-rw-r--r--xbmc/utils/StringValidation.h25
-rw-r--r--xbmc/utils/SystemInfo.cpp1469
-rw-r--r--xbmc/utils/SystemInfo.h165
-rw-r--r--xbmc/utils/Temperature.cpp481
-rw-r--r--xbmc/utils/Temperature.h103
-rw-r--r--xbmc/utils/TextSearch.cpp146
-rw-r--r--xbmc/utils/TextSearch.h37
-rw-r--r--xbmc/utils/TimeFormat.h67
-rw-r--r--xbmc/utils/TimeUtils.cpp108
-rw-r--r--xbmc/utils/TimeUtils.h46
-rw-r--r--xbmc/utils/TransformMatrix.h296
-rw-r--r--xbmc/utils/UDMABufferObject.cpp204
-rw-r--r--xbmc/utils/UDMABufferObject.h39
-rw-r--r--xbmc/utils/URIUtils.cpp1493
-rw-r--r--xbmc/utils/URIUtils.h241
-rw-r--r--xbmc/utils/UrlOptions.cpp170
-rw-r--r--xbmc/utils/UrlOptions.h46
-rw-r--r--xbmc/utils/Utf8Utils.cpp148
-rw-r--r--xbmc/utils/Utf8Utils.h42
-rw-r--r--xbmc/utils/VC1BitstreamParser.cpp149
-rw-r--r--xbmc/utils/VC1BitstreamParser.h31
-rw-r--r--xbmc/utils/Variant.cpp885
-rw-r--r--xbmc/utils/Variant.h170
-rw-r--r--xbmc/utils/Vector.cpp32
-rw-r--r--xbmc/utils/Vector.h39
-rw-r--r--xbmc/utils/XBMCTinyXML.cpp273
-rw-r--r--xbmc/utils/XBMCTinyXML.h59
-rw-r--r--xbmc/utils/XMLUtils.cpp345
-rw-r--r--xbmc/utils/XMLUtils.h95
-rw-r--r--xbmc/utils/XSLTUtils.cpp103
-rw-r--r--xbmc/utils/XSLTUtils.h51
-rw-r--r--xbmc/utils/XTimeUtils.h88
-rw-r--r--xbmc/utils/log.cpp302
-rw-r--r--xbmc/utils/log.h165
-rw-r--r--xbmc/utils/logtypes.h18
-rw-r--r--xbmc/utils/params_check_macros.h62
-rw-r--r--xbmc/utils/rfft.cpp73
-rw-r--r--xbmc/utils/rfft.h39
-rw-r--r--xbmc/utils/test/CMakeLists.txt51
-rw-r--r--xbmc/utils/test/CXBMCTinyXML-test.xml6
-rw-r--r--xbmc/utils/test/TestAlarmClock.cpp25
-rw-r--r--xbmc/utils/test/TestAliasShortcutUtils.cpp91
-rw-r--r--xbmc/utils/test/TestArchive.cpp411
-rw-r--r--xbmc/utils/test/TestBase64.cpp77
-rw-r--r--xbmc/utils/test/TestBitstreamStats.cpp60
-rw-r--r--xbmc/utils/test/TestCPUInfo.cpp72
-rw-r--r--xbmc/utils/test/TestCharsetConverter.cpp401
-rw-r--r--xbmc/utils/test/TestComponentContainer.cpp76
-rw-r--r--xbmc/utils/test/TestCrc32.cpp50
-rw-r--r--xbmc/utils/test/TestDatabaseUtils.cpp1377
-rw-r--r--xbmc/utils/test/TestDigest.cpp99
-rw-r--r--xbmc/utils/test/TestEndianSwap.cpp133
-rw-r--r--xbmc/utils/test/TestExecString.cpp118
-rw-r--r--xbmc/utils/test/TestFileOperationJob.cpp292
-rw-r--r--xbmc/utils/test/TestFileUtils.cpp44
-rw-r--r--xbmc/utils/test/TestGlobalsHandling.cpp25
-rw-r--r--xbmc/utils/test/TestGlobalsHandlingPattern1.h40
-rw-r--r--xbmc/utils/test/TestHTMLUtil.cpp36
-rw-r--r--xbmc/utils/test/TestHttpHeader.cpp505
-rw-r--r--xbmc/utils/test/TestHttpParser.cpp49
-rw-r--r--xbmc/utils/test/TestHttpRangeUtils.cpp887
-rw-r--r--xbmc/utils/test/TestHttpResponse.cpp43
-rw-r--r--xbmc/utils/test/TestJSONVariantParser.cpp189
-rw-r--r--xbmc/utils/test/TestJSONVariantWriter.cpp151
-rw-r--r--xbmc/utils/test/TestJobManager.cpp218
-rw-r--r--xbmc/utils/test/TestLabelFormatter.cpp69
-rw-r--r--xbmc/utils/test/TestLangCodeExpander.cpp29
-rw-r--r--xbmc/utils/test/TestLocale.cpp272
-rw-r--r--xbmc/utils/test/TestMathUtils.cpp59
-rw-r--r--xbmc/utils/test/TestMime.cpp29
-rw-r--r--xbmc/utils/test/TestPOUtils.cpp47
-rw-r--r--xbmc/utils/test/TestRegExp.cpp169
-rw-r--r--xbmc/utils/test/TestRingBuffer.cpp33
-rw-r--r--xbmc/utils/test/TestScraperParser.cpp24
-rw-r--r--xbmc/utils/test/TestScraperUrl.cpp34
-rw-r--r--xbmc/utils/test/TestSortUtils.cpp123
-rw-r--r--xbmc/utils/test/TestStopwatch.cpp66
-rw-r--r--xbmc/utils/test/TestStreamDetails.cpp76
-rw-r--r--xbmc/utils/test/TestStreamUtils.cpp23
-rw-r--r--xbmc/utils/test/TestStringUtils.cpp609
-rw-r--r--xbmc/utils/test/TestSystemInfo.cpp326
-rw-r--r--xbmc/utils/test/TestURIUtils.cpp585
-rw-r--r--xbmc/utils/test/TestUrlOptions.cpp193
-rw-r--r--xbmc/utils/test/TestVariant.cpp334
-rw-r--r--xbmc/utils/test/TestXBMCTinyXML.cpp58
-rw-r--r--xbmc/utils/test/TestXMLUtils.cpp356
-rw-r--r--xbmc/utils/test/Testlog.cpp96
-rw-r--r--xbmc/utils/test/Testrfft.cpp41
-rw-r--r--xbmc/utils/test/data/language/Spanish/strings.po26
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 '&amp;'-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 &current, 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 &current, 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"&amp;", 0x0026},
+ {L"&apos;", 0x0027},
+ {L"&acute;", 0x00B4},
+ {L"&agrave;", 0x00E0},
+ {L"&aacute;", 0x00E1},
+ {L"&acirc;", 0x00E2},
+ {L"&atilde;", 0x00E3},
+ {L"&auml;", 0x00E4},
+ {L"&aring;", 0x00E5},
+ {L"&aelig;", 0x00E6},
+ {L"&Agrave;", 0x00C0},
+ {L"&Aacute;", 0x00C1},
+ {L"&Acirc;", 0x00C2},
+ {L"&Atilde;", 0x00C3},
+ {L"&Auml;", 0x00C4},
+ {L"&Aring;", 0x00C5},
+ {L"&AElig;", 0x00C6},
+ {L"&bdquo;", 0x201E},
+ {L"&brvbar;", 0x00A6},
+ {L"&bull;", 0x2022},
+ {L"&bullet;", 0x2022},
+ {L"&cent;", 0x00A2},
+ {L"&circ;", 0x02C6},
+ {L"&curren;", 0x00A4},
+ {L"&copy;", 0x00A9},
+ {L"&cedil;", 0x00B8},
+ {L"&Ccedil;", 0x00C7},
+ {L"&ccedil;", 0x00E7},
+ {L"&dagger;", 0x2020},
+ {L"&deg;", 0x00B0},
+ {L"&divide;", 0x00F7},
+ {L"&Dagger;", 0x2021},
+ {L"&egrave;", 0x00E8},
+ {L"&eacute;", 0x00E9},
+ {L"&ecirc;", 0x00EA},
+ {L"&emsp;", 0x2003},
+ {L"&ensp;", 0x2002},
+ {L"&euml;", 0x00EB},
+ {L"&eth;", 0x00F0},
+ {L"&euro;", 0x20AC},
+ {L"&Egrave;", 0x00C8},
+ {L"&Eacute;", 0x00C9},
+ {L"&Ecirc;", 0x00CA},
+ {L"&Euml;", 0x00CB},
+ {L"&ETH;", 0x00D0},
+ {L"&quot;", 0x0022},
+ {L"&frasl;", 0x2044},
+ {L"&frac14;", 0x00BC},
+ {L"&frac12;", 0x00BD},
+ {L"&frac34;", 0x00BE},
+ {L"&gt;", 0x003E},
+ {L"&hellip;", 0x2026},
+ {L"&iexcl;", 0x00A1},
+ {L"&iquest;", 0x00BF},
+ {L"&igrave;", 0x00EC},
+ {L"&iacute;", 0x00ED},
+ {L"&icirc;", 0x00EE},
+ {L"&iuml;", 0x00EF},
+ {L"&Igrave;", 0x00CC},
+ {L"&Iacute;", 0x00CD},
+ {L"&Icirc;", 0x00CE},
+ {L"&Iuml;", 0x00CF},
+ {L"&lrm;", 0x200E},
+ {L"&lt;", 0x003C},
+ {L"&laquo;", 0x00AB},
+ {L"&ldquo;", 0x201C},
+ {L"&lsaquo;", 0x2039},
+ {L"&lsquo;", 0x2018},
+ {L"&macr;", 0x00AF},
+ {L"&micro;", 0x00B5},
+ {L"&middot;", 0x00B7},
+ {L"&mdash;", 0x2014},
+ {L"&nbsp;", 0x00A0},
+ {L"&ndash;", 0x2013},
+ {L"&ntilde;", 0x00F1},
+ {L"&not;", 0x00AC},
+ {L"&Ntilde;", 0x00D1},
+ {L"&ordf;", 0x00AA},
+ {L"&ordm;", 0x00BA},
+ {L"&oelig;", 0x0153},
+ {L"&ograve;", 0x00F2},
+ {L"&oacute;", 0x00F3},
+ {L"&ocirc;", 0x00F4},
+ {L"&otilde;", 0x00F5},
+ {L"&ouml;", 0x00F6},
+ {L"&oslash;", 0x00F8},
+ {L"&OElig;", 0x0152},
+ {L"&Ograve;", 0x00D2},
+ {L"&Oacute;", 0x00D3},
+ {L"&Ocirc;", 0x00D4},
+ {L"&Otilde;", 0x00D5},
+ {L"&Ouml;", 0x00D6},
+ {L"&Oslash;", 0x00D8},
+ {L"&para;", 0x00B6},
+ {L"&permil;", 0x2030},
+ {L"&plusmn;", 0x00B1},
+ {L"&pound;", 0x00A3},
+ {L"&raquo;", 0x00BB},
+ {L"&rdquo;", 0x201D},
+ {L"&reg;", 0x00AE},
+ {L"&rlm;", 0x200F},
+ {L"&rsaquo;", 0x203A},
+ {L"&rsquo;", 0x2019},
+ {L"&sbquo;", 0x201A},
+ {L"&scaron;", 0x0161},
+ {L"&sect;", 0x00A7},
+ {L"&shy;", 0x00AD},
+ {L"&sup1;", 0x00B9},
+ {L"&sup2;", 0x00B2},
+ {L"&sup3;", 0x00B3},
+ {L"&szlig;", 0x00DF},
+ {L"&Scaron;", 0x0160},
+ {L"&thinsp;", 0x2009},
+ {L"&thorn;", 0x00FE},
+ {L"&tilde;", 0x02DC},
+ {L"&times;", 0x00D7},
+ {L"&trade;", 0x2122},
+ {L"&THORN;", 0x00DE},
+ {L"&uml;", 0x00A8},
+ {L"&ugrave;", 0x00F9},
+ {L"&uacute;", 0x00FA},
+ {L"&ucirc;", 0x00FB},
+ {L"&uuml;", 0x00FC},
+ {L"&Ugrave;", 0x00D9},
+ {L"&Uacute;", 0x00DA},
+ {L"&Ucirc;", 0x00DB},
+ {L"&Uuml;", 0x00DC},
+ {L"&yen;", 0x00A5},
+ {L"&yuml;", 0x00FF},
+ {L"&yacute;", 0x00FD},
+ {L"&Yacute;", 0x00DD},
+ {L"&Yuml;", 0x0178},
+ {L"&zwj;", 0x200D},
+ {L"&zwnj;", 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> &times, 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 &param)
+{
+ 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 &param);
+
+ /*! \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 '&amp; 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&#x3f;&#x003F;&#0063;</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&#x3f;&#x003F;&#0063;
+ </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"&aring;&amp;&euro;";
+ 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&#x3f;&#x003F;&#0063;</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"