summaryrefslogtreecommitdiffstats
path: root/xbmc/utils
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2020-10-19 00:52:24 +0200
committermanuel <manuel@mausz.at>2020-10-19 00:52:24 +0200
commitbe933ef2241d79558f91796cc5b3a161f72ebf9c (patch)
treefe3ab2f130e20c99001f2d7a81d610c78c96a3f4 /xbmc/utils
parent5f8335c1e49ce108ef3481863833c98efa00411b (diff)
downloadkodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.tar.gz
kodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.tar.bz2
kodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.zip
sync with upstream
Diffstat (limited to 'xbmc/utils')
-rw-r--r--xbmc/utils/ActorProtocol.cpp371
-rw-r--r--xbmc/utils/ActorProtocol.h114
-rw-r--r--xbmc/utils/AlarmClock.cpp148
-rw-r--r--xbmc/utils/AlarmClock.h67
-rw-r--r--xbmc/utils/AliasShortcutUtils.cpp93
-rw-r--r--xbmc/utils/AliasShortcutUtils.h14
-rw-r--r--xbmc/utils/Archive.cpp461
-rw-r--r--xbmc/utils/Archive.h182
-rw-r--r--xbmc/utils/Base64.cpp128
-rw-r--r--xbmc/utils/Base64.h27
-rw-r--r--xbmc/utils/BitstreamConverter.cpp1219
-rw-r--r--xbmc/utils/BitstreamConverter.h139
-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.h43
-rw-r--r--xbmc/utils/BufferObjectFactory.cpp42
-rw-r--r--xbmc/utils/BufferObjectFactory.h48
-rw-r--r--xbmc/utils/CMakeLists.txt227
-rw-r--r--xbmc/utils/CPUInfo.cpp62
-rw-r--r--xbmc/utils/CPUInfo.h120
-rw-r--r--xbmc/utils/CharsetConverter.cpp871
-rw-r--r--xbmc/utils/CharsetConverter.h169
-rw-r--r--xbmc/utils/CharsetDetection.cpp639
-rw-r--r--xbmc/utils/CharsetDetection.h94
-rw-r--r--xbmc/utils/Color.h32
-rw-r--r--xbmc/utils/ColorUtils.cpp19
-rw-r--r--xbmc/utils/ColorUtils.h23
-rw-r--r--xbmc/utils/Crc32.cpp110
-rw-r--r--xbmc/utils/Crc32.h31
-rw-r--r--xbmc/utils/CryptThreading.cpp84
-rw-r--r--xbmc/utils/CryptThreading.h45
-rw-r--r--xbmc/utils/DMAHeapBufferObject.cpp186
-rw-r--r--xbmc/utils/DMAHeapBufferObject.h38
-rw-r--r--xbmc/utils/DatabaseUtils.cpp745
-rw-r--r--xbmc/utils/DatabaseUtils.h181
-rw-r--r--xbmc/utils/Digest.cpp169
-rw-r--r--xbmc/utils/Digest.h138
-rw-r--r--xbmc/utils/DumbBufferObject.cpp160
-rw-r--r--xbmc/utils/DumbBufferObject.h38
-rw-r--r--xbmc/utils/EGLFence.cpp70
-rw-r--r--xbmc/utils/EGLFence.h43
-rw-r--r--xbmc/utils/EGLImage.cpp197
-rw-r--r--xbmc/utils/EGLImage.h58
-rw-r--r--xbmc/utils/EGLUtils.cpp615
-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.h69
-rw-r--r--xbmc/utils/Fanart.cpp175
-rw-r--r--xbmc/utils/Fanart.h107
-rw-r--r--xbmc/utils/FileExtensionProvider.cpp182
-rw-r--r--xbmc/utils/FileExtensionProvider.h79
-rw-r--r--xbmc/utils/FileOperationJob.cpp353
-rw-r--r--xbmc/utils/FileOperationJob.h85
-rw-r--r--xbmc/utils/FileUtils.cpp351
-rw-r--r--xbmc/utils/FileUtils.h31
-rw-r--r--xbmc/utils/GBMBufferObject.cpp98
-rw-r--r--xbmc/utils/GBMBufferObject.h48
-rw-r--r--xbmc/utils/GLUtils.cpp267
-rw-r--r--xbmc/utils/GLUtils.h46
-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/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.cpp59
-rw-r--r--xbmc/utils/InfoLoader.h33
-rw-r--r--xbmc/utils/JSONVariantParser.cpp217
-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.h160
-rw-r--r--xbmc/utils/JobManager.cpp423
-rw-r--r--xbmc/utils/JobManager.h373
-rw-r--r--xbmc/utils/LabelFormatter.cpp479
-rw-r--r--xbmc/utils/LabelFormatter.h76
-rw-r--r--xbmc/utils/LangCodeExpander.cpp1738
-rw-r--r--xbmc/utils/LangCodeExpander.h143
-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/MathUtils.h215
-rw-r--r--xbmc/utils/MemUtils.h30
-rw-r--r--xbmc/utils/Mime.cpp699
-rw-r--r--xbmc/utils/Mime.h46
-rw-r--r--xbmc/utils/Observer.cpp72
-rw-r--r--xbmc/utils/Observer.h91
-rw-r--r--xbmc/utils/POUtils.cpp305
-rw-r--r--xbmc/utils/POUtils.h162
-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.cpp390
-rw-r--r--xbmc/utils/RecentlyAddedJob.h30
-rw-r--r--xbmc/utils/RegExp.cpp642
-rw-r--r--xbmc/utils/RegExp.h165
-rw-r--r--xbmc/utils/RingBuffer.cpp246
-rw-r--r--xbmc/utils/RingBuffer.h40
-rw-r--r--xbmc/utils/RssManager.cpp198
-rw-r--r--xbmc/utils/RssManager.h68
-rw-r--r--xbmc/utils/RssReader.cpp413
-rw-r--r--xbmc/utils/RssReader.h63
-rw-r--r--xbmc/utils/SaveFileStateJob.cpp211
-rw-r--r--xbmc/utils/SaveFileStateJob.h21
-rw-r--r--xbmc/utils/ScopeGuard.h111
-rw-r--r--xbmc/utils/ScraperParser.cpp616
-rw-r--r--xbmc/utils/ScraperParser.h78
-rw-r--r--xbmc/utils/ScraperUrl.cpp432
-rw-r--r--xbmc/utils/ScraperUrl.h122
-rw-r--r--xbmc/utils/Screenshot.cpp116
-rw-r--r--xbmc/utils/Screenshot.h28
-rw-r--r--xbmc/utils/SortUtils.cpp1324
-rw-r--r--xbmc/utils/SortUtils.h224
-rw-r--r--xbmc/utils/Speed.cpp582
-rw-r--r--xbmc/utils/Speed.h116
-rw-r--r--xbmc/utils/StaticLoggerBase.cpp20
-rw-r--r--xbmc/utils/StaticLoggerBase.h21
-rw-r--r--xbmc/utils/Stopwatch.cpp47
-rw-r--r--xbmc/utils/Stopwatch.h100
-rw-r--r--xbmc/utils/StreamDetails.cpp627
-rw-r--r--xbmc/utils/StreamDetails.h137
-rw-r--r--xbmc/utils/StreamUtils.cpp32
-rw-r--r--xbmc/utils/StreamUtils.h17
-rw-r--r--xbmc/utils/StringUtils.cpp1808
-rw-r--r--xbmc/utils/StringUtils.h403
-rw-r--r--xbmc/utils/StringValidation.cpp49
-rw-r--r--xbmc/utils/StringValidation.h25
-rw-r--r--xbmc/utils/SystemInfo.cpp1395
-rw-r--r--xbmc/utils/SystemInfo.h156
-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/TimeUtils.cpp101
-rw-r--r--xbmc/utils/TimeUtils.h45
-rw-r--r--xbmc/utils/TransformMatrix.h246
-rw-r--r--xbmc/utils/UDMABufferObject.cpp201
-rw-r--r--xbmc/utils/UDMABufferObject.h39
-rw-r--r--xbmc/utils/URIUtils.cpp1441
-rw-r--r--xbmc/utils/URIUtils.h228
-rw-r--r--xbmc/utils/UrlOptions.cpp169
-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.cpp264
-rw-r--r--xbmc/utils/XBMCTinyXML.h59
-rw-r--r--xbmc/utils/XMLUtils.cpp343
-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.h76
-rw-r--r--xbmc/utils/auto_buffer.cpp84
-rw-r--r--xbmc/utils/auto_buffer.h93
-rw-r--r--xbmc/utils/log.cpp288
-rw-r--r--xbmc/utils/log.h217
-rw-r--r--xbmc/utils/logtypes.h18
-rw-r--r--xbmc/utils/params_check_macros.h62
-rw-r--r--xbmc/utils/rfft.cpp72
-rw-r--r--xbmc/utils/rfft.h39
-rw-r--r--xbmc/utils/test/CMakeLists.txt53
-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.cpp58
-rw-r--r--xbmc/utils/test/TestCPUInfo.cpp72
-rw-r--r--xbmc/utils/test/TestCharsetConverter.cpp401
-rw-r--r--xbmc/utils/test/TestCrc32.cpp50
-rw-r--r--xbmc/utils/test/TestCryptThreading.cpp79
-rw-r--r--xbmc/utils/test/TestDatabaseUtils.cpp1376
-rw-r--r--xbmc/utils/test/TestDigest.cpp99
-rw-r--r--xbmc/utils/test/TestEndianSwap.cpp133
-rw-r--r--xbmc/utils/test/TestFileOperationJob.cpp288
-rw-r--r--xbmc/utils/test/TestFileUtils.cpp41
-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.cpp215
-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.cpp26
-rw-r--r--xbmc/utils/test/TestScraperUrl.cpp34
-rw-r--r--xbmc/utils/test/TestSortUtils.cpp123
-rw-r--r--xbmc/utils/test/TestStopwatch.cpp64
-rw-r--r--xbmc/utils/test/TestStreamDetails.cpp76
-rw-r--r--xbmc/utils/test/TestStreamUtils.cpp23
-rw-r--r--xbmc/utils/test/TestStringUtils.cpp605
-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.cpp102
-rw-r--r--xbmc/utils/test/Testrfft.cpp41
-rw-r--r--xbmc/utils/test/data/language/Spanish/strings.po26
245 files changed, 47661 insertions, 0 deletions
diff --git a/xbmc/utils/ActorProtocol.cpp b/xbmc/utils/ActorProtocol.cpp
new file mode 100644
index 0000000..f83d2fc
--- /dev/null
+++ b/xbmc/utils/ActorProtocol.cpp
@@ -0,0 +1,371 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "ActorProtocol.h"
10
11#include "threads/Event.h"
12
13#include <cstring>
14
15using namespace Actor;
16
17void Message::Release()
18{
19 bool skip;
20 origin.Lock();
21 skip = isSync ? !isSyncFini : false;
22 isSyncFini = true;
23 origin.Unlock();
24
25 if (skip)
26 return;
27
28 // free data buffer
29 if (data != buffer)
30 delete [] data;
31
32 payloadObj.reset();
33
34 // delete event in case of sync message
35 delete event;
36
37 origin.ReturnMessage(this);
38}
39
40bool Message::Reply(int sig, void *data /* = NULL*/, size_t size /* = 0 */)
41{
42 if (!isSync)
43 {
44 if (isOut)
45 return origin.SendInMessage(sig, data, size);
46 else
47 return origin.SendOutMessage(sig, data, size);
48 }
49
50 origin.Lock();
51
52 if (!isSyncTimeout)
53 {
54 Message *msg = origin.GetMessage();
55 msg->signal = sig;
56 msg->isOut = !isOut;
57 replyMessage = msg;
58 if (data)
59 {
60 if (size > sizeof(msg->buffer))
61 msg->data = new uint8_t[size];
62 else
63 msg->data = msg->buffer;
64 memcpy(msg->data, data, size);
65 }
66 }
67
68 origin.Unlock();
69
70 if (event)
71 event->Set();
72
73 return true;
74}
75
76Protocol::~Protocol()
77{
78 Message *msg;
79 Purge();
80 while (!freeMessageQueue.empty())
81 {
82 msg = freeMessageQueue.front();
83 freeMessageQueue.pop();
84 delete msg;
85 }
86}
87
88Message *Protocol::GetMessage()
89{
90 Message *msg;
91
92 CSingleLock lock(criticalSection);
93
94 if (!freeMessageQueue.empty())
95 {
96 msg = freeMessageQueue.front();
97 freeMessageQueue.pop();
98 }
99 else
100 msg = new Message(*this);
101
102 msg->isSync = false;
103 msg->isSyncFini = false;
104 msg->isSyncTimeout = false;
105 msg->event = NULL;
106 msg->data = NULL;
107 msg->payloadSize = 0;
108 msg->replyMessage = NULL;
109
110 return msg;
111}
112
113void Protocol::ReturnMessage(Message *msg)
114{
115 CSingleLock lock(criticalSection);
116
117 freeMessageQueue.push(msg);
118}
119
120bool Protocol::SendOutMessage(int signal,
121 const void* data /* = NULL */,
122 size_t size /* = 0 */,
123 Message* outMsg /* = NULL */)
124{
125 Message *msg;
126 if (outMsg)
127 msg = outMsg;
128 else
129 msg = GetMessage();
130
131 msg->signal = signal;
132 msg->isOut = true;
133
134 if (data)
135 {
136 if (size > sizeof(msg->buffer))
137 msg->data = new uint8_t[size];
138 else
139 msg->data = msg->buffer;
140 memcpy(msg->data, data, size);
141 }
142
143 { CSingleLock lock(criticalSection);
144 outMessages.push(msg);
145 }
146 if (containerOutEvent)
147 containerOutEvent->Set();
148
149 return true;
150}
151
152bool Protocol::SendOutMessage(int signal, CPayloadWrapBase *payload, Message *outMsg)
153{
154 Message *msg;
155 if (outMsg)
156 msg = outMsg;
157 else
158 msg = GetMessage();
159
160 msg->signal = signal;
161 msg->isOut = true;
162
163 msg->payloadObj.reset(payload);
164
165 { CSingleLock lock(criticalSection);
166 outMessages.push(msg);
167 }
168 if (containerOutEvent)
169 containerOutEvent->Set();
170
171 return true;
172}
173
174bool Protocol::SendInMessage(int signal,
175 const void* data /* = NULL */,
176 size_t size /* = 0 */,
177 Message* outMsg /* = NULL */)
178{
179 Message *msg;
180 if (outMsg)
181 msg = outMsg;
182 else
183 msg = GetMessage();
184
185 msg->signal = signal;
186 msg->isOut = false;
187
188 if (data)
189 {
190 if (size > sizeof(msg->data))
191 msg->data = new uint8_t[size];
192 else
193 msg->data = msg->buffer;
194 memcpy(msg->data, data, size);
195 }
196
197 { CSingleLock lock(criticalSection);
198 inMessages.push(msg);
199 }
200 if (containerInEvent)
201 containerInEvent->Set();
202
203 return true;
204}
205
206bool Protocol::SendInMessage(int signal, CPayloadWrapBase *payload, Message *outMsg)
207{
208 Message *msg;
209 if (outMsg)
210 msg = outMsg;
211 else
212 msg = GetMessage();
213
214 msg->signal = signal;
215 msg->isOut = false;
216
217 msg->payloadObj.reset(payload);
218
219 { CSingleLock lock(criticalSection);
220 inMessages.push(msg);
221 }
222 if (containerInEvent)
223 containerInEvent->Set();
224
225 return true;
226}
227
228bool Protocol::SendOutMessageSync(
229 int signal, Message** retMsg, int timeout, const void* data /* = NULL */, size_t size /* = 0 */)
230{
231 Message *msg = GetMessage();
232 msg->isOut = true;
233 msg->isSync = true;
234 msg->event = new CEvent;
235 msg->event->Reset();
236 SendOutMessage(signal, data, size, msg);
237
238 if (!msg->event->WaitMSec(timeout))
239 {
240 const CSingleLock lock(criticalSection);
241 if (msg->replyMessage)
242 *retMsg = msg->replyMessage;
243 else
244 {
245 *retMsg = NULL;
246 msg->isSyncTimeout = true;
247 }
248 }
249 else
250 *retMsg = msg->replyMessage;
251
252 msg->Release();
253
254 if (*retMsg)
255 return true;
256 else
257 return false;
258}
259
260bool Protocol::SendOutMessageSync(int signal, Message **retMsg, int timeout, CPayloadWrapBase *payload)
261{
262 Message *msg = GetMessage();
263 msg->isOut = true;
264 msg->isSync = true;
265 msg->event = new CEvent;
266 msg->event->Reset();
267 SendOutMessage(signal, payload, msg);
268
269 if (!msg->event->WaitMSec(timeout))
270 {
271 const CSingleLock lock(criticalSection);
272 if (msg->replyMessage)
273 *retMsg = msg->replyMessage;
274 else
275 {
276 *retMsg = NULL;
277 msg->isSyncTimeout = true;
278 }
279 }
280 else
281 *retMsg = msg->replyMessage;
282
283 msg->Release();
284
285 if (*retMsg)
286 return true;
287 else
288 return false;
289}
290
291bool Protocol::ReceiveOutMessage(Message **msg)
292{
293 CSingleLock lock(criticalSection);
294
295 if (outMessages.empty() || outDefered)
296 return false;
297
298 *msg = outMessages.front();
299 outMessages.pop();
300
301 return true;
302}
303
304bool Protocol::ReceiveInMessage(Message **msg)
305{
306 CSingleLock lock(criticalSection);
307
308 if (inMessages.empty() || inDefered)
309 return false;
310
311 *msg = inMessages.front();
312 inMessages.pop();
313
314 return true;
315}
316
317
318void Protocol::Purge()
319{
320 Message *msg;
321
322 while (ReceiveInMessage(&msg))
323 msg->Release();
324
325 while (ReceiveOutMessage(&msg))
326 msg->Release();
327}
328
329void Protocol::PurgeIn(int signal)
330{
331 Message *msg;
332 std::queue<Message*> msgs;
333
334 CSingleLock lock(criticalSection);
335
336 while (!inMessages.empty())
337 {
338 msg = inMessages.front();
339 inMessages.pop();
340 if (msg->signal != signal)
341 msgs.push(msg);
342 }
343 while (!msgs.empty())
344 {
345 msg = msgs.front();
346 msgs.pop();
347 inMessages.push(msg);
348 }
349}
350
351void Protocol::PurgeOut(int signal)
352{
353 Message *msg;
354 std::queue<Message*> msgs;
355
356 CSingleLock lock(criticalSection);
357
358 while (!outMessages.empty())
359 {
360 msg = outMessages.front();
361 outMessages.pop();
362 if (msg->signal != signal)
363 msgs.push(msg);
364 }
365 while (!msgs.empty())
366 {
367 msg = msgs.front();
368 msgs.pop();
369 outMessages.push(msg);
370 }
371}
diff --git a/xbmc/utils/ActorProtocol.h b/xbmc/utils/ActorProtocol.h
new file mode 100644
index 0000000..97297a6
--- /dev/null
+++ b/xbmc/utils/ActorProtocol.h
@@ -0,0 +1,114 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "threads/CriticalSection.h"
12
13#include <cstddef>
14#include <memory>
15#include <queue>
16#include <string>
17
18class CEvent;
19
20namespace Actor
21{
22
23class CPayloadWrapBase
24{
25public:
26 virtual ~CPayloadWrapBase() = default;
27};
28
29template<typename Payload>
30class CPayloadWrap : public CPayloadWrapBase
31{
32public:
33 ~CPayloadWrap() override = default;
34 CPayloadWrap(Payload *data) {m_pPayload.reset(data);};
35 CPayloadWrap(Payload &data) {m_pPayload.reset(new Payload(data));};
36 Payload *GetPlayload() {return m_pPayload.get();};
37protected:
38 std::unique_ptr<Payload> m_pPayload;
39};
40
41class Protocol;
42
43class Message
44{
45 friend class Protocol;
46
47 static constexpr size_t MSG_INTERNAL_BUFFER_SIZE = 32;
48
49public:
50 int signal;
51 bool isSync = false;
52 bool isSyncFini;
53 bool isOut;
54 bool isSyncTimeout;
55 size_t payloadSize;
56 uint8_t buffer[MSG_INTERNAL_BUFFER_SIZE];
57 uint8_t *data = nullptr;
58 std::unique_ptr<CPayloadWrapBase> payloadObj;
59 Message *replyMessage = nullptr;
60 Protocol &origin;
61 CEvent *event = nullptr;
62
63 void Release();
64 bool Reply(int sig, void *data = nullptr, size_t size = 0);
65
66private:
67 explicit Message(Protocol &_origin) noexcept
68 :origin(_origin) {}
69};
70
71class Protocol
72{
73public:
74 Protocol(std::string name, CEvent* inEvent, CEvent *outEvent)
75 :portName(name), containerInEvent(inEvent), containerOutEvent(outEvent) {}
76 Protocol(std::string name)
77 : Protocol(name, nullptr, nullptr) {}
78 ~Protocol();
79 Message *GetMessage();
80 void ReturnMessage(Message *msg);
81 bool SendOutMessage(int signal,
82 const void* data = nullptr,
83 size_t size = 0,
84 Message* outMsg = nullptr);
85 bool SendOutMessage(int signal, CPayloadWrapBase *payload, Message *outMsg = nullptr);
86 bool SendInMessage(int signal,
87 const void* data = nullptr,
88 size_t size = 0,
89 Message* outMsg = nullptr);
90 bool SendInMessage(int signal, CPayloadWrapBase *payload, Message *outMsg = nullptr);
91 bool SendOutMessageSync(
92 int signal, Message** retMsg, int timeout, const void* data = nullptr, size_t size = 0);
93 bool SendOutMessageSync(int signal, Message **retMsg, int timeout, CPayloadWrapBase *payload);
94 bool ReceiveOutMessage(Message **msg);
95 bool ReceiveInMessage(Message **msg);
96 void Purge();
97 void PurgeIn(int signal);
98 void PurgeOut(int signal);
99 void DeferIn(bool value) {inDefered = value;};
100 void DeferOut(bool value) {outDefered = value;};
101 void Lock() {criticalSection.lock();};
102 void Unlock() {criticalSection.unlock();};
103 std::string portName;
104
105protected:
106 CEvent *containerInEvent, *containerOutEvent;
107 CCriticalSection criticalSection;
108 std::queue<Message*> outMessages;
109 std::queue<Message*> inMessages;
110 std::queue<Message*> freeMessageQueue;
111 bool inDefered = false, outDefered = false;
112};
113
114}
diff --git a/xbmc/utils/AlarmClock.cpp b/xbmc/utils/AlarmClock.cpp
new file mode 100644
index 0000000..5e01243
--- /dev/null
+++ b/xbmc/utils/AlarmClock.cpp
@@ -0,0 +1,148 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "AlarmClock.h"
10
11#include "ServiceBroker.h"
12#include "dialogs/GUIDialogKaiToast.h"
13#include "events/EventLog.h"
14#include "events/NotificationEvent.h"
15#include "guilib/LocalizeStrings.h"
16#include "log.h"
17#include "messaging/ApplicationMessenger.h"
18#include "threads/SingleLock.h"
19#include "utils/StringUtils.h"
20
21#include <utility>
22
23using namespace KODI::MESSAGING;
24
25CAlarmClock::CAlarmClock() : CThread("AlarmClock")
26{
27}
28
29CAlarmClock::~CAlarmClock() = default;
30
31void CAlarmClock::Start(const std::string& strName, float n_secs, const std::string& strCommand, bool bSilent /* false */, bool bLoop /* false */)
32{
33 // make lower case so that lookups are case-insensitive
34 std::string lowerName(strName);
35 StringUtils::ToLower(lowerName);
36 Stop(lowerName);
37 SAlarmClockEvent event;
38 event.m_fSecs = n_secs;
39 event.m_strCommand = strCommand;
40 event.m_loop = bLoop;
41 if (!m_bIsRunning)
42 {
43 StopThread();
44 Create();
45 m_bIsRunning = true;
46 }
47
48 uint32_t labelAlarmClock;
49 uint32_t labelStarted;
50 if (StringUtils::EqualsNoCase(strName, "shutdowntimer"))
51 {
52 labelAlarmClock = 20144;
53 labelStarted = 20146;
54 }
55 else
56 {
57 labelAlarmClock = 13208;
58 labelStarted = 13210;
59 }
60
61 EventPtr alarmClockActivity(new CNotificationEvent(labelAlarmClock,
62 StringUtils::Format(g_localizeStrings.Get(labelStarted).c_str(), static_cast<int>(event.m_fSecs) / 60, static_cast<int>(event.m_fSecs) % 60)));
63 if (bSilent)
64 CServiceBroker::GetEventLog().Add(alarmClockActivity);
65 else
66 CServiceBroker::GetEventLog().AddWithNotification(alarmClockActivity);
67
68 event.watch.StartZero();
69 CSingleLock lock(m_events);
70 m_event.insert(make_pair(lowerName,event));
71 CLog::Log(LOGDEBUG,"started alarm with name: %s",lowerName.c_str());
72}
73
74void CAlarmClock::Stop(const std::string& strName, bool bSilent /* false */)
75{
76 CSingleLock lock(m_events);
77
78 std::string lowerName(strName);
79 StringUtils::ToLower(lowerName); // lookup as lowercase only
80 std::map<std::string,SAlarmClockEvent>::iterator iter = m_event.find(lowerName);
81
82 if (iter == m_event.end())
83 return;
84
85 uint32_t labelAlarmClock;
86 if (StringUtils::EqualsNoCase(strName, "shutdowntimer"))
87 labelAlarmClock = 20144;
88 else
89 labelAlarmClock = 13208;
90
91 std::string strMessage;
92 float elapsed = 0.f;
93
94 if (iter->second.watch.IsRunning())
95 elapsed = iter->second.watch.GetElapsedSeconds();
96
97 if (elapsed > iter->second.m_fSecs)
98 strMessage = g_localizeStrings.Get(13211);
99 else
100 {
101 float remaining = static_cast<float>(iter->second.m_fSecs - elapsed);
102 strMessage = StringUtils::Format(g_localizeStrings.Get(13212).c_str(), static_cast<int>(remaining) / 60, static_cast<int>(remaining) % 60);
103 }
104
105 if (iter->second.m_strCommand.empty() || iter->second.m_fSecs > elapsed)
106 {
107 EventPtr alarmClockActivity(new CNotificationEvent(labelAlarmClock, strMessage));
108 if (bSilent)
109 CServiceBroker::GetEventLog().Add(alarmClockActivity);
110 else
111 CServiceBroker::GetEventLog().AddWithNotification(alarmClockActivity);
112 }
113 else
114 {
115 CApplicationMessenger::GetInstance().PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, iter->second.m_strCommand);
116 if (iter->second.m_loop)
117 {
118 iter->second.watch.Reset();
119 return;
120 }
121 }
122
123 iter->second.watch.Stop();
124 m_event.erase(iter);
125}
126
127void CAlarmClock::Process()
128{
129 while( !m_bStop)
130 {
131 std::string strLast;
132 {
133 CSingleLock lock(m_events);
134 for (std::map<std::string,SAlarmClockEvent>::iterator iter=m_event.begin();iter != m_event.end(); ++iter)
135 if ( iter->second.watch.IsRunning()
136 && iter->second.watch.GetElapsedSeconds() >= iter->second.m_fSecs)
137 {
138 Stop(iter->first);
139 if ((iter = m_event.find(strLast)) == m_event.end())
140 break;
141 }
142 else
143 strLast = iter->first;
144 }
145 CThread::Sleep(100);
146 }
147}
148
diff --git a/xbmc/utils/AlarmClock.h b/xbmc/utils/AlarmClock.h
new file mode 100644
index 0000000..e613595
--- /dev/null
+++ b/xbmc/utils/AlarmClock.h
@@ -0,0 +1,67 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "Stopwatch.h"
12#include "threads/CriticalSection.h"
13#include "threads/Thread.h"
14
15#include <map>
16#include <string>
17
18struct SAlarmClockEvent
19{
20 CStopWatch watch;
21 double m_fSecs;
22 std::string m_strCommand;
23 bool m_loop;
24};
25
26class CAlarmClock : public CThread
27{
28public:
29 CAlarmClock();
30 ~CAlarmClock() override;
31 void Start(const std::string& strName, float n_secs, const std::string& strCommand, bool bSilent = false, bool bLoop = false);
32 inline bool IsRunning() const
33 {
34 return m_bIsRunning;
35 }
36
37 inline bool HasAlarm(const std::string& strName)
38 {
39 // note: strName should be lower case only here
40 // No point checking it at the moment due to it only being called
41 // from GUIInfoManager (which is always lowercase)
42 // CLog::Log(LOGDEBUG,"checking for %s",strName.c_str());
43 return (m_event.find(strName) != m_event.end());
44 }
45
46 double GetRemaining(const std::string& strName)
47 {
48 std::map<std::string,SAlarmClockEvent>::iterator iter;
49 if ((iter=m_event.find(strName)) != m_event.end())
50 {
51 return iter->second.m_fSecs-(iter->second.watch.IsRunning() ? iter->second.watch.GetElapsedSeconds() : 0.f);
52 }
53
54 return 0.f;
55 }
56
57 void Stop(const std::string& strName, bool bSilent = false);
58 void Process() override;
59private:
60 std::map<std::string,SAlarmClockEvent> m_event;
61 CCriticalSection m_events;
62
63 bool m_bIsRunning = false;
64};
65
66extern CAlarmClock g_alarmClock;
67
diff --git a/xbmc/utils/AliasShortcutUtils.cpp b/xbmc/utils/AliasShortcutUtils.cpp
new file mode 100644
index 0000000..001bc43
--- /dev/null
+++ b/xbmc/utils/AliasShortcutUtils.cpp
@@ -0,0 +1,93 @@
1/*
2 * Copyright (C) 2009-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#if defined(TARGET_DARWIN_OSX)
10#include "utils/URIUtils.h"
11#include "platform/darwin/DarwinUtils.h"
12#elif defined(TARGET_POSIX)
13#else
14#endif
15
16#include "AliasShortcutUtils.h"
17#include "utils/log.h"
18
19bool IsAliasShortcut(const std::string& path, bool isdirectory)
20{
21 bool rtn = false;
22
23#if defined(TARGET_DARWIN_OSX)
24 // Note: regular files that have an .alias extension can be
25 // reported as an alias when clearly, they are not. Trap them out.
26 if (!URIUtils::HasExtension(path, ".alias"))//! @todo - check if this is still needed with the new API
27 {
28 rtn = CDarwinUtils::IsAliasShortcut(path, isdirectory);
29 }
30#elif defined(TARGET_POSIX)
31 // Linux does not use alias or shortcut methods
32#elif defined(TARGET_WINDOWS)
33/* Needs testing under Windows platform so ignore shortcuts for now
34 if (CUtil::GetExtension(path) == ".lnk")
35 {
36 rtn = true;
37 }
38*/
39#endif
40 return(rtn);
41}
42
43void TranslateAliasShortcut(std::string& path)
44{
45#if defined(TARGET_DARWIN_OSX)
46 CDarwinUtils::TranslateAliasShortcut(path);
47#elif defined(TARGET_POSIX)
48 // Linux does not use alias or shortcut methods
49#elif defined(TARGET_WINDOWS_STORE)
50 // Win10 does not use alias or shortcut methods
51 CLog::Log(LOGDEBUG, "%s is not implemented", __FUNCTION__);
52#elif defined(TARGET_WINDOWS)
53/* Needs testing under Windows platform so ignore shortcuts for now
54 CComPtr<IShellLink> ipShellLink;
55
56 // Get a pointer to the IShellLink interface
57 if (NOERROR == CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)&ipShellLink))
58 WCHAR wszTemp[MAX_PATH];
59
60 // Get a pointer to the IPersistFile interface
61 CComQIPtr<IPersistFile> ipPersistFile(ipShellLink);
62
63 // IPersistFile is using LPCOLESTR so make sure that the string is Unicode
64#if !defined _UNICODE
65 MultiByteToWideChar(CP_ACP, 0, lpszShortcutPath, -1, wszTemp, MAX_PATH);
66#else
67 wcsncpy(wszTemp, lpszShortcutPath, MAX_PATH);
68#endif
69
70 // Open the shortcut file and initialize it from its contents
71 if (NOERROR == ipPersistFile->Load(wszTemp, STGM_READ))
72 {
73 // Try to find the target of a shortcut even if it has been moved or renamed
74 if (NOERROR == ipShellLink->Resolve(NULL, SLR_UPDATE))
75 {
76 WIN32_FIND_DATA wfd;
77 TCHAR real_path[PATH_MAX];
78 // Get the path to the shortcut target
79 if (NOERROR == ipShellLink->GetPath(real_path, MAX_PATH, &wfd, SLGP_RAWPATH))
80 {
81 // Get the description of the target
82 TCHAR szDesc[MAX_PATH];
83 if (NOERROR == ipShellLink->GetDescription(szDesc, MAX_PATH))
84 {
85 path = real_path;
86 }
87 }
88 }
89 }
90 }
91*/
92#endif
93}
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 @@
1/*
2 * Copyright (C) 2009-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13bool IsAliasShortcut(const std::string& path, bool isdirectory);
14void TranslateAliasShortcut(std::string &path);
diff --git a/xbmc/utils/Archive.cpp b/xbmc/utils/Archive.cpp
new file mode 100644
index 0000000..1b8392b
--- /dev/null
+++ b/xbmc/utils/Archive.cpp
@@ -0,0 +1,461 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Archive.h"
10
11#include "IArchivable.h"
12#include "filesystem/File.h"
13#include "utils/Variant.h"
14#include "utils/log.h"
15
16#include <algorithm>
17#include <cstdint>
18#include <cstring>
19#include <stdexcept>
20
21#ifdef __GNUC__
22#pragma GCC diagnostic ignored "-Wlong-long"
23#endif
24
25using namespace XFILE;
26
27//arbitrarily chosen, should be plenty big enough for our strings
28//without causing random bad things happening
29//not very bad, just tiny bad
30#define MAX_STRING_SIZE 100*1024*1024
31
32CArchive::CArchive(CFile* pFile, int mode)
33{
34 m_pFile = pFile;
35 m_iMode = mode;
36
37 m_pBuffer = std::unique_ptr<uint8_t[]>(new uint8_t[CARCHIVE_BUFFER_MAX]);
38 memset(m_pBuffer.get(), 0, CARCHIVE_BUFFER_MAX);
39 if (mode == load)
40 {
41 m_BufferPos = m_pBuffer.get() + CARCHIVE_BUFFER_MAX;
42 m_BufferRemain = 0;
43 }
44 else
45 {
46 m_BufferPos = m_pBuffer.get();
47 m_BufferRemain = CARCHIVE_BUFFER_MAX;
48 }
49}
50
51CArchive::~CArchive()
52{
53 FlushBuffer();
54}
55
56void CArchive::Close()
57{
58 FlushBuffer();
59}
60
61bool CArchive::IsLoading() const
62{
63 return (m_iMode == load);
64}
65
66bool CArchive::IsStoring() const
67{
68 return (m_iMode == store);
69}
70
71CArchive& CArchive::operator<<(float f)
72{
73 return streamout(&f, sizeof(f));
74}
75
76CArchive& CArchive::operator<<(double d)
77{
78 return streamout(&d, sizeof(d));
79}
80
81CArchive& CArchive::operator<<(short int s)
82{
83 return streamout(&s, sizeof(s));
84}
85
86CArchive& CArchive::operator<<(unsigned short int us)
87{
88 return streamout(&us, sizeof(us));
89}
90
91CArchive& CArchive::operator<<(int i)
92{
93 return streamout(&i, sizeof(i));
94}
95
96CArchive& CArchive::operator<<(unsigned int ui)
97{
98 return streamout(&ui, sizeof(ui));
99}
100
101CArchive& CArchive::operator<<(long int l)
102{
103 return streamout(&l, sizeof(l));
104}
105
106CArchive& CArchive::operator<<(unsigned long int ul)
107{
108 return streamout(&ul, sizeof(ul));
109}
110
111CArchive& CArchive::operator<<(long long int ll)
112{
113 return streamout(&ll, sizeof(ll));
114}
115
116CArchive& CArchive::operator<<(unsigned long long int ull)
117{
118 return streamout(&ull, sizeof(ull));
119}
120
121CArchive& CArchive::operator<<(bool b)
122{
123 return streamout(&b, sizeof(b));
124}
125
126CArchive& CArchive::operator<<(char c)
127{
128 return streamout(&c, sizeof(c));
129}
130
131CArchive& CArchive::operator<<(const std::string& str)
132{
133 auto size = static_cast<uint32_t>(str.size());
134 if (size > MAX_STRING_SIZE)
135 throw std::out_of_range("String too large, over 100MB");
136
137 *this << size;
138
139 return streamout(str.data(), size * sizeof(char));
140}
141
142CArchive& CArchive::operator<<(const std::wstring& wstr)
143{
144 if (wstr.size() > MAX_STRING_SIZE)
145 throw std::out_of_range("String too large, over 100MB");
146
147 auto size = static_cast<uint32_t>(wstr.size());
148
149 *this << size;
150
151 return streamout(wstr.data(), size * sizeof(wchar_t));
152}
153
154CArchive& CArchive::operator<<(const KODI::TIME::SystemTime& time)
155{
156 return streamout(&time, sizeof(KODI::TIME::SystemTime));
157}
158
159CArchive& CArchive::operator<<(IArchivable& obj)
160{
161 obj.Archive(*this);
162
163 return *this;
164}
165
166CArchive& CArchive::operator<<(const CVariant& variant)
167{
168 *this << static_cast<int>(variant.type());
169 switch (variant.type())
170 {
171 case CVariant::VariantTypeInteger:
172 *this << variant.asInteger();
173 break;
174 case CVariant::VariantTypeUnsignedInteger:
175 *this << variant.asUnsignedInteger();
176 break;
177 case CVariant::VariantTypeBoolean:
178 *this << variant.asBoolean();
179 break;
180 case CVariant::VariantTypeString:
181 *this << variant.asString();
182 break;
183 case CVariant::VariantTypeWideString:
184 *this << variant.asWideString();
185 break;
186 case CVariant::VariantTypeDouble:
187 *this << variant.asDouble();
188 break;
189 case CVariant::VariantTypeArray:
190 *this << variant.size();
191 for (auto i = variant.begin_array(); i != variant.end_array(); ++i)
192 *this << *i;
193 break;
194 case CVariant::VariantTypeObject:
195 *this << variant.size();
196 for (auto itr = variant.begin_map(); itr != variant.end_map(); ++itr)
197 {
198 *this << itr->first;
199 *this << itr->second;
200 }
201 break;
202 case CVariant::VariantTypeNull:
203 case CVariant::VariantTypeConstNull:
204 default:
205 break;
206 }
207
208 return *this;
209}
210
211CArchive& CArchive::operator<<(const std::vector<std::string>& strArray)
212{
213 if (std::numeric_limits<uint32_t>::max() < strArray.size())
214 throw std::out_of_range("Array too large, over 2^32 in size");
215
216 *this << static_cast<uint32_t>(strArray.size());
217
218 for (auto&& item : strArray)
219 *this << item;
220
221 return *this;
222}
223
224CArchive& CArchive::operator<<(const std::vector<int>& iArray)
225{
226 if (std::numeric_limits<uint32_t>::max() < iArray.size())
227 throw std::out_of_range("Array too large, over 2^32 in size");
228
229 *this << static_cast<uint32_t>(iArray.size());
230
231 for (auto&& item : iArray)
232 *this << item;
233
234 return *this;
235}
236
237CArchive& CArchive::operator>>(std::string& str)
238{
239 uint32_t iLength = 0;
240 *this >> iLength;
241
242 if (iLength > MAX_STRING_SIZE)
243 throw std::out_of_range("String too large, over 100MB");
244
245 auto s = std::unique_ptr<char[]>(new char[iLength]);
246 streamin(s.get(), iLength * sizeof(char));
247 str.assign(s.get(), iLength);
248
249 return *this;
250}
251
252CArchive& CArchive::operator>>(std::wstring& wstr)
253{
254 uint32_t iLength = 0;
255 *this >> iLength;
256
257 if (iLength > MAX_STRING_SIZE)
258 throw std::out_of_range("String too large, over 100MB");
259
260 auto p = std::unique_ptr<wchar_t[]>(new wchar_t[iLength]);
261 streamin(p.get(), iLength * sizeof(wchar_t));
262 wstr.assign(p.get(), iLength);
263
264 return *this;
265}
266
267CArchive& CArchive::operator>>(KODI::TIME::SystemTime& time)
268{
269 return streamin(&time, sizeof(KODI::TIME::SystemTime));
270}
271
272CArchive& CArchive::operator>>(IArchivable& obj)
273{
274 obj.Archive(*this);
275
276 return *this;
277}
278
279CArchive& CArchive::operator>>(CVariant& variant)
280{
281 int type;
282 *this >> type;
283 variant = CVariant(static_cast<CVariant::VariantType>(type));
284
285 switch (variant.type())
286 {
287 case CVariant::VariantTypeInteger:
288 {
289 int64_t value;
290 *this >> value;
291 variant = value;
292 break;
293 }
294 case CVariant::VariantTypeUnsignedInteger:
295 {
296 uint64_t value;
297 *this >> value;
298 variant = value;
299 break;
300 }
301 case CVariant::VariantTypeBoolean:
302 {
303 bool value;
304 *this >> value;
305 variant = value;
306 break;
307 }
308 case CVariant::VariantTypeString:
309 {
310 std::string value;
311 *this >> value;
312 variant = value;
313 break;
314 }
315 case CVariant::VariantTypeWideString:
316 {
317 std::wstring value;
318 *this >> value;
319 variant = value;
320 break;
321 }
322 case CVariant::VariantTypeDouble:
323 {
324 double value;
325 *this >> value;
326 variant = value;
327 break;
328 }
329 case CVariant::VariantTypeArray:
330 {
331 unsigned int size;
332 *this >> size;
333 for (; size > 0; size--)
334 {
335 CVariant value;
336 *this >> value;
337 variant.append(value);
338 }
339 break;
340 }
341 case CVariant::VariantTypeObject:
342 {
343 unsigned int size;
344 *this >> size;
345 for (; size > 0; size--)
346 {
347 std::string name;
348 CVariant value;
349 *this >> name;
350 *this >> value;
351 variant[name] = value;
352 }
353 break;
354 }
355 case CVariant::VariantTypeNull:
356 case CVariant::VariantTypeConstNull:
357 default:
358 break;
359 }
360
361 return *this;
362}
363
364CArchive& CArchive::operator>>(std::vector<std::string>& strArray)
365{
366 uint32_t size;
367 *this >> size;
368 strArray.clear();
369 for (uint32_t index = 0; index < size; index++)
370 {
371 std::string str;
372 *this >> str;
373 strArray.push_back(std::move(str));
374 }
375
376 return *this;
377}
378
379CArchive& CArchive::operator>>(std::vector<int>& iArray)
380{
381 uint32_t size;
382 *this >> size;
383 iArray.clear();
384 for (uint32_t index = 0; index < size; index++)
385 {
386 int i;
387 *this >> i;
388 iArray.push_back(i);
389 }
390
391 return *this;
392}
393
394void CArchive::FlushBuffer()
395{
396 if (m_iMode == store && m_BufferPos != m_pBuffer.get())
397 {
398 if (m_pFile->Write(m_pBuffer.get(), m_BufferPos - m_pBuffer.get()) != m_BufferPos - m_pBuffer.get())
399 CLog::Log(LOGERROR, "%s: Error flushing buffer", __FUNCTION__);
400 else
401 {
402 m_BufferPos = m_pBuffer.get();
403 m_BufferRemain = CARCHIVE_BUFFER_MAX;
404 }
405 }
406}
407
408CArchive &CArchive::streamout_bufferwrap(const uint8_t *ptr, size_t size)
409{
410 do
411 {
412 auto chunkSize = std::min(size, m_BufferRemain);
413 m_BufferPos = std::copy(ptr, ptr + chunkSize, m_BufferPos);
414 ptr += chunkSize;
415 size -= chunkSize;
416 m_BufferRemain -= chunkSize;
417 if (m_BufferRemain == 0)
418 FlushBuffer();
419 } while (size > 0);
420 return *this;
421}
422
423void CArchive::FillBuffer()
424{
425 if (m_iMode == load && m_BufferRemain == 0)
426 {
427 auto read = m_pFile->Read(m_pBuffer.get(), CARCHIVE_BUFFER_MAX);
428 if (read > 0)
429 {
430 m_BufferRemain = read;
431 m_BufferPos = m_pBuffer.get();
432 }
433 }
434}
435
436CArchive &CArchive::streamin_bufferwrap(uint8_t *ptr, size_t size)
437{
438 auto orig_ptr = ptr;
439 auto orig_size = size;
440 do
441 {
442 if (m_BufferRemain == 0)
443 {
444 FillBuffer();
445 if (m_BufferRemain < CARCHIVE_BUFFER_MAX && m_BufferRemain < size)
446 {
447 CLog::Log(LOGERROR, "%s: can't stream in: requested %lu bytes, was read %lu bytes", __FUNCTION__,
448 static_cast<unsigned long>(orig_size), static_cast<unsigned long>(ptr - orig_ptr + m_BufferRemain));
449
450 memset(orig_ptr, 0, orig_size);
451 return *this;
452 }
453 }
454 auto chunkSize = std::min(size, m_BufferRemain);
455 ptr = std::copy(m_BufferPos, m_BufferPos + chunkSize, ptr);
456 m_BufferPos += chunkSize;
457 m_BufferRemain -= chunkSize;
458 size -= chunkSize;
459 } while (size > 0);
460 return *this;
461}
diff --git a/xbmc/utils/Archive.h b/xbmc/utils/Archive.h
new file mode 100644
index 0000000..f8c7513
--- /dev/null
+++ b/xbmc/utils/Archive.h
@@ -0,0 +1,182 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "XBDateTime.h"
12
13#include <memory>
14#include <string>
15#include <vector>
16
17#define CARCHIVE_BUFFER_MAX 4096
18
19namespace XFILE
20{
21 class CFile;
22}
23class CVariant;
24class IArchivable;
25
26class CArchive
27{
28public:
29 CArchive(XFILE::CFile* pFile, int mode);
30 ~CArchive();
31
32 /* CArchive support storing and loading of all C basic integer types
33 * C basic types was chosen instead of fixed size ints (int16_t - int64_t) to support all integer typedefs
34 * For example size_t can be typedef of unsigned int, long or long long depending on platform
35 * while int32_t and int64_t are usually unsigned short, int or long long, but not long
36 * and even if int and long can have same binary representation they are different types for compiler
37 * According to section 5.2.4.2.1 of C99 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
38 * minimal size of short int is 16 bits
39 * minimal size of int is 16 bits (usually 32 or 64 bits, larger or equal to short int)
40 * minimal size of long int is 32 bits (larger or equal to int)
41 * minimal size of long long int is 64 bits (larger or equal to long int) */
42 // storing
43 CArchive& operator<<(float f);
44 CArchive& operator<<(double d);
45 CArchive& operator<<(short int s);
46 CArchive& operator<<(unsigned short int us);
47 CArchive& operator<<(int i);
48 CArchive& operator<<(unsigned int ui);
49 CArchive& operator<<(long int l);
50 CArchive& operator<<(unsigned long int ul);
51 CArchive& operator<<(long long int ll);
52 CArchive& operator<<(unsigned long long int ull);
53 CArchive& operator<<(bool b);
54 CArchive& operator<<(char c);
55 CArchive& operator<<(const std::string &str);
56 CArchive& operator<<(const std::wstring& wstr);
57 CArchive& operator<<(const KODI::TIME::SystemTime& time);
58 CArchive& operator<<(IArchivable& obj);
59 CArchive& operator<<(const CVariant& variant);
60 CArchive& operator<<(const std::vector<std::string>& strArray);
61 CArchive& operator<<(const std::vector<int>& iArray);
62
63 // loading
64 inline CArchive& operator>>(float& f)
65 {
66 return streamin(&f, sizeof(f));
67 }
68
69 inline CArchive& operator>>(double& d)
70 {
71 return streamin(&d, sizeof(d));
72 }
73
74 inline CArchive& operator>>(short int& s)
75 {
76 return streamin(&s, sizeof(s));
77 }
78
79 inline CArchive& operator>>(unsigned short int& us)
80 {
81 return streamin(&us, sizeof(us));
82 }
83
84 inline CArchive& operator>>(int& i)
85 {
86 return streamin(&i, sizeof(i));
87 }
88
89 inline CArchive& operator>>(unsigned int& ui)
90 {
91 return streamin(&ui, sizeof(ui));
92 }
93
94 inline CArchive& operator>>(long int& l)
95 {
96 return streamin(&l, sizeof(l));
97 }
98
99 inline CArchive& operator>>(unsigned long int& ul)
100 {
101 return streamin(&ul, sizeof(ul));
102 }
103
104 inline CArchive& operator>>(long long int& ll)
105 {
106 return streamin(&ll, sizeof(ll));
107 }
108
109 inline CArchive& operator>>(unsigned long long int& ull)
110 {
111 return streamin(&ull, sizeof(ull));
112 }
113
114 inline CArchive& operator>>(bool& b)
115 {
116 return streamin(&b, sizeof(b));
117 }
118
119 inline CArchive& operator>>(char& c)
120 {
121 return streamin(&c, sizeof(c));
122 }
123
124 CArchive& operator>>(std::string &str);
125 CArchive& operator>>(std::wstring& wstr);
126 CArchive& operator>>(KODI::TIME::SystemTime& time);
127 CArchive& operator>>(IArchivable& obj);
128 CArchive& operator>>(CVariant& variant);
129 CArchive& operator>>(std::vector<std::string>& strArray);
130 CArchive& operator>>(std::vector<int>& iArray);
131
132 bool IsLoading() const;
133 bool IsStoring() const;
134
135 void Close();
136
137 enum Mode {load = 0, store};
138
139protected:
140 inline CArchive &streamout(const void *dataPtr, size_t size)
141 {
142 auto ptr = static_cast<const uint8_t *>(dataPtr);
143 /* Note, the buffer is flushed as soon as it is full (m_BufferRemain == size) rather
144 * than waiting until we attempt to put more data into an already full buffer */
145 if (m_BufferRemain > size)
146 {
147 memcpy(m_BufferPos, ptr, size);
148 m_BufferPos += size;
149 m_BufferRemain -= size;
150 return *this;
151 }
152
153 return streamout_bufferwrap(ptr, size);
154 }
155
156 inline CArchive &streamin(void *dataPtr, size_t size)
157 {
158 auto ptr = static_cast<uint8_t *>(dataPtr);
159 /* Note, refilling the buffer is deferred until we know we need to read more from it */
160 if (m_BufferRemain >= size)
161 {
162 memcpy(ptr, m_BufferPos, size);
163 m_BufferPos += size;
164 m_BufferRemain -= size;
165 return *this;
166 }
167
168 return streamin_bufferwrap(ptr, size);
169 }
170
171 XFILE::CFile* m_pFile; //non-owning
172 int m_iMode;
173 std::unique_ptr<uint8_t[]> m_pBuffer;
174 uint8_t *m_BufferPos;
175 size_t m_BufferRemain;
176
177private:
178 void FlushBuffer();
179 CArchive &streamout_bufferwrap(const uint8_t *ptr, size_t size);
180 void FillBuffer();
181 CArchive &streamin_bufferwrap(uint8_t *ptr, size_t size);
182};
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 @@
1/*
2 * Copyright (C) 2011-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Base64.h"
10
11#define PADDING '='
12
13const std::string Base64::m_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
14 "abcdefghijklmnopqrstuvwxyz"
15 "0123456789+/";
16
17void Base64::Encode(const char* input, unsigned int length, std::string &output)
18{
19 if (input == NULL || length == 0)
20 return;
21
22 long l;
23 output.clear();
24 output.reserve(((length + 2) / 3) * 4);
25
26 for (unsigned int i = 0; i < length; i += 3)
27 {
28 l = ((((unsigned long) input[i]) << 16) & 0xFFFFFF) |
29 ((((i + 1) < length) ? (((unsigned long) input[i + 1]) << 8) : 0) & 0xFFFF) |
30 ((((i + 2) < length) ? (((unsigned long) input[i + 2]) << 0) : 0) & 0x00FF);
31
32 output.push_back(m_characters[(l >> 18) & 0x3F]);
33 output.push_back(m_characters[(l >> 12) & 0x3F]);
34
35 if (i + 1 < length)
36 output.push_back(m_characters[(l >> 6) & 0x3F]);
37 if (i + 2 < length)
38 output.push_back(m_characters[(l >> 0) & 0x3F]);
39 }
40
41 int left = 3 - (length % 3);
42
43 if (length % 3)
44 {
45 for (int i = 0; i < left; i++)
46 output.push_back(PADDING);
47 }
48}
49
50std::string Base64::Encode(const char* input, unsigned int length)
51{
52 std::string output;
53 Encode(input, length, output);
54
55 return output;
56}
57
58void Base64::Encode(const std::string &input, std::string &output)
59{
60 Encode(input.c_str(), input.size(), output);
61}
62
63std::string Base64::Encode(const std::string &input)
64{
65 std::string output;
66 Encode(input, output);
67
68 return output;
69}
70
71void Base64::Decode(const char* input, unsigned int length, std::string &output)
72{
73 if (input == NULL || length == 0)
74 return;
75
76 long l;
77 output.clear();
78
79 for (unsigned int index = 0; index < length; index++)
80 {
81 if (input[index] == '=')
82 {
83 length = index;
84 break;
85 }
86 }
87
88 output.reserve(length - ((length + 2) / 4));
89
90 for (unsigned int i = 0; i < length; i += 4)
91 {
92 l = ((((unsigned long) m_characters.find(input[i])) & 0x3F) << 18);
93 l |= (((i + 1) < length) ? ((((unsigned long) m_characters.find(input[i + 1])) & 0x3F) << 12) : 0);
94 l |= (((i + 2) < length) ? ((((unsigned long) m_characters.find(input[i + 2])) & 0x3F) << 6) : 0);
95 l |= (((i + 3) < length) ? ((((unsigned long) m_characters.find(input[i + 3])) & 0x3F) << 0) : 0);
96
97 output.push_back((char)((l >> 16) & 0xFF));
98 if (i + 2 < length)
99 output.push_back((char)((l >> 8) & 0xFF));
100 if (i + 3 < length)
101 output.push_back((char)((l >> 0) & 0xFF));
102 }
103}
104
105std::string Base64::Decode(const char* input, unsigned int length)
106{
107 std::string output;
108 Decode(input, length, output);
109
110 return output;
111}
112
113void Base64::Decode(const std::string &input, std::string &output)
114{
115 size_t length = input.find_first_of(PADDING);
116 if (length == std::string::npos)
117 length = input.size();
118
119 Decode(input.c_str(), length, output);
120}
121
122std::string Base64::Decode(const std::string &input)
123{
124 std::string output;
125 Decode(input, output);
126
127 return output;
128}
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 @@
1/*
2 * Copyright (C) 2011-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13class Base64
14{
15public:
16 static void Encode(const char* input, unsigned int length, std::string &output);
17 static std::string Encode(const char* input, unsigned int length);
18 static void Encode(const std::string &input, std::string &output);
19 static std::string Encode(const std::string &input);
20 static void Decode(const char* input, unsigned int length, std::string &output);
21 static std::string Decode(const char* input, unsigned int length);
22 static void Decode(const std::string &input, std::string &output);
23 static std::string Decode(const std::string &input);
24
25private:
26 static const std::string m_characters;
27};
diff --git a/xbmc/utils/BitstreamConverter.cpp b/xbmc/utils/BitstreamConverter.cpp
new file mode 100644
index 0000000..011a1e4
--- /dev/null
+++ b/xbmc/utils/BitstreamConverter.cpp
@@ -0,0 +1,1219 @@
1/*
2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/log.h"
10
11#include <assert.h>
12
13#ifndef UINT16_MAX
14#define UINT16_MAX (65535U)
15#endif
16
17#include "BitstreamConverter.h"
18#include "BitstreamReader.h"
19#include "BitstreamWriter.h"
20
21#include <algorithm>
22
23enum {
24 AVC_NAL_SLICE=1,
25 AVC_NAL_DPA,
26 AVC_NAL_DPB,
27 AVC_NAL_DPC,
28 AVC_NAL_IDR_SLICE,
29 AVC_NAL_SEI,
30 AVC_NAL_SPS,
31 AVC_NAL_PPS,
32 AVC_NAL_AUD,
33 AVC_NAL_END_SEQUENCE,
34 AVC_NAL_END_STREAM,
35 AVC_NAL_FILLER_DATA,
36 AVC_NAL_SPS_EXT,
37 AVC_NAL_AUXILIARY_SLICE=19
38};
39
40enum {
41 HEVC_NAL_TRAIL_N = 0,
42 HEVC_NAL_TRAIL_R = 1,
43 HEVC_NAL_TSA_N = 2,
44 HEVC_NAL_TSA_R = 3,
45 HEVC_NAL_STSA_N = 4,
46 HEVC_NAL_STSA_R = 5,
47 HEVC_NAL_RADL_N = 6,
48 HEVC_NAL_RADL_R = 7,
49 HEVC_NAL_RASL_N = 8,
50 HEVC_NAL_RASL_R = 9,
51 HEVC_NAL_BLA_W_LP = 16,
52 HEVC_NAL_BLA_W_RADL = 17,
53 HEVC_NAL_BLA_N_LP = 18,
54 HEVC_NAL_IDR_W_RADL = 19,
55 HEVC_NAL_IDR_N_LP = 20,
56 HEVC_NAL_CRA_NUT = 21,
57 HEVC_NAL_VPS = 32,
58 HEVC_NAL_SPS = 33,
59 HEVC_NAL_PPS = 34,
60 HEVC_NAL_AUD = 35,
61 HEVC_NAL_EOS_NUT = 36,
62 HEVC_NAL_EOB_NUT = 37,
63 HEVC_NAL_FD_NUT = 38,
64 HEVC_NAL_SEI_PREFIX = 39,
65 HEVC_NAL_SEI_SUFFIX = 40
66};
67
68enum {
69 SEI_BUFFERING_PERIOD = 0,
70 SEI_PIC_TIMING,
71 SEI_PAN_SCAN_RECT,
72 SEI_FILLER_PAYLOAD,
73 SEI_USER_DATA_REGISTERED_ITU_T_T35,
74 SEI_USER_DATA_UNREGISTERED,
75 SEI_RECOVERY_POINT,
76 SEI_DEC_REF_PIC_MARKING_REPETITION,
77 SEI_SPARE_PIC,
78 SEI_SCENE_INFO,
79 SEI_SUB_SEQ_INFO,
80 SEI_SUB_SEQ_LAYER_CHARACTERISTICS,
81 SEI_SUB_SEQ_CHARACTERISTICS,
82 SEI_FULL_FRAME_FREEZE,
83 SEI_FULL_FRAME_FREEZE_RELEASE,
84 SEI_FULL_FRAME_SNAPSHOT,
85 SEI_PROGRESSIVE_REFINEMENT_SEGMENT_START,
86 SEI_PROGRESSIVE_REFINEMENT_SEGMENT_END,
87 SEI_MOTION_CONSTRAINED_SLICE_GROUP_SET,
88 SEI_FILM_GRAIN_CHARACTERISTICS,
89 SEI_DEBLOCKING_FILTER_DISPLAY_PREFERENCE,
90 SEI_STEREO_VIDEO_INFO,
91 SEI_POST_FILTER_HINTS,
92 SEI_TONE_MAPPING
93};
94
95/*
96 * GStreamer h264 parser
97 * Copyright (C) 2005 Michal Benes <michal.benes@itonis.tv>
98 * (C) 2008 Wim Taymans <wim.taymans@gmail.com>
99 * gsth264parse.c
100 *
101 * SPDX-License-Identifier: LGPL-2.1-or-later
102 * See LICENSES/README.md for more information.
103 */
104static void nal_bs_init(nal_bitstream *bs, const uint8_t *data, size_t size)
105{
106 bs->data = data;
107 bs->end = data + size;
108 bs->head = 0;
109 // fill with something other than 0 to detect
110 // emulation prevention bytes
111 bs->cache = 0xffffffff;
112}
113
114static uint32_t nal_bs_read(nal_bitstream *bs, int n)
115{
116 uint32_t res = 0;
117 int shift;
118
119 if (n == 0)
120 return res;
121
122 // fill up the cache if we need to
123 while (bs->head < n)
124 {
125 uint8_t a_byte;
126 bool check_three_byte;
127
128 check_three_byte = true;
129next_byte:
130 if (bs->data >= bs->end)
131 {
132 // we're at the end, can't produce more than head number of bits
133 n = bs->head;
134 break;
135 }
136 // get the byte, this can be an emulation_prevention_three_byte that we need
137 // to ignore.
138 a_byte = *bs->data++;
139 if (check_three_byte && a_byte == 0x03 && ((bs->cache & 0xffff) == 0))
140 {
141 // next byte goes unconditionally to the cache, even if it's 0x03
142 check_three_byte = false;
143 goto next_byte;
144 }
145 // shift bytes in cache, moving the head bits of the cache left
146 bs->cache = (bs->cache << 8) | a_byte;
147 bs->head += 8;
148 }
149
150 // bring the required bits down and truncate
151 if ((shift = bs->head - n) > 0)
152 res = static_cast<uint32_t>(bs->cache >> shift);
153 else
154 res = static_cast<uint32_t>(bs->cache);
155
156 // mask out required bits
157 if (n < 32)
158 res &= (1 << n) - 1;
159 bs->head = shift;
160
161 return res;
162}
163
164static bool nal_bs_eos(nal_bitstream *bs)
165{
166 return (bs->data >= bs->end) && (bs->head == 0);
167}
168
169// read unsigned Exp-Golomb code
170static int nal_bs_read_ue(nal_bitstream *bs)
171{
172 int i = 0;
173
174 while (nal_bs_read(bs, 1) == 0 && !nal_bs_eos(bs) && i < 31)
175 i++;
176
177 return ((1 << i) - 1 + nal_bs_read(bs, i));
178}
179
180static const uint8_t* avc_find_startcode_internal(const uint8_t *p, const uint8_t *end)
181{
182 const uint8_t *a = p + 4 - ((intptr_t)p & 3);
183
184 for (end -= 3; p < a && p < end; p++)
185 {
186 if (p[0] == 0 && p[1] == 0 && p[2] == 1)
187 return p;
188 }
189
190 for (end -= 3; p < end; p += 4)
191 {
192 uint32_t x = *(const uint32_t*)p;
193 if ((x - 0x01010101) & (~x) & 0x80808080) // generic
194 {
195 if (p[1] == 0)
196 {
197 if (p[0] == 0 && p[2] == 1)
198 return p;
199 if (p[2] == 0 && p[3] == 1)
200 return p+1;
201 }
202 if (p[3] == 0)
203 {
204 if (p[2] == 0 && p[4] == 1)
205 return p+2;
206 if (p[4] == 0 && p[5] == 1)
207 return p+3;
208 }
209 }
210 }
211
212 for (end += 3; p < end; p++)
213 {
214 if (p[0] == 0 && p[1] == 0 && p[2] == 1)
215 return p;
216 }
217
218 return end + 3;
219}
220
221static const uint8_t* avc_find_startcode(const uint8_t *p, const uint8_t *end)
222{
223 const uint8_t *out = avc_find_startcode_internal(p, end);
224 if (p<out && out<end && !out[-1])
225 out--;
226 return out;
227}
228
229static bool has_sei_recovery_point(const uint8_t *p, const uint8_t *end)
230{
231 int pt(0), ps(0), offset(1);
232
233 do
234 {
235 pt = 0;
236 do {
237 pt += p[offset];
238 } while (p[offset++] == 0xFF);
239
240 ps = 0;
241 do {
242 ps += p[offset];
243 } while (p[offset++] == 0xFF);
244
245 if (pt == SEI_RECOVERY_POINT)
246 {
247 nal_bitstream bs;
248 nal_bs_init(&bs, p + offset, ps);
249 return nal_bs_read_ue(&bs) >= 0;
250 }
251 offset += ps;
252 } while (p + offset < end && p[offset] != 0x80);
253
254 return false;
255}
256
257////////////////////////////////////////////////////////////////////////////////////////////
258/////////////////////////////////////////////////////////////////////////////////////////////
259CBitstreamParser::CBitstreamParser() = default;
260
261CBitstreamParser::~CBitstreamParser() = default;
262
263void CBitstreamParser::Close()
264{
265}
266
267bool CBitstreamParser::CanStartDecode(const uint8_t *buf, int buf_size)
268{
269 if (!buf)
270 return false;
271
272 bool rtn = false;
273 uint32_t state = -1;
274 const uint8_t *buf_begin, *buf_end = buf + buf_size;
275
276 for (; rtn == false;)
277 {
278 buf = find_start_code(buf, buf_end, &state);
279 if (buf >= buf_end)
280 {
281 break;
282 }
283
284 switch (state & 0x1f)
285 {
286 case AVC_NAL_SLICE:
287 break;
288 case AVC_NAL_IDR_SLICE:
289 rtn = true;
290 break;
291 case AVC_NAL_SEI:
292 buf_begin = buf - 1;
293 buf = find_start_code(buf, buf_end, &state) - 4;
294 if (has_sei_recovery_point(buf_begin, buf))
295 rtn = true;
296 break;
297 case AVC_NAL_SPS:
298 rtn = true;
299 break;
300 case AVC_NAL_PPS:
301 break;
302 default:
303 break;
304 }
305 }
306
307 return rtn;
308}
309
310////////////////////////////////////////////////////////////////////////////////////////////
311/////////////////////////////////////////////////////////////////////////////////////////////
312CBitstreamConverter::CBitstreamConverter()
313{
314 m_convert_bitstream = false;
315 m_convertBuffer = NULL;
316 m_convertSize = 0;
317 m_inputBuffer = NULL;
318 m_inputSize = 0;
319 m_to_annexb = false;
320 m_extradata = NULL;
321 m_extrasize = 0;
322 m_convert_3byteTo4byteNALSize = false;
323 m_convert_bytestream = false;
324 m_sps_pps_context.sps_pps_data = NULL;
325 m_start_decode = true;
326}
327
328CBitstreamConverter::~CBitstreamConverter()
329{
330 Close();
331}
332
333bool CBitstreamConverter::Open(enum AVCodecID codec, uint8_t *in_extradata, int in_extrasize, bool to_annexb)
334{
335 m_to_annexb = to_annexb;
336
337 m_codec = codec;
338 switch(m_codec)
339 {
340 case AV_CODEC_ID_H264:
341 if (in_extrasize < 7 || in_extradata == NULL)
342 {
343 CLog::Log(LOGERROR, "CBitstreamConverter::Open avcC data too small or missing");
344 return false;
345 }
346 // valid avcC data (bitstream) always starts with the value 1 (version)
347 if(m_to_annexb)
348 {
349 if ( in_extradata[0] == 1 )
350 {
351 CLog::Log(LOGINFO, "CBitstreamConverter::Open bitstream to annexb init");
352 m_extrasize = in_extrasize;
353 m_extradata = (uint8_t*)av_malloc(in_extrasize);
354 memcpy(m_extradata, in_extradata, in_extrasize);
355 m_convert_bitstream = BitstreamConvertInitAVC(m_extradata, m_extrasize);
356 return true;
357 }
358 else
359 CLog::Log(LOGINFO, "CBitstreamConverter::Open Invalid avcC");
360 }
361 else
362 {
363 // valid avcC atom data always starts with the value 1 (version)
364 if ( in_extradata[0] != 1 )
365 {
366 if ( (in_extradata[0] == 0 && in_extradata[1] == 0 && in_extradata[2] == 0 && in_extradata[3] == 1) ||
367 (in_extradata[0] == 0 && in_extradata[1] == 0 && in_extradata[2] == 1) )
368 {
369 CLog::Log(LOGINFO, "CBitstreamConverter::Open annexb to bitstream init");
370 // video content is from x264 or from bytestream h264 (AnnexB format)
371 // NAL reformating to bitstream format needed
372 AVIOContext *pb;
373 if (avio_open_dyn_buf(&pb) < 0)
374 return false;
375 m_convert_bytestream = true;
376 // create a valid avcC atom data from ffmpeg's extradata
377 isom_write_avcc(pb, in_extradata, in_extrasize);
378 // unhook from ffmpeg's extradata
379 in_extradata = NULL;
380 // extract the avcC atom data into extradata then write it into avcCData for VDADecoder
381 in_extrasize = avio_close_dyn_buf(pb, &in_extradata);
382 // make a copy of extradata contents
383 m_extradata = (uint8_t *)av_malloc(in_extrasize);
384 memcpy(m_extradata, in_extradata, in_extrasize);
385 m_extrasize = in_extrasize;
386 // done with the converted extradata, we MUST free using av_free
387 av_free(in_extradata);
388 return true;
389 }
390 else
391 {
392 CLog::Log(LOGINFO, "CBitstreamConverter::Open invalid avcC atom data");
393 return false;
394 }
395 }
396 else
397 {
398 if (in_extradata[4] == 0xFE)
399 {
400 CLog::Log(LOGINFO, "CBitstreamConverter::Open annexb to bitstream init 3 byte to 4 byte nal");
401 // video content is from so silly encoder that think 3 byte NAL sizes
402 // are valid, setup to convert 3 byte NAL sizes to 4 byte.
403 in_extradata[4] = 0xFF;
404 m_convert_3byteTo4byteNALSize = true;
405
406 m_extradata = (uint8_t *)av_malloc(in_extrasize);
407 memcpy(m_extradata, in_extradata, in_extrasize);
408 m_extrasize = in_extrasize;
409 return true;
410 }
411 }
412 // valid avcC atom
413 m_extradata = (uint8_t*)av_malloc(in_extrasize);
414 memcpy(m_extradata, in_extradata, in_extrasize);
415 m_extrasize = in_extrasize;
416 return true;
417 }
418 return false;
419 break;
420 case AV_CODEC_ID_HEVC:
421 if (in_extrasize < 23 || in_extradata == NULL)
422 {
423 CLog::Log(LOGERROR, "CBitstreamConverter::Open hvcC data too small or missing");
424 return false;
425 }
426 // valid hvcC data (bitstream) always starts with the value 1 (version)
427 if(m_to_annexb)
428 {
429 /**
430 * It seems the extradata is encoded as hvcC format.
431 * Temporarily, we support configurationVersion==0 until 14496-15 3rd
432 * is finalized. When finalized, configurationVersion will be 1 and we
433 * can recognize hvcC by checking if extradata[0]==1 or not.
434 */
435
436 if (in_extradata[0] || in_extradata[1] || in_extradata[2] > 1)
437 {
438 CLog::Log(LOGINFO, "CBitstreamConverter::Open bitstream to annexb init");
439 m_extrasize = in_extrasize;
440 m_extradata = (uint8_t*)av_malloc(in_extrasize);
441 memcpy(m_extradata, in_extradata, in_extrasize);
442 m_convert_bitstream = BitstreamConvertInitHEVC(m_extradata, m_extrasize);
443 return true;
444 }
445 else
446 CLog::Log(LOGINFO, "CBitstreamConverter::Open Invalid hvcC");
447 }
448 else
449 {
450 // valid hvcC atom data always starts with the value 1 (version)
451 if ( in_extradata[0] != 1 )
452 {
453 if ( (in_extradata[0] == 0 && in_extradata[1] == 0 && in_extradata[2] == 0 && in_extradata[3] == 1) ||
454 (in_extradata[0] == 0 && in_extradata[1] == 0 && in_extradata[2] == 1) )
455 {
456 CLog::Log(LOGINFO, "CBitstreamConverter::Open annexb to bitstream init");
457 //! @todo convert annexb to bitstream format
458 return false;
459 }
460 else
461 {
462 CLog::Log(LOGINFO, "CBitstreamConverter::Open invalid hvcC atom data");
463 return false;
464 }
465 }
466 else
467 {
468 if ((in_extradata[4] & 0x3) == 2)
469 {
470 CLog::Log(LOGINFO, "CBitstreamConverter::Open annexb to bitstream init 3 byte to 4 byte nal");
471 // video content is from so silly encoder that think 3 byte NAL sizes
472 // are valid, setup to convert 3 byte NAL sizes to 4 byte.
473 in_extradata[4] |= 0x03;
474 m_convert_3byteTo4byteNALSize = true;
475 }
476 }
477 // valid hvcC atom
478 m_extradata = (uint8_t*)av_malloc(in_extrasize);
479 memcpy(m_extradata, in_extradata, in_extrasize);
480 m_extrasize = in_extrasize;
481 return true;
482 }
483 return false;
484 break;
485 default:
486 return false;
487 break;
488 }
489 return false;
490}
491
492void CBitstreamConverter::Close(void)
493{
494 if (m_sps_pps_context.sps_pps_data)
495 av_free(m_sps_pps_context.sps_pps_data), m_sps_pps_context.sps_pps_data = NULL;
496
497 if (m_convertBuffer)
498 av_free(m_convertBuffer), m_convertBuffer = NULL;
499 m_convertSize = 0;
500
501 if (m_extradata)
502 av_free(m_extradata), m_extradata = NULL;
503 m_extrasize = 0;
504
505 m_inputSize = 0;
506 m_inputBuffer = NULL;
507
508 m_convert_bitstream = false;
509 m_convert_bytestream = false;
510 m_convert_3byteTo4byteNALSize = false;
511}
512
513bool CBitstreamConverter::Convert(uint8_t *pData, int iSize)
514{
515 if (m_convertBuffer)
516 {
517 av_free(m_convertBuffer);
518 m_convertBuffer = NULL;
519 }
520 m_inputSize = 0;
521 m_convertSize = 0;
522 m_inputBuffer = NULL;
523
524 if (pData)
525 {
526 if (m_codec == AV_CODEC_ID_H264 ||
527 m_codec == AV_CODEC_ID_HEVC)
528 {
529 if (m_to_annexb)
530 {
531 int demuxer_bytes = iSize;
532 uint8_t *demuxer_content = pData;
533
534 if (m_convert_bitstream)
535 {
536 // convert demuxer packet from bitstream to bytestream (AnnexB)
537 int bytestream_size = 0;
538 uint8_t *bytestream_buff = NULL;
539
540 BitstreamConvert(demuxer_content, demuxer_bytes, &bytestream_buff, &bytestream_size);
541 if (bytestream_buff && (bytestream_size > 0))
542 {
543 m_convertSize = bytestream_size;
544 m_convertBuffer = bytestream_buff;
545 return true;
546 }
547 else
548 {
549 m_convertSize = 0;
550 m_convertBuffer = NULL;
551 CLog::Log(LOGERROR, "CBitstreamConverter::Convert: error converting.");
552 return false;
553 }
554 }
555 else
556 {
557 m_inputSize = iSize;
558 m_inputBuffer = pData;
559 return true;
560 }
561 }
562 else
563 {
564 m_inputSize = iSize;
565 m_inputBuffer = pData;
566
567 if (m_convert_bytestream)
568 {
569 if(m_convertBuffer)
570 {
571 av_free(m_convertBuffer);
572 m_convertBuffer = NULL;
573 }
574 m_convertSize = 0;
575
576 // convert demuxer packet from bytestream (AnnexB) to bitstream
577 AVIOContext *pb;
578
579 if(avio_open_dyn_buf(&pb) < 0)
580 {
581 return false;
582 }
583 m_convertSize = avc_parse_nal_units(pb, pData, iSize);
584 m_convertSize = avio_close_dyn_buf(pb, &m_convertBuffer);
585 }
586 else if (m_convert_3byteTo4byteNALSize)
587 {
588 if(m_convertBuffer)
589 {
590 av_free(m_convertBuffer);
591 m_convertBuffer = NULL;
592 }
593 m_convertSize = 0;
594
595 // convert demuxer packet from 3 byte NAL sizes to 4 byte
596 AVIOContext *pb;
597 if (avio_open_dyn_buf(&pb) < 0)
598 return false;
599
600 uint32_t nal_size;
601 uint8_t *end = pData + iSize;
602 uint8_t *nal_start = pData;
603 while (nal_start < end)
604 {
605 nal_size = BS_RB24(nal_start);
606 avio_wb32(pb, nal_size);
607 nal_start += 3;
608 avio_write(pb, nal_start, nal_size);
609 nal_start += nal_size;
610 }
611
612 m_convertSize = avio_close_dyn_buf(pb, &m_convertBuffer);
613 }
614 return true;
615 }
616 }
617 }
618
619 return false;
620}
621
622
623uint8_t *CBitstreamConverter::GetConvertBuffer() const
624{
625 if((m_convert_bitstream || m_convert_bytestream || m_convert_3byteTo4byteNALSize) && m_convertBuffer != NULL)
626 return m_convertBuffer;
627 else
628 return m_inputBuffer;
629}
630
631int CBitstreamConverter::GetConvertSize() const
632{
633 if((m_convert_bitstream || m_convert_bytestream || m_convert_3byteTo4byteNALSize) && m_convertBuffer != NULL)
634 return m_convertSize;
635 else
636 return m_inputSize;
637}
638
639uint8_t *CBitstreamConverter::GetExtraData() const
640{
641 if(m_convert_bitstream)
642 return m_sps_pps_context.sps_pps_data;
643 else
644 return m_extradata;
645}
646int CBitstreamConverter::GetExtraSize() const
647{
648 if(m_convert_bitstream)
649 return m_sps_pps_context.size;
650 else
651 return m_extrasize;
652}
653
654void CBitstreamConverter::ResetStartDecode(void)
655{
656 m_start_decode = false;
657}
658
659bool CBitstreamConverter::CanStartDecode() const
660{
661 return m_start_decode;
662}
663
664bool CBitstreamConverter::BitstreamConvertInitAVC(void *in_extradata, int in_extrasize)
665{
666 // based on h264_mp4toannexb_bsf.c (ffmpeg)
667 // which is Copyright (c) 2007 Benoit Fouet <benoit.fouet@free.fr>
668 // and Licensed GPL 2.1 or greater
669
670 m_sps_pps_size = 0;
671 m_sps_pps_context.sps_pps_data = NULL;
672
673 // nothing to filter
674 if (!in_extradata || in_extrasize < 6)
675 return false;
676
677 uint16_t unit_size;
678 uint32_t total_size = 0;
679 uint8_t *out = NULL, unit_nb, sps_done = 0, sps_seen = 0, pps_seen = 0;
680 const uint8_t *extradata = (uint8_t*)in_extradata + 4;
681 static const uint8_t nalu_header[4] = {0, 0, 0, 1};
682
683 // retrieve length coded size
684 m_sps_pps_context.length_size = (*extradata++ & 0x3) + 1;
685
686 // retrieve sps and pps unit(s)
687 unit_nb = *extradata++ & 0x1f; // number of sps unit(s)
688 if (!unit_nb)
689 {
690 goto pps;
691 }
692 else
693 {
694 sps_seen = 1;
695 }
696
697 while (unit_nb--)
698 {
699 void *tmp;
700
701 unit_size = extradata[0] << 8 | extradata[1];
702 total_size += unit_size + 4;
703
704 if (total_size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE ||
705 (extradata + 2 + unit_size) > ((uint8_t*)in_extradata + in_extrasize))
706 {
707 av_free(out);
708 return false;
709 }
710 tmp = av_realloc(out, total_size + AV_INPUT_BUFFER_PADDING_SIZE);
711 if (!tmp)
712 {
713 av_free(out);
714 return false;
715 }
716 out = (uint8_t*)tmp;
717 memcpy(out + total_size - unit_size - 4, nalu_header, 4);
718 memcpy(out + total_size - unit_size, extradata + 2, unit_size);
719 extradata += 2 + unit_size;
720
721pps:
722 if (!unit_nb && !sps_done++)
723 {
724 unit_nb = *extradata++; // number of pps unit(s)
725 if (unit_nb)
726 pps_seen = 1;
727 }
728 }
729
730 if (out)
731 memset(out + total_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
732
733 if (!sps_seen)
734 CLog::Log(LOGDEBUG, "SPS NALU missing or invalid. The resulting stream may not play");
735 if (!pps_seen)
736 CLog::Log(LOGDEBUG, "PPS NALU missing or invalid. The resulting stream may not play");
737
738 m_sps_pps_context.sps_pps_data = out;
739 m_sps_pps_context.size = total_size;
740 m_sps_pps_context.first_idr = 1;
741 m_sps_pps_context.idr_sps_pps_seen = 0;
742
743 return true;
744}
745
746bool CBitstreamConverter::BitstreamConvertInitHEVC(void *in_extradata, int in_extrasize)
747{
748 m_sps_pps_size = 0;
749 m_sps_pps_context.sps_pps_data = NULL;
750
751 // nothing to filter
752 if (!in_extradata || in_extrasize < 23)
753 return false;
754
755 uint16_t unit_nb, unit_size;
756 uint32_t total_size = 0;
757 uint8_t *out = NULL, array_nb, nal_type, sps_seen = 0, pps_seen = 0;
758 const uint8_t *extradata = (uint8_t*)in_extradata + 21;
759 static const uint8_t nalu_header[4] = {0, 0, 0, 1};
760
761 // retrieve length coded size
762 m_sps_pps_context.length_size = (*extradata++ & 0x3) + 1;
763
764 array_nb = *extradata++;
765 while (array_nb--)
766 {
767 nal_type = *extradata++ & 0x3f;
768 unit_nb = extradata[0] << 8 | extradata[1];
769 extradata += 2;
770
771 if (nal_type == HEVC_NAL_SPS && unit_nb)
772 {
773 sps_seen = 1;
774 }
775 else if (nal_type == HEVC_NAL_PPS && unit_nb)
776 {
777 pps_seen = 1;
778 }
779 while (unit_nb--)
780 {
781 void *tmp;
782
783 unit_size = extradata[0] << 8 | extradata[1];
784 extradata += 2;
785 if (nal_type != HEVC_NAL_SPS &&
786 nal_type != HEVC_NAL_PPS &&
787 nal_type != HEVC_NAL_VPS)
788 {
789 extradata += unit_size;
790 continue;
791 }
792 total_size += unit_size + 4;
793
794 if (total_size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE ||
795 (extradata + unit_size) > ((uint8_t*)in_extradata + in_extrasize))
796 {
797 av_free(out);
798 return false;
799 }
800 tmp = av_realloc(out, total_size + AV_INPUT_BUFFER_PADDING_SIZE);
801 if (!tmp)
802 {
803 av_free(out);
804 return false;
805 }
806 out = (uint8_t*)tmp;
807 memcpy(out + total_size - unit_size - 4, nalu_header, 4);
808 memcpy(out + total_size - unit_size, extradata, unit_size);
809 extradata += unit_size;
810 }
811 }
812
813 if (out)
814 memset(out + total_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
815
816 if (!sps_seen)
817 CLog::Log(LOGDEBUG, "SPS NALU missing or invalid. The resulting stream may not play");
818 if (!pps_seen)
819 CLog::Log(LOGDEBUG, "PPS NALU missing or invalid. The resulting stream may not play");
820
821 m_sps_pps_context.sps_pps_data = out;
822 m_sps_pps_context.size = total_size;
823 m_sps_pps_context.first_idr = 1;
824 m_sps_pps_context.idr_sps_pps_seen = 0;
825
826 return true;
827}
828
829bool CBitstreamConverter::IsIDR(uint8_t unit_type)
830{
831 switch (m_codec)
832 {
833 case AV_CODEC_ID_H264:
834 return unit_type == AVC_NAL_IDR_SLICE;
835 case AV_CODEC_ID_HEVC:
836 return unit_type == HEVC_NAL_IDR_W_RADL ||
837 unit_type == HEVC_NAL_IDR_N_LP ||
838 unit_type == HEVC_NAL_CRA_NUT;
839 default:
840 return false;
841 }
842}
843
844bool CBitstreamConverter::IsSlice(uint8_t unit_type)
845{
846 switch (m_codec)
847 {
848 case AV_CODEC_ID_H264:
849 return unit_type == AVC_NAL_SLICE;
850 case AV_CODEC_ID_HEVC:
851 return unit_type == HEVC_NAL_TRAIL_R ||
852 unit_type == HEVC_NAL_TRAIL_N ||
853 unit_type == HEVC_NAL_TSA_N ||
854 unit_type == HEVC_NAL_TSA_R ||
855 unit_type == HEVC_NAL_STSA_N ||
856 unit_type == HEVC_NAL_STSA_R ||
857 unit_type == HEVC_NAL_BLA_W_LP ||
858 unit_type == HEVC_NAL_BLA_W_RADL ||
859 unit_type == HEVC_NAL_BLA_N_LP ||
860 unit_type == HEVC_NAL_CRA_NUT ||
861 unit_type == HEVC_NAL_RADL_N ||
862 unit_type == HEVC_NAL_RADL_R ||
863 unit_type == HEVC_NAL_RASL_N ||
864 unit_type == HEVC_NAL_RASL_R;
865 default:
866 return false;
867 }
868}
869
870bool CBitstreamConverter::BitstreamConvert(uint8_t* pData, int iSize, uint8_t **poutbuf, int *poutbuf_size)
871{
872 // based on h264_mp4toannexb_bsf.c (ffmpeg)
873 // which is Copyright (c) 2007 Benoit Fouet <benoit.fouet@free.fr>
874 // and Licensed GPL 2.1 or greater
875
876 int i;
877 uint8_t *buf = pData;
878 uint32_t buf_size = iSize;
879 uint8_t unit_type, nal_sps, nal_pps, nal_sei;
880 int32_t nal_size;
881 uint32_t cumul_size = 0;
882 const uint8_t *buf_end = buf + buf_size;
883
884 switch (m_codec)
885 {
886 case AV_CODEC_ID_H264:
887 nal_sps = AVC_NAL_SPS;
888 nal_pps = AVC_NAL_PPS;
889 nal_sei = AVC_NAL_SEI;
890 break;
891 case AV_CODEC_ID_HEVC:
892 nal_sps = HEVC_NAL_SPS;
893 nal_pps = HEVC_NAL_PPS;
894 nal_sei = HEVC_NAL_SEI_PREFIX;
895 break;
896 default:
897 return false;
898 }
899
900 do
901 {
902 if (buf + m_sps_pps_context.length_size > buf_end)
903 goto fail;
904
905 for (nal_size = 0, i = 0; i < m_sps_pps_context.length_size; i++)
906 nal_size = (nal_size << 8) | buf[i];
907
908 buf += m_sps_pps_context.length_size;
909 if (m_codec == AV_CODEC_ID_H264)
910 {
911 unit_type = *buf & 0x1f;
912 }
913 else
914 {
915 unit_type = (*buf >> 1) & 0x3f;
916 }
917
918 if (buf + nal_size > buf_end || nal_size <= 0)
919 goto fail;
920
921 // Don't add sps/pps if the unit already contain them
922 if (m_sps_pps_context.first_idr && (unit_type == nal_sps || unit_type == nal_pps))
923 m_sps_pps_context.idr_sps_pps_seen = 1;
924
925 if (!m_start_decode && (unit_type == nal_sps || IsIDR(unit_type) || (unit_type == nal_sei && has_sei_recovery_point(buf, buf + nal_size))))
926 m_start_decode = true;
927
928 // prepend only to the first access unit of an IDR picture, if no sps/pps already present
929 if (m_sps_pps_context.first_idr && IsIDR(unit_type) && !m_sps_pps_context.idr_sps_pps_seen)
930 {
931 BitstreamAllocAndCopy(poutbuf, poutbuf_size,
932 m_sps_pps_context.sps_pps_data, m_sps_pps_context.size, buf, nal_size);
933 m_sps_pps_context.first_idr = 0;
934 }
935 else
936 {
937 BitstreamAllocAndCopy(poutbuf, poutbuf_size, NULL, 0, buf, nal_size);
938 if (!m_sps_pps_context.first_idr && IsSlice(unit_type))
939 {
940 m_sps_pps_context.first_idr = 1;
941 m_sps_pps_context.idr_sps_pps_seen = 0;
942 }
943 }
944
945 buf += nal_size;
946 cumul_size += nal_size + m_sps_pps_context.length_size;
947 } while (cumul_size < buf_size);
948
949 return true;
950
951fail:
952 av_free(*poutbuf), *poutbuf = NULL;
953 *poutbuf_size = 0;
954 return false;
955}
956
957void CBitstreamConverter::BitstreamAllocAndCopy( uint8_t **poutbuf, int *poutbuf_size,
958 const uint8_t *sps_pps, uint32_t sps_pps_size, const uint8_t *in, uint32_t in_size)
959{
960 // based on h264_mp4toannexb_bsf.c (ffmpeg)
961 // which is Copyright (c) 2007 Benoit Fouet <benoit.fouet@free.fr>
962 // and Licensed GPL 2.1 or greater
963
964 uint32_t offset = *poutbuf_size;
965 uint8_t nal_header_size = offset ? 3 : 4;
966 void *tmp;
967
968 *poutbuf_size += sps_pps_size + in_size + nal_header_size;
969 tmp = av_realloc(*poutbuf, *poutbuf_size);
970 if (!tmp)
971 return;
972 *poutbuf = (uint8_t*)tmp;
973 if (sps_pps)
974 memcpy(*poutbuf + offset, sps_pps, sps_pps_size);
975
976 memcpy(*poutbuf + sps_pps_size + nal_header_size + offset, in, in_size);
977 if (!offset)
978 {
979 BS_WB32(*poutbuf + sps_pps_size, 1);
980 }
981 else
982 {
983 (*poutbuf + offset + sps_pps_size)[0] = 0;
984 (*poutbuf + offset + sps_pps_size)[1] = 0;
985 (*poutbuf + offset + sps_pps_size)[2] = 1;
986 }
987}
988
989int CBitstreamConverter::avc_parse_nal_units(AVIOContext *pb, const uint8_t *buf_in, int size)
990{
991 const uint8_t *p = buf_in;
992 const uint8_t *end = p + size;
993 const uint8_t *nal_start, *nal_end;
994
995 size = 0;
996 nal_start = avc_find_startcode(p, end);
997
998 for (;;) {
999 while (nal_start < end && !*(nal_start++));
1000 if (nal_start == end)
1001 break;
1002
1003 nal_end = avc_find_startcode(nal_start, end);
1004 avio_wb32(pb, nal_end - nal_start);
1005 avio_write(pb, nal_start, nal_end - nal_start);
1006 size += 4 + nal_end - nal_start;
1007 nal_start = nal_end;
1008 }
1009 return size;
1010}
1011
1012int CBitstreamConverter::avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size)
1013{
1014 AVIOContext *pb;
1015 int ret = avio_open_dyn_buf(&pb);
1016 if (ret < 0)
1017 return ret;
1018
1019 avc_parse_nal_units(pb, buf_in, *size);
1020
1021 av_freep(buf);
1022 *size = avio_close_dyn_buf(pb, buf);
1023 return 0;
1024}
1025
1026int CBitstreamConverter::isom_write_avcc(AVIOContext *pb, const uint8_t *data, int len)
1027{
1028 // extradata from bytestream h264, convert to avcC atom data for bitstream
1029 if (len > 6)
1030 {
1031 /* check for h264 start code */
1032 if (BS_RB32(data) == 0x00000001 || BS_RB24(data) == 0x000001)
1033 {
1034 uint8_t *buf=NULL, *end, *start;
1035 uint32_t sps_size=0, pps_size=0;
1036 uint8_t *sps=0, *pps=0;
1037
1038 int ret = avc_parse_nal_units_buf(data, &buf, &len);
1039 if (ret < 0)
1040 return ret;
1041 start = buf;
1042 end = buf + len;
1043
1044 /* look for sps and pps */
1045 while (end - buf > 4)
1046 {
1047 uint32_t size;
1048 uint8_t nal_type;
1049 size = std::min<uint32_t>(BS_RB32(buf), end - buf - 4);
1050 buf += 4;
1051 nal_type = buf[0] & 0x1f;
1052 if (nal_type == 7) /* SPS */
1053 {
1054 sps = buf;
1055 sps_size = size;
1056 }
1057 else if (nal_type == 8) /* PPS */
1058 {
1059 pps = buf;
1060 pps_size = size;
1061 }
1062 buf += size;
1063 }
1064 if (!sps || !pps || sps_size < 4 || sps_size > UINT16_MAX || pps_size > UINT16_MAX)
1065 assert(0);
1066
1067 avio_w8(pb, 1); /* version */
1068 avio_w8(pb, sps[1]); /* profile */
1069 avio_w8(pb, sps[2]); /* profile compat */
1070 avio_w8(pb, sps[3]); /* level */
1071 avio_w8(pb, 0xff); /* 6 bits reserved (111111) + 2 bits nal size length - 1 (11) */
1072 avio_w8(pb, 0xe1); /* 3 bits reserved (111) + 5 bits number of sps (00001) */
1073
1074 avio_wb16(pb, sps_size);
1075 avio_write(pb, sps, sps_size);
1076 if (pps)
1077 {
1078 avio_w8(pb, 1); /* number of pps */
1079 avio_wb16(pb, pps_size);
1080 avio_write(pb, pps, pps_size);
1081 }
1082 av_free(start);
1083 }
1084 else
1085 {
1086 avio_write(pb, data, len);
1087 }
1088 }
1089 return 0;
1090}
1091
1092////////////////////////////////////////////////////////////////////////////////////////////
1093/////////////////////////////////////////////////////////////////////////////////////////////
1094bool CBitstreamConverter::mpeg2_sequence_header(const uint8_t *data, const uint32_t size, mpeg2_sequence *sequence)
1095{
1096 // parse nal's until a sequence_header_code is found
1097 // and return the width, height, aspect ratio and frame rate if changed.
1098 bool changed = false;
1099
1100 if (!data)
1101 return changed;
1102
1103 const uint8_t *p = data;
1104 const uint8_t *end = p + size;
1105 const uint8_t *nal_start, *nal_end;
1106
1107 nal_start = avc_find_startcode(p, end);
1108 while (nal_start < end)
1109 {
1110 while (!*(nal_start++));
1111 nal_end = avc_find_startcode(nal_start, end);
1112 if (*nal_start == 0xB3)
1113 {
1114 nal_bitstream bs;
1115 nal_bs_init(&bs, nal_start, end - nal_start);
1116
1117 // sequence_header_code
1118 nal_bs_read(&bs, 8);
1119
1120 // width
1121 // nal_start + 12 bits == horizontal_size_value
1122 uint32_t width = nal_bs_read(&bs, 12);
1123 if (width != sequence->width)
1124 {
1125 changed = true;
1126 sequence->width = width;
1127 }
1128 // height
1129 // nal_start + 24 bits == vertical_size_value
1130 uint32_t height = nal_bs_read(&bs, 12);
1131 if (height != sequence->height)
1132 {
1133 changed = true;
1134 sequence->height = height;
1135 }
1136
1137 // aspect ratio
1138 // nal_start + 28 bits == aspect_ratio_information
1139 float ratio = sequence->ratio;
1140 uint32_t ratio_info = nal_bs_read(&bs, 4);
1141 switch(ratio_info)
1142 {
1143 case 0x01:
1144 ratio = 1.0f;
1145 break;
1146 default:
1147 case 0x02:
1148 ratio = 4.0f/3;
1149 break;
1150 case 0x03:
1151 ratio = 16.0f/9;
1152 break;
1153 case 0x04:
1154 ratio = 2.21f;
1155 break;
1156 }
1157 if (ratio_info != sequence->ratio_info)
1158 {
1159 changed = true;
1160 sequence->ratio = ratio;
1161 sequence->ratio_info = ratio_info;
1162 }
1163
1164 // frame rate
1165 // nal_start + 32 bits == frame_rate_code
1166 uint32_t fpsrate = sequence->fps_rate;
1167 uint32_t fpsscale = sequence->fps_scale;
1168 uint32_t rate_info = nal_bs_read(&bs, 4);
1169
1170 switch(rate_info)
1171 {
1172 default:
1173 case 0x01:
1174 fpsrate = 24000;
1175 fpsscale = 1001;
1176 break;
1177 case 0x02:
1178 fpsrate = 24000;
1179 fpsscale = 1000;
1180 break;
1181 case 0x03:
1182 fpsrate = 25000;
1183 fpsscale = 1000;
1184 break;
1185 case 0x04:
1186 fpsrate = 30000;
1187 fpsscale = 1001;
1188 break;
1189 case 0x05:
1190 fpsrate = 30000;
1191 fpsscale = 1000;
1192 break;
1193 case 0x06:
1194 fpsrate = 50000;
1195 fpsscale = 1000;
1196 break;
1197 case 0x07:
1198 fpsrate = 60000;
1199 fpsscale = 1001;
1200 break;
1201 case 0x08:
1202 fpsrate = 60000;
1203 fpsscale = 1000;
1204 break;
1205 }
1206
1207 if (fpsscale != sequence->fps_scale || fpsrate != sequence->fps_rate)
1208 {
1209 changed = true;
1210 sequence->fps_rate = fpsrate;
1211 sequence->fps_scale = fpsscale;
1212 }
1213 }
1214 nal_start = nal_end;
1215 }
1216
1217 return changed;
1218}
1219
diff --git a/xbmc/utils/BitstreamConverter.h b/xbmc/utils/BitstreamConverter.h
new file mode 100644
index 0000000..9244870
--- /dev/null
+++ b/xbmc/utils/BitstreamConverter.h
@@ -0,0 +1,139 @@
1/*
2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12
13extern "C" {
14#include <libavutil/avutil.h>
15#include <libavformat/avformat.h>
16#include <libavfilter/avfilter.h>
17#include <libavcodec/avcodec.h>
18}
19
20typedef struct
21{
22 const uint8_t *data;
23 const uint8_t *end;
24 int head;
25 uint64_t cache;
26} nal_bitstream;
27
28typedef struct mpeg2_sequence
29{
30 uint32_t width;
31 uint32_t height;
32 uint32_t fps_rate;
33 uint32_t fps_scale;
34 float ratio;
35 uint32_t ratio_info;
36} mpeg2_sequence;
37
38typedef struct
39{
40 int profile_idc;
41 int level_idc;
42 int sps_id;
43
44 int chroma_format_idc;
45 int separate_colour_plane_flag;
46 int bit_depth_luma_minus8;
47 int bit_depth_chroma_minus8;
48 int qpprime_y_zero_transform_bypass_flag;
49 int seq_scaling_matrix_present_flag;
50
51 int log2_max_frame_num_minus4;
52 int pic_order_cnt_type;
53 int log2_max_pic_order_cnt_lsb_minus4;
54
55 int max_num_ref_frames;
56 int gaps_in_frame_num_value_allowed_flag;
57 int pic_width_in_mbs_minus1;
58 int pic_height_in_map_units_minus1;
59
60 int frame_mbs_only_flag;
61 int mb_adaptive_frame_field_flag;
62
63 int direct_8x8_inference_flag;
64
65 int frame_cropping_flag;
66 int frame_crop_left_offset;
67 int frame_crop_right_offset;
68 int frame_crop_top_offset;
69 int frame_crop_bottom_offset;
70} sps_info_struct;
71
72class CBitstreamParser
73{
74public:
75 CBitstreamParser();
76 ~CBitstreamParser();
77
78 static bool Open(){ return true; };
79 static void Close();
80 static bool CanStartDecode(const uint8_t *buf, int buf_size);
81};
82
83class CBitstreamConverter
84{
85public:
86 CBitstreamConverter();
87 ~CBitstreamConverter();
88
89 bool Open(enum AVCodecID codec, uint8_t *in_extradata, int in_extrasize, bool to_annexb);
90 void Close(void);
91 bool NeedConvert(void) const { return m_convert_bitstream; };
92 bool Convert(uint8_t *pData, int iSize);
93 uint8_t* GetConvertBuffer(void) const;
94 int GetConvertSize() const;
95 uint8_t* GetExtraData(void) const;
96 int GetExtraSize() const;
97 void ResetStartDecode(void);
98 bool CanStartDecode() const;
99
100 static bool mpeg2_sequence_header(const uint8_t *data, const uint32_t size, mpeg2_sequence *sequence);
101
102protected:
103 static int avc_parse_nal_units(AVIOContext *pb, const uint8_t *buf_in, int size);
104 static int avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size);
105 int isom_write_avcc(AVIOContext *pb, const uint8_t *data, int len);
106 // bitstream to bytestream (Annex B) conversion support.
107 bool IsIDR(uint8_t unit_type);
108 bool IsSlice(uint8_t unit_type);
109 bool BitstreamConvertInitAVC(void *in_extradata, int in_extrasize);
110 bool BitstreamConvertInitHEVC(void *in_extradata, int in_extrasize);
111 bool BitstreamConvert(uint8_t* pData, int iSize, uint8_t **poutbuf, int *poutbuf_size);
112 static void BitstreamAllocAndCopy(uint8_t **poutbuf, int *poutbuf_size,
113 const uint8_t *sps_pps, uint32_t sps_pps_size, const uint8_t *in, uint32_t in_size);
114
115 typedef struct omx_bitstream_ctx {
116 uint8_t length_size;
117 uint8_t first_idr;
118 uint8_t idr_sps_pps_seen;
119 uint8_t *sps_pps_data;
120 uint32_t size;
121 } omx_bitstream_ctx;
122
123 uint8_t *m_convertBuffer;
124 int m_convertSize;
125 uint8_t *m_inputBuffer;
126 int m_inputSize;
127
128 uint32_t m_sps_pps_size;
129 omx_bitstream_ctx m_sps_pps_context;
130 bool m_convert_bitstream;
131 bool m_to_annexb;
132
133 uint8_t *m_extradata;
134 int m_extrasize;
135 bool m_convert_3byteTo4byteNALSize;
136 bool m_convert_bytestream;
137 AVCodecID m_codec;
138 bool m_start_decode;
139};
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 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "BitstreamReader.h"
10
11CBitstreamReader::CBitstreamReader(const uint8_t *buf, int len)
12 : buffer(buf)
13 , start(buf)
14 , offbits(0)
15 , length(len)
16 , oflow(0)
17{
18}
19
20uint32_t CBitstreamReader::ReadBits(int nbits)
21{
22 uint32_t ret = GetBits(nbits);
23
24 offbits += nbits;
25 buffer += offbits / 8;
26 offbits %= 8;
27
28 return ret;
29}
30
31void CBitstreamReader::SkipBits(int nbits)
32{
33 offbits += nbits;
34 buffer += offbits / 8;
35 offbits %= 8;
36
37 if (buffer > (start + length))
38 oflow = 1;
39}
40
41uint32_t CBitstreamReader::GetBits(int nbits)
42{
43 int i, nbytes;
44 uint32_t ret = 0;
45 const uint8_t *buf;
46
47 buf = buffer;
48 nbytes = (offbits + nbits) / 8;
49
50 if (((offbits + nbits) % 8) > 0)
51 nbytes++;
52
53 if ((buf + nbytes) > (start + length))
54 {
55 oflow = 1;
56 return 0;
57 }
58 for (i = 0; i<nbytes; i++)
59 ret += buf[i] << ((nbytes - i - 1) * 8);
60
61 i = (4 - nbytes) * 8 + offbits;
62
63 ret = ((ret << i) >> i) >> ((nbytes * 8) - nbits - offbits);
64
65 return ret;
66}
67
68const uint8_t* find_start_code(const uint8_t *p, const uint8_t *end, uint32_t *state)
69{
70 if (p >= end)
71 return end;
72
73 for (int i = 0; i < 3; i++)
74 {
75 uint32_t tmp = *state << 8;
76 *state = tmp + *(p++);
77 if (tmp == 0x100 || p == end)
78 return p;
79 }
80
81 while (p < end)
82 {
83 if (p[-1] > 1) p += 3;
84 else if (p[-2]) p += 2;
85 else if (p[-3] | (p[-1] - 1)) p++;
86 else {
87 p++;
88 break;
89 }
90 }
91
92 p = (p < end)? p - 4 : end - 4;
93 *state = BS_RB32(p);
94
95 return p + 4;
96}
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 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12
13class CBitstreamReader
14{
15public:
16 CBitstreamReader(const uint8_t *buf, int len);
17 uint32_t ReadBits(int nbits);
18 void SkipBits(int nbits);
19 uint32_t GetBits(int nbits);
20
21private:
22 const uint8_t *buffer, *start;
23 int offbits, length, oflow;
24};
25
26const uint8_t* find_start_code(const uint8_t *p, const uint8_t *end, uint32_t *state);
27
28////////////////////////////////////////////////////////////////////////////////////////////
29//! @todo refactor this so as not to need these ffmpeg routines.
30//! These are not exposed in ffmpeg's API so we dupe them here.
31
32/*
33 * AVC helper functions for muxers
34 * Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@smartjog.com>
35 * This is part of FFmpeg
36 *
37 * SPDX-License-Identifier: LGPL-2.1-or-later
38 * See LICENSES/README.md for more information.
39 */
40constexpr uint32_t BS_RB24(const uint8_t* x)
41{
42 return (x[0] << 16) | (x[1] << 8) | x[2];
43}
44
45constexpr uint32_t BS_RB32(const uint8_t* x)
46{
47 return (x[1] << 24) | (x[1] << 16) | (x[2] << 8) | x[3];
48}
49
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "BitstreamStats.h"
10
11#include "utils/TimeUtils.h"
12
13int64_t BitstreamStats::m_tmFreq;
14
15BitstreamStats::BitstreamStats(unsigned int nEstimatedBitrate)
16{
17 m_dBitrate = 0.0;
18 m_dMaxBitrate = 0.0;
19 m_dMinBitrate = -1.0;
20
21 m_nBitCount = 0;
22 m_nEstimatedBitrate = nEstimatedBitrate;
23 m_tmStart = 0LL;
24
25 if (m_tmFreq == 0LL)
26 m_tmFreq = CurrentHostFrequency();
27}
28
29void BitstreamStats::AddSampleBytes(unsigned int nBytes)
30{
31 AddSampleBits(nBytes*8);
32}
33
34void BitstreamStats::AddSampleBits(unsigned int nBits)
35{
36 m_nBitCount += nBits;
37 if (m_nBitCount >= m_nEstimatedBitrate)
38 CalculateBitrate();
39}
40
41void BitstreamStats::Start()
42{
43 m_nBitCount = 0;
44 m_tmStart = CurrentHostCounter();
45}
46
47void BitstreamStats::CalculateBitrate()
48{
49 int64_t tmNow;
50 tmNow = CurrentHostCounter();
51
52 double elapsed = (double)(tmNow - m_tmStart) / (double)m_tmFreq;
53 // only update once every 2 seconds
54 if (elapsed >= 2)
55 {
56 m_dBitrate = (double)m_nBitCount / elapsed;
57
58 if (m_dBitrate > m_dMaxBitrate)
59 m_dMaxBitrate = m_dBitrate;
60
61 if (m_dBitrate < m_dMinBitrate || m_dMinBitrate == -1)
62 m_dMinBitrate = m_dBitrate;
63
64 Start();
65 }
66}
67
68
69
70
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12
13class BitstreamStats final
14{
15public:
16 // in order not to cause a performance hit, we should only check the clock when
17 // we reach m_nEstimatedBitrate bits.
18 // if this value is 1, we will calculate bitrate on every sample.
19 explicit BitstreamStats(unsigned int nEstimatedBitrate=(10240*8) /*10Kbit*/);
20
21 void AddSampleBytes(unsigned int nBytes);
22 void AddSampleBits(unsigned int nBits);
23
24 inline double GetBitrate() const { return m_dBitrate; }
25 inline double GetMaxBitrate() const { return m_dMaxBitrate; }
26 inline double GetMinBitrate() const { return m_dMinBitrate; }
27
28 void Start();
29 void CalculateBitrate();
30
31private:
32 double m_dBitrate;
33 double m_dMaxBitrate;
34 double m_dMinBitrate;
35 unsigned int m_nBitCount;
36 unsigned int m_nEstimatedBitrate; // when we reach this amount of bits we check current bitrate.
37 int64_t m_tmStart;
38 static int64_t m_tmFreq;
39};
40
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 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "BitstreamWriter.h"
10
11CBitstreamWriter::CBitstreamWriter(uint8_t *buffer, unsigned int buffer_size, int writer_le)
12 : writer_le(writer_le)
13 , bit_buf(0)
14 , bit_left(32)
15 , buf(buffer)
16 , buf_ptr(buf)
17{
18}
19
20void CBitstreamWriter::WriteBits(int n, unsigned int value)
21{
22 // Write up to 32 bits into a bitstream.
23 unsigned int bit_buf;
24 int bit_left;
25
26 if (n == 32)
27 {
28 // Write exactly 32 bits into a bitstream.
29 // danger, recursion in play.
30 int lo = value & 0xffff;
31 int hi = value >> 16;
32 if (writer_le)
33 {
34 WriteBits(16, lo);
35 WriteBits(16, hi);
36 }
37 else
38 {
39 WriteBits(16, hi);
40 WriteBits(16, lo);
41 }
42 return;
43 }
44
45 bit_buf = this->bit_buf;
46 bit_left = this->bit_left;
47
48 if (writer_le)
49 {
50 bit_buf |= value << (32 - bit_left);
51 if (n >= bit_left) {
52 BS_WL32(buf_ptr, bit_buf);
53 buf_ptr += 4;
54 bit_buf = (bit_left == 32) ? 0 : value >> bit_left;
55 bit_left += 32;
56 }
57 bit_left -= n;
58 }
59 else
60 {
61 if (n < bit_left) {
62 bit_buf = (bit_buf << n) | value;
63 bit_left -= n;
64 }
65 else {
66 bit_buf <<= bit_left;
67 bit_buf |= value >> (n - bit_left);
68 BS_WB32(buf_ptr, bit_buf);
69 buf_ptr += 4;
70 bit_left += 32 - n;
71 bit_buf = value;
72 }
73 }
74
75 this->bit_buf = bit_buf;
76 this->bit_left = bit_left;
77}
78
79void CBitstreamWriter::SkipBits(int n)
80{
81 // Skip the given number of bits.
82 // Must only be used if the actual values in the bitstream do not matter.
83 // If n is 0 the behavior is undefined.
84 bit_left -= n;
85 buf_ptr -= 4 * (bit_left >> 5);
86 bit_left &= 31;
87}
88
89void CBitstreamWriter::FlushBits()
90{
91 if (!writer_le)
92 {
93 if (bit_left < 32)
94 bit_buf <<= bit_left;
95 }
96 while (bit_left < 32)
97 {
98
99 if (writer_le)
100 {
101 *buf_ptr++ = bit_buf;
102 bit_buf >>= 8;
103 }
104 else
105 {
106 *buf_ptr++ = bit_buf >> 24;
107 bit_buf <<= 8;
108 }
109 bit_left += 8;
110 }
111 bit_left = 32;
112 bit_buf = 0;
113}
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 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12
13class CBitstreamWriter
14{
15public:
16 CBitstreamWriter(uint8_t *buffer, unsigned int buffer_size, int writer_le);
17 void WriteBits(int n, unsigned int value);
18 void SkipBits(int n);
19 void FlushBits();
20
21private:
22 int writer_le;
23 uint32_t bit_buf;
24 int bit_left;
25 uint8_t *buf, *buf_ptr;
26};
27
28////////////////////////////////////////////////////////////////////////////////////////////
29//! @todo refactor this so as not to need these ffmpeg routines.
30//! These are not exposed in ffmpeg's API so we dupe them here.
31
32/*
33 * AVC helper functions for muxers
34 * Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@smartjog.com>
35 * This is part of FFmpeg
36 *
37 * SPDX-License-Identifier: LGPL-2.1-or-later
38 * See LICENSES/README.md for more information.
39 */
40#define BS_WB32(p, d) { \
41 ((uint8_t*)(p))[3] = (d); \
42 ((uint8_t*)(p))[2] = (d) >> 8; \
43 ((uint8_t*)(p))[1] = (d) >> 16; \
44 ((uint8_t*)(p))[0] = (d) >> 24; }
45
46#define BS_WL32(p, d) { \
47 ((uint8_t*)(p))[0] = (d); \
48 ((uint8_t*)(p))[1] = (d) >> 8; \
49 ((uint8_t*)(p))[2] = (d) >> 16; \
50 ((uint8_t*)(p))[3] = (d) >> 24; }
diff --git a/xbmc/utils/BooleanLogic.cpp b/xbmc/utils/BooleanLogic.cpp
new file mode 100644
index 0000000..c6be261
--- /dev/null
+++ b/xbmc/utils/BooleanLogic.cpp
@@ -0,0 +1,122 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "BooleanLogic.h"
10
11#include "utils/StringUtils.h"
12#include "utils/XBMCTinyXML.h"
13#include "utils/log.h"
14
15bool CBooleanLogicValue::Deserialize(const TiXmlNode *node)
16{
17 if (node == NULL)
18 return false;
19
20 const TiXmlElement *elem = node->ToElement();
21 if (elem == NULL)
22 return false;
23
24 if (node->FirstChild() != NULL && node->FirstChild()->Type() == TiXmlNode::TINYXML_TEXT)
25 m_value = node->FirstChild()->ValueStr();
26
27 m_negated = false;
28 const char *strNegated = elem->Attribute("negated");
29 if (strNegated != NULL)
30 {
31 if (StringUtils::EqualsNoCase(strNegated, "true"))
32 m_negated = true;
33 else if (!StringUtils::EqualsNoCase(strNegated, "false"))
34 {
35 CLog::Log(LOGDEBUG, "CBooleanLogicValue: invalid negated value \"%s\"", strNegated);
36 return false;
37 }
38 }
39
40 return true;
41}
42
43bool CBooleanLogicOperation::Deserialize(const TiXmlNode *node)
44{
45 if (node == NULL)
46 return false;
47
48 // check if this is a simple operation with a single value directly expressed
49 // in the parent tag
50 if (node->FirstChild() == NULL || node->FirstChild()->Type() == TiXmlNode::TINYXML_TEXT)
51 {
52 CBooleanLogicValuePtr value = CBooleanLogicValuePtr(newValue());
53 if (value == NULL || !value->Deserialize(node))
54 {
55 CLog::Log(LOGDEBUG, "CBooleanLogicOperation: failed to deserialize implicit boolean value definition");
56 return false;
57 }
58
59 m_values.push_back(value);
60 return true;
61 }
62
63 const TiXmlNode *operationNode = node->FirstChild();
64 while (operationNode != NULL)
65 {
66 std::string tag = operationNode->ValueStr();
67 if (StringUtils::EqualsNoCase(tag, "and") || StringUtils::EqualsNoCase(tag, "or"))
68 {
69 CBooleanLogicOperationPtr operation = CBooleanLogicOperationPtr(newOperation());
70 if (operation == NULL)
71 return false;
72
73 operation->SetOperation(StringUtils::EqualsNoCase(tag, "and") ? BooleanLogicOperationAnd : BooleanLogicOperationOr);
74 if (!operation->Deserialize(operationNode))
75 {
76 CLog::Log(LOGDEBUG, "CBooleanLogicOperation: failed to deserialize <%s> definition", tag.c_str());
77 return false;
78 }
79
80 m_operations.push_back(operation);
81 }
82 else
83 {
84 CBooleanLogicValuePtr value = CBooleanLogicValuePtr(newValue());
85 if (value == NULL)
86 return false;
87
88 if (StringUtils::EqualsNoCase(tag, value->GetTag()))
89 {
90 if (!value->Deserialize(operationNode))
91 {
92 CLog::Log(LOGDEBUG, "CBooleanLogicOperation: failed to deserialize <%s> definition", tag.c_str());
93 return false;
94 }
95
96 m_values.push_back(value);
97 }
98 else if (operationNode->Type() == TiXmlNode::TINYXML_ELEMENT)
99 CLog::Log(LOGDEBUG, "CBooleanLogicOperation: unknown <%s> definition encountered", tag.c_str());
100 }
101
102 operationNode = operationNode->NextSibling();
103 }
104
105 return true;
106}
107
108bool CBooleanLogic::Deserialize(const TiXmlNode *node)
109{
110 if (node == NULL)
111 return false;
112
113 if (m_operation == NULL)
114 {
115 m_operation = CBooleanLogicOperationPtr(new CBooleanLogicOperation());
116
117 if (m_operation == NULL)
118 return false;
119 }
120
121 return m_operation->Deserialize(node);
122}
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 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/IXmlDeserializable.h"
12
13#include <memory>
14#include <string>
15#include <vector>
16
17typedef enum {
18 BooleanLogicOperationOr = 0,
19 BooleanLogicOperationAnd
20} BooleanLogicOperation;
21
22class CBooleanLogicValue : public IXmlDeserializable
23{
24public:
25 CBooleanLogicValue(const std::string &value = "", bool negated = false)
26 : m_value(value), m_negated(negated)
27 { }
28 ~CBooleanLogicValue() override = default;
29
30 bool Deserialize(const TiXmlNode *node) override;
31
32 virtual const std::string& GetValue() const { return m_value; }
33 virtual bool IsNegated() const { return m_negated; }
34 virtual const char* GetTag() const { return "value"; }
35
36 virtual void SetValue(const std::string &value) { m_value = value; }
37 virtual void SetNegated(bool negated) { m_negated = negated; }
38
39protected:
40 std::string m_value;
41 bool m_negated;
42};
43
44typedef std::shared_ptr<CBooleanLogicValue> CBooleanLogicValuePtr;
45typedef std::vector<CBooleanLogicValuePtr> CBooleanLogicValues;
46
47class CBooleanLogicOperation;
48typedef std::shared_ptr<CBooleanLogicOperation> CBooleanLogicOperationPtr;
49typedef std::vector<CBooleanLogicOperationPtr> CBooleanLogicOperations;
50
51class CBooleanLogicOperation : public IXmlDeserializable
52{
53public:
54 explicit CBooleanLogicOperation(BooleanLogicOperation op = BooleanLogicOperationAnd)
55 : m_operation(op)
56 { }
57 ~CBooleanLogicOperation() override = default;
58
59 bool Deserialize(const TiXmlNode *node) override;
60
61 virtual BooleanLogicOperation GetOperation() const { return m_operation; }
62 virtual const CBooleanLogicOperations& GetOperations() const { return m_operations; }
63 virtual const CBooleanLogicValues& GetValues() const { return m_values; }
64
65 virtual void SetOperation(BooleanLogicOperation op) { m_operation = op; }
66
67protected:
68 virtual CBooleanLogicOperation* newOperation() { return new CBooleanLogicOperation(); }
69 virtual CBooleanLogicValue* newValue() { return new CBooleanLogicValue(); }
70
71 BooleanLogicOperation m_operation;
72 CBooleanLogicOperations m_operations;
73 CBooleanLogicValues m_values;
74};
75
76class CBooleanLogic : public IXmlDeserializable
77{
78protected:
79 /* make sure nobody deletes a pointer to this class */
80 ~CBooleanLogic() override = default;
81
82public:
83 bool Deserialize(const TiXmlNode *node) override;
84
85 const CBooleanLogicOperationPtr& Get() const { return m_operation; }
86 CBooleanLogicOperationPtr Get() { return m_operation; }
87
88protected:
89 CBooleanLogicOperationPtr m_operation;
90};
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 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "BufferObject.h"
10
11#include "BufferObjectFactory.h"
12#include "utils/log.h"
13
14#if defined(HAVE_LINUX_DMA_BUF)
15#include <linux/dma-buf.h>
16#include <sys/ioctl.h>
17#endif
18
19std::unique_ptr<CBufferObject> CBufferObject::GetBufferObject(bool needsCreateBySize)
20{
21 return CBufferObjectFactory::CreateBufferObject(needsCreateBySize);
22}
23
24int CBufferObject::GetFd()
25{
26 return m_fd;
27}
28
29uint32_t CBufferObject::GetStride()
30{
31 return m_stride;
32}
33
34uint64_t CBufferObject::GetModifier()
35{
36 return 0; // linear
37}
38
39void CBufferObject::SyncStart()
40{
41#if defined(HAVE_LINUX_DMA_BUF)
42 struct dma_buf_sync sync;
43 sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_RW;
44
45 int ret = ioctl(m_fd, DMA_BUF_IOCTL_SYNC, &sync);
46 if (ret < 0)
47 CLog::LogF(LOGERROR, "ioctl DMA_BUF_IOCTL_SYNC failed, ret={} errno={}", ret, strerror(errno));
48#endif
49}
50
51void CBufferObject::SyncEnd()
52{
53#if defined(HAVE_LINUX_DMA_BUF)
54 struct dma_buf_sync sync;
55 sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_RW;
56
57 int ret = ioctl(m_fd, DMA_BUF_IOCTL_SYNC, &sync);
58 if (ret < 0)
59 CLog::LogF(LOGERROR, "ioctl DMA_BUF_IOCTL_SYNC failed, ret={} errno={}", ret, strerror(errno));
60#endif
61}
diff --git a/xbmc/utils/BufferObject.h b/xbmc/utils/BufferObject.h
new file mode 100644
index 0000000..11d0a06
--- /dev/null
+++ b/xbmc/utils/BufferObject.h
@@ -0,0 +1,43 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "IBufferObject.h"
12
13#include <memory>
14#include <stdint.h>
15
16/**
17 * @brief base class for using the IBufferObject interface. Derived classes
18 * should be based on this class.
19 *
20 */
21class CBufferObject : public IBufferObject
22{
23public:
24 /**
25 * @brief Get a BufferObject from CBufferObjectFactory
26 *
27 * @return std::unique_ptr<CBufferObject>
28 */
29 static std::unique_ptr<CBufferObject> GetBufferObject(bool needsCreateBySize);
30
31 virtual bool CreateBufferObject(uint64_t size) override { return false; }
32
33 virtual int GetFd() override;
34 virtual uint32_t GetStride() override;
35 virtual uint64_t GetModifier() override;
36
37 void SyncStart() override;
38 void SyncEnd() override;
39
40protected:
41 int m_fd{-1};
42 uint32_t m_stride{0};
43};
diff --git a/xbmc/utils/BufferObjectFactory.cpp b/xbmc/utils/BufferObjectFactory.cpp
new file mode 100644
index 0000000..eb1ec7e
--- /dev/null
+++ b/xbmc/utils/BufferObjectFactory.cpp
@@ -0,0 +1,42 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "BufferObjectFactory.h"
10
11std::list<std::function<std::unique_ptr<CBufferObject>()>> CBufferObjectFactory::m_bufferObjects;
12
13std::unique_ptr<CBufferObject> CBufferObjectFactory::CreateBufferObject(bool needsCreateBySize)
14{
15 for (const auto bufferObject : m_bufferObjects)
16 {
17 auto bo = bufferObject();
18
19 if (needsCreateBySize)
20 {
21 if (!bo->CreateBufferObject(1))
22 continue;
23
24 bo->DestroyBufferObject();
25 }
26
27 return bo;
28 }
29
30 return nullptr;
31}
32
33void CBufferObjectFactory::RegisterBufferObject(
34 std::function<std::unique_ptr<CBufferObject>()> createFunc)
35{
36 m_bufferObjects.emplace_front(createFunc);
37}
38
39void CBufferObjectFactory::ClearBufferObjects()
40{
41 m_bufferObjects.clear();
42}
diff --git a/xbmc/utils/BufferObjectFactory.h b/xbmc/utils/BufferObjectFactory.h
new file mode 100644
index 0000000..a466e84
--- /dev/null
+++ b/xbmc/utils/BufferObjectFactory.h
@@ -0,0 +1,48 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "BufferObject.h"
12
13#include <functional>
14#include <list>
15#include <memory>
16
17/**
18 * @brief Factory that provides CBufferObject registration and creation
19 *
20 */
21class CBufferObjectFactory
22{
23public:
24 /**
25 * @brief Create a CBufferObject from the registered BufferObject types.
26 * In the future this may include some criteria for selecting a specific
27 * CBufferObject derived type. Currently it returns the CBufferObject
28 * implementation that was registered last.
29 *
30 * @return std::unique_ptr<CBufferObject>
31 */
32 static std::unique_ptr<CBufferObject> CreateBufferObject(bool needsCreateBySize);
33
34 /**
35 * @brief Registers a CBufferObject class to class to the factory.
36 *
37 */
38 static void RegisterBufferObject(std::function<std::unique_ptr<CBufferObject>()>);
39
40 /**
41 * @brief Clears the list of registered CBufferObject types
42 *
43 */
44 static void ClearBufferObjects();
45
46protected:
47 static std::list<std::function<std::unique_ptr<CBufferObject>()>> m_bufferObjects;
48};
diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt
new file mode 100644
index 0000000..a04870e
--- /dev/null
+++ b/xbmc/utils/CMakeLists.txt
@@ -0,0 +1,227 @@
1set(SOURCES ActorProtocol.cpp
2 AlarmClock.cpp
3 AliasShortcutUtils.cpp
4 Archive.cpp
5 auto_buffer.cpp
6 Base64.cpp
7 BitstreamConverter.cpp
8 BitstreamReader.cpp
9 BitstreamStats.cpp
10 BitstreamWriter.cpp
11 BooleanLogic.cpp
12 CharsetConverter.cpp
13 CharsetDetection.cpp
14 ColorUtils.cpp
15 CPUInfo.cpp
16 Crc32.cpp
17 CryptThreading.cpp
18 DatabaseUtils.cpp
19 Digest.cpp
20 EndianSwap.cpp
21 EmbeddedArt.cpp
22 FileExtensionProvider.cpp
23 Fanart.cpp
24 FileOperationJob.cpp
25 FileUtils.cpp
26 GroupUtils.cpp
27 HTMLUtil.cpp
28 HttpHeader.cpp
29 HttpParser.cpp
30 HttpRangeUtils.cpp
31 HttpResponse.cpp
32 InfoLoader.cpp
33 JobManager.cpp
34 JSONVariantParser.cpp
35 JSONVariantWriter.cpp
36 LabelFormatter.cpp
37 LangCodeExpander.cpp
38 LegacyPathTranslation.cpp
39 Locale.cpp
40 log.cpp
41 Mime.cpp
42 Observer.cpp
43 POUtils.cpp
44 RecentlyAddedJob.cpp
45 RegExp.cpp
46 rfft.cpp
47 RingBuffer.cpp
48 RssManager.cpp
49 RssReader.cpp
50 ProgressJob.cpp
51 SaveFileStateJob.cpp
52 ScraperParser.cpp
53 ScraperUrl.cpp
54 Screenshot.cpp
55 SortUtils.cpp
56 Speed.cpp
57 StaticLoggerBase.cpp
58 Stopwatch.cpp
59 StreamDetails.cpp
60 StreamUtils.cpp
61 StringUtils.cpp
62 StringValidation.cpp
63 SystemInfo.cpp
64 Temperature.cpp
65 TextSearch.cpp
66 TimeUtils.cpp
67 URIUtils.cpp
68 UrlOptions.cpp
69 Utf8Utils.cpp
70 Variant.cpp
71 VC1BitstreamParser.cpp
72 Vector.cpp
73 XBMCTinyXML.cpp
74 XMLUtils.cpp)
75
76set(HEADERS ActorProtocol.h
77 AlarmClock.h
78 AliasShortcutUtils.h
79 Archive.h
80 auto_buffer.h
81 Base64.h
82 BitstreamConverter.h
83 BitstreamReader.h
84 BitstreamStats.h
85 BitstreamWriter.h
86 BooleanLogic.h
87 CharsetConverter.h
88 CharsetDetection.h
89 CPUInfo.h
90 Color.h
91 ColorUtils.h
92 Crc32.h
93 CryptThreading.h
94 DatabaseUtils.h
95 Digest.h
96 EndianSwap.h
97 EventStream.h
98 EventStreamDetail.h
99 FileExtensionProvider.h
100 Fanart.h
101 FileOperationJob.h
102 FileUtils.h
103 Geometry.h
104 GlobalsHandling.h
105 GroupUtils.h
106 HTMLUtil.h
107 HttpHeader.h
108 HttpParser.h
109 HttpRangeUtils.h
110 HttpResponse.h
111 IArchivable.h
112 IBufferObject.h
113 ILocalizer.h
114 InfoLoader.h
115 IPlatformLog.h
116 IRssObserver.h
117 IScreenshotSurface.h
118 ISerializable.h
119 ISortable.h
120 IXmlDeserializable.h
121 Job.h
122 JobManager.h
123 JSONVariantParser.h
124 JSONVariantWriter.h
125 LabelFormatter.h
126 LangCodeExpander.h
127 LegacyPathTranslation.h
128 Locale.h
129 log.h
130 logtypes.h
131 MathUtils.h
132 MemUtils.h
133 Mime.h
134 Observer.h
135 params_check_macros.h
136 POUtils.h
137 ProgressJob.h
138 RecentlyAddedJob.h
139 RegExp.h
140 rfft.h
141 RingBuffer.h
142 RssManager.h
143 RssReader.h
144 SaveFileStateJob.h
145 ScopeGuard.h
146 ScraperParser.h
147 ScraperUrl.h
148 Screenshot.h
149 SortUtils.h
150 Speed.h
151 StaticLoggerBase.h
152 Stopwatch.h
153 StreamDetails.h
154 StreamUtils.h
155 StringUtils.h
156 StringValidation.h
157 SystemInfo.h
158 Temperature.h
159 TextSearch.h
160 TimeUtils.h
161 TransformMatrix.h
162 URIUtils.h
163 UrlOptions.h
164 Utf8Utils.h
165 Variant.h
166 VC1BitstreamParser.h
167 Vector.h
168 XBMCTinyXML.h
169 XMLUtils.h)
170
171if(XSLT_FOUND)
172 list(APPEND SOURCES XSLTUtils.cpp)
173 list(APPEND HEADERS XSLTUtils.h)
174endif()
175if(EGL_FOUND)
176 list(APPEND SOURCES EGLUtils.cpp
177 EGLFence.cpp)
178 list(APPEND HEADERS EGLUtils.h
179 EGLFence.h)
180endif()
181
182# The large map trips the clang optimizer
183if(CMAKE_CXX_COMPILER_ID STREQUAL Clang)
184 set_source_files_properties(Mime.cpp PROPERTIES COMPILE_FLAGS -O0)
185endif()
186
187if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore)
188 list(APPEND SOURCES GLUtils.cpp)
189 list(APPEND HEADERS GLUtils.h)
190endif()
191
192if(CORE_PLATFORM_NAME_LC STREQUAL gbm OR CORE_PLATFORM_NAME_LC STREQUAL wayland)
193 list(APPEND SOURCES BufferObject.cpp
194 BufferObjectFactory.cpp
195 EGLImage.cpp)
196 list(APPEND HEADERS BufferObject.h
197 BufferObjectFactory.h
198 EGLImage.h)
199
200 if(CORE_PLATFORM_NAME_LC STREQUAL gbm)
201 list(APPEND SOURCES DumbBufferObject.cpp)
202 list(APPEND SOURCES DumbBufferObject.h)
203 endif()
204
205 if(HAVE_LINUX_MEMFD AND HAVE_LINUX_UDMABUF)
206 list(APPEND SOURCES UDMABufferObject.cpp)
207 list(APPEND HEADERS UDMABufferObject.h)
208 endif()
209
210 if(HAVE_LINUX_DMA_HEAP)
211 list(APPEND SOURCES DMAHeapBufferObject.cpp)
212 list(APPEND HEADERS DMAHeapBufferObject.h)
213 endif()
214
215 if(GBM_HAS_BO_MAP)
216 list(APPEND SOURCES GBMBufferObject.cpp)
217 list(APPEND HEADERS GBMBufferObject.h)
218 endif()
219endif()
220
221core_add_library(utils)
222
223if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore)
224 if(HAVE_SSE2)
225 target_compile_options(${CORE_LIBRARY} PRIVATE -msse2)
226 endif()
227endif()
diff --git a/xbmc/utils/CPUInfo.cpp b/xbmc/utils/CPUInfo.cpp
new file mode 100644
index 0000000..d816d1e
--- /dev/null
+++ b/xbmc/utils/CPUInfo.cpp
@@ -0,0 +1,62 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "CPUInfo.h"
10
11#include "utils/StringUtils.h"
12
13bool CCPUInfo::HasCoreId(int coreId) const
14{
15 for (const auto& core : m_cores)
16 {
17 if (core.m_id == coreId)
18 return true;
19 }
20
21 return false;
22}
23
24const CoreInfo CCPUInfo::GetCoreInfo(int coreId)
25{
26 CoreInfo coreInfo;
27
28 for (auto& core : m_cores)
29 {
30 if (core.m_id == coreId)
31 coreInfo = core;
32 }
33
34 return coreInfo;
35}
36
37std::string CCPUInfo::GetCoresUsageString() const
38{
39 std::string strCores;
40
41 if (SupportsCPUUsage())
42 {
43 if (!m_cores.empty())
44 {
45 for (const auto& core : m_cores)
46 {
47 if (!strCores.empty())
48 strCores += ' ';
49 if (core.m_usagePercent < 10.0)
50 strCores += StringUtils::Format("#%d: %1.1f%%", core.m_id, core.m_usagePercent);
51 else
52 strCores += StringUtils::Format("#%d: %3.0f%%", core.m_id, core.m_usagePercent);
53 }
54 }
55 else
56 {
57 strCores += StringUtils::Format("%3.0f%%", static_cast<double>(m_lastUsedPercentage));
58 }
59 }
60
61 return strCores;
62}
diff --git a/xbmc/utils/CPUInfo.h b/xbmc/utils/CPUInfo.h
new file mode 100644
index 0000000..473402d
--- /dev/null
+++ b/xbmc/utils/CPUInfo.h
@@ -0,0 +1,120 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "threads/SystemClock.h"
12#include "utils/Temperature.h"
13
14#include <memory>
15#include <string>
16#include <vector>
17
18enum CpuFeature
19{
20 CPU_FEATURE_MMX = 1 << 0,
21 CPU_FEATURE_MMX2 = 1 << 1,
22 CPU_FEATURE_SSE = 1 << 2,
23 CPU_FEATURE_SSE2 = 1 << 3,
24 CPU_FEATURE_SSE3 = 1 << 4,
25 CPU_FEATURE_SSSE3 = 1 << 5,
26 CPU_FEATURE_SSE4 = 1 << 6,
27 CPU_FEATURE_SSE42 = 1 << 7,
28 CPU_FEATURE_3DNOW = 1 << 8,
29 CPU_FEATURE_3DNOWEXT = 1 << 9,
30 CPU_FEATURE_ALTIVEC = 1 << 10,
31 CPU_FEATURE_NEON = 1 << 11,
32};
33
34struct CoreInfo
35{
36 int m_id = 0;
37 double m_usagePercent = 0.0;
38 std::size_t m_activeTime = 0;
39 std::size_t m_idleTime = 0;
40 std::size_t m_totalTime = 0;
41};
42
43class CCPUInfo
44{
45public:
46 // Defines to help with calls to CPUID
47 const unsigned int CPUID_INFOTYPE_MANUFACTURER = 0x00000000;
48 const unsigned int CPUID_INFOTYPE_STANDARD = 0x00000001;
49 const unsigned int CPUID_INFOTYPE_EXTENDED_IMPLEMENTED = 0x80000000;
50 const unsigned int CPUID_INFOTYPE_EXTENDED = 0x80000001;
51 const unsigned int CPUID_INFOTYPE_PROCESSOR_1 = 0x80000002;
52 const unsigned int CPUID_INFOTYPE_PROCESSOR_2 = 0x80000003;
53 const unsigned int CPUID_INFOTYPE_PROCESSOR_3 = 0x80000004;
54
55 // Standard Features
56 // Bitmasks for the values returned by a call to cpuid with eax=0x00000001
57 const unsigned int CPUID_00000001_ECX_SSE3 = (1 << 0);
58 const unsigned int CPUID_00000001_ECX_SSSE3 = (1 << 9);
59 const unsigned int CPUID_00000001_ECX_SSE4 = (1 << 19);
60 const unsigned int CPUID_00000001_ECX_SSE42 = (1 << 20);
61
62 const unsigned int CPUID_00000001_EDX_MMX = (1 << 23);
63 const unsigned int CPUID_00000001_EDX_SSE = (1 << 25);
64 const unsigned int CPUID_00000001_EDX_SSE2 = (1 << 26);
65
66 // Extended Features
67 // Bitmasks for the values returned by a call to cpuid with eax=0x80000001
68 const unsigned int CPUID_80000001_EDX_MMX2 = (1 << 22);
69 const unsigned int CPUID_80000001_EDX_MMX = (1 << 23);
70 const unsigned int CPUID_80000001_EDX_3DNOWEXT = (1 << 30);
71 const unsigned int CPUID_80000001_EDX_3DNOW = (1 << 31);
72
73 // In milliseconds
74 const int MINIMUM_TIME_BETWEEN_READS{500};
75
76 static std::shared_ptr<CCPUInfo> GetCPUInfo();
77
78 virtual bool SupportsCPUUsage() const { return true; }
79
80 virtual int GetUsedPercentage() = 0;
81 virtual float GetCPUFrequency() = 0;
82 virtual bool GetTemperature(CTemperature& temperature) = 0;
83
84 bool HasCoreId(int coreId) const;
85 const CoreInfo GetCoreInfo(int coreId);
86 std::string GetCoresUsageString() const;
87
88 unsigned int GetCPUFeatures() const { return m_cpuFeatures; }
89 int GetCPUCount() const { return m_cpuCount; }
90 std::string GetCPUModel() { return m_cpuModel; }
91 std::string GetCPUBogoMips() { return m_cpuBogoMips; }
92 std::string GetCPUSoC() { return m_cpuSoC; }
93 std::string GetCPUHardware() { return m_cpuHardware; }
94 std::string GetCPURevision() { return m_cpuRevision; }
95 std::string GetCPUSerial() { return m_cpuSerial; }
96
97protected:
98 CCPUInfo() = default;
99 virtual ~CCPUInfo() = default;
100
101 int m_lastUsedPercentage;
102 XbmcThreads::EndTime m_nextUsedReadTime;
103 std::string m_cpuVendor;
104 std::string m_cpuModel;
105 std::string m_cpuBogoMips;
106 std::string m_cpuSoC;
107 std::string m_cpuHardware;
108 std::string m_cpuRevision;
109 std::string m_cpuSerial;
110
111 double m_usagePercent{0.0};
112 std::size_t m_activeTime{0};
113 std::size_t m_idleTime{0};
114 std::size_t m_totalTime{0};
115
116 int m_cpuCount;
117 unsigned int m_cpuFeatures;
118
119 std::vector<CoreInfo> m_cores;
120};
diff --git a/xbmc/utils/CharsetConverter.cpp b/xbmc/utils/CharsetConverter.cpp
new file mode 100644
index 0000000..8dffd65
--- /dev/null
+++ b/xbmc/utils/CharsetConverter.cpp
@@ -0,0 +1,871 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "CharsetConverter.h"
10
11#include "LangInfo.h"
12#include "guilib/LocalizeStrings.h"
13#include "log.h"
14#include "settings/Settings.h"
15#include "settings/lib/Setting.h"
16#include "settings/lib/SettingDefinitions.h"
17#include "utils/StringUtils.h"
18#include "utils/Utf8Utils.h"
19
20#include <algorithm>
21
22#include <fribidi.h>
23#include <iconv.h>
24
25#ifdef WORDS_BIGENDIAN
26 #define ENDIAN_SUFFIX "BE"
27#else
28 #define ENDIAN_SUFFIX "LE"
29#endif
30
31#if defined(TARGET_DARWIN)
32 #define WCHAR_IS_UCS_4 1
33 #define UTF16_CHARSET "UTF-16" ENDIAN_SUFFIX
34 #define UTF32_CHARSET "UTF-32" ENDIAN_SUFFIX
35 #define UTF8_SOURCE "UTF-8-MAC"
36 #define WCHAR_CHARSET UTF32_CHARSET
37#elif defined(TARGET_WINDOWS)
38 #define WCHAR_IS_UTF16 1
39 #define UTF16_CHARSET "UTF-16" ENDIAN_SUFFIX
40 #define UTF32_CHARSET "UTF-32" ENDIAN_SUFFIX
41 #define UTF8_SOURCE "UTF-8"
42 #define WCHAR_CHARSET UTF16_CHARSET
43#elif defined(TARGET_FREEBSD)
44 #define WCHAR_IS_UCS_4 1
45 #define UTF16_CHARSET "UTF-16" ENDIAN_SUFFIX
46 #define UTF32_CHARSET "UTF-32" ENDIAN_SUFFIX
47 #define UTF8_SOURCE "UTF-8"
48 #define WCHAR_CHARSET UTF32_CHARSET
49#elif defined(TARGET_ANDROID)
50 #define WCHAR_IS_UCS_4 1
51 #define UTF16_CHARSET "UTF-16" ENDIAN_SUFFIX
52 #define UTF32_CHARSET "UTF-32" ENDIAN_SUFFIX
53 #define UTF8_SOURCE "UTF-8"
54 #define WCHAR_CHARSET UTF32_CHARSET
55#else
56 #define UTF16_CHARSET "UTF-16" ENDIAN_SUFFIX
57 #define UTF32_CHARSET "UTF-32" ENDIAN_SUFFIX
58 #define UTF8_SOURCE "UTF-8"
59 #define WCHAR_CHARSET "WCHAR_T"
60 #if __STDC_ISO_10646__
61 #ifdef SIZEOF_WCHAR_T
62 #if SIZEOF_WCHAR_T == 4
63 #define WCHAR_IS_UCS_4 1
64 #elif SIZEOF_WCHAR_T == 2
65 #define WCHAR_IS_UCS_2 1
66 #endif
67 #endif
68 #endif
69#endif
70
71#define NO_ICONV ((iconv_t)-1)
72
73enum SpecialCharset
74{
75 NotSpecialCharset = 0,
76 SystemCharset,
77 UserCharset /* locale.charset */,
78 SubtitleCharset /* subtitles.charset */,
79};
80
81class CConverterType : public CCriticalSection
82{
83public:
84 CConverterType(const std::string& sourceCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen = 1);
85 CConverterType(enum SpecialCharset sourceSpecialCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen = 1);
86 CConverterType(const std::string& sourceCharset, enum SpecialCharset targetSpecialCharset, unsigned int targetSingleCharMaxLen = 1);
87 CConverterType(enum SpecialCharset sourceSpecialCharset, enum SpecialCharset targetSpecialCharset, unsigned int targetSingleCharMaxLen = 1);
88 CConverterType(const CConverterType& other);
89 ~CConverterType();
90
91 iconv_t GetConverter(CSingleLock& converterLock);
92
93 void Reset(void);
94 void ReinitTo(const std::string& sourceCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen = 1);
95 std::string GetSourceCharset(void) const { return m_sourceCharset; }
96 std::string GetTargetCharset(void) const { return m_targetCharset; }
97 unsigned int GetTargetSingleCharMaxLen(void) const { return m_targetSingleCharMaxLen; }
98
99private:
100 static std::string ResolveSpecialCharset(enum SpecialCharset charset);
101
102 enum SpecialCharset m_sourceSpecialCharset;
103 std::string m_sourceCharset;
104 enum SpecialCharset m_targetSpecialCharset;
105 std::string m_targetCharset;
106 iconv_t m_iconv;
107 unsigned int m_targetSingleCharMaxLen;
108};
109
110CConverterType::CConverterType(const std::string& sourceCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen /*= 1*/) : CCriticalSection(),
111 m_sourceSpecialCharset(NotSpecialCharset),
112 m_sourceCharset(sourceCharset),
113 m_targetSpecialCharset(NotSpecialCharset),
114 m_targetCharset(targetCharset),
115 m_iconv(NO_ICONV),
116 m_targetSingleCharMaxLen(targetSingleCharMaxLen)
117{
118}
119
120CConverterType::CConverterType(enum SpecialCharset sourceSpecialCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen /*= 1*/) : CCriticalSection(),
121 m_sourceSpecialCharset(sourceSpecialCharset),
122 m_sourceCharset(),
123 m_targetSpecialCharset(NotSpecialCharset),
124 m_targetCharset(targetCharset),
125 m_iconv(NO_ICONV),
126 m_targetSingleCharMaxLen(targetSingleCharMaxLen)
127{
128}
129
130CConverterType::CConverterType(const std::string& sourceCharset, enum SpecialCharset targetSpecialCharset, unsigned int targetSingleCharMaxLen /*= 1*/) : CCriticalSection(),
131 m_sourceSpecialCharset(NotSpecialCharset),
132 m_sourceCharset(sourceCharset),
133 m_targetSpecialCharset(targetSpecialCharset),
134 m_targetCharset(),
135 m_iconv(NO_ICONV),
136 m_targetSingleCharMaxLen(targetSingleCharMaxLen)
137{
138}
139
140CConverterType::CConverterType(enum SpecialCharset sourceSpecialCharset, enum SpecialCharset targetSpecialCharset, unsigned int targetSingleCharMaxLen /*= 1*/) : CCriticalSection(),
141 m_sourceSpecialCharset(sourceSpecialCharset),
142 m_sourceCharset(),
143 m_targetSpecialCharset(targetSpecialCharset),
144 m_targetCharset(),
145 m_iconv(NO_ICONV),
146 m_targetSingleCharMaxLen(targetSingleCharMaxLen)
147{
148}
149
150CConverterType::CConverterType(const CConverterType& other) : CCriticalSection(),
151 m_sourceSpecialCharset(other.m_sourceSpecialCharset),
152 m_sourceCharset(other.m_sourceCharset),
153 m_targetSpecialCharset(other.m_targetSpecialCharset),
154 m_targetCharset(other.m_targetCharset),
155 m_iconv(NO_ICONV),
156 m_targetSingleCharMaxLen(other.m_targetSingleCharMaxLen)
157{
158}
159
160CConverterType::~CConverterType()
161{
162 CSingleLock lock(*this);
163 if (m_iconv != NO_ICONV)
164 iconv_close(m_iconv);
165 lock.Leave(); // ensure unlocking before final destruction
166}
167
168iconv_t CConverterType::GetConverter(CSingleLock& converterLock)
169{
170 // ensure that this unique instance is locked externally
171 if (&converterLock.get_underlying() != this)
172 return NO_ICONV;
173
174 if (m_iconv == NO_ICONV)
175 {
176 if (m_sourceSpecialCharset)
177 m_sourceCharset = ResolveSpecialCharset(m_sourceSpecialCharset);
178 if (m_targetSpecialCharset)
179 m_targetCharset = ResolveSpecialCharset(m_targetSpecialCharset);
180
181 m_iconv = iconv_open(m_targetCharset.c_str(), m_sourceCharset.c_str());
182
183 if (m_iconv == NO_ICONV)
184 CLog::Log(LOGERROR, "%s: iconv_open() for \"%s\" -> \"%s\" failed, errno = %d (%s)",
185 __FUNCTION__, m_sourceCharset.c_str(), m_targetCharset.c_str(), errno, strerror(errno));
186 }
187
188 return m_iconv;
189}
190
191void CConverterType::Reset(void)
192{
193 CSingleLock lock(*this);
194 if (m_iconv != NO_ICONV)
195 {
196 iconv_close(m_iconv);
197 m_iconv = NO_ICONV;
198 }
199
200 if (m_sourceSpecialCharset)
201 m_sourceCharset.clear();
202 if (m_targetSpecialCharset)
203 m_targetCharset.clear();
204
205}
206
207void CConverterType::ReinitTo(const std::string& sourceCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen /*= 1*/)
208{
209 CSingleLock lock(*this);
210 if (sourceCharset != m_sourceCharset || targetCharset != m_targetCharset)
211 {
212 if (m_iconv != NO_ICONV)
213 {
214 iconv_close(m_iconv);
215 m_iconv = NO_ICONV;
216 }
217
218 m_sourceSpecialCharset = NotSpecialCharset;
219 m_sourceCharset = sourceCharset;
220 m_targetSpecialCharset = NotSpecialCharset;
221 m_targetCharset = targetCharset;
222 m_targetSingleCharMaxLen = targetSingleCharMaxLen;
223 }
224}
225
226std::string CConverterType::ResolveSpecialCharset(enum SpecialCharset charset)
227{
228 switch (charset)
229 {
230 case SystemCharset:
231 return "";
232 case UserCharset:
233 return g_langInfo.GetGuiCharSet();
234 case SubtitleCharset:
235 return g_langInfo.GetSubtitleCharSet();
236 case NotSpecialCharset:
237 default:
238 return "UTF-8"; /* dummy value */
239 }
240}
241
242enum StdConversionType /* Keep it in sync with CCharsetConverter::CInnerConverter::m_stdConversion */
243{
244 NoConversion = -1,
245 Utf8ToUtf32 = 0,
246 Utf32ToUtf8,
247 Utf32ToW,
248 WToUtf32,
249 SubtitleCharsetToUtf8,
250 Utf8ToUserCharset,
251 UserCharsetToUtf8,
252 Utf32ToUserCharset,
253 WtoUtf8,
254 Utf16LEtoW,
255 Utf16BEtoUtf8,
256 Utf16LEtoUtf8,
257 Utf8toW,
258 Utf8ToSystem,
259 SystemToUtf8,
260 Ucs2CharsetToUtf8,
261 NumberOfStdConversionTypes /* Dummy sentinel entry */
262};
263
264/* We don't want to pollute header file with many additional includes and definitions, so put
265 here all staff that require usage of types defined in this file or in additional headers */
266class CCharsetConverter::CInnerConverter
267{
268public:
269 static bool logicalToVisualBiDi(const std::u32string& stringSrc,
270 std::u32string& stringDst,
271 FriBidiCharType base = FRIBIDI_TYPE_LTR,
272 const bool failOnBadString = false,
273 int* visualToLogicalMap = nullptr);
274
275 template<class INPUT,class OUTPUT>
276 static bool stdConvert(StdConversionType convertType, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar = false);
277 template<class INPUT,class OUTPUT>
278 static bool customConvert(const std::string& sourceCharset, const std::string& targetCharset, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar = false);
279
280 template<class INPUT,class OUTPUT>
281 static bool convert(iconv_t type, int multiplier, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar = false);
282
283 static CConverterType m_stdConversion[NumberOfStdConversionTypes];
284 static CCriticalSection m_critSectionFriBiDi;
285};
286
287/* single symbol sizes in chars */
288const int CCharsetConverter::m_Utf8CharMinSize = 1;
289const int CCharsetConverter::m_Utf8CharMaxSize = 4;
290
291CConverterType CCharsetConverter::CInnerConverter::m_stdConversion[NumberOfStdConversionTypes] = /* keep it in sync with enum StdConversionType */
292{
293 /* Utf8ToUtf32 */ CConverterType(UTF8_SOURCE, UTF32_CHARSET),
294 /* Utf32ToUtf8 */ CConverterType(UTF32_CHARSET, "UTF-8", CCharsetConverter::m_Utf8CharMaxSize),
295 /* Utf32ToW */ CConverterType(UTF32_CHARSET, WCHAR_CHARSET),
296 /* WToUtf32 */ CConverterType(WCHAR_CHARSET, UTF32_CHARSET),
297 /* SubtitleCharsetToUtf8*/CConverterType(SubtitleCharset, "UTF-8", CCharsetConverter::m_Utf8CharMaxSize),
298 /* Utf8ToUserCharset */ CConverterType(UTF8_SOURCE, UserCharset),
299 /* UserCharsetToUtf8 */ CConverterType(UserCharset, "UTF-8", CCharsetConverter::m_Utf8CharMaxSize),
300 /* Utf32ToUserCharset */ CConverterType(UTF32_CHARSET, UserCharset),
301 /* WtoUtf8 */ CConverterType(WCHAR_CHARSET, "UTF-8", CCharsetConverter::m_Utf8CharMaxSize),
302 /* Utf16LEtoW */ CConverterType("UTF-16LE", WCHAR_CHARSET),
303 /* Utf16BEtoUtf8 */ CConverterType("UTF-16BE", "UTF-8", CCharsetConverter::m_Utf8CharMaxSize),
304 /* Utf16LEtoUtf8 */ CConverterType("UTF-16LE", "UTF-8", CCharsetConverter::m_Utf8CharMaxSize),
305 /* Utf8toW */ CConverterType(UTF8_SOURCE, WCHAR_CHARSET),
306 /* Utf8ToSystem */ CConverterType(UTF8_SOURCE, SystemCharset),
307 /* SystemToUtf8 */ CConverterType(SystemCharset, UTF8_SOURCE),
308 /* Ucs2CharsetToUtf8 */ CConverterType("UCS-2LE", "UTF-8", CCharsetConverter::m_Utf8CharMaxSize)
309};
310
311CCriticalSection CCharsetConverter::CInnerConverter::m_critSectionFriBiDi;
312
313template<class INPUT,class OUTPUT>
314bool CCharsetConverter::CInnerConverter::stdConvert(StdConversionType convertType, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar /*= false*/)
315{
316 strDest.clear();
317 if (strSource.empty())
318 return true;
319
320 if (convertType < 0 || convertType >= NumberOfStdConversionTypes)
321 return false;
322
323 CConverterType& convType = m_stdConversion[convertType];
324 CSingleLock converterLock(convType);
325
326 return convert(convType.GetConverter(converterLock), convType.GetTargetSingleCharMaxLen(), strSource, strDest, failOnInvalidChar);
327}
328
329template<class INPUT,class OUTPUT>
330bool CCharsetConverter::CInnerConverter::customConvert(const std::string& sourceCharset, const std::string& targetCharset, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar /*= false*/)
331{
332 strDest.clear();
333 if (strSource.empty())
334 return true;
335
336 iconv_t conv = iconv_open(targetCharset.c_str(), sourceCharset.c_str());
337 if (conv == NO_ICONV)
338 {
339 CLog::Log(LOGERROR, "%s: iconv_open() for \"%s\" -> \"%s\" failed, errno = %d (%s)",
340 __FUNCTION__, sourceCharset.c_str(), targetCharset.c_str(), errno, strerror(errno));
341 return false;
342 }
343 const int dstMultp = (targetCharset.compare(0, 5, "UTF-8") == 0) ? CCharsetConverter::m_Utf8CharMaxSize : 1;
344 const bool result = convert(conv, dstMultp, strSource, strDest, failOnInvalidChar);
345 iconv_close(conv);
346
347 return result;
348}
349
350/* iconv may declare inbuf to be char** rather than const char** depending on platform and version,
351 so provide a wrapper that handles both */
352struct charPtrPtrAdapter
353{
354 const char** pointer;
355 explicit charPtrPtrAdapter(const char** p) :
356 pointer(p) { }
357 operator char**()
358 { return const_cast<char**>(pointer); }
359 operator const char**()
360 { return pointer; }
361};
362
363template<class INPUT,class OUTPUT>
364bool CCharsetConverter::CInnerConverter::convert(iconv_t type, int multiplier, const INPUT& strSource, OUTPUT& strDest, bool failOnInvalidChar /*= false*/)
365{
366 if (type == NO_ICONV)
367 return false;
368
369 //input buffer for iconv() is the buffer from strSource
370 size_t inBufSize = (strSource.length() + 1) * sizeof(typename INPUT::value_type);
371 const char* inBuf = (const char*)strSource.c_str();
372
373 //allocate output buffer for iconv()
374 size_t outBufSize = (strSource.length() + 1) * sizeof(typename OUTPUT::value_type) * multiplier;
375 char* outBuf = (char*)malloc(outBufSize);
376 if (outBuf == NULL)
377 {
378 CLog::Log(LOGFATAL, "%s: malloc failed", __FUNCTION__);
379 return false;
380 }
381
382 size_t inBytesAvail = inBufSize; //how many bytes iconv() can read
383 size_t outBytesAvail = outBufSize; //how many bytes iconv() can write
384 const char* inBufStart = inBuf; //where in our input buffer iconv() should start reading
385 char* outBufStart = outBuf; //where in out output buffer iconv() should start writing
386
387 size_t returnV;
388 while(true)
389 {
390 //iconv() will update inBufStart, inBytesAvail, outBufStart and outBytesAvail
391 returnV = iconv(type, charPtrPtrAdapter(&inBufStart), &inBytesAvail, &outBufStart, &outBytesAvail);
392
393 if (returnV == (size_t)-1)
394 {
395 if (errno == E2BIG) //output buffer is not big enough
396 {
397 //save where iconv() ended converting, realloc might make outBufStart invalid
398 size_t bytesConverted = outBufSize - outBytesAvail;
399
400 //make buffer twice as big
401 outBufSize *= 2;
402 char* newBuf = (char*)realloc(outBuf, outBufSize);
403 if (!newBuf)
404 {
405 CLog::Log(LOGFATAL, "%s realloc failed with errno=%d(%s)", __FUNCTION__, errno,
406 strerror(errno));
407 break;
408 }
409 outBuf = newBuf;
410
411 //update the buffer pointer and counter
412 outBufStart = outBuf + bytesConverted;
413 outBytesAvail = outBufSize - bytesConverted;
414
415 //continue in the loop and convert the rest
416 continue;
417 }
418 else if (errno == EILSEQ) //An invalid multibyte sequence has been encountered in the input
419 {
420 if (failOnInvalidChar)
421 break;
422
423 //skip invalid byte
424 inBufStart++;
425 inBytesAvail--;
426 //continue in the loop and convert the rest
427 continue;
428 }
429 else if (errno == EINVAL) /* Invalid sequence at the end of input buffer */
430 {
431 if (!failOnInvalidChar)
432 returnV = 0; /* reset error status to use converted part */
433
434 break;
435 }
436 else //iconv() had some other error
437 {
438 CLog::Log(LOGERROR, "%s: iconv() failed, errno=%d (%s)",
439 __FUNCTION__, errno, strerror(errno));
440 }
441 }
442 break;
443 }
444
445 //complete the conversion (reset buffers), otherwise the current data will prefix the data on the next call
446 if (iconv(type, NULL, NULL, &outBufStart, &outBytesAvail) == (size_t)-1)
447 CLog::Log(LOGERROR, "%s failed cleanup errno=%d(%s)", __FUNCTION__, errno, strerror(errno));
448
449 if (returnV == (size_t)-1)
450 {
451 free(outBuf);
452 return false;
453 }
454 //we're done
455
456 const typename OUTPUT::size_type sizeInChars = (typename OUTPUT::size_type) (outBufSize - outBytesAvail) / sizeof(typename OUTPUT::value_type);
457 typename OUTPUT::const_pointer strPtr = (typename OUTPUT::const_pointer) outBuf;
458 /* Make sure that all buffer is assigned and string is stopped at end of buffer */
459 if (strPtr[sizeInChars-1] == 0 && strSource[strSource.length()-1] != 0)
460 strDest.assign(strPtr, sizeInChars-1);
461 else
462 strDest.assign(strPtr, sizeInChars);
463
464 free(outBuf);
465
466 return true;
467}
468
469bool CCharsetConverter::CInnerConverter::logicalToVisualBiDi(
470 const std::u32string& stringSrc,
471 std::u32string& stringDst,
472 FriBidiCharType base /*= FRIBIDI_TYPE_LTR*/,
473 const bool failOnBadString /*= false*/,
474 int* visualToLogicalMap /*= nullptr*/)
475{
476 stringDst.clear();
477
478 const size_t srcLen = stringSrc.length();
479 if (srcLen == 0)
480 return true;
481
482 stringDst.reserve(srcLen);
483 size_t lineStart = 0;
484
485 // libfribidi is not threadsafe, so make sure we make it so
486 CSingleLock lock(m_critSectionFriBiDi);
487 do
488 {
489 size_t lineEnd = stringSrc.find('\n', lineStart);
490 if (lineEnd >= srcLen) // equal to 'lineEnd == std::string::npos'
491 lineEnd = srcLen;
492 else
493 lineEnd++; // include '\n'
494
495 const size_t lineLen = lineEnd - lineStart;
496
497 FriBidiChar* visual = (FriBidiChar*) malloc((lineLen + 1) * sizeof(FriBidiChar));
498 if (visual == NULL)
499 {
500 free(visual);
501 CLog::Log(LOGFATAL, "%s: can't allocate memory", __FUNCTION__);
502 return false;
503 }
504
505 bool bidiFailed = false;
506 FriBidiCharType baseCopy = base; // preserve same value for all lines, required because fribidi_log2vis will modify parameter value
507 if (fribidi_log2vis(reinterpret_cast<const FriBidiChar*>(stringSrc.c_str() + lineStart),
508 lineLen, &baseCopy, visual, nullptr,
509 !visualToLogicalMap ? nullptr : visualToLogicalMap + lineStart, nullptr))
510 {
511 // Removes bidirectional marks
512 const int newLen = fribidi_remove_bidi_marks(
513 visual, lineLen, nullptr, !visualToLogicalMap ? nullptr : visualToLogicalMap + lineStart,
514 nullptr);
515 if (newLen > 0)
516 stringDst.append((const char32_t*)visual, (size_t)newLen);
517 else if (newLen < 0)
518 bidiFailed = failOnBadString;
519 }
520 else
521 bidiFailed = failOnBadString;
522
523 free(visual);
524
525 if (bidiFailed)
526 return false;
527
528 lineStart = lineEnd;
529 } while (lineStart < srcLen);
530
531 return !stringDst.empty();
532}
533
534static struct SCharsetMapping
535{
536 const char* charset;
537 const char* caption;
538} g_charsets[] = {
539 { "ISO-8859-1", "Western Europe (ISO)" }
540 , { "ISO-8859-2", "Central Europe (ISO)" }
541 , { "ISO-8859-3", "South Europe (ISO)" }
542 , { "ISO-8859-4", "Baltic (ISO)" }
543 , { "ISO-8859-5", "Cyrillic (ISO)" }
544 , { "ISO-8859-6", "Arabic (ISO)" }
545 , { "ISO-8859-7", "Greek (ISO)" }
546 , { "ISO-8859-8", "Hebrew (ISO)" }
547 , { "ISO-8859-9", "Turkish (ISO)" }
548 , { "CP1250", "Central Europe (Windows)" }
549 , { "CP1251", "Cyrillic (Windows)" }
550 , { "CP1252", "Western Europe (Windows)" }
551 , { "CP1253", "Greek (Windows)" }
552 , { "CP1254", "Turkish (Windows)" }
553 , { "CP1255", "Hebrew (Windows)" }
554 , { "CP1256", "Arabic (Windows)" }
555 , { "CP1257", "Baltic (Windows)" }
556 , { "CP1258", "Vietnamese (Windows)" }
557 , { "CP874", "Thai (Windows)" }
558 , { "BIG5", "Chinese Traditional (Big5)" }
559 , { "GBK", "Chinese Simplified (GBK)" }
560 , { "SHIFT_JIS", "Japanese (Shift-JIS)" }
561 , { "CP949", "Korean" }
562 , { "BIG5-HKSCS", "Hong Kong (Big5-HKSCS)" }
563 , { NULL, NULL }
564};
565
566CCharsetConverter::CCharsetConverter() = default;
567
568void CCharsetConverter::OnSettingChanged(std::shared_ptr<const CSetting> setting)
569{
570 if (setting == NULL)
571 return;
572
573 const std::string& settingId = setting->GetId();
574 if (settingId == CSettings::SETTING_LOCALE_CHARSET)
575 resetUserCharset();
576 else if (settingId == CSettings::SETTING_SUBTITLES_CHARSET)
577 resetSubtitleCharset();
578}
579
580void CCharsetConverter::clear()
581{
582}
583
584std::vector<std::string> CCharsetConverter::getCharsetLabels()
585{
586 std::vector<std::string> lab;
587 for(SCharsetMapping* c = g_charsets; c->charset; c++)
588 lab.emplace_back(c->caption);
589
590 return lab;
591}
592
593std::string CCharsetConverter::getCharsetLabelByName(const std::string& charsetName)
594{
595 for(SCharsetMapping* c = g_charsets; c->charset; c++)
596 {
597 if (StringUtils::EqualsNoCase(charsetName,c->charset))
598 return c->caption;
599 }
600
601 return "";
602}
603
604std::string CCharsetConverter::getCharsetNameByLabel(const std::string& charsetLabel)
605{
606 for(SCharsetMapping* c = g_charsets; c->charset; c++)
607 {
608 if (StringUtils::EqualsNoCase(charsetLabel, c->caption))
609 return c->charset;
610 }
611
612 return "";
613}
614
615void CCharsetConverter::reset(void)
616{
617 for (CConverterType& conversion : CInnerConverter::m_stdConversion)
618 conversion.Reset();
619}
620
621void CCharsetConverter::resetSystemCharset(void)
622{
623 CInnerConverter::m_stdConversion[Utf8ToSystem].Reset();
624 CInnerConverter::m_stdConversion[SystemToUtf8].Reset();
625}
626
627void CCharsetConverter::resetUserCharset(void)
628{
629 CInnerConverter::m_stdConversion[UserCharsetToUtf8].Reset();
630 CInnerConverter::m_stdConversion[UserCharsetToUtf8].Reset();
631 CInnerConverter::m_stdConversion[Utf32ToUserCharset].Reset();
632 resetSubtitleCharset();
633}
634
635void CCharsetConverter::resetSubtitleCharset(void)
636{
637 CInnerConverter::m_stdConversion[SubtitleCharsetToUtf8].Reset();
638}
639
640void CCharsetConverter::reinitCharsetsFromSettings(void)
641{
642 resetUserCharset(); // this will also reinit Subtitle charsets
643}
644
645bool CCharsetConverter::utf8ToUtf32(const std::string& utf8StringSrc, std::u32string& utf32StringDst, bool failOnBadChar /*= true*/)
646{
647 return CInnerConverter::stdConvert(Utf8ToUtf32, utf8StringSrc, utf32StringDst, failOnBadChar);
648}
649
650std::u32string CCharsetConverter::utf8ToUtf32(const std::string& utf8StringSrc, bool failOnBadChar /*= true*/)
651{
652 std::u32string converted;
653 utf8ToUtf32(utf8StringSrc, converted, failOnBadChar);
654 return converted;
655}
656
657bool CCharsetConverter::utf8ToUtf32Visual(const std::string& utf8StringSrc, std::u32string& utf32StringDst, bool bVisualBiDiFlip /*= false*/, bool forceLTRReadingOrder /*= false*/, bool failOnBadChar /*= false*/)
658{
659 if (bVisualBiDiFlip)
660 {
661 std::u32string converted;
662 if (!CInnerConverter::stdConvert(Utf8ToUtf32, utf8StringSrc, converted, failOnBadChar))
663 return false;
664
665 return CInnerConverter::logicalToVisualBiDi(converted, utf32StringDst, forceLTRReadingOrder ? FRIBIDI_TYPE_LTR : FRIBIDI_TYPE_PDF, failOnBadChar);
666 }
667 return CInnerConverter::stdConvert(Utf8ToUtf32, utf8StringSrc, utf32StringDst, failOnBadChar);
668}
669
670bool CCharsetConverter::utf32ToUtf8(const std::u32string& utf32StringSrc, std::string& utf8StringDst, bool failOnBadChar /*= true*/)
671{
672 return CInnerConverter::stdConvert(Utf32ToUtf8, utf32StringSrc, utf8StringDst, failOnBadChar);
673}
674
675std::string CCharsetConverter::utf32ToUtf8(const std::u32string& utf32StringSrc, bool failOnBadChar /*= false*/)
676{
677 std::string converted;
678 utf32ToUtf8(utf32StringSrc, converted, failOnBadChar);
679 return converted;
680}
681
682bool CCharsetConverter::utf32ToW(const std::u32string& utf32StringSrc, std::wstring& wStringDst, bool failOnBadChar /*= true*/)
683{
684#ifdef WCHAR_IS_UCS_4
685 wStringDst.assign((const wchar_t*)utf32StringSrc.c_str(), utf32StringSrc.length());
686 return true;
687#else // !WCHAR_IS_UCS_4
688 return CInnerConverter::stdConvert(Utf32ToW, utf32StringSrc, wStringDst, failOnBadChar);
689#endif // !WCHAR_IS_UCS_4
690}
691
692bool CCharsetConverter::utf32logicalToVisualBiDi(const std::u32string& logicalStringSrc,
693 std::u32string& visualStringDst,
694 bool forceLTRReadingOrder /*= false*/,
695 bool failOnBadString /*= false*/,
696 int* visualToLogicalMap /*= nullptr*/)
697{
698 return CInnerConverter::logicalToVisualBiDi(
699 logicalStringSrc, visualStringDst, forceLTRReadingOrder ? FRIBIDI_TYPE_LTR : FRIBIDI_TYPE_PDF,
700 failOnBadString, visualToLogicalMap);
701}
702
703bool CCharsetConverter::wToUtf32(const std::wstring& wStringSrc, std::u32string& utf32StringDst, bool failOnBadChar /*= true*/)
704{
705#ifdef WCHAR_IS_UCS_4
706 /* UCS-4 is almost equal to UTF-32, but UTF-32 has strict limits on possible values, while UCS-4 is usually unchecked.
707 * With this "conversion" we ensure that output will be valid UTF-32 string. */
708#endif
709 return CInnerConverter::stdConvert(WToUtf32, wStringSrc, utf32StringDst, failOnBadChar);
710}
711
712// The bVisualBiDiFlip forces a flip of characters for hebrew/arabic languages, only set to false if the flipping
713// of the string is already made or the string is not displayed in the GUI
714bool CCharsetConverter::utf8ToW(const std::string& utf8StringSrc, std::wstring& wStringDst, bool bVisualBiDiFlip /*= true*/,
715 bool forceLTRReadingOrder /*= false*/, bool failOnBadChar /*= false*/)
716{
717 // Try to flip hebrew/arabic characters, if any
718 if (bVisualBiDiFlip)
719 {
720 wStringDst.clear();
721 std::u32string utf32str;
722 if (!CInnerConverter::stdConvert(Utf8ToUtf32, utf8StringSrc, utf32str, failOnBadChar))
723 return false;
724
725 std::u32string utf32flipped;
726 const bool bidiResult = CInnerConverter::logicalToVisualBiDi(utf32str, utf32flipped, forceLTRReadingOrder ? FRIBIDI_TYPE_LTR : FRIBIDI_TYPE_PDF, failOnBadChar);
727
728 return CInnerConverter::stdConvert(Utf32ToW, utf32flipped, wStringDst, failOnBadChar) && bidiResult;
729 }
730
731 return CInnerConverter::stdConvert(Utf8toW, utf8StringSrc, wStringDst, failOnBadChar);
732}
733
734bool CCharsetConverter::subtitleCharsetToUtf8(const std::string& stringSrc, std::string& utf8StringDst)
735{
736 return CInnerConverter::stdConvert(SubtitleCharsetToUtf8, stringSrc, utf8StringDst, false);
737}
738
739bool CCharsetConverter::fromW(const std::wstring& wStringSrc,
740 std::string& stringDst, const std::string& enc)
741{
742 return CInnerConverter::customConvert(WCHAR_CHARSET, enc, wStringSrc, stringDst);
743}
744
745bool CCharsetConverter::toW(const std::string& stringSrc,
746 std::wstring& wStringDst, const std::string& enc)
747{
748 return CInnerConverter::customConvert(enc, WCHAR_CHARSET, stringSrc, wStringDst);
749}
750
751bool CCharsetConverter::utf8ToStringCharset(const std::string& utf8StringSrc, std::string& stringDst)
752{
753 return CInnerConverter::stdConvert(Utf8ToUserCharset, utf8StringSrc, stringDst);
754}
755
756bool CCharsetConverter::utf8ToStringCharset(std::string& stringSrcDst)
757{
758 std::string strSrc(stringSrcDst);
759 return utf8ToStringCharset(strSrc, stringSrcDst);
760}
761
762bool CCharsetConverter::ToUtf8(const std::string& strSourceCharset, const std::string& stringSrc, std::string& utf8StringDst, bool failOnBadChar /*= false*/)
763{
764 if (strSourceCharset == "UTF-8")
765 { // simple case - no conversion necessary
766 utf8StringDst = stringSrc;
767 return true;
768 }
769
770 return CInnerConverter::customConvert(strSourceCharset, "UTF-8", stringSrc, utf8StringDst, failOnBadChar);
771}
772
773bool CCharsetConverter::utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::string& stringDst)
774{
775 if (strDestCharset == "UTF-8")
776 { // simple case - no conversion necessary
777 stringDst = utf8StringSrc;
778 return true;
779 }
780
781 return CInnerConverter::customConvert(UTF8_SOURCE, strDestCharset, utf8StringSrc, stringDst);
782}
783
784bool CCharsetConverter::utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::u16string& utf16StringDst)
785{
786 return CInnerConverter::customConvert(UTF8_SOURCE, strDestCharset, utf8StringSrc, utf16StringDst);
787}
788
789bool CCharsetConverter::utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::u32string& utf32StringDst)
790{
791 return CInnerConverter::customConvert(UTF8_SOURCE, strDestCharset, utf8StringSrc, utf32StringDst);
792}
793
794bool CCharsetConverter::unknownToUTF8(std::string& stringSrcDst)
795{
796 std::string source(stringSrcDst);
797 return unknownToUTF8(source, stringSrcDst);
798}
799
800bool CCharsetConverter::unknownToUTF8(const std::string& stringSrc, std::string& utf8StringDst, bool failOnBadChar /*= false*/)
801{
802 // checks whether it's utf8 already, and if not converts using the sourceCharset if given, else the string charset
803 if (CUtf8Utils::isValidUtf8(stringSrc))
804 {
805 utf8StringDst = stringSrc;
806 return true;
807 }
808 return CInnerConverter::stdConvert(UserCharsetToUtf8, stringSrc, utf8StringDst, failOnBadChar);
809}
810
811bool CCharsetConverter::wToUTF8(const std::wstring& wStringSrc, std::string& utf8StringDst, bool failOnBadChar /*= false*/)
812{
813 return CInnerConverter::stdConvert(WtoUtf8, wStringSrc, utf8StringDst, failOnBadChar);
814}
815
816bool CCharsetConverter::utf16BEtoUTF8(const std::u16string& utf16StringSrc, std::string& utf8StringDst)
817{
818 return CInnerConverter::stdConvert(Utf16BEtoUtf8, utf16StringSrc, utf8StringDst);
819}
820
821bool CCharsetConverter::utf16LEtoUTF8(const std::u16string& utf16StringSrc,
822 std::string& utf8StringDst)
823{
824 return CInnerConverter::stdConvert(Utf16LEtoUtf8, utf16StringSrc, utf8StringDst);
825}
826
827bool CCharsetConverter::ucs2ToUTF8(const std::u16string& ucs2StringSrc, std::string& utf8StringDst)
828{
829 return CInnerConverter::stdConvert(Ucs2CharsetToUtf8, ucs2StringSrc,utf8StringDst);
830}
831
832bool CCharsetConverter::utf16LEtoW(const std::u16string& utf16String, std::wstring& wString)
833{
834 return CInnerConverter::stdConvert(Utf16LEtoW, utf16String, wString);
835}
836
837bool CCharsetConverter::utf32ToStringCharset(const std::u32string& utf32StringSrc, std::string& stringDst)
838{
839 return CInnerConverter::stdConvert(Utf32ToUserCharset, utf32StringSrc, stringDst);
840}
841
842bool CCharsetConverter::utf8ToSystem(std::string& stringSrcDst, bool failOnBadChar /*= false*/)
843{
844 std::string strSrc(stringSrcDst);
845 return CInnerConverter::stdConvert(Utf8ToSystem, strSrc, stringSrcDst, failOnBadChar);
846}
847
848bool CCharsetConverter::systemToUtf8(const std::string& sysStringSrc, std::string& utf8StringDst, bool failOnBadChar /*= false*/)
849{
850 return CInnerConverter::stdConvert(SystemToUtf8, sysStringSrc, utf8StringDst, failOnBadChar);
851}
852
853bool CCharsetConverter::utf8logicalToVisualBiDi(const std::string& utf8StringSrc, std::string& utf8StringDst, bool failOnBadString /*= false*/)
854{
855 utf8StringDst.clear();
856 std::u32string utf32flipped;
857 if (!utf8ToUtf32Visual(utf8StringSrc, utf32flipped, true, true, failOnBadString))
858 return false;
859
860 return CInnerConverter::stdConvert(Utf32ToUtf8, utf32flipped, utf8StringDst, failOnBadString);
861}
862
863void CCharsetConverter::SettingOptionsCharsetsFiller(SettingConstPtr setting, std::vector<StringSettingOption>& list, std::string& current, void *data)
864{
865 std::vector<std::string> vecCharsets = g_charsetConverter.getCharsetLabels();
866 sort(vecCharsets.begin(), vecCharsets.end(), sortstringbyname());
867
868 list.emplace_back(g_localizeStrings.Get(13278), "DEFAULT"); // "Default"
869 for (int i = 0; i < (int) vecCharsets.size(); ++i)
870 list.emplace_back(vecCharsets[i], g_charsetConverter.getCharsetNameByLabel(vecCharsets[i]));
871}
diff --git a/xbmc/utils/CharsetConverter.h b/xbmc/utils/CharsetConverter.h
new file mode 100644
index 0000000..1c7e014
--- /dev/null
+++ b/xbmc/utils/CharsetConverter.h
@@ -0,0 +1,169 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "settings/lib/ISettingCallback.h"
12#include "utils/GlobalsHandling.h"
13
14#include <string>
15#include <utility>
16#include <vector>
17
18class CSetting;
19struct StringSettingOption;
20
21class CCharsetConverter : public ISettingCallback
22{
23public:
24 CCharsetConverter();
25
26 void OnSettingChanged(std::shared_ptr<const CSetting> setting) override;
27
28 static void reset();
29 static void resetSystemCharset();
30 static void reinitCharsetsFromSettings(void);
31
32 static void clear();
33
34 /**
35 * Convert UTF-8 string to UTF-32 string.
36 * No RTL logical-visual transformation is performed.
37 * @param utf8StringSrc is source UTF-8 string to convert
38 * @param utf32StringDst is output UTF-32 string, empty on any error
39 * @param failOnBadChar if set to true function will fail on invalid character,
40 * otherwise invalid character will be skipped
41 * @return true on successful conversion, false on any error
42 */
43 static bool utf8ToUtf32(const std::string& utf8StringSrc, std::u32string& utf32StringDst, bool failOnBadChar = true);
44 /**
45 * Convert UTF-8 string to UTF-32 string.
46 * No RTL logical-visual transformation is performed.
47 * @param utf8StringSrc is source UTF-8 string to convert
48 * @param failOnBadChar if set to true function will fail on invalid character,
49 * otherwise invalid character will be skipped
50 * @return converted string on successful conversion, empty string on any error
51 */
52 static std::u32string utf8ToUtf32(const std::string& utf8StringSrc, bool failOnBadChar = true);
53 /**
54 * Convert UTF-8 string to UTF-32 string.
55 * RTL logical-visual transformation is optionally performed.
56 * Use it for readable text, GUI strings etc.
57 * @param utf8StringSrc is source UTF-8 string to convert
58 * @param utf32StringDst is output UTF-32 string, empty on any error
59 * @param bVisualBiDiFlip allow RTL visual-logical transformation if set to true, must be set
60 * to false is logical-visual transformation is already done
61 * @param forceLTRReadingOrder force LTR reading order
62 * @param failOnBadChar if set to true function will fail on invalid character,
63 * otherwise invalid character will be skipped
64 * @return true on successful conversion, false on any error
65 */
66 static bool utf8ToUtf32Visual(const std::string& utf8StringSrc, std::u32string& utf32StringDst, bool bVisualBiDiFlip = false, bool forceLTRReadingOrder = false, bool failOnBadChar = false);
67 /**
68 * Convert UTF-32 string to UTF-8 string.
69 * No RTL visual-logical transformation is performed.
70 * @param utf32StringSrc is source UTF-32 string to convert
71 * @param utf8StringDst is output UTF-8 string, empty on any error
72 * @param failOnBadChar if set to true function will fail on invalid character,
73 * otherwise invalid character will be skipped
74 * @return true on successful conversion, false on any error
75 */
76 static bool utf32ToUtf8(const std::u32string& utf32StringSrc, std::string& utf8StringDst, bool failOnBadChar = false);
77 /**
78 * Convert UTF-32 string to UTF-8 string.
79 * No RTL visual-logical transformation is performed.
80 * @param utf32StringSrc is source UTF-32 string to convert
81 * @param failOnBadChar if set to true function will fail on invalid character,
82 * otherwise invalid character will be skipped
83 * @return converted string on successful conversion, empty string on any error
84 */
85 static std::string utf32ToUtf8(const std::u32string& utf32StringSrc, bool failOnBadChar = false);
86 /**
87 * Convert UTF-32 string to wchar_t string (wstring).
88 * No RTL visual-logical transformation is performed.
89 * @param utf32StringSrc is source UTF-32 string to convert
90 * @param wStringDst is output wchar_t string, empty on any error
91 * @param failOnBadChar if set to true function will fail on invalid character,
92 * otherwise invalid character will be skipped
93 * @return true on successful conversion, false on any error
94 */
95 static bool utf32ToW(const std::u32string& utf32StringSrc, std::wstring& wStringDst, bool failOnBadChar = false);
96 /**
97 * Perform logical to visual flip.
98 * @param logicalStringSrc is source string with logical characters order
99 * @param visualStringDst is output string with visual characters order, empty on any error
100 * @param forceLTRReadingOrder force LTR reading order
101 * @param visualToLogicalMap is output mapping of positions in the visual string to the logical string
102 * @return true on success, false otherwise
103 */
104 static bool utf32logicalToVisualBiDi(const std::u32string& logicalStringSrc,
105 std::u32string& visualStringDst,
106 bool forceLTRReadingOrder = false,
107 bool failOnBadString = false,
108 int* visualToLogicalMap = nullptr);
109 /**
110 * Strictly convert wchar_t string (wstring) to UTF-32 string.
111 * No RTL visual-logical transformation is performed.
112 * @param wStringSrc is source wchar_t string to convert
113 * @param utf32StringDst is output UTF-32 string, empty on any error
114 * @param failOnBadChar if set to true function will fail on invalid character,
115 * otherwise invalid character will be skipped
116 * @return true on successful conversion, false on any error
117 */
118 static bool wToUtf32(const std::wstring& wStringSrc, std::u32string& utf32StringDst, bool failOnBadChar = false);
119
120 static bool utf8ToW(const std::string& utf8StringSrc, std::wstring& wStringDst,
121 bool bVisualBiDiFlip = true, bool forceLTRReadingOrder = false,
122 bool failOnBadChar = false);
123
124 static bool utf16LEtoW(const std::u16string& utf16String, std::wstring& wString);
125
126 static bool subtitleCharsetToUtf8(const std::string& stringSrc, std::string& utf8StringDst);
127
128 static bool utf8ToStringCharset(const std::string& utf8StringSrc, std::string& stringDst);
129
130 static bool utf8ToStringCharset(std::string& stringSrcDst);
131 static bool utf8ToSystem(std::string& stringSrcDst, bool failOnBadChar = false);
132 static bool systemToUtf8(const std::string& sysStringSrc, std::string& utf8StringDst, bool failOnBadChar = false);
133
134 static bool utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::string& stringDst);
135 static bool utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::u16string& utf16StringDst);
136 static bool utf8To(const std::string& strDestCharset, const std::string& utf8StringSrc, std::u32string& utf32StringDst);
137
138 static bool ToUtf8(const std::string& strSourceCharset, const std::string& stringSrc, std::string& utf8StringDst, bool failOnBadChar = false);
139
140 static bool wToUTF8(const std::wstring& wStringSrc, std::string& utf8StringDst, bool failOnBadChar = false);
141 static bool utf16BEtoUTF8(const std::u16string& utf16StringSrc, std::string& utf8StringDst);
142 static bool utf16LEtoUTF8(const std::u16string& utf16StringSrc, std::string& utf8StringDst);
143 static bool ucs2ToUTF8(const std::u16string& ucs2StringSrc, std::string& utf8StringDst);
144
145 static bool utf8logicalToVisualBiDi(const std::string& utf8StringSrc, std::string& utf8StringDst, bool failOnBadString = false);
146
147 static bool utf32ToStringCharset(const std::u32string& utf32StringSrc, std::string& stringDst);
148
149 static std::vector<std::string> getCharsetLabels();
150 static std::string getCharsetLabelByName(const std::string& charsetName);
151 static std::string getCharsetNameByLabel(const std::string& charsetLabel);
152
153 static bool unknownToUTF8(std::string& stringSrcDst);
154 static bool unknownToUTF8(const std::string& stringSrc, std::string& utf8StringDst, bool failOnBadChar = false);
155
156 static bool toW(const std::string& stringSrc, std::wstring& wStringDst, const std::string& enc);
157 static bool fromW(const std::wstring& wStringSrc, std::string& stringDst, const std::string& enc);
158
159 static void SettingOptionsCharsetsFiller(std::shared_ptr<const CSetting> setting, std::vector<StringSettingOption>& list, std::string& current, void *data);
160private:
161 static void resetUserCharset(void);
162 static void resetSubtitleCharset(void);
163
164 static const int m_Utf8CharMinSize, m_Utf8CharMaxSize;
165 class CInnerConverter;
166};
167
168XBMC_GLOBAL_REF(CCharsetConverter,g_charsetConverter);
169#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..06a0416
--- /dev/null
+++ b/xbmc/utils/CharsetDetection.cpp
@@ -0,0 +1,639 @@
1/*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "CharsetDetection.h"
10
11#include "LangInfo.h"
12#include "utils/CharsetConverter.h"
13#include "utils/StringUtils.h"
14#include "utils/Utf8Utils.h"
15#include "utils/log.h"
16
17#include <algorithm>
18
19/* XML declaration can be virtually any size (with many-many whitespaces)
20 * but for in real world we don't need to process megabytes of data
21 * so limit search for XML declaration to reasonable value */
22const size_t CCharsetDetection::m_XmlDeclarationMaxLength = 250;
23
24/* According to http://www.w3.org/TR/2013/CR-html5-20130806/single-page.html#charset
25 * encoding must be placed in first 1024 bytes of document */
26const size_t CCharsetDetection::m_HtmlCharsetEndSearchPos = 1024;
27
28/* According to http://www.w3.org/TR/2013/CR-html5-20130806/single-page.html#space-character
29 * tab, LF, FF, CR or space can be used as whitespace */
30const std::string CCharsetDetection::m_HtmlWhitespaceChars("\x09\x0A\x0C\x0D\x20"); // tab, LF, FF, CR and space
31
32std::string CCharsetDetection::GetBomEncoding(const char* const content, const size_t contentLength)
33{
34 if (contentLength < 2)
35 return "";
36 if (content[0] == (char)0xFE && content[1] == (char)0xFF)
37 return "UTF-16BE";
38 if (contentLength >= 4 && content[0] == (char)0xFF && content[1] == (char)0xFE && content[2] == (char)0x00 && content[3] == (char)0x00)
39 return "UTF-32LE"; /* first two bytes are same for UTF-16LE and UTF-32LE, so first check for full UTF-32LE mark */
40 if (content[0] == (char)0xFF && content[1] == (char)0xFE)
41 return "UTF-16LE";
42 if (contentLength < 3)
43 return "";
44 if (content[0] == (char)0xEF && content[1] == (char)0xBB && content[2] == (char)0xBF)
45 return "UTF-8";
46 if (contentLength < 4)
47 return "";
48 if (content[0] == (char)0x00 && content[1] == (char)0x00 && content[2] == (char)0xFE && content[3] == (char)0xFF)
49 return "UTF-32BE";
50 if (contentLength >= 5 && content[0] == (char)0x2B && content[1] == (char)0x2F && content[2] == (char)0x76 &&
51 (content[4] == (char)0x32 || content[4] == (char)0x39 || content[4] == (char)0x2B || content[4] == (char)0x2F))
52 return "UTF-7";
53 if (content[0] == (char)0x84 && content[1] == (char)0x31 && content[2] == (char)0x95 && content[3] == (char)0x33)
54 return "GB18030";
55
56 return "";
57}
58
59bool CCharsetDetection::DetectXmlEncoding(const char* const xmlContent, const size_t contentLength, std::string& detectedEncoding)
60{
61 detectedEncoding.clear();
62
63 if (contentLength < 2)
64 return false; // too short for any detection
65
66 /* Byte Order Mark has priority over "encoding=" parameter */
67 detectedEncoding = GetBomEncoding(xmlContent, contentLength);
68 if (!detectedEncoding.empty())
69 return true;
70
71 /* try to read encoding from XML declaration */
72 if (GetXmlEncodingFromDeclaration(xmlContent, contentLength, detectedEncoding))
73 {
74 StringUtils::ToUpper(detectedEncoding);
75
76 /* make some safety checks */
77 if (detectedEncoding == "UTF-8")
78 return true; // fast track for most common case
79
80 if (StringUtils::StartsWith(detectedEncoding, "UCS-") || StringUtils::StartsWith(detectedEncoding, "UTF-"))
81 {
82 if (detectedEncoding == "UTF-7")
83 return true;
84
85 /* XML declaration was detected in UTF-8 mode (by 'GetXmlEncodingFromDeclaration') so we know
86 * that text in single byte encoding, but declaration itself wrongly specify multibyte encoding */
87 detectedEncoding.clear();
88 return false;
89 }
90 return true;
91 }
92
93 /* try to detect basic encoding */
94 std::string guessedEncoding;
95 if (!GuessXmlEncoding(xmlContent, contentLength, guessedEncoding))
96 return false; /* can't detect any encoding */
97
98 /* have some guessed encoding, try to use it */
99 std::string convertedXml;
100 /* use 'm_XmlDeclarationMaxLength * 4' below for UTF-32-like encodings */
101 if (!g_charsetConverter.ToUtf8(guessedEncoding, std::string(xmlContent, std::min(contentLength, m_XmlDeclarationMaxLength * 4)), convertedXml)
102 || convertedXml.empty())
103 return false; /* can't convert, guessed encoding is wrong */
104
105 /* text converted, hopefully at least XML declaration is in UTF-8 now */
106 std::string declaredEncoding;
107 /* try to read real encoding from converted XML declaration */
108 if (!GetXmlEncodingFromDeclaration(convertedXml.c_str(), convertedXml.length(), declaredEncoding))
109 { /* did not find real encoding in XML declaration, use guessed encoding */
110 detectedEncoding = guessedEncoding;
111 return true;
112 }
113
114 /* found encoding in converted XML declaration, we know correct endianness and number of bytes per char */
115 /* make some safety checks */
116 StringUtils::ToUpper(declaredEncoding);
117 if (declaredEncoding == guessedEncoding)
118 return true;
119
120 if (StringUtils::StartsWith(guessedEncoding, "UCS-4"))
121 {
122 if (declaredEncoding.length() < 5 ||
123 (!StringUtils::StartsWith(declaredEncoding, "UTF-32") && !StringUtils::StartsWith(declaredEncoding, "UCS-4")))
124 { /* Guessed encoding was correct because we can convert and read XML declaration, but declaration itself is wrong (not 4-bytes encoding) */
125 detectedEncoding = guessedEncoding;
126 return true;
127 }
128 }
129 else if (StringUtils::StartsWith(guessedEncoding, "UTF-16"))
130 {
131 if (declaredEncoding.length() < 5 ||
132 (!StringUtils::StartsWith(declaredEncoding, "UTF-16") && !StringUtils::StartsWith(declaredEncoding, "UCS-2")))
133 { /* Guessed encoding was correct because we can read XML declaration, but declaration is wrong (not 2-bytes encoding) */
134 detectedEncoding = guessedEncoding;
135 return true;
136 }
137 }
138
139 if (StringUtils::StartsWith(guessedEncoding, "UCS-4") || StringUtils::StartsWith(guessedEncoding, "UTF-16"))
140 {
141 /* Check endianness in declared encoding. We already know correct endianness as XML declaration was detected after conversion. */
142 /* Guessed UTF/UCS encoding always ends with endianness */
143 std::string guessedEndianness(guessedEncoding, guessedEncoding.length() - 2);
144
145 if (!StringUtils::EndsWith(declaredEncoding, "BE") && !StringUtils::EndsWith(declaredEncoding, "LE")) /* Declared encoding without endianness */
146 detectedEncoding = declaredEncoding + guessedEndianness; /* add guessed endianness */
147 else if (!StringUtils::EndsWith(declaredEncoding, guessedEndianness)) /* Wrong endianness in declared encoding */
148 detectedEncoding = declaredEncoding.substr(0, declaredEncoding.length() - 2) + guessedEndianness; /* replace endianness by guessed endianness */
149 else
150 detectedEncoding = declaredEncoding; /* declared encoding with correct endianness */
151
152 return true;
153 }
154 else if (StringUtils::StartsWith(guessedEncoding, "EBCDIC"))
155 {
156 if (declaredEncoding.find("EBCDIC") != std::string::npos)
157 detectedEncoding = declaredEncoding; /* Declared encoding is some specific EBCDIC encoding */
158 else
159 detectedEncoding = guessedEncoding;
160
161 return true;
162 }
163
164 /* should be unreachable */
165 return false;
166}
167
168bool CCharsetDetection::GetXmlEncodingFromDeclaration(const char* const xmlContent, const size_t contentLength, std::string& declaredEncoding)
169{
170 // following code is std::string-processing analog of regular expression-processing
171 // 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>\"]+\")"
172 // on win32 x86 machine regular expression is slower that std::string 20-40 times and can slowdown XML processing for several times
173 // seems that this regular expression is too slow due to many variable length parts, regexp for '&amp;'-fixing is much faster
174
175 declaredEncoding.clear();
176
177 // avoid extra large search
178 std::string strXml(xmlContent, std::min(contentLength, m_XmlDeclarationMaxLength));
179
180 size_t pos = strXml.find("<?xml");
181 if (pos == std::string::npos || pos + 6 > strXml.length() || pos > strXml.find('<'))
182 return false; // no "<?xml" declaration, "<?xml" is not first element or "<?xml" is incomplete
183
184 pos += 5; // 5 is length of "<?xml"
185
186 const size_t declLength = std::min(std::min(m_XmlDeclarationMaxLength, contentLength - pos), strXml.find('>', pos) - pos);
187 const std::string xmlDecl(xmlContent + pos, declLength);
188 const char* const xmlDeclC = xmlDecl.c_str(); // for faster processing of [] and for null-termination
189
190 static const char* const whiteSpaceChars = " \n\r\t"; // according to W3C Recommendation for XML, any of them can be used as separator
191 pos = 0;
192
193 while (pos + 12 <= declLength) // 12 is minimal length of "encoding='x'"
194 {
195 pos = xmlDecl.find_first_of(whiteSpaceChars, pos);
196 if (pos == std::string::npos)
197 return false; // no " encoding=" in declaration
198
199 pos = xmlDecl.find_first_not_of(whiteSpaceChars, pos);
200 if (pos == std::string::npos)
201 return false; // no "encoding=" in declaration
202
203 if (xmlDecl.compare(pos, 8, "encoding", 8) != 0)
204 continue; // not "encoding" parameter
205 pos += 8; // length of "encoding"
206
207 if (xmlDeclC[pos] == ' ' || xmlDeclC[pos] == '\n' || xmlDeclC[pos] == '\r' || xmlDeclC[pos] == '\t') // no buffer overrun as string is null-terminated
208 {
209 pos = xmlDecl.find_first_not_of(whiteSpaceChars, pos);
210 if (pos == std::string::npos)
211 return false; // this " encoding" is incomplete, only whitespace chars remains
212 }
213 if (xmlDeclC[pos] != '=')
214 { // "encoding" without "=", try to find other
215 pos--; // step back to whitespace
216 continue;
217 }
218
219 pos++; // skip '='
220 if (xmlDeclC[pos] == ' ' || xmlDeclC[pos] == '\n' || xmlDeclC[pos] == '\r' || xmlDeclC[pos] == '\t') // no buffer overrun as string is null-terminated
221 {
222 pos = xmlDecl.find_first_not_of(whiteSpaceChars, pos);
223 if (pos == std::string::npos)
224 return false; // this " encoding" is incomplete, only whitespace chars remains
225 }
226 size_t encNameEndPos;
227 if (xmlDeclC[pos] == '"')
228 encNameEndPos = xmlDecl.find('"', ++pos);
229 else if (xmlDeclC[pos] == '\'')
230 encNameEndPos = xmlDecl.find('\'', ++pos);
231 else
232 continue; // no quote or double quote after 'encoding=', try to find other
233
234 if (encNameEndPos != std::string::npos)
235 {
236 declaredEncoding.assign(xmlDecl, pos, encNameEndPos - pos);
237 return true;
238 }
239 // no closing quote or double quote after 'encoding="x', try to find other
240 }
241
242 return false;
243}
244
245bool CCharsetDetection::GuessXmlEncoding(const char* const xmlContent, const size_t contentLength, std::string& supposedEncoding)
246{
247 supposedEncoding.clear();
248 if (contentLength < 4)
249 return false; // too little data to guess
250
251 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
252 supposedEncoding = "UCS-4BE"; // use UCS-4 according to W3C recommendation
253 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
254 supposedEncoding = "UCS-4LE"; // use UCS-4 according to W3C recommendation
255 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
256 supposedEncoding = "UTF-16BE";
257 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
258 supposedEncoding = "UTF-16LE";
259 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
260 supposedEncoding = "EBCDIC-CP-US"; // guessed value, real value must be read from declaration
261 else
262 return false;
263
264 return true;
265}
266
267bool CCharsetDetection::ConvertHtmlToUtf8(const std::string& htmlContent, std::string& converted, const std::string& serverReportedCharset, std::string& usedHtmlCharset)
268{
269 converted.clear();
270 usedHtmlCharset.clear();
271 if (htmlContent.empty())
272 {
273 usedHtmlCharset = "UTF-8"; // any charset can be used for empty content, use UTF-8 as default
274 return false;
275 }
276
277 // this is relaxed implementation of http://www.w3.org/TR/2013/CR-html5-20130806/single-page.html#determining-the-character-encoding
278
279 // try to get charset from Byte Order Mark
280 std::string bomCharset(GetBomEncoding(htmlContent));
281 if (checkConversion(bomCharset, htmlContent, converted))
282 {
283 usedHtmlCharset = bomCharset;
284 return true;
285 }
286
287 // try charset from HTTP header (or from other out-of-band source)
288 if (checkConversion(serverReportedCharset, htmlContent, converted))
289 {
290 usedHtmlCharset = serverReportedCharset;
291 return true;
292 }
293
294 // try to find charset in HTML
295 std::string declaredCharset(GetHtmlEncodingFromHead(htmlContent));
296 if (!declaredCharset.empty())
297 {
298 if (declaredCharset.compare(0, 3, "UTF", 3) == 0)
299 declaredCharset = "UTF-8"; // charset string was found in singlebyte mode, charset can't be multibyte encoding
300 if (checkConversion(declaredCharset, htmlContent, converted))
301 {
302 usedHtmlCharset = declaredCharset;
303 return true;
304 }
305 }
306
307 // try UTF-8 if not tried before
308 if (bomCharset != "UTF-8" && serverReportedCharset != "UTF-8" && declaredCharset != "UTF-8" && checkConversion("UTF-8", htmlContent, converted))
309 {
310 usedHtmlCharset = "UTF-8";
311 return false; // only guessed value
312 }
313
314 // try user charset
315 std::string userCharset(g_langInfo.GetGuiCharSet());
316 if (checkConversion(userCharset, htmlContent, converted))
317 {
318 usedHtmlCharset = userCharset;
319 return false; // only guessed value
320 }
321
322 // try WINDOWS-1252
323 if (checkConversion("WINDOWS-1252", htmlContent, converted))
324 {
325 usedHtmlCharset = "WINDOWS-1252";
326 return false; // only guessed value
327 }
328
329 // can't find exact charset
330 // use one of detected as fallback
331 if (!bomCharset.empty())
332 usedHtmlCharset = bomCharset;
333 else if (!serverReportedCharset.empty())
334 usedHtmlCharset = serverReportedCharset;
335 else if (!declaredCharset.empty())
336 usedHtmlCharset = declaredCharset;
337 else if (!userCharset.empty())
338 usedHtmlCharset = userCharset;
339 else
340 usedHtmlCharset = "WINDOWS-1252";
341
342 CLog::Log(LOGWARNING, "%s: Can't correctly convert to UTF-8 charset, converting as \"%s\"", __FUNCTION__, usedHtmlCharset.c_str());
343 g_charsetConverter.ToUtf8(usedHtmlCharset, htmlContent, converted, false);
344
345 return false;
346}
347
348bool CCharsetDetection::ConvertPlainTextToUtf8(const std::string& textContent, std::string& converted, const std::string& serverReportedCharset, std::string& usedCharset)
349{
350 converted.clear();
351 usedCharset.clear();
352 if (textContent.empty())
353 {
354 usedCharset = "UTF-8"; // any charset can be used for empty content, use UTF-8 as default
355 return true;
356 }
357
358 // try to get charset from Byte Order Mark
359 std::string bomCharset(GetBomEncoding(textContent));
360 if (checkConversion(bomCharset, textContent, converted))
361 {
362 usedCharset = bomCharset;
363 return true;
364 }
365
366 // try charset from HTTP header (or from other out-of-band source)
367 if (checkConversion(serverReportedCharset, textContent, converted))
368 {
369 usedCharset = serverReportedCharset;
370 return true;
371 }
372
373 // try UTF-8 if not tried before
374 if (bomCharset != "UTF-8" && serverReportedCharset != "UTF-8" && checkConversion("UTF-8", textContent, converted))
375 {
376 usedCharset = "UTF-8";
377 return true;
378 }
379
380 // try user charset
381 std::string userCharset(g_langInfo.GetGuiCharSet());
382 if (checkConversion(userCharset, textContent, converted))
383 {
384 usedCharset = userCharset;
385 return true;
386 }
387
388 // try system default charset
389 if (g_charsetConverter.systemToUtf8(textContent, converted, true))
390 {
391 usedCharset = "char"; // synonym to system charset
392 return true;
393 }
394
395 // try WINDOWS-1252
396 if (checkConversion("WINDOWS-1252", textContent, converted))
397 {
398 usedCharset = "WINDOWS-1252";
399 return true;
400 }
401
402 // can't find correct charset
403 // use one of detected as fallback
404 if (!serverReportedCharset.empty())
405 usedCharset = serverReportedCharset;
406 else if (!bomCharset.empty())
407 usedCharset = bomCharset;
408 else if (!userCharset.empty())
409 usedCharset = userCharset;
410 else
411 usedCharset = "WINDOWS-1252";
412
413 CLog::Log(LOGWARNING, "%s: Can't correctly convert to UTF-8 charset, converting as \"%s\"", __FUNCTION__, usedCharset.c_str());
414 g_charsetConverter.ToUtf8(usedCharset, textContent, converted, false);
415
416 return false;
417}
418
419
420bool CCharsetDetection::checkConversion(const std::string& srcCharset, const std::string& src, std::string& dst)
421{
422 if (srcCharset.empty())
423 return false;
424
425 if (srcCharset != "UTF-8")
426 {
427 if (g_charsetConverter.ToUtf8(srcCharset, src, dst, true))
428 return true;
429 }
430 else if (CUtf8Utils::isValidUtf8(src))
431 {
432 dst = src;
433 return true;
434 }
435
436 return false;
437}
438
439std::string CCharsetDetection::GetHtmlEncodingFromHead(const std::string& htmlContent)
440{
441 std::string smallerHtmlContent;
442 if (htmlContent.length() > 2 * m_HtmlCharsetEndSearchPos)
443 smallerHtmlContent.assign(htmlContent, 0, 2 * m_HtmlCharsetEndSearchPos); // use twice more bytes to search for charset for safety
444
445 const std::string& html = smallerHtmlContent.empty() ? htmlContent : smallerHtmlContent; // limit search
446 const char* const htmlC = html.c_str(); // for null-termination
447 const size_t len = html.length();
448
449 // 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
450 // labels in comments correspond to the labels in HTML5 standard
451 // note: opposite to standard, everything is converted to uppercase instead of lower case
452 size_t pos = 0;
453 while (pos < len) // "loop" label
454 {
455 if (html.compare(pos, 4, "<!--", 4) == 0)
456 {
457 pos = html.find("-->", pos + 2);
458 if (pos == std::string::npos)
459 return "";
460 pos += 2;
461 }
462 else if (htmlC[pos] == '<' && (htmlC[pos + 1] == 'm' || htmlC[pos + 1] == 'M') && (htmlC[pos + 2] == 'e' || htmlC[pos + 2] == 'E')
463 && (htmlC[pos + 3] == 't' || htmlC[pos + 3] == 'T') && (htmlC[pos + 4] == 'a' || htmlC[pos + 4] == 'A')
464 && (htmlC[pos + 5] == 0x09 || htmlC[pos + 5] == 0x0A || htmlC[pos + 5] == 0x0C || htmlC[pos + 5] == 0x0D || htmlC[pos + 5] == 0x20 || htmlC[pos + 5] == 0x2F))
465 { // this is case insensitive "<meta" and one of tab, LF, FF, CR, space or slash
466 pos += 5; // "pos" points to symbol after "<meta"
467 std::string attrName, attrValue;
468 bool gotPragma = false;
469 std::string contentCharset;
470 do // "attributes" label
471 {
472 pos = GetHtmlAttribute(html, pos, attrName, attrValue);
473 if (attrName == "HTTP-EQUIV" && attrValue == "CONTENT-TYPE")
474 gotPragma = true;
475 else if (attrName == "CONTENT")
476 contentCharset = ExtractEncodingFromHtmlMeta(attrValue);
477 else if (attrName == "CHARSET")
478 {
479 StringUtils::Trim(attrValue, m_HtmlWhitespaceChars.c_str()); // tab, LF, FF, CR, space
480 if (!attrValue.empty())
481 return attrValue;
482 }
483 } while (!attrName.empty() && pos < len);
484
485 // "processing" label
486 if (gotPragma && !contentCharset.empty())
487 return contentCharset;
488 }
489 else if (htmlC[pos] == '<' && ((htmlC[pos + 1] >= 'A' && htmlC[pos + 1] <= 'Z') || (htmlC[pos + 1] >= 'a' && htmlC[pos + 1] <= 'z')))
490 {
491 pos = html.find_first_of("\x09\x0A\x0C\x0D >", pos); // tab, LF, FF, CR, space or '>'
492 std::string attrName, attrValue;
493 do
494 {
495 pos = GetHtmlAttribute(html, pos, attrName, attrValue);
496 } while (pos < len && !attrName.empty());
497 }
498 else if (html.compare(pos, 2, "<!", 2) == 0 || html.compare(pos, 2, "</", 2) == 0 || html.compare(pos, 2, "<?", 2) == 0)
499 pos = html.find('>', pos);
500
501 if (pos == std::string::npos)
502 return "";
503
504 // "next byte" label
505 pos++;
506 }
507
508 return ""; // no charset was found
509}
510
511size_t CCharsetDetection::GetHtmlAttribute(const std::string& htmlContent, size_t pos, std::string& attrName, std::string& attrValue)
512{
513 attrName.clear();
514 attrValue.clear();
515 static const char* const htmlWhitespaceSlash = "\x09\x0A\x0C\x0D\x20\x2F"; // tab, LF, FF, CR, space or slash
516 const char* const htmlC = htmlContent.c_str();
517 const size_t len = htmlContent.length();
518
519 // this is an implementation of http://www.w3.org/TR/2013/CR-html5-20130806/single-page.html#concept-get-attributes-when-sniffing
520 // labels in comments correspond to the labels in HTML5 standard
521 // note: opposite to standard, everything is converted to uppercase instead of lower case
522 pos = htmlContent.find_first_not_of(htmlWhitespaceSlash, pos);
523 if (pos == std::string::npos || htmlC[pos] == '>')
524 return pos; // only white spaces or slashes up to the end of the htmlContent or no more attributes
525
526 while (pos < len && htmlC[pos] != '=')
527 {
528 const char chr = htmlC[pos];
529 if (chr == '/' || chr == '>')
530 return pos; // no attributes or empty attribute value
531 else if (m_HtmlWhitespaceChars.find(chr) != std::string::npos) // chr is one of whitespaces
532 {
533 pos = htmlContent.find_first_not_of(m_HtmlWhitespaceChars, pos); // "spaces" label
534 if (pos == std::string::npos || htmlC[pos] != '=')
535 return pos; // only white spaces up to the end or no attribute value
536 break;
537 }
538 else
539 appendCharAsAsciiUpperCase(attrName, chr);
540
541 pos++;
542 }
543
544 if (pos >= len)
545 return std::string::npos; // no '=', '/' or '>' were found up to the end of htmlContent
546
547 pos++; // advance pos to character after '='
548
549 pos = htmlContent.find_first_not_of(m_HtmlWhitespaceChars, pos); // "value" label
550 if (pos == std::string::npos)
551 return pos; // only white spaces remain in htmlContent
552
553 if (htmlC[pos] == '>')
554 return pos; // empty attribute value
555 else if (htmlC[pos] == '"' || htmlC[pos] == '\'')
556 {
557 const char qChr = htmlC[pos];
558 // "quote loop" label
559 while (++pos < len)
560 {
561 const char chr = htmlC[pos];
562 if (chr == qChr)
563 return pos + 1;
564 else
565 appendCharAsAsciiUpperCase(attrValue, chr);
566 }
567 return std::string::npos; // no closing quote is found
568 }
569
570 appendCharAsAsciiUpperCase(attrValue, htmlC[pos]);
571 pos++;
572
573 while (pos < len)
574 {
575 const char chr = htmlC[pos];
576 if (m_HtmlWhitespaceChars.find(chr) != std::string::npos || chr == '>')
577 return pos;
578 else
579 appendCharAsAsciiUpperCase(attrValue, chr);
580
581 pos++;
582 }
583
584 return std::string::npos; // rest of htmlContent was attribute value
585}
586
587std::string CCharsetDetection::ExtractEncodingFromHtmlMeta(std::string metaContent, size_t pos /*= 0*/)
588{
589 size_t len = metaContent.length();
590 if (pos >= len)
591 return "";
592
593 const char* const metaContentC = metaContent.c_str();
594
595 // 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
596 // labels in comments correspond to the labels in HTML5 standard
597 // note: opposite to standard, case sensitive match is used as argument is always in uppercase
598 std::string charset;
599 do
600 {
601 // "loop" label
602 pos = metaContent.find("CHARSET", pos);
603 if (pos == std::string::npos)
604 return "";
605
606 pos = metaContent.find_first_not_of(m_HtmlWhitespaceChars, pos + 7); // '7' is the length of 'CHARSET'
607 if (pos != std::string::npos && metaContentC[pos] == '=')
608 {
609 pos = metaContent.find_first_not_of(m_HtmlWhitespaceChars, pos + 1);
610 if (pos != std::string::npos)
611 {
612 if (metaContentC[pos] == '\'' || metaContentC[pos] == '"')
613 {
614 const char qChr = metaContentC[pos];
615 pos++;
616 const size_t closeQpos = metaContent.find(qChr, pos);
617 if (closeQpos != std::string::npos)
618 charset.assign(metaContent, pos, closeQpos - pos);
619 }
620 else
621 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
622 }
623 break;
624 }
625 } while (pos < len);
626
627 static const char* const htmlWhitespaceCharsC = m_HtmlWhitespaceChars.c_str();
628 StringUtils::Trim(charset, htmlWhitespaceCharsC);
629
630 return charset;
631}
632
633inline void CCharsetDetection::appendCharAsAsciiUpperCase(std::string& str, const char chr)
634{
635 if (chr >= 'a' && chr <= 'z')
636 str.push_back(chr - ('a' - 'A')); // convert to upper case
637 else
638 str.push_back(chr);
639}
diff --git a/xbmc/utils/CharsetDetection.h b/xbmc/utils/CharsetDetection.h
new file mode 100644
index 0000000..1ff3905
--- /dev/null
+++ b/xbmc/utils/CharsetDetection.h
@@ -0,0 +1,94 @@
1/*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13
14class CCharsetDetection
15{
16public:
17 /**
18 * Detect text encoding by Byte Order Mark
19 * Multibyte encodings (UTF-16/32) always ends with explicit endianness (LE/BE)
20 * @param content pointer to text to analyze
21 * @param contentLength length of text
22 * @return detected encoding or empty string if BOM not detected
23 */
24 static std::string GetBomEncoding(const char* const content, const size_t contentLength);
25 /**
26 * Detect text encoding by Byte Order Mark
27 * Multibyte encodings (UTF-16/32) always ends with explicit endianness (LE/BE)
28 * @param content the text to analyze
29 * @return detected encoding or empty string if BOM not detected
30 */
31 static inline std::string GetBomEncoding(const std::string& content)
32 { return GetBomEncoding(content.c_str(), content.length()); }
33
34 static inline bool DetectXmlEncoding(const std::string& xmlContent, std::string& detectedEncoding)
35 { return DetectXmlEncoding(xmlContent.c_str(), xmlContent.length(), detectedEncoding); }
36
37 static bool DetectXmlEncoding(const char* const xmlContent, const size_t contentLength, std::string& detectedEncoding);
38
39 /**
40 * Detect HTML charset and HTML convert to UTF-8
41 * @param htmlContent content of HTML file
42 * @param converted receive result of conversion
43 * @param serverReportedCharset charset from HTTP header or from other out-of-band source, empty if unknown or unset
44 * @return true if charset is properly detected and HTML is correctly converted, false if charset is only guessed
45 */
46 static inline bool ConvertHtmlToUtf8(const std::string& htmlContent, std::string& converted, const std::string& serverReportedCharset = "")
47 {
48 std::string usedHtmlCharset;
49 return ConvertHtmlToUtf8(htmlContent, converted, serverReportedCharset, usedHtmlCharset);
50 }
51 /**
52 * Detect HTML charset and HTML convert to UTF-8
53 * @param htmlContent content of HTML file
54 * @param converted receive result of conversion
55 * @param serverReportedCharset charset from HTTP header or from other out-of-band source, empty if unknown or unset
56 * @param usedHtmlCharset receive charset used for conversion
57 * @return true if charset is properly detected and HTML is correctly converted, false if charset is only guessed
58 */
59 static bool ConvertHtmlToUtf8(const std::string& htmlContent, std::string& converted, const std::string& serverReportedCharset, std::string& usedHtmlCharset);
60
61 /**
62 * Try to convert plain text to UTF-8 using best suitable charset
63 * @param textContent text to convert
64 * @param converted receive result of conversion
65 * @param serverReportedCharset charset from HTTP header or from other out-of-band source, empty if unknown or unset
66 * @param usedCharset receive charset used for conversion
67 * @return true if converted without errors, false otherwise
68 */
69 static bool ConvertPlainTextToUtf8(const std::string& textContent, std::string& converted, const std::string& serverReportedCharset, std::string& usedCharset);
70
71private:
72 static bool GetXmlEncodingFromDeclaration(const char* const xmlContent, const size_t contentLength, std::string& declaredEncoding);
73 /**
74 * Try to guess text encoding by searching for '<?xml' mark in different encodings
75 * Multibyte encodings (UTF/UCS) always ends with explicit endianness (LE/BE)
76 * @param content pointer to text to analyze
77 * @param contentLength length of text
78 * @param detectedEncoding reference to variable that receive supposed encoding
79 * @return true if any encoding supposed, false otherwise
80 */
81 static bool GuessXmlEncoding(const char* const xmlContent, const size_t contentLength, std::string& supposedEncoding);
82
83 static std::string GetHtmlEncodingFromHead(const std::string& htmlContent);
84 static size_t GetHtmlAttribute(const std::string& htmlContent, size_t pos, std::string& atrName, std::string& strValue);
85 static std::string ExtractEncodingFromHtmlMeta(std::string metaContent, size_t pos = 0);
86
87 static bool checkConversion(const std::string& srcCharset, const std::string& src, std::string& dst);
88 static void appendCharAsAsciiUpperCase(std::string& str, const char chr);
89
90 static const size_t m_XmlDeclarationMaxLength;
91 static const size_t m_HtmlCharsetEndSearchPos;
92
93 static const std::string m_HtmlWhitespaceChars;
94};
diff --git a/xbmc/utils/Color.h b/xbmc/utils/Color.h
new file mode 100644
index 0000000..5036ccd
--- /dev/null
+++ b/xbmc/utils/Color.h
@@ -0,0 +1,32 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12
13namespace UTILS
14{
15
16 typedef uint32_t Color;
17
18namespace COLOR
19{
20 static const Color NONE = 0x00000000;
21 static const Color BLACK = 0xFF000000;
22 static const Color YELLOW = 0xFFFFFF00;
23 static const Color WHITE = 0xFFFFFFFF;
24 static const Color LIGHTGREY = 0xFFE5E5E5;
25 static const Color GREY = 0xFFC0C0C0;
26 static const Color BLUE = 0xFF0099FF;
27 static const Color BRIGHTGREEN = 0xFF00FF00;
28 static const Color YELLOWGREEN = 0xFFCCFF00;
29 static const Color CYAN = 0xFF00FFFF;
30 static const Color DARKGREY = 0xFF808080;
31} // namespace COLOR
32} // namespace UTILS
diff --git a/xbmc/utils/ColorUtils.cpp b/xbmc/utils/ColorUtils.cpp
new file mode 100644
index 0000000..80319a0
--- /dev/null
+++ b/xbmc/utils/ColorUtils.cpp
@@ -0,0 +1,19 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "ColorUtils.h"
10
11#include "Color.h"
12
13#include <math.h>
14
15UTILS::Color ColorUtils::ChangeOpacity(const UTILS::Color color, const float opacity)
16{
17 int newAlpha = ceil( ((color >> 24) & 0xff) * opacity);
18 return (color & 0x00FFFFFF) | (newAlpha << 24);
19};
diff --git a/xbmc/utils/ColorUtils.h b/xbmc/utils/ColorUtils.h
new file mode 100644
index 0000000..9522a76
--- /dev/null
+++ b/xbmc/utils/ColorUtils.h
@@ -0,0 +1,23 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "Color.h"
12
13class ColorUtils
14{
15 public:
16 /*! \brief Change the opacity of a given color
17
18 \param color The original color
19 \param opacity The opacity value as a float
20 \return the original color with the changed opacity/alpha value
21 */
22 static UTILS::Color ChangeOpacity(const UTILS::Color color, const float opacity);
23};
diff --git a/xbmc/utils/Crc32.cpp b/xbmc/utils/Crc32.cpp
new file mode 100644
index 0000000..4e002b4
--- /dev/null
+++ b/xbmc/utils/Crc32.cpp
@@ -0,0 +1,110 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Crc32.h"
10
11#include "utils/StringUtils.h"
12
13uint32_t crc_tab[256] =
14{
15 0x00000000L, 0x04C11DB7L, 0x09823B6EL, 0x0D4326D9L,
16 0x130476DCL, 0x17C56B6BL, 0x1A864DB2L, 0x1E475005L,
17 0x2608EDB8L, 0x22C9F00FL, 0x2F8AD6D6L, 0x2B4BCB61L,
18 0x350C9B64L, 0x31CD86D3L, 0x3C8EA00AL, 0x384FBDBDL,
19 0x4C11DB70L, 0x48D0C6C7L, 0x4593E01EL, 0x4152FDA9L,
20 0x5F15ADACL, 0x5BD4B01BL, 0x569796C2L, 0x52568B75L,
21 0x6A1936C8L, 0x6ED82B7FL, 0x639B0DA6L, 0x675A1011L,
22 0x791D4014L, 0x7DDC5DA3L, 0x709F7B7AL, 0x745E66CDL,
23 0x9823B6E0L, 0x9CE2AB57L, 0x91A18D8EL, 0x95609039L,
24 0x8B27C03CL, 0x8FE6DD8BL, 0x82A5FB52L, 0x8664E6E5L,
25 0xBE2B5B58L, 0xBAEA46EFL, 0xB7A96036L, 0xB3687D81L,
26 0xAD2F2D84L, 0xA9EE3033L, 0xA4AD16EAL, 0xA06C0B5DL,
27 0xD4326D90L, 0xD0F37027L, 0xDDB056FEL, 0xD9714B49L,
28 0xC7361B4CL, 0xC3F706FBL, 0xCEB42022L, 0xCA753D95L,
29 0xF23A8028L, 0xF6FB9D9FL, 0xFBB8BB46L, 0xFF79A6F1L,
30 0xE13EF6F4L, 0xE5FFEB43L, 0xE8BCCD9AL, 0xEC7DD02DL,
31 0x34867077L, 0x30476DC0L, 0x3D044B19L, 0x39C556AEL,
32 0x278206ABL, 0x23431B1CL, 0x2E003DC5L, 0x2AC12072L,
33 0x128E9DCFL, 0x164F8078L, 0x1B0CA6A1L, 0x1FCDBB16L,
34 0x018AEB13L, 0x054BF6A4L, 0x0808D07DL, 0x0CC9CDCAL,
35 0x7897AB07L, 0x7C56B6B0L, 0x71159069L, 0x75D48DDEL,
36 0x6B93DDDBL, 0x6F52C06CL, 0x6211E6B5L, 0x66D0FB02L,
37 0x5E9F46BFL, 0x5A5E5B08L, 0x571D7DD1L, 0x53DC6066L,
38 0x4D9B3063L, 0x495A2DD4L, 0x44190B0DL, 0x40D816BAL,
39 0xACA5C697L, 0xA864DB20L, 0xA527FDF9L, 0xA1E6E04EL,
40 0xBFA1B04BL, 0xBB60ADFCL, 0xB6238B25L, 0xB2E29692L,
41 0x8AAD2B2FL, 0x8E6C3698L, 0x832F1041L, 0x87EE0DF6L,
42 0x99A95DF3L, 0x9D684044L, 0x902B669DL, 0x94EA7B2AL,
43 0xE0B41DE7L, 0xE4750050L, 0xE9362689L, 0xEDF73B3EL,
44 0xF3B06B3BL, 0xF771768CL, 0xFA325055L, 0xFEF34DE2L,
45 0xC6BCF05FL, 0xC27DEDE8L, 0xCF3ECB31L, 0xCBFFD686L,
46 0xD5B88683L, 0xD1799B34L, 0xDC3ABDEDL, 0xD8FBA05AL,
47 0x690CE0EEL, 0x6DCDFD59L, 0x608EDB80L, 0x644FC637L,
48 0x7A089632L, 0x7EC98B85L, 0x738AAD5CL, 0x774BB0EBL,
49 0x4F040D56L, 0x4BC510E1L, 0x46863638L, 0x42472B8FL,
50 0x5C007B8AL, 0x58C1663DL, 0x558240E4L, 0x51435D53L,
51 0x251D3B9EL, 0x21DC2629L, 0x2C9F00F0L, 0x285E1D47L,
52 0x36194D42L, 0x32D850F5L, 0x3F9B762CL, 0x3B5A6B9BL,
53 0x0315D626L, 0x07D4CB91L, 0x0A97ED48L, 0x0E56F0FFL,
54 0x1011A0FAL, 0x14D0BD4DL, 0x19939B94L, 0x1D528623L,
55 0xF12F560EL, 0xF5EE4BB9L, 0xF8AD6D60L, 0xFC6C70D7L,
56 0xE22B20D2L, 0xE6EA3D65L, 0xEBA91BBCL, 0xEF68060BL,
57 0xD727BBB6L, 0xD3E6A601L, 0xDEA580D8L, 0xDA649D6FL,
58 0xC423CD6AL, 0xC0E2D0DDL, 0xCDA1F604L, 0xC960EBB3L,
59 0xBD3E8D7EL, 0xB9FF90C9L, 0xB4BCB610L, 0xB07DABA7L,
60 0xAE3AFBA2L, 0xAAFBE615L, 0xA7B8C0CCL, 0xA379DD7BL,
61 0x9B3660C6L, 0x9FF77D71L, 0x92B45BA8L, 0x9675461FL,
62 0x8832161AL, 0x8CF30BADL, 0x81B02D74L, 0x857130C3L,
63 0x5D8A9099L, 0x594B8D2EL, 0x5408ABF7L, 0x50C9B640L,
64 0x4E8EE645L, 0x4A4FFBF2L, 0x470CDD2BL, 0x43CDC09CL,
65 0x7B827D21L, 0x7F436096L, 0x7200464FL, 0x76C15BF8L,
66 0x68860BFDL, 0x6C47164AL, 0x61043093L, 0x65C52D24L,
67 0x119B4BE9L, 0x155A565EL, 0x18197087L, 0x1CD86D30L,
68 0x029F3D35L, 0x065E2082L, 0x0B1D065BL, 0x0FDC1BECL,
69 0x3793A651L, 0x3352BBE6L, 0x3E119D3FL, 0x3AD08088L,
70 0x2497D08DL, 0x2056CD3AL, 0x2D15EBE3L, 0x29D4F654L,
71 0xC5A92679L, 0xC1683BCEL, 0xCC2B1D17L, 0xC8EA00A0L,
72 0xD6AD50A5L, 0xD26C4D12L, 0xDF2F6BCBL, 0xDBEE767CL,
73 0xE3A1CBC1L, 0xE760D676L, 0xEA23F0AFL, 0xEEE2ED18L,
74 0xF0A5BD1DL, 0xF464A0AAL, 0xF9278673L, 0xFDE69BC4L,
75 0x89B8FD09L, 0x8D79E0BEL, 0x803AC667L, 0x84FBDBD0L,
76 0x9ABC8BD5L, 0x9E7D9662L, 0x933EB0BBL, 0x97FFAD0CL,
77 0xAFB010B1L, 0xAB710D06L, 0xA6322BDFL, 0xA2F33668L,
78 0xBCB4666DL, 0xB8757BDAL, 0xB5365D03L, 0xB1F740B4L
79};
80
81Crc32::Crc32()
82{
83 Reset();
84}
85
86void Crc32::Reset()
87{
88 m_crc = 0xFFFFFFFF;
89}
90
91void Crc32::Compute(const char* buffer, size_t count)
92{
93 while (count--)
94 m_crc = (m_crc << 8) ^ crc_tab[((m_crc >> 24) ^ *buffer++) & 0xFF];
95}
96
97uint32_t Crc32::Compute(const std::string& strValue)
98{
99 Crc32 crc;
100 crc.Compute(strValue.c_str(), strValue.size());
101 return crc;
102}
103
104uint32_t Crc32::ComputeFromLowerCase(const std::string& strValue)
105{
106 std::string strLower = strValue;
107 StringUtils::ToLower(strLower);
108 return Compute(strLower.c_str());
109}
110
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12#include <string>
13
14class Crc32
15{
16public:
17 Crc32();
18 void Reset();
19 void Compute(const char* buffer, size_t count);
20 static uint32_t Compute(const std::string& strValue);
21 static uint32_t ComputeFromLowerCase(const std::string& strValue);
22
23 operator uint32_t () const
24 {
25 return m_crc;
26 }
27
28private:
29 uint32_t m_crc;
30};
31
diff --git a/xbmc/utils/CryptThreading.cpp b/xbmc/utils/CryptThreading.cpp
new file mode 100644
index 0000000..3484635
--- /dev/null
+++ b/xbmc/utils/CryptThreading.cpp
@@ -0,0 +1,84 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "CryptThreading.h"
10#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
11
12#include "threads/Thread.h"
13#include "utils/log.h"
14
15#include <atomic>
16
17namespace
18{
19
20CCriticalSection* getlock(int index)
21{
22 return g_cryptThreadingInitializer.GetLock(index);
23}
24
25void lock_callback(int mode, int type, const char* file, int line)
26{
27 if (mode & CRYPTO_LOCK)
28 getlock(type)->lock();
29 else
30 getlock(type)->unlock();
31}
32
33unsigned long GetCryptThreadId()
34{
35 static std::atomic<unsigned long> tidSequence{0};
36 static thread_local unsigned long tidTl{0};
37
38 if (tidTl == 0)
39 tidTl = ++tidSequence;
40 return tidTl;
41}
42
43void thread_id(CRYPTO_THREADID* tid)
44{
45 // C-style cast required due to vastly differing native ID return types
46 CRYPTO_THREADID_set_numeric(tid, GetCryptThreadId());
47}
48
49}
50
51CryptThreadingInitializer::CryptThreadingInitializer()
52{
53 // OpenSSL < 1.1 needs integration code to support multi-threading
54 // This is absolutely required for libcurl if it uses the OpenSSL backend
55 m_locks.resize(CRYPTO_num_locks());
56 CRYPTO_THREADID_set_callback(thread_id);
57 CRYPTO_set_locking_callback(lock_callback);
58}
59
60CryptThreadingInitializer::~CryptThreadingInitializer()
61{
62 CSingleLock l(m_locksLock);
63 CRYPTO_set_locking_callback(nullptr);
64 m_locks.clear();
65}
66
67CCriticalSection* CryptThreadingInitializer::GetLock(int index)
68{
69 CSingleLock l(m_locksLock);
70 auto& curlock = m_locks[index];
71 if (!curlock)
72 {
73 curlock.reset(new CCriticalSection());
74 }
75
76 return curlock.get();
77}
78
79unsigned long CryptThreadingInitializer::GetCurrentCryptThreadId()
80{
81 return GetCryptThreadId();
82}
83
84#endif
diff --git a/xbmc/utils/CryptThreading.h b/xbmc/utils/CryptThreading.h
new file mode 100644
index 0000000..85ec044
--- /dev/null
+++ b/xbmc/utils/CryptThreading.h
@@ -0,0 +1,45 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <openssl/crypto.h>
12
13//! @todo - once we're at OpenSSL 1.1 this class and its .cpp file should be deleted.
14#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
15
16#include <memory>
17#include <vector>
18#include "utils/GlobalsHandling.h"
19#include "threads/CriticalSection.h"
20
21class CryptThreadingInitializer
22{
23 std::vector<std::unique_ptr<CCriticalSection>> m_locks;
24 CCriticalSection m_locksLock;
25
26public:
27 CryptThreadingInitializer();
28 ~CryptThreadingInitializer();
29
30 CCriticalSection* GetLock(int index);
31
32 /**
33 * This is so testing can reach the thread id generation.
34 */
35 unsigned long GetCurrentCryptThreadId();
36
37private:
38 CryptThreadingInitializer(const CryptThreadingInitializer &rhs) = delete;
39 CryptThreadingInitializer& operator=(const CryptThreadingInitializer&) = delete;
40};
41
42XBMC_GLOBAL_REF(CryptThreadingInitializer,g_cryptThreadingInitializer);
43#define g_cryptThreadingInitializer XBMC_GLOBAL_USE(CryptThreadingInitializer)
44
45#endif
diff --git a/xbmc/utils/DMAHeapBufferObject.cpp b/xbmc/utils/DMAHeapBufferObject.cpp
new file mode 100644
index 0000000..c9beeb5
--- /dev/null
+++ b/xbmc/utils/DMAHeapBufferObject.cpp
@@ -0,0 +1,186 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "DMAHeapBufferObject.h"
10
11#include "ServiceBroker.h"
12#include "utils/BufferObjectFactory.h"
13#include "utils/log.h"
14
15#include <array>
16
17#include <drm_fourcc.h>
18#include <linux/dma-heap.h>
19#include <sys/ioctl.h>
20#include <sys/mman.h>
21
22namespace
23{
24
25std::array<const char*, 3> DMA_HEAP_PATHS = {
26 "/dev/dma_heap/reserved",
27 "/dev/dma_heap/linux,cma",
28 "/dev/dma_heap/system",
29};
30
31static const char* DMA_HEAP_PATH;
32
33} // namespace
34
35std::unique_ptr<CBufferObject> CDMAHeapBufferObject::Create()
36{
37 return std::make_unique<CDMAHeapBufferObject>();
38}
39
40void CDMAHeapBufferObject::Register()
41{
42 for (auto path : DMA_HEAP_PATHS)
43 {
44 int fd = open(path, O_RDWR);
45 if (fd < 0)
46 {
47 CLog::Log(LOGDEBUG, "CDMAHeapBufferObject::{} unable to open {}: {}", __FUNCTION__, path,
48 strerror(errno));
49 continue;
50 }
51
52 close(fd);
53 DMA_HEAP_PATH = path;
54 break;
55 }
56
57 if (!DMA_HEAP_PATH)
58 return;
59
60 CLog::Log(LOGDEBUG, "CDMAHeapBufferObject::{} - using {}", __FUNCTION__, DMA_HEAP_PATH);
61
62 CBufferObjectFactory::RegisterBufferObject(CDMAHeapBufferObject::Create);
63}
64
65CDMAHeapBufferObject::~CDMAHeapBufferObject()
66{
67 ReleaseMemory();
68 DestroyBufferObject();
69
70 close(m_dmaheapfd);
71 m_dmaheapfd = -1;
72}
73
74bool CDMAHeapBufferObject::CreateBufferObject(uint32_t format, uint32_t width, uint32_t height)
75{
76 if (m_fd >= 0)
77 return true;
78
79 uint32_t bpp{1};
80
81 switch (format)
82 {
83 case DRM_FORMAT_ARGB8888:
84 bpp = 4;
85 break;
86 case DRM_FORMAT_ARGB1555:
87 case DRM_FORMAT_RGB565:
88 bpp = 2;
89 break;
90 default:
91 throw std::runtime_error("CDMAHeapBufferObject: pixel format not implemented");
92 }
93
94 m_stride = width * bpp;
95
96 return CreateBufferObject(width * height * bpp);
97}
98
99bool CDMAHeapBufferObject::CreateBufferObject(uint64_t size)
100{
101 m_size = size;
102
103 if (m_dmaheapfd < 0)
104 {
105 m_dmaheapfd = open(DMA_HEAP_PATH, O_RDWR);
106 if (m_dmaheapfd < 0)
107 {
108 CLog::LogF(LOGERROR, "failed to open {}:", DMA_HEAP_PATH, strerror(errno));
109 return false;
110 }
111 }
112
113 struct dma_heap_allocation_data allocData = {
114 .len = m_size, .fd_flags = (O_CLOEXEC | O_RDWR), .heap_flags = 0};
115
116 int ret = ioctl(m_dmaheapfd, DMA_HEAP_IOCTL_ALLOC, &allocData);
117 if (ret < 0)
118 {
119 CLog::Log(LOGERROR, "CDMAHeapBufferObject::{} - ioctl DMA_HEAP_IOCTL_ALLOC failed, errno={}",
120 __FUNCTION__, strerror(errno));
121 return false;
122 }
123
124 m_fd = allocData.fd;
125 m_size = allocData.len;
126
127 if (m_fd < 0 || m_size <= 0)
128 {
129 CLog::Log(LOGERROR, "CDMAHeapBufferObject::{} - invalid allocation data: fd={} len={}",
130 __FUNCTION__, m_fd, m_size);
131 return false;
132 }
133
134 return true;
135}
136
137void CDMAHeapBufferObject::DestroyBufferObject()
138{
139 if (m_fd < 0)
140 return;
141
142 int ret = close(m_fd);
143 if (ret < 0)
144 CLog::Log(LOGERROR, "CDMAHeapBufferObject::{} - close failed, errno={}", __FUNCTION__,
145 strerror(errno));
146
147 m_fd = -1;
148 m_stride = 0;
149 m_size = 0;
150}
151
152uint8_t* CDMAHeapBufferObject::GetMemory()
153{
154 if (m_fd < 0)
155 return nullptr;
156
157 if (m_map)
158 {
159 CLog::Log(LOGDEBUG, "CDMAHeapBufferObject::{} - already mapped fd={} map={}", __FUNCTION__,
160 m_fd, fmt::ptr(m_map));
161 return m_map;
162 }
163
164 m_map = static_cast<uint8_t*>(mmap(nullptr, m_size, PROT_WRITE, MAP_SHARED, m_fd, 0));
165 if (m_map == MAP_FAILED)
166 {
167 CLog::Log(LOGERROR, "CDMAHeapBufferObject::{} - mmap failed, errno={}", __FUNCTION__,
168 strerror(errno));
169 return nullptr;
170 }
171
172 return m_map;
173}
174
175void CDMAHeapBufferObject::ReleaseMemory()
176{
177 if (!m_map)
178 return;
179
180 int ret = munmap(m_map, m_size);
181 if (ret < 0)
182 CLog::Log(LOGERROR, "CDMAHeapBufferObject::{} - munmap failed, errno={}", __FUNCTION__,
183 strerror(errno));
184
185 m_map = nullptr;
186}
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 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/BufferObject.h"
12
13#include <memory>
14#include <stdint.h>
15
16class CDMAHeapBufferObject : public CBufferObject
17{
18public:
19 CDMAHeapBufferObject() = default;
20 virtual ~CDMAHeapBufferObject() override;
21
22 // Registration
23 static std::unique_ptr<CBufferObject> Create();
24 static void Register();
25
26 // IBufferObject overrides via CBufferObject
27 bool CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) override;
28 bool CreateBufferObject(uint64_t size) override;
29 void DestroyBufferObject() override;
30 uint8_t* GetMemory() override;
31 void ReleaseMemory() override;
32 std::string GetName() const override { return "CDMAHeapBufferObject"; }
33
34private:
35 int m_dmaheapfd{-1};
36 uint64_t m_size{0};
37 uint8_t* m_map{nullptr};
38};
diff --git a/xbmc/utils/DatabaseUtils.cpp b/xbmc/utils/DatabaseUtils.cpp
new file mode 100644
index 0000000..fdff052
--- /dev/null
+++ b/xbmc/utils/DatabaseUtils.cpp
@@ -0,0 +1,745 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "DatabaseUtils.h"
10
11#include "dbwrappers/dataset.h"
12#include "music/MusicDatabase.h"
13#include "utils/StringUtils.h"
14#include "utils/Variant.h"
15#include "utils/log.h"
16#include "video/VideoDatabase.h"
17
18#include <sstream>
19
20MediaType DatabaseUtils::MediaTypeFromVideoContentType(int videoContentType)
21{
22 VIDEODB_CONTENT_TYPE type = (VIDEODB_CONTENT_TYPE)videoContentType;
23 switch (type)
24 {
25 case VIDEODB_CONTENT_MOVIES:
26 return MediaTypeMovie;
27
28 case VIDEODB_CONTENT_MOVIE_SETS:
29 return MediaTypeVideoCollection;
30
31 case VIDEODB_CONTENT_TVSHOWS:
32 return MediaTypeTvShow;
33
34 case VIDEODB_CONTENT_EPISODES:
35 return MediaTypeEpisode;
36
37 case VIDEODB_CONTENT_MUSICVIDEOS:
38 return MediaTypeMusicVideo;
39
40 default:
41 break;
42 }
43
44 return MediaTypeNone;
45}
46
47std::string DatabaseUtils::GetField(Field field, const MediaType &mediaType, DatabaseQueryPart queryPart)
48{
49 if (field == FieldNone || mediaType == MediaTypeNone)
50 return "";
51
52 if (mediaType == MediaTypeAlbum)
53 {
54 if (field == FieldId) return "albumview.idAlbum";
55 else if (field == FieldAlbum) return "albumview.strAlbum";
56 else if (field == FieldArtist || field == FieldAlbumArtist) return "albumview.strArtists";
57 else if (field == FieldGenre)
58 return "albumview.strGenres";
59 else if (field == FieldYear)
60 return "albumview.strReleaseDate";
61 else if (field == FieldOrigYear || field == FieldOrigDate)
62 return "albumview.strOrigReleaseDate";
63 else if (field == FieldMoods) return "albumview.strMoods";
64 else if (field == FieldStyles) return "albumview.strStyles";
65 else if (field == FieldThemes) return "albumview.strThemes";
66 else if (field == FieldReview) return "albumview.strReview";
67 else if (field == FieldMusicLabel) return "albumview.strLabel";
68 else if (field == FieldAlbumType) return "albumview.strType";
69 else if (field == FieldCompilation) return "albumview.bCompilation";
70 else if (field == FieldRating) return "albumview.fRating";
71 else if (field == FieldVotes) return "albumview.iVotes";
72 else if (field == FieldUserRating) return "albumview.iUserrating";
73 else if (field == FieldDateAdded) return "albumview.dateAdded";
74 else if (field == FieldDateNew) return "albumview.dateNew";
75 else if (field == FieldDateModified) return "albumview.dateModified";
76 else if (field == FieldPlaycount) return "albumview.iTimesPlayed";
77 else if (field == FieldLastPlayed) return "albumview.lastPlayed";
78 else if (field == FieldTotalDiscs)
79 return "albumview.iDiscTotal";
80 else if (field == FieldAlbumStatus)
81 return "albumview.strReleaseStatus";
82 }
83 else if (mediaType == MediaTypeSong)
84 {
85 if (field == FieldId) return "songview.idSong";
86 else if (field == FieldTitle) return "songview.strTitle";
87 else if (field == FieldTrackNumber) return "songview.iTrack";
88 else if (field == FieldTime) return "songview.iDuration";
89 else if (field == FieldYear)
90 return "songview.strReleaseDate";
91 else if (field == FieldOrigYear || field == FieldOrigDate)
92 return "songview.strOrigReleaseDate";
93 else if (field == FieldFilename) return "songview.strFilename";
94 else if (field == FieldPlaycount) return "songview.iTimesPlayed";
95 else if (field == FieldStartOffset) return "songview.iStartOffset";
96 else if (field == FieldEndOffset) return "songview.iEndOffset";
97 else if (field == FieldLastPlayed) return "songview.lastPlayed";
98 else if (field == FieldRating) return "songview.rating";
99 else if (field == FieldVotes) return "songview.votes";
100 else if (field == FieldUserRating) return "songview.userrating";
101 else if (field == FieldComment) return "songview.comment";
102 else if (field == FieldMoods) return "songview.mood";
103 else if (field == FieldAlbum) return "songview.strAlbum";
104 else if (field == FieldPath) return "songview.strPath";
105 else if (field == FieldArtist || field == FieldAlbumArtist) return "songview.strArtists";
106 else if (field == FieldGenre)
107 return "songview.strGenres";
108 else if (field == FieldDateAdded) return "songview.dateAdded";
109 else if (field == FieldDateNew) return "songview.dateNew";
110 else if (field == FieldDateModified) return "songview.dateModified";
111
112 else if (field == FieldDiscTitle)
113 return "songview.strDiscSubtitle";
114 else if (field == FieldBPM)
115 return "songview.iBPM";
116 else if (field == FieldMusicBitRate)
117 return "songview.iBitRate";
118 else if (field == FieldSampleRate)
119 return "songview.iSampleRate";
120 else if (field == FieldNoOfChannels)
121 return "songview.iChannels";
122 }
123 else if (mediaType == MediaTypeArtist)
124 {
125 if (field == FieldId) return "artistview.idArtist";
126 else if (field == FieldArtistSort) return "artistview.strSortName";
127 else if (field == FieldArtist) return "artistview.strArtist";
128 else if (field == FieldArtistType) return "artistview.strType";
129 else if (field == FieldGender) return "artistview.strGender";
130 else if (field == FieldDisambiguation) return "artistview.strDisambiguation";
131 else if (field == FieldGenre) return "artistview.strGenres";
132 else if (field == FieldMoods) return "artistview.strMoods";
133 else if (field == FieldStyles) return "artistview.strStyles";
134 else if (field == FieldInstruments) return "artistview.strInstruments";
135 else if (field == FieldBiography) return "artistview.strBiography";
136 else if (field == FieldBorn) return "artistview.strBorn";
137 else if (field == FieldBandFormed) return "artistview.strFormed";
138 else if (field == FieldDisbanded) return "artistview.strDisbanded";
139 else if (field == FieldDied) return "artistview.strDied";
140 else if (field == FieldDateAdded) return "artistview.dateAdded";
141 else if (field == FieldDateNew) return "artistview.dateNew";
142 else if (field == FieldDateModified) return "artistview.dateModified";
143 }
144 else if (mediaType == MediaTypeMusicVideo)
145 {
146 std::string result;
147 if (field == FieldId) return "musicvideo_view.idMVideo";
148 else if (field == FieldTitle) result = StringUtils::Format("musicvideo_view.c%02d",VIDEODB_ID_MUSICVIDEO_TITLE);
149 else if (field == FieldTime) result = StringUtils::Format("musicvideo_view.c%02d", VIDEODB_ID_MUSICVIDEO_RUNTIME);
150 else if (field == FieldDirector) result = StringUtils::Format("musicvideo_view.c%02d", VIDEODB_ID_MUSICVIDEO_DIRECTOR);
151 else if (field == FieldStudio) result = StringUtils::Format("musicvideo_view.c%02d", VIDEODB_ID_MUSICVIDEO_STUDIOS);
152 else if (field == FieldYear) return "musicvideo_view.premiered";
153 else if (field == FieldPlot) result = StringUtils::Format("musicvideo_view.c%02d", VIDEODB_ID_MUSICVIDEO_PLOT);
154 else if (field == FieldAlbum) result = StringUtils::Format("musicvideo_view.c%02d",VIDEODB_ID_MUSICVIDEO_ALBUM);
155 else if (field == FieldArtist) result = StringUtils::Format("musicvideo_view.c%02d", VIDEODB_ID_MUSICVIDEO_ARTIST);
156 else if (field == FieldGenre) result = StringUtils::Format("musicvideo_view.c%02d", VIDEODB_ID_MUSICVIDEO_GENRE);
157 else if (field == FieldTrackNumber) result = StringUtils::Format("musicvideo_view.c%02d", VIDEODB_ID_MUSICVIDEO_TRACK);
158 else if (field == FieldFilename) return "musicvideo_view.strFilename";
159 else if (field == FieldPath) return "musicvideo_view.strPath";
160 else if (field == FieldPlaycount) return "musicvideo_view.playCount";
161 else if (field == FieldLastPlayed) return "musicvideo_view.lastPlayed";
162 else if (field == FieldDateAdded) return "musicvideo_view.dateAdded";
163 else if (field == FieldUserRating) return "musicvideo_view.userrating";
164
165 if (!result.empty())
166 return result;
167 }
168 else if (mediaType == MediaTypeMovie)
169 {
170 std::string result;
171 if (field == FieldId) return "movie_view.idMovie";
172 else if (field == FieldTitle)
173 {
174 // We need some extra logic to get the title value if sorttitle isn't set
175 if (queryPart == DatabaseQueryPartOrderBy)
176 result = StringUtils::Format("CASE WHEN length(movie_view.c%02d) > 0 THEN movie_view.c%02d ELSE movie_view.c%02d END", VIDEODB_ID_SORTTITLE, VIDEODB_ID_SORTTITLE, VIDEODB_ID_TITLE);
177 else
178 result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_TITLE);
179 }
180 else if (field == FieldPlot) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_PLOT);
181 else if (field == FieldPlotOutline) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_PLOTOUTLINE);
182 else if (field == FieldTagline) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_TAGLINE);
183 else if (field == FieldVotes) return "movie_view.votes";
184 else if (field == FieldRating) return "movie_view.rating";
185 else if (field == FieldWriter) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_CREDITS);
186 else if (field == FieldYear) return "movie_view.premiered";
187 else if (field == FieldSortTitle) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_SORTTITLE);
188 else if (field == FieldOriginalTitle) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_ORIGINALTITLE);
189 else if (field == FieldTime) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_RUNTIME);
190 else if (field == FieldMPAA) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_MPAA);
191 else if (field == FieldTop250) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_TOP250);
192 else if (field == FieldSet) return "movie_view.strSet";
193 else if (field == FieldGenre) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_GENRE);
194 else if (field == FieldDirector) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_DIRECTOR);
195 else if (field == FieldStudio) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_STUDIOS);
196 else if (field == FieldTrailer) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_TRAILER);
197 else if (field == FieldCountry) result = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_COUNTRY);
198 else if (field == FieldFilename) return "movie_view.strFilename";
199 else if (field == FieldPath) return "movie_view.strPath";
200 else if (field == FieldPlaycount) return "movie_view.playCount";
201 else if (field == FieldLastPlayed) return "movie_view.lastPlayed";
202 else if (field == FieldDateAdded) return "movie_view.dateAdded";
203 else if (field == FieldUserRating) return "movie_view.userrating";
204
205 if (!result.empty())
206 return result;
207 }
208 else if (mediaType == MediaTypeTvShow)
209 {
210 std::string result;
211 if (field == FieldId) return "tvshow_view.idShow";
212 else if (field == FieldTitle)
213 {
214 // We need some extra logic to get the title value if sorttitle isn't set
215 if (queryPart == DatabaseQueryPartOrderBy)
216 result = StringUtils::Format("CASE WHEN length(tvshow_view.c%02d) > 0 THEN tvshow_view.c%02d ELSE tvshow_view.c%02d END", VIDEODB_ID_TV_SORTTITLE, VIDEODB_ID_TV_SORTTITLE, VIDEODB_ID_TV_TITLE);
217 else
218 result = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_TITLE);
219 }
220 else if (field == FieldPlot) result = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_PLOT);
221 else if (field == FieldTvShowStatus) result = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_STATUS);
222 else if (field == FieldVotes) return "tvshow_view.votes";
223 else if (field == FieldRating) return "tvshow_view.rating";
224 else if (field == FieldYear) result = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_PREMIERED);
225 else if (field == FieldGenre) result = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_GENRE);
226 else if (field == FieldMPAA) result = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_MPAA);
227 else if (field == FieldStudio) result = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_STUDIOS);
228 else if (field == FieldSortTitle) result = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_SORTTITLE);
229 else if (field == FieldOriginalTitle) result = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_ORIGINALTITLE);
230 else if (field == FieldPath) return "tvshow_view.strPath";
231 else if (field == FieldDateAdded) return "tvshow_view.dateAdded";
232 else if (field == FieldLastPlayed) return "tvshow_view.lastPlayed";
233 else if (field == FieldSeason) return "tvshow_view.totalSeasons";
234 else if (field == FieldNumberOfEpisodes) return "tvshow_view.totalCount";
235 else if (field == FieldNumberOfWatchedEpisodes) return "tvshow_view.watchedcount";
236 else if (field == FieldUserRating) return "tvshow_view.userrating";
237
238 if (!result.empty())
239 return result;
240 }
241 else if (mediaType == MediaTypeEpisode)
242 {
243 std::string result;
244 if (field == FieldId) return "episode_view.idEpisode";
245 else if (field == FieldTitle) result = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_TITLE);
246 else if (field == FieldPlot) result = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_PLOT);
247 else if (field == FieldVotes) return "episode_view.votes";
248 else if (field == FieldRating) return "episode_view.rating";
249 else if (field == FieldWriter) result = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_CREDITS);
250 else if (field == FieldAirDate) result = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_AIRED);
251 else if (field == FieldTime) result = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_RUNTIME);
252 else if (field == FieldDirector) result = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_DIRECTOR);
253 else if (field == FieldSeason) result = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_SEASON);
254 else if (field == FieldEpisodeNumber) result = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_EPISODE);
255 else if (field == FieldUniqueId) result = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_IDENT_ID);
256 else if (field == FieldEpisodeNumberSpecialSort) result = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_SORTEPISODE);
257 else if (field == FieldSeasonSpecialSort) result = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_SORTSEASON);
258 else if (field == FieldFilename) return "episode_view.strFilename";
259 else if (field == FieldPath) return "episode_view.strPath";
260 else if (field == FieldPlaycount) return "episode_view.playCount";
261 else if (field == FieldLastPlayed) return "episode_view.lastPlayed";
262 else if (field == FieldDateAdded) return "episode_view.dateAdded";
263 else if (field == FieldTvShowTitle) return "episode_view.strTitle";
264 else if (field == FieldYear) return "episode_view.premiered";
265 else if (field == FieldMPAA) return "episode_view.mpaa";
266 else if (field == FieldStudio) return "episode_view.strStudio";
267 else if (field == FieldUserRating) return "episode_view.userrating";
268
269 if (!result.empty())
270 return result;
271 }
272
273 if (field == FieldRandom && queryPart == DatabaseQueryPartOrderBy)
274 return "RANDOM()";
275
276 return "";
277}
278
279int DatabaseUtils::GetField(Field field, const MediaType &mediaType)
280{
281 if (field == FieldNone || mediaType == MediaTypeNone)
282 return -1;
283
284 return GetField(field, mediaType, false);
285}
286
287int DatabaseUtils::GetFieldIndex(Field field, const MediaType &mediaType)
288{
289 if (field == FieldNone || mediaType == MediaTypeNone)
290 return -1;
291
292 return GetField(field, mediaType, true);
293}
294
295bool DatabaseUtils::GetSelectFields(const Fields &fields, const MediaType &mediaType, FieldList &selectFields)
296{
297 if (mediaType == MediaTypeNone || fields.empty())
298 return false;
299
300 Fields sortFields = fields;
301
302 // add necessary fields to create the label
303 if (mediaType == MediaTypeSong || mediaType == MediaTypeVideo || mediaType == MediaTypeVideoCollection ||
304 mediaType == MediaTypeMusicVideo || mediaType == MediaTypeMovie || mediaType == MediaTypeTvShow || mediaType == MediaTypeEpisode)
305 sortFields.insert(FieldTitle);
306 if (mediaType == MediaTypeEpisode)
307 {
308 sortFields.insert(FieldSeason);
309 sortFields.insert(FieldEpisodeNumber);
310 }
311 else if (mediaType == MediaTypeAlbum)
312 sortFields.insert(FieldAlbum);
313 else if (mediaType == MediaTypeSong)
314 sortFields.insert(FieldTrackNumber);
315 else if (mediaType == MediaTypeArtist)
316 sortFields.insert(FieldArtist);
317
318 selectFields.clear();
319 for (Fields::const_iterator it = sortFields.begin(); it != sortFields.end(); ++it)
320 {
321 // ignore FieldLabel because it needs special handling (see further up)
322 if (*it == FieldLabel)
323 continue;
324
325 if (GetField(*it, mediaType, DatabaseQueryPartSelect).empty())
326 {
327 CLog::Log(LOGDEBUG, "DatabaseUtils::GetSortFieldList: unknown field %d", *it);
328 continue;
329 }
330 selectFields.push_back(*it);
331 }
332
333 return !selectFields.empty();
334}
335
336bool DatabaseUtils::GetFieldValue(const dbiplus::field_value &fieldValue, CVariant &variantValue)
337{
338 if (fieldValue.get_isNull())
339 {
340 variantValue = CVariant::ConstNullVariant;
341 return true;
342 }
343
344 switch (fieldValue.get_fType())
345 {
346 case dbiplus::ft_String:
347 case dbiplus::ft_WideString:
348 case dbiplus::ft_Object:
349 variantValue = fieldValue.get_asString();
350 return true;
351 case dbiplus::ft_Char:
352 case dbiplus::ft_WChar:
353 variantValue = fieldValue.get_asChar();
354 return true;
355 case dbiplus::ft_Boolean:
356 variantValue = fieldValue.get_asBool();
357 return true;
358 case dbiplus::ft_Short:
359 variantValue = fieldValue.get_asShort();
360 return true;
361 case dbiplus::ft_UShort:
362 variantValue = fieldValue.get_asShort();
363 return true;
364 case dbiplus::ft_Int:
365 variantValue = fieldValue.get_asInt();
366 return true;
367 case dbiplus::ft_UInt:
368 variantValue = fieldValue.get_asUInt();
369 return true;
370 case dbiplus::ft_Float:
371 variantValue = fieldValue.get_asFloat();
372 return true;
373 case dbiplus::ft_Double:
374 case dbiplus::ft_LongDouble:
375 variantValue = fieldValue.get_asDouble();
376 return true;
377 case dbiplus::ft_Int64:
378 variantValue = fieldValue.get_asInt64();
379 return true;
380 }
381
382 return false;
383}
384
385bool DatabaseUtils::GetDatabaseResults(const MediaType &mediaType, const FieldList &fields, const std::unique_ptr<dbiplus::Dataset> &dataset, DatabaseResults &results)
386{
387 if (dataset->num_rows() == 0)
388 return true;
389
390 const dbiplus::result_set &resultSet = dataset->get_result_set();
391 unsigned int offset = results.size();
392
393 if (fields.empty())
394 {
395 DatabaseResult result;
396 for (unsigned int index = 0; index < resultSet.records.size(); index++)
397 {
398 result[FieldRow] = index + offset;
399 results.push_back(result);
400 }
401
402 return true;
403 }
404
405 if (resultSet.record_header.size() < fields.size())
406 return false;
407
408 std::vector<int> fieldIndexLookup;
409 fieldIndexLookup.reserve(fields.size());
410 for (FieldList::const_iterator it = fields.begin(); it != fields.end(); ++it)
411 fieldIndexLookup.push_back(GetFieldIndex(*it, mediaType));
412
413 results.reserve(resultSet.records.size() + offset);
414 for (unsigned int index = 0; index < resultSet.records.size(); index++)
415 {
416 DatabaseResult result;
417 result[FieldRow] = index + offset;
418
419 unsigned int lookupIndex = 0;
420 for (FieldList::const_iterator it = fields.begin(); it != fields.end(); ++it)
421 {
422 int fieldIndex = fieldIndexLookup[lookupIndex++];
423 if (fieldIndex < 0)
424 return false;
425
426 std::pair<Field, CVariant> value;
427 value.first = *it;
428 if (!GetFieldValue(resultSet.records[index]->at(fieldIndex), value.second))
429 CLog::Log(LOGWARNING, "GetDatabaseResults: unable to retrieve value of field %s", resultSet.record_header[fieldIndex].name.c_str());
430
431 if (value.first == FieldYear &&
432 (mediaType == MediaTypeTvShow || mediaType == MediaTypeEpisode))
433 {
434 CDateTime dateTime;
435 dateTime.SetFromDBDate(value.second.asString());
436 if (dateTime.IsValid())
437 {
438 value.second.clear();
439 value.second = dateTime.GetYear();
440 }
441 }
442
443 result.insert(value);
444 }
445
446 result[FieldMediaType] = mediaType;
447 if (mediaType == MediaTypeMovie || mediaType == MediaTypeVideoCollection ||
448 mediaType == MediaTypeTvShow || mediaType == MediaTypeMusicVideo)
449 result[FieldLabel] = result.at(FieldTitle).asString();
450 else if (mediaType == MediaTypeEpisode)
451 {
452 std::ostringstream label;
453 label << (int)(result.at(FieldSeason).asInteger() * 100 + result.at(FieldEpisodeNumber).asInteger());
454 label << ". ";
455 label << result.at(FieldTitle).asString();
456 result[FieldLabel] = label.str();
457 }
458 else if (mediaType == MediaTypeAlbum)
459 result[FieldLabel] = result.at(FieldAlbum).asString();
460 else if (mediaType == MediaTypeSong)
461 {
462 std::ostringstream label;
463 label << (int)result.at(FieldTrackNumber).asInteger();
464 label << ". ";
465 label << result.at(FieldTitle).asString();
466 result[FieldLabel] = label.str();
467 }
468 else if (mediaType == MediaTypeArtist)
469 result[FieldLabel] = result.at(FieldArtist).asString();
470
471 results.push_back(result);
472 }
473
474 return true;
475}
476
477std::string DatabaseUtils::BuildLimitClause(int end, int start /* = 0 */)
478{
479 return " LIMIT " + BuildLimitClauseOnly(end, start);
480}
481
482std::string DatabaseUtils::BuildLimitClauseOnly(int end, int start /* = 0 */)
483{
484 std::ostringstream sql;
485 if (start > 0)
486 {
487 if (end > 0)
488 {
489 end = end - start;
490 if (end < 0)
491 end = 0;
492 }
493
494 sql << start << "," << end;
495 }
496 else
497 sql << end;
498
499 return sql.str();
500}
501
502size_t DatabaseUtils::GetLimitCount(int end, int start)
503{
504 if (start > 0)
505 {
506 if (end - start < 0)
507 return 0;
508 else
509 return static_cast<size_t>(end - start);
510 }
511 else if (end > 0)
512 return static_cast<size_t>(end);
513 return 0;
514}
515
516int DatabaseUtils::GetField(Field field, const MediaType &mediaType, bool asIndex)
517{
518 if (field == FieldNone || mediaType == MediaTypeNone)
519 return -1;
520
521 int index = -1;
522
523 if (mediaType == MediaTypeAlbum)
524 {
525 if (field == FieldId) return CMusicDatabase::album_idAlbum;
526 else if (field == FieldAlbum) return CMusicDatabase::album_strAlbum;
527 else if (field == FieldArtist || field == FieldAlbumArtist) return CMusicDatabase::album_strArtists;
528 else if (field == FieldGenre) return CMusicDatabase::album_strGenres;
529 else if (field == FieldYear) return CMusicDatabase::album_strReleaseDate;
530 else if (field == FieldMoods) return CMusicDatabase::album_strMoods;
531 else if (field == FieldStyles) return CMusicDatabase::album_strStyles;
532 else if (field == FieldThemes) return CMusicDatabase::album_strThemes;
533 else if (field == FieldReview) return CMusicDatabase::album_strReview;
534 else if (field == FieldMusicLabel) return CMusicDatabase::album_strLabel;
535 else if (field == FieldAlbumType) return CMusicDatabase::album_strType;
536 else if (field == FieldRating) return CMusicDatabase::album_fRating;
537 else if (field == FieldVotes) return CMusicDatabase::album_iVotes;
538 else if (field == FieldUserRating) return CMusicDatabase::album_iUserrating;
539 else if (field == FieldPlaycount) return CMusicDatabase::album_iTimesPlayed;
540 else if (field == FieldLastPlayed) return CMusicDatabase::album_dtLastPlayed;
541 else if (field == FieldDateAdded) return CMusicDatabase::album_dateAdded;
542 else if (field == FieldDateNew) return CMusicDatabase::album_dateNew;
543 else if (field == FieldDateModified) return CMusicDatabase::album_dateModified;
544 else if (field == FieldTotalDiscs)
545 return CMusicDatabase::album_iTotalDiscs;
546 else if (field == FieldOrigYear || field == FieldOrigDate)
547 return CMusicDatabase::album_strOrigReleaseDate;
548 else if (field == FieldAlbumStatus)
549 return CMusicDatabase::album_strReleaseStatus;
550 }
551 else if (mediaType == MediaTypeSong)
552 {
553 if (field == FieldId) return CMusicDatabase::song_idSong;
554 else if (field == FieldTitle) return CMusicDatabase::song_strTitle;
555 else if (field == FieldTrackNumber) return CMusicDatabase::song_iTrack;
556 else if (field == FieldTime) return CMusicDatabase::song_iDuration;
557 else if (field == FieldYear) return CMusicDatabase::song_strReleaseDate;
558 else if (field == FieldFilename) return CMusicDatabase::song_strFileName;
559 else if (field == FieldPlaycount) return CMusicDatabase::song_iTimesPlayed;
560 else if (field == FieldStartOffset) return CMusicDatabase::song_iStartOffset;
561 else if (field == FieldEndOffset) return CMusicDatabase::song_iEndOffset;
562 else if (field == FieldLastPlayed) return CMusicDatabase::song_lastplayed;
563 else if (field == FieldRating) return CMusicDatabase::song_rating;
564 else if (field == FieldUserRating) return CMusicDatabase::song_userrating;
565 else if (field == FieldVotes) return CMusicDatabase::song_votes;
566 else if (field == FieldComment) return CMusicDatabase::song_comment;
567 else if (field == FieldMoods) return CMusicDatabase::song_mood;
568 else if (field == FieldAlbum) return CMusicDatabase::song_strAlbum;
569 else if (field == FieldPath) return CMusicDatabase::song_strPath;
570 else if (field == FieldGenre) return CMusicDatabase::song_strGenres;
571 else if (field == FieldArtist || field == FieldAlbumArtist) return CMusicDatabase::song_strArtists;
572 else if (field == FieldDateAdded) return CMusicDatabase::song_dateAdded;
573 else if (field == FieldDateNew) return CMusicDatabase::song_dateNew;
574 else if (field == FieldDateModified) return CMusicDatabase::song_dateModified;
575 else if (field == FieldBPM)
576 return CMusicDatabase::song_iBPM;
577 else if (field == FieldMusicBitRate)
578 return CMusicDatabase::song_iBitRate;
579 else if (field == FieldSampleRate)
580 return CMusicDatabase::song_iSampleRate;
581 else if (field == FieldNoOfChannels)
582 return CMusicDatabase::song_iChannels;
583 }
584 else if (mediaType == MediaTypeArtist)
585 {
586 if (field == FieldId) return CMusicDatabase::artist_idArtist;
587 else if (field == FieldArtist) return CMusicDatabase::artist_strArtist;
588 else if (field == FieldArtistSort) return CMusicDatabase::artist_strSortName;
589 else if (field == FieldArtistType) return CMusicDatabase::artist_strType;
590 else if (field == FieldGender) return CMusicDatabase::artist_strGender;
591 else if (field == FieldDisambiguation) return CMusicDatabase::artist_strDisambiguation;
592 else if (field == FieldGenre) return CMusicDatabase::artist_strGenres;
593 else if (field == FieldMoods) return CMusicDatabase::artist_strMoods;
594 else if (field == FieldStyles) return CMusicDatabase::artist_strStyles;
595 else if (field == FieldInstruments) return CMusicDatabase::artist_strInstruments;
596 else if (field == FieldBiography) return CMusicDatabase::artist_strBiography;
597 else if (field == FieldBorn) return CMusicDatabase::artist_strBorn;
598 else if (field == FieldBandFormed) return CMusicDatabase::artist_strFormed;
599 else if (field == FieldDisbanded) return CMusicDatabase::artist_strDisbanded;
600 else if (field == FieldDied) return CMusicDatabase::artist_strDied;
601 else if (field == FieldDateAdded) return CMusicDatabase::artist_dateAdded;
602 else if (field == FieldDateNew) return CMusicDatabase::artist_dateNew;
603 else if (field == FieldDateModified) return CMusicDatabase::artist_dateModified;
604 }
605 else if (mediaType == MediaTypeMusicVideo)
606 {
607 if (field == FieldId) return 0;
608 else if (field == FieldTitle) index = VIDEODB_ID_MUSICVIDEO_TITLE;
609 else if (field == FieldTime) index = VIDEODB_ID_MUSICVIDEO_RUNTIME;
610 else if (field == FieldDirector) index = VIDEODB_ID_MUSICVIDEO_DIRECTOR;
611 else if (field == FieldStudio) index = VIDEODB_ID_MUSICVIDEO_STUDIOS;
612 else if (field == FieldYear) return VIDEODB_DETAILS_MUSICVIDEO_PREMIERED;
613 else if (field == FieldPlot) index = VIDEODB_ID_MUSICVIDEO_PLOT;
614 else if (field == FieldAlbum) index = VIDEODB_ID_MUSICVIDEO_ALBUM;
615 else if (field == FieldArtist) index = VIDEODB_ID_MUSICVIDEO_ARTIST;
616 else if (field == FieldGenre) index = VIDEODB_ID_MUSICVIDEO_GENRE;
617 else if (field == FieldTrackNumber) index = VIDEODB_ID_MUSICVIDEO_TRACK;
618 else if (field == FieldFilename) return VIDEODB_DETAILS_MUSICVIDEO_FILE;
619 else if (field == FieldPath) return VIDEODB_DETAILS_MUSICVIDEO_PATH;
620 else if (field == FieldPlaycount) return VIDEODB_DETAILS_MUSICVIDEO_PLAYCOUNT;
621 else if (field == FieldLastPlayed) return VIDEODB_DETAILS_MUSICVIDEO_LASTPLAYED;
622 else if (field == FieldDateAdded) return VIDEODB_DETAILS_MUSICVIDEO_DATEADDED;
623 else if (field == FieldUserRating) return VIDEODB_DETAILS_MUSICVIDEO_USER_RATING;
624
625 if (index < 0)
626 return index;
627
628 if (asIndex)
629 {
630 // see VideoDatabase.h
631 // the first field is the item's ID and the second is the item's file ID
632 index += 2;
633 }
634 }
635 else if (mediaType == MediaTypeMovie)
636 {
637 if (field == FieldId) return 0;
638 else if (field == FieldTitle) index = VIDEODB_ID_TITLE;
639 else if (field == FieldSortTitle) index = VIDEODB_ID_SORTTITLE;
640 else if (field == FieldOriginalTitle) index = VIDEODB_ID_ORIGINALTITLE;
641 else if (field == FieldPlot) index = VIDEODB_ID_PLOT;
642 else if (field == FieldPlotOutline) index = VIDEODB_ID_PLOTOUTLINE;
643 else if (field == FieldTagline) index = VIDEODB_ID_TAGLINE;
644 else if (field == FieldVotes) return VIDEODB_DETAILS_MOVIE_VOTES;
645 else if (field == FieldRating) return VIDEODB_DETAILS_MOVIE_RATING;
646 else if (field == FieldWriter) index = VIDEODB_ID_CREDITS;
647 else if (field == FieldYear) return VIDEODB_DETAILS_MOVIE_PREMIERED;
648 else if (field == FieldTime) index = VIDEODB_ID_RUNTIME;
649 else if (field == FieldMPAA) index = VIDEODB_ID_MPAA;
650 else if (field == FieldTop250) index = VIDEODB_ID_TOP250;
651 else if (field == FieldSet) return VIDEODB_DETAILS_MOVIE_SET_NAME;
652 else if (field == FieldGenre) index = VIDEODB_ID_GENRE;
653 else if (field == FieldDirector) index = VIDEODB_ID_DIRECTOR;
654 else if (field == FieldStudio) index = VIDEODB_ID_STUDIOS;
655 else if (field == FieldTrailer) index = VIDEODB_ID_TRAILER;
656 else if (field == FieldCountry) index = VIDEODB_ID_COUNTRY;
657 else if (field == FieldFilename) index = VIDEODB_DETAILS_MOVIE_FILE;
658 else if (field == FieldPath) return VIDEODB_DETAILS_MOVIE_PATH;
659 else if (field == FieldPlaycount) return VIDEODB_DETAILS_MOVIE_PLAYCOUNT;
660 else if (field == FieldLastPlayed) return VIDEODB_DETAILS_MOVIE_LASTPLAYED;
661 else if (field == FieldDateAdded) return VIDEODB_DETAILS_MOVIE_DATEADDED;
662 else if (field == FieldUserRating) return VIDEODB_DETAILS_MOVIE_USER_RATING;
663
664 if (index < 0)
665 return index;
666
667 if (asIndex)
668 {
669 // see VideoDatabase.h
670 // the first field is the item's ID and the second is the item's file ID
671 index += 2;
672 }
673 }
674 else if (mediaType == MediaTypeTvShow)
675 {
676 if (field == FieldId) return 0;
677 else if (field == FieldTitle) index = VIDEODB_ID_TV_TITLE;
678 else if (field == FieldSortTitle) index = VIDEODB_ID_TV_SORTTITLE;
679 else if (field == FieldOriginalTitle) index = VIDEODB_ID_TV_ORIGINALTITLE;
680 else if (field == FieldPlot) index = VIDEODB_ID_TV_PLOT;
681 else if (field == FieldTvShowStatus) index = VIDEODB_ID_TV_STATUS;
682 else if (field == FieldVotes) return VIDEODB_DETAILS_TVSHOW_VOTES;
683 else if (field == FieldRating) return VIDEODB_DETAILS_TVSHOW_RATING;
684 else if (field == FieldYear) index = VIDEODB_ID_TV_PREMIERED;
685 else if (field == FieldGenre) index = VIDEODB_ID_TV_GENRE;
686 else if (field == FieldMPAA) index = VIDEODB_ID_TV_MPAA;
687 else if (field == FieldStudio) index = VIDEODB_ID_TV_STUDIOS;
688 else if (field == FieldPath) return VIDEODB_DETAILS_TVSHOW_PATH;
689 else if (field == FieldDateAdded) return VIDEODB_DETAILS_TVSHOW_DATEADDED;
690 else if (field == FieldLastPlayed) return VIDEODB_DETAILS_TVSHOW_LASTPLAYED;
691 else if (field == FieldNumberOfEpisodes) return VIDEODB_DETAILS_TVSHOW_NUM_EPISODES;
692 else if (field == FieldNumberOfWatchedEpisodes) return VIDEODB_DETAILS_TVSHOW_NUM_WATCHED;
693 else if (field == FieldSeason) return VIDEODB_DETAILS_TVSHOW_NUM_SEASONS;
694 else if (field == FieldUserRating) return VIDEODB_DETAILS_TVSHOW_USER_RATING;
695
696 if (index < 0)
697 return index;
698
699 if (asIndex)
700 {
701 // see VideoDatabase.h
702 // the first field is the item's ID
703 index += 1;
704 }
705 }
706 else if (mediaType == MediaTypeEpisode)
707 {
708 if (field == FieldId) return 0;
709 else if (field == FieldTitle) index = VIDEODB_ID_EPISODE_TITLE;
710 else if (field == FieldPlot) index = VIDEODB_ID_EPISODE_PLOT;
711 else if (field == FieldVotes) return VIDEODB_DETAILS_EPISODE_VOTES;
712 else if (field == FieldRating) return VIDEODB_DETAILS_EPISODE_RATING;
713 else if (field == FieldWriter) index = VIDEODB_ID_EPISODE_CREDITS;
714 else if (field == FieldAirDate) index = VIDEODB_ID_EPISODE_AIRED;
715 else if (field == FieldTime) index = VIDEODB_ID_EPISODE_RUNTIME;
716 else if (field == FieldDirector) index = VIDEODB_ID_EPISODE_DIRECTOR;
717 else if (field == FieldSeason) index = VIDEODB_ID_EPISODE_SEASON;
718 else if (field == FieldEpisodeNumber) index = VIDEODB_ID_EPISODE_EPISODE;
719 else if (field == FieldUniqueId) index = VIDEODB_ID_EPISODE_IDENT_ID;
720 else if (field == FieldEpisodeNumberSpecialSort) index = VIDEODB_ID_EPISODE_SORTEPISODE;
721 else if (field == FieldSeasonSpecialSort) index = VIDEODB_ID_EPISODE_SORTSEASON;
722 else if (field == FieldFilename) return VIDEODB_DETAILS_EPISODE_FILE;
723 else if (field == FieldPath) return VIDEODB_DETAILS_EPISODE_PATH;
724 else if (field == FieldPlaycount) return VIDEODB_DETAILS_EPISODE_PLAYCOUNT;
725 else if (field == FieldLastPlayed) return VIDEODB_DETAILS_EPISODE_LASTPLAYED;
726 else if (field == FieldDateAdded) return VIDEODB_DETAILS_EPISODE_DATEADDED;
727 else if (field == FieldTvShowTitle) return VIDEODB_DETAILS_EPISODE_TVSHOW_NAME;
728 else if (field == FieldStudio) return VIDEODB_DETAILS_EPISODE_TVSHOW_STUDIO;
729 else if (field == FieldYear) return VIDEODB_DETAILS_EPISODE_TVSHOW_AIRED;
730 else if (field == FieldMPAA) return VIDEODB_DETAILS_EPISODE_TVSHOW_MPAA;
731 else if (field == FieldUserRating) return VIDEODB_DETAILS_EPISODE_USER_RATING;
732
733 if (index < 0)
734 return index;
735
736 if (asIndex)
737 {
738 // see VideoDatabase.h
739 // the first field is the item's ID and the second is the item's file ID
740 index += 2;
741 }
742 }
743
744 return index;
745}
diff --git a/xbmc/utils/DatabaseUtils.h b/xbmc/utils/DatabaseUtils.h
new file mode 100644
index 0000000..98761db
--- /dev/null
+++ b/xbmc/utils/DatabaseUtils.h
@@ -0,0 +1,181 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "media/MediaType.h"
12
13#include <map>
14#include <memory>
15#include <set>
16#include <string>
17#include <vector>
18
19class CVariant;
20
21namespace dbiplus
22{
23 class Dataset;
24 class field_value;
25}
26
27typedef enum {
28 // special fields used during sorting
29 FieldUnknown = -1,
30 FieldNone = 0,
31 FieldSort, // used to store the string to use for sorting
32 FieldSortSpecial, // whether the item needs special handling (0 = no, 1 = sort on top, 2 = sort on bottom)
33 FieldLabel,
34 FieldFolder,
35 FieldMediaType,
36 FieldRow, // the row number in a dataset
37
38 // special fields not retrieved from the database
39 FieldSize,
40 FieldDate,
41 FieldDriveType,
42 FieldStartOffset,
43 FieldEndOffset,
44 FieldProgramCount,
45 FieldBitrate,
46 FieldListeners,
47 FieldPlaylist,
48 FieldVirtualFolder,
49 FieldRandom,
50 FieldDateTaken,
51 FieldAudioCount,
52 FieldSubtitleCount,
53
54 FieldInstallDate,
55 FieldLastUpdated,
56 FieldLastUsed,
57
58 // fields retrievable from the database
59 FieldId,
60 FieldGenre,
61 FieldAlbum,
62 FieldDiscTitle,
63 FieldIsBoxset,
64 FieldTotalDiscs,
65 FieldOrigYear,
66 FieldOrigDate,
67 FieldArtist,
68 FieldArtistSort,
69 FieldAlbumArtist,
70 FieldTitle,
71 FieldSortTitle,
72 FieldOriginalTitle,
73 FieldYear,
74 FieldTime,
75 FieldTrackNumber,
76 FieldFilename,
77 FieldPath,
78 FieldPlaycount,
79 FieldLastPlayed,
80 FieldInProgress,
81 FieldRating,
82 FieldComment,
83 FieldRole,
84 FieldDateAdded,
85 FieldDateModified,
86 FieldDateNew,
87 FieldTvShowTitle,
88 FieldPlot,
89 FieldPlotOutline,
90 FieldTagline,
91 FieldTvShowStatus,
92 FieldVotes,
93 FieldDirector,
94 FieldActor,
95 FieldStudio,
96 FieldCountry,
97 FieldMPAA,
98 FieldTop250,
99 FieldSet,
100 FieldNumberOfEpisodes,
101 FieldNumberOfWatchedEpisodes,
102 FieldWriter,
103 FieldAirDate,
104 FieldEpisodeNumber,
105 FieldUniqueId,
106 FieldSeason,
107 FieldEpisodeNumberSpecialSort,
108 FieldSeasonSpecialSort,
109 FieldReview,
110 FieldThemes,
111 FieldMoods,
112 FieldStyles,
113 FieldAlbumType,
114 FieldMusicLabel,
115 FieldCompilation,
116 FieldSource,
117 FieldTrailer,
118 FieldVideoResolution,
119 FieldVideoAspectRatio,
120 FieldVideoCodec,
121 FieldAudioChannels,
122 FieldAudioCodec,
123 FieldAudioLanguage,
124 FieldSubtitleLanguage,
125 FieldProductionCode,
126 FieldTag,
127 FieldChannelName,
128 FieldChannelNumber,
129 FieldInstruments,
130 FieldBiography,
131 FieldArtistType,
132 FieldGender,
133 FieldDisambiguation,
134 FieldBorn,
135 FieldBandFormed,
136 FieldDisbanded,
137 FieldDied,
138 FieldStereoMode,
139 FieldUserRating,
140 FieldRelevance, // Used for actors' appearances
141 FieldClientChannelOrder,
142 FieldBPM,
143 FieldMusicBitRate,
144 FieldSampleRate,
145 FieldNoOfChannels,
146 FieldAlbumStatus,
147 FieldMax
148} Field;
149
150typedef std::set<Field> Fields;
151typedef std::vector<Field> FieldList;
152
153typedef enum {
154 DatabaseQueryPartSelect,
155 DatabaseQueryPartWhere,
156 DatabaseQueryPartOrderBy,
157} DatabaseQueryPart;
158
159typedef std::map<Field, CVariant> DatabaseResult;
160typedef std::vector<DatabaseResult> DatabaseResults;
161
162class DatabaseUtils
163{
164public:
165 static MediaType MediaTypeFromVideoContentType(int videoContentType);
166
167 static std::string GetField(Field field, const MediaType &mediaType, DatabaseQueryPart queryPart);
168 static int GetField(Field field, const MediaType &mediaType);
169 static int GetFieldIndex(Field field, const MediaType &mediaType);
170 static bool GetSelectFields(const Fields &fields, const MediaType &mediaType, FieldList &selectFields);
171
172 static bool GetFieldValue(const dbiplus::field_value &fieldValue, CVariant &variantValue);
173 static bool GetDatabaseResults(const MediaType &mediaType, const FieldList &fields, const std::unique_ptr<dbiplus::Dataset> &dataset, DatabaseResults &results);
174
175 static std::string BuildLimitClause(int end, int start = 0);
176 static std::string BuildLimitClauseOnly(int end, int start = 0);
177 static size_t GetLimitCount(int end, int start);
178
179private:
180 static int GetField(Field field, const MediaType &mediaType, bool asIndex);
181};
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 @@
1/*
2 * Copyright (C) 2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Digest.h"
10
11#include "StringUtils.h"
12
13#include <array>
14#include <stdexcept>
15
16#include <openssl/evp.h>
17
18namespace KODI
19{
20namespace UTILITY
21{
22
23namespace
24{
25
26EVP_MD const * TypeToEVPMD(CDigest::Type type)
27{
28 switch (type)
29 {
30 case CDigest::Type::MD5:
31 return EVP_md5();
32 case CDigest::Type::SHA1:
33 return EVP_sha1();
34 case CDigest::Type::SHA256:
35 return EVP_sha256();
36 case CDigest::Type::SHA512:
37 return EVP_sha512();
38 default:
39 throw std::invalid_argument("Unknown digest type");
40 }
41}
42
43}
44
45std::ostream& operator<<(std::ostream& os, TypedDigest const& digest)
46{
47 return os << "{" << CDigest::TypeToString(digest.type) << "}" << digest.value;
48}
49
50std::string CDigest::TypeToString(Type type)
51{
52 switch (type)
53 {
54 case Type::MD5:
55 return "md5";
56 case Type::SHA1:
57 return "sha1";
58 case Type::SHA256:
59 return "sha256";
60 case Type::SHA512:
61 return "sha512";
62 case Type::INVALID:
63 return "invalid";
64 default:
65 throw std::invalid_argument("Unknown digest type");
66 }
67}
68
69CDigest::Type CDigest::TypeFromString(std::string const& type)
70{
71 std::string typeLower{type};
72 StringUtils::ToLower(typeLower);
73 if (type == "md5")
74 {
75 return Type::MD5;
76 }
77 else if (type == "sha1")
78 {
79 return Type::SHA1;
80 }
81 else if (type == "sha256")
82 {
83 return Type::SHA256;
84 }
85 else if (type == "sha512")
86 {
87 return Type::SHA512;
88 }
89 else
90 {
91 throw std::invalid_argument(std::string("Unknown digest type \"") + type + "\"");
92 }
93}
94
95void CDigest::MdCtxDeleter::operator()(EVP_MD_CTX* context)
96{
97 EVP_MD_CTX_destroy(context);
98}
99
100CDigest::CDigest(Type type)
101: m_context{EVP_MD_CTX_create()}, m_md(TypeToEVPMD(type))
102{
103 if (1 != EVP_DigestInit_ex(m_context.get(), m_md, nullptr))
104 {
105 throw std::runtime_error("EVP_DigestInit_ex failed");
106 }
107}
108
109void CDigest::Update(std::string const& data)
110{
111 Update(data.c_str(), data.size());
112}
113
114void CDigest::Update(void const* data, std::size_t size)
115{
116 if (m_finalized)
117 {
118 throw std::logic_error("Finalized digest cannot be updated any more");
119 }
120
121 if (1 != EVP_DigestUpdate(m_context.get(), data, size))
122 {
123 throw std::runtime_error("EVP_DigestUpdate failed");
124 }
125}
126
127std::string CDigest::FinalizeRaw()
128{
129 if (m_finalized)
130 {
131 throw std::logic_error("Digest can only be finalized once");
132 }
133
134 m_finalized = true;
135
136 std::array<unsigned char, 64> digest;
137 std::size_t size = EVP_MD_size(m_md);
138 if (size > digest.size())
139 {
140 throw std::runtime_error("Digest unexpectedly long");
141 }
142 if (1 != EVP_DigestFinal_ex(m_context.get(), digest.data(), nullptr))
143 {
144 throw std::runtime_error("EVP_DigestFinal_ex failed");
145 }
146 return {reinterpret_cast<char*> (digest.data()), size};
147}
148
149std::string CDigest::Finalize()
150{
151 return StringUtils::ToHexadecimal(FinalizeRaw());
152}
153
154std::string CDigest::Calculate(Type type, std::string const& data)
155{
156 CDigest digest{type};
157 digest.Update(data);
158 return digest.Finalize();
159}
160
161std::string CDigest::Calculate(Type type, void const* data, std::size_t size)
162{
163 CDigest digest{type};
164 digest.Update(data, size);
165 return digest.Finalize();
166}
167
168}
169}
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 @@
1/*
2 * Copyright (C) 2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "StringUtils.h"
12
13#include <iostream>
14#include <memory>
15#include <stdexcept>
16#include <string>
17
18#include <openssl/evp.h>
19
20namespace KODI
21{
22namespace UTILITY
23{
24
25/**
26 * Utility class for calculating message digests/hashes, currently using OpenSSL
27 */
28class CDigest
29{
30public:
31 enum class Type
32 {
33 MD5,
34 SHA1,
35 SHA256,
36 SHA512,
37 INVALID
38 };
39
40 /**
41 * Convert type enumeration value to lower-case string representation
42 */
43 static std::string TypeToString(Type type);
44 /**
45 * Convert digest type string representation to enumeration value
46 */
47 static Type TypeFromString(std::string const& type);
48
49 /**
50 * Create a digest calculation object
51 */
52 CDigest(Type type);
53 /**
54 * Update digest with data
55 *
56 * Cannot be called after \ref Finalize has been called
57 */
58 void Update(std::string const& data);
59 /**
60 * Update digest with data
61 *
62 * Cannot be called after \ref Finalize has been called
63 */
64 void Update(void const* data, std::size_t size);
65 /**
66 * Finalize and return the digest
67 *
68 * The digest object cannot be used any more after this function
69 * has been called.
70 *
71 * \return digest value as string in lower-case hexadecimal notation
72 */
73 std::string Finalize();
74 /**
75 * Finalize and return the digest
76 *
77 * The digest object cannot be used any more after this
78 * function has been called.
79 *
80 * \return digest value as binary std::string
81 */
82 std::string FinalizeRaw();
83
84 /**
85 * Calculate message digest
86 */
87 static std::string Calculate(Type type, std::string const& data);
88 /**
89 * Calculate message digest
90 */
91 static std::string Calculate(Type type, void const* data, std::size_t size);
92
93private:
94 struct MdCtxDeleter
95 {
96 void operator()(EVP_MD_CTX* context);
97 };
98
99 bool m_finalized{false};
100 std::unique_ptr<EVP_MD_CTX, MdCtxDeleter> m_context;
101 EVP_MD const* m_md;
102};
103
104struct TypedDigest
105{
106 CDigest::Type type{CDigest::Type::INVALID};
107 std::string value;
108
109 TypedDigest() = default;
110
111 TypedDigest(CDigest::Type type, std::string const& value)
112 : type(type), value(value)
113 {}
114
115 bool Empty() const
116 {
117 return (type == CDigest::Type::INVALID || value.empty());
118 }
119};
120
121inline bool operator==(TypedDigest const& left, TypedDigest const& right)
122{
123 if (left.type != right.type)
124 {
125 throw std::logic_error("Cannot compare digests of different type");
126 }
127 return StringUtils::EqualsNoCase(left.value, right.value);
128}
129
130inline bool operator!=(TypedDigest const& left, TypedDigest const& right)
131{
132 return !(left == right);
133}
134
135std::ostream& operator<<(std::ostream& os, TypedDigest const& digest);
136
137}
138}
diff --git a/xbmc/utils/DumbBufferObject.cpp b/xbmc/utils/DumbBufferObject.cpp
new file mode 100644
index 0000000..84d28ea
--- /dev/null
+++ b/xbmc/utils/DumbBufferObject.cpp
@@ -0,0 +1,160 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "DumbBufferObject.h"
10
11#include "ServiceBroker.h"
12#include "utils/BufferObjectFactory.h"
13#include "utils/log.h"
14#include "windowing/gbm/WinSystemGbm.h"
15#include "windowing/gbm/WinSystemGbmEGLContext.h"
16
17#include <drm_fourcc.h>
18#include <sys/mman.h>
19
20using namespace KODI::WINDOWING::GBM;
21
22std::unique_ptr<CBufferObject> CDumbBufferObject::Create()
23{
24 return std::make_unique<CDumbBufferObject>();
25}
26
27void CDumbBufferObject::Register()
28{
29 CBufferObjectFactory::RegisterBufferObject(CDumbBufferObject::Create);
30}
31
32CDumbBufferObject::CDumbBufferObject()
33{
34 auto winSystem = static_cast<CWinSystemGbmEGLContext*>(CServiceBroker::GetWinSystem());
35
36 m_device = winSystem->GetDrm()->GetFileDescriptor();
37}
38
39CDumbBufferObject::~CDumbBufferObject()
40{
41 ReleaseMemory();
42 DestroyBufferObject();
43}
44
45bool CDumbBufferObject::CreateBufferObject(uint32_t format, uint32_t width, uint32_t height)
46{
47 if (m_fd >= 0)
48 return true;
49
50 uint32_t bpp;
51
52 switch (format)
53 {
54 case DRM_FORMAT_ARGB1555:
55 case DRM_FORMAT_RGB565:
56 bpp = 16;
57 break;
58 case DRM_FORMAT_ARGB8888:
59 bpp = 32;
60 break;
61 default:
62 throw std::runtime_error("CDumbBufferObject: pixel format not implemented");
63 }
64
65 struct drm_mode_create_dumb create_dumb = {.height = height, .width = width, .bpp = bpp};
66
67 int ret = drmIoctl(m_device, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
68 if (ret < 0)
69 {
70 CLog::Log(LOGERROR, "CDumbBufferObject::{} - ioctl DRM_IOCTL_MODE_CREATE_DUMB failed, errno={}",
71 __FUNCTION__, strerror(errno));
72 return false;
73 }
74
75 m_size = create_dumb.size;
76 m_stride = create_dumb.pitch;
77
78 ret = drmPrimeHandleToFD(m_device, create_dumb.handle, 0, &m_fd);
79 if (ret < 0)
80 {
81 CLog::Log(LOGERROR, "CDumbBufferObject::{} - failed to get fd from prime handle, errno={}",
82 __FUNCTION__, strerror(errno));
83 return false;
84 }
85
86 return true;
87}
88
89void CDumbBufferObject::DestroyBufferObject()
90{
91 if (m_fd < 0)
92 return;
93
94 int ret = close(m_fd);
95 if (ret < 0)
96 CLog::Log(LOGERROR, "CDumbBufferObject::{} - close failed, errno={}", __FUNCTION__,
97 strerror(errno));
98
99 m_fd = -1;
100 m_stride = 0;
101 m_size = 0;
102}
103
104uint8_t* CDumbBufferObject::GetMemory()
105{
106 if (m_fd < 0)
107 return nullptr;
108
109 if (m_map)
110 {
111 CLog::Log(LOGDEBUG, "CDumbBufferObject::{} - already mapped fd={} map={}", __FUNCTION__, m_fd,
112 fmt::ptr(m_map));
113 return m_map;
114 }
115
116 uint32_t handle;
117 int ret = drmPrimeFDToHandle(m_device, m_fd, &handle);
118 if (ret < 0)
119 {
120 CLog::Log(LOGERROR, "CDumbBufferObject::{} - failed to get handle from prime fd, errno={}",
121 __FUNCTION__, strerror(errno));
122 return nullptr;
123 }
124
125 struct drm_mode_map_dumb map_dumb = {.handle = handle};
126
127 ret = drmIoctl(m_device, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
128 if (ret < 0)
129 {
130 CLog::Log(LOGERROR, "CDumbBufferObject::{} - ioctl DRM_IOCTL_MODE_MAP_DUMB failed, errno={}",
131 __FUNCTION__, strerror(errno));
132 return nullptr;
133 }
134
135 m_offset = map_dumb.offset;
136
137 m_map = static_cast<uint8_t*>(mmap(nullptr, m_size, PROT_WRITE, MAP_SHARED, m_device, m_offset));
138 if (m_map == MAP_FAILED)
139 {
140 CLog::Log(LOGERROR, "CDumbBufferObject::{} - mmap failed, errno={}", __FUNCTION__,
141 strerror(errno));
142 return nullptr;
143 }
144
145 return m_map;
146}
147
148void CDumbBufferObject::ReleaseMemory()
149{
150 if (!m_map)
151 return;
152
153 int ret = munmap(m_map, m_size);
154 if (ret < 0)
155 CLog::Log(LOGERROR, "CDumbBufferObject::{} - munmap failed, errno={}", __FUNCTION__,
156 strerror(errno));
157
158 m_map = nullptr;
159 m_offset = 0;
160}
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 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/BufferObject.h"
12
13#include <memory>
14#include <stdint.h>
15
16class CDumbBufferObject : public CBufferObject
17{
18public:
19 CDumbBufferObject();
20 virtual ~CDumbBufferObject() override;
21
22 // Registration
23 static std::unique_ptr<CBufferObject> Create();
24 static void Register();
25
26 // IBufferObject overrides via CBufferObject
27 bool CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) override;
28 void DestroyBufferObject() override;
29 uint8_t* GetMemory() override;
30 void ReleaseMemory() override;
31 std::string GetName() const override { return "CDumbBufferObject"; }
32
33private:
34 int m_device{-1};
35 uint64_t m_size{0};
36 uint64_t m_offset{0};
37 uint8_t* m_map{nullptr};
38};
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 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "EGLFence.h"
10
11#include "EGLUtils.h"
12#include "utils/log.h"
13
14using namespace KODI::UTILS::EGL;
15
16CEGLFence::CEGLFence(EGLDisplay display) :
17 m_display(display)
18{
19 m_eglCreateSyncKHR = CEGLUtils::GetRequiredProcAddress<PFNEGLCREATESYNCKHRPROC>("eglCreateSyncKHR");
20 m_eglDestroySyncKHR = CEGLUtils::GetRequiredProcAddress<PFNEGLDESTROYSYNCKHRPROC>("eglDestroySyncKHR");
21 m_eglGetSyncAttribKHR = CEGLUtils::GetRequiredProcAddress<PFNEGLGETSYNCATTRIBKHRPROC>("eglGetSyncAttribKHR");
22}
23
24void CEGLFence::CreateFence()
25{
26 m_fence = m_eglCreateSyncKHR(m_display, EGL_SYNC_FENCE_KHR, nullptr);
27 if (m_fence == EGL_NO_SYNC_KHR)
28 {
29 CEGLUtils::Log(LOGERROR, "failed to create egl sync fence");
30 throw std::runtime_error("failed to create egl sync fence");
31 }
32}
33
34void CEGLFence::DestroyFence()
35{
36 if (m_fence == EGL_NO_SYNC_KHR)
37 {
38 return;
39 }
40
41 if (m_eglDestroySyncKHR(m_display, m_fence) != EGL_TRUE)
42 {
43 CEGLUtils::Log(LOGERROR, "failed to destroy egl sync fence");
44 }
45
46 m_fence = EGL_NO_SYNC_KHR;
47}
48
49bool CEGLFence::IsSignaled()
50{
51 // fence has been destroyed so return true immediately so buffer can be used
52 if (m_fence == EGL_NO_SYNC_KHR)
53 {
54 return true;
55 }
56
57 EGLint status = EGL_UNSIGNALED_KHR;
58 if (m_eglGetSyncAttribKHR(m_display, m_fence, EGL_SYNC_STATUS_KHR, &status) != EGL_TRUE)
59 {
60 CEGLUtils::Log(LOGERROR, "failed to query egl sync fence");
61 return false;
62 }
63
64 if (status == EGL_SIGNALED_KHR)
65 {
66 return true;
67 }
68
69 return false;
70}
diff --git a/xbmc/utils/EGLFence.h b/xbmc/utils/EGLFence.h
new file mode 100644
index 0000000..1664772
--- /dev/null
+++ b/xbmc/utils/EGLFence.h
@@ -0,0 +1,43 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <EGL/egl.h>
12#include <EGL/eglext.h>
13
14namespace KODI
15{
16namespace UTILS
17{
18namespace EGL
19{
20
21class CEGLFence
22{
23public:
24 explicit CEGLFence(EGLDisplay display);
25 CEGLFence(CEGLFence const& other) = delete;
26 CEGLFence& operator=(CEGLFence const& other) = delete;
27
28 void CreateFence();
29 void DestroyFence();
30 bool IsSignaled();
31
32private:
33 EGLDisplay m_display{nullptr};
34 EGLSyncKHR m_fence{nullptr};
35
36 PFNEGLCREATESYNCKHRPROC m_eglCreateSyncKHR{nullptr};
37 PFNEGLDESTROYSYNCKHRPROC m_eglDestroySyncKHR{nullptr};
38 PFNEGLGETSYNCATTRIBKHRPROC m_eglGetSyncAttribKHR{nullptr};
39};
40
41}
42}
43}
diff --git a/xbmc/utils/EGLImage.cpp b/xbmc/utils/EGLImage.cpp
new file mode 100644
index 0000000..633a44d
--- /dev/null
+++ b/xbmc/utils/EGLImage.cpp
@@ -0,0 +1,197 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "EGLImage.h"
10
11#include "EGLUtils.h"
12#include "log.h"
13
14#include <map>
15
16namespace
17{
18 const EGLint eglDmabufPlaneFdAttr[CEGLImage::MAX_NUM_PLANES] =
19 {
20 EGL_DMA_BUF_PLANE0_FD_EXT,
21 EGL_DMA_BUF_PLANE1_FD_EXT,
22 EGL_DMA_BUF_PLANE2_FD_EXT,
23 };
24
25 const EGLint eglDmabufPlaneOffsetAttr[CEGLImage::MAX_NUM_PLANES] =
26 {
27 EGL_DMA_BUF_PLANE0_OFFSET_EXT,
28 EGL_DMA_BUF_PLANE1_OFFSET_EXT,
29 EGL_DMA_BUF_PLANE2_OFFSET_EXT,
30 };
31
32 const EGLint eglDmabufPlanePitchAttr[CEGLImage::MAX_NUM_PLANES] =
33 {
34 EGL_DMA_BUF_PLANE0_PITCH_EXT,
35 EGL_DMA_BUF_PLANE1_PITCH_EXT,
36 EGL_DMA_BUF_PLANE2_PITCH_EXT,
37 };
38
39#if defined(EGL_EXT_image_dma_buf_import_modifiers)
40 const EGLint eglDmabufPlaneModifierLoAttr[CEGLImage::MAX_NUM_PLANES] =
41 {
42 EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
43 EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
44 EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
45 };
46
47 const EGLint eglDmabufPlaneModifierHiAttr[CEGLImage::MAX_NUM_PLANES] =
48 {
49 EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
50 EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT,
51 EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT,
52 };
53#endif
54
55#define X(VAL) std::make_pair(VAL, #VAL)
56std::map<EGLint, const char*> eglAttributes =
57{
58 X(EGL_WIDTH),
59 X(EGL_HEIGHT),
60
61 // please keep attributes in accordance to:
62 // https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import.txt
63 X(EGL_LINUX_DRM_FOURCC_EXT),
64 X(EGL_DMA_BUF_PLANE0_FD_EXT),
65 X(EGL_DMA_BUF_PLANE0_OFFSET_EXT),
66 X(EGL_DMA_BUF_PLANE0_PITCH_EXT),
67 X(EGL_DMA_BUF_PLANE1_FD_EXT),
68 X(EGL_DMA_BUF_PLANE1_OFFSET_EXT),
69 X(EGL_DMA_BUF_PLANE1_PITCH_EXT),
70 X(EGL_DMA_BUF_PLANE2_FD_EXT),
71 X(EGL_DMA_BUF_PLANE2_OFFSET_EXT),
72 X(EGL_DMA_BUF_PLANE2_PITCH_EXT),
73 X(EGL_YUV_COLOR_SPACE_HINT_EXT),
74 X(EGL_SAMPLE_RANGE_HINT_EXT),
75 X(EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT),
76 X(EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT),
77 X(EGL_ITU_REC601_EXT),
78 X(EGL_ITU_REC709_EXT),
79 X(EGL_ITU_REC2020_EXT),
80 X(EGL_YUV_FULL_RANGE_EXT),
81 X(EGL_YUV_NARROW_RANGE_EXT),
82 X(EGL_YUV_CHROMA_SITING_0_EXT),
83 X(EGL_YUV_CHROMA_SITING_0_5_EXT),
84
85#if defined(EGL_EXT_image_dma_buf_import_modifiers)
86 // please keep attributes in accordance to:
87 // https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt
88 X(EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT),
89 X(EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT),
90 X(EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT),
91 X(EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT),
92 X(EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT),
93 X(EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT),
94 X(EGL_DMA_BUF_PLANE3_FD_EXT),
95 X(EGL_DMA_BUF_PLANE3_OFFSET_EXT),
96 X(EGL_DMA_BUF_PLANE3_PITCH_EXT),
97 X(EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT),
98 X(EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT),
99#endif
100};
101
102} // namespace
103
104CEGLImage::CEGLImage(EGLDisplay display) :
105 m_display(display)
106{
107 m_eglCreateImageKHR = CEGLUtils::GetRequiredProcAddress<PFNEGLCREATEIMAGEKHRPROC>("eglCreateImageKHR");
108 m_eglDestroyImageKHR = CEGLUtils::GetRequiredProcAddress<PFNEGLDESTROYIMAGEKHRPROC>("eglDestroyImageKHR");
109 m_glEGLImageTargetTexture2DOES = CEGLUtils::GetRequiredProcAddress<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>("glEGLImageTargetTexture2DOES");
110}
111
112bool CEGLImage::CreateImage(EglAttrs imageAttrs)
113{
114 CEGLAttributes<22> attribs;
115 attribs.Add({{EGL_WIDTH, imageAttrs.width},
116 {EGL_HEIGHT, imageAttrs.height},
117 {EGL_LINUX_DRM_FOURCC_EXT, static_cast<EGLint>(imageAttrs.format)}});
118
119 if (imageAttrs.colorSpace != 0 && imageAttrs.colorRange != 0)
120 {
121 attribs.Add({{EGL_YUV_COLOR_SPACE_HINT_EXT, imageAttrs.colorSpace},
122 {EGL_SAMPLE_RANGE_HINT_EXT, imageAttrs.colorRange},
123 {EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_EXT},
124 {EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_EXT}});
125 }
126
127 for (int i = 0; i < MAX_NUM_PLANES; i++)
128 {
129 if (imageAttrs.planes[i].fd != 0)
130 {
131 attribs.Add({{eglDmabufPlaneFdAttr[i], imageAttrs.planes[i].fd},
132 {eglDmabufPlaneOffsetAttr[i], imageAttrs.planes[i].offset},
133 {eglDmabufPlanePitchAttr[i], imageAttrs.planes[i].pitch}});
134
135#if defined(EGL_EXT_image_dma_buf_import_modifiers)
136 if (imageAttrs.planes[i].modifier != DRM_FORMAT_MOD_INVALID && imageAttrs.planes[i].modifier != DRM_FORMAT_MOD_LINEAR)
137 attribs.Add({{eglDmabufPlaneModifierLoAttr[i], static_cast<EGLint>(imageAttrs.planes[i].modifier & 0xFFFFFFFF)},
138 {eglDmabufPlaneModifierHiAttr[i], static_cast<EGLint>(imageAttrs.planes[i].modifier >> 32)}});
139#endif
140 }
141 }
142
143 m_image = m_eglCreateImageKHR(m_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.Get());
144
145 if(!m_image)
146 {
147 CLog::Log(LOGERROR, "CEGLImage::{} - failed to import buffer into EGL image: {}", __FUNCTION__, eglGetError());
148
149 const EGLint* attrs = attribs.Get();
150
151 std::string eglString;
152
153 for (int i = 0; i < (attribs.Size()); i += 2)
154 {
155 std::string keyStr;
156 std::string valueStr;
157
158 auto eglAttr = eglAttributes.find(attrs[i]);
159 if (eglAttr != eglAttributes.end())
160 {
161 keyStr = eglAttr->second;
162 }
163 else
164 {
165 keyStr = std::to_string(attrs[i]);
166 }
167
168 eglAttr = eglAttributes.find(attrs[i + 1]);
169 if (eglAttr != eglAttributes.end())
170 {
171 valueStr = eglAttr->second;
172 }
173 else
174 {
175 valueStr = std::to_string(attrs[i + 1]);
176 }
177
178 eglString.append(StringUtils::Format("%s: %s\n", keyStr, valueStr));
179 }
180
181 CLog::Log(LOGDEBUG, "CEGLImage::{} - attributes:\n{}", __FUNCTION__, eglString);
182
183 return false;
184 }
185
186 return true;
187}
188
189void CEGLImage::UploadImage(GLenum textureTarget)
190{
191 m_glEGLImageTargetTexture2DOES(textureTarget, m_image);
192}
193
194void CEGLImage::DestroyImage()
195{
196 m_eglDestroyImageKHR(m_display, m_image);
197}
diff --git a/xbmc/utils/EGLImage.h b/xbmc/utils/EGLImage.h
new file mode 100644
index 0000000..d9a63e5
--- /dev/null
+++ b/xbmc/utils/EGLImage.h
@@ -0,0 +1,58 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <array>
12
13#include <EGL/egl.h>
14#include <EGL/eglext.h>
15#include <drm_fourcc.h>
16
17#include "system_gl.h"
18
19class CEGLImage
20{
21public:
22 static const int MAX_NUM_PLANES{3};
23
24 struct EglPlane
25 {
26 int fd{0};
27 int offset{0};
28 int pitch{0};
29 uint64_t modifier{DRM_FORMAT_MOD_INVALID};
30 };
31
32 struct EglAttrs
33 {
34 int width{0};
35 int height{0};
36 uint32_t format{0};
37 int colorSpace{0};
38 int colorRange{0};
39 std::array<EglPlane, MAX_NUM_PLANES> planes;
40 };
41
42 explicit CEGLImage(EGLDisplay display);
43
44 CEGLImage(CEGLImage const& other) = delete;
45 CEGLImage& operator=(CEGLImage const& other) = delete;
46
47 bool CreateImage(EglAttrs imageAttrs);
48 void UploadImage(GLenum textureTarget);
49 void DestroyImage();
50
51private:
52 EGLDisplay m_display{nullptr};
53 EGLImageKHR m_image{nullptr};
54
55 PFNEGLCREATEIMAGEKHRPROC m_eglCreateImageKHR{nullptr};
56 PFNEGLDESTROYIMAGEKHRPROC m_eglDestroyImageKHR{nullptr};
57 PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_glEGLImageTargetTexture2DOES{nullptr};
58};
diff --git a/xbmc/utils/EGLUtils.cpp b/xbmc/utils/EGLUtils.cpp
new file mode 100644
index 0000000..01df4ba
--- /dev/null
+++ b/xbmc/utils/EGLUtils.cpp
@@ -0,0 +1,615 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "EGLUtils.h"
10
11#include "ServiceBroker.h"
12#include "StringUtils.h"
13#include "guilib/IDirtyRegionSolver.h"
14#include "log.h"
15#include "settings/AdvancedSettings.h"
16#include "settings/SettingsComponent.h"
17
18#include <map>
19
20#include <EGL/eglext.h>
21
22namespace
23{
24//! @todo remove when Raspberry Pi updates their EGL headers
25#ifndef EGL_NO_CONFIG_KHR
26#define EGL_NO_CONFIG_KHR static_cast<EGLConfig>(0)
27#endif
28#ifndef EGL_CONTEXT_PRIORITY_LEVEL_IMG
29#define EGL_CONTEXT_PRIORITY_LEVEL_IMG 0x3100
30#endif
31#ifndef EGL_CONTEXT_PRIORITY_HIGH_IMG
32#define EGL_CONTEXT_PRIORITY_HIGH_IMG 0x3101
33#endif
34#ifndef EGL_CONTEXT_PRIORITY_MEDIUM_IMG
35#define EGL_CONTEXT_PRIORITY_MEDIUM_IMG 0x3102
36#endif
37
38#define X(VAL) std::make_pair(VAL, #VAL)
39std::map<EGLint, const char*> eglAttributes =
40{
41 // please keep attributes in accordance to:
42 // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglGetConfigAttrib.xhtml
43 X(EGL_ALPHA_SIZE),
44 X(EGL_ALPHA_MASK_SIZE),
45 X(EGL_BIND_TO_TEXTURE_RGB),
46 X(EGL_BIND_TO_TEXTURE_RGBA),
47 X(EGL_BLUE_SIZE),
48 X(EGL_BUFFER_SIZE),
49 X(EGL_COLOR_BUFFER_TYPE),
50 X(EGL_CONFIG_CAVEAT),
51 X(EGL_CONFIG_ID),
52 X(EGL_CONFORMANT),
53 X(EGL_DEPTH_SIZE),
54 X(EGL_GREEN_SIZE),
55 X(EGL_LEVEL),
56 X(EGL_LUMINANCE_SIZE),
57 X(EGL_MAX_PBUFFER_WIDTH),
58 X(EGL_MAX_PBUFFER_HEIGHT),
59 X(EGL_MAX_PBUFFER_PIXELS),
60 X(EGL_MAX_SWAP_INTERVAL),
61 X(EGL_MIN_SWAP_INTERVAL),
62 X(EGL_NATIVE_RENDERABLE),
63 X(EGL_NATIVE_VISUAL_ID),
64 X(EGL_NATIVE_VISUAL_TYPE),
65 X(EGL_RED_SIZE),
66 X(EGL_RENDERABLE_TYPE),
67 X(EGL_SAMPLE_BUFFERS),
68 X(EGL_SAMPLES),
69 X(EGL_STENCIL_SIZE),
70 X(EGL_SURFACE_TYPE),
71 X(EGL_TRANSPARENT_TYPE),
72 X(EGL_TRANSPARENT_RED_VALUE),
73 X(EGL_TRANSPARENT_GREEN_VALUE),
74 X(EGL_TRANSPARENT_BLUE_VALUE)
75};
76
77std::map<EGLenum, const char*> eglErrors =
78{
79 // please keep errors in accordance to:
80 // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglGetError.xhtml
81 X(EGL_SUCCESS),
82 X(EGL_NOT_INITIALIZED),
83 X(EGL_BAD_ACCESS),
84 X(EGL_BAD_ALLOC),
85 X(EGL_BAD_ATTRIBUTE),
86 X(EGL_BAD_CONFIG),
87 X(EGL_BAD_CONTEXT),
88 X(EGL_BAD_CURRENT_SURFACE),
89 X(EGL_BAD_DISPLAY),
90 X(EGL_BAD_MATCH),
91 X(EGL_BAD_NATIVE_PIXMAP),
92 X(EGL_BAD_NATIVE_WINDOW),
93 X(EGL_BAD_PARAMETER),
94 X(EGL_BAD_SURFACE),
95 X(EGL_CONTEXT_LOST),
96};
97
98std::map<EGLint, const char*> eglErrorType =
99{
100//! @todo remove when Raspberry Pi updates their EGL headers
101#if !defined(TARGET_RASPBERRY_PI)
102 X(EGL_DEBUG_MSG_CRITICAL_KHR),
103 X(EGL_DEBUG_MSG_ERROR_KHR),
104 X(EGL_DEBUG_MSG_WARN_KHR),
105 X(EGL_DEBUG_MSG_INFO_KHR),
106#endif
107};
108#undef X
109
110} // namespace
111
112//! @todo remove when Raspberry Pi updates their EGL headers
113#if !defined(TARGET_RASPBERRY_PI)
114void EglErrorCallback(EGLenum error, const char* command, EGLint messageType, EGLLabelKHR threadLabel, EGLLabelKHR objectLabel, const char* message)
115{
116 std::string errorStr;
117 std::string typeStr;
118
119 auto eglError = eglErrors.find(error);
120 if (eglError != eglErrors.end())
121 {
122 errorStr = eglError->second;
123 }
124
125 auto eglType = eglErrorType.find(messageType);
126 if (eglType != eglErrorType.end())
127 {
128 typeStr = eglType->second;
129 }
130
131 CLog::Log(LOGDEBUG, "EGL Debugging:\nError: {}\nCommand: {}\nType: {}\nMessage: {}", errorStr, command, typeStr, message);
132}
133#endif
134
135std::set<std::string> CEGLUtils::GetClientExtensions()
136{
137 const char* extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
138 if (!extensions)
139 {
140 return {};
141 }
142 std::set<std::string> result;
143 StringUtils::SplitTo(std::inserter(result, result.begin()), extensions, " ");
144 return result;
145}
146
147std::set<std::string> CEGLUtils::GetExtensions(EGLDisplay eglDisplay)
148{
149 const char* extensions = eglQueryString(eglDisplay, EGL_EXTENSIONS);
150 if (!extensions)
151 {
152 throw std::runtime_error("Could not query EGL for extensions");
153 }
154 std::set<std::string> result;
155 StringUtils::SplitTo(std::inserter(result, result.begin()), extensions, " ");
156 return result;
157}
158
159bool CEGLUtils::HasExtension(EGLDisplay eglDisplay, const std::string& name)
160{
161 auto exts = GetExtensions(eglDisplay);
162 return (exts.find(name) != exts.end());
163}
164
165bool CEGLUtils::HasClientExtension(const std::string& name)
166{
167 auto exts = GetClientExtensions();
168 return (exts.find(name) != exts.end());
169}
170
171void CEGLUtils::Log(int logLevel, const std::string& what)
172{
173 EGLenum error = eglGetError();
174 std::string errorStr = StringUtils::Format("0x%04X", error);
175
176 auto eglError = eglErrors.find(error);
177 if (eglError != eglErrors.end())
178 {
179 errorStr = eglError->second;
180 }
181
182 CLog::Log(logLevel, "{} ({})", what.c_str(), errorStr);
183}
184
185CEGLContextUtils::CEGLContextUtils(EGLenum platform, std::string const& platformExtension)
186: m_platform{platform}
187{
188//! @todo remove when Raspberry Pi updates their EGL headers
189#if !defined(TARGET_RASPBERRY_PI)
190 if (CEGLUtils::HasClientExtension("EGL_KHR_debug"))
191 {
192 auto eglDebugMessageControl = CEGLUtils::GetRequiredProcAddress<PFNEGLDEBUGMESSAGECONTROLKHRPROC>("eglDebugMessageControlKHR");
193
194 EGLAttrib eglDebugAttribs[] = {EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE,
195 EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE,
196 EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE,
197 EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE,
198 EGL_NONE};
199
200 eglDebugMessageControl(EglErrorCallback, eglDebugAttribs);
201 }
202#endif
203
204 m_platformSupported = CEGLUtils::HasClientExtension("EGL_EXT_platform_base") && CEGLUtils::HasClientExtension(platformExtension);
205}
206
207bool CEGLContextUtils::IsPlatformSupported() const
208{
209 return m_platformSupported;
210}
211
212CEGLContextUtils::~CEGLContextUtils()
213{
214 Destroy();
215}
216
217bool CEGLContextUtils::CreateDisplay(EGLNativeDisplayType nativeDisplay)
218{
219 if (m_eglDisplay != EGL_NO_DISPLAY)
220 {
221 throw std::logic_error("Do not call CreateDisplay when display has already been created");
222 }
223
224 m_eglDisplay = eglGetDisplay(nativeDisplay);
225 if (m_eglDisplay == EGL_NO_DISPLAY)
226 {
227 CEGLUtils::Log(LOGERROR, "failed to get EGL display");
228 return false;
229 }
230
231 return true;
232}
233
234bool CEGLContextUtils::CreatePlatformDisplay(void* nativeDisplay, EGLNativeDisplayType nativeDisplayLegacy)
235{
236 if (m_eglDisplay != EGL_NO_DISPLAY)
237 {
238 throw std::logic_error("Do not call CreateDisplay when display has already been created");
239 }
240
241#if defined(EGL_EXT_platform_base)
242 if (IsPlatformSupported())
243 {
244 // Theoretically it is possible to use eglGetDisplay() and eglCreateWindowSurface,
245 // but then the EGL library basically has to guess which platform we want
246 // if it supports multiple which is usually the case -
247 // it's better and safer to make it explicit
248
249 auto getPlatformDisplayEXT = CEGLUtils::GetRequiredProcAddress<PFNEGLGETPLATFORMDISPLAYEXTPROC>("eglGetPlatformDisplayEXT");
250 m_eglDisplay = getPlatformDisplayEXT(m_platform, nativeDisplay, nullptr);
251
252 if (m_eglDisplay == EGL_NO_DISPLAY)
253 {
254 CEGLUtils::Log(LOGERROR, "failed to get platform display");
255 return false;
256 }
257 }
258#endif
259
260 if (m_eglDisplay == EGL_NO_DISPLAY)
261 {
262 return CreateDisplay(nativeDisplayLegacy);
263 }
264
265 return true;
266}
267
268bool CEGLContextUtils::InitializeDisplay(EGLint renderingApi)
269{
270 if (!eglInitialize(m_eglDisplay, nullptr, nullptr))
271 {
272 CEGLUtils::Log(LOGERROR, "failed to initialize EGL display");
273 Destroy();
274 return false;
275 }
276
277 const char* value;
278 value = eglQueryString(m_eglDisplay, EGL_VERSION);
279 CLog::Log(LOGINFO, "EGL_VERSION = %s", value ? value : "NULL");
280
281 value = eglQueryString(m_eglDisplay, EGL_VENDOR);
282 CLog::Log(LOGINFO, "EGL_VENDOR = %s", value ? value : "NULL");
283
284 value = eglQueryString(m_eglDisplay, EGL_EXTENSIONS);
285 CLog::Log(LOGINFO, "EGL_EXTENSIONS = %s", value ? value : "NULL");
286
287 value = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
288 CLog::Log(LOGINFO, "EGL_CLIENT_EXTENSIONS = %s", value ? value : "NULL");
289
290 if (eglBindAPI(renderingApi) != EGL_TRUE)
291 {
292 CEGLUtils::Log(LOGERROR, "failed to bind EGL API");
293 Destroy();
294 return false;
295 }
296
297 return true;
298}
299
300bool CEGLContextUtils::ChooseConfig(EGLint renderableType, EGLint visualId, bool hdr)
301{
302 EGLint numMatched{0};
303
304 if (m_eglDisplay == EGL_NO_DISPLAY)
305 {
306 throw std::logic_error("Choosing an EGLConfig requires an EGL display");
307 }
308
309 EGLint surfaceType = EGL_WINDOW_BIT;
310 // for the non-trivial dirty region modes, we need the EGL buffer to be preserved across updates
311 int guiAlgorithmDirtyRegions = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiAlgorithmDirtyRegions;
312 if (guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_COST_REDUCTION ||
313 guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_UNION)
314 surfaceType |= EGL_SWAP_BEHAVIOR_PRESERVED_BIT;
315
316 CEGLAttributesVec attribs;
317 attribs.Add({{EGL_RED_SIZE, 8},
318 {EGL_GREEN_SIZE, 8},
319 {EGL_BLUE_SIZE, 8},
320 {EGL_ALPHA_SIZE, 2},
321 {EGL_DEPTH_SIZE, 16},
322 {EGL_STENCIL_SIZE, 0},
323 {EGL_SAMPLE_BUFFERS, 0},
324 {EGL_SAMPLES, 0},
325 {EGL_SURFACE_TYPE, surfaceType},
326 {EGL_RENDERABLE_TYPE, renderableType}});
327
328 EGLConfig* currentConfig(hdr ? &m_eglHDRConfig : &m_eglConfig);
329
330 if (hdr)
331#if EGL_EXT_pixel_format_float
332 attribs.Add({{EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT}});
333#else
334 return false;
335#endif
336
337 const char* errorMsg = nullptr;
338
339 if (eglChooseConfig(m_eglDisplay, attribs.Get(), nullptr, 0, &numMatched) != EGL_TRUE)
340 errorMsg = "failed to query number of EGL configs";
341
342 std::vector<EGLConfig> eglConfigs(numMatched);
343 if (eglChooseConfig(m_eglDisplay, attribs.Get(), eglConfigs.data(), numMatched, &numMatched) != EGL_TRUE)
344 errorMsg = "failed to find EGL configs with appropriate attributes";
345
346 if (errorMsg)
347 {
348 if (!hdr)
349 {
350 CEGLUtils::Log(LOGERROR, errorMsg);
351 Destroy();
352 }
353 else
354 CEGLUtils::Log(LOGINFO, errorMsg);
355 return false;
356 }
357
358 EGLint id{0};
359 for (const auto &eglConfig: eglConfigs)
360 {
361 *currentConfig = eglConfig;
362
363 if (visualId == 0)
364 break;
365
366 if (eglGetConfigAttrib(m_eglDisplay, *currentConfig, EGL_NATIVE_VISUAL_ID, &id) != EGL_TRUE)
367 CEGLUtils::Log(LOGERROR, "failed to query EGL attribute EGL_NATIVE_VISUAL_ID");
368
369 if (visualId == id)
370 break;
371 }
372
373 if (visualId != 0 && visualId != id)
374 {
375 CLog::Log(LOGDEBUG, "failed to find EGL config with EGL_NATIVE_VISUAL_ID={}", visualId);
376 return false;
377 }
378
379 CLog::Log(LOGDEBUG, "EGL %sConfig Attributes:", hdr ? "HDR " : "");
380
381 for (const auto &eglAttribute : eglAttributes)
382 {
383 EGLint value{0};
384 if (eglGetConfigAttrib(m_eglDisplay, *currentConfig, eglAttribute.first, &value) != EGL_TRUE)
385 CEGLUtils::Log(LOGERROR, StringUtils::Format("failed to query EGL attribute %s", eglAttribute.second));
386
387 // we only need to print the hex value if it's an actual EGL define
388 CLog::Log(LOGDEBUG, " %s: %s", eglAttribute.second, (value >= 0x3000 && value <= 0x3200) ? StringUtils::Format("0x%04x", value) : StringUtils::Format("%d", value));
389 }
390
391 return true;
392}
393
394EGLint CEGLContextUtils::GetConfigAttrib(EGLint attribute) const
395{
396 EGLint value{0};
397 if (eglGetConfigAttrib(m_eglDisplay, m_eglConfig, attribute, &value) != EGL_TRUE)
398 CEGLUtils::Log(LOGERROR, "failed to query EGL attribute");
399 return value;
400}
401
402bool CEGLContextUtils::CreateContext(CEGLAttributesVec contextAttribs)
403{
404 if (m_eglContext != EGL_NO_CONTEXT)
405 {
406 throw std::logic_error("Do not call CreateContext when context has already been created");
407 }
408
409 EGLConfig eglConfig{m_eglConfig};
410
411 if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_KHR_no_config_context"))
412 eglConfig = EGL_NO_CONFIG_KHR;
413
414 if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_IMG_context_priority"))
415 contextAttribs.Add({{EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG}});
416
417//! @todo remove when Raspberry Pi updates their EGL headers
418#if !defined(TARGET_RASPBERRY_PI)
419 if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_KHR_create_context") &&
420 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_openGlDebugging)
421 {
422 contextAttribs.Add({{EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR}});
423 }
424#endif
425
426 m_eglContext = eglCreateContext(m_eglDisplay, eglConfig,
427 EGL_NO_CONTEXT, contextAttribs.Get());
428
429 if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_IMG_context_priority"))
430 {
431 EGLint value{EGL_CONTEXT_PRIORITY_MEDIUM_IMG};
432
433 if (eglQueryContext(m_eglDisplay, m_eglContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value) != EGL_TRUE)
434 CEGLUtils::Log(LOGERROR, "failed to query EGL context attribute EGL_CONTEXT_PRIORITY_LEVEL_IMG");
435
436 if (value != EGL_CONTEXT_PRIORITY_HIGH_IMG)
437 CLog::Log(LOGDEBUG, "Failed to obtain a high priority EGL context");
438 }
439
440 if (m_eglContext == EGL_NO_CONTEXT)
441 {
442 // This is expected to fail under some circumstances, so log as debug
443 CLog::Log(LOGDEBUG, "Failed to create EGL context (EGL error %d)", eglGetError());
444 return false;
445 }
446
447 return true;
448}
449
450bool CEGLContextUtils::BindContext()
451{
452 if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE || m_eglContext == EGL_NO_CONTEXT)
453 {
454 throw std::logic_error("Activating an EGLContext requires display, surface, and context");
455 }
456
457 if (eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext) != EGL_TRUE)
458 {
459 CLog::Log(LOGERROR, "Failed to make context current %p %p %p",
460 m_eglDisplay, m_eglSurface, m_eglContext);
461 return false;
462 }
463
464 return true;
465}
466
467void CEGLContextUtils::SurfaceAttrib()
468{
469 if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE)
470 {
471 throw std::logic_error("Setting surface attributes requires a surface");
472 }
473
474 // for the non-trivial dirty region modes, we need the EGL buffer to be preserved across updates
475 int guiAlgorithmDirtyRegions = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiAlgorithmDirtyRegions;
476 if (guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_COST_REDUCTION ||
477 guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_UNION)
478 {
479 if (eglSurfaceAttrib(m_eglDisplay, m_eglSurface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED) != EGL_TRUE)
480 {
481 CEGLUtils::Log(LOGERROR, "failed to set EGL_BUFFER_PRESERVED swap behavior");
482 }
483 }
484}
485
486void CEGLContextUtils::SurfaceAttrib(EGLint attribute, EGLint value)
487{
488 if (eglSurfaceAttrib(m_eglDisplay, m_eglSurface, attribute, value) != EGL_TRUE)
489 {
490 CEGLUtils::Log(LOGERROR, "failed to set EGL_BUFFER_PRESERVED swap behavior");
491 }
492}
493
494bool CEGLContextUtils::CreateSurface(EGLNativeWindowType nativeWindow, EGLint HDRcolorSpace /* = EGL_NONE */)
495{
496 if (m_eglDisplay == EGL_NO_DISPLAY)
497 {
498 throw std::logic_error("Creating a surface requires a display");
499 }
500 if (m_eglSurface != EGL_NO_SURFACE)
501 {
502 throw std::logic_error("Do not call CreateSurface when surface has already been created");
503 }
504
505 CEGLAttributesVec attribs;
506 EGLConfig config = m_eglConfig;
507
508#ifdef EGL_GL_COLORSPACE
509 if (HDRcolorSpace != EGL_NONE)
510 {
511 attribs.Add({{EGL_GL_COLORSPACE, HDRcolorSpace}});
512 config = m_eglHDRConfig;
513 }
514#endif
515
516 m_eglSurface = eglCreateWindowSurface(m_eglDisplay, config, nativeWindow, attribs.Get());
517
518 if (m_eglSurface == EGL_NO_SURFACE)
519 {
520 CEGLUtils::Log(LOGERROR, "failed to create window surface");
521 return false;
522 }
523
524 SurfaceAttrib();
525
526 return true;
527}
528
529bool CEGLContextUtils::CreatePlatformSurface(void* nativeWindow, EGLNativeWindowType nativeWindowLegacy)
530{
531 if (m_eglDisplay == EGL_NO_DISPLAY)
532 {
533 throw std::logic_error("Creating a surface requires a display");
534 }
535 if (m_eglSurface != EGL_NO_SURFACE)
536 {
537 throw std::logic_error("Do not call CreateSurface when surface has already been created");
538 }
539
540#if defined(EGL_EXT_platform_base)
541 if (IsPlatformSupported())
542 {
543 auto createPlatformWindowSurfaceEXT = CEGLUtils::GetRequiredProcAddress<PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC>("eglCreatePlatformWindowSurfaceEXT");
544 m_eglSurface = createPlatformWindowSurfaceEXT(m_eglDisplay, m_eglConfig, nativeWindow, nullptr);
545
546 if (m_eglSurface == EGL_NO_SURFACE)
547 {
548 CEGLUtils::Log(LOGERROR, "failed to create platform window surface");
549 return false;
550 }
551 }
552#endif
553
554 if (m_eglSurface == EGL_NO_SURFACE)
555 {
556 return CreateSurface(nativeWindowLegacy);
557 }
558
559 SurfaceAttrib();
560
561 return true;
562}
563
564void CEGLContextUtils::Destroy()
565{
566 DestroyContext();
567 DestroySurface();
568
569 if (m_eglDisplay != EGL_NO_DISPLAY)
570 {
571 eglTerminate(m_eglDisplay);
572 m_eglDisplay = EGL_NO_DISPLAY;
573 }
574}
575
576void CEGLContextUtils::DestroyContext()
577{
578 if (m_eglContext != EGL_NO_CONTEXT)
579 {
580 eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
581 eglDestroyContext(m_eglDisplay, m_eglContext);
582 m_eglContext = EGL_NO_CONTEXT;
583 }
584}
585
586void CEGLContextUtils::DestroySurface()
587{
588 if (m_eglSurface != EGL_NO_SURFACE)
589 {
590 eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
591 eglDestroySurface(m_eglDisplay, m_eglSurface);
592 m_eglSurface = EGL_NO_SURFACE;
593 }
594}
595
596
597bool CEGLContextUtils::SetVSync(bool enable)
598{
599 if (m_eglDisplay == EGL_NO_DISPLAY)
600 {
601 return false;
602 }
603
604 return (eglSwapInterval(m_eglDisplay, enable) == EGL_TRUE);
605}
606
607bool CEGLContextUtils::TrySwapBuffers()
608{
609 if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE)
610 {
611 return false;
612 }
613
614 return (eglSwapBuffers(m_eglDisplay, m_eglSurface) == EGL_TRUE);
615}
diff --git a/xbmc/utils/EGLUtils.h b/xbmc/utils/EGLUtils.h
new file mode 100644
index 0000000..ad3b04d
--- /dev/null
+++ b/xbmc/utils/EGLUtils.h
@@ -0,0 +1,232 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <array>
12#include <set>
13#include <stdexcept>
14#include <string>
15#include <vector>
16
17#include <EGL/egl.h>
18
19class CEGLUtils
20{
21public:
22 static std::set<std::string> GetClientExtensions();
23 static std::set<std::string> GetExtensions(EGLDisplay eglDisplay);
24 static bool HasExtension(EGLDisplay eglDisplay, std::string const & name);
25 static bool HasClientExtension(std::string const& name);
26 static void Log(int logLevel, std::string const& what);
27 template<typename T>
28 static T GetRequiredProcAddress(const char * procname)
29 {
30 T p = reinterpret_cast<T>(eglGetProcAddress(procname));
31 if (!p)
32 {
33 throw std::runtime_error(std::string("Could not get EGL function \"") + procname + "\" - maybe a required extension is not supported?");
34 }
35 return p;
36 }
37
38private:
39 CEGLUtils();
40};
41
42/**
43 * Convenience wrapper for heap-allocated EGL attribute arrays
44 *
45 * The wrapper makes sure that the key/value pairs are always written in actual
46 * pairs and that the array is always terminated with EGL_NONE.
47 */
48class CEGLAttributesVec
49{
50public:
51 struct EGLAttribute
52 {
53 EGLint key;
54 EGLint value;
55 };
56
57 /**
58 * Add multiple attributes
59 *
60 * The array is automatically terminated with EGL_NONE
61 */
62 void Add(std::initializer_list<EGLAttribute> const& attributes)
63 {
64 for (auto const& attribute : attributes)
65 {
66 m_attributes.insert(m_attributes.begin(), attribute.value);
67 m_attributes.insert(m_attributes.begin(), attribute.key);
68 }
69 }
70
71 /**
72 * Add one attribute
73 *
74 * The array is automatically terminated with EGL_NONE
75 */
76 void Add(EGLAttribute const& attribute)
77 {
78 Add({attribute});
79 }
80
81 EGLint const * Get() const
82 {
83 return m_attributes.data();
84 }
85
86private:
87 std::vector<EGLint> m_attributes{EGL_NONE};
88};
89
90/**
91 * Convenience wrapper for stack-allocated EGL attribute arrays
92 *
93 * The wrapper makes sure that the key/value pairs are always written in actual
94 * pairs, that the array is always terminated with EGL_NONE, and that the bounds
95 * of the array are not exceeded (checked on runtime).
96 *
97 * \tparam AttributeCount maximum number of attributes that can be added.
98 * Determines the size of the storage array.
99 */
100template<std::size_t AttributeCount>
101class CEGLAttributes
102{
103public:
104 struct EGLAttribute
105 {
106 EGLint key;
107 EGLint value;
108 };
109
110 CEGLAttributes()
111 {
112 m_attributes[0] = EGL_NONE;
113 }
114
115 /**
116 * Add multiple attributes
117 *
118 * The array is automatically terminated with EGL_NONE
119 *
120 * \throws std::out_of_range if more than AttributeCount attributes are added
121 * in total
122 */
123 void Add(std::initializer_list<EGLAttribute> const& attributes)
124 {
125 if (m_writePosition + attributes.size() * 2 + 1 > m_attributes.size())
126 {
127 throw std::out_of_range("CEGLAttributes::Add");
128 }
129
130 for (auto const& attribute : attributes)
131 {
132 m_attributes[m_writePosition++] = attribute.key;
133 m_attributes[m_writePosition++] = attribute.value;
134 }
135 m_attributes[m_writePosition] = EGL_NONE;
136 }
137
138 /**
139 * Add one attribute
140 *
141 * The array is automatically terminated with EGL_NONE
142 *
143 * \throws std::out_of_range if more than AttributeCount attributes are added
144 * in total
145 */
146 void Add(EGLAttribute const& attribute)
147 {
148 Add({attribute});
149 }
150
151 EGLint const * Get() const
152 {
153 return m_attributes.data();
154 }
155
156 int Size() const
157 {
158 return m_writePosition;
159 }
160
161private:
162 std::array<EGLint, AttributeCount * 2 + 1> m_attributes;
163 int m_writePosition{};
164};
165
166class CEGLContextUtils final
167{
168public:
169 CEGLContextUtils() = default;
170 /**
171 * \param platform platform as constant from an extension building on EGL_EXT_platform_base
172 */
173 CEGLContextUtils(EGLenum platform, std::string const& platformExtension);
174 ~CEGLContextUtils();
175
176 bool CreateDisplay(EGLNativeDisplayType nativeDisplay);
177 /**
178 * Create EGLDisplay with EGL_EXT_platform_base
179 *
180 * Falls back to \ref CreateDisplay (with nativeDisplayLegacy) on failure.
181 * The native displays to use with the platform-based and the legacy approach
182 * may be defined to have different types and/or semantics, so this function takes
183 * both as separate parameters.
184 *
185 * \param nativeDisplay native display to use with eglGetPlatformDisplayEXT
186 * \param nativeDisplayLegacy native display to use with eglGetDisplay
187 */
188 bool CreatePlatformDisplay(void* nativeDisplay, EGLNativeDisplayType nativeDisplayLegacy);
189
190 void SurfaceAttrib(EGLint attribute, EGLint value);
191 bool CreateSurface(EGLNativeWindowType nativeWindow, EGLint HDRcolorSpace = EGL_NONE);
192 bool CreatePlatformSurface(void* nativeWindow, EGLNativeWindowType nativeWindowLegacy);
193 bool InitializeDisplay(EGLint renderingApi);
194 bool ChooseConfig(EGLint renderableType, EGLint visualId = 0, bool hdr = false);
195 bool CreateContext(CEGLAttributesVec contextAttribs);
196 bool BindContext();
197 void Destroy();
198 void DestroySurface();
199 void DestroyContext();
200 bool SetVSync(bool enable);
201 bool TrySwapBuffers();
202 bool IsPlatformSupported() const;
203 EGLint GetConfigAttrib(EGLint attribute) const;
204
205 EGLDisplay GetEGLDisplay() const
206 {
207 return m_eglDisplay;
208 }
209 EGLSurface GetEGLSurface() const
210 {
211 return m_eglSurface;
212 }
213 EGLContext GetEGLContext() const
214 {
215 return m_eglContext;
216 }
217 EGLConfig GetEGLConfig() const
218 {
219 return m_eglConfig;
220 }
221
222private:
223 void SurfaceAttrib();
224
225 EGLenum m_platform{EGL_NONE};
226 bool m_platformSupported{false};
227
228 EGLDisplay m_eglDisplay{EGL_NO_DISPLAY};
229 EGLSurface m_eglSurface{EGL_NO_SURFACE};
230 EGLContext m_eglContext{EGL_NO_CONTEXT};
231 EGLConfig m_eglConfig{}, m_eglHDRConfig{};
232};
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "EmbeddedArt.h"
10
11#include "Archive.h"
12
13EmbeddedArtInfo::EmbeddedArtInfo(size_t size,
14 const std::string &mime, const std::string& type)
15{
16 Set(size, mime, type);
17}
18
19void EmbeddedArtInfo::Set(size_t size, const std::string &mime, const std::string& type)
20{
21 m_size = size;
22 m_mime = mime;
23 m_type = type;
24}
25
26void EmbeddedArtInfo::Clear()
27{
28 m_mime.clear();
29 m_size = 0;
30}
31
32bool EmbeddedArtInfo::Empty() const
33{
34 return m_size == 0;
35}
36
37bool EmbeddedArtInfo::Matches(const EmbeddedArtInfo &right) const
38{
39 return (m_size == right.m_size &&
40 m_mime == right.m_mime &&
41 m_type == right.m_type);
42}
43
44void EmbeddedArtInfo::Archive(CArchive &ar)
45{
46 if (ar.IsStoring())
47 {
48 ar << m_size;
49 ar << m_mime;
50 ar << m_type;
51 }
52 else
53 {
54 ar >> m_size;
55 ar >> m_mime;
56 ar >> m_type;
57 }
58}
59
60EmbeddedArt::EmbeddedArt(const uint8_t *data, size_t size,
61 const std::string &mime, const std::string& type)
62{
63 Set(data, size, mime, type);
64}
65
66void EmbeddedArt::Set(const uint8_t *data, size_t size,
67 const std::string &mime, const std::string& type)
68{
69 EmbeddedArtInfo::Set(size, mime, type);
70 m_data.resize(size);
71 m_data.assign(data, data+size);
72}
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 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "IArchivable.h"
12
13#include <stdint.h>
14#include <string>
15#include <vector>
16
17class EmbeddedArtInfo : public IArchivable
18{
19public:
20 EmbeddedArtInfo() = default;
21 EmbeddedArtInfo(size_t size, const std::string &mime, const std::string& type = "");
22 virtual ~EmbeddedArtInfo() = default;
23
24 // implementation of IArchivable
25 void Archive(CArchive& ar) override;
26
27 void Set(size_t size, const std::string &mime, const std::string& type = "");
28 void Clear();
29 bool Empty() const;
30 bool Matches(const EmbeddedArtInfo &right) const;
31 void SetType(const std::string& type) { m_type = type; }
32
33 size_t m_size = 0;
34 std::string m_mime;
35 std::string m_type;
36};
37
38class EmbeddedArt : public EmbeddedArtInfo
39{
40public:
41 EmbeddedArt() = default;
42 EmbeddedArt(const uint8_t *data, size_t size,
43 const std::string &mime, const std::string& type = "");
44
45 void Set(const uint8_t *data, size_t size,
46 const std::string &mime, const std::string& type = "");
47
48 std::vector<uint8_t> m_data;
49};
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 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "EndianSwap.h"
10
11/* based on libavformat/spdif.c */
12void Endian_Swap16_buf(uint16_t *dst, uint16_t *src, int w)
13{
14 int i;
15
16 for (i = 0; i + 8 <= w; i += 8) {
17 dst[i + 0] = Endian_Swap16(src[i + 0]);
18 dst[i + 1] = Endian_Swap16(src[i + 1]);
19 dst[i + 2] = Endian_Swap16(src[i + 2]);
20 dst[i + 3] = Endian_Swap16(src[i + 3]);
21 dst[i + 4] = Endian_Swap16(src[i + 4]);
22 dst[i + 5] = Endian_Swap16(src[i + 5]);
23 dst[i + 6] = Endian_Swap16(src[i + 6]);
24 dst[i + 7] = Endian_Swap16(src[i + 7]);
25 }
26
27 for (; i < w; i++)
28 dst[i + 0] = Endian_Swap16(src[i + 0]);
29}
30
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11 /* Endian_SwapXX functions taken from SDL (SDL_endian.h) */
12
13#ifdef TARGET_POSIX
14#include <inttypes.h>
15#elif TARGET_WINDOWS
16#define __inline__ __inline
17#include <stdint.h>
18#endif
19
20
21/* Set up for C function definitions, even when using C++ */
22#ifdef __cplusplus
23extern "C" {
24#endif
25
26#if defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__))
27static __inline__ uint16_t Endian_Swap16(uint16_t x)
28{
29 uint16_t result;
30
31 __asm__("rlwimi %0,%2,8,16,23" : "=&r" (result) : "0" (x >> 8), "r" (x));
32 return result;
33}
34
35static __inline__ uint32_t Endian_Swap32(uint32_t x)
36{
37 uint32_t result;
38
39 __asm__("rlwimi %0,%2,24,16,23" : "=&r" (result) : "0" (x>>24), "r" (x));
40 __asm__("rlwimi %0,%2,8,8,15" : "=&r" (result) : "0" (result), "r" (x));
41 __asm__("rlwimi %0,%2,24,0,7" : "=&r" (result) : "0" (result), "r" (x));
42 return result;
43}
44#else
45static __inline__ uint16_t Endian_Swap16(uint16_t x) {
46 return((x<<8)|(x>>8));
47}
48
49static __inline__ uint32_t Endian_Swap32(uint32_t x) {
50 return((x<<24)|((x<<8)&0x00FF0000)|((x>>8)&0x0000FF00)|(x>>24));
51}
52#endif
53
54static __inline__ uint64_t Endian_Swap64(uint64_t x) {
55 uint32_t hi, lo;
56
57 /* Separate into high and low 32-bit values and swap them */
58 lo = (uint32_t)(x&0xFFFFFFFF);
59 x >>= 32;
60 hi = (uint32_t)(x&0xFFFFFFFF);
61 x = Endian_Swap32(lo);
62 x <<= 32;
63 x |= Endian_Swap32(hi);
64 return(x);
65
66}
67
68void Endian_Swap16_buf(uint16_t *dst, uint16_t *src, int w);
69
70#ifndef WORDS_BIGENDIAN
71#define Endian_SwapLE16(X) (X)
72#define Endian_SwapLE32(X) (X)
73#define Endian_SwapLE64(X) (X)
74#define Endian_SwapBE16(X) Endian_Swap16(X)
75#define Endian_SwapBE32(X) Endian_Swap32(X)
76#define Endian_SwapBE64(X) Endian_Swap64(X)
77#else
78#define Endian_SwapLE16(X) Endian_Swap16(X)
79#define Endian_SwapLE32(X) Endian_Swap32(X)
80#define Endian_SwapLE64(X) Endian_Swap64(X)
81#define Endian_SwapBE16(X) (X)
82#define Endian_SwapBE32(X) (X)
83#define Endian_SwapBE64(X) (X)
84#endif
85
86/* Ends C function definitions when using C++ */
87#ifdef __cplusplus
88}
89#endif
90
diff --git a/xbmc/utils/EventStream.h b/xbmc/utils/EventStream.h
new file mode 100644
index 0000000..42a17df
--- /dev/null
+++ b/xbmc/utils/EventStream.h
@@ -0,0 +1,100 @@
1/*
2 * Copyright (C) 2016-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "EventStreamDetail.h"
12#include "JobManager.h"
13#include "threads/CriticalSection.h"
14#include "threads/SingleLock.h"
15
16#include <algorithm>
17#include <memory>
18#include <vector>
19
20
21template<typename Event>
22class CEventStream
23{
24public:
25
26 template<typename A>
27 void Subscribe(A* owner, void (A::*fn)(const Event&))
28 {
29 auto subscription = std::make_shared<detail::CSubscription<Event, A>>(owner, fn);
30 CSingleLock lock(m_criticalSection);
31 m_subscriptions.emplace_back(std::move(subscription));
32 }
33
34 template<typename A>
35 void Unsubscribe(A* obj)
36 {
37 std::vector<std::shared_ptr<detail::ISubscription<Event>>> toCancel;
38 {
39 CSingleLock lock(m_criticalSection);
40 auto it = m_subscriptions.begin();
41 while (it != m_subscriptions.end())
42 {
43 if ((*it)->IsOwnedBy(obj))
44 {
45 toCancel.push_back(*it);
46 it = m_subscriptions.erase(it);
47 }
48 else
49 {
50 ++it;
51 }
52 }
53 }
54 for (auto& subscription : toCancel)
55 subscription->Cancel();
56 }
57
58protected:
59 std::vector<std::shared_ptr<detail::ISubscription<Event>>> m_subscriptions;
60 CCriticalSection m_criticalSection;
61};
62
63
64template<typename Event>
65class CEventSource : public CEventStream<Event>
66{
67public:
68 explicit CEventSource() : m_queue(false, 1, CJob::PRIORITY_HIGH) {};
69
70 template<typename A>
71 void Publish(A event)
72 {
73 CSingleLock lock(this->m_criticalSection);
74 auto& subscriptions = this->m_subscriptions;
75 auto task = [subscriptions, event](){
76 for (auto& s: subscriptions)
77 s->HandleEvent(event);
78 };
79 lock.Leave();
80 m_queue.Submit(std::move(task));
81 }
82
83private:
84 CJobQueue m_queue;
85};
86
87template<typename Event>
88class CBlockingEventSource : public CEventStream<Event>
89{
90public:
91 template<typename A>
92 void HandleEvent(A event)
93 {
94 CSingleLock lock(this->m_criticalSection);
95 for (const auto& subscription : this->m_subscriptions)
96 {
97 subscription->HandleEvent(event);
98 }
99 }
100};
diff --git a/xbmc/utils/EventStreamDetail.h b/xbmc/utils/EventStreamDetail.h
new file mode 100644
index 0000000..c8eec67
--- /dev/null
+++ b/xbmc/utils/EventStreamDetail.h
@@ -0,0 +1,69 @@
1/*
2 * Copyright (C) 2016-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "threads/CriticalSection.h"
12#include "threads/SingleLock.h"
13
14namespace detail
15{
16
17template<typename Event>
18class ISubscription
19{
20public:
21 virtual void HandleEvent(const Event& event) = 0;
22 virtual void Cancel() = 0;
23 virtual bool IsOwnedBy(void* obj) = 0;
24 virtual ~ISubscription() = default;
25};
26
27template<typename Event, typename Owner>
28class CSubscription : public ISubscription<Event>
29{
30public:
31 typedef void (Owner::*Fn)(const Event&);
32 CSubscription(Owner* owner, Fn fn);
33 void HandleEvent(const Event& event) override;
34 void Cancel() override;
35 bool IsOwnedBy(void *obj) override;
36
37private:
38 Owner* m_owner;
39 Fn m_eventHandler;
40 CCriticalSection m_criticalSection;
41};
42
43template<typename Event, typename Owner>
44CSubscription<Event, Owner>::CSubscription(Owner* owner, Fn fn)
45 : m_owner(owner), m_eventHandler(fn)
46{}
47
48template<typename Event, typename Owner>
49bool CSubscription<Event, Owner>::IsOwnedBy(void* obj)
50{
51 CSingleLock lock(m_criticalSection);
52 return obj != nullptr && obj == m_owner;
53}
54
55template<typename Event, typename Owner>
56void CSubscription<Event, Owner>::Cancel()
57{
58 CSingleLock lock(m_criticalSection);
59 m_owner = nullptr;
60}
61
62template<typename Event, typename Owner>
63void CSubscription<Event, Owner>::HandleEvent(const Event& event)
64{
65 CSingleLock lock(m_criticalSection);
66 if (m_owner)
67 (m_owner->*m_eventHandler)(event);
68}
69}
diff --git a/xbmc/utils/Fanart.cpp b/xbmc/utils/Fanart.cpp
new file mode 100644
index 0000000..fbc3774
--- /dev/null
+++ b/xbmc/utils/Fanart.cpp
@@ -0,0 +1,175 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Fanart.h"
10
11#include "StringUtils.h"
12#include "URIUtils.h"
13#include "utils/XBMCTinyXML.h"
14#include "utils/XMLUtils.h"
15
16#include <algorithm>
17#include <functional>
18
19const unsigned int CFanart::max_fanart_colors=3;
20
21
22/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
23/// CFanart Functions
24/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
25
26CFanart::CFanart() = default;
27
28void CFanart::Pack()
29{
30 // Take our data and pack it into the m_xml string
31 m_xml.clear();
32 TiXmlElement fanart("fanart");
33 for (std::vector<SFanartData>::const_iterator it = m_fanart.begin(); it != m_fanart.end(); ++it)
34 {
35 TiXmlElement thumb("thumb");
36 thumb.SetAttribute("colors", it->strColors.c_str());
37 thumb.SetAttribute("preview", it->strPreview.c_str());
38 TiXmlText text(it->strImage);
39 thumb.InsertEndChild(text);
40 fanart.InsertEndChild(thumb);
41 }
42 m_xml << fanart;
43}
44
45void CFanart::AddFanart(const std::string& image, const std::string& preview, const std::string& colors)
46{
47 SFanartData info;
48 info.strPreview = preview;
49 info.strImage = image;
50 ParseColors(colors, info.strColors);
51 m_fanart.push_back(std::move(info));
52}
53
54void CFanart::Clear()
55{
56 m_fanart.clear();
57 m_xml.clear();
58}
59
60bool CFanart::Unpack()
61{
62 CXBMCTinyXML doc;
63 doc.Parse(m_xml);
64
65 m_fanart.clear();
66
67 TiXmlElement *fanart = doc.FirstChildElement("fanart");
68 while (fanart)
69 {
70 std::string url = XMLUtils::GetAttribute(fanart, "url");
71 TiXmlElement *fanartThumb = fanart->FirstChildElement("thumb");
72 while (fanartThumb)
73 {
74 if (!fanartThumb->NoChildren())
75 {
76 SFanartData data;
77 if (url.empty())
78 {
79 data.strImage = fanartThumb->FirstChild()->ValueStr();
80 data.strPreview = XMLUtils::GetAttribute(fanartThumb, "preview");
81 }
82 else
83 {
84 data.strImage = URIUtils::AddFileToFolder(url, fanartThumb->FirstChild()->ValueStr());
85 if (fanartThumb->Attribute("preview"))
86 data.strPreview = URIUtils::AddFileToFolder(url, fanartThumb->Attribute("preview"));
87 }
88 ParseColors(XMLUtils::GetAttribute(fanartThumb, "colors"), data.strColors);
89 m_fanart.push_back(data);
90 }
91 fanartThumb = fanartThumb->NextSiblingElement("thumb");
92 }
93 fanart = fanart->NextSiblingElement("fanart");
94 }
95 return true;
96}
97
98std::string CFanart::GetImageURL(unsigned int index) const
99{
100 if (index >= m_fanart.size())
101 return "";
102
103 return m_fanart[index].strImage;
104}
105
106std::string CFanart::GetPreviewURL(unsigned int index) const
107{
108 if (index >= m_fanart.size())
109 return "";
110
111 return m_fanart[index].strPreview.empty() ? m_fanart[index].strImage : m_fanart[index].strPreview;
112}
113
114const std::string CFanart::GetColor(unsigned int index) const
115{
116 if (index >= max_fanart_colors || m_fanart.empty() ||
117 m_fanart[0].strColors.size() < index*9+8)
118 return "FFFFFFFF";
119
120 // format is AARRGGBB,AARRGGBB etc.
121 return m_fanart[0].strColors.substr(index*9, 8);
122}
123
124bool CFanart::SetPrimaryFanart(unsigned int index)
125{
126 if (index >= m_fanart.size())
127 return false;
128
129 std::iter_swap(m_fanart.begin()+index, m_fanart.begin());
130
131 // repack our data
132 Pack();
133
134 return true;
135}
136
137unsigned int CFanart::GetNumFanarts() const
138{
139 return m_fanart.size();
140}
141
142bool CFanart::ParseColors(const std::string &colorsIn, std::string &colorsOut)
143{
144 // Formats:
145 // 0: XBMC ARGB Hexadecimal string comma separated "FFFFFFFF,DDDDDDDD,AAAAAAAA"
146 // 1: The TVDB RGB Int Triplets, pipe separate with leading/trailing pipes "|68,69,59|69,70,58|78,78,68|"
147
148 // Essentially we read the colors in using the proper format, and store them in our own fixed temporary format (3 DWORDS), and then
149 // write them back in in the specified format.
150
151 if (colorsIn.empty())
152 return false;
153
154 // check for the TVDB RGB triplets "|68,69,59|69,70,58|78,78,68|"
155 if (colorsIn[0] == '|')
156 { // need conversion
157 colorsOut.clear();
158 std::vector<std::string> strColors = StringUtils::Split(colorsIn, "|");
159 for (int i = 0; i < std::min((int)strColors.size()-1, (int)max_fanart_colors); i++)
160 { // split up each color
161 std::vector<std::string> strTriplets = StringUtils::Split(strColors[i+1], ",");
162 if (strTriplets.size() == 3)
163 { // convert
164 if (colorsOut.size())
165 colorsOut += ",";
166 colorsOut += StringUtils::Format("FF%2lx%2lx%2lx", atol(strTriplets[0].c_str()), atol(strTriplets[1].c_str()), atol(strTriplets[2].c_str()));
167 }
168 }
169 }
170 else
171 { // assume is our format
172 colorsOut = colorsIn;
173 }
174 return true;
175}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11// Fanart.h
12//////////////////////////////////////////////////////////////////////
13
14#include <string>
15#include <vector>
16
17///
18/// /brief CFanart is the core of fanart support and contains all fanart data for a specific show
19///
20/// CFanart stores all data related to all available fanarts for a given TV show and provides
21/// functions required to manipulate and access that data.
22/// In order to provide an interface between the fanart data and the XBMC database, all data
23/// is stored internally it its own form, as well as packed into an XML formatted string
24/// stored in the member variable m_xml.
25/// Information on multiple fanarts for a given show is stored, but XBMC only cares about the
26/// very first fanart stored. These interfaces provide a means to access the data in that first
27/// fanart record, as well as to set which fanart is the first record. Externally, all XBMC needs
28/// to care about is getting and setting that first record. Everything else is maintained internally
29/// by CFanart. This point is key to using the interface properly.
30class CFanart
31{
32public:
33 ///
34 /// Standard constructor doesn't need to do anything
35 CFanart();
36 ///
37 /// Takes the internal fanart data and packs it into an XML formatted string in m_xml
38 /// \sa m_xml
39 void Pack();
40 ///
41 /// Takes the XML formatted string m_xml and unpacks the fanart data contained into the internal data
42 /// \return A boolean indicating success or failure
43 /// \sa m_xml
44 bool Unpack();
45 ///
46 /// Retrieves the fanart full res image URL
47 /// \param index - index of image to retrieve (defaults to 0)
48 /// \return A string containing the full URL to the full resolution fanart image
49 std::string GetImageURL(unsigned int index = 0) const;
50 ///
51 /// Retrieves the fanart preview image URL, or full res image URL if that doesn't exist
52 /// \param index - index of image to retrieve (defaults to 0)
53 /// \return A string containing the full URL to the full resolution fanart image
54 std::string GetPreviewURL(unsigned int index = 0) const;
55 ///
56 /// Used to return a specified fanart theme color value
57 /// \param index: 0 based index of the color to retrieve. A fanart theme contains 3 colors, indices 0-2, arranged from darkest to lightest.
58 const std::string GetColor(unsigned int index) const;
59 ///
60 /// Sets a particular fanart to be the "primary" fanart, or in other words, sets which fanart is actually used by XBMC
61 ///
62 /// 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.
63 /// \param index: 0 based index of which fanart to set as the primary fanart
64 /// \return A boolean value indicating success or failure. This should only return false if the specified index is not a valid fanart
65 bool SetPrimaryFanart(unsigned int index);
66 ///
67 /// Returns how many fanarts are stored
68 /// \return An integer indicating how many fanarts are stored in the class. Fanart indices are 0 to (GetNumFanarts() - 1)
69 unsigned int GetNumFanarts() const;
70 /// Adds an image to internal fanart data
71 void AddFanart(const std::string& image, const std::string& preview, const std::string& colors);
72 /// Clear all internal fanart data
73 void Clear();
74 ///
75 /// m_xml contains an XML formatted string which is all fanart packed into one string.
76 ///
77 /// 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
78 /// anytime this string is changed, the change should be followed up by a call to CFanart::UnPack(). This XML formatted string is
79 /// also the interface used to pass the fanart data from the scraper to CFanart.
80 std::string m_xml;
81private:
82 static const unsigned int max_fanart_colors;
83 ///
84 /// Parse various color formats as returned by the sites scraped into a format we recognize
85 ///
86 /// Supported Formats:
87 ///
88 /// * The TVDB RGB Int Triplets, pipe separate with leading/trailing pipes "|68,69,59|69,70,58|78,78,68|"
89 /// * XBMC ARGB Hexadecimal string comma separated "FFFFFFFF,DDDDDDDD,AAAAAAAA"
90 ///
91 /// \param colorsIn: string containing colors in some format to be converted
92 /// \param colorsOut: XBMC ARGB Hexadecimal string comma separated "FFFFFFFF,DDDDDDDD,AAAAAAAA"
93 /// \return boolean indicating success or failure.
94 static bool ParseColors(const std::string&colorsIn, std::string&colorsOut);
95
96 struct SFanartData
97 {
98 std::string strImage;
99 std::string strColors;
100 std::string strPreview;
101 };
102
103 ///
104 /// std::vector that stores all our fanart data
105 std::vector<SFanartData> m_fanart;
106};
107
diff --git a/xbmc/utils/FileExtensionProvider.cpp b/xbmc/utils/FileExtensionProvider.cpp
new file mode 100644
index 0000000..001629e
--- /dev/null
+++ b/xbmc/utils/FileExtensionProvider.cpp
@@ -0,0 +1,182 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "FileExtensionProvider.h"
10
11#include "ServiceBroker.h"
12#include "addons/AddonManager.h"
13#include "settings/AdvancedSettings.h"
14#include "settings/SettingsComponent.h"
15
16#include <string>
17#include <vector>
18
19using namespace ADDON;
20
21const std::vector<TYPE> ADDON_TYPES = {
22 ADDON_VFS,
23 ADDON_IMAGEDECODER,
24 ADDON_AUDIODECODER
25};
26
27CFileExtensionProvider::CFileExtensionProvider(ADDON::CAddonMgr& addonManager)
28 : m_advancedSettings(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()),
29 m_addonManager(addonManager)
30{
31 SetAddonExtensions();
32
33 m_addonManager.Events().Subscribe(this, &CFileExtensionProvider::OnAddonEvent);
34}
35
36CFileExtensionProvider::~CFileExtensionProvider()
37{
38 m_addonManager.Events().Unsubscribe(this);
39
40 m_advancedSettings.reset();
41 m_addonExtensions.clear();
42}
43
44std::string CFileExtensionProvider::GetDiscStubExtensions() const
45{
46 return m_advancedSettings->m_discStubExtensions;
47}
48
49std::string CFileExtensionProvider::GetMusicExtensions() const
50{
51 std::string extensions(m_advancedSettings->m_musicExtensions);
52 extensions += '|' + GetAddonExtensions(ADDON_VFS);
53 extensions += '|' + GetAddonExtensions(ADDON_AUDIODECODER);
54
55 return extensions;
56}
57
58std::string CFileExtensionProvider::GetPictureExtensions() const
59{
60 std::string extensions(m_advancedSettings->m_pictureExtensions);
61 extensions += '|' + GetAddonExtensions(ADDON_VFS);
62 extensions += '|' + GetAddonExtensions(ADDON_IMAGEDECODER);
63
64 return extensions;
65}
66
67std::string CFileExtensionProvider::GetSubtitleExtensions() const
68{
69 std::string extensions(m_advancedSettings->m_subtitlesExtensions);
70 extensions += '|' + GetAddonExtensions(ADDON_VFS);
71
72 return extensions;
73}
74
75std::string CFileExtensionProvider::GetVideoExtensions() const
76{
77 std::string extensions(m_advancedSettings->m_videoExtensions);
78 if (!extensions.empty())
79 extensions += '|';
80 extensions += GetAddonExtensions(ADDON_VFS);
81
82 return extensions;
83}
84
85std::string CFileExtensionProvider::GetFileFolderExtensions() const
86{
87 std::string extensions(GetAddonFileFolderExtensions(ADDON_VFS));
88 if (!extensions.empty())
89 extensions += '|';
90 extensions += GetAddonFileFolderExtensions(ADDON_AUDIODECODER);
91
92 return extensions;
93}
94
95std::string CFileExtensionProvider::GetAddonExtensions(const TYPE &type) const
96{
97 auto it = m_addonExtensions.find(type);
98 if (it != m_addonExtensions.end())
99 return it->second;
100
101 return "";
102}
103
104std::string CFileExtensionProvider::GetAddonFileFolderExtensions(const TYPE &type) const
105{
106 auto it = m_addonExtensions.find(type);
107 if (it != m_addonExtensions.end())
108 return it->second;
109
110 return "";
111}
112
113void CFileExtensionProvider::SetAddonExtensions()
114{
115 for (auto const type : ADDON_TYPES)
116 {
117 SetAddonExtensions(type);
118 }
119}
120
121void CFileExtensionProvider::SetAddonExtensions(const TYPE& type)
122{
123 std::vector<std::string> extensions;
124 std::vector<std::string> fileFolderExtensions;
125 std::vector<AddonInfoPtr> addonInfos;
126 m_addonManager.GetAddonInfos(addonInfos, true, type);
127 for (const auto& addonInfo : addonInfos)
128 {
129 std::string info = ADDON_VFS == type ? "@extensions" : "@extension";
130 std::string ext = addonInfo->Type(type)->GetValue(info).asString();
131 if (!ext.empty())
132 {
133 extensions.push_back(ext);
134 if (type == ADDON_VFS || type == ADDON_AUDIODECODER)
135 {
136 std::string info2 = ADDON_VFS == type ? "@filedirectories" : "@tracks";
137 if (addonInfo->Type(type)->GetValue(info2).asBoolean())
138 fileFolderExtensions.push_back(ext);
139 }
140 if (type == ADDON_VFS)
141 {
142 if (addonInfo->Type(type)->GetValue("@encodedhostname").asBoolean())
143 {
144 std::string prot = addonInfo->Type(type)->GetValue("@protocols").asString();
145 auto prots = StringUtils::Split(prot, "|");
146 for (const std::string& it : prots)
147 m_encoded.push_back(it);
148 }
149 }
150 }
151 }
152
153 m_addonExtensions.insert(make_pair(type, StringUtils::Join(extensions, "|")));
154 if (!fileFolderExtensions.empty())
155 m_addonFileFolderExtensions.insert(make_pair(type, StringUtils::Join(fileFolderExtensions, "|")));
156}
157
158void CFileExtensionProvider::OnAddonEvent(const AddonEvent& event)
159{
160 if (typeid(event) == typeid(AddonEvents::Enabled) ||
161 typeid(event) == typeid(AddonEvents::Disabled) ||
162 typeid(event) == typeid(AddonEvents::ReInstalled))
163 {
164 for (auto &type : ADDON_TYPES)
165 {
166 if (m_addonManager.HasType(event.id, type))
167 {
168 SetAddonExtensions(type);
169 break;
170 }
171 }
172 }
173 else if (typeid(event) == typeid(AddonEvents::UnInstalled))
174 {
175 SetAddonExtensions();
176 }
177}
178
179bool CFileExtensionProvider::EncodedHostName(const std::string& protocol) const
180{
181 return std::find(m_encoded.begin(),m_encoded.end(),protocol) != m_encoded.end();
182}
diff --git a/xbmc/utils/FileExtensionProvider.h b/xbmc/utils/FileExtensionProvider.h
new file mode 100644
index 0000000..fce384a
--- /dev/null
+++ b/xbmc/utils/FileExtensionProvider.h
@@ -0,0 +1,79 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "addons/AddonEvents.h"
12#include "addons/addoninfo/AddonInfo.h"
13#include "settings/AdvancedSettings.h"
14
15namespace ADDON
16{
17 class CAddonMgr;
18}
19
20class CFileExtensionProvider
21{
22public:
23 CFileExtensionProvider(ADDON::CAddonMgr& addonManager);
24 ~CFileExtensionProvider();
25
26 /*!
27 * @brief Returns a list of picture extensions
28 */
29 std::string GetPictureExtensions() const;
30
31 /*!
32 * @brief Returns a list of music extensions
33 */
34 std::string GetMusicExtensions() const;
35
36 /*!
37 * @brief Returns a list of video extensions
38 */
39 std::string GetVideoExtensions() const;
40
41 /*!
42 * @brief Returns a list of subtitle extensions
43 */
44 std::string GetSubtitleExtensions() const;
45
46 /*!
47 * @brief Returns a list of disc stub extensions
48 */
49 std::string GetDiscStubExtensions() const;
50
51 /*!
52 * @brief Returns a file folder extensions
53 */
54 std::string GetFileFolderExtensions() const;
55
56 /*!
57 * @brief Returns whether a url protocol from add-ons use encoded hostnames
58 */
59 bool EncodedHostName(const std::string& protocol) const;
60
61private:
62 std::string GetAddonExtensions(const ADDON::TYPE &type) const;
63 std::string GetAddonFileFolderExtensions(const ADDON::TYPE &type) const;
64 void SetAddonExtensions();
65 void SetAddonExtensions(const ADDON::TYPE &type);
66
67 void OnAddonEvent(const ADDON::AddonEvent& event);
68
69 // Construction properties
70 std::shared_ptr<CAdvancedSettings> m_advancedSettings;
71 ADDON::CAddonMgr &m_addonManager;
72
73 // File extension properties
74 std::map<ADDON::TYPE, std::string> m_addonExtensions;
75 std::map<ADDON::TYPE, std::string> m_addonFileFolderExtensions;
76
77 // Protocols from add-ons with encoded host names
78 std::vector<std::string> m_encoded;
79};
diff --git a/xbmc/utils/FileOperationJob.cpp b/xbmc/utils/FileOperationJob.cpp
new file mode 100644
index 0000000..67a5536
--- /dev/null
+++ b/xbmc/utils/FileOperationJob.cpp
@@ -0,0 +1,353 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "FileOperationJob.h"
10
11#include "ServiceBroker.h"
12#include "URL.h"
13#include "Util.h"
14#include "dialogs/GUIDialogExtendedProgressBar.h"
15#include "filesystem/Directory.h"
16#include "filesystem/File.h"
17#include "filesystem/FileDirectoryFactory.h"
18#include "guilib/GUIComponent.h"
19#include "guilib/GUIWindowManager.h"
20#include "guilib/LocalizeStrings.h"
21#include "utils/StringUtils.h"
22#include "utils/URIUtils.h"
23#include "utils/log.h"
24
25using namespace XFILE;
26
27CFileOperationJob::CFileOperationJob()
28 : m_items(),
29 m_strDestFile(),
30 m_avgSpeed(),
31 m_currentOperation(),
32 m_currentFile()
33{ }
34
35CFileOperationJob::CFileOperationJob(FileAction action, CFileItemList & items,
36 const std::string& strDestFile,
37 bool displayProgress /* = false */,
38 int heading /* = 0 */, int line /* = 0 */)
39 : m_action(action),
40 m_items(),
41 m_strDestFile(strDestFile),
42 m_avgSpeed(),
43 m_currentOperation(),
44 m_currentFile(),
45 m_displayProgress(displayProgress),
46 m_heading(heading),
47 m_line(line)
48{
49 SetFileOperation(action, items, strDestFile);
50}
51
52void CFileOperationJob::SetFileOperation(FileAction action, CFileItemList &items, const std::string &strDestFile)
53{
54 m_action = action;
55 m_strDestFile = strDestFile;
56
57 m_items.Clear();
58 for (int i = 0; i < items.Size(); i++)
59 m_items.Add(CFileItemPtr(new CFileItem(*items[i])));
60}
61
62bool CFileOperationJob::DoWork()
63{
64 FileOperationList ops;
65 double totalTime = 0.0;
66
67 if (m_displayProgress && GetProgressDialog() == NULL)
68 {
69 CGUIDialogExtendedProgressBar* dialog =
70 CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogExtendedProgressBar>(WINDOW_DIALOG_EXT_PROGRESS);
71 SetProgressBar(dialog->GetHandle(GetActionString(m_action)));
72 }
73
74 bool success = DoProcess(m_action, m_items, m_strDestFile, ops, totalTime);
75
76 unsigned int size = ops.size();
77
78 double opWeight = 100.0 / totalTime;
79 double current = 0.0;
80
81 for (unsigned int i = 0; i < size && success; i++)
82 success &= ops[i].ExecuteOperation(this, current, opWeight);
83
84 MarkFinished();
85
86 return success;
87}
88
89bool CFileOperationJob::DoProcessFile(FileAction action, const std::string& strFileA, const std::string& strFileB, FileOperationList &fileOperations, double &totalTime)
90{
91 int64_t time = 1;
92
93 if (action == ActionCopy || action == ActionReplace || (action == ActionMove && !CanBeRenamed(strFileA, strFileB)))
94 {
95 struct __stat64 data;
96 if (CFile::Stat(strFileA, &data) == 0)
97 time += data.st_size;
98 }
99
100 fileOperations.push_back(CFileOperation(action, strFileA, strFileB, time));
101
102 totalTime += time;
103
104 return true;
105}
106
107bool CFileOperationJob::DoProcessFolder(FileAction action, const std::string& strPath, const std::string& strDestFile, FileOperationList &fileOperations, double &totalTime)
108{
109 // check whether this folder is a filedirectory - if so, we don't process it's contents
110 CFileItem item(strPath, false);
111 IFileDirectory *file = CFileDirectoryFactory::Create(item.GetURL(), &item);
112 if (file)
113 {
114 delete file;
115 return true;
116 }
117
118 CFileItemList items;
119 CDirectory::GetDirectory(strPath, items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_GET_HIDDEN);
120 for (int i = 0; i < items.Size(); i++)
121 {
122 CFileItemPtr pItem = items[i];
123 pItem->Select(true);
124 }
125
126 if (!DoProcess(action, items, strDestFile, fileOperations, totalTime))
127 {
128 CLog::Log(LOGERROR,"FileManager: error while processing folder: %s", strPath.c_str());
129 return false;
130 }
131
132 if (action == ActionMove)
133 {
134 fileOperations.push_back(CFileOperation(ActionDeleteFolder, strPath, "", 1));
135 totalTime += 1.0;
136 }
137
138 return true;
139}
140
141bool CFileOperationJob::DoProcess(FileAction action, CFileItemList & items, const std::string& strDestFile, FileOperationList &fileOperations, double &totalTime)
142{
143 for (int iItem = 0; iItem < items.Size(); ++iItem)
144 {
145 CFileItemPtr pItem = items[iItem];
146 if (pItem->IsSelected())
147 {
148 std::string strNoSlash = pItem->GetPath();
149 URIUtils::RemoveSlashAtEnd(strNoSlash);
150 std::string strFileName = URIUtils::GetFileName(strNoSlash);
151
152 // special case for upnp
153 if (URIUtils::IsUPnP(items.GetPath()) || URIUtils::IsUPnP(pItem->GetPath()))
154 {
155 // get filename from label instead of path
156 strFileName = pItem->GetLabel();
157
158 if (!pItem->m_bIsFolder && !URIUtils::HasExtension(strFileName))
159 {
160 // FIXME: for now we only work well if the url has the extension
161 // we should map the content type to the extension otherwise
162 strFileName += URIUtils::GetExtension(pItem->GetPath());
163 }
164
165 strFileName = CUtil::MakeLegalFileName(strFileName);
166 }
167
168 std::string strnewDestFile;
169 if (!strDestFile.empty()) // only do this if we have a destination
170 strnewDestFile = URIUtils::ChangeBasePath(pItem->GetPath(), strFileName, strDestFile); // Convert (URL) encoding + slashes (if source / target differ)
171
172 if (pItem->m_bIsFolder)
173 {
174 // in ActionReplace mode all subdirectories will be removed by the below
175 // DoProcessFolder(ActionDelete) call as well, so ActionCopy is enough when
176 // processing those
177 FileAction subdirAction = (action == ActionReplace) ? ActionCopy : action;
178 // create folder on dest. drive
179 if (action != ActionDelete && action != ActionDeleteFolder)
180 DoProcessFile(ActionCreateFolder, strnewDestFile, "", fileOperations, totalTime);
181
182 if (action == ActionReplace && CDirectory::Exists(strnewDestFile))
183 DoProcessFolder(ActionDelete, strnewDestFile, "", fileOperations, totalTime);
184
185 if (!DoProcessFolder(subdirAction, pItem->GetPath(), strnewDestFile, fileOperations, totalTime))
186 return false;
187
188 if (action == ActionDelete || action == ActionDeleteFolder)
189 DoProcessFile(ActionDeleteFolder, pItem->GetPath(), "", fileOperations, totalTime);
190 }
191 else
192 DoProcessFile(action, pItem->GetPath(), strnewDestFile, fileOperations, totalTime);
193 }
194 }
195
196 return true;
197}
198
199CFileOperationJob::CFileOperation::CFileOperation(FileAction action, const std::string &strFileA, const std::string &strFileB, int64_t time)
200 : m_action(action),
201 m_strFileA(strFileA),
202 m_strFileB(strFileB),
203 m_time(time)
204{ }
205
206struct DataHolder
207{
208 CFileOperationJob *base;
209 double current;
210 double opWeight;
211};
212
213std::string CFileOperationJob::GetActionString(FileAction action)
214{
215 std::string result;
216 switch (action)
217 {
218 case ActionCopy:
219 case ActionReplace:
220 result = g_localizeStrings.Get(115);
221 break;
222
223 case ActionMove:
224 result = g_localizeStrings.Get(116);
225 break;
226
227 case ActionDelete:
228 case ActionDeleteFolder:
229 result = g_localizeStrings.Get(117);
230 break;
231
232 case ActionCreateFolder:
233 result = g_localizeStrings.Get(119);
234 break;
235
236 default:
237 break;
238 }
239
240 return result;
241}
242
243bool CFileOperationJob::CFileOperation::ExecuteOperation(CFileOperationJob *base, double &current, double opWeight)
244{
245 bool bResult = true;
246
247 base->m_currentFile = CURL(m_strFileA).GetFileNameWithoutPath();
248 base->m_currentOperation = GetActionString(m_action);
249
250 if (base->ShouldCancel((unsigned int)current, 100))
251 return false;
252
253 base->SetText(base->GetCurrentFile());
254
255 DataHolder data = {base, current, opWeight};
256
257 switch (m_action)
258 {
259 case ActionCopy:
260 case ActionReplace:
261 bResult = CFile::Copy(m_strFileA, m_strFileB, this, &data);
262 break;
263
264 case ActionMove:
265 if (CanBeRenamed(m_strFileA, m_strFileB))
266 bResult = CFile::Rename(m_strFileA, m_strFileB);
267 else if (CFile::Copy(m_strFileA, m_strFileB, this, &data))
268 bResult = CFile::Delete(m_strFileA);
269 else
270 bResult = false;
271 break;
272
273 case ActionDelete:
274 bResult = CFile::Delete(m_strFileA);
275 break;
276
277 case ActionDeleteFolder:
278 bResult = CDirectory::Remove(m_strFileA);
279 break;
280
281 case ActionCreateFolder:
282 bResult = CDirectory::Create(m_strFileA);
283 break;
284
285 default:
286 CLog::Log(LOGERROR, "FileManager: unknown operation");
287 bResult = false;
288 break;
289 }
290
291 current += (double)m_time * opWeight;
292
293 return bResult;
294}
295
296inline bool CFileOperationJob::CanBeRenamed(const std::string &strFileA, const std::string &strFileB)
297{
298#ifndef TARGET_POSIX
299 if (strFileA[1] == ':' && strFileA[0] == strFileB[0])
300 return true;
301#else
302 if (URIUtils::IsHD(strFileA) && URIUtils::IsHD(strFileB))
303 return true;
304 else if (URIUtils::IsSmb(strFileA) && URIUtils::IsSmb(strFileB)) {
305 CURL smbFileA(strFileA), smbFileB(strFileB);
306 return smbFileA.GetHostName() == smbFileB.GetHostName() &&
307 smbFileA.GetShareName() == smbFileB.GetShareName();
308 }
309#endif
310 return false;
311}
312
313bool CFileOperationJob::CFileOperation::OnFileCallback(void* pContext, int ipercent, float avgSpeed)
314{
315 DataHolder *data = static_cast<DataHolder*>(pContext);
316 double current = data->current + ((double)ipercent * data->opWeight * (double)m_time)/ 100.0;
317
318 if (avgSpeed > 1000000.0f)
319 data->base->m_avgSpeed = StringUtils::Format("%.1f MB/s", avgSpeed / 1000000.0f);
320 else
321 data->base->m_avgSpeed = StringUtils::Format("%.1f KB/s", avgSpeed / 1000.0f);
322
323 std::string line;
324 line = StringUtils::Format("%s (%s)",
325 data->base->GetCurrentFile().c_str(),
326 data->base->GetAverageSpeed().c_str());
327 data->base->SetText(line);
328 return !data->base->ShouldCancel((unsigned)current, 100);
329}
330
331bool CFileOperationJob::operator==(const CJob* job) const
332{
333 if (strcmp(job->GetType(), GetType()) != 0)
334 return false;
335
336 const CFileOperationJob* rjob = dynamic_cast<const CFileOperationJob*>(job);
337 if (rjob == NULL)
338 return false;
339
340 if (GetAction() != rjob->GetAction() ||
341 m_strDestFile != rjob->m_strDestFile ||
342 m_items.Size() != rjob->m_items.Size())
343 return false;
344
345 for (int i = 0; i < m_items.Size(); i++)
346 {
347 if (m_items[i]->GetPath() != rjob->m_items[i]->GetPath() ||
348 m_items[i]->IsSelected() != rjob->m_items[i]->IsSelected())
349 return false;
350 }
351
352 return true;
353}
diff --git a/xbmc/utils/FileOperationJob.h b/xbmc/utils/FileOperationJob.h
new file mode 100644
index 0000000..de1264e
--- /dev/null
+++ b/xbmc/utils/FileOperationJob.h
@@ -0,0 +1,85 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "FileItem.h"
12#include "filesystem/File.h"
13#include "utils/ProgressJob.h"
14
15#include <string>
16#include <vector>
17
18class CFileOperationJob : public CProgressJob
19{
20public:
21 enum FileAction
22 {
23 ActionCopy = 1,
24 ActionMove,
25 ActionDelete,
26 ActionReplace, ///< Copy, emptying any existing destination directories first
27 ActionCreateFolder,
28 ActionDeleteFolder,
29 };
30
31 CFileOperationJob();
32 CFileOperationJob(FileAction action, CFileItemList & items,
33 const std::string& strDestFile,
34 bool displayProgress = false,
35 int errorHeading = 0, int errorLine = 0);
36
37 static std::string GetActionString(FileAction action);
38
39 // implementations of CJob
40 bool DoWork() override;
41 const char* GetType() const override { return m_displayProgress ? "filemanager" : ""; }
42 bool operator==(const CJob *job) const override;
43
44 void SetFileOperation(FileAction action, CFileItemList &items, const std::string &strDestFile);
45
46 const std::string &GetAverageSpeed() const { return m_avgSpeed; }
47 const std::string &GetCurrentOperation() const { return m_currentOperation; }
48 const std::string &GetCurrentFile() const { return m_currentFile; }
49 const CFileItemList &GetItems() const { return m_items; }
50 FileAction GetAction() const { return m_action; }
51 int GetHeading() const { return m_heading; }
52 int GetLine() const { return m_line; }
53
54private:
55 class CFileOperation : public XFILE::IFileCallback
56 {
57 public:
58 CFileOperation(FileAction action, const std::string &strFileA, const std::string &strFileB, int64_t time);
59
60 bool OnFileCallback(void* pContext, int ipercent, float avgSpeed) override;
61
62 bool ExecuteOperation(CFileOperationJob *base, double &current, double opWeight);
63
64 private:
65 FileAction m_action;
66 std::string m_strFileA, m_strFileB;
67 int64_t m_time;
68 };
69 friend class CFileOperation;
70
71 typedef std::vector<CFileOperation> FileOperationList;
72 bool DoProcess(FileAction action, CFileItemList & items, const std::string& strDestFile, FileOperationList &fileOperations, double &totalTime);
73 bool DoProcessFolder(FileAction action, const std::string& strPath, const std::string& strDestFile, FileOperationList &fileOperations, double &totalTime);
74 bool DoProcessFile(FileAction action, const std::string& strFileA, const std::string& strFileB, FileOperationList &fileOperations, double &totalTime);
75
76 static inline bool CanBeRenamed(const std::string &strFileA, const std::string &strFileB);
77
78 FileAction m_action = ActionCopy;
79 CFileItemList m_items;
80 std::string m_strDestFile;
81 std::string m_avgSpeed, m_currentOperation, m_currentFile;
82 bool m_displayProgress = false;
83 int m_heading = 0;
84 int m_line = 0;
85};
diff --git a/xbmc/utils/FileUtils.cpp b/xbmc/utils/FileUtils.cpp
new file mode 100644
index 0000000..e51f3d6
--- /dev/null
+++ b/xbmc/utils/FileUtils.cpp
@@ -0,0 +1,351 @@
1/*
2 * Copyright (C) 2010-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "FileUtils.h"
10#include "ServiceBroker.h"
11#include "guilib/GUIKeyboardFactory.h"
12#include "utils/log.h"
13#include "guilib/LocalizeStrings.h"
14#include "JobManager.h"
15#include "FileOperationJob.h"
16#include "URIUtils.h"
17#include "filesystem/MultiPathDirectory.h"
18#include "filesystem/SpecialProtocol.h"
19#include "filesystem/StackDirectory.h"
20#include "settings/MediaSourceSettings.h"
21#include "Util.h"
22#include "StringUtils.h"
23#include "URL.h"
24#include "settings/Settings.h"
25#include "settings/SettingsComponent.h"
26#include "storage/MediaManager.h"
27#include "utils/Variant.h"
28
29#if defined(TARGET_WINDOWS)
30#include "platform/win32/WIN32Util.h"
31#include "utils/CharsetConverter.h"
32#endif
33
34#include <vector>
35
36using namespace XFILE;
37
38bool CFileUtils::DeleteItem(const std::string &strPath)
39{
40 CFileItemPtr item(new CFileItem(strPath));
41 item->SetPath(strPath);
42 item->m_bIsFolder = URIUtils::HasSlashAtEnd(strPath);
43 item->Select(true);
44 return DeleteItem(item);
45}
46
47bool CFileUtils::DeleteItem(const CFileItemPtr &item)
48{
49 if (!item || item->IsParentFolder())
50 return false;
51
52 // Create a temporary item list containing the file/folder for deletion
53 CFileItemPtr pItemTemp(new CFileItem(*item));
54 pItemTemp->Select(true);
55 CFileItemList items;
56 items.Add(pItemTemp);
57
58 // grab the real filemanager window, set up the progress bar,
59 // and process the delete action
60 CFileOperationJob op(CFileOperationJob::ActionDelete, items, "");
61
62 return op.DoWork();
63}
64
65bool CFileUtils::RenameFile(const std::string &strFile)
66{
67 std::string strFileAndPath(strFile);
68 URIUtils::RemoveSlashAtEnd(strFileAndPath);
69 std::string strFileName = URIUtils::GetFileName(strFileAndPath);
70 std::string strPath = URIUtils::GetDirectory(strFileAndPath);
71 if (CGUIKeyboardFactory::ShowAndGetInput(strFileName, CVariant{g_localizeStrings.Get(16013)}, false))
72 {
73 strPath = URIUtils::AddFileToFolder(strPath, strFileName);
74 CLog::Log(LOGINFO, "FileUtils: rename %s->%s", strFileAndPath.c_str(), strPath.c_str());
75 if (URIUtils::IsMultiPath(strFileAndPath))
76 { // special case for multipath renames - rename all the paths.
77 std::vector<std::string> paths;
78 CMultiPathDirectory::GetPaths(strFileAndPath, paths);
79 bool success = false;
80 for (unsigned int i = 0; i < paths.size(); ++i)
81 {
82 std::string filePath(paths[i]);
83 URIUtils::RemoveSlashAtEnd(filePath);
84 filePath = URIUtils::GetDirectory(filePath);
85 filePath = URIUtils::AddFileToFolder(filePath, strFileName);
86 if (CFile::Rename(paths[i], filePath))
87 success = true;
88 }
89 return success;
90 }
91 return CFile::Rename(strFileAndPath, strPath);
92 }
93 return false;
94}
95
96bool CFileUtils::RemoteAccessAllowed(const std::string &strPath)
97{
98 std::string SourceNames[] = { "programs", "files", "video", "music", "pictures" };
99
100 std::string realPath = URIUtils::GetRealPath(strPath);
101 // for rar:// and zip:// paths we need to extract the path to the archive
102 // instead of using the VFS path
103 while (URIUtils::IsInArchive(realPath))
104 realPath = CURL(realPath).GetHostName();
105
106 if (StringUtils::StartsWithNoCase(realPath, "virtualpath://upnproot/"))
107 return true;
108 else if (StringUtils::StartsWithNoCase(realPath, "musicdb://"))
109 return true;
110 else if (StringUtils::StartsWithNoCase(realPath, "videodb://"))
111 return true;
112 else if (StringUtils::StartsWithNoCase(realPath, "library://video"))
113 return true;
114 else if (StringUtils::StartsWithNoCase(realPath, "library://music"))
115 return true;
116 else if (StringUtils::StartsWithNoCase(realPath, "sources://video"))
117 return true;
118 else if (StringUtils::StartsWithNoCase(realPath, "special://musicplaylists"))
119 return true;
120 else if (StringUtils::StartsWithNoCase(realPath, "special://profile/playlists"))
121 return true;
122 else if (StringUtils::StartsWithNoCase(realPath, "special://videoplaylists"))
123 return true;
124 else if (StringUtils::StartsWithNoCase(realPath, "special://skin"))
125 return true;
126 else if (StringUtils::StartsWithNoCase(realPath, "special://profile/addon_data"))
127 return true;
128 else if (StringUtils::StartsWithNoCase(realPath, "addons://sources"))
129 return true;
130 else if (StringUtils::StartsWithNoCase(realPath, "upnp://"))
131 return true;
132 else if (StringUtils::StartsWithNoCase(realPath, "plugin://"))
133 return true;
134 else
135 {
136 std::string strPlaylistsPath = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH);
137 URIUtils::RemoveSlashAtEnd(strPlaylistsPath);
138 if (StringUtils::StartsWithNoCase(realPath, strPlaylistsPath))
139 return true;
140 }
141 bool isSource;
142 // Check manually added sources (held in sources.xml)
143 for (const std::string& sourceName : SourceNames)
144 {
145 VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources(sourceName);
146 int sourceIndex = CUtil::GetMatchingSource(realPath, *sources, isSource);
147 if (sourceIndex >= 0 && sourceIndex < static_cast<int>(sources->size()) &&
148 sources->at(sourceIndex).m_iHasLock != LOCK_STATE_LOCKED &&
149 sources->at(sourceIndex).m_allowSharing)
150 return true;
151 }
152 // Check auto-mounted sources
153 VECSOURCES sources;
154 CServiceBroker::GetMediaManager().GetRemovableDrives(
155 sources); // Sources returned allways have m_allowsharing = true
156 //! @todo Make sharing of auto-mounted sources user configurable
157 int sourceIndex = CUtil::GetMatchingSource(realPath, sources, isSource);
158 if (sourceIndex >= 0 && sourceIndex < static_cast<int>(sources.size()) &&
159 sources.at(sourceIndex).m_iHasLock != LOCK_STATE_LOCKED &&
160 sources.at(sourceIndex).m_allowSharing)
161 return true;
162
163 return false;
164}
165
166CDateTime CFileUtils::GetModificationDate(const std::string& strFileNameAndPath,
167 const bool& bUseLatestDate)
168{
169 if (bUseLatestDate)
170 return GetModificationDate(1, strFileNameAndPath);
171 else
172 return GetModificationDate(0, strFileNameAndPath);
173}
174
175CDateTime CFileUtils::GetModificationDate(const int& code, const std::string& strFileNameAndPath)
176{
177 CDateTime dateAdded;
178 if (strFileNameAndPath.empty())
179 {
180 CLog::Log(LOGDEBUG, "%s empty strFileNameAndPath variable", __FUNCTION__);
181 return dateAdded;
182 }
183
184 try
185 {
186 std::string file = strFileNameAndPath;
187 if (URIUtils::IsStack(strFileNameAndPath))
188 file = CStackDirectory::GetFirstStackedFile(strFileNameAndPath);
189
190 if (URIUtils::IsInArchive(file))
191 file = CURL(file).GetHostName();
192
193 // Try to get ctime (creation on Windows, metadata change on Linux) and mtime (modification)
194 struct __stat64 buffer;
195 if (CFile::Stat(file, &buffer) == 0 && (buffer.st_mtime != 0 || buffer.st_ctime != 0))
196 {
197 time_t now = time(NULL);
198 time_t addedTime;
199 // Prefer the modification time if it's valid, fallback to ctime
200 if (code == 0)
201 {
202 if (buffer.st_mtime != 0 && static_cast<time_t>(buffer.st_mtime) <= now)
203 addedTime = static_cast<time_t>(buffer.st_mtime);
204 else
205 addedTime = static_cast<time_t>(buffer.st_ctime);
206 }
207 // Use the later of the ctime and mtime
208 else if (code == 1)
209 {
210 addedTime =
211 std::max(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime));
212 // if the newer of the two dates is in the future, we try it with the older one
213 if (addedTime > now)
214 addedTime =
215 std::min(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime));
216 }
217 // Perfer the earliest of ctime and mtime, fallback to other
218 else
219 {
220 addedTime =
221 std::min(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime));
222 // if the older of the two dates is invalid, we try it with the newer one
223 if (addedTime == 0)
224 addedTime =
225 std::max(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime));
226 }
227
228
229 // make sure the datetime does is not in the future
230 if (addedTime <= now)
231 {
232 struct tm* time;
233#ifdef HAVE_LOCALTIME_R
234 struct tm result = {};
235 time = localtime_r(&addedTime, &result);
236#else
237 time = localtime(&addedTime);
238#endif
239 if (time)
240 dateAdded = *time;
241 }
242 }
243 }
244 catch (...)
245 {
246 CLog::Log(LOGERROR, "%s unable to extract modification date for file (%s)", __FUNCTION__,
247 strFileNameAndPath.c_str());
248 }
249 return dateAdded;
250}
251
252bool CFileUtils::CheckFileAccessAllowed(const std::string &filePath)
253{
254 // DENY access to paths matching
255 const std::vector<std::string> blacklist = {
256 "passwords.xml",
257 "sources.xml",
258 "guisettings.xml",
259 "advancedsettings.xml",
260 "server.key",
261 "/.ssh/",
262 };
263 // ALLOW kodi paths
264 const std::vector<std::string> whitelist = {
265 CSpecialProtocol::TranslatePath("special://home"),
266 CSpecialProtocol::TranslatePath("special://xbmc"),
267 CSpecialProtocol::TranslatePath("special://musicartistsinfo")
268 };
269
270 // image urls come in the form of image://... sometimes with a / appended at the end
271 // and can be embedded in a music or video file image://music@...
272 // strip this off to get the real file path
273 bool isImage = false;
274 std::string decodePath = CURL::Decode(filePath);
275 size_t pos = decodePath.find("image://");
276 if (pos != std::string::npos)
277 {
278 isImage = true;
279 decodePath.erase(pos, 8);
280 URIUtils::RemoveSlashAtEnd(decodePath);
281 if (StringUtils::StartsWith(decodePath, "music@") || StringUtils::StartsWith(decodePath, "video@"))
282 decodePath.erase(pos, 6);
283 }
284
285 // check blacklist
286 for (const auto &b : blacklist)
287 {
288 if (decodePath.find(b) != std::string::npos)
289 {
290 CLog::Log(LOGERROR,"%s denied access to %s", __FUNCTION__, decodePath.c_str());
291 return false;
292 }
293 }
294
295#if defined(TARGET_POSIX)
296 std::string whiteEntry;
297 char *fullpath = realpath(decodePath.c_str(), nullptr);
298
299 // if this is a locally existing file, check access permissions
300 if (fullpath)
301 {
302 const std::string realPath = fullpath;
303 free(fullpath);
304
305 // check whitelist
306 for (const auto &w : whitelist)
307 {
308 char *realtemp = realpath(w.c_str(), nullptr);
309 if (realtemp)
310 {
311 whiteEntry = realtemp;
312 free(realtemp);
313 }
314 if (StringUtils::StartsWith(realPath, whiteEntry))
315 return true;
316 }
317 // check sources with realPath
318 return CFileUtils::RemoteAccessAllowed(realPath);
319 }
320#elif defined(TARGET_WINDOWS)
321 CURL url(decodePath);
322 if (url.GetProtocol().empty())
323 {
324 std::wstring decodePathW;
325 g_charsetConverter.utf8ToW(decodePath, decodePathW, false);
326 CWIN32Util::AddExtraLongPathPrefix(decodePathW);
327 DWORD bufSize = GetFullPathNameW(decodePathW.c_str(), 0, nullptr, nullptr);
328 if (bufSize > 0)
329 {
330 std::wstring fullpathW;
331 fullpathW.resize(bufSize);
332 if (GetFullPathNameW(decodePathW.c_str(), bufSize, const_cast<wchar_t*>(fullpathW.c_str()), nullptr) <= bufSize - 1)
333 {
334 CWIN32Util::RemoveExtraLongPathPrefix(fullpathW);
335 std::string fullpath;
336 g_charsetConverter.wToUTF8(fullpathW, fullpath, false);
337 for (const std::string& whiteEntry : whitelist)
338 {
339 if (StringUtils::StartsWith(fullpath, whiteEntry))
340 return true;
341 }
342 return CFileUtils::RemoteAccessAllowed(fullpath);
343 }
344 }
345 }
346#endif
347 // if it isn't a local file, it must be a vfs entry
348 if (! isImage)
349 return CFileUtils::RemoteAccessAllowed(decodePath);
350 return true;
351}
diff --git a/xbmc/utils/FileUtils.h b/xbmc/utils/FileUtils.h
new file mode 100644
index 0000000..afd1552
--- /dev/null
+++ b/xbmc/utils/FileUtils.h
@@ -0,0 +1,31 @@
1/*
2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "FileItem.h"
12
13#include <string>
14
15class CFileUtils
16{
17public:
18 static bool CheckFileAccessAllowed(const std::string &filePath);
19 static bool DeleteItem(const CFileItemPtr &item);
20 static bool DeleteItem(const std::string &strPath);
21 static bool RenameFile(const std::string &strFile);
22 static bool RemoteAccessAllowed(const std::string &strPath);
23 static unsigned int LoadFile(const std::string &filename, void* &outputBuffer);
24 /*! \brief Get the modified date of a file if its invalid it returns the creation date - this behavior changes when you set bUseLatestDate
25 \param strFileNameAndPath path to the file
26 \param bUseLatestDate use the newer datetime of the files mtime and ctime
27 \return Returns the file date, can return a invalid date if problems occur
28 */
29 static CDateTime GetModificationDate(const std::string& strFileNameAndPath, const bool& bUseLatestDate);
30 static CDateTime GetModificationDate(const int& code, const std::string& strFileNameAndPath);
31};
diff --git a/xbmc/utils/GBMBufferObject.cpp b/xbmc/utils/GBMBufferObject.cpp
new file mode 100644
index 0000000..88b7575
--- /dev/null
+++ b/xbmc/utils/GBMBufferObject.cpp
@@ -0,0 +1,98 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "GBMBufferObject.h"
10
11#include "BufferObjectFactory.h"
12#include "ServiceBroker.h"
13#include "windowing/gbm/WinSystemGbmEGLContext.h"
14
15#include <gbm.h>
16
17using namespace KODI::WINDOWING::GBM;
18
19std::unique_ptr<CBufferObject> CGBMBufferObject::Create()
20{
21 return std::make_unique<CGBMBufferObject>();
22}
23
24void CGBMBufferObject::Register()
25{
26 CBufferObjectFactory::RegisterBufferObject(CGBMBufferObject::Create);
27}
28
29CGBMBufferObject::CGBMBufferObject()
30{
31 m_device = static_cast<CWinSystemGbmEGLContext*>(CServiceBroker::GetWinSystem())->GetGBMDevice();
32}
33
34CGBMBufferObject::~CGBMBufferObject()
35{
36 ReleaseMemory();
37 DestroyBufferObject();
38}
39
40bool CGBMBufferObject::CreateBufferObject(uint32_t format, uint32_t width, uint32_t height)
41{
42 if (m_fd >= 0)
43 return true;
44
45 m_width = width;
46 m_height = height;
47
48 m_bo = gbm_bo_create(m_device, m_width, m_height, format, GBM_BO_USE_LINEAR);
49
50 if (!m_bo)
51 return false;
52
53 m_fd = gbm_bo_get_fd(m_bo);
54
55 return true;
56}
57
58void CGBMBufferObject::DestroyBufferObject()
59{
60 close(m_fd);
61
62 if (m_bo)
63 gbm_bo_destroy(m_bo);
64
65 m_bo = nullptr;
66 m_fd = -1;
67}
68
69uint8_t* CGBMBufferObject::GetMemory()
70{
71 if (m_bo)
72 {
73 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));
74 if (m_map)
75 return m_map;
76 }
77
78 return nullptr;
79}
80
81void CGBMBufferObject::ReleaseMemory()
82{
83 if (m_bo && m_map)
84 {
85 gbm_bo_unmap(m_bo, m_map_data);
86 m_map_data = nullptr;
87 m_map = nullptr;
88 }
89}
90
91uint64_t CGBMBufferObject::GetModifier()
92{
93#if defined(HAS_GBM_MODIFIERS)
94 return gbm_bo_get_modifier(m_bo);
95#else
96 return 0;
97#endif
98}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/BufferObject.h"
12
13#include <memory>
14#include <stdint.h>
15
16struct gbm_bo;
17struct gbm_device;
18
19class CGBMBufferObject : public CBufferObject
20{
21public:
22 CGBMBufferObject();
23 ~CGBMBufferObject() override;
24
25 // Registration
26 static std::unique_ptr<CBufferObject> Create();
27 static void Register();
28
29 // IBufferObject overrides via CBufferObject
30 bool CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) override;
31 void DestroyBufferObject() override;
32 uint8_t* GetMemory() override;
33 void ReleaseMemory() override;
34 std::string GetName() const override { return "CGBMBufferObject"; }
35
36 // CBufferObject overrides
37 uint64_t GetModifier() override;
38
39private:
40 gbm_device* m_device{nullptr};
41 gbm_bo* m_bo{nullptr};
42
43 uint32_t m_width{0};
44 uint32_t m_height{0};
45
46 uint8_t* m_map{nullptr};
47 void* m_map_data{nullptr};
48};
diff --git a/xbmc/utils/GLUtils.cpp b/xbmc/utils/GLUtils.cpp
new file mode 100644
index 0000000..be94eb5
--- /dev/null
+++ b/xbmc/utils/GLUtils.cpp
@@ -0,0 +1,267 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "GLUtils.h"
10
11#include "ServiceBroker.h"
12#include "log.h"
13#include "rendering/MatrixGL.h"
14#include "rendering/RenderSystem.h"
15#include "settings/AdvancedSettings.h"
16#include "settings/SettingsComponent.h"
17#include "utils/StringUtils.h"
18
19#include <map>
20#include <utility>
21
22namespace
23{
24
25#define X(VAL) std::make_pair(VAL, #VAL)
26std::map<GLenum, const char*> glErrors =
27{
28 // please keep attributes in accordance to:
29 // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetError.xhtml
30 X(GL_NO_ERROR),
31 X(GL_INVALID_ENUM),
32 X(GL_INVALID_VALUE),
33 X(GL_INVALID_OPERATION),
34 X(GL_INVALID_FRAMEBUFFER_OPERATION),
35 X(GL_OUT_OF_MEMORY),
36#if defined(HAS_GL)
37 X(GL_STACK_UNDERFLOW),
38 X(GL_STACK_OVERFLOW),
39#endif
40};
41
42std::map<GLenum, const char*> glErrorSource =
43{
44//! @todo remove TARGET_RASPBERRY_PI when Raspberry Pi updates their GL headers
45#if defined(HAS_GLES) && defined(TARGET_LINUX) && !defined(TARGET_RASPBERRY_PI)
46 X(GL_DEBUG_SOURCE_API_KHR),
47 X(GL_DEBUG_SOURCE_WINDOW_SYSTEM_KHR),
48 X(GL_DEBUG_SOURCE_SHADER_COMPILER_KHR),
49 X(GL_DEBUG_SOURCE_THIRD_PARTY_KHR),
50 X(GL_DEBUG_SOURCE_APPLICATION_KHR),
51 X(GL_DEBUG_SOURCE_OTHER_KHR),
52#endif
53};
54
55std::map<GLenum, const char*> glErrorType =
56{
57//! @todo remove TARGET_RASPBERRY_PI when Raspberry Pi updates their GL headers
58#if defined(HAS_GLES) && defined(TARGET_LINUX) && !defined(TARGET_RASPBERRY_PI)
59 X(GL_DEBUG_TYPE_ERROR_KHR),
60 X(GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR),
61 X(GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR),
62 X(GL_DEBUG_TYPE_PORTABILITY_KHR),
63 X(GL_DEBUG_TYPE_PERFORMANCE_KHR),
64 X(GL_DEBUG_TYPE_OTHER_KHR),
65 X(GL_DEBUG_TYPE_MARKER_KHR),
66#endif
67};
68
69std::map<GLenum, const char*> glErrorSeverity =
70{
71//! @todo remove TARGET_RASPBERRY_PI when Raspberry Pi updates their GL headers
72#if defined(HAS_GLES) && defined(TARGET_LINUX) && !defined(TARGET_RASPBERRY_PI)
73 X(GL_DEBUG_SEVERITY_HIGH_KHR),
74 X(GL_DEBUG_SEVERITY_MEDIUM_KHR),
75 X(GL_DEBUG_SEVERITY_LOW_KHR),
76 X(GL_DEBUG_SEVERITY_NOTIFICATION_KHR),
77#endif
78};
79#undef X
80
81} // namespace
82
83void KODI::UTILS::GL::GlErrorCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
84{
85 std::string sourceStr;
86 std::string typeStr;
87 std::string severityStr;
88
89 auto glSource = glErrorSource.find(source);
90 if (glSource != glErrorSource.end())
91 {
92 sourceStr = glSource->second;
93 }
94
95 auto glType = glErrorType.find(type);
96 if (glType != glErrorType.end())
97 {
98 typeStr = glType->second;
99 }
100
101 auto glSeverity = glErrorSeverity.find(severity);
102 if (glSeverity != glErrorSeverity.end())
103 {
104 severityStr = glSeverity->second;
105 }
106
107 CLog::Log(LOGDEBUG, "OpenGL(ES) Debugging:\nSource: {}\nType: {}\nSeverity: {}\nID: {}\nMessage: {}", sourceStr, typeStr, severityStr, id, message);
108}
109
110static void PrintMatrix(const GLfloat* matrix, std::string matrixName)
111{
112 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}",
113 matrixName,
114 matrix[0], matrix[1], matrix[2], matrix[3],
115 matrix[4], matrix[5], matrix[6], matrix[7],
116 matrix[8], matrix[9], matrix[10], matrix[11],
117 matrix[12], matrix[13], matrix[14], matrix[15]);
118}
119
120void _VerifyGLState(const char* szfile, const char* szfunction, int lineno)
121{
122 GLenum err = glGetError();
123 if (err == GL_NO_ERROR)
124 {
125 return;
126 }
127
128 auto error = glErrors.find(err);
129 if (error != glErrors.end())
130 {
131 CLog::Log(LOGERROR, "GL(ES) ERROR: {}", error->second);
132 }
133
134 if (szfile && szfunction)
135 {
136 CLog::Log(LOGERROR, "In file: {} function: {} line: {}", szfile, szfunction, lineno);
137 }
138
139 GLboolean scissors;
140 glGetBooleanv(GL_SCISSOR_TEST, &scissors);
141 CLog::Log(LOGDEBUG, "Scissor test enabled: {}", scissors == GL_TRUE ? "True" : "False");
142
143 GLfloat matrix[16];
144 glGetFloatv(GL_SCISSOR_BOX, matrix);
145 CLog::Log(LOGDEBUG, "Scissor box: {}, {}, {}, {}", matrix[0], matrix[1], matrix[2], matrix[3]);
146
147 glGetFloatv(GL_VIEWPORT, matrix);
148 CLog::Log(LOGDEBUG, "Viewport: {}, {}, {}, {}", matrix[0], matrix[1], matrix[2], matrix[3]);
149
150 PrintMatrix(glMatrixProject.Get(), "Projection Matrix");
151 PrintMatrix(glMatrixModview.Get(), "Modelview Matrix");
152}
153
154void LogGraphicsInfo()
155{
156#if defined(HAS_GL) || defined(HAS_GLES)
157 const GLubyte *s;
158
159 s = glGetString(GL_VENDOR);
160 if (s)
161 CLog::Log(LOGINFO, "GL_VENDOR = %s", s);
162 else
163 CLog::Log(LOGINFO, "GL_VENDOR = NULL");
164
165 s = glGetString(GL_RENDERER);
166 if (s)
167 CLog::Log(LOGINFO, "GL_RENDERER = %s", s);
168 else
169 CLog::Log(LOGINFO, "GL_RENDERER = NULL");
170
171 s = glGetString(GL_VERSION);
172 if (s)
173 CLog::Log(LOGINFO, "GL_VERSION = %s", s);
174 else
175 CLog::Log(LOGINFO, "GL_VERSION = NULL");
176
177 s = glGetString(GL_SHADING_LANGUAGE_VERSION);
178 if (s)
179 CLog::Log(LOGINFO, "GL_SHADING_LANGUAGE_VERSION = %s", s);
180 else
181 CLog::Log(LOGINFO, "GL_SHADING_LANGUAGE_VERSION = NULL");
182
183 //GL_NVX_gpu_memory_info extension
184#define GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX 0x9047
185#define GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX 0x9048
186#define GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX 0x9049
187#define GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX 0x904A
188#define GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX 0x904B
189
190 if (CServiceBroker::GetRenderSystem()->IsExtSupported("GL_NVX_gpu_memory_info"))
191 {
192 GLint mem = 0;
193
194 glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &mem);
195 CLog::Log(LOGINFO, "GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX = %i", mem);
196
197 //this seems to be the amount of ram on the videocard
198 glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &mem);
199 CLog::Log(LOGINFO, "GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX = %i", mem);
200 }
201
202 std::string extensions;
203#if defined(HAS_GL)
204 unsigned int renderVersionMajor, renderVersionMinor;
205 CServiceBroker::GetRenderSystem()->GetRenderVersion(renderVersionMajor, renderVersionMinor);
206 if (renderVersionMajor > 3 ||
207 (renderVersionMajor == 3 && renderVersionMinor >= 2))
208 {
209 GLint n;
210 glGetIntegerv(GL_NUM_EXTENSIONS, &n);
211 if (n > 0)
212 {
213 GLint i;
214 for (i = 0; i < n; i++)
215 {
216 extensions += (const char*)glGetStringi(GL_EXTENSIONS, i);
217 extensions += " ";
218 }
219 }
220 }
221 else
222#endif
223 {
224 extensions += (const char*) glGetString(GL_EXTENSIONS);
225 }
226
227 if (!extensions.empty())
228 CLog::Log(LOGINFO, "GL_EXTENSIONS = %s", extensions.c_str());
229 else
230 CLog::Log(LOGINFO, "GL_EXTENSIONS = NULL");
231
232
233#else /* !HAS_GL */
234 CLog::Log(LOGINFO, "Please define LogGraphicsInfo for your chosen graphics library");
235#endif /* !HAS_GL */
236}
237
238int glFormatElementByteCount(GLenum format)
239{
240 switch (format)
241 {
242#ifdef HAS_GL
243 case GL_BGRA:
244 return 4;
245 case GL_RED:
246 return 1;
247 case GL_GREEN:
248 return 1;
249 case GL_RG:
250 return 2;
251 case GL_BGR:
252 return 3;
253#endif
254 case GL_RGBA:
255 return 4;
256 case GL_RGB:
257 return 3;
258 case GL_LUMINANCE_ALPHA:
259 return 2;
260 case GL_LUMINANCE:
261 case GL_ALPHA:
262 return 1;
263 default:
264 CLog::Log(LOGERROR, "glFormatElementByteCount - Unknown format %u", format);
265 return 1;
266 }
267}
diff --git a/xbmc/utils/GLUtils.h b/xbmc/utils/GLUtils.h
new file mode 100644
index 0000000..2dea067
--- /dev/null
+++ b/xbmc/utils/GLUtils.h
@@ -0,0 +1,46 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11// GL Error checking macro
12// this function is useful for tracking down GL errors, which otherwise
13// just result in undefined behavior and can be difficult to track down.
14//
15// Just call it 'VerifyGLState()' after a sequence of GL calls
16//
17// if GL_DEBUGGING and HAS_GL are defined, the function checks
18// for GL errors and prints the current state of the various matrices;
19// if not it's just an empty inline stub, and thus won't affect performance
20// and will be optimized out.
21
22#include "system_gl.h"
23
24namespace KODI
25{
26namespace UTILS
27{
28namespace GL
29{
30
31void GlErrorCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam);
32
33}
34}
35}
36
37void _VerifyGLState(const char* szfile, const char* szfunction, int lineno);
38#if defined(GL_DEBUGGING) && (defined(HAS_GL) || defined(HAS_GLES))
39#define VerifyGLState() _VerifyGLState(__FILE__, __FUNCTION__, __LINE__)
40#else
41#define VerifyGLState()
42#endif
43
44void LogGraphicsInfo();
45
46int glFormatElementByteCount(GLenum format);
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#ifdef __GNUC__
12// under gcc, inline will only take place if optimizations are applied (-O). this will force inline even with optimizations.
13#define XBMC_FORCE_INLINE __attribute__((always_inline))
14#else
15#define XBMC_FORCE_INLINE
16#endif
17
18#include <algorithm>
19#include <stdexcept>
20#include <vector>
21
22template <typename T> class CPointGen
23{
24public:
25 typedef CPointGen<T> this_type;
26
27 CPointGen() noexcept = default;
28
29 constexpr CPointGen(T a, T b)
30 : x{a}, y{b}
31 {}
32
33 template<class U> explicit constexpr CPointGen(const CPointGen<U>& rhs)
34 : x{static_cast<T> (rhs.x)}, y{static_cast<T> (rhs.y)}
35 {}
36
37 constexpr this_type operator+(const this_type &point) const
38 {
39 return {x + point.x, y + point.y};
40 };
41
42 this_type& operator+=(const this_type &point)
43 {
44 x += point.x;
45 y += point.y;
46 return *this;
47 };
48
49 constexpr this_type operator-(const this_type &point) const
50 {
51 return {x - point.x, y - point.y};
52 };
53
54 this_type& operator-=(const this_type &point)
55 {
56 x -= point.x;
57 y -= point.y;
58 return *this;
59 };
60
61 constexpr this_type operator*(T factor) const
62 {
63 return {x * factor, y * factor};
64 }
65
66 this_type& operator*=(T factor)
67 {
68 x *= factor;
69 y *= factor;
70 return *this;
71 }
72
73 constexpr this_type operator/(T factor) const
74 {
75 return {x / factor, y / factor};
76 }
77
78 this_type& operator/=(T factor)
79 {
80 x /= factor;
81 y /= factor;
82 return *this;
83 }
84
85 T x{}, y{};
86};
87
88template<typename T>
89constexpr bool operator==(const CPointGen<T> &point1, const CPointGen<T> &point2) noexcept
90{
91 return (point1.x == point2.x && point1.y == point2.y);
92}
93
94template<typename T>
95constexpr bool operator!=(const CPointGen<T> &point1, const CPointGen<T> &point2) noexcept
96{
97 return !(point1 == point2);
98}
99
100using CPoint = CPointGen<float>;
101using CPointInt = CPointGen<int>;
102
103
104/**
105 * Generic two-dimensional size representation
106 *
107 * Class invariant: width and height are both non-negative
108 * Throws std::out_of_range if invariant would be violated. The class
109 * is exception-safe. If modification would violate the invariant, the size
110 * is not changed.
111 */
112template <typename T> class CSizeGen
113{
114 T m_w{}, m_h{};
115
116 void CheckSet(T width, T height)
117 {
118 if (width < 0)
119 {
120 throw std::out_of_range("Size may not have negative width");
121 }
122 if (height < 0)
123 {
124 throw std::out_of_range("Size may not have negative height");
125 }
126 m_w = width;
127 m_h = height;
128 }
129
130public:
131 typedef CSizeGen<T> this_type;
132
133 CSizeGen() noexcept = default;
134
135 CSizeGen(T width, T height)
136 {
137 CheckSet(width, height);
138 }
139
140 T Width() const
141 {
142 return m_w;
143 }
144
145 T Height() const
146 {
147 return m_h;
148 }
149
150 void SetWidth(T width)
151 {
152 CheckSet(width, m_h);
153 }
154
155 void SetHeight(T height)
156 {
157 CheckSet(m_w, height);
158 }
159
160 void Set(T width, T height)
161 {
162 CheckSet(width, height);
163 }
164
165 bool IsZero() const
166 {
167 return (m_w == static_cast<T> (0) && m_h == static_cast<T> (0));
168 }
169
170 T Area() const
171 {
172 return m_w * m_h;
173 }
174
175 CPointGen<T> ToPoint() const
176 {
177 return {m_w, m_h};
178 }
179
180 template<class U> explicit CSizeGen<T>(const CSizeGen<U>& rhs)
181 {
182 CheckSet(static_cast<T> (rhs.m_w), static_cast<T> (rhs.m_h));
183 }
184
185 this_type operator+(const this_type& size) const
186 {
187 return {m_w + size.m_w, m_h + size.m_h};
188 };
189
190 this_type& operator+=(const this_type& size)
191 {
192 CheckSet(m_w + size.m_w, m_h + size.m_h);
193 return *this;
194 };
195
196 this_type operator-(const this_type& size) const
197 {
198 return {m_w - size.m_w, m_h - size.m_h};
199 };
200
201 this_type& operator-=(const this_type& size)
202 {
203 CheckSet(m_w - size.m_w, m_h - size.m_h);
204 return *this;
205 };
206
207 this_type operator*(T factor) const
208 {
209 return {m_w * factor, m_h * factor};
210 }
211
212 this_type& operator*=(T factor)
213 {
214 CheckSet(m_w * factor, m_h * factor);
215 return *this;
216 }
217
218 this_type operator/(T factor) const
219 {
220 return {m_w / factor, m_h / factor};
221 }
222
223 this_type& operator/=(T factor)
224 {
225 CheckSet(m_w / factor, m_h / factor);
226 return *this;
227 }
228};
229
230template<typename T>
231inline bool operator==(const CSizeGen<T>& size1, const CSizeGen<T>& size2) noexcept
232{
233 return (size1.Width() == size2.Width() && size1.Height() == size2.Height());
234}
235
236template<typename T>
237inline bool operator!=(const CSizeGen<T>& size1, const CSizeGen<T>& size2) noexcept
238{
239 return !(size1 == size2);
240}
241
242using CSize = CSizeGen<float>;
243using CSizeInt = CSizeGen<int>;
244
245
246template <typename T> class CRectGen
247{
248public:
249 typedef CRectGen<T> this_type;
250 typedef CPointGen<T> point_type;
251 typedef CSizeGen<T> size_type;
252
253 CRectGen() noexcept = default;
254
255 constexpr CRectGen(T left, T top, T right, T bottom)
256 : x1{left}, y1{top}, x2{right}, y2{bottom}
257 {}
258
259 constexpr CRectGen(const point_type &p1, const point_type &p2)
260 : x1{p1.x}, y1{p1.y}, x2{p2.x}, y2{p2.y}
261 {}
262
263 constexpr CRectGen(const point_type &origin, const size_type &size)
264 : x1{origin.x}, y1{origin.y}, x2{x1 + size.Width()}, y2{y1 + size.Height()}
265 {}
266
267 template<class U> explicit constexpr CRectGen(const CRectGen<U>& rhs)
268 : x1{static_cast<T> (rhs.x1)}, y1{static_cast<T> (rhs.y1)}, x2{static_cast<T> (rhs.x2)}, y2{static_cast<T> (rhs.y2)}
269 {}
270
271 void SetRect(T left, T top, T right, T bottom)
272 {
273 x1 = left;
274 y1 = top;
275 x2 = right;
276 y2 = bottom;
277 }
278
279 constexpr bool PtInRect(const point_type &point) const
280 {
281 return (x1 <= point.x && point.x <= x2 && y1 <= point.y && point.y <= y2);
282 };
283
284 this_type& operator-=(const point_type &point) XBMC_FORCE_INLINE
285 {
286 x1 -= point.x;
287 y1 -= point.y;
288 x2 -= point.x;
289 y2 -= point.y;
290 return *this;
291 };
292
293 constexpr this_type operator-(const point_type &point) const
294 {
295 return {x1 - point.x, y1 - point.y, x2 - point.x, y2 - point.y};
296 }
297
298 this_type& operator+=(const point_type &point) XBMC_FORCE_INLINE
299 {
300 x1 += point.x;
301 y1 += point.y;
302 x2 += point.x;
303 y2 += point.y;
304 return *this;
305 };
306
307 constexpr this_type operator+(const point_type &point) const
308 {
309 return {x1 + point.x, y1 + point.y, x2 + point.x, y2 + point.y};
310 }
311
312 this_type& operator-=(const size_type &size)
313 {
314 x2 -= size.Width();
315 y2 -= size.Height();
316 return *this;
317 };
318
319 constexpr this_type operator-(const size_type &size) const
320 {
321 return {x1, y1, x2 - size.Width(), y2 - size.Height()};
322 }
323
324 this_type& operator+=(const size_type &size)
325 {
326 x2 += size.Width();
327 y2 += size.Height();
328 return *this;
329 };
330
331 constexpr this_type operator+(const size_type &size) const
332 {
333 return {x1, y1, x2 + size.Width(), y2 + size.Height()};
334 }
335
336 this_type& Intersect(const this_type &rect)
337 {
338 x1 = clamp_range(x1, rect.x1, rect.x2);
339 x2 = clamp_range(x2, rect.x1, rect.x2);
340 y1 = clamp_range(y1, rect.y1, rect.y2);
341 y2 = clamp_range(y2, rect.y1, rect.y2);
342 return *this;
343 };
344
345 this_type& Union(const this_type &rect)
346 {
347 if (IsEmpty())
348 *this = rect;
349 else if (!rect.IsEmpty())
350 {
351 x1 = std::min(x1,rect.x1);
352 y1 = std::min(y1,rect.y1);
353
354 x2 = std::max(x2,rect.x2);
355 y2 = std::max(y2,rect.y2);
356 }
357
358 return *this;
359 };
360
361 constexpr bool IsEmpty() const XBMC_FORCE_INLINE
362 {
363 return (x2 - x1) * (y2 - y1) == 0;
364 };
365
366 constexpr point_type P1() const XBMC_FORCE_INLINE
367 {
368 return {x1, y1};
369 }
370
371 constexpr point_type P2() const XBMC_FORCE_INLINE
372 {
373 return {x2, y2};
374 }
375
376 constexpr T Width() const XBMC_FORCE_INLINE
377 {
378 return x2 - x1;
379 };
380
381 constexpr T Height() const XBMC_FORCE_INLINE
382 {
383 return y2 - y1;
384 };
385
386 constexpr T Area() const XBMC_FORCE_INLINE
387 {
388 return Width() * Height();
389 };
390
391 size_type ToSize() const
392 {
393 return {Width(), Height()};
394 };
395
396 std::vector<this_type> SubtractRect(this_type splitterRect)
397 {
398 std::vector<this_type> newRectanglesList;
399 this_type intersection = splitterRect.Intersect(*this);
400
401 if (!intersection.IsEmpty())
402 {
403 this_type add;
404
405 // add rect above intersection if not empty
406 add = this_type(x1, y1, x2, intersection.y1);
407 if (!add.IsEmpty())
408 newRectanglesList.push_back(add);
409
410 // add rect below intersection if not empty
411 add = this_type(x1, intersection.y2, x2, y2);
412 if (!add.IsEmpty())
413 newRectanglesList.push_back(add);
414
415 // add rect left intersection if not empty
416 add = this_type(x1, intersection.y1, intersection.x1, intersection.y2);
417 if (!add.IsEmpty())
418 newRectanglesList.push_back(add);
419
420 // add rect right intersection if not empty
421 add = this_type(intersection.x2, intersection.y1, x2, intersection.y2);
422 if (!add.IsEmpty())
423 newRectanglesList.push_back(add);
424 }
425 else
426 {
427 newRectanglesList.push_back(*this);
428 }
429
430 return newRectanglesList;
431 }
432
433 std::vector<this_type> SubtractRects(std::vector<this_type> intersectionList)
434 {
435 std::vector<this_type> fragmentsList;
436 fragmentsList.push_back(*this);
437
438 for (typename std::vector<this_type>::iterator splitter = intersectionList.begin(); splitter != intersectionList.end(); ++splitter)
439 {
440 typename std::vector<this_type> toAddList;
441
442 for (typename std::vector<this_type>::iterator fragment = fragmentsList.begin(); fragment != fragmentsList.end(); ++fragment)
443 {
444 std::vector<this_type> newFragmentsList = fragment->SubtractRect(*splitter);
445 toAddList.insert(toAddList.end(), newFragmentsList.begin(), newFragmentsList.end());
446 }
447
448 fragmentsList.clear();
449 fragmentsList.insert(fragmentsList.end(), toAddList.begin(), toAddList.end());
450 }
451
452 return fragmentsList;
453 }
454
455 void GetQuad(point_type (&points)[4])
456 {
457 points[0] = { x1, y1 };
458 points[1] = { x2, y1 };
459 points[2] = { x2, y2 };
460 points[3] = { x1, y2 };
461 }
462
463 T x1{}, y1{}, x2{}, y2{};
464private:
465 static constexpr T clamp_range(T x, T l, T h) XBMC_FORCE_INLINE
466 {
467 return (x > h) ? h : ((x < l) ? l : x);
468 }
469};
470
471template<typename T>
472constexpr bool operator==(const CRectGen<T> &rect1, const CRectGen<T> &rect2) noexcept
473{
474 return (rect1.x1 == rect2.x1 && rect1.y1 == rect2.y1 && rect1.x2 == rect2.x2 && rect1.y2 == rect2.y2);
475}
476
477template<typename T>
478constexpr bool operator!=(const CRectGen<T> &rect1, const CRectGen<T> &rect2) noexcept
479{
480 return !(rect1 == rect2);
481}
482
483using CRect = CRectGen<float>;
484using 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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <memory>
12
13/**
14 * This file contains the pattern for moving "globals" from the BSS Segment to the heap.
15 * A note on usage of this pattern for globals replacement:
16 *
17 * This pattern uses a singleton pattern and some compiler/C preprocessor sugar to allow
18 * "global" variables to be lazy instantiated and initialized and moved from the BSS segment
19 * to the heap (that is, they are instantiated on the heap when they are first used rather
20 * than relying on the startup code to initialize the BSS segment). This eliminates the
21 * problem associated with global variable dependencies across compilation units.
22 *
23 * Reference counting from the BSS segment is used to destruct these globals at the time the
24 * last compilation unit that knows about it is finalized by the post-main shutdown. The book
25 * keeping is done by smuggling a smart pointer into every file that references a particular
26 * "global class" through the use of a 'static' declaration of an instance of that smart
27 * pointer in the header file of the global class (did you ever think you'd see a file scope
28 * 'static' variable in a header file - on purpose?)
29 *
30 * There are two different ways to use this pattern when replacing global variables.
31 * The selection of which one to use depends on whether or not there is a possibility
32 * that the code in the .cpp file for the global can be executed from a static method
33 * somewhere. This may take some explanation.
34 *
35 * The (at least) two ways to do this:
36 *
37 * 1) You can use the reference object std::shared_ptr to access the global variable.
38 *
39 * This would be the preferred means since it is (very slightly) more efficient than
40 * the alternative. To use this pattern you replace standard static references to
41 * the global with access through the reference. If you use the C preprocessor to
42 * do this for you can put the following code in the header file where the global's
43 * class is declared:
44 *
45 * static std::shared_ptr<GlobalVariableClass> g_globalVariableRef(xbmcutil::GlobalsSingleton<GlobalVariableClass>::getInstance());
46 * #define g_globalVariable (*(g_globalVariableRef.get()))
47 *
48 * Note what this does. In every file that includes this header there will be a *static*
49 * instance of the std::shared_ptr<GlobalVariableClass> smart pointer. This effectively
50 * reference counts the singleton from every compilation unit (ie, object code file that
51 * results from a compilation of a .c/.cpp file) that references this global directly.
52 *
53 * There is a problem with this, however. Keep in mind that the instance of the smart pointer
54 * (being in the BSS segment of the compilation unit) is ITSELF an object that depends on
55 * the BSS segment initialization in order to be initialized with an instance from the
56 * singleton. That means, depending on the code structure, it is possible to get into a
57 * circumstance where the above #define could be exercised PRIOR TO the setting of the
58 * value of the smart pointer.
59 *
60 * Some reflection on this should lead you to the conclusion that the only way for this to
61 * happen is if access to this global can take place through a static/global method, directly
62 * or indirectly (ie, the static/global method can call another method that uses the
63 * reference), where that static is called from initialization code exercised prior to
64 * the start of 'main.'
65 *
66 * Because of the "indirectly" in the above statement, this situation can be difficult to
67 * determine beforehand.
68 *
69 * 2) Alternatively, when you KNOW that the global variable can suffer from the above described
70 * problem, you can restrict all access to the variable to the singleton by changing
71 * the #define to:
72 *
73 * #define g_globalVariable (*(xbmcutil::Singleton<GlobalVariableClass>::getInstance()))
74 *
75 * A few things to note about this. First, this separates the reference counting aspect
76 * from the access aspect of this solution. The smart pointers are no longer used for
77 * access, only for reference counting. Secondly, all access is through the singleton directly
78 * so there is no reliance on the state of the BSS segment for the code to operate
79 * correctly.
80 *
81 * This solution is required for g_Windowing because it's accessed (both directly and
82 * indirectly) from the static methods of CLog which are called repeatedly from
83 * code exercised during the initialization of the BSS segment.
84 */
85
86namespace xbmcutil
87{
88 /**
89 * This class is an implementation detail of the macros defined below and
90 * is NOT meant to be used as a general purpose utility. IOW, DO NOT USE THIS
91 * CLASS to support a general singleton design pattern, it's specialized
92 * for solving the initialization/finalization order/dependency problem
93 * with global variables and should only be used via the macros below.
94 *
95 * Currently THIS IS NOT THREAD SAFE! Why not just add a lock you ask?
96 * Because this singleton is used to initialize global variables and
97 * there is an issue with having the lock used prior to its
98 * initialization. No matter what, if this class is used as a replacement
99 * for global variables there's going to be a race condition if it's used
100 * anywhere else. So currently this is the only prescribed use.
101 *
102 * Therefore this hack depends on the fact that compilation unit global/static
103 * initialization is done in a single thread.
104 */
105 template <class T> class GlobalsSingleton
106 {
107 /**
108 * This thing just deletes the shared_ptr when the 'instance'
109 * goes out of scope (when the bss segment of the compilation unit
110 * that 'instance' is sitting in is deinitialized). See the comment
111 * on 'instance' for more information.
112 */
113 template <class K> class Deleter
114 {
115 public:
116 K* guarded;
117 ~Deleter() { if (guarded) delete guarded; }
118 };
119
120 /**
121 * Is it possible that getInstance can be called prior to the shared_ptr 'instance'
122 * being initialized as a global? If so, then the shared_ptr constructor would
123 * effectively 'reset' the shared pointer after it had been set by the prior
124 * getInstance call, and a second instance would be created. We really don't
125 * want this to happen so 'instance' is a pointer to a smart pointer so that
126 * we can deterministically handle its construction. It is guarded by the
127 * Deleter class above so that when the bss segment that this static is
128 * sitting in is deinitialized, the shared_ptr pointer will be cleaned up.
129 */
130 static Deleter<std::shared_ptr<T> > instance;
131
132 /**
133 * See 'getQuick' below.
134 */
135 static T* quick;
136 public:
137
138 /**
139 * Retrieve an instance of the singleton using a shared pointer for
140 * reference counting.
141 */
142 inline static std::shared_ptr<T> getInstance()
143 {
144 if (!instance.guarded)
145 {
146 if (!quick)
147 quick = new T;
148 instance.guarded = new std::shared_ptr<T>(quick);
149 }
150 return *(instance.guarded);
151 }
152
153 /**
154 * This is for quick access when using form (2) of the pattern. Before 'mdd' points
155 * it out, this might be a case of 'solving problems we don't have' but this access
156 * is used frequently within the event loop so any help here should benefit the
157 * overall performance and there is nothing complicated or tricky here and not
158 * a lot of code to maintain.
159 */
160 inline static T* getQuick()
161 {
162 if (!quick)
163 quick = new T;
164
165 return quick;
166 }
167
168 };
169
170 template <class T> typename GlobalsSingleton<T>::template Deleter<std::shared_ptr<T> > GlobalsSingleton<T>::instance;
171 template <class T> T* GlobalsSingleton<T>::quick;
172
173 /**
174 * This is another bit of hackery that will act as a flag for
175 * whether or not a global/static has been initialized yet. An instance
176 * should be placed in the cpp file after the static/global it's meant to
177 * monitor.
178 */
179 class InitFlag { public: explicit InitFlag(bool& flag) { flag = true; } };
180}
181
182/**
183 * For pattern (2) above, you can use the following macro. This pattern is safe to
184 * use in all cases but may be very slightly less efficient.
185 *
186 * Also, you must also use a #define to replace the actual global variable since
187 * there's no way to use a macro to add a #define. An example would be:
188 *
189 * XBMC_GLOBAL_REF(CWinSystemWin32DX, g_Windowing);
190 * #define g_Windowing XBMC_GLOBAL_USE(CWinSystemWin32DX)
191 *
192 */
193#define XBMC_GLOBAL_REF(classname,g_variable) \
194 static std::shared_ptr<classname> g_variable##Ref(xbmcutil::GlobalsSingleton<classname>::getInstance())
195
196/**
197 * This declares the actual use of the variable. It needs to be used in another #define
198 * of the form:
199 *
200 * #define g_variable XBMC_GLOBAL_USE(classname)
201 */
202#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..340c11a
--- /dev/null
+++ b/xbmc/utils/GroupUtils.cpp
@@ -0,0 +1,157 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "GroupUtils.h"
10
11#include "FileItem.h"
12#include "filesystem/MultiPathDirectory.h"
13#include "utils/StringUtils.h"
14#include "utils/URIUtils.h"
15#include "video/VideoDbUrl.h"
16#include "video/VideoInfoTag.h"
17
18#include <map>
19#include <set>
20
21using SetMap = std::map<int, std::set<CFileItemPtr> >;
22
23bool GroupUtils::Group(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItems, GroupAttribute groupAttributes /* = GroupAttributeNone */)
24{
25 CFileItemList ungroupedItems;
26 return Group(groupBy, baseDir, items, groupedItems, ungroupedItems, groupAttributes);
27}
28
29bool GroupUtils::Group(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItems, CFileItemList &ungroupedItems, GroupAttribute groupAttributes /* = GroupAttributeNone */)
30{
31 if (groupBy == GroupByNone)
32 return false;
33
34 // nothing to do if there are no items to group
35 if (items.Size() <= 0)
36 return true;
37
38 SetMap setMap;
39 for (int index = 0; index < items.Size(); index++)
40 {
41 bool ungrouped = true;
42 const CFileItemPtr item = items.Get(index);
43
44 // group by sets
45 if ((groupBy & GroupBySet) &&
46 item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_set.id > 0)
47 {
48 ungrouped = false;
49 setMap[item->GetVideoInfoTag()->m_set.id].insert(item);
50 }
51
52 if (ungrouped)
53 ungroupedItems.Add(item);
54 }
55
56 if ((groupBy & GroupBySet) && !setMap.empty())
57 {
58 CVideoDbUrl itemsUrl;
59 if (!itemsUrl.FromString(baseDir))
60 return false;
61
62 for (SetMap::const_iterator set = setMap.begin(); set != setMap.end(); ++set)
63 {
64 // only one item in the set, so add it to the ungrouped items
65 if (set->second.size() == 1 && (groupAttributes & GroupAttributeIgnoreSingleItems))
66 {
67 ungroupedItems.Add(*set->second.begin());
68 continue;
69 }
70
71 CFileItemPtr pItem(new CFileItem((*set->second.begin())->GetVideoInfoTag()->m_set.title));
72 pItem->GetVideoInfoTag()->m_iDbId = set->first;
73 pItem->GetVideoInfoTag()->m_type = MediaTypeVideoCollection;
74
75 std::string basePath = StringUtils::Format("videodb://movies/sets/%i/", set->first);
76 CVideoDbUrl videoUrl;
77 if (!videoUrl.FromString(basePath))
78 pItem->SetPath(basePath);
79 else
80 {
81 videoUrl.AddOptions((*set->second.begin())->GetURL().GetOptions());
82 pItem->SetPath(videoUrl.ToString());
83 }
84 pItem->m_bIsFolder = true;
85
86 CVideoInfoTag* setInfo = pItem->GetVideoInfoTag();
87 setInfo->m_strPath = pItem->GetPath();
88 setInfo->m_strTitle = pItem->GetLabel();
89 setInfo->m_strPlot = (*set->second.begin())->GetVideoInfoTag()->m_set.overview;
90
91 int ratings = 0;
92 float totalRatings = 0;
93 int iWatched = 0; // have all the movies been played at least once?
94 std::set<std::string> pathSet;
95 for (std::set<CFileItemPtr>::const_iterator movie = set->second.begin(); movie != set->second.end(); ++movie)
96 {
97 CVideoInfoTag* movieInfo = (*movie)->GetVideoInfoTag();
98 // handle rating
99 if (movieInfo->GetRating().rating > 0.0f)
100 {
101 ratings++;
102 totalRatings += movieInfo->GetRating().rating;
103 }
104
105 // handle year
106 if (movieInfo->GetYear() > setInfo->GetYear())
107 setInfo->SetYear(movieInfo->GetYear());
108
109 // handle lastplayed
110 if (movieInfo->m_lastPlayed.IsValid() && movieInfo->m_lastPlayed > setInfo->m_lastPlayed)
111 setInfo->m_lastPlayed = movieInfo->m_lastPlayed;
112
113 // handle dateadded
114 if (movieInfo->m_dateAdded.IsValid() && movieInfo->m_dateAdded > setInfo->m_dateAdded)
115 setInfo->m_dateAdded = movieInfo->m_dateAdded;
116
117 // handle playcount/watched
118 setInfo->SetPlayCount(setInfo->GetPlayCount() + movieInfo->GetPlayCount());
119 if (movieInfo->GetPlayCount() > 0)
120 iWatched++;
121
122 //accumulate the path for a multipath construction
123 CFileItem video(movieInfo->m_basePath, false);
124 if (video.IsVideo())
125 pathSet.insert(URIUtils::GetParentPath(movieInfo->m_basePath));
126 else
127 pathSet.insert(movieInfo->m_basePath);
128 }
129 setInfo->m_basePath = XFILE::CMultiPathDirectory::ConstructMultiPath(pathSet);
130
131 if (ratings > 1)
132 pItem->GetVideoInfoTag()->SetRating(totalRatings / ratings);
133
134 setInfo->SetPlayCount(iWatched >= static_cast<int>(set->second.size()) ? (setInfo->GetPlayCount() / set->second.size()) : 0);
135 pItem->SetProperty("total", (int)set->second.size());
136 pItem->SetProperty("watched", iWatched);
137 pItem->SetProperty("unwatched", (int)set->second.size() - iWatched);
138 pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, setInfo->GetPlayCount() > 0);
139
140 groupedItems.Add(pItem);
141 }
142 }
143
144 return true;
145}
146
147bool GroupUtils::GroupAndMix(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItemsMixed, GroupAttribute groupAttributes /* = GroupAttributeNone */)
148{
149 CFileItemList ungroupedItems;
150 if (!Group(groupBy, baseDir, items, groupedItemsMixed, ungroupedItems, groupAttributes))
151 return false;
152
153 // add all the ungrouped items as well
154 groupedItemsMixed.Append(ungroupedItems);
155
156 return true;
157}
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 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13class CFileItemList;
14
15// can be used as a flag
16typedef enum {
17 GroupByNone = 0x0,
18 GroupBySet = 0x1
19} GroupBy;
20
21typedef enum {
22 GroupAttributeNone = 0x0,
23 GroupAttributeIgnoreSingleItems = 0x1
24} GroupAttribute;
25
26class GroupUtils
27{
28public:
29 static bool Group(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItems, GroupAttribute groupAttributes = GroupAttributeNone);
30 static bool Group(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItems, CFileItemList &ungroupedItems, GroupAttribute groupAttributes = GroupAttributeNone);
31 static bool GroupAndMix(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItemsMixed, GroupAttribute groupAttributes = GroupAttributeNone);
32};
diff --git a/xbmc/utils/HTMLUtil.cpp b/xbmc/utils/HTMLUtil.cpp
new file mode 100644
index 0000000..dbe1843
--- /dev/null
+++ b/xbmc/utils/HTMLUtil.cpp
@@ -0,0 +1,229 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "HTMLUtil.h"
10
11#include "utils/StringUtils.h"
12
13#include <wctype.h>
14
15using namespace HTML;
16
17CHTMLUtil::CHTMLUtil(void) = default;
18
19CHTMLUtil::~CHTMLUtil(void) = default;
20
21void CHTMLUtil::RemoveTags(std::string& strHTML)
22{
23 int iNested = 0;
24 std::string strReturn = "";
25 for (int i = 0; i < (int) strHTML.size(); ++i)
26 {
27 if (strHTML[i] == '<') iNested++;
28 else if (strHTML[i] == '>') iNested--;
29 else
30 {
31 if (!iNested)
32 {
33 strReturn += strHTML[i];
34 }
35 }
36 }
37
38 strHTML = strReturn;
39}
40
41typedef struct
42{
43 const wchar_t* html;
44 const wchar_t w;
45} HTMLMapping;
46
47static const HTMLMapping mappings[] =
48 {{L"&amp;", 0x0026},
49 {L"&apos;", 0x0027},
50 {L"&acute;", 0x00B4},
51 {L"&agrave;", 0x00E0},
52 {L"&aacute;", 0x00E1},
53 {L"&acirc;", 0x00E2},
54 {L"&atilde;", 0x00E3},
55 {L"&auml;", 0x00E4},
56 {L"&aring;", 0x00E5},
57 {L"&aelig;", 0x00E6},
58 {L"&Agrave;", 0x00C0},
59 {L"&Aacute;", 0x00C1},
60 {L"&Acirc;", 0x00C2},
61 {L"&Atilde;", 0x00C3},
62 {L"&Auml;", 0x00C4},
63 {L"&Aring;", 0x00C5},
64 {L"&AElig;", 0x00C6},
65 {L"&bdquo;", 0x201E},
66 {L"&brvbar;", 0x00A6},
67 {L"&bull;", 0x2022},
68 {L"&bullet;", 0x2022},
69 {L"&cent;", 0x00A2},
70 {L"&circ;", 0x02C6},
71 {L"&curren;", 0x00A4},
72 {L"&copy;", 0x00A9},
73 {L"&cedil;", 0x00B8},
74 {L"&Ccedil;", 0x00C7},
75 {L"&ccedil;", 0x00E7},
76 {L"&dagger;", 0x2020},
77 {L"&deg;", 0x00B0},
78 {L"&divide;", 0x00F7},
79 {L"&Dagger;", 0x2021},
80 {L"&egrave;", 0x00E8},
81 {L"&eacute;", 0x00E9},
82 {L"&ecirc;", 0x00EA},
83 {L"&emsp;", 0x2003},
84 {L"&ensp;", 0x2002},
85 {L"&euml;", 0x00EB},
86 {L"&eth;", 0x00F0},
87 {L"&euro;", 0x20AC},
88 {L"&Egrave;", 0x00C8},
89 {L"&Eacute;", 0x00C9},
90 {L"&Ecirc;", 0x00CA},
91 {L"&Euml;", 0x00CB},
92 {L"&ETH;", 0x00D0},
93 {L"&quot;", 0x0022},
94 {L"&frasl;", 0x2044},
95 {L"&frac14;", 0x00BC},
96 {L"&frac12;", 0x00BD},
97 {L"&frac34;", 0x00BE},
98 {L"&gt;", 0x003E},
99 {L"&hellip;", 0x2026},
100 {L"&iexcl;", 0x00A1},
101 {L"&iquest;", 0x00BF},
102 {L"&igrave;", 0x00EC},
103 {L"&iacute;", 0x00ED},
104 {L"&icirc;", 0x00EE},
105 {L"&iuml;", 0x00EF},
106 {L"&Igrave;", 0x00CC},
107 {L"&Iacute;", 0x00CD},
108 {L"&Icirc;", 0x00CE},
109 {L"&Iuml;", 0x00CF},
110 {L"&lrm;", 0x200E},
111 {L"&lt;", 0x003C},
112 {L"&laquo;", 0x00AB},
113 {L"&ldquo;", 0x201C},
114 {L"&lsaquo;", 0x2039},
115 {L"&lsquo;", 0x2018},
116 {L"&macr;", 0x00AF},
117 {L"&micro;", 0x00B5},
118 {L"&middot;", 0x00B7},
119 {L"&mdash;", 0x2014},
120 {L"&nbsp;", 0x00A0},
121 {L"&ndash;", 0x2013},
122 {L"&ntilde;", 0x00F1},
123 {L"&not;", 0x00AC},
124 {L"&Ntilde;", 0x00D1},
125 {L"&ordf;", 0x00AA},
126 {L"&ordm;", 0x00BA},
127 {L"&oelig;", 0x0153},
128 {L"&ograve;", 0x00F2},
129 {L"&oacute;", 0x00F3},
130 {L"&ocirc;", 0x00F4},
131 {L"&otilde;", 0x00F5},
132 {L"&ouml;", 0x00F6},
133 {L"&oslash;", 0x00F8},
134 {L"&OElig;", 0x0152},
135 {L"&Ograve;", 0x00D2},
136 {L"&Oacute;", 0x00D3},
137 {L"&Ocirc;", 0x00D4},
138 {L"&Otilde;", 0x00D5},
139 {L"&Ouml;", 0x00D6},
140 {L"&Oslash;", 0x00D8},
141 {L"&para;", 0x00B6},
142 {L"&permil;", 0x2030},
143 {L"&plusmn;", 0x00B1},
144 {L"&pound;", 0x00A3},
145 {L"&raquo;", 0x00BB},
146 {L"&rdquo;", 0x201D},
147 {L"&reg;", 0x00AE},
148 {L"&rlm;", 0x200F},
149 {L"&rsaquo;", 0x203A},
150 {L"&rsquo;", 0x2019},
151 {L"&sbquo;", 0x201A},
152 {L"&scaron;", 0x0161},
153 {L"&sect;", 0x00A7},
154 {L"&shy;", 0x00AD},
155 {L"&sup1;", 0x00B9},
156 {L"&sup2;", 0x00B2},
157 {L"&sup3;", 0x00B3},
158 {L"&szlig;", 0x00DF},
159 {L"&Scaron;", 0x0160},
160 {L"&thinsp;", 0x2009},
161 {L"&thorn;", 0x00FE},
162 {L"&tilde;", 0x02DC},
163 {L"&times;", 0x00D7},
164 {L"&trade;", 0x2122},
165 {L"&THORN;", 0x00DE},
166 {L"&uml;", 0x00A8},
167 {L"&ugrave;", 0x00F9},
168 {L"&uacute;", 0x00FA},
169 {L"&ucirc;", 0x00FB},
170 {L"&uuml;", 0x00FC},
171 {L"&Ugrave;", 0x00D9},
172 {L"&Uacute;", 0x00DA},
173 {L"&Ucirc;", 0x00DB},
174 {L"&Uuml;", 0x00DC},
175 {L"&yen;", 0x00A5},
176 {L"&yuml;", 0x00FF},
177 {L"&yacute;", 0x00FD},
178 {L"&Yacute;", 0x00DD},
179 {L"&Yuml;", 0x0178},
180 {L"&zwj;", 0x200D},
181 {L"&zwnj;", 0x200C},
182 {NULL, L'\0'}};
183
184void CHTMLUtil::ConvertHTMLToW(const std::wstring& strHTML, std::wstring& strStripped)
185{
186 //! @todo STRING_CLEANUP
187 if (strHTML.empty())
188 {
189 strStripped.clear();
190 return ;
191 }
192 size_t iPos = 0;
193 strStripped = strHTML;
194 while (mappings[iPos].html)
195 {
196 StringUtils::Replace(strStripped, mappings[iPos].html,std::wstring(1, mappings[iPos].w));
197 iPos++;
198 }
199
200 iPos = strStripped.find(L"&#");
201 while (iPos > 0 && iPos < strStripped.size() - 4)
202 {
203 size_t iStart = iPos + 1;
204 iPos += 2;
205 std::wstring num;
206 int base = 10;
207 if (strStripped[iPos] == L'x')
208 {
209 base = 16;
210 iPos++;
211 }
212
213 size_t i = iPos;
214 while (iPos < strStripped.size() &&
215 (base == 16 ? iswxdigit(strStripped[iPos]) : iswdigit(strStripped[iPos])))
216 iPos++;
217
218 num = strStripped.substr(i, iPos-i);
219 wchar_t val = (wchar_t)wcstol(num.c_str(),NULL,base);
220 if (base == 10)
221 num = StringUtils::Format(L"&#%ls;", num.c_str());
222 else
223 num = StringUtils::Format(L"&#x%ls;", num.c_str());
224
225 StringUtils::Replace(strStripped, num,std::wstring(1,val));
226 iPos = strStripped.find(L"&#", iStart);
227 }
228}
229
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13namespace HTML
14{
15class CHTMLUtil
16{
17public:
18 CHTMLUtil(void);
19 virtual ~CHTMLUtil(void);
20 static void RemoveTags(std::string& strHTML);
21 static void ConvertHTMLToW(const std::wstring& strHTML, std::wstring& strStripped);
22};
23}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "HttpHeader.h"
10
11#include "utils/StringUtils.h"
12
13// header white space characters according to RFC 2616
14const char* const CHttpHeader::m_whitespaceChars = " \t";
15
16
17CHttpHeader::CHttpHeader()
18{
19 m_headerdone = false;
20}
21
22CHttpHeader::~CHttpHeader() = default;
23
24void CHttpHeader::Parse(const std::string& strData)
25{
26 size_t pos = 0;
27 const size_t len = strData.length();
28 const char* const strDataC = strData.c_str();
29
30 // According to RFC 2616 any header line can have continuation on next line, if next line is started from whitespace char
31 // 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
32 // 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)
33 while (pos < len)
34 {
35 size_t lineEnd = strData.find('\x0a', pos); // use '\x0a' instead of '\n' to be platform independent
36
37 if (lineEnd == std::string::npos)
38 return; // error: expected only complete lines
39
40 const size_t nextLine = lineEnd + 1;
41 if (lineEnd > pos && strDataC[lineEnd - 1] == '\x0d') // use '\x0d' instead of '\r' to be platform independent
42 lineEnd--;
43
44 if (m_headerdone)
45 Clear(); // clear previous header and process new one
46
47 if (strDataC[pos] == ' ' || strDataC[pos] == '\t') // same chars as in CHttpHeader::m_whitespaceChars
48 { // line is started from whitespace char: this is continuation of previous line
49 pos = strData.find_first_not_of(m_whitespaceChars, pos);
50
51 m_lastHeaderLine.push_back(' '); // replace all whitespace chars at start of the line with single space
52 m_lastHeaderLine.append(strData, pos, lineEnd - pos); // append current line
53 }
54 else
55 { // this line is NOT continuation, this line is new header line
56 if (!m_lastHeaderLine.empty())
57 ParseLine(m_lastHeaderLine); // process previously stored completed line (if any)
58
59 m_lastHeaderLine.assign(strData, pos, lineEnd - pos); // store current line to (possibly) complete later. Will be parsed on next turns.
60
61 if (pos == lineEnd)
62 m_headerdone = true; // current line is bare "\r\n" (or "\n"), means end of header; no need to process current m_lastHeaderLine
63 }
64
65 pos = nextLine; // go to next line (if any)
66 }
67}
68
69bool CHttpHeader::ParseLine(const std::string& headerLine)
70{
71 const size_t valueStart = headerLine.find(':');
72
73 if (valueStart != std::string::npos)
74 {
75 std::string strParam(headerLine, 0, valueStart);
76 std::string strValue(headerLine, valueStart + 1);
77
78 StringUtils::Trim(strParam, m_whitespaceChars);
79 StringUtils::ToLower(strParam);
80
81 StringUtils::Trim(strValue, m_whitespaceChars);
82
83 if (!strParam.empty() && !strValue.empty())
84 m_params.push_back(HeaderParams::value_type(strParam, strValue));
85 else
86 return false;
87 }
88 else if (m_protoLine.empty())
89 m_protoLine = headerLine;
90
91 return true;
92}
93
94void CHttpHeader::AddParam(const std::string& param, const std::string& value, const bool overwrite /*= false*/)
95{
96 std::string paramLower(param);
97 StringUtils::ToLower(paramLower);
98 StringUtils::Trim(paramLower, m_whitespaceChars);
99 if (paramLower.empty())
100 return;
101
102 if (overwrite)
103 { // delete ALL parameters with the same name
104 // note: 'GetValue' always returns last added parameter,
105 // so you probably don't need to overwrite
106 for (size_t i = 0; i < m_params.size();)
107 {
108 if (m_params[i].first == paramLower)
109 m_params.erase(m_params.begin() + i);
110 else
111 ++i;
112 }
113 }
114
115 std::string valueTrim(value);
116 StringUtils::Trim(valueTrim, m_whitespaceChars);
117 if (valueTrim.empty())
118 return;
119
120 m_params.push_back(HeaderParams::value_type(paramLower, valueTrim));
121}
122
123std::string CHttpHeader::GetValue(const std::string& strParam) const
124{
125 std::string paramLower(strParam);
126 StringUtils::ToLower(paramLower);
127
128 return GetValueRaw(paramLower);
129}
130
131std::string CHttpHeader::GetValueRaw(const std::string& strParam) const
132{
133 // look in reverse to find last parameter (probably most important)
134 for (HeaderParams::const_reverse_iterator iter = m_params.rbegin(); iter != m_params.rend(); ++iter)
135 {
136 if (iter->first == strParam)
137 return iter->second;
138 }
139
140 return "";
141}
142
143std::vector<std::string> CHttpHeader::GetValues(std::string strParam) const
144{
145 StringUtils::ToLower(strParam);
146 std::vector<std::string> values;
147
148 for (HeaderParams::const_iterator iter = m_params.begin(); iter != m_params.end(); ++iter)
149 {
150 if (iter->first == strParam)
151 values.push_back(iter->second);
152 }
153
154 return values;
155}
156
157std::string CHttpHeader::GetHeader(void) const
158{
159 if (m_protoLine.empty() && m_params.empty())
160 return "";
161
162 std::string strHeader(m_protoLine + "\r\n");
163
164 for (HeaderParams::const_iterator iter = m_params.begin(); iter != m_params.end(); ++iter)
165 strHeader += ((*iter).first + ": " + (*iter).second + "\r\n");
166
167 strHeader += "\r\n";
168 return strHeader;
169}
170
171std::string CHttpHeader::GetMimeType(void) const
172{
173 std::string strValue(GetValueRaw("content-type"));
174
175 std::string mimeType(strValue, 0, strValue.find(';'));
176 StringUtils::TrimRight(mimeType, m_whitespaceChars);
177
178 return mimeType;
179}
180
181std::string CHttpHeader::GetCharset(void) const
182{
183 std::string strValue(GetValueRaw("content-type"));
184 if (strValue.empty())
185 return strValue;
186
187 StringUtils::ToUpper(strValue);
188 const size_t len = strValue.length();
189
190 // extract charset value from 'contenttype/contentsubtype;pram1=param1Val ; charset=XXXX\t;param2=param2Val'
191 // most common form: 'text/html; charset=XXXX'
192 // charset value can be in double quotes: 'text/xml; charset="XXX XX"'
193
194 size_t pos = strValue.find(';');
195 while (pos < len)
196 {
197 // move to the next non-whitespace character
198 pos = strValue.find_first_not_of(m_whitespaceChars, pos + 1);
199
200 if (pos != std::string::npos)
201 {
202 if (strValue.compare(pos, 8, "CHARSET=", 8) == 0)
203 {
204 pos += 8; // move position to char after 'CHARSET='
205 size_t len = strValue.find(';', pos);
206 if (len != std::string::npos)
207 len -= pos;
208 std::string charset(strValue, pos, len); // intentionally ignoring possible ';' inside quoted string
209 // as we don't support any charset with ';' in name
210 StringUtils::Trim(charset, m_whitespaceChars);
211 if (!charset.empty())
212 {
213 if (charset[0] != '"')
214 return charset;
215 else
216 { // charset contains quoted string (allowed according to RFC 2616)
217 StringUtils::Replace(charset, "\\", ""); // unescape chars, ignoring possible '\"' and '\\'
218 const size_t closingQ = charset.find('"', 1);
219 if (closingQ == std::string::npos)
220 return ""; // no closing quote
221
222 return charset.substr(1, closingQ - 1);
223 }
224 }
225 }
226 pos = strValue.find(';', pos); // find next parameter
227 }
228 }
229
230 return ""; // no charset is detected
231}
232
233void CHttpHeader::Clear()
234{
235 m_params.clear();
236 m_protoLine.clear();
237 m_headerdone = false;
238 m_lastHeaderLine.clear();
239}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12#include <utility>
13#include <vector>
14
15class CHttpHeader
16{
17public:
18 typedef std::pair<std::string, std::string> HeaderParamValue;
19 typedef std::vector<HeaderParamValue> HeaderParams;
20 typedef HeaderParams::iterator HeaderParamsIter;
21
22 CHttpHeader();
23 ~CHttpHeader();
24
25 void Parse(const std::string& strData);
26 void AddParam(const std::string& param, const std::string& value, const bool overwrite = false);
27
28 std::string GetValue(const std::string& strParam) const;
29 std::vector<std::string> GetValues(std::string strParam) const;
30
31 std::string GetHeader(void) const;
32
33 std::string GetMimeType(void) const;
34 std::string GetCharset(void) const;
35 inline std::string GetProtoLine() const
36 { return m_protoLine; }
37
38 inline bool IsHeaderDone(void) const
39 { return m_headerdone; }
40
41 void Clear();
42
43protected:
44 std::string GetValueRaw(const std::string& strParam) const;
45 bool ParseLine(const std::string& headerLine);
46
47 HeaderParams m_params;
48 std::string m_protoLine;
49 bool m_headerdone;
50 std::string m_lastHeaderLine;
51 static const char* const m_whitespaceChars;
52};
53
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 @@
1/*
2 * Copyright (C) 2011-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 *
8 * This code implements parsing of HTTP requests.
9 * This code was written by Steve Hanov in 2009, no copyright is claimed.
10 * This code is in the public domain.
11 * Code was taken from http://refactormycode.com/codes/778-an-efficient-http-parser
12 */
13
14#include "HttpParser.h"
15
16HttpParser::~HttpParser() = default;
17
18void
19HttpParser::parseHeader()
20{
21 // run the fsm.
22 const int CR = 13;
23 const int LF = 10;
24 const int ANY = 256;
25
26 enum Action {
27 // make lower case
28 LOWER = 0x1,
29
30 // convert current character to null.
31 NULLIFY = 0x2,
32
33 // set the header index to the current position
34 SET_HEADER_START = 0x4,
35
36 // set the key index to the current position
37 SET_KEY = 0x8,
38
39 // set value index to the current position.
40 SET_VALUE = 0x10,
41
42 // store current key/value pair.
43 STORE_KEY_VALUE = 0x20,
44
45 // sets content start to current position + 1
46 SET_CONTENT_START = 0x40
47 };
48
49 static const struct FSM {
50 State curState;
51 int c;
52 State nextState;
53 unsigned actions;
54 } fsm[] = {
55 { p_request_line, CR, p_request_line_cr, NULLIFY },
56 { p_request_line, ANY, p_request_line, 0 },
57 { p_request_line_cr, LF, p_request_line_crlf, 0 },
58 { p_request_line_crlf, CR, p_request_line_crlfcr, 0 },
59 { p_request_line_crlf, ANY, p_key, SET_HEADER_START | SET_KEY | LOWER },
60 { p_request_line_crlfcr, LF, p_content, SET_CONTENT_START },
61 { p_key, ':', p_key_colon, NULLIFY },
62 { p_key, ANY, p_key, LOWER },
63 { p_key_colon, ' ', p_key_colon_sp, 0 },
64 { p_key_colon_sp, ANY, p_value, SET_VALUE },
65 { p_value, CR, p_value_cr, NULLIFY | STORE_KEY_VALUE },
66 { p_value, ANY, p_value, 0 },
67 { p_value_cr, LF, p_value_crlf, 0 },
68 { p_value_crlf, CR, p_value_crlfcr, 0 },
69 { p_value_crlf, ANY, p_key, SET_KEY | LOWER },
70 { p_value_crlfcr, LF, p_content, SET_CONTENT_START },
71 { p_error, ANY, p_error, 0 }
72 };
73
74 for( unsigned i = _parsedTo; i < _data.length(); ++i) {
75 char c = _data[i];
76 State nextState = p_error;
77
78 for (const FSM& f : fsm) {
79 if ( f.curState == _state &&
80 ( c == f.c || f.c == ANY ) ) {
81
82 nextState = f.nextState;
83
84 if ( f.actions & LOWER ) {
85 _data[i] = tolower( _data[i] );
86 }
87
88 if ( f.actions & NULLIFY ) {
89 _data[i] = 0;
90 }
91
92 if ( f.actions & SET_HEADER_START ) {
93 _headerStart = i;
94 }
95
96 if ( f.actions & SET_KEY ) {
97 _keyIndex = i;
98 }
99
100 if ( f.actions & SET_VALUE ) {
101 _valueIndex = i;
102 }
103
104 if ( f.actions & SET_CONTENT_START ) {
105 _contentStart = i + 1;
106 }
107
108 if ( f.actions & STORE_KEY_VALUE ) {
109 // store position of first character of key.
110 _keys.push_back( _keyIndex );
111 }
112
113 break;
114 }
115 }
116
117 _state = nextState;
118
119 if ( _state == p_content ) {
120 const char* str = getValue("content-length");
121 if ( str ) {
122 _contentLength = atoi( str );
123 }
124 break;
125 }
126 }
127
128 _parsedTo = _data.length();
129
130}
131
132bool
133HttpParser::parseRequestLine()
134{
135 size_t sp1;
136 size_t sp2;
137
138 sp1 = _data.find( ' ', 0 );
139 if ( sp1 == std::string::npos ) return false;
140 sp2 = _data.find( ' ', sp1 + 1 );
141 if ( sp2 == std::string::npos ) return false;
142
143 _data[sp1] = 0;
144 _data[sp2] = 0;
145 _uriIndex = sp1 + 1;
146 return true;
147}
148
149HttpParser::status_t
150HttpParser::addBytes( const char* bytes, unsigned len )
151{
152 if ( _status != Incomplete ) {
153 return _status;
154 }
155
156 // append the bytes to data.
157 _data.append( bytes, len );
158
159 if ( _state < p_content ) {
160 parseHeader();
161 }
162
163 if ( _state == p_error ) {
164 _status = Error;
165 } else if ( _state == p_content ) {
166 if ( _contentLength == 0 || _data.length() - _contentStart >= _contentLength ) {
167 if ( parseRequestLine() ) {
168 _status = Done;
169 } else {
170 _status = Error;
171 }
172 }
173 }
174
175 return _status;
176}
177
178const char*
179HttpParser::getMethod() const
180{
181 return &_data[0];
182}
183
184const char*
185HttpParser::getUri() const
186{
187 return &_data[_uriIndex];
188}
189
190const char*
191HttpParser::getQueryString() const
192{
193 const char* pos = getUri();
194 while( *pos ) {
195 if ( *pos == '?' ) {
196 pos++;
197 break;
198 }
199 pos++;
200 }
201 return pos;
202}
203
204const char*
205HttpParser::getBody() const
206{
207 if ( _contentLength > 0 ) {
208 return &_data[_contentStart];
209 } else {
210 return NULL;
211 }
212}
213
214// key should be in lower case.
215const char*
216HttpParser::getValue( const char* key ) const
217{
218 for( IntArray::const_iterator iter = _keys.begin();
219 iter != _keys.end(); ++iter )
220 {
221 unsigned index = *iter;
222 if ( strcmp( &_data[index], key ) == 0 ) {
223 return &_data[index + strlen(key) + 2];
224 }
225
226 }
227
228 return NULL;
229}
230
231unsigned
232HttpParser::getContentLength() const
233{
234 return _contentLength;
235}
236
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 @@
1/*
2 * Copyright (C) 2011-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 *
8 * This code implements parsing of HTTP requests.
9 * This code was written by Steve Hanov in 2009, no copyright is claimed.
10 * This code is in the public domain.
11 * Code was taken from http://refactormycode.com/codes/778-an-efficient-http-parser
12 */
13
14#pragma once
15
16#include <stdlib.h>
17#include <string.h>
18#include <string>
19#include <vector>
20
21// A class to incrementally parse an HTTP header as it comes in. It
22// lets you know when it has received all required bytes, as specified
23// by the content-length header (if present). If there is no content-length,
24// it will stop reading after the final "\n\r".
25//
26// Example usage:
27//
28// HttpParser parser;
29// HttpParser::status_t status;
30//
31// for( ;; ) {
32// // read bytes from socket into buffer, break on error
33// status = parser.addBytes( buffer, length );
34// if ( status != HttpParser::Incomplete ) break;
35// }
36//
37// if ( status == HttpParser::Done ) {
38// // parse fully formed http message.
39// }
40
41
42class HttpParser
43{
44public:
45 ~HttpParser();
46
47 enum status_t {
48 Done,
49 Error,
50 Incomplete
51 };
52
53 status_t addBytes( const char* bytes, unsigned len );
54
55 const char* getMethod() const;
56 const char* getUri() const;
57 const char* getQueryString() const;
58 const char* getBody() const;
59 // key should be in lower case when looking up.
60 const char* getValue( const char* key ) const;
61 unsigned getContentLength() const;
62
63private:
64 void parseHeader();
65 bool parseRequestLine();
66
67 std::string _data;
68 unsigned _headerStart = 0;
69 unsigned _parsedTo = 0 ;
70 int _state = 0 ;
71 unsigned _keyIndex = 0;
72 unsigned _valueIndex = 0;
73 unsigned _contentLength = 0;
74 unsigned _contentStart = 0;
75 unsigned _uriIndex = 0;
76
77 typedef std::vector<unsigned> IntArray;
78 IntArray _keys;
79
80 enum State {
81 p_request_line=0,
82 p_request_line_cr=1,
83 p_request_line_crlf=2,
84 p_request_line_crlfcr=3,
85 p_key=4,
86 p_key_colon=5,
87 p_key_colon_sp=6,
88 p_value=7,
89 p_value_cr=8,
90 p_value_crlf=9,
91 p_value_crlfcr=10,
92 p_content=11, // here we are done parsing the header.
93 p_error=12 // here an error has occurred and the parse failed.
94 };
95
96 status_t _status = Incomplete ;
97};
98
diff --git a/xbmc/utils/HttpRangeUtils.cpp b/xbmc/utils/HttpRangeUtils.cpp
new file mode 100644
index 0000000..e25ccf6
--- /dev/null
+++ b/xbmc/utils/HttpRangeUtils.cpp
@@ -0,0 +1,424 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include <algorithm>
10
11#include "HttpRangeUtils.h"
12#include "Util.h"
13#ifdef HAS_WEB_SERVER
14#include "network/httprequesthandler/IHTTPRequestHandler.h"
15#endif // HAS_WEB_SERVER
16#include "utils/StringUtils.h"
17#include "utils/Variant.h"
18
19#include <inttypes.h>
20
21#define HEADER_NEWLINE "\r\n"
22#define HEADER_SEPARATOR HEADER_NEWLINE HEADER_NEWLINE
23#define HEADER_BOUNDARY "--"
24
25#define HEADER_CONTENT_RANGE_VALUE "%" PRIu64
26#define HEADER_CONTENT_RANGE_VALUE_UNKNOWN "*"
27#define HEADER_CONTENT_RANGE_FORMAT_BYTES "bytes " HEADER_CONTENT_RANGE_VALUE "-" HEADER_CONTENT_RANGE_VALUE "/"
28#define CONTENT_RANGE_FORMAT_TOTAL HEADER_CONTENT_RANGE_FORMAT_BYTES HEADER_CONTENT_RANGE_VALUE
29#define CONTENT_RANGE_FORMAT_TOTAL_UNKNOWN HEADER_CONTENT_RANGE_FORMAT_BYTES HEADER_CONTENT_RANGE_VALUE_UNKNOWN
30
31CHttpRange::CHttpRange(uint64_t firstPosition, uint64_t lastPosition)
32 : m_first(firstPosition),
33 m_last(lastPosition)
34{ }
35
36bool CHttpRange::operator<(const CHttpRange &other) const
37{
38 return (m_first < other.m_first) ||
39 (m_first == other.m_first && m_last < other.m_last);
40}
41
42bool CHttpRange::operator==(const CHttpRange &other) const
43{
44 return m_first == other.m_first && m_last == other.m_last;
45}
46
47bool CHttpRange::operator!=(const CHttpRange &other) const
48{
49 return !(*this == other);
50}
51
52uint64_t CHttpRange::GetLength() const
53{
54 if (!IsValid())
55 return 0;
56
57 return m_last - m_first + 1;
58}
59
60void CHttpRange::SetLength(uint64_t length)
61{
62 m_last = m_first + length - 1;
63}
64
65bool CHttpRange::IsValid() const
66{
67 return m_last >= m_first;
68}
69
70CHttpResponseRange::CHttpResponseRange()
71 : CHttpRange(),
72 m_data(NULL)
73{ }
74
75CHttpResponseRange::CHttpResponseRange(uint64_t firstPosition, uint64_t lastPosition)
76 : CHttpRange(firstPosition, lastPosition),
77 m_data(NULL)
78{ }
79
80CHttpResponseRange::CHttpResponseRange(const void* data, uint64_t firstPosition, uint64_t lastPosition)
81 : CHttpRange(firstPosition, lastPosition),
82 m_data(data)
83{ }
84
85CHttpResponseRange::CHttpResponseRange(const void* data, uint64_t length)
86 : CHttpRange(0, length - 1),
87 m_data(data)
88{ }
89
90bool CHttpResponseRange::operator==(const CHttpResponseRange &other) const
91{
92 if (!CHttpRange::operator==(other))
93 return false;
94
95 return m_data == other.m_data;
96}
97
98bool CHttpResponseRange::operator!=(const CHttpResponseRange &other) const
99{
100 return !(*this == other);
101}
102
103void CHttpResponseRange::SetData(const void* data, uint64_t length)
104{
105 if (length == 0)
106 return;
107
108 m_first = 0;
109
110 SetData(data);
111 SetLength(length);
112}
113
114void CHttpResponseRange::SetData(const void* data, uint64_t firstPosition, uint64_t lastPosition)
115{
116 SetData(data);
117 SetFirstPosition(firstPosition);
118 SetLastPosition(lastPosition);
119}
120
121bool CHttpResponseRange::IsValid() const
122{
123 if (!CHttpRange::IsValid())
124 return false;
125
126 return m_data != NULL;
127}
128
129CHttpRanges::CHttpRanges()
130: m_ranges()
131{ }
132
133CHttpRanges::CHttpRanges(const HttpRanges& httpRanges)
134: m_ranges(httpRanges)
135{
136 SortAndCleanup();
137}
138
139bool CHttpRanges::Get(size_t index, CHttpRange& range) const
140{
141 if (index >= Size())
142 return false;
143
144 range = m_ranges.at(index);
145 return true;
146}
147
148bool CHttpRanges::GetFirst(CHttpRange& range) const
149{
150 if (m_ranges.empty())
151 return false;
152
153 range = m_ranges.front();
154 return true;
155}
156
157bool CHttpRanges::GetLast(CHttpRange& range) const
158{
159 if (m_ranges.empty())
160 return false;
161
162 range = m_ranges.back();
163 return true;
164}
165
166bool CHttpRanges::GetFirstPosition(uint64_t& position) const
167{
168 if (m_ranges.empty())
169 return false;
170
171 position = m_ranges.front().GetFirstPosition();
172 return true;
173}
174
175bool CHttpRanges::GetLastPosition(uint64_t& position) const
176{
177 if (m_ranges.empty())
178 return false;
179
180 position = m_ranges.back().GetLastPosition();
181 return true;
182}
183
184uint64_t CHttpRanges::GetLength() const
185{
186 uint64_t length = 0;
187 for (HttpRanges::const_iterator range = m_ranges.begin(); range != m_ranges.end(); ++range)
188 length += range->GetLength();
189
190 return length;
191}
192
193bool CHttpRanges::GetTotalRange(CHttpRange& range) const
194{
195 if (m_ranges.empty())
196 return false;
197
198 uint64_t firstPosition, lastPosition;
199 if (!GetFirstPosition(firstPosition) || !GetLastPosition(lastPosition))
200 return false;
201
202 range.SetFirstPosition(firstPosition);
203 range.SetLastPosition(lastPosition);
204
205 return range.IsValid();
206}
207
208void CHttpRanges::Add(const CHttpRange& range)
209{
210 if (!range.IsValid())
211 return;
212
213 m_ranges.push_back(range);
214
215 SortAndCleanup();
216}
217
218void CHttpRanges::Remove(size_t index)
219{
220 if (index >= Size())
221 return;
222
223 m_ranges.erase(m_ranges.begin() + index);
224}
225
226void CHttpRanges::Clear()
227{
228 m_ranges.clear();
229}
230
231bool CHttpRanges::Parse(const std::string& header)
232{
233 return Parse(header, std::numeric_limits<uint64_t>::max());
234}
235
236bool CHttpRanges::Parse(const std::string& header, uint64_t totalLength)
237{
238 m_ranges.clear();
239
240 if (header.empty() || totalLength == 0 || !StringUtils::StartsWithNoCase(header, "bytes="))
241 return false;
242
243 uint64_t lastPossiblePosition = totalLength - 1;
244
245 // remove "bytes=" from the beginning
246 std::string rangesValue = header.substr(6);
247
248 // split the value of the "Range" header by ","
249 std::vector<std::string> rangeValues = StringUtils::Split(rangesValue, ",");
250
251 for (std::vector<std::string>::const_iterator range = rangeValues.begin(); range != rangeValues.end(); ++range)
252 {
253 // there must be a "-" in the range definition
254 if (range->find("-") == std::string::npos)
255 return false;
256
257 std::vector<std::string> positions = StringUtils::Split(*range, "-");
258 if (positions.size() != 2)
259 return false;
260
261 bool hasStart = false;
262 uint64_t start = 0;
263 bool hasEnd = false;
264 uint64_t end = 0;
265
266 // parse the start and end positions
267 if (!positions.front().empty())
268 {
269 if (!StringUtils::IsNaturalNumber(positions.front()))
270 return false;
271
272 start = str2uint64(positions.front(), 0);
273 hasStart = true;
274 }
275 if (!positions.back().empty())
276 {
277 if (!StringUtils::IsNaturalNumber(positions.back()))
278 return false;
279
280 end = str2uint64(positions.back(), 0);
281 hasEnd = true;
282 }
283
284 // nothing defined at all
285 if (!hasStart && !hasEnd)
286 return false;
287
288 // make sure that the end position makes sense
289 if (hasEnd)
290 end = std::min(end, lastPossiblePosition);
291
292 if (!hasStart && hasEnd)
293 {
294 // the range is defined as the number of bytes from the end
295 start = totalLength - end;
296 end = lastPossiblePosition;
297 }
298 else if (hasStart && !hasEnd)
299 end = lastPossiblePosition;
300
301 // make sure the start position makes sense
302 if (start > lastPossiblePosition)
303 return false;
304
305 // make sure that the start position is smaller or equal to the end position
306 if (end < start)
307 return false;
308
309 m_ranges.push_back(CHttpRange(start, end));
310 }
311
312 if (m_ranges.empty())
313 return false;
314
315 SortAndCleanup();
316 return !m_ranges.empty();
317}
318
319void CHttpRanges::SortAndCleanup()
320{
321 // sort the ranges by their first position
322 std::sort(m_ranges.begin(), m_ranges.end());
323
324 // check for overlapping ranges
325 for (HttpRanges::iterator range = m_ranges.begin() + 1; range != m_ranges.end();)
326 {
327 HttpRanges::iterator previous = range - 1;
328
329 // check if the current and the previous range overlap
330 if (previous->GetLastPosition() + 1 >= range->GetFirstPosition())
331 {
332 // combine the previous and the current ranges by setting the last position of the previous range
333 // to the last position of the current range
334 previous->SetLastPosition(range->GetLastPosition());
335
336 // then remove the current range which is not needed anymore
337 range = m_ranges.erase(range);
338 }
339 else
340 ++range;
341 }
342}
343
344std::string HttpRangeUtils::GenerateContentRangeHeaderValue(const CHttpRange* range)
345{
346 if (range == NULL)
347 return "";
348
349 return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL, range->GetFirstPosition(), range->GetLastPosition(), range->GetLength());
350}
351
352std::string HttpRangeUtils::GenerateContentRangeHeaderValue(uint64_t start, uint64_t end, uint64_t total)
353{
354 if (total > 0)
355 return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL, start, end, total);
356
357 return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL_UNKNOWN, start, end);
358}
359
360#ifdef HAS_WEB_SERVER
361
362std::string HttpRangeUtils::GenerateMultipartBoundary()
363{
364 static char chars[] = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
365
366 // create a string of length 30 to 40 and pre-fill it with "-"
367 size_t count = static_cast<size_t>(CUtil::GetRandomNumber()) % 11 + 30;
368 std::string boundary(count, '-');
369
370 for (size_t i = static_cast<size_t>(CUtil::GetRandomNumber()) % 5 + 8; i < count; i++)
371 boundary.replace(i, 1, 1, chars[static_cast<size_t>(CUtil::GetRandomNumber()) % 64]);
372
373 return boundary;
374}
375
376std::string HttpRangeUtils::GenerateMultipartBoundaryContentType(const std::string& multipartBoundary)
377{
378 if (multipartBoundary.empty())
379 return "";
380
381 return "multipart/byteranges; boundary=" + multipartBoundary;
382}
383
384std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType)
385{
386 if (multipartBoundary.empty())
387 return "";
388
389 std::string boundaryWithHeader = HEADER_BOUNDARY + multipartBoundary + HEADER_NEWLINE;
390 if (!contentType.empty())
391 boundaryWithHeader += MHD_HTTP_HEADER_CONTENT_TYPE ": " + contentType + HEADER_NEWLINE;
392
393 return boundaryWithHeader;
394}
395
396std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType, const CHttpRange* range)
397{
398 if (multipartBoundary.empty() || range == NULL)
399 return "";
400
401 return GenerateMultipartBoundaryWithHeader(GenerateMultipartBoundaryWithHeader(multipartBoundary, contentType), range);
402}
403
404std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundaryWithContentType, const CHttpRange* range)
405{
406 if (multipartBoundaryWithContentType.empty() || range == NULL)
407 return "";
408
409 std::string boundaryWithHeader = multipartBoundaryWithContentType;
410 boundaryWithHeader += MHD_HTTP_HEADER_CONTENT_RANGE ": " + GenerateContentRangeHeaderValue(range);
411 boundaryWithHeader += HEADER_SEPARATOR;
412
413 return boundaryWithHeader;
414}
415
416std::string HttpRangeUtils::GenerateMultipartBoundaryEnd(const std::string& multipartBoundary)
417{
418 if (multipartBoundary.empty())
419 return "";
420
421 return HEADER_NEWLINE HEADER_BOUNDARY + multipartBoundary + HEADER_BOUNDARY;
422}
423
424#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 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12#include <string>
13#include <vector>
14
15class CHttpRange
16{
17public:
18 CHttpRange() = default;
19 CHttpRange(uint64_t firstPosition, uint64_t lastPosition);
20 virtual ~CHttpRange() = default;
21
22 bool operator<(const CHttpRange &other) const;
23 bool operator==(const CHttpRange &other) const;
24 bool operator!=(const CHttpRange &other) const;
25
26 virtual uint64_t GetFirstPosition() const { return m_first; }
27 virtual void SetFirstPosition(uint64_t firstPosition) { m_first = firstPosition; }
28 virtual uint64_t GetLastPosition() const { return m_last; }
29 virtual void SetLastPosition(uint64_t lastPosition) { m_last = lastPosition; }
30
31 virtual uint64_t GetLength() const;
32 virtual void SetLength(uint64_t length);
33
34 virtual bool IsValid() const;
35
36protected:
37 uint64_t m_first = 1;
38 uint64_t m_last = 0;
39};
40
41typedef std::vector<CHttpRange> HttpRanges;
42
43class CHttpResponseRange : public CHttpRange
44{
45public:
46 CHttpResponseRange();
47 CHttpResponseRange(uint64_t firstPosition, uint64_t lastPosition);
48 CHttpResponseRange(const void* data, uint64_t firstPosition, uint64_t lastPosition);
49 CHttpResponseRange(const void* data, uint64_t length);
50 ~CHttpResponseRange() override = default;
51
52 bool operator==(const CHttpResponseRange &other) const;
53 bool operator!=(const CHttpResponseRange &other) const;
54
55 const void* GetData() const { return m_data; }
56 void SetData(const void* data) { m_data = data; }
57 void SetData(const void* data, uint64_t length);
58 void SetData(const void* data, uint64_t firstPosition, uint64_t lastPosition);
59
60 bool IsValid() const override;
61
62protected:
63 const void* m_data;
64};
65
66typedef std::vector<CHttpResponseRange> HttpResponseRanges;
67
68class CHttpRanges final
69{
70public:
71 CHttpRanges();
72 explicit CHttpRanges(const HttpRanges& httpRanges);
73
74 const HttpRanges& Get() const { return m_ranges; }
75 bool Get(size_t index, CHttpRange& range) const;
76 bool GetFirst(CHttpRange& range) const;
77 bool GetLast(CHttpRange& range) const;
78 size_t Size() const { return m_ranges.size(); }
79 bool IsEmpty() const { return m_ranges.empty(); }
80
81 bool GetFirstPosition(uint64_t& position) const;
82 bool GetLastPosition(uint64_t& position) const;
83 uint64_t GetLength() const;
84
85 bool GetTotalRange(CHttpRange& range) const;
86
87 void Add(const CHttpRange& range);
88 void Remove(size_t index);
89 void Clear();
90
91 HttpRanges::const_iterator Begin() const { return m_ranges.begin(); }
92 HttpRanges::const_iterator End() const { return m_ranges.end(); }
93
94 bool Parse(const std::string& header);
95 bool Parse(const std::string& header, uint64_t totalLength);
96
97protected:
98 void SortAndCleanup();
99
100 HttpRanges m_ranges;
101};
102
103class HttpRangeUtils
104{
105public:
106 /*!
107 * \brief Generates a valid Content-Range HTTP header value for the given HTTP
108 * range definition.
109 *
110 * \param range HTTP range definition used to generate the Content-Range HTTP header
111 * \return Content-Range HTTP header value
112 */
113 static std::string GenerateContentRangeHeaderValue(const CHttpRange* range);
114
115 /*!
116 * \brief Generates a valid Content-Range HTTP header value for the given HTTP
117 * range properties.
118 *
119 * \param start Start position of the HTTP range
120 * \param end Last/End position of the HTTP range
121 * \param total Total length of original content (not just the range)
122 * \return Content-Range HTTP header value
123 */
124 static std::string GenerateContentRangeHeaderValue(uint64_t start, uint64_t end, uint64_t total);
125
126#ifdef HAS_WEB_SERVER
127 /*!
128 * \brief Generates a multipart boundary that can be used in ranged HTTP
129 * responses.
130 *
131 * \return Multipart boundary that can be used in ranged HTTP responses
132 */
133 static std::string GenerateMultipartBoundary();
134
135 /*!
136 * \brief Generates the multipart/byteranges Content-Type HTTP header value
137 * containing the given multipart boundary for a ranged HTTP response.
138 *
139 * \param multipartBoundary Multipart boundary to be used in the ranged HTTP response
140 * \return multipart/byteranges Content-Type HTTP header value
141 */
142 static std::string GenerateMultipartBoundaryContentType(const std::string& multipartBoundary);
143
144 /*!
145 * \brief Generates a multipart boundary including the Content-Type HTTP
146 * header value with the (actual) given content type of the original
147 * content.
148 *
149 * \param multipartBoundary Multipart boundary to be used in the ranged HTTP response
150 * \param contentType (Actual) Content type of the original content
151 * \return Multipart boundary (including the Content-Type HTTP header) value that can be used in ranged HTTP responses
152 */
153 static std::string GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType);
154
155 /*!
156 * \brief Generates a multipart boundary including the Content-Type HTTP
157 * header value with the (actual) given content type of the original
158 * content and the Content-Range HTTP header value for the given range.
159 *
160 * \param multipartBoundary Multipart boundary to be used in the ranged HTTP response
161 * \param contentType (Actual) Content type of the original content
162 * \param range HTTP range definition used to generate the Content-Range HTTP header
163 * \return Multipart boundary (including the Content-Type and Content-Range HTTP headers) value that can be used in ranged HTTP responses
164 */
165 static std::string GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType, const CHttpRange* range);
166
167 /*!
168 * \brief Generates a multipart boundary including the Content-Type HTTP
169 * header value with the (actual) given content type of the original
170 * content and the Content-Range HTTP header value for the given range.
171 *
172 * \param multipartBoundaryWithContentType Multipart boundary (already including the Content-Type HTTP header value) to be used in the ranged HTTP response
173 * \param range HTTP range definition used to generate the Content-Range HTTP header
174 * \return Multipart boundary (including the Content-Type and Content-Range HTTP headers) value that can be used in ranged HTTP responses
175 */
176 static std::string GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundaryWithContentType, const CHttpRange* range);
177
178 /*!
179 * \brief Generates a multipart boundary end that can be used in ranged HTTP
180 * responses.
181 *
182 * \param multipartBoundary Multipart boundary to be used in the ranged HTTP response
183 * \return Multipart boundary end that can be used in a ranged HTTP response
184 */
185 static std::string GenerateMultipartBoundaryEnd(const std::string& multipartBoundary);
186#endif // HAS_WEB_SERVER
187};
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 @@
1/*
2 * Copyright (C) 2011-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "HttpResponse.h"
10
11#include <stdio.h>
12
13#define SPACE " "
14#define SEPARATOR ": "
15#define LINEBREAK "\r\n"
16
17#define HEADER_CONTENT_LENGTH "Content-Length"
18
19std::map<HTTP::StatusCode, std::string> CHttpResponse::m_statusCodeText = CHttpResponse::createStatusCodes();
20
21CHttpResponse::CHttpResponse(HTTP::Method method, HTTP::StatusCode status, HTTP::Version version /* = HTTPVersion1_1 */)
22{
23 m_method = method;
24 m_status = status;
25 m_version = version;
26
27 m_content = NULL;
28 m_contentLength = 0;
29}
30
31void CHttpResponse::AddHeader(const std::string &field, const std::string &value)
32{
33 if (field.empty())
34 return;
35
36 m_headers.emplace_back(field, value);
37}
38
39void CHttpResponse::SetContent(const char* data, unsigned int length)
40{
41 m_content = data;
42
43 if (m_content == NULL)
44 m_contentLength = 0;
45 else
46 m_contentLength = length;
47}
48
49std::string CHttpResponse::Create()
50{
51 m_buffer.clear();
52
53 m_buffer.append("HTTP/");
54 switch (m_version)
55 {
56 case HTTP::Version1_0:
57 m_buffer.append("1.0");
58 break;
59
60 case HTTP::Version1_1:
61 m_buffer.append("1.1");
62 break;
63
64 default:
65 return 0;
66 }
67
68 char statusBuffer[4];
69 sprintf(statusBuffer, "%d", (int)m_status);
70 m_buffer.append(SPACE);
71 m_buffer.append(statusBuffer);
72
73 m_buffer.append(SPACE);
74 m_buffer.append(m_statusCodeText.find(m_status)->second);
75 m_buffer.append(LINEBREAK);
76
77 bool hasContentLengthHeader = false;
78 for (unsigned int index = 0; index < m_headers.size(); index++)
79 {
80 m_buffer.append(m_headers[index].first);
81 m_buffer.append(SEPARATOR);
82 m_buffer.append(m_headers[index].second);
83 m_buffer.append(LINEBREAK);
84
85 if (m_headers[index].first.compare(HEADER_CONTENT_LENGTH) == 0)
86 hasContentLengthHeader = true;
87 }
88
89 if (!hasContentLengthHeader && m_content != NULL && m_contentLength > 0)
90 {
91 m_buffer.append(HEADER_CONTENT_LENGTH);
92 m_buffer.append(SEPARATOR);
93 char lengthBuffer[11];
94 sprintf(lengthBuffer, "%u", m_contentLength);
95 m_buffer.append(lengthBuffer);
96 m_buffer.append(LINEBREAK);
97 }
98
99 m_buffer.append(LINEBREAK);
100 if (m_content != NULL && m_contentLength > 0)
101 m_buffer.append(m_content, m_contentLength);
102
103 return m_buffer;
104}
105
106std::map<HTTP::StatusCode, std::string> CHttpResponse::createStatusCodes()
107{
108 std::map<HTTP::StatusCode, std::string> map;
109 map[HTTP::Continue] = "Continue";
110 map[HTTP::SwitchingProtocols] = "Switching Protocols";
111 map[HTTP::Processing] = "Processing";
112 map[HTTP::ConnectionTimedOut] = "Connection timed out";
113 map[HTTP::OK] = "OK";
114 map[HTTP::Created] = "Created";
115 map[HTTP::Accepted] = "Accepted";
116 map[HTTP::NonAuthoritativeInformation] = "Non-Authoritative Information";
117 map[HTTP::NoContent] = "No Content";
118 map[HTTP::ResetContent] = "Reset Content";
119 map[HTTP::PartialContent] = "Partial Content";
120 map[HTTP::MultiStatus] = "Multi-Status";
121 map[HTTP::MultipleChoices] = "Multiple Choices";
122 map[HTTP::MovedPermanently] = "Moved Permanently";
123 map[HTTP::Found] = "Found";
124 map[HTTP::SeeOther] = "See Other";
125 map[HTTP::NotModified] = "Not Modified";
126 map[HTTP::UseProxy] = "Use Proxy";
127 //map[HTTP::SwitchProxy] = "Switch Proxy";
128 map[HTTP::TemporaryRedirect] = "Temporary Redirect";
129 map[HTTP::BadRequest] = "Bad Request";
130 map[HTTP::Unauthorized] = "Unauthorized";
131 map[HTTP::PaymentRequired] = "Payment Required";
132 map[HTTP::Forbidden] = "Forbidden";
133 map[HTTP::NotFound] = "Not Found";
134 map[HTTP::MethodNotAllowed] = "Method Not Allowed";
135 map[HTTP::NotAcceptable] = "Not Acceptable";
136 map[HTTP::ProxyAuthenticationRequired] = "Proxy Authentication Required";
137 map[HTTP::RequestTimeout] = "Request Time-out";
138 map[HTTP::Conflict] = "Conflict";
139 map[HTTP::Gone] = "Gone";
140 map[HTTP::LengthRequired] = "Length Required";
141 map[HTTP::PreconditionFailed] = "Precondition Failed";
142 map[HTTP::RequestEntityTooLarge] = "Request Entity Too Large";
143 map[HTTP::RequestURITooLong] = "Request-URI Too Long";
144 map[HTTP::UnsupportedMediaType] = "Unsupported Media Type";
145 map[HTTP::RequestedRangeNotSatisfiable] = "Requested range not satisfiable";
146 map[HTTP::ExpectationFailed] = "Expectation Failed";
147 map[HTTP::ImATeapot] = "I'm a Teapot";
148 map[HTTP::TooManyConnections] = "There are too many connections from your internet address";
149 map[HTTP::UnprocessableEntity] = "Unprocessable Entity";
150 map[HTTP::Locked] = "Locked";
151 map[HTTP::FailedDependency] = "Failed Dependency";
152 map[HTTP::UnorderedCollection] = "UnorderedCollection";
153 map[HTTP::UpgradeRequired] = "Upgrade Required";
154 map[HTTP::InternalServerError] = "Internal Server Error";
155 map[HTTP::NotImplemented] = "Not Implemented";
156 map[HTTP::BadGateway] = "Bad Gateway";
157 map[HTTP::ServiceUnavailable] = "Service Unavailable";
158 map[HTTP::GatewayTimeout] = "Gateway Time-out";
159 map[HTTP::HTTPVersionNotSupported] = "HTTP Version not supported";
160 map[HTTP::VariantAlsoNegotiates] = "Variant Also Negotiates";
161 map[HTTP::InsufficientStorage] = "Insufficient Storage";
162 map[HTTP::BandwidthLimitExceeded] = "Bandwidth Limit Exceeded";
163 map[HTTP::NotExtended] = "Not Extended";
164
165 return map;
166}
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 @@
1/*
2 * Copyright (C) 2011-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <map>
12#include <string>
13#include <utility>
14#include <vector>
15
16namespace HTTP
17{
18 enum Version
19 {
20 Version1_0,
21 Version1_1
22 };
23
24 enum Method
25 {
26 Get,
27 Head,
28 POST,
29 PUT,
30 Delete,
31 Trace,
32 Connect
33 };
34
35 enum StatusCode
36 {
37 // Information 1xx
38 Continue = 100,
39 SwitchingProtocols = 101,
40 Processing = 102,
41 ConnectionTimedOut = 103,
42
43 // Success 2xx
44 OK = 200,
45 Created = 201,
46 Accepted = 202,
47 NonAuthoritativeInformation = 203,
48 NoContent = 204,
49 ResetContent = 205,
50 PartialContent = 206,
51 MultiStatus = 207,
52
53 // Redirects 3xx
54 MultipleChoices = 300,
55 MovedPermanently = 301,
56 Found = 302,
57 SeeOther = 303,
58 NotModified = 304,
59 UseProxy = 305,
60 //SwitchProxy = 306,
61 TemporaryRedirect = 307,
62
63 // Client errors 4xx
64 BadRequest = 400,
65 Unauthorized = 401,
66 PaymentRequired = 402,
67 Forbidden = 403,
68 NotFound = 404,
69 MethodNotAllowed = 405,
70 NotAcceptable = 406,
71 ProxyAuthenticationRequired = 407,
72 RequestTimeout = 408,
73 Conflict = 409,
74 Gone = 410,
75 LengthRequired = 411,
76 PreconditionFailed = 412,
77 RequestEntityTooLarge = 413,
78 RequestURITooLong = 414,
79 UnsupportedMediaType = 415,
80 RequestedRangeNotSatisfiable = 416,
81 ExpectationFailed = 417,
82 ImATeapot = 418,
83 TooManyConnections = 421,
84 UnprocessableEntity = 422,
85 Locked = 423,
86 FailedDependency = 424,
87 UnorderedCollection = 425,
88 UpgradeRequired = 426,
89
90 // Server errors 5xx
91 InternalServerError = 500,
92 NotImplemented = 501,
93 BadGateway = 502,
94 ServiceUnavailable = 503,
95 GatewayTimeout = 504,
96 HTTPVersionNotSupported = 505,
97 VariantAlsoNegotiates = 506,
98 InsufficientStorage = 507,
99 BandwidthLimitExceeded = 509,
100 NotExtended = 510
101 };
102}
103
104class CHttpResponse
105{
106public:
107 CHttpResponse(HTTP::Method method, HTTP::StatusCode status, HTTP::Version version = HTTP::Version1_1);
108
109 void AddHeader(const std::string &field, const std::string &value);
110 void SetContent(const char* data, unsigned int length);
111
112 std::string Create();
113
114private:
115 HTTP::Method m_method;
116 HTTP::StatusCode m_status;
117 HTTP::Version m_version;
118 std::vector< std::pair<std::string, std::string> > m_headers;
119 const char* m_content;
120 unsigned int m_contentLength;
121 std::string m_buffer;
122
123 static std::map<HTTP::StatusCode, std::string> m_statusCodeText;
124 static std::map<HTTP::StatusCode, std::string> createStatusCodes();
125};
diff --git a/xbmc/utils/IArchivable.h b/xbmc/utils/IArchivable.h
new file mode 100644
index 0000000..1ad58a1
--- /dev/null
+++ b/xbmc/utils/IArchivable.h
@@ -0,0 +1,22 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11class CArchive;
12
13class IArchivable
14{
15protected:
16 /* make sure nobody deletes a pointer to this class */
17 ~IArchivable() = default;
18
19public:
20 virtual void Archive(CArchive& ar) = 0;
21};
22
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 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12#include <string>
13
14/**
15 * @brief Interface to describe CBufferObjects.
16 *
17 * BufferObjects are used to abstract various memory types and present them
18 * with a generic interface. Typically on a posix system exists the concept
19 * of Direct Memory Access (DMA) which allows various systems to interact
20 * with a memory location directly without having to copy the data into
21 * userspace (ie. from kernel space). These DMA buffers are presented as
22 * file descriptors (fds) which can be passed around to gain access to
23 * the buffers memory location.
24 *
25 * In order to write to these buffer types typically the memory location has
26 * to be mapped into userspace via a call to mmap (or similar). This presents
27 * userspace with a memory location that can be initially written to (ie. by
28 * using memcpy or similar). Depending on the underlying implementation a
29 * stride might be specified if the memory type requires padding to a certain
30 * size (such as page size). The stride must be used when copying into the buffer.
31 *
32 * Some memory implementation may provide special memory layouts in which case
33 * modifiers are provided that describe tiling or compression. This should be
34 * transparent to the caller as the data copied into a BufferObject will likely
35 * be linear. The modifier will be needed when presenting the buffer via DRM or
36 * EGL even if it is linear.
37 *
38 */
39class IBufferObject
40{
41public:
42 virtual ~IBufferObject() = default;
43
44 /**
45 * @brief Create a BufferObject based on the format, width, and height of the desired buffer
46 *
47 * @param format framebuffer pixel formats are described using the fourcc codes defined in
48 * https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_fourcc.h
49 * @param width width of the requested buffer.
50 * @param height height of the requested buffer.
51 * @return true BufferObject creation was successful.
52 * @return false BufferObject creation was unsuccessful.
53 */
54 virtual bool CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) = 0;
55
56 /**
57 * @brief Create a BufferObject based only on the size of the desired buffer. Not all
58 * CBufferObject implementations may support this. This method is required for
59 * use with the CAddonVideoCodec as it only knows the decoded buffer size.
60 *
61 * @param size of the requested buffer.
62 * @return true BufferObject creation was successful.
63 * @return false BufferObject creation was unsuccessful.
64 */
65 virtual bool CreateBufferObject(uint64_t size) = 0;
66
67 /**
68 * @brief Destroy a BufferObject.
69 *
70 */
71 virtual void DestroyBufferObject() = 0;
72
73 /**
74 * @brief Get the Memory location of the BufferObject. This method needs to be
75 * called to be able to copy data into the BufferObject.
76 *
77 * @return uint8_t* pointer to the memory location of the BufferObject.
78 */
79 virtual uint8_t *GetMemory() = 0;
80
81 /**
82 * @brief Release the mapped memory of the BufferObject. After calling this the memory
83 * location pointed to by GetMemory() will be invalid.
84 *
85 */
86 virtual void ReleaseMemory() = 0;
87
88 /**
89 * @brief Get the File Descriptor of the BufferObject. The fd is guaranteed to be
90 * available after calling CreateBufferObject().
91 *
92 * @return int fd for the BufferObject. Invalid if -1.
93 */
94 virtual int GetFd() = 0;
95
96 /**
97 * @brief Get the Stride of the BufferObject. The stride is guaranteed to be
98 * available after calling GetMemory().
99 *
100 * @return uint32_t stride of the BufferObject.
101 */
102 virtual uint32_t GetStride() = 0;
103
104 /**
105 * @brief Get the Modifier of the BufferObject. Format Modifiers further describe
106 * the buffer's format such as for tiling or compression.
107 * see https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_fourcc.h
108 *
109 * @return uint64_t modifier of the BufferObject. 0 means the layout is linear (default).
110 */
111 virtual uint64_t GetModifier() = 0;
112
113 /**
114 * @brief Must be called before reading/writing data to the BufferObject.
115 *
116 */
117 virtual void SyncStart() = 0;
118
119 /**
120 * @brief Must be called after reading/writing data to the BufferObject.
121 *
122 */
123 virtual void SyncEnd() = 0;
124
125 /**
126 * @brief Get the Name of the BufferObject type in use
127 *
128 * @return std::string name of the BufferObject type in use
129 */
130 virtual std::string GetName() const = 0;
131};
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 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <cstdint>
12#include <string>
13
14class ILocalizer
15{
16public:
17 virtual ~ILocalizer() = default;
18
19 virtual std::string Localize(std::uint32_t code) const = 0;
20
21protected:
22 ILocalizer() = default;
23};
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 @@
1/*
2 * Copyright (C) 2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <memory>
12#include <mutex>
13#include <string>
14
15#ifdef TARGET_WINDOWS
16using spdlog_filename_t = std::wstring;
17#else
18using spdlog_filename_t = std::string;
19#endif
20
21namespace spdlog
22{
23namespace sinks
24{
25template<typename Mutex>
26class dist_sink;
27}
28} // namespace spdlog
29
30class IPlatformLog
31{
32public:
33 virtual ~IPlatformLog() = default;
34
35 static std::unique_ptr<IPlatformLog> CreatePlatformLog();
36
37 virtual spdlog_filename_t GetLogFilename(const std::string& filename) const = 0;
38 virtual void AddSinks(
39 std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const = 0;
40};
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 @@
1/*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <vector>
12
13typedef uint32_t character_t;
14typedef std::vector<character_t> vecText;
15
16class IRssObserver
17{
18protected:
19 /* make sure nobody deletes a pointer to this class */
20 ~IRssObserver() = default;
21
22public:
23 virtual void OnFeedUpdate(const vecText &feed) = 0;
24 virtual void OnFeedRelease() = 0;
25};
diff --git a/xbmc/utils/IScreenshotSurface.h b/xbmc/utils/IScreenshotSurface.h
new file mode 100644
index 0000000..3414cbc
--- /dev/null
+++ b/xbmc/utils/IScreenshotSurface.h
@@ -0,0 +1,36 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11class IScreenshotSurface
12{
13public:
14 virtual ~IScreenshotSurface() = default;
15 virtual bool Capture() { return false; }
16 virtual void CaptureVideo(bool blendToBuffer) { };
17
18 int GetWidth() const { return m_width; }
19 int GetHeight() const { return m_height; }
20 int GetStride() const { return m_stride; }
21 unsigned char* GetBuffer() const { return m_buffer; }
22 void ReleaseBuffer()
23 {
24 if (m_buffer)
25 {
26 delete m_buffer;
27 m_buffer = nullptr;
28 }
29 };
30
31protected:
32 int m_width{0};
33 int m_height{0};
34 int m_stride{0};
35 unsigned char* m_buffer{nullptr};
36};
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11class CVariant;
12
13class ISerializable
14{
15protected:
16 /* make sure nobody deletes a pointer to this class */
17 ~ISerializable() = default;
18
19 public:
20 virtual void Serialize(CVariant& value) const = 0;
21};
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 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "SortUtils.h"
12
13#include <map>
14
15class ISortable
16{
17protected:
18 /* make sure nobody deletes a pointer to this class */
19 ~ISortable() = default;
20
21public:
22 virtual void ToSortable(SortItem& sortable, Field field) const = 0;
23};
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 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11class TiXmlNode;
12
13class IXmlDeserializable
14{
15public:
16 virtual ~IXmlDeserializable() = default;
17
18 virtual bool Deserialize(const TiXmlNode *node) = 0;
19};
diff --git a/xbmc/utils/InfoLoader.cpp b/xbmc/utils/InfoLoader.cpp
new file mode 100644
index 0000000..cef6b70
--- /dev/null
+++ b/xbmc/utils/InfoLoader.cpp
@@ -0,0 +1,59 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "InfoLoader.h"
10
11#include "JobManager.h"
12#include "TimeUtils.h"
13#include "guilib/LocalizeStrings.h"
14
15CInfoLoader::CInfoLoader(unsigned int timeToRefresh)
16{
17 m_refreshTime = 0;
18 m_timeToRefresh = timeToRefresh;
19 m_busy = false;
20}
21
22CInfoLoader::~CInfoLoader() = default;
23
24void CInfoLoader::OnJobComplete(unsigned int jobID, bool success, CJob *job)
25{
26 m_refreshTime = CTimeUtils::GetFrameTime() + m_timeToRefresh;
27 m_busy = false;
28}
29
30std::string CInfoLoader::GetInfo(int info)
31{
32 // Refresh if need be
33 if (m_refreshTime < CTimeUtils::GetFrameTime() && !m_busy)
34 { // queue up the job
35 m_busy = true;
36 CJobManager::GetInstance().AddJob(GetJob(), this);
37 }
38 if (m_busy && CTimeUtils::GetFrameTime() - m_refreshTime > 1000)
39 {
40 return BusyInfo(info);
41 }
42 return TranslateInfo(info);
43}
44
45std::string CInfoLoader::BusyInfo(int info) const
46{
47 return g_localizeStrings.Get(503);
48}
49
50std::string CInfoLoader::TranslateInfo(int info) const
51{
52 return "";
53}
54
55void CInfoLoader::Refresh()
56{
57 m_refreshTime = CTimeUtils::GetFrameTime();
58}
59
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "Job.h"
12
13#include <string>
14
15class CInfoLoader : public IJobCallback
16{
17public:
18 explicit CInfoLoader(unsigned int timeToRefresh = 5 * 60 * 1000);
19 ~CInfoLoader() override;
20
21 std::string GetInfo(int info);
22 void Refresh();
23
24 void OnJobComplete(unsigned int jobID, bool success, CJob *job) override;
25protected:
26 virtual CJob *GetJob() const=0;
27 virtual std::string TranslateInfo(int info) const;
28 virtual std::string BusyInfo(int info) const;
29private:
30 unsigned int m_refreshTime;
31 unsigned int m_timeToRefresh;
32 bool m_busy;
33};
diff --git a/xbmc/utils/JSONVariantParser.cpp b/xbmc/utils/JSONVariantParser.cpp
new file mode 100644
index 0000000..f003cdf
--- /dev/null
+++ b/xbmc/utils/JSONVariantParser.cpp
@@ -0,0 +1,217 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "JSONVariantParser.h"
10
11#include <rapidjson/reader.h>
12
13class CJSONVariantParserHandler
14{
15public:
16 explicit CJSONVariantParserHandler(CVariant& parsedObject);
17
18 bool Null();
19 bool Bool(bool b);
20 bool Int(int i);
21 bool Uint(unsigned u);
22 bool Int64(int64_t i);
23 bool Uint64(uint64_t u);
24 bool Double(double d);
25 bool RawNumber(const char* str, rapidjson::SizeType length, bool copy);
26 bool String(const char* str, rapidjson::SizeType length, bool copy);
27 bool StartObject();
28 bool Key(const char* str, rapidjson::SizeType length, bool copy);
29 bool EndObject(rapidjson::SizeType memberCount);
30 bool StartArray();
31 bool EndArray(rapidjson::SizeType elementCount);
32
33private:
34 template <typename... TArgs>
35 bool Primitive(TArgs... args)
36 {
37 PushObject(CVariant(std::forward<TArgs>(args)...));
38 PopObject();
39
40 return true;
41 }
42
43 void PushObject(CVariant variant);
44 void PopObject();
45
46 CVariant& m_parsedObject;
47 std::vector<CVariant *> m_parse;
48 std::string m_key;
49
50 enum class PARSE_STATUS
51 {
52 Variable,
53 Array,
54 Object
55 };
56 PARSE_STATUS m_status;
57};
58
59CJSONVariantParserHandler::CJSONVariantParserHandler(CVariant& parsedObject)
60 : m_parsedObject(parsedObject),
61 m_parse(),
62 m_key(),
63 m_status(PARSE_STATUS::Variable)
64{ }
65
66bool CJSONVariantParserHandler::Null()
67{
68 PushObject(CVariant::ConstNullVariant);
69 PopObject();
70
71 return true;
72}
73
74bool CJSONVariantParserHandler::Bool(bool b)
75{
76 return Primitive(b);
77}
78
79bool CJSONVariantParserHandler::Int(int i)
80{
81 return Primitive(i);
82}
83
84bool CJSONVariantParserHandler::Uint(unsigned u)
85{
86 return Primitive(u);
87}
88
89bool CJSONVariantParserHandler::Int64(int64_t i)
90{
91 return Primitive(i);
92}
93
94bool CJSONVariantParserHandler::Uint64(uint64_t u)
95{
96 return Primitive(u);
97}
98
99bool CJSONVariantParserHandler::Double(double d)
100{
101 return Primitive(d);
102}
103
104bool CJSONVariantParserHandler::RawNumber(const char* str, rapidjson::SizeType length, bool copy)
105{
106 return Primitive(str, length);
107}
108
109bool CJSONVariantParserHandler::String(const char* str, rapidjson::SizeType length, bool copy)
110{
111 return Primitive(str, length);
112}
113
114bool CJSONVariantParserHandler::StartObject()
115{
116 PushObject(CVariant::VariantTypeObject);
117
118 return true;
119}
120
121bool CJSONVariantParserHandler::Key(const char* str, rapidjson::SizeType length, bool copy)
122{
123 m_key = std::string(str, 0, length);
124
125 return true;
126}
127
128bool CJSONVariantParserHandler::EndObject(rapidjson::SizeType memberCount)
129{
130 PopObject();
131
132 return true;
133}
134
135bool CJSONVariantParserHandler::StartArray()
136{
137 PushObject(CVariant::VariantTypeArray);
138
139 return true;
140}
141
142bool CJSONVariantParserHandler::EndArray(rapidjson::SizeType elementCount)
143{
144 PopObject();
145
146 return true;
147}
148
149void CJSONVariantParserHandler::PushObject(CVariant variant)
150{
151 if (m_status == PARSE_STATUS::Object)
152 {
153 (*m_parse[m_parse.size() - 1])[m_key] = variant;
154 m_parse.push_back(&(*m_parse[m_parse.size() - 1])[m_key]);
155 }
156 else if (m_status == PARSE_STATUS::Array)
157 {
158 CVariant *temp = m_parse[m_parse.size() - 1];
159 temp->push_back(variant);
160 m_parse.push_back(&(*temp)[temp->size() - 1]);
161 }
162 else if (m_parse.empty())
163 m_parse.push_back(new CVariant(variant));
164
165 if (variant.isObject())
166 m_status = PARSE_STATUS::Object;
167 else if (variant.isArray())
168 m_status = PARSE_STATUS::Array;
169 else
170 m_status = PARSE_STATUS::Variable;
171}
172
173void CJSONVariantParserHandler::PopObject()
174{
175 CVariant *variant = m_parse[m_parse.size() - 1];
176 m_parse.pop_back();
177
178 if (!m_parse.empty())
179 {
180 variant = m_parse[m_parse.size() - 1];
181 if (variant->isObject())
182 m_status = PARSE_STATUS::Object;
183 else if (variant->isArray())
184 m_status = PARSE_STATUS::Array;
185 else
186 m_status = PARSE_STATUS::Variable;
187 }
188 else
189 {
190 m_parsedObject = *variant;
191 delete variant;
192
193 m_status = PARSE_STATUS::Variable;
194 }
195}
196
197bool CJSONVariantParser::Parse(const char* json, CVariant& data)
198{
199 if (json == nullptr)
200 return false;
201
202 rapidjson::Reader reader;
203 rapidjson::StringStream stringStream(json);
204
205 CJSONVariantParserHandler handler(data);
206 // use kParseIterativeFlag to eliminate possible stack overflow
207 // from json parsing via reentrant calls
208 if (reader.Parse<rapidjson::kParseIterativeFlag>(stringStream, handler))
209 return true;
210
211 return false;
212}
213
214bool CJSONVariantParser::Parse(const std::string& json, CVariant& data)
215{
216 return Parse(json.c_str(), data);
217}
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 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/Variant.h"
12
13#include <string>
14
15class CJSONVariantParser
16{
17public:
18 CJSONVariantParser() = delete;
19
20 static bool Parse(const char* json, CVariant& data);
21 static bool Parse(const std::string& json, CVariant& data);
22};
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 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "JSONVariantWriter.h"
10
11#include "utils/Variant.h"
12
13#include <rapidjson/prettywriter.h>
14#include <rapidjson/stringbuffer.h>
15#include <rapidjson/writer.h>
16
17template<class TWriter>
18bool InternalWrite(TWriter& writer, const CVariant &value)
19{
20 switch (value.type())
21 {
22 case CVariant::VariantTypeInteger:
23 return writer.Int64(value.asInteger());
24
25 case CVariant::VariantTypeUnsignedInteger:
26 return writer.Uint64(value.asUnsignedInteger());
27
28 case CVariant::VariantTypeDouble:
29 return writer.Double(value.asDouble());
30
31 case CVariant::VariantTypeBoolean:
32 return writer.Bool(value.asBoolean());
33
34 case CVariant::VariantTypeString:
35 return writer.String(value.c_str(), value.size());
36
37 case CVariant::VariantTypeArray:
38 if (!writer.StartArray())
39 return false;
40
41 for (CVariant::const_iterator_array itr = value.begin_array(); itr != value.end_array(); ++itr)
42 {
43 if (!InternalWrite(writer, *itr))
44 return false;
45 }
46
47 return writer.EndArray(value.size());
48
49 case CVariant::VariantTypeObject:
50 if (!writer.StartObject())
51 return false;
52
53 for (CVariant::const_iterator_map itr = value.begin_map(); itr != value.end_map(); ++itr)
54 {
55 if (!writer.Key(itr->first.c_str()) ||
56 !InternalWrite(writer, itr->second))
57 return false;
58 }
59
60 return writer.EndObject(value.size());
61
62 case CVariant::VariantTypeConstNull:
63 case CVariant::VariantTypeNull:
64 default:
65 return writer.Null();
66 }
67
68 return false;
69}
70
71bool CJSONVariantWriter::Write(const CVariant &value, std::string& output, bool compact)
72{
73 rapidjson::StringBuffer stringBuffer;
74 if (compact)
75 {
76 rapidjson::Writer<rapidjson::StringBuffer> writer(stringBuffer);
77
78 if (!InternalWrite(writer, value) || !writer.IsComplete())
79 return false;
80 }
81 else
82 {
83 rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(stringBuffer);
84 writer.SetIndent('\t', 1);
85
86 if (!InternalWrite(writer, value) || !writer.IsComplete())
87 return false;
88 }
89
90 output = stringBuffer.GetString();
91 return true;
92}
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 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13class CVariant;
14
15class CJSONVariantWriter
16{
17public:
18 CJSONVariantWriter() = delete;
19
20 static bool Write(const CVariant &value, std::string& output, bool compact);
21};
diff --git a/xbmc/utils/Job.h b/xbmc/utils/Job.h
new file mode 100644
index 0000000..8acdd4e
--- /dev/null
+++ b/xbmc/utils/Job.h
@@ -0,0 +1,160 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11class CJob;
12
13#include <stddef.h>
14
15#define kJobTypeMediaFlags "mediaflags"
16#define kJobTypeCacheImage "cacheimage"
17#define kJobTypeDDSCompress "ddscompress"
18
19/*!
20 \ingroup jobs
21 \brief Callback interface for asynchronous jobs.
22
23 Used by clients of the CJobManager to receive progress and completion notification of jobs.
24 Clients of small jobs wishing to perform actions on job completion should implement the
25 IJobCallback::OnJobComplete() function. Clients of larger jobs may choose to implement the
26 IJobCallback::OnJobProgress() function in order to be kept informed of progress.
27
28 \sa CJobManager and CJob
29 */
30class IJobCallback
31{
32public:
33 /*!
34 \brief Destructor for job call back objects.
35
36 \sa CJobManager and CJob
37 */
38 virtual ~IJobCallback() = default;
39
40 /*!
41 \brief The callback used when a job completes.
42
43 OnJobComplete is called at the completion of the job's DoWork() function, and is used
44 to return information to the caller on the result of the job. On returning form this function
45 the CJobManager will destroy this job.
46
47 \param jobID the unique id of the job (as retrieved from CJobManager::AddJob)
48 \param success the result from the DoWork call
49 \param job the job that has been processed. The job will be destroyed after this function returns
50 \sa CJobManager and CJob
51 */
52 virtual void OnJobComplete(unsigned int jobID, bool success, CJob *job)=0;
53
54 /*!
55 \brief An optional callback function that a job may call while processing.
56
57 OnJobProgress may be called periodically by a job during it's DoWork() function. It is used
58 by the job to report on progress.
59
60 \param jobID the unique id of the job (as retrieved from CJobManager::AddJob)
61 \param progress the current progress of the job, out of total.
62 \param total the total amount of work to be processed.
63 \param job the job that has been processed.
64 \sa CJobManager and CJob
65 */
66 virtual void OnJobProgress(unsigned int jobID, unsigned int progress, unsigned int total, const CJob *job) {};
67};
68
69class CJobManager;
70
71/*!
72 \ingroup jobs
73 \brief Base class for jobs that are executed asynchronously.
74
75 Clients of the CJobManager should subclass CJob and provide the DoWork() function. Data should be
76 passed to the job on creation, and any data sharing between the job and the client should be kept to within
77 the callback functions if possible, and guarded with critical sections as appropriate.
78
79 Jobs typically fall into two groups: small jobs that perform a single function, and larger jobs that perform a
80 sequence of functions. Clients with small jobs should implement the IJobCallback::OnJobComplete() callback to receive results.
81 Clients with larger jobs may wish to implement both the IJobCallback::OnJobComplete() and IJobCallback::OnJobProgress()
82 callbacks to receive updates. Jobs may be cancelled at any point by the client via CJobManager::CancelJob(), however
83 effort should be taken to ensure that any callbacks and cancellation is suitably guarded against simultaneous thread access.
84
85 Handling cancellation of jobs within the OnJobProgress callback is a threadsafe operation, as all execution is
86 then in the Job thread.
87
88 \sa CJobManager and IJobCallback
89 */
90class CJob
91{
92public:
93 /*!
94 \brief Priority levels for jobs, specified by clients when adding jobs to the CJobManager.
95 \sa CJobManager
96 */
97 enum PRIORITY {
98 PRIORITY_LOW_PAUSABLE = 0,
99 PRIORITY_LOW,
100 PRIORITY_NORMAL,
101 PRIORITY_HIGH,
102 PRIORITY_DEDICATED, // will create a new worker if no worker is available at queue time
103 };
104 CJob() { m_callback = NULL; };
105
106 /*!
107 \brief Destructor for job objects.
108
109 Jobs are destroyed by the CJobManager after the OnJobComplete() callback is complete.
110 CJob subclasses should therefore supply a virtual destructor to cleanup any memory allocated by
111 complete or cancelled jobs.
112
113 \sa CJobManager
114 */
115 virtual ~CJob() = default;
116
117 /*!
118 \brief Main workhorse function of CJob instances
119
120 All CJob subclasses must implement this function, performing all processing. Once this function
121 is complete, the OnJobComplete() callback is called, and the job is then destroyed.
122
123 \sa CJobManager, IJobCallback::OnJobComplete()
124 */
125 virtual bool DoWork() = 0; // function to do the work
126
127 /*!
128 \brief Function that returns the type of job.
129
130 CJob subclasses may optionally implement this function to specify the type of job.
131 This is useful for the CJobManager::AddLIFOJob() routine, which preempts similar jobs
132 with the new job.
133
134 \return a unique character string describing the job.
135 \sa CJobManager
136 */
137 virtual const char *GetType() const { return ""; };
138
139 virtual bool operator==(const CJob* job) const
140 {
141 return false;
142 }
143
144 /*!
145 \brief Function for longer jobs to report progress and check whether they have been cancelled.
146
147 Jobs that contain loops that may take time should check this routine each iteration of the loop,
148 both to (optionally) report progress, and to check for cancellation.
149
150 \param progress the amount of the job performed, out of total.
151 \param total the total amount of processing to be performed
152 \return if true, the job has been asked to cancel.
153
154 \sa IJobCallback::OnJobProgress()
155 */
156 virtual bool ShouldCancel(unsigned int progress, unsigned int total) const;
157private:
158 friend class CJobManager;
159 CJobManager *m_callback;
160};
diff --git a/xbmc/utils/JobManager.cpp b/xbmc/utils/JobManager.cpp
new file mode 100644
index 0000000..3c8e04b
--- /dev/null
+++ b/xbmc/utils/JobManager.cpp
@@ -0,0 +1,423 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "JobManager.h"
10
11#include "threads/SingleLock.h"
12#include "utils/XTimeUtils.h"
13#include "utils/log.h"
14
15#include <algorithm>
16#include <functional>
17#include <stdexcept>
18
19bool CJob::ShouldCancel(unsigned int progress, unsigned int total) const
20{
21 if (m_callback)
22 return m_callback->OnJobProgress(progress, total, this);
23 return false;
24}
25
26CJobWorker::CJobWorker(CJobManager *manager) : CThread("JobWorker")
27{
28 m_jobManager = manager;
29 Create(true); // start work immediately, and kill ourselves when we're done
30}
31
32CJobWorker::~CJobWorker()
33{
34 // while we should already be removed from the job manager, if an exception
35 // occurs during processing that we haven't caught, we may skip over that step.
36 // Thus, before we go out of scope, ensure the job manager knows we're gone.
37 m_jobManager->RemoveWorker(this);
38 if(!IsAutoDelete())
39 StopThread();
40}
41
42void CJobWorker::Process()
43{
44 SetPriority( GetMinPriority() );
45 while (true)
46 {
47 // request an item from our manager (this call is blocking)
48 CJob *job = m_jobManager->GetNextJob(this);
49 if (!job)
50 break;
51
52 bool success = false;
53 try
54 {
55 success = job->DoWork();
56 }
57 catch (...)
58 {
59 CLog::Log(LOGERROR, "%s error processing job %s", __FUNCTION__, job->GetType());
60 }
61 m_jobManager->OnJobComplete(success, job);
62 }
63}
64
65void CJobQueue::CJobPointer::CancelJob()
66{
67 CJobManager::GetInstance().CancelJob(m_id);
68 m_id = 0;
69}
70
71CJobQueue::CJobQueue(bool lifo, unsigned int jobsAtOnce, CJob::PRIORITY priority)
72: m_jobsAtOnce(jobsAtOnce), m_priority(priority), m_lifo(lifo)
73{
74}
75
76CJobQueue::~CJobQueue()
77{
78 CancelJobs();
79}
80
81void CJobQueue::OnJobComplete(unsigned int jobID, bool success, CJob *job)
82{
83 CSingleLock lock(m_section);
84 // check if this job is in our processing list
85 Processing::iterator i = find(m_processing.begin(), m_processing.end(), job);
86 if (i != m_processing.end())
87 m_processing.erase(i);
88 // request a new job be queued
89 QueueNextJob();
90}
91
92void CJobQueue::CancelJob(const CJob *job)
93{
94 CSingleLock lock(m_section);
95 Processing::iterator i = find(m_processing.begin(), m_processing.end(), job);
96 if (i != m_processing.end())
97 {
98 i->CancelJob();
99 m_processing.erase(i);
100 return;
101 }
102 Queue::iterator j = find(m_jobQueue.begin(), m_jobQueue.end(), job);
103 if (j != m_jobQueue.end())
104 {
105 j->FreeJob();
106 m_jobQueue.erase(j);
107 }
108}
109
110bool CJobQueue::AddJob(CJob *job)
111{
112 CSingleLock lock(m_section);
113 // check if we have this job already. If so, we're done.
114 if (find(m_jobQueue.begin(), m_jobQueue.end(), job) != m_jobQueue.end() ||
115 find(m_processing.begin(), m_processing.end(), job) != m_processing.end())
116 {
117 delete job;
118 return false;
119 }
120
121 if (m_lifo)
122 m_jobQueue.push_back(CJobPointer(job));
123 else
124 m_jobQueue.push_front(CJobPointer(job));
125 QueueNextJob();
126
127 return true;
128}
129
130void CJobQueue::QueueNextJob()
131{
132 CSingleLock lock(m_section);
133 if (m_jobQueue.size() && m_processing.size() < m_jobsAtOnce)
134 {
135 CJobPointer &job = m_jobQueue.back();
136 job.m_id = CJobManager::GetInstance().AddJob(job.m_job, this, m_priority);
137 m_processing.push_back(job);
138 m_jobQueue.pop_back();
139 }
140}
141
142void CJobQueue::CancelJobs()
143{
144 CSingleLock lock(m_section);
145 for_each(m_processing.begin(), m_processing.end(), [](CJobPointer& jp) { jp.CancelJob(); });
146 for_each(m_jobQueue.begin(), m_jobQueue.end(), [](CJobPointer& jp) { jp.FreeJob(); });
147 m_jobQueue.clear();
148 m_processing.clear();
149}
150
151bool CJobQueue::IsProcessing() const
152{
153 return CJobManager::GetInstance().m_running && (!m_processing.empty() || !m_jobQueue.empty());
154}
155
156bool CJobQueue::QueueEmpty() const
157{
158 CSingleLock lock(m_section);
159 return m_jobQueue.empty();
160}
161
162CJobManager &CJobManager::GetInstance()
163{
164 static CJobManager sJobManager;
165 return sJobManager;
166}
167
168CJobManager::CJobManager()
169{
170 m_jobCounter = 0;
171 m_running = true;
172 m_pauseJobs = false;
173}
174
175void CJobManager::Restart()
176{
177 CSingleLock lock(m_section);
178
179 if (m_running)
180 throw std::logic_error("CJobManager already running");
181 m_running = true;
182}
183
184void CJobManager::CancelJobs()
185{
186 CSingleLock lock(m_section);
187 m_running = false;
188
189 // clear any pending jobs
190 for (unsigned int priority = CJob::PRIORITY_LOW_PAUSABLE; priority <= CJob::PRIORITY_DEDICATED; ++priority)
191 {
192 for_each(m_jobQueue[priority].begin(), m_jobQueue[priority].end(), [](CWorkItem& wi) { wi.FreeJob(); });
193 m_jobQueue[priority].clear();
194 }
195
196 // cancel any callbacks on jobs still processing
197 for_each(m_processing.begin(), m_processing.end(), [](CWorkItem& wi) { wi.Cancel(); });
198
199 // tell our workers to finish
200 while (m_workers.size())
201 {
202 lock.Leave();
203 m_jobEvent.Set();
204 std::this_thread::yield(); // yield after setting the event to give the workers some time to die
205 lock.Enter();
206 }
207}
208
209unsigned int CJobManager::AddJob(CJob *job, IJobCallback *callback, CJob::PRIORITY priority)
210{
211 CSingleLock lock(m_section);
212
213 if (!m_running)
214 return 0;
215
216 // increment the job counter, ensuring 0 (invalid job) is never hit
217 m_jobCounter++;
218 if (m_jobCounter == 0)
219 m_jobCounter++;
220
221 // create a work item for this job
222 CWorkItem work(job, m_jobCounter, priority, callback);
223 m_jobQueue[priority].push_back(work);
224
225 StartWorkers(priority);
226 return work.m_id;
227}
228
229void CJobManager::CancelJob(unsigned int jobID)
230{
231 CSingleLock lock(m_section);
232
233 // check whether we have this job in the queue
234 for (unsigned int priority = CJob::PRIORITY_LOW_PAUSABLE; priority <= CJob::PRIORITY_DEDICATED; ++priority)
235 {
236 JobQueue::iterator i = find(m_jobQueue[priority].begin(), m_jobQueue[priority].end(), jobID);
237 if (i != m_jobQueue[priority].end())
238 {
239 delete i->m_job;
240 m_jobQueue[priority].erase(i);
241 return;
242 }
243 }
244 // or if we're processing it
245 Processing::iterator it = find(m_processing.begin(), m_processing.end(), jobID);
246 if (it != m_processing.end())
247 it->m_callback = NULL; // job is in progress, so only thing to do is to remove callback
248}
249
250void CJobManager::StartWorkers(CJob::PRIORITY priority)
251{
252 CSingleLock lock(m_section);
253
254 // check how many free threads we have
255 if (m_processing.size() >= GetMaxWorkers(priority))
256 return;
257
258 // do we have any sleeping threads?
259 if (m_processing.size() < m_workers.size())
260 {
261 m_jobEvent.Set();
262 return;
263 }
264
265 // everyone is busy - we need more workers
266 m_workers.push_back(new CJobWorker(this));
267}
268
269CJob *CJobManager::PopJob()
270{
271 CSingleLock lock(m_section);
272 for (int priority = CJob::PRIORITY_DEDICATED; priority >= CJob::PRIORITY_LOW_PAUSABLE; --priority)
273 {
274 // Check whether we're pausing pausable jobs
275 if (priority == CJob::PRIORITY_LOW_PAUSABLE && m_pauseJobs)
276 continue;
277
278 if (m_jobQueue[priority].size() && m_processing.size() < GetMaxWorkers(CJob::PRIORITY(priority)))
279 {
280 // pop the job off the queue
281 CWorkItem job = m_jobQueue[priority].front();
282 m_jobQueue[priority].pop_front();
283
284 // add to the processing vector
285 m_processing.push_back(job);
286 job.m_job->m_callback = this;
287 return job.m_job;
288 }
289 }
290 return NULL;
291}
292
293void CJobManager::PauseJobs()
294{
295 CSingleLock lock(m_section);
296 m_pauseJobs = true;
297}
298
299void CJobManager::UnPauseJobs()
300{
301 CSingleLock lock(m_section);
302 m_pauseJobs = false;
303}
304
305bool CJobManager::IsProcessing(const CJob::PRIORITY &priority) const
306{
307 CSingleLock lock(m_section);
308
309 if (m_pauseJobs)
310 return false;
311
312 for(Processing::const_iterator it = m_processing.begin(); it < m_processing.end(); ++it)
313 {
314 if (priority == it->m_priority)
315 return true;
316 }
317 return false;
318}
319
320int CJobManager::IsProcessing(const std::string &type) const
321{
322 int jobsMatched = 0;
323 CSingleLock lock(m_section);
324
325 if (m_pauseJobs)
326 return 0;
327
328 for(Processing::const_iterator it = m_processing.begin(); it < m_processing.end(); ++it)
329 {
330 if (type == std::string(it->m_job->GetType()))
331 jobsMatched++;
332 }
333 return jobsMatched;
334}
335
336CJob *CJobManager::GetNextJob(const CJobWorker *worker)
337{
338 CSingleLock lock(m_section);
339 while (m_running)
340 {
341 // grab a job off the queue if we have one
342 CJob *job = PopJob();
343 if (job)
344 return job;
345 // no jobs are left - sleep for 30 seconds to allow new jobs to come in
346 lock.Leave();
347 bool newJob = m_jobEvent.WaitMSec(30000);
348 lock.Enter();
349 if (!newJob)
350 break;
351 }
352 // ensure no jobs have come in during the period after
353 // timeout and before we held the lock
354 CJob *job = PopJob();
355 if (job)
356 return job;
357 // have no jobs
358 RemoveWorker(worker);
359 return NULL;
360}
361
362bool CJobManager::OnJobProgress(unsigned int progress, unsigned int total, const CJob *job) const
363{
364 CSingleLock lock(m_section);
365 // find the job in the processing queue, and check whether it's cancelled (no callback)
366 Processing::const_iterator i = find(m_processing.begin(), m_processing.end(), job);
367 if (i != m_processing.end())
368 {
369 CWorkItem item(*i);
370 lock.Leave(); // leave section prior to call
371 if (item.m_callback)
372 {
373 item.m_callback->OnJobProgress(item.m_id, progress, total, job);
374 return false;
375 }
376 }
377 return true; // couldn't find the job, or it's been cancelled
378}
379
380void CJobManager::OnJobComplete(bool success, CJob *job)
381{
382 CSingleLock lock(m_section);
383 // remove the job from the processing queue
384 Processing::iterator i = find(m_processing.begin(), m_processing.end(), job);
385 if (i != m_processing.end())
386 {
387 // tell any listeners we're done with the job, then delete it
388 CWorkItem item(*i);
389 lock.Leave();
390 try
391 {
392 if (item.m_callback)
393 item.m_callback->OnJobComplete(item.m_id, success, item.m_job);
394 }
395 catch (...)
396 {
397 CLog::Log(LOGERROR, "%s error processing job %s", __FUNCTION__, item.m_job->GetType());
398 }
399 lock.Enter();
400 Processing::iterator j = find(m_processing.begin(), m_processing.end(), job);
401 if (j != m_processing.end())
402 m_processing.erase(j);
403 lock.Leave();
404 item.FreeJob();
405 }
406}
407
408void CJobManager::RemoveWorker(const CJobWorker *worker)
409{
410 CSingleLock lock(m_section);
411 // remove our worker
412 Workers::iterator i = find(m_workers.begin(), m_workers.end(), worker);
413 if (i != m_workers.end())
414 m_workers.erase(i); // workers auto-delete
415}
416
417unsigned int CJobManager::GetMaxWorkers(CJob::PRIORITY priority)
418{
419 static const unsigned int max_workers = 5;
420 if (priority == CJob::PRIORITY_DEDICATED)
421 return 10000; // A large number..
422 return max_workers - (CJob::PRIORITY_HIGH - priority);
423}
diff --git a/xbmc/utils/JobManager.h b/xbmc/utils/JobManager.h
new file mode 100644
index 0000000..ac4aa4e
--- /dev/null
+++ b/xbmc/utils/JobManager.h
@@ -0,0 +1,373 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "Job.h"
12#include "threads/CriticalSection.h"
13#include "threads/Thread.h"
14
15#include <queue>
16#include <string>
17#include <vector>
18
19class CJobManager;
20
21class CJobWorker : public CThread
22{
23public:
24 explicit CJobWorker(CJobManager *manager);
25 ~CJobWorker() override;
26
27 void Process() override;
28private:
29 CJobManager *m_jobManager;
30};
31
32template<typename F>
33class CLambdaJob : public CJob
34{
35public:
36 CLambdaJob(F&& f) : m_f(std::forward<F>(f)) {};
37 bool DoWork() override
38 {
39 m_f();
40 return true;
41 }
42 bool operator==(const CJob *job) const override
43 {
44 return this == job;
45 };
46private:
47 F m_f;
48};
49
50/*!
51 \ingroup jobs
52 \brief Job Queue class to handle a queue of unique jobs to be processed sequentially
53
54 Holds a queue of jobs to be processed sequentially, either first in,first out
55 or last in, first out. Jobs are unique, so queueing multiple copies of the same job
56 (based on the CJob::operator==) will not add additional jobs.
57
58 Classes should subclass this class and override OnJobCallback should they require
59 information from the job.
60
61 \sa CJob and IJobCallback
62 */
63class CJobQueue: public IJobCallback
64{
65 class CJobPointer
66 {
67 public:
68 explicit CJobPointer(CJob *job)
69 {
70 m_job = job;
71 m_id = 0;
72 };
73 void CancelJob();
74 void FreeJob()
75 {
76 delete m_job;
77 m_job = NULL;
78 };
79 bool operator==(const CJob *job) const
80 {
81 if (m_job)
82 return *m_job == job;
83 return false;
84 };
85 CJob *m_job;
86 unsigned int m_id;
87 };
88public:
89 /*!
90 \brief CJobQueue constructor
91 \param lifo whether the queue should be processed last in first out or first in first out. Defaults to false (first in first out)
92 \param jobsAtOnce number of jobs at once to process. Defaults to 1.
93 \param priority priority of this queue.
94 \sa CJob
95 */
96 CJobQueue(bool lifo = false, unsigned int jobsAtOnce = 1, CJob::PRIORITY priority = CJob::PRIORITY_LOW);
97
98 /*!
99 \brief CJobQueue destructor
100 Cancels any in-process jobs, and destroys the job queue.
101 \sa CJob
102 */
103 ~CJobQueue() override;
104
105 /*!
106 \brief Add a job to the queue
107 On completion of the job (or destruction of the job queue) the CJob object will be destroyed.
108 \param job a pointer to the job to add. The job should be subclassed from CJob.
109 \sa CJob
110 */
111 bool AddJob(CJob *job);
112
113 /*!
114 \brief Add a function f to this job queue
115 */
116 template<typename F>
117 void Submit(F&& f)
118 {
119 AddJob(new CLambdaJob<F>(std::forward<F>(f)));
120 }
121
122 /*!
123 \brief Cancel a job in the queue
124 Cancels a job in the queue. Any job currently being processed may complete after this
125 call has completed, but OnJobComplete will not be performed. If the job is only queued
126 then it will be removed from the queue and deleted.
127 \param job a pointer to the job to cancel. The job should be subclassed from CJob.
128 \sa CJob
129 */
130 void CancelJob(const CJob *job);
131
132 /*!
133 \brief Cancel all jobs in the queue
134 Removes all jobs from the queue. Any job currently being processed may complete after this
135 call has completed, but OnJobComplete will not be performed.
136 \sa CJob
137 */
138 void CancelJobs();
139
140 /*!
141 \brief Check whether the queue is processing a job
142 */
143 bool IsProcessing() const;
144
145 /*!
146 \brief The callback used when a job completes.
147
148 OnJobComplete is called at the completion of the CJob::DoWork function, and is used
149 to return information to the caller on the result of the job. On returning from this function
150 the CJobManager will destroy this job.
151
152 Subclasses should override this function if they wish to transfer information from the job prior
153 to it's deletion. They must then call this base class function, which will move on to the next
154 job.
155
156 \sa CJobManager, IJobCallback and CJob
157 */
158 void OnJobComplete(unsigned int jobID, bool success, CJob *job) override;
159
160protected:
161 /*!
162 \brief Returns if we still have jobs waiting to be processed
163 NOTE: This function does not take into account the jobs that are currently processing
164 */
165 bool QueueEmpty() const;
166
167private:
168 void QueueNextJob();
169
170 typedef std::deque<CJobPointer> Queue;
171 typedef std::vector<CJobPointer> Processing;
172 Queue m_jobQueue;
173 Processing m_processing;
174
175 unsigned int m_jobsAtOnce;
176 CJob::PRIORITY m_priority;
177 mutable CCriticalSection m_section;
178 bool m_lifo;
179};
180
181/*!
182 \ingroup jobs
183 \brief Job Manager class for scheduling asynchronous jobs.
184
185 Controls asynchronous job execution, by allowing clients to add and cancel jobs.
186 Should be accessed via CJobManager::GetInstance(). Jobs are allocated based on
187 priority levels. Lower priority jobs are executed only if there are sufficient
188 spare worker threads free to allow for higher priority jobs that may arise.
189
190 \sa CJob and IJobCallback
191 */
192class CJobManager final
193{
194 class CWorkItem
195 {
196 public:
197 CWorkItem(CJob *job, unsigned int id, CJob::PRIORITY priority, IJobCallback *callback)
198 {
199 m_job = job;
200 m_id = id;
201 m_callback = callback;
202 m_priority = priority;
203 }
204 bool operator==(unsigned int jobID) const
205 {
206 return m_id == jobID;
207 };
208 bool operator==(const CJob *job) const
209 {
210 return m_job == job;
211 };
212 void FreeJob()
213 {
214 delete m_job;
215 m_job = NULL;
216 };
217 void Cancel()
218 {
219 m_callback = NULL;
220 };
221 CJob *m_job;
222 unsigned int m_id;
223 IJobCallback *m_callback;
224 CJob::PRIORITY m_priority;
225 };
226
227public:
228 /*!
229 \brief The only way through which the global instance of the CJobManager should be accessed.
230 \return the global instance.
231 */
232 static CJobManager &GetInstance();
233
234 /*!
235 \brief Add a job to the threaded job manager.
236 \param job a pointer to the job to add. The job should be subclassed from CJob
237 \param callback a pointer to an IJobCallback instance to receive job progress and completion notices.
238 \param priority the priority that this job should run at.
239 \return a unique identifier for this job, to be used with other interaction
240 \sa CJob, IJobCallback, CancelJob()
241 */
242 unsigned int AddJob(CJob *job, IJobCallback *callback, CJob::PRIORITY priority = CJob::PRIORITY_LOW);
243
244 /*!
245 \brief Add a function f to this job manager for asynchronously execution.
246 */
247 template<typename F>
248 void Submit(F&& f, CJob::PRIORITY priority = CJob::PRIORITY_LOW)
249 {
250 AddJob(new CLambdaJob<F>(std::forward<F>(f)), nullptr, priority);
251 }
252
253 /*!
254 \brief Add a function f to this job manager for asynchronously execution.
255 */
256 template<typename F>
257 void Submit(F&& f, IJobCallback *callback, CJob::PRIORITY priority = CJob::PRIORITY_LOW)
258 {
259 AddJob(new CLambdaJob<F>(std::forward<F>(f)), callback, priority);
260 }
261
262 /*!
263 \brief Cancel a job with the given id.
264 \param jobID the id of the job to cancel, retrieved previously from AddJob()
265 \sa AddJob()
266 */
267 void CancelJob(unsigned int jobID);
268
269 /*!
270 \brief Cancel all remaining jobs, preparing for shutdown
271 Should be called prior to destroying any objects that may be being used as callbacks
272 \sa CancelJob(), AddJob()
273 */
274 void CancelJobs();
275
276 /*!
277 \brief Re-start accepting jobs again
278 Called after calling CancelJobs() to allow this manager to accept more jobs
279 \throws std::logic_error if the manager was not previously cancelled
280 \sa CancelJobs()
281 */
282 void Restart();
283
284 /*!
285 \brief Checks to see if any jobs of a specific type are currently processing.
286 \param type Job type to search for
287 \return Number of matching jobs
288 */
289 int IsProcessing(const std::string &type) const;
290
291 /*!
292 \brief Suspends queueing of jobs with priority PRIORITY_LOW_PAUSABLE until unpaused
293 Useful to (for ex) stop queuing thumb jobs during video start/playback.
294 Does not affect currently processing jobs, use IsProcessing to see if any need to be waited on
295 \sa UnPauseJobs()
296 */
297 void PauseJobs();
298
299 /*!
300 \brief Resumes queueing of (previously paused) jobs with priority PRIORITY_LOW_PAUSABLE
301 \sa PauseJobs()
302 */
303 void UnPauseJobs();
304
305 /*!
306 \brief Checks to see if any jobs with specific priority are currently processing.
307 \param priority to search for
308 \return true if processing jobs, else returns false
309 */
310 bool IsProcessing(const CJob::PRIORITY &priority) const;
311
312protected:
313 friend class CJobWorker;
314 friend class CJob;
315 friend class CJobQueue;
316
317 /*!
318 \brief Get a new job to process. Blocks until a new job is available, or a timeout has occurred.
319 \param worker a pointer to the current CJobWorker instance requesting a job.
320 \sa CJob
321 */
322 CJob *GetNextJob(const CJobWorker *worker);
323
324 /*!
325 \brief Callback from CJobWorker after a job has completed.
326 Calls IJobCallback::OnJobComplete(), and then destroys job.
327 \param job a pointer to the calling subclassed CJob instance.
328 \param success the result from the DoWork call
329 \sa IJobCallback, CJob
330 */
331 void OnJobComplete(bool success, CJob *job);
332
333 /*!
334 \brief Callback from CJob to report progress and check for cancellation.
335 Checks for cancellation, and calls IJobCallback::OnJobProgress().
336 \param progress amount of processing performed to date, out of total.
337 \param total total amount of processing.
338 \param job pointer to the calling subclassed CJob instance.
339 \return true if the job has been cancelled, else returns false.
340 \sa IJobCallback, CJob
341 */
342 bool OnJobProgress(unsigned int progress, unsigned int total, const CJob *job) const;
343
344private:
345 // private construction, and no assignments; use the provided singleton methods
346 CJobManager();
347 CJobManager(const CJobManager&) = delete;
348 CJobManager const& operator=(CJobManager const&) = delete;
349
350 /*! \brief Pop a job off the job queue and add to the processing queue ready to process
351 \return the job to process, NULL if no jobs are available
352 */
353 CJob *PopJob();
354
355 void StartWorkers(CJob::PRIORITY priority);
356 void RemoveWorker(const CJobWorker *worker);
357 static unsigned int GetMaxWorkers(CJob::PRIORITY priority);
358
359 unsigned int m_jobCounter;
360
361 typedef std::deque<CWorkItem> JobQueue;
362 typedef std::vector<CWorkItem> Processing;
363 typedef std::vector<CJobWorker*> Workers;
364
365 JobQueue m_jobQueue[CJob::PRIORITY_DEDICATED + 1];
366 bool m_pauseJobs;
367 Processing m_processing;
368 Workers m_workers;
369
370 mutable CCriticalSection m_section;
371 CEvent m_jobEvent;
372 bool m_running;
373};
diff --git a/xbmc/utils/LabelFormatter.cpp b/xbmc/utils/LabelFormatter.cpp
new file mode 100644
index 0000000..55eeb4f
--- /dev/null
+++ b/xbmc/utils/LabelFormatter.cpp
@@ -0,0 +1,479 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "LabelFormatter.h"
10
11#include "FileItem.h"
12#include "RegExp.h"
13#include "ServiceBroker.h"
14#include "StringUtils.h"
15#include "URIUtils.h"
16#include "Util.h"
17#include "Variant.h"
18#include "guilib/LocalizeStrings.h"
19#include "music/tags/MusicInfoTag.h"
20#include "pictures/PictureInfoTag.h"
21#include "settings/AdvancedSettings.h"
22#include "settings/Settings.h"
23#include "settings/SettingsComponent.h"
24#include "video/VideoInfoTag.h"
25
26#include <cassert>
27#include <cstdlib>
28#include <inttypes.h>
29
30using namespace MUSIC_INFO;
31
32/* LabelFormatter
33 * ==============
34 *
35 * The purpose of this class is to parse a mask string of the form
36 *
37 * [%N. ][%T] - [%A][ (%Y)]
38 *
39 * and provide methods to format up a CFileItem's label(s).
40 *
41 * The %N/%A/%B masks are replaced with the corresponding metadata (if available).
42 *
43 * Square brackets are treated as a metadata block. Anything inside the block other
44 * than the metadata mask is treated as either a prefix or postfix to the metadata. This
45 * information is only included in the formatted string when the metadata is non-empty.
46 *
47 * Any metadata tags not enclosed with square brackets are treated as if it were immediately
48 * enclosed - i.e. with no prefix or postfix.
49 *
50 * The special characters %, [, and ] can be produced using %%, %[, and %] respectively.
51 *
52 * Any static text outside of the metadata blocks is only shown if the blocks on either side
53 * (or just one side in the case of an end) are both non-empty.
54 *
55 * Examples (using the above expression):
56 *
57 * Track Title Artist Year Resulting Label
58 * ----- ----- ------ ---- ---------------
59 * 10 "40" U2 1983 10. "40" - U2 (1983)
60 * "40" U2 1983 "40" - U2 (1983)
61 * 10 U2 1983 10. U2 (1983)
62 * 10 "40" 1983 "40" (1983)
63 * 10 "40" U2 10. "40" - U2
64 * 10 "40" 10. "40"
65 *
66 * Available metadata masks:
67 *
68 * %A - Artist
69 * %B - Album
70 * %C - Programs count
71 * %D - Duration
72 * %E - episode number
73 * %F - FileName
74 * %G - Genre
75 * %H - season*100+episode
76 * %I - Size
77 * %J - Date
78 * %K - Movie/Game title
79 * %L - existing Label
80 * %M - number of episodes
81 * %N - Track Number
82 * %O - mpaa rating
83 * %P - production code
84 * %Q - file time
85 * %R - Movie rating
86 * %S - Disc Number
87 * %T - Title
88 * %U - studio
89 * %V - Playcount
90 * %W - Listeners
91 * %X - Bitrate
92 * %Y - Year
93 * %Z - tvshow title
94 * %a - Date Added
95 * %b - Total number of discs
96 * %c - Relevance - Used for actors' appearances
97 * %d - Date and Time
98 * %e - Original release date
99 * %f - bpm
100 * %p - Last Played
101 * %r - User Rating
102 * *t - Date Taken (suitable for Pictures)
103 */
104
105#define MASK_CHARS "NSATBGYFLDIJRCKMEPHZOQUVXWabcdefiprstuv"
106
107CLabelFormatter::CLabelFormatter(const std::string &mask, const std::string &mask2)
108{
109 // assemble our label masks
110 AssembleMask(0, mask);
111 AssembleMask(1, mask2);
112 // save a bool for faster lookups
113 m_hideFileExtensions = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWEXTENSIONS);
114}
115
116std::string CLabelFormatter::GetContent(unsigned int label, const CFileItem *item) const
117{
118 assert(label < 2);
119 assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1);
120
121 if (!item) return "";
122
123 std::string strLabel, dynamicLeft, dynamicRight;
124 for (unsigned int i = 0; i < m_dynamicContent[label].size(); i++)
125 {
126 dynamicRight = GetMaskContent(m_dynamicContent[label][i], item);
127 if ((i == 0 || !dynamicLeft.empty()) && !dynamicRight.empty())
128 strLabel += m_staticContent[label][i];
129 strLabel += dynamicRight;
130 dynamicLeft = dynamicRight;
131 }
132 if (!dynamicLeft.empty())
133 strLabel += m_staticContent[label][m_dynamicContent[label].size()];
134
135 return strLabel;
136}
137
138void CLabelFormatter::FormatLabel(CFileItem *item) const
139{
140 std::string maskedLabel = GetContent(0, item);
141 if (!maskedLabel.empty())
142 item->SetLabel(maskedLabel);
143 else if (!item->m_bIsFolder && m_hideFileExtensions)
144 item->RemoveExtension();
145}
146
147void CLabelFormatter::FormatLabel2(CFileItem *item) const
148{
149 item->SetLabel2(GetContent(1, item));
150}
151
152std::string CLabelFormatter::GetMaskContent(const CMaskString &mask, const CFileItem *item) const
153{
154 if (!item) return "";
155 const CMusicInfoTag *music = item->GetMusicInfoTag();
156 const CVideoInfoTag *movie = item->GetVideoInfoTag();
157 const CPictureInfoTag *pic = item->GetPictureInfoTag();
158 std::string value;
159 switch (mask.m_content)
160 {
161 case 'N':
162 if (music && music->GetTrackNumber() > 0)
163 value = StringUtils::Format("%2.2i", music->GetTrackNumber());
164 if (movie&& movie->m_iTrack > 0)
165 value = StringUtils::Format("%2.2i", movie->m_iTrack);
166 break;
167 case 'S':
168 if (music && music->GetDiscNumber() > 0)
169 value = StringUtils::Format("%2.2i", music->GetDiscNumber());
170 break;
171 case 'A':
172 if (music && music->GetArtistString().size())
173 value = music->GetArtistString();
174 if (movie && movie->m_artist.size())
175 value = StringUtils::Join(movie->m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
176 break;
177 case 'T':
178 if (music && music->GetTitle().size())
179 value = music->GetTitle();
180 if (movie && movie->m_strTitle.size())
181 value = movie->m_strTitle;
182 break;
183 case 'Z':
184 if (movie && !movie->m_strShowTitle.empty())
185 value = movie->m_strShowTitle;
186 break;
187 case 'B':
188 if (music && music->GetAlbum().size())
189 value = music->GetAlbum();
190 else if (movie)
191 value = movie->m_strAlbum;
192 break;
193 case 'G':
194 if (music && music->GetGenre().size())
195 value = StringUtils::Join(music->GetGenre(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
196 if (movie && movie->m_genre.size())
197 value = StringUtils::Join(movie->m_genre, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
198 break;
199 case 'Y':
200 if (music)
201 value = music->GetYearString();
202 if (movie)
203 {
204 if (movie->m_firstAired.IsValid())
205 value = movie->m_firstAired.GetAsLocalizedDate();
206 else if (movie->HasYear())
207 value = StringUtils::Format("%i", movie->GetYear());
208 }
209 break;
210 case 'F': // filename
211 value = CUtil::GetTitleFromPath(item->GetPath(), item->m_bIsFolder && !item->IsFileFolder());
212 break;
213 case 'L':
214 value = item->GetLabel();
215 // is the label the actual file or folder name?
216 if (value == URIUtils::GetFileName(item->GetPath()))
217 { // label is the same as filename, clean it up as appropriate
218 value = CUtil::GetTitleFromPath(item->GetPath(), item->m_bIsFolder && !item->IsFileFolder());
219 }
220 break;
221 case 'D':
222 { // duration
223 int nDuration=0;
224 if (music)
225 nDuration = music->GetDuration();
226 if (movie)
227 nDuration = movie->GetDuration();
228 if (nDuration > 0)
229 value = StringUtils::SecondsToTimeString(nDuration, (nDuration >= 3600) ? TIME_FORMAT_H_MM_SS : TIME_FORMAT_MM_SS);
230 else if (item->m_dwSize > 0)
231 value = StringUtils::SizeToString(item->m_dwSize);
232 }
233 break;
234 case 'I': // size
235 if( (item->m_bIsFolder && item->m_dwSize != 0) || item->m_dwSize >= 0 )
236 value = StringUtils::SizeToString(item->m_dwSize);
237 break;
238 case 'J': // date
239 if (item->m_dateTime.IsValid())
240 value = item->m_dateTime.GetAsLocalizedDate();
241 break;
242 case 'Q': // time
243 if (item->m_dateTime.IsValid())
244 value = item->m_dateTime.GetAsLocalizedTime("", false);
245 break;
246 case 'R': // rating
247 if (music && music->GetRating() != 0.f)
248 value = StringUtils::Format("%.1f", music->GetRating());
249 else if (movie && movie->GetRating().rating != 0.f)
250 value = StringUtils::Format("%.1f", movie->GetRating().rating);
251 break;
252 case 'C': // programs count
253 value = StringUtils::Format("%i", item->m_iprogramCount);
254 break;
255 case 'c': // relevance
256 value = StringUtils::Format("%i", movie->m_relevance);
257 break;
258 case 'K':
259 value = item->m_strTitle;
260 break;
261 case 'M':
262 if (movie && movie->m_iEpisode > 0)
263 value = StringUtils::Format("%i %s",
264 movie->m_iEpisode,
265 g_localizeStrings.Get(movie->m_iEpisode == 1 ? 20452 : 20453).c_str());
266 break;
267 case 'E':
268 if (movie && movie->m_iEpisode > 0)
269 { // episode number
270 if (movie->m_iSeason == 0)
271 value = StringUtils::Format("S%2.2i", movie->m_iEpisode);
272 else
273 value = StringUtils::Format("%2.2i", movie->m_iEpisode);
274 }
275 break;
276 case 'P':
277 if (movie) // tvshow production code
278 value = movie->m_strProductionCode;
279 break;
280 case 'H':
281 if (movie && movie->m_iEpisode > 0)
282 { // season*100+episode number
283 if (movie->m_iSeason == 0)
284 value = StringUtils::Format("S%2.2i", movie->m_iEpisode);
285 else
286 value = StringUtils::Format("%ix%2.2i", movie->m_iSeason,movie->m_iEpisode);
287 }
288 break;
289 case 'O':
290 if (movie)
291 {// MPAA Rating
292 value = movie->m_strMPAARating;
293 }
294 break;
295 case 'U':
296 if (movie && !movie->m_studio.empty())
297 {// Studios
298 value = StringUtils::Join(movie ->m_studio, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
299 }
300 break;
301 case 'V': // Playcount
302 if (music)
303 value = StringUtils::Format("%i", music->GetPlayCount());
304 if (movie)
305 value = StringUtils::Format("%i", movie->GetPlayCount());
306 break;
307 case 'X': // Bitrate
308 if( !item->m_bIsFolder && item->m_dwSize != 0 )
309 value = StringUtils::Format("%" PRId64" kbps", item->m_dwSize);
310 break;
311 case 'W': // Listeners
312 if( !item->m_bIsFolder && music && music->GetListeners() != 0 )
313 value = StringUtils::Format("%i %s",
314 music->GetListeners(),
315 g_localizeStrings.Get(music->GetListeners() == 1 ? 20454 : 20455).c_str());
316 break;
317 case 'a': // Date Added
318 if (movie && movie->m_dateAdded.IsValid())
319 value = movie->m_dateAdded.GetAsLocalizedDate();
320 if (music && music->GetDateAdded().IsValid())
321 value = music->GetDateAdded().GetAsLocalizedDate();
322 break;
323 case 'b': // Total number of discs
324 if (music)
325 value = StringUtils::Format("%i", music->GetTotalDiscs());
326 break;
327 case 'e': // Original release date
328 if (music)
329 {
330 value = music->GetOriginalDate();
331 if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bMusicLibraryUseISODates)
332 value = StringUtils::ISODateToLocalizedDate(value);
333 break;
334 }
335 case 'd': // date and time
336 if (item->m_dateTime.IsValid())
337 value = item->m_dateTime.GetAsLocalizedDateTime();
338 break;
339 case 'p': // Last played
340 if (movie && movie->m_lastPlayed.IsValid())
341 value = movie->m_lastPlayed.GetAsLocalizedDate();
342 if (music && music->GetLastPlayed().IsValid())
343 value = music->GetLastPlayed().GetAsLocalizedDate();
344 break;
345 case 'r': // userrating
346 if (movie && movie->m_iUserRating != 0)
347 value = StringUtils::Format("%i", movie->m_iUserRating);
348 if (music && music->GetUserrating() != 0)
349 value = StringUtils::Format("%i", music->GetUserrating());
350 break;
351 case 't': // Date Taken
352 if (pic && pic->GetDateTimeTaken().IsValid())
353 value = pic->GetDateTimeTaken().GetAsLocalizedDate();
354 break;
355 case 's': // Addon status
356 if (item->HasProperty("Addon.Status"))
357 value = item->GetProperty("Addon.Status").asString();
358 break;
359 case 'i': // Install date
360 if (item->HasAddonInfo() && item->GetAddonInfo()->InstallDate().IsValid())
361 value = item->GetAddonInfo()->InstallDate().GetAsLocalizedDate();
362 break;
363 case 'u': // Last used
364 if (item->HasAddonInfo() && item->GetAddonInfo()->LastUsed().IsValid())
365 value = item->GetAddonInfo()->LastUsed().GetAsLocalizedDate();
366 break;
367 case 'v': // Last updated
368 if (item->HasAddonInfo() && item->GetAddonInfo()->LastUpdated().IsValid())
369 value = item->GetAddonInfo()->LastUpdated().GetAsLocalizedDate();
370 break;
371 case 'f': // BPM
372 if (music)
373 value = StringUtils::Format("%i", music->GetBPM());
374 break;
375 }
376 if (!value.empty())
377 return mask.m_prefix + value + mask.m_postfix;
378 return "";
379}
380
381void CLabelFormatter::SplitMask(unsigned int label, const std::string &mask)
382{
383 assert(label < 2);
384 CRegExp reg;
385 reg.RegComp("%([" MASK_CHARS "])");
386 std::string work(mask);
387 int findStart = -1;
388 while ((findStart = reg.RegFind(work.c_str())) >= 0)
389 { // we've found a match
390 m_staticContent[label].push_back(work.substr(0, findStart));
391 m_dynamicContent[label].emplace_back("", reg.GetMatch(1)[0], "");
392 work = work.substr(findStart + reg.GetFindLen());
393 }
394 m_staticContent[label].push_back(work);
395}
396
397void CLabelFormatter::AssembleMask(unsigned int label, const std::string& mask)
398{
399 assert(label < 2);
400 m_staticContent[label].clear();
401 m_dynamicContent[label].clear();
402
403 // we want to match [<prefix>%A<postfix]
404 // but allow %%, %[, %] to be in the prefix and postfix. Anything before the first [
405 // could be a mask that's not surrounded with [], so pass to SplitMask.
406 CRegExp reg;
407 reg.RegComp("(^|[^%])\\[(([^%]|%%|%\\]|%\\[)*)%([" MASK_CHARS "])(([^%]|%%|%\\]|%\\[)*)\\]");
408 std::string work(mask);
409 int findStart = -1;
410 while ((findStart = reg.RegFind(work.c_str())) >= 0)
411 { // we've found a match for a pre/postfixed string
412 // send anything
413 SplitMask(label, work.substr(0, findStart) + reg.GetMatch(1));
414 m_dynamicContent[label].emplace_back(reg.GetMatch(2), reg.GetMatch(4)[0], reg.GetMatch(5));
415 work = work.substr(findStart + reg.GetFindLen());
416 }
417 SplitMask(label, work);
418 assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1);
419}
420
421bool CLabelFormatter::FillMusicTag(const std::string &fileName, CMusicInfoTag *tag) const
422{
423 // run through and find static content to split the string up
424 size_t pos1 = fileName.find(m_staticContent[0][0], 0);
425 if (pos1 == std::string::npos)
426 return false;
427 for (unsigned int i = 1; i < m_staticContent[0].size(); i++)
428 {
429 size_t pos2 = m_staticContent[0][i].size() ? fileName.find(m_staticContent[0][i], pos1) : fileName.size();
430 if (pos2 == std::string::npos)
431 return false;
432 // found static content - thus we have the dynamic content surrounded
433 FillMusicMaskContent(m_dynamicContent[0][i - 1].m_content, fileName.substr(pos1, pos2 - pos1), tag);
434 pos1 = pos2 + m_staticContent[0][i].size();
435 }
436 return true;
437}
438
439void CLabelFormatter::FillMusicMaskContent(const char mask, const std::string &value, CMusicInfoTag *tag) const
440{
441 if (!tag) return;
442 switch (mask)
443 {
444 case 'N':
445 tag->SetTrackNumber(atol(value.c_str()));
446 break;
447 case 'S':
448 tag->SetDiscNumber(atol(value.c_str()));
449 break;
450 case 'A':
451 tag->SetArtist(value);
452 break;
453 case 'T':
454 tag->SetTitle(value);
455 break;
456 case 'B':
457 tag->SetAlbum(value);
458 break;
459 case 'G':
460 tag->SetGenre(value);
461 break;
462 case 'Y':
463 tag->SetYear(atol(value.c_str()));
464 break;
465 case 'D':
466 tag->SetDuration(StringUtils::TimeStringToSeconds(value));
467 break;
468 case 'R': // rating
469 tag->SetRating(value[0]);
470 break;
471 case 'r': // userrating
472 tag->SetUserrating(value[0]);
473 break;
474 case 'b': // total discs
475 tag->SetTotalDiscs(atol(value.c_str()));
476 break;
477 }
478}
479
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12#include <vector>
13
14namespace MUSIC_INFO
15{
16 class CMusicInfoTag;
17}
18
19class CFileItem; // forward
20
21struct LABEL_MASKS
22{
23 LABEL_MASKS(const std::string& strLabelFile="", const std::string& strLabel2File="", const std::string& strLabelFolder="", const std::string& strLabel2Folder="") :
24 m_strLabelFile(strLabelFile),
25 m_strLabel2File(strLabel2File),
26 m_strLabelFolder(strLabelFolder),
27 m_strLabel2Folder(strLabel2Folder)
28 {}
29 std::string m_strLabelFile;
30 std::string m_strLabel2File;
31 std::string m_strLabelFolder;
32 std::string m_strLabel2Folder;
33};
34
35class CLabelFormatter
36{
37public:
38 CLabelFormatter(const std::string &mask, const std::string &mask2);
39
40 void FormatLabel(CFileItem *item) const;
41 void FormatLabel2(CFileItem *item) const;
42 void FormatLabels(CFileItem *item) const // convenient shorthand
43 {
44 FormatLabel(item);
45 FormatLabel2(item);
46 }
47
48 bool FillMusicTag(const std::string &fileName, MUSIC_INFO::CMusicInfoTag *tag) const;
49
50private:
51 class CMaskString
52 {
53 public:
54 CMaskString(const std::string &prefix, char content, const std::string &postfix) :
55 m_prefix(prefix),
56 m_postfix(postfix),
57 m_content(content)
58 {};
59 std::string m_prefix;
60 std::string m_postfix;
61 char m_content;
62 };
63
64 // functions for assembling the mask vectors
65 void AssembleMask(unsigned int label, const std::string &mask);
66 void SplitMask(unsigned int label, const std::string &mask);
67
68 // functions for retrieving content based on our mask vectors
69 std::string GetContent(unsigned int label, const CFileItem *item) const;
70 std::string GetMaskContent(const CMaskString &mask, const CFileItem *item) const;
71 void FillMusicMaskContent(const char mask, const std::string &value, MUSIC_INFO::CMusicInfoTag *tag) const;
72
73 std::vector<std::string> m_staticContent[2];
74 std::vector<CMaskString> m_dynamicContent[2];
75 bool m_hideFileExtensions;
76};
diff --git a/xbmc/utils/LangCodeExpander.cpp b/xbmc/utils/LangCodeExpander.cpp
new file mode 100644
index 0000000..bc1e06b
--- /dev/null
+++ b/xbmc/utils/LangCodeExpander.cpp
@@ -0,0 +1,1738 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "LangCodeExpander.h"
10
11#include "utils/StringUtils.h"
12#include "utils/XBMCTinyXML.h"
13
14#include <algorithm>
15#include <array>
16
17#define MAKECODE(a, b, c, d) \
18 ((((long)(a)) << 24) | (((long)(b)) << 16) | (((long)(c)) << 8) | (long)(d))
19#define MAKETWOCHARCODE(a, b) ((((long)(a)) << 8) | (long)(b))
20
21typedef struct LCENTRY
22{
23 long code;
24 const char* name;
25} LCENTRY;
26
27extern const std::array<struct LCENTRY, 186> g_iso639_1;
28extern const std::array<struct LCENTRY, 540> g_iso639_2;
29
30struct ISO639
31{
32 const char* iso639_1;
33 const char* iso639_2b;
34 const char* iso639_2t;
35 const char* win_id;
36};
37
38struct ISO3166_1
39{
40 const char* alpha2;
41 const char* alpha3;
42};
43
44// declared as extern to allow forward declaration
45extern const std::array<ISO639, 190> LanguageCodes;
46extern const std::array<ISO3166_1, 245> RegionCodes;
47
48CLangCodeExpander::CLangCodeExpander() = default;
49
50CLangCodeExpander::~CLangCodeExpander() = default;
51
52void CLangCodeExpander::Clear()
53{
54 m_mapUser.clear();
55}
56
57void CLangCodeExpander::LoadUserCodes(const TiXmlElement* pRootElement)
58{
59 if (pRootElement != NULL)
60 {
61 m_mapUser.clear();
62
63 std::string sShort, sLong;
64
65 const TiXmlNode* pLangCode = pRootElement->FirstChild("code");
66 while (pLangCode != NULL)
67 {
68 const TiXmlNode* pShort = pLangCode->FirstChildElement("short");
69 const TiXmlNode* pLong = pLangCode->FirstChildElement("long");
70 if (pShort != NULL && pLong != NULL)
71 {
72 sShort = pShort->FirstChild()->Value();
73 sLong = pLong->FirstChild()->Value();
74 StringUtils::ToLower(sShort);
75
76 m_mapUser[sShort] = sLong;
77 }
78
79 pLangCode = pLangCode->NextSibling();
80 }
81 }
82}
83
84bool CLangCodeExpander::Lookup(const std::string& code, std::string& desc)
85{
86 int iSplit = code.find("-");
87 if (iSplit > 0)
88 {
89 std::string strLeft, strRight;
90 const bool bLeft = Lookup(code.substr(0, iSplit), strLeft);
91 const bool bRight = Lookup(code.substr(iSplit + 1), strRight);
92 if (bLeft || bRight)
93 {
94 desc = "";
95 if (strLeft.length() > 0)
96 desc = strLeft;
97 else
98 desc = code.substr(0, iSplit);
99
100 if (strRight.length() > 0)
101 {
102 desc += " - ";
103 desc += strRight;
104 }
105 else
106 {
107 desc += " - ";
108 desc += code.substr(iSplit + 1);
109 }
110
111 return true;
112 }
113
114 return false;
115 }
116
117 if (LookupInUserMap(code, desc))
118 return true;
119
120 if (LookupInISO639Tables(code, desc))
121 return true;
122
123 return false;
124}
125
126bool CLangCodeExpander::Lookup(const int code, std::string& desc)
127{
128 char lang[3];
129 lang[2] = 0;
130 lang[1] = (code & 0xFF);
131 lang[0] = (code >> 8) & 0xFF;
132
133 return Lookup(lang, desc);
134}
135
136bool CLangCodeExpander::ConvertISO6391ToISO6392B(const std::string& strISO6391,
137 std::string& strISO6392B,
138 bool checkWin32Locales /*= false*/)
139{
140 // not a 2 char code
141 if (strISO6391.length() != 2)
142 return false;
143
144 std::string strISO6391Lower(strISO6391);
145 StringUtils::ToLower(strISO6391Lower);
146 StringUtils::Trim(strISO6391Lower);
147
148 for (const auto& codes : LanguageCodes)
149 {
150 if (strISO6391Lower == codes.iso639_1)
151 {
152 if (checkWin32Locales && codes.win_id)
153 {
154 strISO6392B = codes.win_id;
155 return true;
156 }
157
158 strISO6392B = codes.iso639_2b;
159 return true;
160 }
161 }
162
163 return false;
164}
165
166bool CLangCodeExpander::ConvertToISO6392B(const std::string& strCharCode,
167 std::string& strISO6392B,
168 bool checkWin32Locales /* = false */)
169{
170
171 //first search in the user defined map
172 if (LookupUserCode(strCharCode, strISO6392B))
173 return true;
174
175 if (strCharCode.size() == 2)
176 return g_LangCodeExpander.ConvertISO6391ToISO6392B(strCharCode, strISO6392B, checkWin32Locales);
177
178 if (strCharCode.size() == 3)
179 {
180 std::string charCode(strCharCode);
181 StringUtils::ToLower(charCode);
182 for (const auto& codes : LanguageCodes)
183 {
184 if (charCode == codes.iso639_2b ||
185 (checkWin32Locales && codes.win_id != NULL && charCode == codes.win_id))
186 {
187 strISO6392B = charCode;
188 return true;
189 }
190 }
191
192 for (const auto& codes : RegionCodes)
193 {
194 if (charCode == codes.alpha3)
195 {
196 strISO6392B = charCode;
197 return true;
198 }
199 }
200 }
201 else if (strCharCode.size() > 3)
202 {
203 for (const auto& codes : g_iso639_2)
204 {
205 if (StringUtils::EqualsNoCase(strCharCode, codes.name))
206 {
207 CodeToString(codes.code, strISO6392B);
208 return true;
209 }
210 }
211 }
212 return false;
213}
214
215bool CLangCodeExpander::ConvertToISO6392T(const std::string& strCharCode,
216 std::string& strISO6392T,
217 bool checkWin32Locales /* = false */)
218{
219 if (!ConvertToISO6392B(strCharCode, strISO6392T, checkWin32Locales))
220 return false;
221
222 for (const auto& codes : LanguageCodes)
223 {
224 if (strISO6392T == codes.iso639_2b ||
225 (checkWin32Locales && codes.win_id != NULL && strISO6392T == codes.win_id))
226 {
227 if (codes.iso639_2t != nullptr)
228 strISO6392T = codes.iso639_2t;
229 return true;
230 }
231 }
232 return false;
233}
234
235
236bool CLangCodeExpander::LookupUserCode(const std::string& desc, std::string& userCode)
237{
238 for (STRINGLOOKUPTABLE::const_iterator it = m_mapUser.begin(); it != m_mapUser.end(); ++it)
239 {
240 if (StringUtils::EqualsNoCase(desc, it->first) || StringUtils::EqualsNoCase(desc, it->second))
241 {
242 userCode = it->first;
243 return true;
244 }
245 }
246 return false;
247}
248
249#ifdef TARGET_WINDOWS
250bool CLangCodeExpander::ConvertISO31661Alpha2ToISO31661Alpha3(const std::string& strISO31661Alpha2,
251 std::string& strISO31661Alpha3)
252{
253 if (strISO31661Alpha2.length() != 2)
254 return false;
255
256 std::string strLower(strISO31661Alpha2);
257 StringUtils::ToLower(strLower);
258 StringUtils::Trim(strLower);
259 for (const auto& codes : RegionCodes)
260 {
261 if (strLower == codes.alpha2)
262 {
263 strISO31661Alpha3 = codes.alpha3;
264 return true;
265 }
266 }
267
268 return true;
269}
270
271bool CLangCodeExpander::ConvertWindowsLanguageCodeToISO6392B(
272 const std::string& strWindowsLanguageCode, std::string& strISO6392B)
273{
274 if (strWindowsLanguageCode.length() != 3)
275 return false;
276
277 std::string strLower(strWindowsLanguageCode);
278 StringUtils::ToLower(strLower);
279 for (const auto& codes : LanguageCodes)
280 {
281 if ((codes.win_id && strLower == codes.win_id) || strLower == codes.iso639_2b)
282 {
283 strISO6392B = codes.iso639_2b;
284 return true;
285 }
286 }
287
288 return false;
289}
290#endif
291
292bool CLangCodeExpander::ConvertToISO6391(const std::string& lang, std::string& code)
293{
294 if (lang.empty())
295 return false;
296
297 //first search in the user defined map
298 if (LookupUserCode(lang, code))
299 return true;
300
301 if (lang.length() == 2)
302 {
303 std::string tmp;
304 if (Lookup(lang, tmp))
305 {
306 code = lang;
307 return true;
308 }
309 }
310 else if (lang.length() == 3)
311 {
312 std::string lower(lang);
313 StringUtils::ToLower(lower);
314 for (const auto& codes : LanguageCodes)
315 {
316 if (lower == codes.iso639_2b || (codes.win_id && lower == codes.win_id))
317 {
318 code = codes.iso639_1;
319 return true;
320 }
321 }
322
323 for (const auto& codes : RegionCodes)
324 {
325 if (lower == codes.alpha3)
326 {
327 code = codes.alpha2;
328 return true;
329 }
330 }
331 }
332
333 // check if lang is full language name
334 std::string tmp;
335 if (ReverseLookup(lang, tmp))
336 {
337 if (tmp.length() == 2)
338 {
339 code = tmp;
340 return true;
341 }
342
343 if (tmp.length() == 3)
344 {
345 // there's only an iso639-2 code that is identical to the language name, e.g. Yao
346 if (StringUtils::EqualsNoCase(tmp, lang))
347 return false;
348
349 return ConvertToISO6391(tmp, code);
350 }
351 }
352
353 return false;
354}
355
356bool CLangCodeExpander::ReverseLookup(const std::string& desc, std::string& code)
357{
358 if (desc.empty())
359 return false;
360
361 std::string descTmp(desc);
362 StringUtils::Trim(descTmp);
363 for (STRINGLOOKUPTABLE::const_iterator it = m_mapUser.begin(); it != m_mapUser.end(); ++it)
364 {
365 if (StringUtils::EqualsNoCase(descTmp, it->second))
366 {
367 code = it->first;
368 return true;
369 }
370 }
371
372 for (const auto& codes : g_iso639_1)
373 {
374 if (StringUtils::EqualsNoCase(descTmp, codes.name))
375 {
376 CodeToString(codes.code, code);
377 return true;
378 }
379 }
380
381 for (const auto& codes : g_iso639_2)
382 {
383 if (StringUtils::EqualsNoCase(descTmp, codes.name))
384 {
385 CodeToString(codes.code, code);
386 return true;
387 }
388 }
389
390 return false;
391}
392
393bool CLangCodeExpander::LookupInUserMap(const std::string& code, std::string& desc)
394{
395 if (code.empty())
396 return false;
397
398 // make sure we convert to lowercase before trying to find it
399 std::string sCode(code);
400 StringUtils::ToLower(sCode);
401 StringUtils::Trim(sCode);
402
403 STRINGLOOKUPTABLE::iterator it = m_mapUser.find(sCode);
404 if (it != m_mapUser.end())
405 {
406 desc = it->second;
407 return true;
408 }
409
410 return false;
411}
412
413bool CLangCodeExpander::LookupInISO639Tables(const std::string& code, std::string& desc)
414{
415 if (code.empty())
416 return false;
417
418 long longcode;
419 std::string sCode(code);
420 StringUtils::ToLower(sCode);
421 StringUtils::Trim(sCode);
422
423 if (sCode.length() == 2)
424 {
425 longcode = MAKECODE('\0', '\0', sCode[0], sCode[1]);
426 for (const auto& codes : g_iso639_1)
427 {
428 if (codes.code == longcode)
429 {
430 desc = codes.name;
431 return true;
432 }
433 }
434 }
435 else if (sCode.length() == 3)
436 {
437 longcode = MAKECODE('\0', sCode[0], sCode[1], sCode[2]);
438 for (const auto& codes : g_iso639_2)
439 {
440 if (codes.code == longcode)
441 {
442 desc = codes.name;
443 return true;
444 }
445 }
446 }
447 return false;
448}
449
450void CLangCodeExpander::CodeToString(long code, std::string& ret)
451{
452 ret.clear();
453 for (unsigned int j = 0; j < 4; j++)
454 {
455 char c = (char)code & 0xFF;
456 if (c == '\0')
457 return;
458
459 ret.insert(0, 1, c);
460 code >>= 8;
461 }
462}
463
464bool CLangCodeExpander::CompareFullLanguageNames(const std::string& lang1, const std::string& lang2)
465{
466 if (StringUtils::EqualsNoCase(lang1, lang2))
467 return true;
468
469 std::string expandedLang1, expandedLang2, code1, code2;
470
471 if (!ReverseLookup(lang1, code1))
472 return false;
473
474 code1 = lang1;
475 if (!ReverseLookup(lang2, code2))
476 return false;
477
478 code2 = lang2;
479 Lookup(expandedLang1, code1);
480 Lookup(expandedLang2, code2);
481
482 return StringUtils::EqualsNoCase(expandedLang1, expandedLang2);
483}
484
485std::vector<std::string> CLangCodeExpander::GetLanguageNames(
486 LANGFORMATS format /* = CLangCodeExpander::ISO_639_1 */, bool customNames /* = false */)
487{
488 std::vector<std::string> languages;
489
490 if (format == CLangCodeExpander::ISO_639_2)
491 std::transform(g_iso639_2.begin(), g_iso639_2.end(), std::back_inserter(languages),
492 [](const LCENTRY& e) { return e.name; });
493 else
494 std::transform(g_iso639_1.begin(), g_iso639_1.end(), std::back_inserter(languages),
495 [](const LCENTRY& e) { return e.name; });
496
497 if (customNames)
498 std::transform(m_mapUser.begin(), m_mapUser.end(), std::back_inserter(languages),
499 [](const STRINGLOOKUPTABLE::value_type& e) { return e.second; });
500
501 return languages;
502}
503
504bool CLangCodeExpander::CompareISO639Codes(const std::string& code1, const std::string& code2)
505{
506 if (StringUtils::EqualsNoCase(code1, code2))
507 return true;
508
509 std::string expandedLang1;
510 if (!Lookup(code1, expandedLang1))
511 return false;
512
513 std::string expandedLang2;
514 if (!Lookup(code2, expandedLang2))
515 return false;
516
517 return StringUtils::EqualsNoCase(expandedLang1, expandedLang2);
518}
519
520std::string CLangCodeExpander::ConvertToISO6392B(const std::string& lang)
521{
522 if (lang.empty())
523 return lang;
524
525 std::string two, three;
526 if (ConvertToISO6391(lang, two))
527 {
528 if (ConvertToISO6392B(two, three))
529 return three;
530 }
531
532 return lang;
533}
534
535std::string CLangCodeExpander::ConvertToISO6392T(const std::string& lang)
536{
537 if (lang.empty())
538 return lang;
539
540 std::string two, three;
541 if (ConvertToISO6391(lang, two))
542 {
543 if (ConvertToISO6392T(two, three))
544 return three;
545 }
546
547 return lang;
548}
549
550// clang-format off
551const std::array<struct LCENTRY, 186> g_iso639_1 = {{
552 {MAKECODE('\0', '\0', 'a', 'a'), "Afar"},
553 {MAKECODE('\0', '\0', 'a', 'b'), "Abkhazian"},
554 {MAKECODE('\0', '\0', 'a', 'e'), "Avestan"},
555 {MAKECODE('\0', '\0', 'a', 'f'), "Afrikaans"},
556 {MAKECODE('\0', '\0', 'a', 'k'), "Akan"},
557 {MAKECODE('\0', '\0', 'a', 'm'), "Amharic"},
558 {MAKECODE('\0', '\0', 'a', 'n'), "Aragonese"},
559 {MAKECODE('\0', '\0', 'a', 'r'), "Arabic"},
560 {MAKECODE('\0', '\0', 'a', 's'), "Assamese"},
561 {MAKECODE('\0', '\0', 'a', 'v'), "Avaric"},
562 {MAKECODE('\0', '\0', 'a', 'y'), "Aymara"},
563 {MAKECODE('\0', '\0', 'a', 'z'), "Azerbaijani"},
564 {MAKECODE('\0', '\0', 'b', 'a'), "Bashkir"},
565 {MAKECODE('\0', '\0', 'b', 'e'), "Belarusian"},
566 {MAKECODE('\0', '\0', 'b', 'g'), "Bulgarian"},
567 {MAKECODE('\0', '\0', 'b', 'h'), "Bihari"},
568 {MAKECODE('\0', '\0', 'b', 'i'), "Bislama"},
569 {MAKECODE('\0', '\0', 'b', 'm'), "Bambara"},
570 {MAKECODE('\0', '\0', 'b', 'n'), "Bengali; Bangla"},
571 {MAKECODE('\0', '\0', 'b', 'o'), "Tibetan"},
572 {MAKECODE('\0', '\0', 'b', 'r'), "Breton"},
573 {MAKECODE('\0', '\0', 'b', 's'), "Bosnian"},
574 {MAKECODE('\0', '\0', 'c', 'a'), "Catalan"},
575 {MAKECODE('\0', '\0', 'c', 'e'), "Chechen"},
576 {MAKECODE('\0', '\0', 'c', 'h'), "Chamorro"},
577 {MAKECODE('\0', '\0', 'c', 'o'), "Corsican"},
578 {MAKECODE('\0', '\0', 'c', 'r'), "Cree"},
579 {MAKECODE('\0', '\0', 'c', 's'), "Czech"},
580 {MAKECODE('\0', '\0', 'c', 'u'), "Church Slavic"},
581 {MAKECODE('\0', '\0', 'c', 'v'), "Chuvash"},
582 {MAKECODE('\0', '\0', 'c', 'y'), "Welsh"},
583 {MAKECODE('\0', '\0', 'd', 'a'), "Danish"},
584 {MAKECODE('\0', '\0', 'd', 'e'), "German"},
585 {MAKECODE('\0', '\0', 'd', 'v'), "Dhivehi"},
586 {MAKECODE('\0', '\0', 'd', 'z'), "Dzongkha"},
587 {MAKECODE('\0', '\0', 'e', 'e'), "Ewe"},
588 {MAKECODE('\0', '\0', 'e', 'l'), "Greek"},
589 {MAKECODE('\0', '\0', 'e', 'n'), "English"},
590 {MAKECODE('\0', '\0', 'e', 'o'), "Esperanto"},
591 {MAKECODE('\0', '\0', 'e', 's'), "Spanish"},
592 {MAKECODE('\0', '\0', 'e', 't'), "Estonian"},
593 {MAKECODE('\0', '\0', 'e', 'u'), "Basque"},
594 {MAKECODE('\0', '\0', 'f', 'a'), "Persian"},
595 {MAKECODE('\0', '\0', 'f', 'f'), "Fulah"},
596 {MAKECODE('\0', '\0', 'f', 'i'), "Finnish"},
597 {MAKECODE('\0', '\0', 'f', 'j'), "Fijian"},
598 {MAKECODE('\0', '\0', 'f', 'o'), "Faroese"},
599 {MAKECODE('\0', '\0', 'f', 'r'), "French"},
600 {MAKECODE('\0', '\0', 'f', 'y'), "Western Frisian"},
601 {MAKECODE('\0', '\0', 'g', 'a'), "Irish"},
602 {MAKECODE('\0', '\0', 'g', 'd'), "Scottish Gaelic"},
603 {MAKECODE('\0', '\0', 'g', 'l'), "Galician"},
604 {MAKECODE('\0', '\0', 'g', 'n'), "Guarani"},
605 {MAKECODE('\0', '\0', 'g', 'u'), "Gujarati"},
606 {MAKECODE('\0', '\0', 'g', 'v'), "Manx"},
607 {MAKECODE('\0', '\0', 'h', 'a'), "Hausa"},
608 {MAKECODE('\0', '\0', 'h', 'e'), "Hebrew"},
609 {MAKECODE('\0', '\0', 'h', 'i'), "Hindi"},
610 {MAKECODE('\0', '\0', 'h', 'o'), "Hiri Motu"},
611 {MAKECODE('\0', '\0', 'h', 'r'), "Croatian"},
612 {MAKECODE('\0', '\0', 'h', 't'), "Haitian"},
613 {MAKECODE('\0', '\0', 'h', 'u'), "Hungarian"},
614 {MAKECODE('\0', '\0', 'h', 'y'), "Armenian"},
615 {MAKECODE('\0', '\0', 'h', 'z'), "Herero"},
616 {MAKECODE('\0', '\0', 'i', 'a'), "Interlingua"},
617 {MAKECODE('\0', '\0', 'i', 'd'), "Indonesian"},
618 {MAKECODE('\0', '\0', 'i', 'e'), "Interlingue"},
619 {MAKECODE('\0', '\0', 'i', 'g'), "Igbo"},
620 {MAKECODE('\0', '\0', 'i', 'i'), "Sichuan Yi"},
621 {MAKECODE('\0', '\0', 'i', 'k'), "Inupiat"},
622 {MAKECODE('\0', '\0', 'i', 'o'), "Ido"},
623 {MAKECODE('\0', '\0', 'i', 's'), "Icelandic"},
624 {MAKECODE('\0', '\0', 'i', 't'), "Italian"},
625 {MAKECODE('\0', '\0', 'i', 'u'), "Inuktitut"},
626 {MAKECODE('\0', '\0', 'j', 'a'), "Japanese"},
627 {MAKECODE('\0', '\0', 'j', 'v'), "Javanese"},
628 {MAKECODE('\0', '\0', 'k', 'a'), "Georgian"},
629 {MAKECODE('\0', '\0', 'k', 'g'), "Kongo"},
630 {MAKECODE('\0', '\0', 'k', 'i'), "Kikuyu"},
631 {MAKECODE('\0', '\0', 'k', 'j'), "Kuanyama"},
632 {MAKECODE('\0', '\0', 'k', 'k'), "Kazakh"},
633 {MAKECODE('\0', '\0', 'k', 'l'), "Kalaallisut"},
634 {MAKECODE('\0', '\0', 'k', 'm'), "Khmer"},
635 {MAKECODE('\0', '\0', 'k', 'n'), "Kannada"},
636 {MAKECODE('\0', '\0', 'k', 'o'), "Korean"},
637 {MAKECODE('\0', '\0', 'k', 'r'), "Kanuri"},
638 {MAKECODE('\0', '\0', 'k', 's'), "Kashmiri"},
639 {MAKECODE('\0', '\0', 'k', 'u'), "Kurdish"},
640 {MAKECODE('\0', '\0', 'k', 'v'), "Komi"},
641 {MAKECODE('\0', '\0', 'k', 'w'), "Cornish"},
642 {MAKECODE('\0', '\0', 'k', 'y'), "Kirghiz"},
643 {MAKECODE('\0', '\0', 'l', 'a'), "Latin"},
644 {MAKECODE('\0', '\0', 'l', 'b'), "Luxembourgish"},
645 {MAKECODE('\0', '\0', 'l', 'g'), "Ganda"},
646 {MAKECODE('\0', '\0', 'l', 'i'), "Limburgan"},
647 {MAKECODE('\0', '\0', 'l', 'n'), "Lingala"},
648 {MAKECODE('\0', '\0', 'l', 'o'), "Lao"},
649 {MAKECODE('\0', '\0', 'l', 't'), "Lithuanian"},
650 {MAKECODE('\0', '\0', 'l', 'u'), "Luba-Katanga"},
651 {MAKECODE('\0', '\0', 'l', 'v'), "Latvian, Lettish"},
652 {MAKECODE('\0', '\0', 'm', 'g'), "Malagasy"},
653 {MAKECODE('\0', '\0', 'm', 'h'), "Marshallese"},
654 {MAKECODE('\0', '\0', 'm', 'i'), "Maori"},
655 {MAKECODE('\0', '\0', 'm', 'k'), "Macedonian"},
656 {MAKECODE('\0', '\0', 'm', 'l'), "Malayalam"},
657 {MAKECODE('\0', '\0', 'm', 'n'), "Mongolian"},
658 {MAKECODE('\0', '\0', 'm', 'r'), "Marathi"},
659 {MAKECODE('\0', '\0', 'm', 's'), "Malay"},
660 {MAKECODE('\0', '\0', 'm', 't'), "Maltese"},
661 {MAKECODE('\0', '\0', 'm', 'y'), "Burmese"},
662 {MAKECODE('\0', '\0', 'n', 'a'), "Nauru"},
663 {MAKECODE('\0', '\0', 'n', 'b'), "Norwegian Bokm\xC3\xA5l"},
664 {MAKECODE('\0', '\0', 'n', 'd'), "Ndebele, North"},
665 {MAKECODE('\0', '\0', 'n', 'e'), "Nepali"},
666 {MAKECODE('\0', '\0', 'n', 'g'), "Ndonga"},
667 {MAKECODE('\0', '\0', 'n', 'l'), "Dutch"},
668 {MAKECODE('\0', '\0', 'n', 'n'), "Norwegian Nynorsk"},
669 {MAKECODE('\0', '\0', 'n', 'o'), "Norwegian"},
670 {MAKECODE('\0', '\0', 'n', 'r'), "Ndebele, South"},
671 {MAKECODE('\0', '\0', 'n', 'v'), "Navajo"},
672 {MAKECODE('\0', '\0', 'n', 'y'), "Chichewa"},
673 {MAKECODE('\0', '\0', 'o', 'c'), "Occitan"},
674 {MAKECODE('\0', '\0', 'o', 'j'), "Ojibwa"},
675 {MAKECODE('\0', '\0', 'o', 'm'), "Oromo"},
676 {MAKECODE('\0', '\0', 'o', 'r'), "Oriya"},
677 {MAKECODE('\0', '\0', 'o', 's'), "Ossetic"},
678 {MAKECODE('\0', '\0', 'p', 'a'), "Punjabi"},
679 {MAKECODE('\0', '\0', 'p', 'i'), "Pali"},
680 {MAKECODE('\0', '\0', 'p', 'l'), "Polish"},
681 {MAKECODE('\0', '\0', 'p', 's'), "Pashto, Pushto"},
682 {MAKECODE('\0', '\0', 'p', 't'), "Portuguese"},
683 // pb = unofficial language code for Brazilian Portuguese
684 {MAKECODE('\0', '\0', 'p', 'b'), "Portuguese (Brazil)"},
685 {MAKECODE('\0', '\0', 'q', 'u'), "Quechua"},
686 {MAKECODE('\0', '\0', 'r', 'm'), "Romansh"},
687 {MAKECODE('\0', '\0', 'r', 'n'), "Kirundi"},
688 {MAKECODE('\0', '\0', 'r', 'o'), "Romanian"},
689 {MAKECODE('\0', '\0', 'r', 'u'), "Russian"},
690 {MAKECODE('\0', '\0', 'r', 'w'), "Kinyarwanda"},
691 {MAKECODE('\0', '\0', 's', 'a'), "Sanskrit"},
692 {MAKECODE('\0', '\0', 's', 'c'), "Sardinian"},
693 {MAKECODE('\0', '\0', 's', 'd'), "Sindhi"},
694 {MAKECODE('\0', '\0', 's', 'e'), "Northern Sami"},
695 {MAKECODE('\0', '\0', 's', 'g'), "Sangho"},
696 {MAKECODE('\0', '\0', 's', 'h'), "Serbo-Croatian"},
697 {MAKECODE('\0', '\0', 's', 'i'), "Sinhalese"},
698 {MAKECODE('\0', '\0', 's', 'k'), "Slovak"},
699 {MAKECODE('\0', '\0', 's', 'l'), "Slovenian"},
700 {MAKECODE('\0', '\0', 's', 'm'), "Samoan"},
701 {MAKECODE('\0', '\0', 's', 'n'), "Shona"},
702 {MAKECODE('\0', '\0', 's', 'o'), "Somali"},
703 {MAKECODE('\0', '\0', 's', 'q'), "Albanian"},
704 {MAKECODE('\0', '\0', 's', 'r'), "Serbian"},
705 {MAKECODE('\0', '\0', 's', 's'), "Swati"},
706 {MAKECODE('\0', '\0', 's', 't'), "Sesotho"},
707 {MAKECODE('\0', '\0', 's', 'u'), "Sundanese"},
708 {MAKECODE('\0', '\0', 's', 'v'), "Swedish"},
709 {MAKECODE('\0', '\0', 's', 'w'), "Swahili"},
710 {MAKECODE('\0', '\0', 't', 'a'), "Tamil"},
711 {MAKECODE('\0', '\0', 't', 'e'), "Telugu"},
712 {MAKECODE('\0', '\0', 't', 'g'), "Tajik"},
713 {MAKECODE('\0', '\0', 't', 'h'), "Thai"},
714 {MAKECODE('\0', '\0', 't', 'i'), "Tigrinya"},
715 {MAKECODE('\0', '\0', 't', 'k'), "Turkmen"},
716 {MAKECODE('\0', '\0', 't', 'l'), "Tagalog"},
717 {MAKECODE('\0', '\0', 't', 'n'), "Tswana"},
718 {MAKECODE('\0', '\0', 't', 'o'), "Tonga"},
719 {MAKECODE('\0', '\0', 't', 'r'), "Turkish"},
720 {MAKECODE('\0', '\0', 't', 's'), "Tsonga"},
721 {MAKECODE('\0', '\0', 't', 't'), "Tatar"},
722 {MAKECODE('\0', '\0', 't', 'w'), "Twi"},
723 {MAKECODE('\0', '\0', 't', 'y'), "Tahitian"},
724 {MAKECODE('\0', '\0', 'u', 'g'), "Uighur"},
725 {MAKECODE('\0', '\0', 'u', 'k'), "Ukrainian"},
726 {MAKECODE('\0', '\0', 'u', 'r'), "Urdu"},
727 {MAKECODE('\0', '\0', 'u', 'z'), "Uzbek"},
728 {MAKECODE('\0', '\0', 'v', 'e'), "Venda"},
729 {MAKECODE('\0', '\0', 'v', 'i'), "Vietnamese"},
730 {MAKECODE('\0', '\0', 'v', 'o'), "Volapuk"},
731 {MAKECODE('\0', '\0', 'w', 'a'), "Walloon"},
732 {MAKECODE('\0', '\0', 'w', 'o'), "Wolof"},
733 {MAKECODE('\0', '\0', 'x', 'h'), "Xhosa"},
734 {MAKECODE('\0', '\0', 'y', 'i'), "Yiddish"},
735 {MAKECODE('\0', '\0', 'y', 'o'), "Yoruba"},
736 {MAKECODE('\0', '\0', 'z', 'a'), "Zhuang"},
737 {MAKECODE('\0', '\0', 'z', 'h'), "Chinese"},
738 {MAKECODE('\0', '\0', 'z', 'u'), "Zulu"},
739}};
740// clang-format on
741
742// clang-format off
743const std::array<struct LCENTRY, 540> g_iso639_2 = {{
744 {MAKECODE('\0', 'a', 'b', 'k'), "Abkhaz"},
745 {MAKECODE('\0', 'a', 'b', 'k'), "Abkhazian"},
746 {MAKECODE('\0', 'a', 'c', 'e'), "Achinese"},
747 {MAKECODE('\0', 'a', 'c', 'h'), "Acoli"},
748 {MAKECODE('\0', 'a', 'd', 'a'), "Adangme"},
749 {MAKECODE('\0', 'a', 'd', 'y'), "Adygei"},
750 {MAKECODE('\0', 'a', 'd', 'y'), "Adyghe"},
751 {MAKECODE('\0', 'a', 'a', 'r'), "Afar"},
752 {MAKECODE('\0', 'a', 'f', 'h'), "Afrihili"},
753 {MAKECODE('\0', 'a', 'f', 'r'), "Afrikaans"},
754 {MAKECODE('\0', 'a', 'f', 'a'), "Afro-Asiatic (Other)"},
755 {MAKECODE('\0', 'a', 'k', 'a'), "Akan"},
756 {MAKECODE('\0', 'a', 'k', 'k'), "Akkadian"},
757 {MAKECODE('\0', 'a', 'l', 'b'), "Albanian"},
758 {MAKECODE('\0', 's', 'q', 'i'), "Albanian"},
759 {MAKECODE('\0', 'a', 'l', 'e'), "Aleut"},
760 {MAKECODE('\0', 'a', 'l', 'g'), "Algonquian languages"},
761 {MAKECODE('\0', 't', 'u', 't'), "Altaic (Other)"},
762 {MAKECODE('\0', 'a', 'm', 'h'), "Amharic"},
763 {MAKECODE('\0', 'a', 'p', 'a'), "Apache languages"},
764 {MAKECODE('\0', 'a', 'r', 'a'), "Arabic"},
765 {MAKECODE('\0', 'a', 'r', 'g'), "Aragonese"},
766 {MAKECODE('\0', 'a', 'r', 'c'), "Aramaic"},
767 {MAKECODE('\0', 'a', 'r', 'p'), "Arapaho"},
768 {MAKECODE('\0', 'a', 'r', 'n'), "Araucanian"},
769 {MAKECODE('\0', 'a', 'r', 'w'), "Arawak"},
770 {MAKECODE('\0', 'a', 'r', 'm'), "Armenian"},
771 {MAKECODE('\0', 'h', 'y', 'e'), "Armenian"},
772 {MAKECODE('\0', 'a', 'r', 't'), "Artificial (Other)"},
773 {MAKECODE('\0', 'a', 's', 'm'), "Assamese"},
774 {MAKECODE('\0', 'a', 's', 't'), "Asturian"},
775 {MAKECODE('\0', 'a', 't', 'h'), "Athapascan languages"},
776 {MAKECODE('\0', 'a', 'u', 's'), "Australian languages"},
777 {MAKECODE('\0', 'm', 'a', 'p'), "Austronesian (Other)"},
778 {MAKECODE('\0', 'a', 'v', 'a'), "Avaric"},
779 {MAKECODE('\0', 'a', 'v', 'e'), "Avestan"},
780 {MAKECODE('\0', 'a', 'w', 'a'), "Awadhi"},
781 {MAKECODE('\0', 'a', 'y', 'm'), "Aymara"},
782 {MAKECODE('\0', 'a', 'z', 'e'), "Azerbaijani"},
783 {MAKECODE('\0', 'a', 's', 't'), "Bable"},
784 {MAKECODE('\0', 'b', 'a', 'n'), "Balinese"},
785 {MAKECODE('\0', 'b', 'a', 't'), "Baltic (Other)"},
786 {MAKECODE('\0', 'b', 'a', 'l'), "Baluchi"},
787 {MAKECODE('\0', 'b', 'a', 'm'), "Bambara"},
788 {MAKECODE('\0', 'b', 'a', 'i'), "Bamileke languages"},
789 {MAKECODE('\0', 'b', 'a', 'd'), "Banda"},
790 {MAKECODE('\0', 'b', 'n', 't'), "Bantu (Other)"},
791 {MAKECODE('\0', 'b', 'a', 's'), "Basa"},
792 {MAKECODE('\0', 'b', 'a', 'k'), "Bashkir"},
793 {MAKECODE('\0', 'b', 'a', 'q'), "Basque"},
794 {MAKECODE('\0', 'e', 'u', 's'), "Basque"},
795 {MAKECODE('\0', 'b', 't', 'k'), "Batak (Indonesia)"},
796 {MAKECODE('\0', 'b', 'e', 'j'), "Beja"},
797 {MAKECODE('\0', 'b', 'e', 'l'), "Belarusian"},
798 {MAKECODE('\0', 'b', 'e', 'm'), "Bemba"},
799 {MAKECODE('\0', 'b', 'e', 'n'), "Bengali"},
800 {MAKECODE('\0', 'b', 'e', 'r'), "Berber (Other)"},
801 {MAKECODE('\0', 'b', 'h', 'o'), "Bhojpuri"},
802 {MAKECODE('\0', 'b', 'i', 'h'), "Bihari"},
803 {MAKECODE('\0', 'b', 'i', 'k'), "Bikol"},
804 {MAKECODE('\0', 'b', 'y', 'n'), "Bilin"},
805 {MAKECODE('\0', 'b', 'i', 'n'), "Bini"},
806 {MAKECODE('\0', 'b', 'i', 's'), "Bislama"},
807 {MAKECODE('\0', 'b', 'y', 'n'), "Blin"},
808 {MAKECODE('\0', 'n', 'o', 'b'), "Bokm\xC3\xA5l, Norwegian"},
809 {MAKECODE('\0', 'b', 'o', 's'), "Bosnian"},
810 {MAKECODE('\0', 'b', 'r', 'a'), "Braj"},
811 {MAKECODE('\0', 'b', 'r', 'e'), "Breton"},
812 {MAKECODE('\0', 'b', 'u', 'g'), "Buginese"},
813 {MAKECODE('\0', 'b', 'u', 'l'), "Bulgarian"},
814 {MAKECODE('\0', 'b', 'u', 'a'), "Buriat"},
815 {MAKECODE('\0', 'b', 'u', 'r'), "Burmese"},
816 {MAKECODE('\0', 'm', 'y', 'a'), "Burmese"},
817 {MAKECODE('\0', 'c', 'a', 'd'), "Caddo"},
818 {MAKECODE('\0', 'c', 'a', 'r'), "Carib"},
819 {MAKECODE('\0', 's', 'p', 'a'), "Spanish"},
820 {MAKECODE('\0', 'c', 'a', 't'), "Catalan"},
821 {MAKECODE('\0', 'c', 'a', 'u'), "Caucasian (Other)"},
822 {MAKECODE('\0', 'c', 'e', 'b'), "Cebuano"},
823 {MAKECODE('\0', 'c', 'e', 'l'), "Celtic (Other)"},
824 {MAKECODE('\0', 'c', 'h', 'g'), "Chagatai"},
825 {MAKECODE('\0', 'c', 'm', 'c'), "Chamic languages"},
826 {MAKECODE('\0', 'c', 'h', 'a'), "Chamorro"},
827 {MAKECODE('\0', 'c', 'h', 'e'), "Chechen"},
828 {MAKECODE('\0', 'c', 'h', 'r'), "Cherokee"},
829 {MAKECODE('\0', 'n', 'y', 'a'), "Chewa"},
830 {MAKECODE('\0', 'c', 'h', 'y'), "Cheyenne"},
831 {MAKECODE('\0', 'c', 'h', 'b'), "Chibcha"},
832 {MAKECODE('\0', 'n', 'y', 'a'), "Chichewa"},
833 {MAKECODE('\0', 'c', 'h', 'i'), "Chinese"},
834 {MAKECODE('\0', 'z', 'h', 'o'), "Chinese"},
835 {MAKECODE('\0', 'c', 'h', 'n'), "Chinook jargon"},
836 {MAKECODE('\0', 'c', 'h', 'p'), "Chipewyan"},
837 {MAKECODE('\0', 'c', 'h', 'o'), "Choctaw"},
838 {MAKECODE('\0', 'z', 'h', 'a'), "Chuang"},
839 {MAKECODE('\0', 'c', 'h', 'u'), "Church Slavonic"},
840 {MAKECODE('\0', 'c', 'h', 'k'), "Chuukese"},
841 {MAKECODE('\0', 'c', 'h', 'v'), "Chuvash"},
842 {MAKECODE('\0', 'n', 'w', 'c'), "Classical Nepal Bhasa"},
843 {MAKECODE('\0', 'n', 'w', 'c'), "Classical Newari"},
844 {MAKECODE('\0', 'c', 'o', 'p'), "Coptic"},
845 {MAKECODE('\0', 'c', 'o', 'r'), "Cornish"},
846 {MAKECODE('\0', 'c', 'o', 's'), "Corsican"},
847 {MAKECODE('\0', 'c', 'r', 'e'), "Cree"},
848 {MAKECODE('\0', 'm', 'u', 's'), "Creek"},
849 {MAKECODE('\0', 'c', 'r', 'p'), "Creoles and pidgins (Other)"},
850 {MAKECODE('\0', 'c', 'p', 'e'), "English-based (Other)"},
851 {MAKECODE('\0', 'c', 'p', 'f'), "French-based (Other)"},
852 {MAKECODE('\0', 'c', 'p', 'p'), "Portuguese-based (Other)"},
853 {MAKECODE('\0', 'c', 'r', 'h'), "Crimean Tatar"},
854 {MAKECODE('\0', 'c', 'r', 'h'), "Crimean Turkish"},
855 {MAKECODE('\0', 'h', 'r', 'v'), "Croatian"},
856 {MAKECODE('\0', 's', 'c', 'r'), "Croatian"},
857 {MAKECODE('\0', 'c', 'u', 's'), "Cushitic (Other)"},
858 {MAKECODE('\0', 'c', 'z', 'e'), "Czech"},
859 {MAKECODE('\0', 'c', 'e', 's'), "Czech"},
860 {MAKECODE('\0', 'd', 'a', 'k'), "Dakota"},
861 {MAKECODE('\0', 'd', 'a', 'n'), "Danish"},
862 {MAKECODE('\0', 'd', 'a', 'r'), "Dargwa"},
863 {MAKECODE('\0', 'd', 'a', 'y'), "Dayak"},
864 {MAKECODE('\0', 'd', 'e', 'l'), "Delaware"},
865 {MAKECODE('\0', 'd', 'i', 'n'), "Dinka"},
866 {MAKECODE('\0', 'd', 'i', 'v'), "Divehi"},
867 {MAKECODE('\0', 'd', 'o', 'i'), "Dogri"},
868 {MAKECODE('\0', 'd', 'g', 'r'), "Dogrib"},
869 {MAKECODE('\0', 'd', 'r', 'a'), "Dravidian (Other)"},
870 {MAKECODE('\0', 'd', 'u', 'a'), "Duala"},
871 {MAKECODE('\0', 'd', 'u', 't'), "Dutch"},
872 {MAKECODE('\0', 'n', 'l', 'd'), "Dutch"},
873 {MAKECODE('\0', 'd', 'u', 'm'), "Dutch, Middle (ca. 1050-1350)"},
874 {MAKECODE('\0', 'd', 'y', 'u'), "Dyula"},
875 {MAKECODE('\0', 'd', 'z', 'o'), "Dzongkha"},
876 {MAKECODE('\0', 'e', 'f', 'i'), "Efik"},
877 {MAKECODE('\0', 'e', 'g', 'y'), "Egyptian (Ancient)"},
878 {MAKECODE('\0', 'e', 'k', 'a'), "Ekajuk"},
879 {MAKECODE('\0', 'e', 'l', 'x'), "Elamite"},
880 {MAKECODE('\0', 'e', 'n', 'g'), "English"},
881 {MAKECODE('\0', 'e', 'n', 'm'), "English, Middle (1100-1500)"},
882 {MAKECODE('\0', 'a', 'n', 'g'), "English, Old (ca.450-1100)"},
883 {MAKECODE('\0', 'm', 'y', 'v'), "Erzya"},
884 {MAKECODE('\0', 'e', 'p', 'o'), "Esperanto"},
885 {MAKECODE('\0', 'e', 's', 't'), "Estonian"},
886 {MAKECODE('\0', 'e', 'w', 'e'), "Ewe"},
887 {MAKECODE('\0', 'e', 'w', 'o'), "Ewondo"},
888 {MAKECODE('\0', 'f', 'a', 'n'), "Fang"},
889 {MAKECODE('\0', 'f', 'a', 't'), "Fanti"},
890 {MAKECODE('\0', 'f', 'a', 'o'), "Faroese"},
891 {MAKECODE('\0', 'f', 'i', 'j'), "Fijian"},
892 {MAKECODE('\0', 'f', 'i', 'l'), "Filipino"},
893 {MAKECODE('\0', 'f', 'i', 'n'), "Finnish"},
894 {MAKECODE('\0', 'f', 'i', 'u'), "Finno-Ugrian (Other)"},
895 {MAKECODE('\0', 'd', 'u', 't'), "Flemish"},
896 {MAKECODE('\0', 'n', 'l', 'd'), "Flemish"},
897 {MAKECODE('\0', 'f', 'o', 'n'), "Fon"},
898 {MAKECODE('\0', 'f', 'r', 'e'), "French"},
899 {MAKECODE('\0', 'f', 'r', 'a'), "French"},
900 {MAKECODE('\0', 'f', 'r', 'm'), "French, Middle (ca.1400-1600)"},
901 {MAKECODE('\0', 'f', 'r', 'o'), "French, Old (842-ca.1400)"},
902 {MAKECODE('\0', 'f', 'r', 'y'), "Frisian"},
903 {MAKECODE('\0', 'f', 'u', 'r'), "Friulian"},
904 {MAKECODE('\0', 'f', 'u', 'l'), "Fulah"},
905 {MAKECODE('\0', 'g', 'a', 'a'), "Ga"},
906 {MAKECODE('\0', 'g', 'l', 'a'), "Gaelic"},
907 {MAKECODE('\0', 'g', 'l', 'g'), "Gallegan"},
908 {MAKECODE('\0', 'l', 'u', 'g'), "Ganda"},
909 {MAKECODE('\0', 'g', 'a', 'y'), "Gayo"},
910 {MAKECODE('\0', 'g', 'b', 'a'), "Gbaya"},
911 {MAKECODE('\0', 'g', 'e', 'z'), "Geez"},
912 {MAKECODE('\0', 'g', 'e', 'o'), "Georgian"},
913 {MAKECODE('\0', 'k', 'a', 't'), "Georgian"},
914 {MAKECODE('\0', 'g', 'e', 'r'), "German"},
915 {MAKECODE('\0', 'd', 'e', 'u'), "German"},
916 {MAKECODE('\0', 'n', 'd', 's'), "German, Low"},
917 {MAKECODE('\0', 'g', 'm', 'h'), "German, Middle High (ca.1050-1500)"},
918 {MAKECODE('\0', 'g', 'o', 'h'), "German, Old High (ca.750-1050)"},
919 {MAKECODE('\0', 'g', 's', 'w'), "German, Swiss German"},
920 {MAKECODE('\0', 'g', 'e', 'm'), "Germanic (Other)"},
921 {MAKECODE('\0', 'k', 'i', 'k'), "Gikuyu"},
922 {MAKECODE('\0', 'g', 'i', 'l'), "Gilbertese"},
923 {MAKECODE('\0', 'g', 'o', 'n'), "Gondi"},
924 {MAKECODE('\0', 'g', 'o', 'r'), "Gorontalo"},
925 {MAKECODE('\0', 'g', 'o', 't'), "Gothic"},
926 {MAKECODE('\0', 'g', 'r', 'b'), "Grebo"},
927 {MAKECODE('\0', 'g', 'r', 'c'), "Greek, Ancient (to 1453)"},
928 {MAKECODE('\0', 'g', 'r', 'e'), "Greek, Modern (1453-)"},
929 {MAKECODE('\0', 'e', 'l', 'l'), "Greek, Modern (1453-)"},
930 {MAKECODE('\0', 'k', 'a', 'l'), "Greenlandic"},
931 {MAKECODE('\0', 'g', 'r', 'n'), "Guarani"},
932 {MAKECODE('\0', 'g', 'u', 'j'), "Gujarati"},
933 {MAKECODE('\0', 'g', 'w', 'i'), "Gwich\xC2\xB4in"},
934 {MAKECODE('\0', 'h', 'a', 'i'), "Haida"},
935 {MAKECODE('\0', 'h', 'a', 't'), "Haitian"},
936 {MAKECODE('\0', 'h', 'a', 't'), "Haitian Creole"},
937 {MAKECODE('\0', 'h', 'a', 'u'), "Hausa"},
938 {MAKECODE('\0', 'h', 'a', 'w'), "Hawaiian"},
939 {MAKECODE('\0', 'h', 'e', 'b'), "Hebrew"},
940 {MAKECODE('\0', 'h', 'e', 'r'), "Herero"},
941 {MAKECODE('\0', 'h', 'i', 'l'), "Hiligaynon"},
942 {MAKECODE('\0', 'h', 'i', 'm'), "Himachali"},
943 {MAKECODE('\0', 'h', 'i', 'n'), "Hindi"},
944 {MAKECODE('\0', 'h', 'm', 'o'), "Hiri Motu"},
945 {MAKECODE('\0', 'h', 'i', 't'), "Hittite"},
946 {MAKECODE('\0', 'h', 'm', 'n'), "Hmong"},
947 {MAKECODE('\0', 'h', 'u', 'n'), "Hungarian"},
948 {MAKECODE('\0', 'h', 'u', 'p'), "Hupa"},
949 {MAKECODE('\0', 'i', 'b', 'a'), "Iban"},
950 {MAKECODE('\0', 'i', 'c', 'e'), "Icelandic"},
951 {MAKECODE('\0', 'i', 's', 'l'), "Icelandic"},
952 {MAKECODE('\0', 'i', 'd', 'o'), "Ido"},
953 {MAKECODE('\0', 'i', 'b', 'o'), "Igbo"},
954 {MAKECODE('\0', 'i', 'j', 'o'), "Ijo"},
955 {MAKECODE('\0', 'i', 'l', 'o'), "Iloko"},
956 {MAKECODE('\0', 's', 'm', 'n'), "Inari Sami"},
957 {MAKECODE('\0', 'i', 'n', 'c'), "Indic (Other)"},
958 {MAKECODE('\0', 'i', 'n', 'e'), "Indo-European (Other)"},
959 {MAKECODE('\0', 'i', 'n', 'd'), "Indonesian"},
960 {MAKECODE('\0', 'i', 'n', 'h'), "Ingush"},
961 {MAKECODE('\0', 'i', 'n', 'a'), "Auxiliary Language Association)"},
962 {MAKECODE('\0', 'i', 'l', 'e'), "Interlingue"},
963 {MAKECODE('\0', 'i', 'k', 'u'), "Inuktitut"},
964 {MAKECODE('\0', 'i', 'p', 'k'), "Inupiaq"},
965 {MAKECODE('\0', 'i', 'r', 'a'), "Iranian (Other)"},
966 {MAKECODE('\0', 'g', 'l', 'e'), "Irish"},
967 {MAKECODE('\0', 'm', 'g', 'a'), "Irish, Middle (900-1200)"},
968 {MAKECODE('\0', 's', 'g', 'a'), "Irish, Old (to 900)"},
969 {MAKECODE('\0', 'i', 'r', 'o'), "Iroquoian languages"},
970 {MAKECODE('\0', 'i', 't', 'a'), "Italian"},
971 {MAKECODE('\0', 'j', 'p', 'n'), "Japanese"},
972 {MAKECODE('\0', 'j', 'a', 'v'), "Javanese"},
973 {MAKECODE('\0', 'j', 'r', 'b'), "Judeo-Arabic"},
974 {MAKECODE('\0', 'j', 'p', 'r'), "Judeo-Persian"},
975 {MAKECODE('\0', 'k', 'b', 'd'), "Kabardian"},
976 {MAKECODE('\0', 'k', 'a', 'b'), "Kabyle"},
977 {MAKECODE('\0', 'k', 'a', 'c'), "Kachin"},
978 {MAKECODE('\0', 'k', 'a', 'l'), "Kalaallisut"},
979 {MAKECODE('\0', 'x', 'a', 'l'), "Kalmyk"},
980 {MAKECODE('\0', 'k', 'a', 'm'), "Kamba"},
981 {MAKECODE('\0', 'k', 'a', 'n'), "Kannada"},
982 {MAKECODE('\0', 'k', 'a', 'u'), "Kanuri"},
983 {MAKECODE('\0', 'k', 'r', 'c'), "Karachay-Balkar"},
984 {MAKECODE('\0', 'k', 'a', 'a'), "Kara-Kalpak"},
985 {MAKECODE('\0', 'k', 'a', 'r'), "Karen"},
986 {MAKECODE('\0', 'k', 'a', 's'), "Kashmiri"},
987 {MAKECODE('\0', 'c', 's', 'b'), "Kashubian"},
988 {MAKECODE('\0', 'k', 'a', 'w'), "Kawi"},
989 {MAKECODE('\0', 'k', 'a', 'z'), "Kazakh"},
990 {MAKECODE('\0', 'k', 'h', 'a'), "Khasi"},
991 {MAKECODE('\0', 'k', 'h', 'm'), "Khmer"},
992 {MAKECODE('\0', 'k', 'h', 'i'), "Khoisan (Other)"},
993 {MAKECODE('\0', 'k', 'h', 'o'), "Khotanese"},
994 {MAKECODE('\0', 'k', 'i', 'k'), "Kikuyu"},
995 {MAKECODE('\0', 'k', 'm', 'b'), "Kimbundu"},
996 {MAKECODE('\0', 'k', 'i', 'n'), "Kinyarwanda"},
997 {MAKECODE('\0', 'k', 'i', 'r'), "Kirghiz"},
998 {MAKECODE('\0', 't', 'l', 'h'), "Klingon"},
999 {MAKECODE('\0', 'k', 'o', 'm'), "Komi"},
1000 {MAKECODE('\0', 'k', 'o', 'n'), "Kongo"},
1001 {MAKECODE('\0', 'k', 'o', 'k'), "Konkani"},
1002 {MAKECODE('\0', 'k', 'o', 'r'), "Korean"},
1003 {MAKECODE('\0', 'k', 'o', 's'), "Kosraean"},
1004 {MAKECODE('\0', 'k', 'p', 'e'), "Kpelle"},
1005 {MAKECODE('\0', 'k', 'r', 'o'), "Kru"},
1006 {MAKECODE('\0', 'k', 'u', 'a'), "Kuanyama"},
1007 {MAKECODE('\0', 'k', 'u', 'm'), "Kumyk"},
1008 {MAKECODE('\0', 'k', 'u', 'r'), "Kurdish"},
1009 {MAKECODE('\0', 'k', 'r', 'u'), "Kurukh"},
1010 {MAKECODE('\0', 'k', 'u', 't'), "Kutenai"},
1011 {MAKECODE('\0', 'k', 'u', 'a'), "Kwanyama, Kuanyama"},
1012 {MAKECODE('\0', 'l', 'a', 'd'), "Ladino"},
1013 {MAKECODE('\0', 'l', 'a', 'h'), "Lahnda"},
1014 {MAKECODE('\0', 'l', 'a', 'm'), "Lamba"},
1015 {MAKECODE('\0', 'l', 'a', 'o'), "Lao"},
1016 {MAKECODE('\0', 'l', 'a', 't'), "Latin"},
1017 {MAKECODE('\0', 'l', 'a', 'v'), "Latvian"},
1018 {MAKECODE('\0', 'l', 't', 'z'), "Letzeburgesch"},
1019 {MAKECODE('\0', 'l', 'e', 'z'), "Lezghian"},
1020 {MAKECODE('\0', 'l', 'i', 'm'), "Limburgan"},
1021 {MAKECODE('\0', 'l', 'i', 'm'), "Limburger"},
1022 {MAKECODE('\0', 'l', 'i', 'm'), "Limburgish"},
1023 {MAKECODE('\0', 'l', 'i', 'n'), "Lingala"},
1024 {MAKECODE('\0', 'l', 'i', 't'), "Lithuanian"},
1025 {MAKECODE('\0', 'j', 'b', 'o'), "Lojban"},
1026 {MAKECODE('\0', 'n', 'd', 's'), "Low German"},
1027 {MAKECODE('\0', 'n', 'd', 's'), "Low Saxon"},
1028 {MAKECODE('\0', 'd', 's', 'b'), "Lower Sorbian"},
1029 {MAKECODE('\0', 'l', 'o', 'z'), "Lozi"},
1030 {MAKECODE('\0', 'l', 'u', 'b'), "Luba-Katanga"},
1031 {MAKECODE('\0', 'l', 'u', 'a'), "Luba-Lulua"},
1032 {MAKECODE('\0', 'l', 'u', 'i'), "Luiseno"},
1033 {MAKECODE('\0', 's', 'm', 'j'), "Lule Sami"},
1034 {MAKECODE('\0', 'l', 'u', 'n'), "Lunda"},
1035 {MAKECODE('\0', 'l', 'u', 'o'), "Luo (Kenya and Tanzania)"},
1036 {MAKECODE('\0', 'l', 'u', 's'), "Lushai"},
1037 {MAKECODE('\0', 'l', 't', 'z'), "Luxembourgish"},
1038 {MAKECODE('\0', 'm', 'a', 'c'), "Macedonian"},
1039 {MAKECODE('\0', 'm', 'k', 'd'), "Macedonian"},
1040 {MAKECODE('\0', 'm', 'a', 'd'), "Madurese"},
1041 {MAKECODE('\0', 'm', 'a', 'g'), "Magahi"},
1042 {MAKECODE('\0', 'm', 'a', 'i'), "Maithili"},
1043 {MAKECODE('\0', 'm', 'a', 'k'), "Makasar"},
1044 {MAKECODE('\0', 'm', 'l', 'g'), "Malagasy"},
1045 {MAKECODE('\0', 'm', 'a', 'y'), "Malay"},
1046 {MAKECODE('\0', 'm', 's', 'a'), "Malay"},
1047 {MAKECODE('\0', 'm', 'a', 'l'), "Malayalam"},
1048 {MAKECODE('\0', 'm', 'l', 't'), "Maltese"},
1049 {MAKECODE('\0', 'm', 'n', 'c'), "Manchu"},
1050 {MAKECODE('\0', 'm', 'd', 'r'), "Mandar"},
1051 {MAKECODE('\0', 'm', 'a', 'n'), "Mandingo"},
1052 {MAKECODE('\0', 'm', 'n', 'i'), "Manipuri"},
1053 {MAKECODE('\0', 'm', 'n', 'o'), "Manobo languages"},
1054 {MAKECODE('\0', 'g', 'l', 'v'), "Manx"},
1055 {MAKECODE('\0', 'm', 'a', 'o'), "Maori"},
1056 {MAKECODE('\0', 'm', 'r', 'i'), "Maori"},
1057 {MAKECODE('\0', 'm', 'a', 'r'), "Marathi"},
1058 {MAKECODE('\0', 'c', 'h', 'm'), "Mari"},
1059 {MAKECODE('\0', 'm', 'a', 'h'), "Marshallese"},
1060 {MAKECODE('\0', 'm', 'w', 'r'), "Marwari"},
1061 {MAKECODE('\0', 'm', 'a', 's'), "Masai"},
1062 {MAKECODE('\0', 'm', 'y', 'n'), "Mayan languages"},
1063 {MAKECODE('\0', 'm', 'e', 'n'), "Mende"},
1064 {MAKECODE('\0', 'm', 'i', 'c'), "Micmac"},
1065 {MAKECODE('\0', 'm', 'i', 'c'), "Mi'kmaq"},
1066 {MAKECODE('\0', 'm', 'i', 'n'), "Minangkabau"},
1067 {MAKECODE('\0', 'm', 'w', 'l'), "Mirandese"},
1068 {MAKECODE('\0', 'm', 'i', 's'), "Miscellaneous languages"},
1069 {MAKECODE('\0', 'm', 'o', 'h'), "Mohawk"},
1070 {MAKECODE('\0', 'm', 'd', 'f'), "Moksha"},
1071 {MAKECODE('\0', 'm', 'o', 'l'), "Moldavian"},
1072 {MAKECODE('\0', 'm', 'k', 'h'), "Mon-Khmer (Other)"},
1073 {MAKECODE('\0', 'l', 'o', 'l'), "Mongo"},
1074 {MAKECODE('\0', 'm', 'o', 'n'), "Mongolian"},
1075 {MAKECODE('\0', 'm', 'o', 's'), "Mossi"},
1076 {MAKECODE('\0', 'm', 'u', 'l'), "Multiple languages"},
1077 {MAKECODE('\0', 'm', 'u', 'n'), "Munda languages"},
1078 {MAKECODE('\0', 'n', 'a', 'h'), "Nahuatl"},
1079 {MAKECODE('\0', 'n', 'a', 'u'), "Nauru"},
1080 {MAKECODE('\0', 'n', 'a', 'v'), "Navaho, Navajo"},
1081 {MAKECODE('\0', 'n', 'a', 'v'), "Navajo"},
1082 {MAKECODE('\0', 'n', 'd', 'e'), "Ndebele, North"},
1083 {MAKECODE('\0', 'n', 'b', 'l'), "Ndebele, South"},
1084 {MAKECODE('\0', 'n', 'd', 'o'), "Ndonga"},
1085 {MAKECODE('\0', 'n', 'a', 'p'), "Neapolitan"},
1086 {MAKECODE('\0', 'n', 'e', 'w'), "Nepal Bhasa"},
1087 {MAKECODE('\0', 'n', 'e', 'p'), "Nepali"},
1088 {MAKECODE('\0', 'n', 'e', 'w'), "Newari"},
1089 {MAKECODE('\0', 'n', 'i', 'a'), "Nias"},
1090 {MAKECODE('\0', 'n', 'i', 'c'), "Niger-Kordofanian (Other)"},
1091 {MAKECODE('\0', 's', 's', 'a'), "Nilo-Saharan (Other)"},
1092 {MAKECODE('\0', 'n', 'i', 'u'), "Niuean"},
1093 {MAKECODE('\0', 'z', 'x', 'x'), "No linguistic content"},
1094 {MAKECODE('\0', 'n', 'o', 'g'), "Nogai"},
1095 {MAKECODE('\0', 'n', 'o', 'n'), "Norse, Old"},
1096 {MAKECODE('\0', 'n', 'a', 'i'), "North American Indian (Other)"},
1097 {MAKECODE('\0', 's', 'm', 'e'), "Northern Sami"},
1098 {MAKECODE('\0', 'n', 's', 'o'), "Northern Sotho"},
1099 {MAKECODE('\0', 'n', 'd', 'e'), "North Ndebele"},
1100 {MAKECODE('\0', 'n', 'o', 'r'), "Norwegian"},
1101 {MAKECODE('\0', 'n', 'o', 'b'), "Norwegian Bokm\xC3\xA5l"},
1102 {MAKECODE('\0', 'n', 'n', 'o'), "Norwegian Nynorsk"},
1103 {MAKECODE('\0', 'n', 'u', 'b'), "Nubian languages"},
1104 {MAKECODE('\0', 'n', 'y', 'm'), "Nyamwezi"},
1105 {MAKECODE('\0', 'n', 'y', 'a'), "Nyanja"},
1106 {MAKECODE('\0', 'n', 'y', 'n'), "Nyankole"},
1107 {MAKECODE('\0', 'n', 'n', 'o'), "Nynorsk, Norwegian"},
1108 {MAKECODE('\0', 'n', 'y', 'o'), "Nyoro"},
1109 {MAKECODE('\0', 'n', 'z', 'i'), "Nzima"},
1110 {MAKECODE('\0', 'o', 'c', 'i'), "Occitan (post 1500)"},
1111 {MAKECODE('\0', 'o', 'j', 'i'), "Ojibwa"},
1112 {MAKECODE('\0', 'c', 'h', 'u'), "Old Bulgarian"},
1113 {MAKECODE('\0', 'c', 'h', 'u'), "Old Church Slavonic"},
1114 {MAKECODE('\0', 'n', 'w', 'c'), "Old Newari"},
1115 {MAKECODE('\0', 'c', 'h', 'u'), "Old Slavonic"},
1116 {MAKECODE('\0', 'o', 'r', 'i'), "Oriya"},
1117 {MAKECODE('\0', 'o', 'r', 'm'), "Oromo"},
1118 {MAKECODE('\0', 'o', 's', 'a'), "Osage"},
1119 {MAKECODE('\0', 'o', 's', 's'), "Ossetian"},
1120 {MAKECODE('\0', 'o', 's', 's'), "Ossetic"},
1121 {MAKECODE('\0', 'o', 't', 'o'), "Otomian languages"},
1122 {MAKECODE('\0', 'p', 'a', 'l'), "Pahlavi"},
1123 {MAKECODE('\0', 'p', 'a', 'u'), "Palauan"},
1124 {MAKECODE('\0', 'p', 'l', 'i'), "Pali"},
1125 {MAKECODE('\0', 'p', 'a', 'm'), "Pampanga"},
1126 {MAKECODE('\0', 'p', 'a', 'g'), "Pangasinan"},
1127 {MAKECODE('\0', 'p', 'a', 'n'), "Panjabi"},
1128 {MAKECODE('\0', 'p', 'a', 'p'), "Papiamento"},
1129 {MAKECODE('\0', 'p', 'a', 'a'), "Papuan (Other)"},
1130 {MAKECODE('\0', 'n', 's', 'o'), "Pedi"},
1131 {MAKECODE('\0', 'p', 'e', 'r'), "Persian"},
1132 {MAKECODE('\0', 'f', 'a', 's'), "Persian"},
1133 {MAKECODE('\0', 'p', 'e', 'o'), "Persian, Old (ca.600-400 B.C.)"},
1134 {MAKECODE('\0', 'p', 'h', 'i'), "Philippine (Other)"},
1135 {MAKECODE('\0', 'p', 'h', 'n'), "Phoenician"},
1136 {MAKECODE('\0', 'f', 'i', 'l'), "Pilipino"},
1137 {MAKECODE('\0', 'p', 'o', 'n'), "Pohnpeian"},
1138 {MAKECODE('\0', 'p', 'o', 'l'), "Polish"},
1139 {MAKECODE('\0', 'p', 'o', 'r'), "Portuguese"},
1140 // pob = unofficial language code for Brazilian Portuguese
1141 {MAKECODE('\0', 'p', 'o', 'b'), "Portuguese (Brazil)"},
1142 {MAKECODE('\0', 'p', 'r', 'a'), "Prakrit languages"},
1143 {MAKECODE('\0', 'o', 'c', 'i'), "Proven\xC3\xA7"
1144 "al"},
1145 {MAKECODE('\0', 'p', 'r', 'o'), "Proven\xC3\xA7"
1146 "al, Old (to 1500)"},
1147 {MAKECODE('\0', 'p', 'a', 'n'), "Punjabi"},
1148 {MAKECODE('\0', 'p', 'u', 's'), "Pushto"},
1149 {MAKECODE('\0', 'q', 'u', 'e'), "Quechua"},
1150 {MAKECODE('\0', 'r', 'o', 'h'), "Raeto-Romance"},
1151 {MAKECODE('\0', 'r', 'a', 'j'), "Rajasthani"},
1152 {MAKECODE('\0', 'r', 'a', 'p'), "Rapanui"},
1153 {MAKECODE('\0', 'r', 'a', 'r'), "Rarotongan"},
1154 // { "qaa-qtz", "Reserved for local use" },
1155 {MAKECODE('\0', 'r', 'o', 'a'), "Romance (Other)"},
1156 {MAKECODE('\0', 'r', 'u', 'm'), "Romanian"},
1157 {MAKECODE('\0', 'r', 'o', 'n'), "Romanian"},
1158 {MAKECODE('\0', 'r', 'o', 'm'), "Romany"},
1159 {MAKECODE('\0', 'r', 'u', 'n'), "Rundi"},
1160 {MAKECODE('\0', 'r', 'u', 's'), "Russian"},
1161 {MAKECODE('\0', 's', 'a', 'l'), "Salishan languages"},
1162 {MAKECODE('\0', 's', 'a', 'm'), "Samaritan Aramaic"},
1163 {MAKECODE('\0', 's', 'm', 'i'), "Sami languages (Other)"},
1164 {MAKECODE('\0', 's', 'm', 'o'), "Samoan"},
1165 {MAKECODE('\0', 's', 'a', 'd'), "Sandawe"},
1166 {MAKECODE('\0', 's', 'a', 'g'), "Sango"},
1167 {MAKECODE('\0', 's', 'a', 'n'), "Sanskrit"},
1168 {MAKECODE('\0', 's', 'a', 't'), "Santali"},
1169 {MAKECODE('\0', 's', 'r', 'd'), "Sardinian"},
1170 {MAKECODE('\0', 's', 'a', 's'), "Sasak"},
1171 {MAKECODE('\0', 'n', 'd', 's'), "Saxon, Low"},
1172 {MAKECODE('\0', 's', 'c', 'o'), "Scots"},
1173 {MAKECODE('\0', 'g', 'l', 'a'), "Scottish Gaelic"},
1174 {MAKECODE('\0', 's', 'e', 'l'), "Selkup"},
1175 {MAKECODE('\0', 's', 'e', 'm'), "Semitic (Other)"},
1176 {MAKECODE('\0', 'n', 's', 'o'), "Sepedi"},
1177 {MAKECODE('\0', 's', 'c', 'c'), "Serbian"},
1178 {MAKECODE('\0', 's', 'r', 'p'), "Serbian"},
1179 {MAKECODE('\0', 's', 'r', 'r'), "Serer"},
1180 {MAKECODE('\0', 's', 'h', 'n'), "Shan"},
1181 {MAKECODE('\0', 's', 'n', 'a'), "Shona"},
1182 {MAKECODE('\0', 'i', 'i', 'i'), "Sichuan Yi"},
1183 {MAKECODE('\0', 's', 'c', 'n'), "Sicilian"},
1184 {MAKECODE('\0', 's', 'i', 'd'), "Sidamo"},
1185 {MAKECODE('\0', 's', 'g', 'n'), "Sign languages"},
1186 {MAKECODE('\0', 'b', 'l', 'a'), "Siksika"},
1187 {MAKECODE('\0', 's', 'n', 'd'), "Sindhi"},
1188 {MAKECODE('\0', 's', 'i', 'n'), "Sinhala"},
1189 {MAKECODE('\0', 's', 'i', 'n'), "Sinhalese"},
1190 {MAKECODE('\0', 's', 'i', 't'), "Sino-Tibetan (Other)"},
1191 {MAKECODE('\0', 's', 'i', 'o'), "Siouan languages"},
1192 {MAKECODE('\0', 's', 'm', 's'), "Skolt Sami"},
1193 {MAKECODE('\0', 'd', 'e', 'n'), "Slave (Athapascan)"},
1194 {MAKECODE('\0', 's', 'l', 'a'), "Slavic (Other)"},
1195 {MAKECODE('\0', 's', 'l', 'o'), "Slovak"},
1196 {MAKECODE('\0', 's', 'l', 'k'), "Slovak"},
1197 {MAKECODE('\0', 's', 'l', 'v'), "Slovenian"},
1198 {MAKECODE('\0', 's', 'o', 'g'), "Sogdian"},
1199 {MAKECODE('\0', 's', 'o', 'm'), "Somali"},
1200 {MAKECODE('\0', 's', 'o', 'n'), "Songhai"},
1201 {MAKECODE('\0', 's', 'n', 'k'), "Soninke"},
1202 {MAKECODE('\0', 'w', 'e', 'n'), "Sorbian languages"},
1203 {MAKECODE('\0', 'n', 's', 'o'), "Sotho, Northern"},
1204 {MAKECODE('\0', 's', 'o', 't'), "Sotho, Southern"},
1205 {MAKECODE('\0', 's', 'a', 'i'), "South American Indian (Other)"},
1206 {MAKECODE('\0', 's', 'm', 'a'), "Southern Sami"},
1207 {MAKECODE('\0', 'n', 'b', 'l'), "South Ndebele"},
1208 {MAKECODE('\0', 's', 'p', 'a'), "Castilian"},
1209 {MAKECODE('\0', 's', 'u', 'k'), "Sukuma"},
1210 {MAKECODE('\0', 's', 'u', 'x'), "Sumerian"},
1211 {MAKECODE('\0', 's', 'u', 'n'), "Sundanese"},
1212 {MAKECODE('\0', 's', 'u', 's'), "Susu"},
1213 {MAKECODE('\0', 's', 'w', 'a'), "Swahili"},
1214 {MAKECODE('\0', 's', 's', 'w'), "Swati"},
1215 {MAKECODE('\0', 's', 'w', 'e'), "Swedish"},
1216 {MAKECODE('\0', 's', 'y', 'r'), "Syriac"},
1217 {MAKECODE('\0', 't', 'g', 'l'), "Tagalog"},
1218 {MAKECODE('\0', 't', 'a', 'h'), "Tahitian"},
1219 {MAKECODE('\0', 't', 'a', 'i'), "Tai (Other)"},
1220 {MAKECODE('\0', 't', 'g', 'k'), "Tajik"},
1221 {MAKECODE('\0', 't', 'm', 'h'), "Tamashek"},
1222 {MAKECODE('\0', 't', 'a', 'm'), "Tamil"},
1223 {MAKECODE('\0', 't', 'a', 't'), "Tatar"},
1224 {MAKECODE('\0', 't', 'e', 'l'), "Telugu"},
1225 {MAKECODE('\0', 't', 'e', 'r'), "Tereno"},
1226 {MAKECODE('\0', 't', 'e', 't'), "Tetum"},
1227 {MAKECODE('\0', 't', 'h', 'a'), "Thai"},
1228 {MAKECODE('\0', 't', 'i', 'b'), "Tibetan"},
1229 {MAKECODE('\0', 'b', 'o', 'd'), "Tibetan"},
1230 {MAKECODE('\0', 't', 'i', 'g'), "Tigre"},
1231 {MAKECODE('\0', 't', 'i', 'r'), "Tigrinya"},
1232 {MAKECODE('\0', 't', 'e', 'm'), "Timne"},
1233 {MAKECODE('\0', 't', 'i', 'v'), "Tiv"},
1234 {MAKECODE('\0', 't', 'l', 'h'), "tlhIngan-Hol"},
1235 {MAKECODE('\0', 't', 'l', 'i'), "Tlingit"},
1236 {MAKECODE('\0', 't', 'p', 'i'), "Tok Pisin"},
1237 {MAKECODE('\0', 't', 'k', 'l'), "Tokelau"},
1238 {MAKECODE('\0', 't', 'o', 'g'), "Tonga (Nyasa)"},
1239 {MAKECODE('\0', 't', 'o', 'n'), "Tonga (Tonga Islands)"},
1240 {MAKECODE('\0', 't', 's', 'i'), "Tsimshian"},
1241 {MAKECODE('\0', 't', 's', 'o'), "Tsonga"},
1242 {MAKECODE('\0', 't', 's', 'n'), "Tswana"},
1243 {MAKECODE('\0', 't', 'u', 'm'), "Tumbuka"},
1244 {MAKECODE('\0', 't', 'u', 'p'), "Tupi languages"},
1245 {MAKECODE('\0', 't', 'u', 'r'), "Turkish"},
1246 {MAKECODE('\0', 'o', 't', 'a'), "Turkish, Ottoman (1500-1928)"},
1247 {MAKECODE('\0', 't', 'u', 'k'), "Turkmen"},
1248 {MAKECODE('\0', 't', 'v', 'l'), "Tuvalu"},
1249 {MAKECODE('\0', 't', 'y', 'v'), "Tuvinian"},
1250 {MAKECODE('\0', 't', 'w', 'i'), "Twi"},
1251 {MAKECODE('\0', 'u', 'd', 'm'), "Udmurt"},
1252 {MAKECODE('\0', 'u', 'g', 'a'), "Ugaritic"},
1253 {MAKECODE('\0', 'u', 'i', 'g'), "Uighur"},
1254 {MAKECODE('\0', 'u', 'k', 'r'), "Ukrainian"},
1255 {MAKECODE('\0', 'u', 'm', 'b'), "Umbundu"},
1256 {MAKECODE('\0', 'u', 'n', 'd'), "Undetermined"},
1257 {MAKECODE('\0', 'h', 's', 'b'), "Upper Sorbian"},
1258 {MAKECODE('\0', 'u', 'r', 'd'), "Urdu"},
1259 {MAKECODE('\0', 'u', 'i', 'g'), "Uyghur"},
1260 {MAKECODE('\0', 'u', 'z', 'b'), "Uzbek"},
1261 {MAKECODE('\0', 'v', 'a', 'i'), "Vai"},
1262 {MAKECODE('\0', 'c', 'a', 't'), "Valencian"},
1263 {MAKECODE('\0', 'v', 'e', 'n'), "Venda"},
1264 {MAKECODE('\0', 'v', 'i', 'e'), "Vietnamese"},
1265 {MAKECODE('\0', 'v', 'o', 'l'), "Volap\xC3\xBCk"},
1266 {MAKECODE('\0', 'v', 'o', 't'), "Votic"},
1267 {MAKECODE('\0', 'w', 'a', 'k'), "Wakashan languages"},
1268 {MAKECODE('\0', 'w', 'a', 'l'), "Walamo"},
1269 {MAKECODE('\0', 'w', 'l', 'n'), "Walloon"},
1270 {MAKECODE('\0', 'w', 'a', 'r'), "Waray"},
1271 {MAKECODE('\0', 'w', 'a', 's'), "Washo"},
1272 {MAKECODE('\0', 'w', 'e', 'l'), "Welsh"},
1273 {MAKECODE('\0', 'c', 'y', 'm'), "Welsh"},
1274 {MAKECODE('\0', 'w', 'o', 'l'), "Wolof"},
1275 {MAKECODE('\0', 'x', 'h', 'o'), "Xhosa"},
1276 {MAKECODE('\0', 's', 'a', 'h'), "Yakut"},
1277 {MAKECODE('\0', 'y', 'a', 'o'), "Yao"},
1278 {MAKECODE('\0', 'y', 'a', 'p'), "Yapese"},
1279 {MAKECODE('\0', 'y', 'i', 'd'), "Yiddish"},
1280 {MAKECODE('\0', 'y', 'o', 'r'), "Yoruba"},
1281 {MAKECODE('\0', 'y', 'p', 'k'), "Yupik languages"},
1282 {MAKECODE('\0', 'z', 'n', 'd'), "Zande"},
1283 {MAKECODE('\0', 'z', 'a', 'p'), "Zapotec"},
1284 {MAKECODE('\0', 'z', 'e', 'n'), "Zenaga"},
1285 {MAKECODE('\0', 'z', 'h', 'a'), "Zhuang"},
1286 {MAKECODE('\0', 'z', 'u', 'l'), "Zulu"},
1287 {MAKECODE('\0', 'z', 'u', 'n'), "Zuni"},
1288}};
1289// clang-format on
1290
1291// clang-format off
1292const std::array<ISO639, 190> LanguageCodes = {{
1293 {"aa", "aar", NULL, NULL},
1294 {"ab", "abk", NULL, NULL},
1295 {"af", "afr", NULL, NULL},
1296 {"ak", "aka", NULL, NULL},
1297 {"am", "amh", NULL, NULL},
1298 {"ar", "ara", NULL, NULL},
1299 {"an", "arg", NULL, NULL},
1300 {"as", "asm", NULL, NULL},
1301 {"av", "ava", NULL, NULL},
1302 {"ae", "ave", NULL, NULL},
1303 {"ay", "aym", NULL, NULL},
1304 {"az", "aze", NULL, NULL},
1305 {"ba", "bak", NULL, NULL},
1306 {"bm", "bam", NULL, NULL},
1307 {"be", "bel", NULL, NULL},
1308 {"bn", "ben", NULL, NULL},
1309 {"bh", "bih", NULL, NULL},
1310 {"bi", "bis", NULL, NULL},
1311 {"bo", "tib", NULL, "bod"},
1312 {"bs", "bos", NULL, NULL},
1313 {"br", "bre", NULL, NULL},
1314 {"bg", "bul", NULL, NULL},
1315 {"ca", "cat", NULL, NULL},
1316 {"cs", "cze", "ces", "ces"},
1317 {"ch", "cha", NULL, NULL},
1318 {"ce", "che", NULL, NULL},
1319 {"cu", "chu", NULL, NULL},
1320 {"cv", "chv", NULL, NULL},
1321 {"kw", "cor", NULL, NULL},
1322 {"co", "cos", NULL, NULL},
1323 {"cr", "cre", NULL, NULL},
1324 {"cy", "wel", NULL, "cym"},
1325 {"da", "dan", NULL, NULL},
1326 {"de", "ger", "deu", "deu"},
1327 {"dv", "div", NULL, NULL},
1328 {"dz", "dzo", NULL, NULL},
1329 {"el", "gre", "ell", "ell"},
1330 {"en", "eng", NULL, NULL},
1331 {"eo", "epo", NULL, NULL},
1332 {"et", "est", NULL, NULL},
1333 {"eu", "baq", NULL, "eus"},
1334 {"ee", "ewe", NULL, NULL},
1335 {"fo", "fao", NULL, NULL},
1336 {"fa", "per", NULL, "fas"},
1337 {"fj", "fij", NULL, NULL},
1338 {"fi", "fin", NULL, NULL},
1339 {"fr", "fre", "fra", "fra"},
1340 {"fy", "fry", NULL, NULL},
1341 {"ff", "ful", NULL, NULL},
1342 {"gd", "gla", NULL, NULL},
1343 {"ga", "gle", NULL, NULL},
1344 {"gl", "glg", NULL, NULL},
1345 {"gv", "glv", NULL, NULL},
1346 {"gn", "grn", NULL, NULL},
1347 {"gu", "guj", NULL, NULL},
1348 {"ht", "hat", NULL, NULL},
1349 {"ha", "hau", NULL, NULL},
1350 {"he", "heb", NULL, NULL},
1351 {"hz", "her", NULL, NULL},
1352 {"hi", "hin", NULL, NULL},
1353 {"ho", "hmo", NULL, NULL},
1354 {"hr", "hrv", NULL, NULL},
1355 {"hu", "hun", NULL, NULL},
1356 {"hy", "arm", NULL, "hye"},
1357 {"ig", "ibo", NULL, NULL},
1358 {"io", "ido", NULL, NULL},
1359 {"ii", "iii", NULL, NULL},
1360 {"iu", "iku", NULL, NULL},
1361 {"ie", "ile", NULL, NULL},
1362 {"ia", "ina", NULL, NULL},
1363 {"id", "ind", NULL, NULL},
1364 {"ik", "ipk", NULL, NULL},
1365 {"is", "ice", "isl", "isl"},
1366 {"it", "ita", NULL, NULL},
1367 {"jv", "jav", NULL, NULL},
1368 {"ja", "jpn", NULL, NULL},
1369 {"kl", "kal", NULL, NULL},
1370 {"kn", "kan", NULL, NULL},
1371 {"ks", "kas", NULL, NULL},
1372 {"ka", "geo", NULL, "kat"},
1373 {"kr", "kau", NULL, NULL},
1374 {"kk", "kaz", NULL, NULL},
1375 {"km", "khm", NULL, NULL},
1376 {"ki", "kik", NULL, NULL},
1377 {"rw", "kin", NULL, NULL},
1378 {"ky", "kir", NULL, NULL},
1379 {"kv", "kom", NULL, NULL},
1380 {"kg", "kon", NULL, NULL},
1381 {"ko", "kor", NULL, NULL},
1382 {"kj", "kua", NULL, NULL},
1383 {"ku", "kur", NULL, NULL},
1384 {"lo", "lao", NULL, NULL},
1385 {"la", "lat", NULL, NULL},
1386 {"lv", "lav", NULL, NULL},
1387 {"li", "lim", NULL, NULL},
1388 {"ln", "lin", NULL, NULL},
1389 {"lt", "lit", NULL, NULL},
1390 {"lb", "ltz", NULL, NULL},
1391 {"lu", "lub", NULL, NULL},
1392 {"lg", "lug", NULL, NULL},
1393 {"mk", "mac", NULL, "mdk"},
1394 {"mh", "mah", NULL, NULL},
1395 {"ml", "mal", NULL, NULL},
1396 {"mi", "mao", NULL, "mri"},
1397 {"mr", "mar", NULL, NULL},
1398 {"ms", "may", NULL, "msa"},
1399 {"mg", "mlg", NULL, NULL},
1400 {"mt", "mlt", NULL, NULL},
1401 {"mn", "mon", NULL, NULL},
1402 {"my", "bur", NULL, "mya"},
1403 {"na", "nau", NULL, NULL},
1404 {"nv", "nav", NULL, NULL},
1405 {"nr", "nbl", NULL, NULL},
1406 {"nd", "nde", NULL, NULL},
1407 {"ng", "ndo", NULL, NULL},
1408 {"ne", "nep", NULL, NULL},
1409 {"nl", "dut", "nld", "nld"},
1410 {"nn", "nno", NULL, NULL},
1411 {"nb", "nob", NULL, NULL},
1412 {"no", "nor", NULL, NULL},
1413 {"ny", "nya", NULL, NULL},
1414 {"oc", "oci", NULL, NULL},
1415 {"oj", "oji", NULL, NULL},
1416 {"or", "ori", NULL, NULL},
1417 {"om", "orm", NULL, NULL},
1418 {"os", "oss", NULL, NULL},
1419 {"pa", "pan", NULL, NULL},
1420 // pb / pob = unofficial language code for Brazilian Portuguese
1421 {"pb", "pob", NULL, NULL},
1422 {"pi", "pli", NULL, NULL},
1423 {"pl", "pol", "plk", NULL},
1424 {"pt", "por", "ptg", NULL},
1425 {"ps", "pus", NULL, NULL},
1426 {"qu", "que", NULL, NULL},
1427 {"rm", "roh", NULL, NULL},
1428 {"ro", "rum", "ron", "ron"},
1429 {"rn", "run", NULL, NULL},
1430 {"ru", "rus", NULL, NULL},
1431 {"sh", "scr", NULL, NULL},
1432 {"sg", "sag", NULL, NULL},
1433 {"sa", "san", NULL, NULL},
1434 {"si", "sin", NULL, NULL},
1435 {"sk", "slo", "sky", "slk"},
1436 {"sl", "slv", NULL, NULL},
1437 {"se", "sme", NULL, NULL},
1438 {"sm", "smo", NULL, NULL},
1439 {"sn", "sna", NULL, NULL},
1440 {"sd", "snd", NULL, NULL},
1441 {"so", "som", NULL, NULL},
1442 {"st", "sot", NULL, NULL},
1443 {"es", "spa", "esp", NULL},
1444 {"sq", "alb", NULL, "sqi"},
1445 {"sc", "srd", NULL, NULL},
1446 {"sr", "srp", NULL, NULL},
1447 {"ss", "ssw", NULL, NULL},
1448 {"su", "sun", NULL, NULL},
1449 {"sw", "swa", NULL, NULL},
1450 {"sv", "swe", "sve", NULL},
1451 {"ty", "tah", NULL, NULL},
1452 {"ta", "tam", NULL, NULL},
1453 {"tt", "tat", NULL, NULL},
1454 {"te", "tel", NULL, NULL},
1455 {"tg", "tgk", NULL, NULL},
1456 {"tl", "tgl", NULL, NULL},
1457 {"th", "tha", NULL, NULL},
1458 {"ti", "tir", NULL, NULL},
1459 {"to", "ton", NULL, NULL},
1460 {"tn", "tsn", NULL, NULL},
1461 {"ts", "tso", NULL, NULL},
1462 {"tk", "tuk", NULL, NULL},
1463 {"tr", "tur", "trk", NULL},
1464 {"tw", "twi", NULL, NULL},
1465 {"ug", "uig", NULL, NULL},
1466 {"uk", "ukr", NULL, NULL},
1467 {"ur", "urd", NULL, NULL},
1468 {"uz", "uzb", NULL, NULL},
1469 {"ve", "ven", NULL, NULL},
1470 {"vi", "vie", NULL, NULL},
1471 {"vo", "vol", NULL, NULL},
1472 {"wa", "wln", NULL, NULL},
1473 {"wo", "wol", NULL, NULL},
1474 {"xh", "xho", NULL, NULL},
1475 {"yi", "yid", NULL, NULL},
1476 {"yo", "yor", NULL, NULL},
1477 {"za", "zha", NULL, NULL},
1478 {"zh", "chi", "zho", "zho"},
1479 {"zu", "zul", NULL, NULL},
1480 {"zv", "und", NULL, NULL}, // Kodi intern mapping for missing "Undetermined" iso639-1 code
1481 {"zx", "zxx", NULL,
1482 NULL}, // Kodi intern mapping for missing "No linguistic content" iso639-1 code
1483 {"zy", "mis", NULL,
1484 NULL}, // Kodi intern mapping for missing "Miscellaneous languages" iso639-1 code
1485 {"zz", "mul", NULL, NULL} // Kodi intern mapping for missing "Multiple languages" iso639-1 code
1486}};
1487// clang-format on
1488
1489// Based on ISO 3166
1490// clang-format off
1491const std::array<ISO3166_1, 245> RegionCodes = {{
1492 {"af", "afg"},
1493 {"ax", "ala"},
1494 {"al", "alb"},
1495 {"dz", "dza"},
1496 {"as", "asm"},
1497 {"ad", "and"},
1498 {"ao", "ago"},
1499 {"ai", "aia"},
1500 {"aq", "ata"},
1501 {"ag", "atg"},
1502 {"ar", "arg"},
1503 {"am", "arm"},
1504 {"aw", "abw"},
1505 {"au", "aus"},
1506 {"at", "aut"},
1507 {"az", "aze"},
1508 {"bs", "bhs"},
1509 {"bh", "bhr"},
1510 {"bd", "bgd"},
1511 {"bb", "brb"},
1512 {"by", "blr"},
1513 {"be", "bel"},
1514 {"bz", "blz"},
1515 {"bj", "ben"},
1516 {"bm", "bmu"},
1517 {"bt", "btn"},
1518 {"bo", "bol"},
1519 {"ba", "bih"},
1520 {"bw", "bwa"},
1521 {"bv", "bvt"},
1522 {"br", "bra"},
1523 {"io", "iot"},
1524 {"bn", "brn"},
1525 {"bg", "bgr"},
1526 {"bf", "bfa"},
1527 {"bi", "bdi"},
1528 {"kh", "khm"},
1529 {"cm", "cmr"},
1530 {"ca", "can"},
1531 {"cv", "cpv"},
1532 {"ky", "cym"},
1533 {"cf", "caf"},
1534 {"td", "tcd"},
1535 {"cl", "chl"},
1536 {"cn", "chn"},
1537 {"cx", "cxr"},
1538 {"co", "col"},
1539 {"km", "com"},
1540 {"cg", "cog"},
1541 {"cd", "cod"},
1542 {"ck", "cok"},
1543 {"cr", "cri"},
1544 {"ci", "civ"},
1545 {"hr", "hrv"},
1546 {"cu", "cub"},
1547 {"cy", "cyp"},
1548 {"cz", "cze"},
1549 {"dk", "dnk"},
1550 {"dj", "dji"},
1551 {"dm", "dma"},
1552 {"do", "dom"},
1553 {"ec", "ecu"},
1554 {"eg", "egy"},
1555 {"sv", "slv"},
1556 {"gq", "gnq"},
1557 {"er", "eri"},
1558 {"ee", "est"},
1559 {"et", "eth"},
1560 {"fk", "flk"},
1561 {"fo", "fro"},
1562 {"fj", "fji"},
1563 {"fi", "fin"},
1564 {"fr", "fra"},
1565 {"gf", "guf"},
1566 {"pf", "pyf"},
1567 {"tf", "atf"},
1568 {"ga", "gab"},
1569 {"gm", "gmb"},
1570 {"ge", "geo"},
1571 {"de", "deu"},
1572 {"gh", "gha"},
1573 {"gi", "gib"},
1574 {"gr", "grc"},
1575 {"gl", "grl"},
1576 {"gd", "grd"},
1577 {"gp", "glp"},
1578 {"gu", "gum"},
1579 {"gt", "gtm"},
1580 {"gg", "ggy"},
1581 {"gn", "gin"},
1582 {"gw", "gnb"},
1583 {"gy", "guy"},
1584 {"ht", "hti"},
1585 {"hm", "hmd"},
1586 {"va", "vat"},
1587 {"hn", "hnd"},
1588 {"hk", "hkg"},
1589 {"hu", "hun"},
1590 {"is", "isl"},
1591 {"in", "ind"},
1592 {"id", "idn"},
1593 {"ir", "irn"},
1594 {"iq", "irq"},
1595 {"ie", "irl"},
1596 {"im", "imn"},
1597 {"il", "isr"},
1598 {"it", "ita"},
1599 {"jm", "jam"},
1600 {"jp", "jpn"},
1601 {"je", "jey"},
1602 {"jo", "jor"},
1603 {"kz", "kaz"},
1604 {"ke", "ken"},
1605 {"ki", "kir"},
1606 {"kp", "prk"},
1607 {"kr", "kor"},
1608 {"kw", "kwt"},
1609 {"kg", "kgz"},
1610 {"la", "lao"},
1611 {"lv", "lva"},
1612 {"lb", "lbn"},
1613 {"ls", "lso"},
1614 {"lr", "lbr"},
1615 {"ly", "lby"},
1616 {"li", "lie"},
1617 {"lt", "ltu"},
1618 {"lu", "lux"},
1619 {"mo", "mac"},
1620 {"mk", "mkd"},
1621 {"mg", "mdg"},
1622 {"mw", "mwi"},
1623 {"my", "mys"},
1624 {"mv", "mdv"},
1625 {"ml", "mli"},
1626 {"mt", "mlt"},
1627 {"mh", "mhl"},
1628 {"mq", "mtq"},
1629 {"mr", "mrt"},
1630 {"mu", "mus"},
1631 {"yt", "myt"},
1632 {"mx", "mex"},
1633 {"fm", "fsm"},
1634 {"md", "mda"},
1635 {"mc", "mco"},
1636 {"mn", "mng"},
1637 {"me", "mne"},
1638 {"ms", "msr"},
1639 {"ma", "mar"},
1640 {"mz", "moz"},
1641 {"mm", "mmr"},
1642 {"na", "nam"},
1643 {"nr", "nru"},
1644 {"np", "npl"},
1645 {"nl", "nld"},
1646 {"an", "ant"},
1647 {"nc", "ncl"},
1648 {"nz", "nzl"},
1649 {"ni", "nic"},
1650 {"ne", "ner"},
1651 {"ng", "nga"},
1652 {"nu", "niu"},
1653 {"nf", "nfk"},
1654 {"mp", "mnp"},
1655 {"no", "nor"},
1656 {"om", "omn"},
1657 {"pk", "pak"},
1658 {"pw", "plw"},
1659 {"ps", "pse"},
1660 {"pa", "pan"},
1661 {"pg", "png"},
1662 {"py", "pry"},
1663 {"pe", "per"},
1664 {"ph", "phl"},
1665 {"pn", "pcn"},
1666 {"pl", "pol"},
1667 {"pt", "prt"},
1668 {"pr", "pri"},
1669 {"qa", "qat"},
1670 {"re", "reu"},
1671 {"ro", "rou"},
1672 {"ru", "rus"},
1673 {"rw", "rwa"},
1674 {"bl", "blm"},
1675 {"sh", "shn"},
1676 {"kn", "kna"},
1677 {"lc", "lca"},
1678 {"mf", "maf"},
1679 {"pm", "spm"},
1680 {"vc", "vct"},
1681 {"ws", "wsm"},
1682 {"sm", "smr"},
1683 {"st", "stp"},
1684 {"sa", "sau"},
1685 {"sn", "sen"},
1686 {"rs", "srb"},
1687 {"sc", "syc"},
1688 {"sl", "sle"},
1689 {"sg", "sgp"},
1690 {"sk", "svk"},
1691 {"si", "svn"},
1692 {"sb", "slb"},
1693 {"so", "som"},
1694 {"za", "zaf"},
1695 {"gs", "sgs"},
1696 {"es", "esp"},
1697 {"lk", "lka"},
1698 {"sd", "sdn"},
1699 {"sr", "sur"},
1700 {"sj", "sjm"},
1701 {"sz", "swz"},
1702 {"se", "swe"},
1703 {"ch", "che"},
1704 {"sy", "syr"},
1705 {"tw", "twn"},
1706 {"tj", "tjk"},
1707 {"tz", "tza"},
1708 {"th", "tha"},
1709 {"tl", "tls"},
1710 {"tg", "tgo"},
1711 {"tk", "tkl"},
1712 {"to", "ton"},
1713 {"tt", "tto"},
1714 {"tn", "tun"},
1715 {"tr", "tur"},
1716 {"tm", "tkm"},
1717 {"tc", "tca"},
1718 {"tv", "tuv"},
1719 {"ug", "uga"},
1720 {"ua", "ukr"},
1721 {"ae", "are"},
1722 {"gb", "gbr"},
1723 {"us", "usa"},
1724 {"um", "umi"},
1725 {"uy", "ury"},
1726 {"uz", "uzb"},
1727 {"vu", "vut"},
1728 {"ve", "ven"},
1729 {"vn", "vnm"},
1730 {"vg", "vgb"},
1731 {"vi", "vir"},
1732 {"wf", "wlf"},
1733 {"eh", "esh"},
1734 {"ye", "yem"},
1735 {"zm", "zmb"},
1736 {"zw", "zwe"}
1737}};
1738// clang-format on
diff --git a/xbmc/utils/LangCodeExpander.h b/xbmc/utils/LangCodeExpander.h
new file mode 100644
index 0000000..5e26a65
--- /dev/null
+++ b/xbmc/utils/LangCodeExpander.h
@@ -0,0 +1,143 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <map>
12#include <string>
13#include <vector>
14
15class TiXmlElement;
16
17class CLangCodeExpander
18{
19public:
20 CLangCodeExpander();
21 ~CLangCodeExpander();
22
23 enum LANGFORMATS
24 {
25 ISO_639_1,
26 ISO_639_2,
27 ENGLISH_NAME
28 };
29
30 void LoadUserCodes(const TiXmlElement* pRootElement);
31 void Clear();
32
33 bool Lookup(const std::string& code, std::string& desc);
34 bool Lookup(const int code, std::string& desc);
35
36 /** \brief Determines if two english language names represent the same language.
37 * \param[in] lang1 The first language string to compare given as english language name.
38 * \param[in] lang2 The second language string to compare given as english language name.
39 * \return true if the two language strings represent the same language, false otherwise.
40 * For example "Abkhaz" and "Abkhazian" represent the same language.
41 */
42 bool CompareFullLanguageNames(const std::string& lang1, const std::string& lang2);
43
44 /** \brief Determines if two languages given as ISO 639-1, ISO 639-2/T, or ISO 639-2/B codes represent the same language.
45 * \param[in] code1 The first language to compare given as ISO 639-1, ISO 639-2/T, or ISO 639-2/B code.
46 * \param[in] code2 The second language to compare given as ISO 639-1, ISO 639-2/T, or ISO 639-2/B code.
47 * \return true if the two language codes represent the same language, false otherwise.
48 * For example "ger", "deu" and "de" represent the same language.
49 */
50 bool CompareISO639Codes(const std::string& code1, const std::string& code2);
51
52 /** \brief Converts a language given as 2-Char (ISO 639-1),
53 * 3-Char (ISO 639-2/T or ISO 639-2/B),
54 * or full english name string to a 2-Char (ISO 639-1) code.
55 * \param[out] code The 2-Char language code of the given language lang.
56 * \param[in] lang The language that should be converted.
57 * \return true if the conversion succeeded, false otherwise.
58 */
59 bool ConvertToISO6391(const std::string& lang, std::string& code);
60
61 /** \brief Converts a language given as 2-Char (ISO 639-1),
62 * 3-Char (ISO 639-2/T or ISO 639-2/B),
63 * or full english name string to a 3-Char ISO 639-2/B code.
64 * \param[in] lang The language that should be converted.
65 * \return The 3-Char ISO 639-2/B code of lang if that code exists, lang otherwise.
66 */
67 std::string ConvertToISO6392B(const std::string& lang);
68
69 /** \brief Converts a language given as 2-Char (ISO 639-1) to a 3-Char (ISO 639-2/T) code.
70 * \param[in] strISO6391 The language that should be converted.
71 * \param[out] strISO6392B The 3-Char (ISO 639-2/B) language code of the given language strISO6391.
72 * \param[in] checkWin32Locales Whether to also check WIN32 specific language codes.
73 * \return true if the conversion succeeded, false otherwise.
74 */
75 static bool ConvertISO6391ToISO6392B(const std::string& strISO6391, std::string& strISO6392B, bool checkWin32Locales = false);
76
77 /** \brief Converts a language given as 2-Char (ISO 639-1),
78 * 3-Char (ISO 639-2/T or ISO 639-2/B),
79 * or full english name string to a 3-Char ISO 639-2/T code.
80 * \param[in] strCharCode The language that should be converted.
81 * \param[out] strISO6392B The 3-Char (ISO 639-2/B) language code of the given language strISO6391.
82 * \param[in] checkWin32Locales Whether to also check WIN32 specific language codes.
83 * \return true if the conversion succeeded, false otherwise.
84 */
85 bool ConvertToISO6392B(const std::string& strCharCode, std::string& strISO6392B, bool checkWin32Locales = false);
86
87 /** \brief Converts a language given as 2-Char (ISO 639-1),
88 * 3-Char (ISO 639-2/T or ISO 639-2/B),
89 * or full english name string to a 3-Char ISO 639-2/T code.
90 * \param[in] strCharCode The language that should be converted.
91 * \param[out] strISO6392T The 3-Char (ISO 639-2/T) language code of the given language strISO6391.
92 * \param[in] checkWin32Locales Whether to also check WIN32 specific language codes.
93 * \return true if the conversion succeeded, false otherwise.
94 */
95 bool ConvertToISO6392T(const std::string& strCharCode, std::string& strISO6392T, bool checkWin32Locales = false);
96
97 /** \brief Converts a language given as 2-Char (ISO 639-1),
98 * 3-Char (ISO 639-2/T or ISO 639-2/B),
99 * or full english name string to a 3-Char ISO 639-2/T code.
100 * \param[in] lang The language that should be converted.
101 * \return The 3-Char ISO 639-2/T code of lang if that code exists, lang otherwise.
102 */
103 std::string ConvertToISO6392T(const std::string& lang);
104
105#ifdef TARGET_WINDOWS
106 static bool ConvertISO31661Alpha2ToISO31661Alpha3(const std::string& strISO31661Alpha2, std::string& strISO31661Alpha3);
107 static bool ConvertWindowsLanguageCodeToISO6392B(const std::string& strWindowsLanguageCode, std::string& strISO6392B);
108#endif
109
110 std::vector<std::string> GetLanguageNames(LANGFORMATS format = ISO_639_1, bool customNames = false);
111protected:
112
113 /** \brief Converts a language code given as a long, see #MAKECODE(a, b, c, d)
114 * to its string representation.
115 * \param[in] code The language code given as a long, see #MAKECODE(a, b, c, d).
116 * \param[out] ret The string representation of the given language code code.
117 */
118 static void CodeToString(long code, std::string& ret);
119
120 static bool LookupInISO639Tables(const std::string& code, std::string& desc);
121 bool LookupInUserMap(const std::string& code, std::string& desc);
122
123 /** \brief Looks up the ISO 639-1, ISO 639-2/T, or ISO 639-2/B, whichever it finds first,
124 * code of the given english language name.
125 * \param[in] desc The english language name for which a code is looked for.
126 * \param[out] code The ISO 639-1, ISO 639-2/T, or ISO 639-2/B code of the given language desc.
127 * \return true if the a code was found, false otherwise.
128 */
129 bool ReverseLookup(const std::string& desc, std::string& code);
130
131
132 /** \brief Looks up the user defined code of the given code or language name.
133 * \param[in] desc The language code or name that should be converted.
134 * \param[out] userCode The user defined language code of the given language desc.
135 * \return true if desc was found, false otherwise.
136 */
137 bool LookupUserCode(const std::string& desc, std::string &userCode);
138
139 typedef std::map<std::string, std::string> STRINGLOOKUPTABLE;
140 STRINGLOOKUPTABLE m_mapUser;
141};
142
143extern 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 @@
1/*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "LegacyPathTranslation.h"
10
11#include "URL.h"
12#include "utils/StringUtils.h"
13
14typedef struct Translator {
15 const char *legacyPath;
16 const char *newPath;
17} Translator;
18
19// ATTENTION: Make sure the longer match strings go first
20// because the string match is performed with StringUtils::StartsWith()
21static Translator s_videoDbTranslator[] = {
22 { "videodb://1/1", "videodb://movies/genres" },
23 { "videodb://1/2", "videodb://movies/titles" },
24 { "videodb://1/3", "videodb://movies/years" },
25 { "videodb://1/4", "videodb://movies/actors" },
26 { "videodb://1/5", "videodb://movies/directors" },
27 { "videodb://1/6", "videodb://movies/studios" },
28 { "videodb://1/7", "videodb://movies/sets" },
29 { "videodb://1/8", "videodb://movies/countries" },
30 { "videodb://1/9", "videodb://movies/tags" },
31 { "videodb://1", "videodb://movies" },
32 { "videodb://2/1", "videodb://tvshows/genres" },
33 { "videodb://2/2", "videodb://tvshows/titles" },
34 { "videodb://2/3", "videodb://tvshows/years" },
35 { "videodb://2/4", "videodb://tvshows/actors" },
36 { "videodb://2/5", "videodb://tvshows/studios" },
37 { "videodb://2/9", "videodb://tvshows/tags" },
38 { "videodb://2", "videodb://tvshows" },
39 { "videodb://3/1", "videodb://musicvideos/genres" },
40 { "videodb://3/2", "videodb://musicvideos/titles" },
41 { "videodb://3/3", "videodb://musicvideos/years" },
42 { "videodb://3/4", "videodb://musicvideos/artists" },
43 { "videodb://3/5", "videodb://musicvideos/albums" },
44 { "videodb://3/9", "videodb://musicvideos/tags" },
45 { "videodb://3", "videodb://musicvideos" },
46 { "videodb://4", "videodb://recentlyaddedmovies" },
47 { "videodb://5", "videodb://recentlyaddedepisodes" },
48 { "videodb://6", "videodb://recentlyaddedmusicvideos" }
49};
50
51#define VideoDbTranslatorSize sizeof(s_videoDbTranslator) / sizeof(Translator)
52
53// ATTENTION: Make sure the longer match strings go first
54// because the string match is performed with StringUtils::StartsWith()
55static Translator s_musicDbTranslator[] = {
56 { "musicdb://10", "musicdb://singles" },
57 { "musicdb://1", "musicdb://genres" },
58 { "musicdb://2", "musicdb://artists" },
59 { "musicdb://3", "musicdb://albums" },
60 { "musicdb://4", "musicdb://songs" },
61 { "musicdb://5/1", "musicdb://top100/albums" },
62 { "musicdb://5/2", "musicdb://top100/songs" },
63 { "musicdb://5", "musicdb://top100" },
64 { "musicdb://6", "musicdb://recentlyaddedalbums" },
65 { "musicdb://7", "musicdb://recentlyplayedalbums" },
66 { "musicdb://8", "musicdb://compilations" },
67 { "musicdb://9", "musicdb://years" }
68};
69
70#define MusicDbTranslatorSize sizeof(s_musicDbTranslator) / sizeof(Translator)
71
72std::string CLegacyPathTranslation::TranslateVideoDbPath(const CURL &legacyPath)
73{
74 return TranslatePath(legacyPath.Get(), s_videoDbTranslator, VideoDbTranslatorSize);
75}
76
77std::string CLegacyPathTranslation::TranslateMusicDbPath(const CURL &legacyPath)
78{
79 return TranslatePath(legacyPath.Get(), s_musicDbTranslator, MusicDbTranslatorSize);
80}
81
82std::string CLegacyPathTranslation::TranslateVideoDbPath(const std::string &legacyPath)
83{
84 return TranslatePath(legacyPath, s_videoDbTranslator, VideoDbTranslatorSize);
85}
86
87std::string CLegacyPathTranslation::TranslateMusicDbPath(const std::string &legacyPath)
88{
89 return TranslatePath(legacyPath, s_musicDbTranslator, MusicDbTranslatorSize);
90}
91
92std::string CLegacyPathTranslation::TranslatePath(const std::string &legacyPath, Translator *translationMap, size_t translationMapSize)
93{
94 std::string newPath = legacyPath;
95 for (size_t index = 0; index < translationMapSize; index++)
96 {
97 if (StringUtils::StartsWithNoCase(newPath, translationMap[index].legacyPath))
98 {
99 StringUtils::Replace(newPath, translationMap[index].legacyPath, translationMap[index].newPath);
100 break;
101 }
102 }
103
104 return newPath;
105}
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 @@
1/*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13typedef struct Translator Translator;
14
15class CURL;
16
17/*!
18 \brief Translates old internal paths into new ones
19
20 Translates old videodb:// and musicdb:// paths which used numbers
21 to indicate a specific category to new paths using more descriptive
22 strings to indicate categories.
23 */
24class CLegacyPathTranslation
25{
26public:
27 /*!
28 \brief Translates old videodb:// paths to new ones
29
30 \param legacyPath Path in the old videodb:// format using numbers
31 \return Path in the new videodb:// format using descriptive strings
32 */
33 static std::string TranslateVideoDbPath(const CURL &legacyPath);
34 static std::string TranslateVideoDbPath(const std::string &legacyPath);
35
36 /*!
37 \brief Translates old musicdb:// paths to new ones
38
39 \param legacyPath Path in the old musicdb:// format using numbers
40 \return Path in the new musicdb:// format using descriptive strings
41 */
42 static std::string TranslateMusicDbPath(const CURL &legacyPath);
43 static std::string TranslateMusicDbPath(const std::string &legacyPath);
44
45private:
46 static std::string TranslatePath(const std::string &legacyPath, Translator *translationMap, size_t translationMapSize);
47};
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 @@
1/*
2 * Copyright (C) 2014-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11constexpr unsigned long long int operator"" _kib (unsigned long long int val)
12{
13 return val * 1024ull;
14}
15
16constexpr unsigned long long int operator"" _kb (unsigned long long int val)
17{
18 return val * 1000ull;
19}
20
21constexpr unsigned long long int operator"" _mib (unsigned long long int val)
22{
23 return val * 1024ull * 1024ull;
24}
25
26constexpr unsigned long long int operator"" _mb (unsigned long long int val)
27{
28 return val * 1000ull * 1000ull;
29}
diff --git a/xbmc/utils/Locale.cpp b/xbmc/utils/Locale.cpp
new file mode 100644
index 0000000..0a2d692
--- /dev/null
+++ b/xbmc/utils/Locale.cpp
@@ -0,0 +1,284 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Locale.h"
10
11#include "utils/StringUtils.h"
12
13const CLocale CLocale::Empty;
14
15CLocale::CLocale()
16 : m_language(),
17 m_territory(),
18 m_codeset(),
19 m_modifier()
20{ }
21
22CLocale::CLocale(const std::string& language)
23 : m_language(),
24 m_territory(),
25 m_codeset(),
26 m_modifier()
27{
28 m_valid = ParseLocale(language, m_language, m_territory, m_codeset, m_modifier);
29}
30
31CLocale::CLocale(const std::string& language, const std::string& territory)
32 : m_language(language),
33 m_territory(territory),
34 m_codeset(),
35 m_modifier()
36{
37 Initialize();
38}
39
40CLocale::CLocale(const std::string& language, const std::string& territory, const std::string& codeset)
41 : m_language(language),
42 m_territory(territory),
43 m_codeset(codeset),
44 m_modifier()
45{
46 Initialize();
47}
48
49CLocale::CLocale(const std::string& language, const std::string& territory, const std::string& codeset, const std::string& modifier)
50 : m_language(language),
51 m_territory(territory),
52 m_codeset(codeset),
53 m_modifier(modifier)
54{
55 Initialize();
56}
57
58CLocale::~CLocale() = default;
59
60CLocale CLocale::FromString(const std::string& locale)
61{
62 return CLocale(locale);
63}
64
65bool CLocale::operator==(const CLocale& other) const
66{
67 if (!m_valid && !other.m_valid)
68 return true;
69
70 return m_valid == other.m_valid &&
71 StringUtils::EqualsNoCase(m_language, other.m_language) &&
72 StringUtils::EqualsNoCase(m_territory, other.m_territory) &&
73 StringUtils::EqualsNoCase(m_codeset, other.m_codeset) &&
74 StringUtils::EqualsNoCase(m_modifier, other.m_modifier);
75}
76
77std::string CLocale::ToString() const
78{
79 if (!m_valid)
80 return "";
81
82 std::string locale = ToShortString();
83
84 if (!m_codeset.empty())
85 locale += "." + m_codeset;
86
87 if (!m_modifier.empty())
88 locale += "@" + m_modifier;
89
90 return locale;
91}
92
93std::string CLocale::ToStringLC() const
94{
95 if (!m_valid)
96 return "";
97
98 std::string locale = ToString();
99 StringUtils::ToLower(locale);
100
101 return locale;
102}
103
104std::string CLocale::ToShortString() const
105{
106 if (!m_valid)
107 return "";
108
109 std::string locale = m_language;
110
111 if (!m_territory.empty())
112 locale += "_" + m_territory;
113
114 return locale;
115}
116
117std::string CLocale::ToShortStringLC() const
118{
119 if (!m_valid)
120 return "";
121
122 std::string locale = ToShortString();
123 StringUtils::ToLower(locale);
124
125 return locale;
126}
127
128bool CLocale::Equals(const std::string& locale) const
129{
130 CLocale other = FromString(locale);
131
132 return *this == other;
133}
134
135bool CLocale::Matches(const std::string& locale) const
136{
137 CLocale other = FromString(locale);
138
139 if (!m_valid && !other.m_valid)
140 return true;
141 if (!m_valid || !other.m_valid)
142 return false;
143
144 if (!StringUtils::EqualsNoCase(m_language, other.m_language))
145 return false;
146 if (!m_territory.empty() && !other.m_territory.empty() && !StringUtils::EqualsNoCase(m_territory, other.m_territory))
147 return false;
148 if (!m_codeset.empty() && !other.m_codeset.empty() && !StringUtils::EqualsNoCase(m_codeset, other.m_codeset))
149 return false;
150 if (!m_modifier.empty() && !other.m_modifier.empty() && !StringUtils::EqualsNoCase(m_modifier, other.m_modifier))
151 return false;
152
153 return true;
154}
155
156std::string CLocale::FindBestMatch(const std::set<std::string>& locales) const
157{
158 std::string bestMatch = "";
159 int bestMatchRank = -1;
160
161 for (auto const& locale : locales)
162 {
163 // check if there is an exact match
164 if (Equals(locale))
165 return locale;
166
167 int matchRank = GetMatchRank(locale);
168 if (matchRank > bestMatchRank)
169 {
170 bestMatchRank = matchRank;
171 bestMatch = locale;
172 }
173 }
174
175 return bestMatch;
176}
177
178std::string CLocale::FindBestMatch(const std::unordered_map<std::string, std::string>& locales) const
179{
180 std::string bestMatch = "";
181 int bestMatchRank = -1;
182
183 for (auto const& locale : locales)
184 {
185 // check if there is an exact match
186 if (Equals(locale.first))
187 return locale.first;
188
189 int matchRank = GetMatchRank(locale.first);
190 if (matchRank > bestMatchRank)
191 {
192 bestMatchRank = matchRank;
193 bestMatch = locale.first;
194 }
195 }
196
197 return bestMatch;
198}
199
200bool CLocale::CheckValidity(const std::string& language, const std::string& territory, const std::string& codeset, const std::string& modifier)
201{
202 static_cast<void>(territory);
203 static_cast<void>(codeset);
204 static_cast<void>(modifier);
205
206 return !language.empty();
207}
208
209bool CLocale::ParseLocale(const std::string &locale, std::string &language, std::string &territory, std::string &codeset, std::string &modifier)
210{
211 if (locale.empty())
212 return false;
213
214 language.clear();
215 territory.clear();
216 codeset.clear();
217 modifier.clear();
218
219 // the format for a locale is [language[_territory][.codeset][@modifier]]
220 std::string tmp = locale;
221
222 // look for the modifier after @
223 size_t pos = tmp.find("@");
224 if (pos != std::string::npos)
225 {
226 modifier = tmp.substr(pos + 1);
227 tmp = tmp.substr(0, pos);
228 }
229
230 // look for the codeset after .
231 pos = tmp.find(".");
232 if (pos != std::string::npos)
233 {
234 codeset = tmp.substr(pos + 1);
235 tmp = tmp.substr(0, pos);
236 }
237
238 // look for the codeset after _
239 pos = tmp.find("_");
240 if (pos != std::string::npos)
241 {
242 territory = tmp.substr(pos + 1);
243 StringUtils::ToUpper(territory);
244 tmp = tmp.substr(0, pos);
245 }
246
247 // what remains is the language
248 language = tmp;
249 StringUtils::ToLower(language);
250
251 return CheckValidity(language, territory, codeset, modifier);
252}
253
254void CLocale::Initialize()
255{
256 m_valid = CheckValidity(m_language, m_territory, m_codeset, m_modifier);
257 if (m_valid)
258 {
259 StringUtils::ToLower(m_language);
260 StringUtils::ToUpper(m_territory);
261 }
262}
263
264int CLocale::GetMatchRank(const std::string& locale) const
265{
266 CLocale other = FromString(locale);
267
268 // both locales must be valid and match in language
269 if (!m_valid || !other.m_valid ||
270 !StringUtils::EqualsNoCase(m_language, other.m_language))
271 return -1;
272
273 int rank = 0;
274 // matching in territory is considered more important than matching in
275 // codeset and/or modifier
276 if (!m_territory.empty() && !other.m_territory.empty() && StringUtils::EqualsNoCase(m_territory, other.m_territory))
277 rank += 3;
278 if (!m_codeset.empty() && !other.m_codeset.empty() && StringUtils::EqualsNoCase(m_codeset, other.m_codeset))
279 rank += 1;
280 if (!m_modifier.empty() && !other.m_modifier.empty() && StringUtils::EqualsNoCase(m_modifier, other.m_modifier))
281 rank += 1;
282
283 return rank;
284}
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 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <set>
12#include <string>
13#include <unordered_map>
14
15/*!
16 \brief Class representing a full locale of the form `[language[_territory][.codeset][@modifier]]`.
17 */
18class CLocale
19{
20public:
21 CLocale();
22 explicit CLocale(const std::string& language);
23 CLocale(const std::string& language, const std::string& territory);
24 CLocale(const std::string& language, const std::string& territory, const std::string& codeset);
25 CLocale(const std::string& language, const std::string& territory, const std::string& codeset, const std::string& modifier);
26 ~CLocale();
27
28 /*!
29 \brief Empty (and invalid) CLocale instance.
30 */
31 static const CLocale Empty;
32
33 /*!
34 \brief Parses the given string representation and turns it into a locale.
35
36 \param locale String representation of a locale
37 */
38 static CLocale FromString(const std::string& locale);
39
40 bool operator==(const CLocale& other) const;
41 inline bool operator!=(const CLocale& other) const { return !(*this == other); }
42
43 /*!
44 \brief Whether the locale is valid or not.
45
46 \details A locale is considered valid if at least the language code is set.
47 */
48 bool IsValid() const { return m_valid; }
49
50 /*!
51 \brief Returns the (lower-case) ISO 639-1 language code of the locale.
52 */
53 const std::string& GetLanguageCode() const { return m_language; }
54 /*!
55 \brief Returns the (upper-case) ISO 3166-1 Alpha-2 territory code of the locale.
56 */
57 const std::string& GetTerritoryCode() const { return m_territory; }
58 /*!
59 \brief Returns the codeset of the locale.
60 */
61 const std::string& GetCodeset() const { return m_codeset; }
62 /*!
63 \brief Returns the modifier of the locale.
64 */
65 const std::string& GetModifier() const { return m_modifier; }
66
67 /*!
68 \brief Returns the full string representation of the locale.
69
70 \details The format of the string representation is
71 `[language[_territory][.codeset][@modifier]]` where the language is
72 represented as a (lower-case) two character ISO 639-1 code and the territory
73 is represented as a (upper-case) two character ISO 3166-1 Alpha-2 code.
74 */
75 std::string ToString() const;
76 /*!
77 \brief Returns the full string representation of the locale in lowercase.
78
79 \details The format of the string representation is
80 `language[_territory][.codeset][@modifier]]` where the language is
81 represented as a two character ISO 639-1 code and the territory is
82 represented as a two character ISO 3166-1 Alpha-2 code.
83 */
84 std::string ToStringLC() const;
85 /*!
86 \brief Returns the short string representation of the locale.
87
88 \details The format of the short string representation is
89 `[language[_territory]` where the language is represented as a (lower-case)
90 two character ISO 639-1 code and the territory is represented as a
91 (upper-case) two character ISO 3166-1 Alpha-2 code.
92 */
93 std::string ToShortString() const;
94 /*!
95 \brief Returns the short string representation of the locale in lowercase.
96
97 \details The format of the short string representation is
98 `[language[_territory]` where the language is represented as a two character
99 ISO 639-1 code and the territory is represented as a two character
100 ISO 3166-1 Alpha-2 code.
101 */
102 std::string ToShortStringLC() const;
103
104 /*!
105 \brief Checks if the given string representation of a locale exactly matches
106 the locale.
107
108 \param locale String representation of a locale
109 \return True if the string representation matches the locale, false otherwise.
110 */
111 bool Equals(const std::string& locale) const;
112
113 /*!
114 \brief Checks if the given string representation of a locale partly matches
115 the locale.
116
117 \details Partial matching means that every available locale part needs to
118 match the same locale part of the other locale if present.
119
120 \param locale String representation of a locale
121 \return True if the string representation matches the locale, false otherwise.
122 */
123 bool Matches(const std::string& locale) const;
124
125 /*!
126 \brief Tries to find the locale in the given list that matches this locale
127 best.
128
129 \param locales List of string representations of locales
130 \return Best matching locale from the given list or empty string.
131 */
132 std::string FindBestMatch(const std::set<std::string>& locales) const;
133
134 /*!
135 \brief Tries to find the locale in the given list that matches this locale
136 best.
137
138 \param locales Map list of string representations of locales with first as
139 locale identifier
140 \return Best matching locale from the given list or empty string.
141
142 \remark Used from \ref CAddonInfo::GetTranslatedText to prevent copy from map
143 to set.
144 */
145 std::string FindBestMatch(const std::unordered_map<std::string, std::string>& locales) const;
146
147private:
148 static bool CheckValidity(const std::string& language, const std::string& territory, const std::string& codeset, const std::string& modifier);
149 static bool ParseLocale(const std::string &locale, std::string &language, std::string &territory, std::string &codeset, std::string &modifier);
150
151 void Initialize();
152
153 int GetMatchRank(const std::string& locale) const;
154
155 bool m_valid = false;
156 std::string m_language;
157 std::string m_territory;
158 std::string m_codeset;
159 std::string m_modifier;
160};
161
diff --git a/xbmc/utils/MathUtils.h b/xbmc/utils/MathUtils.h
new file mode 100644
index 0000000..7a69db7
--- /dev/null
+++ b/xbmc/utils/MathUtils.h
@@ -0,0 +1,215 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12#include <assert.h>
13#include <climits>
14#include <cmath>
15
16#if defined(HAVE_SSE2) && defined(__SSE2__)
17#include <emmintrin.h>
18#endif
19
20// use real compiler defines in here as we want to
21// avoid including system.h or other magic includes.
22// use 'gcc -dM -E - < /dev/null' or similar to find them.
23
24#if defined(__ppc__) || \
25 defined(__powerpc__) || \
26 defined(__mips__) || \
27 defined(__arm__) || \
28 defined(__aarch64__) || \
29 defined(__SH4__) || \
30 defined(__sparc__) || \
31 defined(__arc__) || \
32 defined(_M_ARM) || \
33 defined(__or1k__) || \
34 defined(__xtensa__)
35 #define DISABLE_MATHUTILS_ASM_ROUND_INT
36#endif
37
38/*! \brief Math utility class.
39 Note that the test() routine should return true for all implementations
40
41 See http://ldesoras.free.fr/doc/articles/rounding_en.pdf for an explanation
42 of the technique used on x86.
43 */
44namespace MathUtils
45{
46 // GCC does something stupid with optimization on release builds if we try
47 // to assert in these functions
48
49 /*! \brief Round to nearest integer.
50 This routine does fast rounding to the nearest integer.
51 In the case (k + 0.5 for any integer k) we round up to k+1, and in all other
52 instances we should return the nearest integer.
53 Thus, { -1.5, -0.5, 0.5, 1.5 } is rounded to { -1, 0, 1, 2 }.
54 It preserves the property that round(k) - round(k-1) = 1 for all doubles k.
55
56 Make sure MathUtils::test() returns true for each implementation.
57 \sa truncate_int, test
58 */
59 inline int round_int(double x)
60 {
61 assert(x > static_cast<double>((int) (INT_MIN / 2)) - 1.0);
62 assert(x < static_cast<double>((int) (INT_MAX / 2)) + 1.0);
63
64#if defined(DISABLE_MATHUTILS_ASM_ROUND_INT)
65 /* This implementation warrants some further explanation.
66 *
67 * First, a couple of notes on rounding:
68 * 1) C casts from float/double to integer round towards zero.
69 * 2) Float/double additions are rounded according to the normal rules,
70 * in other words: on some architectures, it's fixed at compile-time,
71 * and on others it can be set using fesetround()). The following
72 * analysis assumes round-to-nearest with ties rounding to even. This
73 * is a fairly sensible choice, and is the default with ARM VFP.
74 *
75 * What this function wants is round-to-nearest with ties rounding to
76 * +infinity. This isn't an IEEE rounding mode, even if we could guarantee
77 * that all architectures supported fesetround(), which they don't. Instead,
78 * this adds an offset of 2147483648.5 (= 0x80000000.8p0), then casts to
79 * an unsigned int (crucially, all possible inputs are now in a range where
80 * round to zero acts the same as round to -infinity) and then subtracts
81 * 0x80000000 in the integer domain. The 0.5 component of the offset
82 * converts what is effectively a round down into a round to nearest, with
83 * ties rounding up, as desired.
84 *
85 * There is a catch, that because there is a double rounding, there is a
86 * small region where the input falls just *below* a tie, where the addition
87 * of the offset causes a round *up* to an exact integer, due to the finite
88 * level of precision available in floating point. You need to be aware of
89 * this when calling this function, although at present it is not believed
90 * that XBMC ever attempts to round numbers in this window.
91 *
92 * It is worth proving the size of the affected window. Recall that double
93 * precision employs a mantissa of 52 bits.
94 * 1) For all inputs -0.5 <= x <= INT_MAX
95 * Once the offset is applied, the most significant binary digit in the
96 * floating-point representation is +2^31.
97 * At this magnitude, the smallest step representable in double precision
98 * is 2^31 / 2^52 = 0.000000476837158203125
99 * So the size of the range which is rounded up due to the addition is
100 * half the size of this step, or 0.0000002384185791015625
101 *
102 * 2) For all inputs INT_MIN/2 < x < -0.5
103 * Once the offset is applied, the most significant binary digit in the
104 * floating-point representation is +2^30.
105 * At this magnitude, the smallest step representable in double precision
106 * is 2^30 / 2^52 = 0.0000002384185791015625
107 * So the size of the range which is rounded up due to the addition is
108 * half the size of this step, or 0.00000011920928955078125
109 *
110 * 3) For all inputs INT_MIN <= x <= INT_MIN/2
111 * The representation once the offset is applied has equal or greater
112 * precision than the input, so the addition does not cause rounding.
113 */
114 return ((unsigned int) (x + 2147483648.5)) - 0x80000000;
115
116#else
117 const float round_to_nearest = 0.5f;
118 int i;
119#if defined(HAVE_SSE2) && defined(__SSE2__)
120 const float round_dn_to_nearest = 0.4999999f;
121 i = (x > 0) ? _mm_cvttsd_si32(_mm_set_sd(x + round_to_nearest)) : _mm_cvttsd_si32(_mm_set_sd(x - round_dn_to_nearest));
122
123#elif defined(TARGET_WINDOWS)
124 __asm
125 {
126 fld x
127 fadd st, st (0)
128 fadd round_to_nearest
129 fistp i
130 sar i, 1
131 }
132
133#else
134 __asm__ __volatile__ (
135 "fadd %%st\n\t"
136 "fadd %%st(1)\n\t"
137 "fistpl %0\n\t"
138 "sarl $1, %0\n"
139 : "=m"(i) : "u"(round_to_nearest), "t"(x) : "st"
140 );
141
142#endif
143 return i;
144#endif
145 }
146
147 /*! \brief Truncate to nearest integer.
148 This routine does fast truncation to an integer.
149 It should simply drop the fractional portion of the floating point number.
150
151 Make sure MathUtils::test() returns true for each implementation.
152 \sa round_int, test
153 */
154 inline int truncate_int(double x)
155 {
156 assert(x > static_cast<double>(INT_MIN / 2) - 1.0);
157 assert(x < static_cast<double>(INT_MAX / 2) + 1.0);
158 return static_cast<int>(x);
159 }
160
161 inline int64_t abs(int64_t a)
162 {
163 return (a < 0) ? -a : a;
164 }
165
166 inline unsigned bitcount(unsigned v)
167 {
168 unsigned c = 0;
169 for (c = 0; v; c++)
170 v &= v - 1; // clear the least significant bit set
171 return c;
172 }
173
174 inline void hack()
175 {
176 // stupid hack to keep compiler from dropping these
177 // functions as unused
178 MathUtils::round_int(0.0);
179 MathUtils::truncate_int(0.0);
180 MathUtils::abs(0);
181 }
182
183 /**
184 * Compare two floating-point numbers for equality and regard them
185 * as equal if their difference is below a given threshold.
186 *
187 * It is usually not useful to compare float numbers for equality with
188 * the standard operator== since very close numbers might have different
189 * representations.
190 */
191 template<typename FloatT>
192 inline bool FloatEquals(FloatT f1, FloatT f2, FloatT maxDelta)
193 {
194 return (std::abs(f2 - f1) < maxDelta);
195 }
196
197#if 0
198 /*! \brief test routine for round_int and truncate_int
199 Must return true on all platforms.
200 */
201 inline bool test()
202 {
203 for (int i = -8; i < 8; ++i)
204 {
205 double d = 0.25*i;
206 int r = (i < 0) ? (i - 1) / 4 : (i + 2) / 4;
207 int t = i / 4;
208 if (round_int(d) != r || truncate_int(d) != t)
209 return false;
210 }
211 return true;
212 }
213#endif
214} // namespace MathUtils
215
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <cstdint>
12#include <memory>
13
14namespace KODI
15{
16namespace MEMORY
17{
18struct MemoryStatus
19{
20 unsigned int memoryLoad;
21
22 uint64_t totalPhys;
23 uint64_t availPhys;
24};
25
26void* AlignedMalloc(size_t s, size_t alignTo);
27void AlignedFree(void* p);
28void GetMemoryStatus(MemoryStatus* buffer);
29}
30}
diff --git a/xbmc/utils/Mime.cpp b/xbmc/utils/Mime.cpp
new file mode 100644
index 0000000..5aa4c3c
--- /dev/null
+++ b/xbmc/utils/Mime.cpp
@@ -0,0 +1,699 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Mime.h"
10
11#include "FileItem.h"
12#include "URIUtils.h"
13#include "URL.h"
14#include "filesystem/CurlFile.h"
15#include "music/tags/MusicInfoTag.h"
16#include "utils/StringUtils.h"
17#include "video/VideoInfoTag.h"
18
19#include <algorithm>
20
21const std::map<std::string, std::string> CMime::m_mimetypes =
22 {{{"3dm", "x-world/x-3dmf"},
23 {"3dmf", "x-world/x-3dmf"},
24 {"3fr", "image/3fr"},
25 {"a", "application/octet-stream"},
26 {"aab", "application/x-authorware-bin"},
27 {"aam", "application/x-authorware-map"},
28 {"aas", "application/x-authorware-seg"},
29 {"abc", "text/vnd.abc"},
30 {"acgi", "text/html"},
31 {"afl", "video/animaflex"},
32 {"ai", "application/postscript"},
33 {"aif", "audio/aiff"},
34 {"aifc", "audio/x-aiff"},
35 {"aiff", "audio/aiff"},
36 {"aim", "application/x-aim"},
37 {"aip", "text/x-audiosoft-intra"},
38 {"ani", "application/x-navi-animation"},
39 {"aos", "application/x-nokia-9000-communicator-add-on-software"},
40 {"apng", "image/apng"},
41 {"aps", "application/mime"},
42 {"arc", "application/octet-stream"},
43 {"arj", "application/arj"},
44 {"art", "image/x-jg"},
45 {"arw", "image/arw"},
46 {"asf", "video/x-ms-asf"},
47 {"asm", "text/x-asm"},
48 {"asp", "text/asp"},
49 {"asx", "video/x-ms-asf"},
50 {"au", "audio/basic"},
51 {"avi", "video/avi"},
52 {"avs", "video/avs-video"},
53 {"bcpio", "application/x-bcpio"},
54 {"bin", "application/octet-stream"},
55 {"bm", "image/bmp"},
56 {"bmp", "image/bmp"},
57 {"boo", "application/book"},
58 {"book", "application/book"},
59 {"boz", "application/x-bzip2"},
60 {"bsh", "application/x-bsh"},
61 {"bz", "application/x-bzip"},
62 {"bz2", "application/x-bzip2"},
63 {"c", "text/plain"},
64 {"c++", "text/plain"},
65 {"cat", "application/vnd.ms-pki.seccat"},
66 {"cc", "text/plain"},
67 {"ccad", "application/clariscad"},
68 {"cco", "application/x-cocoa"},
69 {"cdf", "application/cdf"},
70 {"cer", "application/pkix-cert"},
71 {"cer", "application/x-x509-ca-cert"},
72 {"cha", "application/x-chat"},
73 {"chat", "application/x-chat"},
74 {"class", "application/java"},
75 {"com", "application/octet-stream"},
76 {"conf", "text/plain"},
77 {"cpio", "application/x-cpio"},
78 {"cpp", "text/x-c"},
79 {"cpt", "application/x-cpt"},
80 {"crl", "application/pkcs-crl"},
81 {"crt", "application/pkix-cert"},
82 {"cr2", "image/cr2"},
83 {"crw", "image/crw"},
84 {"csh", "application/x-csh"},
85 {"css", "text/css"},
86 {"cxx", "text/plain"},
87 {"dcr", "application/x-director"},
88 {"deepv", "application/x-deepv"},
89 {"def", "text/plain"},
90 {"der", "application/x-x509-ca-cert"},
91 {"dif", "video/x-dv"},
92 {"dir", "application/x-director"},
93 {"dl", "video/dl"},
94 {"divx", "video/x-msvideo"},
95 {"dng", "image/dng"},
96 {"doc", "application/msword"},
97 {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
98 {"dot", "application/msword"},
99 {"dp", "application/commonground"},
100 {"drw", "application/drafting"},
101 {"dump", "application/octet-stream"},
102 {"dv", "video/x-dv"},
103 {"dvi", "application/x-dvi"},
104 {"dwf", "model/vnd.dwf"},
105 {"dwg", "image/vnd.dwg"},
106 {"dxf", "image/vnd.dwg"},
107 {"dxr", "application/x-director"},
108 {"el", "text/x-script.elisp"},
109 {"elc", "application/x-elc"},
110 {"env", "application/x-envoy"},
111 {"eps", "application/postscript"},
112 {"erf", "image/erf"},
113 {"es", "application/x-esrehber"},
114 {"etx", "text/x-setext"},
115 {"evy", "application/envoy"},
116 {"exe", "application/octet-stream"},
117 {"f", "text/x-fortran"},
118 {"f77", "text/x-fortran"},
119 {"f90", "text/x-fortran"},
120 {"fdf", "application/vnd.fdf"},
121 {"fif", "image/fif"},
122 {"flac", "audio/flac"},
123 {"fli", "video/fli"},
124 {"flo", "image/florian"},
125 {"flv", "video/x-flv"},
126 {"flx", "text/vnd.fmi.flexstor"},
127 {"fmf", "video/x-atomic3d-feature"},
128 {"for", "text/plain"},
129 {"for", "text/x-fortran"},
130 {"fpx", "image/vnd.fpx"},
131 {"frl", "application/freeloader"},
132 {"funk", "audio/make"},
133 {"g", "text/plain"},
134 {"g3", "image/g3fax"},
135 {"gif", "image/gif"},
136 {"gl", "video/x-gl"},
137 {"gsd", "audio/x-gsm"},
138 {"gsm", "audio/x-gsm"},
139 {"gsp", "application/x-gsp"},
140 {"gss", "application/x-gss"},
141 {"gtar", "application/x-gtar"},
142 {"gz", "application/x-compressed"},
143 {"gzip", "application/x-gzip"},
144 {"h", "text/plain"},
145 {"hdf", "application/x-hdf"},
146 {"heic", "image/heic"},
147 {"heif", "image/heif"},
148 {"help", "application/x-helpfile"},
149 {"hgl", "application/vnd.hp-hpgl"},
150 {"hh", "text/plain"},
151 {"hlb", "text/x-script"},
152 {"hlp", "application/hlp"},
153 {"hpg", "application/vnd.hp-hpgl"},
154 {"hpgl", "application/vnd.hp-hpgl"},
155 {"hqx", "application/binhex"},
156 {"hta", "application/hta"},
157 {"htc", "text/x-component"},
158 {"htm", "text/html"},
159 {"html", "text/html"},
160 {"htmls", "text/html"},
161 {"htt", "text/webviewhtml"},
162 {"htx", "text/html"},
163 {"ice", "x-conference/x-cooltalk"},
164 {"ico", "image/x-icon"},
165 {"idc", "text/plain"},
166 {"ief", "image/ief"},
167 {"iefs", "image/ief"},
168 {"iges", "application/iges"},
169 {"igs", "application/iges"},
170 {"ima", "application/x-ima"},
171 {"imap", "application/x-httpd-imap"},
172 {"inf", "application/inf"},
173 {"ins", "application/x-internet-signup"},
174 {"ip", "application/x-ip2"},
175 {"isu", "video/x-isvideo"},
176 {"it", "audio/it"},
177 {"iv", "application/x-inventor"},
178 {"ivr", "i-world/i-vrml"},
179 {"ivy", "application/x-livescreen"},
180 {"jam", "audio/x-jam"},
181 {"jav", "text/x-java-source"},
182 {"java", "text/x-java-source"},
183 {"jcm", "application/x-java-commerce"},
184 {"jfif", "image/jpeg"},
185 {"jp2", "image/jp2"},
186 {"jfif-tbnl", "image/jpeg"},
187 {"jpe", "image/jpeg"},
188 {"jpeg", "image/jpeg"},
189 {"jpg", "image/jpeg"},
190 {"jps", "image/x-jps"},
191 {"js", "application/javascript"},
192 {"json", "application/json"},
193 {"jut", "image/jutvision"},
194 {"kar", "music/x-karaoke"},
195 {"kdc", "image/kdc"},
196 {"ksh", "text/x-script.ksh"},
197 {"la", "audio/nspaudio"},
198 {"lam", "audio/x-liveaudio"},
199 {"latex", "application/x-latex"},
200 {"lha", "application/lha"},
201 {"lhx", "application/octet-stream"},
202 {"list", "text/plain"},
203 {"lma", "audio/nspaudio"},
204 {"log", "text/plain"},
205 {"lsp", "application/x-lisp"},
206 {"lst", "text/plain"},
207 {"lsx", "text/x-la-asf"},
208 {"ltx", "application/x-latex"},
209 {"lzh", "application/x-lzh"},
210 {"lzx", "application/lzx"},
211 {"m", "text/x-m"},
212 {"m1v", "video/mpeg"},
213 {"m2a", "audio/mpeg"},
214 {"m2v", "video/mpeg"},
215 {"m3u", "audio/x-mpegurl"},
216 {"man", "application/x-troff-man"},
217 {"map", "application/x-navimap"},
218 {"mar", "text/plain"},
219 {"mbd", "application/mbedlet"},
220 {"mc$", "application/x-magic-cap-package-1.0"},
221 {"mcd", "application/x-mathcad"},
222 {"mcf", "text/mcf"},
223 {"mcp", "application/netmc"},
224 {"mdc", "image/mdc"},
225 {"me", "application/x-troff-me"},
226 {"mef", "image/mef"},
227 {"mht", "message/rfc822"},
228 {"mhtml", "message/rfc822"},
229 {"mid", "audio/midi"},
230 {"midi", "audio/midi"},
231 {"mif", "application/x-mif"},
232 {"mime", "message/rfc822"},
233 {"mjf", "audio/x-vnd.audioexplosion.mjuicemediafile"},
234 {"mjpg", "video/x-motion-jpeg"},
235 {"mka", "audio/x-matroska"},
236 {"mkv", "video/x-matroska"},
237 {"mk3d", "video/x-matroska-3d"},
238 {"mm", "application/x-meme"},
239 {"mme", "application/base64"},
240 {"mod", "audio/mod"},
241 {"moov", "video/quicktime"},
242 {"mov", "video/quicktime"},
243 {"movie", "video/x-sgi-movie"},
244 {"mos", "image/mos"},
245 {"mp2", "audio/mpeg"},
246 {"mp3", "audio/mpeg3"},
247 {"mp4", "video/mp4"},
248 {"mpa", "audio/mpeg"},
249 {"mpc", "application/x-project"},
250 {"mpe", "video/mpeg"},
251 {"mpeg", "video/mpeg"},
252 {"mpg", "video/mpeg"},
253 {"mpga", "audio/mpeg"},
254 {"mpp", "application/vnd.ms-project"},
255 {"mpt", "application/x-project"},
256 {"mpv", "application/x-project"},
257 {"mpx", "application/x-project"},
258 {"mrc", "application/marc"},
259 {"mrw", "image/mrw"},
260 {"ms", "application/x-troff-ms"},
261 {"mv", "video/x-sgi-movie"},
262 {"my", "audio/make"},
263 {"mzz", "application/x-vnd.audioexplosion.mzz"},
264 {"nap", "image/naplps"},
265 {"naplps", "image/naplps"},
266 {"nc", "application/x-netcdf"},
267 {"ncm", "application/vnd.nokia.configuration-message"},
268 {"nef", "image/nef"},
269 {"nfo", "text/xml"},
270 {"nif", "image/x-niff"},
271 {"niff", "image/x-niff"},
272 {"nix", "application/x-mix-transfer"},
273 {"nrw", "image/nrw"},
274 {"nsc", "application/x-conference"},
275 {"nvd", "application/x-navidoc"},
276 {"o", "application/octet-stream"},
277 {"oda", "application/oda"},
278 {"ogg", "audio/ogg"},
279 {"omc", "application/x-omc"},
280 {"omcd", "application/x-omcdatamaker"},
281 {"omcr", "application/x-omcregerator"},
282 {"orf", "image/orf"},
283 {"p", "text/x-pascal"},
284 {"p10", "application/pkcs10"},
285 {"p12", "application/pkcs-12"},
286 {"p7a", "application/x-pkcs7-signature"},
287 {"p7c", "application/pkcs7-mime"},
288 {"p7m", "application/pkcs7-mime"},
289 {"p7r", "application/x-pkcs7-certreqresp"},
290 {"p7s", "application/pkcs7-signature"},
291 {"part", "application/pro_eng"},
292 {"pas", "text/pascal"},
293 {"pbm", "image/x-portable-bitmap"},
294 {"pcl", "application/vnd.hp-pcl"},
295 {"pct", "image/x-pict"},
296 {"pcx", "image/x-pcx"},
297 {"pdb", "chemical/x-pdb"},
298 {"pdf", "application/pdf"},
299 {"pef", "image/pef"},
300 {"pfunk", "audio/make.my.funk"},
301 {"pgm", "image/x-portable-greymap"},
302 {"pic", "image/pict"},
303 {"pict", "image/pict"},
304 {"pkg", "application/x-newton-compatible-pkg"},
305 {"pko", "application/vnd.ms-pki.pko"},
306 {"pl", "text/x-script.perl"},
307 {"plx", "application/x-pixclscript"},
308 {"pm", "text/x-script.perl-module"},
309 {"pm4", "application/x-pagemaker"},
310 {"pm5", "application/x-pagemaker"},
311 {"png", "image/png"},
312 {"pnm", "application/x-portable-anymap"},
313 {"pot", "application/vnd.ms-powerpoint"},
314 {"pov", "model/x-pov"},
315 {"ppa", "application/vnd.ms-powerpoint"},
316 {"ppm", "image/x-portable-pixmap"},
317 {"pps", "application/mspowerpoint"},
318 {"ppt", "application/mspowerpoint"},
319 {"ppz", "application/mspowerpoint"},
320 {"pre", "application/x-freelance"},
321 {"prt", "application/pro_eng"},
322 {"ps", "application/postscript"},
323 {"psd", "application/octet-stream"},
324 {"pvu", "paleovu/x-pv"},
325 {"pwz", "application/vnd.ms-powerpoint"},
326 {"py", "text/x-script.phyton"},
327 {"pyc", "application/x-bytecode.python"},
328 {"qcp", "audio/vnd.qcelp"},
329 {"qd3", "x-world/x-3dmf"},
330 {"qd3d", "x-world/x-3dmf"},
331 {"qif", "image/x-quicktime"},
332 {"qt", "video/quicktime"},
333 {"qtc", "video/x-qtc"},
334 {"qti", "image/x-quicktime"},
335 {"qtif", "image/x-quicktime"},
336 {"ra", "audio/x-realaudio"},
337 {"raf", "image/raf"},
338 {"ram", "audio/x-pn-realaudio"},
339 {"ras", "image/cmu-raster"},
340 {"rast", "image/cmu-raster"},
341 {"raw", "image/raw"},
342 {"rexx", "text/x-script.rexx"},
343 {"rf", "image/vnd.rn-realflash"},
344 {"rgb", "image/x-rgb"},
345 {"rm", "audio/x-pn-realaudio"},
346 {"rmi", "audio/mid"},
347 {"rmm", "audio/x-pn-realaudio"},
348 {"rmp", "audio/x-pn-realaudio"},
349 {"rng", "application/ringing-tones"},
350 {"rnx", "application/vnd.rn-realplayer"},
351 {"roff", "application/x-troff"},
352 {"rp", "image/vnd.rn-realpix"},
353 {"rpm", "audio/x-pn-realaudio-plugin"},
354 {"rt", "text/richtext"},
355 {"rtf", "text/richtext"},
356 {"rtx", "text/richtext"},
357 {"rv", "video/vnd.rn-realvideo"},
358 {"rw2", "image/rw2"},
359 {"s", "text/x-asm"},
360 {"s3m", "audio/s3m"},
361 {"saveme", "application/octet-stream"},
362 {"sbk", "application/x-tbook"},
363 {"scm", "video/x-scm"},
364 {"sdml", "text/plain"},
365 {"sdp", "application/sdp"},
366 {"sdr", "application/sounder"},
367 {"sea", "application/sea"},
368 {"set", "application/set"},
369 {"sgm", "text/sgml"},
370 {"sgml", "text/sgml"},
371 {"sh", "text/x-script.sh"},
372 {"shar", "application/x-bsh"},
373 {"shtml", "text/x-server-parsed-html"},
374 {"sid", "audio/x-psid"},
375 {"sit", "application/x-stuffit"},
376 {"skd", "application/x-koan"},
377 {"skm", "application/x-koan"},
378 {"skp", "application/x-koan"},
379 {"skt", "application/x-koan"},
380 {"sl", "application/x-seelogo"},
381 {"smi", "application/smil"},
382 {"smil", "application/smil"},
383 {"snd", "audio/basic"},
384 {"sol", "application/solids"},
385 {"spc", "text/x-speech"},
386 {"spl", "application/futuresplash"},
387 {"spr", "application/x-sprite"},
388 {"sprite", "application/x-sprite"},
389 {"src", "application/x-wais-source"},
390 {"srw", "image/srw"},
391 {"ssi", "text/x-server-parsed-html"},
392 {"ssm", "application/streamingmedia"},
393 {"sst", "application/vnd.ms-pki.certstore"},
394 {"step", "application/step"},
395 {"stl", "application/sla"},
396 {"stp", "application/step"},
397 {"sup", "application/x-pgs"},
398 {"sv4cpio", "application/x-sv4cpio"},
399 {"sv4crc", "application/x-sv4crc"},
400 {"svf", "image/vnd.dwg"},
401 {"svg", "image/svg+xml"},
402 {"svr", "application/x-world"},
403 {"swf", "application/x-shockwave-flash"},
404 {"t", "application/x-troff"},
405 {"talk", "text/x-speech"},
406 {"tar", "application/x-tar"},
407 {"tbk", "application/toolbook"},
408 {"tcl", "text/x-script.tcl"},
409 {"tcsh", "text/x-script.tcsh"},
410 {"tex", "application/x-tex"},
411 {"texi", "application/x-texinfo"},
412 {"texinfo", "application/x-texinfo"},
413 {"text", "text/plain"},
414 {"tgz", "application/x-compressed"},
415 {"tif", "image/tiff"},
416 {"tiff", "image/tiff"},
417 {"tr", "application/x-troff"},
418 {"ts", "video/mp2t"},
419 {"tsi", "audio/tsp-audio"},
420 {"tsp", "audio/tsplayer"},
421 {"tsv", "text/tab-separated-values"},
422 {"turbot", "image/florian"},
423 {"txt", "text/plain"},
424 {"uil", "text/x-uil"},
425 {"uni", "text/uri-list"},
426 {"unis", "text/uri-list"},
427 {"unv", "application/i-deas"},
428 {"uri", "text/uri-list"},
429 {"uris", "text/uri-list"},
430 {"ustar", "application/x-ustar"},
431 {"uu", "text/x-uuencode"},
432 {"uue", "text/x-uuencode"},
433 {"vcd", "application/x-cdlink"},
434 {"vcs", "text/x-vcalendar"},
435 {"vda", "application/vda"},
436 {"vdo", "video/vdo"},
437 {"vew", "application/groupwise"},
438 {"viv", "video/vivo"},
439 {"vivo", "video/vivo"},
440 {"vmd", "application/vocaltec-media-desc"},
441 {"vmf", "application/vocaltec-media-file"},
442 {"voc", "audio/voc"},
443 {"vos", "video/vosaic"},
444 {"vox", "audio/voxware"},
445 {"vqe", "audio/x-twinvq-plugin"},
446 {"vqf", "audio/x-twinvq"},
447 {"vql", "audio/x-twinvq-plugin"},
448 {"vrml", "application/x-vrml"},
449 {"vrt", "x-world/x-vrt"},
450 {"vsd", "application/x-visio"},
451 {"vst", "application/x-visio"},
452 {"vsw", "application/x-visio"},
453 {"w60", "application/wordperfect6.0"},
454 {"w61", "application/wordperfect6.1"},
455 {"w6w", "application/msword"},
456 {"wav", "audio/wav"},
457 {"wb1", "application/x-qpro"},
458 {"wbmp", "image/vnd.wap.wbmp"},
459 {"web", "application/vnd.xara"},
460 {"webp", "image/webp"},
461 {"wiz", "application/msword"},
462 {"wk1", "application/x-123"},
463 {"wma", "audio/x-ms-wma"},
464 {"wmf", "windows/metafile"},
465 {"wml", "text/vnd.wap.wml"},
466 {"wmlc", "application/vnd.wap.wmlc"},
467 {"wmls", "text/vnd.wap.wmlscript"},
468 {"wmlsc", "application/vnd.wap.wmlscriptc"},
469 {"wmv", "video/x-ms-wmv"},
470 {"word", "application/msword"},
471 {"wp", "application/wordperfect"},
472 {"wp5", "application/wordperfect"},
473 {"wp6", "application/wordperfect"},
474 {"wpd", "application/wordperfect"},
475 {"wq1", "application/x-lotus"},
476 {"wri", "application/mswrite"},
477 {"wrl", "model/vrml"},
478 {"wrz", "model/vrml"},
479 {"wsc", "text/scriplet"},
480 {"wsrc", "application/x-wais-source"},
481 {"wtk", "application/x-wintalk"},
482 {"x3f", "image/x3f"},
483 {"xbm", "image/xbm"},
484 {"xdr", "video/x-amt-demorun"},
485 {"xgz", "xgl/drawing"},
486 {"xif", "image/vnd.xiff"},
487 {"xl", "application/excel"},
488 {"xla", "application/excel"},
489 {"xlb", "application/excel"},
490 {"xlc", "application/excel"},
491 {"xld", "application/excel"},
492 {"xlk", "application/excel"},
493 {"xll", "application/excel"},
494 {"xlm", "application/excel"},
495 {"xls", "application/excel"},
496 {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
497 {"xlt", "application/excel"},
498 {"xlv", "application/excel"},
499 {"xlw", "application/excel"},
500 {"xm", "audio/xm"},
501 {"xml", "text/xml"},
502 {"xmz", "xgl/movie"},
503 {"xpix", "application/x-vnd.ls-xpix"},
504 {"xpm", "image/xpm"},
505 {"x-png", "image/png"},
506 {"xspf", "application/xspf+xml"},
507 {"xsr", "video/x-amt-showrun"},
508 {"xvid", "video/x-msvideo"},
509 {"xwd", "image/x-xwd"},
510 {"xyz", "chemical/x-pdb"},
511 {"z", "application/x-compressed"},
512 {"zip", "application/zip"},
513 {"zoo", "application/octet-stream"},
514 {"zsh", "text/x-script.zsh"}}};
515
516std::string CMime::GetMimeType(const std::string &extension)
517{
518 if (extension.empty())
519 return "";
520
521 std::string ext = extension;
522 size_t posNotPoint = ext.find_first_not_of('.');
523 if (posNotPoint != std::string::npos && posNotPoint > 0)
524 ext = extension.substr(posNotPoint);
525 transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
526
527 std::map<std::string, std::string>::const_iterator it = m_mimetypes.find(ext);
528 if (it != m_mimetypes.end())
529 return it->second;
530
531 return "";
532}
533
534std::string CMime::GetMimeType(const CFileItem &item)
535{
536 std::string path = item.GetDynPath();
537 if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->GetPath().empty())
538 path = item.GetVideoInfoTag()->GetPath();
539 else if (item.HasMusicInfoTag() && !item.GetMusicInfoTag()->GetURL().empty())
540 path = item.GetMusicInfoTag()->GetURL();
541
542 return GetMimeType(URIUtils::GetExtension(path));
543}
544
545std::string CMime::GetMimeType(const CURL &url, bool lookup)
546{
547
548 std::string strMimeType;
549
550 if( url.IsProtocol("shout") || url.IsProtocol("http") || url.IsProtocol("https"))
551 {
552 // If lookup is false, bail out early to leave mime type empty
553 if (!lookup)
554 return strMimeType;
555
556 std::string strmime;
557 XFILE::CCurlFile::GetMimeType(url, strmime);
558
559 // try to get mime-type again but with an NSPlayer User-Agent
560 // in order for server to provide correct mime-type. Allows us
561 // to properly detect an MMS stream
562 if (StringUtils::StartsWithNoCase(strmime, "video/x-ms-"))
563 XFILE::CCurlFile::GetMimeType(url, strmime, "NSPlayer/11.00.6001.7000");
564
565 // make sure there are no options set in mime-type
566 // mime-type can look like "video/x-ms-asf ; charset=utf8"
567 size_t i = strmime.find(';');
568 if(i != std::string::npos)
569 strmime.erase(i, strmime.length() - i);
570 StringUtils::Trim(strmime);
571 strMimeType = strmime;
572 }
573 else
574 strMimeType = GetMimeType(url.GetFileType());
575
576 // if it's still empty set to an unknown type
577 if (strMimeType.empty())
578 strMimeType = "application/octet-stream";
579
580 return strMimeType;
581}
582
583CMime::EFileType CMime::GetFileTypeFromMime(const std::string& mimeType)
584{
585 // based on http://mimesniff.spec.whatwg.org/
586
587 std::string type, subtype;
588 if (!parseMimeType(mimeType, type, subtype))
589 return FileTypeUnknown;
590
591 if (type == "application")
592 {
593 if (subtype == "zip")
594 return FileTypeZip;
595 if (subtype == "x-gzip")
596 return FileTypeGZip;
597 if (subtype == "x-rar-compressed")
598 return FileTypeRar;
599
600 if (subtype == "xml")
601 return FileTypeXml;
602 }
603 else if (type == "text")
604 {
605 if (subtype == "xml")
606 return FileTypeXml;
607 if (subtype == "html")
608 return FileTypeHtml;
609 if (subtype == "plain")
610 return FileTypePlainText;
611 }
612 else if (type == "image")
613 {
614 if (subtype == "bmp")
615 return FileTypeBmp;
616 if (subtype == "gif")
617 return FileTypeGif;
618 if (subtype == "png")
619 return FileTypePng;
620 if (subtype == "jpeg" || subtype == "pjpeg")
621 return FileTypeJpeg;
622 }
623
624 if (StringUtils::EndsWith(subtype, "+zip"))
625 return FileTypeZip;
626 if (StringUtils::EndsWith(subtype, "+xml"))
627 return FileTypeXml;
628
629 return FileTypeUnknown;
630}
631
632CMime::EFileType CMime::GetFileTypeFromContent(const std::string& fileContent)
633{
634 // based on http://mimesniff.spec.whatwg.org/#matching-a-mime-type-pattern
635
636 const size_t len = fileContent.length();
637 if (len < 2)
638 return FileTypeUnknown;
639
640 const unsigned char* const b = (const unsigned char*)fileContent.c_str();
641
642 //! @todo add detection for text types
643
644 // check image types
645 if (b[0] == 'B' && b[1] == 'M')
646 return FileTypeBmp;
647 if (len >= 6 && b[0] == 'G' && b[1] == 'I' && b[2] == 'F' && b[3] == '8' && (b[4] == '7' || b[4] == '9') && b[5] == 'a')
648 return FileTypeGif;
649 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)
650 return FileTypePng;
651 if (len >= 3 && b[0] == 0xFF && b[1] == 0xD8 && b[2] == 0xFF)
652 return FileTypeJpeg;
653
654 // check archive types
655 if (len >= 3 && b[0] == 0x1F && b[1] == 0x8B && b[2] == 0x08)
656 return FileTypeGZip;
657 if (len >= 4 && b[0] == 'P' && b[1] == 'K' && b[2] == 0x03 && b[3] == 0x04)
658 return FileTypeZip;
659 if (len >= 7 && b[0] == 'R' && b[1] == 'a' && b[2] == 'r' && b[3] == ' ' && b[4] == 0x1A && b[5] == 0x07 && b[6] == 0x00)
660 return FileTypeRar;
661
662 //! @todo add detection for other types if required
663
664 return FileTypeUnknown;
665}
666
667bool CMime::parseMimeType(const std::string& mimeType, std::string& type, std::string& subtype)
668{
669 static const char* const whitespaceChars = "\x09\x0A\x0C\x0D\x20"; // tab, LF, FF, CR and space
670
671 type.clear();
672 subtype.clear();
673
674 const size_t slashPos = mimeType.find('/');
675 if (slashPos == std::string::npos)
676 return false;
677
678 type.assign(mimeType, 0, slashPos);
679 subtype.assign(mimeType, slashPos + 1, std::string::npos);
680
681 const size_t semicolonPos = subtype.find(';');
682 if (semicolonPos != std::string::npos)
683 subtype.erase(semicolonPos);
684
685 StringUtils::Trim(type, whitespaceChars);
686 StringUtils::Trim(subtype, whitespaceChars);
687
688 if (type.empty() || subtype.empty())
689 {
690 type.clear();
691 subtype.clear();
692 return false;
693 }
694
695 StringUtils::ToLower(type);
696 StringUtils::ToLower(subtype);
697
698 return true;
699}
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 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <map>
12#include <string>
13
14class CURL;
15
16class CFileItem;
17
18class CMime
19{
20public:
21 static std::string GetMimeType(const std::string &extension);
22 static std::string GetMimeType(const CFileItem &item);
23 static std::string GetMimeType(const CURL &url, bool lookup = true);
24
25 enum EFileType
26 {
27 FileTypeUnknown = 0,
28 FileTypeHtml,
29 FileTypeXml,
30 FileTypePlainText,
31 FileTypeZip,
32 FileTypeGZip,
33 FileTypeRar,
34 FileTypeBmp,
35 FileTypeGif,
36 FileTypePng,
37 FileTypeJpeg,
38 };
39 static EFileType GetFileTypeFromMime(const std::string& mimeType);
40 static EFileType GetFileTypeFromContent(const std::string& fileContent);
41
42private:
43 static bool parseMimeType(const std::string& mimeType, std::string& type, std::string& subtype);
44
45 static const std::map<std::string, std::string> m_mimetypes;
46};
diff --git a/xbmc/utils/Observer.cpp b/xbmc/utils/Observer.cpp
new file mode 100644
index 0000000..729f61d
--- /dev/null
+++ b/xbmc/utils/Observer.cpp
@@ -0,0 +1,72 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9
10#include "Observer.h"
11
12#include "threads/SingleLock.h"
13
14#include <algorithm>
15
16Observable &Observable::operator=(const Observable &observable)
17{
18 CSingleLock lock(m_obsCritSection);
19
20 m_bObservableChanged = static_cast<bool>(observable.m_bObservableChanged);
21 m_observers = observable.m_observers;
22
23 return *this;
24}
25
26bool Observable::IsObserving(const Observer &obs) const
27{
28 CSingleLock lock(m_obsCritSection);
29 return std::find(m_observers.begin(), m_observers.end(), &obs) != m_observers.end();
30}
31
32void Observable::RegisterObserver(Observer *obs)
33{
34 CSingleLock lock(m_obsCritSection);
35 if (!IsObserving(*obs))
36 {
37 m_observers.push_back(obs);
38 }
39}
40
41void Observable::UnregisterObserver(Observer *obs)
42{
43 CSingleLock lock(m_obsCritSection);
44 auto iter = std::remove(m_observers.begin(), m_observers.end(), obs);
45 if (iter != m_observers.end())
46 m_observers.erase(iter);
47}
48
49void Observable::NotifyObservers(const ObservableMessage message /* = ObservableMessageNone */)
50{
51 // Make sure the set/compare is atomic
52 // so we don't clobber the variable in a race condition
53 auto bNotify = m_bObservableChanged.exchange(false);
54
55 if (bNotify)
56 SendMessage(message);
57}
58
59void Observable::SetChanged(bool SetTo)
60{
61 m_bObservableChanged = SetTo;
62}
63
64void Observable::SendMessage(const ObservableMessage message)
65{
66 CSingleLock lock(m_obsCritSection);
67
68 for (auto& observer : m_observers)
69 {
70 observer->Notify(*this, message);
71 }
72}
diff --git a/xbmc/utils/Observer.h b/xbmc/utils/Observer.h
new file mode 100644
index 0000000..feb201a
--- /dev/null
+++ b/xbmc/utils/Observer.h
@@ -0,0 +1,91 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "threads/CriticalSection.h"
12
13#include <atomic>
14#include <vector>
15
16class Observable;
17class ObservableMessageJob;
18
19typedef enum
20{
21 ObservableMessageNone,
22 ObservableMessagePeripheralsChanged,
23 ObservableMessageSettingsChanged,
24 ObservableMessageButtonMapsChanged,
25} ObservableMessage;
26
27class Observer
28{
29public:
30 Observer() = default;
31 virtual ~Observer() = default;
32 /*!
33 * @brief Process a message from an observable.
34 * @param obs The observable that sends the message.
35 * @param msg The message.
36 */
37 virtual void Notify(const Observable &obs, const ObservableMessage msg) = 0;
38};
39
40class Observable
41{
42 friend class ObservableMessageJob;
43
44public:
45 Observable() = default;
46 virtual ~Observable() = default;
47 virtual Observable &operator=(const Observable &observable);
48
49 /*!
50 * @brief Register an observer.
51 * @param obs The observer to register.
52 */
53 virtual void RegisterObserver(Observer *obs);
54
55 /*!
56 * @brief Unregister an observer.
57 * @param obs The observer to unregister.
58 */
59 virtual void UnregisterObserver(Observer *obs);
60
61 /*!
62 * @brief Send a message to all observers when m_bObservableChanged is true.
63 * @param message The message to send.
64 */
65 virtual void NotifyObservers(const ObservableMessage message = ObservableMessageNone);
66
67 /*!
68 * @brief Mark an observable changed.
69 * @param bSetTo True to mark the observable changed, false to mark it as unchanged.
70 */
71 virtual void SetChanged(bool bSetTo = true);
72
73 /*!
74 * @brief Check whether this observable is being observed by an observer.
75 * @param obs The observer to check.
76 * @return True if this observable is being observed by the given observer, false otherwise.
77 */
78 virtual bool IsObserving(const Observer &obs) const;
79
80protected:
81 /*!
82 * @brief Send a message to all observer when m_bObservableChanged is true.
83 * @param obs The observer that sends the message.
84 * @param message The message to send.
85 */
86 void SendMessage(const ObservableMessage message);
87
88 std::atomic<bool> m_bObservableChanged{false}; /*!< true when the observable is marked as changed, false otherwise */
89 std::vector<Observer *> m_observers; /*!< all observers */
90 mutable CCriticalSection m_obsCritSection; /*!< mutex */
91};
diff --git a/xbmc/utils/POUtils.cpp b/xbmc/utils/POUtils.cpp
new file mode 100644
index 0000000..7d8afd3
--- /dev/null
+++ b/xbmc/utils/POUtils.cpp
@@ -0,0 +1,305 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/POUtils.h"
10
11#include "URL.h"
12#include "filesystem/File.h"
13#include "utils/log.h"
14
15#include <stdlib.h>
16
17CPODocument::CPODocument()
18{
19 m_CursorPos = 0;
20 m_nextEntryPos = 0;
21 m_POfilelength = 0;
22 m_Entry.msgStrPlural.clear();
23 m_Entry.msgStrPlural.resize(1);
24}
25
26CPODocument::~CPODocument() = default;
27
28bool CPODocument::LoadFile(const std::string &pofilename)
29{
30 CURL poFileUrl(pofilename);
31 if (!XFILE::CFile::Exists(poFileUrl))
32 return false;
33
34 XFILE::CFile file;
35 XFILE::auto_buffer buf;
36 if (file.LoadFile(poFileUrl, buf) < 18) // at least a size of a minimalistic header
37 {
38 CLog::Log(LOGERROR, "%s: can't load file \"%s\" or file is too small", __FUNCTION__, pofilename.c_str());
39 return false;
40 }
41
42 m_strBuffer = '\n';
43 m_strBuffer.append(buf.get(), buf.size());
44 buf.clear();
45
46 ConvertLineEnds(pofilename);
47
48 // we make sure, to have an LF at the end of buffer
49 if (*m_strBuffer.rbegin() != '\n')
50 {
51 m_strBuffer += "\n";
52 }
53
54 m_POfilelength = m_strBuffer.size();
55
56 if (GetNextEntry() && m_Entry.Type == MSGID_FOUND)
57 return true;
58
59 CLog::Log(LOGERROR, "POParser: unable to read PO file header from file: %s", pofilename.c_str());
60 return false;
61}
62
63bool CPODocument::GetNextEntry()
64{
65 do
66 {
67 // if we don't find LFLF, we reached the end of the buffer and the last entry to check
68 // we indicate this with setting m_nextEntryPos to the end of the buffer
69 if ((m_nextEntryPos = m_strBuffer.find("\n\n", m_CursorPos)) == std::string::npos)
70 m_nextEntryPos = m_POfilelength-1;
71
72 // now we read the actual entry into a temp string for further processing
73 m_Entry.Content.assign(m_strBuffer, m_CursorPos, m_nextEntryPos - m_CursorPos +1);
74 m_CursorPos = m_nextEntryPos+1; // jump cursor to the second LF character
75
76 if (FindLineStart ("\nmsgid ", m_Entry.msgID.Pos))
77 {
78 if (FindLineStart ("\nmsgctxt \"#", m_Entry.xIDPos) && ParseNumID())
79 {
80 m_Entry.Type = ID_FOUND; // we found an entry with a valid numeric id
81 return true;
82 }
83
84 size_t plurPos;
85 if (FindLineStart ("\nmsgid_plural ", plurPos))
86 {
87 m_Entry.Type = MSGID_PLURAL_FOUND; // we found a pluralized entry
88 return true;
89 }
90
91 m_Entry.Type = MSGID_FOUND; // we found a normal entry, with no numeric id
92 return true;
93 }
94 }
95 while (m_nextEntryPos != m_POfilelength-1);
96 // we reached the end of buffer AND we have not found a valid entry
97
98 return false;
99}
100
101void CPODocument::ParseEntry(bool bisSourceLang)
102{
103 if (bisSourceLang)
104 {
105 if (m_Entry.Type == ID_FOUND)
106 GetString(m_Entry.msgID);
107 else
108 m_Entry.msgID.Str.clear();
109 return;
110 }
111
112 if (m_Entry.Type != ID_FOUND)
113 {
114 GetString(m_Entry.msgID);
115 if (FindLineStart ("\nmsgctxt ", m_Entry.msgCtxt.Pos))
116 GetString(m_Entry.msgCtxt);
117 else
118 m_Entry.msgCtxt.Str.clear();
119 }
120
121 if (m_Entry.Type != MSGID_PLURAL_FOUND)
122 {
123 if (FindLineStart ("\nmsgstr ", m_Entry.msgStr.Pos))
124 {
125 GetString(m_Entry.msgStr);
126 GetString(m_Entry.msgID);
127 }
128 else
129 {
130 CLog::Log(LOGERROR, "POParser: missing msgstr line in entry. Failed entry: %s",
131 m_Entry.Content.c_str());
132 m_Entry.msgStr.Str.clear();
133 }
134 return;
135 }
136
137 // We found a plural form entry. We read it into a vector of CStrEntry types
138 m_Entry.msgStrPlural.clear();
139 std::string strPattern = "\nmsgstr[0] ";
140 CStrEntry strEntry;
141
142 for (int n=0; n<7 ; n++)
143 {
144 strPattern[8] = static_cast<char>(n+'0');
145 if (FindLineStart (strPattern, strEntry.Pos))
146 {
147 GetString(strEntry);
148 if (strEntry.Str.empty())
149 break;
150 m_Entry.msgStrPlural.push_back(strEntry);
151 }
152 else
153 break;
154 }
155
156 if (m_Entry.msgStrPlural.empty())
157 {
158 CLog::Log(LOGERROR, "POParser: msgstr[] plural lines have zero valid strings. "
159 "Failed entry: %s", m_Entry.Content.c_str());
160 m_Entry.msgStrPlural.resize(1); // Put 1 element with an empty string into the vector
161 }
162}
163
164const std::string& CPODocument::GetPlurMsgstr(size_t plural) const
165{
166 if (m_Entry.msgStrPlural.size() < plural+1)
167 {
168 CLog::Log(LOGERROR, "POParser: msgstr[%i] plural field requested, but not found in PO file. "
169 "Failed entry: %s", static_cast<int>(plural), m_Entry.Content.c_str());
170 plural = m_Entry.msgStrPlural.size()-1;
171 }
172 return m_Entry.msgStrPlural[plural].Str;
173}
174
175std::string CPODocument::UnescapeString(const std::string &strInput)
176{
177 std::string strOutput;
178 if (strInput.empty())
179 return strOutput;
180
181 char oescchar;
182 strOutput.reserve(strInput.size());
183 std::string::const_iterator it = strInput.begin();
184 while (it < strInput.end())
185 {
186 oescchar = *it++;
187 if (oescchar == '\\')
188 {
189 if (it == strInput.end())
190 {
191 CLog::Log(LOGERROR,
192 "POParser: warning, unhandled escape character "
193 "at line-end. Problematic entry: %s",
194 m_Entry.Content.c_str());
195 break;
196 }
197 switch (*it++)
198 {
199 case 'a': oescchar = '\a'; break;
200 case 'b': oescchar = '\b'; break;
201 case 'v': oescchar = '\v'; break;
202 case 'n': oescchar = '\n'; break;
203 case 't': oescchar = '\t'; break;
204 case 'r': oescchar = '\r'; break;
205 case '"': oescchar = '"' ; break;
206 case '0': oescchar = '\0'; break;
207 case 'f': oescchar = '\f'; break;
208 case '?': oescchar = '\?'; break;
209 case '\'': oescchar = '\''; break;
210 case '\\': oescchar = '\\'; break;
211
212 default:
213 {
214 CLog::Log(LOGERROR,
215 "POParser: warning, unhandled escape character. Problematic entry: %s",
216 m_Entry.Content.c_str());
217 continue;
218 }
219 }
220 }
221 strOutput.push_back(oescchar);
222 }
223 return strOutput;
224}
225
226bool CPODocument::FindLineStart(const std::string &strToFind, size_t &FoundPos)
227{
228
229 FoundPos = m_Entry.Content.find(strToFind);
230
231 if (FoundPos == std::string::npos || FoundPos + strToFind.size() + 2 > m_Entry.Content.size())
232 return false; // if we don't find the string or if we don't have at least one char after it
233
234 FoundPos += strToFind.size(); // to set the pos marker to the exact start of the real data
235 return true;
236}
237
238bool CPODocument::ParseNumID()
239{
240 if (isdigit(m_Entry.Content.at(m_Entry.xIDPos))) // verify if the first char is digit
241 {
242 // we check for the numeric id for the fist 10 chars (uint32)
243 m_Entry.xID = strtol(&m_Entry.Content[m_Entry.xIDPos], NULL, 10);
244 return true;
245 }
246
247 CLog::Log(LOGERROR, "POParser: found numeric id descriptor, but no valid id can be read, "
248 "entry was handled as normal msgid entry");
249 CLog::Log(LOGERROR, "POParser: The problematic entry: %s",
250 m_Entry.Content.c_str());
251 return false;
252}
253
254void CPODocument::GetString(CStrEntry &strEntry)
255{
256 size_t nextLFPos;
257 size_t startPos = strEntry.Pos;
258 strEntry.Str.clear();
259
260 while (startPos < m_Entry.Content.size())
261 {
262 nextLFPos = m_Entry.Content.find("\n", startPos);
263 if (nextLFPos == std::string::npos)
264 nextLFPos = m_Entry.Content.size();
265
266 // check syntax, if it really is a valid quoted string line
267 if (nextLFPos-startPos < 2 || m_Entry.Content[startPos] != '\"' ||
268 m_Entry.Content[nextLFPos-1] != '\"')
269 break;
270
271 strEntry.Str.append(m_Entry.Content, startPos+1, nextLFPos-2-startPos);
272 startPos = nextLFPos+1;
273 }
274
275 strEntry.Str = UnescapeString(strEntry.Str);
276}
277
278void CPODocument::ConvertLineEnds(const std::string &filename)
279{
280 size_t foundPos = m_strBuffer.find_first_of("\r");
281 if (foundPos == std::string::npos)
282 return; // We have only Linux style line endings in the file, nothing to do
283
284 if (foundPos+1 >= m_strBuffer.size() || m_strBuffer[foundPos+1] != '\n')
285 CLog::Log(LOGDEBUG, "POParser: PO file has Mac Style Line Endings. "
286 "Converted in memory to Linux LF for file: %s", filename.c_str());
287 else
288 CLog::Log(LOGDEBUG, "POParser: PO file has Win Style Line Endings. "
289 "Converted in memory to Linux LF for file: %s", filename.c_str());
290
291 std::string strTemp;
292 strTemp.reserve(m_strBuffer.size());
293 for (std::string::const_iterator it = m_strBuffer.begin(); it < m_strBuffer.end(); ++it)
294 {
295 if (*it == '\r')
296 {
297 if (it+1 == m_strBuffer.end() || *(it+1) != '\n')
298 strTemp.push_back('\n'); // convert Mac style line ending and continue
299 continue; // we have Win style line ending so we exclude this CR now
300 }
301 strTemp.push_back(*it);
302 }
303 m_strBuffer.swap(strTemp);
304 m_POfilelength = m_strBuffer.size();
305}
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 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12#include <string>
13#include <vector>
14
15typedef enum
16{
17 ID_FOUND = 0, // We have an entry with a numeric (previously XML) identification number.
18 MSGID_FOUND = 1, // We have a classic gettext entry with textual msgid. No numeric ID.
19 MSGID_PLURAL_FOUND = 2 // We have a classic gettext entry with textual msgid in plural form.
20} POIdType;
21
22enum
23{
24 ISSOURCELANG=true
25};
26
27// Struct to hold current position and text of the string field in the main PO entry.
28struct CStrEntry
29{
30 size_t Pos;
31 std::string Str;
32};
33
34// Struct to collect all important data of the current processed entry.
35struct CPOEntry
36{
37 int Type;
38 uint32_t xID;
39 size_t xIDPos;
40 std::string Content;
41 CStrEntry msgCtxt;
42 CStrEntry msgID;
43 CStrEntry msgStr;
44 std::vector<CStrEntry> msgStrPlural;
45};
46
47class CPODocument
48{
49public:
50 CPODocument();
51 ~CPODocument();
52
53 /*! \brief Tries to load a PO file into a temporary memory buffer.
54 * It also tries to parse the header of the PO file.
55 \param pofilename filename of the PO file to load.
56 \return true if the load was successful, unless return false
57 */
58 bool LoadFile(const std::string &pofilename);
59
60 /*! \brief Fast jumps to the next entry in PO buffer.
61 * Finds next entry started with "#: id:" or msgctx or msgid.
62 * to be as fast as possible this does not even get the id number
63 * just the type of the entry found. GetEntryID() has to be called
64 * for getting the id. After that ParseEntry() needs a call for
65 * actually getting the msg strings. The reason for this is to
66 * have calls and checks as fast as possible generally and specially
67 * for parsing weather tokens and to parse only the needed strings from
68 * the fallback language (missing from the gui language translation)
69 \return true if there was an entry found, false if reached the end of buffer
70 */
71 bool GetNextEntry();
72
73 /*! \brief Gets the type of entry found with GetNextEntry.
74 \return the type of entry: ID_FOUND || MSGID_FOUND || MSGID_PLURAL_FOUND
75 */
76 int GetEntryType() const {return m_Entry.Type;}
77
78 /*! \brief Parses the numeric ID from current entry.
79 * This function can only be called right after GetNextEntry()
80 * to make sure that we have a valid entry detected.
81 \return parsed ID number
82 */
83 uint32_t GetEntryID() const {return m_Entry.xID;}
84
85 /*! \brief Parses current entry.
86 * Reads msgid, msgstr, msgstr[x], msgctxt strings.
87 * Note that this function also back-converts the c++ style escape sequences.
88 * The function only parses the needed strings, considering if it is a source language file.
89 \param bisSourceLang if we parse a source English file.
90 */
91 void ParseEntry(bool bisSourceLang);
92
93 /*! \brief Gets the msgctxt string previously parsed by ParseEntry().
94 \return string* containing the msgctxt string, unescaped and linked together.
95 */
96 const std::string& GetMsgctxt() const {return m_Entry.msgCtxt.Str;}
97
98 /*! \brief Gets the msgid string previously parsed by ParseEntry().
99 \return string* containing the msgid string, unescaped and linked together.
100 */
101 const std::string& GetMsgid() const {return m_Entry.msgID.Str;}
102
103 /*! \brief Gets the msgstr string previously parsed by ParseEntry().
104 \return string* containing the msgstr string, unescaped and linked together.
105 */
106 const std::string& GetMsgstr() const {return m_Entry.msgStr.Str;}
107
108 /*! \brief Gets the msgstr[x] string previously parsed by ParseEntry().
109 \param plural the number of plural-form expected to get (0-6).
110 \return string* containing the msgstr string, unescaped and linked together.
111 */
112 const std::string& GetPlurMsgstr (size_t plural) const;
113
114protected:
115
116 /*! \brief Converts c++ style char escape sequences back to char.
117 * Supports: \a \v \n \t \r \" \0 \f \? \' \\
118 \param strInput string contains the string to be unescaped.
119 \return unescaped string.
120 */
121 std::string UnescapeString(const std::string &strInput);
122
123 /*! \brief Finds the position of line, starting with a given string in current entry.
124 * This function can only be called after GetNextEntry()
125 \param strToFind a string what we look for, at beginning of the lines.
126 \param FoundPos will get the position where we found the line starting with the string.
127 \return false if no line like that can be found in the entry (m_Entry)
128 */
129 bool FindLineStart(const std::string &strToFind, size_t &FoundPos);
130
131 /*! \brief Reads, and links together the quoted strings found with ParseEntry().
132 * This function can only be called after GetNextEntry() called.
133 \param strEntry.Str a string where we get the appended string lines.
134 \param strEntry.Pos the position in m_Entry.Content to start reading the string.
135 */
136 void GetString(CStrEntry &strEntry);
137
138 /*! \brief Parses the numeric id and checks if it is valid.
139 * This function can only be called after GetNextEntry()
140 * It checks m_Entry.Content at position m_Entry.xIDPos for the numeric id.
141 * The converted ID number goes into m_Entry.xID for public read out.
142 \return false, if parse and convert of the id number was unsuccessful.
143 */
144 bool ParseNumID();
145
146 /*! \brief If we have Windows or Mac line-end chars in PO file, convert them to Unix LFs
147 */
148 void ConvertLineEnds(const std::string &filename);
149
150 // Temporary string buffer to read file in.
151 std::string m_strBuffer;
152 // Size of the string buffer.
153 size_t m_POfilelength;
154
155 // Current cursor position in m_strBuffer.
156 size_t m_CursorPos;
157 // The next PO entry position in m_strBuffer.
158 size_t m_nextEntryPos;
159
160 // Variable to hold all data of currently processed entry.
161 CPOEntry m_Entry;
162};
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 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "ProgressJob.h"
10
11#include "ServiceBroker.h"
12#include "dialogs/GUIDialogExtendedProgressBar.h"
13#include "dialogs/GUIDialogProgress.h"
14#include "guilib/GUIComponent.h"
15#include "guilib/GUIWindowManager.h"
16#include "utils/Variant.h"
17
18#include <math.h>
19
20CProgressJob::CProgressJob()
21 : m_progress(NULL),
22 m_progressDialog(NULL)
23{ }
24
25CProgressJob::CProgressJob(CGUIDialogProgressBarHandle* progressBar)
26 : m_progress(progressBar),
27 m_progressDialog(NULL)
28{ }
29
30CProgressJob::~CProgressJob()
31{
32 MarkFinished();
33
34 m_progress = NULL;
35 m_progressDialog = NULL;
36}
37
38bool CProgressJob::ShouldCancel(unsigned int progress, unsigned int total) const
39{
40 if (IsCancelled())
41 return true;
42
43 SetProgress(progress, total);
44
45 return CJob::ShouldCancel(progress, total);
46}
47
48bool CProgressJob::DoModal()
49{
50 m_progress = NULL;
51
52 // get a progress dialog if we don't already have one
53 if (m_progressDialog == NULL)
54 {
55 m_progressDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
56
57 if (m_progressDialog == NULL)
58 return false;
59 }
60
61 m_modal = true;
62
63 // do the work
64 bool result = DoWork();
65
66 // mark the progress dialog as finished (will close it)
67 MarkFinished();
68 m_modal = false;
69
70 return result;
71}
72
73void CProgressJob::SetProgressIndicators(CGUIDialogProgressBarHandle* progressBar, CGUIDialogProgress* progressDialog, bool updateProgress /* = true */, bool updateInformation /* = true */)
74{
75 SetProgressBar(progressBar);
76 SetProgressDialog(progressDialog);
77 SetUpdateProgress(updateProgress);
78 SetUpdateInformation(updateInformation);
79
80 // disable auto-closing
81 SetAutoClose(false);
82}
83
84void CProgressJob::ShowProgressDialog() const
85{
86 if (!IsModal() || m_progressDialog == NULL ||
87 m_progressDialog->IsDialogRunning())
88 return;
89
90 // show the progress dialog as a modal dialog with a progress bar
91 m_progressDialog->Open();
92 m_progressDialog->ShowProgressBar(true);
93}
94
95void CProgressJob::SetTitle(const std::string &title)
96{
97 if (!m_updateInformation)
98 return;
99
100 if (m_progress != NULL)
101 m_progress->SetTitle(title);
102 else if (m_progressDialog != NULL)
103 {
104 m_progressDialog->SetHeading(CVariant{title});
105
106 ShowProgressDialog();
107 }
108}
109
110void CProgressJob::SetText(const std::string &text)
111{
112 if (!m_updateInformation)
113 return;
114
115 if (m_progress != NULL)
116 m_progress->SetText(text);
117 else if (m_progressDialog != NULL)
118 {
119 m_progressDialog->SetText(CVariant{text});
120
121 ShowProgressDialog();
122 }
123}
124
125void CProgressJob::SetProgress(float percentage) const
126{
127 if (!m_updateProgress)
128 return;
129
130 if (m_progress != NULL)
131 m_progress->SetPercentage(percentage);
132 else if (m_progressDialog != NULL)
133 {
134 ShowProgressDialog();
135
136 int iPercentage = static_cast<int>(ceil(percentage));
137 // only change and update the progress bar if its percentage value changed
138 // (this can have a huge impact on performance if it's called a lot)
139 if (iPercentage != m_progressDialog->GetPercentage())
140 {
141 m_progressDialog->SetPercentage(iPercentage);
142 m_progressDialog->Progress();
143 }
144 }
145}
146
147void CProgressJob::SetProgress(int currentStep, int totalSteps) const
148{
149 if (!m_updateProgress)
150 return;
151
152 if (m_progress != NULL)
153 m_progress->SetProgress(currentStep, totalSteps);
154 else if (m_progressDialog != NULL)
155 SetProgress((static_cast<float>(currentStep) * 100.0f) / totalSteps);
156}
157
158void CProgressJob::MarkFinished()
159{
160 if (m_progress != NULL)
161 {
162 if (m_updateProgress)
163 {
164 m_progress->MarkFinished();
165 // We don't own this pointer and it will be deleted after it's marked finished
166 // just set it to nullptr so we don't try to use it again
167 m_progress = nullptr;
168 }
169 }
170 else if (m_progressDialog != NULL && m_autoClose)
171 m_progressDialog->Close();
172}
173
174bool CProgressJob::IsCancelled() const
175{
176 if (m_progressDialog != NULL)
177 return m_progressDialog->IsCanceled();
178
179 return false;
180}
181
182bool CProgressJob::HasProgressIndicator() const
183{
184 return m_progress != nullptr || m_progressDialog != nullptr;
185}
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 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/Job.h"
12
13#include <string>
14
15class CGUIDialogProgress;
16class CGUIDialogProgressBarHandle;
17
18/*!
19 \brief Basic implementation of a CJob with a progress bar to indicate the
20 progress of the job being processed.
21 */
22class CProgressJob : public CJob
23{
24public:
25 ~CProgressJob() override;
26
27 // implementation of CJob
28 const char *GetType() const override { return "ProgressJob"; }
29 bool operator==(const CJob* job) const override { return false; }
30 bool ShouldCancel(unsigned int progress, unsigned int total) const override;
31
32 /*!
33 \brief Executes the job showing a modal progress dialog.
34 */
35 bool DoModal();
36
37 /*!
38 \brief Sets the given progress indicators to be used during execution of
39 the job.
40
41 \details This automatically disables auto-closing the given progress
42 indicators once the job has been finished.
43
44 \param progressBar Progress bar handle to be used.
45 \param progressDialog Progress dialog to be used.
46 \param updateProgress (optional) Whether to show progress updates.
47 \param updateInformation (optional) Whether to show progress information.
48 */
49 void SetProgressIndicators(CGUIDialogProgressBarHandle* progressBar, CGUIDialogProgress* progressDialog, bool updateProgress = true, bool updateInformation = true);
50
51 bool HasProgressIndicator() const;
52
53protected:
54 CProgressJob();
55 explicit CProgressJob(CGUIDialogProgressBarHandle* progressBar);
56
57 /*!
58 \brief Whether the job is being run modally or in the background.
59 */
60 bool IsModal() const { return m_modal; }
61
62 /*!
63 \brief Returns the progress bar indicating the progress of the job.
64 */
65 CGUIDialogProgressBarHandle* GetProgressBar() const { return m_progress; }
66
67 /*!
68 \brief Sets the progress bar indicating the progress of the job.
69 */
70 void SetProgressBar(CGUIDialogProgressBarHandle* progress) { m_progress = progress; }
71
72 /*!
73 \brief Returns the progress dialog indicating the progress of the job.
74 */
75 CGUIDialogProgress* GetProgressDialog() const { return m_progressDialog; }
76
77 /*!
78 \brief Sets the progress bar indicating the progress of the job.
79 */
80 void SetProgressDialog(CGUIDialogProgress* progressDialog) { m_progressDialog = progressDialog; }
81
82 /*!
83 \brief Whether to automatically close the progress indicator in MarkFinished().
84 */
85 bool GetAutoClose() { return m_autoClose; }
86
87 /*!
88 \brief Set whether to automatically close the progress indicator in MarkFinished().
89 */
90 void SetAutoClose(bool autoClose) { m_autoClose = autoClose; }
91
92 /*!
93 \brief Whether to update the progress bar or not.
94 */
95 bool GetUpdateProgress() { return m_updateProgress; }
96
97 /*!
98 \brief Set whether to update the progress bar or not.
99 */
100 void SetUpdateProgress(bool updateProgress) { m_updateProgress = updateProgress; }
101
102 /*!
103 \brief Whether to update the progress information or not.
104 */
105 bool GetUpdateInformation() { return m_updateInformation; }
106
107 /*!
108 \brief Set whether to update the progress information or not.
109 */
110 void SetUpdateInformation(bool updateInformation) { m_updateInformation = updateInformation; }
111
112 /*!
113 \brief Makes sure that the modal dialog is being shown.
114 */
115 void ShowProgressDialog() const;
116
117 /*!
118 \brief Sets the given title as the title of the progress bar.
119
120 \param[in] title Title to be set
121 */
122 void SetTitle(const std::string &title);
123
124 /*!
125 \brief Sets the given text as the description of the progress bar.
126
127 \param[in] text Text to be set
128 */
129 void SetText(const std::string &text);
130
131 /*!
132 \brief Sets the progress of the progress bar to the given value in percentage.
133
134 \param[in] percentage Percentage to be set as the current progress
135 */
136 void SetProgress(float percentage) const;
137
138 /*!
139 \brief Sets the progress of the progress bar to the given value.
140
141 \param[in] currentStep Current step being processed
142 \param[in] totalSteps Total steps to be processed
143 */
144 void SetProgress(int currentStep, int totalSteps) const;
145
146 /*!
147 \brief Marks the progress as finished by setting it to 100%.
148 */
149 void MarkFinished();
150
151 /*!
152 \brief Checks if the progress dialog has been cancelled.
153 */
154 bool IsCancelled() const;
155
156private:
157 bool m_modal = false;
158 bool m_autoClose = true;
159 bool m_updateProgress = true;
160 bool m_updateInformation = true;
161 mutable CGUIDialogProgressBarHandle* m_progress;
162 mutable CGUIDialogProgress* m_progressDialog;
163};
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <algorithm>
12#include <random>
13
14namespace KODI
15{
16namespace UTILS
17{
18template<class TIterator>
19void RandomShuffle(TIterator begin, TIterator end)
20{
21 std::random_device rd;
22 std::mt19937 mt(rd());
23 std::shuffle(begin, end, mt);
24}
25}
26}
diff --git a/xbmc/utils/RecentlyAddedJob.cpp b/xbmc/utils/RecentlyAddedJob.cpp
new file mode 100644
index 0000000..b45fade
--- /dev/null
+++ b/xbmc/utils/RecentlyAddedJob.cpp
@@ -0,0 +1,390 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "RecentlyAddedJob.h"
10
11#include "FileItem.h"
12#include "ServiceBroker.h"
13#include "guilib/GUIComponent.h"
14#include "guilib/GUIWindow.h"
15#include "guilib/GUIWindowManager.h"
16#include "guilib/WindowIDs.h"
17#include "music/MusicDatabase.h"
18#include "music/MusicThumbLoader.h"
19#include "music/tags/MusicInfoTag.h"
20#include "settings/AdvancedSettings.h"
21#include "settings/Settings.h"
22#include "settings/SettingsComponent.h"
23#include "utils/StringUtils.h"
24#include "utils/log.h"
25#include "video/VideoDatabase.h"
26#include "video/VideoInfoTag.h"
27#include "video/VideoThumbLoader.h"
28
29#if defined(TARGET_DARWIN_TVOS)
30#include "platform/darwin/tvos/TVOSTopShelf.h"
31#endif
32
33#define NUM_ITEMS 10
34
35CRecentlyAddedJob::CRecentlyAddedJob(int flag)
36{
37 m_flag = flag;
38}
39
40bool CRecentlyAddedJob::UpdateVideo()
41{
42 auto home = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_HOME);
43
44 if ( home == nullptr )
45 return false;
46
47 CLog::Log(LOGDEBUG, "CRecentlyAddedJob::UpdateVideos() - Running RecentlyAdded home screen update");
48
49 int i = 0;
50 CFileItemList items;
51 CVideoDatabase videodatabase;
52 CVideoThumbLoader loader;
53 loader.OnLoaderStart();
54
55 videodatabase.Open();
56
57 if (videodatabase.GetRecentlyAddedMoviesNav("videodb://recentlyaddedmovies/", items, NUM_ITEMS))
58 {
59 for (; i < items.Size(); ++i)
60 {
61 auto item = items.Get(i);
62 std::string value = StringUtils::Format("%i", i + 1);
63 std::string strRating = StringUtils::Format("%.1f", item->GetVideoInfoTag()->GetRating().rating);
64
65 home->SetProperty("LatestMovie." + value + ".Title" , item->GetLabel());
66 home->SetProperty("LatestMovie." + value + ".Rating" , strRating);
67 home->SetProperty("LatestMovie." + value + ".Year" , item->GetVideoInfoTag()->GetYear());
68 home->SetProperty("LatestMovie." + value + ".Plot" , item->GetVideoInfoTag()->m_strPlot);
69 home->SetProperty("LatestMovie." + value + ".RunningTime" , item->GetVideoInfoTag()->GetDuration() / 60);
70 home->SetProperty("LatestMovie." + value + ".Path" , item->GetVideoInfoTag()->m_strFileNameAndPath);
71 home->SetProperty("LatestMovie." + value + ".Trailer" , item->GetVideoInfoTag()->m_strTrailer);
72
73 if (!item->HasArt("thumb"))
74 loader.LoadItem(item.get());
75
76 home->SetProperty("LatestMovie." + value + ".Thumb" , item->GetArt("thumb"));
77 home->SetProperty("LatestMovie." + value + ".Fanart" , item->GetArt("fanart"));
78 }
79 }
80 for (; i < NUM_ITEMS; ++i)
81 {
82 std::string value = StringUtils::Format("%i", i + 1);
83 home->SetProperty("LatestMovie." + value + ".Title" , "");
84 home->SetProperty("LatestMovie." + value + ".Thumb" , "");
85 home->SetProperty("LatestMovie." + value + ".Rating" , "");
86 home->SetProperty("LatestMovie." + value + ".Year" , "");
87 home->SetProperty("LatestMovie." + value + ".Plot" , "");
88 home->SetProperty("LatestMovie." + value + ".RunningTime" , "");
89 home->SetProperty("LatestMovie." + value + ".Path" , "");
90 home->SetProperty("LatestMovie." + value + ".Trailer" , "");
91 home->SetProperty("LatestMovie." + value + ".Fanart" , "");
92 }
93
94 i = 0;
95 CFileItemList TVShowItems;
96
97 if (videodatabase.GetRecentlyAddedEpisodesNav("videodb://recentlyaddedepisodes/", TVShowItems, NUM_ITEMS))
98 {
99 for (; i < TVShowItems.Size(); ++i)
100 {
101 auto item = TVShowItems.Get(i);
102 int EpisodeSeason = item->GetVideoInfoTag()->m_iSeason;
103 int EpisodeNumber = item->GetVideoInfoTag()->m_iEpisode;
104 std::string EpisodeNo = StringUtils::Format("s%02de%02d", EpisodeSeason, EpisodeNumber);
105 std::string value = StringUtils::Format("%i", i + 1);
106 std::string strRating = StringUtils::Format("%.1f", item->GetVideoInfoTag()->GetRating().rating);
107
108 home->SetProperty("LatestEpisode." + value + ".ShowTitle" , item->GetVideoInfoTag()->m_strShowTitle);
109 home->SetProperty("LatestEpisode." + value + ".EpisodeTitle" , item->GetVideoInfoTag()->m_strTitle);
110 home->SetProperty("LatestEpisode." + value + ".Rating" , strRating);
111 home->SetProperty("LatestEpisode." + value + ".Plot" , item->GetVideoInfoTag()->m_strPlot);
112 home->SetProperty("LatestEpisode." + value + ".EpisodeNo" , EpisodeNo);
113 home->SetProperty("LatestEpisode." + value + ".EpisodeSeason" , EpisodeSeason);
114 home->SetProperty("LatestEpisode." + value + ".EpisodeNumber" , EpisodeNumber);
115 home->SetProperty("LatestEpisode." + value + ".Path" , item->GetVideoInfoTag()->m_strFileNameAndPath);
116
117 if (!item->HasArt("thumb"))
118 loader.LoadItem(item.get());
119
120 std::string seasonThumb;
121 if (item->GetVideoInfoTag()->m_iIdSeason > 0)
122 seasonThumb = videodatabase.GetArtForItem(item->GetVideoInfoTag()->m_iIdSeason, MediaTypeSeason, "thumb");
123
124 home->SetProperty("LatestEpisode." + value + ".Thumb" , item->GetArt("thumb"));
125 home->SetProperty("LatestEpisode." + value + ".ShowThumb" , item->GetArt("tvshow.thumb"));
126 home->SetProperty("LatestEpisode." + value + ".SeasonThumb" , seasonThumb);
127 home->SetProperty("LatestEpisode." + value + ".Fanart" , item->GetArt("fanart"));
128 }
129 }
130 for (; i < NUM_ITEMS; ++i)
131 {
132 std::string value = StringUtils::Format("%i", i + 1);
133 home->SetProperty("LatestEpisode." + value + ".ShowTitle" , "");
134 home->SetProperty("LatestEpisode." + value + ".EpisodeTitle" , "");
135 home->SetProperty("LatestEpisode." + value + ".Rating" , "");
136 home->SetProperty("LatestEpisode." + value + ".Plot" , "");
137 home->SetProperty("LatestEpisode." + value + ".EpisodeNo" , "");
138 home->SetProperty("LatestEpisode." + value + ".EpisodeSeason" , "");
139 home->SetProperty("LatestEpisode." + value + ".EpisodeNumber" , "");
140 home->SetProperty("LatestEpisode." + value + ".Path" , "");
141 home->SetProperty("LatestEpisode." + value + ".Thumb" , "");
142 home->SetProperty("LatestEpisode." + value + ".ShowThumb" , "");
143 home->SetProperty("LatestEpisode." + value + ".SeasonThumb" , "");
144 home->SetProperty("LatestEpisode." + value + ".Fanart" , "");
145 }
146
147#if defined(TARGET_DARWIN_TVOS)
148 // send recently added Movies and TvShows to TopShelf
149 CTVOSTopShelf::GetInstance().SetTopShelfItems(items, TVShowItems);
150#endif
151
152 i = 0;
153 CFileItemList MusicVideoItems;
154
155 if (videodatabase.GetRecentlyAddedMusicVideosNav("videodb://recentlyaddedmusicvideos/", MusicVideoItems, NUM_ITEMS))
156 {
157 for (; i < MusicVideoItems.Size(); ++i)
158 {
159 auto item = MusicVideoItems.Get(i);
160 std::string value = StringUtils::Format("%i", i + 1);
161
162 home->SetProperty("LatestMusicVideo." + value + ".Title" , item->GetLabel());
163 home->SetProperty("LatestMusicVideo." + value + ".Year" , item->GetVideoInfoTag()->GetYear());
164 home->SetProperty("LatestMusicVideo." + value + ".Plot" , item->GetVideoInfoTag()->m_strPlot);
165 home->SetProperty("LatestMusicVideo." + value + ".RunningTime" , item->GetVideoInfoTag()->GetDuration() / 60);
166 home->SetProperty("LatestMusicVideo." + value + ".Path" , item->GetVideoInfoTag()->m_strFileNameAndPath);
167 home->SetProperty("LatestMusicVideo." + value + ".Artist" , StringUtils::Join(item->GetVideoInfoTag()->m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator));
168
169 if (!item->HasArt("thumb"))
170 loader.LoadItem(item.get());
171
172 home->SetProperty("LatestMusicVideo." + value + ".Thumb" , item->GetArt("thumb"));
173 home->SetProperty("LatestMusicVideo." + value + ".Fanart" , item->GetArt("fanart"));
174 }
175 }
176 for (; i < NUM_ITEMS; ++i)
177 {
178 std::string value = StringUtils::Format("%i", i + 1);
179 home->SetProperty("LatestMusicVideo." + value + ".Title" , "");
180 home->SetProperty("LatestMusicVideo." + value + ".Thumb" , "");
181 home->SetProperty("LatestMusicVideo." + value + ".Year" , "");
182 home->SetProperty("LatestMusicVideo." + value + ".Plot" , "");
183 home->SetProperty("LatestMusicVideo." + value + ".RunningTime" , "");
184 home->SetProperty("LatestMusicVideo." + value + ".Path" , "");
185 home->SetProperty("LatestMusicVideo." + value + ".Artist" , "");
186 home->SetProperty("LatestMusicVideo." + value + ".Fanart" , "");
187 }
188
189 videodatabase.Close();
190 return true;
191}
192
193bool CRecentlyAddedJob::UpdateMusic()
194{
195 auto home = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_HOME);
196
197 if ( home == nullptr )
198 return false;
199
200 CLog::Log(LOGDEBUG, "CRecentlyAddedJob::UpdateMusic() - Running RecentlyAdded home screen update");
201
202 int i = 0;
203 CFileItemList musicItems;
204 CMusicDatabase musicdatabase;
205 CMusicThumbLoader loader;
206 loader.OnLoaderStart();
207
208 musicdatabase.Open();
209
210 if (musicdatabase.GetRecentlyAddedAlbumSongs("musicdb://songs/", musicItems, NUM_ITEMS))
211 {
212 long idAlbum = -1;
213 std::string strAlbumThumb;
214 std::string strAlbumFanart;
215 for (; i < musicItems.Size(); ++i)
216 {
217 auto item = musicItems.Get(i);
218 std::string value = StringUtils::Format("%d", i + 1);
219
220 std::string strRating;
221 std::string strAlbum = item->GetMusicInfoTag()->GetAlbum();
222 std::string strArtist = item->GetMusicInfoTag()->GetArtistString();
223
224 if (idAlbum != item->GetMusicInfoTag()->GetAlbumId())
225 {
226 strAlbumThumb.clear();
227 strAlbumFanart.clear();
228 idAlbum = item->GetMusicInfoTag()->GetAlbumId();
229
230 if (loader.LoadItem(item.get()))
231 {
232 strAlbumThumb = item->GetArt("thumb");
233 strAlbumFanart = item->GetArt("fanart");
234 }
235 }
236
237 strRating = StringUtils::Format("%c", item->GetMusicInfoTag()->GetUserrating());
238
239 home->SetProperty("LatestSong." + value + ".Title" , item->GetMusicInfoTag()->GetTitle());
240 home->SetProperty("LatestSong." + value + ".Year" , item->GetMusicInfoTag()->GetYear());
241 home->SetProperty("LatestSong." + value + ".Artist" , strArtist);
242 home->SetProperty("LatestSong." + value + ".Album" , strAlbum);
243 home->SetProperty("LatestSong." + value + ".Rating" , strRating);
244 home->SetProperty("LatestSong." + value + ".Path" , item->GetMusicInfoTag()->GetURL());
245 home->SetProperty("LatestSong." + value + ".Thumb" , strAlbumThumb);
246 home->SetProperty("LatestSong." + value + ".Fanart" , strAlbumFanart);
247 }
248 }
249 for (; i < NUM_ITEMS; ++i)
250 {
251 std::string value = StringUtils::Format("%i", i + 1);
252 home->SetProperty("LatestSong." + value + ".Title" , "");
253 home->SetProperty("LatestSong." + value + ".Year" , "");
254 home->SetProperty("LatestSong." + value + ".Artist" , "");
255 home->SetProperty("LatestSong." + value + ".Album" , "");
256 home->SetProperty("LatestSong." + value + ".Rating" , "");
257 home->SetProperty("LatestSong." + value + ".Path" , "");
258 home->SetProperty("LatestSong." + value + ".Thumb" , "");
259 home->SetProperty("LatestSong." + value + ".Fanart" , "");
260 }
261
262 i = 0;
263 VECALBUMS albums;
264
265 if (musicdatabase.GetRecentlyAddedAlbums(albums, NUM_ITEMS))
266 {
267 size_t j = 0;
268 for (; j < albums.size(); ++j)
269 {
270 auto& album=albums[j];
271 std::string value = StringUtils::Format("%lu", j + 1);
272 std::string strThumb;
273 std::string strFanart;
274 bool artfound = false;
275 std::vector<ArtForThumbLoader> art;
276 // Get album thumb and fanart for first album artist
277 artfound = musicdatabase.GetArtForItem(-1, album.idAlbum, -1, true, art);
278 if (artfound)
279 {
280 for (auto artitem : art)
281 {
282 if (artitem.mediaType == MediaTypeAlbum && artitem.artType == "thumb")
283 strThumb = artitem.url;
284 else if (artitem.mediaType == MediaTypeArtist && artitem.artType == "fanart")
285 strFanart = artitem.url;
286 }
287 }
288
289 std::string strDBpath = StringUtils::Format("musicdb://albums/%li/", album.idAlbum);
290
291 home->SetProperty("LatestAlbum." + value + ".Title" , album.strAlbum);
292 home->SetProperty("LatestAlbum." + value + ".Year" , album.strReleaseDate);
293 home->SetProperty("LatestAlbum." + value + ".Artist" , album.GetAlbumArtistString());
294 home->SetProperty("LatestAlbum." + value + ".Rating" , album.fRating);
295 home->SetProperty("LatestAlbum." + value + ".Path" , strDBpath);
296 home->SetProperty("LatestAlbum." + value + ".Thumb" , strThumb);
297 home->SetProperty("LatestAlbum." + value + ".Fanart" , strFanart);
298 }
299 i = j;
300 }
301 for (; i < NUM_ITEMS; ++i)
302 {
303 std::string value = StringUtils::Format("%i", i + 1);
304 home->SetProperty("LatestAlbum." + value + ".Title" , "");
305 home->SetProperty("LatestAlbum." + value + ".Year" , "");
306 home->SetProperty("LatestAlbum." + value + ".Artist" , "");
307 home->SetProperty("LatestAlbum." + value + ".Rating" , "");
308 home->SetProperty("LatestAlbum." + value + ".Path" , "");
309 home->SetProperty("LatestAlbum." + value + ".Thumb" , "");
310 home->SetProperty("LatestAlbum." + value + ".Fanart" , "");
311 }
312
313 musicdatabase.Close();
314 return true;
315}
316
317bool CRecentlyAddedJob::UpdateTotal()
318{
319 auto home = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_HOME);
320
321 if ( home == nullptr )
322 return false;
323
324 CLog::Log(LOGDEBUG, "CRecentlyAddedJob::UpdateTotal() - Running RecentlyAdded home screen update");
325
326 CVideoDatabase videodatabase;
327 CMusicDatabase musicdatabase;
328
329 musicdatabase.Open();
330
331 CMusicDbUrl musicUrl;
332 musicUrl.FromString("musicdb://artists/");
333 musicUrl.AddOption("albumartistsonly", !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS));
334
335 CFileItemList items;
336 CDatabase::Filter filter;
337 musicdatabase.GetArtistsByWhere(musicUrl.ToString(), filter, items, SortDescription(), true);
338 int MusArtistTotals = 0;
339 if (items.Size() == 1 && items.Get(0)->HasProperty("total"))
340 MusArtistTotals = items.Get(0)->GetProperty("total").asInteger();
341
342 int MusSongTotals = atoi(musicdatabase.GetSingleValue("songview" , "count(1)").c_str());
343 int MusAlbumTotals = atoi(musicdatabase.GetSingleValue("songview" , "count(distinct strAlbum)").c_str());
344 musicdatabase.Close();
345
346 videodatabase.Open();
347 int tvShowCount = atoi(videodatabase.GetSingleValue("tvshow_view" , "count(1)").c_str());
348 int movieTotals = atoi(videodatabase.GetSingleValue("movie_view" , "count(1)").c_str());
349 int movieWatched = atoi(videodatabase.GetSingleValue("movie_view" , "count(playCount)").c_str());
350 int MusVidTotals = atoi(videodatabase.GetSingleValue("musicvideo_view" , "count(1)").c_str());
351 int MusVidWatched = atoi(videodatabase.GetSingleValue("musicvideo_view" , "count(playCount)").c_str());
352 int EpWatched = atoi(videodatabase.GetSingleValue("tvshow_view" , "sum(watchedcount)").c_str());
353 int EpCount = atoi(videodatabase.GetSingleValue("tvshow_view" , "sum(totalcount)").c_str());
354 int TvShowsWatched = atoi(videodatabase.GetSingleValue("tvshow_view" , "sum(watchedcount = totalcount)").c_str());
355 videodatabase.Close();
356
357 home->SetProperty("TVShows.Count" , tvShowCount);
358 home->SetProperty("TVShows.Watched" , TvShowsWatched);
359 home->SetProperty("TVShows.UnWatched" , tvShowCount - TvShowsWatched);
360 home->SetProperty("Episodes.Count" , EpCount);
361 home->SetProperty("Episodes.Watched" , EpWatched);
362 home->SetProperty("Episodes.UnWatched" , EpCount-EpWatched);
363 home->SetProperty("Movies.Count" , movieTotals);
364 home->SetProperty("Movies.Watched" , movieWatched);
365 home->SetProperty("Movies.UnWatched" , movieTotals - movieWatched);
366 home->SetProperty("MusicVideos.Count" , MusVidTotals);
367 home->SetProperty("MusicVideos.Watched" , MusVidWatched);
368 home->SetProperty("MusicVideos.UnWatched" , MusVidTotals - MusVidWatched);
369 home->SetProperty("Music.SongsCount" , MusSongTotals);
370 home->SetProperty("Music.AlbumsCount" , MusAlbumTotals);
371 home->SetProperty("Music.ArtistsCount" , MusArtistTotals);
372
373 return true;
374}
375
376
377bool CRecentlyAddedJob::DoWork()
378{
379 bool ret = true;
380 if (m_flag & Audio)
381 ret &= UpdateMusic();
382
383 if (m_flag & Video)
384 ret &= UpdateVideo();
385
386 if (m_flag & Totals)
387 ret &= UpdateTotal();
388
389 return ret;
390}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "Job.h"
12
13enum ERecentlyAddedFlag
14{
15 Audio = 0x1,
16 Video = 0x2,
17 Totals = 0x4
18};
19
20class CRecentlyAddedJob : public CJob
21{
22public:
23 explicit CRecentlyAddedJob(int flag);
24 static bool UpdateVideo();
25 static bool UpdateMusic();
26 static bool UpdateTotal();
27 bool DoWork() override;
28private:
29 int m_flag;
30};
diff --git a/xbmc/utils/RegExp.cpp b/xbmc/utils/RegExp.cpp
new file mode 100644
index 0000000..b6fe9d5
--- /dev/null
+++ b/xbmc/utils/RegExp.cpp
@@ -0,0 +1,642 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "RegExp.h"
10
11#include "log.h"
12#include "utils/StringUtils.h"
13#include "utils/Utf8Utils.h"
14
15#include <algorithm>
16#include <stdlib.h>
17#include <string.h>
18
19using namespace PCRE;
20
21#ifndef PCRE_UCP
22#define PCRE_UCP 0
23#endif // PCRE_UCP
24
25#ifdef PCRE_CONFIG_JIT
26#define PCRE_HAS_JIT_CODE 1
27#endif
28
29#ifndef PCRE_STUDY_JIT_COMPILE
30#define PCRE_STUDY_JIT_COMPILE 0
31#endif
32#ifndef PCRE_INFO_JIT
33// some unused number
34#define PCRE_INFO_JIT 2048
35#endif
36#ifndef PCRE_HAS_JIT_CODE
37#define pcre_free_study(x) pcre_free((x))
38#endif
39
40int CRegExp::m_Utf8Supported = -1;
41int CRegExp::m_UcpSupported = -1;
42int CRegExp::m_JitSupported = -1;
43
44
45CRegExp::CRegExp(bool caseless /*= false*/, CRegExp::utf8Mode utf8 /*= asciiOnly*/)
46{
47 InitValues(caseless, utf8);
48}
49
50void CRegExp::InitValues(bool caseless /*= false*/, CRegExp::utf8Mode utf8 /*= asciiOnly*/)
51{
52 m_utf8Mode = utf8;
53 m_re = NULL;
54 m_sd = NULL;
55 m_iOptions = PCRE_DOTALL | PCRE_NEWLINE_ANY;
56 if(caseless)
57 m_iOptions |= PCRE_CASELESS;
58 if (m_utf8Mode == forceUtf8)
59 {
60 if (IsUtf8Supported())
61 m_iOptions |= PCRE_UTF8;
62 if (AreUnicodePropertiesSupported())
63 m_iOptions |= PCRE_UCP;
64 }
65
66 m_offset = 0;
67 m_jitCompiled = false;
68 m_bMatched = false;
69 m_iMatchCount = 0;
70 m_jitStack = NULL;
71
72 memset(m_iOvector, 0, sizeof(m_iOvector));
73}
74
75CRegExp::CRegExp(bool caseless, CRegExp::utf8Mode utf8, const char *re, studyMode study /*= NoStudy*/)
76{
77 if (utf8 == autoUtf8)
78 utf8 = requireUtf8(re) ? forceUtf8 : asciiOnly;
79
80 InitValues(caseless, utf8);
81 RegComp(re, study);
82}
83
84bool CRegExp::requireUtf8(const std::string& regexp)
85{
86 // enable UTF-8 mode if regexp string has UTF-8 multibyte sequences
87 if (CUtf8Utils::checkStrForUtf8(regexp) == CUtf8Utils::utf8string)
88 return true;
89
90 // check for explicit Unicode Properties (\p, \P, \X) and for Unicode character codes (greater than 0xFF) in form \x{hhh..}
91 // note: PCRE change meaning of \w, \s, \d (and \W, \S, \D) when Unicode Properties are enabled,
92 // but in auto mode we enable UNP for US-ASCII regexp only if regexp contains explicit \p, \P, \X or Unicode character code
93 const char* const regexpC = regexp.c_str();
94 const size_t len = regexp.length();
95 size_t pos = 0;
96
97 while (pos < len)
98 {
99 const char chr = regexpC[pos];
100 if (chr == '\\')
101 {
102 const char nextChr = regexpC[pos + 1];
103
104 if (nextChr == 'p' || nextChr == 'P' || nextChr == 'X')
105 return true; // found Unicode Properties
106 else if (nextChr == 'Q')
107 pos = regexp.find("\\E", pos + 2); // skip all literals in "\Q...\E"
108 else if (nextChr == 'x' && regexpC[pos + 2] == '{')
109 { // Unicode character with hex code
110 if (readCharXCode(regexp, pos) >= 0x100)
111 return true; // found Unicode character code
112 }
113 else if (nextChr == '\\' || nextChr == '(' || nextChr == ')'
114 || nextChr == '[' || nextChr == ']')
115 pos++; // exclude next character from analyze
116
117 } // chr != '\\'
118 else if (chr == '(' && regexpC[pos + 1] == '?' && regexpC[pos + 2] == '#') // comment in regexp
119 pos = regexp.find(')', pos); // skip comment
120 else if (chr == '[')
121 {
122 if (isCharClassWithUnicode(regexp, pos))
123 return true;
124 }
125
126 if (pos == std::string::npos) // check results of regexp.find() and isCharClassWithUnicode
127 return false;
128
129 pos++;
130 }
131
132 // no Unicode Properties was found
133 return false;
134}
135
136inline int CRegExp::readCharXCode(const std::string& regexp, size_t& pos)
137{
138 // read hex character code in form "\x{hh..}"
139 // 'pos' must point to '\'
140 if (pos >= regexp.length())
141 return -1;
142 const char* const regexpC = regexp.c_str();
143 if (regexpC[pos] != '\\' || regexpC[pos + 1] != 'x' || regexpC[pos + 2] != '{')
144 return -1;
145
146 pos++;
147 const size_t startPos = pos; // 'startPos' points to 'x'
148 const size_t closingBracketPos = regexp.find('}', startPos + 2);
149 if (closingBracketPos == std::string::npos)
150 return 0; // return character zero code, leave 'pos' at 'x'
151
152 pos++; // 'pos' points to '{'
153 int chCode = 0;
154 while (++pos < closingBracketPos)
155 {
156 const int xdigitVal = StringUtils::asciixdigitvalue(regexpC[pos]);
157 if (xdigitVal >= 0)
158 chCode = chCode * 16 + xdigitVal;
159 else
160 { // found non-hexdigit
161 pos = startPos; // reset 'pos' to 'startPos', process "{hh..}" as non-code
162 return 0; // return character zero code
163 }
164 }
165
166 return chCode;
167}
168
169bool CRegExp::isCharClassWithUnicode(const std::string& regexp, size_t& pos)
170{
171 const char* const regexpC = regexp.c_str();
172 const size_t len = regexp.length();
173 if (pos > len || regexpC[pos] != '[')
174 return false;
175
176 // look for Unicode character code "\x{hhh..}" and Unicode properties "\P", "\p" and "\X"
177 // find end (terminating ']') of character class (like "[a-h45]")
178 // detect nested POSIX classes like "[[:lower:]]" and escaped brackets like "[\]]"
179 bool needUnicode = false;
180 while (++pos < len)
181 {
182 if (regexpC[pos] == '[' && regexpC[pos + 1] == ':')
183 { // possible POSIX character class, like "[:alpha:]"
184 const size_t nextClosingBracketPos = regexp.find(']', pos + 2); // don't care about "\]", as it produce error if used inside POSIX char class
185
186 if (nextClosingBracketPos == std::string::npos)
187 { // error in regexp: no closing ']' for character class
188 pos = std::string::npos;
189 return needUnicode;
190 }
191 else if (regexpC[nextClosingBracketPos - 1] == ':')
192 pos = nextClosingBracketPos; // skip POSIX character class
193 // if ":]" is not found, process "[:..." as part of normal character class
194 }
195 else if (regexpC[pos] == ']')
196 return needUnicode; // end of character class
197 else if (regexpC[pos] == '\\')
198 {
199 const char nextChar = regexpC[pos + 1];
200 if (nextChar == ']' || nextChar == '[')
201 pos++; // skip next character
202 else if (nextChar == 'Q')
203 {
204 pos = regexp.find("\\E", pos + 2);
205 if (pos == std::string::npos)
206 return needUnicode; // error in regexp: no closing "\E" after "\Q" in character class
207 else
208 pos++; // skip "\E"
209 }
210 else if (nextChar == 'p' || nextChar == 'P' || nextChar == 'X')
211 needUnicode = true; // don't care about property name as it can contain only ASCII chars
212 else if (nextChar == 'x')
213 {
214 if (readCharXCode(regexp, pos) >= 0x100)
215 needUnicode = true;
216 }
217 }
218 }
219 pos = std::string::npos; // closing square bracket was not found
220
221 return needUnicode;
222}
223
224
225CRegExp::CRegExp(const CRegExp& re)
226{
227 m_re = NULL;
228 m_sd = NULL;
229 m_jitStack = NULL;
230 m_utf8Mode = re.m_utf8Mode;
231 m_iOptions = re.m_iOptions;
232 *this = re;
233}
234
235CRegExp& CRegExp::operator=(const CRegExp& re)
236{
237 size_t size;
238 Cleanup();
239 m_jitCompiled = false;
240 m_pattern = re.m_pattern;
241 if (re.m_re)
242 {
243 if (pcre_fullinfo(re.m_re, NULL, PCRE_INFO_SIZE, &size) >= 0)
244 {
245 if ((m_re = (pcre*)malloc(size)))
246 {
247 memcpy(m_re, re.m_re, size);
248 memcpy(m_iOvector, re.m_iOvector, OVECCOUNT*sizeof(int));
249 m_offset = re.m_offset;
250 m_iMatchCount = re.m_iMatchCount;
251 m_bMatched = re.m_bMatched;
252 m_subject = re.m_subject;
253 m_iOptions = re.m_iOptions;
254 }
255 else
256 CLog::Log(LOGFATAL, "%s: Failed to allocate memory", __FUNCTION__);
257 }
258 }
259 return *this;
260}
261
262CRegExp::~CRegExp()
263{
264 Cleanup();
265}
266
267bool CRegExp::RegComp(const char *re, studyMode study /*= NoStudy*/)
268{
269 if (!re)
270 return false;
271
272 m_offset = 0;
273 m_jitCompiled = false;
274 m_bMatched = false;
275 m_iMatchCount = 0;
276 const char *errMsg = NULL;
277 int errOffset = 0;
278 int options = m_iOptions;
279 if (m_utf8Mode == autoUtf8 && requireUtf8(re))
280 options |= (IsUtf8Supported() ? PCRE_UTF8 : 0) | (AreUnicodePropertiesSupported() ? PCRE_UCP : 0);
281
282 Cleanup();
283
284 m_re = pcre_compile(re, options, &errMsg, &errOffset, NULL);
285 if (!m_re)
286 {
287 m_pattern.clear();
288 CLog::Log(LOGERROR, "PCRE: %s. Compilation failed at offset %d in expression '%s'",
289 errMsg, errOffset, re);
290 return false;
291 }
292
293 m_pattern = re;
294
295 if (study)
296 {
297 const bool jitCompile = (study == StudyWithJitComp) && IsJitSupported();
298 const int studyOptions = jitCompile ? PCRE_STUDY_JIT_COMPILE : 0;
299
300 m_sd = pcre_study(m_re, studyOptions, &errMsg);
301 if (errMsg != NULL)
302 {
303 CLog::Log(LOGWARNING, "%s: PCRE error \"%s\" while studying expression", __FUNCTION__, errMsg);
304 if (m_sd != NULL)
305 {
306 pcre_free_study(m_sd);
307 m_sd = NULL;
308 }
309 }
310 else if (jitCompile)
311 {
312 int jitPresent = 0;
313 m_jitCompiled = (pcre_fullinfo(m_re, m_sd, PCRE_INFO_JIT, &jitPresent) == 0 && jitPresent == 1);
314 }
315 }
316
317 return true;
318}
319
320int CRegExp::RegFind(const char *str, unsigned int startoffset /*= 0*/, int maxNumberOfCharsToTest /*= -1*/)
321{
322 return PrivateRegFind(strlen(str), str, startoffset, maxNumberOfCharsToTest);
323}
324
325int CRegExp::PrivateRegFind(size_t bufferLen, const char *str, unsigned int startoffset /* = 0*/, int maxNumberOfCharsToTest /*= -1*/)
326{
327 m_offset = 0;
328 m_bMatched = false;
329 m_iMatchCount = 0;
330
331 if (!m_re)
332 {
333 CLog::Log(LOGERROR, "PCRE: Called before compilation");
334 return -1;
335 }
336
337 if (!str)
338 {
339 CLog::Log(LOGERROR, "PCRE: Called without a string to match");
340 return -1;
341 }
342
343 if (startoffset > bufferLen)
344 {
345 CLog::Log(LOGERROR, "%s: startoffset is beyond end of string to match", __FUNCTION__);
346 return -1;
347 }
348
349#ifdef PCRE_HAS_JIT_CODE
350 if (m_jitCompiled && !m_jitStack)
351 {
352 m_jitStack = pcre_jit_stack_alloc(32*1024, 512*1024);
353 if (m_jitStack == NULL)
354 CLog::Log(LOGWARNING, "%s: can't allocate address space for JIT stack", __FUNCTION__);
355
356 pcre_assign_jit_stack(m_sd, NULL, m_jitStack);
357 }
358#endif
359
360 if (maxNumberOfCharsToTest >= 0)
361 bufferLen = std::min<size_t>(bufferLen, startoffset + maxNumberOfCharsToTest);
362
363 m_subject.assign(str + startoffset, bufferLen - startoffset);
364 int rc = pcre_exec(m_re, NULL, m_subject.c_str(), m_subject.length(), 0, 0, m_iOvector, OVECCOUNT);
365
366 if (rc<1)
367 {
368 static const int fragmentLen = 80; // length of excerpt before erroneous char for log
369 switch(rc)
370 {
371 case PCRE_ERROR_NOMATCH:
372 return -1;
373
374 case PCRE_ERROR_MATCHLIMIT:
375 CLog::Log(LOGERROR, "PCRE: Match limit reached");
376 return -1;
377
378#ifdef PCRE_ERROR_SHORTUTF8
379 case PCRE_ERROR_SHORTUTF8:
380 {
381 const size_t startPos = (m_subject.length() > fragmentLen) ? CUtf8Utils::RFindValidUtf8Char(m_subject, m_subject.length() - fragmentLen) : 0;
382 if (startPos != std::string::npos)
383 CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character at the end of string. Text before bad character: \"%s\"", m_subject.substr(startPos).c_str());
384 else
385 CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character at the end of string");
386 return -1;
387 }
388#endif
389 case PCRE_ERROR_BADUTF8:
390 {
391 const size_t startPos = (m_iOvector[0] > fragmentLen) ? CUtf8Utils::RFindValidUtf8Char(m_subject, m_iOvector[0] - fragmentLen) : 0;
392 if (m_iOvector[0] >= 0 && startPos != std::string::npos)
393 CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character, error code: %d, position: %d. Text before bad char: \"%s\"", m_iOvector[1], m_iOvector[0], m_subject.substr(startPos, m_iOvector[0] - startPos + 1).c_str());
394 else
395 CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character, error code: %d, position: %d", m_iOvector[1], m_iOvector[0]);
396 return -1;
397 }
398 case PCRE_ERROR_BADUTF8_OFFSET:
399 CLog::Log(LOGERROR, "PCRE: Offset is pointing to the middle of UTF-8 character");
400 return -1;
401
402 default:
403 CLog::Log(LOGERROR, "PCRE: Unknown error: %d", rc);
404 return -1;
405 }
406 }
407 m_offset = startoffset;
408 m_bMatched = true;
409 m_iMatchCount = rc;
410 return m_iOvector[0] + m_offset;
411}
412
413int CRegExp::GetCaptureTotal() const
414{
415 int c = -1;
416 if (m_re)
417 pcre_fullinfo(m_re, NULL, PCRE_INFO_CAPTURECOUNT, &c);
418 return c;
419}
420
421std::string CRegExp::GetReplaceString(const std::string& sReplaceExp) const
422{
423 if (!m_bMatched || sReplaceExp.empty())
424 return "";
425
426 const char* const expr = sReplaceExp.c_str();
427
428 size_t pos = sReplaceExp.find_first_of("\\&");
429 std::string result(sReplaceExp, 0, pos);
430 result.reserve(sReplaceExp.size()); // very rough estimate
431
432 while(pos != std::string::npos)
433 {
434 if (expr[pos] == '\\')
435 {
436 // string is null-terminated and current char isn't null, so it's safe to advance to next char
437 pos++; // advance to next char
438 const char nextChar = expr[pos];
439 if (nextChar == '&' || nextChar == '\\')
440 { // this is "\&" or "\\" combination
441 result.push_back(nextChar); // add '&' or '\' to result
442 pos++;
443 }
444 else if (isdigit(nextChar))
445 { // this is "\0" - "\9" combination
446 int subNum = nextChar - '0';
447 pos++; // advance to second next char
448 const char secondNextChar = expr[pos];
449 if (isdigit(secondNextChar))
450 { // this is "\00" - "\99" combination
451 subNum = subNum * 10 + (secondNextChar - '0');
452 pos++;
453 }
454 result.append(GetMatch(subNum));
455 }
456 }
457 else
458 { // '&' char
459 result.append(GetMatch(0));
460 pos++;
461 }
462
463 const size_t nextPos = sReplaceExp.find_first_of("\\&", pos);
464 result.append(sReplaceExp, pos, nextPos - pos);
465 pos = nextPos;
466 }
467
468 return result;
469}
470
471int CRegExp::GetSubStart(int iSub) const
472{
473 if (!IsValidSubNumber(iSub))
474 return -1;
475
476 return m_iOvector[iSub*2] + m_offset;
477}
478
479int CRegExp::GetSubStart(const std::string& subName) const
480{
481 return GetSubStart(GetNamedSubPatternNumber(subName.c_str()));
482}
483
484int CRegExp::GetSubLength(int iSub) const
485{
486 if (!IsValidSubNumber(iSub))
487 return -1;
488
489 return m_iOvector[(iSub*2)+1] - m_iOvector[(iSub*2)];
490}
491
492int CRegExp::GetSubLength(const std::string& subName) const
493{
494 return GetSubLength(GetNamedSubPatternNumber(subName.c_str()));
495}
496
497std::string CRegExp::GetMatch(int iSub /* = 0 */) const
498{
499 if (!IsValidSubNumber(iSub))
500 return "";
501
502 int pos = m_iOvector[(iSub*2)];
503 int len = m_iOvector[(iSub*2)+1] - pos;
504 if (pos < 0 || len <= 0)
505 return "";
506
507 return m_subject.substr(pos, len);
508}
509
510std::string CRegExp::GetMatch(const std::string& subName) const
511{
512 return GetMatch(GetNamedSubPatternNumber(subName.c_str()));
513}
514
515bool CRegExp::GetNamedSubPattern(const char* strName, std::string& strMatch) const
516{
517 strMatch.clear();
518 int iSub = pcre_get_stringnumber(m_re, strName);
519 if (!IsValidSubNumber(iSub))
520 return false;
521 strMatch = GetMatch(iSub);
522 return true;
523}
524
525int CRegExp::GetNamedSubPatternNumber(const char* strName) const
526{
527 return pcre_get_stringnumber(m_re, strName);
528}
529
530void CRegExp::DumpOvector(int iLog /* = LOGDEBUG */)
531{
532 if (iLog < LOGDEBUG || iLog > LOGNONE)
533 return;
534
535 std::string str = "{";
536 int size = GetSubCount(); // past the subpatterns is junk
537 for (int i = 0; i <= size; i++)
538 {
539 std::string t = StringUtils::Format("[%i,%i]", m_iOvector[(i*2)], m_iOvector[(i*2)+1]);
540 if (i != size)
541 t += ",";
542 str += t;
543 }
544 str += "}";
545 CLog::Log(iLog, "regexp ovector=%s", str.c_str());
546}
547
548void CRegExp::Cleanup()
549{
550 if (m_re)
551 {
552 pcre_free(m_re);
553 m_re = NULL;
554 }
555
556 if (m_sd)
557 {
558 pcre_free_study(m_sd);
559 m_sd = NULL;
560 }
561
562#ifdef PCRE_HAS_JIT_CODE
563 if (m_jitStack)
564 {
565 pcre_jit_stack_free(m_jitStack);
566 m_jitStack = NULL;
567 }
568#endif
569}
570
571inline bool CRegExp::IsValidSubNumber(int iSub) const
572{
573 return iSub >= 0 && iSub <= m_iMatchCount && iSub <= m_MaxNumOfBackrefrences;
574}
575
576
577bool CRegExp::IsUtf8Supported(void)
578{
579 if (m_Utf8Supported == -1)
580 {
581 if (pcre_config(PCRE_CONFIG_UTF8, &m_Utf8Supported) != 0)
582 m_Utf8Supported = 0;
583 }
584
585 return m_Utf8Supported == 1;
586}
587
588bool CRegExp::AreUnicodePropertiesSupported(void)
589{
590#if defined(PCRE_CONFIG_UNICODE_PROPERTIES) && PCRE_UCP != 0
591 if (m_UcpSupported == -1)
592 {
593 if (pcre_config(PCRE_CONFIG_UNICODE_PROPERTIES, &m_UcpSupported) != 0)
594 m_UcpSupported = 0;
595 }
596#endif
597
598 return m_UcpSupported == 1;
599}
600
601bool CRegExp::LogCheckUtf8Support(void)
602{
603 bool utf8FullSupport = true;
604
605 if (!CRegExp::IsUtf8Supported())
606 {
607 utf8FullSupport = false;
608 CLog::Log(LOGWARNING, "UTF-8 is not supported in PCRE lib, support for national symbols is limited!");
609 }
610
611 if (!CRegExp::AreUnicodePropertiesSupported())
612 {
613 utf8FullSupport = false;
614 CLog::Log(LOGWARNING, "Unicode properties are not enabled in PCRE lib, support for national symbols may be limited!");
615 }
616
617 if (!utf8FullSupport)
618 {
619 CLog::Log(LOGINFO,
620 "Consider installing PCRE lib version 8.10 or later with enabled Unicode properties "
621 "and UTF-8 support. Your PCRE lib version: %s",
622 PCRE::pcre_version());
623#if PCRE_UCP == 0
624 CLog::Log(LOGINFO, "You will need to rebuild XBMC after PCRE lib update.");
625#endif
626 }
627
628 return utf8FullSupport;
629}
630
631bool CRegExp::IsJitSupported(void)
632{
633 if (m_JitSupported == -1)
634 {
635#ifdef PCRE_HAS_JIT_CODE
636 if (pcre_config(PCRE_CONFIG_JIT, &m_JitSupported) != 0)
637#endif
638 m_JitSupported = 0;
639 }
640
641 return m_JitSupported == 1;
642}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11//! @todo - move to std::regex (after switching to gcc 4.9 or higher) and get rid of CRegExp
12
13#include <string>
14#include <vector>
15
16/* make sure stdlib.h is included before including pcre.h inside the
17 namespace; this works around stdlib.h definitions also living in
18 the PCRE namespace */
19#include <stdlib.h>
20
21namespace PCRE {
22struct real_pcre_jit_stack; // forward declaration for PCRE without JIT
23typedef struct real_pcre_jit_stack pcre_jit_stack;
24#include <pcre.h>
25}
26
27class CRegExp
28{
29public:
30 enum studyMode
31 {
32 NoStudy = 0, // do not study expression
33 StudyRegExp = 1, // study expression (slower compilation, faster find)
34 StudyWithJitComp // study expression and JIT-compile it, if possible (heavyweight optimization)
35 };
36 enum utf8Mode
37 {
38 autoUtf8 = -1, // analyze regexp for UTF-8 multi-byte chars, for Unicode codes > 0xFF
39 // or explicit Unicode properties (\p, \P and \X), enable UTF-8 mode if any of them are found
40 asciiOnly = 0, // process regexp and strings as single-byte encoded strings
41 forceUtf8 = 1 // enable UTF-8 mode (with Unicode properties)
42 };
43
44 static const int m_MaxNumOfBackrefrences = 20;
45 /**
46 * @param caseless (optional) Matching will be case insensitive if set to true
47 * or case sensitive if set to false
48 * @param utf8 (optional) Control UTF-8 processing
49 */
50 CRegExp(bool caseless = false, utf8Mode utf8 = asciiOnly);
51 /**
52 * Create new CRegExp object and compile regexp expression in one step
53 * @warning Use only with hardcoded regexp when you're sure that regexp is compiled without errors
54 * @param caseless Matching will be case insensitive if set to true
55 * or case sensitive if set to false
56 * @param utf8 Control UTF-8 processing
57 * @param re The regular expression
58 * @param study (optional) Controls study of expression, useful if expression will be used
59 * several times
60 */
61 CRegExp(bool caseless, utf8Mode utf8, const char *re, studyMode study = NoStudy);
62
63 CRegExp(const CRegExp& re);
64 ~CRegExp();
65
66 /**
67 * Compile (prepare) regular expression
68 * @param re The regular expression
69 * @param study (optional) Controls study of expression, useful if expression will be used
70 * several times
71 * @return true on success, false on any error
72 */
73 bool RegComp(const char *re, studyMode study = NoStudy);
74
75 /**
76 * Compile (prepare) regular expression
77 * @param re The regular expression
78 * @param study (optional) Controls study of expression, useful if expression will be used
79 * several times
80 * @return true on success, false on any error
81 */
82 bool RegComp(const std::string& re, studyMode study = NoStudy)
83 { return RegComp(re.c_str(), study); }
84
85 /**
86 * Find first match of regular expression in given string
87 * @param str The string to match against regular expression
88 * @param startoffset (optional) The string offset to start matching
89 * @param maxNumberOfCharsToTest (optional) The maximum number of characters to test (match) in
90 * string. If set to -1 string checked up to the end.
91 * @return staring position of match in string, negative value in case of error or no match
92 */
93 int RegFind(const char* str, unsigned int startoffset = 0, int maxNumberOfCharsToTest = -1);
94 /**
95 * Find first match of regular expression in given string
96 * @param str The string to match against regular expression
97 * @param startoffset (optional) The string offset to start matching
98 * @param maxNumberOfCharsToTest (optional) The maximum number of characters to test (match) in
99 * string. If set to -1 string checked up to the end.
100 * @return staring position of match in string, negative value in case of error or no match
101 */
102 int RegFind(const std::string& str, unsigned int startoffset = 0, int maxNumberOfCharsToTest = -1)
103 { return PrivateRegFind(str.length(), str.c_str(), startoffset, maxNumberOfCharsToTest); }
104 std::string GetReplaceString(const std::string& sReplaceExp) const;
105 int GetFindLen() const
106 {
107 if (!m_re || !m_bMatched)
108 return 0;
109
110 return (m_iOvector[1] - m_iOvector[0]);
111 };
112 int GetSubCount() const { return m_iMatchCount - 1; } // PCRE returns the number of sub-patterns + 1
113 int GetSubStart(int iSub) const;
114 int GetSubStart(const std::string& subName) const;
115 int GetSubLength(int iSub) const;
116 int GetSubLength(const std::string& subName) const;
117 int GetCaptureTotal() const;
118 std::string GetMatch(int iSub = 0) const;
119 std::string GetMatch(const std::string& subName) const;
120 const std::string& GetPattern() const { return m_pattern; }
121 bool GetNamedSubPattern(const char* strName, std::string& strMatch) const;
122 int GetNamedSubPatternNumber(const char* strName) const;
123 void DumpOvector(int iLog);
124 /**
125 * Check is RegExp object is ready for matching
126 * @return true if RegExp object is ready for matching, false otherwise
127 */
128 inline bool IsCompiled(void) const
129 { return !m_pattern.empty(); }
130 CRegExp& operator= (const CRegExp& re);
131 static bool IsUtf8Supported(void);
132 static bool AreUnicodePropertiesSupported(void);
133 static bool LogCheckUtf8Support(void);
134 static bool IsJitSupported(void);
135
136private:
137 int PrivateRegFind(size_t bufferLen, const char *str, unsigned int startoffset = 0, int maxNumberOfCharsToTest = -1);
138 void InitValues(bool caseless = false, CRegExp::utf8Mode utf8 = asciiOnly);
139 static bool requireUtf8(const std::string& regexp);
140 static int readCharXCode(const std::string& regexp, size_t& pos);
141 static bool isCharClassWithUnicode(const std::string& regexp, size_t& pos);
142
143 void Cleanup();
144 inline bool IsValidSubNumber(int iSub) const;
145
146 PCRE::pcre* m_re;
147 PCRE::pcre_extra* m_sd;
148 static const int OVECCOUNT=(m_MaxNumOfBackrefrences + 1) * 3;
149 unsigned int m_offset;
150 int m_iOvector[OVECCOUNT];
151 utf8Mode m_utf8Mode;
152 int m_iMatchCount;
153 int m_iOptions;
154 bool m_jitCompiled;
155 bool m_bMatched;
156 PCRE::pcre_jit_stack* m_jitStack;
157 std::string m_subject;
158 std::string m_pattern;
159 static int m_Utf8Supported;
160 static int m_UcpSupported;
161 static int m_JitSupported;
162};
163
164typedef std::vector<CRegExp> VECCREGEXP;
165
diff --git a/xbmc/utils/RingBuffer.cpp b/xbmc/utils/RingBuffer.cpp
new file mode 100644
index 0000000..f44ab57
--- /dev/null
+++ b/xbmc/utils/RingBuffer.cpp
@@ -0,0 +1,246 @@
1/*
2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "RingBuffer.h"
10
11#include "threads/SingleLock.h"
12
13#include <algorithm>
14#include <cstdlib>
15#include <cstring>
16
17/* Constructor */
18CRingBuffer::CRingBuffer()
19{
20 m_buffer = NULL;
21 m_size = 0;
22 m_readPtr = 0;
23 m_writePtr = 0;
24 m_fillCount = 0;
25}
26
27/* Destructor */
28CRingBuffer::~CRingBuffer()
29{
30 Destroy();
31}
32
33/* Create a ring buffer with the specified 'size' */
34bool CRingBuffer::Create(unsigned int size)
35{
36 CSingleLock lock(m_critSection);
37 m_buffer = (char*)malloc(size);
38 if (m_buffer != NULL)
39 {
40 m_size = size;
41 return true;
42 }
43 return false;
44}
45
46/* Free the ring buffer and set all values to NULL or 0 */
47void CRingBuffer::Destroy()
48{
49 CSingleLock lock(m_critSection);
50 if (m_buffer != NULL)
51 {
52 free(m_buffer);
53 m_buffer = NULL;
54 }
55 m_size = 0;
56 m_readPtr = 0;
57 m_writePtr = 0;
58 m_fillCount = 0;
59}
60
61/* Clear the ring buffer */
62void CRingBuffer::Clear()
63{
64 CSingleLock lock(m_critSection);
65 m_readPtr = 0;
66 m_writePtr = 0;
67 m_fillCount = 0;
68}
69
70/* Read in data from the ring buffer to the supplied buffer 'buf'. The amount
71 * read in is specified by 'size'.
72 */
73bool CRingBuffer::ReadData(char *buf, unsigned int size)
74{
75 CSingleLock lock(m_critSection);
76 if (size > m_fillCount)
77 {
78 return false;
79 }
80 if (size + m_readPtr > m_size)
81 {
82 unsigned int chunk = m_size - m_readPtr;
83 memcpy(buf, m_buffer + m_readPtr, chunk);
84 memcpy(buf + chunk, m_buffer, size - chunk);
85 m_readPtr = size - chunk;
86 }
87 else
88 {
89 memcpy(buf, m_buffer + m_readPtr, size);
90 m_readPtr += size;
91 }
92 if (m_readPtr == m_size)
93 m_readPtr = 0;
94 m_fillCount -= size;
95 return true;
96}
97
98/* Read in data from the ring buffer to another ring buffer object specified by
99 * 'rBuf'.
100 */
101bool CRingBuffer::ReadData(CRingBuffer &rBuf, unsigned int size)
102{
103 CSingleLock lock(m_critSection);
104 if (rBuf.getBuffer() == NULL)
105 rBuf.Create(size);
106
107 bool bOk = size <= rBuf.getMaxWriteSize() && size <= getMaxReadSize();
108 if (bOk)
109 {
110 unsigned int chunksize = std::min(size, m_size - m_readPtr);
111 bOk = rBuf.WriteData(&getBuffer()[m_readPtr], chunksize);
112 if (bOk && chunksize < size)
113 bOk = rBuf.WriteData(&getBuffer()[0], size - chunksize);
114 if (bOk)
115 SkipBytes(size);
116 }
117
118 return bOk;
119}
120
121/* Write data to ring buffer from buffer specified in 'buf'. Amount read in is
122 * specified by 'size'.
123 */
124bool CRingBuffer::WriteData(const char *buf, unsigned int size)
125{
126 CSingleLock lock(m_critSection);
127 if (size > m_size - m_fillCount)
128 {
129 return false;
130 }
131 if (size + m_writePtr > m_size)
132 {
133 unsigned int chunk = m_size - m_writePtr;
134 memcpy(m_buffer + m_writePtr, buf, chunk);
135 memcpy(m_buffer, buf + chunk, size - chunk);
136 m_writePtr = size - chunk;
137 }
138 else
139 {
140 memcpy(m_buffer + m_writePtr, buf, size);
141 m_writePtr += size;
142 }
143 if (m_writePtr == m_size)
144 m_writePtr = 0;
145 m_fillCount += size;
146 return true;
147}
148
149/* Write data to ring buffer from another ring buffer object specified by
150 * 'rBuf'.
151 */
152bool CRingBuffer::WriteData(CRingBuffer &rBuf, unsigned int size)
153{
154 CSingleLock lock(m_critSection);
155 if (m_buffer == NULL)
156 Create(size);
157
158 bool bOk = size <= rBuf.getMaxReadSize() && size <= getMaxWriteSize();
159 if (bOk)
160 {
161 unsigned int readpos = rBuf.getReadPtr();
162 unsigned int chunksize = std::min(size, rBuf.getSize() - readpos);
163 bOk = WriteData(&rBuf.getBuffer()[readpos], chunksize);
164 if (bOk && chunksize < size)
165 bOk = WriteData(&rBuf.getBuffer()[0], size - chunksize);
166 }
167
168 return bOk;
169}
170
171/* Skip bytes in buffer to be read */
172bool CRingBuffer::SkipBytes(int skipSize)
173{
174 CSingleLock lock(m_critSection);
175 if (skipSize < 0)
176 {
177 return false; // skipping backwards is not supported
178 }
179
180 unsigned int size = skipSize;
181 if (size > m_fillCount)
182 {
183 return false;
184 }
185 if (size + m_readPtr > m_size)
186 {
187 unsigned int chunk = m_size - m_readPtr;
188 m_readPtr = size - chunk;
189 }
190 else
191 {
192 m_readPtr += size;
193 }
194 if (m_readPtr == m_size)
195 m_readPtr = 0;
196 m_fillCount -= size;
197 return true;
198}
199
200/* Append all content from ring buffer 'rBuf' to this ring buffer */
201bool CRingBuffer::Append(CRingBuffer &rBuf)
202{
203 return WriteData(rBuf, rBuf.getMaxReadSize());
204}
205
206/* Copy all content from ring buffer 'rBuf' to this ring buffer overwriting any existing data */
207bool CRingBuffer::Copy(CRingBuffer &rBuf)
208{
209 Clear();
210 return Append(rBuf);
211}
212
213/* Our various 'get' methods */
214char *CRingBuffer::getBuffer()
215{
216 return m_buffer;
217}
218
219unsigned int CRingBuffer::getSize()
220{
221 CSingleLock lock(m_critSection);
222 return m_size;
223}
224
225unsigned int CRingBuffer::getReadPtr() const
226{
227 return m_readPtr;
228}
229
230unsigned int CRingBuffer::getWritePtr()
231{
232 CSingleLock lock(m_critSection);
233 return m_writePtr;
234}
235
236unsigned int CRingBuffer::getMaxReadSize()
237{
238 CSingleLock lock(m_critSection);
239 return m_fillCount;
240}
241
242unsigned int CRingBuffer::getMaxWriteSize()
243{
244 CSingleLock lock(m_critSection);
245 return m_size - m_fillCount;
246}
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 @@
1/*
2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "threads/CriticalSection.h"
12
13class CRingBuffer
14{
15 CCriticalSection m_critSection;
16 char *m_buffer;
17 unsigned int m_size;
18 unsigned int m_readPtr;
19 unsigned int m_writePtr;
20 unsigned int m_fillCount;
21public:
22 CRingBuffer();
23 ~CRingBuffer();
24 bool Create(unsigned int size);
25 void Destroy();
26 void Clear();
27 bool ReadData(char *buf, unsigned int size);
28 bool ReadData(CRingBuffer &rBuf, unsigned int size);
29 bool WriteData(const char *buf, unsigned int size);
30 bool WriteData(CRingBuffer &rBuf, unsigned int size);
31 bool SkipBytes(int skipSize);
32 bool Append(CRingBuffer &rBuf);
33 bool Copy(CRingBuffer &rBuf);
34 char *getBuffer();
35 unsigned int getSize();
36 unsigned int getReadPtr() const;
37 unsigned int getWritePtr();
38 unsigned int getMaxReadSize();
39 unsigned int getMaxWriteSize();
40};
diff --git a/xbmc/utils/RssManager.cpp b/xbmc/utils/RssManager.cpp
new file mode 100644
index 0000000..2e26b4e
--- /dev/null
+++ b/xbmc/utils/RssManager.cpp
@@ -0,0 +1,198 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "RssManager.h"
10
11#include "ServiceBroker.h"
12#include "addons/AddonInstaller.h"
13#include "addons/AddonManager.h"
14#include "filesystem/File.h"
15#include "interfaces/builtins/Builtins.h"
16#include "messaging/helpers/DialogHelper.h"
17#include "profiles/ProfileManager.h"
18#include "settings/Settings.h"
19#include "settings/SettingsComponent.h"
20#include "settings/lib/Setting.h"
21#include "threads/SingleLock.h"
22#include "utils/RssReader.h"
23#include "utils/StringUtils.h"
24#include "utils/Variant.h"
25#include "utils/log.h"
26
27#include <utility>
28
29using namespace XFILE;
30using namespace KODI::MESSAGING;
31
32
33CRssManager::CRssManager()
34{
35 m_bActive = false;
36}
37
38CRssManager::~CRssManager()
39{
40 Stop();
41}
42
43CRssManager& CRssManager::GetInstance()
44{
45 static CRssManager sRssManager;
46 return sRssManager;
47}
48
49void CRssManager::OnSettingsLoaded()
50{
51 Load();
52}
53
54void CRssManager::OnSettingsUnloaded()
55{
56 Clear();
57}
58
59void CRssManager::OnSettingAction(std::shared_ptr<const CSetting> setting)
60{
61 if (setting == NULL)
62 return;
63
64 const std::string &settingId = setting->GetId();
65 if (settingId == CSettings::SETTING_LOOKANDFEEL_RSSEDIT)
66 {
67 ADDON::AddonPtr addon;
68 if (!CServiceBroker::GetAddonMgr().GetAddon("script.rss.editor", addon))
69 {
70 if (!CAddonInstaller::GetInstance().InstallModal("script.rss.editor", addon))
71 return;
72 }
73 CBuiltins::GetInstance().Execute("RunScript(script.rss.editor)");
74 }
75}
76
77void CRssManager::Start()
78 {
79 m_bActive = true;
80}
81
82void CRssManager::Stop()
83{
84 CSingleLock lock(m_critical);
85 m_bActive = false;
86 for (unsigned int i = 0; i < m_readers.size(); i++)
87 {
88 if (m_readers[i].reader)
89 delete m_readers[i].reader;
90 }
91 m_readers.clear();
92}
93
94bool CRssManager::Load()
95{
96 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
97
98 CSingleLock lock(m_critical);
99
100 std::string rssXML = profileManager->GetUserDataItem("RssFeeds.xml");
101 if (!CFile::Exists(rssXML))
102 return false;
103
104 CXBMCTinyXML rssDoc;
105 if (!rssDoc.LoadFile(rssXML))
106 {
107 CLog::Log(LOGERROR, "CRssManager: error loading %s, Line %d\n%s", rssXML.c_str(), rssDoc.ErrorRow(), rssDoc.ErrorDesc());
108 return false;
109 }
110
111 const TiXmlElement *pRootElement = rssDoc.RootElement();
112 if (pRootElement == NULL || !StringUtils::EqualsNoCase(pRootElement->ValueStr(), "rssfeeds"))
113 {
114 CLog::Log(LOGERROR, "CRssManager: error loading %s, no <rssfeeds> node", rssXML.c_str());
115 return false;
116 }
117
118 m_mapRssUrls.clear();
119 const TiXmlElement* pSet = pRootElement->FirstChildElement("set");
120 while (pSet != NULL)
121 {
122 int iId;
123 if (pSet->QueryIntAttribute("id", &iId) == TIXML_SUCCESS)
124 {
125 RssSet set;
126 set.rtl = pSet->Attribute("rtl") != NULL &&
127 StringUtils::CompareNoCase(pSet->Attribute("rtl"), "true") == 0;
128 const TiXmlElement* pFeed = pSet->FirstChildElement("feed");
129 while (pFeed != NULL)
130 {
131 int iInterval;
132 if (pFeed->QueryIntAttribute("updateinterval", &iInterval) != TIXML_SUCCESS)
133 {
134 iInterval = 30; // default to 30 min
135 CLog::Log(LOGDEBUG, "CRssManager: no interval set, default to 30!");
136 }
137
138 if (pFeed->FirstChild() != NULL)
139 {
140 //! @todo UTF-8: Do these URLs need to be converted to UTF-8?
141 //! What about the xml encoding?
142 std::string strUrl = pFeed->FirstChild()->ValueStr();
143 set.url.push_back(strUrl);
144 set.interval.push_back(iInterval);
145 }
146 pFeed = pFeed->NextSiblingElement("feed");
147 }
148
149 m_mapRssUrls.insert(std::make_pair(iId,set));
150 }
151 else
152 CLog::Log(LOGERROR, "CRssManager: found rss url set with no id in RssFeeds.xml, ignored");
153
154 pSet = pSet->NextSiblingElement("set");
155 }
156
157 return true;
158}
159
160bool CRssManager::Reload()
161{
162 Stop();
163 if (!Load())
164 return false;
165 Start();
166
167 return true;
168}
169
170void CRssManager::Clear()
171{
172 CSingleLock lock(m_critical);
173 m_mapRssUrls.clear();
174}
175
176// returns true if the reader doesn't need creating, false otherwise
177bool CRssManager::GetReader(int controlID, int windowID, IRssObserver* observer, CRssReader *&reader)
178{
179 CSingleLock lock(m_critical);
180 // check to see if we've already created this reader
181 for (unsigned int i = 0; i < m_readers.size(); i++)
182 {
183 if (m_readers[i].controlID == controlID && m_readers[i].windowID == windowID)
184 {
185 reader = m_readers[i].reader;
186 reader->SetObserver(observer);
187 reader->UpdateObserver();
188 return true;
189 }
190 }
191 // need to create a new one
192 READERCONTROL readerControl;
193 readerControl.controlID = controlID;
194 readerControl.windowID = windowID;
195 reader = readerControl.reader = new CRssReader;
196 m_readers.push_back(readerControl);
197 return false;
198}
diff --git a/xbmc/utils/RssManager.h b/xbmc/utils/RssManager.h
new file mode 100644
index 0000000..399cfa4
--- /dev/null
+++ b/xbmc/utils/RssManager.h
@@ -0,0 +1,68 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "settings/lib/ISettingCallback.h"
12#include "settings/lib/ISettingsHandler.h"
13#include "threads/CriticalSection.h"
14
15#include <map>
16#include <string>
17#include <vector>
18
19class CRssReader;
20class IRssObserver;
21
22typedef struct
23{
24 bool rtl;
25 std::vector<int> interval;
26 std::vector<std::string> url;
27} RssSet;
28typedef std::map<int, RssSet> RssUrls;
29
30class CRssManager : public ISettingCallback, public ISettingsHandler
31{
32public:
33 static CRssManager& GetInstance();
34
35 void OnSettingsLoaded() override;
36 void OnSettingsUnloaded() override;
37
38 void OnSettingAction(std::shared_ptr<const CSetting> setting) override;
39
40 void Start();
41 void Stop();
42 bool Load();
43 bool Reload();
44 void Clear();
45 bool IsActive() const { return m_bActive; }
46
47 bool GetReader(int controlID, int windowID, IRssObserver* observer, CRssReader *&reader);
48 const RssUrls& GetUrls() const { return m_mapRssUrls; }
49
50protected:
51 CRssManager();
52 ~CRssManager() override;
53
54private:
55 CRssManager(const CRssManager&) = delete;
56 CRssManager& operator=(const CRssManager&) = delete;
57 struct READERCONTROL
58 {
59 int controlID;
60 int windowID;
61 CRssReader *reader;
62 };
63
64 std::vector<READERCONTROL> m_readers;
65 RssUrls m_mapRssUrls;
66 bool m_bActive;
67 CCriticalSection m_critical;
68};
diff --git a/xbmc/utils/RssReader.cpp b/xbmc/utils/RssReader.cpp
new file mode 100644
index 0000000..ecebc93
--- /dev/null
+++ b/xbmc/utils/RssReader.cpp
@@ -0,0 +1,413 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "RssReader.h"
10
11#include "CharsetConverter.h"
12#include "ServiceBroker.h"
13#include "URL.h"
14#include "filesystem/CurlFile.h"
15#include "filesystem/File.h"
16#include "guilib/GUIRSSControl.h"
17#include "guilib/LocalizeStrings.h"
18#include "log.h"
19#include "network/Network.h"
20#include "settings/AdvancedSettings.h"
21#include "settings/SettingsComponent.h"
22#include "threads/SingleLock.h"
23#include "threads/SystemClock.h"
24#include "utils/HTMLUtil.h"
25#include "utils/XTimeUtils.h"
26
27#define RSS_COLOR_BODY 0
28#define RSS_COLOR_HEADLINE 1
29#define RSS_COLOR_CHANNEL 2
30
31using namespace XFILE;
32
33//////////////////////////////////////////////////////////////////////
34// Construction/Destruction
35//////////////////////////////////////////////////////////////////////
36
37CRssReader::CRssReader() : CThread("RSSReader")
38{
39 m_pObserver = NULL;
40 m_spacesBetweenFeeds = 0;
41 m_bIsRunning = false;
42 m_savedScrollPixelPos = 0;
43 m_rtlText = false;
44 m_requestRefresh = false;
45}
46
47CRssReader::~CRssReader()
48{
49 if (m_pObserver)
50 m_pObserver->OnFeedRelease();
51 StopThread();
52 for (unsigned int i = 0; i < m_vecTimeStamps.size(); i++)
53 delete m_vecTimeStamps[i];
54}
55
56void CRssReader::Create(IRssObserver* aObserver, const std::vector<std::string>& aUrls, const std::vector<int> &times, int spacesBetweenFeeds, bool rtl)
57{
58 CSingleLock lock(m_critical);
59
60 m_pObserver = aObserver;
61 m_spacesBetweenFeeds = spacesBetweenFeeds;
62 m_vecUrls = aUrls;
63 m_strFeed.resize(aUrls.size());
64 m_strColors.resize(aUrls.size());
65 // set update times
66 m_vecUpdateTimes = times;
67 m_rtlText = rtl;
68 m_requestRefresh = false;
69
70 // update each feed on creation
71 for (unsigned int i = 0; i < m_vecUpdateTimes.size(); ++i)
72 {
73 AddToQueue(i);
74 KODI::TIME::SystemTime* time = new KODI::TIME::SystemTime;
75 KODI::TIME::GetLocalTime(time);
76 m_vecTimeStamps.push_back(time);
77 }
78}
79
80void CRssReader::requestRefresh()
81{
82 m_requestRefresh = true;
83}
84
85void CRssReader::AddToQueue(int iAdd)
86{
87 CSingleLock lock(m_critical);
88 if (iAdd < (int)m_vecUrls.size())
89 m_vecQueue.push_back(iAdd);
90 if (!m_bIsRunning)
91 {
92 StopThread();
93 m_bIsRunning = true;
94 CThread::Create(false);
95 }
96}
97
98void CRssReader::OnExit()
99{
100 m_bIsRunning = false;
101}
102
103int CRssReader::GetQueueSize()
104{
105 CSingleLock lock(m_critical);
106 return m_vecQueue.size();
107}
108
109void CRssReader::Process()
110{
111 while (GetQueueSize())
112 {
113 CSingleLock lock(m_critical);
114
115 int iFeed = m_vecQueue.front();
116 m_vecQueue.erase(m_vecQueue.begin());
117
118 m_strFeed[iFeed].clear();
119 m_strColors[iFeed].clear();
120
121 CCurlFile http;
122 http.SetUserAgent(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_userAgent);
123 http.SetTimeout(2);
124 std::string strXML;
125 std::string strUrl = m_vecUrls[iFeed];
126 lock.Leave();
127
128 int nRetries = 3;
129 CURL url(strUrl);
130 std::string fileCharset;
131
132 // we wait for the network to come up
133 if ((url.IsProtocol("http") || url.IsProtocol("https")) &&
134 !CServiceBroker::GetNetwork().IsAvailable())
135 {
136 CLog::Log(LOGWARNING, "RSS: No network connection");
137 strXML = "<rss><item><title>"+g_localizeStrings.Get(15301)+"</title></item></rss>";
138 }
139 else
140 {
141 XbmcThreads::EndTime timeout(15000);
142 while (!m_bStop && nRetries > 0)
143 {
144 if (timeout.IsTimePast())
145 {
146 CLog::Log(LOGERROR, "Timeout while retrieving rss feed: %s", strUrl.c_str());
147 break;
148 }
149 nRetries--;
150
151 if (!url.IsProtocol("http") && !url.IsProtocol("https"))
152 {
153 CFile file;
154 auto_buffer buffer;
155 if (file.LoadFile(strUrl, buffer) > 0)
156 {
157 strXML.assign(buffer.get(), buffer.length());
158 break;
159 }
160 }
161 else
162 {
163 if (http.Get(strUrl, strXML))
164 {
165 fileCharset = http.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET);
166 CLog::Log(LOGDEBUG, "Got rss feed: %s", strUrl.c_str());
167 break;
168 }
169 else if (nRetries > 0)
170 CThread::Sleep(5000); // Network problems? Retry, but not immediately.
171 else
172 CLog::Log(LOGERROR, "Unable to obtain rss feed: %s", strUrl.c_str());
173 }
174 }
175 http.Cancel();
176 }
177 if (!strXML.empty() && m_pObserver)
178 {
179 // erase any <content:encoded> tags (also unsupported by tinyxml)
180 size_t iStart = strXML.find("<content:encoded>");
181 size_t iEnd = 0;
182 while (iStart != std::string::npos)
183 {
184 // get <content:encoded> end position
185 iEnd = strXML.find("</content:encoded>", iStart) + 18;
186
187 // erase the section
188 strXML = strXML.erase(iStart, iEnd - iStart);
189
190 iStart = strXML.find("<content:encoded>");
191 }
192
193 if (Parse(strXML, iFeed, fileCharset))
194 CLog::Log(LOGDEBUG, "Parsed rss feed: %s", strUrl.c_str());
195 }
196 }
197 UpdateObserver();
198}
199
200void CRssReader::getFeed(vecText &text)
201{
202 text.clear();
203 // double the spaces at the start of the set
204 for (int j = 0; j < m_spacesBetweenFeeds; j++)
205 text.push_back(L' ');
206 for (unsigned int i = 0; i < m_strFeed.size(); i++)
207 {
208 for (int j = 0; j < m_spacesBetweenFeeds; j++)
209 text.push_back(L' ');
210
211 for (unsigned int j = 0; j < m_strFeed[i].size(); j++)
212 {
213 character_t letter = m_strFeed[i][j] | ((m_strColors[i][j] - 48) << 16);
214 text.push_back(letter);
215 }
216 }
217}
218
219void CRssReader::AddTag(const std::string &aString)
220{
221 m_tagSet.push_back(aString);
222}
223
224void CRssReader::AddString(std::wstring aString, int aColour, int iFeed)
225{
226 if (m_rtlText)
227 m_strFeed[iFeed] = aString + m_strFeed[iFeed];
228 else
229 m_strFeed[iFeed] += aString;
230
231 size_t nStringLength = aString.size();
232
233 for (size_t i = 0;i < nStringLength;i++)
234 aString[i] = static_cast<char>(48 + aColour);
235
236 if (m_rtlText)
237 m_strColors[iFeed] = aString + m_strColors[iFeed];
238 else
239 m_strColors[iFeed] += aString;
240}
241
242void CRssReader::GetNewsItems(TiXmlElement* channelXmlNode, int iFeed)
243{
244 HTML::CHTMLUtil html;
245
246 TiXmlElement * itemNode = channelXmlNode->FirstChildElement("item");
247 std::map<std::string, std::wstring> mTagElements;
248 typedef std::pair<std::string, std::wstring> StrPair;
249 std::list<std::string>::iterator i;
250
251 // Add the title tag in if we didn't pass any tags in at all
252 // Represents default behaviour before configurability
253
254 if (m_tagSet.empty())
255 AddTag("title");
256
257 while (itemNode != nullptr)
258 {
259 TiXmlNode* childNode = itemNode->FirstChild();
260 mTagElements.clear();
261 while (childNode != nullptr)
262 {
263 std::string strName = childNode->ValueStr();
264
265 for (i = m_tagSet.begin(); i != m_tagSet.end(); ++i)
266 {
267 if (!childNode->NoChildren() && *i == strName)
268 {
269 std::string htmlText = childNode->FirstChild()->ValueStr();
270
271 // This usually happens in right-to-left languages where they want to
272 // specify in the RSS body that the text should be RTL.
273 // <title>
274 // <div dir="RTL">��� ����: ���� �� �����</div>
275 // </title>
276 if (htmlText == "div" || htmlText == "span")
277 htmlText = childNode->FirstChild()->FirstChild()->ValueStr();
278
279 std::wstring unicodeText, unicodeText2;
280
281 g_charsetConverter.utf8ToW(htmlText, unicodeText2, m_rtlText);
282 html.ConvertHTMLToW(unicodeText2, unicodeText);
283
284 mTagElements.insert(StrPair(*i, unicodeText));
285 }
286 }
287 childNode = childNode->NextSibling();
288 }
289
290 int rsscolour = RSS_COLOR_HEADLINE;
291 for (i = m_tagSet.begin(); i != m_tagSet.end(); ++i)
292 {
293 std::map<std::string, std::wstring>::iterator j = mTagElements.find(*i);
294
295 if (j == mTagElements.end())
296 continue;
297
298 std::wstring& text = j->second;
299 AddString(text, rsscolour, iFeed);
300 rsscolour = RSS_COLOR_BODY;
301 text = L" - ";
302 AddString(text, rsscolour, iFeed);
303 }
304 itemNode = itemNode->NextSiblingElement("item");
305 }
306}
307
308bool CRssReader::Parse(const std::string& data, int iFeed, const std::string& charset)
309{
310 m_xml.Clear();
311 m_xml.Parse(data, charset);
312
313 CLog::Log(LOGDEBUG, "RSS feed encoding: %s", m_xml.GetUsedCharset().c_str());
314
315 return Parse(iFeed);
316}
317
318bool CRssReader::Parse(int iFeed)
319{
320 TiXmlElement* rootXmlNode = m_xml.RootElement();
321
322 if (!rootXmlNode)
323 return false;
324
325 TiXmlElement* rssXmlNode = NULL;
326
327 std::string strValue = rootXmlNode->ValueStr();
328 if (strValue.find("rss") != std::string::npos ||
329 strValue.find("rdf") != std::string::npos)
330 rssXmlNode = rootXmlNode;
331 else
332 {
333 // Unable to find root <rss> or <rdf> node
334 return false;
335 }
336
337 TiXmlElement* channelXmlNode = rssXmlNode->FirstChildElement("channel");
338 if (channelXmlNode)
339 {
340 TiXmlElement* titleNode = channelXmlNode->FirstChildElement("title");
341 if (titleNode && !titleNode->NoChildren())
342 {
343 std::string strChannel = titleNode->FirstChild()->Value();
344 std::wstring strChannelUnicode;
345 g_charsetConverter.utf8ToW(strChannel, strChannelUnicode, m_rtlText);
346 AddString(strChannelUnicode, RSS_COLOR_CHANNEL, iFeed);
347
348 AddString(L":", RSS_COLOR_CHANNEL, iFeed);
349 AddString(L" ", RSS_COLOR_CHANNEL, iFeed);
350 }
351
352 GetNewsItems(channelXmlNode,iFeed);
353 }
354
355 GetNewsItems(rssXmlNode,iFeed);
356
357 // avoid trailing ' - '
358 if (m_strFeed[iFeed].size() > 3 && m_strFeed[iFeed].substr(m_strFeed[iFeed].size() - 3) == L" - ")
359 {
360 if (m_rtlText)
361 {
362 m_strFeed[iFeed].erase(0, 3);
363 m_strColors[iFeed].erase(0, 3);
364 }
365 else
366 {
367 m_strFeed[iFeed].erase(m_strFeed[iFeed].length() - 3);
368 m_strColors[iFeed].erase(m_strColors[iFeed].length() - 3);
369 }
370 }
371 return true;
372}
373
374void CRssReader::SetObserver(IRssObserver *observer)
375{
376 m_pObserver = observer;
377}
378
379void CRssReader::UpdateObserver()
380{
381 if (!m_pObserver)
382 return;
383
384 vecText feed;
385 getFeed(feed);
386 if (!feed.empty())
387 {
388 CSingleLock lock(CServiceBroker::GetWinSystem()->GetGfxContext());
389 if (m_pObserver) // need to check again when locked to make sure observer wasnt removed
390 m_pObserver->OnFeedUpdate(feed);
391 }
392}
393
394void CRssReader::CheckForUpdates()
395{
396 KODI::TIME::SystemTime time;
397 KODI::TIME::GetLocalTime(&time);
398
399 for (unsigned int i = 0;i < m_vecUpdateTimes.size(); ++i )
400 {
401 if (m_requestRefresh || ((time.day * 24 * 60) + (time.hour * 60) + time.minute) -
402 ((m_vecTimeStamps[i]->day * 24 * 60) +
403 (m_vecTimeStamps[i]->hour * 60) + m_vecTimeStamps[i]->minute) >
404 m_vecUpdateTimes[i])
405 {
406 CLog::Log(LOGDEBUG, "Updating RSS");
407 KODI::TIME::GetLocalTime(m_vecTimeStamps[i]);
408 AddToQueue(i);
409 }
410 }
411
412 m_requestRefresh = false;
413}
diff --git a/xbmc/utils/RssReader.h b/xbmc/utils/RssReader.h
new file mode 100644
index 0000000..6e259ff
--- /dev/null
+++ b/xbmc/utils/RssReader.h
@@ -0,0 +1,63 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "XBDateTime.h"
12#include "threads/CriticalSection.h"
13#include "threads/Thread.h"
14#include "utils/IRssObserver.h"
15#include "utils/XBMCTinyXML.h"
16
17#include <list>
18#include <string>
19#include <vector>
20
21class CRssReader : public CThread
22{
23public:
24 CRssReader();
25 ~CRssReader() override;
26
27 void Create(IRssObserver* aObserver, const std::vector<std::string>& aUrl, const std::vector<int>& times, int spacesBetweenFeeds, bool rtl);
28 bool Parse(const std::string& data, int iFeed, const std::string& charset);
29 void getFeed(vecText &text);
30 void AddTag(const std::string &addTag);
31 void AddToQueue(int iAdd);
32 void UpdateObserver();
33 void SetObserver(IRssObserver* observer);
34 void CheckForUpdates();
35 void requestRefresh();
36 float m_savedScrollPixelPos;
37
38private:
39 void Process() override;
40 bool Parse(int iFeed);
41 void GetNewsItems(TiXmlElement* channelXmlNode, int iFeed);
42 void AddString(std::wstring aString, int aColour, int iFeed);
43 void UpdateFeed();
44 void OnExit() override;
45 int GetQueueSize();
46
47 IRssObserver* m_pObserver;
48
49 std::vector<std::wstring> m_strFeed;
50 std::vector<std::wstring> m_strColors;
51 std::vector<KODI::TIME::SystemTime*> m_vecTimeStamps;
52 std::vector<int> m_vecUpdateTimes;
53 int m_spacesBetweenFeeds;
54 CXBMCTinyXML m_xml;
55 std::list<std::string> m_tagSet;
56 std::vector<std::string> m_vecUrls;
57 std::vector<int> m_vecQueue;
58 bool m_bIsRunning;
59 bool m_rtlText;
60 bool m_requestRefresh;
61
62 CCriticalSection m_critical;
63};
diff --git a/xbmc/utils/SaveFileStateJob.cpp b/xbmc/utils/SaveFileStateJob.cpp
new file mode 100644
index 0000000..26b7b5c
--- /dev/null
+++ b/xbmc/utils/SaveFileStateJob.cpp
@@ -0,0 +1,211 @@
1/*
2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "SaveFileStateJob.h"
10
11#include "Application.h"
12#include "FileItem.h"
13#include "GUIUserMessages.h"
14#include "ServiceBroker.h"
15#include "StringUtils.h"
16#include "URIUtils.h"
17#include "URL.h"
18#include "Util.h"
19#include "guilib/GUIComponent.h"
20#include "guilib/GUIMessage.h"
21#include "guilib/GUIWindowManager.h"
22#include "interfaces/AnnouncementManager.h"
23#include "log.h"
24#include "music/MusicDatabase.h"
25#include "music/tags/MusicInfoTag.h"
26#include "network/upnp/UPnP.h"
27#include "utils/Variant.h"
28#include "video/Bookmark.h"
29#include "video/VideoDatabase.h"
30
31void CSaveFileState::DoWork(CFileItem& item,
32 CBookmark& bookmark,
33 bool updatePlayCount)
34{
35 std::string progressTrackingFile = item.GetPath();
36
37 if (item.HasVideoInfoTag() && StringUtils::StartsWith(item.GetVideoInfoTag()->m_strFileNameAndPath, "removable://"))
38 progressTrackingFile = item.GetVideoInfoTag()->m_strFileNameAndPath; // this variable contains removable:// suffixed by disc label+uniqueid or is empty if label not uniquely identified
39 else if (item.HasVideoInfoTag() && item.IsVideoDb())
40 progressTrackingFile = item.GetVideoInfoTag()->m_strFileNameAndPath; // we need the file url of the video db item to create the bookmark
41 else if (item.HasProperty("original_listitem_url"))
42 {
43 // only use original_listitem_url for Python, UPnP and Bluray sources
44 std::string original = item.GetProperty("original_listitem_url").asString();
45 if (URIUtils::IsPlugin(original) || URIUtils::IsUPnP(original) || URIUtils::IsBluray(item.GetPath()))
46 progressTrackingFile = original;
47 }
48
49 if (!progressTrackingFile.empty())
50 {
51#ifdef HAS_UPNP
52 // checks if UPnP server of this file is available and supports updating
53 if (URIUtils::IsUPnP(progressTrackingFile)
54 && UPNP::CUPnP::SaveFileState(item, bookmark, updatePlayCount))
55 {
56 return;
57 }
58#endif
59 if (item.IsVideo())
60 {
61 std::string redactPath = CURL::GetRedacted(progressTrackingFile);
62 CLog::Log(LOGDEBUG, "%s - Saving file state for video item %s", __FUNCTION__, redactPath.c_str());
63
64 CVideoDatabase videodatabase;
65 if (!videodatabase.Open())
66 {
67 CLog::Log(LOGWARNING, "%s - Unable to open video database. Can not save file state!", __FUNCTION__);
68 }
69 else
70 {
71 if (URIUtils::IsPlugin(progressTrackingFile) && !(item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId >= 0))
72 {
73 // FileItem from plugin can lack information, make sure all needed fields are set
74 CVideoInfoTag *tag = item.GetVideoInfoTag();
75 CStreamDetails streams = tag->m_streamDetails;
76 if (videodatabase.LoadVideoInfo(progressTrackingFile, *tag))
77 {
78 item.SetPath(progressTrackingFile);
79 item.ClearProperty("original_listitem_url");
80 tag->m_streamDetails = streams;
81 }
82 }
83
84 bool updateListing = false;
85 // No resume & watched status for livetv
86 if (!item.IsLiveTV())
87 {
88 if (updatePlayCount)
89 {
90 // no watched for not yet finished pvr recordings
91 if (!item.IsInProgressPVRRecording())
92 {
93 CLog::Log(LOGDEBUG, "%s - Marking video item %s as watched", __FUNCTION__, redactPath.c_str());
94
95 // consider this item as played
96 videodatabase.IncrementPlayCount(item);
97 item.GetVideoInfoTag()->IncrementPlayCount();
98
99 item.SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, true);
100 updateListing = true;
101
102 if (item.HasVideoInfoTag())
103 {
104 CVariant data;
105 data["id"] = item.GetVideoInfoTag()->m_iDbId;
106 data["type"] = item.GetVideoInfoTag()->m_type;
107 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnUpdate", data);
108 }
109 }
110 }
111 else
112 videodatabase.UpdateLastPlayed(item);
113
114 if (!item.HasVideoInfoTag() ||
115 item.GetVideoInfoTag()->GetResumePoint().timeInSeconds != bookmark.timeInSeconds)
116 {
117 if (bookmark.timeInSeconds <= 0.0f)
118 videodatabase.ClearBookMarksOfFile(progressTrackingFile, CBookmark::RESUME);
119 else
120 videodatabase.AddBookMarkToFile(progressTrackingFile, bookmark, CBookmark::RESUME);
121 if (item.HasVideoInfoTag())
122 item.GetVideoInfoTag()->SetResumePoint(bookmark);
123
124 // UPnP announce resume point changes to clients
125 // however not if playcount is modified as that already announces
126 if (item.HasVideoInfoTag() && !updatePlayCount)
127 {
128 CVariant data;
129 data["id"] = item.GetVideoInfoTag()->m_iDbId;
130 data["type"] = item.GetVideoInfoTag()->m_type;
131 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnUpdate", data);
132 }
133
134 updateListing = true;
135 }
136 }
137
138 if (item.HasVideoInfoTag() && item.GetVideoInfoTag()->HasStreamDetails())
139 {
140 CFileItem dbItem(item);
141
142 // Check whether the item's db streamdetails need updating
143 if (!videodatabase.GetStreamDetails(dbItem) ||
144 dbItem.GetVideoInfoTag()->m_streamDetails != item.GetVideoInfoTag()->m_streamDetails)
145 {
146 videodatabase.SetStreamDetailsForFile(item.GetVideoInfoTag()->m_streamDetails, progressTrackingFile);
147 updateListing = true;
148 }
149 }
150
151 // Could be part of an ISO stack. In this case the bookmark is saved onto the part.
152 // In order to properly update the list, we need to refresh the stack's resume point
153 CApplicationStackHelper& stackHelper = g_application.GetAppStackHelper();
154 if (stackHelper.HasRegisteredStack(item) && stackHelper.GetRegisteredStackTotalTimeMs(item) == 0)
155 videodatabase.GetResumePoint(*(stackHelper.GetRegisteredStack(item)->GetVideoInfoTag()));
156
157 videodatabase.Close();
158
159 if (updateListing)
160 {
161 CUtil::DeleteVideoDatabaseDirectoryCache();
162 CFileItemPtr msgItem(new CFileItem(item));
163 if (item.HasProperty("original_listitem_url"))
164 msgItem->SetPath(item.GetProperty("original_listitem_url").asString());
165 CGUIMessage message(GUI_MSG_NOTIFY_ALL, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(), 0, GUI_MSG_UPDATE_ITEM, 0, msgItem);
166 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message);
167 }
168 }
169 }
170
171 if (item.IsAudio())
172 {
173 std::string redactPath = CURL::GetRedacted(progressTrackingFile);
174 CLog::Log(LOGDEBUG, "%s - Saving file state for audio item %s", __FUNCTION__, redactPath.c_str());
175
176 CMusicDatabase musicdatabase;
177 if (updatePlayCount)
178 {
179 if (!musicdatabase.Open())
180 {
181 CLog::Log(LOGWARNING, "%s - Unable to open music database. Can not save file state!", __FUNCTION__);
182 }
183 else
184 {
185 // consider this item as played
186 CLog::Log(LOGDEBUG, "%s - Marking audio item %s as listened", __FUNCTION__, redactPath.c_str());
187
188 musicdatabase.IncrementPlayCount(item);
189 musicdatabase.Close();
190
191 // UPnP announce resume point changes to clients
192 // however not if playcount is modified as that already announces
193 if (item.IsMusicDb())
194 {
195 CVariant data;
196 data["id"] = item.GetMusicInfoTag()->GetDatabaseId();
197 data["type"] = item.GetMusicInfoTag()->GetType();
198 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnUpdate", data);
199 }
200 }
201 }
202
203 if (item.IsAudioBook())
204 {
205 musicdatabase.Open();
206 musicdatabase.SetResumeBookmarkForAudioBook(item, item.m_lStartOffset + CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds));
207 musicdatabase.Close();
208 }
209 }
210 }
211}
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 @@
1/*
2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11class CBookmark;
12class CFileItem;
13
14class CSaveFileState
15{
16public:
17 static void DoWork(CFileItem& item,
18 CBookmark& bookmark,
19 bool updatePlayCount);
20};
21
diff --git a/xbmc/utils/ScopeGuard.h b/xbmc/utils/ScopeGuard.h
new file mode 100644
index 0000000..a1aa0a6
--- /dev/null
+++ b/xbmc/utils/ScopeGuard.h
@@ -0,0 +1,111 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <functional>
12
13namespace KODI
14{
15namespace UTILS
16{
17
18/*! \class CScopeGuard
19 \brief Generic scopeguard designed to handle any type of handle
20
21 This is not necessary but recommended to cut down on some typing
22 using CSocketHandle = CScopeGuard<SOCKET, INVALID_SOCKET, closesocket>;
23
24 CSocketHandle sh(closesocket, open(thingy));
25 */
26template<typename Handle, Handle invalid, typename Deleter>
27class CScopeGuard
28{
29
30public:
31
32 CScopeGuard(std::function<Deleter> del, Handle handle = invalid)
33 : m_handle{handle}
34 , m_deleter{del}
35 { };
36
37 ~CScopeGuard() noexcept
38 {
39 reset();
40 }
41
42 operator Handle() const
43 {
44 return m_handle;
45 }
46
47 operator bool() const
48 {
49 return m_handle != invalid;
50 }
51
52 /*! \brief attach a new handle to this instance, if there's
53 already a handle it will be closed.
54
55 \param[in] handle The handle to manage
56 */
57 void attach(Handle handle)
58 {
59 reset();
60
61 m_handle = handle;
62 }
63
64 /*! \brief release the managed handle so that it won't be auto closed
65
66 \return The handle being managed by the guard
67 */
68 Handle release()
69 {
70 Handle h = m_handle;
71 m_handle = invalid;
72 return h;
73 }
74
75 /*! \brief reset the instance, closing any managed handle and setting it to invalid
76 */
77 void reset()
78 {
79 if (m_handle != invalid)
80 {
81 m_deleter(m_handle);
82 m_handle = invalid;
83 }
84 }
85
86 //Disallow default construction and copying
87 CScopeGuard() = delete;
88 CScopeGuard(const CScopeGuard& rhs) = delete;
89 CScopeGuard& operator= (const CScopeGuard& rhs) = delete;
90
91 //Allow moving
92 CScopeGuard(CScopeGuard&& rhs)
93 : m_handle{std::move(rhs.m_handle)}, m_deleter{std::move(rhs.m_deleter)}
94 {
95 // Bring moved-from object into released state so destructor will not do anything
96 rhs.release();
97 }
98 CScopeGuard& operator=(CScopeGuard&& rhs)
99 {
100 attach(rhs.release());
101 m_deleter = std::move(rhs.m_deleter);
102 return *this;
103 }
104
105private:
106 Handle m_handle;
107 std::function<Deleter> m_deleter;
108};
109
110}
111}
diff --git a/xbmc/utils/ScraperParser.cpp b/xbmc/utils/ScraperParser.cpp
new file mode 100644
index 0000000..81fcf37
--- /dev/null
+++ b/xbmc/utils/ScraperParser.cpp
@@ -0,0 +1,616 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "ScraperParser.h"
10
11#include "addons/AddonManager.h"
12#include "guilib/LocalizeStrings.h"
13#include "RegExp.h"
14#include "HTMLUtil.h"
15#include "addons/Scraper.h"
16#include "URL.h"
17#include "utils/StringUtils.h"
18#include "log.h"
19#include "CharsetConverter.h"
20#ifdef HAVE_LIBXSLT
21#include "utils/XSLTUtils.h"
22#endif
23#include "utils/XMLUtils.h"
24#include <sstream>
25#include <cstring>
26
27using namespace ADDON;
28using namespace XFILE;
29
30CScraperParser::CScraperParser()
31{
32 m_pRootElement = NULL;
33 m_document = NULL;
34 m_SearchStringEncoding = "UTF-8";
35 m_scraper = NULL;
36 m_isNoop = true;
37}
38
39CScraperParser::CScraperParser(const CScraperParser& parser)
40{
41 m_pRootElement = NULL;
42 m_document = NULL;
43 m_SearchStringEncoding = "UTF-8";
44 m_scraper = NULL;
45 m_isNoop = true;
46 *this = parser;
47}
48
49CScraperParser &CScraperParser::operator=(const CScraperParser &parser)
50{
51 if (this != &parser)
52 {
53 Clear();
54 if (parser.m_document)
55 {
56 m_scraper = parser.m_scraper;
57 m_document = new CXBMCTinyXML(*parser.m_document);
58 LoadFromXML();
59 }
60 else
61 m_scraper = NULL;
62 }
63 return *this;
64}
65
66CScraperParser::~CScraperParser()
67{
68 Clear();
69}
70
71void CScraperParser::Clear()
72{
73 m_pRootElement = NULL;
74 delete m_document;
75
76 m_document = NULL;
77 m_strFile.clear();
78}
79
80bool CScraperParser::Load(const std::string& strXMLFile)
81{
82 Clear();
83
84 m_document = new CXBMCTinyXML();
85
86 if (!m_document)
87 return false;
88
89 m_strFile = strXMLFile;
90
91 if (m_document->LoadFile(strXMLFile))
92 return LoadFromXML();
93
94 delete m_document;
95 m_document = NULL;
96 return false;
97}
98
99bool CScraperParser::LoadFromXML()
100{
101 if (!m_document)
102 return false;
103
104 m_pRootElement = m_document->RootElement();
105 std::string strValue = m_pRootElement->ValueStr();
106 if (strValue == "scraper")
107 {
108 TiXmlElement* pChildElement = m_pRootElement->FirstChildElement("CreateSearchUrl");
109 if (pChildElement)
110 {
111 m_isNoop = false;
112 if (!(m_SearchStringEncoding = pChildElement->Attribute("SearchStringEncoding")))
113 m_SearchStringEncoding = "UTF-8";
114 }
115
116 pChildElement = m_pRootElement->FirstChildElement("CreateArtistSearchUrl");
117 if (pChildElement)
118 {
119 m_isNoop = false;
120 if (!(m_SearchStringEncoding = pChildElement->Attribute("SearchStringEncoding")))
121 m_SearchStringEncoding = "UTF-8";
122 }
123 pChildElement = m_pRootElement->FirstChildElement("CreateAlbumSearchUrl");
124 if (pChildElement)
125 {
126 m_isNoop = false;
127 if (!(m_SearchStringEncoding = pChildElement->Attribute("SearchStringEncoding")))
128 m_SearchStringEncoding = "UTF-8";
129 }
130
131 return true;
132 }
133
134 delete m_document;
135 m_document = NULL;
136 m_pRootElement = NULL;
137 return false;
138}
139
140void CScraperParser::ReplaceBuffers(std::string& strDest)
141{
142 // insert buffers
143 size_t iIndex;
144 for (int i=MAX_SCRAPER_BUFFERS-1; i>=0; i--)
145 {
146 iIndex = 0;
147 std::string temp = StringUtils::Format("$$%i",i+1);
148 while ((iIndex = strDest.find(temp,iIndex)) != std::string::npos)
149 {
150 strDest.replace(strDest.begin()+iIndex,strDest.begin()+iIndex+temp.size(),m_param[i]);
151 iIndex += m_param[i].length();
152 }
153 }
154 // insert settings
155 iIndex = 0;
156 while ((iIndex = strDest.find("$INFO[", iIndex)) != std::string::npos)
157 {
158 size_t iEnd = strDest.find("]", iIndex);
159 std::string strInfo = strDest.substr(iIndex+6, iEnd - iIndex - 6);
160 std::string strReplace;
161 if (m_scraper)
162 strReplace = m_scraper->GetSetting(strInfo);
163 strDest.replace(strDest.begin()+iIndex,strDest.begin()+iEnd+1,strReplace);
164 iIndex += strReplace.length();
165 }
166 // insert localize strings
167 iIndex = 0;
168 while ((iIndex = strDest.find("$LOCALIZE[", iIndex)) != std::string::npos)
169 {
170 size_t iEnd = strDest.find("]", iIndex);
171 std::string strInfo = strDest.substr(iIndex+10, iEnd - iIndex - 10);
172 std::string strReplace;
173 if (m_scraper)
174 strReplace = g_localizeStrings.GetAddonString(m_scraper->ID(), strtol(strInfo.c_str(),NULL,10));
175 strDest.replace(strDest.begin()+iIndex,strDest.begin()+iEnd+1,strReplace);
176 iIndex += strReplace.length();
177 }
178 iIndex = 0;
179 while ((iIndex = strDest.find("\\n",iIndex)) != std::string::npos)
180 strDest.replace(strDest.begin()+iIndex,strDest.begin()+iIndex+2,"\n");
181}
182
183void CScraperParser::ParseExpression(const std::string& input, std::string& dest, TiXmlElement* element, bool bAppend)
184{
185 std::string strOutput = XMLUtils::GetAttribute(element, "output");
186
187 TiXmlElement* pExpression = element->FirstChildElement("expression");
188 if (pExpression)
189 {
190 bool bInsensitive=true;
191 const char* sensitive = pExpression->Attribute("cs");
192 if (sensitive)
193 if (StringUtils::CompareNoCase(sensitive, "yes") == 0)
194 bInsensitive=false; // match case sensitive
195
196 CRegExp::utf8Mode eUtf8 = CRegExp::autoUtf8;
197 const char* const strUtf8 = pExpression->Attribute("utf8");
198 if (strUtf8)
199 {
200 if (StringUtils::CompareNoCase(strUtf8, "yes") == 0)
201 eUtf8 = CRegExp::forceUtf8;
202 else if (StringUtils::CompareNoCase(strUtf8, "no") == 0)
203 eUtf8 = CRegExp::asciiOnly;
204 else if (StringUtils::CompareNoCase(strUtf8, "auto") == 0)
205 eUtf8 = CRegExp::autoUtf8;
206 }
207
208 CRegExp reg(bInsensitive, eUtf8);
209 std::string strExpression;
210 if (pExpression->FirstChild())
211 strExpression = pExpression->FirstChild()->Value();
212 else
213 strExpression = "(.*)";
214 ReplaceBuffers(strExpression);
215 ReplaceBuffers(strOutput);
216
217 if (!reg.RegComp(strExpression.c_str()))
218 {
219 return;
220 }
221
222 bool bRepeat = false;
223 const char* szRepeat = pExpression->Attribute("repeat");
224 if (szRepeat)
225 if (StringUtils::CompareNoCase(szRepeat, "yes") == 0)
226 bRepeat = true;
227
228 const char* szClear = pExpression->Attribute("clear");
229 if (szClear)
230 if (StringUtils::CompareNoCase(szClear, "yes") == 0)
231 dest=""; // clear no matter if regexp fails
232
233 bool bClean[MAX_SCRAPER_BUFFERS];
234 GetBufferParams(bClean,pExpression->Attribute("noclean"),true);
235
236 bool bTrim[MAX_SCRAPER_BUFFERS];
237 GetBufferParams(bTrim,pExpression->Attribute("trim"),false);
238
239 bool bFixChars[MAX_SCRAPER_BUFFERS];
240 GetBufferParams(bFixChars,pExpression->Attribute("fixchars"),false);
241
242 bool bEncode[MAX_SCRAPER_BUFFERS];
243 GetBufferParams(bEncode,pExpression->Attribute("encode"),false);
244
245 int iOptional = -1;
246 pExpression->QueryIntAttribute("optional",&iOptional);
247
248 int iCompare = -1;
249 pExpression->QueryIntAttribute("compare",&iCompare);
250 if (iCompare > -1)
251 StringUtils::ToLower(m_param[iCompare-1]);
252 std::string curInput = input;
253 for (int iBuf=0;iBuf<MAX_SCRAPER_BUFFERS;++iBuf)
254 {
255 if (bClean[iBuf])
256 InsertToken(strOutput,iBuf+1,"!!!CLEAN!!!");
257 if (bTrim[iBuf])
258 InsertToken(strOutput,iBuf+1,"!!!TRIM!!!");
259 if (bFixChars[iBuf])
260 InsertToken(strOutput,iBuf+1,"!!!FIXCHARS!!!");
261 if (bEncode[iBuf])
262 InsertToken(strOutput,iBuf+1,"!!!ENCODE!!!");
263 }
264 int i = reg.RegFind(curInput.c_str());
265 while (i > -1 && (i < (int)curInput.size() || curInput.empty()))
266 {
267 if (!bAppend)
268 {
269 dest = "";
270 bAppend = true;
271 }
272 std::string strCurOutput=strOutput;
273
274 if (iOptional > -1) // check that required param is there
275 {
276 char temp[12];
277 sprintf(temp,"\\%i",iOptional);
278 std::string szParam = reg.GetReplaceString(temp);
279 CRegExp reg2;
280 reg2.RegComp("(.*)(\\\\\\(.*\\\\2.*)\\\\\\)(.*)");
281 int i2=reg2.RegFind(strCurOutput.c_str());
282 while (i2 > -1)
283 {
284 std::string szRemove(reg2.GetMatch(2));
285 int iRemove = szRemove.size();
286 int i3 = strCurOutput.find(szRemove);
287 if (!szParam.empty())
288 {
289 strCurOutput.erase(i3+iRemove,2);
290 strCurOutput.erase(i3,2);
291 }
292 else
293 strCurOutput.replace(strCurOutput.begin()+i3,strCurOutput.begin()+i3+iRemove+2,"");
294
295 i2 = reg2.RegFind(strCurOutput.c_str());
296 }
297 }
298
299 int iLen = reg.GetFindLen();
300 // nasty hack #1 - & means \0 in a replace string
301 StringUtils::Replace(strCurOutput, "&","!!!AMPAMP!!!");
302 std::string result = reg.GetReplaceString(strCurOutput.c_str());
303 if (!result.empty())
304 {
305 std::string strResult(result);
306 StringUtils::Replace(strResult, "!!!AMPAMP!!!","&");
307 Clean(strResult);
308 ReplaceBuffers(strResult);
309 if (iCompare > -1)
310 {
311 std::string strResultNoCase = strResult;
312 StringUtils::ToLower(strResultNoCase);
313 if (strResultNoCase.find(m_param[iCompare-1]) != std::string::npos)
314 dest += strResult;
315 }
316 else
317 dest += strResult;
318 }
319 if (bRepeat && iLen > 0)
320 {
321 curInput.erase(0,i+iLen>(int)curInput.size()?curInput.size():i+iLen);
322 i = reg.RegFind(curInput.c_str());
323 }
324 else
325 i = -1;
326 }
327 }
328}
329
330void CScraperParser::ParseXSLT(const std::string& input, std::string& dest, TiXmlElement* element, bool bAppend)
331{
332#ifdef HAVE_LIBXSLT
333 TiXmlElement* pSheet = element->FirstChildElement();
334 if (pSheet)
335 {
336 XSLTUtils xsltUtils;
337 std::string strXslt;
338 strXslt << *pSheet;
339 ReplaceBuffers(strXslt);
340
341 if (!xsltUtils.SetInput(input))
342 CLog::Log(LOGDEBUG, "could not parse input XML");
343
344 if (!xsltUtils.SetStylesheet(strXslt))
345 CLog::Log(LOGDEBUG, "could not parse stylesheet XML");
346
347 xsltUtils.XSLTTransform(dest);
348 }
349#endif
350}
351
352TiXmlElement *FirstChildScraperElement(TiXmlElement *element)
353{
354 for (TiXmlElement *child = element->FirstChildElement(); child; child = child->NextSiblingElement())
355 {
356#ifdef HAVE_LIBXSLT
357 if (child->ValueStr() == "XSLT")
358 return child;
359#endif
360 if (child->ValueStr() == "RegExp")
361 return child;
362 }
363 return NULL;
364}
365
366TiXmlElement *NextSiblingScraperElement(TiXmlElement *element)
367{
368 for (TiXmlElement *next = element->NextSiblingElement(); next; next = next->NextSiblingElement())
369 {
370#ifdef HAVE_LIBXSLT
371 if (next->ValueStr() == "XSLT")
372 return next;
373#endif
374 if (next->ValueStr() == "RegExp")
375 return next;
376 }
377 return NULL;
378}
379
380void CScraperParser::ParseNext(TiXmlElement* element)
381{
382 TiXmlElement* pReg = element;
383 while (pReg)
384 {
385 TiXmlElement* pChildReg = FirstChildScraperElement(pReg);
386 if (pChildReg)
387 ParseNext(pChildReg);
388 else
389 {
390 TiXmlElement* pChildReg = pReg->FirstChildElement("clear");
391 if (pChildReg)
392 ParseNext(pChildReg);
393 }
394
395 int iDest = 1;
396 bool bAppend = false;
397 const char* szDest = pReg->Attribute("dest");
398 if (szDest && strlen(szDest))
399 {
400 if (szDest[strlen(szDest)-1] == '+')
401 bAppend = true;
402
403 iDest = atoi(szDest);
404 }
405
406 const char *szInput = pReg->Attribute("input");
407 std::string strInput;
408 if (szInput)
409 {
410 strInput = szInput;
411 ReplaceBuffers(strInput);
412 }
413 else
414 strInput = m_param[0];
415
416 const char* szConditional = pReg->Attribute("conditional");
417 bool bExecute = true;
418 if (szConditional)
419 {
420 bool bInverse=false;
421 if (szConditional[0] == '!')
422 {
423 bInverse = true;
424 szConditional++;
425 }
426 std::string strSetting;
427 if (m_scraper && m_scraper->HasSettings())
428 strSetting = m_scraper->GetSetting(szConditional);
429 bExecute = bInverse != (strSetting == "true");
430 }
431
432 if (bExecute)
433 {
434 if (iDest-1 < MAX_SCRAPER_BUFFERS && iDest-1 > -1)
435 {
436#ifdef HAVE_LIBXSLT
437 if (pReg->ValueStr() == "XSLT")
438 ParseXSLT(strInput, m_param[iDest - 1], pReg, bAppend);
439 else
440#endif
441 ParseExpression(strInput, m_param[iDest - 1],pReg,bAppend);
442 }
443 else
444 CLog::Log(LOGERROR,"CScraperParser::ParseNext: destination buffer "
445 "out of bounds, skipping expression");
446 }
447 pReg = NextSiblingScraperElement(pReg);
448 }
449}
450
451const std::string CScraperParser::Parse(const std::string& strTag,
452 CScraper* scraper)
453{
454 TiXmlElement* pChildElement = m_pRootElement->FirstChildElement(strTag.c_str());
455 if(pChildElement == NULL)
456 {
457 CLog::Log(LOGERROR,"%s: Could not find scraper function %s",__FUNCTION__,strTag.c_str());
458 return "";
459 }
460 int iResult = 1; // default to param 1
461 pChildElement->QueryIntAttribute("dest",&iResult);
462 TiXmlElement* pChildStart = FirstChildScraperElement(pChildElement);
463 m_scraper = scraper;
464 ParseNext(pChildStart);
465 std::string tmp = m_param[iResult-1];
466
467 const char* szClearBuffers = pChildElement->Attribute("clearbuffers");
468 if (!szClearBuffers || StringUtils::CompareNoCase(szClearBuffers, "no") != 0)
469 ClearBuffers();
470
471 return tmp;
472}
473
474void CScraperParser::Clean(std::string& strDirty)
475{
476 size_t i = 0;
477 std::string strBuffer;
478 while ((i = strDirty.find("!!!CLEAN!!!",i)) != std::string::npos)
479 {
480 size_t i2;
481 if ((i2 = strDirty.find("!!!CLEAN!!!",i+11)) != std::string::npos)
482 {
483 strBuffer = strDirty.substr(i+11,i2-i-11);
484 std::string strConverted(strBuffer);
485 HTML::CHTMLUtil::RemoveTags(strConverted);
486 StringUtils::Trim(strConverted);
487 strDirty.replace(i, i2-i+11, strConverted);
488 i += strConverted.size();
489 }
490 else
491 break;
492 }
493 i=0;
494 while ((i = strDirty.find("!!!TRIM!!!",i)) != std::string::npos)
495 {
496 size_t i2;
497 if ((i2 = strDirty.find("!!!TRIM!!!",i+10)) != std::string::npos)
498 {
499 strBuffer = strDirty.substr(i+10,i2-i-10);
500 StringUtils::Trim(strBuffer);
501 strDirty.replace(i, i2-i+10, strBuffer);
502 i += strBuffer.size();
503 }
504 else
505 break;
506 }
507 i=0;
508 while ((i = strDirty.find("!!!FIXCHARS!!!",i)) != std::string::npos)
509 {
510 size_t i2;
511 if ((i2 = strDirty.find("!!!FIXCHARS!!!",i+14)) != std::string::npos)
512 {
513 strBuffer = strDirty.substr(i+14,i2-i-14);
514 std::wstring wbuffer;
515 g_charsetConverter.utf8ToW(strBuffer, wbuffer, false, false, false);
516 std::wstring wConverted;
517 HTML::CHTMLUtil::ConvertHTMLToW(wbuffer,wConverted);
518 g_charsetConverter.wToUTF8(wConverted, strBuffer, false);
519 StringUtils::Trim(strBuffer);
520 ConvertJSON(strBuffer);
521 strDirty.replace(i, i2-i+14, strBuffer);
522 i += strBuffer.size();
523 }
524 else
525 break;
526 }
527 i=0;
528 while ((i=strDirty.find("!!!ENCODE!!!",i)) != std::string::npos)
529 {
530 size_t i2;
531 if ((i2 = strDirty.find("!!!ENCODE!!!",i+12)) != std::string::npos)
532 {
533 strBuffer = CURL::Encode(strDirty.substr(i + 12, i2 - i - 12));
534 strDirty.replace(i, i2-i+12, strBuffer);
535 i += strBuffer.size();
536 }
537 else
538 break;
539 }
540}
541
542void CScraperParser::ConvertJSON(std::string &string)
543{
544 CRegExp reg;
545 reg.RegComp("\\\\u([0-f]{4})");
546 while (reg.RegFind(string.c_str()) > -1)
547 {
548 int pos = reg.GetSubStart(1);
549 std::string szReplace(reg.GetMatch(1));
550
551 std::string replace = StringUtils::Format("&#x%s;", szReplace.c_str());
552 string.replace(string.begin()+pos-2, string.begin()+pos+4, replace);
553 }
554
555 CRegExp reg2;
556 reg2.RegComp("\\\\x([0-9]{2})([^\\\\]+;)");
557 while (reg2.RegFind(string.c_str()) > -1)
558 {
559 int pos1 = reg2.GetSubStart(1);
560 int pos2 = reg2.GetSubStart(2);
561 std::string szHexValue(reg2.GetMatch(1));
562
563 std::string replace = StringUtils::Format("%li", strtol(szHexValue.c_str(), NULL, 16));
564 string.replace(string.begin()+pos1-2, string.begin()+pos2+reg2.GetSubLength(2), replace);
565 }
566
567 StringUtils::Replace(string, "\\\"","\"");
568}
569
570void CScraperParser::ClearBuffers()
571{
572 //clear all m_param strings
573 for (std::string& param : m_param)
574 param.clear();
575}
576
577void CScraperParser::GetBufferParams(bool* result, const char* attribute, bool defvalue)
578{
579 for (int iBuf=0;iBuf<MAX_SCRAPER_BUFFERS;++iBuf)
580 result[iBuf] = defvalue;
581 if (attribute)
582 {
583 std::vector<std::string> vecBufs;
584 StringUtils::Tokenize(attribute,vecBufs,",");
585 for (size_t nToken=0; nToken < vecBufs.size(); nToken++)
586 {
587 int index = atoi(vecBufs[nToken].c_str())-1;
588 if (index < MAX_SCRAPER_BUFFERS)
589 result[index] = !defvalue;
590 }
591 }
592}
593
594void CScraperParser::InsertToken(std::string& strOutput, int buf, const char* token)
595{
596 char temp[4];
597 sprintf(temp,"\\%i",buf);
598 size_t i2=0;
599 while ((i2 = strOutput.find(temp,i2)) != std::string::npos)
600 {
601 strOutput.insert(i2,token);
602 i2 += strlen(token) + strlen(temp);
603 strOutput.insert(i2,token);
604 }
605}
606
607void CScraperParser::AddDocument(const CXBMCTinyXML* doc)
608{
609 const TiXmlNode* node = doc->RootElement()->FirstChild();
610 while (node)
611 {
612 m_pRootElement->InsertEndChild(*node);
613 node = node->NextSibling();
614 }
615}
616
diff --git a/xbmc/utils/ScraperParser.h b/xbmc/utils/ScraperParser.h
new file mode 100644
index 0000000..293dbcc
--- /dev/null
+++ b/xbmc/utils/ScraperParser.h
@@ -0,0 +1,78 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12#include <vector>
13
14#define MAX_SCRAPER_BUFFERS 20
15
16namespace ADDON
17{
18 class CScraper;
19}
20
21class TiXmlElement;
22class CXBMCTinyXML;
23
24class CScraperSettings;
25
26class CScraperParser
27{
28public:
29 CScraperParser();
30 CScraperParser(const CScraperParser& parser);
31 ~CScraperParser();
32 CScraperParser& operator= (const CScraperParser& parser);
33 bool Load(const std::string& strXMLFile);
34 bool IsNoop() const { return m_isNoop; };
35
36 void Clear();
37 const std::string& GetFilename() const { return m_strFile; }
38 std::string GetSearchStringEncoding() const
39 { return m_SearchStringEncoding; }
40 const std::string Parse(const std::string& strTag,
41 ADDON::CScraper* scraper);
42
43 void AddDocument(const CXBMCTinyXML* doc);
44
45 std::string m_param[MAX_SCRAPER_BUFFERS];
46
47private:
48 bool LoadFromXML();
49 void ReplaceBuffers(std::string& strDest);
50 void ParseExpression(const std::string& input, std::string& dest, TiXmlElement* element, bool bAppend);
51
52 /*! \brief Parse an 'XSLT' declaration from the scraper
53 This allow us to transform an inbound XML document using XSLT
54 to a different type of XML document, ready to be output direct
55 to the album loaders or similar
56 \param input the input document
57 \param dest the output destination for the conversion
58 \param element the current XML element
59 \param bAppend append or clear the buffer
60 */
61 void ParseXSLT(const std::string& input, std::string& dest, TiXmlElement* element, bool bAppend);
62 void ParseNext(TiXmlElement* element);
63 void Clean(std::string& strDirty);
64 void ConvertJSON(std::string &string);
65 void ClearBuffers();
66 void GetBufferParams(bool* result, const char* attribute, bool defvalue);
67 void InsertToken(std::string& strOutput, int buf, const char* token);
68
69 CXBMCTinyXML* m_document;
70 TiXmlElement* m_pRootElement;
71
72 const char* m_SearchStringEncoding;
73 bool m_isNoop;
74
75 std::string m_strFile;
76 ADDON::CScraper* m_scraper;
77};
78
diff --git a/xbmc/utils/ScraperUrl.cpp b/xbmc/utils/ScraperUrl.cpp
new file mode 100644
index 0000000..f242a40
--- /dev/null
+++ b/xbmc/utils/ScraperUrl.cpp
@@ -0,0 +1,432 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "ScraperUrl.h"
10
11#include "CharsetConverter.h"
12#include "ServiceBroker.h"
13#include "URIUtils.h"
14#include "URL.h"
15#include "XMLUtils.h"
16#include "filesystem/CurlFile.h"
17#include "filesystem/ZipFile.h"
18#include "settings/AdvancedSettings.h"
19#include "settings/SettingsComponent.h"
20#include "utils/CharsetDetection.h"
21#include "utils/Mime.h"
22#include "utils/StringUtils.h"
23#include "utils/XBMCTinyXML.h"
24#include "utils/log.h"
25
26#include <algorithm>
27#include <cstring>
28#include <sstream>
29
30CScraperUrl::CScraperUrl() : m_relevance(0.0), m_parsed(false)
31{
32}
33
34CScraperUrl::CScraperUrl(std::string strUrl) : CScraperUrl()
35{
36 ParseFromData(std::move(strUrl));
37}
38
39CScraperUrl::CScraperUrl(const TiXmlElement* element) : CScraperUrl()
40{
41 ParseAndAppendUrl(element);
42}
43
44CScraperUrl::~CScraperUrl() = default;
45
46void CScraperUrl::Clear()
47{
48 m_urls.clear();
49 m_data.clear();
50 m_relevance = 0.0;
51 m_parsed = false;
52}
53
54void CScraperUrl::SetData(std::string data)
55{
56 m_data = std::move(data);
57 m_parsed = false;
58}
59
60const CScraperUrl::SUrlEntry CScraperUrl::GetFirstUrlByType(const std::string& type) const
61{
62 const auto url = std::find_if(m_urls.begin(), m_urls.end(), [type](const SUrlEntry& url) {
63 return url.m_type == UrlType::General && (type.empty() || url.m_aspect == type);
64 });
65 if (url != m_urls.end())
66 return *url;
67
68 return SUrlEntry();
69}
70
71const CScraperUrl::SUrlEntry CScraperUrl::GetSeasonUrl(int season, const std::string& type) const
72{
73 const auto url = std::find_if(m_urls.begin(), m_urls.end(), [season, type](const SUrlEntry& url) {
74 return url.m_type == UrlType::Season && url.m_season == season &&
75 (type.empty() || type == "thumb" || url.m_aspect == type);
76 });
77 if (url != m_urls.end())
78 return *url;
79
80 return SUrlEntry();
81}
82
83unsigned int CScraperUrl::GetMaxSeasonUrl() const
84{
85 unsigned int maxSeason = 0;
86 for (const auto& url : m_urls)
87 {
88 if (url.m_type == UrlType::Season && url.m_season > 0 &&
89 static_cast<unsigned int>(url.m_season) > maxSeason)
90 maxSeason = url.m_season;
91 }
92 return maxSeason;
93}
94
95std::string CScraperUrl::GetFirstThumbUrl() const
96{
97 if (m_urls.empty())
98 return {};
99
100 return GetThumbUrl(m_urls.front());
101}
102
103void CScraperUrl::GetThumbUrls(std::vector<std::string>& thumbs,
104 const std::string& type,
105 int season,
106 bool unique) const
107{
108 for (const auto& url : m_urls)
109 {
110 if (url.m_aspect == type || type.empty() || url.m_aspect.empty())
111 {
112 if ((url.m_type == CScraperUrl::UrlType::General && season == -1) ||
113 (url.m_type == CScraperUrl::UrlType::Season && url.m_season == season))
114 {
115 std::string thumbUrl = GetThumbUrl(url);
116 if (!unique || std::find(thumbs.begin(), thumbs.end(), thumbUrl) == thumbs.end())
117 thumbs.push_back(thumbUrl);
118 }
119 }
120 }
121}
122
123bool CScraperUrl::Parse()
124{
125 if (m_parsed)
126 return true;
127
128 auto dataToParse = m_data;
129 m_data.clear();
130 return ParseFromData(std::move(dataToParse));
131}
132
133bool CScraperUrl::ParseFromData(std::string data)
134{
135 if (data.empty())
136 return false;
137
138 CXBMCTinyXML doc;
139 /* strUrl is coming from internal sources (usually generated by scraper or from database)
140 * so strUrl is always in UTF-8 */
141 doc.Parse(data, TIXML_ENCODING_UTF8);
142
143 auto pElement = doc.RootElement();
144 if (pElement == nullptr)
145 {
146 m_urls.emplace_back(data);
147 m_data = data;
148 }
149 else
150 {
151 while (pElement != nullptr)
152 {
153 ParseAndAppendUrl(pElement);
154 pElement = pElement->NextSiblingElement(pElement->Value());
155 }
156 }
157
158 m_parsed = true;
159 return true;
160}
161
162bool CScraperUrl::ParseAndAppendUrl(const TiXmlElement* element)
163{
164 if (element == nullptr || element->FirstChild() == nullptr ||
165 element->FirstChild()->Value() == nullptr)
166 return false;
167
168 bool wasEmpty = m_data.empty();
169
170 std::stringstream stream;
171 stream << *element;
172 m_data += stream.str();
173
174 SUrlEntry url(element->FirstChild()->ValueStr());
175 url.m_spoof = XMLUtils::GetAttribute(element, "spoof");
176
177 const char* szPost = element->Attribute("post");
178 if (szPost && StringUtils::CompareNoCase(szPost, "yes") == 0)
179 url.m_post = true;
180 else
181 url.m_post = false;
182
183 const char* szIsGz = element->Attribute("gzip");
184 if (szIsGz && StringUtils::CompareNoCase(szIsGz, "yes") == 0)
185 url.m_isgz = true;
186 else
187 url.m_isgz = false;
188
189 url.m_cache = XMLUtils::GetAttribute(element, "cache");
190
191 const char* szType = element->Attribute("type");
192 if (szType && StringUtils::CompareNoCase(szType, "season") == 0)
193 {
194 url.m_type = UrlType::Season;
195 const char* szSeason = element->Attribute("season");
196 if (szSeason)
197 url.m_season = atoi(szSeason);
198 }
199
200 url.m_aspect = XMLUtils::GetAttribute(element, "aspect");
201
202 m_urls.push_back(url);
203
204 if (wasEmpty)
205 m_parsed = true;
206
207 return true;
208}
209
210// XML format is of strUrls is:
211// <TAG><url>...</url>...</TAG> (parsed by ParseElement) or <url>...</url> (ditto)
212bool CScraperUrl::ParseAndAppendUrlsFromEpisodeGuide(std::string episodeGuide)
213{
214 if (episodeGuide.empty())
215 return false;
216
217 // ok, now parse the xml file
218 CXBMCTinyXML doc;
219 /* strUrls is coming from internal sources so strUrls is always in UTF-8 */
220 doc.Parse(episodeGuide, TIXML_ENCODING_UTF8);
221 if (doc.RootElement() == nullptr)
222 return false;
223
224 bool wasEmpty = m_data.empty();
225
226 TiXmlHandle docHandle(&doc);
227 auto link = docHandle.FirstChild("episodeguide").Element();
228 if (link->FirstChildElement("url"))
229 {
230 for (link = link->FirstChildElement("url"); link; link = link->NextSiblingElement("url"))
231 ParseAndAppendUrl(link);
232 }
233 else if (link->FirstChild() && link->FirstChild()->Value())
234 ParseAndAppendUrl(link);
235
236 if (wasEmpty)
237 m_parsed = true;
238
239 return true;
240}
241
242void CScraperUrl::AddParsedUrl(std::string url,
243 std::string aspect,
244 std::string preview,
245 std::string referrer,
246 std::string cache,
247 bool post,
248 bool isgz,
249 int season)
250{
251 bool wasEmpty = m_data.empty();
252
253 TiXmlElement thumb("thumb");
254 thumb.SetAttribute("spoof", referrer);
255 thumb.SetAttribute("cache", cache);
256 if (post)
257 thumb.SetAttribute("post", "yes");
258 if (isgz)
259 thumb.SetAttribute("gzip", "yes");
260 if (season >= 0)
261 {
262 thumb.SetAttribute("season", StringUtils::Format("%i", season));
263 thumb.SetAttribute("type", "season");
264 }
265 thumb.SetAttribute("aspect", aspect);
266 thumb.SetAttribute("preview", preview);
267 TiXmlText text(url);
268 thumb.InsertEndChild(text);
269
270 m_data << thumb;
271
272 SUrlEntry nUrl(url);
273 nUrl.m_spoof = referrer;
274 nUrl.m_post = post;
275 nUrl.m_isgz = isgz;
276 nUrl.m_cache = cache;
277 if (season >= 0)
278 {
279 nUrl.m_type = UrlType::Season;
280 nUrl.m_season = season;
281 }
282 nUrl.m_aspect = aspect;
283
284 m_urls.push_back(nUrl);
285
286 if (wasEmpty)
287 m_parsed = true;
288}
289
290std::string CScraperUrl::GetThumbUrl(const CScraperUrl::SUrlEntry& entry)
291{
292 if (entry.m_spoof.empty())
293 return entry.m_url;
294
295 return entry.m_url + "|Referer=" + CURL::Encode(entry.m_spoof);
296}
297
298bool CScraperUrl::Get(const SUrlEntry& scrURL,
299 std::string& strHTML,
300 XFILE::CCurlFile& http,
301 const std::string& cacheContext)
302{
303 CURL url(scrURL.m_url);
304 http.SetReferer(scrURL.m_spoof);
305 std::string strCachePath;
306
307 if (!scrURL.m_cache.empty())
308 {
309 strCachePath = URIUtils::AddFileToFolder(
310 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cachePath, "scrapers",
311 cacheContext, scrURL.m_cache);
312 if (XFILE::CFile::Exists(strCachePath))
313 {
314 XFILE::CFile file;
315 XFILE::auto_buffer buffer;
316 if (file.LoadFile(strCachePath, buffer) > 0)
317 {
318 strHTML.assign(buffer.get(), buffer.length());
319 return true;
320 }
321 }
322 }
323
324 auto strHTML1 = strHTML;
325
326 if (scrURL.m_post)
327 {
328 std::string strOptions = url.GetOptions();
329 strOptions = strOptions.substr(1);
330 url.SetOptions("");
331
332 if (!http.Post(url.Get(), strOptions, strHTML1))
333 return false;
334 }
335 else if (!http.Get(url.Get(), strHTML1))
336 return false;
337
338 strHTML = strHTML1;
339
340 const auto mimeType = http.GetProperty(XFILE::FILE_PROPERTY_MIME_TYPE);
341 CMime::EFileType ftype = CMime::GetFileTypeFromMime(mimeType);
342 if (ftype == CMime::FileTypeUnknown)
343 ftype = CMime::GetFileTypeFromContent(strHTML);
344
345 if (ftype == CMime::FileTypeZip || ftype == CMime::FileTypeGZip)
346 {
347 XFILE::CZipFile file;
348 std::string strBuffer;
349 auto iSize = file.UnpackFromMemory(
350 strBuffer, strHTML, scrURL.m_isgz); // FIXME: use FileTypeGZip instead of scrURL.m_isgz?
351 if (iSize > 0)
352 {
353 strHTML = strBuffer;
354 CLog::Log(LOGDEBUG, "{}: Archive \"{}\" was unpacked in memory", __FUNCTION__, scrURL.m_url);
355 }
356 else
357 CLog::Log(LOGWARNING, "{}: \"{}\" looks like archive but cannot be unpacked", __FUNCTION__,
358 scrURL.m_url);
359 }
360
361 const auto reportedCharset = http.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET);
362 if (ftype == CMime::FileTypeHtml)
363 {
364 std::string realHtmlCharset, converted;
365 if (!CCharsetDetection::ConvertHtmlToUtf8(strHTML, converted, reportedCharset, realHtmlCharset))
366 CLog::Log(LOGWARNING,
367 "{}: Can't find precise charset for HTML \"{}\", using \"{}\" as fallback",
368 __FUNCTION__, scrURL.m_url, realHtmlCharset);
369 else
370 CLog::Log(LOGDEBUG, "{}: Using \"{}\" charset for HTML \"{}\"", __FUNCTION__, realHtmlCharset,
371 scrURL.m_url);
372
373 strHTML = converted;
374 }
375 else if (ftype == CMime::FileTypeXml)
376 {
377 CXBMCTinyXML xmlDoc;
378 xmlDoc.Parse(strHTML, reportedCharset);
379
380 const auto realXmlCharset = xmlDoc.GetUsedCharset();
381 if (!realXmlCharset.empty())
382 {
383 CLog::Log(LOGDEBUG, "{}: Using \"{}\" charset for XML \"{}\"", __FUNCTION__, realXmlCharset,
384 scrURL.m_url);
385 std::string converted;
386 g_charsetConverter.ToUtf8(realXmlCharset, strHTML, converted);
387 strHTML = converted;
388 }
389 }
390 else if (ftype == CMime::FileTypePlainText ||
391 StringUtils::EqualsNoCase(mimeType.substr(0, 5), "text/"))
392 {
393 std::string realTextCharset;
394 std::string converted;
395 CCharsetDetection::ConvertPlainTextToUtf8(strHTML, converted, reportedCharset, realTextCharset);
396 strHTML = converted;
397 if (reportedCharset != realTextCharset)
398 CLog::Log(LOGWARNING,
399 "{}: Using \"{}\" charset for plain text \"{}\" instead of server reported \"{}\" "
400 "charset",
401 __FUNCTION__, realTextCharset, scrURL.m_url, reportedCharset);
402 else
403 CLog::Log(LOGDEBUG, "{}: Using \"{}\" charset for plain text \"{}\"", __FUNCTION__,
404 realTextCharset, scrURL.m_url);
405 }
406 else if (!reportedCharset.empty())
407 {
408 CLog::Log(LOGDEBUG, "{}: Using \"{}\" charset for \"{}\"", __FUNCTION__, reportedCharset,
409 scrURL.m_url);
410 if (reportedCharset != "UTF-8")
411 {
412 std::string converted;
413 g_charsetConverter.ToUtf8(reportedCharset, strHTML, converted);
414 strHTML = converted;
415 }
416 }
417 else
418 CLog::Log(LOGDEBUG, "{}: Using content of \"{}\" as binary or text with \"UTF-8\" charset",
419 __FUNCTION__, scrURL.m_url);
420
421 if (!scrURL.m_cache.empty())
422 {
423 const auto strCachePath = URIUtils::AddFileToFolder(
424 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cachePath, "scrapers",
425 cacheContext, scrURL.m_cache);
426 XFILE::CFile file;
427 if (!file.OpenForWrite(strCachePath, true) ||
428 file.Write(strHTML.data(), strHTML.size()) != static_cast<ssize_t>(strHTML.size()))
429 return false;
430 }
431 return true;
432}
diff --git a/xbmc/utils/ScraperUrl.h b/xbmc/utils/ScraperUrl.h
new file mode 100644
index 0000000..f6c13ba
--- /dev/null
+++ b/xbmc/utils/ScraperUrl.h
@@ -0,0 +1,122 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <map>
12#include <string>
13#include <vector>
14
15class TiXmlElement;
16namespace XFILE
17{
18class CCurlFile;
19}
20
21class CScraperUrl
22{
23public:
24 enum class UrlType
25 {
26 General = 1,
27 Season = 2
28 };
29
30 struct SUrlEntry
31 {
32 explicit SUrlEntry(std::string url = "")
33 : m_url(std::move(url)), m_type(UrlType::General), m_post(false), m_isgz(false), m_season(-1)
34 {
35 }
36
37 std::string m_spoof;
38 std::string m_url;
39 std::string m_cache;
40 std::string m_aspect;
41 UrlType m_type;
42 bool m_post;
43 bool m_isgz;
44 int m_season;
45 };
46
47 CScraperUrl();
48 explicit CScraperUrl(std::string strUrl);
49 explicit CScraperUrl(const TiXmlElement* element);
50 ~CScraperUrl();
51
52 void Clear();
53
54 bool HasData() const { return !m_data.empty(); }
55 const std::string& GetData() const { return m_data; }
56 void SetData(std::string data);
57
58 const std::string& GetTitle() const { return m_title; }
59 void SetTitle(std::string title) { m_title = std::move(title); }
60
61 const std::string& GetId() const { return m_id; }
62 void SetId(std::string id) { m_id = std::move(id); }
63
64 double GetRelevance() const { return m_relevance; }
65 void SetRelevance(double relevance) { m_relevance = relevance; }
66
67 bool HasUrls() const { return !m_urls.empty(); }
68 const std::vector<SUrlEntry>& GetUrls() const { return m_urls; }
69 void SetUrls(std::vector<SUrlEntry> urls) { m_urls = std::move(urls); }
70 void AppendUrl(SUrlEntry url) { m_urls.push_back(std::move(url)); }
71
72 const SUrlEntry GetFirstUrlByType(const std::string& type = "") const;
73 const SUrlEntry GetSeasonUrl(int season, const std::string& type = "") const;
74 unsigned int GetMaxSeasonUrl() const;
75
76 std::string GetFirstThumbUrl() const;
77
78 /*! \brief fetch the full URLs (including referrer) of thumbs
79 \param thumbs [out] vector of thumb URLs to fill
80 \param type the type of thumb URLs to fetch, if empty (the default) picks any
81 \param season number of season that we want thumbs for, -1 indicates no season (the default)
82 \param unique avoid adding duplicate URLs when adding to a thumbs vector with existing items
83 */
84 void GetThumbUrls(std::vector<std::string>& thumbs,
85 const std::string& type = "",
86 int season = -1,
87 bool unique = false) const;
88
89 bool Parse();
90 bool ParseFromData(std::string data); // copies by intention
91 bool ParseAndAppendUrl(const TiXmlElement* element);
92 bool ParseAndAppendUrlsFromEpisodeGuide(std::string episodeGuide); // copies by intention
93 void AddParsedUrl(std::string url,
94 std::string aspect = "",
95 std::string preview = "",
96 std::string referrer = "",
97 std::string cache = "",
98 bool post = false,
99 bool isgz = false,
100 int season = -1);
101
102 /*! \brief fetch the full URL (including referrer) of a thumb
103 \param URL entry to use to create the full URL
104 \return the full URL, including referrer
105 */
106 static std::string GetThumbUrl(const CScraperUrl::SUrlEntry& entry);
107
108 static bool Get(const SUrlEntry& scrURL,
109 std::string& strHTML,
110 XFILE::CCurlFile& http,
111 const std::string& cacheContext);
112
113 // ATTENTION: this member MUST NOT be used directly except from databases
114 std::string m_data;
115
116private:
117 std::string m_title;
118 std::string m_id;
119 double m_relevance;
120 std::vector<SUrlEntry> m_urls;
121 bool m_parsed;
122};
diff --git a/xbmc/utils/Screenshot.cpp b/xbmc/utils/Screenshot.cpp
new file mode 100644
index 0000000..638ea64
--- /dev/null
+++ b/xbmc/utils/Screenshot.cpp
@@ -0,0 +1,116 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Screenshot.h"
10
11#include "ServiceBroker.h"
12#include "URL.h"
13#include "Util.h"
14#include "filesystem/File.h"
15#include "guilib/LocalizeStrings.h"
16#include "pictures/Picture.h"
17#include "settings/SettingPath.h"
18#include "settings/Settings.h"
19#include "settings/SettingsComponent.h"
20#include "settings/windows/GUIControlSettings.h"
21#include "utils/JobManager.h"
22#include "utils/URIUtils.h"
23#include "utils/log.h"
24
25using namespace XFILE;
26
27std::vector<std::function<std::unique_ptr<IScreenshotSurface>()>> CScreenShot::m_screenShotSurfaces;
28
29void CScreenShot::Register(std::function<std::unique_ptr<IScreenshotSurface>()> createFunc)
30{
31 m_screenShotSurfaces.emplace_back(createFunc);
32}
33
34void CScreenShot::TakeScreenshot(const std::string& filename, bool sync)
35{
36 auto surface = m_screenShotSurfaces.back()();
37
38 if (!surface)
39 {
40 CLog::Log(LOGERROR, "failed to create screenshot surface");
41 return;
42 }
43
44 if (!surface->Capture())
45 {
46 CLog::Log(LOGERROR, "Screenshot %s failed", CURL::GetRedacted(filename).c_str());
47 return;
48 }
49
50 surface->CaptureVideo(true);
51
52 CLog::Log(LOGDEBUG, "Saving screenshot %s", CURL::GetRedacted(filename).c_str());
53
54 //set alpha byte to 0xFF
55 for (int y = 0; y < surface->GetHeight(); y++)
56 {
57 unsigned char* alphaptr = surface->GetBuffer() - 1 + y * surface->GetStride();
58 for (int x = 0; x < surface->GetWidth(); x++)
59 *(alphaptr += 4) = 0xFF;
60 }
61
62 //if sync is true, the png file needs to be completely written when this function returns
63 if (sync)
64 {
65 if (!CPicture::CreateThumbnailFromSurface(surface->GetBuffer(), surface->GetWidth(), surface->GetHeight(), surface->GetStride(), filename))
66 CLog::Log(LOGERROR, "Unable to write screenshot %s", CURL::GetRedacted(filename).c_str());
67
68 surface->ReleaseBuffer();
69 }
70 else
71 {
72 //make sure the file exists to avoid concurrency issues
73 XFILE::CFile file;
74 if (file.OpenForWrite(filename))
75 file.Close();
76 else
77 CLog::Log(LOGERROR, "Unable to create file %s", CURL::GetRedacted(filename).c_str());
78
79 //write .png file asynchronous with CThumbnailWriter, prevents stalling of the render thread
80 //buffer is deleted from CThumbnailWriter
81 CThumbnailWriter* thumbnailwriter = new CThumbnailWriter(surface->GetBuffer(), surface->GetWidth(), surface->GetHeight(), surface->GetStride(), filename);
82 CJobManager::GetInstance().AddJob(thumbnailwriter, NULL);
83 }
84}
85
86void CScreenShot::TakeScreenshot()
87{
88 std::shared_ptr<CSettingPath> screenshotSetting = std::static_pointer_cast<CSettingPath>(CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(CSettings::SETTING_DEBUG_SCREENSHOTPATH));
89 if (!screenshotSetting)
90 return;
91
92 std::string strDir = screenshotSetting->GetValue();
93 if (strDir.empty())
94 {
95 if (!CGUIControlButtonSetting::GetPath(screenshotSetting, &g_localizeStrings))
96 return;
97
98 strDir = screenshotSetting->GetValue();
99 }
100
101 URIUtils::RemoveSlashAtEnd(strDir);
102
103 if (!strDir.empty())
104 {
105 std::string file = CUtil::GetNextFilename(URIUtils::AddFileToFolder(strDir, "screenshot%03d.png"), 999);
106
107 if (!file.empty())
108 {
109 TakeScreenshot(file, false);
110 }
111 else
112 {
113 CLog::Log(LOGWARNING, "Too many screen shots or invalid folder");
114 }
115 }
116}
diff --git a/xbmc/utils/Screenshot.h b/xbmc/utils/Screenshot.h
new file mode 100644
index 0000000..8642ca3
--- /dev/null
+++ b/xbmc/utils/Screenshot.h
@@ -0,0 +1,28 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "IScreenshotSurface.h"
12
13#include <functional>
14#include <memory>
15#include <string>
16#include <vector>
17
18class CScreenShot
19{
20public:
21 static void Register(std::function<std::unique_ptr<IScreenshotSurface>()> createFunc);
22
23 static void TakeScreenshot();
24 static void TakeScreenshot(const std::string &filename, bool sync);
25
26private:
27 static std::vector<std::function<std::unique_ptr<IScreenshotSurface>()>> m_screenShotSurfaces;
28};
diff --git a/xbmc/utils/SortUtils.cpp b/xbmc/utils/SortUtils.cpp
new file mode 100644
index 0000000..840e69e
--- /dev/null
+++ b/xbmc/utils/SortUtils.cpp
@@ -0,0 +1,1324 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "SortUtils.h"
10
11#include "LangInfo.h"
12#include "URL.h"
13#include "Util.h"
14#include "utils/CharsetConverter.h"
15#include "utils/StringUtils.h"
16#include "utils/Variant.h"
17
18#include <algorithm>
19#include <inttypes.h>
20
21std::string ArrayToString(SortAttribute attributes, const CVariant &variant, const std::string &separator = " / ")
22{
23 std::vector<std::string> strArray;
24 if (variant.isArray())
25 {
26 for (CVariant::const_iterator_array it = variant.begin_array(); it != variant.end_array(); it++)
27 {
28 if (attributes & SortAttributeIgnoreArticle)
29 strArray.push_back(SortUtils::RemoveArticles(it->asString()));
30 else
31 strArray.push_back(it->asString());
32 }
33
34 return StringUtils::Join(strArray, separator);
35 }
36 else if (variant.isString())
37 {
38 if (attributes & SortAttributeIgnoreArticle)
39 return SortUtils::RemoveArticles(variant.asString());
40 else
41 return variant.asString();
42 }
43
44 return "";
45}
46
47std::string ByLabel(SortAttribute attributes, const SortItem &values)
48{
49 if (attributes & SortAttributeIgnoreArticle)
50 return SortUtils::RemoveArticles(values.at(FieldLabel).asString());
51
52 return values.at(FieldLabel).asString();
53}
54
55std::string ByFile(SortAttribute attributes, const SortItem &values)
56{
57 CURL url(values.at(FieldPath).asString());
58
59 return StringUtils::Format("%s %" PRId64, url.GetFileNameWithoutPath().c_str(), values.at(FieldStartOffset).asInteger());
60}
61
62std::string ByPath(SortAttribute attributes, const SortItem &values)
63{
64 return StringUtils::Format("%s %" PRId64, values.at(FieldPath).asString().c_str(), values.at(FieldStartOffset).asInteger());
65}
66
67std::string ByLastPlayed(SortAttribute attributes, const SortItem &values)
68{
69 if (attributes & SortAttributeIgnoreLabel)
70 return values.at(FieldLastPlayed).asString();
71
72 return StringUtils::Format("%s %s", values.at(FieldLastPlayed).asString().c_str(), ByLabel(attributes, values).c_str());
73}
74
75std::string ByPlaycount(SortAttribute attributes, const SortItem &values)
76{
77 return StringUtils::Format("%i %s", (int)values.at(FieldPlaycount).asInteger(), ByLabel(attributes, values).c_str());
78}
79
80std::string ByDate(SortAttribute attributes, const SortItem &values)
81{
82 return values.at(FieldDate).asString() + " " + ByLabel(attributes, values);
83}
84
85std::string ByDateAdded(SortAttribute attributes, const SortItem &values)
86{
87 return StringUtils::Format("%s %d", values.at(FieldDateAdded).asString().c_str(), (int)values.at(FieldId).asInteger());
88}
89
90std::string BySize(SortAttribute attributes, const SortItem &values)
91{
92 return StringUtils::Format("%" PRId64, values.at(FieldSize).asInteger());
93}
94
95std::string ByDriveType(SortAttribute attributes, const SortItem &values)
96{
97 return StringUtils::Format("%d %s", (int)values.at(FieldDriveType).asInteger(), ByLabel(attributes, values).c_str());
98}
99
100std::string ByTitle(SortAttribute attributes, const SortItem &values)
101{
102 if (attributes & SortAttributeIgnoreArticle)
103 return SortUtils::RemoveArticles(values.at(FieldTitle).asString());
104
105 return values.at(FieldTitle).asString();
106}
107
108std::string ByAlbum(SortAttribute attributes, const SortItem &values)
109{
110 std::string album = values.at(FieldAlbum).asString();
111 if (attributes & SortAttributeIgnoreArticle)
112 album = SortUtils::RemoveArticles(album);
113
114 std::string label = StringUtils::Format("%s %s", album.c_str(), ArrayToString(attributes, values.at(FieldArtist)).c_str());
115
116 const CVariant &track = values.at(FieldTrackNumber);
117 if (!track.isNull())
118 label += StringUtils::Format(" %i", (int)track.asInteger());
119
120 return label;
121}
122
123std::string ByAlbumType(SortAttribute attributes, const SortItem &values)
124{
125 return values.at(FieldAlbumType).asString() + " " + ByLabel(attributes, values);
126}
127
128std::string ByArtist(SortAttribute attributes, const SortItem &values)
129{
130 std::string label;
131 if (attributes & SortAttributeUseArtistSortName)
132 {
133 const CVariant &artistsort = values.at(FieldArtistSort);
134 if (!artistsort.isNull())
135 label = artistsort.asString();
136 }
137 if (label.empty())
138 label = ArrayToString(attributes, values.at(FieldArtist));
139
140 const CVariant &album = values.at(FieldAlbum);
141 if (!album.isNull())
142 label += " " + SortUtils::RemoveArticles(album.asString());
143
144 const CVariant &track = values.at(FieldTrackNumber);
145 if (!track.isNull())
146 label += StringUtils::Format(" %i", (int)track.asInteger());
147
148 return label;
149}
150
151std::string ByArtistThenYear(SortAttribute attributes, const SortItem &values)
152{
153 std::string label;
154 if (attributes & SortAttributeUseArtistSortName)
155 {
156 const CVariant &artistsort = values.at(FieldArtistSort);
157 if (!artistsort.isNull())
158 label = artistsort.asString();
159 }
160 if (label.empty())
161 label = ArrayToString(attributes, values.at(FieldArtist));
162
163 const CVariant &year = values.at(FieldYear);
164 if (!year.isNull())
165 label += StringUtils::Format(" %i", static_cast<int>(year.asInteger()));
166
167 const CVariant &album = values.at(FieldAlbum);
168 if (!album.isNull())
169 label += " " + SortUtils::RemoveArticles(album.asString());
170
171 const CVariant &track = values.at(FieldTrackNumber);
172 if (!track.isNull())
173 label += StringUtils::Format(" %i", (int)track.asInteger());
174
175 return label;
176}
177
178std::string ByTrackNumber(SortAttribute attributes, const SortItem &values)
179{
180 return StringUtils::Format("%i", (int)values.at(FieldTrackNumber).asInteger());
181}
182
183std::string ByTotalDiscs(SortAttribute attributes, const SortItem& values)
184{
185 return StringUtils::Format("%d %s", static_cast<int>(values.at(FieldTotalDiscs).asInteger()),
186 ByLabel(attributes, values));
187}
188std::string ByTime(SortAttribute attributes, const SortItem &values)
189{
190 std::string label;
191 const CVariant &time = values.at(FieldTime);
192 if (time.isInteger())
193 label = StringUtils::Format("%i", (int)time.asInteger());
194 else
195 label = StringUtils::Format("%s", time.asString().c_str());
196 return label;
197}
198
199std::string ByProgramCount(SortAttribute attributes, const SortItem &values)
200{
201 return StringUtils::Format("%i", (int)values.at(FieldProgramCount).asInteger());
202}
203
204std::string ByPlaylistOrder(SortAttribute attributes, const SortItem &values)
205{
206 //! @todo Playlist order is hacked into program count variable (not nice, but ok until 2.0)
207 return ByProgramCount(attributes, values);
208}
209
210std::string ByGenre(SortAttribute attributes, const SortItem &values)
211{
212 return ArrayToString(attributes, values.at(FieldGenre));
213}
214
215std::string ByCountry(SortAttribute attributes, const SortItem &values)
216{
217 return ArrayToString(attributes, values.at(FieldCountry));
218}
219
220std::string ByYear(SortAttribute attributes, const SortItem &values)
221{
222 std::string label;
223 const CVariant &airDate = values.at(FieldAirDate);
224 if (!airDate.isNull() && !airDate.asString().empty())
225 label = airDate.asString() + " ";
226
227 label += StringUtils::Format("%i", (int)values.at(FieldYear).asInteger());
228
229 const CVariant &album = values.at(FieldAlbum);
230 if (!album.isNull())
231 label += " " + SortUtils::RemoveArticles(album.asString());
232
233 const CVariant &track = values.at(FieldTrackNumber);
234 if (!track.isNull())
235 label += StringUtils::Format(" %i", (int)track.asInteger());
236
237 label += " " + ByLabel(attributes, values);
238
239 return label;
240}
241
242std::string ByOrigDate(SortAttribute attributes, const SortItem& values)
243{
244 std::string label;
245 label = values.at(FieldOrigDate).asString();
246
247 const CVariant &album = values.at(FieldAlbum);
248 if (!album.isNull())
249 label += " " + SortUtils::RemoveArticles(album.asString());
250
251 const CVariant &track = values.at(FieldTrackNumber);
252 if (!track.isNull())
253 label += StringUtils::Format(" %i", static_cast<int>(track.asInteger()));
254
255 label += " " + ByLabel(attributes, values);
256
257 return label;
258}
259
260std::string BySortTitle(SortAttribute attributes, const SortItem &values)
261{
262 std::string title = values.at(FieldSortTitle).asString();
263 if (title.empty())
264 title = values.at(FieldTitle).asString();
265
266 if (attributes & SortAttributeIgnoreArticle)
267 title = SortUtils::RemoveArticles(title);
268
269 return title;
270}
271
272std::string ByRating(SortAttribute attributes, const SortItem &values)
273{
274 return StringUtils::Format("%f %s", values.at(FieldRating).asFloat(), ByLabel(attributes, values).c_str());
275}
276
277std::string ByUserRating(SortAttribute attributes, const SortItem &values)
278{
279 return StringUtils::Format("%d %s", static_cast<int>(values.at(FieldUserRating).asInteger()), ByLabel(attributes, values).c_str());
280}
281
282std::string ByVotes(SortAttribute attributes, const SortItem &values)
283{
284 return StringUtils::Format("%d %s", (int)values.at(FieldVotes).asInteger(), ByLabel(attributes, values).c_str());
285}
286
287std::string ByTop250(SortAttribute attributes, const SortItem &values)
288{
289 return StringUtils::Format("%d %s", (int)values.at(FieldTop250).asInteger(), ByLabel(attributes, values).c_str());
290}
291
292std::string ByMPAA(SortAttribute attributes, const SortItem &values)
293{
294 return values.at(FieldMPAA).asString() + " " + ByLabel(attributes, values);
295}
296
297std::string ByStudio(SortAttribute attributes, const SortItem &values)
298{
299 return ArrayToString(attributes, values.at(FieldStudio));
300}
301
302std::string ByEpisodeNumber(SortAttribute attributes, const SortItem &values)
303{
304 // we calculate an offset number based on the episode's
305 // sort season and episode values. in addition
306 // we include specials 'episode' numbers to get proper
307 // sorting of multiple specials in a row. each
308 // of these are given their particular ranges to semi-ensure uniqueness.
309 // theoretical problem: if a show has > 2^15 specials and two of these are placed
310 // after each other they will sort backwards. if a show has > 2^32-1 seasons
311 // or if a season has > 2^16-1 episodes strange things will happen (overflow)
312 uint64_t num;
313 const CVariant &episodeSpecial = values.at(FieldEpisodeNumberSpecialSort);
314 const CVariant &seasonSpecial = values.at(FieldSeasonSpecialSort);
315 if (!episodeSpecial.isNull() && !seasonSpecial.isNull() &&
316 (episodeSpecial.asInteger() > 0 || seasonSpecial.asInteger() > 0))
317 num = ((uint64_t)seasonSpecial.asInteger() << 32) + (episodeSpecial.asInteger() << 16) - ((2 << 15) - values.at(FieldEpisodeNumber).asInteger());
318 else
319 num = ((uint64_t)values.at(FieldSeason).asInteger() << 32) + (values.at(FieldEpisodeNumber).asInteger() << 16);
320
321 std::string title;
322 if (values.find(FieldMediaType) != values.end() && values.at(FieldMediaType).asString() == MediaTypeMovie)
323 title = BySortTitle(attributes, values);
324 if (title.empty())
325 title = ByLabel(attributes, values);
326
327 return StringUtils::Format("%" PRIu64" %s", num, title.c_str());
328}
329
330std::string BySeason(SortAttribute attributes, const SortItem &values)
331{
332 int season = (int)values.at(FieldSeason).asInteger();
333 const CVariant &specialSeason = values.at(FieldSeasonSpecialSort);
334 if (!specialSeason.isNull())
335 season = (int)specialSeason.asInteger();
336
337 return StringUtils::Format("%i %s", season, ByLabel(attributes, values).c_str());
338}
339
340std::string ByNumberOfEpisodes(SortAttribute attributes, const SortItem &values)
341{
342 return StringUtils::Format("%i %s", (int)values.at(FieldNumberOfEpisodes).asInteger(), ByLabel(attributes, values).c_str());
343}
344
345std::string ByNumberOfWatchedEpisodes(SortAttribute attributes, const SortItem &values)
346{
347 return StringUtils::Format("%i %s", (int)values.at(FieldNumberOfWatchedEpisodes).asInteger(), ByLabel(attributes, values).c_str());
348}
349
350std::string ByTvShowStatus(SortAttribute attributes, const SortItem &values)
351{
352 return values.at(FieldTvShowStatus).asString() + " " + ByLabel(attributes, values);
353}
354
355std::string ByTvShowTitle(SortAttribute attributes, const SortItem &values)
356{
357 return values.at(FieldTvShowTitle).asString() + " " + ByLabel(attributes, values);
358}
359
360std::string ByProductionCode(SortAttribute attributes, const SortItem &values)
361{
362 return values.at(FieldProductionCode).asString();
363}
364
365std::string ByVideoResolution(SortAttribute attributes, const SortItem &values)
366{
367 return StringUtils::Format("%i %s", (int)values.at(FieldVideoResolution).asInteger(), ByLabel(attributes, values).c_str());
368}
369
370std::string ByVideoCodec(SortAttribute attributes, const SortItem &values)
371{
372 return StringUtils::Format("%s %s", values.at(FieldVideoCodec).asString().c_str(), ByLabel(attributes, values).c_str());
373}
374
375std::string ByVideoAspectRatio(SortAttribute attributes, const SortItem &values)
376{
377 return StringUtils::Format("%.3f %s", values.at(FieldVideoAspectRatio).asFloat(), ByLabel(attributes, values).c_str());
378}
379
380std::string ByAudioChannels(SortAttribute attributes, const SortItem &values)
381{
382 return StringUtils::Format("%i %s", (int)values.at(FieldAudioChannels).asInteger(), ByLabel(attributes, values).c_str());
383}
384
385std::string ByAudioCodec(SortAttribute attributes, const SortItem &values)
386{
387 return StringUtils::Format("%s %s", values.at(FieldAudioCodec).asString().c_str(), ByLabel(attributes, values).c_str());
388}
389
390std::string ByAudioLanguage(SortAttribute attributes, const SortItem &values)
391{
392 return StringUtils::Format("%s %s", values.at(FieldAudioLanguage).asString().c_str(), ByLabel(attributes, values).c_str());
393}
394
395std::string BySubtitleLanguage(SortAttribute attributes, const SortItem &values)
396{
397 return StringUtils::Format("%s %s", values.at(FieldSubtitleLanguage).asString().c_str(), ByLabel(attributes, values).c_str());
398}
399
400std::string ByBitrate(SortAttribute attributes, const SortItem &values)
401{
402 return StringUtils::Format("%" PRId64, values.at(FieldBitrate).asInteger());
403}
404
405std::string ByListeners(SortAttribute attributes, const SortItem &values)
406{
407 return StringUtils::Format("%" PRId64, values.at(FieldListeners).asInteger());
408}
409
410std::string ByRandom(SortAttribute attributes, const SortItem &values)
411{
412 return StringUtils::Format("%i", CUtil::GetRandomNumber());
413}
414
415std::string ByChannel(SortAttribute attributes, const SortItem &values)
416{
417 return values.at(FieldChannelName).asString();
418}
419
420std::string ByChannelNumber(SortAttribute attributes, const SortItem &values)
421{
422 return values.at(FieldChannelNumber).asString();
423}
424
425std::string ByClientChannelOrder(SortAttribute attributes, const SortItem& values)
426{
427 return values.at(FieldClientChannelOrder).asString();
428}
429
430std::string ByDateTaken(SortAttribute attributes, const SortItem &values)
431{
432 return values.at(FieldDateTaken).asString();
433}
434
435std::string ByRelevance(SortAttribute attributes, const SortItem &values)
436{
437 return StringUtils::Format("%i", (int)values.at(FieldRelevance).asInteger());
438}
439
440std::string ByInstallDate(SortAttribute attributes, const SortItem &values)
441{
442 return values.at(FieldInstallDate).asString();
443}
444
445std::string ByLastUpdated(SortAttribute attributes, const SortItem &values)
446{
447 return values.at(FieldLastUpdated).asString();
448}
449
450std::string ByLastUsed(SortAttribute attributes, const SortItem &values)
451{
452 return values.at(FieldLastUsed).asString();
453}
454
455std::string ByBPM(SortAttribute attributes, const SortItem& values)
456{
457 return StringUtils::Format("%d %s", static_cast<int>(values.at(FieldBPM).asInteger()),
458 ByLabel(attributes, values));
459}
460
461bool preliminarySort(const SortItem &left, const SortItem &right, bool handleFolder, bool &result, std::wstring &labelLeft, std::wstring &labelRight)
462{
463 // make sure both items have the necessary data to do the sorting
464 SortItem::const_iterator itLeftSort, itRightSort;
465 if ((itLeftSort = left.find(FieldSort)) == left.end())
466 {
467 result = false;
468 return true;
469 }
470 if ((itRightSort = right.find(FieldSort)) == right.end())
471 {
472 result = true;
473 return true;
474 }
475
476 // look at special sorting behaviour
477 SortItem::const_iterator itLeft, itRight;
478 SortSpecial leftSortSpecial = SortSpecialNone;
479 SortSpecial rightSortSpecial = SortSpecialNone;
480 if ((itLeft = left.find(FieldSortSpecial)) != left.end() && itLeft->second.asInteger() <= (int64_t)SortSpecialOnBottom)
481 leftSortSpecial = (SortSpecial)itLeft->second.asInteger();
482 if ((itRight = right.find(FieldSortSpecial)) != right.end() && itRight->second.asInteger() <= (int64_t)SortSpecialOnBottom)
483 rightSortSpecial = (SortSpecial)itRight->second.asInteger();
484
485 // one has a special sort
486 if (leftSortSpecial != rightSortSpecial)
487 {
488 // left should be sorted on top
489 // or right should be sorted on bottom
490 // => left is sorted above right
491 if (leftSortSpecial == SortSpecialOnTop ||
492 rightSortSpecial == SortSpecialOnBottom)
493 {
494 result = true;
495 return true;
496 }
497
498 // otherwise right is sorted above left
499 result = false;
500 return true;
501 }
502 // both have either sort on top or sort on bottom -> leave as-is
503 else if (leftSortSpecial != SortSpecialNone && leftSortSpecial == rightSortSpecial)
504 {
505 result = false;
506 return true;
507 }
508
509 if (handleFolder)
510 {
511 itLeft = left.find(FieldFolder);
512 itRight = right.find(FieldFolder);
513 if (itLeft != left.end() && itRight != right.end() &&
514 itLeft->second.asBoolean() != itRight->second.asBoolean())
515 {
516 result = itLeft->second.asBoolean();
517 return true;
518 }
519 }
520
521 labelLeft = itLeftSort->second.asWideString();
522 labelRight = itRightSort->second.asWideString();
523
524 return false;
525}
526
527bool SorterAscending(const SortItem &left, const SortItem &right)
528{
529 bool result;
530 std::wstring labelLeft, labelRight;
531 if (preliminarySort(left, right, true, result, labelLeft, labelRight))
532 return result;
533
534 return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) < 0;
535}
536
537bool SorterDescending(const SortItem &left, const SortItem &right)
538{
539 bool result;
540 std::wstring labelLeft, labelRight;
541 if (preliminarySort(left, right, true, result, labelLeft, labelRight))
542 return result;
543
544 return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) > 0;
545}
546
547bool SorterIgnoreFoldersAscending(const SortItem &left, const SortItem &right)
548{
549 bool result;
550 std::wstring labelLeft, labelRight;
551 if (preliminarySort(left, right, false, result, labelLeft, labelRight))
552 return result;
553
554 return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) < 0;
555}
556
557bool SorterIgnoreFoldersDescending(const SortItem &left, const SortItem &right)
558{
559 bool result;
560 std::wstring labelLeft, labelRight;
561 if (preliminarySort(left, right, false, result, labelLeft, labelRight))
562 return result;
563
564 return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) > 0;
565}
566
567bool SorterIndirectAscending(const SortItemPtr &left, const SortItemPtr &right)
568{
569 return SorterAscending(*left, *right);
570}
571
572bool SorterIndirectDescending(const SortItemPtr &left, const SortItemPtr &right)
573{
574 return SorterDescending(*left, *right);
575}
576
577bool SorterIndirectIgnoreFoldersAscending(const SortItemPtr &left, const SortItemPtr &right)
578{
579 return SorterIgnoreFoldersAscending(*left, *right);
580}
581
582bool SorterIndirectIgnoreFoldersDescending(const SortItemPtr &left, const SortItemPtr &right)
583{
584 return SorterIgnoreFoldersDescending(*left, *right);
585}
586
587// clang-format off
588std::map<SortBy, SortUtils::SortPreparator> fillPreparators()
589{
590 std::map<SortBy, SortUtils::SortPreparator> preparators;
591
592 preparators[SortByNone] = NULL;
593 preparators[SortByLabel] = ByLabel;
594 preparators[SortByDate] = ByDate;
595 preparators[SortBySize] = BySize;
596 preparators[SortByFile] = ByFile;
597 preparators[SortByPath] = ByPath;
598 preparators[SortByDriveType] = ByDriveType;
599 preparators[SortByTitle] = ByTitle;
600 preparators[SortByTrackNumber] = ByTrackNumber;
601 preparators[SortByTime] = ByTime;
602 preparators[SortByArtist] = ByArtist;
603 preparators[SortByArtistThenYear] = ByArtistThenYear;
604 preparators[SortByAlbum] = ByAlbum;
605 preparators[SortByAlbumType] = ByAlbumType;
606 preparators[SortByGenre] = ByGenre;
607 preparators[SortByCountry] = ByCountry;
608 preparators[SortByYear] = ByYear;
609 preparators[SortByRating] = ByRating;
610 preparators[SortByUserRating] = ByUserRating;
611 preparators[SortByVotes] = ByVotes;
612 preparators[SortByTop250] = ByTop250;
613 preparators[SortByProgramCount] = ByProgramCount;
614 preparators[SortByPlaylistOrder] = ByPlaylistOrder;
615 preparators[SortByEpisodeNumber] = ByEpisodeNumber;
616 preparators[SortBySeason] = BySeason;
617 preparators[SortByNumberOfEpisodes] = ByNumberOfEpisodes;
618 preparators[SortByNumberOfWatchedEpisodes] = ByNumberOfWatchedEpisodes;
619 preparators[SortByTvShowStatus] = ByTvShowStatus;
620 preparators[SortByTvShowTitle] = ByTvShowTitle;
621 preparators[SortBySortTitle] = BySortTitle;
622 preparators[SortByProductionCode] = ByProductionCode;
623 preparators[SortByMPAA] = ByMPAA;
624 preparators[SortByVideoResolution] = ByVideoResolution;
625 preparators[SortByVideoCodec] = ByVideoCodec;
626 preparators[SortByVideoAspectRatio] = ByVideoAspectRatio;
627 preparators[SortByAudioChannels] = ByAudioChannels;
628 preparators[SortByAudioCodec] = ByAudioCodec;
629 preparators[SortByAudioLanguage] = ByAudioLanguage;
630 preparators[SortBySubtitleLanguage] = BySubtitleLanguage;
631 preparators[SortByStudio] = ByStudio;
632 preparators[SortByDateAdded] = ByDateAdded;
633 preparators[SortByLastPlayed] = ByLastPlayed;
634 preparators[SortByPlaycount] = ByPlaycount;
635 preparators[SortByListeners] = ByListeners;
636 preparators[SortByBitrate] = ByBitrate;
637 preparators[SortByRandom] = ByRandom;
638 preparators[SortByChannel] = ByChannel;
639 preparators[SortByChannelNumber] = ByChannelNumber;
640 preparators[SortByClientChannelOrder] = ByClientChannelOrder;
641 preparators[SortByDateTaken] = ByDateTaken;
642 preparators[SortByRelevance] = ByRelevance;
643 preparators[SortByInstallDate] = ByInstallDate;
644 preparators[SortByLastUpdated] = ByLastUpdated;
645 preparators[SortByLastUsed] = ByLastUsed;
646 preparators[SortByTotalDiscs] = ByTotalDiscs;
647 preparators[SortByOrigDate] = ByOrigDate;
648 preparators[SortByBPM] = ByBPM;
649
650 return preparators;
651}
652// clang-format on
653
654std::map<SortBy, Fields> fillSortingFields()
655{
656 std::map<SortBy, Fields> sortingFields;
657
658 sortingFields.insert(std::pair<SortBy, Fields>(SortByNone, Fields()));
659
660 sortingFields[SortByLabel].insert(FieldLabel);
661 sortingFields[SortByDate].insert(FieldDate);
662 sortingFields[SortBySize].insert(FieldSize);
663 sortingFields[SortByFile].insert(FieldPath);
664 sortingFields[SortByFile].insert(FieldStartOffset);
665 sortingFields[SortByPath].insert(FieldPath);
666 sortingFields[SortByPath].insert(FieldStartOffset);
667 sortingFields[SortByDriveType].insert(FieldDriveType);
668 sortingFields[SortByTitle].insert(FieldTitle);
669 sortingFields[SortByTrackNumber].insert(FieldTrackNumber);
670 sortingFields[SortByTime].insert(FieldTime);
671 sortingFields[SortByArtist].insert(FieldArtist);
672 sortingFields[SortByArtist].insert(FieldArtistSort);
673 sortingFields[SortByArtist].insert(FieldYear);
674 sortingFields[SortByArtist].insert(FieldAlbum);
675 sortingFields[SortByArtist].insert(FieldTrackNumber);
676 sortingFields[SortByArtistThenYear].insert(FieldArtist);
677 sortingFields[SortByArtistThenYear].insert(FieldArtistSort);
678 sortingFields[SortByArtistThenYear].insert(FieldYear);
679 sortingFields[SortByArtistThenYear].insert(FieldOrigDate);
680 sortingFields[SortByArtistThenYear].insert(FieldAlbum);
681 sortingFields[SortByArtistThenYear].insert(FieldTrackNumber);
682 sortingFields[SortByAlbum].insert(FieldAlbum);
683 sortingFields[SortByAlbum].insert(FieldArtist);
684 sortingFields[SortByAlbum].insert(FieldArtistSort);
685 sortingFields[SortByAlbum].insert(FieldTrackNumber);
686 sortingFields[SortByAlbumType].insert(FieldAlbumType);
687 sortingFields[SortByGenre].insert(FieldGenre);
688 sortingFields[SortByCountry].insert(FieldCountry);
689 sortingFields[SortByYear].insert(FieldYear);
690 sortingFields[SortByYear].insert(FieldAirDate);
691 sortingFields[SortByYear].insert(FieldAlbum);
692 sortingFields[SortByYear].insert(FieldTrackNumber);
693 sortingFields[SortByYear].insert(FieldOrigDate);
694 sortingFields[SortByRating].insert(FieldRating);
695 sortingFields[SortByUserRating].insert(FieldUserRating);
696 sortingFields[SortByVotes].insert(FieldVotes);
697 sortingFields[SortByTop250].insert(FieldTop250);
698 sortingFields[SortByProgramCount].insert(FieldProgramCount);
699 sortingFields[SortByPlaylistOrder].insert(FieldProgramCount);
700 sortingFields[SortByEpisodeNumber].insert(FieldEpisodeNumber);
701 sortingFields[SortByEpisodeNumber].insert(FieldSeason);
702 sortingFields[SortByEpisodeNumber].insert(FieldEpisodeNumberSpecialSort);
703 sortingFields[SortByEpisodeNumber].insert(FieldSeasonSpecialSort);
704 sortingFields[SortByEpisodeNumber].insert(FieldTitle);
705 sortingFields[SortByEpisodeNumber].insert(FieldSortTitle);
706 sortingFields[SortBySeason].insert(FieldSeason);
707 sortingFields[SortBySeason].insert(FieldSeasonSpecialSort);
708 sortingFields[SortByNumberOfEpisodes].insert(FieldNumberOfEpisodes);
709 sortingFields[SortByNumberOfWatchedEpisodes].insert(FieldNumberOfWatchedEpisodes);
710 sortingFields[SortByTvShowStatus].insert(FieldTvShowStatus);
711 sortingFields[SortByTvShowTitle].insert(FieldTvShowTitle);
712 sortingFields[SortBySortTitle].insert(FieldSortTitle);
713 sortingFields[SortBySortTitle].insert(FieldTitle);
714 sortingFields[SortByProductionCode].insert(FieldProductionCode);
715 sortingFields[SortByMPAA].insert(FieldMPAA);
716 sortingFields[SortByVideoResolution].insert(FieldVideoResolution);
717 sortingFields[SortByVideoCodec].insert(FieldVideoCodec);
718 sortingFields[SortByVideoAspectRatio].insert(FieldVideoAspectRatio);
719 sortingFields[SortByAudioChannels].insert(FieldAudioChannels);
720 sortingFields[SortByAudioCodec].insert(FieldAudioCodec);
721 sortingFields[SortByAudioLanguage].insert(FieldAudioLanguage);
722 sortingFields[SortBySubtitleLanguage].insert(FieldSubtitleLanguage);
723 sortingFields[SortByStudio].insert(FieldStudio);
724 sortingFields[SortByDateAdded].insert(FieldDateAdded);
725 sortingFields[SortByDateAdded].insert(FieldId);
726 sortingFields[SortByLastPlayed].insert(FieldLastPlayed);
727 sortingFields[SortByPlaycount].insert(FieldPlaycount);
728 sortingFields[SortByListeners].insert(FieldListeners);
729 sortingFields[SortByBitrate].insert(FieldBitrate);
730 sortingFields[SortByChannel].insert(FieldChannelName);
731 sortingFields[SortByChannelNumber].insert(FieldChannelNumber);
732 sortingFields[SortByClientChannelOrder].insert(FieldClientChannelOrder);
733 sortingFields[SortByDateTaken].insert(FieldDateTaken);
734 sortingFields[SortByRelevance].insert(FieldRelevance);
735 sortingFields[SortByInstallDate].insert(FieldInstallDate);
736 sortingFields[SortByLastUpdated].insert(FieldLastUpdated);
737 sortingFields[SortByLastUsed].insert(FieldLastUsed);
738 sortingFields[SortByTotalDiscs].insert(FieldTotalDiscs);
739 sortingFields[SortByOrigDate].insert(FieldOrigDate);
740 sortingFields[SortByOrigDate].insert(FieldAlbum);
741 sortingFields[SortByOrigDate].insert(FieldTrackNumber);
742 sortingFields[SortByBPM].insert(FieldBPM);
743 sortingFields.insert(std::pair<SortBy, Fields>(SortByRandom, Fields()));
744
745 return sortingFields;
746}
747
748std::map<SortBy, SortUtils::SortPreparator> SortUtils::m_preparators = fillPreparators();
749std::map<SortBy, Fields> SortUtils::m_sortingFields = fillSortingFields();
750
751void SortUtils::GetFieldsForSQLSort(const MediaType& mediaType,
752 SortBy sortMethod,
753 FieldList& fields)
754{
755 fields.clear();
756 if (mediaType == MediaTypeNone)
757 return;
758
759 if (mediaType == MediaTypeAlbum)
760 {
761 if (sortMethod == SortByLabel || sortMethod == SortByAlbum || sortMethod == SortByTitle)
762 {
763 fields.emplace_back(FieldAlbum);
764 fields.emplace_back(FieldArtist);
765 }
766 else if (sortMethod == SortByAlbumType)
767 {
768 fields.emplace_back(FieldAlbumType);
769 fields.emplace_back(FieldAlbum);
770 fields.emplace_back(FieldArtist);
771 }
772 else if (sortMethod == SortByArtist)
773 {
774 fields.emplace_back(FieldArtist);
775 fields.emplace_back(FieldAlbum);
776 }
777 else if (sortMethod == SortByArtistThenYear)
778 {
779 fields.emplace_back(FieldArtist);
780 fields.emplace_back(FieldYear);
781 fields.emplace_back(FieldAlbum);
782 }
783 else if (sortMethod == SortByYear)
784 {
785 fields.emplace_back(FieldYear);
786 fields.emplace_back(FieldAlbum);
787 }
788 else if (sortMethod == SortByGenre)
789 {
790 fields.emplace_back(FieldGenre);
791 fields.emplace_back(FieldAlbum);
792 }
793 else if (sortMethod == SortByDateAdded)
794 fields.emplace_back(FieldDateAdded);
795 else if (sortMethod == SortByPlaycount)
796 {
797 fields.emplace_back(FieldPlaycount);
798 fields.emplace_back(FieldAlbum);
799 }
800 else if (sortMethod == SortByLastPlayed)
801 {
802 fields.emplace_back(FieldLastPlayed);
803 fields.emplace_back(FieldAlbum);
804 }
805 else if (sortMethod == SortByRating)
806 {
807 fields.emplace_back(FieldRating);
808 fields.emplace_back(FieldAlbum);
809 }
810 else if (sortMethod == SortByVotes)
811 {
812 fields.emplace_back(FieldVotes);
813 fields.emplace_back(FieldAlbum);
814 }
815 else if (sortMethod == SortByUserRating)
816 {
817 fields.emplace_back(FieldUserRating);
818 fields.emplace_back(FieldAlbum);
819 }
820 else if (sortMethod == SortByTotalDiscs)
821 {
822 fields.emplace_back(FieldTotalDiscs);
823 fields.emplace_back(FieldAlbum);
824 }
825 else if (sortMethod == SortByOrigDate)
826 {
827 fields.emplace_back(FieldOrigDate);
828 fields.emplace_back(FieldAlbum);
829 }
830 }
831 else if (mediaType == MediaTypeSong)
832 {
833 if (sortMethod == SortByLabel || sortMethod == SortByTrackNumber)
834 fields.emplace_back(FieldTrackNumber);
835 else if (sortMethod == SortByTitle)
836 fields.emplace_back(FieldTitle);
837 else if (sortMethod == SortByAlbum)
838 {
839 fields.emplace_back(FieldAlbum);
840 fields.emplace_back(FieldAlbumArtist);
841 fields.emplace_back(FieldTrackNumber);
842 }
843 else if (sortMethod == SortByArtist)
844 {
845 fields.emplace_back(FieldArtist);
846 fields.emplace_back(FieldAlbum);
847 fields.emplace_back(FieldTrackNumber);
848 }
849 else if (sortMethod == SortByArtistThenYear)
850 {
851 fields.emplace_back(FieldArtist);
852 fields.emplace_back(FieldYear);
853 fields.emplace_back(FieldAlbum);
854 fields.emplace_back(FieldTrackNumber);
855 }
856 else if (sortMethod == SortByYear)
857 {
858 fields.emplace_back(FieldYear);
859 fields.emplace_back(FieldAlbum);
860 fields.emplace_back(FieldTrackNumber);
861 }
862 else if (sortMethod == SortByGenre)
863 {
864 fields.emplace_back(FieldGenre);
865 fields.emplace_back(FieldAlbum);
866 }
867 else if (sortMethod == SortByDateAdded)
868 fields.emplace_back(FieldDateAdded);
869 else if (sortMethod == SortByPlaycount)
870 {
871 fields.emplace_back(FieldPlaycount);
872 fields.emplace_back(FieldTrackNumber);
873 }
874 else if (sortMethod == SortByLastPlayed)
875 {
876 fields.emplace_back(FieldLastPlayed);
877 fields.emplace_back(FieldTrackNumber);
878 }
879 else if (sortMethod == SortByRating)
880 {
881 fields.emplace_back(FieldRating);
882 fields.emplace_back(FieldTrackNumber);
883 }
884 else if (sortMethod == SortByVotes)
885 {
886 fields.emplace_back(FieldVotes);
887 fields.emplace_back(FieldTrackNumber);
888 }
889 else if (sortMethod == SortByUserRating)
890 {
891 fields.emplace_back(FieldUserRating);
892 fields.emplace_back(FieldTrackNumber);
893 }
894 else if (sortMethod == SortByFile)
895 {
896 fields.emplace_back(FieldPath);
897 fields.emplace_back(FieldFilename);
898 fields.emplace_back(FieldStartOffset);
899 }
900 else if (sortMethod == SortByTime)
901 fields.emplace_back(FieldTime);
902 else if (sortMethod == SortByAlbumType)
903 {
904 fields.emplace_back(FieldAlbumType);
905 fields.emplace_back(FieldAlbum);
906 fields.emplace_back(FieldTrackNumber);
907 }
908 else if (sortMethod == SortByOrigDate)
909 {
910 fields.emplace_back(FieldOrigDate);
911 fields.emplace_back(FieldAlbum);
912 fields.emplace_back(FieldTrackNumber);
913 }
914 else if (sortMethod == SortByBPM)
915 fields.emplace_back(FieldBPM);
916 }
917 else if (mediaType == MediaTypeArtist)
918 {
919 if (sortMethod == SortByLabel || sortMethod == SortByTitle || sortMethod == SortByArtist)
920 fields.emplace_back(FieldArtist);
921 else if (sortMethod == SortByGenre)
922 fields.emplace_back(FieldGenre);
923 else if (sortMethod == SortByDateAdded)
924 fields.emplace_back(FieldDateAdded);
925 }
926
927 // Add sort by id to define order when other fields same or sort none
928 fields.emplace_back(FieldId);
929 return;
930}
931
932
933void SortUtils::Sort(SortBy sortBy, SortOrder sortOrder, SortAttribute attributes, DatabaseResults& items, int limitEnd /* = -1 */, int limitStart /* = 0 */)
934{
935 if (sortBy != SortByNone)
936 {
937 // get the matching SortPreparator
938 SortPreparator preparator = getPreparator(sortBy);
939 if (preparator != NULL)
940 {
941 Fields sortingFields = GetFieldsForSorting(sortBy);
942
943 // Prepare the string used for sorting and store it under FieldSort
944 for (DatabaseResults::iterator item = items.begin(); item != items.end(); ++item)
945 {
946 // add all fields to the item that are required for sorting if they are currently missing
947 for (Fields::const_iterator field = sortingFields.begin(); field != sortingFields.end(); ++field)
948 {
949 if (item->find(*field) == item->end())
950 item->insert(std::pair<Field, CVariant>(*field, CVariant::ConstNullVariant));
951 }
952
953 std::wstring sortLabel;
954 g_charsetConverter.utf8ToW(preparator(attributes, *item), sortLabel, false);
955 item->insert(std::pair<Field, CVariant>(FieldSort, CVariant(sortLabel)));
956 }
957
958 // Do the sorting
959 std::stable_sort(items.begin(), items.end(), getSorter(sortOrder, attributes));
960 }
961 }
962
963 if (limitStart > 0 && (size_t)limitStart < items.size())
964 {
965 items.erase(items.begin(), items.begin() + limitStart);
966 limitEnd -= limitStart;
967 }
968 if (limitEnd > 0 && (size_t)limitEnd < items.size())
969 items.erase(items.begin() + limitEnd, items.end());
970}
971
972void SortUtils::Sort(SortBy sortBy, SortOrder sortOrder, SortAttribute attributes, SortItems& items, int limitEnd /* = -1 */, int limitStart /* = 0 */)
973{
974 if (sortBy != SortByNone)
975 {
976 // get the matching SortPreparator
977 SortPreparator preparator = getPreparator(sortBy);
978 if (preparator != NULL)
979 {
980 Fields sortingFields = GetFieldsForSorting(sortBy);
981
982 // Prepare the string used for sorting and store it under FieldSort
983 for (SortItems::iterator item = items.begin(); item != items.end(); ++item)
984 {
985 // add all fields to the item that are required for sorting if they are currently missing
986 for (Fields::const_iterator field = sortingFields.begin(); field != sortingFields.end(); ++field)
987 {
988 if ((*item)->find(*field) == (*item)->end())
989 (*item)->insert(std::pair<Field, CVariant>(*field, CVariant::ConstNullVariant));
990 }
991
992 std::wstring sortLabel;
993 g_charsetConverter.utf8ToW(preparator(attributes, **item), sortLabel, false);
994 (*item)->insert(std::pair<Field, CVariant>(FieldSort, CVariant(sortLabel)));
995 }
996
997 // Do the sorting
998 std::stable_sort(items.begin(), items.end(), getSorterIndirect(sortOrder, attributes));
999 }
1000 }
1001
1002 if (limitStart > 0 && (size_t)limitStart < items.size())
1003 {
1004 items.erase(items.begin(), items.begin() + limitStart);
1005 limitEnd -= limitStart;
1006 }
1007 if (limitEnd > 0 && (size_t)limitEnd < items.size())
1008 items.erase(items.begin() + limitEnd, items.end());
1009}
1010
1011void SortUtils::Sort(const SortDescription &sortDescription, DatabaseResults& items)
1012{
1013 Sort(sortDescription.sortBy, sortDescription.sortOrder, sortDescription.sortAttributes, items, sortDescription.limitEnd, sortDescription.limitStart);
1014}
1015
1016void SortUtils::Sort(const SortDescription &sortDescription, SortItems& items)
1017{
1018 Sort(sortDescription.sortBy, sortDescription.sortOrder, sortDescription.sortAttributes, items, sortDescription.limitEnd, sortDescription.limitStart);
1019}
1020
1021bool SortUtils::SortFromDataset(const SortDescription &sortDescription, const MediaType &mediaType, const std::unique_ptr<dbiplus::Dataset> &dataset, DatabaseResults &results)
1022{
1023 FieldList fields;
1024 if (!DatabaseUtils::GetSelectFields(SortUtils::GetFieldsForSorting(sortDescription.sortBy), mediaType, fields))
1025 fields.clear();
1026
1027 if (!DatabaseUtils::GetDatabaseResults(mediaType, fields, dataset, results))
1028 return false;
1029
1030 SortDescription sorting = sortDescription;
1031 if (sortDescription.sortBy == SortByNone)
1032 {
1033 sorting.limitStart = 0;
1034 sorting.limitEnd = -1;
1035 }
1036
1037 Sort(sorting, results);
1038
1039 return true;
1040}
1041
1042const SortUtils::SortPreparator& SortUtils::getPreparator(SortBy sortBy)
1043{
1044 std::map<SortBy, SortPreparator>::const_iterator it = m_preparators.find(sortBy);
1045 if (it != m_preparators.end())
1046 return it->second;
1047
1048 return m_preparators[SortByNone];
1049}
1050
1051SortUtils::Sorter SortUtils::getSorter(SortOrder sortOrder, SortAttribute attributes)
1052{
1053 if (attributes & SortAttributeIgnoreFolders)
1054 return sortOrder == SortOrderDescending ? SorterIgnoreFoldersDescending : SorterIgnoreFoldersAscending;
1055
1056 return sortOrder == SortOrderDescending ? SorterDescending : SorterAscending;
1057}
1058
1059SortUtils::SorterIndirect SortUtils::getSorterIndirect(SortOrder sortOrder, SortAttribute attributes)
1060{
1061 if (attributes & SortAttributeIgnoreFolders)
1062 return sortOrder == SortOrderDescending ? SorterIndirectIgnoreFoldersDescending : SorterIndirectIgnoreFoldersAscending;
1063
1064 return sortOrder == SortOrderDescending ? SorterIndirectDescending : SorterIndirectAscending;
1065}
1066
1067const Fields& SortUtils::GetFieldsForSorting(SortBy sortBy)
1068{
1069 std::map<SortBy, Fields>::const_iterator it = m_sortingFields.find(sortBy);
1070 if (it != m_sortingFields.end())
1071 return it->second;
1072
1073 return m_sortingFields[SortByNone];
1074}
1075
1076std::string SortUtils::RemoveArticles(const std::string &label)
1077{
1078 std::set<std::string> sortTokens = g_langInfo.GetSortTokens();
1079 for (std::set<std::string>::const_iterator token = sortTokens.begin(); token != sortTokens.end(); ++token)
1080 {
1081 if (token->size() < label.size() && StringUtils::StartsWithNoCase(label, *token))
1082 return label.substr(token->size());
1083 }
1084
1085 return label;
1086}
1087
1088typedef struct
1089{
1090 SortBy sort;
1091 SORT_METHOD old;
1092 SortAttribute flags;
1093 int label;
1094} sort_map;
1095
1096// clang-format off
1097const sort_map table[] = {
1098 { SortByLabel, SORT_METHOD_LABEL, SortAttributeNone, 551 },
1099 { SortByLabel, SORT_METHOD_LABEL_IGNORE_THE, SortAttributeIgnoreArticle, 551 },
1100 { SortByLabel, SORT_METHOD_LABEL_IGNORE_FOLDERS, SortAttributeIgnoreFolders, 551 },
1101 { SortByDate, SORT_METHOD_DATE, SortAttributeNone, 552 },
1102 { SortBySize, SORT_METHOD_SIZE, SortAttributeNone, 553 },
1103 { SortByBitrate, SORT_METHOD_BITRATE, SortAttributeNone, 623 },
1104 { SortByDriveType, SORT_METHOD_DRIVE_TYPE, SortAttributeNone, 564 },
1105 { SortByTrackNumber, SORT_METHOD_TRACKNUM, SortAttributeNone, 554 },
1106 { SortByEpisodeNumber, SORT_METHOD_EPISODE, SortAttributeNone, 20359 },// 20360 "Episodes" used for SORT_METHOD_EPISODE for sorting tvshows by episode count
1107 { SortByTime, SORT_METHOD_DURATION, SortAttributeNone, 180 },
1108 { SortByTime, SORT_METHOD_VIDEO_RUNTIME, SortAttributeNone, 180 },
1109 { SortByTitle, SORT_METHOD_TITLE, SortAttributeNone, 556 },
1110 { SortByTitle, SORT_METHOD_TITLE_IGNORE_THE, SortAttributeIgnoreArticle, 556 },
1111 { SortByTitle, SORT_METHOD_VIDEO_TITLE, SortAttributeNone, 556 },
1112 { SortByArtist, SORT_METHOD_ARTIST, SortAttributeNone, 557 },
1113 { SortByArtistThenYear, SORT_METHOD_ARTIST_AND_YEAR, SortAttributeNone, 578 },
1114 { SortByArtist, SORT_METHOD_ARTIST_IGNORE_THE, SortAttributeIgnoreArticle, 557 },
1115 { SortByAlbum, SORT_METHOD_ALBUM, SortAttributeNone, 558 },
1116 { SortByAlbum, SORT_METHOD_ALBUM_IGNORE_THE, SortAttributeIgnoreArticle, 558 },
1117 { SortByGenre, SORT_METHOD_GENRE, SortAttributeNone, 515 },
1118 { SortByCountry, SORT_METHOD_COUNTRY, SortAttributeNone, 574 },
1119 { SortByDateAdded, SORT_METHOD_DATEADDED, SortAttributeIgnoreFolders, 570 },
1120 { SortByFile, SORT_METHOD_FILE, SortAttributeIgnoreFolders, 561 },
1121 { SortByRating, SORT_METHOD_SONG_RATING, SortAttributeNone, 563 },
1122 { SortByRating, SORT_METHOD_VIDEO_RATING, SortAttributeIgnoreFolders, 563 },
1123 { SortByUserRating, SORT_METHOD_SONG_USER_RATING, SortAttributeIgnoreFolders, 38018 },
1124 { SortByUserRating, SORT_METHOD_VIDEO_USER_RATING, SortAttributeIgnoreFolders, 38018 },
1125 { SortBySortTitle, SORT_METHOD_VIDEO_SORT_TITLE, SortAttributeIgnoreFolders, 171 },
1126 { SortBySortTitle, SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE, (SortAttribute)(SortAttributeIgnoreFolders | SortAttributeIgnoreArticle), 171 },
1127 { SortByYear, SORT_METHOD_YEAR, SortAttributeIgnoreFolders, 562 },
1128 { SortByProductionCode, SORT_METHOD_PRODUCTIONCODE, SortAttributeNone, 20368 },
1129 { SortByProgramCount, SORT_METHOD_PROGRAM_COUNT, SortAttributeNone, 567 }, // label is "play count"
1130 { SortByPlaylistOrder, SORT_METHOD_PLAYLIST_ORDER, SortAttributeIgnoreFolders, 559 },
1131 { SortByMPAA, SORT_METHOD_MPAA_RATING, SortAttributeNone, 20074 },
1132 { SortByStudio, SORT_METHOD_STUDIO, SortAttributeNone, 572 },
1133 { SortByStudio, SORT_METHOD_STUDIO_IGNORE_THE, SortAttributeIgnoreArticle, 572 },
1134 { SortByPath, SORT_METHOD_FULLPATH, SortAttributeNone, 573 },
1135 { SortByLastPlayed, SORT_METHOD_LASTPLAYED, SortAttributeIgnoreFolders, 568 },
1136 { SortByPlaycount, SORT_METHOD_PLAYCOUNT, SortAttributeIgnoreFolders, 567 },
1137 { SortByListeners, SORT_METHOD_LISTENERS, SortAttributeNone, 20455 },
1138 { SortByChannel, SORT_METHOD_CHANNEL, SortAttributeNone, 19029 },
1139 { SortByChannel, SORT_METHOD_CHANNEL_NUMBER, SortAttributeNone, 549 },
1140 { SortByChannel, SORT_METHOD_CLIENT_CHANNEL_ORDER, SortAttributeNone, 19315 },
1141 { SortByDateTaken, SORT_METHOD_DATE_TAKEN, SortAttributeIgnoreFolders, 577 },
1142 { SortByNone, SORT_METHOD_NONE, SortAttributeNone, 16018 },
1143 { SortByTotalDiscs, SORT_METHOD_TOTAL_DISCS, SortAttributeNone, 38077 },
1144 { SortByOrigDate, SORT_METHOD_ORIG_DATE, SortAttributeNone, 38079 },
1145 { SortByBPM, SORT_METHOD_BPM, SortAttributeNone, 38080 },
1146
1147 // the following have no corresponding SORT_METHOD_*
1148 { SortByAlbumType, SORT_METHOD_NONE, SortAttributeNone, 564 },
1149 { SortByVotes, SORT_METHOD_NONE, SortAttributeNone, 205 },
1150 { SortByTop250, SORT_METHOD_NONE, SortAttributeNone, 13409 },
1151 { SortByMPAA, SORT_METHOD_NONE, SortAttributeNone, 20074 },
1152 { SortByDateAdded, SORT_METHOD_NONE, SortAttributeNone, 570 },
1153 { SortByTvShowTitle, SORT_METHOD_NONE, SortAttributeNone, 20364 },
1154 { SortByTvShowStatus, SORT_METHOD_NONE, SortAttributeNone, 126 },
1155 { SortBySeason, SORT_METHOD_NONE, SortAttributeNone, 20373 },
1156 { SortByNumberOfEpisodes, SORT_METHOD_NONE, SortAttributeNone, 20360 },
1157 { SortByNumberOfWatchedEpisodes, SORT_METHOD_NONE, SortAttributeNone, 21441 },
1158 { SortByVideoResolution, SORT_METHOD_NONE, SortAttributeNone, 21443 },
1159 { SortByVideoCodec, SORT_METHOD_NONE, SortAttributeNone, 21445 },
1160 { SortByVideoAspectRatio, SORT_METHOD_NONE, SortAttributeNone, 21374 },
1161 { SortByAudioChannels, SORT_METHOD_NONE, SortAttributeNone, 21444 },
1162 { SortByAudioCodec, SORT_METHOD_NONE, SortAttributeNone, 21446 },
1163 { SortByAudioLanguage, SORT_METHOD_NONE, SortAttributeNone, 21447 },
1164 { SortBySubtitleLanguage, SORT_METHOD_NONE, SortAttributeNone, 21448 },
1165 { SortByRandom, SORT_METHOD_NONE, SortAttributeNone, 590 }
1166};
1167// clang-format on
1168
1169SORT_METHOD SortUtils::TranslateOldSortMethod(SortBy sortBy, bool ignoreArticle)
1170{
1171 for (const sort_map& t : table)
1172 {
1173 if (t.sort == sortBy)
1174 {
1175 if (ignoreArticle == ((t.flags & SortAttributeIgnoreArticle) == SortAttributeIgnoreArticle))
1176 return t.old;
1177 }
1178 }
1179 for (const sort_map& t : table)
1180 {
1181 if (t.sort == sortBy)
1182 return t.old;
1183 }
1184 return SORT_METHOD_NONE;
1185}
1186
1187SortDescription SortUtils::TranslateOldSortMethod(SORT_METHOD sortBy)
1188{
1189 SortDescription description;
1190 for (const sort_map& t : table)
1191 {
1192 if (t.old == sortBy)
1193 {
1194 description.sortBy = t.sort;
1195 description.sortAttributes = t.flags;
1196 break;
1197 }
1198 }
1199 return description;
1200}
1201
1202int SortUtils::GetSortLabel(SortBy sortBy)
1203{
1204 for (const sort_map& t : table)
1205 {
1206 if (t.sort == sortBy)
1207 return t.label;
1208 }
1209 return 16018; // None
1210}
1211
1212template<typename T>
1213T TypeFromString(const std::map<std::string, T>& typeMap, const std::string& name, const T& defaultType)
1214{
1215 auto it = typeMap.find(name);
1216 if (it == typeMap.end())
1217 return defaultType;
1218
1219 return it->second;
1220}
1221
1222template<typename T>
1223const std::string& TypeToString(const std::map<std::string, T>& typeMap, const T& value)
1224{
1225 auto it = std::find_if(typeMap.begin(), typeMap.end(),
1226 [&value](const std::pair<std::string, T>& pair)
1227 {
1228 return pair.second == value;
1229 });
1230
1231 if (it == typeMap.end())
1232 return StringUtils::Empty;
1233
1234 return it->first;
1235}
1236
1237/**
1238 * @brief Sort methods to translate string values to enum values.
1239 *
1240 * @warning On string changes, edit __SortBy__ enumerator to have strings right
1241 * for documentation!
1242 */
1243const std::map<std::string, SortBy> sortMethods = {
1244 { "label", SortByLabel },
1245 { "date", SortByDate },
1246 { "size", SortBySize },
1247 { "file", SortByFile },
1248 { "path", SortByPath },
1249 { "drivetype", SortByDriveType },
1250 { "title", SortByTitle },
1251 { "track", SortByTrackNumber },
1252 { "time", SortByTime },
1253 { "artist", SortByArtist },
1254 { "artistyear", SortByArtistThenYear },
1255 { "album", SortByAlbum },
1256 { "albumtype", SortByAlbumType },
1257 { "genre", SortByGenre },
1258 { "country", SortByCountry },
1259 { "year", SortByYear },
1260 { "rating", SortByRating },
1261 { "votes", SortByVotes },
1262 { "top250", SortByTop250 },
1263 { "programcount", SortByProgramCount },
1264 { "playlist", SortByPlaylistOrder },
1265 { "episode", SortByEpisodeNumber },
1266 { "season", SortBySeason },
1267 { "totalepisodes", SortByNumberOfEpisodes },
1268 { "watchedepisodes", SortByNumberOfWatchedEpisodes },
1269 { "tvshowstatus", SortByTvShowStatus },
1270 { "tvshowtitle", SortByTvShowTitle },
1271 { "sorttitle", SortBySortTitle },
1272 { "productioncode", SortByProductionCode },
1273 { "mpaa", SortByMPAA },
1274 { "videoresolution", SortByVideoResolution },
1275 { "videocodec", SortByVideoCodec },
1276 { "videoaspectratio", SortByVideoAspectRatio },
1277 { "audiochannels", SortByAudioChannels },
1278 { "audiocodec", SortByAudioCodec },
1279 { "audiolanguage", SortByAudioLanguage },
1280 { "subtitlelanguage", SortBySubtitleLanguage },
1281 { "studio", SortByStudio },
1282 { "dateadded", SortByDateAdded },
1283 { "lastplayed", SortByLastPlayed },
1284 { "playcount", SortByPlaycount },
1285 { "listeners", SortByListeners },
1286 { "bitrate", SortByBitrate },
1287 { "random", SortByRandom },
1288 { "channel", SortByChannel },
1289 { "channelnumber", SortByChannelNumber },
1290 { "clientchannelorder", SortByClientChannelOrder },
1291 { "datetaken", SortByDateTaken },
1292 { "userrating", SortByUserRating },
1293 { "installdate", SortByInstallDate },
1294 { "lastupdated", SortByLastUpdated },
1295 { "lastused", SortByLastUsed },
1296 { "totaldiscs", SortByTotalDiscs },
1297 { "originaldate", SortByOrigDate },
1298 { "bpm", SortByBPM },
1299};
1300
1301SortBy SortUtils::SortMethodFromString(const std::string& sortMethod)
1302{
1303 return TypeFromString<SortBy>(sortMethods, sortMethod, SortByNone);
1304}
1305
1306const std::string& SortUtils::SortMethodToString(SortBy sortMethod)
1307{
1308 return TypeToString<SortBy>(sortMethods, sortMethod);
1309}
1310
1311const std::map<std::string, SortOrder> sortOrders = {
1312 { "ascending", SortOrderAscending },
1313 { "descending", SortOrderDescending }
1314};
1315
1316SortOrder SortUtils::SortOrderFromString(const std::string& sortOrder)
1317{
1318 return TypeFromString<SortOrder>(sortOrders, sortOrder, SortOrderNone);
1319}
1320
1321const std::string& SortUtils::SortOrderToString(SortOrder sortOrder)
1322{
1323 return TypeToString<SortOrder>(sortOrders, sortOrder);
1324}
diff --git a/xbmc/utils/SortUtils.h b/xbmc/utils/SortUtils.h
new file mode 100644
index 0000000..79629d4
--- /dev/null
+++ b/xbmc/utils/SortUtils.h
@@ -0,0 +1,224 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "DatabaseUtils.h"
12#include "LabelFormatter.h"
13#include "SortFileItem.h"
14
15#include <map>
16#include <memory>
17#include <string>
18#include <vector>
19
20typedef enum {
21 SortOrderNone = 0,
22 SortOrderAscending,
23 SortOrderDescending
24} SortOrder;
25
26typedef enum {
27 SortAttributeNone = 0x0,
28 SortAttributeIgnoreArticle = 0x1,
29 SortAttributeIgnoreFolders = 0x2,
30 SortAttributeUseArtistSortName = 0x4,
31 SortAttributeIgnoreLabel = 0x8
32} SortAttribute;
33
34typedef enum {
35 SortSpecialNone = 0,
36 SortSpecialOnTop = 1,
37 SortSpecialOnBottom = 2
38} SortSpecial;
39
40///
41/// \defgroup List_of_sort_methods List of sort methods
42/// \addtogroup List_of_sort_methods
43///
44/// \brief These ID's can be used with the \ref built_in_functions_6 "Container.SetSortMethod(id)" function
45/// \note The on field named part with <b>String</b> shows the string used on
46/// GUI to set this sort type.
47///
48///@{
49typedef enum {
50 /// __0__ :
51 SortByNone = 0,
52 /// __1__ : Sort by Name <em>(String: <b><c>Label</c></b>)</em>
53 SortByLabel,
54 /// __2__ : Sort by Date <em>(String: <b><c>Date</c></b>)</em>
55 SortByDate,
56 /// __3__ : Sort by Size <em>(String: <b><c>Size</c></b>)</em>
57 SortBySize,
58 /// __4__ : Sort by filename <em>(String: <b><c>File</c></b>)</em>
59 SortByFile,
60 /// __5__ : Sort by path <em>(String: <b><c>Path</c></b>)</em>
61 SortByPath,
62 /// __6__ : Sort by drive type <em>(String: <b><c>DriveType</c></b>)</em>
63 SortByDriveType,
64 /// __7__ : Sort by title <em>(String: <b><c>Title</c></b>)</em>
65 SortByTitle,
66 /// __8__ : Sort by track number <em>(String: <b><c>TrackNumber</c></b>)</em>
67 SortByTrackNumber,
68 /// __9__ : Sort by time <em>(String: <b><c>Time</c></b>)</em>
69 SortByTime,
70 /// __10__ : Sort by artist <em>(String: <b><c>Artist</c></b>)</em>
71 SortByArtist,
72 /// __11__ : Sort by first artist then year <em>(String: <b><c>ArtistYear</c></b>)</em>
73 SortByArtistThenYear,
74 /// __12__ : Sort by album <em>(String: <b><c>Album</c></b>)</em>
75 SortByAlbum,
76 /// __13__ : Sort by album type <em>(String: <b><c>AlbumType</c></b>)</em>
77 SortByAlbumType,
78 /// __14__ : Sort by genre <em>(String: <b><c>Genre</c></b>)</em>
79 SortByGenre,
80 /// __15__ : Sort by country <em>(String: <b><c>Country</c></b>)</em>
81 SortByCountry,
82 /// __16__ : Sort by year <em>(String: <b><c>Year</c></b>)</em>
83 SortByYear,
84 /// __17__ : Sort by rating <em>(String: <b><c>Rating</c></b>)</em>
85 SortByRating,
86 /// __18__ : Sort by user rating <em>(String: <b><c>UserRating</c></b>)</em>
87 SortByUserRating,
88 /// __19__ : Sort by votes <em>(String: <b><c>Votes</c></b>)</em>
89 SortByVotes,
90 /// __20__ : Sort by top 250 <em>(String: <b><c>Top250</c></b>)</em>
91 SortByTop250,
92 /// __21__ : Sort by program count <em>(String: <b><c>ProgramCount</c></b>)</em>
93 SortByProgramCount,
94 /// __22__ : Sort by playlist order <em>(String: <b><c>Playlist</c></b>)</em>
95 SortByPlaylistOrder,
96 /// __23__ : Sort by episode number <em>(String: <b><c>Episode</c></b>)</em>
97 SortByEpisodeNumber,
98 /// __24__ : Sort by season <em>(String: <b><c>Season</c></b>)</em>
99 SortBySeason,
100 /// __25__ : Sort by number of episodes <em>(String: <b><c>TotalEpisodes</c></b>)</em>
101 SortByNumberOfEpisodes,
102 /// __26__ : Sort by number of watched episodes <em>(String: <b><c>WatchedEpisodes</c></b>)</em>
103 SortByNumberOfWatchedEpisodes,
104 /// __27__ : Sort by TV show status <em>(String: <b><c>TvShowStatus</c></b>)</em>
105 SortByTvShowStatus,
106 /// __28__ : Sort by TV show title <em>(String: <b><c>TvShowTitle</c></b>)</em>
107 SortByTvShowTitle,
108 /// __29__ : Sort by sort title <em>(String: <b><c>SortTitle</c></b>)</em>
109 SortBySortTitle,
110 /// __30__ : Sort by production code <em>(String: <b><c>ProductionCode</c></b>)</em>
111 SortByProductionCode,
112 /// __31__ : Sort by MPAA <em>(String: <b><c>MPAA</c></b>)</em>
113 SortByMPAA,
114 /// __32__ : Sort by video resolution <em>(String: <b><c>VideoResolution</c></b>)</em>
115 SortByVideoResolution,
116 /// __33__ : Sort by video codec <em>(String: <b><c>VideoCodec</c></b>)</em>
117 SortByVideoCodec,
118 /// __34__ : Sort by video aspect ratio <em>(String: <b><c>VideoAspectRatio</c></b>)</em>
119 SortByVideoAspectRatio,
120 /// __35__ : Sort by audio channels <em>(String: <b><c>AudioChannels</c></b>)</em>
121 SortByAudioChannels,
122 /// __36__ : Sort by audio codec <em>(String: <b><c>AudioCodec</c></b>)</em>
123 SortByAudioCodec,
124 /// __37__ : Sort by audio language <em>(String: <b><c>AudioLanguage</c></b>)</em>
125 SortByAudioLanguage,
126 /// __38__ : Sort by subtitle language <em>(String: <b><c>SubtitleLanguage</c></b>)</em>
127 SortBySubtitleLanguage,
128 /// __39__ : Sort by studio <em>(String: <b><c>Studio</c></b>)</em>
129 SortByStudio,
130 /// __40__ : Sort by date added <em>(String: <b><c>DateAdded</c></b>)</em>
131 SortByDateAdded,
132 /// __41__ : Sort by last played <em>(String: <b><c>LastPlayed</c></b>)</em>
133 SortByLastPlayed,
134 /// __42__ : Sort by playcount <em>(String: <b><c>PlayCount</c></b>)</em>
135 SortByPlaycount,
136 /// __43__ : Sort by listener <em>(String: <b><c>Listeners</c></b>)</em>
137 SortByListeners,
138 /// __44__ : Sort by bitrate <em>(String: <b><c>Bitrate</c></b>)</em>
139 SortByBitrate,
140 /// __45__ : Sort by random <em>(String: <b><c>Random</c></b>)</em>
141 SortByRandom,
142 /// __46__ : Sort by channel <em>(String: <b><c>Channel</c></b>)</em>
143 SortByChannel,
144 /// __47__ : Sort by channel number <em>(String: <b><c>ChannelNumber</c></b>)</em>
145 SortByChannelNumber,
146 /// __48__ : Sort by date taken <em>(String: <b><c>DateTaken</c></b>)</em>
147 SortByDateTaken,
148 /// __49__ : Sort by relevance
149 SortByRelevance,
150 /// __50__ : Sort by installation date <en>(String: <b><c>installdate</c></b>)</em>
151 SortByInstallDate,
152 /// __51__ : Sort by last updated <en>(String: <b><c>lastupdated</c></b>)</em>
153 SortByLastUpdated,
154 /// __52__ : Sort by last used <em>(String: <b><c>lastused</c></b>)</em>
155 SortByLastUsed,
156 /// __53__ : Sort by client channel order <em>(String: <b><c>ClientChannelOrder</c></b>)</em>
157 SortByClientChannelOrder,
158 /// __54__ : Sort by total number of discs <em>(String: <b><c>totaldiscs</c></b>)</em>
159 SortByTotalDiscs,
160 /// __55__ : Sort by original release date <em>(String: <b><c>Originaldate</c></b>)</em>
161 SortByOrigDate,
162 /// __56__ : Sort by BPM <em>(String: <b><c>bpm</c></b>)</em>
163 SortByBPM,
164} SortBy;
165///@}
166
167typedef struct SortDescription {
168 SortBy sortBy = SortByNone;
169 SortOrder sortOrder = SortOrderAscending;
170 SortAttribute sortAttributes = SortAttributeNone;
171 int limitStart = 0;
172 int limitEnd = -1;
173} SortDescription;
174
175typedef struct GUIViewSortDetails
176{
177 SortDescription m_sortDescription;
178 int m_buttonLabel;
179 LABEL_MASKS m_labelMasks;
180} GUIViewSortDetails;
181
182typedef DatabaseResult SortItem;
183typedef std::shared_ptr<SortItem> SortItemPtr;
184typedef std::vector<SortItemPtr> SortItems;
185
186class SortUtils
187{
188public:
189 static SORT_METHOD TranslateOldSortMethod(SortBy sortBy, bool ignoreArticle);
190 static SortDescription TranslateOldSortMethod(SORT_METHOD sortBy);
191
192 static SortBy SortMethodFromString(const std::string& sortMethod);
193 static const std::string& SortMethodToString(SortBy sortMethod);
194 static SortOrder SortOrderFromString(const std::string& sortOrder);
195 static const std::string& SortOrderToString(SortOrder sortOrder);
196
197 /*! \brief retrieve the label id associated with a sort method for displaying in the UI.
198 \param sortBy the sort method in question.
199 \return the label id of the sort method.
200 */
201 static int GetSortLabel(SortBy sortBy);
202
203 static void Sort(SortBy sortBy, SortOrder sortOrder, SortAttribute attributes, DatabaseResults& items, int limitEnd = -1, int limitStart = 0);
204 static void Sort(SortBy sortBy, SortOrder sortOrder, SortAttribute attributes, SortItems& items, int limitEnd = -1, int limitStart = 0);
205 static void Sort(const SortDescription &sortDescription, DatabaseResults& items);
206 static void Sort(const SortDescription &sortDescription, SortItems& items);
207 static bool SortFromDataset(const SortDescription &sortDescription, const MediaType &mediaType, const std::unique_ptr<dbiplus::Dataset> &dataset, DatabaseResults &results);
208
209 static void GetFieldsForSQLSort(const MediaType& mediaType, SortBy sortMethod, FieldList& fields);
210 static const Fields& GetFieldsForSorting(SortBy sortBy);
211 static std::string RemoveArticles(const std::string &label);
212
213 typedef std::string (*SortPreparator) (SortAttribute, const SortItem&);
214 typedef bool (*Sorter) (const DatabaseResult &, const DatabaseResult &);
215 typedef bool (*SorterIndirect) (const SortItemPtr &, const SortItemPtr &);
216
217private:
218 static const SortPreparator& getPreparator(SortBy sortBy);
219 static Sorter getSorter(SortOrder sortOrder, SortAttribute attributes);
220 static SorterIndirect getSorterIndirect(SortOrder sortOrder, SortAttribute attributes);
221
222 static std::map<SortBy, SortPreparator> m_preparators;
223 static std::map<SortBy, Fields> m_sortingFields;
224};
diff --git a/xbmc/utils/Speed.cpp b/xbmc/utils/Speed.cpp
new file mode 100644
index 0000000..441c16c
--- /dev/null
+++ b/xbmc/utils/Speed.cpp
@@ -0,0 +1,582 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Speed.h"
10
11#include "utils/Archive.h"
12#include "utils/StringUtils.h"
13
14#include <assert.h>
15
16CSpeed::CSpeed()
17{
18 m_value = 0.0;
19 m_valid = false;
20}
21
22CSpeed::CSpeed(const CSpeed& speed)
23{
24 m_value = speed.m_value;
25 m_valid = speed.m_valid;
26}
27
28CSpeed::CSpeed(double value)
29{
30 m_value = value;
31 m_valid = true;
32}
33
34bool CSpeed::operator >(const CSpeed& right) const
35{
36 assert(IsValid());
37 assert(right.IsValid());
38
39 if (!IsValid() || !right.IsValid())
40 return false;
41
42 if (this == &right)
43 return false;
44
45 return (m_value > right.m_value);
46}
47
48bool CSpeed::operator >=(const CSpeed& right) const
49{
50 return operator >(right) || operator ==(right);
51}
52
53bool CSpeed::operator <(const CSpeed& right) const
54{
55 assert(IsValid());
56 assert(right.IsValid());
57
58 if (!IsValid() || !right.IsValid())
59 return false;
60
61 if (this == &right)
62 return false;
63
64 return (m_value < right.m_value);
65}
66
67bool CSpeed::operator <=(const CSpeed& right) const
68{
69 return operator <(right) || operator ==(right);
70}
71
72bool CSpeed::operator ==(const CSpeed& right) const
73{
74 assert(IsValid());
75 assert(right.IsValid());
76
77 if (!IsValid() || !right.IsValid())
78 return false;
79
80 if (this == &right)
81 return true;
82
83 return (m_value == right.m_value);
84}
85
86bool CSpeed::operator !=(const CSpeed& right) const
87{
88 return !operator ==(right.m_value);
89}
90
91CSpeed& CSpeed::operator =(const CSpeed& right)
92{
93 m_valid = right.m_valid;
94 m_value = right.m_value;
95 return *this;
96}
97
98const CSpeed& CSpeed::operator +=(const CSpeed& right)
99{
100 assert(IsValid());
101 assert(right.IsValid());
102
103 m_value += right.m_value;
104 return *this;
105}
106
107const CSpeed& CSpeed::operator -=(const CSpeed& right)
108{
109 assert(IsValid());
110 assert(right.IsValid());
111
112 m_value -= right.m_value;
113 return *this;
114}
115
116const CSpeed& CSpeed::operator *=(const CSpeed& right)
117{
118 assert(IsValid());
119 assert(right.IsValid());
120
121 m_value *= right.m_value;
122 return *this;
123}
124
125const CSpeed& CSpeed::operator /=(const CSpeed& right)
126{
127 assert(IsValid());
128 assert(right.IsValid());
129
130 m_value /= right.m_value;
131 return *this;
132}
133
134CSpeed CSpeed::operator +(const CSpeed& right) const
135{
136 assert(IsValid());
137 assert(right.IsValid());
138
139 CSpeed temp(*this);
140
141 if (!IsValid() || !right.IsValid())
142 temp.SetValid(false);
143 else
144 temp.m_value += right.m_value;
145
146 return temp;
147}
148
149CSpeed CSpeed::operator -(const CSpeed& right) const
150{
151 assert(IsValid());
152 assert(right.IsValid());
153
154 CSpeed temp(*this);
155 if (!IsValid() || !right.IsValid())
156 temp.SetValid(false);
157 else
158 temp.m_value -= right.m_value;
159
160 return temp;
161}
162
163CSpeed CSpeed::operator *(const CSpeed& right) const
164{
165 assert(IsValid());
166 assert(right.IsValid());
167
168 CSpeed temp(*this);
169 if (!IsValid() || !right.IsValid())
170 temp.SetValid(false);
171 else
172 temp.m_value *= right.m_value;
173 return temp;
174}
175
176CSpeed CSpeed::operator /(const CSpeed& right) const
177{
178 assert(IsValid());
179 assert(right.IsValid());
180
181 CSpeed temp(*this);
182 if (!IsValid() || !right.IsValid())
183 temp.SetValid(false);
184 else
185 temp.m_value /= right.m_value;
186 return temp;
187}
188
189CSpeed& CSpeed::operator ++()
190{
191 assert(IsValid());
192
193 m_value++;
194 return *this;
195}
196
197CSpeed& CSpeed::operator --()
198{
199 assert(IsValid());
200
201 m_value--;
202 return *this;
203}
204
205CSpeed CSpeed::operator ++(int)
206{
207 assert(IsValid());
208
209 CSpeed temp(*this);
210 m_value++;
211 return temp;
212}
213
214CSpeed CSpeed::operator --(int)
215{
216 assert(IsValid());
217
218 CSpeed temp(*this);
219 m_value--;
220 return temp;
221}
222
223bool CSpeed::operator >(double right) const
224{
225 assert(IsValid());
226
227 if (!IsValid())
228 return false;
229
230 return (m_value > right);
231}
232
233bool CSpeed::operator >=(double right) const
234{
235 return operator >(right) || operator ==(right);
236}
237
238bool CSpeed::operator <(double right) const
239{
240 assert(IsValid());
241
242 if (!IsValid())
243 return false;
244
245 return (m_value < right);
246}
247
248bool CSpeed::operator <=(double right) const
249{
250 return operator <(right) || operator ==(right);
251}
252
253bool CSpeed::operator ==(double right) const
254{
255 if (!IsValid())
256 return false;
257
258 return (m_value == right);
259}
260
261bool CSpeed::operator !=(double right) const
262{
263 return !operator ==(right);
264}
265
266const CSpeed& CSpeed::operator +=(double right)
267{
268 assert(IsValid());
269
270 m_value += right;
271 return *this;
272}
273
274const CSpeed& CSpeed::operator -=(double right)
275{
276 assert(IsValid());
277
278 m_value -= right;
279 return *this;
280}
281
282const CSpeed& CSpeed::operator *=(double right)
283{
284 assert(IsValid());
285
286 m_value *= right;
287 return *this;
288}
289
290const CSpeed& CSpeed::operator /=(double right)
291{
292 assert(IsValid());
293
294 m_value /= right;
295 return *this;
296}
297
298CSpeed CSpeed::operator +(double right) const
299{
300 assert(IsValid());
301
302 CSpeed temp(*this);
303 temp.m_value += right;
304 return temp;
305}
306
307CSpeed CSpeed::operator -(double right) const
308{
309 assert(IsValid());
310
311 CSpeed temp(*this);
312 temp.m_value -= right;
313 return temp;
314}
315
316CSpeed CSpeed::operator *(double right) const
317{
318 assert(IsValid());
319
320 CSpeed temp(*this);
321 temp.m_value *= right;
322 return temp;
323}
324
325CSpeed CSpeed::operator /(double right) const
326{
327 assert(IsValid());
328
329 CSpeed temp(*this);
330 temp.m_value /= right;
331 return temp;
332}
333
334CSpeed CSpeed::CreateFromKilometresPerHour(double value)
335{
336 return CSpeed(value / 3.6);
337}
338
339CSpeed CSpeed::CreateFromMetresPerMinute(double value)
340{
341 return CSpeed(value / 60.0);
342}
343
344CSpeed CSpeed::CreateFromMetresPerSecond(double value)
345{
346 return CSpeed(value);
347}
348
349CSpeed CSpeed::CreateFromFeetPerHour(double value)
350{
351 return CreateFromFeetPerMinute(value / 60.0);
352}
353
354CSpeed CSpeed::CreateFromFeetPerMinute(double value)
355{
356 return CreateFromFeetPerSecond(value / 60.0);
357}
358
359CSpeed CSpeed::CreateFromFeetPerSecond(double value)
360{
361 return CSpeed(value / 3.280839895);
362}
363
364CSpeed CSpeed::CreateFromMilesPerHour(double value)
365{
366 return CSpeed(value / 2.236936292);
367}
368
369CSpeed CSpeed::CreateFromKnots(double value)
370{
371 return CSpeed(value / 1.943846172);
372}
373
374CSpeed CSpeed::CreateFromBeaufort(unsigned int value)
375{
376 if (value == 0)
377 return CSpeed(0.15);
378 if (value == 1)
379 return CSpeed(0.9);
380 if (value == 2)
381 return CSpeed(2.4);
382 if (value == 3)
383 return CSpeed(4.4);
384 if (value == 4)
385 return CSpeed(6.75);
386 if (value == 5)
387 return CSpeed(9.4);
388 if (value == 6)
389 return CSpeed(12.35);
390 if (value == 7)
391 return CSpeed(15.55);
392 if (value == 8)
393 return CSpeed(18.95);
394 if (value == 9)
395 return CSpeed(22.6);
396 if (value == 10)
397 return CSpeed(26.45);
398 if (value == 11)
399 return CSpeed(30.5);
400
401 return CSpeed(32.6);
402}
403
404CSpeed CSpeed::CreateFromInchPerSecond(double value)
405{
406 return CSpeed(value / 39.37007874);
407}
408
409CSpeed CSpeed::CreateFromYardPerSecond(double value)
410{
411 return CSpeed(value / 1.093613298);
412}
413
414CSpeed CSpeed::CreateFromFurlongPerFortnight(double value)
415{
416 return CSpeed(value / 6012.885613871);
417}
418
419void CSpeed::Archive(CArchive& ar)
420{
421 if (ar.IsStoring())
422 {
423 ar << m_value;
424 ar << m_valid;
425 }
426 else
427 {
428 ar >> m_value;
429 ar >> m_valid;
430 }
431}
432
433bool CSpeed::IsValid() const
434{
435 return m_valid;
436}
437
438double CSpeed::ToKilometresPerHour() const
439{
440 return m_value * 3.6;
441}
442
443double CSpeed::ToMetresPerMinute() const
444{
445 return m_value * 60.0;
446}
447
448double CSpeed::ToMetresPerSecond() const
449{
450 return m_value;
451}
452
453double CSpeed::ToFeetPerHour() const
454{
455 return ToFeetPerMinute() * 60.0;
456}
457
458double CSpeed::ToFeetPerMinute() const
459{
460 return ToFeetPerSecond() * 60.0;
461}
462
463double CSpeed::ToFeetPerSecond() const
464{
465 return m_value * 3.280839895;
466}
467
468double CSpeed::ToMilesPerHour() const
469{
470 return m_value * 2.236936292;
471}
472
473double CSpeed::ToKnots() const
474{
475 return m_value * 1.943846172;
476}
477
478double CSpeed::ToBeaufort() const
479{
480 if (m_value < 0.3)
481 return 0;
482 if (m_value >= 0.3 && m_value < 1.5)
483 return 1;
484 if (m_value >= 1.5 && m_value < 3.3)
485 return 2;
486 if (m_value >= 3.3 && m_value < 5.5)
487 return 3;
488 if (m_value >= 5.5 && m_value < 8.0)
489 return 4;
490 if (m_value >= 8.0 && m_value < 10.8)
491 return 5;
492 if (m_value >= 10.8 && m_value < 13.9)
493 return 6;
494 if (m_value >= 13.9 && m_value < 17.2)
495 return 7;
496 if (m_value >= 17.2 && m_value < 20.7)
497 return 8;
498 if (m_value >= 20.7 && m_value < 24.5)
499 return 9;
500 if (m_value >= 24.5 && m_value < 28.4)
501 return 10;
502 if (m_value >= 28.4 && m_value < 32.6)
503 return 11;
504
505 return 12;
506}
507
508double CSpeed::ToInchPerSecond() const
509{
510 return m_value * 39.37007874;
511}
512
513double CSpeed::ToYardPerSecond() const
514{
515 return m_value * 1.093613298;
516}
517
518double CSpeed::ToFurlongPerFortnight() const
519{
520 return m_value * 6012.885613871;
521}
522
523double CSpeed::To(Unit speedUnit) const
524{
525 if (!IsValid())
526 return 0;
527
528 double value = 0.0;
529
530 switch (speedUnit)
531 {
532 case UnitKilometresPerHour:
533 value = ToKilometresPerHour();
534 break;
535 case UnitMetresPerMinute:
536 value = ToMetresPerMinute();
537 break;
538 case UnitMetresPerSecond:
539 value = ToMetresPerSecond();
540 break;
541 case UnitFeetPerHour:
542 value = ToFeetPerHour();
543 break;
544 case UnitFeetPerMinute:
545 value = ToFeetPerMinute();
546 break;
547 case UnitFeetPerSecond:
548 value = ToFeetPerSecond();
549 break;
550 case UnitMilesPerHour:
551 value = ToMilesPerHour();
552 break;
553 case UnitKnots:
554 value = ToKnots();
555 break;
556 case UnitBeaufort:
557 value = ToBeaufort();
558 break;
559 case UnitInchPerSecond:
560 value = ToInchPerSecond();
561 break;
562 case UnitYardPerSecond:
563 value = ToYardPerSecond();
564 break;
565 case UnitFurlongPerFortnight:
566 value = ToFurlongPerFortnight();
567 break;
568 default:
569 assert(false);
570 break;
571 }
572 return value;
573}
574
575// Returns temperature as localized string
576std::string CSpeed::ToString(Unit speedUnit) const
577{
578 if (!IsValid())
579 return "";
580
581 return StringUtils::Format("%2.0f", To(speedUnit));
582}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/IArchivable.h"
12
13#include <string>
14
15class CSpeed : public IArchivable
16{
17public:
18 CSpeed();
19 CSpeed(const CSpeed& speed);
20
21 typedef enum Unit
22 {
23 UnitKilometresPerHour = 0,
24 UnitMetresPerMinute,
25 UnitMetresPerSecond,
26 UnitFeetPerHour,
27 UnitFeetPerMinute,
28 UnitFeetPerSecond,
29 UnitMilesPerHour,
30 UnitKnots,
31 UnitBeaufort,
32 UnitInchPerSecond,
33 UnitYardPerSecond,
34 UnitFurlongPerFortnight
35 } Unit;
36
37 static CSpeed CreateFromKilometresPerHour(double value);
38 static CSpeed CreateFromMetresPerMinute(double value);
39 static CSpeed CreateFromMetresPerSecond(double value);
40 static CSpeed CreateFromFeetPerHour(double value);
41 static CSpeed CreateFromFeetPerMinute(double value);
42 static CSpeed CreateFromFeetPerSecond(double value);
43 static CSpeed CreateFromMilesPerHour(double value);
44 static CSpeed CreateFromKnots(double value);
45 static CSpeed CreateFromBeaufort(unsigned int value);
46 static CSpeed CreateFromInchPerSecond(double value);
47 static CSpeed CreateFromYardPerSecond(double value);
48 static CSpeed CreateFromFurlongPerFortnight(double value);
49
50 bool operator >(const CSpeed& right) const;
51 bool operator >=(const CSpeed& right) const;
52 bool operator <(const CSpeed& right) const;
53 bool operator <=(const CSpeed& right) const;
54 bool operator ==(const CSpeed& right) const;
55 bool operator !=(const CSpeed& right) const;
56
57 CSpeed& operator =(const CSpeed& right);
58 const CSpeed& operator +=(const CSpeed& right);
59 const CSpeed& operator -=(const CSpeed& right);
60 const CSpeed& operator *=(const CSpeed& right);
61 const CSpeed& operator /=(const CSpeed& right);
62 CSpeed operator +(const CSpeed& right) const;
63 CSpeed operator -(const CSpeed& right) const;
64 CSpeed operator *(const CSpeed& right) const;
65 CSpeed operator /(const CSpeed& right) const;
66
67 bool operator >(double right) const;
68 bool operator >=(double right) const;
69 bool operator <(double right) const;
70 bool operator <=(double right) const;
71 bool operator ==(double right) const;
72 bool operator !=(double right) const;
73
74 const CSpeed& operator +=(double right);
75 const CSpeed& operator -=(double right);
76 const CSpeed& operator *=(double right);
77 const CSpeed& operator /=(double right);
78 CSpeed operator +(double right) const;
79 CSpeed operator -(double right) const;
80 CSpeed operator *(double right) const;
81 CSpeed operator /(double right) const;
82
83 CSpeed& operator ++();
84 CSpeed& operator --();
85 CSpeed operator ++(int);
86 CSpeed operator --(int);
87
88 void Archive(CArchive& ar) override;
89
90 bool IsValid() const;
91
92 double ToKilometresPerHour() const;
93 double ToMetresPerMinute() const;
94 double ToMetresPerSecond() const;
95 double ToFeetPerHour() const;
96 double ToFeetPerMinute() const;
97 double ToFeetPerSecond() const;
98 double ToMilesPerHour() const;
99 double ToKnots() const;
100 double ToBeaufort() const;
101 double ToInchPerSecond() const;
102 double ToYardPerSecond() const;
103 double ToFurlongPerFortnight() const;
104
105 double To(Unit speedUnit) const;
106 std::string ToString(Unit speedUnit) const;
107
108protected:
109 explicit CSpeed(double value);
110
111 void SetValid(bool valid) { m_valid = valid; }
112
113 double m_value; // we store in m/s
114 bool m_valid;
115};
116
diff --git a/xbmc/utils/StaticLoggerBase.cpp b/xbmc/utils/StaticLoggerBase.cpp
new file mode 100644
index 0000000..5bcc06a
--- /dev/null
+++ b/xbmc/utils/StaticLoggerBase.cpp
@@ -0,0 +1,20 @@
1/*
2 * Copyright (C) 2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "StaticLoggerBase.h"
10
11#include "ServiceBroker.h"
12#include "utils/log.h"
13
14Logger CStaticLoggerBase::s_logger;
15
16CStaticLoggerBase::CStaticLoggerBase(const std::string& loggerName)
17{
18 if (s_logger == nullptr)
19 s_logger = CServiceBroker::GetLogging().GetLogger(loggerName);
20}
diff --git a/xbmc/utils/StaticLoggerBase.h b/xbmc/utils/StaticLoggerBase.h
new file mode 100644
index 0000000..a3da35f
--- /dev/null
+++ b/xbmc/utils/StaticLoggerBase.h
@@ -0,0 +1,21 @@
1/*
2 * Copyright (C) 2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/logtypes.h"
12
13#include <string>
14
15class CStaticLoggerBase
16{
17protected:
18 explicit CStaticLoggerBase(const std::string& loggerName);
19
20 static Logger s_logger;
21};
diff --git a/xbmc/utils/Stopwatch.cpp b/xbmc/utils/Stopwatch.cpp
new file mode 100644
index 0000000..bec498f
--- /dev/null
+++ b/xbmc/utils/Stopwatch.cpp
@@ -0,0 +1,47 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Stopwatch.h"
10#if defined(TARGET_POSIX)
11#include "threads/SystemClock.h"
12#if !defined(TARGET_DARWIN) && !defined(TARGET_FREEBSD)
13#include <sys/sysinfo.h>
14#endif
15#endif
16#include "utils/TimeUtils.h"
17
18CStopWatch::CStopWatch(bool useFrameTime /*=false*/)
19{
20 m_timerPeriod = 0.0f;
21 m_startTick = 0;
22 m_stopTick = 0;
23 m_isRunning = false;
24 m_useFrameTime = useFrameTime;
25
26#ifdef TARGET_POSIX
27 m_timerPeriod = 1.0f / 1000.0f; // we want seconds
28#else
29 if (m_useFrameTime)
30 m_timerPeriod = 1.0f / 1000.0f; //frametime is in milliseconds
31 else
32 m_timerPeriod = 1.0f / (float)CurrentHostFrequency();
33#endif
34}
35
36CStopWatch::~CStopWatch() = default;
37
38int64_t CStopWatch::GetTicks() const
39{
40 if (m_useFrameTime)
41 return CTimeUtils::GetFrameTime();
42#ifndef TARGET_POSIX
43 return CurrentHostCounter();
44#else
45 return XbmcThreads::SystemClockMillis();
46#endif
47}
diff --git a/xbmc/utils/Stopwatch.h b/xbmc/utils/Stopwatch.h
new file mode 100644
index 0000000..186a54c
--- /dev/null
+++ b/xbmc/utils/Stopwatch.h
@@ -0,0 +1,100 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12
13class CStopWatch
14{
15public:
16 explicit CStopWatch(bool useFrameTime=false);
17 ~CStopWatch();
18
19 /*!
20 \brief Retrieve the running state of the stopwatch.
21
22 \return True if stopwatch has been started but not stopped.
23 */
24 inline bool IsRunning() const
25 {
26 return m_isRunning;
27 }
28
29 /*!
30 \brief Record start time and change state to running.
31 */
32 inline void StartZero()
33 {
34 m_startTick = GetTicks();
35 m_isRunning = true;
36 }
37
38 /*!
39 \brief Record start time and change state to running, only if the stopwatch is stopped.
40 */
41 inline void Start()
42 {
43 if (!m_isRunning)
44 StartZero();
45 }
46
47 /*!
48 \brief Record stop time and change state to not running.
49 */
50 inline void Stop()
51 {
52 if(m_isRunning)
53 {
54 m_stopTick = GetTicks();
55 m_isRunning = false;
56 }
57 }
58
59 /*!
60 \brief Set the start time such that time elapsed is now zero.
61 */
62 void Reset()
63 {
64 if (m_isRunning)
65 m_startTick = GetTicks();
66 else
67 m_startTick = m_stopTick;
68 }
69
70 /*!
71 \brief Retrieve time elapsed between the last call to Start(), StartZero()
72 or Reset() and; if running, now; if stopped, the last call to Stop().
73
74 \return Elapsed time, in seconds, as a float.
75 */
76 float GetElapsedSeconds() const
77 {
78 int64_t totalTicks = (m_isRunning ? GetTicks() : m_stopTick) - m_startTick;
79 return (float)totalTicks * m_timerPeriod;
80 }
81
82 /*!
83 \brief Retrieve time elapsed between the last call to Start(), StartZero()
84 or Reset() and; if running, now; if stopped, the last call to Stop().
85
86 \return Elapsed time, in milliseconds, as a float.
87 */
88 float GetElapsedMilliseconds() const
89 {
90 return GetElapsedSeconds() * 1000.0f;
91 }
92
93private:
94 int64_t GetTicks() const;
95 float m_timerPeriod; // to save division in GetElapsed...()
96 int64_t m_startTick;
97 int64_t m_stopTick;
98 bool m_isRunning;
99 bool m_useFrameTime;
100};
diff --git a/xbmc/utils/StreamDetails.cpp b/xbmc/utils/StreamDetails.cpp
new file mode 100644
index 0000000..558af46
--- /dev/null
+++ b/xbmc/utils/StreamDetails.cpp
@@ -0,0 +1,627 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "StreamDetails.h"
10
11#include "LangInfo.h"
12#include "StreamUtils.h"
13#include "cores/VideoPlayer/Interface/StreamInfo.h"
14#include "utils/Archive.h"
15#include "utils/LangCodeExpander.h"
16#include "utils/Variant.h"
17
18#include <math.h>
19
20const float VIDEOASPECT_EPSILON = 0.025f;
21
22CStreamDetailVideo::CStreamDetailVideo() :
23 CStreamDetail(CStreamDetail::VIDEO)
24{
25}
26
27CStreamDetailVideo::CStreamDetailVideo(const VideoStreamInfo &info, int duration) :
28 CStreamDetail(CStreamDetail::VIDEO),
29 m_iWidth(info.width),
30 m_iHeight(info.height),
31 m_fAspect(info.videoAspectRatio),
32 m_iDuration(duration),
33 m_strCodec(info.codecName),
34 m_strStereoMode(info.stereoMode),
35 m_strLanguage(info.language)
36{
37}
38
39void CStreamDetailVideo::Archive(CArchive& ar)
40{
41 if (ar.IsStoring())
42 {
43 ar << m_strCodec;
44 ar << m_fAspect;
45 ar << m_iHeight;
46 ar << m_iWidth;
47 ar << m_iDuration;
48 ar << m_strStereoMode;
49 ar << m_strLanguage;
50 }
51 else
52 {
53 ar >> m_strCodec;
54 ar >> m_fAspect;
55 ar >> m_iHeight;
56 ar >> m_iWidth;
57 ar >> m_iDuration;
58 ar >> m_strStereoMode;
59 ar >> m_strLanguage;
60 }
61}
62void CStreamDetailVideo::Serialize(CVariant& value) const
63{
64 value["codec"] = m_strCodec;
65 value["aspect"] = m_fAspect;
66 value["height"] = m_iHeight;
67 value["width"] = m_iWidth;
68 value["duration"] = m_iDuration;
69 value["stereomode"] = m_strStereoMode;
70 value["language"] = m_strLanguage;
71}
72
73bool CStreamDetailVideo::IsWorseThan(const CStreamDetail &that) const
74{
75 if (that.m_eType != CStreamDetail::VIDEO)
76 return true;
77
78 // Best video stream is that with the most pixels
79 auto &sdv = static_cast<const CStreamDetailVideo &>(that);
80 return (sdv.m_iWidth * sdv.m_iHeight) > (m_iWidth * m_iHeight);
81}
82
83CStreamDetailAudio::CStreamDetailAudio() :
84 CStreamDetail(CStreamDetail::AUDIO)
85{
86}
87
88CStreamDetailAudio::CStreamDetailAudio(const AudioStreamInfo &info) :
89 CStreamDetail(CStreamDetail::AUDIO),
90 m_iChannels(info.channels),
91 m_strCodec(info.codecName),
92 m_strLanguage(info.language)
93{
94}
95
96void CStreamDetailAudio::Archive(CArchive& ar)
97{
98 if (ar.IsStoring())
99 {
100 ar << m_strCodec;
101 ar << m_strLanguage;
102 ar << m_iChannels;
103 }
104 else
105 {
106 ar >> m_strCodec;
107 ar >> m_strLanguage;
108 ar >> m_iChannels;
109 }
110}
111void CStreamDetailAudio::Serialize(CVariant& value) const
112{
113 value["codec"] = m_strCodec;
114 value["language"] = m_strLanguage;
115 value["channels"] = m_iChannels;
116}
117
118bool CStreamDetailAudio::IsWorseThan(const CStreamDetail &that) const
119{
120 if (that.m_eType != CStreamDetail::AUDIO)
121 return true;
122
123 auto &sda = static_cast<const CStreamDetailAudio &>(that);
124 // First choice is the thing with the most channels
125 if (sda.m_iChannels > m_iChannels)
126 return true;
127 if (m_iChannels > sda.m_iChannels)
128 return false;
129
130 // In case of a tie, revert to codec priority
131 return StreamUtils::GetCodecPriority(sda.m_strCodec) > StreamUtils::GetCodecPriority(m_strCodec);
132}
133
134CStreamDetailSubtitle::CStreamDetailSubtitle() :
135 CStreamDetail(CStreamDetail::SUBTITLE)
136{
137}
138
139CStreamDetailSubtitle::CStreamDetailSubtitle(const SubtitleStreamInfo &info) :
140 CStreamDetail(CStreamDetail::SUBTITLE),
141 m_strLanguage(info.language)
142{
143}
144
145void CStreamDetailSubtitle::Archive(CArchive& ar)
146{
147 if (ar.IsStoring())
148 {
149 ar << m_strLanguage;
150 }
151 else
152 {
153 ar >> m_strLanguage;
154 }
155}
156void CStreamDetailSubtitle::Serialize(CVariant& value) const
157{
158 value["language"] = m_strLanguage;
159}
160
161bool CStreamDetailSubtitle::IsWorseThan(const CStreamDetail &that) const
162{
163 if (that.m_eType != CStreamDetail::SUBTITLE)
164 return true;
165
166 if (g_LangCodeExpander.CompareISO639Codes(m_strLanguage, static_cast<const CStreamDetailSubtitle &>(that).m_strLanguage))
167 return false;
168
169 // the best subtitle should be the one in the user's preferred language
170 // If preferred language is set to "original" this is "eng"
171 return m_strLanguage.empty() ||
172 g_LangCodeExpander.CompareISO639Codes(static_cast<const CStreamDetailSubtitle &>(that).m_strLanguage, g_langInfo.GetSubtitleLanguage());
173}
174
175CStreamDetailSubtitle& CStreamDetailSubtitle::operator=(const CStreamDetailSubtitle &that)
176{
177 if (this != &that)
178 {
179 this->m_pParent = that.m_pParent;
180 this->m_strLanguage = that.m_strLanguage;
181 }
182 return *this;
183}
184
185CStreamDetails& CStreamDetails::operator=(const CStreamDetails &that)
186{
187 if (this != &that)
188 {
189 Reset();
190 for (const auto &iter : that.m_vecItems)
191 {
192 switch (iter->m_eType)
193 {
194 case CStreamDetail::VIDEO:
195 AddStream(new CStreamDetailVideo(static_cast<const CStreamDetailVideo&>(*iter)));
196 break;
197 case CStreamDetail::AUDIO:
198 AddStream(new CStreamDetailAudio(static_cast<const CStreamDetailAudio&>(*iter)));
199 break;
200 case CStreamDetail::SUBTITLE:
201 AddStream(new CStreamDetailSubtitle(static_cast<const CStreamDetailSubtitle&>(*iter)));
202 break;
203 }
204 }
205
206 DetermineBestStreams();
207 } /* if this != that */
208
209 return *this;
210}
211
212bool CStreamDetails::operator ==(const CStreamDetails &right) const
213{
214 if (this == &right) return true;
215
216 if (GetVideoStreamCount() != right.GetVideoStreamCount() ||
217 GetAudioStreamCount() != right.GetAudioStreamCount() ||
218 GetSubtitleStreamCount() != right.GetSubtitleStreamCount())
219 return false;
220
221 for (int iStream=1; iStream<=GetVideoStreamCount(); iStream++)
222 {
223 if (GetVideoCodec(iStream) != right.GetVideoCodec(iStream) ||
224 GetVideoWidth(iStream) != right.GetVideoWidth(iStream) ||
225 GetVideoHeight(iStream) != right.GetVideoHeight(iStream) ||
226 GetVideoDuration(iStream) != right.GetVideoDuration(iStream) ||
227 fabs(GetVideoAspect(iStream) - right.GetVideoAspect(iStream)) > VIDEOASPECT_EPSILON)
228 return false;
229 }
230
231 for (int iStream=1; iStream<=GetAudioStreamCount(); iStream++)
232 {
233 if (GetAudioCodec(iStream) != right.GetAudioCodec(iStream) ||
234 GetAudioLanguage(iStream) != right.GetAudioLanguage(iStream) ||
235 GetAudioChannels(iStream) != right.GetAudioChannels(iStream) )
236 return false;
237 }
238
239 for (int iStream=1; iStream<=GetSubtitleStreamCount(); iStream++)
240 {
241 if (GetSubtitleLanguage(iStream) != right.GetSubtitleLanguage(iStream) )
242 return false;
243 }
244
245 return true;
246}
247
248bool CStreamDetails::operator !=(const CStreamDetails &right) const
249{
250 if (this == &right) return false;
251
252 return !(*this == right);
253}
254
255CStreamDetail *CStreamDetails::NewStream(CStreamDetail::StreamType type)
256{
257 CStreamDetail *retVal = NULL;
258 switch (type)
259 {
260 case CStreamDetail::VIDEO:
261 retVal = new CStreamDetailVideo();
262 break;
263 case CStreamDetail::AUDIO:
264 retVal = new CStreamDetailAudio();
265 break;
266 case CStreamDetail::SUBTITLE:
267 retVal = new CStreamDetailSubtitle();
268 break;
269 }
270
271 if (retVal)
272 AddStream(retVal);
273
274 return retVal;
275}
276
277std::string CStreamDetails::GetVideoLanguage(int idx) const
278{
279 const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
280 if (item)
281 return item->m_strLanguage;
282 else
283 return "";
284}
285
286int CStreamDetails::GetStreamCount(CStreamDetail::StreamType type) const
287{
288 int retVal = 0;
289 for (const auto &iter : m_vecItems)
290 if (iter->m_eType == type)
291 retVal++;
292 return retVal;
293}
294
295int CStreamDetails::GetVideoStreamCount(void) const
296{
297 return GetStreamCount(CStreamDetail::VIDEO);
298}
299
300int CStreamDetails::GetAudioStreamCount(void) const
301{
302 return GetStreamCount(CStreamDetail::AUDIO);
303}
304
305int CStreamDetails::GetSubtitleStreamCount(void) const
306{
307 return GetStreamCount(CStreamDetail::SUBTITLE);
308}
309
310CStreamDetails::CStreamDetails(const CStreamDetails &that)
311{
312 m_pBestVideo = nullptr;
313 m_pBestAudio = nullptr;
314 m_pBestSubtitle = nullptr;
315 *this = that;
316}
317
318void CStreamDetails::AddStream(CStreamDetail *item)
319{
320 item->m_pParent = this;
321 m_vecItems.emplace_back(item);
322}
323
324void CStreamDetails::Reset(void)
325{
326 m_pBestVideo = nullptr;
327 m_pBestAudio = nullptr;
328 m_pBestSubtitle = nullptr;
329
330 m_vecItems.clear();
331}
332
333const CStreamDetail* CStreamDetails::GetNthStream(CStreamDetail::StreamType type, int idx) const
334{
335 if (idx == 0)
336 {
337 switch (type)
338 {
339 case CStreamDetail::VIDEO:
340 return m_pBestVideo;
341 break;
342 case CStreamDetail::AUDIO:
343 return m_pBestAudio;
344 break;
345 case CStreamDetail::SUBTITLE:
346 return m_pBestSubtitle;
347 break;
348 default:
349 return NULL;
350 break;
351 }
352 }
353
354 for (const auto &iter : m_vecItems)
355 if (iter->m_eType == type)
356 {
357 idx--;
358 if (idx < 1)
359 return iter.get();
360 }
361
362 return NULL;
363}
364
365std::string CStreamDetails::GetVideoCodec(int idx) const
366{
367 const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
368 if (item)
369 return item->m_strCodec;
370 else
371 return "";
372}
373
374float CStreamDetails::GetVideoAspect(int idx) const
375{
376 const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
377 if (item)
378 return item->m_fAspect;
379 else
380 return 0.0;
381}
382
383int CStreamDetails::GetVideoWidth(int idx) const
384{
385 const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
386 if (item)
387 return item->m_iWidth;
388 else
389 return 0;
390}
391
392int CStreamDetails::GetVideoHeight(int idx) const
393{
394 const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
395 if (item)
396 return item->m_iHeight;
397 else
398 return 0;
399}
400
401int CStreamDetails::GetVideoDuration(int idx) const
402{
403 const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
404 if (item)
405 return item->m_iDuration;
406 else
407 return 0;
408}
409
410void CStreamDetails::SetVideoDuration(int idx, const int duration)
411{
412 CStreamDetailVideo *item = const_cast<CStreamDetailVideo*>(static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx)));
413 if (item)
414 item->m_iDuration = duration;
415}
416
417std::string CStreamDetails::GetStereoMode(int idx) const
418{
419 const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
420 if (item)
421 return item->m_strStereoMode;
422 else
423 return "";
424}
425
426std::string CStreamDetails::GetAudioCodec(int idx) const
427{
428 const CStreamDetailAudio *item = static_cast<const CStreamDetailAudio*>(GetNthStream(CStreamDetail::AUDIO, idx));
429 if (item)
430 return item->m_strCodec;
431 else
432 return "";
433}
434
435std::string CStreamDetails::GetAudioLanguage(int idx) const
436{
437 const CStreamDetailAudio *item = static_cast<const CStreamDetailAudio*>(GetNthStream(CStreamDetail::AUDIO, idx));
438 if (item)
439 return item->m_strLanguage;
440 else
441 return "";
442}
443
444int CStreamDetails::GetAudioChannels(int idx) const
445{
446 const CStreamDetailAudio *item = static_cast<const CStreamDetailAudio*>(GetNthStream(CStreamDetail::AUDIO, idx));
447 if (item)
448 return item->m_iChannels;
449 else
450 return -1;
451}
452
453std::string CStreamDetails::GetSubtitleLanguage(int idx) const
454{
455 const CStreamDetailSubtitle *item = static_cast<const CStreamDetailSubtitle*>(GetNthStream(CStreamDetail::SUBTITLE, idx));
456 if (item)
457 return item->m_strLanguage;
458 else
459 return "";
460}
461
462void CStreamDetails::Archive(CArchive& ar)
463{
464 if (ar.IsStoring())
465 {
466 ar << (int)m_vecItems.size();
467
468 for (auto &iter : m_vecItems)
469 {
470 // the type goes before the actual item. When loading we need
471 // to know the type before we can construct an instance to serialize
472 ar << (int)iter->m_eType;
473 ar << (*iter);
474 }
475 }
476 else
477 {
478 int count;
479 ar >> count;
480
481 Reset();
482 for (int i=0; i<count; i++)
483 {
484 int type;
485 CStreamDetail *p = NULL;
486
487 ar >> type;
488 p = NewStream(CStreamDetail::StreamType(type));
489 if (p)
490 ar >> (*p);
491 }
492
493 DetermineBestStreams();
494 }
495}
496void CStreamDetails::Serialize(CVariant& value) const
497{
498 // make sure these properties are always present
499 value["audio"] = CVariant(CVariant::VariantTypeArray);
500 value["video"] = CVariant(CVariant::VariantTypeArray);
501 value["subtitle"] = CVariant(CVariant::VariantTypeArray);
502
503 CVariant v;
504 for (const auto &iter : m_vecItems)
505 {
506 v.clear();
507 iter->Serialize(v);
508 switch (iter->m_eType)
509 {
510 case CStreamDetail::AUDIO:
511 value["audio"].push_back(v);
512 break;
513 case CStreamDetail::VIDEO:
514 value["video"].push_back(v);
515 break;
516 case CStreamDetail::SUBTITLE:
517 value["subtitle"].push_back(v);
518 break;
519 }
520 }
521}
522
523void CStreamDetails::DetermineBestStreams(void)
524{
525 m_pBestVideo = NULL;
526 m_pBestAudio = NULL;
527 m_pBestSubtitle = NULL;
528
529 for (const auto &iter : m_vecItems)
530 {
531 const CStreamDetail **champion;
532 switch (iter->m_eType)
533 {
534 case CStreamDetail::VIDEO:
535 champion = (const CStreamDetail **)&m_pBestVideo;
536 break;
537 case CStreamDetail::AUDIO:
538 champion = (const CStreamDetail **)&m_pBestAudio;
539 break;
540 case CStreamDetail::SUBTITLE:
541 champion = (const CStreamDetail **)&m_pBestSubtitle;
542 break;
543 default:
544 champion = NULL;
545 } /* switch type */
546
547 if (!champion)
548 continue;
549
550 if ((*champion == NULL) || (*champion)->IsWorseThan(*iter))
551 *champion = iter.get();
552 } /* for each */
553}
554
555std::string CStreamDetails::VideoDimsToResolutionDescription(int iWidth, int iHeight)
556{
557 if (iWidth == 0 || iHeight == 0)
558 return "";
559
560 else if (iWidth <= 720 && iHeight <= 480)
561 return "480";
562 // 720x576 (PAL) (768 when rescaled for square pixels)
563 else if (iWidth <= 768 && iHeight <= 576)
564 return "576";
565 // 960x540 (sometimes 544 which is multiple of 16)
566 else if (iWidth <= 960 && iHeight <= 544)
567 return "540";
568 // 1280x720
569 else if (iWidth <= 1280 && iHeight <= 720)
570 return "720";
571 // 1920x1080
572 else if (iWidth <= 1920 && iHeight <= 1080)
573 return "1080";
574 // 4K
575 else if (iWidth <= 4096 && iHeight <= 2160)
576 return "4K";
577 // 8K
578 else if (iWidth <= 8192 && iHeight <= 4320)
579 return "8K";
580 else
581 return "";
582}
583
584std::string CStreamDetails::VideoAspectToAspectDescription(float fAspect)
585{
586 if (fAspect == 0.0f)
587 return "";
588
589 // Given that we're never going to be able to handle every single possibility in
590 // aspect ratios, particularly when cropping prior to video encoding is taken into account
591 // the best we can do is take the "common" aspect ratios, and return the closest one available.
592 // The cutoffs are the geometric mean of the two aspect ratios either side.
593 if (fAspect < 1.3499f) // sqrt(1.33*1.37)
594 return "1.33";
595 else if (fAspect < 1.5080f) // sqrt(1.37*1.66)
596 return "1.37";
597 else if (fAspect < 1.7190f) // sqrt(1.66*1.78)
598 return "1.66";
599 else if (fAspect < 1.8147f) // sqrt(1.78*1.85)
600 return "1.78";
601 else if (fAspect < 2.0174f) // sqrt(1.85*2.20)
602 return "1.85";
603 else if (fAspect < 2.2738f) // sqrt(2.20*2.35)
604 return "2.20";
605 else if (fAspect < 2.3749f) // sqrt(2.35*2.40)
606 return "2.35";
607 else if (fAspect < 2.4739f) // sqrt(2.40*2.55)
608 return "2.40";
609 else if (fAspect < 2.6529f) // sqrt(2.55*2.76)
610 return "2.55";
611 return "2.76";
612}
613
614bool CStreamDetails::SetStreams(const VideoStreamInfo& videoInfo, int videoDuration, const AudioStreamInfo& audioInfo, const SubtitleStreamInfo& subtitleInfo)
615{
616 if (!videoInfo.valid && !audioInfo.valid && !subtitleInfo.valid)
617 return false;
618 Reset();
619 if (videoInfo.valid)
620 AddStream(new CStreamDetailVideo(videoInfo, videoDuration));
621 if (audioInfo.valid)
622 AddStream(new CStreamDetailAudio(audioInfo));
623 if (subtitleInfo.valid)
624 AddStream(new CStreamDetailSubtitle(subtitleInfo));
625 DetermineBestStreams();
626 return true;
627}
diff --git a/xbmc/utils/StreamDetails.h b/xbmc/utils/StreamDetails.h
new file mode 100644
index 0000000..fae9d46
--- /dev/null
+++ b/xbmc/utils/StreamDetails.h
@@ -0,0 +1,137 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "ISerializable.h"
12#include "utils/IArchivable.h"
13
14#include <memory>
15#include <string>
16#include <vector>
17
18class CStreamDetails;
19class CVariant;
20struct VideoStreamInfo;
21struct AudioStreamInfo;
22struct SubtitleStreamInfo;
23
24class CStreamDetail : public IArchivable, public ISerializable
25{
26public:
27 enum StreamType {
28 VIDEO,
29 AUDIO,
30 SUBTITLE
31 };
32
33 explicit CStreamDetail(StreamType type) : m_eType(type), m_pParent(NULL) {};
34 virtual ~CStreamDetail() = default;
35 virtual bool IsWorseThan(const CStreamDetail &that) const = 0;
36
37 const StreamType m_eType;
38
39protected:
40 CStreamDetails *m_pParent;
41 friend class CStreamDetails;
42};
43
44class CStreamDetailVideo final : public CStreamDetail
45{
46public:
47 CStreamDetailVideo();
48 CStreamDetailVideo(const VideoStreamInfo &info, int duration = 0);
49 void Archive(CArchive& ar) override;
50 void Serialize(CVariant& value) const override;
51 bool IsWorseThan(const CStreamDetail &that) const override;
52
53 int m_iWidth = 0;
54 int m_iHeight = 0;
55 float m_fAspect = 0.0;
56 int m_iDuration = 0;
57 std::string m_strCodec;
58 std::string m_strStereoMode;
59 std::string m_strLanguage;
60};
61
62class CStreamDetailAudio final : public CStreamDetail
63{
64public:
65 CStreamDetailAudio();
66 CStreamDetailAudio(const AudioStreamInfo &info);
67 void Archive(CArchive& ar) override;
68 void Serialize(CVariant& value) const override;
69 bool IsWorseThan(const CStreamDetail &that) const override;
70
71 int m_iChannels = -1;
72 std::string m_strCodec;
73 std::string m_strLanguage;
74};
75
76class CStreamDetailSubtitle final : public CStreamDetail
77{
78public:
79 CStreamDetailSubtitle();
80 CStreamDetailSubtitle(const SubtitleStreamInfo &info);
81 CStreamDetailSubtitle& operator=(const CStreamDetailSubtitle &that);
82 void Archive(CArchive& ar) override;
83 void Serialize(CVariant& value) const override;
84 bool IsWorseThan(const CStreamDetail &that) const override;
85
86 std::string m_strLanguage;
87};
88
89class CStreamDetails final : public IArchivable, public ISerializable
90{
91public:
92 CStreamDetails() { Reset(); };
93 CStreamDetails(const CStreamDetails &that);
94 CStreamDetails& operator=(const CStreamDetails &that);
95 bool operator ==(const CStreamDetails &that) const;
96 bool operator !=(const CStreamDetails &that) const;
97
98 static std::string VideoDimsToResolutionDescription(int iWidth, int iHeight);
99 static std::string VideoAspectToAspectDescription(float fAspect);
100
101 bool HasItems(void) const { return m_vecItems.size() > 0; };
102 int GetStreamCount(CStreamDetail::StreamType type) const;
103 int GetVideoStreamCount(void) const;
104 int GetAudioStreamCount(void) const;
105 int GetSubtitleStreamCount(void) const;
106 const CStreamDetail* GetNthStream(CStreamDetail::StreamType type, int idx) const;
107
108 std::string GetVideoCodec(int idx = 0) const;
109 float GetVideoAspect(int idx = 0) const;
110 int GetVideoWidth(int idx = 0) const;
111 int GetVideoHeight(int idx = 0) const;
112 int GetVideoDuration(int idx = 0) const;
113 void SetVideoDuration(int idx, const int duration);
114 std::string GetStereoMode(int idx = 0) const;
115 std::string GetVideoLanguage(int idx = 0) const;
116
117 std::string GetAudioCodec(int idx = 0) const;
118 std::string GetAudioLanguage(int idx = 0) const;
119 int GetAudioChannels(int idx = 0) const;
120
121 std::string GetSubtitleLanguage(int idx = 0) const;
122
123 void AddStream(CStreamDetail *item);
124 void Reset(void);
125 void DetermineBestStreams(void);
126
127 void Archive(CArchive& ar) override;
128 void Serialize(CVariant& value) const override;
129
130 bool SetStreams(const VideoStreamInfo& videoInfo, int videoDuration, const AudioStreamInfo& audioInfo, const SubtitleStreamInfo& subtitleInfo);
131private:
132 CStreamDetail *NewStream(CStreamDetail::StreamType type);
133 std::vector<std::unique_ptr<CStreamDetail>> m_vecItems;
134 const CStreamDetailVideo *m_pBestVideo;
135 const CStreamDetailAudio *m_pBestAudio;
136 const CStreamDetailSubtitle *m_pBestSubtitle;
137};
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "StreamUtils.h"
10
11int StreamUtils::GetCodecPriority(const std::string &codec)
12{
13 /*
14 * Technically flac, truehd, and dtshd_ma are equivalently good as they're all lossless. However,
15 * ffmpeg can't decode dtshd_ma losslessy yet.
16 */
17 if (codec == "flac") // Lossless FLAC
18 return 7;
19 if (codec == "truehd") // Dolby TrueHD
20 return 6;
21 if (codec == "dtshd_ma") // DTS-HD Master Audio (previously known as DTS++)
22 return 5;
23 if (codec == "dtshd_hra") // DTS-HD High Resolution Audio
24 return 4;
25 if (codec == "eac3") // Dolby Digital Plus
26 return 3;
27 if (codec == "dca") // DTS
28 return 2;
29 if (codec == "ac3") // Dolby Digital
30 return 1;
31 return 0;
32}
diff --git a/xbmc/utils/StreamUtils.h b/xbmc/utils/StreamUtils.h
new file mode 100644
index 0000000..e4c2f66
--- /dev/null
+++ b/xbmc/utils/StreamUtils.h
@@ -0,0 +1,17 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13class StreamUtils
14{
15public:
16 static int GetCodecPriority(const std::string &codec);
17};
diff --git a/xbmc/utils/StringUtils.cpp b/xbmc/utils/StringUtils.cpp
new file mode 100644
index 0000000..4195b18
--- /dev/null
+++ b/xbmc/utils/StringUtils.cpp
@@ -0,0 +1,1808 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8//-----------------------------------------------------------------------
9//
10// File: StringUtils.cpp
11//
12// Purpose: ATL split string utility
13// Author: Paul J. Weiss
14//
15// Modified to use J O'Leary's std::string class by kraqh3d
16//
17//------------------------------------------------------------------------
18
19#ifdef HAVE_NEW_CROSSGUID
20#include <guid.hpp>
21#else
22#include <guid.h>
23#endif
24
25#if defined(TARGET_ANDROID)
26#include <androidjni/JNIThreading.h>
27#endif
28
29#include "CharsetConverter.h"
30#include "LangInfo.h"
31#include "StringUtils.h"
32#include "Util.h"
33
34#include <algorithm>
35#include <array>
36#include <assert.h>
37#include <functional>
38#include <inttypes.h>
39#include <iomanip>
40#include <math.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <time.h>
45
46#include <fstrcmp.h>
47#include <memory.h>
48
49// don't move or std functions end up in PCRE namespace
50// clang-format off
51#include "utils/RegExp.h"
52// clang-format on
53
54#define FORMAT_BLOCK_SIZE 512 // # of bytes for initial allocation for printf
55
56static 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}$";
57
58/* empty string for use in returns by ref */
59const std::string StringUtils::Empty = "";
60
61// Copyright (c) Leigh Brasington 2012. All rights reserved.
62// This code may be used and reproduced without written permission.
63// http://www.leighb.com/tounicupper.htm
64//
65// The tables were constructed from
66// http://publib.boulder.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=%2Fnls%2Frbagslowtoupmaptable.htm
67
68static constexpr wchar_t unicode_lowers[] = {
69 (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,
70 (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,
71 (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,
72 (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,
73 (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,
74 (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,
75 (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,
76 (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,
77 (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,
78 (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,
79 (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,
80 (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,
81 (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,
82 (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,
83 (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,
84 (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,
85 (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,
86 (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,
87 (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,
88 (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,
89 (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,
90 (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,
91 (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,
92 (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,
93 (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,
94 (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,
95 (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,
96 (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,
97 (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,
98 (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,
99 (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,
100 (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,
101 (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,
102 (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,
103 (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,
104 (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,
105 (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,
106 (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,
107 (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,
108 (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,
109 (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,
110 (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,
111 (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,
112 (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,
113 (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,
114 (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,
115 (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,
116 (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,
117 (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,
118 (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,
119 (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,
120 (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,
121 (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,
122 (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,
123 (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,
124 (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,
125 (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,
126 (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,
127 (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,
128 (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,
129 (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,
130 (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,
131 (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,
132 (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,
133 (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,
134 (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,
135 (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,
136 (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,
137 (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,
138 (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,
139 (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,
140 (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,
141 (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,
142 (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
143};
144
145static const wchar_t unicode_uppers[] = {
146 (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,
147 (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,
148 (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,
149 (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,
150 (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,
151 (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,
152 (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,
153 (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,
154 (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,
155 (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,
156 (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,
157 (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,
158 (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,
159 (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,
160 (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,
161 (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,
162 (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,
163 (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,
164 (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,
165 (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,
166 (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,
167 (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,
168 (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,
169 (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,
170 (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,
171 (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,
172 (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,
173 (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,
174 (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,
175 (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,
176 (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,
177 (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,
178 (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,
179 (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,
180 (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,
181 (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,
182 (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,
183 (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,
184 (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,
185 (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,
186 (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,
187 (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,
188 (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,
189 (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,
190 (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,
191 (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,
192 (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,
193 (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,
194 (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,
195 (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,
196 (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,
197 (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,
198 (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,
199 (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,
200 (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,
201 (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,
202 (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,
203 (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,
204 (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,
205 (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,
206 (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,
207 (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,
208 (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,
209 (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,
210 (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,
211 (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,
212 (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,
213 (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,
214 (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,
215 (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,
216 (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,
217 (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,
218 (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,
219 (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
220};
221
222
223std::string StringUtils::FormatV(const char *fmt, va_list args)
224{
225 if (!fmt || !fmt[0])
226 return "";
227
228 int size = FORMAT_BLOCK_SIZE;
229 va_list argCopy;
230
231 while (true)
232 {
233 char *cstr = reinterpret_cast<char*>(malloc(sizeof(char) * size));
234 if (!cstr)
235 return "";
236
237 va_copy(argCopy, args);
238 int nActual = vsnprintf(cstr, size, fmt, argCopy);
239 va_end(argCopy);
240
241 if (nActual > -1 && nActual < size) // We got a valid result
242 {
243 std::string str(cstr, nActual);
244 free(cstr);
245 return str;
246 }
247 free(cstr);
248#ifndef TARGET_WINDOWS
249 if (nActual > -1) // Exactly what we will need (glibc 2.1)
250 size = nActual + 1;
251 else // Let's try to double the size (glibc 2.0)
252 size *= 2;
253#else // TARGET_WINDOWS
254 va_copy(argCopy, args);
255 size = _vscprintf(fmt, argCopy);
256 va_end(argCopy);
257 if (size < 0)
258 return "";
259 else
260 size++; // increment for null-termination
261#endif // TARGET_WINDOWS
262 }
263
264 return ""; // unreachable
265}
266
267std::wstring StringUtils::FormatV(const wchar_t *fmt, va_list args)
268{
269 if (!fmt || !fmt[0])
270 return L"";
271
272 int size = FORMAT_BLOCK_SIZE;
273 va_list argCopy;
274
275 while (true)
276 {
277 wchar_t *cstr = reinterpret_cast<wchar_t*>(malloc(sizeof(wchar_t) * size));
278 if (!cstr)
279 return L"";
280
281 va_copy(argCopy, args);
282 int nActual = vswprintf(cstr, size, fmt, argCopy);
283 va_end(argCopy);
284
285 if (nActual > -1 && nActual < size) // We got a valid result
286 {
287 std::wstring str(cstr, nActual);
288 free(cstr);
289 return str;
290 }
291 free(cstr);
292
293#ifndef TARGET_WINDOWS
294 if (nActual > -1) // Exactly what we will need (glibc 2.1)
295 size = nActual + 1;
296 else // Let's try to double the size (glibc 2.0)
297 size *= 2;
298#else // TARGET_WINDOWS
299 va_copy(argCopy, args);
300 size = _vscwprintf(fmt, argCopy);
301 va_end(argCopy);
302 if (size < 0)
303 return L"";
304 else
305 size++; // increment for null-termination
306#endif // TARGET_WINDOWS
307 }
308
309 return L"";
310}
311
312int compareWchar (const void* a, const void* b)
313{
314 if (*(const wchar_t*)a < *(const wchar_t*)b)
315 return -1;
316 else if (*(const wchar_t*)a > *(const wchar_t*)b)
317 return 1;
318 return 0;
319}
320
321wchar_t tolowerUnicode(const wchar_t& c)
322{
323 wchar_t* p = (wchar_t*) bsearch (&c, unicode_uppers, sizeof(unicode_uppers) / sizeof(wchar_t), sizeof(wchar_t), compareWchar);
324 if (p)
325 return *(unicode_lowers + (p - unicode_uppers));
326
327 return c;
328}
329
330wchar_t toupperUnicode(const wchar_t& c)
331{
332 wchar_t* p = (wchar_t*) bsearch (&c, unicode_lowers, sizeof(unicode_lowers) / sizeof(wchar_t), sizeof(wchar_t), compareWchar);
333 if (p)
334 return *(unicode_uppers + (p - unicode_lowers));
335
336 return c;
337}
338
339void StringUtils::ToUpper(std::string &str)
340{
341 std::transform(str.begin(), str.end(), str.begin(), ::toupper);
342}
343
344void StringUtils::ToUpper(std::wstring &str)
345{
346 transform(str.begin(), str.end(), str.begin(), toupperUnicode);
347}
348
349void StringUtils::ToLower(std::string &str)
350{
351 transform(str.begin(), str.end(), str.begin(), ::tolower);
352}
353
354void StringUtils::ToLower(std::wstring &str)
355{
356 transform(str.begin(), str.end(), str.begin(), tolowerUnicode);
357}
358
359void StringUtils::ToCapitalize(std::string &str)
360{
361 std::wstring wstr;
362 g_charsetConverter.utf8ToW(str, wstr);
363 ToCapitalize(wstr);
364 g_charsetConverter.wToUTF8(wstr, str);
365}
366
367void StringUtils::ToCapitalize(std::wstring &str)
368{
369 const std::locale& loc = g_langInfo.GetSystemLocale();
370 bool isFirstLetter = true;
371 for (std::wstring::iterator it = str.begin(); it < str.end(); ++it)
372 {
373 // capitalize after spaces and punctuation characters (except apostrophes)
374 if (std::isspace(*it, loc) || (std::ispunct(*it, loc) && *it != '\''))
375 isFirstLetter = true;
376 else if (isFirstLetter)
377 {
378 *it = std::toupper(*it, loc);
379 isFirstLetter = false;
380 }
381 }
382}
383
384bool StringUtils::EqualsNoCase(const std::string &str1, const std::string &str2)
385{
386 // before we do the char-by-char comparison, first compare sizes of both strings.
387 // This led to a 33% improvement in benchmarking on average. (size() just returns a member of std::string)
388 if (str1.size() != str2.size())
389 return false;
390 return EqualsNoCase(str1.c_str(), str2.c_str());
391}
392
393bool StringUtils::EqualsNoCase(const std::string &str1, const char *s2)
394{
395 return EqualsNoCase(str1.c_str(), s2);
396}
397
398bool StringUtils::EqualsNoCase(const char *s1, const char *s2)
399{
400 char c2; // we need only one char outside the loop
401 do
402 {
403 const char c1 = *s1++; // const local variable should help compiler to optimize
404 c2 = *s2++;
405 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.
406 return false;
407 } while (c2 != '\0'); // At this point, we know c1 == c2, so there's no need to test them both.
408 return true;
409}
410
411int StringUtils::CompareNoCase(const std::string& str1, const std::string& str2, size_t n /* = 0 */)
412{
413 return CompareNoCase(str1.c_str(), str2.c_str(), n);
414}
415
416int StringUtils::CompareNoCase(const char* s1, const char* s2, size_t n /* = 0 */)
417{
418 char c2; // we need only one char outside the loop
419 size_t index = 0;
420 do
421 {
422 const char c1 = *s1++; // const local variable should help compiler to optimize
423 c2 = *s2++;
424 index++;
425 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.
426 return ::tolower(c1) - ::tolower(c2);
427 } while (c2 != '\0' &&
428 index != n); // At this point, we know c1 == c2, so there's no need to test them both.
429 return 0;
430}
431
432std::string StringUtils::Left(const std::string &str, size_t count)
433{
434 count = std::max((size_t)0, std::min(count, str.size()));
435 return str.substr(0, count);
436}
437
438std::string StringUtils::Mid(const std::string &str, size_t first, size_t count /* = string::npos */)
439{
440 if (first + count > str.size())
441 count = str.size() - first;
442
443 if (first > str.size())
444 return std::string();
445
446 assert(first + count <= str.size());
447
448 return str.substr(first, count);
449}
450
451std::string StringUtils::Right(const std::string &str, size_t count)
452{
453 count = std::max((size_t)0, std::min(count, str.size()));
454 return str.substr(str.size() - count);
455}
456
457std::string& StringUtils::Trim(std::string &str)
458{
459 TrimLeft(str);
460 return TrimRight(str);
461}
462
463std::string& StringUtils::Trim(std::string &str, const char* const chars)
464{
465 TrimLeft(str, chars);
466 return TrimRight(str, chars);
467}
468
469// hack to check only first byte of UTF-8 character
470// without this hack "TrimX" functions failed on Win32 and OS X with UTF-8 strings
471static int isspace_c(char c)
472{
473 return (c & 0x80) == 0 && ::isspace(c);
474}
475
476std::string& StringUtils::TrimLeft(std::string &str)
477{
478 str.erase(str.begin(), std::find_if(str.begin(), str.end(), std::not1(std::function<int(char)>(isspace_c))));
479 return str;
480}
481
482std::string& StringUtils::TrimLeft(std::string &str, const char* const chars)
483{
484 size_t nidx = str.find_first_not_of(chars);
485 str.erase(0, nidx);
486 return str;
487}
488
489std::string& StringUtils::TrimRight(std::string &str)
490{
491 str.erase(std::find_if(str.rbegin(), str.rend(), std::not1(std::function<int(char)>(isspace_c))).base(), str.end());
492 return str;
493}
494
495std::string& StringUtils::TrimRight(std::string &str, const char* const chars)
496{
497 size_t nidx = str.find_last_not_of(chars);
498 str.erase(str.npos == nidx ? 0 : ++nidx);
499 return str;
500}
501
502int StringUtils::ReturnDigits(const std::string& str)
503{
504 std::stringstream ss;
505 for (const auto& character : str)
506 {
507 if (isdigit(character))
508 ss << character;
509 }
510 return atoi(ss.str().c_str());
511}
512
513std::string& StringUtils::RemoveDuplicatedSpacesAndTabs(std::string& str)
514{
515 std::string::iterator it = str.begin();
516 bool onSpace = false;
517 while(it != str.end())
518 {
519 if (*it == '\t')
520 *it = ' ';
521
522 if (*it == ' ')
523 {
524 if (onSpace)
525 {
526 it = str.erase(it);
527 continue;
528 }
529 else
530 onSpace = true;
531 }
532 else
533 onSpace = false;
534
535 ++it;
536 }
537 return str;
538}
539
540int StringUtils::Replace(std::string &str, char oldChar, char newChar)
541{
542 int replacedChars = 0;
543 for (std::string::iterator it = str.begin(); it != str.end(); ++it)
544 {
545 if (*it == oldChar)
546 {
547 *it = newChar;
548 replacedChars++;
549 }
550 }
551
552 return replacedChars;
553}
554
555int StringUtils::Replace(std::string &str, const std::string &oldStr, const std::string &newStr)
556{
557 if (oldStr.empty())
558 return 0;
559
560 int replacedChars = 0;
561 size_t index = 0;
562
563 while (index < str.size() && (index = str.find(oldStr, index)) != std::string::npos)
564 {
565 str.replace(index, oldStr.size(), newStr);
566 index += newStr.size();
567 replacedChars++;
568 }
569
570 return replacedChars;
571}
572
573int StringUtils::Replace(std::wstring &str, const std::wstring &oldStr, const std::wstring &newStr)
574{
575 if (oldStr.empty())
576 return 0;
577
578 int replacedChars = 0;
579 size_t index = 0;
580
581 while (index < str.size() && (index = str.find(oldStr, index)) != std::string::npos)
582 {
583 str.replace(index, oldStr.size(), newStr);
584 index += newStr.size();
585 replacedChars++;
586 }
587
588 return replacedChars;
589}
590
591bool StringUtils::StartsWith(const std::string &str1, const std::string &str2)
592{
593 return str1.compare(0, str2.size(), str2) == 0;
594}
595
596bool StringUtils::StartsWith(const std::string &str1, const char *s2)
597{
598 return StartsWith(str1.c_str(), s2);
599}
600
601bool StringUtils::StartsWith(const char *s1, const char *s2)
602{
603 while (*s2 != '\0')
604 {
605 if (*s1 != *s2)
606 return false;
607 s1++;
608 s2++;
609 }
610 return true;
611}
612
613bool StringUtils::StartsWithNoCase(const std::string &str1, const std::string &str2)
614{
615 return StartsWithNoCase(str1.c_str(), str2.c_str());
616}
617
618bool StringUtils::StartsWithNoCase(const std::string &str1, const char *s2)
619{
620 return StartsWithNoCase(str1.c_str(), s2);
621}
622
623bool StringUtils::StartsWithNoCase(const char *s1, const char *s2)
624{
625 while (*s2 != '\0')
626 {
627 if (::tolower(*s1) != ::tolower(*s2))
628 return false;
629 s1++;
630 s2++;
631 }
632 return true;
633}
634
635bool StringUtils::EndsWith(const std::string &str1, const std::string &str2)
636{
637 if (str1.size() < str2.size())
638 return false;
639 return str1.compare(str1.size() - str2.size(), str2.size(), str2) == 0;
640}
641
642bool StringUtils::EndsWith(const std::string &str1, const char *s2)
643{
644 size_t len2 = strlen(s2);
645 if (str1.size() < len2)
646 return false;
647 return str1.compare(str1.size() - len2, len2, s2) == 0;
648}
649
650bool StringUtils::EndsWithNoCase(const std::string &str1, const std::string &str2)
651{
652 if (str1.size() < str2.size())
653 return false;
654 const char *s1 = str1.c_str() + str1.size() - str2.size();
655 const char *s2 = str2.c_str();
656 while (*s2 != '\0')
657 {
658 if (::tolower(*s1) != ::tolower(*s2))
659 return false;
660 s1++;
661 s2++;
662 }
663 return true;
664}
665
666bool StringUtils::EndsWithNoCase(const std::string &str1, const char *s2)
667{
668 size_t len2 = strlen(s2);
669 if (str1.size() < len2)
670 return false;
671 const char *s1 = str1.c_str() + str1.size() - len2;
672 while (*s2 != '\0')
673 {
674 if (::tolower(*s1) != ::tolower(*s2))
675 return false;
676 s1++;
677 s2++;
678 }
679 return true;
680}
681
682std::vector<std::string> StringUtils::Split(const std::string& input, const std::string& delimiter, unsigned int iMaxStrings)
683{
684 std::vector<std::string> result;
685 SplitTo(std::back_inserter(result), input, delimiter, iMaxStrings);
686 return result;
687}
688
689std::vector<std::string> StringUtils::Split(const std::string& input, const char delimiter, size_t iMaxStrings)
690{
691 std::vector<std::string> result;
692 SplitTo(std::back_inserter(result), input, delimiter, iMaxStrings);
693 return result;
694}
695
696std::vector<std::string> StringUtils::Split(const std::string& input, const std::vector<std::string>& delimiters)
697{
698 std::vector<std::string> result;
699 SplitTo(std::back_inserter(result), input, delimiters);
700 return result;
701}
702
703std::vector<std::string> StringUtils::SplitMulti(const std::vector<std::string> &input, const std::vector<std::string> &delimiters, unsigned int iMaxStrings /* = 0 */)
704{
705 if (input.empty())
706 return std::vector<std::string>();
707
708 std::vector<std::string> results(input);
709
710 if (delimiters.empty() || (iMaxStrings > 0 && iMaxStrings <= input.size()))
711 return results;
712
713 std::vector<std::string> strings1;
714 if (iMaxStrings == 0)
715 {
716 for (size_t di = 0; di < delimiters.size(); di++)
717 {
718 for (size_t i = 0; i < results.size(); i++)
719 {
720 std::vector<std::string> substrings = StringUtils::Split(results[i], delimiters[di]);
721 for (size_t j = 0; j < substrings.size(); j++)
722 strings1.push_back(substrings[j]);
723 }
724 results = strings1;
725 strings1.clear();
726 }
727 return results;
728 }
729
730 // Control the number of strings input is split into, keeping the original strings.
731 // Note iMaxStrings > input.size()
732 int iNew = iMaxStrings - results.size();
733 for (size_t di = 0; di < delimiters.size(); di++)
734 {
735 for (size_t i = 0; i < results.size(); i++)
736 {
737 if (iNew > 0)
738 {
739 std::vector<std::string> substrings = StringUtils::Split(results[i], delimiters[di], iNew + 1);
740 iNew = iNew - substrings.size() + 1;
741 for (size_t j = 0; j < substrings.size(); j++)
742 strings1.push_back(substrings[j]);
743 }
744 else
745 strings1.push_back(results[i]);
746 }
747 results = strings1;
748 iNew = iMaxStrings - results.size();
749 strings1.clear();
750 if ((iNew <= 0))
751 break; //Stop trying any more delimiters
752 }
753 return results;
754}
755
756// returns the number of occurrences of strFind in strInput.
757int StringUtils::FindNumber(const std::string& strInput, const std::string &strFind)
758{
759 size_t pos = strInput.find(strFind, 0);
760 int numfound = 0;
761 while (pos != std::string::npos)
762 {
763 numfound++;
764 pos = strInput.find(strFind, pos + 1);
765 }
766 return numfound;
767}
768
769// Plane maps for MySQL utf8_general_ci (now known as utf8mb3_general_ci) collation
770// Derived from https://github.com/MariaDB/server/blob/10.5/strings/ctype-utf8.c
771
772// clang-format off
773static const uint16_t plane00[] = {
774 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
775 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
776 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
777 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
778 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
779 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
780 0x0060, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
781 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
782 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
783 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
784 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
785 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x039C, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
786 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00C6, 0x0043, 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
787 0x00D0, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x00D7, 0x00D8, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00DE, 0x0053,
788 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00C6, 0x0043, 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
789 0x00D0, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x00F7, 0x00D8, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00DE, 0x0059
790};
791
792static const uint16_t plane01[] = {
793 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0043, 0x0043, 0x0043, 0x0043, 0x0043, 0x0043, 0x0043, 0x0043, 0x0044, 0x0044,
794 0x0110, 0x0110, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0047, 0x0047, 0x0047, 0x0047,
795 0x0047, 0x0047, 0x0047, 0x0047, 0x0048, 0x0048, 0x0126, 0x0126, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049,
796 0x0049, 0x0049, 0x0132, 0x0132, 0x004A, 0x004A, 0x004B, 0x004B, 0x0138, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x013F,
797 0x013F, 0x0141, 0x0141, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x0149, 0x014A, 0x014A, 0x004F, 0x004F, 0x004F, 0x004F,
798 0x004F, 0x004F, 0x0152, 0x0152, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053,
799 0x0053, 0x0053, 0x0054, 0x0054, 0x0054, 0x0054, 0x0166, 0x0166, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055,
800 0x0055, 0x0055, 0x0055, 0x0055, 0x0057, 0x0057, 0x0059, 0x0059, 0x0059, 0x005A, 0x005A, 0x005A, 0x005A, 0x005A, 0x005A, 0x0053,
801 0x0180, 0x0181, 0x0182, 0x0182, 0x0184, 0x0184, 0x0186, 0x0187, 0x0187, 0x0189, 0x018A, 0x018B, 0x018B, 0x018D, 0x018E, 0x018F,
802 0x0190, 0x0191, 0x0191, 0x0193, 0x0194, 0x01F6, 0x0196, 0x0197, 0x0198, 0x0198, 0x019A, 0x019B, 0x019C, 0x019D, 0x019E, 0x019F,
803 0x004F, 0x004F, 0x01A2, 0x01A2, 0x01A4, 0x01A4, 0x01A6, 0x01A7, 0x01A7, 0x01A9, 0x01AA, 0x01AB, 0x01AC, 0x01AC, 0x01AE, 0x0055,
804 0x0055, 0x01B1, 0x01B2, 0x01B3, 0x01B3, 0x01B5, 0x01B5, 0x01B7, 0x01B8, 0x01B8, 0x01BA, 0x01BB, 0x01BC, 0x01BC, 0x01BE, 0x01F7,
805 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x01C4, 0x01C4, 0x01C4, 0x01C7, 0x01C7, 0x01C7, 0x01CA, 0x01CA, 0x01CA, 0x0041, 0x0041, 0x0049,
806 0x0049, 0x004F, 0x004F, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x018E, 0x0041, 0x0041,
807 0x0041, 0x0041, 0x00C6, 0x00C6, 0x01E4, 0x01E4, 0x0047, 0x0047, 0x004B, 0x004B, 0x004F, 0x004F, 0x004F, 0x004F, 0x01B7, 0x01B7,
808 0x004A, 0x01F1, 0x01F1, 0x01F1, 0x0047, 0x0047, 0x01F6, 0x01F7, 0x004E, 0x004E, 0x0041, 0x0041, 0x00C6, 0x00C6, 0x00D8, 0x00D8
809};
810
811static const uint16_t plane02[] = {
812 0x0041, 0x0041, 0x0041, 0x0041, 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049, 0x004F, 0x004F, 0x004F, 0x004F,
813 0x0052, 0x0052, 0x0052, 0x0052, 0x0055, 0x0055, 0x0055, 0x0055, 0x0053, 0x0053, 0x0054, 0x0054, 0x021C, 0x021C, 0x0048, 0x0048,
814 0x0220, 0x0221, 0x0222, 0x0222, 0x0224, 0x0224, 0x0041, 0x0041, 0x0045, 0x0045, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F,
815 0x004F, 0x004F, 0x0059, 0x0059, 0x0234, 0x0235, 0x0236, 0x0237, 0x0238, 0x0239, 0x023A, 0x023B, 0x023C, 0x023D, 0x023E, 0x023F,
816 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024A, 0x024B, 0x024C, 0x024D, 0x024E, 0x024F,
817 0x0250, 0x0251, 0x0252, 0x0181, 0x0186, 0x0255, 0x0189, 0x018A, 0x0258, 0x018F, 0x025A, 0x0190, 0x025C, 0x025D, 0x025E, 0x025F,
818 0x0193, 0x0261, 0x0262, 0x0194, 0x0264, 0x0265, 0x0266, 0x0267, 0x0197, 0x0196, 0x026A, 0x026B, 0x026C, 0x026D, 0x026E, 0x019C,
819 0x0270, 0x0271, 0x019D, 0x0273, 0x0274, 0x019F, 0x0276, 0x0277, 0x0278, 0x0279, 0x027A, 0x027B, 0x027C, 0x027D, 0x027E, 0x027F,
820 0x01A6, 0x0281, 0x0282, 0x01A9, 0x0284, 0x0285, 0x0286, 0x0287, 0x01AE, 0x0289, 0x01B1, 0x01B2, 0x028C, 0x028D, 0x028E, 0x028F,
821 0x0290, 0x0291, 0x01B7, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297, 0x0298, 0x0299, 0x029A, 0x029B, 0x029C, 0x029D, 0x029E, 0x029F,
822 0x02A0, 0x02A1, 0x02A2, 0x02A3, 0x02A4, 0x02A5, 0x02A6, 0x02A7, 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC, 0x02AD, 0x02AE, 0x02AF,
823 0x02B0, 0x02B1, 0x02B2, 0x02B3, 0x02B4, 0x02B5, 0x02B6, 0x02B7, 0x02B8, 0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF,
824 0x02C0, 0x02C1, 0x02C2, 0x02C3, 0x02C4, 0x02C5, 0x02C6, 0x02C7, 0x02C8, 0x02C9, 0x02CA, 0x02CB, 0x02CC, 0x02CD, 0x02CE, 0x02CF,
825 0x02D0, 0x02D1, 0x02D2, 0x02D3, 0x02D4, 0x02D5, 0x02D6, 0x02D7, 0x02D8, 0x02D9, 0x02DA, 0x02DB, 0x02DC, 0x02DD, 0x02DE, 0x02DF,
826 0x02E0, 0x02E1, 0x02E2, 0x02E3, 0x02E4, 0x02E5, 0x02E6, 0x02E7, 0x02E8, 0x02E9, 0x02EA, 0x02EB, 0x02EC, 0x02ED, 0x02EE, 0x02EF,
827 0x02F0, 0x02F1, 0x02F2, 0x02F3, 0x02F4, 0x02F5, 0x02F6, 0x02F7, 0x02F8, 0x02F9, 0x02FA, 0x02FB, 0x02FC, 0x02FD, 0x02FE, 0x02FF
828};
829
830static const uint16_t plane03[] = {
831 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F,
832 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F,
833 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F,
834 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F,
835 0x0340, 0x0341, 0x0342, 0x0343, 0x0344, 0x0399, 0x0346, 0x0347, 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F,
836 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F,
837 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F,
838 0x0370, 0x0371, 0x0372, 0x0373, 0x0374, 0x0375, 0x0376, 0x0377, 0x0378, 0x0379, 0x037A, 0x037B, 0x037C, 0x037D, 0x037E, 0x037F,
839 0x0380, 0x0381, 0x0382, 0x0383, 0x0384, 0x0385, 0x0391, 0x0387, 0x0395, 0x0397, 0x0399, 0x038B, 0x039F, 0x038D, 0x03A5, 0x03A9,
840 0x0399, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
841 0x03A0, 0x03A1, 0x03A2, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x0399, 0x03A5, 0x0391, 0x0395, 0x0397, 0x0399,
842 0x03A5, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
843 0x03A0, 0x03A1, 0x03A3, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x0399, 0x03A5, 0x039F, 0x03A5, 0x03A9, 0x03CF,
844 0x0392, 0x0398, 0x03D2, 0x03D2, 0x03D2, 0x03A6, 0x03A0, 0x03D7, 0x03D8, 0x03D9, 0x03DA, 0x03DA, 0x03DC, 0x03DC, 0x03DE, 0x03DE,
845 0x03E0, 0x03E0, 0x03E2, 0x03E2, 0x03E4, 0x03E4, 0x03E6, 0x03E6, 0x03E8, 0x03E8, 0x03EA, 0x03EA, 0x03EC, 0x03EC, 0x03EE, 0x03EE,
846 0x039A, 0x03A1, 0x03A3, 0x03F3, 0x03F4, 0x03F5, 0x03F6, 0x03F7, 0x03F8, 0x03F9, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF
847};
848
849static const uint16_t plane04[] = {
850 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406, 0x0408, 0x0409, 0x040A, 0x040B, 0x041A, 0x0418, 0x0423, 0x040F,
851 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
852 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
853 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
854 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
855 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406, 0x0408, 0x0409, 0x040A, 0x040B, 0x041A, 0x0418, 0x0423, 0x040F,
856 0x0460, 0x0460, 0x0462, 0x0462, 0x0464, 0x0464, 0x0466, 0x0466, 0x0468, 0x0468, 0x046A, 0x046A, 0x046C, 0x046C, 0x046E, 0x046E,
857 0x0470, 0x0470, 0x0472, 0x0472, 0x0474, 0x0474, 0x0474, 0x0474, 0x0478, 0x0478, 0x047A, 0x047A, 0x047C, 0x047C, 0x047E, 0x047E,
858 0x0480, 0x0480, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048C, 0x048E, 0x048E,
859 0x0490, 0x0490, 0x0492, 0x0492, 0x0494, 0x0494, 0x0496, 0x0496, 0x0498, 0x0498, 0x049A, 0x049A, 0x049C, 0x049C, 0x049E, 0x049E,
860 0x04A0, 0x04A0, 0x04A2, 0x04A2, 0x04A4, 0x04A4, 0x04A6, 0x04A6, 0x04A8, 0x04A8, 0x04AA, 0x04AA, 0x04AC, 0x04AC, 0x04AE, 0x04AE,
861 0x04B0, 0x04B0, 0x04B2, 0x04B2, 0x04B4, 0x04B4, 0x04B6, 0x04B6, 0x04B8, 0x04B8, 0x04BA, 0x04BA, 0x04BC, 0x04BC, 0x04BE, 0x04BE,
862 0x04C0, 0x0416, 0x0416, 0x04C3, 0x04C3, 0x04C5, 0x04C6, 0x04C7, 0x04C7, 0x04C9, 0x04CA, 0x04CB, 0x04CB, 0x04CD, 0x04CE, 0x04CF,
863 0x0410, 0x0410, 0x0410, 0x0410, 0x04D4, 0x04D4, 0x0415, 0x0415, 0x04D8, 0x04D8, 0x04D8, 0x04D8, 0x0416, 0x0416, 0x0417, 0x0417,
864 0x04E0, 0x04E0, 0x0418, 0x0418, 0x0418, 0x0418, 0x041E, 0x041E, 0x04E8, 0x04E8, 0x04E8, 0x04E8, 0x042D, 0x042D, 0x0423, 0x0423,
865 0x0423, 0x0423, 0x0423, 0x0423, 0x0427, 0x0427, 0x04F6, 0x04F7, 0x042B, 0x042B, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF
866};
867
868static const uint16_t plane05[] = {
869 0x0500, 0x0501, 0x0502, 0x0503, 0x0504, 0x0505, 0x0506, 0x0507, 0x0508, 0x0509, 0x050A, 0x050B, 0x050C, 0x050D, 0x050E, 0x050F,
870 0x0510, 0x0511, 0x0512, 0x0513, 0x0514, 0x0515, 0x0516, 0x0517, 0x0518, 0x0519, 0x051A, 0x051B, 0x051C, 0x051D, 0x051E, 0x051F,
871 0x0520, 0x0521, 0x0522, 0x0523, 0x0524, 0x0525, 0x0526, 0x0527, 0x0528, 0x0529, 0x052A, 0x052B, 0x052C, 0x052D, 0x052E, 0x052F,
872 0x0530, 0x0531, 0x0532, 0x0533, 0x0534, 0x0535, 0x0536, 0x0537, 0x0538, 0x0539, 0x053A, 0x053B, 0x053C, 0x053D, 0x053E, 0x053F,
873 0x0540, 0x0541, 0x0542, 0x0543, 0x0544, 0x0545, 0x0546, 0x0547, 0x0548, 0x0549, 0x054A, 0x054B, 0x054C, 0x054D, 0x054E, 0x054F,
874 0x0550, 0x0551, 0x0552, 0x0553, 0x0554, 0x0555, 0x0556, 0x0557, 0x0558, 0x0559, 0x055A, 0x055B, 0x055C, 0x055D, 0x055E, 0x055F,
875 0x0560, 0x0531, 0x0532, 0x0533, 0x0534, 0x0535, 0x0536, 0x0537, 0x0538, 0x0539, 0x053A, 0x053B, 0x053C, 0x053D, 0x053E, 0x053F,
876 0x0540, 0x0541, 0x0542, 0x0543, 0x0544, 0x0545, 0x0546, 0x0547, 0x0548, 0x0549, 0x054A, 0x054B, 0x054C, 0x054D, 0x054E, 0x054F,
877 0x0550, 0x0551, 0x0552, 0x0553, 0x0554, 0x0555, 0x0556, 0x0587, 0x0588, 0x0589, 0x058A, 0x058B, 0x058C, 0x058D, 0x058E, 0x058F,
878 0x0590, 0x0591, 0x0592, 0x0593, 0x0594, 0x0595, 0x0596, 0x0597, 0x0598, 0x0599, 0x059A, 0x059B, 0x059C, 0x059D, 0x059E, 0x059F,
879 0x05A0, 0x05A1, 0x05A2, 0x05A3, 0x05A4, 0x05A5, 0x05A6, 0x05A7, 0x05A8, 0x05A9, 0x05AA, 0x05AB, 0x05AC, 0x05AD, 0x05AE, 0x05AF,
880 0x05B0, 0x05B1, 0x05B2, 0x05B3, 0x05B4, 0x05B5, 0x05B6, 0x05B7, 0x05B8, 0x05B9, 0x05BA, 0x05BB, 0x05BC, 0x05BD, 0x05BE, 0x05BF,
881 0x05C0, 0x05C1, 0x05C2, 0x05C3, 0x05C4, 0x05C5, 0x05C6, 0x05C7, 0x05C8, 0x05C9, 0x05CA, 0x05CB, 0x05CC, 0x05CD, 0x05CE, 0x05CF,
882 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF,
883 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x05EB, 0x05EC, 0x05ED, 0x05EE, 0x05EF,
884 0x05F0, 0x05F1, 0x05F2, 0x05F3, 0x05F4, 0x05F5, 0x05F6, 0x05F7, 0x05F8, 0x05F9, 0x05FA, 0x05FB, 0x05FC, 0x05FD, 0x05FE, 0x05FF
885};
886
887static const uint16_t plane1E[] = {
888 0x0041, 0x0041, 0x0042, 0x0042, 0x0042, 0x0042, 0x0042, 0x0042, 0x0043, 0x0043, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044,
889 0x0044, 0x0044, 0x0044, 0x0044, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0046, 0x0046,
890 0x0047, 0x0047, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0049, 0x0049, 0x0049, 0x0049,
891 0x004B, 0x004B, 0x004B, 0x004B, 0x004B, 0x004B, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x004C, 0x004D, 0x004D,
892 0x004D, 0x004D, 0x004D, 0x004D, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F,
893 0x004F, 0x004F, 0x004F, 0x004F, 0x0050, 0x0050, 0x0050, 0x0050, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052,
894 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0054, 0x0054, 0x0054, 0x0054, 0x0054, 0x0054,
895 0x0054, 0x0054, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0056, 0x0056, 0x0056, 0x0056,
896 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0058, 0x0058, 0x0058, 0x0058, 0x0059, 0x0059,
897 0x005A, 0x005A, 0x005A, 0x005A, 0x005A, 0x005A, 0x0048, 0x0054, 0x0057, 0x0059, 0x1E9A, 0x0053, 0x1E9C, 0x1E9D, 0x1E9E, 0x1E9F,
898 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041,
899 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045,
900 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049, 0x004F, 0x004F, 0x004F, 0x004F,
901 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F,
902 0x004F, 0x004F, 0x004F, 0x004F, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055, 0x0055,
903 0x0055, 0x0055, 0x0059, 0x0059, 0x0059, 0x0059, 0x0059, 0x0059, 0x0059, 0x0059, 0x1EFA, 0x1EFB, 0x1EFC, 0x1EFD, 0x1EFE, 0x1EFF
904};
905
906static const uint16_t plane1F[] = {
907 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391,
908 0x0395, 0x0395, 0x0395, 0x0395, 0x0395, 0x0395, 0x1F16, 0x1F17, 0x0395, 0x0395, 0x0395, 0x0395, 0x0395, 0x0395, 0x1F1E, 0x1F1F,
909 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397,
910 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399,
911 0x039F, 0x039F, 0x039F, 0x039F, 0x039F, 0x039F, 0x1F46, 0x1F47, 0x039F, 0x039F, 0x039F, 0x039F, 0x039F, 0x039F, 0x1F4E, 0x1F4F,
912 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x1F58, 0x03A5, 0x1F5A, 0x03A5, 0x1F5C, 0x03A5, 0x1F5E, 0x03A5,
913 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9,
914 0x0391, 0x1FBB, 0x0395, 0x1FC9, 0x0397, 0x1FCB, 0x0399, 0x1FDB, 0x039F, 0x1FF9, 0x03A5, 0x1FEB, 0x03A9, 0x1FFB, 0x1F7E, 0x1F7F,
915 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391,
916 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397, 0x0397,
917 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9, 0x03A9,
918 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x1FB5, 0x0391, 0x0391, 0x0391, 0x0391, 0x0391, 0x1FBB, 0x0391, 0x1FBD, 0x0399, 0x1FBF,
919 0x1FC0, 0x1FC1, 0x0397, 0x0397, 0x0397, 0x1FC5, 0x0397, 0x0397, 0x0395, 0x1FC9, 0x0397, 0x1FCB, 0x0397, 0x1FCD, 0x1FCE, 0x1FCF,
920 0x0399, 0x0399, 0x0399, 0x1FD3, 0x1FD4, 0x1FD5, 0x0399, 0x0399, 0x0399, 0x0399, 0x0399, 0x1FDB, 0x1FDC, 0x1FDD, 0x1FDE, 0x1FDF,
921 0x03A5, 0x03A5, 0x03A5, 0x1FE3, 0x03A1, 0x03A1, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x03A5, 0x1FEB, 0x03A1, 0x1FED, 0x1FEE, 0x1FEF,
922 0x1FF0, 0x1FF1, 0x03A9, 0x03A9, 0x03A9, 0x1FF5, 0x03A9, 0x03A9, 0x039F, 0x1FF9, 0x03A9, 0x1FFB, 0x03A9, 0x1FFD, 0x1FFE, 0x1FFF
923};
924
925static const uint16_t plane21[] = {
926 0x2100, 0x2101, 0x2102, 0x2103, 0x2104, 0x2105, 0x2106, 0x2107, 0x2108, 0x2109, 0x210A, 0x210B, 0x210C, 0x210D, 0x210E, 0x210F,
927 0x2110, 0x2111, 0x2112, 0x2113, 0x2114, 0x2115, 0x2116, 0x2117, 0x2118, 0x2119, 0x211A, 0x211B, 0x211C, 0x211D, 0x211E, 0x211F,
928 0x2120, 0x2121, 0x2122, 0x2123, 0x2124, 0x2125, 0x2126, 0x2127, 0x2128, 0x2129, 0x212A, 0x212B, 0x212C, 0x212D, 0x212E, 0x212F,
929 0x2130, 0x2131, 0x2132, 0x2133, 0x2134, 0x2135, 0x2136, 0x2137, 0x2138, 0x2139, 0x213A, 0x213B, 0x213C, 0x213D, 0x213E, 0x213F,
930 0x2140, 0x2141, 0x2142, 0x2143, 0x2144, 0x2145, 0x2146, 0x2147, 0x2148, 0x2149, 0x214A, 0x214B, 0x214C, 0x214D, 0x214E, 0x214F,
931 0x2150, 0x2151, 0x2152, 0x2153, 0x2154, 0x2155, 0x2156, 0x2157, 0x2158, 0x2159, 0x215A, 0x215B, 0x215C, 0x215D, 0x215E, 0x215F,
932 0x2160, 0x2161, 0x2162, 0x2163, 0x2164, 0x2165, 0x2166, 0x2167, 0x2168, 0x2169, 0x216A, 0x216B, 0x216C, 0x216D, 0x216E, 0x216F,
933 0x2160, 0x2161, 0x2162, 0x2163, 0x2164, 0x2165, 0x2166, 0x2167, 0x2168, 0x2169, 0x216A, 0x216B, 0x216C, 0x216D, 0x216E, 0x216F,
934 0x2180, 0x2181, 0x2182, 0x2183, 0x2184, 0x2185, 0x2186, 0x2187, 0x2188, 0x2189, 0x218A, 0x218B, 0x218C, 0x218D, 0x218E, 0x218F,
935 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199, 0x219A, 0x219B, 0x219C, 0x219D, 0x219E, 0x219F,
936 0x21A0, 0x21A1, 0x21A2, 0x21A3, 0x21A4, 0x21A5, 0x21A6, 0x21A7, 0x21A8, 0x21A9, 0x21AA, 0x21AB, 0x21AC, 0x21AD, 0x21AE, 0x21AF,
937 0x21B0, 0x21B1, 0x21B2, 0x21B3, 0x21B4, 0x21B5, 0x21B6, 0x21B7, 0x21B8, 0x21B9, 0x21BA, 0x21BB, 0x21BC, 0x21BD, 0x21BE, 0x21BF,
938 0x21C0, 0x21C1, 0x21C2, 0x21C3, 0x21C4, 0x21C5, 0x21C6, 0x21C7, 0x21C8, 0x21C9, 0x21CA, 0x21CB, 0x21CC, 0x21CD, 0x21CE, 0x21CF,
939 0x21D0, 0x21D1, 0x21D2, 0x21D3, 0x21D4, 0x21D5, 0x21D6, 0x21D7, 0x21D8, 0x21D9, 0x21DA, 0x21DB, 0x21DC, 0x21DD, 0x21DE, 0x21DF,
940 0x21E0, 0x21E1, 0x21E2, 0x21E3, 0x21E4, 0x21E5, 0x21E6, 0x21E7, 0x21E8, 0x21E9, 0x21EA, 0x21EB, 0x21EC, 0x21ED, 0x21EE, 0x21EF,
941 0x21F0, 0x21F1, 0x21F2, 0x21F3, 0x21F4, 0x21F5, 0x21F6, 0x21F7, 0x21F8, 0x21F9, 0x21FA, 0x21FB, 0x21FC, 0x21FD, 0x21FE, 0x21FF
942};
943
944static const uint16_t plane24[] = {
945 0x2400, 0x2401, 0x2402, 0x2403, 0x2404, 0x2405, 0x2406, 0x2407, 0x2408, 0x2409, 0x240A, 0x240B, 0x240C, 0x240D, 0x240E, 0x240F,
946 0x2410, 0x2411, 0x2412, 0x2413, 0x2414, 0x2415, 0x2416, 0x2417, 0x2418, 0x2419, 0x241A, 0x241B, 0x241C, 0x241D, 0x241E, 0x241F,
947 0x2420, 0x2421, 0x2422, 0x2423, 0x2424, 0x2425, 0x2426, 0x2427, 0x2428, 0x2429, 0x242A, 0x242B, 0x242C, 0x242D, 0x242E, 0x242F,
948 0x2430, 0x2431, 0x2432, 0x2433, 0x2434, 0x2435, 0x2436, 0x2437, 0x2438, 0x2439, 0x243A, 0x243B, 0x243C, 0x243D, 0x243E, 0x243F,
949 0x2440, 0x2441, 0x2442, 0x2443, 0x2444, 0x2445, 0x2446, 0x2447, 0x2448, 0x2449, 0x244A, 0x244B, 0x244C, 0x244D, 0x244E, 0x244F,
950 0x2450, 0x2451, 0x2452, 0x2453, 0x2454, 0x2455, 0x2456, 0x2457, 0x2458, 0x2459, 0x245A, 0x245B, 0x245C, 0x245D, 0x245E, 0x245F,
951 0x2460, 0x2461, 0x2462, 0x2463, 0x2464, 0x2465, 0x2466, 0x2467, 0x2468, 0x2469, 0x246A, 0x246B, 0x246C, 0x246D, 0x246E, 0x246F,
952 0x2470, 0x2471, 0x2472, 0x2473, 0x2474, 0x2475, 0x2476, 0x2477, 0x2478, 0x2479, 0x247A, 0x247B, 0x247C, 0x247D, 0x247E, 0x247F,
953 0x2480, 0x2481, 0x2482, 0x2483, 0x2484, 0x2485, 0x2486, 0x2487, 0x2488, 0x2489, 0x248A, 0x248B, 0x248C, 0x248D, 0x248E, 0x248F,
954 0x2490, 0x2491, 0x2492, 0x2493, 0x2494, 0x2495, 0x2496, 0x2497, 0x2498, 0x2499, 0x249A, 0x249B, 0x249C, 0x249D, 0x249E, 0x249F,
955 0x24A0, 0x24A1, 0x24A2, 0x24A3, 0x24A4, 0x24A5, 0x24A6, 0x24A7, 0x24A8, 0x24A9, 0x24AA, 0x24AB, 0x24AC, 0x24AD, 0x24AE, 0x24AF,
956 0x24B0, 0x24B1, 0x24B2, 0x24B3, 0x24B4, 0x24B5, 0x24B6, 0x24B7, 0x24B8, 0x24B9, 0x24BA, 0x24BB, 0x24BC, 0x24BD, 0x24BE, 0x24BF,
957 0x24C0, 0x24C1, 0x24C2, 0x24C3, 0x24C4, 0x24C5, 0x24C6, 0x24C7, 0x24C8, 0x24C9, 0x24CA, 0x24CB, 0x24CC, 0x24CD, 0x24CE, 0x24CF,
958 0x24B6, 0x24B7, 0x24B8, 0x24B9, 0x24BA, 0x24BB, 0x24BC, 0x24BD, 0x24BE, 0x24BF, 0x24C0, 0x24C1, 0x24C2, 0x24C3, 0x24C4, 0x24C5,
959 0x24C6, 0x24C7, 0x24C8, 0x24C9, 0x24CA, 0x24CB, 0x24CC, 0x24CD, 0x24CE, 0x24CF, 0x24EA, 0x24EB, 0x24EC, 0x24ED, 0x24EE, 0x24EF,
960 0x24F0, 0x24F1, 0x24F2, 0x24F3, 0x24F4, 0x24F5, 0x24F6, 0x24F7, 0x24F8, 0x24F9, 0x24FA, 0x24FB, 0x24FC, 0x24FD, 0x24FE, 0x24FF
961};
962
963static const uint16_t planeFF[] = {
964 0xFF00, 0xFF01, 0xFF02, 0xFF03, 0xFF04, 0xFF05, 0xFF06, 0xFF07, 0xFF08, 0xFF09, 0xFF0A, 0xFF0B, 0xFF0C, 0xFF0D, 0xFF0E, 0xFF0F,
965 0xFF10, 0xFF11, 0xFF12, 0xFF13, 0xFF14, 0xFF15, 0xFF16, 0xFF17, 0xFF18, 0xFF19, 0xFF1A, 0xFF1B, 0xFF1C, 0xFF1D, 0xFF1E, 0xFF1F,
966 0xFF20, 0xFF21, 0xFF22, 0xFF23, 0xFF24, 0xFF25, 0xFF26, 0xFF27, 0xFF28, 0xFF29, 0xFF2A, 0xFF2B, 0xFF2C, 0xFF2D, 0xFF2E, 0xFF2F,
967 0xFF30, 0xFF31, 0xFF32, 0xFF33, 0xFF34, 0xFF35, 0xFF36, 0xFF37, 0xFF38, 0xFF39, 0xFF3A, 0xFF3B, 0xFF3C, 0xFF3D, 0xFF3E, 0xFF3F,
968 0xFF40, 0xFF21, 0xFF22, 0xFF23, 0xFF24, 0xFF25, 0xFF26, 0xFF27, 0xFF28, 0xFF29, 0xFF2A, 0xFF2B, 0xFF2C, 0xFF2D, 0xFF2E, 0xFF2F,
969 0xFF30, 0xFF31, 0xFF32, 0xFF33, 0xFF34, 0xFF35, 0xFF36, 0xFF37, 0xFF38, 0xFF39, 0xFF3A, 0xFF5B, 0xFF5C, 0xFF5D, 0xFF5E, 0xFF5F,
970 0xFF60, 0xFF61, 0xFF62, 0xFF63, 0xFF64, 0xFF65, 0xFF66, 0xFF67, 0xFF68, 0xFF69, 0xFF6A, 0xFF6B, 0xFF6C, 0xFF6D, 0xFF6E, 0xFF6F,
971 0xFF70, 0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF76, 0xFF77, 0xFF78, 0xFF79, 0xFF7A, 0xFF7B, 0xFF7C, 0xFF7D, 0xFF7E, 0xFF7F,
972 0xFF80, 0xFF81, 0xFF82, 0xFF83, 0xFF84, 0xFF85, 0xFF86, 0xFF87, 0xFF88, 0xFF89, 0xFF8A, 0xFF8B, 0xFF8C, 0xFF8D, 0xFF8E, 0xFF8F,
973 0xFF90, 0xFF91, 0xFF92, 0xFF93, 0xFF94, 0xFF95, 0xFF96, 0xFF97, 0xFF98, 0xFF99, 0xFF9A, 0xFF9B, 0xFF9C, 0xFF9D, 0xFF9E, 0xFF9F,
974 0xFFA0, 0xFFA1, 0xFFA2, 0xFFA3, 0xFFA4, 0xFFA5, 0xFFA6, 0xFFA7, 0xFFA8, 0xFFA9, 0xFFAA, 0xFFAB, 0xFFAC, 0xFFAD, 0xFFAE, 0xFFAF,
975 0xFFB0, 0xFFB1, 0xFFB2, 0xFFB3, 0xFFB4, 0xFFB5, 0xFFB6, 0xFFB7, 0xFFB8, 0xFFB9, 0xFFBA, 0xFFBB, 0xFFBC, 0xFFBD, 0xFFBE, 0xFFBF,
976 0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC4, 0xFFC5, 0xFFC6, 0xFFC7, 0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF,
977 0xFFD0, 0xFFD1, 0xFFD2, 0xFFD3, 0xFFD4, 0xFFD5, 0xFFD6, 0xFFD7, 0xFFD8, 0xFFD9, 0xFFDA, 0xFFDB, 0xFFDC, 0xFFDD, 0xFFDE, 0xFFDF,
978 0xFFE0, 0xFFE1, 0xFFE2, 0xFFE3, 0xFFE4, 0xFFE5, 0xFFE6, 0xFFE7, 0xFFE8, 0xFFE9, 0xFFEA, 0xFFEB, 0xFFEC, 0xFFED, 0xFFEE, 0xFFEF,
979 0xFFF0, 0xFFF1, 0xFFF2, 0xFFF3, 0xFFF4, 0xFFF5, 0xFFF6, 0xFFF7, 0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF
980};
981
982static const uint16_t* const planemap[256] = {
983 plane00, plane01, plane02, plane03, plane04, plane05, NULL, NULL, NULL, NULL, NULL,
984 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
985 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, plane1E, plane1F, NULL,
986 plane21, NULL, NULL, plane24, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
987 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
988 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
989 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
990 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
991 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
992 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
993 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
994 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
995 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
996 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
997 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
998 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
999 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1000 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1001 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1002 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1003 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1004 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1005 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1006 NULL, NULL, planeFF
1007};
1008// clang-format on
1009
1010static wchar_t GetCollationWeight(const wchar_t& r)
1011{
1012 // Lookup the "weight" of a UTF8 char, equivalent lowercase ascii letter, in the plane map,
1013 // the character comparison value used by using "accent folding" collation utf8_general_ci
1014 // in MySQL (AKA utf8mb3_general_ci in MariaDB 10)
1015 auto index = r >> 8;
1016 if (index > 255)
1017 return 0xFFFD;
1018 auto plane = planemap[index];
1019 if (plane == nullptr)
1020 return r;
1021 return static_cast<wchar_t>(plane[r & 0xFF]);
1022}
1023
1024// Compares separately the numeric and alphabetic parts of a wide string.
1025// returns negative if left < right, positive if left > right
1026// and 0 if they are identical.
1027// See also the equivalent StringUtils::AlphaNumericCollation() for UFT8 data
1028int64_t StringUtils::AlphaNumericCompare(const wchar_t* left, const wchar_t* right)
1029{
1030 const wchar_t *l = left;
1031 const wchar_t *r = right;
1032 const wchar_t *ld, *rd;
1033 wchar_t lc, rc;
1034 int64_t lnum, rnum;
1035 bool lsym, rsym;
1036 while (*l != 0 && *r != 0)
1037 {
1038 // check if we have a numerical value
1039 if (*l >= L'0' && *l <= L'9' && *r >= L'0' && *r <= L'9')
1040 {
1041 ld = l;
1042 lnum = *ld++ - L'0';
1043 while (*ld >= L'0' && *ld <= L'9' && ld < l + 15)
1044 { // compare only up to 15 digits
1045 lnum *= 10;
1046 lnum += *ld++ - L'0';
1047 }
1048 rd = r;
1049 rnum = *rd++ - L'0';
1050 while (*rd >= L'0' && *rd <= L'9' && rd < r + 15)
1051 { // compare only up to 15 digits
1052 rnum *= 10;
1053 rnum += *rd++ - L'0';
1054 }
1055 // do we have numbers?
1056 if (lnum != rnum)
1057 { // yes - and they're different!
1058 return lnum - rnum;
1059 }
1060 l = ld;
1061 r = rd;
1062 continue;
1063 }
1064
1065 lc = *l;
1066 rc = *r;
1067 // Put ascii punctuation and symbols e.g. !#$&()*+,-./:;<=>?@[\]^_ `{|}~ above the other
1068 // alphanumeric ascii, rather than some being mixed between the numbers and letters, and
1069 // above all other unicode letters, symbols and punctuation.
1070 // (Locale collation of these chars varies across platforms)
1071 lsym = (lc >= 32 && lc < L'0') || (lc > L'9' && lc < L'A') ||
1072 (lc > L'Z' && lc < L'a') || (lc > L'z' && lc < 128);
1073 rsym = (rc >= 32 && rc < L'0') || (rc > L'9' && rc < L'A') ||
1074 (rc > L'Z' && rc < L'a') || (rc > L'z' && rc < 128);
1075 if (lsym && !rsym)
1076 return -1;
1077 if (!lsym && rsym)
1078 return 1;
1079 if (lsym && rsym)
1080 {
1081 if (lc != rc)
1082 return lc - rc;
1083 else
1084 { // Same symbol advance to next wchar
1085 l++;
1086 r++;
1087 continue;
1088 }
1089 }
1090 if (!g_langInfo.UseLocaleCollation())
1091 {
1092 // Apply case sensitive accent folding collation to non-ascii chars.
1093 // This mimics utf8_general_ci collation, and provides simple collation of LATIN-1 chars
1094 // for any platformthat doesn't have a language specific collate facet implemented
1095 if (lc > 128)
1096 lc = GetCollationWeight(lc);
1097 if (rc > 128)
1098 rc = GetCollationWeight(rc);
1099 }
1100 // Do case less comparison, convert ascii upper case to lower case
1101 if (lc >= L'A' && lc <= L'Z')
1102 lc += L'a' - L'A';
1103 if (rc >= L'A' && rc <= L'Z')
1104 rc += L'a' - L'A';
1105
1106 if (lc != rc)
1107 {
1108 if (!g_langInfo.UseLocaleCollation())
1109 {
1110 // Compare unicode (having applied accent folding collation to non-ascii chars).
1111 int i = wcsncmp(&lc, &rc, 1);
1112 return i;
1113 }
1114 else
1115 {
1116 // Fetch collation facet from locale to do comparison of wide char although on some
1117 // platforms this is not langauge specific but just compares unicode
1118 const std::collate<wchar_t>& coll =
1119 std::use_facet<std::collate<wchar_t>>(g_langInfo.GetSystemLocale());
1120 int cmp_res = coll.compare(&lc, &lc + 1, &rc, &rc + 1);
1121 if (cmp_res != 0)
1122 return cmp_res;
1123 }
1124 }
1125 l++; r++;
1126 }
1127 if (*r)
1128 { // r is longer
1129 return -1;
1130 }
1131 else if (*l)
1132 { // l is longer
1133 return 1;
1134 }
1135 return 0; // files are the same
1136}
1137
1138/*
1139 Convert the UTF8 character to which z points into a 31-bit Unicode point.
1140 Return how many bytes (0 to 3) of UTF8 data encode the character.
1141 This only works right if z points to a well-formed UTF8 string.
1142 Byte-0 Byte-1 Byte-2 Byte-3 Value
1143 0xxxxxxx 00000000 00000000 0xxxxxxx
1144 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx
1145 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx
1146 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx
1147*/
1148static uint32_t UTF8ToUnicode(const unsigned char* z, int nKey, unsigned char& bytes)
1149{
1150 // Lookup table used decode the first byte of a multi-byte UTF8 character
1151 // clang-format off
1152 static const unsigned char utf8Trans1[] = {
1153 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1154 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
1155 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
1156 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
1157 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1158 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
1159 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1160 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
1161 };
1162 // clang-format on
1163
1164 uint32_t c;
1165 bytes = 0;
1166 c = z[0];
1167 if (c >= 0xc0)
1168 {
1169 c = utf8Trans1[c - 0xc0];
1170 int index = 1;
1171 while (index < nKey && (z[index] & 0xc0) == 0x80)
1172 {
1173 c = (c << 6) + (0x3f & z[index]);
1174 index++;
1175 }
1176 if (c < 0x80 || (c & 0xFFFFF800) == 0xD800 || (c & 0xFFFFFFFE) == 0xFFFE)
1177 c = 0xFFFD;
1178 bytes = static_cast<unsigned char>(index - 1);
1179 }
1180 return c;
1181}
1182
1183/*
1184 SQLite collating function, see sqlite3_create_collation
1185 The equivalent of AlphaNumericCompare() but for comparing UTF8 encoded data
1186
1187 This only processes enough data to find a difference, and avoids expensive data conversions.
1188 When sorting in memory item data is converted once to wstring in advance prior to sorting, the
1189 SQLite callback function can not do that kind of preparation. Instead, in order to use
1190 AlphaNumericCompare(), it would have to repeatedly convert the full input data to wstring for
1191 every pair comparison made. That approach was found to be 10 times slower than using this
1192 separate routine.
1193*/
1194int StringUtils::AlphaNumericCollation(int nKey1, const void* pKey1, int nKey2, const void* pKey2)
1195{
1196 // Get exact matches of shorter text to start of larger test fast
1197 int n = std::min(nKey1, nKey2);
1198 int r = memcmp(pKey1, pKey2, n);
1199 if (r == 0)
1200 return nKey1 - nKey2;
1201
1202 //Not a binary match, so process character at a time
1203 const unsigned char* zA = static_cast<const unsigned char*>(pKey1);
1204 const unsigned char* zB = static_cast<const unsigned char*>(pKey2);
1205 wchar_t lc, rc;
1206 unsigned char bytes;
1207 int64_t lnum, rnum;
1208 bool lsym, rsym;
1209 int ld, rd;
1210 int i = 0;
1211 int j = 0;
1212 // Looping Unicode point at a time through potentially 1 to 4 multi-byte encoded UTF8 data
1213 while (i < nKey1 && j < nKey2)
1214 {
1215 // Check if we have numerical values, compare only up to 15 digits
1216 if (isdigit(zA[i]) && isdigit(zB[j]))
1217 {
1218 lnum = zA[i] - '0';
1219 ld = i + 1;
1220 while (ld < nKey1 && isdigit(zA[ld]) && ld < i + 15)
1221 {
1222 lnum *= 10;
1223 lnum += zA[ld] - '0';
1224 ld++;
1225 }
1226 rnum = zB[j] - '0';
1227 rd = j + 1;
1228 while (rd < nKey2 && isdigit(zB[rd]) && rd < j + 15)
1229 {
1230 rnum *= 10;
1231 rnum += zB[rd] - '0';
1232 rd++;
1233 }
1234 // do we have numbers?
1235 if (lnum != rnum)
1236 { // yes - and they're different!
1237 return lnum - rnum;
1238 }
1239 // Advance to after digits
1240 i = ld;
1241 j = rd;
1242 continue;
1243 }
1244 // Put ascii punctuation and symbols e.g. !#$&()*+,-./:;<=>?@[\]^_ `{|}~ before the other
1245 // alphanumeric ascii, rather than some being mixed between the numbers and letters, and
1246 // above all other unicode letters, symbols and punctuation.
1247 // (Locale collation of these chars varies across platforms)
1248 lsym = (zA[i] >= 32 && zA[i] < '0') || (zA[i] > '9' && zA[i] < 'A') ||
1249 (zA[i] > 'Z' && zA[i] < 'a') || (zA[i] > 'z' && zA[i] < 128);
1250 rsym = (zB[j] >= 32 && zB[j] < '0') || (zB[j] > '9' && zB[j] < 'A') ||
1251 (zB[j] > 'Z' && zB[j] < 'a') || (zB[j] > 'z' && zB[j] < 128);
1252 if (lsym && !rsym)
1253 return -1;
1254 if (!lsym && rsym)
1255 return 1;
1256 if (lsym && rsym)
1257 {
1258 if (zA[i] != zB[j])
1259 return zA[i] - zB[j];
1260 else
1261 { // Same symbol advance to next
1262 i++;
1263 j++;
1264 continue;
1265 }
1266 }
1267 //Decode single (1 to 4 bytes) UTF8 character to Unicode
1268 lc = UTF8ToUnicode(&zA[i], nKey1 - i, bytes);
1269 i += bytes;
1270 rc = UTF8ToUnicode(&zB[j], nKey2 - j, bytes);
1271 j += bytes;
1272 if (!g_langInfo.UseLocaleCollation())
1273 {
1274 // Apply case sensitive accent folding collation to non-ascii chars.
1275 // This mimics utf8_general_ci collation, and provides simple collation of LATIN-1 chars
1276 // for any platform that doesn't have a language specific collate facet implemented
1277 if (lc > 128)
1278 lc = GetCollationWeight(lc);
1279 if (rc > 128)
1280 rc = GetCollationWeight(rc);
1281 }
1282 // Caseless comparison so convert ascii upper case to lower case
1283 if (lc >= 'A' && lc <= 'Z')
1284 lc += 'a' - 'A';
1285 if (rc >= 'A' && rc <= 'Z')
1286 rc += 'a' - 'A';
1287
1288 if (lc != rc)
1289 {
1290 if (!g_langInfo.UseLocaleCollation() || (lc <= 128 && rc <= 128))
1291 // Compare unicode (having applied accent folding collation to non-ascii chars).
1292 return lc - rc;
1293 else
1294 {
1295 // Fetch collation facet from locale to do comparison of wide char although on some
1296 // platforms this is not langauge specific but just compares unicode
1297 const std::collate<wchar_t>& coll =
1298 std::use_facet<std::collate<wchar_t>>(g_langInfo.GetSystemLocale());
1299 int cmp_res = coll.compare(&lc, &lc + 1, &rc, &rc + 1);
1300 if (cmp_res != 0)
1301 return cmp_res;
1302 }
1303 }
1304 i++;
1305 j++;
1306 }
1307 // Compared characters of shortest are the same as longest, length determines order
1308 return (nKey1 - nKey2);
1309}
1310
1311int StringUtils::DateStringToYYYYMMDD(const std::string &dateString)
1312{
1313 std::vector<std::string> days = StringUtils::Split(dateString, '-');
1314 if (days.size() == 1)
1315 return atoi(days[0].c_str());
1316 else if (days.size() == 2)
1317 return atoi(days[0].c_str())*100+atoi(days[1].c_str());
1318 else if (days.size() == 3)
1319 return atoi(days[0].c_str())*10000+atoi(days[1].c_str())*100+atoi(days[2].c_str());
1320 else
1321 return -1;
1322}
1323
1324std::string StringUtils::ISODateToLocalizedDate(const std::string& strIsoDate)
1325{
1326 // Convert ISO8601 date strings YYYY, YYYY-MM, or YYYY-MM-DD to (partial) localized date strings
1327 CDateTime date;
1328 std::string formattedDate = strIsoDate;
1329 if (formattedDate.size() == 10)
1330 {
1331 date.SetFromDBDate(strIsoDate);
1332 formattedDate = date.GetAsLocalizedDate();
1333 }
1334 else if (formattedDate.size() == 7)
1335 {
1336 std::string strFormat = date.GetAsLocalizedDate(false);
1337 std::string tempdate;
1338 // find which date separator we are using. Can be -./
1339 size_t pos = strFormat.find_first_of("-./");
1340 if (pos != std::string::npos)
1341 {
1342 bool yearFirst = strFormat.find("1601") == 0; // true if year comes first
1343 std::string sep = strFormat.substr(pos, 1);
1344 if (yearFirst)
1345 { // build formatted date with year first, then separator and month
1346 tempdate = formattedDate.substr(0, 4);
1347 tempdate += sep;
1348 tempdate += formattedDate.substr(5, 2);
1349 }
1350 else
1351 {
1352 tempdate = formattedDate.substr(5, 2);
1353 tempdate += sep;
1354 tempdate += formattedDate.substr(0, 4);
1355 }
1356 formattedDate = tempdate;
1357 }
1358 // return either just the year or the locally formatted version of the ISO date
1359 }
1360 return formattedDate;
1361}
1362
1363long StringUtils::TimeStringToSeconds(const std::string &timeString)
1364{
1365 std::string strCopy(timeString);
1366 StringUtils::Trim(strCopy);
1367 if(StringUtils::EndsWithNoCase(strCopy, " min"))
1368 {
1369 // this is imdb format of "XXX min"
1370 return 60 * atoi(strCopy.c_str());
1371 }
1372 else
1373 {
1374 std::vector<std::string> secs = StringUtils::Split(strCopy, ':');
1375 int timeInSecs = 0;
1376 for (unsigned int i = 0; i < 3 && i < secs.size(); i++)
1377 {
1378 timeInSecs *= 60;
1379 timeInSecs += atoi(secs[i].c_str());
1380 }
1381 return timeInSecs;
1382 }
1383}
1384
1385std::string StringUtils::SecondsToTimeString(long lSeconds, TIME_FORMAT format)
1386{
1387 bool isNegative = lSeconds < 0;
1388 lSeconds = std::abs(lSeconds);
1389
1390 std::string strHMS;
1391 if (format == TIME_FORMAT_SECS)
1392 strHMS = StringUtils::Format("%i", lSeconds);
1393 else if (format == TIME_FORMAT_MINS)
1394 strHMS = StringUtils::Format("%i", lrintf(static_cast<float>(lSeconds) / 60.0f));
1395 else if (format == TIME_FORMAT_HOURS)
1396 strHMS = StringUtils::Format("%i", lrintf(static_cast<float>(lSeconds) / 3600.0f));
1397 else if (format & TIME_FORMAT_M)
1398 strHMS += StringUtils::Format("%i", lSeconds % 3600 / 60);
1399 else
1400 {
1401 int hh = lSeconds / 3600;
1402 lSeconds = lSeconds % 3600;
1403 int mm = lSeconds / 60;
1404 int ss = lSeconds % 60;
1405
1406 if (format == TIME_FORMAT_GUESS)
1407 format = (hh >= 1) ? TIME_FORMAT_HH_MM_SS : TIME_FORMAT_MM_SS;
1408 if (format & TIME_FORMAT_HH)
1409 strHMS += StringUtils::Format("%2.2i", hh);
1410 else if (format & TIME_FORMAT_H)
1411 strHMS += StringUtils::Format("%i", hh);
1412 if (format & TIME_FORMAT_MM)
1413 strHMS += StringUtils::Format(strHMS.empty() ? "%2.2i" : ":%2.2i", mm);
1414 if (format & TIME_FORMAT_SS)
1415 strHMS += StringUtils::Format(strHMS.empty() ? "%2.2i" : ":%2.2i", ss);
1416 }
1417
1418 if (isNegative)
1419 strHMS = "-" + strHMS;
1420
1421 return strHMS;
1422}
1423
1424bool StringUtils::IsNaturalNumber(const std::string& str)
1425{
1426 size_t i = 0, n = 0;
1427 // allow whitespace,digits,whitespace
1428 while (i < str.size() && isspace((unsigned char) str[i]))
1429 i++;
1430 while (i < str.size() && isdigit((unsigned char) str[i]))
1431 {
1432 i++; n++;
1433 }
1434 while (i < str.size() && isspace((unsigned char) str[i]))
1435 i++;
1436 return i == str.size() && n > 0;
1437}
1438
1439bool StringUtils::IsInteger(const std::string& str)
1440{
1441 size_t i = 0, n = 0;
1442 // allow whitespace,-,digits,whitespace
1443 while (i < str.size() && isspace((unsigned char) str[i]))
1444 i++;
1445 if (i < str.size() && str[i] == '-')
1446 i++;
1447 while (i < str.size() && isdigit((unsigned char) str[i]))
1448 {
1449 i++; n++;
1450 }
1451 while (i < str.size() && isspace((unsigned char) str[i]))
1452 i++;
1453 return i == str.size() && n > 0;
1454}
1455
1456int StringUtils::asciidigitvalue(char chr)
1457{
1458 if (!isasciidigit(chr))
1459 return -1;
1460
1461 return chr - '0';
1462}
1463
1464int StringUtils::asciixdigitvalue(char chr)
1465{
1466 int v = asciidigitvalue(chr);
1467 if (v >= 0)
1468 return v;
1469 if (chr >= 'a' && chr <= 'f')
1470 return chr - 'a' + 10;
1471 if (chr >= 'A' && chr <= 'F')
1472 return chr - 'A' + 10;
1473
1474 return -1;
1475}
1476
1477
1478void StringUtils::RemoveCRLF(std::string& strLine)
1479{
1480 StringUtils::TrimRight(strLine, "\n\r");
1481}
1482
1483std::string StringUtils::SizeToString(int64_t size)
1484{
1485 std::string strLabel;
1486 const char prefixes[] = {' ', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'};
1487 unsigned int i = 0;
1488 double s = (double)size;
1489 while (i < ARRAY_SIZE(prefixes) && s >= 1000.0)
1490 {
1491 s /= 1024.0;
1492 i++;
1493 }
1494
1495 if (!i)
1496 strLabel = StringUtils::Format("%.lf B", s);
1497 else if (i == ARRAY_SIZE(prefixes))
1498 {
1499 if (s >= 1000.0)
1500 strLabel = StringUtils::Format(">999.99 %cB", prefixes[i - 1]);
1501 else
1502 strLabel = StringUtils::Format("%.2lf %cB", s, prefixes[i - 1]);
1503 }
1504 else if (s >= 100.0)
1505 strLabel = StringUtils::Format("%.1lf %cB", s, prefixes[i]);
1506 else
1507 strLabel = StringUtils::Format("%.2lf %cB", s, prefixes[i]);
1508
1509 return strLabel;
1510}
1511
1512std::string StringUtils::BinaryStringToString(const std::string& in)
1513{
1514 std::string out;
1515 out.reserve(in.size() / 2);
1516 for (const char *cur = in.c_str(), *end = cur + in.size(); cur != end; ++cur) {
1517 if (*cur == '\\') {
1518 ++cur;
1519 if (cur == end) {
1520 break;
1521 }
1522 if (isdigit(*cur)) {
1523 char* end;
1524 unsigned long num = strtol(cur, &end, 10);
1525 cur = end - 1;
1526 out.push_back(num);
1527 continue;
1528 }
1529 }
1530 out.push_back(*cur);
1531 }
1532 return out;
1533}
1534
1535std::string StringUtils::ToHexadecimal(const std::string& in)
1536{
1537 std::ostringstream ss;
1538 ss << std::hex;
1539 for (unsigned char ch : in) {
1540 ss << std::setw(2) << std::setfill('0') << static_cast<unsigned long> (ch);
1541 }
1542 return ss.str();
1543}
1544
1545// return -1 if not, else return the utf8 char length.
1546int IsUTF8Letter(const unsigned char *str)
1547{
1548 // reference:
1549 // unicode -> utf8 table: http://www.utf8-chartable.de/
1550 // latin characters in unicode: http://en.wikipedia.org/wiki/Latin_characters_in_Unicode
1551 unsigned char ch = str[0];
1552 if (!ch)
1553 return -1;
1554 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
1555 return 1;
1556 if (!(ch & 0x80))
1557 return -1;
1558 unsigned char ch2 = str[1];
1559 if (!ch2)
1560 return -1;
1561 // check latin 1 letter table: http://en.wikipedia.org/wiki/C1_Controls_and_Latin-1_Supplement
1562 if (ch == 0xC3 && ch2 >= 0x80 && ch2 <= 0xBF && ch2 != 0x97 && ch2 != 0xB7)
1563 return 2;
1564 // check latin extended A table: http://en.wikipedia.org/wiki/Latin_Extended-A
1565 if (ch >= 0xC4 && ch <= 0xC7 && ch2 >= 0x80 && ch2 <= 0xBF)
1566 return 2;
1567 // check latin extended B table: http://en.wikipedia.org/wiki/Latin_Extended-B
1568 // and International Phonetic Alphabet: http://en.wikipedia.org/wiki/IPA_Extensions_(Unicode_block)
1569 if (((ch == 0xC8 || ch == 0xC9) && ch2 >= 0x80 && ch2 <= 0xBF)
1570 || (ch == 0xCA && ch2 >= 0x80 && ch2 <= 0xAF))
1571 return 2;
1572 return -1;
1573}
1574
1575size_t StringUtils::FindWords(const char *str, const char *wordLowerCase)
1576{
1577 // NOTE: This assumes word is lowercase!
1578 const unsigned char *s = (const unsigned char *)str;
1579 do
1580 {
1581 // start with a compare
1582 const unsigned char *c = s;
1583 const unsigned char *w = (const unsigned char *)wordLowerCase;
1584 bool same = true;
1585 while (same && *c && *w)
1586 {
1587 unsigned char lc = *c++;
1588 if (lc >= 'A' && lc <= 'Z')
1589 lc += 'a'-'A';
1590
1591 if (lc != *w++) // different
1592 same = false;
1593 }
1594 if (same && *w == 0) // only the same if word has been exhausted
1595 return (const char *)s - str;
1596
1597 // otherwise, skip current word (composed by latin letters) or number
1598 int l;
1599 if (*s >= '0' && *s <= '9')
1600 {
1601 ++s;
1602 while (*s >= '0' && *s <= '9') ++s;
1603 }
1604 else if ((l = IsUTF8Letter(s)) > 0)
1605 {
1606 s += l;
1607 while ((l = IsUTF8Letter(s)) > 0) s += l;
1608 }
1609 else
1610 ++s;
1611 while (*s && *s == ' ') s++;
1612
1613 // and repeat until we're done
1614 } while (*s);
1615
1616 return std::string::npos;
1617}
1618
1619// assumes it is called from after the first open bracket is found
1620int StringUtils::FindEndBracket(const std::string &str, char opener, char closer, int startPos)
1621{
1622 int blocks = 1;
1623 for (unsigned int i = startPos; i < str.size(); i++)
1624 {
1625 if (str[i] == opener)
1626 blocks++;
1627 else if (str[i] == closer)
1628 {
1629 blocks--;
1630 if (!blocks)
1631 return i;
1632 }
1633 }
1634
1635 return (int)std::string::npos;
1636}
1637
1638void StringUtils::WordToDigits(std::string &word)
1639{
1640 static const char word_to_letter[] = "22233344455566677778889999";
1641 StringUtils::ToLower(word);
1642 for (unsigned int i = 0; i < word.size(); ++i)
1643 { // NB: This assumes ascii, which probably needs extending at some point.
1644 char letter = word[i];
1645 if ((letter >= 'a' && letter <= 'z')) // assume contiguous letter range
1646 {
1647 word[i] = word_to_letter[letter-'a'];
1648 }
1649 else if (letter < '0' || letter > '9') // We want to keep 0-9!
1650 {
1651 word[i] = ' '; // replace everything else with a space
1652 }
1653 }
1654}
1655
1656std::string StringUtils::CreateUUID()
1657{
1658#ifdef HAVE_NEW_CROSSGUID
1659 return xg::newGuid().str();
1660#else
1661 static GuidGenerator guidGenerator;
1662 auto guid = guidGenerator.newGuid();
1663
1664 std::stringstream strGuid; strGuid << guid;
1665 return strGuid.str();
1666#endif
1667}
1668
1669bool StringUtils::ValidateUUID(const std::string &uuid)
1670{
1671 CRegExp guidRE;
1672 guidRE.RegComp(ADDON_GUID_RE);
1673 return (guidRE.RegFind(uuid.c_str()) == 0);
1674}
1675
1676double StringUtils::CompareFuzzy(const std::string &left, const std::string &right)
1677{
1678 return (0.5 + fstrcmp(left.c_str(), right.c_str()) * (left.length() + right.length())) / 2.0;
1679}
1680
1681int StringUtils::FindBestMatch(const std::string &str, const std::vector<std::string> &strings, double &matchscore)
1682{
1683 int best = -1;
1684 matchscore = 0;
1685
1686 int i = 0;
1687 for (std::vector<std::string>::const_iterator it = strings.begin(); it != strings.end(); ++it, i++)
1688 {
1689 int maxlength = std::max(str.length(), it->length());
1690 double score = StringUtils::CompareFuzzy(str, *it) / maxlength;
1691 if (score > matchscore)
1692 {
1693 matchscore = score;
1694 best = i;
1695 }
1696 }
1697 return best;
1698}
1699
1700bool StringUtils::ContainsKeyword(const std::string &str, const std::vector<std::string> &keywords)
1701{
1702 for (std::vector<std::string>::const_iterator it = keywords.begin(); it != keywords.end(); ++it)
1703 {
1704 if (str.find(*it) != str.npos)
1705 return true;
1706 }
1707 return false;
1708}
1709
1710size_t StringUtils::utf8_strlen(const char *s)
1711{
1712 size_t length = 0;
1713 while (*s)
1714 {
1715 if ((*s++ & 0xC0) != 0x80)
1716 length++;
1717 }
1718 return length;
1719}
1720
1721std::string StringUtils::Paramify(const std::string &param)
1722{
1723 std::string result = param;
1724 // escape backspaces
1725 StringUtils::Replace(result, "\\", "\\\\");
1726 // escape double quotes
1727 StringUtils::Replace(result, "\"", "\\\"");
1728
1729 // add double quotes around the whole string
1730 return "\"" + result + "\"";
1731}
1732
1733std::vector<std::string> StringUtils::Tokenize(const std::string &input, const std::string &delimiters)
1734{
1735 std::vector<std::string> tokens;
1736 Tokenize(input, tokens, delimiters);
1737 return tokens;
1738}
1739
1740void StringUtils::Tokenize(const std::string& input, std::vector<std::string>& tokens, const std::string& delimiters)
1741{
1742 tokens.clear();
1743 // Skip delimiters at beginning.
1744 std::string::size_type dataPos = input.find_first_not_of(delimiters);
1745 while (dataPos != std::string::npos)
1746 {
1747 // Find next delimiter
1748 const std::string::size_type nextDelimPos = input.find_first_of(delimiters, dataPos);
1749 // Found a token, add it to the vector.
1750 tokens.push_back(input.substr(dataPos, nextDelimPos - dataPos));
1751 // Skip delimiters. Note the "not_of"
1752 dataPos = input.find_first_not_of(delimiters, nextDelimPos);
1753 }
1754}
1755
1756std::vector<std::string> StringUtils::Tokenize(const std::string &input, const char delimiter)
1757{
1758 std::vector<std::string> tokens;
1759 Tokenize(input, tokens, delimiter);
1760 return tokens;
1761}
1762
1763void StringUtils::Tokenize(const std::string& input, std::vector<std::string>& tokens, const char delimiter)
1764{
1765 tokens.clear();
1766 // Skip delimiters at beginning.
1767 std::string::size_type dataPos = input.find_first_not_of(delimiter);
1768 while (dataPos != std::string::npos)
1769 {
1770 // Find next delimiter
1771 const std::string::size_type nextDelimPos = input.find(delimiter, dataPos);
1772 // Found a token, add it to the vector.
1773 tokens.push_back(input.substr(dataPos, nextDelimPos - dataPos));
1774 // Skip delimiters. Note the "not_of"
1775 dataPos = input.find_first_not_of(delimiter, nextDelimPos);
1776 }
1777}
1778
1779uint64_t StringUtils::ToUint64(std::string str, uint64_t fallback) noexcept
1780{
1781 std::istringstream iss(str);
1782 uint64_t result(fallback);
1783 iss >> result;
1784 return result;
1785}
1786
1787std::string StringUtils::FormatFileSize(uint64_t bytes)
1788{
1789 const std::array<std::string, 6> units{{"B", "kB", "MB", "GB", "TB", "PB"}};
1790 if (bytes < 1000)
1791 return Format("%" PRIu64 "B", bytes);
1792
1793 size_t i = 0;
1794 double value = static_cast<double>(bytes);
1795 while (i + 1 < units.size() && value >= 999.5)
1796 {
1797 ++i;
1798 value /= 1024.0;
1799 }
1800 unsigned int decimals = value < 9.995 ? 2 : (value < 99.95 ? 1 : 0);
1801 auto frmt = "%." + Format("%u", decimals) + "f%s";
1802 return Format(frmt.c_str(), value, units[i].c_str());
1803}
1804
1805const std::locale& StringUtils::GetOriginalLocale() noexcept
1806{
1807 return g_langInfo.GetOriginalLocale();
1808}
diff --git a/xbmc/utils/StringUtils.h b/xbmc/utils/StringUtils.h
new file mode 100644
index 0000000..6aab4cd
--- /dev/null
+++ b/xbmc/utils/StringUtils.h
@@ -0,0 +1,403 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11//-----------------------------------------------------------------------
12//
13// File: StringUtils.h
14//
15// Purpose: ATL split string utility
16// Author: Paul J. Weiss
17//
18// Modified to support J O'Leary's std::string class by kraqh3d
19//
20//------------------------------------------------------------------------
21
22#include <stdarg.h>
23#include <stdint.h>
24#include <string>
25#include <vector>
26#include <sstream>
27#include <locale>
28
29// workaround for broken [[depreciated]] in coverity
30#if defined(__COVERITY__)
31#undef FMT_DEPRECATED
32#define FMT_DEPRECATED
33#endif
34#include <fmt/format.h>
35
36#if FMT_VERSION >= 40000
37#include <fmt/printf.h>
38#endif
39
40#include "XBDateTime.h"
41#include "utils/params_check_macros.h"
42
43/*! \brief C-processor Token stringification
44
45The following macros can be used to stringify definitions to
46C style strings.
47
48Example:
49
50#define foo 4
51DEF_TO_STR_NAME(foo) // outputs "foo"
52DEF_TO_STR_VALUE(foo) // outputs "4"
53
54*/
55
56#define DEF_TO_STR_NAME(x) #x
57#define DEF_TO_STR_VALUE(x) DEF_TO_STR_NAME(x)
58
59template<typename T, std::enable_if_t<!std::is_enum<T>::value, int> = 0>
60constexpr auto&& EnumToInt(T&& arg) noexcept
61{
62 return arg;
63}
64template<typename T, std::enable_if_t<std::is_enum<T>::value, int> = 0>
65constexpr auto EnumToInt(T&& arg) noexcept
66{
67 return static_cast<int>(arg);
68}
69
70class StringUtils
71{
72public:
73 /*! \brief Get a formatted string similar to sprintf
74
75 Beware that this does not support directly passing in
76 std::string objects. You need to call c_str() to pass
77 the const char* buffer representing the value of the
78 std::string object.
79
80 \param fmt Format of the resulting string
81 \param ... variable number of value type arguments
82 \return Formatted string
83 */
84 template<typename... Args>
85 static std::string Format(const std::string& fmt, Args&&... args)
86 {
87 // coverity[fun_call_w_exception : FALSE]
88 auto result = ::fmt::format(fmt, EnumToInt(std::forward<Args>(args))...);
89 if (result == fmt)
90 result = ::fmt::sprintf(fmt, EnumToInt(std::forward<Args>(args))...);
91
92 return result;
93 }
94 template<typename... Args>
95 static std::wstring Format(const std::wstring& fmt, Args&&... args)
96 {
97 // coverity[fun_call_w_exception : FALSE]
98 auto result = ::fmt::format(fmt, EnumToInt(std::forward<Args>(args))...);
99 if (result == fmt)
100 result = ::fmt::sprintf(fmt, EnumToInt(std::forward<Args>(args))...);
101
102 return result;
103 }
104
105 static std::string FormatV(PRINTF_FORMAT_STRING const char *fmt, va_list args);
106 static std::wstring FormatV(PRINTF_FORMAT_STRING const wchar_t *fmt, va_list args);
107 static void ToUpper(std::string &str);
108 static void ToUpper(std::wstring &str);
109 static void ToLower(std::string &str);
110 static void ToLower(std::wstring &str);
111 static void ToCapitalize(std::string &str);
112 static void ToCapitalize(std::wstring &str);
113 static bool EqualsNoCase(const std::string &str1, const std::string &str2);
114 static bool EqualsNoCase(const std::string &str1, const char *s2);
115 static bool EqualsNoCase(const char *s1, const char *s2);
116 static int CompareNoCase(const std::string& str1, const std::string& str2, size_t n = 0);
117 static int CompareNoCase(const char* s1, const char* s2, size_t n = 0);
118 static int ReturnDigits(const std::string &str);
119 static std::string Left(const std::string &str, size_t count);
120 static std::string Mid(const std::string &str, size_t first, size_t count = std::string::npos);
121 static std::string Right(const std::string &str, size_t count);
122 static std::string& Trim(std::string &str);
123 static std::string& Trim(std::string &str, const char* const chars);
124 static std::string& TrimLeft(std::string &str);
125 static std::string& TrimLeft(std::string &str, const char* const chars);
126 static std::string& TrimRight(std::string &str);
127 static std::string& TrimRight(std::string &str, const char* const chars);
128 static std::string& RemoveDuplicatedSpacesAndTabs(std::string& str);
129 static int Replace(std::string &str, char oldChar, char newChar);
130 static int Replace(std::string &str, const std::string &oldStr, const std::string &newStr);
131 static int Replace(std::wstring &str, const std::wstring &oldStr, const std::wstring &newStr);
132 static bool StartsWith(const std::string &str1, const std::string &str2);
133 static bool StartsWith(const std::string &str1, const char *s2);
134 static bool StartsWith(const char *s1, const char *s2);
135 static bool StartsWithNoCase(const std::string &str1, const std::string &str2);
136 static bool StartsWithNoCase(const std::string &str1, const char *s2);
137 static bool StartsWithNoCase(const char *s1, const char *s2);
138 static bool EndsWith(const std::string &str1, const std::string &str2);
139 static bool EndsWith(const std::string &str1, const char *s2);
140 static bool EndsWithNoCase(const std::string &str1, const std::string &str2);
141 static bool EndsWithNoCase(const std::string &str1, const char *s2);
142
143 template<typename CONTAINER>
144 static std::string Join(const CONTAINER &strings, const std::string& delimiter)
145 {
146 std::string result;
147 for (const auto& str : strings)
148 result += str + delimiter;
149
150 if (!result.empty())
151 result.erase(result.size() - delimiter.size());
152 return result;
153 }
154
155 /*! \brief Splits the given input string using the given delimiter into separate strings.
156
157 If the given input string is empty the result will be an empty array (not
158 an array containing an empty string).
159
160 \param input Input string to be split
161 \param delimiter Delimiter to be used to split the input string
162 \param iMaxStrings (optional) Maximum number of splitted strings
163 */
164 static std::vector<std::string> Split(const std::string& input, const std::string& delimiter, unsigned int iMaxStrings = 0);
165 static std::vector<std::string> Split(const std::string& input, const char delimiter, size_t iMaxStrings = 0);
166 static std::vector<std::string> Split(const std::string& input, const std::vector<std::string> &delimiters);
167 /*! \brief Splits the given input string using the given delimiter into separate strings.
168
169 If the given input string is empty nothing will be put into the target iterator.
170
171 \param d_first the beginning of the destination range
172 \param input Input string to be split
173 \param delimiter Delimiter to be used to split the input string
174 \param iMaxStrings (optional) Maximum number of splitted strings
175 \return output iterator to the element in the destination range, one past the last element
176 * that was put there
177 */
178 template<typename OutputIt>
179 static OutputIt SplitTo(OutputIt d_first, const std::string& input, const std::string& delimiter, unsigned int iMaxStrings = 0)
180 {
181 OutputIt dest = d_first;
182
183 if (input.empty())
184 return dest;
185 if (delimiter.empty())
186 {
187 *d_first++ = input;
188 return dest;
189 }
190
191 const size_t delimLen = delimiter.length();
192 size_t nextDelim;
193 size_t textPos = 0;
194 do
195 {
196 if (--iMaxStrings == 0)
197 {
198 *dest++ = input.substr(textPos);
199 break;
200 }
201 nextDelim = input.find(delimiter, textPos);
202 *dest++ = input.substr(textPos, nextDelim - textPos);
203 textPos = nextDelim + delimLen;
204 } while (nextDelim != std::string::npos);
205
206 return dest;
207 }
208 template<typename OutputIt>
209 static OutputIt SplitTo(OutputIt d_first, const std::string& input, const char delimiter, size_t iMaxStrings = 0)
210 {
211 return SplitTo(d_first, input, std::string(1, delimiter), iMaxStrings);
212 }
213 template<typename OutputIt>
214 static OutputIt SplitTo(OutputIt d_first, const std::string& input, const std::vector<std::string> &delimiters)
215 {
216 OutputIt dest = d_first;
217 if (input.empty())
218 return dest;
219
220 if (delimiters.empty())
221 {
222 *dest++ = input;
223 return dest;
224 }
225 std::string str = input;
226 for (size_t di = 1; di < delimiters.size(); di++)
227 StringUtils::Replace(str, delimiters[di], delimiters[0]);
228 return SplitTo(dest, str, delimiters[0]);
229 }
230
231 /*! \brief Splits the given input strings using the given delimiters into further separate strings.
232
233 If the given input string vector is empty the result will be an empty array (not
234 an array containing an empty string).
235
236 Delimiter strings are applied in order, so once the (optional) maximum number of
237 items is produced no other delimiters are applied. This produces different results
238 to applying all delimiters at once e.g. "a/b#c/d" becomes "a", "b#c", "d" rather
239 than "a", "b", "c/d"
240
241 \param input Input vector of strings each to be split
242 \param delimiters Delimiter strings to be used to split the input strings
243 \param iMaxStrings (optional) Maximum number of resulting split strings
244 */
245 static std::vector<std::string> SplitMulti(const std::vector<std::string> &input, const std::vector<std::string> &delimiters, unsigned int iMaxStrings = 0);
246 static int FindNumber(const std::string& strInput, const std::string &strFind);
247 static int64_t AlphaNumericCompare(const wchar_t *left, const wchar_t *right);
248 static int AlphaNumericCollation(int nKey1, const void* pKey1, int nKey2, const void* pKey2);
249 static long TimeStringToSeconds(const std::string &timeString);
250 static void RemoveCRLF(std::string& strLine);
251
252 /*! \brief utf8 version of strlen - skips any non-starting bytes in the count, thus returning the number of utf8 characters
253 \param s c-string to find the length of.
254 \return the number of utf8 characters in the string.
255 */
256 static size_t utf8_strlen(const char *s);
257
258 /*! \brief convert a time in seconds to a string based on the given time format
259 \param seconds time in seconds
260 \param format the format we want the time in.
261 \return the formatted time
262 \sa TIME_FORMAT
263 */
264 static std::string SecondsToTimeString(long seconds, TIME_FORMAT format = TIME_FORMAT_GUESS);
265
266 /*! \brief check whether a string is a natural number.
267 Matches [ \t]*[0-9]+[ \t]*
268 \param str the string to check
269 \return true if the string is a natural number, false otherwise.
270 */
271 static bool IsNaturalNumber(const std::string& str);
272
273 /*! \brief check whether a string is an integer.
274 Matches [ \t]*[\-]*[0-9]+[ \t]*
275 \param str the string to check
276 \return true if the string is an integer, false otherwise.
277 */
278 static bool IsInteger(const std::string& str);
279
280 /* The next several isasciiXX and asciiXXvalue functions are locale independent (US-ASCII only),
281 * as opposed to standard ::isXX (::isalpha, ::isdigit...) which are locale dependent.
282 * Next functions get parameter as char and don't need double cast ((int)(unsigned char) is required for standard functions). */
283 inline static bool isasciidigit(char chr) // locale independent
284 {
285 return chr >= '0' && chr <= '9';
286 }
287 inline static bool isasciixdigit(char chr) // locale independent
288 {
289 return (chr >= '0' && chr <= '9') || (chr >= 'a' && chr <= 'f') || (chr >= 'A' && chr <= 'F');
290 }
291 static int asciidigitvalue(char chr); // locale independent
292 static int asciixdigitvalue(char chr); // locale independent
293 inline static bool isasciiuppercaseletter(char chr) // locale independent
294 {
295 return (chr >= 'A' && chr <= 'Z');
296 }
297 inline static bool isasciilowercaseletter(char chr) // locale independent
298 {
299 return (chr >= 'a' && chr <= 'z');
300 }
301 inline static bool isasciialphanum(char chr) // locale independent
302 {
303 return isasciiuppercaseletter(chr) || isasciilowercaseletter(chr) || isasciidigit(chr);
304 }
305 static std::string SizeToString(int64_t size);
306 static const std::string Empty;
307 static size_t FindWords(const char *str, const char *wordLowerCase);
308 static int FindEndBracket(const std::string &str, char opener, char closer, int startPos = 0);
309 static int DateStringToYYYYMMDD(const std::string &dateString);
310 static std::string ISODateToLocalizedDate (const std::string& strIsoDate);
311 static void WordToDigits(std::string &word);
312 static std::string CreateUUID();
313 static bool ValidateUUID(const std::string &uuid); // NB only validates syntax
314 static double CompareFuzzy(const std::string &left, const std::string &right);
315 static int FindBestMatch(const std::string &str, const std::vector<std::string> &strings, double &matchscore);
316 static bool ContainsKeyword(const std::string &str, const std::vector<std::string> &keywords);
317
318 /*! \brief Convert the string of binary chars to the actual string.
319
320 Convert the string representation of binary chars to the actual string.
321 For example \1\2\3 is converted to a string with binary char \1, \2 and \3
322
323 \param param String to convert
324 \return Converted string
325 */
326 static std::string BinaryStringToString(const std::string& in);
327 /**
328 * Convert each character in the string to its hexadecimal
329 * representation and return the concatenated result
330 *
331 * example: "abc\n" -> "6162630a"
332 */
333 static std::string ToHexadecimal(const std::string& in);
334 /*! \brief Format the string with locale separators.
335
336 Format the string with locale separators.
337 For example 10000.57 in en-us is '10,000.57' but in italian is '10.000,57'
338
339 \param param String to format
340 \return Formatted string
341 */
342 template<typename T>
343 static std::string FormatNumber(T num)
344 {
345 std::stringstream ss;
346// ifdef is needed because when you set _ITERATOR_DEBUG_LEVEL=0 and you use custom numpunct you will get runtime error in debug mode
347// for more info https://connect.microsoft.com/VisualStudio/feedback/details/2655363
348#if !(defined(_DEBUG) && defined(TARGET_WINDOWS))
349 ss.imbue(GetOriginalLocale());
350#endif
351 ss.precision(1);
352 ss << std::fixed << num;
353 return ss.str();
354 }
355
356 /*! \brief Escapes the given string to be able to be used as a parameter.
357
358 Escapes backslashes and double-quotes with an additional backslash and
359 adds double-quotes around the whole string.
360
361 \param param String to escape/paramify
362 \return Escaped/Paramified string
363 */
364 static std::string Paramify(const std::string &param);
365
366 /*! \brief Split a string by the specified delimiters.
367 Splits a string using one or more delimiting characters, ignoring empty tokens.
368 Differs from Split() in two ways:
369 1. The delimiters are treated as individual characters, rather than a single delimiting string.
370 2. Empty tokens are ignored.
371 \return a vector of tokens
372 */
373 static std::vector<std::string> Tokenize(const std::string& input, const std::string& delimiters);
374 static void Tokenize(const std::string& input, std::vector<std::string>& tokens, const std::string& delimiters);
375 static std::vector<std::string> Tokenize(const std::string& input, const char delimiter);
376 static void Tokenize(const std::string& input, std::vector<std::string>& tokens, const char delimiter);
377 static uint64_t ToUint64(std::string str, uint64_t fallback) noexcept;
378
379 /*!
380 * Returns bytes in a human readable format using the smallest unit that will fit `bytes` in at
381 * most three digits. The number of decimals are adjusted with significance such that 'small'
382 * numbers will have more decimals than larger ones.
383 *
384 * For example: 1024 bytes will be formatted as "1.00kB", 10240 bytes as "10.0kB" and
385 * 102400 bytes as "100kB". See TestStringUtils for more examples.
386 */
387 static std::string FormatFileSize(uint64_t bytes);
388
389private:
390 /*!
391 * Wrapper for CLangInfo::GetOriginalLocale() which allows us to
392 * avoid including LangInfo.h from this header.
393 */
394 static const std::locale& GetOriginalLocale() noexcept;
395};
396
397struct sortstringbyname
398{
399 bool operator()(const std::string& strItem1, const std::string& strItem2)
400 {
401 return StringUtils::CompareNoCase(strItem1, strItem2) < 0;
402 }
403};
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 @@
1/*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "StringValidation.h"
10
11#include "utils/StringUtils.h"
12
13bool StringValidation::IsInteger(const std::string &input, void *data)
14{
15 return StringUtils::IsInteger(input);
16}
17
18bool StringValidation::IsPositiveInteger(const std::string &input, void *data)
19{
20 return StringUtils::IsNaturalNumber(input);
21}
22
23bool StringValidation::IsTime(const std::string &input, void *data)
24{
25 std::string strTime = input;
26 StringUtils::Trim(strTime);
27
28 if (StringUtils::EndsWithNoCase(strTime, " min"))
29 {
30 strTime = StringUtils::Left(strTime, strTime.size() - 4);
31 StringUtils::TrimRight(strTime);
32
33 return IsPositiveInteger(strTime, NULL);
34 }
35 else
36 {
37 // support [[HH:]MM:]SS
38 std::vector<std::string> bits = StringUtils::Split(input, ":");
39 if (bits.size() > 3)
40 return false;
41
42 for (std::vector<std::string>::const_iterator i = bits.begin(); i != bits.end(); ++i)
43 if (!IsPositiveInteger(*i, NULL))
44 return false;
45
46 return true;
47 }
48 return false;
49}
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 @@
1/*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13class StringValidation
14{
15public:
16 typedef bool (*Validator)(const std::string &input, void *data);
17
18 static bool NonEmpty(const std::string &input, void *data) { return !input.empty(); }
19 static bool IsInteger(const std::string &input, void *data);
20 static bool IsPositiveInteger(const std::string &input, void *data);
21 static bool IsTime(const std::string &input, void *data);
22
23private:
24 StringValidation() = default;
25};
diff --git a/xbmc/utils/SystemInfo.cpp b/xbmc/utils/SystemInfo.cpp
new file mode 100644
index 0000000..5b1d89e
--- /dev/null
+++ b/xbmc/utils/SystemInfo.cpp
@@ -0,0 +1,1395 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include <limits.h>
10
11#include "threads/SystemClock.h"
12#include "SystemInfo.h"
13#ifndef TARGET_POSIX
14#include <conio.h>
15#else
16#include <sys/utsname.h>
17#endif
18#include "CompileInfo.h"
19#include "ServiceBroker.h"
20#include "filesystem/CurlFile.h"
21#include "filesystem/File.h"
22#include "guilib/LocalizeStrings.h"
23#include "guilib/guiinfo/GUIInfoLabels.h"
24#include "network/Network.h"
25#include "platform/Filesystem.h"
26#include "rendering/RenderSystem.h"
27#include "settings/Settings.h"
28#include "settings/SettingsComponent.h"
29#include "utils/CPUInfo.h"
30#include "utils/log.h"
31
32#ifdef TARGET_WINDOWS
33#include <dwmapi.h>
34#include "utils/CharsetConverter.h"
35#include <VersionHelpers.h>
36
37#ifdef TARGET_WINDOWS_STORE
38#include <winrt/Windows.Security.ExchangeActiveSyncProvisioning.h>
39#include <winrt/Windows.System.Profile.h>
40
41using namespace winrt::Windows::ApplicationModel;
42using namespace winrt::Windows::Security::ExchangeActiveSyncProvisioning;
43using namespace winrt::Windows::System;
44using namespace winrt::Windows::System::Profile;
45#endif
46#include <wincrypt.h>
47#include "platform/win32/CharsetConverter.h"
48#endif
49#if defined(TARGET_DARWIN)
50#include "platform/darwin/DarwinUtils.h"
51#endif
52#include "powermanagement/PowerManager.h"
53#include "utils/StringUtils.h"
54#include "utils/XMLUtils.h"
55#if defined(TARGET_ANDROID)
56#include <androidjni/Build.h>
57#endif
58
59/* Platform identification */
60#if defined(TARGET_DARWIN)
61#include <Availability.h>
62#include <mach-o/arch.h>
63#include <sys/sysctl.h>
64#include "utils/auto_buffer.h"
65#elif defined(TARGET_ANDROID)
66#include <android/api-level.h>
67#include <sys/system_properties.h>
68#elif defined(TARGET_FREEBSD)
69#include <sys/param.h>
70#elif defined(TARGET_LINUX)
71#include "platform/linux/SysfsPath.h"
72
73#include <linux/version.h>
74#endif
75
76#include <system_error>
77
78/* Expand macro before stringify */
79#define STR_MACRO(x) #x
80#define XSTR_MACRO(x) STR_MACRO(x)
81
82using namespace XFILE;
83
84#ifdef TARGET_WINDOWS_DESKTOP
85static bool sysGetVersionExWByRef(OSVERSIONINFOEXW& osVerInfo)
86{
87 ZeroMemory(&osVerInfo, sizeof(osVerInfo));
88 osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo);
89
90 typedef NTSTATUS(__stdcall *RtlGetVersionPtr)(RTL_OSVERSIONINFOEXW* pOsInfo);
91 static HMODULE hNtDll = GetModuleHandleW(L"ntdll.dll");
92 if (hNtDll != NULL)
93 {
94 static RtlGetVersionPtr RtlGetVer = (RtlGetVersionPtr) GetProcAddress(hNtDll, "RtlGetVersion");
95 if (RtlGetVer && RtlGetVer(&osVerInfo) == 0)
96 return true;
97 }
98 // failed to get OS information directly from ntdll.dll
99 // use GetVersionExW() as fallback
100 // note: starting from Windows 8.1 GetVersionExW() may return unfaithful information
101 if (GetVersionExW((OSVERSIONINFOW*) &osVerInfo) != 0)
102 return true;
103
104 ZeroMemory(&osVerInfo, sizeof(osVerInfo));
105 return false;
106}
107#endif // TARGET_WINDOWS_DESKTOP
108
109#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID)
110static std::string getValueFromOs_release(std::string key)
111{
112 FILE* os_rel = fopen("/etc/os-release", "r");
113 if (!os_rel)
114 return "";
115
116 char* buf = new char[10 * 1024]; // more than enough
117 size_t len = fread(buf, 1, 10 * 1024, os_rel);
118 fclose(os_rel);
119 if (len == 0)
120 {
121 delete[] buf;
122 return "";
123 }
124
125 std::string content(buf, len);
126 delete[] buf;
127
128 // find begin of value string
129 size_t valStart = 0, seachPos;
130 key += '=';
131 if (content.compare(0, key.length(), key) == 0)
132 valStart = key.length();
133 else
134 {
135 key = "\n" + key;
136 seachPos = 0;
137 do
138 {
139 seachPos = content.find(key, seachPos);
140 if (seachPos == std::string::npos)
141 return "";
142 if (seachPos == 0 || content[seachPos - 1] != '\\')
143 valStart = seachPos + key.length();
144 else
145 seachPos++;
146 } while (valStart == 0);
147 }
148
149 if (content[valStart] == '\n')
150 return "";
151
152 // find end of value string
153 seachPos = valStart;
154 do
155 {
156 seachPos = content.find('\n', seachPos + 1);
157 } while (seachPos != std::string::npos && content[seachPos - 1] == '\\');
158 size_t const valEnd = seachPos;
159
160 std::string value(content, valStart, valEnd - valStart);
161 if (value.empty())
162 return value;
163
164 // remove quotes
165 if (value[0] == '\'' || value[0] == '"')
166 {
167 if (value.length() < 2)
168 return value;
169 size_t qEnd = value.rfind(value[0]);
170 if (qEnd != std::string::npos)
171 {
172 value.erase(qEnd);
173 value.erase(0, 1);
174 }
175 }
176
177 // unescape characters
178 for (size_t slashPos = value.find('\\'); slashPos < value.length() - 1; slashPos = value.find('\\', slashPos))
179 {
180 if (value[slashPos + 1] == '\n')
181 value.erase(slashPos, 2);
182 else
183 {
184 value.erase(slashPos, 1);
185 slashPos++; // skip unescaped character
186 }
187 }
188
189 return value;
190}
191
192enum lsb_rel_info_type
193{
194 lsb_rel_distributor,
195 lsb_rel_description,
196 lsb_rel_release,
197 lsb_rel_codename
198};
199
200static std::string getValueFromLsb_release(enum lsb_rel_info_type infoType)
201{
202 std::string key, command("unset PYTHONHOME; unset PYTHONPATH; lsb_release ");
203 switch (infoType)
204 {
205 case lsb_rel_distributor:
206 command += "-i";
207 key = "Distributor ID:\t";
208 break;
209 case lsb_rel_description:
210 command += "-d";
211 key = "Description:\t";
212 break;
213 case lsb_rel_release:
214 command += "-r";
215 key = "Release:\t";
216 break;
217 case lsb_rel_codename:
218 command += "-c";
219 key = "Codename:\t";
220 break;
221 default:
222 return "";
223 }
224 command += " 2>/dev/null";
225 FILE* lsb_rel = popen(command.c_str(), "r");
226 if (lsb_rel == NULL)
227 return "";
228
229 char buf[300]; // more than enough
230 if (fgets(buf, 300, lsb_rel) == NULL)
231 {
232 pclose(lsb_rel);
233 return "";
234 }
235 pclose(lsb_rel);
236
237 std::string response(buf);
238 if (response.compare(0, key.length(), key) != 0)
239 return "";
240
241 return response.substr(key.length(), response.find('\n') - key.length());
242}
243#endif // TARGET_LINUX && !TARGET_ANDROID
244
245CSysInfo g_sysinfo;
246
247CSysInfoJob::CSysInfoJob() = default;
248
249bool CSysInfoJob::DoWork()
250{
251 m_info.systemUptime = GetSystemUpTime(false);
252 m_info.systemTotalUptime = GetSystemUpTime(true);
253 m_info.internetState = GetInternetState();
254 m_info.videoEncoder = GetVideoEncoder();
255 m_info.cpuFrequency =
256 StringUtils::Format("%4.0f MHz", CServiceBroker::GetCPUInfo()->GetCPUFrequency());
257 m_info.osVersionInfo = CSysInfo::GetOsPrettyNameWithVersion() + " (kernel: " + CSysInfo::GetKernelName() + " " + CSysInfo::GetKernelVersionFull() + ")";
258 m_info.macAddress = GetMACAddress();
259 m_info.batteryLevel = GetBatteryLevel();
260 return true;
261}
262
263const CSysData &CSysInfoJob::GetData() const
264{
265 return m_info;
266}
267
268CSysData::INTERNET_STATE CSysInfoJob::GetInternetState()
269{
270 // Internet connection state!
271 XFILE::CCurlFile http;
272 if (http.IsInternet())
273 return CSysData::CONNECTED;
274 return CSysData::DISCONNECTED;
275}
276
277std::string CSysInfoJob::GetMACAddress()
278{
279 CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface();
280 if (iface)
281 return iface->GetMacAddress();
282
283 return "";
284}
285
286std::string CSysInfoJob::GetVideoEncoder()
287{
288 return "GPU: " + CServiceBroker::GetRenderSystem()->GetRenderRenderer();
289}
290
291std::string CSysInfoJob::GetBatteryLevel()
292{
293 return StringUtils::Format("%d%%", CServiceBroker::GetPowerManager().BatteryLevel());
294}
295
296bool CSysInfoJob::SystemUpTime(int iInputMinutes, int &iMinutes, int &iHours, int &iDays)
297{
298 iHours = 0; iDays = 0;
299 iMinutes = iInputMinutes;
300 if (iMinutes >= 60) // Hour's
301 {
302 iHours = iMinutes / 60;
303 iMinutes = iMinutes - (iHours *60);
304 }
305 if (iHours >= 24) // Days
306 {
307 iDays = iHours / 24;
308 iHours = iHours - (iDays * 24);
309 }
310 return true;
311}
312
313std::string CSysInfoJob::GetSystemUpTime(bool bTotalUptime)
314{
315 std::string strSystemUptime;
316 int iInputMinutes, iMinutes,iHours,iDays;
317
318 if(bTotalUptime)
319 {
320 //Total Uptime
321 iInputMinutes = g_sysinfo.GetTotalUptime() + ((int)(XbmcThreads::SystemClockMillis() / 60000));
322 }
323 else
324 {
325 //Current UpTime
326 iInputMinutes = (int)(XbmcThreads::SystemClockMillis() / 60000);
327 }
328
329 SystemUpTime(iInputMinutes,iMinutes, iHours, iDays);
330 if (iDays > 0)
331 {
332 strSystemUptime = StringUtils::Format("%i %s, %i %s, %i %s",
333 iDays, g_localizeStrings.Get(12393).c_str(),
334 iHours, g_localizeStrings.Get(12392).c_str(),
335 iMinutes, g_localizeStrings.Get(12391).c_str());
336 }
337 else if (iDays == 0 && iHours >= 1 )
338 {
339 strSystemUptime = StringUtils::Format("%i %s, %i %s",
340 iHours, g_localizeStrings.Get(12392).c_str(),
341 iMinutes, g_localizeStrings.Get(12391).c_str());
342 }
343 else if (iDays == 0 && iHours == 0 && iMinutes >= 0)
344 {
345 strSystemUptime = StringUtils::Format("%i %s",
346 iMinutes, g_localizeStrings.Get(12391).c_str());
347 }
348 return strSystemUptime;
349}
350
351std::string CSysInfo::TranslateInfo(int info) const
352{
353 switch(info)
354 {
355 case SYSTEM_VIDEO_ENCODER_INFO:
356 return m_info.videoEncoder;
357 case NETWORK_MAC_ADDRESS:
358 return m_info.macAddress;
359 case SYSTEM_OS_VERSION_INFO:
360 return m_info.osVersionInfo;
361 case SYSTEM_CPUFREQUENCY:
362 return m_info.cpuFrequency;
363 case SYSTEM_UPTIME:
364 return m_info.systemUptime;
365 case SYSTEM_TOTALUPTIME:
366 return m_info.systemTotalUptime;
367 case SYSTEM_INTERNET_STATE:
368 if (m_info.internetState == CSysData::CONNECTED)
369 return g_localizeStrings.Get(13296);
370 else
371 return g_localizeStrings.Get(13297);
372 case SYSTEM_BATTERY_LEVEL:
373 return m_info.batteryLevel;
374 default:
375 return "";
376 }
377}
378
379void CSysInfo::Reset()
380{
381 m_info.Reset();
382}
383
384CSysInfo::CSysInfo(void) : CInfoLoader(15 * 1000)
385{
386 memset(MD5_Sign, 0, sizeof(MD5_Sign));
387 m_iSystemTimeTotalUp = 0;
388}
389
390CSysInfo::~CSysInfo() = default;
391
392bool CSysInfo::Load(const TiXmlNode *settings)
393{
394 if (settings == NULL)
395 return false;
396
397 const TiXmlElement *pElement = settings->FirstChildElement("general");
398 if (pElement)
399 XMLUtils::GetInt(pElement, "systemtotaluptime", m_iSystemTimeTotalUp, 0, INT_MAX);
400
401 return true;
402}
403
404bool CSysInfo::Save(TiXmlNode *settings) const
405{
406 if (settings == NULL)
407 return false;
408
409 TiXmlNode *generalNode = settings->FirstChild("general");
410 if (generalNode == NULL)
411 {
412 TiXmlElement generalNodeNew("general");
413 generalNode = settings->InsertEndChild(generalNodeNew);
414 if (generalNode == NULL)
415 return false;
416 }
417 XMLUtils::SetInt(generalNode, "systemtotaluptime", m_iSystemTimeTotalUp);
418
419 return true;
420}
421
422const std::string& CSysInfo::GetAppName(void)
423{
424 assert(CCompileInfo::GetAppName() != NULL);
425 static const std::string appName(CCompileInfo::GetAppName());
426
427 return appName;
428}
429
430bool CSysInfo::GetDiskSpace(std::string drive,int& iTotal, int& iTotalFree, int& iTotalUsed, int& iPercentFree, int& iPercentUsed)
431{
432 using namespace KODI::PLATFORM::FILESYSTEM;
433
434 space_info total = { 0 };
435 std::error_code ec;
436
437 // None of this makes sense but the idea of total space
438 // makes no sense on any system really.
439 // Return space for / or for C: as it's correct in a sense
440 // and not much worse than trying to count a total for different
441 // drives/mounts
442 if (drive.empty() || drive == "*")
443 {
444#if defined(TARGET_WINDOWS)
445 drive = "C";
446#elif defined(TARGET_POSIX)
447 drive = "/";
448#endif
449 }
450
451#ifdef TARGET_WINDOWS_DESKTOP
452 using KODI::PLATFORM::WINDOWS::ToW;
453 UINT uidriveType = GetDriveType(ToW(drive + ":\\").c_str());
454 if (uidriveType != DRIVE_UNKNOWN && uidriveType != DRIVE_NO_ROOT_DIR)
455 total = space(drive + ":\\", ec);
456#elif defined(TARGET_POSIX)
457 total = space(drive, ec);
458#endif
459 if (ec.value() != 0)
460 return false;
461
462 iTotal = static_cast<int>(total.capacity / MB);
463 iTotalFree = static_cast<int>(total.free / MB);
464 iTotalUsed = iTotal - iTotalFree;
465 if (total.capacity > 0)
466 iPercentUsed = static_cast<int>(100.0f * (total.capacity - total.free) / total.capacity + 0.5f);
467 else
468 iPercentUsed = 0;
469
470 iPercentFree = 100 - iPercentUsed;
471
472 return true;
473}
474
475std::string CSysInfo::GetKernelName(bool emptyIfUnknown /*= false*/)
476{
477 static std::string kernelName;
478 if (kernelName.empty())
479 {
480#if defined(TARGET_WINDOWS_DESKTOP)
481 OSVERSIONINFOEXW osvi;
482 if (sysGetVersionExWByRef(osvi) && osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
483 kernelName = "Windows NT";
484#elif defined(TARGET_WINDOWS_STORE)
485 auto e = EasClientDeviceInformation();
486 auto os = e.OperatingSystem();
487 g_charsetConverter.wToUTF8(std::wstring(os.c_str()), kernelName);
488#elif defined(TARGET_POSIX)
489 struct utsname un;
490 if (uname(&un) == 0)
491 kernelName.assign(un.sysname);
492#endif // defined(TARGET_POSIX)
493
494 if (kernelName.empty())
495 kernelName = "Unknown kernel"; // can't detect
496 }
497
498 if (emptyIfUnknown && kernelName == "Unknown kernel")
499 return "";
500
501 return kernelName;
502}
503
504std::string CSysInfo::GetKernelVersionFull(void)
505{
506 static std::string kernelVersionFull;
507 if (!kernelVersionFull.empty())
508 return kernelVersionFull;
509
510#if defined(TARGET_WINDOWS_DESKTOP)
511 OSVERSIONINFOEXW osvi;
512 if (sysGetVersionExWByRef(osvi))
513 kernelVersionFull = StringUtils::Format("%d.%d.%d", osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber);
514#elif defined(TARGET_WINDOWS_STORE)
515 // get the system version number
516 auto sv = AnalyticsInfo::VersionInfo().DeviceFamilyVersion();
517 wchar_t* end;
518 unsigned long long v = wcstoull(sv.c_str(), &end, 10);
519 unsigned long long v1 = (v & 0xFFFF000000000000L) >> 48;
520 unsigned long long v2 = (v & 0x0000FFFF00000000L) >> 32;
521 unsigned long long v3 = (v & 0x00000000FFFF0000L) >> 16;
522 kernelVersionFull = StringUtils::Format("%lld.%lld.%lld", v1, v2, v3);
523
524#elif defined(TARGET_POSIX)
525 struct utsname un;
526 if (uname(&un) == 0)
527 kernelVersionFull.assign(un.release);
528#endif // defined(TARGET_POSIX)
529
530 if (kernelVersionFull.empty())
531 kernelVersionFull = "0.0.0"; // can't detect
532
533 return kernelVersionFull;
534}
535
536std::string CSysInfo::GetKernelVersion(void)
537{
538 static std::string kernelVersionClear;
539 if (kernelVersionClear.empty())
540 {
541 kernelVersionClear = GetKernelVersionFull();
542 const size_t erasePos = kernelVersionClear.find_first_not_of("0123456789.");
543 if (erasePos != std::string::npos)
544 kernelVersionClear.erase(erasePos);
545 }
546
547 return kernelVersionClear;
548}
549
550std::string CSysInfo::GetOsName(bool emptyIfUnknown /* = false*/)
551{
552 static std::string osName;
553 if (osName.empty())
554 {
555#if defined (TARGET_WINDOWS)
556 osName = GetKernelName() + "-based OS";
557#elif defined(TARGET_FREEBSD)
558 osName = GetKernelName(true); // FIXME: for FreeBSD OS name is a kernel name
559#elif defined(TARGET_DARWIN_IOS)
560 osName = "iOS";
561#elif defined(TARGET_DARWIN_TVOS)
562 osName = "tvOS";
563#elif defined(TARGET_DARWIN_OSX)
564 osName = "OS X";
565#elif defined (TARGET_ANDROID)
566 osName = "Android";
567#elif defined(TARGET_LINUX)
568 osName = getValueFromOs_release("NAME");
569 if (osName.empty())
570 osName = getValueFromLsb_release(lsb_rel_distributor);
571 if (osName.empty())
572 osName = getValueFromOs_release("ID");
573#endif // defined(TARGET_LINUX)
574
575 if (osName.empty())
576 osName = "Unknown OS";
577 }
578
579 if (emptyIfUnknown && osName == "Unknown OS")
580 return "";
581
582 return osName;
583}
584
585std::string CSysInfo::GetOsVersion(void)
586{
587 static std::string osVersion;
588 if (!osVersion.empty())
589 return osVersion;
590
591#if defined(TARGET_WINDOWS) || defined(TARGET_FREEBSD)
592 osVersion = GetKernelVersion(); // FIXME: for Win32 and FreeBSD OS version is a kernel version
593#elif defined(TARGET_DARWIN)
594 osVersion = CDarwinUtils::GetVersionString();
595#elif defined(TARGET_ANDROID)
596 char versionCStr[PROP_VALUE_MAX];
597 int propLen = __system_property_get("ro.build.version.release", versionCStr);
598 osVersion.assign(versionCStr, (propLen > 0 && propLen <= PROP_VALUE_MAX) ? propLen : 0);
599
600 if (osVersion.empty() || std::string("0123456789").find(versionCStr[0]) == std::string::npos)
601 osVersion.clear(); // can't correctly detect Android version
602 else
603 {
604 size_t pointPos = osVersion.find('.');
605 if (pointPos == std::string::npos)
606 osVersion += ".0.0";
607 else if (osVersion.find('.', pointPos + 1) == std::string::npos)
608 osVersion += ".0";
609 }
610#elif defined(TARGET_LINUX)
611 osVersion = getValueFromOs_release("VERSION_ID");
612 if (osVersion.empty())
613 osVersion = getValueFromLsb_release(lsb_rel_release);
614#endif // defined(TARGET_LINUX)
615
616 if (osVersion.empty())
617 osVersion = "0.0";
618
619 return osVersion;
620}
621
622std::string CSysInfo::GetOsPrettyNameWithVersion(void)
623{
624 static std::string osNameVer;
625 if (!osNameVer.empty())
626 return osNameVer;
627
628#if defined (TARGET_WINDOWS_DESKTOP)
629 OSVERSIONINFOEXW osvi = {};
630
631 osNameVer = "Windows ";
632 if (sysGetVersionExWByRef(osvi))
633 {
634 switch (GetWindowsVersion())
635 {
636 case WindowsVersionWin7:
637 if (osvi.wProductType == VER_NT_WORKSTATION)
638 osNameVer.append("7");
639 else
640 osNameVer.append("Server 2008 R2");
641 break;
642 case WindowsVersionWin8:
643 if (osvi.wProductType == VER_NT_WORKSTATION)
644 osNameVer.append("8");
645 else
646 osNameVer.append("Server 2012");
647 break;
648 case WindowsVersionWin8_1:
649 if (osvi.wProductType == VER_NT_WORKSTATION)
650 osNameVer.append("8.1");
651 else
652 osNameVer.append("Server 2012 R2");
653 break;
654 case WindowsVersionWin10:
655 case WindowsVersionWin10_FCU:
656 if (osvi.wProductType == VER_NT_WORKSTATION)
657 osNameVer.append("10");
658 else
659 osNameVer.append("Unknown future server version");
660 break;
661 case WindowsVersionFuture:
662 osNameVer.append("Unknown future version");
663 break;
664 default:
665 osNameVer.append("Unknown version");
666 break;
667 }
668
669 // Append Service Pack version if any
670 if (osvi.wServicePackMajor > 0 || osvi.wServicePackMinor > 0)
671 {
672 osNameVer.append(StringUtils::Format(" SP%d", osvi.wServicePackMajor));
673 if (osvi.wServicePackMinor > 0)
674 {
675 osNameVer.append(StringUtils::Format(".%d", osvi.wServicePackMinor));
676 }
677 }
678 }
679 else
680 osNameVer.append(" unknown");
681#elif defined(TARGET_WINDOWS_STORE)
682 osNameVer = GetKernelName() + " " + GetOsVersion();
683#elif defined(TARGET_FREEBSD) || defined(TARGET_DARWIN)
684 osNameVer = GetOsName() + " " + GetOsVersion();
685#elif defined(TARGET_ANDROID)
686 osNameVer = GetOsName() + " " + GetOsVersion() + " API level " + StringUtils::Format("%d", CJNIBuild::SDK_INT);
687#elif defined(TARGET_LINUX)
688 osNameVer = getValueFromOs_release("PRETTY_NAME");
689 if (osNameVer.empty())
690 {
691 osNameVer = getValueFromLsb_release(lsb_rel_description);
692 std::string osName(GetOsName(true));
693 if (!osName.empty() && osNameVer.find(osName) == std::string::npos)
694 osNameVer = osName + osNameVer;
695 if (osNameVer.empty())
696 osNameVer = "Unknown Linux Distribution";
697 }
698
699 if (osNameVer.find(GetOsVersion()) == std::string::npos)
700 osNameVer += " " + GetOsVersion();
701#endif // defined(TARGET_LINUX)
702
703 if (osNameVer.empty())
704 osNameVer = "Unknown OS Unknown version";
705
706 return osNameVer;
707
708}
709
710std::string CSysInfo::GetManufacturerName(void)
711{
712 static std::string manufName;
713 static bool inited = false;
714 if (!inited)
715 {
716#if defined(TARGET_ANDROID)
717 char deviceCStr[PROP_VALUE_MAX];
718 int propLen = __system_property_get("ro.product.manufacturer", deviceCStr);
719 manufName.assign(deviceCStr, (propLen > 0 && propLen <= PROP_VALUE_MAX) ? propLen : 0);
720#elif defined(TARGET_DARWIN)
721 manufName = CDarwinUtils::GetManufacturer();
722#elif defined(TARGET_WINDOWS_STORE)
723 auto eas = EasClientDeviceInformation();
724 auto manufacturer = eas.SystemManufacturer();
725 g_charsetConverter.wToUTF8(std::wstring(manufacturer.c_str()), manufName);
726#elif defined(TARGET_LINUX)
727
728 auto cpuInfo = CServiceBroker::GetCPUInfo();
729 manufName = cpuInfo->GetCPUSoC();
730
731#elif defined(TARGET_WINDOWS)
732 // We just don't care, might be useful on embedded
733#endif
734 inited = true;
735 }
736
737 return manufName;
738}
739
740std::string CSysInfo::GetModelName(void)
741{
742 static std::string modelName;
743 static bool inited = false;
744 if (!inited)
745 {
746#if defined(TARGET_ANDROID)
747 char deviceCStr[PROP_VALUE_MAX];
748 int propLen = __system_property_get("ro.product.model", deviceCStr);
749 modelName.assign(deviceCStr, (propLen > 0 && propLen <= PROP_VALUE_MAX) ? propLen : 0);
750#elif defined(TARGET_DARWIN_EMBEDDED)
751 modelName = CDarwinUtils::getIosPlatformString();
752#elif defined(TARGET_DARWIN_OSX)
753 size_t nameLen = 0; // 'nameLen' should include terminating null
754 if (sysctlbyname("hw.model", NULL, &nameLen, NULL, 0) == 0 && nameLen > 1)
755 {
756 XUTILS::auto_buffer buf(nameLen);
757 if (sysctlbyname("hw.model", buf.get(), &nameLen, NULL, 0) == 0 && nameLen == buf.size())
758 modelName.assign(buf.get(), nameLen - 1); // assign exactly 'nameLen-1' characters to 'modelName'
759 }
760#elif defined(TARGET_WINDOWS_STORE)
761 auto eas = EasClientDeviceInformation();
762 auto manufacturer = eas.SystemProductName();
763 g_charsetConverter.wToUTF8(std::wstring(manufacturer.c_str()), modelName);
764#elif defined(TARGET_LINUX)
765 auto cpuInfo = CServiceBroker::GetCPUInfo();
766 modelName = cpuInfo->GetCPUHardware();
767#elif defined(TARGET_WINDOWS)
768 // We just don't care, might be useful on embedded
769#endif
770 inited = true;
771 }
772
773 return modelName;
774}
775
776bool CSysInfo::IsAeroDisabled()
777{
778#ifdef TARGET_WINDOWS_STORE
779 return true; // need to review https://msdn.microsoft.com/en-us/library/windows/desktop/aa969518(v=vs.85).aspx
780#elif defined(TARGET_WINDOWS)
781 BOOL aeroEnabled = FALSE;
782 HRESULT res = DwmIsCompositionEnabled(&aeroEnabled);
783 if (SUCCEEDED(res))
784 return !aeroEnabled;
785#endif
786 return false;
787}
788
789CSysInfo::WindowsVersion CSysInfo::m_WinVer = WindowsVersionUnknown;
790
791bool CSysInfo::IsWindowsVersion(WindowsVersion ver)
792{
793 if (ver == WindowsVersionUnknown)
794 return false;
795 return GetWindowsVersion() == ver;
796}
797
798bool CSysInfo::IsWindowsVersionAtLeast(WindowsVersion ver)
799{
800 if (ver == WindowsVersionUnknown)
801 return false;
802 return GetWindowsVersion() >= ver;
803}
804
805CSysInfo::WindowsVersion CSysInfo::GetWindowsVersion()
806{
807#ifdef TARGET_WINDOWS_DESKTOP
808 if (m_WinVer == WindowsVersionUnknown)
809 {
810 OSVERSIONINFOEXW osvi = {};
811 if (sysGetVersionExWByRef(osvi))
812 {
813 if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1)
814 m_WinVer = WindowsVersionWin7;
815 else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2)
816 m_WinVer = WindowsVersionWin8;
817 else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3)
818 m_WinVer = WindowsVersionWin8_1;
819 else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber < 16299)
820 m_WinVer = WindowsVersionWin10;
821 else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= 16299)
822 m_WinVer = WindowsVersionWin10_FCU;
823 /* Insert checks for new Windows versions here */
824 else if ( (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion > 3) || osvi.dwMajorVersion > 10)
825 m_WinVer = WindowsVersionFuture;
826 }
827 }
828#elif defined(TARGET_WINDOWS_STORE)
829 m_WinVer = WindowsVersionWin10;
830#endif // TARGET_WINDOWS
831 return m_WinVer;
832}
833
834int CSysInfo::GetKernelBitness(void)
835{
836 static int kernelBitness = -1;
837 if (kernelBitness == -1)
838 {
839#ifdef TARGET_WINDOWS_STORE
840 Package package = Package::Current();
841 auto arch = package.Id().Architecture();
842 switch (arch)
843 {
844 case ProcessorArchitecture::X86:
845 kernelBitness = 32;
846 break;
847 case ProcessorArchitecture::X64:
848 kernelBitness = 64;
849 break;
850 case ProcessorArchitecture::Arm:
851 kernelBitness = 32;
852 break;
853 case ProcessorArchitecture::Unknown: // not sure what to do here. guess 32 for now
854 case ProcessorArchitecture::Neutral:
855 kernelBitness = 32;
856 break;
857 }
858#elif defined(TARGET_WINDOWS_DESKTOP)
859 SYSTEM_INFO si;
860 GetNativeSystemInfo(&si);
861 if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL || si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM)
862 kernelBitness = 32;
863 else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
864 kernelBitness = 64;
865 else
866 {
867 BOOL isWow64 = FALSE;
868 if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) // fallback
869 kernelBitness = 64;
870 }
871#elif defined(TARGET_DARWIN_EMBEDDED)
872 // Note: OS X return x86 CPU type without CPU_ARCH_ABI64 flag
873 const NXArchInfo* archInfo = NXGetLocalArchInfo();
874 if (archInfo)
875 kernelBitness = ((archInfo->cputype & CPU_ARCH_ABI64) != 0) ? 64 : 32;
876#elif defined(TARGET_POSIX)
877 struct utsname un;
878 if (uname(&un) == 0)
879 {
880 std::string machine(un.machine);
881 if (machine == "x86_64" || machine == "amd64" || machine == "arm64" || machine == "aarch64" || machine == "ppc64" ||
882 machine == "ia64" || machine == "mips64" || machine == "s390x")
883 kernelBitness = 64;
884 else
885 kernelBitness = 32;
886 }
887#endif
888 if (kernelBitness == -1)
889 kernelBitness = 0; // can't detect
890 }
891
892 return kernelBitness;
893}
894
895const std::string& CSysInfo::GetKernelCpuFamily(void)
896{
897 static std::string kernelCpuFamily;
898 if (kernelCpuFamily.empty())
899 {
900#ifdef TARGET_WINDOWS
901 SYSTEM_INFO si;
902 GetNativeSystemInfo(&si);
903 if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL ||
904 si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
905 kernelCpuFamily = "x86";
906 else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM)
907 kernelCpuFamily = "ARM";
908#elif defined(TARGET_DARWIN)
909 const NXArchInfo* archInfo = NXGetLocalArchInfo();
910 if (archInfo)
911 {
912 const cpu_type_t cpuType = (archInfo->cputype & ~CPU_ARCH_ABI64); // get CPU family without 64-bit ABI flag
913 if (cpuType == CPU_TYPE_I386)
914 kernelCpuFamily = "x86";
915 else if (cpuType == CPU_TYPE_ARM)
916 kernelCpuFamily = "ARM";
917#ifdef CPU_TYPE_MIPS
918 else if (cpuType == CPU_TYPE_MIPS)
919 kernelCpuFamily = "MIPS";
920#endif // CPU_TYPE_MIPS
921 }
922#elif defined(TARGET_POSIX)
923 struct utsname un;
924 if (uname(&un) == 0)
925 {
926 std::string machine(un.machine);
927 if (machine.compare(0, 3, "arm", 3) == 0 || machine.compare(0, 7, "aarch64", 7) == 0)
928 kernelCpuFamily = "ARM";
929 else if (machine.compare(0, 4, "mips", 4) == 0)
930 kernelCpuFamily = "MIPS";
931 else if (machine.compare(0, 4, "i686", 4) == 0 || machine == "i386" || machine == "amd64" || machine.compare(0, 3, "x86", 3) == 0)
932 kernelCpuFamily = "x86";
933 else if (machine.compare(0, 4, "s390", 4) == 0)
934 kernelCpuFamily = "s390";
935 else if (machine.compare(0, 3, "ppc", 3) == 0 || machine.compare(0, 5, "power", 5) == 0)
936 kernelCpuFamily = "PowerPC";
937 }
938#endif
939 if (kernelCpuFamily.empty())
940 kernelCpuFamily = "unknown CPU family";
941 }
942 return kernelCpuFamily;
943}
944
945int CSysInfo::GetXbmcBitness(void)
946{
947 return static_cast<int>(sizeof(void*) * 8);
948}
949
950bool CSysInfo::HasInternet()
951{
952 if (m_info.internetState != CSysData::UNKNOWN)
953 return m_info.internetState == CSysData::CONNECTED;
954 return (m_info.internetState = CSysInfoJob::GetInternetState()) == CSysData::CONNECTED;
955}
956
957std::string CSysInfo::GetHddSpaceInfo(int drive, bool shortText)
958{
959 int percent;
960 return GetHddSpaceInfo( percent, drive, shortText);
961}
962
963std::string CSysInfo::GetHddSpaceInfo(int& percent, int drive, bool shortText)
964{
965 int total, totalFree, totalUsed, percentFree, percentused;
966 std::string strRet;
967 percent = 0;
968 if (g_sysinfo.GetDiskSpace("", total, totalFree, totalUsed, percentFree, percentused))
969 {
970 if (shortText)
971 {
972 switch(drive)
973 {
974 case SYSTEM_FREE_SPACE:
975 percent = percentFree;
976 break;
977 case SYSTEM_USED_SPACE:
978 percent = percentused;
979 break;
980 }
981 }
982 else
983 {
984 switch(drive)
985 {
986 case SYSTEM_FREE_SPACE:
987 strRet = StringUtils::Format("%i MB %s", totalFree, g_localizeStrings.Get(160).c_str());
988 break;
989 case SYSTEM_USED_SPACE:
990 strRet = StringUtils::Format("%i MB %s", totalUsed, g_localizeStrings.Get(20162).c_str());
991 break;
992 case SYSTEM_TOTAL_SPACE:
993 strRet = StringUtils::Format("%i MB %s", total, g_localizeStrings.Get(20161).c_str());
994 break;
995 case SYSTEM_FREE_SPACE_PERCENT:
996 strRet = StringUtils::Format("%i %% %s", percentFree, g_localizeStrings.Get(160).c_str());
997 break;
998 case SYSTEM_USED_SPACE_PERCENT:
999 strRet = StringUtils::Format("%i %% %s", percentused, g_localizeStrings.Get(20162).c_str());
1000 break;
1001 }
1002 }
1003 }
1004 else
1005 {
1006 if (shortText)
1007 strRet = g_localizeStrings.Get(10006); // N/A
1008 else
1009 strRet = g_localizeStrings.Get(10005); // Not available
1010 }
1011 return strRet;
1012}
1013
1014std::string CSysInfo::GetUserAgent()
1015{
1016 static std::string result;
1017 if (!result.empty())
1018 return result;
1019
1020 result = GetAppName() + "/" + CSysInfo::GetVersionShort() + " (";
1021#if defined(TARGET_WINDOWS)
1022 result += GetKernelName() + " " + GetKernelVersion();
1023#ifndef TARGET_WINDOWS_STORE
1024 BOOL bIsWow = FALSE;
1025 if (IsWow64Process(GetCurrentProcess(), &bIsWow) && bIsWow)
1026 result.append("; WOW64");
1027 else
1028#endif
1029 {
1030 SYSTEM_INFO si = {};
1031 GetSystemInfo(&si);
1032 if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
1033 result.append("; Win64; x64");
1034 else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64)
1035 result.append("; Win64; IA64");
1036 else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM)
1037 result.append("; ARM");
1038 }
1039#elif defined(TARGET_DARWIN)
1040#if defined(TARGET_DARWIN_EMBEDDED)
1041 std::string iDevStr(GetModelName()); // device model name with number of model version
1042 size_t iDevStrDigit = iDevStr.find_first_of("0123456789");
1043 std::string iDev(iDevStr, 0, iDevStrDigit); // device model name without number
1044 if (iDevStrDigit == 0)
1045 iDev = "unknown";
1046 result += iDev + "; ";
1047 std::string iOSVersion(GetOsVersion());
1048 size_t lastDotPos = iOSVersion.rfind('.');
1049 if (lastDotPos != std::string::npos && iOSVersion.find('.') != lastDotPos
1050 && iOSVersion.find_first_not_of('0', lastDotPos + 1) == std::string::npos)
1051 iOSVersion.erase(lastDotPos);
1052 StringUtils::Replace(iOSVersion, '.', '_');
1053 if (iDev == "AppleTV")
1054 {
1055 // check if it's ATV4 (AppleTV5,3) or later
1056 auto modelMajorNumberEndPos = iDevStr.find_first_of(',', iDevStrDigit);
1057 std::string s{iDevStr, iDevStrDigit, modelMajorNumberEndPos - iDevStrDigit};
1058 if (stoi(s) >= 5)
1059 result += "CPU TVOS";
1060 else
1061 result += "CPU OS";
1062 }
1063 else if (iDev == "iPad")
1064 result += "CPU OS";
1065 else
1066 result += "CPU iPhone OS ";
1067 result += iOSVersion + " like Mac OS X";
1068#else
1069 result += "Macintosh; ";
1070 std::string cpuFam(GetBuildTargetCpuFamily());
1071 if (cpuFam == "x86")
1072 result += "Intel ";
1073 result += "Mac OS X ";
1074 std::string OSXVersion(GetOsVersion());
1075 StringUtils::Replace(OSXVersion, '.', '_');
1076 result += OSXVersion;
1077#endif
1078#elif defined(TARGET_ANDROID)
1079 result += "Linux; Android ";
1080 std::string versionStr(GetOsVersion());
1081 const size_t verLen = versionStr.length();
1082 if (verLen >= 2 && versionStr.compare(verLen - 2, 2, ".0", 2) == 0)
1083 versionStr.erase(verLen - 2); // remove last ".0" if any
1084 result += versionStr;
1085 std::string deviceInfo(GetModelName());
1086
1087 char buildId[PROP_VALUE_MAX];
1088 int propLen = __system_property_get("ro.build.id", buildId);
1089 if (propLen > 0 && propLen <= PROP_VALUE_MAX)
1090 {
1091 if (!deviceInfo.empty())
1092 deviceInfo += " ";
1093 deviceInfo += "Build/";
1094 deviceInfo.append(buildId, propLen);
1095 }
1096
1097 if (!deviceInfo.empty())
1098 result += "; " + deviceInfo;
1099#elif defined(TARGET_POSIX)
1100 result += "X11; ";
1101 struct utsname un;
1102 if (uname(&un) == 0)
1103 {
1104 std::string cpuStr(un.machine);
1105 if (cpuStr == "x86_64" && GetXbmcBitness() == 32)
1106 cpuStr = "i686 (x86_64)";
1107 result += un.sysname;
1108 result += " ";
1109 result += cpuStr;
1110 }
1111 else
1112 result += "Unknown";
1113#else
1114 result += "Unknown";
1115#endif
1116 result += ")";
1117
1118 if (GetAppName() != "Kodi")
1119 result += " Kodi_Fork_" + GetAppName() + "/1.0"; // default fork number is '1.0', replace it with actual number if necessary
1120
1121#ifdef TARGET_LINUX
1122 // Add distribution name
1123 std::string linuxOSName(GetOsName(true));
1124 if (!linuxOSName.empty())
1125 result += " " + linuxOSName + "/" + GetOsVersion();
1126#endif
1127
1128#ifdef TARGET_RASPBERRY_PI
1129 result += " HW_RaspberryPi/1.0";
1130#elif defined (TARGET_DARWIN_EMBEDDED)
1131 std::string iDevVer;
1132 if (iDevStrDigit == std::string::npos)
1133 iDevVer = "0.0";
1134 else
1135 iDevVer.assign(iDevStr, iDevStrDigit, std::string::npos);
1136 StringUtils::Replace(iDevVer, ',', '.');
1137 result += " HW_" + iDev + "/" + iDevVer;
1138#endif
1139 // add more device IDs here if needed.
1140 // keep only one device ID in result! Form:
1141 // result += " HW_" + "deviceID" + "/" + "1.0"; // '1.0' if device has no version
1142
1143#if defined(TARGET_ANDROID)
1144 // Android has no CPU string by default, so add it as additional parameter
1145 struct utsname un1;
1146 if (uname(&un1) == 0)
1147 {
1148 std::string cpuStr(un1.machine);
1149 StringUtils::Replace(cpuStr, ' ', '_');
1150 result += " Sys_CPU/" + cpuStr;
1151 }
1152#endif
1153
1154 result += " App_Bitness/" + StringUtils::Format("%d", GetXbmcBitness());
1155
1156 std::string fullVer(CSysInfo::GetVersion());
1157 StringUtils::Replace(fullVer, ' ', '-');
1158 result += " Version/" + fullVer;
1159
1160 return result;
1161}
1162
1163std::string CSysInfo::GetDeviceName()
1164{
1165 std::string friendlyName = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SERVICES_DEVICENAME);
1166 if (StringUtils::EqualsNoCase(friendlyName, CCompileInfo::GetAppName()))
1167 {
1168 std::string hostname("[unknown]");
1169 CServiceBroker::GetNetwork().GetHostName(hostname);
1170 return StringUtils::Format("%s (%s)", friendlyName.c_str(), hostname.c_str());
1171 }
1172
1173 return friendlyName;
1174}
1175
1176// Version string MUST NOT contain spaces. It is used
1177// in the HTTP request user agent.
1178std::string CSysInfo::GetVersionShort()
1179{
1180 if (strlen(CCompileInfo::GetSuffix()) == 0)
1181 return StringUtils::Format("%d.%d", CCompileInfo::GetMajor(), CCompileInfo::GetMinor());
1182 else
1183 return StringUtils::Format("%d.%d-%s", CCompileInfo::GetMajor(), CCompileInfo::GetMinor(), CCompileInfo::GetSuffix());
1184}
1185
1186std::string CSysInfo::GetVersion()
1187{
1188 return GetVersionShort() + " (" + CCompileInfo::GetVersionCode() + ")" +
1189 " Git:" + CCompileInfo::GetSCMID();
1190}
1191
1192std::string CSysInfo::GetVersionCode()
1193{
1194 return CCompileInfo::GetVersionCode();
1195}
1196
1197std::string CSysInfo::GetVersionGit()
1198{
1199 return CCompileInfo::GetSCMID();
1200}
1201
1202std::string CSysInfo::GetBuildDate()
1203{
1204 return CCompileInfo::GetBuildDate();
1205}
1206
1207std::string CSysInfo::GetBuildTargetPlatformName(void)
1208{
1209#if defined(TARGET_DARWIN_OSX)
1210 return "OS X";
1211#elif defined(TARGET_DARWIN_IOS)
1212 return "iOS";
1213#elif defined(TARGET_DARWIN_TVOS)
1214 return "tvOS";
1215#elif defined(TARGET_FREEBSD)
1216 return "FreeBSD";
1217#elif defined(TARGET_ANDROID)
1218 return "Android";
1219#elif defined(TARGET_LINUX)
1220 return "Linux";
1221#elif defined(TARGET_WINDOWS)
1222#ifdef NTDDI_VERSION
1223 return "Windows NT";
1224#else // !NTDDI_VERSION
1225 return "unknown Win32 platform";
1226#endif // !NTDDI_VERSION
1227#else
1228 return "unknown platform";
1229#endif
1230}
1231
1232std::string CSysInfo::GetBuildTargetPlatformVersion(void)
1233{
1234#if defined(TARGET_DARWIN_OSX)
1235 return XSTR_MACRO(__MAC_OS_X_VERSION_MIN_REQUIRED);
1236#elif defined(TARGET_DARWIN_IOS)
1237 return XSTR_MACRO(__IPHONE_OS_VERSION_MIN_REQUIRED);
1238#elif defined(TARGET_DARWIN_TVOS)
1239 return XSTR_MACRO(__TV_OS_VERSION_MIN_REQUIRED);
1240#elif defined(TARGET_FREEBSD)
1241 return XSTR_MACRO(__FreeBSD_version);
1242#elif defined(TARGET_ANDROID)
1243 return "API level " XSTR_MACRO(__ANDROID_API__);
1244#elif defined(TARGET_LINUX)
1245 return XSTR_MACRO(LINUX_VERSION_CODE);
1246#elif defined(TARGET_WINDOWS)
1247#ifdef NTDDI_VERSION
1248 return XSTR_MACRO(NTDDI_VERSION);
1249#else // !NTDDI_VERSION
1250 return "(unknown Win32 platform)";
1251#endif // !NTDDI_VERSION
1252#else
1253 return "(unknown platform)";
1254#endif
1255}
1256
1257std::string CSysInfo::GetBuildTargetPlatformVersionDecoded(void)
1258{
1259#if defined(TARGET_DARWIN_OSX)
1260 if (__MAC_OS_X_VERSION_MIN_REQUIRED % 100)
1261 return StringUtils::Format("version %d.%d.%d", __MAC_OS_X_VERSION_MIN_REQUIRED / 10000,
1262 (__MAC_OS_X_VERSION_MIN_REQUIRED / 100) % 100,
1263 __MAC_OS_X_VERSION_MIN_REQUIRED % 100);
1264 else
1265 return StringUtils::Format("version %d.%d", __MAC_OS_X_VERSION_MIN_REQUIRED / 10000,
1266 (__MAC_OS_X_VERSION_MIN_REQUIRED / 100) % 100);
1267#elif defined(TARGET_DARWIN_EMBEDDED)
1268 std::string versionStr = GetBuildTargetPlatformVersion();
1269 static const int major = (std::stoi(versionStr) / 10000) % 100;
1270 static const int minor = (std::stoi(versionStr) / 100) % 100;
1271 static const int rev = std::stoi(versionStr) % 100;
1272 return StringUtils::Format("version %d.%d.%d", major, minor, rev);
1273#elif defined(TARGET_FREEBSD)
1274 // FIXME: should works well starting from FreeBSD 8.1
1275 static const int major = (__FreeBSD_version / 100000) % 100;
1276 static const int minor = (__FreeBSD_version / 1000) % 100;
1277 static const int Rxx = __FreeBSD_version % 1000;
1278 if ((major < 9 && Rxx == 0))
1279 return StringUtils::Format("version %d.%d-RELEASE", major, minor);
1280 if (Rxx >= 500)
1281 return StringUtils::Format("version %d.%d-STABLE", major, minor);
1282
1283 return StringUtils::Format("version %d.%d-CURRENT", major, minor);
1284#elif defined(TARGET_ANDROID)
1285 return "API level " XSTR_MACRO(__ANDROID_API__);
1286#elif defined(TARGET_LINUX)
1287 return StringUtils::Format("version %d.%d.%d", (LINUX_VERSION_CODE >> 16) & 0xFF , (LINUX_VERSION_CODE >> 8) & 0xFF, LINUX_VERSION_CODE & 0xFF);
1288#elif defined(TARGET_WINDOWS)
1289#ifdef NTDDI_VERSION
1290 std::string version(StringUtils::Format("version %d.%d", int(NTDDI_VERSION >> 24) & 0xFF, int(NTDDI_VERSION >> 16) & 0xFF));
1291 if (SPVER(NTDDI_VERSION))
1292 version += StringUtils::Format(" SP%d", int(SPVER(NTDDI_VERSION)));
1293 return version;
1294#else // !NTDDI_VERSION
1295 return "(unknown Win32 platform)";
1296#endif // !NTDDI_VERSION
1297#else
1298 return "(unknown platform)";
1299#endif
1300}
1301
1302std::string CSysInfo::GetBuildTargetCpuFamily(void)
1303{
1304#if defined(__thumb__) || defined(_M_ARMT)
1305 return "ARM (Thumb)";
1306#elif defined(__arm__) || defined(_M_ARM) || defined (__aarch64__)
1307 return "ARM";
1308#elif defined(__mips__) || defined(mips) || defined(__mips)
1309 return "MIPS";
1310#elif defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) || \
1311 defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(_X86_)
1312 return "x86";
1313#elif defined(__s390x__)
1314 return "s390";
1315#elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || defined(__ppc__) || defined(__ppc64__) || defined(_M_PPC)
1316 return "PowerPC";
1317#else
1318 return "unknown CPU family";
1319#endif
1320}
1321
1322std::string CSysInfo::GetUsedCompilerNameAndVer(void)
1323{
1324#if defined(__clang__)
1325#ifdef __clang_version__
1326 return "Clang " __clang_version__;
1327#else // ! __clang_version__
1328 return "Clang " XSTR_MACRO(__clang_major__) "." XSTR_MACRO(__clang_minor__) "." XSTR_MACRO(__clang_patchlevel__);
1329#endif //! __clang_version__
1330#elif defined (__INTEL_COMPILER)
1331 return "Intel Compiler " XSTR_MACRO(__INTEL_COMPILER);
1332#elif defined (__GNUC__)
1333 std::string compilerStr;
1334#ifdef __llvm__
1335 /* Note: this will not detect GCC + DragonEgg */
1336 compilerStr = "llvm-gcc ";
1337#else // __llvm__
1338 compilerStr = "GCC ";
1339#endif // !__llvm__
1340 compilerStr += XSTR_MACRO(__GNUC__) "." XSTR_MACRO(__GNUC_MINOR__) "." XSTR_MACRO(__GNUC_PATCHLEVEL__);
1341 return compilerStr;
1342#elif defined (_MSC_VER)
1343 return "MSVC " XSTR_MACRO(_MSC_FULL_VER);
1344#else
1345 return "unknown compiler";
1346#endif
1347}
1348
1349std::string CSysInfo::GetPrivacyPolicy()
1350{
1351 if (m_privacyPolicy.empty())
1352 {
1353 CFile file;
1354 XFILE::auto_buffer buf;
1355 if (file.LoadFile("special://xbmc/privacy-policy.txt", buf) > 0)
1356 {
1357 std::string strBuf(buf.get(), buf.length());
1358 m_privacyPolicy = strBuf;
1359 }
1360 else
1361 m_privacyPolicy = g_localizeStrings.Get(19055);
1362 }
1363 return m_privacyPolicy;
1364}
1365
1366CSysInfo::WindowsDeviceFamily CSysInfo::GetWindowsDeviceFamily()
1367{
1368#ifdef TARGET_WINDOWS_STORE
1369 auto familyName = AnalyticsInfo::VersionInfo().DeviceFamily();
1370 if (familyName == L"Windows.Desktop")
1371 return WindowsDeviceFamily::Desktop;
1372 else if (familyName == L"Windows.Mobile")
1373 return WindowsDeviceFamily::Mobile;
1374 else if (familyName == L"Windows.Universal")
1375 return WindowsDeviceFamily::IoT;
1376 else if (familyName == L"Windows.Team")
1377 return WindowsDeviceFamily::Surface;
1378 else if (familyName == L"Windows.Xbox")
1379 return WindowsDeviceFamily::Xbox;
1380 else
1381 return WindowsDeviceFamily::Other;
1382#endif // TARGET_WINDOWS_STORE
1383 return WindowsDeviceFamily::Desktop;
1384}
1385
1386CJob *CSysInfo::GetJob() const
1387{
1388 return new CSysInfoJob();
1389}
1390
1391void CSysInfo::OnJobComplete(unsigned int jobID, bool success, CJob *job)
1392{
1393 m_info = static_cast<CSysInfoJob*>(job)->GetData();
1394 CInfoLoader::OnJobComplete(jobID, success, job);
1395}
diff --git a/xbmc/utils/SystemInfo.h b/xbmc/utils/SystemInfo.h
new file mode 100644
index 0000000..c853192
--- /dev/null
+++ b/xbmc/utils/SystemInfo.h
@@ -0,0 +1,156 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "InfoLoader.h"
12#include "settings/ISubSettings.h"
13
14#include <string>
15
16#define KB (1024) // 1 KiloByte (1KB) 1024 Byte (2^10 Byte)
17#define MB (1024*KB) // 1 MegaByte (1MB) 1024 KB (2^10 KB)
18#define GB (1024*MB) // 1 GigaByte (1GB) 1024 MB (2^10 MB)
19#define TB (1024*GB) // 1 TerraByte (1TB) 1024 GB (2^10 GB)
20
21#define MAX_KNOWN_ATTRIBUTES 46
22
23
24class CSysData
25{
26public:
27 enum INTERNET_STATE { UNKNOWN, CONNECTED, DISCONNECTED };
28 CSysData()
29 {
30 Reset();
31 };
32
33 void Reset()
34 {
35 internetState = UNKNOWN;
36 };
37
38 std::string systemUptime;
39 std::string systemTotalUptime;
40 INTERNET_STATE internetState;
41 std::string videoEncoder;
42 std::string cpuFrequency;
43 std::string osVersionInfo;
44 std::string macAddress;
45 std::string batteryLevel;
46};
47
48class CSysInfoJob : public CJob
49{
50public:
51 CSysInfoJob();
52
53 bool DoWork() override;
54 const CSysData &GetData() const;
55
56 static CSysData::INTERNET_STATE GetInternetState();
57private:
58 static bool SystemUpTime(int iInputMinutes, int &iMinutes, int &iHours, int &iDays);
59 static std::string GetSystemUpTime(bool bTotalUptime);
60 static std::string GetMACAddress();
61 static std::string GetVideoEncoder();
62 static std::string GetBatteryLevel();
63
64 CSysData m_info;
65};
66
67class CSysInfo : public CInfoLoader, public ISubSettings
68{
69public:
70 enum WindowsVersion
71 {
72 WindowsVersionUnknown = -1, // Undetected, unsupported Windows version or OS in not Windows
73 WindowsVersionWin7, // Windows 7, Windows Server 2008 R2
74 WindowsVersionWin8, // Windows 8, Windows Server 2012
75 WindowsVersionWin8_1, // Windows 8.1
76 WindowsVersionWin10, // Windows 10
77 WindowsVersionWin10_FCU, // Windows 10 Fall Creators Update
78 /* Insert new Windows versions here, when they'll be known */
79 WindowsVersionFuture = 100 // Future Windows version, not known to code
80 };
81 enum WindowsDeviceFamily
82 {
83 Mobile = 1,
84 Desktop = 2,
85 IoT = 3,
86 Xbox = 4,
87 Surface = 5,
88 Other = 100
89 };
90
91 CSysInfo(void);
92 ~CSysInfo() override;
93
94 bool Load(const TiXmlNode *settings) override;
95 bool Save(TiXmlNode *settings) const override;
96
97 char MD5_Sign[32 + 1];
98
99 static const std::string& GetAppName(void); // the same name as CCompileInfo::GetAppName(), but const ref to std::string
100
101 static std::string GetKernelName(bool emptyIfUnknown = false);
102 static std::string GetKernelVersionFull(void); // full version string, including "-generic", "-RELEASE" etc.
103 static std::string GetKernelVersion(void); // only digits with dots
104 static std::string GetOsName(bool emptyIfUnknown = false);
105 static std::string GetOsVersion(void);
106 static std::string GetOsPrettyNameWithVersion(void);
107 static std::string GetUserAgent();
108 static std::string GetDeviceName();
109 static std::string GetVersion();
110 static std::string GetVersionShort();
111 static std::string GetVersionCode();
112 static std::string GetVersionGit();
113 static std::string GetBuildDate();
114
115 bool HasInternet();
116 bool IsAeroDisabled();
117 static bool IsWindowsVersion(WindowsVersion ver);
118 static bool IsWindowsVersionAtLeast(WindowsVersion ver);
119 static WindowsVersion GetWindowsVersion();
120 static int GetKernelBitness(void);
121 static int GetXbmcBitness(void);
122 static const std::string& GetKernelCpuFamily(void);
123 static std::string GetManufacturerName(void);
124 static std::string GetModelName(void);
125 bool GetDiskSpace(std::string drive,int& iTotal, int& iTotalFree, int& iTotalUsed, int& iPercentFree, int& iPercentUsed);
126 std::string GetHddSpaceInfo(int& percent, int drive, bool shortText=false);
127 std::string GetHddSpaceInfo(int drive, bool shortText=false);
128
129 int GetTotalUptime() const { return m_iSystemTimeTotalUp; }
130 void SetTotalUptime(int uptime) { m_iSystemTimeTotalUp = uptime; }
131
132 static std::string GetBuildTargetPlatformName(void);
133 static std::string GetBuildTargetPlatformVersion(void);
134 static std::string GetBuildTargetPlatformVersionDecoded(void);
135 static std::string GetBuildTargetCpuFamily(void);
136
137 static std::string GetUsedCompilerNameAndVer(void);
138 std::string GetPrivacyPolicy();
139
140 static WindowsDeviceFamily GetWindowsDeviceFamily();
141
142protected:
143 CJob *GetJob() const override;
144 std::string TranslateInfo(int info) const override;
145 void OnJobComplete(unsigned int jobID, bool success, CJob *job) override;
146
147private:
148 CSysData m_info;
149 std::string m_privacyPolicy;
150 static WindowsVersion m_WinVer;
151 int m_iSystemTimeTotalUp; // Uptime in minutes!
152 void Reset();
153};
154
155extern CSysInfo g_sysinfo;
156
diff --git a/xbmc/utils/Temperature.cpp b/xbmc/utils/Temperature.cpp
new file mode 100644
index 0000000..cc6d510
--- /dev/null
+++ b/xbmc/utils/Temperature.cpp
@@ -0,0 +1,481 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Temperature.h"
10
11#include "utils/Archive.h"
12#include "utils/StringUtils.h"
13
14#include <assert.h>
15
16CTemperature::CTemperature()
17{
18 m_value=0.0f;
19 m_valid=false;
20}
21
22CTemperature::CTemperature(const CTemperature& temperature)
23{
24 m_value=temperature.m_value;
25 m_valid=temperature.m_valid;
26}
27
28CTemperature::CTemperature(double value)
29{
30 m_value=value;
31 m_valid=true;
32}
33
34bool CTemperature::operator >(const CTemperature& right) const
35{
36 assert(IsValid());
37 assert(right.IsValid());
38
39 if (!IsValid() || !right.IsValid())
40 return false;
41
42 if (this==&right)
43 return false;
44
45 return (m_value>right.m_value);
46}
47
48bool CTemperature::operator >=(const CTemperature& right) const
49{
50 return operator >(right) || operator ==(right);
51}
52
53bool CTemperature::operator <(const CTemperature& right) const
54{
55 assert(IsValid());
56 assert(right.IsValid());
57
58 if (!IsValid() || !right.IsValid())
59 return false;
60
61 if (this==&right)
62 return false;
63
64 return (m_value<right.m_value);
65}
66
67bool CTemperature::operator <=(const CTemperature& right) const
68{
69 return operator <(right) || operator ==(right);
70}
71
72bool CTemperature::operator ==(const CTemperature& right) const
73{
74 assert(IsValid());
75 assert(right.IsValid());
76
77 if (!IsValid() || !right.IsValid())
78 return false;
79
80 if (this==&right)
81 return true;
82
83 return (m_value==right.m_value);
84}
85
86bool CTemperature::operator !=(const CTemperature& right) const
87{
88 return !operator ==(right.m_value);
89}
90
91CTemperature& CTemperature::operator =(const CTemperature& right)
92{
93 m_valid=right.m_valid;
94 m_value=right.m_value;
95 return *this;
96}
97
98const CTemperature& CTemperature::operator +=(const CTemperature& right)
99{
100 assert(IsValid());
101 assert(right.IsValid());
102
103 m_value+=right.m_value;
104 return *this;
105}
106
107const CTemperature& CTemperature::operator -=(const CTemperature& right)
108{
109 assert(IsValid());
110 assert(right.IsValid());
111
112 m_value-=right.m_value;
113 return *this;
114}
115
116const CTemperature& CTemperature::operator *=(const CTemperature& right)
117{
118 assert(IsValid());
119 assert(right.IsValid());
120
121 m_value*=right.m_value;
122 return *this;
123}
124
125const CTemperature& CTemperature::operator /=(const CTemperature& right)
126{
127 assert(IsValid());
128 assert(right.IsValid());
129
130 m_value/=right.m_value;
131 return *this;
132}
133
134CTemperature CTemperature::operator +(const CTemperature& right) const
135{
136 assert(IsValid());
137 assert(right.IsValid());
138
139 CTemperature temp(*this);
140
141 if (!IsValid() || !right.IsValid())
142 temp.SetValid(false);
143 else
144 temp.m_value+=right.m_value;
145
146 return temp;
147}
148
149CTemperature CTemperature::operator -(const CTemperature& right) const
150{
151 assert(IsValid());
152 assert(right.IsValid());
153
154 CTemperature temp(*this);
155 if (!IsValid() || !right.IsValid())
156 temp.SetValid(false);
157 else
158 temp.m_value-=right.m_value;
159
160 return temp;
161}
162
163CTemperature CTemperature::operator *(const CTemperature& right) const
164{
165 assert(IsValid());
166 assert(right.IsValid());
167
168 CTemperature temp(*this);
169 if (!IsValid() || !right.IsValid())
170 temp.SetValid(false);
171 else
172 temp.m_value*=right.m_value;
173 return temp;
174}
175
176CTemperature CTemperature::operator /(const CTemperature& right) const
177{
178 assert(IsValid());
179 assert(right.IsValid());
180
181 CTemperature temp(*this);
182 if (!IsValid() || !right.IsValid())
183 temp.SetValid(false);
184 else
185 temp.m_value/=right.m_value;
186 return temp;
187}
188
189CTemperature& CTemperature::operator ++()
190{
191 assert(IsValid());
192
193 m_value++;
194 return *this;
195}
196
197CTemperature& CTemperature::operator --()
198{
199 assert(IsValid());
200
201 m_value--;
202 return *this;
203}
204
205CTemperature CTemperature::operator ++(int)
206{
207 assert(IsValid());
208
209 CTemperature temp(*this);
210 m_value++;
211 return temp;
212}
213
214CTemperature CTemperature::operator --(int)
215{
216 assert(IsValid());
217
218 CTemperature temp(*this);
219 m_value--;
220 return temp;
221}
222
223bool CTemperature::operator >(double right) const
224{
225 assert(IsValid());
226
227 if (!IsValid())
228 return false;
229
230 return (m_value>right);
231}
232
233bool CTemperature::operator >=(double right) const
234{
235 return operator >(right) || operator ==(right);
236}
237
238bool CTemperature::operator <(double right) const
239{
240 assert(IsValid());
241
242 if (!IsValid())
243 return false;
244
245 return (m_value<right);
246}
247
248bool CTemperature::operator <=(double right) const
249{
250 return operator <(right) || operator ==(right);
251}
252
253bool CTemperature::operator ==(double right) const
254{
255 if (!IsValid())
256 return false;
257
258 return (m_value==right);
259}
260
261bool CTemperature::operator !=(double right) const
262{
263 return !operator ==(right);
264}
265
266const CTemperature& CTemperature::operator +=(double right)
267{
268 assert(IsValid());
269
270 m_value+=right;
271 return *this;
272}
273
274const CTemperature& CTemperature::operator -=(double right)
275{
276 assert(IsValid());
277
278 m_value-=right;
279 return *this;
280}
281
282const CTemperature& CTemperature::operator *=(double right)
283{
284 assert(IsValid());
285
286 m_value*=right;
287 return *this;
288}
289
290const CTemperature& CTemperature::operator /=(double right)
291{
292 assert(IsValid());
293
294 m_value/=right;
295 return *this;
296}
297
298CTemperature CTemperature::operator +(double right) const
299{
300 assert(IsValid());
301
302 CTemperature temp(*this);
303 temp.m_value+=right;
304 return temp;
305}
306
307CTemperature CTemperature::operator -(double right) const
308{
309 assert(IsValid());
310
311 CTemperature temp(*this);
312 temp.m_value-=right;
313 return temp;
314}
315
316CTemperature CTemperature::operator *(double right) const
317{
318 assert(IsValid());
319
320 CTemperature temp(*this);
321 temp.m_value*=right;
322 return temp;
323}
324
325CTemperature CTemperature::operator /(double right) const
326{
327 assert(IsValid());
328
329 CTemperature temp(*this);
330 temp.m_value/=right;
331 return temp;
332}
333
334CTemperature CTemperature::CreateFromFahrenheit(double value)
335{
336 return CTemperature(value);
337}
338
339CTemperature CTemperature::CreateFromReaumur(double value)
340{
341 return CTemperature(value*2.25f+32.0f);
342}
343
344CTemperature CTemperature::CreateFromRankine(double value)
345{
346 return CTemperature(value-459.67f);
347}
348
349CTemperature CTemperature::CreateFromRomer(double value)
350{
351 return CTemperature((value-7.5f)*24.0f/7.0f+32.0f);
352}
353
354CTemperature CTemperature::CreateFromDelisle(double value)
355{
356 CTemperature temp(212.0f - value * 1.2f);
357 return temp;
358}
359
360CTemperature CTemperature::CreateFromNewton(double value)
361{
362 return CTemperature(value*60.0f/11.0f+32.0f);
363}
364
365CTemperature CTemperature::CreateFromCelsius(double value)
366{
367 return CTemperature(value*1.8f+32.0f);
368}
369
370CTemperature CTemperature::CreateFromKelvin(double value)
371{
372 return CTemperature((value - 273.15) * 1.8 + 32.0);
373}
374
375void CTemperature::Archive(CArchive& ar)
376{
377 if (ar.IsStoring())
378 {
379 ar<<m_value;
380 ar<<m_valid;
381 }
382 else
383 {
384 ar>>m_value;
385 ar>>m_valid;
386 }
387}
388
389bool CTemperature::IsValid() const
390{
391 return m_valid;
392}
393
394double CTemperature::ToFahrenheit() const
395{
396 return m_value;
397}
398
399double CTemperature::ToKelvin() const
400{
401 return (m_value+459.67F)/1.8f;
402}
403
404double CTemperature::ToCelsius() const
405{
406 return (m_value-32.0f)/1.8f;
407}
408
409double CTemperature::ToReaumur() const
410{
411 return (m_value-32.0f)/2.25f;
412}
413
414double CTemperature::ToRankine() const
415{
416 return m_value+459.67f;
417}
418
419double CTemperature::ToRomer() const
420{
421 return (m_value-32.0f)*7.0f/24.0f+7.5f;
422}
423
424double CTemperature::ToDelisle() const
425{
426 return (212.f-m_value)*5.0f/6.0f;
427}
428
429double CTemperature::ToNewton() const
430{
431 return (m_value-32.0f)*11.0f/60.0f;
432}
433
434double CTemperature::To(Unit temperatureUnit) const
435{
436 if (!IsValid())
437 return 0;
438
439 double value = 0.0;
440
441 switch (temperatureUnit)
442 {
443 case UnitFahrenheit:
444 value=ToFahrenheit();
445 break;
446 case UnitKelvin:
447 value=ToKelvin();
448 break;
449 case UnitCelsius:
450 value=ToCelsius();
451 break;
452 case UnitReaumur:
453 value=ToReaumur();
454 break;
455 case UnitRankine:
456 value=ToRankine();
457 break;
458 case UnitRomer:
459 value=ToRomer();
460 break;
461 case UnitDelisle:
462 value=ToDelisle();
463 break;
464 case UnitNewton:
465 value=ToNewton();
466 break;
467 default:
468 assert(false);
469 break;
470 }
471 return value;
472}
473
474// Returns temperature as localized string
475std::string CTemperature::ToString(Unit temperatureUnit) const
476{
477 if (!IsValid())
478 return "";
479
480 return StringUtils::Format("%2.0f", To(temperatureUnit));
481}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/IArchivable.h"
12
13#include <string>
14
15class CTemperature : public IArchivable
16{
17public:
18 CTemperature();
19 CTemperature(const CTemperature& temperature);
20
21 typedef enum Unit
22 {
23 UnitFahrenheit = 0,
24 UnitKelvin,
25 UnitCelsius,
26 UnitReaumur,
27 UnitRankine,
28 UnitRomer,
29 UnitDelisle,
30 UnitNewton
31 } Unit;
32
33 static CTemperature CreateFromFahrenheit(double value);
34 static CTemperature CreateFromKelvin(double value);
35 static CTemperature CreateFromCelsius(double value);
36 static CTemperature CreateFromReaumur(double value);
37 static CTemperature CreateFromRankine(double value);
38 static CTemperature CreateFromRomer(double value);
39 static CTemperature CreateFromDelisle(double value);
40 static CTemperature CreateFromNewton(double value);
41
42 bool operator >(const CTemperature& right) const;
43 bool operator >=(const CTemperature& right) const;
44 bool operator <(const CTemperature& right) const;
45 bool operator <=(const CTemperature& right) const;
46 bool operator ==(const CTemperature& right) const;
47 bool operator !=(const CTemperature& right) const;
48
49 CTemperature& operator =(const CTemperature& right);
50 const CTemperature& operator +=(const CTemperature& right);
51 const CTemperature& operator -=(const CTemperature& right);
52 const CTemperature& operator *=(const CTemperature& right);
53 const CTemperature& operator /=(const CTemperature& right);
54 CTemperature operator +(const CTemperature& right) const;
55 CTemperature operator -(const CTemperature& right) const;
56 CTemperature operator *(const CTemperature& right) const;
57 CTemperature operator /(const CTemperature& right) const;
58
59 bool operator >(double right) const;
60 bool operator >=(double right) const;
61 bool operator <(double right) const;
62 bool operator <=(double right) const;
63 bool operator ==(double right) const;
64 bool operator !=(double right) const;
65
66 const CTemperature& operator +=(double right);
67 const CTemperature& operator -=(double right);
68 const CTemperature& operator *=(double right);
69 const CTemperature& operator /=(double right);
70 CTemperature operator +(double right) const;
71 CTemperature operator -(double right) const;
72 CTemperature operator *(double right) const;
73 CTemperature operator /(double right) const;
74
75 CTemperature& operator ++();
76 CTemperature& operator --();
77 CTemperature operator ++(int);
78 CTemperature operator --(int);
79
80 void Archive(CArchive& ar) override;
81
82 bool IsValid() const;
83 void SetValid(bool valid) { m_valid = valid; }
84
85 double ToFahrenheit() const;
86 double ToKelvin() const;
87 double ToCelsius() const;
88 double ToReaumur() const;
89 double ToRankine() const;
90 double ToRomer() const;
91 double ToDelisle() const;
92 double ToNewton() const;
93
94 double To(Unit temperatureUnit) const;
95 std::string ToString(Unit temperatureUnit) const;
96
97protected:
98 explicit CTemperature(double value);
99
100 double m_value; // we store as fahrenheit
101 bool m_valid;
102};
103
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "TextSearch.h"
10
11#include "StringUtils.h"
12
13CTextSearch::CTextSearch(const std::string &strSearchTerms, bool bCaseSensitive /* = false */, TextSearchDefault defaultSearchMode /* = SEARCH_DEFAULT_OR */)
14{
15 m_bCaseSensitive = bCaseSensitive;
16 ExtractSearchTerms(strSearchTerms, defaultSearchMode);
17}
18
19bool CTextSearch::IsValid(void) const
20{
21 return m_AND.size() > 0 || m_OR.size() > 0 || m_NOT.size() > 0;
22}
23
24bool CTextSearch::Search(const std::string &strHaystack) const
25{
26 if (strHaystack.empty() || !IsValid())
27 return false;
28
29 std::string strSearch(strHaystack);
30 if (!m_bCaseSensitive)
31 StringUtils::ToLower(strSearch);
32
33 /* check whether any of the NOT terms matches and return false if there's a match */
34 for (unsigned int iNotPtr = 0; iNotPtr < m_NOT.size(); iNotPtr++)
35 {
36 if (strSearch.find(m_NOT.at(iNotPtr)) != std::string::npos)
37 return false;
38 }
39
40 /* check whether at least one of the OR terms matches and return false if there's no match found */
41 bool bFound(m_OR.empty());
42 for (unsigned int iOrPtr = 0; iOrPtr < m_OR.size(); iOrPtr++)
43 {
44 if (strSearch.find(m_OR.at(iOrPtr)) != std::string::npos)
45 {
46 bFound = true;
47 break;
48 }
49 }
50 if (!bFound)
51 return false;
52
53 /* check whether all of the AND terms match and return false if one of them wasn't found */
54 for (unsigned int iAndPtr = 0; iAndPtr < m_AND.size(); iAndPtr++)
55 {
56 if (strSearch.find(m_AND[iAndPtr]) == std::string::npos)
57 return false;
58 }
59
60 /* all ok, return true */
61 return true;
62}
63
64void CTextSearch::GetAndCutNextTerm(std::string &strSearchTerm, std::string &strNextTerm)
65{
66 std::string strFindNext(" ");
67
68 if (StringUtils::EndsWith(strSearchTerm, "\""))
69 {
70 strSearchTerm.erase(0, 1);
71 strFindNext = "\"";
72 }
73
74 size_t iNextPos = strSearchTerm.find(strFindNext);
75 if (iNextPos != std::string::npos)
76 {
77 strNextTerm = strSearchTerm.substr(0, iNextPos);
78 strSearchTerm.erase(0, iNextPos + 1);
79 }
80 else
81 {
82 strNextTerm = strSearchTerm;
83 strSearchTerm.clear();
84 }
85}
86
87void CTextSearch::ExtractSearchTerms(const std::string &strSearchTerm, TextSearchDefault defaultSearchMode)
88{
89 std::string strParsedSearchTerm(strSearchTerm);
90 StringUtils::Trim(strParsedSearchTerm);
91
92 if (!m_bCaseSensitive)
93 StringUtils::ToLower(strParsedSearchTerm);
94
95 bool bNextAND(defaultSearchMode == SEARCH_DEFAULT_AND);
96 bool bNextOR(defaultSearchMode == SEARCH_DEFAULT_OR);
97 bool bNextNOT(defaultSearchMode == SEARCH_DEFAULT_NOT);
98
99 while (strParsedSearchTerm.length() > 0)
100 {
101 StringUtils::TrimLeft(strParsedSearchTerm);
102
103 if (StringUtils::StartsWith(strParsedSearchTerm, "!") || StringUtils::StartsWithNoCase(strParsedSearchTerm, "not"))
104 {
105 std::string strDummy;
106 GetAndCutNextTerm(strParsedSearchTerm, strDummy);
107 bNextNOT = true;
108 }
109 else if (StringUtils::StartsWith(strParsedSearchTerm, "+") || StringUtils::StartsWithNoCase(strParsedSearchTerm, "and"))
110 {
111 std::string strDummy;
112 GetAndCutNextTerm(strParsedSearchTerm, strDummy);
113 bNextAND = true;
114 }
115 else if (StringUtils::StartsWith(strParsedSearchTerm, "|") || StringUtils::StartsWithNoCase(strParsedSearchTerm, "or"))
116 {
117 std::string strDummy;
118 GetAndCutNextTerm(strParsedSearchTerm, strDummy);
119 bNextOR = true;
120 }
121 else
122 {
123 std::string strTerm;
124 GetAndCutNextTerm(strParsedSearchTerm, strTerm);
125 if (strTerm.length() > 0)
126 {
127 if (bNextAND)
128 m_AND.push_back(strTerm);
129 else if (bNextOR)
130 m_OR.push_back(strTerm);
131 else if (bNextNOT)
132 m_NOT.push_back(strTerm);
133 }
134 else
135 {
136 break;
137 }
138
139 bNextAND = (defaultSearchMode == SEARCH_DEFAULT_AND);
140 bNextOR = (defaultSearchMode == SEARCH_DEFAULT_OR);
141 bNextNOT = (defaultSearchMode == SEARCH_DEFAULT_NOT);
142 }
143
144 StringUtils::TrimLeft(strParsedSearchTerm);
145 }
146}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12#include <vector>
13
14typedef enum TextSearchDefault
15{
16 SEARCH_DEFAULT_AND = 0,
17 SEARCH_DEFAULT_OR,
18 SEARCH_DEFAULT_NOT
19} TextSearchDefault;
20
21class CTextSearch final
22{
23public:
24 CTextSearch(const std::string &strSearchTerms, bool bCaseSensitive = false, TextSearchDefault defaultSearchMode = SEARCH_DEFAULT_OR);
25
26 bool Search(const std::string &strHaystack) const;
27 bool IsValid(void) const;
28
29private:
30 static void GetAndCutNextTerm(std::string &strSearchTerm, std::string &strNextTerm);
31 void ExtractSearchTerms(const std::string &strSearchTerm, TextSearchDefault defaultSearchMode);
32
33 bool m_bCaseSensitive;
34 std::vector<std::string> m_AND;
35 std::vector<std::string> m_OR;
36 std::vector<std::string> m_NOT;
37};
diff --git a/xbmc/utils/TimeUtils.cpp b/xbmc/utils/TimeUtils.cpp
new file mode 100644
index 0000000..16d75b9
--- /dev/null
+++ b/xbmc/utils/TimeUtils.cpp
@@ -0,0 +1,101 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "TimeUtils.h"
10#include "XBDateTime.h"
11#include "threads/SystemClock.h"
12#include "windowing/GraphicContext.h"
13
14#if defined(TARGET_DARWIN)
15#include <mach/mach_time.h>
16#include <CoreVideo/CVHostTime.h>
17#elif defined(TARGET_WINDOWS)
18#include <windows.h>
19#else
20#include <time.h>
21#endif
22
23int64_t CurrentHostCounter(void)
24{
25#if defined(TARGET_DARWIN)
26 return( (int64_t)CVGetCurrentHostTime() );
27#elif defined(TARGET_WINDOWS)
28 LARGE_INTEGER PerformanceCount;
29 QueryPerformanceCounter(&PerformanceCount);
30 return( (int64_t)PerformanceCount.QuadPart );
31#else
32 struct timespec now;
33#if defined(CLOCK_MONOTONIC_RAW) && !defined(TARGET_ANDROID)
34 clock_gettime(CLOCK_MONOTONIC_RAW, &now);
35#else
36 clock_gettime(CLOCK_MONOTONIC, &now);
37#endif // CLOCK_MONOTONIC_RAW && !TARGET_ANDROID
38 return( ((int64_t)now.tv_sec * 1000000000L) + now.tv_nsec );
39#endif
40}
41
42int64_t CurrentHostFrequency(void)
43{
44#if defined(TARGET_DARWIN)
45 return( (int64_t)CVGetHostClockFrequency() );
46#elif defined(TARGET_WINDOWS)
47 LARGE_INTEGER Frequency;
48 QueryPerformanceFrequency(&Frequency);
49 return( (int64_t)Frequency.QuadPart );
50#else
51 return( (int64_t)1000000000L );
52#endif
53}
54
55unsigned int CTimeUtils::frameTime = 0;
56
57void CTimeUtils::UpdateFrameTime(bool flip)
58{
59 unsigned int currentTime = XbmcThreads::SystemClockMillis();
60 unsigned int last = frameTime;
61 while (frameTime < currentTime)
62 {
63 frameTime += (unsigned int)(1000 / CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS());
64 // observe wrap around
65 if (frameTime < last)
66 break;
67 }
68}
69
70unsigned int CTimeUtils::GetFrameTime()
71{
72 return frameTime;
73}
74
75CDateTime CTimeUtils::GetLocalTime(time_t time)
76{
77 CDateTime result;
78
79 tm *local;
80#ifdef HAVE_LOCALTIME_R
81 tm res = {};
82 local = localtime_r(&time, &res); // Conversion to local time
83#else
84 local = localtime(&time); // Conversion to local time
85#endif
86 /*
87 * Microsoft implementation of localtime returns NULL if on or before epoch.
88 * http://msdn.microsoft.com/en-us/library/bf12f0hc(VS.80).aspx
89 */
90 if (local)
91 result = *local;
92 else
93 result = time; // Use the original time as close enough.
94
95 return result;
96}
97
98std::string CTimeUtils::WithoutSeconds(const std::string hhmmss)
99{
100 return hhmmss.substr(0, 5);
101}
diff --git a/xbmc/utils/TimeUtils.h b/xbmc/utils/TimeUtils.h
new file mode 100644
index 0000000..078753e
--- /dev/null
+++ b/xbmc/utils/TimeUtils.h
@@ -0,0 +1,45 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12#include <string>
13#include <time.h>
14
15class CDateTime;
16
17int64_t CurrentHostCounter(void);
18int64_t CurrentHostFrequency(void);
19
20class CTimeUtils
21{
22public:
23
24 /*!
25 * @brief Update the time frame
26 * @note Not threadsafe
27 */
28 static void UpdateFrameTime(bool flip);
29
30 /*!
31 * @brief Returns the frame time in MS
32 * @note Not threadsafe
33 */
34 static unsigned int GetFrameTime();
35 static CDateTime GetLocalTime(time_t time);
36
37 /*!
38 * @brief Returns a time string without seconds, i.e: HH:MM
39 * @param hhmmss Time string in the format HH:MM:SS
40 */
41 static std::string WithoutSeconds(const std::string hhmmss);
42private:
43 static unsigned int frameTime;
44};
45
diff --git a/xbmc/utils/TransformMatrix.h b/xbmc/utils/TransformMatrix.h
new file mode 100644
index 0000000..34c2092
--- /dev/null
+++ b/xbmc/utils/TransformMatrix.h
@@ -0,0 +1,246 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/Color.h"
12
13#include <algorithm>
14#include <math.h>
15#include <memory>
16#include <string.h>
17
18#ifdef __GNUC__
19// under gcc, inline will only take place if optimizations are applied (-O). this will force inline even with optimizations.
20#define XBMC_FORCE_INLINE __attribute__((always_inline))
21#else
22#define XBMC_FORCE_INLINE
23#endif
24
25class TransformMatrix
26{
27public:
28 TransformMatrix()
29 {
30 Reset();
31 };
32 void Reset()
33 {
34 m[0][0] = 1.0f; m[0][1] = m[0][2] = m[0][3] = 0.0f;
35 m[1][0] = m[1][2] = m[1][3] = 0.0f; m[1][1] = 1.0f;
36 m[2][0] = m[2][1] = m[2][3] = 0.0f; m[2][2] = 1.0f;
37 alpha = 1.0f;
38 identity = true;
39 };
40 static TransformMatrix CreateTranslation(float transX, float transY, float transZ = 0)
41 {
42 TransformMatrix translation;
43 translation.SetTranslation(transX, transY, transZ);
44 return translation;
45 }
46 void SetTranslation(float transX, float transY, float transZ)
47 {
48 m[0][1] = m[0][2] = 0.0f; m[0][0] = 1.0f; m[0][3] = transX;
49 m[1][0] = m[1][2] = 0.0f; m[1][1] = 1.0f; m[1][3] = transY;
50 m[2][0] = m[2][1] = 0.0f; m[2][2] = 1.0f; m[2][3] = transZ;
51 alpha = 1.0f;
52 identity = (transX == 0 && transY == 0 && transZ == 0);
53 }
54 static TransformMatrix CreateScaler(float scaleX, float scaleY, float scaleZ = 1.0f)
55 {
56 TransformMatrix scaler;
57 scaler.m[0][0] = scaleX;
58 scaler.m[1][1] = scaleY;
59 scaler.m[2][2] = scaleZ;
60 scaler.identity = (scaleX == 1 && scaleY == 1 && scaleZ == 1);
61 return scaler;
62 };
63 void SetScaler(float scaleX, float scaleY, float centerX, float centerY)
64 {
65 // Trans(centerX,centerY,centerZ)*Scale(scaleX,scaleY,scaleZ)*Trans(-centerX,-centerY,-centerZ)
66 float centerZ = 0.0f, scaleZ = 1.0f;
67 m[0][0] = scaleX; m[0][1] = 0.0f; m[0][2] = 0.0f; m[0][3] = centerX*(1-scaleX);
68 m[1][0] = 0.0f; m[1][1] = scaleY; m[1][2] = 0.0f; m[1][3] = centerY*(1-scaleY);
69 m[2][0] = 0.0f; m[2][1] = 0.0f; m[2][2] = scaleZ; m[2][3] = centerZ*(1-scaleZ);
70 alpha = 1.0f;
71 identity = (scaleX == 1 && scaleY == 1);
72 };
73 void SetXRotation(float angle, float y, float z, float ar = 1.0f)
74 { // angle about the X axis, centered at y,z where our coordinate system has aspect ratio ar.
75 // Trans(0,y,z)*Scale(1,1/ar,1)*RotateX(angle)*Scale(ar,1,1)*Trans(0,-y,-z);
76 float c = cos(angle); float s = sin(angle);
77 m[0][0] = ar; m[0][1] = 0.0f; m[0][2] = 0.0f; m[0][3] = 0.0f;
78 m[1][0] = 0.0f; m[1][1] = c/ar; m[1][2] = -s/ar; m[1][3] = (-y*c+s*z)/ar + y;
79 m[2][0] = 0.0f; m[2][1] = s; m[2][2] = c; m[2][3] = (-y*s-c*z) + z;
80 alpha = 1.0f;
81 identity = (angle == 0);
82 }
83 void SetYRotation(float angle, float x, float z, float ar = 1.0f)
84 { // angle about the Y axis, centered at x,z where our coordinate system has aspect ratio ar.
85 // Trans(x,0,z)*Scale(1/ar,1,1)*RotateY(angle)*Scale(ar,1,1)*Trans(-x,0,-z);
86 float c = cos(angle); float s = sin(angle);
87 m[0][0] = c; m[0][1] = 0.0f; m[0][2] = -s/ar; m[0][3] = -x*c + s*z/ar + x;
88 m[1][0] = 0.0f; m[1][1] = 1.0f; m[1][2] = 0.0f; m[1][3] = 0.0f;
89 m[2][0] = ar*s; m[2][1] = 0.0f; m[2][2] = c; m[2][3] = -ar*x*s - c*z + z;
90 alpha = 1.0f;
91 identity = (angle == 0);
92 }
93 static TransformMatrix CreateZRotation(float angle, float x, float y, float ar = 1.0f)
94 { // angle about the Z axis, centered at x,y where our coordinate system has aspect ratio ar.
95 // Trans(x,y,0)*Scale(1/ar,1,1)*RotateZ(angle)*Scale(ar,1,1)*Trans(-x,-y,0)
96 TransformMatrix rot;
97 rot.SetZRotation(angle, x, y, ar);
98 return rot;
99 }
100 void SetZRotation(float angle, float x, float y, float ar = 1.0f)
101 { // angle about the Z axis, centered at x,y where our coordinate system has aspect ratio ar.
102 // Trans(x,y,0)*Scale(1/ar,1,1)*RotateZ(angle)*Scale(ar,1,1)*Trans(-x,-y,0)
103 float c = cos(angle); float s = sin(angle);
104 m[0][0] = c; m[0][1] = -s/ar; m[0][2] = 0.0f; m[0][3] = -x*c + s*y/ar + x;
105 m[1][0] = s*ar; m[1][1] = c; m[1][2] = 0.0f; m[1][3] = -ar*x*s - c*y + y;
106 m[2][0] = 0.0f; m[2][1] = 0.0f; m[2][2] = 1.0f; m[2][3] = 0.0f;
107 alpha = 1.0f;
108 identity = (angle == 0);
109 }
110 static TransformMatrix CreateFader(float a)
111 {
112 TransformMatrix fader;
113 fader.SetFader(a);
114 return fader;
115 }
116 void SetFader(float a)
117 {
118 m[0][0] = 1.0f; m[0][1] = 0.0f; m[0][2] = 0.0f; m[0][3] = 0.0f;
119 m[1][0] = 0.0f; m[1][1] = 1.0f; m[1][2] = 0.0f; m[1][3] = 0.0f;
120 m[2][0] = 0.0f; m[2][1] = 0.0f; m[2][2] = 1.0f; m[2][3] = 0.0f;
121 alpha = a;
122 identity = (a == 1.0f);
123 }
124
125 // multiplication operators
126 const TransformMatrix &operator *=(const TransformMatrix &right)
127 {
128 if (right.identity)
129 return *this;
130 if (identity)
131 {
132 *this = right;
133 return *this;
134 }
135 float t00 = m[0][0] * right.m[0][0] + m[0][1] * right.m[1][0] + m[0][2] * right.m[2][0];
136 float t01 = m[0][0] * right.m[0][1] + m[0][1] * right.m[1][1] + m[0][2] * right.m[2][1];
137 float t02 = m[0][0] * right.m[0][2] + m[0][1] * right.m[1][2] + m[0][2] * right.m[2][2];
138 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];
139 m[0][0] = t00; m[0][1] = t01; m[0][2] = t02;
140 t00 = m[1][0] * right.m[0][0] + m[1][1] * right.m[1][0] + m[1][2] * right.m[2][0];
141 t01 = m[1][0] * right.m[0][1] + m[1][1] * right.m[1][1] + m[1][2] * right.m[2][1];
142 t02 = m[1][0] * right.m[0][2] + m[1][1] * right.m[1][2] + m[1][2] * right.m[2][2];
143 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];
144 m[1][0] = t00; m[1][1] = t01; m[1][2] = t02;
145 t00 = m[2][0] * right.m[0][0] + m[2][1] * right.m[1][0] + m[2][2] * right.m[2][0];
146 t01 = m[2][0] * right.m[0][1] + m[2][1] * right.m[1][1] + m[2][2] * right.m[2][1];
147 t02 = m[2][0] * right.m[0][2] + m[2][1] * right.m[1][2] + m[2][2] * right.m[2][2];
148 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];
149 m[2][0] = t00; m[2][1] = t01; m[2][2] = t02;
150 alpha *= right.alpha;
151 identity = false;
152 return *this;
153 }
154
155 TransformMatrix operator *(const TransformMatrix &right) const
156 {
157 if (right.identity)
158 return *this;
159 if (identity)
160 return right;
161 TransformMatrix result;
162 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];
163 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];
164 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];
165 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];
166 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];
167 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];
168 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];
169 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];
170 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];
171 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];
172 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];
173 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];
174 result.alpha = alpha * right.alpha;
175 result.identity = false;
176 return result;
177 }
178
179 inline void TransformPosition(float &x, float &y, float &z) const XBMC_FORCE_INLINE
180 {
181 float newX = m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3];
182 float newY = m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3];
183 z = m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3];
184 y = newY;
185 x = newX;
186 }
187
188 inline void TransformPositionUnscaled(float &x, float &y, float &z) const XBMC_FORCE_INLINE
189 {
190 float n;
191 // calculate the norm of the transformed (but not translated) vectors involved
192 n = sqrt(m[0][0]*m[0][0] + m[0][1]*m[0][1] + m[0][2]*m[0][2]);
193 float newX = (m[0][0] * x + m[0][1] * y + m[0][2] * z)/n + m[0][3];
194 n = sqrt(m[1][0]*m[1][0] + m[1][1]*m[1][1] + m[1][2]*m[1][2]);
195 float newY = (m[1][0] * x + m[1][1] * y + m[1][2] * z)/n + m[1][3];
196 n = sqrt(m[2][0]*m[2][0] + m[2][1]*m[2][1] + m[2][2]*m[2][2]);
197 float newZ = (m[2][0] * x + m[2][1] * y + m[2][2] * z)/n + m[2][3];
198 z = newZ;
199 y = newY;
200 x = newX;
201 }
202
203 inline void InverseTransformPosition(float &x, float &y) const XBMC_FORCE_INLINE
204 { // used for mouse - no way to find z
205 x -= m[0][3]; y -= m[1][3];
206 float detM = m[0][0]*m[1][1] - m[0][1]*m[1][0];
207 float newX = (m[1][1] * x - m[0][1] * y)/detM;
208 y = (-m[1][0] * x + m[0][0] * y)/detM;
209 x = newX;
210 }
211
212 inline float TransformXCoord(float x, float y, float z) const XBMC_FORCE_INLINE
213 {
214 return m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3];
215 }
216
217 inline float TransformYCoord(float x, float y, float z) const XBMC_FORCE_INLINE
218 {
219 return m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3];
220 }
221
222 inline float TransformZCoord(float x, float y, float z) const XBMC_FORCE_INLINE
223 {
224 return m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3];
225 }
226
227 inline UTILS::Color TransformAlpha(UTILS::Color color) const XBMC_FORCE_INLINE
228 {
229 return static_cast<UTILS::Color>(color * alpha);
230 }
231
232 float m[3][4];
233 float alpha;
234 bool identity;
235};
236
237inline bool operator==(const TransformMatrix &a, const TransformMatrix &b)
238{
239 return a.alpha == b.alpha && ((a.identity && b.identity) ||
240 (!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])));
241}
242
243inline bool operator!=(const TransformMatrix &a, const TransformMatrix &b)
244{
245 return !operator==(a, b);
246}
diff --git a/xbmc/utils/UDMABufferObject.cpp b/xbmc/utils/UDMABufferObject.cpp
new file mode 100644
index 0000000..b488d78
--- /dev/null
+++ b/xbmc/utils/UDMABufferObject.cpp
@@ -0,0 +1,201 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "UDMABufferObject.h"
10
11#include "utils/BufferObjectFactory.h"
12#include "utils/log.h"
13
14#include <drm_fourcc.h>
15#include <linux/udmabuf.h>
16#include <sys/ioctl.h>
17#include <sys/mman.h>
18
19namespace
20{
21
22const auto PAGESIZE = getpagesize();
23
24int RoundUp(int num, int factor)
25{
26 return num + factor - 1 - (num - 1) % factor;
27}
28
29} // namespace
30
31std::unique_ptr<CBufferObject> CUDMABufferObject::Create()
32{
33 return std::make_unique<CUDMABufferObject>();
34}
35
36void CUDMABufferObject::Register()
37{
38 int fd = open("/dev/udmabuf", O_RDWR);
39 if (fd < 0)
40 {
41 CLog::Log(LOGDEBUG, "CUDMABufferObject::{} - unable to open /dev/udmabuf: {}", __FUNCTION__,
42 strerror(errno));
43 return;
44 }
45
46 close(fd);
47
48 CBufferObjectFactory::RegisterBufferObject(CUDMABufferObject::Create);
49}
50
51CUDMABufferObject::~CUDMABufferObject()
52{
53 ReleaseMemory();
54 DestroyBufferObject();
55
56 int ret = close(m_udmafd);
57 if (ret < 0)
58 CLog::Log(LOGERROR, "CUDMABufferObject::{} - close /dev/udmabuf failed, errno={}", __FUNCTION__,
59 strerror(errno));
60
61 m_udmafd = -1;
62}
63
64bool CUDMABufferObject::CreateBufferObject(uint32_t format, uint32_t width, uint32_t height)
65{
66 if (m_fd >= 0)
67 return true;
68
69 uint32_t bpp{1};
70
71 switch (format)
72 {
73 case DRM_FORMAT_ARGB8888:
74 bpp = 4;
75 break;
76 case DRM_FORMAT_ARGB1555:
77 case DRM_FORMAT_RGB565:
78 bpp = 2;
79 break;
80 default:
81 throw std::runtime_error("CUDMABufferObject: pixel format not implemented");
82 }
83
84 m_stride = width * bpp;
85
86 return CreateBufferObject(width * height * bpp);
87}
88
89bool CUDMABufferObject::CreateBufferObject(uint64_t size)
90{
91 // Must be rounded to the system page size
92 m_size = RoundUp(size, PAGESIZE);
93
94 m_memfd = memfd_create("kodi", MFD_CLOEXEC | MFD_ALLOW_SEALING);
95 if (m_memfd < 0)
96 {
97 CLog::Log(LOGERROR, "CUDMABufferObject::{} - memfd_create failed: {}", __FUNCTION__,
98 strerror(errno));
99 return false;
100 }
101
102 if (ftruncate(m_memfd, m_size) < 0)
103 {
104 CLog::Log(LOGERROR, "CUDMABufferObject::{} - ftruncate failed: {}", __FUNCTION__,
105 strerror(errno));
106 return false;
107 }
108
109 if (fcntl(m_memfd, F_ADD_SEALS, F_SEAL_SHRINK) < 0)
110 {
111 CLog::Log(LOGERROR, "CUDMABufferObject::{} - fcntl failed: {}", __FUNCTION__, strerror(errno));
112 close(m_memfd);
113 return false;
114 }
115
116 if (m_udmafd < 0)
117 {
118 m_udmafd = open("/dev/udmabuf", O_RDWR);
119 if (m_udmafd < 0)
120 {
121 CLog::Log(LOGERROR, "CUDMABufferObject::{} - unable to open /dev/udmabuf: {}", __FUNCTION__,
122 strerror(errno));
123 close(m_memfd);
124 return false;
125 }
126 }
127
128 struct udmabuf_create_item create = {
129 .memfd = static_cast<uint32_t>(m_memfd),
130 .offset = 0,
131 .size = m_size,
132 };
133
134 m_fd = ioctl(m_udmafd, UDMABUF_CREATE, &create);
135 if (m_fd < 0)
136 {
137 CLog::Log(LOGERROR, "CUDMABufferObject::{} - ioctl UDMABUF_CREATE failed: {}", __FUNCTION__,
138 strerror(errno));
139 close(m_memfd);
140 return false;
141 }
142
143 return true;
144}
145
146void CUDMABufferObject::DestroyBufferObject()
147{
148 if (m_fd < 0)
149 return;
150
151 int ret = close(m_fd);
152 if (ret < 0)
153 CLog::Log(LOGERROR, "CUDMABufferObject::{} - close fd failed, errno={}", __FUNCTION__,
154 strerror(errno));
155
156 ret = close(m_memfd);
157 if (ret < 0)
158 CLog::Log(LOGERROR, "CUDMABufferObject::{} - close memfd failed, errno={}", __FUNCTION__,
159 strerror(errno));
160
161 m_memfd = -1;
162 m_fd = -1;
163 m_stride = 0;
164 m_size = 0;
165}
166
167uint8_t* CUDMABufferObject::GetMemory()
168{
169 if (m_fd < 0)
170 return nullptr;
171
172 if (m_map)
173 {
174 CLog::Log(LOGDEBUG, "CUDMABufferObject::{} - already mapped fd={} map={}", __FUNCTION__, m_fd,
175 fmt::ptr(m_map));
176 return m_map;
177 }
178
179 m_map = static_cast<uint8_t*>(mmap(nullptr, m_size, PROT_WRITE, MAP_SHARED, m_memfd, 0));
180 if (m_map == MAP_FAILED)
181 {
182 CLog::Log(LOGERROR, "CUDMABufferObject::{} - mmap failed, errno={}", __FUNCTION__,
183 strerror(errno));
184 return nullptr;
185 }
186
187 return m_map;
188}
189
190void CUDMABufferObject::ReleaseMemory()
191{
192 if (!m_map)
193 return;
194
195 int ret = munmap(m_map, m_size);
196 if (ret < 0)
197 CLog::Log(LOGERROR, "CUDMABufferObject::{} - munmap failed, errno={}", __FUNCTION__,
198 strerror(errno));
199
200 m_map = nullptr;
201}
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 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/BufferObject.h"
12
13#include <memory>
14#include <stdint.h>
15
16class CUDMABufferObject : public CBufferObject
17{
18public:
19 CUDMABufferObject() = default;
20 virtual ~CUDMABufferObject() override;
21
22 // Registration
23 static std::unique_ptr<CBufferObject> Create();
24 static void Register();
25
26 // IBufferObject overrides via CBufferObject
27 bool CreateBufferObject(uint32_t format, uint32_t width, uint32_t height) override;
28 bool CreateBufferObject(uint64_t size) override;
29 void DestroyBufferObject() override;
30 uint8_t* GetMemory() override;
31 void ReleaseMemory() override;
32 std::string GetName() const override { return "CUDMABufferObject"; }
33
34private:
35 int m_memfd{-1};
36 int m_udmafd{-1};
37 uint64_t m_size{0};
38 uint8_t* m_map{nullptr};
39};
diff --git a/xbmc/utils/URIUtils.cpp b/xbmc/utils/URIUtils.cpp
new file mode 100644
index 0000000..738c946
--- /dev/null
+++ b/xbmc/utils/URIUtils.cpp
@@ -0,0 +1,1441 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "network/Network.h"
10#include "URIUtils.h"
11#include "FileItem.h"
12#include "filesystem/MultiPathDirectory.h"
13#include "filesystem/SpecialProtocol.h"
14#include "filesystem/StackDirectory.h"
15#include "network/DNSNameCache.h"
16#include "pvr/channels/PVRChannelsPath.h"
17#include "settings/AdvancedSettings.h"
18#include "URL.h"
19#include "utils/FileExtensionProvider.h"
20#include "ServiceBroker.h"
21#include "StringUtils.h"
22#include "utils/log.h"
23
24#if defined(TARGET_WINDOWS)
25#include "platform/win32/CharsetConverter.h"
26#endif
27
28#include <algorithm>
29#include <cassert>
30#include <netinet/in.h>
31#include <arpa/inet.h>
32
33using namespace PVR;
34using namespace XFILE;
35
36const CAdvancedSettings* URIUtils::m_advancedSettings = nullptr;
37
38void URIUtils::RegisterAdvancedSettings(const CAdvancedSettings& advancedSettings)
39{
40 m_advancedSettings = &advancedSettings;
41}
42
43void URIUtils::UnregisterAdvancedSettings()
44{
45 m_advancedSettings = nullptr;
46}
47
48/* returns filename extension including period of filename */
49std::string URIUtils::GetExtension(const CURL& url)
50{
51 return URIUtils::GetExtension(url.GetFileName());
52}
53
54std::string URIUtils::GetExtension(const std::string& strFileName)
55{
56 if (IsURL(strFileName))
57 {
58 CURL url(strFileName);
59 return GetExtension(url.GetFileName());
60 }
61
62 size_t period = strFileName.find_last_of("./\\");
63 if (period == std::string::npos || strFileName[period] != '.')
64 return std::string();
65
66 return strFileName.substr(period);
67}
68
69bool URIUtils::HasExtension(const std::string& strFileName)
70{
71 if (IsURL(strFileName))
72 {
73 CURL url(strFileName);
74 return HasExtension(url.GetFileName());
75 }
76
77 size_t iPeriod = strFileName.find_last_of("./\\");
78 return iPeriod != std::string::npos && strFileName[iPeriod] == '.';
79}
80
81bool URIUtils::HasExtension(const CURL& url, const std::string& strExtensions)
82{
83 return HasExtension(url.GetFileName(), strExtensions);
84}
85
86bool URIUtils::HasExtension(const std::string& strFileName, const std::string& strExtensions)
87{
88 if (IsURL(strFileName))
89 {
90 CURL url(strFileName);
91 return HasExtension(url.GetFileName(), strExtensions);
92 }
93
94 // Search backwards so that '.' can be used as a search terminator.
95 std::string::const_reverse_iterator itExtensions = strExtensions.rbegin();
96 while (itExtensions != strExtensions.rend())
97 {
98 // Iterate backwards over strFileName untill we hit a '.' or a mismatch
99 for (std::string::const_reverse_iterator itFileName = strFileName.rbegin();
100 itFileName != strFileName.rend() && itExtensions != strExtensions.rend() &&
101 tolower(*itFileName) == *itExtensions;
102 ++itFileName, ++itExtensions)
103 {
104 if (*itExtensions == '.')
105 return true; // Match
106 }
107
108 // No match. Look for more extensions to try.
109 while (itExtensions != strExtensions.rend() && *itExtensions != '|')
110 ++itExtensions;
111
112 while (itExtensions != strExtensions.rend() && *itExtensions == '|')
113 ++itExtensions;
114 }
115
116 return false;
117}
118
119void URIUtils::RemoveExtension(std::string& strFileName)
120{
121 if(IsURL(strFileName))
122 {
123 CURL url(strFileName);
124 strFileName = url.GetFileName();
125 RemoveExtension(strFileName);
126 url.SetFileName(strFileName);
127 strFileName = url.Get();
128 return;
129 }
130
131 size_t period = strFileName.find_last_of("./\\");
132 if (period != std::string::npos && strFileName[period] == '.')
133 {
134 std::string strExtension = strFileName.substr(period);
135 StringUtils::ToLower(strExtension);
136 strExtension += "|";
137
138 std::string strFileMask;
139 strFileMask = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions();
140 strFileMask += "|" + CServiceBroker::GetFileExtensionProvider().GetMusicExtensions();
141 strFileMask += "|" + CServiceBroker::GetFileExtensionProvider().GetVideoExtensions();
142 strFileMask += "|" + CServiceBroker::GetFileExtensionProvider().GetSubtitleExtensions();
143#if defined(TARGET_DARWIN)
144 strFileMask += "|.py|.xml|.milk|.xbt|.cdg|.app|.applescript|.workflow";
145#else
146 strFileMask += "|.py|.xml|.milk|.xbt|.cdg";
147#endif
148 strFileMask += "|";
149
150 if (strFileMask.find(strExtension) != std::string::npos)
151 strFileName.erase(period);
152 }
153}
154
155std::string URIUtils::ReplaceExtension(const std::string& strFile,
156 const std::string& strNewExtension)
157{
158 if(IsURL(strFile))
159 {
160 CURL url(strFile);
161 url.SetFileName(ReplaceExtension(url.GetFileName(), strNewExtension));
162 return url.Get();
163 }
164
165 std::string strChangedFile;
166 std::string strExtension = GetExtension(strFile);
167 if ( strExtension.size() )
168 {
169 strChangedFile = strFile.substr(0, strFile.size() - strExtension.size()) ;
170 strChangedFile += strNewExtension;
171 }
172 else
173 {
174 strChangedFile = strFile;
175 strChangedFile += strNewExtension;
176 }
177 return strChangedFile;
178}
179
180std::string URIUtils::GetFileName(const CURL& url)
181{
182 return GetFileName(url.GetFileName());
183}
184
185/* returns a filename given an url */
186/* handles both / and \, and options in urls*/
187std::string URIUtils::GetFileName(const std::string& strFileNameAndPath)
188{
189 if(IsURL(strFileNameAndPath))
190 {
191 CURL url(strFileNameAndPath);
192 return GetFileName(url.GetFileName());
193 }
194
195 /* find the last slash */
196 const size_t slash = strFileNameAndPath.find_last_of("/\\");
197 return strFileNameAndPath.substr(slash+1);
198}
199
200void URIUtils::Split(const std::string& strFileNameAndPath,
201 std::string& strPath, std::string& strFileName)
202{
203 //Splits a full filename in path and file.
204 //ex. smb://computer/share/directory/filename.ext -> strPath:smb://computer/share/directory/ and strFileName:filename.ext
205 //Trailing slash will be preserved
206 strFileName = "";
207 strPath = "";
208 int i = strFileNameAndPath.size() - 1;
209 while (i > 0)
210 {
211 char ch = strFileNameAndPath[i];
212 // Only break on ':' if it's a drive separator for DOS (ie d:foo)
213 if (ch == '/' || ch == '\\' || (ch == ':' && i == 1)) break;
214 else i--;
215 }
216 if (i == 0)
217 i--;
218
219 // take left including the directory separator
220 strPath = strFileNameAndPath.substr(0, i+1);
221 // everything to the right of the directory separator
222 strFileName = strFileNameAndPath.substr(i+1);
223
224 // if actual uri, ignore options
225 if (IsURL(strFileNameAndPath))
226 {
227 i = strFileName.size() - 1;
228 while (i > 0)
229 {
230 char ch = strFileName[i];
231 if (ch == '?' || ch == '|') break;
232 else i--;
233 }
234 if (i > 0)
235 strFileName = strFileName.substr(0, i);
236 }
237}
238
239std::vector<std::string> URIUtils::SplitPath(const std::string& strPath)
240{
241 CURL url(strPath);
242
243 // silly std::string can't take a char in the constructor
244 std::string sep(1, url.GetDirectorySeparator());
245
246 // split the filename portion of the URL up into separate dirs
247 std::vector<std::string> dirs = StringUtils::Split(url.GetFileName(), sep);
248
249 // we start with the root path
250 std::string dir = url.GetWithoutFilename();
251
252 if (!dir.empty())
253 dirs.insert(dirs.begin(), dir);
254
255 // we don't need empty token on the end
256 if (dirs.size() > 1 && dirs.back().empty())
257 dirs.erase(dirs.end() - 1);
258
259 return dirs;
260}
261
262void URIUtils::GetCommonPath(std::string& strParent, const std::string& strPath)
263{
264 // find the common path of parent and path
265 unsigned int j = 1;
266 while (j <= std::min(strParent.size(), strPath.size()) &&
267 StringUtils::CompareNoCase(strParent, strPath, j) == 0)
268 j++;
269 strParent.erase(j - 1);
270 // they should at least share a / at the end, though for things such as path/cd1 and path/cd2 there won't be
271 if (!HasSlashAtEnd(strParent))
272 {
273 strParent = GetDirectory(strParent);
274 AddSlashAtEnd(strParent);
275 }
276}
277
278bool URIUtils::HasParentInHostname(const CURL& url)
279{
280 return url.IsProtocol("zip") || url.IsProtocol("apk") || url.IsProtocol("bluray") ||
281 url.IsProtocol("udf") || url.IsProtocol("iso9660") || url.IsProtocol("xbt") ||
282 (CServiceBroker::IsBinaryAddonCacheUp() &&
283 CServiceBroker::GetFileExtensionProvider().EncodedHostName(url.GetProtocol()));
284}
285
286bool URIUtils::HasEncodedHostname(const CURL& url)
287{
288 return HasParentInHostname(url)
289 || url.IsProtocol("musicsearch")
290 || url.IsProtocol( "image");
291}
292
293bool URIUtils::HasEncodedFilename(const CURL& url)
294{
295 const std::string prot2 = url.GetTranslatedProtocol();
296
297 // For now assume only (quasi) http internet streams use URL encoding
298 return CURL::IsProtocolEqual(prot2, "http") ||
299 CURL::IsProtocolEqual(prot2, "https");
300}
301
302std::string URIUtils::GetParentPath(const std::string& strPath)
303{
304 std::string strReturn;
305 GetParentPath(strPath, strReturn);
306 return strReturn;
307}
308
309bool URIUtils::GetParentPath(const std::string& strPath, std::string& strParent)
310{
311 strParent.clear();
312
313 CURL url(strPath);
314 std::string strFile = url.GetFileName();
315 if ( URIUtils::HasParentInHostname(url) && strFile.empty())
316 {
317 strFile = url.GetHostName();
318 return GetParentPath(strFile, strParent);
319 }
320 else if (url.IsProtocol("stack"))
321 {
322 CStackDirectory dir;
323 CFileItemList items;
324 if (!dir.GetDirectory(url, items))
325 return false;
326 CURL url2(GetDirectory(items[0]->GetPath()));
327 if (HasParentInHostname(url2))
328 GetParentPath(url2.Get(), strParent);
329 else
330 strParent = url2.Get();
331 for( int i=1;i<items.Size();++i)
332 {
333 items[i]->m_strDVDLabel = GetDirectory(items[i]->GetPath());
334 if (HasParentInHostname(url2))
335 items[i]->SetPath(GetParentPath(items[i]->m_strDVDLabel));
336 else
337 items[i]->SetPath(items[i]->m_strDVDLabel);
338
339 GetCommonPath(strParent,items[i]->GetPath());
340 }
341 return true;
342 }
343 else if (url.IsProtocol("multipath"))
344 {
345 // get the parent path of the first item
346 return GetParentPath(CMultiPathDirectory::GetFirstPath(strPath), strParent);
347 }
348 else if (url.IsProtocol("plugin"))
349 {
350 if (!url.GetOptions().empty())
351 {
352 //! @todo Make a new python call to get the plugin content type and remove this temporary hack
353 // When a plugin provides multiple types, it has "plugin://addon.id/?content_type=xxx" root URL
354 if (url.GetFileName().empty() && url.HasOption("content_type") && url.GetOptions().find('&') == std::string::npos)
355 url.SetHostName("");
356 //
357 url.SetOptions("");
358 strParent = url.Get();
359 return true;
360 }
361 if (!url.GetFileName().empty())
362 {
363 url.SetFileName("");
364 strParent = url.Get();
365 return true;
366 }
367 if (!url.GetHostName().empty())
368 {
369 url.SetHostName("");
370 strParent = url.Get();
371 return true;
372 }
373 return true; // already at root
374 }
375 else if (url.IsProtocol("special"))
376 {
377 if (HasSlashAtEnd(strFile))
378 strFile.erase(strFile.size() - 1);
379 if(strFile.rfind('/') == std::string::npos)
380 return false;
381 }
382 else if (strFile.empty())
383 {
384 if (!url.GetHostName().empty())
385 {
386 // we have an share with only server or workgroup name
387 // set hostname to "" and return true to get back to root
388 url.SetHostName("");
389 strParent = url.Get();
390 return true;
391 }
392 return false;
393 }
394
395 if (HasSlashAtEnd(strFile) )
396 {
397 strFile.erase(strFile.size() - 1);
398 }
399
400 size_t iPos = strFile.rfind('/');
401#ifndef TARGET_POSIX
402 if (iPos == std::string::npos)
403 {
404 iPos = strFile.rfind('\\');
405 }
406#endif
407 if (iPos == std::string::npos)
408 {
409 url.SetFileName("");
410 strParent = url.Get();
411 return true;
412 }
413
414 strFile.erase(iPos);
415
416 AddSlashAtEnd(strFile);
417
418 url.SetFileName(strFile);
419 strParent = url.Get();
420 return true;
421}
422
423std::string URIUtils::GetBasePath(const std::string& strPath)
424{
425 std::string strCheck(strPath);
426 if (IsStack(strPath))
427 strCheck = CStackDirectory::GetFirstStackedFile(strPath);
428
429 std::string strDirectory = GetDirectory(strCheck);
430 if (IsInRAR(strCheck))
431 {
432 std::string strPath=strDirectory;
433 GetParentPath(strPath, strDirectory);
434 }
435 if (IsStack(strPath))
436 {
437 strCheck = strDirectory;
438 RemoveSlashAtEnd(strCheck);
439 if (GetFileName(strCheck).size() == 3 && StringUtils::StartsWithNoCase(GetFileName(strCheck), "cd"))
440 strDirectory = GetDirectory(strCheck);
441 }
442 return strDirectory;
443}
444
445std::string URLEncodePath(const std::string& strPath)
446{
447 std::vector<std::string> segments = StringUtils::Split(strPath, "/");
448 for (std::vector<std::string>::iterator i = segments.begin(); i != segments.end(); ++i)
449 *i = CURL::Encode(*i);
450
451 return StringUtils::Join(segments, "/");
452}
453
454std::string URLDecodePath(const std::string& strPath)
455{
456 std::vector<std::string> segments = StringUtils::Split(strPath, "/");
457 for (std::vector<std::string>::iterator i = segments.begin(); i != segments.end(); ++i)
458 *i = CURL::Decode(*i);
459
460 return StringUtils::Join(segments, "/");
461}
462
463std::string URIUtils::ChangeBasePath(const std::string &fromPath, const std::string &fromFile, const std::string &toPath, const bool &bAddPath /* = true */)
464{
465 std::string toFile = fromFile;
466
467 // Convert back slashes to forward slashes, if required
468 if (IsDOSPath(fromPath) && !IsDOSPath(toPath))
469 StringUtils::Replace(toFile, "\\", "/");
470
471 // Handle difference in URL encoded vs. not encoded
472 if ( HasEncodedFilename(CURL(fromPath))
473 && !HasEncodedFilename(CURL(toPath)) )
474 {
475 toFile = URLDecodePath(toFile); // Decode path
476 }
477 else if (!HasEncodedFilename(CURL(fromPath))
478 && HasEncodedFilename(CURL(toPath)) )
479 {
480 toFile = URLEncodePath(toFile); // Encode path
481 }
482
483 // Convert forward slashes to back slashes, if required
484 if (!IsDOSPath(fromPath) && IsDOSPath(toPath))
485 StringUtils::Replace(toFile, "/", "\\");
486
487 if (bAddPath)
488 return AddFileToFolder(toPath, toFile);
489
490 return toFile;
491}
492
493CURL URIUtils::SubstitutePath(const CURL& url, bool reverse /* = false */)
494{
495 const std::string pathToUrl = url.Get();
496 return CURL(SubstitutePath(pathToUrl, reverse));
497}
498
499std::string URIUtils::SubstitutePath(const std::string& strPath, bool reverse /* = false */)
500{
501 if (!m_advancedSettings)
502 {
503 // path substitution not needed / not working during Kodi bootstrap.
504 return strPath;
505 }
506
507 for (const auto& pathPair : m_advancedSettings->m_pathSubstitutions)
508 {
509 const std::string fromPath = reverse ? pathPair.second : pathPair.first;
510 const std::string toPath = reverse ? pathPair.first : pathPair.second;
511
512 if (strncmp(strPath.c_str(), fromPath.c_str(), HasSlashAtEnd(fromPath) ? fromPath.size() - 1 : fromPath.size()) == 0)
513 {
514 if (strPath.size() > fromPath.size())
515 {
516 std::string strSubPathAndFileName = strPath.substr(fromPath.size());
517 return ChangeBasePath(fromPath, strSubPathAndFileName, toPath); // Fix encoding + slash direction
518 }
519 else
520 {
521 return toPath;
522 }
523 }
524 }
525 return strPath;
526}
527
528bool URIUtils::IsProtocol(const std::string& url, const std::string &type)
529{
530 return StringUtils::StartsWithNoCase(url, type + "://");
531}
532
533bool URIUtils::PathHasParent(std::string path, std::string parent, bool translate /* = false */)
534{
535 if (translate)
536 {
537 path = CSpecialProtocol::TranslatePath(path);
538 parent = CSpecialProtocol::TranslatePath(parent);
539 }
540
541 if (parent.empty())
542 return false;
543
544 if (path == parent)
545 return true;
546
547 // Make sure parent has a trailing slash
548 AddSlashAtEnd(parent);
549
550 return StringUtils::StartsWith(path, parent);
551}
552
553bool URIUtils::PathEquals(std::string path1, std::string path2, bool ignoreTrailingSlash /* = false */, bool ignoreURLOptions /* = false */)
554{
555 if (ignoreURLOptions)
556 {
557 path1 = CURL(path1).GetWithoutOptions();
558 path2 = CURL(path2).GetWithoutOptions();
559 }
560
561 if (ignoreTrailingSlash)
562 {
563 RemoveSlashAtEnd(path1);
564 RemoveSlashAtEnd(path2);
565 }
566
567 return (path1 == path2);
568}
569
570bool URIUtils::IsRemote(const std::string& strFile)
571{
572 if (IsCDDA(strFile) || IsISO9660(strFile))
573 return false;
574
575 if (IsStack(strFile))
576 return IsRemote(CStackDirectory::GetFirstStackedFile(strFile));
577
578 if (IsSpecial(strFile))
579 return IsRemote(CSpecialProtocol::TranslatePath(strFile));
580
581 if(IsMultiPath(strFile))
582 { // virtual paths need to be checked separately
583 std::vector<std::string> paths;
584 if (CMultiPathDirectory::GetPaths(strFile, paths))
585 {
586 for (unsigned int i = 0; i < paths.size(); i++)
587 if (IsRemote(paths[i])) return true;
588 }
589 return false;
590 }
591
592 CURL url(strFile);
593 if(HasParentInHostname(url))
594 return IsRemote(url.GetHostName());
595
596 if (IsAddonsPath(strFile))
597 return false;
598
599 if (IsSourcesPath(strFile))
600 return false;
601
602 if (IsVideoDb(strFile) || IsMusicDb(strFile))
603 return false;
604
605 if (IsLibraryFolder(strFile))
606 return false;
607
608 if (IsPlugin(strFile))
609 return false;
610
611 if (IsAndroidApp(strFile))
612 return false;
613
614 if (!url.IsLocal())
615 return true;
616
617 return false;
618}
619
620bool URIUtils::IsOnDVD(const std::string& strFile)
621{
622 if (IsProtocol(strFile, "dvd"))
623 return true;
624
625 if (IsProtocol(strFile, "udf"))
626 return true;
627
628 if (IsProtocol(strFile, "iso9660"))
629 return true;
630
631 if (IsProtocol(strFile, "cdda"))
632 return true;
633
634#if defined(TARGET_WINDOWS_STORE)
635 CLog::Log(LOGDEBUG, "%s is not implemented", __FUNCTION__);
636#elif defined(TARGET_WINDOWS_DESKTOP)
637 using KODI::PLATFORM::WINDOWS::ToW;
638 if (strFile.size() >= 2 && strFile.substr(1, 1) == ":")
639 return (GetDriveType(ToW(strFile.substr(0, 3)).c_str()) == DRIVE_CDROM);
640#endif
641 return false;
642}
643
644bool URIUtils::IsOnLAN(const std::string& strPath)
645{
646 if(IsMultiPath(strPath))
647 return IsOnLAN(CMultiPathDirectory::GetFirstPath(strPath));
648
649 if(IsStack(strPath))
650 return IsOnLAN(CStackDirectory::GetFirstStackedFile(strPath));
651
652 if(IsSpecial(strPath))
653 return IsOnLAN(CSpecialProtocol::TranslatePath(strPath));
654
655 if(IsPlugin(strPath))
656 return false;
657
658 if(IsUPnP(strPath))
659 return true;
660
661 CURL url(strPath);
662 if (HasParentInHostname(url))
663 return IsOnLAN(url.GetHostName());
664
665 if(!IsRemote(strPath))
666 return false;
667
668 std::string host = url.GetHostName();
669
670 return IsHostOnLAN(host);
671}
672
673static bool addr_match(uint32_t addr, const char* target, const char* submask)
674{
675 uint32_t addr2 = ntohl(inet_addr(target));
676 uint32_t mask = ntohl(inet_addr(submask));
677 return (addr & mask) == (addr2 & mask);
678}
679
680bool URIUtils::IsHostOnLAN(const std::string& host, bool offLineCheck)
681{
682 if(host.length() == 0)
683 return false;
684
685 // assume a hostname without dot's
686 // is local (smb netbios hostnames)
687 if(host.find('.') == std::string::npos)
688 return true;
689
690 uint32_t address = ntohl(inet_addr(host.c_str()));
691 if(address == INADDR_NONE)
692 {
693 std::string ip;
694 if(CDNSNameCache::Lookup(host, ip))
695 address = ntohl(inet_addr(ip.c_str()));
696 }
697
698 if(address != INADDR_NONE)
699 {
700 if (offLineCheck) // check if in private range, ref https://en.wikipedia.org/wiki/Private_network
701 {
702 if (
703 addr_match(address, "192.168.0.0", "255.255.0.0") ||
704 addr_match(address, "10.0.0.0", "255.0.0.0") ||
705 addr_match(address, "172.16.0.0", "255.240.0.0")
706 )
707 return true;
708 }
709 // check if we are on the local subnet
710 if (!CServiceBroker::GetNetwork().GetFirstConnectedInterface())
711 return false;
712
713 if (CServiceBroker::GetNetwork().HasInterfaceForIP(address))
714 return true;
715 }
716
717 return false;
718}
719
720bool URIUtils::IsMultiPath(const std::string& strPath)
721{
722 return IsProtocol(strPath, "multipath");
723}
724
725bool URIUtils::IsHD(const std::string& strFileName)
726{
727 CURL url(strFileName);
728
729 if (IsStack(strFileName))
730 return IsHD(CStackDirectory::GetFirstStackedFile(strFileName));
731
732 if (IsSpecial(strFileName))
733 return IsHD(CSpecialProtocol::TranslatePath(strFileName));
734
735 if (HasParentInHostname(url))
736 return IsHD(url.GetHostName());
737
738 return url.GetProtocol().empty() || url.IsProtocol("file") || url.IsProtocol("win-lib");
739}
740
741bool URIUtils::IsDVD(const std::string& strFile)
742{
743 std::string strFileLow = strFile;
744 StringUtils::ToLower(strFileLow);
745 if (strFileLow.find("video_ts.ifo") != std::string::npos && IsOnDVD(strFile))
746 return true;
747
748#if defined(TARGET_WINDOWS)
749 if (IsProtocol(strFile, "dvd"))
750 return true;
751
752 if(strFile.size() < 2 || (strFile.substr(1) != ":\\" && strFile.substr(1) != ":"))
753 return false;
754
755#ifndef TARGET_WINDOWS_STORE
756 if(GetDriveType(KODI::PLATFORM::WINDOWS::ToW(strFile).c_str()) == DRIVE_CDROM)
757 return true;
758#endif
759#else
760 if (strFileLow == "iso9660://" || strFileLow == "udf://" || strFileLow == "dvd://1" )
761 return true;
762#endif
763
764 return false;
765}
766
767bool URIUtils::IsStack(const std::string& strFile)
768{
769 return IsProtocol(strFile, "stack");
770}
771
772bool URIUtils::IsRAR(const std::string& strFile)
773{
774 std::string strExtension = GetExtension(strFile);
775
776 if (strExtension == ".001" && !StringUtils::EndsWithNoCase(strFile, ".ts.001"))
777 return true;
778
779 if (StringUtils::EqualsNoCase(strExtension, ".cbr"))
780 return true;
781
782 if (StringUtils::EqualsNoCase(strExtension, ".rar"))
783 return true;
784
785 return false;
786}
787
788bool URIUtils::IsInArchive(const std::string &strFile)
789{
790 CURL url(strFile);
791
792 bool archiveProto = url.IsProtocol("archive") && !url.GetFileName().empty();
793 return archiveProto || IsInZIP(strFile) || IsInRAR(strFile) || IsInAPK(strFile);
794}
795
796bool URIUtils::IsInAPK(const std::string& strFile)
797{
798 CURL url(strFile);
799
800 return url.IsProtocol("apk") && !url.GetFileName().empty();
801}
802
803bool URIUtils::IsInZIP(const std::string& strFile)
804{
805 CURL url(strFile);
806
807 if (url.GetFileName().empty())
808 return false;
809
810 if (url.IsProtocol("archive"))
811 return IsZIP(url.GetHostName());
812
813 return url.IsProtocol("zip");
814}
815
816bool URIUtils::IsInRAR(const std::string& strFile)
817{
818 CURL url(strFile);
819
820 if (url.GetFileName().empty())
821 return false;
822
823 if (url.IsProtocol("archive"))
824 return IsRAR(url.GetHostName());
825
826 return url.IsProtocol("rar");
827}
828
829bool URIUtils::IsAPK(const std::string& strFile)
830{
831 return HasExtension(strFile, ".apk");
832}
833
834bool URIUtils::IsZIP(const std::string& strFile) // also checks for comic books!
835{
836 return HasExtension(strFile, ".zip|.cbz");
837}
838
839bool URIUtils::IsArchive(const std::string& strFile)
840{
841 return HasExtension(strFile, ".zip|.rar|.apk|.cbz|.cbr");
842}
843
844bool URIUtils::IsSpecial(const std::string& strFile)
845{
846 if (IsStack(strFile))
847 return IsSpecial(CStackDirectory::GetFirstStackedFile(strFile));
848
849 return IsProtocol(strFile, "special");
850}
851
852bool URIUtils::IsPlugin(const std::string& strFile)
853{
854 CURL url(strFile);
855 return url.IsProtocol("plugin");
856}
857
858bool URIUtils::IsScript(const std::string& strFile)
859{
860 CURL url(strFile);
861 return url.IsProtocol("script");
862}
863
864bool URIUtils::IsAddonsPath(const std::string& strFile)
865{
866 CURL url(strFile);
867 return url.IsProtocol("addons");
868}
869
870bool URIUtils::IsSourcesPath(const std::string& strPath)
871{
872 CURL url(strPath);
873 return url.IsProtocol("sources");
874}
875
876bool URIUtils::IsCDDA(const std::string& strFile)
877{
878 return IsProtocol(strFile, "cdda");
879}
880
881bool URIUtils::IsISO9660(const std::string& strFile)
882{
883 return IsProtocol(strFile, "iso9660");
884}
885
886bool URIUtils::IsSmb(const std::string& strFile)
887{
888 if (IsStack(strFile))
889 return IsSmb(CStackDirectory::GetFirstStackedFile(strFile));
890
891 if (IsSpecial(strFile))
892 return IsSmb(CSpecialProtocol::TranslatePath(strFile));
893
894 CURL url(strFile);
895 if (HasParentInHostname(url))
896 return IsSmb(url.GetHostName());
897
898 return IsProtocol(strFile, "smb");
899}
900
901bool URIUtils::IsURL(const std::string& strFile)
902{
903 return strFile.find("://") != std::string::npos;
904}
905
906bool URIUtils::IsFTP(const std::string& strFile)
907{
908 if (IsStack(strFile))
909 return IsFTP(CStackDirectory::GetFirstStackedFile(strFile));
910
911 if (IsSpecial(strFile))
912 return IsFTP(CSpecialProtocol::TranslatePath(strFile));
913
914 CURL url(strFile);
915 if (HasParentInHostname(url))
916 return IsFTP(url.GetHostName());
917
918 return IsProtocol(strFile, "ftp") ||
919 IsProtocol(strFile, "ftps");
920}
921
922bool URIUtils::IsHTTP(const std::string& strFile)
923{
924 if (IsStack(strFile))
925 return IsHTTP(CStackDirectory::GetFirstStackedFile(strFile));
926
927 if (IsSpecial(strFile))
928 return IsHTTP(CSpecialProtocol::TranslatePath(strFile));
929
930 CURL url(strFile);
931 if (HasParentInHostname(url))
932 return IsHTTP(url.GetHostName());
933
934 return IsProtocol(strFile, "http") ||
935 IsProtocol(strFile, "https");
936}
937
938bool URIUtils::IsUDP(const std::string& strFile)
939{
940 if (IsStack(strFile))
941 return IsUDP(CStackDirectory::GetFirstStackedFile(strFile));
942
943 return IsProtocol(strFile, "udp");
944}
945
946bool URIUtils::IsTCP(const std::string& strFile)
947{
948 if (IsStack(strFile))
949 return IsTCP(CStackDirectory::GetFirstStackedFile(strFile));
950
951 return IsProtocol(strFile, "tcp");
952}
953
954bool URIUtils::IsPVR(const std::string& strFile)
955{
956 if (IsStack(strFile))
957 return IsPVR(CStackDirectory::GetFirstStackedFile(strFile));
958
959 return IsProtocol(strFile, "pvr");
960}
961
962bool URIUtils::IsPVRChannel(const std::string& strFile)
963{
964 if (IsStack(strFile))
965 return IsPVRChannel(CStackDirectory::GetFirstStackedFile(strFile));
966
967 return IsProtocol(strFile, "pvr") && CPVRChannelsPath(strFile).IsChannel();
968}
969
970bool URIUtils::IsPVRChannelGroup(const std::string& strFile)
971{
972 if (IsStack(strFile))
973 return IsPVRChannelGroup(CStackDirectory::GetFirstStackedFile(strFile));
974
975 return IsProtocol(strFile, "pvr") && CPVRChannelsPath(strFile).IsChannelGroup();
976}
977
978bool URIUtils::IsPVRGuideItem(const std::string& strFile)
979{
980 if (IsStack(strFile))
981 return IsPVRGuideItem(CStackDirectory::GetFirstStackedFile(strFile));
982
983 return StringUtils::StartsWithNoCase(strFile, "pvr://guide");
984}
985
986bool URIUtils::IsDAV(const std::string& strFile)
987{
988 if (IsStack(strFile))
989 return IsDAV(CStackDirectory::GetFirstStackedFile(strFile));
990
991 if (IsSpecial(strFile))
992 return IsDAV(CSpecialProtocol::TranslatePath(strFile));
993
994 CURL url(strFile);
995 if (HasParentInHostname(url))
996 return IsDAV(url.GetHostName());
997
998 return IsProtocol(strFile, "dav") ||
999 IsProtocol(strFile, "davs");
1000}
1001
1002bool URIUtils::IsInternetStream(const std::string &path, bool bStrictCheck /* = false */)
1003{
1004 const CURL pathToUrl(path);
1005 return IsInternetStream(pathToUrl, bStrictCheck);
1006}
1007
1008bool URIUtils::IsInternetStream(const CURL& url, bool bStrictCheck /* = false */)
1009{
1010 if (url.GetProtocol().empty())
1011 return false;
1012
1013 // there's nothing to stop internet streams from being stacked
1014 if (url.IsProtocol("stack"))
1015 return IsInternetStream(CStackDirectory::GetFirstStackedFile(url.Get()));
1016
1017 // Special case these
1018 //! @todo sftp special case has to be handled by vfs addon
1019 if (url.IsProtocol("ftp") || url.IsProtocol("ftps") ||
1020 url.IsProtocol("dav") || url.IsProtocol("davs") ||
1021 url.IsProtocol("sftp"))
1022 return bStrictCheck;
1023
1024 std::string protocol = url.GetTranslatedProtocol();
1025 if (CURL::IsProtocolEqual(protocol, "http") || CURL::IsProtocolEqual(protocol, "https") ||
1026 CURL::IsProtocolEqual(protocol, "tcp") || CURL::IsProtocolEqual(protocol, "udp") ||
1027 CURL::IsProtocolEqual(protocol, "rtp") || CURL::IsProtocolEqual(protocol, "sdp") ||
1028 CURL::IsProtocolEqual(protocol, "mms") || CURL::IsProtocolEqual(protocol, "mmst") ||
1029 CURL::IsProtocolEqual(protocol, "mmsh") || CURL::IsProtocolEqual(protocol, "rtsp") ||
1030 CURL::IsProtocolEqual(protocol, "rtmp") || CURL::IsProtocolEqual(protocol, "rtmpt") ||
1031 CURL::IsProtocolEqual(protocol, "rtmpe") || CURL::IsProtocolEqual(protocol, "rtmpte") ||
1032 CURL::IsProtocolEqual(protocol, "rtmps"))
1033 return true;
1034
1035 return false;
1036}
1037
1038bool URIUtils::IsUPnP(const std::string& strFile)
1039{
1040 return IsProtocol(strFile, "upnp");
1041}
1042
1043bool URIUtils::IsLiveTV(const std::string& strFile)
1044{
1045 std::string strFileWithoutSlash(strFile);
1046 RemoveSlashAtEnd(strFileWithoutSlash);
1047
1048 if (StringUtils::EndsWithNoCase(strFileWithoutSlash, ".pvr") &&
1049 !StringUtils::StartsWith(strFileWithoutSlash, "pvr://recordings"))
1050 return true;
1051
1052 return false;
1053}
1054
1055bool URIUtils::IsPVRRecording(const std::string& strFile)
1056{
1057 std::string strFileWithoutSlash(strFile);
1058 RemoveSlashAtEnd(strFileWithoutSlash);
1059
1060 return StringUtils::EndsWithNoCase(strFileWithoutSlash, ".pvr") &&
1061 StringUtils::StartsWith(strFile, "pvr://recordings");
1062}
1063
1064bool URIUtils::IsPVRRecordingFileOrFolder(const std::string& strFile)
1065{
1066 return StringUtils::StartsWith(strFile, "pvr://recordings");
1067}
1068
1069bool URIUtils::IsMusicDb(const std::string& strFile)
1070{
1071 return IsProtocol(strFile, "musicdb");
1072}
1073
1074bool URIUtils::IsNfs(const std::string& strFile)
1075{
1076 if (IsStack(strFile))
1077 return IsNfs(CStackDirectory::GetFirstStackedFile(strFile));
1078
1079 if (IsSpecial(strFile))
1080 return IsNfs(CSpecialProtocol::TranslatePath(strFile));
1081
1082 CURL url(strFile);
1083 if (HasParentInHostname(url))
1084 return IsNfs(url.GetHostName());
1085
1086 return IsProtocol(strFile, "nfs");
1087}
1088
1089bool URIUtils::IsVideoDb(const std::string& strFile)
1090{
1091 return IsProtocol(strFile, "videodb");
1092}
1093
1094bool URIUtils::IsBluray(const std::string& strFile)
1095{
1096 return IsProtocol(strFile, "bluray");
1097}
1098
1099bool URIUtils::IsAndroidApp(const std::string &path)
1100{
1101 return IsProtocol(path, "androidapp");
1102}
1103
1104bool URIUtils::IsLibraryFolder(const std::string& strFile)
1105{
1106 CURL url(strFile);
1107 return url.IsProtocol("library");
1108}
1109
1110bool URIUtils::IsLibraryContent(const std::string &strFile)
1111{
1112 return (IsProtocol(strFile, "library") ||
1113 IsProtocol(strFile, "videodb") ||
1114 IsProtocol(strFile, "musicdb") ||
1115 StringUtils::EndsWith(strFile, ".xsp"));
1116}
1117
1118bool URIUtils::IsDOSPath(const std::string &path)
1119{
1120 if (path.size() > 1 && path[1] == ':' && isalpha(path[0]))
1121 return true;
1122
1123 // windows network drives
1124 if (path.size() > 1 && path[0] == '\\' && path[1] == '\\')
1125 return true;
1126
1127 return false;
1128}
1129
1130std::string URIUtils::AppendSlash(std::string strFolder)
1131{
1132 AddSlashAtEnd(strFolder);
1133 return strFolder;
1134}
1135
1136void URIUtils::AddSlashAtEnd(std::string& strFolder)
1137{
1138 if (IsURL(strFolder))
1139 {
1140 CURL url(strFolder);
1141 std::string file = url.GetFileName();
1142 if(!file.empty() && file != strFolder)
1143 {
1144 AddSlashAtEnd(file);
1145 url.SetFileName(file);
1146 strFolder = url.Get();
1147 }
1148 return;
1149 }
1150
1151 if (!HasSlashAtEnd(strFolder))
1152 {
1153 if (IsDOSPath(strFolder))
1154 strFolder += '\\';
1155 else
1156 strFolder += '/';
1157 }
1158}
1159
1160bool URIUtils::HasSlashAtEnd(const std::string& strFile, bool checkURL /* = false */)
1161{
1162 if (strFile.empty()) return false;
1163 if (checkURL && IsURL(strFile))
1164 {
1165 CURL url(strFile);
1166 std::string file = url.GetFileName();
1167 return file.empty() || HasSlashAtEnd(file, false);
1168 }
1169 char kar = strFile.c_str()[strFile.size() - 1];
1170
1171 if (kar == '/' || kar == '\\')
1172 return true;
1173
1174 return false;
1175}
1176
1177void URIUtils::RemoveSlashAtEnd(std::string& strFolder)
1178{
1179 // performance optimization. pvr guide items are mass objects, uri never has a slash at end, and this method is quite expensive...
1180 if (IsPVRGuideItem(strFolder))
1181 return;
1182
1183 if (IsURL(strFolder))
1184 {
1185 CURL url(strFolder);
1186 std::string file = url.GetFileName();
1187 if (!file.empty() && file != strFolder)
1188 {
1189 RemoveSlashAtEnd(file);
1190 url.SetFileName(file);
1191 strFolder = url.Get();
1192 return;
1193 }
1194 if(url.GetHostName().empty())
1195 return;
1196 }
1197
1198 while (HasSlashAtEnd(strFolder))
1199 strFolder.erase(strFolder.size()-1, 1);
1200}
1201
1202bool URIUtils::CompareWithoutSlashAtEnd(const std::string& strPath1, const std::string& strPath2)
1203{
1204 std::string strc1 = strPath1, strc2 = strPath2;
1205 RemoveSlashAtEnd(strc1);
1206 RemoveSlashAtEnd(strc2);
1207 return StringUtils::EqualsNoCase(strc1, strc2);
1208}
1209
1210
1211std::string URIUtils::FixSlashesAndDups(const std::string& path, const char slashCharacter /* = '/' */, const size_t startFrom /*= 0*/)
1212{
1213 const size_t len = path.length();
1214 if (startFrom >= len)
1215 return path;
1216
1217 std::string result(path, 0, startFrom);
1218 result.reserve(len);
1219
1220 const char* const str = path.c_str();
1221 size_t pos = startFrom;
1222 do
1223 {
1224 if (str[pos] == '\\' || str[pos] == '/')
1225 {
1226 result.push_back(slashCharacter); // append one slash
1227 pos++;
1228 // skip any following slashes
1229 while (str[pos] == '\\' || str[pos] == '/') // str is null-terminated, no need to check for buffer overrun
1230 pos++;
1231 }
1232 else
1233 result.push_back(str[pos++]); // append current char and advance pos to next char
1234
1235 } while (pos < len);
1236
1237 return result;
1238}
1239
1240
1241std::string URIUtils::CanonicalizePath(const std::string& path, const char slashCharacter /*= '\\'*/)
1242{
1243 assert(slashCharacter == '\\' || slashCharacter == '/');
1244
1245 if (path.empty())
1246 return path;
1247
1248 const std::string slashStr(1, slashCharacter);
1249 std::vector<std::string> pathVec, resultVec;
1250 StringUtils::Tokenize(path, pathVec, slashStr);
1251
1252 for (std::vector<std::string>::const_iterator it = pathVec.begin(); it != pathVec.end(); ++it)
1253 {
1254 if (*it == ".")
1255 { /* skip - do nothing */ }
1256 else if (*it == ".." && !resultVec.empty() && resultVec.back() != "..")
1257 resultVec.pop_back();
1258 else
1259 resultVec.push_back(*it);
1260 }
1261
1262 std::string result;
1263 if (path[0] == slashCharacter)
1264 result.push_back(slashCharacter); // add slash at the begin
1265
1266 result += StringUtils::Join(resultVec, slashStr);
1267
1268 if (path[path.length() - 1] == slashCharacter && !result.empty() && result[result.length() - 1] != slashCharacter)
1269 result.push_back(slashCharacter); // add slash at the end if result isn't empty and result isn't "/"
1270
1271 return result;
1272}
1273
1274std::string URIUtils::AddFileToFolder(const std::string& strFolder,
1275 const std::string& strFile)
1276{
1277 if (IsURL(strFolder))
1278 {
1279 CURL url(strFolder);
1280 if (url.GetFileName() != strFolder)
1281 {
1282 url.SetFileName(AddFileToFolder(url.GetFileName(), strFile));
1283 return url.Get();
1284 }
1285 }
1286
1287 std::string strResult = strFolder;
1288 if (!strResult.empty())
1289 AddSlashAtEnd(strResult);
1290
1291 // Remove any slash at the start of the file
1292 if (strFile.size() && (strFile[0] == '/' || strFile[0] == '\\'))
1293 strResult += strFile.substr(1);
1294 else
1295 strResult += strFile;
1296
1297 // correct any slash directions
1298 if (!IsDOSPath(strFolder))
1299 StringUtils::Replace(strResult, '\\', '/');
1300 else
1301 StringUtils::Replace(strResult, '/', '\\');
1302
1303 return strResult;
1304}
1305
1306std::string URIUtils::GetDirectory(const std::string &strFilePath)
1307{
1308 // Will from a full filename return the directory the file resides in.
1309 // Keeps the final slash at end and possible |option=foo options.
1310
1311 size_t iPosSlash = strFilePath.find_last_of("/\\");
1312 if (iPosSlash == std::string::npos)
1313 return ""; // No slash, so no path (ignore any options)
1314
1315 size_t iPosBar = strFilePath.rfind('|');
1316 if (iPosBar == std::string::npos)
1317 return strFilePath.substr(0, iPosSlash + 1); // Only path
1318
1319 return strFilePath.substr(0, iPosSlash + 1) + strFilePath.substr(iPosBar); // Path + options
1320}
1321
1322CURL URIUtils::CreateArchivePath(const std::string& type,
1323 const CURL& archiveUrl,
1324 const std::string& pathInArchive,
1325 const std::string& password)
1326{
1327 CURL url;
1328 url.SetProtocol(type);
1329 if (!password.empty())
1330 url.SetUserName(password);
1331 url.SetHostName(archiveUrl.Get());
1332
1333 /* NOTE: on posix systems, the replacement of \ with / is incorrect.
1334 Ideally this would not be done. We need to check that the ZipManager
1335 code (and elsewhere) doesn't pass in non-posix paths.
1336 */
1337 std::string strBuffer(pathInArchive);
1338 StringUtils::Replace(strBuffer, '\\', '/');
1339 StringUtils::TrimLeft(strBuffer, "/");
1340 url.SetFileName(strBuffer);
1341
1342 return url;
1343}
1344
1345std::string URIUtils::GetRealPath(const std::string &path)
1346{
1347 if (path.empty())
1348 return path;
1349
1350 CURL url(path);
1351 url.SetHostName(GetRealPath(url.GetHostName()));
1352 url.SetFileName(resolvePath(url.GetFileName()));
1353
1354 return url.Get();
1355}
1356
1357std::string URIUtils::resolvePath(const std::string &path)
1358{
1359 if (path.empty())
1360 return path;
1361
1362 size_t posSlash = path.find('/');
1363 size_t posBackslash = path.find('\\');
1364 std::string delim = posSlash < posBackslash ? "/" : "\\";
1365 std::vector<std::string> parts = StringUtils::Split(path, delim);
1366 std::vector<std::string> realParts;
1367
1368 for (std::vector<std::string>::const_iterator part = parts.begin(); part != parts.end(); ++part)
1369 {
1370 if (part->empty() || part->compare(".") == 0)
1371 continue;
1372
1373 // go one level back up
1374 if (part->compare("..") == 0)
1375 {
1376 if (!realParts.empty())
1377 realParts.pop_back();
1378 continue;
1379 }
1380
1381 realParts.push_back(*part);
1382 }
1383
1384 std::string realPath;
1385 // re-add any / or \ at the beginning
1386 for (std::string::const_iterator itPath = path.begin(); itPath != path.end(); ++itPath)
1387 {
1388 if (*itPath != delim.at(0))
1389 break;
1390
1391 realPath += delim;
1392 }
1393 // put together the path
1394 realPath += StringUtils::Join(realParts, delim);
1395 // re-add any / or \ at the end
1396 if (path.at(path.size() - 1) == delim.at(0) &&
1397 realPath.size() > 0 && realPath.at(realPath.size() - 1) != delim.at(0))
1398 realPath += delim;
1399
1400 return realPath;
1401}
1402
1403bool URIUtils::UpdateUrlEncoding(std::string &strFilename)
1404{
1405 if (strFilename.empty())
1406 return false;
1407
1408 CURL url(strFilename);
1409 // if this is a stack:// URL we need to work with its filename
1410 if (URIUtils::IsStack(strFilename))
1411 {
1412 std::vector<std::string> files;
1413 if (!CStackDirectory::GetPaths(strFilename, files))
1414 return false;
1415
1416 for (std::vector<std::string>::iterator file = files.begin(); file != files.end(); ++file)
1417 UpdateUrlEncoding(*file);
1418
1419 std::string stackPath;
1420 if (!CStackDirectory::ConstructStackPath(files, stackPath))
1421 return false;
1422
1423 url.Parse(stackPath);
1424 }
1425 // if the protocol has an encoded hostname we need to work with its hostname
1426 else if (URIUtils::HasEncodedHostname(url))
1427 {
1428 std::string hostname = url.GetHostName();
1429 UpdateUrlEncoding(hostname);
1430 url.SetHostName(hostname);
1431 }
1432 else
1433 return false;
1434
1435 std::string newFilename = url.Get();
1436 if (newFilename == strFilename)
1437 return false;
1438
1439 strFilename = newFilename;
1440 return true;
1441}
diff --git a/xbmc/utils/URIUtils.h b/xbmc/utils/URIUtils.h
new file mode 100644
index 0000000..31db0ec
--- /dev/null
+++ b/xbmc/utils/URIUtils.h
@@ -0,0 +1,228 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12#include <vector>
13
14class CURL;
15class CAdvancedSettings;
16
17class URIUtils
18{
19public:
20 static void RegisterAdvancedSettings(const CAdvancedSettings& advancedSettings);
21 static void UnregisterAdvancedSettings();
22
23 static std::string GetDirectory(const std::string &strFilePath);
24
25 static std::string GetFileName(const CURL& url);
26 static std::string GetFileName(const std::string& strFileNameAndPath);
27
28 static std::string GetExtension(const CURL& url);
29 static std::string GetExtension(const std::string& strFileName);
30
31 /*!
32 \brief Check if there is a file extension
33 \param strFileName Path or URL to check
34 \return \e true if strFileName have an extension.
35 \note Returns false when strFileName is empty.
36 \sa GetExtension
37 */
38 static bool HasExtension(const std::string& strFileName);
39
40 /*!
41 \brief Check if filename have any of the listed extensions
42 \param strFileName Path or URL to check
43 \param strExtensions List of '.' prefixed lowercase extensions separated with '|'
44 \return \e true if strFileName have any one of the extensions.
45 \note The check is case insensitive for strFileName, but requires
46 strExtensions to be lowercase. Returns false when strFileName or
47 strExtensions is empty.
48 \sa GetExtension
49 */
50 static bool HasExtension(const std::string& strFileName, const std::string& strExtensions);
51 static bool HasExtension(const CURL& url, const std::string& strExtensions);
52
53 static void RemoveExtension(std::string& strFileName);
54 static std::string ReplaceExtension(const std::string& strFile,
55 const std::string& strNewExtension);
56 static void Split(const std::string& strFileNameAndPath,
57 std::string& strPath, std::string& strFileName);
58 static std::vector<std::string> SplitPath(const std::string& strPath);
59
60 static void GetCommonPath(std::string& strPath, const std::string& strPath2);
61 static std::string GetParentPath(const std::string& strPath);
62 static bool GetParentPath(const std::string& strPath, std::string& strParent);
63
64 /*! \brief Retrieve the base path, accounting for stacks and files in rars.
65 \param strPath path.
66 \return the folder that contains the item.
67 */
68 static std::string GetBasePath(const std::string& strPath);
69
70 /* \brief Change the base path of a URL: fromPath/fromFile -> toPath/toFile
71 Handles changes in path separator and filename URL encoding if necessary to derive toFile.
72 \param fromPath the base path of the original URL
73 \param fromFile the filename portion of the original URL
74 \param toPath the base path of the resulting URL
75 \return the full path.
76 */
77 static std::string ChangeBasePath(const std::string &fromPath, const std::string &fromFile, const std::string &toPath, const bool &bAddPath = true);
78
79 static CURL SubstitutePath(const CURL& url, bool reverse = false);
80 static std::string SubstitutePath(const std::string& strPath, bool reverse = false);
81
82 /*! \brief Check whether a URL is a given URL scheme.
83 Comparison is case-insensitive as per RFC1738
84 \param url a std::string path.
85 \param type a lower-case scheme name, e.g. "smb".
86 \return true if the url is of the given scheme, false otherwise.
87 \sa PathHasParent, PathEquals
88 */
89 static bool IsProtocol(const std::string& url, const std::string& type);
90
91 /*! \brief Check whether a path has a given parent.
92 Comparison is case-sensitive.
93 Use IsProtocol() to compare the protocol portion only.
94 \param path a std::string path.
95 \param parent the string the parent of the path should be compared against.
96 \param translate whether to translate any special paths into real paths
97 \return true if the path has the given parent string, false otherwise.
98 \sa IsProtocol, PathEquals
99 */
100 static bool PathHasParent(std::string path, std::string parent, bool translate = false);
101
102 /*! \brief Check whether a path equals another path.
103 Comparison is case-sensitive.
104 \param path1 a std::string path.
105 \param path2 the second path the path should be compared against.
106 \param ignoreTrailingSlash ignore any trailing slashes in both paths
107 \return true if the paths are equal, false otherwise.
108 \sa IsProtocol, PathHasParent
109 */
110 static bool PathEquals(std::string path1, std::string path2, bool ignoreTrailingSlash = false, bool ignoreURLOptions = false);
111
112 static bool IsAddonsPath(const std::string& strFile);
113 static bool IsSourcesPath(const std::string& strFile);
114 static bool IsCDDA(const std::string& strFile);
115 static bool IsDAV(const std::string& strFile);
116 static bool IsDOSPath(const std::string &path);
117 static bool IsDVD(const std::string& strFile);
118 static bool IsFTP(const std::string& strFile);
119 static bool IsHTTP(const std::string& strFile);
120 static bool IsUDP(const std::string& strFile);
121 static bool IsTCP(const std::string& strFile);
122 static bool IsHD(const std::string& strFileName);
123 static bool IsInArchive(const std::string& strFile);
124 static bool IsInRAR(const std::string& strFile);
125 static bool IsInternetStream(const std::string& path, bool bStrictCheck = false);
126 static bool IsInternetStream(const CURL& url, bool bStrictCheck = false);
127 static bool IsInAPK(const std::string& strFile);
128 static bool IsInZIP(const std::string& strFile);
129 static bool IsISO9660(const std::string& strFile);
130 static bool IsLiveTV(const std::string& strFile);
131 static bool IsPVRRecording(const std::string& strFile);
132 static bool IsPVRRecordingFileOrFolder(const std::string& strFile);
133 static bool IsMultiPath(const std::string& strPath);
134 static bool IsMusicDb(const std::string& strFile);
135 static bool IsNfs(const std::string& strFile);
136 static bool IsOnDVD(const std::string& strFile);
137 static bool IsOnLAN(const std::string& strFile);
138 static bool IsHostOnLAN(const std::string& hostName, bool offLineCheck = false);
139 static bool IsPlugin(const std::string& strFile);
140 static bool IsScript(const std::string& strFile);
141 static bool IsRAR(const std::string& strFile);
142 static bool IsRemote(const std::string& strFile);
143 static bool IsSmb(const std::string& strFile);
144 static bool IsSpecial(const std::string& strFile);
145 static bool IsStack(const std::string& strFile);
146 static bool IsUPnP(const std::string& strFile);
147 static bool IsURL(const std::string& strFile);
148 static bool IsVideoDb(const std::string& strFile);
149 static bool IsAPK(const std::string& strFile);
150 static bool IsZIP(const std::string& strFile);
151 static bool IsArchive(const std::string& strFile);
152 static bool IsBluray(const std::string& strFile);
153 static bool IsAndroidApp(const std::string& strFile);
154 static bool IsLibraryFolder(const std::string& strFile);
155 static bool IsLibraryContent(const std::string& strFile);
156 static bool IsPVR(const std::string& strFile);
157 static bool IsPVRChannel(const std::string& strFile);
158 static bool IsPVRChannelGroup(const std::string& strFile);
159 static bool IsPVRGuideItem(const std::string& strFile);
160
161 static std::string AppendSlash(std::string strFolder);
162 static void AddSlashAtEnd(std::string& strFolder);
163 static bool HasSlashAtEnd(const std::string& strFile, bool checkURL = false);
164 static void RemoveSlashAtEnd(std::string& strFolder);
165 static bool CompareWithoutSlashAtEnd(const std::string& strPath1, const std::string& strPath2);
166 static std::string FixSlashesAndDups(const std::string& path, const char slashCharacter = '/', const size_t startFrom = 0);
167 /**
168 * Convert path to form without duplicated slashes and without relative directories
169 * Strip duplicated slashes
170 * Resolve and remove relative directories ("/../" and "/./")
171 * Will ignore slashes with other direction than specified
172 * Will not resolve path starting from relative directory
173 * @warning Don't use with "protocol://path"-style URLs
174 * @param path string to process
175 * @param slashCharacter character to use as directory delimiter
176 * @return transformed path
177 */
178 static std::string CanonicalizePath(const std::string& path, const char slashCharacter = '\\');
179
180 static CURL CreateArchivePath(const std::string& type,
181 const CURL& archiveUrl,
182 const std::string& pathInArchive = "",
183 const std::string& password = "");
184
185 static std::string AddFileToFolder(const std::string& strFolder, const std::string& strFile);
186 template <typename... T>
187 static std::string AddFileToFolder(const std::string& strFolder, const std::string& strFile, T... args)
188 {
189 auto newPath = AddFileToFolder(strFolder, strFile);
190 return AddFileToFolder(newPath, args...);
191 }
192
193 static bool HasParentInHostname(const CURL& url);
194 static bool HasEncodedHostname(const CURL& url);
195 static bool HasEncodedFilename(const CURL& url);
196
197 /*!
198 \brief Cleans up the given path by resolving "." and ".."
199 and returns it.
200
201 This methods goes through the given path and removes any "."
202 (as it states "this directory") and resolves any ".." by
203 removing the previous directory from the path. This is done
204 for file paths and host names (in case of VFS paths).
205
206 \param path Path to be cleaned up
207 \return Actual path without any "." or ".."
208 */
209 static std::string GetRealPath(const std::string &path);
210
211 /*!
212 \brief Updates the URL encoded hostname of the given path
213
214 This method must only be used to update paths encoded with
215 the old (Eden) URL encoding implementation to the new (Frodo)
216 URL encoding implementation (which does not URL encode -_.!().
217
218 \param strFilename Path to update
219 \return True if the path has been updated/changed otherwise false
220 */
221 static bool UpdateUrlEncoding(std::string &strFilename);
222
223private:
224 static std::string resolvePath(const std::string &path);
225
226 static const CAdvancedSettings* m_advancedSettings;
227};
228
diff --git a/xbmc/utils/UrlOptions.cpp b/xbmc/utils/UrlOptions.cpp
new file mode 100644
index 0000000..2aa304b
--- /dev/null
+++ b/xbmc/utils/UrlOptions.cpp
@@ -0,0 +1,169 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "UrlOptions.h"
10
11#include "URL.h"
12#include "utils/StringUtils.h"
13#include "utils/log.h"
14
15CUrlOptions::CUrlOptions() = default;
16
17CUrlOptions::CUrlOptions(const std::string &options, const char *strLead /* = "" */)
18 : m_strLead(strLead)
19{
20 AddOptions(options);
21}
22
23CUrlOptions::~CUrlOptions() = default;
24
25std::string CUrlOptions::GetOptionsString(bool withLeadingSeparator /* = false */) const
26{
27 std::string options;
28 for (const auto &opt : m_options)
29 {
30 if (!options.empty())
31 options += "&";
32
33 options += CURL::Encode(opt.first);
34 if (!opt.second.empty())
35 options += "=" + CURL::Encode(opt.second.asString());
36 }
37
38 if (withLeadingSeparator && !options.empty())
39 {
40 if (m_strLead.empty())
41 options = "?" + options;
42 else
43 options = m_strLead + options;
44 }
45
46 return options;
47}
48
49void CUrlOptions::AddOption(const std::string &key, const char *value)
50{
51 if (key.empty() || value == NULL)
52 return;
53
54 return AddOption(key, std::string(value));
55}
56
57void CUrlOptions::AddOption(const std::string &key, const std::string &value)
58{
59 if (key.empty())
60 return;
61
62 m_options[key] = value;
63}
64
65void CUrlOptions::AddOption(const std::string &key, int value)
66{
67 if (key.empty())
68 return;
69
70 m_options[key] = value;
71}
72
73void CUrlOptions::AddOption(const std::string &key, float value)
74{
75 if (key.empty())
76 return;
77
78 m_options[key] = value;
79}
80
81void CUrlOptions::AddOption(const std::string &key, double value)
82{
83 if (key.empty())
84 return;
85
86 m_options[key] = value;
87}
88
89void CUrlOptions::AddOption(const std::string &key, bool value)
90{
91 if (key.empty())
92 return;
93
94 m_options[key] = value;
95}
96
97void CUrlOptions::AddOptions(const std::string &options)
98{
99 if (options.empty())
100 return;
101
102 std::string strOptions = options;
103
104 // if matching the preset leading str, remove from options.
105 if (!m_strLead.empty() && strOptions.compare(0, m_strLead.length(), m_strLead) == 0)
106 strOptions.erase(0, m_strLead.length());
107 else if (strOptions.at(0) == '?' || strOptions.at(0) == '#' || strOptions.at(0) == ';' || strOptions.at(0) == '|')
108 {
109 // remove leading ?, #, ; or | if present
110 if (!m_strLead.empty())
111 CLog::Log(LOGWARNING, "%s: original leading str %s overridden by %c", __FUNCTION__, m_strLead.c_str(), strOptions.at(0));
112 m_strLead = strOptions.at(0);
113 strOptions.erase(0, 1);
114 }
115
116 // split the options by & and process them one by one
117 for (const auto &option : StringUtils::Split(strOptions, "&"))
118 {
119 if (option.empty())
120 continue;
121
122 std::string key, value;
123
124 size_t pos = option.find('=');
125 key = CURL::Decode(option.substr(0, pos));
126 if (pos != std::string::npos)
127 value = CURL::Decode(option.substr(pos + 1));
128
129 // the key cannot be empty
130 if (!key.empty())
131 AddOption(key, value);
132 }
133}
134
135void CUrlOptions::AddOptions(const CUrlOptions &options)
136{
137 m_options.insert(options.m_options.begin(), options.m_options.end());
138}
139
140void CUrlOptions::RemoveOption(const std::string &key)
141{
142 if (key.empty())
143 return;
144
145 auto option = m_options.find(key);
146 if (option != m_options.end())
147 m_options.erase(option);
148}
149
150bool CUrlOptions::HasOption(const std::string &key) const
151{
152 if (key.empty())
153 return false;
154
155 return m_options.find(key) != m_options.end();
156}
157
158bool CUrlOptions::GetOption(const std::string &key, CVariant &value) const
159{
160 if (key.empty())
161 return false;
162
163 auto option = m_options.find(key);
164 if (option == m_options.end())
165 return false;
166
167 value = option->second;
168 return true;
169}
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 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/Variant.h"
12
13#include <map>
14#include <string>
15
16class CUrlOptions
17{
18public:
19 typedef std::map<std::string, CVariant> UrlOptions;
20
21 CUrlOptions();
22 CUrlOptions(const std::string &options, const char *strLead = "");
23 virtual ~CUrlOptions();
24
25 void Clear() { m_options.clear(); m_strLead.clear(); }
26
27 const UrlOptions& GetOptions() const { return m_options; }
28 std::string GetOptionsString(bool withLeadingSeparator = false) const;
29
30 virtual void AddOption(const std::string &key, const char *value);
31 virtual void AddOption(const std::string &key, const std::string &value);
32 virtual void AddOption(const std::string &key, int value);
33 virtual void AddOption(const std::string &key, float value);
34 virtual void AddOption(const std::string &key, double value);
35 virtual void AddOption(const std::string &key, bool value);
36 virtual void AddOptions(const std::string &options);
37 virtual void AddOptions(const CUrlOptions &options);
38 virtual void RemoveOption(const std::string &key);
39
40 bool HasOption(const std::string &key) const;
41 bool GetOption(const std::string &key, CVariant &value) const;
42
43protected:
44 UrlOptions m_options;
45 std::string m_strLead;
46};
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 @@
1/*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Utf8Utils.h"
10
11
12CUtf8Utils::utf8CheckResult CUtf8Utils::checkStrForUtf8(const std::string& str)
13{
14 const char* const strC = str.c_str();
15 const size_t len = str.length();
16 size_t pos = 0;
17 bool isPlainAscii = true;
18
19 while (pos < len)
20 {
21 const size_t chrLen = SizeOfUtf8Char(strC + pos);
22 if (chrLen == 0)
23 return hiAscii; // non valid UTF-8 sequence
24 else if (chrLen > 1)
25 isPlainAscii = false;
26
27 pos += chrLen;
28 }
29
30 if (isPlainAscii)
31 return plainAscii; // only single-byte characters (valid for US-ASCII and for UTF-8)
32
33 return utf8string; // valid UTF-8 with at least one valid UTF-8 multi-byte sequence
34}
35
36
37
38size_t CUtf8Utils::FindValidUtf8Char(const std::string& str, const size_t startPos /*= 0*/)
39{
40 const char* strC = str.c_str();
41 const size_t len = str.length();
42
43 size_t pos = startPos;
44 while (pos < len)
45 {
46 if (SizeOfUtf8Char(strC + pos))
47 return pos;
48
49 pos++;
50 }
51
52 return std::string::npos;
53}
54
55size_t CUtf8Utils::RFindValidUtf8Char(const std::string& str, const size_t startPos)
56{
57 const size_t len = str.length();
58 if (!len)
59 return std::string::npos;
60
61 const char* strC = str.c_str();
62 size_t pos = (startPos >= len) ? len - 1 : startPos;
63 while (pos < len) // pos is unsigned, after zero pos becomes large then len
64 {
65 if (SizeOfUtf8Char(strC + pos))
66 return pos;
67
68 pos--;
69 }
70
71 return std::string::npos;
72}
73
74inline size_t CUtf8Utils::SizeOfUtf8Char(const std::string& str, const size_t charStart /*= 0*/)
75{
76 if (charStart >= str.length())
77 return std::string::npos;
78
79 return SizeOfUtf8Char(str.c_str() + charStart);
80}
81
82// must be used only internally in class!
83// str must be null-terminated
84inline size_t CUtf8Utils::SizeOfUtf8Char(const char* const str)
85{
86 if (!str)
87 return 0;
88
89 const unsigned char* const strU = (const unsigned char*)str;
90 const unsigned char chr = strU[0];
91
92 /* this is an implementation of http://www.unicode.org/versions/Unicode6.2.0/ch03.pdf#G27506 */
93
94 /* U+0000 - U+007F in UTF-8 */
95 if (chr <= 0x7F)
96 return 1;
97
98 /* U+0080 - U+07FF in UTF-8 */ /* binary representation and range */
99 if (chr >= 0xC2 && chr <= 0xDF /* C2=1100 0010 - DF=1101 1111 */
100 // as str is null terminated,
101 && ((strU[1] & 0xC0) == 0x80)) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */
102 return 2; // valid UTF-8 2 bytes sequence
103
104 /* U+0800 - U+0FFF in UTF-8 */
105 if (chr == 0xE0 /* E0=1110 0000 */
106 && (strU[1] & 0xE0) == 0xA0 /* E0=1110 0000, A0=1010 0000 - BF=1011 1111 */
107 && (strU[2] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */
108 return 3; // valid UTF-8 3 bytes sequence
109
110 /* U+1000 - U+CFFF in UTF-8 */
111 /* skip U+D000 - U+DFFF (handled later) */
112 /* U+E000 - U+FFFF in UTF-8 */
113 if (((chr >= 0xE1 && chr <= 0xEC) /* E1=1110 0001 - EC=1110 1100 */
114 || chr == 0xEE || chr == 0xEF) /* EE=1110 1110 - EF=1110 1111 */
115 && (strU[1] & 0xC0) == 0x80 /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */
116 && (strU[2] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */
117 return 3; // valid UTF-8 3 bytes sequence
118
119 /* U+D000 - U+D7FF in UTF-8 */
120 /* note: range U+D800 - U+DFFF is reserved and invalid */
121 if (chr == 0xED /* ED=1110 1101 */
122 && (strU[1] & 0xE0) == 0x80 /* E0=1110 0000, 80=1000 0000 - 9F=1001 1111 */
123 && (strU[2] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */
124 return 3; // valid UTF-8 3 bytes sequence
125
126 /* U+10000 - U+3FFFF in UTF-8 */
127 if (chr == 0xF0 /* F0=1111 0000 */
128 && (strU[1] & 0xE0) == 0x80 /* E0=1110 0000, 80=1000 0000 - 9F=1001 1111 */
129 && strU[2] >= 0x90 && strU[2] <= 0xBF /* 90=1001 0000 - BF=1011 1111 */
130 && (strU[3] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */
131 return 4; // valid UTF-8 4 bytes sequence
132
133 /* U+40000 - U+FFFFF in UTF-8 */
134 if (chr >= 0xF1 && chr <= 0xF3 /* F1=1111 0001 - F3=1111 0011 */
135 && (strU[1] & 0xC0) == 0x80 /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */
136 && (strU[2] & 0xC0) == 0x80 /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */
137 && (strU[3] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */
138 return 4; // valid UTF-8 4 bytes sequence
139
140 /* U+100000 - U+10FFFF in UTF-8 */
141 if (chr == 0xF4 /* F4=1111 0100 */
142 && (strU[1] & 0xF0) == 0x80 /* F0=1111 0000, 80=1000 0000 - 8F=1000 1111 */
143 && (strU[2] & 0xC0) == 0x80 /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */
144 && (strU[3] & 0xC0) == 0x80) /* C0=1100 0000, 80=1000 0000 - BF=1011 1111 */
145 return 4; // valid UTF-8 4 bytes sequence
146
147 return 0; // invalid UTF-8 char sequence
148}
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 @@
1/*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13class CUtf8Utils
14{
15public:
16 enum utf8CheckResult
17 {
18 plainAscii = -1, // only US-ASCII characters (valid for UTF-8 too)
19 hiAscii = 0, // non-UTF-8 sequence with high ASCII characters
20 // (possible single-byte national encoding like WINDOWS-1251, multi-byte encoding like UTF-32 or invalid UTF-8)
21 utf8string = 1 // valid UTF-8 sequences, but not US-ASCII only
22 };
23
24 /**
25 * Check given string for valid UTF-8 sequences
26 * @param str string to check
27 * @return result of check, "plainAscii" for empty string
28 */
29 static utf8CheckResult checkStrForUtf8(const std::string& str);
30
31 static inline bool isValidUtf8(const std::string& str)
32 {
33 return checkStrForUtf8(str) != hiAscii;
34 }
35
36 static size_t FindValidUtf8Char(const std::string& str, const size_t startPos = 0);
37 static size_t RFindValidUtf8Char(const std::string& str, const size_t startPos);
38
39 static size_t SizeOfUtf8Char(const std::string& str, const size_t charStart = 0);
40private:
41 static size_t SizeOfUtf8Char(const char* const str);
42};
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 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "VC1BitstreamParser.h"
10
11#include "BitstreamReader.h"
12
13enum
14{
15 VC1_PROFILE_SIMPLE,
16 VC1_PROFILE_MAIN,
17 VC1_PROFILE_RESERVED,
18 VC1_PROFILE_ADVANCED,
19 VC1_PROFILE_NOPROFILE
20};
21
22enum
23{
24 VC1_END_OF_SEQ = 0x0A,
25 VC1_SLICE = 0x0B,
26 VC1_FIELD = 0x0C,
27 VC1_FRAME = 0x0D,
28 VC1_ENTRYPOINT = 0x0E,
29 VC1_SEQUENCE = 0x0F,
30 VC1_SLICE_USER = 0x1B,
31 VC1_FIELD_USER = 0x1C,
32 VC1_FRAME_USER = 0x1D,
33 VC1_ENTRY_POINT_USER = 0x1E,
34 VC1_SEQUENCE_USER = 0x1F
35};
36
37enum
38{
39 VC1_FRAME_PROGRESSIVE = 0x0,
40 VC1_FRAME_INTERLACE = 0x10,
41 VC1_FIELD_INTERLACE = 0x11
42};
43
44CVC1BitstreamParser::CVC1BitstreamParser()
45{
46 Reset();
47}
48
49void CVC1BitstreamParser::Reset()
50{
51 m_Profile = VC1_PROFILE_NOPROFILE;
52}
53
54bool CVC1BitstreamParser::IsRecoveryPoint(const uint8_t *buf, int buf_size)
55{
56 return vc1_parse_frame(buf, buf + buf_size, true);
57};
58
59bool CVC1BitstreamParser::IsIFrame(const uint8_t *buf, int buf_size)
60{
61 return vc1_parse_frame(buf, buf + buf_size, false);
62};
63
64bool CVC1BitstreamParser::vc1_parse_frame(const uint8_t *buf, const uint8_t *buf_end, bool sequence_only)
65{
66 uint32_t state = -1;
67 for (;;)
68 {
69 buf = find_start_code(buf, buf_end, &state);
70 if (buf >= buf_end)
71 break;
72 if (buf[-1] == VC1_SEQUENCE)
73 {
74 if (m_Profile != VC1_PROFILE_NOPROFILE)
75 return false;
76 CBitstreamReader br(buf, buf_end - buf);
77 // Read the profile
78 m_Profile = static_cast<uint8_t>(br.ReadBits(2));
79 if (m_Profile == VC1_PROFILE_ADVANCED)
80 {
81 br.SkipBits(39);
82 m_AdvInterlace = br.ReadBits(1);
83 }
84 else
85 {
86 br.SkipBits(22);
87
88 m_SimpleSkipBits = 2;
89 if (br.ReadBits(1)) //rangered
90 ++m_SimpleSkipBits;
91
92 m_MaxBFrames = br.ReadBits(3);
93
94 br.SkipBits(2); // quantizer
95 if (br.ReadBits(1)) //finterpflag
96 ++m_SimpleSkipBits;
97 }
98 if (sequence_only)
99 return true;
100 }
101 else if (buf[-1] == VC1_FRAME)
102 {
103 CBitstreamReader br(buf, buf_end - buf);
104
105 if (sequence_only)
106 return false;
107 if (m_Profile == VC1_PROFILE_ADVANCED)
108 {
109 uint8_t fcm;
110 if (m_AdvInterlace) {
111 fcm = br.ReadBits(1);
112 if (fcm)
113 fcm = br.ReadBits(1) + 1;
114 }
115 else
116 fcm = VC1_FRAME_PROGRESSIVE;
117 if (fcm == VC1_FIELD_INTERLACE) {
118 uint8_t pic = br.ReadBits(3);
119 return pic == 0x00 || pic == 0x01;
120 }
121 else
122 {
123 uint8_t pic(0);
124 while (pic < 4 && br.ReadBits(1))++pic;
125 return pic == 2;
126 }
127 return false;
128 }
129 else if (m_Profile != VC1_PROFILE_NOPROFILE)
130 {
131 br.SkipBits(m_SimpleSkipBits); // quantizer
132 uint8_t pic(br.ReadBits(1));
133 if (m_MaxBFrames) {
134 if (!pic) {
135 pic = br.ReadBits(1);
136 return pic != 0;
137 }
138 else
139 return false;
140 }
141 else
142 return pic != 0;
143 }
144 else
145 break;
146 }
147 }
148 return false;
149}
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 @@
1/*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stdint.h>
12
13class CVC1BitstreamParser
14{
15public:
16 CVC1BitstreamParser();
17 ~CVC1BitstreamParser() = default;
18
19 void Reset();
20
21 inline bool IsRecoveryPoint(const uint8_t *buf, int buf_size);
22 inline bool IsIFrame(const uint8_t *buf, int buf_size);
23
24protected:
25 bool vc1_parse_frame(const uint8_t *buf, const uint8_t *buf_end, bool sequenceOnly);
26private:
27 uint8_t m_Profile;
28 uint8_t m_MaxBFrames;
29 uint8_t m_SimpleSkipBits;
30 uint8_t m_AdvInterlace;
31};
diff --git a/xbmc/utils/Variant.cpp b/xbmc/utils/Variant.cpp
new file mode 100644
index 0000000..97676f6
--- /dev/null
+++ b/xbmc/utils/Variant.cpp
@@ -0,0 +1,885 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Variant.h"
10
11#include <stdlib.h>
12#include <string.h>
13#include <utility>
14
15#ifndef strtoll
16#ifdef TARGET_WINDOWS
17#define strtoll _strtoi64
18#define strtoull _strtoui64
19#define wcstoll _wcstoi64
20#define wcstoull _wcstoui64
21#else // TARGET_WINDOWS
22#if !defined(TARGET_DARWIN)
23#define strtoll(str, endptr, base) (int64_t)strtod(str, endptr)
24#define strtoull(str, endptr, base) (uint64_t)strtod(str, endptr)
25#define wcstoll(str, endptr, base) (int64_t)wcstod(str, endptr)
26#define wcstoull(str, endptr, base) (uint64_t)wcstod(str, endptr)
27#endif
28#endif // TARGET_WINDOWS
29#endif // strtoll
30
31std::string trimRight(const std::string &str)
32{
33 std::string tmp = str;
34 // find_last_not_of will return string::npos (which is defined as -1)
35 // or a value between 0 and size() - 1 => find_last_not_of() + 1 will
36 // always result in a valid index between 0 and size()
37 tmp.erase(tmp.find_last_not_of(" \n\r\t") + 1);
38
39 return tmp;
40}
41
42std::wstring trimRight(const std::wstring &str)
43{
44 std::wstring tmp = str;
45 // find_last_not_of will return string::npos (which is defined as -1)
46 // or a value between 0 and size() - 1 => find_last_not_of() + 1 will
47 // always result in a valid index between 0 and size()
48 tmp.erase(tmp.find_last_not_of(L" \n\r\t") + 1);
49
50 return tmp;
51}
52
53int64_t str2int64(const std::string &str, int64_t fallback /* = 0 */)
54{
55 char *end = NULL;
56 std::string tmp = trimRight(str);
57 int64_t result = strtoll(tmp.c_str(), &end, 0);
58 if (end == NULL || *end == '\0')
59 return result;
60
61 return fallback;
62}
63
64int64_t str2int64(const std::wstring &str, int64_t fallback /* = 0 */)
65{
66 wchar_t *end = NULL;
67 std::wstring tmp = trimRight(str);
68 int64_t result = wcstoll(tmp.c_str(), &end, 0);
69 if (end == NULL || *end == '\0')
70 return result;
71
72 return fallback;
73}
74
75uint64_t str2uint64(const std::string &str, uint64_t fallback /* = 0 */)
76{
77 char *end = NULL;
78 std::string tmp = trimRight(str);
79 uint64_t result = strtoull(tmp.c_str(), &end, 0);
80 if (end == NULL || *end == '\0')
81 return result;
82
83 return fallback;
84}
85
86uint64_t str2uint64(const std::wstring &str, uint64_t fallback /* = 0 */)
87{
88 wchar_t *end = NULL;
89 std::wstring tmp = trimRight(str);
90 uint64_t result = wcstoull(tmp.c_str(), &end, 0);
91 if (end == NULL || *end == '\0')
92 return result;
93
94 return fallback;
95}
96
97double str2double(const std::string &str, double fallback /* = 0.0 */)
98{
99 char *end = NULL;
100 std::string tmp = trimRight(str);
101 double result = strtod(tmp.c_str(), &end);
102 if (end == NULL || *end == '\0')
103 return result;
104
105 return fallback;
106}
107
108double str2double(const std::wstring &str, double fallback /* = 0.0 */)
109{
110 wchar_t *end = NULL;
111 std::wstring tmp = trimRight(str);
112 double result = wcstod(tmp.c_str(), &end);
113 if (end == NULL || *end == '\0')
114 return result;
115
116 return fallback;
117}
118
119CVariant::CVariant()
120 : CVariant(VariantTypeNull)
121{
122}
123
124CVariant CVariant::ConstNullVariant = CVariant::VariantTypeConstNull;
125CVariant::VariantArray CVariant::EMPTY_ARRAY;
126CVariant::VariantMap CVariant::EMPTY_MAP;
127
128CVariant::CVariant(VariantType type)
129{
130 m_type = type;
131
132 switch (type)
133 {
134 case VariantTypeInteger:
135 m_data.integer = 0;
136 break;
137 case VariantTypeUnsignedInteger:
138 m_data.unsignedinteger = 0;
139 break;
140 case VariantTypeBoolean:
141 m_data.boolean = false;
142 break;
143 case VariantTypeDouble:
144 m_data.dvalue = 0.0;
145 break;
146 case VariantTypeString:
147 m_data.string = new std::string();
148 break;
149 case VariantTypeWideString:
150 m_data.wstring = new std::wstring();
151 break;
152 case VariantTypeArray:
153 m_data.array = new VariantArray();
154 break;
155 case VariantTypeObject:
156 m_data.map = new VariantMap();
157 break;
158 default:
159#ifndef TARGET_WINDOWS_STORE // this corrupts the heap in Win10 UWP version
160 memset(&m_data, 0, sizeof(m_data));
161#endif
162 break;
163 }
164}
165
166CVariant::CVariant(int integer)
167{
168 m_type = VariantTypeInteger;
169 m_data.integer = integer;
170}
171
172CVariant::CVariant(int64_t integer)
173{
174 m_type = VariantTypeInteger;
175 m_data.integer = integer;
176}
177
178CVariant::CVariant(unsigned int unsignedinteger)
179{
180 m_type = VariantTypeUnsignedInteger;
181 m_data.unsignedinteger = unsignedinteger;
182}
183
184CVariant::CVariant(uint64_t unsignedinteger)
185{
186 m_type = VariantTypeUnsignedInteger;
187 m_data.unsignedinteger = unsignedinteger;
188}
189
190CVariant::CVariant(double value)
191{
192 m_type = VariantTypeDouble;
193 m_data.dvalue = value;
194}
195
196CVariant::CVariant(float value)
197{
198 m_type = VariantTypeDouble;
199 m_data.dvalue = (double)value;
200}
201
202CVariant::CVariant(bool boolean)
203{
204 m_type = VariantTypeBoolean;
205 m_data.boolean = boolean;
206}
207
208CVariant::CVariant(const char *str)
209{
210 m_type = VariantTypeString;
211 m_data.string = new std::string(str);
212}
213
214CVariant::CVariant(const char *str, unsigned int length)
215{
216 m_type = VariantTypeString;
217 m_data.string = new std::string(str, length);
218}
219
220CVariant::CVariant(const std::string &str)
221{
222 m_type = VariantTypeString;
223 m_data.string = new std::string(str);
224}
225
226CVariant::CVariant(std::string &&str)
227{
228 m_type = VariantTypeString;
229 m_data.string = new std::string(std::move(str));
230}
231
232CVariant::CVariant(const wchar_t *str)
233{
234 m_type = VariantTypeWideString;
235 m_data.wstring = new std::wstring(str);
236}
237
238CVariant::CVariant(const wchar_t *str, unsigned int length)
239{
240 m_type = VariantTypeWideString;
241 m_data.wstring = new std::wstring(str, length);
242}
243
244CVariant::CVariant(const std::wstring &str)
245{
246 m_type = VariantTypeWideString;
247 m_data.wstring = new std::wstring(str);
248}
249
250CVariant::CVariant(std::wstring &&str)
251{
252 m_type = VariantTypeWideString;
253 m_data.wstring = new std::wstring(std::move(str));
254}
255
256CVariant::CVariant(const std::vector<std::string> &strArray)
257{
258 m_type = VariantTypeArray;
259 m_data.array = new VariantArray;
260 m_data.array->reserve(strArray.size());
261 for (const auto& item : strArray)
262 m_data.array->push_back(CVariant(item));
263}
264
265CVariant::CVariant(const std::map<std::string, std::string> &strMap)
266{
267 m_type = VariantTypeObject;
268 m_data.map = new VariantMap;
269 for (std::map<std::string, std::string>::const_iterator it = strMap.begin(); it != strMap.end(); ++it)
270 m_data.map->insert(make_pair(it->first, CVariant(it->second)));
271}
272
273CVariant::CVariant(const std::map<std::string, CVariant> &variantMap)
274{
275 m_type = VariantTypeObject;
276 m_data.map = new VariantMap(variantMap.begin(), variantMap.end());
277}
278
279CVariant::CVariant(const CVariant &variant)
280{
281 m_type = VariantTypeNull;
282 *this = variant;
283}
284
285CVariant::CVariant(CVariant&& rhs)
286{
287 //Set this so that operator= don't try and run cleanup
288 //when we're not initialized.
289 m_type = VariantTypeNull;
290
291 *this = std::move(rhs);
292}
293
294CVariant::~CVariant()
295{
296 cleanup();
297}
298
299void CVariant::cleanup()
300{
301 switch (m_type)
302 {
303 case VariantTypeString:
304 delete m_data.string;
305 m_data.string = nullptr;
306 break;
307
308 case VariantTypeWideString:
309 delete m_data.wstring;
310 m_data.wstring = nullptr;
311 break;
312
313 case VariantTypeArray:
314 delete m_data.array;
315 m_data.array = nullptr;
316 break;
317
318 case VariantTypeObject:
319 delete m_data.map;
320 m_data.map = nullptr;
321 break;
322 default:
323 break;
324 }
325 m_type = VariantTypeNull;
326}
327
328bool CVariant::isInteger() const
329{
330 return isSignedInteger() || isUnsignedInteger();
331}
332
333bool CVariant::isSignedInteger() const
334{
335 return m_type == VariantTypeInteger;
336}
337
338bool CVariant::isUnsignedInteger() const
339{
340 return m_type == VariantTypeUnsignedInteger;
341}
342
343bool CVariant::isBoolean() const
344{
345 return m_type == VariantTypeBoolean;
346}
347
348bool CVariant::isDouble() const
349{
350 return m_type == VariantTypeDouble;
351}
352
353bool CVariant::isString() const
354{
355 return m_type == VariantTypeString;
356}
357
358bool CVariant::isWideString() const
359{
360 return m_type == VariantTypeWideString;
361}
362
363bool CVariant::isArray() const
364{
365 return m_type == VariantTypeArray;
366}
367
368bool CVariant::isObject() const
369{
370 return m_type == VariantTypeObject;
371}
372
373bool CVariant::isNull() const
374{
375 return m_type == VariantTypeNull || m_type == VariantTypeConstNull;
376}
377
378CVariant::VariantType CVariant::type() const
379{
380 return m_type;
381}
382
383int64_t CVariant::asInteger(int64_t fallback) const
384{
385 switch (m_type)
386 {
387 case VariantTypeInteger:
388 return m_data.integer;
389 case VariantTypeUnsignedInteger:
390 return (int64_t)m_data.unsignedinteger;
391 case VariantTypeDouble:
392 return (int64_t)m_data.dvalue;
393 case VariantTypeString:
394 return str2int64(*m_data.string, fallback);
395 case VariantTypeWideString:
396 return str2int64(*m_data.wstring, fallback);
397 default:
398 return fallback;
399 }
400
401 return fallback;
402}
403
404int32_t CVariant::asInteger32(int32_t fallback) const
405{
406 return static_cast<int32_t>(asInteger(fallback));
407}
408
409uint64_t CVariant::asUnsignedInteger(uint64_t fallback) const
410{
411 switch (m_type)
412 {
413 case VariantTypeUnsignedInteger:
414 return m_data.unsignedinteger;
415 case VariantTypeInteger:
416 return (uint64_t)m_data.integer;
417 case VariantTypeDouble:
418 return (uint64_t)m_data.dvalue;
419 case VariantTypeString:
420 return str2uint64(*m_data.string, fallback);
421 case VariantTypeWideString:
422 return str2uint64(*m_data.wstring, fallback);
423 default:
424 return fallback;
425 }
426
427 return fallback;
428}
429
430uint32_t CVariant::asUnsignedInteger32(uint32_t fallback) const
431{
432 return static_cast<uint32_t>(asUnsignedInteger(fallback));
433}
434
435double CVariant::asDouble(double fallback) const
436{
437 switch (m_type)
438 {
439 case VariantTypeDouble:
440 return m_data.dvalue;
441 case VariantTypeInteger:
442 return (double)m_data.integer;
443 case VariantTypeUnsignedInteger:
444 return (double)m_data.unsignedinteger;
445 case VariantTypeString:
446 return str2double(*m_data.string, fallback);
447 case VariantTypeWideString:
448 return str2double(*m_data.wstring, fallback);
449 default:
450 return fallback;
451 }
452
453 return fallback;
454}
455
456float CVariant::asFloat(float fallback) const
457{
458 switch (m_type)
459 {
460 case VariantTypeDouble:
461 return (float)m_data.dvalue;
462 case VariantTypeInteger:
463 return (float)m_data.integer;
464 case VariantTypeUnsignedInteger:
465 return (float)m_data.unsignedinteger;
466 case VariantTypeString:
467 return (float)str2double(*m_data.string, fallback);
468 case VariantTypeWideString:
469 return (float)str2double(*m_data.wstring, fallback);
470 default:
471 return fallback;
472 }
473
474 return fallback;
475}
476
477bool CVariant::asBoolean(bool fallback) const
478{
479 switch (m_type)
480 {
481 case VariantTypeBoolean:
482 return m_data.boolean;
483 case VariantTypeInteger:
484 return (m_data.integer != 0);
485 case VariantTypeUnsignedInteger:
486 return (m_data.unsignedinteger != 0);
487 case VariantTypeDouble:
488 return (m_data.dvalue != 0);
489 case VariantTypeString:
490 if (m_data.string->empty() || m_data.string->compare("0") == 0 || m_data.string->compare("false") == 0)
491 return false;
492 return true;
493 case VariantTypeWideString:
494 if (m_data.wstring->empty() || m_data.wstring->compare(L"0") == 0 || m_data.wstring->compare(L"false") == 0)
495 return false;
496 return true;
497 default:
498 return fallback;
499 }
500
501 return fallback;
502}
503
504std::string CVariant::asString(const std::string &fallback /* = "" */) const
505{
506 switch (m_type)
507 {
508 case VariantTypeString:
509 return *m_data.string;
510 case VariantTypeBoolean:
511 return m_data.boolean ? "true" : "false";
512 case VariantTypeInteger:
513 return std::to_string(m_data.integer);
514 case VariantTypeUnsignedInteger:
515 return std::to_string(m_data.unsignedinteger);
516 case VariantTypeDouble:
517 return std::to_string(m_data.dvalue);
518 default:
519 return fallback;
520 }
521
522 return fallback;
523}
524
525std::wstring CVariant::asWideString(const std::wstring &fallback /* = L"" */) const
526{
527 switch (m_type)
528 {
529 case VariantTypeWideString:
530 return *m_data.wstring;
531 case VariantTypeBoolean:
532 return m_data.boolean ? L"true" : L"false";
533 case VariantTypeInteger:
534 return std::to_wstring(m_data.integer);
535 case VariantTypeUnsignedInteger:
536 return std::to_wstring(m_data.unsignedinteger);
537 case VariantTypeDouble:
538 return std::to_wstring(m_data.dvalue);
539 default:
540 return fallback;
541 }
542
543 return fallback;
544}
545
546CVariant &CVariant::operator[](const std::string &key)
547{
548 if (m_type == VariantTypeNull)
549 {
550 m_type = VariantTypeObject;
551 m_data.map = new VariantMap;
552 }
553
554 if (m_type == VariantTypeObject)
555 return (*m_data.map)[key];
556 else
557 return ConstNullVariant;
558}
559
560const CVariant &CVariant::operator[](const std::string &key) const
561{
562 VariantMap::const_iterator it;
563 if (m_type == VariantTypeObject && (it = m_data.map->find(key)) != m_data.map->end())
564 return it->second;
565 else
566 return ConstNullVariant;
567}
568
569CVariant &CVariant::operator[](unsigned int position)
570{
571 if (m_type == VariantTypeArray && size() > position)
572 return m_data.array->at(position);
573 else
574 return ConstNullVariant;
575}
576
577const CVariant &CVariant::operator[](unsigned int position) const
578{
579 if (m_type == VariantTypeArray && size() > position)
580 return m_data.array->at(position);
581 else
582 return ConstNullVariant;
583}
584
585CVariant &CVariant::operator=(const CVariant &rhs)
586{
587 if (m_type == VariantTypeConstNull || this == &rhs)
588 return *this;
589
590 cleanup();
591
592 m_type = rhs.m_type;
593
594 switch (m_type)
595 {
596 case VariantTypeInteger:
597 m_data.integer = rhs.m_data.integer;
598 break;
599 case VariantTypeUnsignedInteger:
600 m_data.unsignedinteger = rhs.m_data.unsignedinteger;
601 break;
602 case VariantTypeBoolean:
603 m_data.boolean = rhs.m_data.boolean;
604 break;
605 case VariantTypeDouble:
606 m_data.dvalue = rhs.m_data.dvalue;
607 break;
608 case VariantTypeString:
609 m_data.string = new std::string(*rhs.m_data.string);
610 break;
611 case VariantTypeWideString:
612 m_data.wstring = new std::wstring(*rhs.m_data.wstring);
613 break;
614 case VariantTypeArray:
615 m_data.array = new VariantArray(rhs.m_data.array->begin(), rhs.m_data.array->end());
616 break;
617 case VariantTypeObject:
618 m_data.map = new VariantMap(rhs.m_data.map->begin(), rhs.m_data.map->end());
619 break;
620 default:
621 break;
622 }
623
624 return *this;
625}
626
627CVariant& CVariant::operator=(CVariant&& rhs)
628{
629 if (m_type == VariantTypeConstNull || this == &rhs)
630 return *this;
631
632 //Make sure that if we're moved into we don't leak any pointers
633 if (m_type != VariantTypeNull)
634 cleanup();
635
636 m_type = rhs.m_type;
637 m_data = std::move(rhs.m_data);
638
639 //Should be enough to just set m_type here
640 //but better safe than sorry, could probably lead to coverity warnings
641 if (rhs.m_type == VariantTypeString)
642 rhs.m_data.string = nullptr;
643 else if (rhs.m_type == VariantTypeWideString)
644 rhs.m_data.wstring = nullptr;
645 else if (rhs.m_type == VariantTypeArray)
646 rhs.m_data.array = nullptr;
647 else if (rhs.m_type == VariantTypeObject)
648 rhs.m_data.map = nullptr;
649
650 rhs.m_type = VariantTypeNull;
651
652 return *this;
653}
654
655bool CVariant::operator==(const CVariant &rhs) const
656{
657 if (m_type == rhs.m_type)
658 {
659 switch (m_type)
660 {
661 case VariantTypeInteger:
662 return m_data.integer == rhs.m_data.integer;
663 case VariantTypeUnsignedInteger:
664 return m_data.unsignedinteger == rhs.m_data.unsignedinteger;
665 case VariantTypeBoolean:
666 return m_data.boolean == rhs.m_data.boolean;
667 case VariantTypeDouble:
668 return m_data.dvalue == rhs.m_data.dvalue;
669 case VariantTypeString:
670 return *m_data.string == *rhs.m_data.string;
671 case VariantTypeWideString:
672 return *m_data.wstring == *rhs.m_data.wstring;
673 case VariantTypeArray:
674 return *m_data.array == *rhs.m_data.array;
675 case VariantTypeObject:
676 return *m_data.map == *rhs.m_data.map;
677 default:
678 break;
679 }
680 }
681
682 return false;
683}
684
685void CVariant::reserve(size_t length)
686{
687 if (m_type == VariantTypeNull)
688 {
689 m_type = VariantTypeArray;
690 m_data.array = new VariantArray;
691 }
692 if (m_type == VariantTypeArray)
693 m_data.array->reserve(length);
694}
695
696void CVariant::push_back(const CVariant &variant)
697{
698 if (m_type == VariantTypeNull)
699 {
700 m_type = VariantTypeArray;
701 m_data.array = new VariantArray;
702 }
703
704 if (m_type == VariantTypeArray)
705 m_data.array->push_back(variant);
706}
707
708void CVariant::push_back(CVariant &&variant)
709{
710 if (m_type == VariantTypeNull)
711 {
712 m_type = VariantTypeArray;
713 m_data.array = new VariantArray;
714 }
715
716 if (m_type == VariantTypeArray)
717 m_data.array->push_back(std::move(variant));
718}
719
720void CVariant::append(const CVariant &variant)
721{
722 push_back(variant);
723}
724
725void CVariant::append(CVariant&& variant)
726{
727 push_back(std::move(variant));
728}
729
730const char *CVariant::c_str() const
731{
732 if (m_type == VariantTypeString)
733 return m_data.string->c_str();
734 else
735 return NULL;
736}
737
738void CVariant::swap(CVariant &rhs)
739{
740 VariantType temp_type = m_type;
741 VariantUnion temp_data = m_data;
742
743 m_type = rhs.m_type;
744 m_data = rhs.m_data;
745
746 rhs.m_type = temp_type;
747 rhs.m_data = temp_data;
748}
749
750CVariant::iterator_array CVariant::begin_array()
751{
752 if (m_type == VariantTypeArray)
753 return m_data.array->begin();
754 else
755 return EMPTY_ARRAY.begin();
756}
757
758CVariant::const_iterator_array CVariant::begin_array() const
759{
760 if (m_type == VariantTypeArray)
761 return m_data.array->begin();
762 else
763 return EMPTY_ARRAY.begin();
764}
765
766CVariant::iterator_array CVariant::end_array()
767{
768 if (m_type == VariantTypeArray)
769 return m_data.array->end();
770 else
771 return EMPTY_ARRAY.end();
772}
773
774CVariant::const_iterator_array CVariant::end_array() const
775{
776 if (m_type == VariantTypeArray)
777 return m_data.array->end();
778 else
779 return EMPTY_ARRAY.end();
780}
781
782CVariant::iterator_map CVariant::begin_map()
783{
784 if (m_type == VariantTypeObject)
785 return m_data.map->begin();
786 else
787 return EMPTY_MAP.begin();
788}
789
790CVariant::const_iterator_map CVariant::begin_map() const
791{
792 if (m_type == VariantTypeObject)
793 return m_data.map->begin();
794 else
795 return EMPTY_MAP.begin();
796}
797
798CVariant::iterator_map CVariant::end_map()
799{
800 if (m_type == VariantTypeObject)
801 return m_data.map->end();
802 else
803 return EMPTY_MAP.end();
804}
805
806CVariant::const_iterator_map CVariant::end_map() const
807{
808 if (m_type == VariantTypeObject)
809 return m_data.map->end();
810 else
811 return EMPTY_MAP.end();
812}
813
814unsigned int CVariant::size() const
815{
816 if (m_type == VariantTypeObject)
817 return m_data.map->size();
818 else if (m_type == VariantTypeArray)
819 return m_data.array->size();
820 else if (m_type == VariantTypeString)
821 return m_data.string->size();
822 else if (m_type == VariantTypeWideString)
823 return m_data.wstring->size();
824 else
825 return 0;
826}
827
828bool CVariant::empty() const
829{
830 if (m_type == VariantTypeObject)
831 return m_data.map->empty();
832 else if (m_type == VariantTypeArray)
833 return m_data.array->empty();
834 else if (m_type == VariantTypeString)
835 return m_data.string->empty();
836 else if (m_type == VariantTypeWideString)
837 return m_data.wstring->empty();
838 else if (m_type == VariantTypeNull)
839 return true;
840
841 return false;
842}
843
844void CVariant::clear()
845{
846 if (m_type == VariantTypeObject)
847 m_data.map->clear();
848 else if (m_type == VariantTypeArray)
849 m_data.array->clear();
850 else if (m_type == VariantTypeString)
851 m_data.string->clear();
852 else if (m_type == VariantTypeWideString)
853 m_data.wstring->clear();
854}
855
856void CVariant::erase(const std::string &key)
857{
858 if (m_type == VariantTypeNull)
859 {
860 m_type = VariantTypeObject;
861 m_data.map = new VariantMap;
862 }
863 else if (m_type == VariantTypeObject)
864 m_data.map->erase(key);
865}
866
867void CVariant::erase(unsigned int position)
868{
869 if (m_type == VariantTypeNull)
870 {
871 m_type = VariantTypeArray;
872 m_data.array = new VariantArray;
873 }
874
875 if (m_type == VariantTypeArray && position < size())
876 m_data.array->erase(m_data.array->begin() + position);
877}
878
879bool CVariant::isMember(const std::string &key) const
880{
881 if (m_type == VariantTypeObject)
882 return m_data.map->find(key) != m_data.map->end();
883
884 return false;
885}
diff --git a/xbmc/utils/Variant.h b/xbmc/utils/Variant.h
new file mode 100644
index 0000000..45f8e90
--- /dev/null
+++ b/xbmc/utils/Variant.h
@@ -0,0 +1,170 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <map>
12#include <stdint.h>
13#include <string>
14#include <vector>
15#include <wchar.h>
16
17int64_t str2int64(const std::string &str, int64_t fallback = 0);
18int64_t str2int64(const std::wstring &str, int64_t fallback = 0);
19uint64_t str2uint64(const std::string &str, uint64_t fallback = 0);
20uint64_t str2uint64(const std::wstring &str, uint64_t fallback = 0);
21double str2double(const std::string &str, double fallback = 0.0);
22double str2double(const std::wstring &str, double fallback = 0.0);
23
24#ifdef TARGET_WINDOWS_STORE
25#pragma pack(push)
26#pragma pack(8)
27#endif
28
29class CVariant
30{
31public:
32 enum VariantType
33 {
34 VariantTypeInteger,
35 VariantTypeUnsignedInteger,
36 VariantTypeBoolean,
37 VariantTypeString,
38 VariantTypeWideString,
39 VariantTypeDouble,
40 VariantTypeArray,
41 VariantTypeObject,
42 VariantTypeNull,
43 VariantTypeConstNull
44 };
45
46 CVariant();
47 CVariant(VariantType type);
48 CVariant(int integer);
49 CVariant(int64_t integer);
50 CVariant(unsigned int unsignedinteger);
51 CVariant(uint64_t unsignedinteger);
52 CVariant(double value);
53 CVariant(float value);
54 CVariant(bool boolean);
55 CVariant(const char *str);
56 CVariant(const char *str, unsigned int length);
57 CVariant(const std::string &str);
58 CVariant(std::string &&str);
59 CVariant(const wchar_t *str);
60 CVariant(const wchar_t *str, unsigned int length);
61 CVariant(const std::wstring &str);
62 CVariant(std::wstring &&str);
63 CVariant(const std::vector<std::string> &strArray);
64 CVariant(const std::map<std::string, std::string> &strMap);
65 CVariant(const std::map<std::string, CVariant> &variantMap);
66 CVariant(const CVariant &variant);
67 CVariant(CVariant &&rhs);
68 ~CVariant();
69
70
71
72 bool isInteger() const;
73 bool isSignedInteger() const;
74 bool isUnsignedInteger() const;
75 bool isBoolean() const;
76 bool isString() const;
77 bool isWideString() const;
78 bool isDouble() const;
79 bool isArray() const;
80 bool isObject() const;
81 bool isNull() const;
82
83 VariantType type() const;
84
85 int64_t asInteger(int64_t fallback = 0) const;
86 int32_t asInteger32(int32_t fallback = 0) const;
87 uint64_t asUnsignedInteger(uint64_t fallback = 0u) const;
88 uint32_t asUnsignedInteger32(uint32_t fallback = 0u) const;
89 bool asBoolean(bool fallback = false) const;
90 std::string asString(const std::string &fallback = "") const;
91 std::wstring asWideString(const std::wstring &fallback = L"") const;
92 double asDouble(double fallback = 0.0) const;
93 float asFloat(float fallback = 0.0f) const;
94
95 CVariant &operator[](const std::string &key);
96 const CVariant &operator[](const std::string &key) const;
97 CVariant &operator[](unsigned int position);
98 const CVariant &operator[](unsigned int position) const;
99
100 CVariant &operator=(const CVariant &rhs);
101 CVariant &operator=(CVariant &&rhs);
102 bool operator==(const CVariant &rhs) const;
103 bool operator!=(const CVariant &rhs) const { return !(*this == rhs); }
104
105 void reserve(size_t length);
106 void push_back(const CVariant &variant);
107 void push_back(CVariant &&variant);
108 void append(const CVariant &variant);
109 void append(CVariant &&variant);
110
111 const char *c_str() const;
112
113 void swap(CVariant &rhs);
114
115private:
116 typedef std::vector<CVariant> VariantArray;
117 typedef std::map<std::string, CVariant> VariantMap;
118
119public:
120 typedef VariantArray::iterator iterator_array;
121 typedef VariantArray::const_iterator const_iterator_array;
122
123 typedef VariantMap::iterator iterator_map;
124 typedef VariantMap::const_iterator const_iterator_map;
125
126 iterator_array begin_array();
127 const_iterator_array begin_array() const;
128 iterator_array end_array();
129 const_iterator_array end_array() const;
130
131 iterator_map begin_map();
132 const_iterator_map begin_map() const;
133 iterator_map end_map();
134 const_iterator_map end_map() const;
135
136 unsigned int size() const;
137 bool empty() const;
138 void clear();
139 void erase(const std::string &key);
140 void erase(unsigned int position);
141
142 bool isMember(const std::string &key) const;
143
144 static CVariant ConstNullVariant;
145
146private:
147 void cleanup();
148 union VariantUnion
149 {
150 int64_t integer;
151 uint64_t unsignedinteger;
152 bool boolean;
153 double dvalue;
154 std::string *string;
155 std::wstring *wstring;
156 VariantArray *array;
157 VariantMap *map;
158 };
159
160 VariantType m_type;
161 VariantUnion m_data;
162
163 static VariantArray EMPTY_ARRAY;
164 static VariantMap EMPTY_MAP;
165};
166
167#ifdef TARGET_WINDOWS_STORE
168#pragma pack(pop)
169#endif
170
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 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "Vector.h"
10
11#include <math.h>
12
13CVector& CVector::operator+=(const CVector &other)
14{
15 x += other.x;
16 y += other.y;
17
18 return *this;
19}
20
21CVector& CVector::operator-=(const CVector &other)
22{
23 x -= other.x;
24 y -= other.y;
25
26 return *this;
27}
28
29float CVector::length() const
30{
31 return sqrt(pow(x, 2) + pow(y, 2));
32}
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 @@
1/*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11class CVector
12{
13public:
14 CVector() = default;
15 constexpr CVector(float xCoord, float yCoord):x(xCoord), y(yCoord) {}
16
17 constexpr CVector operator+(const CVector &other) const
18 {
19 return CVector(x + other.x, y + other.y);
20 }
21
22 constexpr CVector operator-(const CVector &other) const
23 {
24 return CVector(x - other.x, y - other.y);
25 }
26
27 CVector& operator+=(const CVector &other);
28 CVector& operator-=(const CVector &other);
29
30 constexpr float scalar(const CVector &other) const
31 {
32 return x * other.x + y * other.y;
33 }
34
35 float length() const;
36
37 float x = 0;
38 float y = 0;
39};
diff --git a/xbmc/utils/XBMCTinyXML.cpp b/xbmc/utils/XBMCTinyXML.cpp
new file mode 100644
index 0000000..6180522
--- /dev/null
+++ b/xbmc/utils/XBMCTinyXML.cpp
@@ -0,0 +1,264 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "XBMCTinyXML.h"
10
11#include "LangInfo.h"
12#include "RegExp.h"
13#include "filesystem/File.h"
14#include "utils/CharsetConverter.h"
15#include "utils/CharsetDetection.h"
16#include "utils/StringUtils.h"
17#include "utils/Utf8Utils.h"
18#include "utils/log.h"
19
20#define MAX_ENTITY_LENGTH 8 // size of largest entity "&#xNNNN;"
21#define BUFFER_SIZE 4096
22
23CXBMCTinyXML::CXBMCTinyXML()
24: TiXmlDocument()
25{
26}
27
28CXBMCTinyXML::CXBMCTinyXML(const char *documentName)
29: TiXmlDocument(documentName)
30{
31}
32
33CXBMCTinyXML::CXBMCTinyXML(const std::string& documentName)
34: TiXmlDocument(documentName)
35{
36}
37
38CXBMCTinyXML::CXBMCTinyXML(const std::string& documentName, const std::string& documentCharset)
39: TiXmlDocument(documentName), m_SuggestedCharset(documentCharset)
40{
41 StringUtils::ToUpper(m_SuggestedCharset);
42}
43
44bool CXBMCTinyXML::LoadFile(TiXmlEncoding encoding)
45{
46 return LoadFile(value, encoding);
47}
48
49bool CXBMCTinyXML::LoadFile(const char *_filename, TiXmlEncoding encoding)
50{
51 return LoadFile(std::string(_filename), encoding);
52}
53
54bool CXBMCTinyXML::LoadFile(const std::string& _filename, TiXmlEncoding encoding)
55{
56 value = _filename.c_str();
57
58 XFILE::CFile file;
59 XFILE::auto_buffer buffer;
60
61 if (file.LoadFile(value, buffer) <= 0)
62 {
63 SetError(TIXML_ERROR_OPENING_FILE, NULL, NULL, TIXML_ENCODING_UNKNOWN);
64 return false;
65 }
66
67 // Delete the existing data:
68 Clear();
69 location.Clear();
70
71 std::string data(buffer.get(), buffer.length());
72 buffer.clear(); // free memory early
73
74 if (encoding == TIXML_ENCODING_UNKNOWN)
75 Parse(data, file.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET));
76 else
77 Parse(data, encoding);
78
79 if (Error())
80 return false;
81 return true;
82}
83
84bool CXBMCTinyXML::LoadFile(const std::string& _filename, const std::string& documentCharset)
85{
86 m_SuggestedCharset = documentCharset;
87 StringUtils::ToUpper(m_SuggestedCharset);
88 return LoadFile(_filename, TIXML_ENCODING_UNKNOWN);
89}
90
91bool CXBMCTinyXML::LoadFile(FILE *f, TiXmlEncoding encoding)
92{
93 std::string data;
94 char buf[BUFFER_SIZE];
95 memset(buf, 0, BUFFER_SIZE);
96 int result;
97 while ((result = fread(buf, 1, BUFFER_SIZE, f)) > 0)
98 data.append(buf, result);
99 return Parse(data, encoding);
100}
101
102bool CXBMCTinyXML::SaveFile(const char *_filename) const
103{
104 return SaveFile(std::string(_filename));
105}
106
107bool CXBMCTinyXML::SaveFile(const std::string& filename) const
108{
109 XFILE::CFile file;
110 if (file.OpenForWrite(filename, true))
111 {
112 TiXmlPrinter printer;
113 Accept(&printer);
114 bool suc = file.Write(printer.CStr(), printer.Size()) == static_cast<ssize_t>(printer.Size());
115 if (suc)
116 file.Flush();
117
118 return suc;
119 }
120 return false;
121}
122
123bool CXBMCTinyXML::Parse(const std::string& data, const std::string& dataCharset)
124{
125 m_SuggestedCharset = dataCharset;
126 StringUtils::ToUpper(m_SuggestedCharset);
127 return Parse(data, TIXML_ENCODING_UNKNOWN);
128}
129
130bool CXBMCTinyXML::Parse(const std::string& data, TiXmlEncoding encoding /*= TIXML_DEFAULT_ENCODING */)
131{
132 m_UsedCharset.clear();
133 if (encoding != TIXML_ENCODING_UNKNOWN)
134 { // encoding != TIXML_ENCODING_UNKNOWN means "do not use m_SuggestedCharset and charset detection"
135 m_SuggestedCharset.clear();
136 if (encoding == TIXML_ENCODING_UTF8)
137 m_UsedCharset = "UTF-8";
138
139 return InternalParse(data, encoding);
140 }
141
142 if (!m_SuggestedCharset.empty() && TryParse(data, m_SuggestedCharset))
143 return true;
144
145 std::string detectedCharset;
146 if (CCharsetDetection::DetectXmlEncoding(data, detectedCharset) && TryParse(data, detectedCharset))
147 {
148 if (!m_SuggestedCharset.empty())
149 CLog::Log(LOGWARNING, "%s: \"%s\" charset was used instead of suggested charset \"%s\" for %s", __FUNCTION__, m_UsedCharset.c_str(), m_SuggestedCharset.c_str(),
150 (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str()));
151
152 return true;
153 }
154
155 // check for valid UTF-8
156 if (m_SuggestedCharset != "UTF-8" && detectedCharset != "UTF-8" && CUtf8Utils::isValidUtf8(data) &&
157 TryParse(data, "UTF-8"))
158 {
159 if (!m_SuggestedCharset.empty())
160 CLog::Log(LOGWARNING, "%s: \"%s\" charset was used instead of suggested charset \"%s\" for %s", __FUNCTION__, m_UsedCharset.c_str(), m_SuggestedCharset.c_str(),
161 (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str()));
162 else if (!detectedCharset.empty())
163 CLog::Log(LOGWARNING, "%s: \"%s\" charset was used instead of detected charset \"%s\" for %s", __FUNCTION__, m_UsedCharset.c_str(), detectedCharset.c_str(),
164 (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str()));
165 return true;
166 }
167
168 // fallback: try user GUI charset
169 if (TryParse(data, g_langInfo.GetGuiCharSet()))
170 {
171 if (!m_SuggestedCharset.empty())
172 CLog::Log(LOGWARNING, "%s: \"%s\" charset was used instead of suggested charset \"%s\" for %s", __FUNCTION__, m_UsedCharset.c_str(), m_SuggestedCharset.c_str(),
173 (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str()));
174 else if (!detectedCharset.empty())
175 CLog::Log(LOGWARNING, "%s: \"%s\" charset was used instead of detected charset \"%s\" for %s", __FUNCTION__, m_UsedCharset.c_str(), detectedCharset.c_str(),
176 (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str()));
177 return true;
178 }
179
180 // can't detect correct data charset, try to process data as is
181 if (InternalParse(data, TIXML_ENCODING_UNKNOWN))
182 {
183 if (!m_SuggestedCharset.empty())
184 CLog::Log(LOGWARNING, "%s: Processed %s as unknown encoding instead of suggested \"%s\"", __FUNCTION__,
185 (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str()), m_SuggestedCharset.c_str());
186 else if (!detectedCharset.empty())
187 CLog::Log(LOGWARNING, "%s: Processed %s as unknown encoding instead of detected \"%s\"", __FUNCTION__,
188 (value.empty() ? "XML data" : ("file \"" + value + "\"").c_str()), detectedCharset.c_str());
189 return true;
190 }
191
192 return false;
193}
194
195bool CXBMCTinyXML::TryParse(const std::string& data, const std::string& tryDataCharset)
196{
197 if (tryDataCharset == "UTF-8")
198 InternalParse(data, TIXML_ENCODING_UTF8); // process data without conversion
199 else if (!tryDataCharset.empty())
200 {
201 std::string converted;
202 /* some wrong conversions can leave US-ASCII XML header and structure untouched but break non-English data
203 * so conversion must fail on wrong character and then other encodings will be tried */
204 if (!g_charsetConverter.ToUtf8(tryDataCharset, data, converted, true) || converted.empty())
205 return false; // can't convert data
206
207 InternalParse(converted, TIXML_ENCODING_UTF8);
208 }
209 else
210 InternalParse(data, TIXML_ENCODING_LEGACY);
211
212 // 'Error()' contains result of last run of 'TiXmlDocument::Parse()'
213 if (Error())
214 {
215 Clear();
216 location.Clear();
217
218 return false;
219 }
220
221 m_UsedCharset = tryDataCharset;
222 return true;
223}
224
225bool CXBMCTinyXML::InternalParse(const std::string& rawdata, TiXmlEncoding encoding /*= TIXML_DEFAULT_ENCODING */)
226{
227 // Preprocess string, replacing '&' with '&amp; for invalid XML entities
228 size_t pos = rawdata.find('&');
229 if (pos == std::string::npos)
230 return (TiXmlDocument::Parse(rawdata.c_str(), NULL, encoding) != NULL); // nothing to fix, process data directly
231
232 std::string data(rawdata);
233 CRegExp re(false, CRegExp::asciiOnly, "^&(amp|lt|gt|quot|apos|#x[a-fA-F0-9]{1,4}|#[0-9]{1,5});.*");
234 do
235 {
236 if (re.RegFind(data, pos, MAX_ENTITY_LENGTH) < 0)
237 data.insert(pos + 1, "amp;");
238 pos = data.find('&', pos + 1);
239 } while (pos != std::string::npos);
240
241 return (TiXmlDocument::Parse(data.c_str(), NULL, encoding) != NULL);
242}
243
244bool CXBMCTinyXML::Test()
245{
246 // scraper results with unescaped &
247 CXBMCTinyXML doc;
248 std::string data("<details><url function=\"ParseTMDBRating\" "
249 "cache=\"tmdb-en-12244.json\">"
250 "http://api.themoviedb.org/3/movie/12244"
251 "?api_key=57983e31fb435df4df77afb854740ea9"
252 "&language=en&#x3f;&#x003F;&#0063;</url></details>");
253 doc.Parse(data, TIXML_DEFAULT_ENCODING);
254 TiXmlNode *root = doc.RootElement();
255 if (root && root->ValueStr() == "details")
256 {
257 TiXmlElement *url = root->FirstChildElement("url");
258 if (url && url->FirstChild())
259 {
260 return (url->FirstChild()->ValueStr() == "http://api.themoviedb.org/3/movie/12244?api_key=57983e31fb435df4df77afb854740ea9&language=en???");
261 }
262 }
263 return false;
264}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#ifndef TARGET_WINDOWS
12//compile fix for TinyXml < 2.6.0
13#define DOCUMENT TINYXML_DOCUMENT
14#define ELEMENT TINYXML_ELEMENT
15#define COMMENT TINYXML_COMMENT
16#define UNKNOWN TINYXML_UNKNOWN
17#define TEXT TINYXML_TEXT
18#define DECLARATION TINYXML_DECLARATION
19#define TYPECOUNT TINYXML_TYPECOUNT
20#endif
21
22#include <tinyxml.h>
23#include <string>
24
25#undef DOCUMENT
26#undef ELEMENT
27#undef COMMENT
28#undef UNKNOWN
29//#undef TEXT
30#undef DECLARATION
31#undef TYPECOUNT
32
33class CXBMCTinyXML : public TiXmlDocument
34{
35public:
36 CXBMCTinyXML();
37 explicit CXBMCTinyXML(const char*);
38 explicit CXBMCTinyXML(const std::string& documentName);
39 CXBMCTinyXML(const std::string& documentName, const std::string& documentCharset);
40 bool LoadFile(TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING);
41 bool LoadFile(const char*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING);
42 bool LoadFile(const std::string& _filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING);
43 bool LoadFile(const std::string& _filename, const std::string& documentCharset);
44 bool LoadFile(FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING);
45 bool SaveFile(const char*) const;
46 bool SaveFile(const std::string& filename) const;
47 bool Parse(const std::string& data, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING);
48 bool Parse(const std::string& data, const std::string& dataCharset);
49 inline std::string GetSuggestedCharset(void) const { return m_SuggestedCharset; }
50 inline std::string GetUsedCharset(void) const { return m_UsedCharset; }
51 static bool Test();
52protected:
53 using TiXmlDocument::Parse;
54 bool TryParse(const std::string& data, const std::string& tryDataCharset);
55 bool InternalParse(const std::string& rawdata, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING);
56
57 std::string m_SuggestedCharset;
58 std::string m_UsedCharset;
59};
diff --git a/xbmc/utils/XMLUtils.cpp b/xbmc/utils/XMLUtils.cpp
new file mode 100644
index 0000000..d921602
--- /dev/null
+++ b/xbmc/utils/XMLUtils.cpp
@@ -0,0 +1,343 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "XMLUtils.h"
10#include "URL.h"
11#include "StringUtils.h"
12
13bool XMLUtils::GetHex(const TiXmlNode* pRootNode, const char* strTag, uint32_t& hexValue)
14{
15 const TiXmlNode* pNode = pRootNode->FirstChild(strTag );
16 if (!pNode || !pNode->FirstChild()) return false;
17 return sscanf(pNode->FirstChild()->Value(), "%x", &hexValue) == 1;
18}
19
20
21bool XMLUtils::GetUInt(const TiXmlNode* pRootNode, const char* strTag, uint32_t& uintValue)
22{
23 const TiXmlNode* pNode = pRootNode->FirstChild(strTag );
24 if (!pNode || !pNode->FirstChild()) return false;
25 uintValue = atol(pNode->FirstChild()->Value());
26 return true;
27}
28
29bool XMLUtils::GetUInt(const TiXmlNode* pRootNode, const char* strTag, uint32_t &value, const uint32_t min, const uint32_t max)
30{
31 if (GetUInt(pRootNode, strTag, value))
32 {
33 if (value < min) value = min;
34 if (value > max) value = max;
35 return true;
36 }
37 return false;
38}
39
40bool XMLUtils::GetLong(const TiXmlNode* pRootNode, const char* strTag, long& lLongValue)
41{
42 const TiXmlNode* pNode = pRootNode->FirstChild(strTag );
43 if (!pNode || !pNode->FirstChild()) return false;
44 lLongValue = atol(pNode->FirstChild()->Value());
45 return true;
46}
47
48bool XMLUtils::GetInt(const TiXmlNode* pRootNode, const char* strTag, int& iIntValue)
49{
50 const TiXmlNode* pNode = pRootNode->FirstChild(strTag );
51 if (!pNode || !pNode->FirstChild()) return false;
52 iIntValue = atoi(pNode->FirstChild()->Value());
53 return true;
54}
55
56bool XMLUtils::GetInt(const TiXmlNode* pRootNode, const char* strTag, int &value, const int min, const int max)
57{
58 if (GetInt(pRootNode, strTag, value))
59 {
60 if (value < min) value = min;
61 if (value > max) value = max;
62 return true;
63 }
64 return false;
65}
66
67bool XMLUtils::GetDouble(const TiXmlNode* root, const char* tag, double& value)
68{
69 const TiXmlNode* node = root->FirstChild(tag);
70 if (!node || !node->FirstChild()) return false;
71 value = atof(node->FirstChild()->Value());
72 return true;
73}
74
75bool XMLUtils::GetFloat(const TiXmlNode* pRootNode, const char* strTag, float& value)
76{
77 const TiXmlNode* pNode = pRootNode->FirstChild(strTag );
78 if (!pNode || !pNode->FirstChild()) return false;
79 value = (float)atof(pNode->FirstChild()->Value());
80 return true;
81}
82
83bool XMLUtils::GetFloat(const TiXmlNode* pRootElement, const char *tagName, float& fValue, const float fMin, const float fMax)
84{
85 if (GetFloat(pRootElement, tagName, fValue))
86 { // check range
87 if (fValue < fMin) fValue = fMin;
88 if (fValue > fMax) fValue = fMax;
89 return true;
90 }
91 return false;
92}
93
94bool XMLUtils::GetBoolean(const TiXmlNode* pRootNode, const char* strTag, bool& bBoolValue)
95{
96 const TiXmlNode* pNode = pRootNode->FirstChild(strTag );
97 if (!pNode || !pNode->FirstChild()) return false;
98 std::string strEnabled = pNode->FirstChild()->ValueStr();
99 StringUtils::ToLower(strEnabled);
100 if (strEnabled == "off" || strEnabled == "no" || strEnabled == "disabled" || strEnabled == "false" || strEnabled == "0" )
101 bBoolValue = false;
102 else
103 {
104 bBoolValue = true;
105 if (strEnabled != "on" && strEnabled != "yes" && strEnabled != "enabled" && strEnabled != "true")
106 return false; // invalid bool switch - it's probably some other string.
107 }
108 return true;
109}
110
111bool XMLUtils::GetString(const TiXmlNode* pRootNode, const char* strTag, std::string& strStringValue)
112{
113 const TiXmlElement* pElement = pRootNode->FirstChildElement(strTag);
114 if (!pElement) return false;
115
116 const char* encoded = pElement->Attribute("urlencoded");
117 const TiXmlNode* pNode = pElement->FirstChild();
118 if (pNode != NULL)
119 {
120 strStringValue = pNode->ValueStr();
121 if (encoded && StringUtils::CompareNoCase(encoded, "yes") == 0)
122 strStringValue = CURL::Decode(strStringValue);
123 return true;
124 }
125 strStringValue.clear();
126 return true;
127}
128
129std::string XMLUtils::GetString(const TiXmlNode* pRootNode, const char* strTag)
130{
131 std::string temp;
132 GetString(pRootNode, strTag, temp);
133 return temp;
134}
135
136bool XMLUtils::HasChild(const TiXmlNode* pRootNode, const char* strTag)
137{
138 const TiXmlElement* pElement = pRootNode->FirstChildElement(strTag);
139 if (!pElement) return false;
140 const TiXmlNode* pNode = pElement->FirstChild();
141 return (pNode != NULL);
142}
143
144bool XMLUtils::GetAdditiveString(const TiXmlNode* pRootNode, const char* strTag,
145 const std::string& strSeparator, std::string& strStringValue,
146 bool clear)
147{
148 std::string strTemp;
149 const TiXmlElement* node = pRootNode->FirstChildElement(strTag);
150 bool bResult=false;
151 if (node && node->FirstChild() && clear)
152 strStringValue.clear();
153 while (node)
154 {
155 if (node->FirstChild())
156 {
157 bResult = true;
158 strTemp = node->FirstChild()->Value();
159 const char* clear=node->Attribute("clear");
160 if (strStringValue.empty() || (clear && StringUtils::CompareNoCase(clear, "true") == 0))
161 strStringValue = strTemp;
162 else
163 strStringValue += strSeparator+strTemp;
164 }
165 node = node->NextSiblingElement(strTag);
166 }
167
168 return bResult;
169}
170
171/*!
172 Parses the XML for multiple tags of the given name.
173 Does not clear the array to support chaining.
174*/
175bool XMLUtils::GetStringArray(const TiXmlNode* pRootNode, const char* strTag, std::vector<std::string>& arrayValue, bool clear /* = false */, const std::string& separator /* = "" */)
176{
177 std::string strTemp;
178 const TiXmlElement* node = pRootNode->FirstChildElement(strTag);
179 bool bResult=false;
180 if (node && node->FirstChild() && clear)
181 arrayValue.clear();
182 while (node)
183 {
184 if (node->FirstChild())
185 {
186 bResult = true;
187 strTemp = node->FirstChild()->ValueStr();
188
189 const char* clearAttr = node->Attribute("clear");
190 if (clearAttr && StringUtils::CompareNoCase(clearAttr, "true") == 0)
191 arrayValue.clear();
192
193 if (strTemp.empty())
194 continue;
195
196 if (separator.empty())
197 arrayValue.push_back(strTemp);
198 else
199 {
200 std::vector<std::string> tempArray = StringUtils::Split(strTemp, separator);
201 arrayValue.insert(arrayValue.end(), tempArray.begin(), tempArray.end());
202 }
203 }
204 node = node->NextSiblingElement(strTag);
205 }
206
207 return bResult;
208}
209
210bool XMLUtils::GetPath(const TiXmlNode* pRootNode, const char* strTag, std::string& strStringValue)
211{
212 const TiXmlElement* pElement = pRootNode->FirstChildElement(strTag);
213 if (!pElement) return false;
214
215 const char* encoded = pElement->Attribute("urlencoded");
216 const TiXmlNode* pNode = pElement->FirstChild();
217 if (pNode != NULL)
218 {
219 strStringValue = pNode->Value();
220 if (encoded && StringUtils::CompareNoCase(encoded, "yes") == 0)
221 strStringValue = CURL::Decode(strStringValue);
222 return true;
223 }
224 strStringValue.clear();
225 return false;
226}
227
228bool XMLUtils::GetDate(const TiXmlNode* pRootNode, const char* strTag, CDateTime& date)
229{
230 std::string strDate;
231 if (GetString(pRootNode, strTag, strDate) && !strDate.empty())
232 {
233 date.SetFromDBDate(strDate);
234 return true;
235 }
236
237 return false;
238}
239
240bool XMLUtils::GetDateTime(const TiXmlNode* pRootNode, const char* strTag, CDateTime& dateTime)
241{
242 std::string strDateTime;
243 if (GetString(pRootNode, strTag, strDateTime) && !strDateTime.empty())
244 {
245 dateTime.SetFromDBDateTime(strDateTime);
246 return true;
247 }
248
249 return false;
250}
251
252std::string XMLUtils::GetAttribute(const TiXmlElement *element, const char *tag)
253{
254 if (element)
255 {
256 const char *attribute = element->Attribute(tag);
257 if (attribute)
258 return attribute;
259 }
260 return "";
261}
262
263void XMLUtils::SetAdditiveString(TiXmlNode* pRootNode, const char *strTag, const std::string& strSeparator, const std::string& strValue)
264{
265 std::vector<std::string> list = StringUtils::Split(strValue, strSeparator);
266 for (std::vector<std::string>::const_iterator i = list.begin(); i != list.end(); ++i)
267 SetString(pRootNode, strTag, *i);
268}
269
270void XMLUtils::SetStringArray(TiXmlNode* pRootNode, const char *strTag, const std::vector<std::string>& arrayValue)
271{
272 for (unsigned int i = 0; i < arrayValue.size(); i++)
273 SetString(pRootNode, strTag, arrayValue.at(i));
274}
275
276TiXmlNode* XMLUtils::SetString(TiXmlNode* pRootNode, const char *strTag, const std::string& strValue)
277{
278 TiXmlElement newElement(strTag);
279 TiXmlNode *pNewNode = pRootNode->InsertEndChild(newElement);
280 if (pNewNode)
281 {
282 TiXmlText value(strValue);
283 pNewNode->InsertEndChild(value);
284 }
285 return pNewNode;
286}
287
288TiXmlNode* XMLUtils::SetInt(TiXmlNode* pRootNode, const char *strTag, int value)
289{
290 std::string strValue = StringUtils::Format("%i", value);
291 return SetString(pRootNode, strTag, strValue);
292}
293
294void XMLUtils::SetLong(TiXmlNode* pRootNode, const char *strTag, long value)
295{
296 std::string strValue = StringUtils::Format("%ld", value);
297 SetString(pRootNode, strTag, strValue);
298}
299
300TiXmlNode* XMLUtils::SetFloat(TiXmlNode* pRootNode, const char *strTag, float value)
301{
302 std::string strValue = StringUtils::Format("%f", value);
303 return SetString(pRootNode, strTag, strValue);
304}
305
306TiXmlNode* XMLUtils::SetDouble(TiXmlNode* pRootNode, const char* strTag, double value)
307{
308 std::string strValue = StringUtils::Format("%lf", value);
309 return SetString(pRootNode, strTag, strValue);
310}
311
312void XMLUtils::SetBoolean(TiXmlNode* pRootNode, const char *strTag, bool value)
313{
314 SetString(pRootNode, strTag, value ? "true" : "false");
315}
316
317void XMLUtils::SetHex(TiXmlNode* pRootNode, const char *strTag, uint32_t value)
318{
319 std::string strValue = StringUtils::Format("%x", value);
320 SetString(pRootNode, strTag, strValue);
321}
322
323void XMLUtils::SetPath(TiXmlNode* pRootNode, const char *strTag, const std::string& strValue)
324{
325 TiXmlElement newElement(strTag);
326 newElement.SetAttribute("pathversion", path_version);
327 TiXmlNode *pNewNode = pRootNode->InsertEndChild(newElement);
328 if (pNewNode)
329 {
330 TiXmlText value(strValue);
331 pNewNode->InsertEndChild(value);
332 }
333}
334
335void XMLUtils::SetDate(TiXmlNode* pRootNode, const char *strTag, const CDateTime& date)
336{
337 SetString(pRootNode, strTag, date.IsValid() ? date.GetAsDBDate() : "");
338}
339
340void XMLUtils::SetDateTime(TiXmlNode* pRootNode, const char *strTag, const CDateTime& dateTime)
341{
342 SetString(pRootNode, strTag, dateTime.IsValid() ? dateTime.GetAsDBDateTime() : "");
343}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/XBMCTinyXML.h"
12
13#include <stdint.h>
14#include <string>
15#include <vector>
16
17class CDateTime;
18
19class XMLUtils
20{
21public:
22 static bool HasChild(const TiXmlNode* pRootNode, const char* strTag);
23
24 static bool GetHex(const TiXmlNode* pRootNode, const char* strTag, uint32_t& dwHexValue);
25 static bool GetUInt(const TiXmlNode* pRootNode, const char* strTag, uint32_t& dwUIntValue);
26 static bool GetLong(const TiXmlNode* pRootNode, const char* strTag, long& lLongValue);
27 static bool GetFloat(const TiXmlNode* pRootNode, const char* strTag, float& value);
28 static bool GetDouble(const TiXmlNode* pRootNode, const char* strTag, double& value);
29 static bool GetInt(const TiXmlNode* pRootNode, const char* strTag, int& iIntValue);
30 static bool GetBoolean(const TiXmlNode* pRootNode, const char* strTag, bool& bBoolValue);
31
32 /*! \brief Get a string value from the xml tag
33 If the specified tag isn't found strStringvalue is not modified and will contain whatever
34 value it had before the method call.
35
36 \param[in] pRootNode the xml node that contains the tag
37 \param[in] strTag the xml tag to read from
38 \param[in,out] strStringValue where to store the read string
39 \return true on success, false if the tag isn't found
40 */
41 static bool GetString(const TiXmlNode* pRootNode, const char* strTag, std::string& strStringValue);
42
43 /*! \brief Get a string value from the xml tag
44
45 \param[in] pRootNode the xml node that contains the tag
46 \param[in] strTag the tag to read from
47
48 \return the value in the specified tag or an empty string if the tag isn't found
49 */
50 static std::string GetString(const TiXmlNode* pRootNode, const char* strTag);
51 /*! \brief Get multiple tags, concatenating the values together.
52 Transforms
53 <tag>value1</tag>
54 <tag clear="true">value2</tag>
55 ...
56 <tag>valuen</tag>
57 into value2<sep>...<sep>valuen, appending it to the value string. Note that <value1> is overwritten by the clear="true" tag.
58
59 \param rootNode the parent containing the <tag>'s.
60 \param tag the <tag> in question.
61 \param separator the separator to use when concatenating values.
62 \param value [out] the resulting string. Remains untouched if no <tag> is available, else is appended (or cleared based on the clear parameter).
63 \param clear if true, clears the string prior to adding tags, if tags are available. Defaults to false.
64 */
65 static bool GetAdditiveString(const TiXmlNode* rootNode, const char* tag, const std::string& separator, std::string& value, bool clear = false);
66 static bool GetStringArray(const TiXmlNode* rootNode, const char* tag, std::vector<std::string>& arrayValue, bool clear = false, const std::string& separator = "");
67 static bool GetPath(const TiXmlNode* pRootNode, const char* strTag, std::string& strStringValue);
68 static bool GetFloat(const TiXmlNode* pRootNode, const char* strTag, float& value, const float min, const float max);
69 static bool GetUInt(const TiXmlNode* pRootNode, const char* strTag, uint32_t& dwUIntValue, const uint32_t min, const uint32_t max);
70 static bool GetInt(const TiXmlNode* pRootNode, const char* strTag, int& iIntValue, const int min, const int max);
71 static bool GetDate(const TiXmlNode* pRootNode, const char* strTag, CDateTime& date);
72 static bool GetDateTime(const TiXmlNode* pRootNode, const char* strTag, CDateTime& dateTime);
73 /*! \brief Fetch a std::string copy of an attribute, if it exists. Cannot distinguish between empty and non-existent attributes.
74 \param element the element to query.
75 \param tag the name of the attribute.
76 \return the attribute, if it exists, else an empty string
77 */
78 static std::string GetAttribute(const TiXmlElement *element, const char *tag);
79
80 static TiXmlNode* SetString(TiXmlNode* pRootNode, const char *strTag, const std::string& strValue);
81 static void SetAdditiveString(TiXmlNode* pRootNode, const char *strTag, const std::string& strSeparator, const std::string& strValue);
82 static void SetStringArray(TiXmlNode* pRootNode, const char *strTag, const std::vector<std::string>& arrayValue);
83 static TiXmlNode* SetInt(TiXmlNode* pRootNode, const char *strTag, int value);
84 static TiXmlNode* SetFloat(TiXmlNode* pRootNode, const char *strTag, float value);
85 static TiXmlNode* SetDouble(TiXmlNode* pRootNode, const char* strTag, double value);
86 static void SetBoolean(TiXmlNode* pRootNode, const char *strTag, bool value);
87 static void SetHex(TiXmlNode* pRootNode, const char *strTag, uint32_t value);
88 static void SetPath(TiXmlNode* pRootNode, const char *strTag, const std::string& strValue);
89 static void SetLong(TiXmlNode* pRootNode, const char *strTag, long iValue);
90 static void SetDate(TiXmlNode* pRootNode, const char *strTag, const CDateTime& date);
91 static void SetDateTime(TiXmlNode* pRootNode, const char *strTag, const CDateTime& dateTime);
92
93 static const int path_version = 1;
94};
95
diff --git a/xbmc/utils/XSLTUtils.cpp b/xbmc/utils/XSLTUtils.cpp
new file mode 100644
index 0000000..b2ef27b
--- /dev/null
+++ b/xbmc/utils/XSLTUtils.cpp
@@ -0,0 +1,103 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "XSLTUtils.h"
10#include "log.h"
11#include <libxslt/xslt.h>
12#include <libxslt/transform.h>
13
14#ifndef TARGET_WINDOWS
15#include <iostream>
16#endif
17
18#define TMP_BUF_SIZE 512
19void err(void *ctx, const char *msg, ...) {
20 char string[TMP_BUF_SIZE];
21 va_list arg_ptr;
22 va_start(arg_ptr, msg);
23 vsnprintf(string, TMP_BUF_SIZE, msg, arg_ptr);
24 va_end(arg_ptr);
25 CLog::Log(LOGDEBUG, "XSLT: %s", string);
26}
27
28XSLTUtils::XSLTUtils()
29{
30 // initialize libxslt
31 xmlSubstituteEntitiesDefault(1);
32 xmlLoadExtDtdDefaultValue = 0;
33 xsltSetGenericErrorFunc(NULL, err);
34}
35
36XSLTUtils::~XSLTUtils()
37{
38 if (m_xmlInput)
39 xmlFreeDoc(m_xmlInput);
40 if (m_xmlOutput)
41 xmlFreeDoc(m_xmlOutput);
42 if (m_xsltStylesheet)
43 xsltFreeStylesheet(m_xsltStylesheet);
44}
45
46bool XSLTUtils::XSLTTransform(std::string& output)
47{
48 const char *params[16+1];
49 params[0] = NULL;
50 m_xmlOutput = xsltApplyStylesheet(m_xsltStylesheet, m_xmlInput, params);
51 if (!m_xmlOutput)
52 {
53 CLog::Log(LOGDEBUG, "XSLT: xslt transformation failed");
54 return false;
55 }
56
57 xmlChar *xmlResultBuffer = NULL;
58 int xmlResultLength = 0;
59 int res = xsltSaveResultToString(&xmlResultBuffer, &xmlResultLength, m_xmlOutput, m_xsltStylesheet);
60 if (res == -1)
61 {
62 xmlFree(xmlResultBuffer);
63 return false;
64 }
65
66 output.append((const char *)xmlResultBuffer, xmlResultLength);
67 xmlFree(xmlResultBuffer);
68
69 return true;
70}
71
72bool XSLTUtils::SetInput(const std::string& input)
73{
74 m_xmlInput = xmlParseMemory(input.c_str(), input.size());
75 if (!m_xmlInput)
76 return false;
77 return true;
78}
79
80bool XSLTUtils::SetStylesheet(const std::string& stylesheet)
81{
82 if (m_xsltStylesheet) {
83 xsltFreeStylesheet(m_xsltStylesheet);
84 m_xsltStylesheet = NULL;
85 }
86
87 m_xmlStylesheet = xmlParseMemory(stylesheet.c_str(), stylesheet.size());
88 if (!m_xmlStylesheet)
89 {
90 CLog::Log(LOGDEBUG, "could not xmlParseMemory stylesheetdoc");
91 return false;
92 }
93
94 m_xsltStylesheet = xsltParseStylesheetDoc(m_xmlStylesheet);
95 if (!m_xsltStylesheet) {
96 CLog::Log(LOGDEBUG, "could not parse stylesheetdoc");
97 xmlFree(m_xmlStylesheet);
98 m_xmlStylesheet = NULL;
99 return false;
100 }
101
102 return true;
103}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13#include <libxslt/xslt.h>
14#include <libxslt/xsltutils.h>
15
16class XSLTUtils
17{
18public:
19 XSLTUtils();
20 ~XSLTUtils();
21
22 /*! \brief Set the input XML for an XSLT transform from a string.
23 This sets up the XSLT transformer with some input XML from a string in memory.
24 The input XML should be well formed.
25 \param input the XML document to be transformed.
26 */
27 bool SetInput(const std::string& input);
28
29 /*! \brief Set the stylesheet (XSL) for an XSLT transform from a string.
30 This sets up the XSLT transformer with some stylesheet XML from a string in memory.
31 The input XSL should be well formed.
32 \param input the XSL document to be transformed.
33 */
34 bool SetStylesheet(const std::string& stylesheet);
35
36 /*! \brief Perform an XSLT transform on an inbound XML document.
37 This will apply an XSLT transformation on an input XML document,
38 giving an output XML document, using the specified XSLT document
39 as the transformer.
40 \param input the parent containing the <tag>'s.
41 \param filename the <tag> in question.
42 */
43 bool XSLTTransform(std::string& output);
44
45
46private:
47 xmlDocPtr m_xmlInput = nullptr;
48 xmlDocPtr m_xmlOutput = nullptr;
49 xmlDocPtr m_xmlStylesheet = nullptr;
50 xsltStylesheetPtr m_xsltStylesheet = nullptr;
51};
diff --git a/xbmc/utils/XTimeUtils.h b/xbmc/utils/XTimeUtils.h
new file mode 100644
index 0000000..721c1f7
--- /dev/null
+++ b/xbmc/utils/XTimeUtils.h
@@ -0,0 +1,76 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <string>
12
13#if !defined(TARGET_WINDOWS)
14#include "PlatformDefs.h"
15#else
16// This is needed, a forward declaration of FILETIME
17// breaks everything
18#ifndef WIN32_LEAN_AND_MEAN
19#define WIN32_LEAN_AND_MEAN
20#endif
21#include <Windows.h>
22#endif
23
24namespace KODI
25{
26namespace TIME
27{
28struct SystemTime
29{
30 unsigned short year;
31 unsigned short month;
32 unsigned short dayOfWeek;
33 unsigned short day;
34 unsigned short hour;
35 unsigned short minute;
36 unsigned short second;
37 unsigned short milliseconds;
38};
39
40struct TimeZoneInformation
41{
42 long bias;
43 std::string standardName;
44 SystemTime standardDate;
45 long standardBias;
46 std::string daylightName;
47 SystemTime daylightDate;
48 long daylightBias;
49};
50
51constexpr int KODI_TIME_ZONE_ID_INVALID{-1};
52constexpr int KODI_TIME_ZONE_ID_UNKNOWN{0};
53constexpr int KODI_TIME_ZONE_ID_STANDARD{1};
54constexpr int KODI_TIME_ZONE_ID_DAYLIGHT{2};
55
56struct FileTime
57{
58 unsigned int lowDateTime;
59 unsigned int highDateTime;
60};
61
62void GetLocalTime(SystemTime* systemTime);
63uint32_t GetTimeZoneInformation(TimeZoneInformation* timeZoneInformation);
64
65void Sleep(uint32_t milliSeconds);
66
67int FileTimeToLocalFileTime(const FileTime* fileTime, FileTime* localFileTime);
68int SystemTimeToFileTime(const SystemTime* systemTime, FileTime* fileTime);
69long CompareFileTime(const FileTime* fileTime1, const FileTime* fileTime2);
70int FileTimeToSystemTime(const FileTime* fileTime, SystemTime* systemTime);
71int LocalFileTimeToFileTime(const FileTime* LocalFileTime, FileTime* fileTime);
72
73int FileTimeToTimeT(const FileTime* localFileTime, time_t* pTimeT);
74int TimeTToFileTime(time_t timeT, FileTime* localFileTime);
75} // namespace TIME
76} // namespace KODI
diff --git a/xbmc/utils/auto_buffer.cpp b/xbmc/utils/auto_buffer.cpp
new file mode 100644
index 0000000..e88a960
--- /dev/null
+++ b/xbmc/utils/auto_buffer.cpp
@@ -0,0 +1,84 @@
1/*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "auto_buffer.h"
10
11#include <new> // for std::bad_alloc
12#include <stdlib.h> // for malloc(), realloc() and free()
13
14using namespace XUTILS;
15
16auto_buffer::auto_buffer(size_t size)
17{
18 if (!size)
19 return;
20
21 p = malloc(size); // "malloc()" instead of "new" allow to use "realloc()"
22 if (!p)
23 throw std::bad_alloc();
24 s = size;
25}
26
27auto_buffer::~auto_buffer()
28{
29 free(p);
30}
31
32auto_buffer& auto_buffer::allocate(size_t size)
33{
34 clear();
35 if (size)
36 {
37 p = malloc(size);
38 if (!p)
39 throw std::bad_alloc();
40 s = size;
41 }
42 return *this;
43}
44
45auto_buffer& auto_buffer::resize(size_t newSize)
46{
47 if (!newSize)
48 return clear();
49
50 void* newPtr = realloc(p, newSize);
51 if (!newPtr)
52 throw std::bad_alloc();
53 p = newPtr;
54 s = newSize;
55 return *this;
56}
57
58auto_buffer& auto_buffer::clear(void)
59{
60 free(p);
61 p = 0;
62 s = 0;
63 return *this;
64}
65
66auto_buffer& auto_buffer::attach(void* pointer, size_t size)
67{
68 clear();
69 if ((pointer && size) || (!pointer && !size))
70 {
71 p = pointer;
72 s = size;
73 }
74 return *this;
75}
76
77void* auto_buffer::detach(void)
78{
79 void* returnPtr = p;
80 p = 0;
81 s = 0;
82 return returnPtr;
83}
84
diff --git a/xbmc/utils/auto_buffer.h b/xbmc/utils/auto_buffer.h
new file mode 100644
index 0000000..066b6f8
--- /dev/null
+++ b/xbmc/utils/auto_buffer.h
@@ -0,0 +1,93 @@
1/*
2 * Copyright (C) 2013-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <stddef.h> // for size_t
12
13namespace XUTILS
14{
15
16 class auto_buffer
17 {
18 public:
19 /**
20 * Create buffer with zero size
21 */
22 auto_buffer(void) = default;
23 /**
24 * Create buffer with specified size
25 * @param size of created buffer
26 */
27 explicit auto_buffer(size_t size);
28 ~auto_buffer();
29
30 /**
31 * Allocate specified size for buffer, discarding current buffer content
32 * @param size of buffer to allocate
33 * @return reference to itself
34 */
35 auto_buffer& allocate(size_t size);
36 /**
37 * Resize current buffer to new size. Buffer will be extended or truncated at the end.
38 * @param newSize of buffer
39 * @return reference to itself
40 */
41 auto_buffer& resize(size_t newSize);
42 /**
43 * Reset buffer to zero size
44 * @return reference to itself
45 */
46 auto_buffer& clear(void);
47
48 /**
49 * Get pointer to buffer content
50 * @return pointer to buffer content or NULL if buffer is zero size
51 */
52 inline char* get(void) { return static_cast<char*>(p); }
53 /**
54 * Get constant pointer to buffer content
55 * @return constant pointer to buffer content
56 */
57 inline const char* get(void) const { return static_cast<char*>(p); }
58 /**
59 * Get size of the buffer
60 * @return size of the buffer
61 */
62 inline size_t size(void) const { return s; }
63 /**
64 * Get size of the buffer
65 * @return size of the buffer
66 */
67 inline size_t length(void) const { return s; }
68
69 /**
70 * Attach malloc'ed pointer to the buffer, discarding current buffer content
71 * Pointer must be acquired by malloc() or realloc().
72 * Pointer will be automatically freed on destroy of the buffer.
73 * @param pointer to attach
74 * @param size of new memory region pointed by pointer
75 * @return reference to itself
76 */
77 auto_buffer& attach(void* pointer, size_t size);
78 /**
79 * Detach current buffer content from the buffer, reset buffer to zero size
80 * Caller is responsible to free memory by calling free() for returned pointer
81 * when pointer in not needed anymore
82 * @return detached from buffer pointer to content
83 */
84 void* detach(void);
85
86 private:
87 auto_buffer(const auto_buffer& other) = delete; // disallow copy constructor
88 auto_buffer& operator=(const auto_buffer& other) = delete; // disallow assignment
89
90 void* p = 0;
91 size_t s = 0;
92 };
93}
diff --git a/xbmc/utils/log.cpp b/xbmc/utils/log.cpp
new file mode 100644
index 0000000..7fb87fe
--- /dev/null
+++ b/xbmc/utils/log.cpp
@@ -0,0 +1,288 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "log.h"
10
11#include "CompileInfo.h"
12#include "ServiceBroker.h"
13#include "filesystem/File.h"
14#include "guilib/LocalizeStrings.h"
15#if defined(TARGET_ANDROID)
16#include "platform/android/utils/AndroidInterfaceForCLog.h"
17#elif defined(TARGET_DARWIN)
18#include "platform/darwin/utils/DarwinInterfaceForCLog.h"
19#elif defined(TARGET_WINDOWS) || defined(TARGET_WIN10)
20#include "platform/win32/utils/Win32InterfaceForCLog.h"
21#else
22#include "platform/posix/utils/PosixInterfaceForCLog.h"
23#endif
24#include "settings/SettingUtils.h"
25#include "settings/Settings.h"
26#include "settings/SettingsComponent.h"
27#include "settings/lib/Setting.h"
28#include "settings/lib/SettingsManager.h"
29#include "utils/URIUtils.h"
30
31#include <cstring>
32#include <set>
33
34#include <spdlog/sinks/basic_file_sink.h>
35#include <spdlog/sinks/dist_sink.h>
36
37static constexpr unsigned char Utf8Bom[3] = {0xEF, 0xBB, 0xBF};
38static const std::string LogFileExtension = ".log";
39static const std::string LogPattern = "%Y-%m-%d %T.%e T:%-5t %7l <%n>: %v";
40
41CLog::CLog()
42 : m_platform(IPlatformLog::CreatePlatformLog()),
43 m_sinks(std::make_shared<spdlog::sinks::dist_sink_mt>()),
44 m_defaultLogger(CreateLogger("general")),
45 m_logLevel(LOG_LEVEL_DEBUG),
46 m_componentLogEnabled(false),
47 m_componentLogLevels(0)
48{
49 // add platform-specific debug sinks
50 m_platform->AddSinks(m_sinks);
51
52 // register the default logger with spdlog
53 spdlog::set_default_logger(m_defaultLogger);
54
55 // set the formatting pattern globally
56 spdlog::set_pattern(LogPattern);
57
58 // flush on debug logs
59 spdlog::flush_on(spdlog::level::debug);
60
61 // set the log level
62 SetLogLevel(m_logLevel);
63}
64
65void CLog::OnSettingsLoaded()
66{
67 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
68 m_componentLogEnabled = settings->GetBool(CSettings::SETTING_DEBUG_EXTRALOGGING);
69 SetComponentLogLevel(settings->GetList(CSettings::SETTING_DEBUG_SETEXTRALOGLEVEL));
70}
71
72void CLog::OnSettingChanged(std::shared_ptr<const CSetting> setting)
73{
74 if (setting == NULL)
75 return;
76
77 const std::string& settingId = setting->GetId();
78 if (settingId == CSettings::SETTING_DEBUG_EXTRALOGGING)
79 m_componentLogEnabled = std::static_pointer_cast<const CSettingBool>(setting)->GetValue();
80 else if (settingId == CSettings::SETTING_DEBUG_SETEXTRALOGLEVEL)
81 SetComponentLogLevel(
82 CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(setting)));
83}
84
85void CLog::Initialize(const std::string& path)
86{
87 if (m_fileSink != nullptr)
88 return;
89
90 // register setting callbacks
91 auto settingsManager =
92 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSettingsManager();
93 settingsManager->RegisterSettingOptionsFiller("loggingcomponents",
94 SettingOptionsLoggingComponentsFiller);
95 settingsManager->RegisterSettingsHandler(this);
96 std::set<std::string> settingSet;
97 settingSet.insert(CSettings::SETTING_DEBUG_EXTRALOGGING);
98 settingSet.insert(CSettings::SETTING_DEBUG_SETEXTRALOGLEVEL);
99 settingsManager->RegisterCallback(this, settingSet);
100
101 if (path.empty())
102 return;
103
104 // put together the path to the log file(s)
105 std::string appName = CCompileInfo::GetAppName();
106 StringUtils::ToLower(appName);
107 const std::string filePathBase = URIUtils::AddFileToFolder(path, appName);
108 const std::string filePath = filePathBase + LogFileExtension;
109 const std::string oldFilePath = filePathBase + ".old" + LogFileExtension;
110
111 // handle old.log by deleting an existing old.log and renaming the last log to old.log
112 XFILE::CFile::Delete(oldFilePath);
113 XFILE::CFile::Rename(filePath, oldFilePath);
114
115 // write UTF-8 BOM
116 {
117 XFILE::CFile file;
118 if (file.OpenForWrite(filePath, true))
119 file.Write(Utf8Bom, sizeof(Utf8Bom));
120 }
121
122 // create the file sink
123 m_fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(
124 m_platform->GetLogFilename(filePath), false);
125 m_fileSink->set_pattern(LogPattern);
126
127 // add it to the existing sinks
128 m_sinks->add_sink(m_fileSink);
129}
130
131void CLog::Uninitialize()
132{
133 if (m_fileSink == nullptr)
134 return;
135
136 // unregister setting callbacks
137 auto settingsManager =
138 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSettingsManager();
139 settingsManager->UnregisterSettingOptionsFiller("loggingcomponents");
140 settingsManager->UnregisterSettingsHandler(this);
141 settingsManager->UnregisterCallback(this);
142
143 // flush all loggers
144 spdlog::apply_all([](std::shared_ptr<spdlog::logger> logger) { logger->flush(); });
145
146 // flush the file sink
147 m_fileSink->flush();
148
149 // remove and destroy the file sink
150 m_sinks->remove_sink(m_fileSink);
151 m_fileSink.reset();
152}
153
154void CLog::SetLogLevel(int level)
155{
156 if (level < LOG_LEVEL_NONE || level > LOG_LEVEL_MAX)
157 return;
158
159 m_logLevel = level;
160
161 auto spdLevel = spdlog::level::info;
162 if (level <= LOG_LEVEL_NONE)
163 spdLevel = spdlog::level::off;
164 else if (level >= LOG_LEVEL_DEBUG)
165 spdLevel = spdlog::level::trace;
166
167 if (m_defaultLogger != nullptr && m_defaultLogger->level() == spdLevel)
168 return;
169
170 spdlog::set_level(spdLevel);
171 FormatAndLogInternal(spdlog::level::info, "Log level changed to \"{}\"",
172 spdlog::level::to_string_view(spdLevel));
173}
174
175bool CLog::IsLogLevelLogged(int loglevel)
176{
177 if (m_logLevel >= LOG_LEVEL_DEBUG)
178 return true;
179 if (m_logLevel <= LOG_LEVEL_NONE)
180 return false;
181
182 return (loglevel & LOGMASK) >= LOGNOTICE;
183}
184
185bool CLog::CanLogComponent(uint32_t component) const
186{
187 if (!m_componentLogEnabled || component == 0)
188 return false;
189
190 return ((m_componentLogLevels & component) == component);
191}
192
193void CLog::SettingOptionsLoggingComponentsFiller(SettingConstPtr setting,
194 std::vector<IntegerSettingOption>& list,
195 int& current,
196 void* data)
197{
198 list.emplace_back(g_localizeStrings.Get(669), LOGSAMBA);
199 list.emplace_back(g_localizeStrings.Get(670), LOGCURL);
200 list.emplace_back(g_localizeStrings.Get(672), LOGFFMPEG);
201 list.emplace_back(g_localizeStrings.Get(675), LOGJSONRPC);
202 list.emplace_back(g_localizeStrings.Get(676), LOGAUDIO);
203 list.emplace_back(g_localizeStrings.Get(680), LOGVIDEO);
204 list.emplace_back(g_localizeStrings.Get(683), LOGAVTIMING);
205 list.emplace_back(g_localizeStrings.Get(684), LOGWINDOWING);
206 list.emplace_back(g_localizeStrings.Get(685), LOGPVR);
207 list.emplace_back(g_localizeStrings.Get(686), LOGEPG);
208 list.emplace_back(g_localizeStrings.Get(39117), LOGANNOUNCE);
209#ifdef HAS_DBUS
210 list.emplace_back(g_localizeStrings.Get(674), LOGDBUS);
211#endif
212#ifdef HAS_WEB_SERVER
213 list.emplace_back(g_localizeStrings.Get(681), LOGWEBSERVER);
214#endif
215#ifdef HAS_AIRTUNES
216 list.emplace_back(g_localizeStrings.Get(677), LOGAIRTUNES);
217#endif
218#ifdef HAS_UPNP
219 list.emplace_back(g_localizeStrings.Get(678), LOGUPNP);
220#endif
221#ifdef HAVE_LIBCEC
222 list.emplace_back(g_localizeStrings.Get(679), LOGCEC);
223#endif
224 list.emplace_back(g_localizeStrings.Get(682), LOGDATABASE);
225}
226
227Logger CLog::GetLogger(const std::string& loggerName)
228{
229 auto logger = spdlog::get(loggerName);
230 if (logger == nullptr)
231 logger = CreateLogger(loggerName);
232
233 return logger;
234}
235
236CLog& CLog::GetInstance()
237{
238 return CServiceBroker::GetLogging();
239}
240
241spdlog::level::level_enum CLog::MapLogLevel(int level)
242{
243 switch (level)
244 {
245 case LOGDEBUG:
246 return spdlog::level::debug;
247 case LOGINFO:
248 case LOGNOTICE:
249 return spdlog::level::info;
250 case LOGWARNING:
251 return spdlog::level::warn;
252 case LOGERROR:
253 return spdlog::level::err;
254 case LOGSEVERE:
255 case LOGFATAL:
256 return spdlog::level::critical;
257 case LOGNONE:
258 return spdlog::level::off;
259
260 default:
261 break;
262 }
263
264 return spdlog::level::info;
265}
266
267Logger CLog::CreateLogger(const std::string& loggerName)
268{
269 // create the logger
270 auto logger = std::make_shared<spdlog::logger>(loggerName, m_sinks);
271
272 // initialize the logger
273 spdlog::initialize_logger(logger);
274
275 return logger;
276}
277
278void CLog::SetComponentLogLevel(const std::vector<CVariant>& components)
279{
280 m_componentLogLevels = 0;
281 for (const auto& component : components)
282 {
283 if (!component.isInteger())
284 continue;
285
286 m_componentLogLevels |= static_cast<uint32_t>(component.asInteger());
287 }
288}
diff --git a/xbmc/utils/log.h b/xbmc/utils/log.h
new file mode 100644
index 0000000..7287918
--- /dev/null
+++ b/xbmc/utils/log.h
@@ -0,0 +1,217 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11// spdlog specific defines
12#define SPDLOG_LEVEL_NAMES {"TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "FATAL", "OFF"};
13
14#include "commons/ilog.h"
15#include "settings/lib/ISettingCallback.h"
16#include "settings/lib/ISettingsHandler.h"
17#include "settings/lib/SettingDefinitions.h"
18#include "utils/IPlatformLog.h"
19#include "utils/StringUtils.h"
20#include "utils/logtypes.h"
21
22#include <string>
23#include <vector>
24
25#include <spdlog/spdlog.h>
26
27namespace spdlog
28{
29namespace sinks
30{
31template<typename Mutex>
32class basic_file_sink;
33
34template<typename Mutex>
35class dist_sink;
36} // namespace sinks
37} // namespace spdlog
38
39class CLog : public ISettingsHandler, public ISettingCallback
40{
41public:
42 CLog();
43 ~CLog() = default;
44
45 // implementation of ISettingsHandler
46 void OnSettingsLoaded() override;
47
48 // implementation of ISettingCallback
49 void OnSettingChanged(std::shared_ptr<const CSetting> setting) override;
50
51 void Initialize(const std::string& path);
52 void Uninitialize();
53
54 void SetLogLevel(int level);
55 int GetLogLevel() { return m_logLevel; }
56 bool IsLogLevelLogged(int loglevel);
57
58 bool CanLogComponent(uint32_t component) const;
59 static void SettingOptionsLoggingComponentsFiller(std::shared_ptr<const CSetting> setting,
60 std::vector<IntegerSettingOption>& list,
61 int& current,
62 void* data);
63
64 Logger GetLogger(const std::string& loggerName);
65
66 template<typename Char, typename... Args>
67 static inline void Log(int level, const Char* format, Args&&... args)
68 {
69 Log(MapLogLevel(level), format, std::forward<Args>(args)...);
70 }
71
72 template<typename Char, typename... Args>
73 static inline void Log(int level, uint32_t component, const Char* format, Args&&... args)
74 {
75 if (!GetInstance().CanLogComponent(component))
76 return;
77
78 Log(level, format, std::forward<Args>(args)...);
79 }
80
81 template<typename Char, typename... Args>
82 static inline void Log(spdlog::level::level_enum level, const Char* format, Args&&... args)
83 {
84 GetInstance().FormatAndLogInternal(level, format, std::forward<Args>(args)...);
85 }
86
87 template<typename Char, typename... Args>
88 static inline void Log(spdlog::level::level_enum level,
89 uint32_t component,
90 const Char* format,
91 Args&&... args)
92 {
93 if (!GetInstance().CanLogComponent(component))
94 return;
95
96 Log(level, format, std::forward<Args>(args)...);
97 }
98
99 template<typename Char, typename... Args>
100 static inline void LogFunction(int level,
101 const char* functionName,
102 const Char* format,
103 Args&&... args)
104 {
105 LogFunction(MapLogLevel(level), functionName, format, std::forward<Args>(args)...);
106 }
107
108 template<typename Char, typename... Args>
109 static inline void LogFunction(
110 int level, const char* functionName, uint32_t component, const Char* format, Args&&... args)
111 {
112 if (!GetInstance().CanLogComponent(component))
113 return;
114
115 LogFunction(level, functionName, format, std::forward<Args>(args)...);
116 }
117
118 template<typename Char, typename... Args>
119 static inline void LogFunction(spdlog::level::level_enum level,
120 const char* functionName,
121 const Char* format,
122 Args&&... args)
123 {
124 if (functionName == nullptr || strlen(functionName) == 0)
125 GetInstance().FormatAndLogInternal(level, format, std::forward<Args>(args)...);
126 else
127 GetInstance().FormatAndLogFunctionInternal(level, functionName, format,
128 std::forward<Args>(args)...);
129 }
130
131 template<typename Char, typename... Args>
132 static inline void LogFunction(spdlog::level::level_enum level,
133 const char* functionName,
134 uint32_t component,
135 const Char* format,
136 Args&&... args)
137 {
138 if (!GetInstance().CanLogComponent(component))
139 return;
140
141 LogFunction(level, functionName, format, std::forward<Args>(args)...);
142 }
143
144#define LogF(level, format, ...) LogFunction((level), __FUNCTION__, (format), ##__VA_ARGS__)
145#define LogFC(level, component, format, ...) \
146 LogFunction((level), __FUNCTION__, (component), (format), ##__VA_ARGS__)
147
148private:
149 static CLog& GetInstance();
150
151 static spdlog::level::level_enum MapLogLevel(int level);
152
153 template<typename... Args>
154 static inline void FormatAndLogFunctionInternal(spdlog::level::level_enum level,
155 const char* functionName,
156 const char* format,
157 Args&&... args)
158 {
159 GetInstance().FormatAndLogInternal(
160 level, StringUtils::Format("{0:s}: {1:s}", functionName, format).c_str(),
161 std::forward<Args>(args)...);
162 }
163
164 template<typename... Args>
165 static inline void FormatAndLogFunctionInternal(spdlog::level::level_enum level,
166 const char* functionName,
167 const wchar_t* format,
168 Args&&... args)
169 {
170 GetInstance().FormatAndLogInternal(
171 level, StringUtils::Format(L"{0:s}: {1:s}", functionName, format).c_str(),
172 std::forward<Args>(args)...);
173 }
174
175 template<typename Char, typename... Args>
176 inline void FormatAndLogInternal(spdlog::level::level_enum level,
177 const Char* format,
178 Args&&... args)
179 {
180 // TODO: for now we manually format the messages to support both python- and printf-style formatting.
181 // this can be removed once all log messages have been adjusted to python-style formatting
182 auto logString = StringUtils::Format(format, std::forward<Args>(args)...);
183
184 // fixup newline alignment, number of spaces should equal prefix length
185 StringUtils::Replace(logString, "\n", "\n ");
186
187 m_defaultLogger->log(level, std::move(logString));
188 }
189
190 Logger CreateLogger(const std::string& loggerName);
191
192 void SetComponentLogLevel(const std::vector<CVariant>& components);
193
194 std::unique_ptr<IPlatformLog> m_platform;
195 std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> m_sinks;
196 Logger m_defaultLogger;
197
198 std::shared_ptr<spdlog::sinks::basic_file_sink<std::mutex>> m_fileSink;
199
200 int m_logLevel;
201
202 bool m_componentLogEnabled;
203 uint32_t m_componentLogLevels;
204};
205
206namespace XbmcUtils
207{
208class LogImplementation : public XbmcCommons::ILogger
209{
210public:
211 ~LogImplementation() override = default;
212 inline void log(int logLevel, IN_STRING const char* message) override
213 {
214 CLog::Log(logLevel, "{0:s}", message);
215 }
216};
217} // namespace XbmcUtils
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 @@
1/*
2 * Copyright (C) 2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include <memory>
12
13namespace spdlog
14{
15class logger;
16}
17
18using 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..30f2355
--- /dev/null
+++ b/xbmc/utils/params_check_macros.h
@@ -0,0 +1,62 @@
1/*
2 * Copyright (C) 2014-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11// macros for gcc, clang & others
12#ifndef PARAM1_PRINTF_FORMAT
13#ifdef __GNUC__
14// for use in functions that take printf format string as first parameter and additional printf parameters as second parameter
15// for example: int myprintf(const char* format, ...) PARAM1_PRINTF_FORMAT;
16#define PARAM1_PRINTF_FORMAT __attribute__((format(printf,1,2)))
17
18// for use in functions that take printf format string as second parameter and additional printf parameters as third parameter
19// for example: bool log_string(int logLevel, const char* format, ...) PARAM2_PRINTF_FORMAT;
20// note: all non-static class member functions take pointer to class object as hidden first parameter
21#define PARAM2_PRINTF_FORMAT __attribute__((format(printf,2,3)))
22
23// for use in functions that take printf format string as third parameter and additional printf parameters as fourth parameter
24// note: all non-static class member functions take pointer to class object as hidden first parameter
25// for example: class A { bool log_string(int logLevel, const char* functionName, const char* format, ...) PARAM3_PRINTF_FORMAT; };
26#define PARAM3_PRINTF_FORMAT __attribute__((format(printf,3,4)))
27
28// for use in functions that take printf format string as fourth parameter and additional printf parameters as fith parameter
29// note: all non-static class member functions take pointer to class object as hidden first parameter
30// for example: class A { bool log_string(int logLevel, const char* functionName, int component, const char* format, ...) PARAM4_PRINTF_FORMAT; };
31#define PARAM4_PRINTF_FORMAT __attribute__((format(printf,4,5)))
32#else // ! __GNUC__
33#define PARAM1_PRINTF_FORMAT
34#define PARAM2_PRINTF_FORMAT
35#define PARAM3_PRINTF_FORMAT
36#define PARAM4_PRINTF_FORMAT
37#endif // ! __GNUC__
38#endif // PARAM1_PRINTF_FORMAT
39
40// macros for VC
41// VC check parameters only when "Code Analysis" is called
42#ifndef PRINTF_FORMAT_STRING
43#ifdef _MSC_VER
44#include <sal.h>
45
46// for use in any function that take printf format string and parameters
47// for example: bool log_string(int logLevel, PRINTF_FORMAT_STRING const char* format, ...);
48#define PRINTF_FORMAT_STRING _In_z_ _Printf_format_string_
49
50// specify that parameter must be zero-terminated string
51// for example: void SetName(IN_STRING const char* newName);
52#define IN_STRING _In_z_
53
54// specify that parameter must be zero-terminated string or NULL
55// for example: bool SetAdditionalName(IN_OPT_STRING const char* addName);
56#define IN_OPT_STRING _In_opt_z_
57#else // ! _MSC_VER
58#define PRINTF_FORMAT_STRING
59#define IN_STRING
60#define IN_OPT_STRING
61#endif // ! _MSC_VER
62#endif // PRINTF_FORMAT_STRING
diff --git a/xbmc/utils/rfft.cpp b/xbmc/utils/rfft.cpp
new file mode 100644
index 0000000..871eea7
--- /dev/null
+++ b/xbmc/utils/rfft.cpp
@@ -0,0 +1,72 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "rfft.h"
10
11#if defined(TARGET_WINDOWS) && !defined(_USE_MATH_DEFINES)
12#define _USE_MATH_DEFINES
13#endif
14#include <math.h>
15
16RFFT::RFFT(int size, bool windowed) :
17 m_size(size), m_windowed(windowed)
18{
19 m_cfg = kiss_fftr_alloc(m_size,0,nullptr,nullptr);
20}
21
22RFFT::~RFFT()
23{
24 // we don' use kiss_fftr_free here because
25 // its hardcoded to free and doesn't pay attention
26 // to SIMD (which might be used during kiss_fftr_alloc
27 //in the C'tor).
28 KISS_FFT_FREE(m_cfg);
29}
30
31void RFFT::calc(const float* input, float* output)
32{
33 // temporary buffers
34 std::vector<kiss_fft_scalar> linput(m_size), rinput(m_size);
35 std::vector<kiss_fft_cpx> loutput(m_size), routput(m_size);
36
37 for (size_t i=0;i<m_size;++i)
38 {
39 linput[i] = input[2*i];
40 rinput[i] = input[2*i+1];
41 }
42
43 if (m_windowed)
44 {
45 hann(linput);
46 hann(rinput);
47 }
48
49 // transform channels
50 kiss_fftr(m_cfg, &linput[0], &loutput[0]);
51 kiss_fftr(m_cfg, &rinput[0], &routput[0]);
52
53 auto&& filter = [&](kiss_fft_cpx& data)
54 {
55 return sqrt(data.r*data.r+data.i*data.i) * 2.0/m_size * (m_windowed?sqrt(8.0/3.0):1.0);
56 };
57
58 // interleave while taking magnitudes and normalizing
59 for (size_t i=0;i<m_size/2;++i)
60 {
61 output[2*i] = filter(loutput[i]);
62 output[2*i+1] = filter(routput[i]);
63 }
64}
65
66#include <iostream>
67
68void RFFT::hann(std::vector<kiss_fft_scalar>& data)
69{
70 for (size_t i=0;i<data.size();++i)
71 data[i] *= 0.5*(1.0-cos(2*M_PI*i/(data.size()-1)));
72}
diff --git a/xbmc/utils/rfft.h b/xbmc/utils/rfft.h
new file mode 100644
index 0000000..0ec151d
--- /dev/null
+++ b/xbmc/utils/rfft.h
@@ -0,0 +1,39 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "contrib/kissfft/kiss_fftr.h"
12
13#include <vector>
14
15//! \brief Class performing a RFFT of interleaved stereo data.
16class RFFT
17{
18public:
19 //! \brief The constructor creates a RFFT plan.
20 //! \brief size Length of time data for a single channel.
21 //! \brief windowed Whether or not to apply a Hann window to data.
22 RFFT(int size, bool windowed=false);
23
24 //! \brief Free the RFFT plan
25 ~RFFT();
26
27 //! \brief Calculate FFTs
28 //! \param input Input data of size 2*m_size
29 //! \param output Output data of size m_size.
30 void calc(const float* input, float* output);
31protected:
32 //! \brief Apply a Hann window to a buffer.
33 //! \param data Vector with data to apply window to.
34 static void hann(std::vector<kiss_fft_scalar>& data);
35
36 size_t m_size; //!< Size for a single channel.
37 bool m_windowed; //!< Whether or not a Hann window is applied.
38 kiss_fftr_cfg m_cfg; //!< FFT plan
39};
diff --git a/xbmc/utils/test/CMakeLists.txt b/xbmc/utils/test/CMakeLists.txt
new file mode 100644
index 0000000..e953af6
--- /dev/null
+++ b/xbmc/utils/test/CMakeLists.txt
@@ -0,0 +1,53 @@
1set(SOURCES TestAlarmClock.cpp
2 TestAliasShortcutUtils.cpp
3 TestArchive.cpp
4 TestBase64.cpp
5 TestBitstreamStats.cpp
6 TestCharsetConverter.cpp
7 TestCPUInfo.cpp
8 TestCrc32.cpp
9 TestDatabaseUtils.cpp
10 TestDigest.cpp
11 TestEndianSwap.cpp
12 TestFileOperationJob.cpp
13 TestFileUtils.cpp
14 TestGlobalsHandling.cpp
15 TestHTMLUtil.cpp
16 TestHttpHeader.cpp
17 TestHttpParser.cpp
18 TestHttpRangeUtils.cpp
19 TestHttpResponse.cpp
20 TestJobManager.cpp
21 TestJSONVariantParser.cpp
22 TestJSONVariantWriter.cpp
23 TestLabelFormatter.cpp
24 TestLangCodeExpander.cpp
25 TestLocale.cpp
26 Testlog.cpp
27 TestMathUtils.cpp
28 TestMime.cpp
29 TestPOUtils.cpp
30 TestRegExp.cpp
31 Testrfft.cpp
32 TestRingBuffer.cpp
33 TestScraperParser.cpp
34 TestScraperUrl.cpp
35 TestSortUtils.cpp
36 TestStopwatch.cpp
37 TestStreamDetails.cpp
38 TestStreamUtils.cpp
39 TestStringUtils.cpp
40 TestSystemInfo.cpp
41 TestURIUtils.cpp
42 TestUrlOptions.cpp
43 TestVariant.cpp
44 TestXBMCTinyXML.cpp
45 TestXMLUtils.cpp)
46
47set(HEADERS TestGlobalsHandlingPattern1.h)
48
49if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore)
50 list(APPEND SOURCES TestCryptThreading.cpp)
51endif()
52
53core_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 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<details>
3 <url function="ParseTMDBRating" cache="tmdb-en-12244.json">
4 http://api.themoviedb.org/3/movie/12244?api_key=57983e31fb435df4df77afb854740ea9&language=en&#x3f;&#x003F;&#0063;
5 </url>
6</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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/AlarmClock.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestAlarmClock, General)
14{
15 CAlarmClock a;
16 EXPECT_FALSE(a.IsRunning());
17 EXPECT_FALSE(a.HasAlarm("test"));
18 a.Start("test", 100.f, "test");
19 EXPECT_TRUE(a.IsRunning());
20 EXPECT_TRUE(a.HasAlarm("test"));
21 EXPECT_FALSE(a.HasAlarm("test2"));
22 EXPECT_NE(0.f, a.GetRemaining("test"));
23 EXPECT_EQ(0.f, a.GetRemaining("test2"));
24 a.Stop("test");
25}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/AliasShortcutUtils.h"
10#include "filesystem/File.h"
11#include "test/TestUtils.h"
12
13#if defined(TARGET_DARWIN_OSX)
14#include "platform/darwin/DarwinUtils.h"
15#endif
16#include <gtest/gtest.h>
17
18TEST(TestAliasShortcutUtils, IsAliasShortcut)
19{
20 XFILE::CFile *tmpFile = XBMC_CREATETEMPFILE("noaliastest");
21 std::string noalias = XBMC_TEMPFILEPATH(tmpFile);
22
23#if defined(TARGET_DARWIN_OSX)
24 XFILE::CFile *aliasDestFile = XBMC_CREATETEMPFILE("aliastest");
25 std::string alias = XBMC_TEMPFILEPATH(aliasDestFile);
26
27 //we only need the path here so delete the alias file
28 //which will be recreated as shortcut later:
29 XBMC_DELETETEMPFILE(aliasDestFile);
30
31 // create alias from a pointing to /Volumes
32 CDarwinUtils::CreateAliasShortcut(alias, "/Volumes");
33 EXPECT_TRUE(IsAliasShortcut(alias, true));
34 XFILE::CFile::Delete(alias);
35
36 // volumes is not a shortcut but a dir
37 EXPECT_FALSE(IsAliasShortcut("/Volumes", true));
38#endif
39
40 // a regular file is not a shortcut
41 EXPECT_FALSE(IsAliasShortcut(noalias, false));
42 XBMC_DELETETEMPFILE(tmpFile);
43
44 // empty string is not an alias
45 std::string emptyString;
46 EXPECT_FALSE(IsAliasShortcut(emptyString, false));
47
48 // non-existent file is no alias
49 std::string nonExistingFile="/IDontExistsNormally/somefile.txt";
50 EXPECT_FALSE(IsAliasShortcut(nonExistingFile, false));
51}
52
53TEST(TestAliasShortcutUtils, TranslateAliasShortcut)
54{
55 XFILE::CFile *tmpFile = XBMC_CREATETEMPFILE("noaliastest");
56 std::string noalias = XBMC_TEMPFILEPATH(tmpFile);
57 std::string noaliastemp = noalias;
58
59#if defined(TARGET_DARWIN_OSX)
60 XFILE::CFile *aliasDestFile = XBMC_CREATETEMPFILE("aliastest");
61 std::string alias = XBMC_TEMPFILEPATH(aliasDestFile);
62
63 //we only need the path here so delete the alias file
64 //which will be recreated as shortcut later:
65 XBMC_DELETETEMPFILE(aliasDestFile);
66
67 // create alias from a pointing to /Volumes
68 CDarwinUtils::CreateAliasShortcut(alias, "/Volumes");
69
70 // resolve the shortcut
71 TranslateAliasShortcut(alias);
72 EXPECT_STREQ("/Volumes", alias.c_str());
73 XFILE::CFile::Delete(alias);
74#endif
75
76 // translating a non-shortcut url should result in no change...
77 TranslateAliasShortcut(noaliastemp);
78 EXPECT_STREQ(noaliastemp.c_str(), noalias.c_str());
79 XBMC_DELETETEMPFILE(tmpFile);
80
81 //translate empty should stay empty
82 std::string emptyString;
83 TranslateAliasShortcut(emptyString);
84 EXPECT_STREQ("", emptyString.c_str());
85
86 // translate non-existent file should result in no change...
87 std::string nonExistingFile="/IDontExistsNormally/somefile.txt";
88 std::string resolvedNonExistingFile=nonExistingFile;
89 TranslateAliasShortcut(resolvedNonExistingFile);
90 EXPECT_STREQ(resolvedNonExistingFile.c_str(), nonExistingFile.c_str());
91}
diff --git a/xbmc/utils/test/TestArchive.cpp b/xbmc/utils/test/TestArchive.cpp
new file mode 100644
index 0000000..65023fd
--- /dev/null
+++ b/xbmc/utils/test/TestArchive.cpp
@@ -0,0 +1,411 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#if defined(TARGET_WINDOWS)
10# include <windows.h>
11#endif
12
13#include "utils/Archive.h"
14#include "utils/Variant.h"
15#include "filesystem/File.h"
16
17#include "test/TestUtils.h"
18
19#include <gtest/gtest.h>
20
21class TestArchive : public testing::Test
22{
23protected:
24 TestArchive()
25 {
26 file = XBMC_CREATETEMPFILE(".ar");
27 }
28 ~TestArchive() override
29 {
30 EXPECT_TRUE(XBMC_DELETETEMPFILE(file));
31 }
32 XFILE::CFile *file;
33};
34
35TEST_F(TestArchive, IsStoring)
36{
37 ASSERT_NE(nullptr, file);
38 CArchive arstore(file, CArchive::store);
39 EXPECT_TRUE(arstore.IsStoring());
40 EXPECT_FALSE(arstore.IsLoading());
41 arstore.Close();
42}
43
44TEST_F(TestArchive, IsLoading)
45{
46 ASSERT_NE(nullptr, file);
47 CArchive arload(file, CArchive::load);
48 EXPECT_TRUE(arload.IsLoading());
49 EXPECT_FALSE(arload.IsStoring());
50 arload.Close();
51}
52
53TEST_F(TestArchive, FloatArchive)
54{
55 ASSERT_NE(nullptr, file);
56 float float_ref = 1, float_var = 0;
57
58 CArchive arstore(file, CArchive::store);
59 arstore << float_ref;
60 arstore.Close();
61
62 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
63 CArchive arload(file, CArchive::load);
64 arload >> float_var;
65 arload.Close();
66
67 EXPECT_EQ(float_ref, float_var);
68}
69
70TEST_F(TestArchive, DoubleArchive)
71{
72 ASSERT_NE(nullptr, file);
73 double double_ref = 2, double_var = 0;
74
75 CArchive arstore(file, CArchive::store);
76 arstore << double_ref;
77 arstore.Close();
78
79 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
80 CArchive arload(file, CArchive::load);
81 arload >> double_var;
82 arload.Close();
83
84 EXPECT_EQ(double_ref, double_var);
85}
86
87TEST_F(TestArchive, IntegerArchive)
88{
89 ASSERT_NE(nullptr, file);
90 int int_ref = 3, int_var = 0;
91
92 CArchive arstore(file, CArchive::store);
93 arstore << int_ref;
94 arstore.Close();
95
96 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
97 CArchive arload(file, CArchive::load);
98 arload >> int_var;
99 arload.Close();
100
101 EXPECT_EQ(int_ref, int_var);
102}
103
104TEST_F(TestArchive, UnsignedIntegerArchive)
105{
106 ASSERT_NE(nullptr, file);
107 unsigned int unsigned_int_ref = 4, unsigned_int_var = 0;
108
109 CArchive arstore(file, CArchive::store);
110 arstore << unsigned_int_ref;
111 arstore.Close();
112
113 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
114 CArchive arload(file, CArchive::load);
115 arload >> unsigned_int_var;
116 arload.Close();
117
118 EXPECT_EQ(unsigned_int_ref, unsigned_int_var);
119}
120
121TEST_F(TestArchive, Int64tArchive)
122{
123 ASSERT_NE(nullptr, file);
124 int64_t int64_t_ref = 5, int64_t_var = 0;
125
126 CArchive arstore(file, CArchive::store);
127 arstore << int64_t_ref;
128 arstore.Close();
129
130 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
131 CArchive arload(file, CArchive::load);
132 arload >> int64_t_var;
133 arload.Close();
134
135 EXPECT_EQ(int64_t_ref, int64_t_var);
136}
137
138TEST_F(TestArchive, UInt64tArchive)
139{
140 ASSERT_NE(nullptr, file);
141 uint64_t uint64_t_ref = 6, uint64_t_var = 0;
142
143 CArchive arstore(file, CArchive::store);
144 arstore << uint64_t_ref;
145 arstore.Close();
146
147 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
148 CArchive arload(file, CArchive::load);
149 arload >> uint64_t_var;
150 arload.Close();
151
152 EXPECT_EQ(uint64_t_ref, uint64_t_var);
153}
154
155TEST_F(TestArchive, BoolArchive)
156{
157 ASSERT_NE(nullptr, file);
158 bool bool_ref = true, bool_var = false;
159
160 CArchive arstore(file, CArchive::store);
161 arstore << bool_ref;
162 arstore.Close();
163
164 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
165 CArchive arload(file, CArchive::load);
166 arload >> bool_var;
167 arload.Close();
168
169 EXPECT_EQ(bool_ref, bool_var);
170}
171
172TEST_F(TestArchive, CharArchive)
173{
174 ASSERT_NE(nullptr, file);
175 char char_ref = 'A', char_var = '\0';
176
177 CArchive arstore(file, CArchive::store);
178 arstore << char_ref;
179 arstore.Close();
180
181 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
182 CArchive arload(file, CArchive::load);
183 arload >> char_var;
184 arload.Close();
185
186 EXPECT_EQ(char_ref, char_var);
187}
188
189TEST_F(TestArchive, WStringArchive)
190{
191 ASSERT_NE(nullptr, file);
192 std::wstring wstring_ref = L"test wstring", wstring_var;
193
194 CArchive arstore(file, CArchive::store);
195 arstore << wstring_ref;
196 arstore.Close();
197
198 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
199 CArchive arload(file, CArchive::load);
200 arload >> wstring_var;
201 arload.Close();
202
203 EXPECT_STREQ(wstring_ref.c_str(), wstring_var.c_str());
204}
205
206TEST_F(TestArchive, StringArchive)
207{
208 ASSERT_NE(nullptr, file);
209 std::string string_ref = "test string", string_var;
210
211 CArchive arstore(file, CArchive::store);
212 arstore << string_ref;
213 arstore.Close();
214
215 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
216 CArchive arload(file, CArchive::load);
217 arload >> string_var;
218 arload.Close();
219
220 EXPECT_STREQ(string_ref.c_str(), string_var.c_str());
221}
222
223TEST_F(TestArchive, SystemTimeArchive)
224{
225 ASSERT_NE(nullptr, file);
226 KODI::TIME::SystemTime SystemTime_ref = {1, 2, 3, 4, 5, 6, 7, 8};
227 KODI::TIME::SystemTime SystemTime_var = {0, 0, 0, 0, 0, 0, 0, 0};
228
229 CArchive arstore(file, CArchive::store);
230 arstore << SystemTime_ref;
231 arstore.Close();
232
233 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
234 CArchive arload(file, CArchive::load);
235 arload >> SystemTime_var;
236 arload.Close();
237
238 EXPECT_TRUE(!memcmp(&SystemTime_ref, &SystemTime_var, sizeof(KODI::TIME::SystemTime)));
239}
240
241TEST_F(TestArchive, CVariantArchive)
242{
243 ASSERT_NE(nullptr, file);
244 CVariant CVariant_ref((int)1), CVariant_var;
245
246 CArchive arstore(file, CArchive::store);
247 arstore << CVariant_ref;
248 arstore.Close();
249
250 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
251 CArchive arload(file, CArchive::load);
252 arload >> CVariant_var;
253 arload.Close();
254
255 EXPECT_TRUE(CVariant_var.isInteger());
256 EXPECT_EQ(1, CVariant_var.asInteger());
257}
258
259TEST_F(TestArchive, CVariantArchiveString)
260{
261 ASSERT_NE(nullptr, file);
262 CVariant CVariant_ref("teststring"), CVariant_var;
263
264 CArchive arstore(file, CArchive::store);
265 arstore << CVariant_ref;
266 arstore.Close();
267
268 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
269 CArchive arload(file, CArchive::load);
270 arload >> CVariant_var;
271 arload.Close();
272
273 EXPECT_TRUE(CVariant_var.isString());
274 EXPECT_STREQ("teststring", CVariant_var.asString().c_str());
275}
276
277TEST_F(TestArchive, StringVectorArchive)
278{
279 ASSERT_NE(nullptr, file);
280 std::vector<std::string> strArray_ref, strArray_var;
281 strArray_ref.emplace_back("test strArray_ref 0");
282 strArray_ref.emplace_back("test strArray_ref 1");
283 strArray_ref.emplace_back("test strArray_ref 2");
284 strArray_ref.emplace_back("test strArray_ref 3");
285
286 CArchive arstore(file, CArchive::store);
287 arstore << strArray_ref;
288 arstore.Close();
289
290 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
291 CArchive arload(file, CArchive::load);
292 arload >> strArray_var;
293 arload.Close();
294
295 EXPECT_STREQ("test strArray_ref 0", strArray_var.at(0).c_str());
296 EXPECT_STREQ("test strArray_ref 1", strArray_var.at(1).c_str());
297 EXPECT_STREQ("test strArray_ref 2", strArray_var.at(2).c_str());
298 EXPECT_STREQ("test strArray_ref 3", strArray_var.at(3).c_str());
299}
300
301TEST_F(TestArchive, IntegerVectorArchive)
302{
303 ASSERT_NE(nullptr, file);
304 std::vector<int> iArray_ref, iArray_var;
305 iArray_ref.push_back(0);
306 iArray_ref.push_back(1);
307 iArray_ref.push_back(2);
308 iArray_ref.push_back(3);
309
310 CArchive arstore(file, CArchive::store);
311 arstore << iArray_ref;
312 arstore.Close();
313
314 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
315 CArchive arload(file, CArchive::load);
316 arload >> iArray_var;
317 arload.Close();
318
319 EXPECT_EQ(0, iArray_var.at(0));
320 EXPECT_EQ(1, iArray_var.at(1));
321 EXPECT_EQ(2, iArray_var.at(2));
322 EXPECT_EQ(3, iArray_var.at(3));
323}
324
325TEST_F(TestArchive, MultiTypeArchive)
326{
327 ASSERT_NE(nullptr, file);
328 float float_ref = 1, float_var = 0;
329 double double_ref = 2, double_var = 0;
330 int int_ref = 3, int_var = 0;
331 unsigned int unsigned_int_ref = 4, unsigned_int_var = 0;
332 int64_t int64_t_ref = 5, int64_t_var = 0;
333 uint64_t uint64_t_ref = 6, uint64_t_var = 0;
334 bool bool_ref = true, bool_var = false;
335 char char_ref = 'A', char_var = '\0';
336 std::string string_ref = "test string", string_var;
337 std::wstring wstring_ref = L"test wstring", wstring_var;
338 KODI::TIME::SystemTime SystemTime_ref = {1, 2, 3, 4, 5, 6, 7, 8};
339 KODI::TIME::SystemTime SystemTime_var = {0, 0, 0, 0, 0, 0, 0, 0};
340 CVariant CVariant_ref((int)1), CVariant_var;
341 std::vector<std::string> strArray_ref, strArray_var;
342 strArray_ref.emplace_back("test strArray_ref 0");
343 strArray_ref.emplace_back("test strArray_ref 1");
344 strArray_ref.emplace_back("test strArray_ref 2");
345 strArray_ref.emplace_back("test strArray_ref 3");
346 std::vector<int> iArray_ref, iArray_var;
347 iArray_ref.push_back(0);
348 iArray_ref.push_back(1);
349 iArray_ref.push_back(2);
350 iArray_ref.push_back(3);
351
352 CArchive arstore(file, CArchive::store);
353 EXPECT_TRUE(arstore.IsStoring());
354 EXPECT_FALSE(arstore.IsLoading());
355 arstore << float_ref;
356 arstore << double_ref;
357 arstore << int_ref;
358 arstore << unsigned_int_ref;
359 arstore << int64_t_ref;
360 arstore << uint64_t_ref;
361 arstore << bool_ref;
362 arstore << char_ref;
363 arstore << string_ref;
364 arstore << wstring_ref;
365 arstore << SystemTime_ref;
366 arstore << CVariant_ref;
367 arstore << strArray_ref;
368 arstore << iArray_ref;
369 arstore.Close();
370
371 ASSERT_EQ(0, file->Seek(0, SEEK_SET));
372 CArchive arload(file, CArchive::load);
373 EXPECT_TRUE(arload.IsLoading());
374 EXPECT_FALSE(arload.IsStoring());
375 arload >> float_var;
376 arload >> double_var;
377 arload >> int_var;
378 arload >> unsigned_int_var;
379 arload >> int64_t_var;
380 arload >> uint64_t_var;
381 arload >> bool_var;
382 arload >> char_var;
383 arload >> string_var;
384 arload >> wstring_var;
385 arload >> SystemTime_var;
386 arload >> CVariant_var;
387 arload >> strArray_var;
388 arload >> iArray_var;
389 arload.Close();
390
391 EXPECT_EQ(float_ref, float_var);
392 EXPECT_EQ(double_ref, double_var);
393 EXPECT_EQ(int_ref, int_var);
394 EXPECT_EQ(unsigned_int_ref, unsigned_int_var);
395 EXPECT_EQ(int64_t_ref, int64_t_var);
396 EXPECT_EQ(uint64_t_ref, uint64_t_var);
397 EXPECT_EQ(bool_ref, bool_var);
398 EXPECT_EQ(char_ref, char_var);
399 EXPECT_STREQ(string_ref.c_str(), string_var.c_str());
400 EXPECT_STREQ(wstring_ref.c_str(), wstring_var.c_str());
401 EXPECT_TRUE(!memcmp(&SystemTime_ref, &SystemTime_var, sizeof(KODI::TIME::SystemTime)));
402 EXPECT_TRUE(CVariant_var.isInteger());
403 EXPECT_STREQ("test strArray_ref 0", strArray_var.at(0).c_str());
404 EXPECT_STREQ("test strArray_ref 1", strArray_var.at(1).c_str());
405 EXPECT_STREQ("test strArray_ref 2", strArray_var.at(2).c_str());
406 EXPECT_STREQ("test strArray_ref 3", strArray_var.at(3).c_str());
407 EXPECT_EQ(0, iArray_var.at(0));
408 EXPECT_EQ(1, iArray_var.at(1));
409 EXPECT_EQ(2, iArray_var.at(2));
410 EXPECT_EQ(3, iArray_var.at(3));
411}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/Base64.h"
10
11#include <gtest/gtest.h>
12
13static const char refdata[] = "\x01\x02\x03\x04\x05\x06\x07\x08"
14 "\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
15 "\x11\x12\x13\x14\x15\x16\x17\x18"
16 "\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
17 "\x21\x22\x23\x24\x25\x26\x27\x28"
18 "\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30";
19
20static const char refbase64data[] = "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY"
21 "GRobHB0eHyAhIiMkJSYnKCkqKywtLi8w";
22
23TEST(TestBase64, Encode_1)
24{
25 std::string a;
26 Base64::Encode(refdata, sizeof(refdata) - 1, a);
27 EXPECT_STREQ(refbase64data, a.c_str());
28}
29
30TEST(TestBase64, Encode_2)
31{
32 std::string a;
33 a = Base64::Encode(refdata, sizeof(refdata) - 1);
34 EXPECT_STREQ(refbase64data, a.c_str());
35}
36
37TEST(TestBase64, Encode_3)
38{
39 std::string a;
40 Base64::Encode(refdata, a);
41 EXPECT_STREQ(refbase64data, a.c_str());
42}
43
44TEST(TestBase64, Encode_4)
45{
46 std::string a;
47 a = Base64::Encode(refdata);
48 EXPECT_STREQ(refbase64data, a.c_str());
49}
50
51TEST(TestBase64, Decode_1)
52{
53 std::string a;
54 Base64::Decode(refbase64data, sizeof(refbase64data) - 1, a);
55 EXPECT_STREQ(refdata, a.c_str());
56}
57
58TEST(TestBase64, Decode_2)
59{
60 std::string a;
61 a = Base64::Decode(refbase64data, sizeof(refbase64data) - 1);
62 EXPECT_STREQ(refdata, a.c_str());
63}
64
65TEST(TestBase64, Decode_3)
66{
67 std::string a;
68 Base64::Decode(refbase64data, a);
69 EXPECT_STREQ(refdata, a.c_str());
70}
71
72TEST(TestBase64, Decode_4)
73{
74 std::string a;
75 a = Base64::Decode(refbase64data);
76 EXPECT_STREQ(refdata, a.c_str());
77}
diff --git a/xbmc/utils/test/TestBitstreamStats.cpp b/xbmc/utils/test/TestBitstreamStats.cpp
new file mode 100644
index 0000000..69e4c88
--- /dev/null
+++ b/xbmc/utils/test/TestBitstreamStats.cpp
@@ -0,0 +1,58 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "threads/Thread.h"
10#include "utils/BitstreamStats.h"
11
12#include <gtest/gtest.h>
13
14#define BITS (256 * 8)
15#define BYTES (256)
16
17class CTestBitstreamStatsThread : public CThread
18{
19public:
20 CTestBitstreamStatsThread() :
21 CThread("TestBitstreamStats"){}
22
23};
24
25TEST(TestBitstreamStats, General)
26{
27 int i;
28 BitstreamStats a;
29 CTestBitstreamStatsThread t;
30
31 i = 0;
32 a.Start();
33 EXPECT_EQ(0.0, a.GetBitrate());
34 EXPECT_EQ(0.0, a.GetMaxBitrate());
35 EXPECT_EQ(-1.0, a.GetMinBitrate());
36 while (i <= BITS)
37 {
38 a.AddSampleBits(1);
39 i++;
40 t.Sleep(1);
41 }
42 a.CalculateBitrate();
43 EXPECT_GT(a.GetBitrate(), 0.0);
44 EXPECT_GT(a.GetMaxBitrate(), 0.0);
45 EXPECT_GT(a.GetMinBitrate(), 0.0);
46
47 i = 0;
48 while (i <= BYTES)
49 {
50 a.AddSampleBytes(1);
51 t.Sleep(2);
52 i++;
53 }
54 a.CalculateBitrate();
55 EXPECT_GT(a.GetBitrate(), 0.0);
56 EXPECT_GT(a.GetMaxBitrate(), 0.0);
57 EXPECT_LE(a.GetMinBitrate(), a.GetMaxBitrate());
58}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#if defined(TARGET_WINDOWS)
10# include <windows.h>
11#endif
12
13#include "ServiceBroker.h"
14#include "settings/AdvancedSettings.h"
15#include "settings/SettingsComponent.h"
16#include "utils/CPUInfo.h"
17#include "utils/Temperature.h"
18#include "utils/XTimeUtils.h"
19
20#include <gtest/gtest.h>
21
22struct TestCPUInfo : public ::testing::Test
23{
24 TestCPUInfo() { CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo()); }
25
26 ~TestCPUInfo() { CServiceBroker::UnregisterCPUInfo(); }
27};
28
29TEST_F(TestCPUInfo, GetUsedPercentage)
30{
31 EXPECT_GE(CServiceBroker::GetCPUInfo()->GetUsedPercentage(), 0);
32}
33
34TEST_F(TestCPUInfo, GetCPUCount)
35{
36 EXPECT_GT(CServiceBroker::GetCPUInfo()->GetCPUCount(), 0);
37}
38
39TEST_F(TestCPUInfo, GetCPUFrequency)
40{
41 EXPECT_GE(CServiceBroker::GetCPUInfo()->GetCPUFrequency(), 0.f);
42}
43
44#if defined(TARGET_WINDOWS)
45TEST_F(TestCPUInfo, DISABLED_GetTemperature)
46#else
47TEST_F(TestCPUInfo, GetTemperature)
48#endif
49{
50 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cpuTempCmd = "echo '50 c'";
51 CTemperature t;
52 EXPECT_TRUE(CServiceBroker::GetCPUInfo()->GetTemperature(t));
53 EXPECT_TRUE(t.IsValid());
54}
55
56TEST_F(TestCPUInfo, CoreInfo)
57{
58 ASSERT_TRUE(CServiceBroker::GetCPUInfo()->HasCoreId(0));
59 const CoreInfo c = CServiceBroker::GetCPUInfo()->GetCoreInfo(0);
60 EXPECT_TRUE(c.m_id == 0);
61}
62
63TEST_F(TestCPUInfo, GetCoresUsageString)
64{
65 EXPECT_STRNE("", CServiceBroker::GetCPUInfo()->GetCoresUsageString().c_str());
66}
67
68TEST_F(TestCPUInfo, GetCPUFeatures)
69{
70 unsigned int a = CServiceBroker::GetCPUInfo()->GetCPUFeatures();
71 (void)a;
72}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "ServiceBroker.h"
10#include "settings/Settings.h"
11#include "settings/SettingsComponent.h"
12#include "utils/CharsetConverter.h"
13#include "utils/Utf8Utils.h"
14
15#include <gtest/gtest.h>
16
17#if 0
18static const uint16_t refutf16LE1[] = { 0xff54, 0xff45, 0xff53, 0xff54,
19 0xff3f, 0xff55, 0xff54, 0xff46,
20 0xff11, 0xff16, 0xff2c, 0xff25,
21 0xff54, 0xff4f, 0xff57, 0x0 };
22
23static const uint16_t refutf16LE2[] = { 0xff54, 0xff45, 0xff53, 0xff54,
24 0xff3f, 0xff55, 0xff54, 0xff46,
25 0xff18, 0xff34, 0xff4f, 0xff1a,
26 0xff3f, 0xff43, 0xff48, 0xff41,
27 0xff52, 0xff53, 0xff45, 0xff54,
28 0xff3f, 0xff35, 0xff34, 0xff26,
29 0xff0d, 0xff11, 0xff16, 0xff2c,
30 0xff25, 0xff0c, 0xff3f, 0xff23,
31 0xff33, 0xff54, 0xff44, 0xff33,
32 0xff54, 0xff52, 0xff49, 0xff4e,
33 0xff47, 0xff11, 0xff16, 0x0 };
34#endif
35
36static const char refutf16LE3[] = "T\377E\377S\377T\377?\377S\377T\377"
37 "R\377I\377N\377G\377#\377H\377A\377"
38 "R\377S\377E\377T\377\064\377O\377\065"
39 "\377T\377F\377\030\377";
40
41#if 0
42static const uint16_t refutf16LE4[] = { 0xff54, 0xff45, 0xff53, 0xff54,
43 0xff3f, 0xff55, 0xff54, 0xff46,
44 0xff11, 0xff16, 0xff2c, 0xff25,
45 0xff54, 0xff4f, 0xff35, 0xff34,
46 0xff26, 0xff18, 0x0 };
47
48static const uint32_t refutf32LE1[] = { 0xff54, 0xff45, 0xff53, 0xff54,
49 0xff3f, 0xff55, 0xff54, 0xff46,
50 0xff18, 0xff34, 0xff4f, 0xff1a,
51 0xff3f, 0xff43, 0xff48, 0xff41,
52 0xff52, 0xff53, 0xff45, 0xff54,
53 0xff3f, 0xff35, 0xff34, 0xff26,
54 0xff0d, 0xff13, 0xff12, 0xff2c,
55 0xff25, 0xff0c, 0xff3f, 0xff23,
56 0xff33, 0xff54, 0xff44, 0xff33,
57 0xff54, 0xff52, 0xff49, 0xff4e,
58 0xff47, 0xff13, 0xff12, 0xff3f,
59#ifdef TARGET_DARWIN
60 0x0 };
61#else
62 0x1f42d, 0x1f42e, 0x0 };
63#endif
64
65static const uint16_t refutf16BE[] = { 0x54ff, 0x45ff, 0x53ff, 0x54ff,
66 0x3fff, 0x55ff, 0x54ff, 0x46ff,
67 0x11ff, 0x16ff, 0x22ff, 0x25ff,
68 0x54ff, 0x4fff, 0x35ff, 0x34ff,
69 0x26ff, 0x18ff, 0x0};
70
71static const uint16_t refucs2[] = { 0xff54, 0xff45, 0xff53, 0xff54,
72 0xff3f, 0xff55, 0xff43, 0xff53,
73 0xff12, 0xff54, 0xff4f, 0xff35,
74 0xff34, 0xff26, 0xff18, 0x0 };
75#endif
76
77class TestCharsetConverter : public testing::Test
78{
79protected:
80 TestCharsetConverter()
81 {
82 /* Add default settings for locale.
83 * Settings here are taken from CGUISettings::Initialize()
84 */
85 /*
86 //! @todo implement
87 CSettingsCategory *loc = CServiceBroker::GetSettingsComponent()->GetSettings()->AddCategory(7, "locale", 14090);
88 CServiceBroker::GetSettingsComponent()->GetSettings()->AddString(loc, CSettings::SETTING_LOCALE_LANGUAGE,248,"english",
89 SPIN_CONTROL_TEXT);
90 CServiceBroker::GetSettingsComponent()->GetSettings()->AddString(loc, CSettings::SETTING_LOCALE_COUNTRY, 20026, "USA",
91 SPIN_CONTROL_TEXT);
92 CServiceBroker::GetSettingsComponent()->GetSettings()->AddString(loc, CSettings::SETTING_LOCALE_CHARSET, 14091, "DEFAULT",
93 SPIN_CONTROL_TEXT); // charset is set by the
94 // language file
95
96 // Add default settings for subtitles
97 CSettingsCategory *sub = CServiceBroker::GetSettingsComponent()->GetSettings()->AddCategory(5, "subtitles", 287);
98 CServiceBroker::GetSettingsComponent()->GetSettings()->AddString(sub, CSettings::SETTING_SUBTITLES_CHARSET, 735, "DEFAULT",
99 SPIN_CONTROL_TEXT);
100 */
101 g_charsetConverter.reset();
102 g_charsetConverter.clear();
103 }
104
105 ~TestCharsetConverter() override
106 {
107 CServiceBroker::GetSettingsComponent()->GetSettings()->Unload();
108 }
109
110 std::string refstra1, refstra2, varstra1;
111 std::wstring refstrw1, varstrw1;
112 std::string refstr1;
113};
114
115TEST_F(TestCharsetConverter, utf8ToW)
116{
117 refstra1 = "test utf8ToW";
118 refstrw1 = L"test utf8ToW";
119 varstrw1.clear();
120 g_charsetConverter.utf8ToW(refstra1, varstrw1, true, false, false);
121 EXPECT_STREQ(refstrw1.c_str(), varstrw1.c_str());
122}
123
124
125//TEST_F(TestCharsetConverter, utf16LEtoW)
126//{
127// refstrw1 = L"test_utf16LEtow";
128// //! @todo Should be able to use '=' operator instead of assign()
129// std::wstring refstr16_1;
130// refstr16_1.assign(refutf16LE1);
131// varstrw1.clear();
132// g_charsetConverter.utf16LEtoW(refstr16_1, varstrw1);
133// EXPECT_STREQ(refstrw1.c_str(), varstrw1.c_str());
134//}
135
136TEST_F(TestCharsetConverter, subtitleCharsetToUtf8)
137{
138 refstra1 = "test subtitleCharsetToW";
139 varstra1.clear();
140 g_charsetConverter.subtitleCharsetToUtf8(refstra1, varstra1);
141
142 /* Assign refstra1 to refstrw1 so that we can compare */
143 EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
144}
145
146TEST_F(TestCharsetConverter, utf8ToStringCharset_1)
147{
148 refstra1 = "test utf8ToStringCharset";
149 varstra1.clear();
150 g_charsetConverter.utf8ToStringCharset(refstra1, varstra1);
151 EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
152}
153
154TEST_F(TestCharsetConverter, utf8ToStringCharset_2)
155{
156 refstra1 = "test utf8ToStringCharset";
157 varstra1 = "test utf8ToStringCharset";
158 g_charsetConverter.utf8ToStringCharset(varstra1);
159 EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
160}
161
162TEST_F(TestCharsetConverter, utf8ToSystem)
163{
164 refstra1 = "test utf8ToSystem";
165 varstra1 = "test utf8ToSystem";
166 g_charsetConverter.utf8ToSystem(varstra1);
167 EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
168}
169
170TEST_F(TestCharsetConverter, utf8To_ASCII)
171{
172 refstra1 = "test utf8To: charset ASCII, std::string";
173 varstra1.clear();
174 g_charsetConverter.utf8To("ASCII", refstra1, varstra1);
175 EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
176}
177
178/*
179TEST_F(TestCharsetConverter, utf8To_UTF16LE)
180{
181 refstra1 = "test_utf8To:_charset_UTF-16LE,_"
182 "CStdString16";
183 refstr16_1.assign(refutf16LE2);
184 varstr16_1.clear();
185 g_charsetConverter.utf8To("UTF-16LE", refstra1, varstr16_1);
186 EXPECT_TRUE(!memcmp(refstr16_1.c_str(), varstr16_1.c_str(),
187 refstr16_1.length() * sizeof(uint16_t)));
188}
189*/
190
191//TEST_F(TestCharsetConverter, utf8To_UTF32LE)
192//{
193// refstra1 = "test_utf8To:_charset_UTF-32LE,_"
194//#ifdef TARGET_DARWIN
195///* OSX has its own 'special' utf-8 charset which we use (see UTF8_SOURCE in CharsetConverter.cpp)
196// which is basically NFD (decomposed) utf-8. The trouble is, it fails on the COW FACE and MOUSE FACE
197// characters for some reason (possibly anything over 0x100000, or maybe there's a decomposed form of these
198// that I couldn't find???) If UTF8_SOURCE is switched to UTF-8 then this test would pass as-is, but then
199// some filenames stored in utf8-mac wouldn't display correctly in the UI. */
200// "CStdString32_";
201//#else
202// "CStdString32_🐭🐮";
203//#endif
204// refstr32_1.assign(refutf32LE1);
205// varstr32_1.clear();
206// g_charsetConverter.utf8To("UTF-32LE", refstra1, varstr32_1);
207// EXPECT_TRUE(!memcmp(refstr32_1.c_str(), varstr32_1.c_str(),
208// sizeof(refutf32LE1)));
209//}
210
211TEST_F(TestCharsetConverter, stringCharsetToUtf8)
212{
213 refstra1 = "test_stringCharsetToUtf8";
214 varstra1.clear();
215 g_charsetConverter.ToUtf8("UTF-16LE", refutf16LE3, varstra1);
216 EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
217}
218
219TEST_F(TestCharsetConverter, isValidUtf8_1)
220{
221 varstra1.clear();
222 g_charsetConverter.ToUtf8("UTF-16LE", refutf16LE3, varstra1);
223 EXPECT_TRUE(CUtf8Utils::isValidUtf8(varstra1.c_str()));
224}
225
226TEST_F(TestCharsetConverter, isValidUtf8_2)
227{
228 refstr1 = refutf16LE3;
229 EXPECT_FALSE(CUtf8Utils::isValidUtf8(refstr1));
230}
231
232TEST_F(TestCharsetConverter, isValidUtf8_3)
233{
234 varstra1.clear();
235 g_charsetConverter.ToUtf8("UTF-16LE", refutf16LE3, varstra1);
236 EXPECT_TRUE(CUtf8Utils::isValidUtf8(varstra1.c_str()));
237}
238
239TEST_F(TestCharsetConverter, isValidUtf8_4)
240{
241 EXPECT_FALSE(CUtf8Utils::isValidUtf8(refutf16LE3));
242}
243
244//! @todo Resolve correct input/output for this function
245// TEST_F(TestCharsetConverter, ucs2CharsetToStringCharset)
246// {
247// void ucs2CharsetToStringCharset(const std::wstring& strSource,
248// std::string& strDest, bool swap = false);
249// }
250
251TEST_F(TestCharsetConverter, wToUTF8)
252{
253 refstrw1 = L"test_wToUTF8";
254 refstra1 = u8"test_wToUTF8";
255 varstra1.clear();
256 g_charsetConverter.wToUTF8(refstrw1, varstra1);
257 EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
258}
259
260//TEST_F(TestCharsetConverter, utf16BEtoUTF8)
261//{
262// refstr16_1.assign(refutf16BE);
263// refstra1 = "test_utf16BEtoUTF8";
264// varstra1.clear();
265// g_charsetConverter.utf16BEtoUTF8(refstr16_1, varstra1);
266// EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
267//}
268
269//TEST_F(TestCharsetConverter, utf16LEtoUTF8)
270//{
271// refstr16_1.assign(refutf16LE4);
272// refstra1 = "test_utf16LEtoUTF8";
273// varstra1.clear();
274// g_charsetConverter.utf16LEtoUTF8(refstr16_1, varstra1);
275// EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
276//}
277
278//TEST_F(TestCharsetConverter, ucs2ToUTF8)
279//{
280// refstr16_1.assign(refucs2);
281// refstra1 = "test_ucs2toUTF8";
282// varstra1.clear();
283// g_charsetConverter.ucs2ToUTF8(refstr16_1, varstra1);
284// EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
285//}
286
287TEST_F(TestCharsetConverter, utf8logicalToVisualBiDi)
288{
289 refstra1 = "test_utf8logicalToVisualBiDi";
290 refstra2 = "test_utf8logicalToVisualBiDi";
291 varstra1.clear();
292 g_charsetConverter.utf8logicalToVisualBiDi(refstra1, varstra1);
293 EXPECT_STREQ(refstra2.c_str(), varstra1.c_str());
294}
295
296//! @todo Resolve correct input/output for this function
297// TEST_F(TestCharsetConverter, utf32ToStringCharset)
298// {
299// void utf32ToStringCharset(const unsigned long* strSource, std::string& strDest);
300// }
301
302TEST_F(TestCharsetConverter, getCharsetLabels)
303{
304 std::vector<std::string> reflabels;
305 reflabels.emplace_back("Western Europe (ISO)");
306 reflabels.emplace_back("Central Europe (ISO)");
307 reflabels.emplace_back("South Europe (ISO)");
308 reflabels.emplace_back("Baltic (ISO)");
309 reflabels.emplace_back("Cyrillic (ISO)");
310 reflabels.emplace_back("Arabic (ISO)");
311 reflabels.emplace_back("Greek (ISO)");
312 reflabels.emplace_back("Hebrew (ISO)");
313 reflabels.emplace_back("Turkish (ISO)");
314 reflabels.emplace_back("Central Europe (Windows)");
315 reflabels.emplace_back("Cyrillic (Windows)");
316 reflabels.emplace_back("Western Europe (Windows)");
317 reflabels.emplace_back("Greek (Windows)");
318 reflabels.emplace_back("Turkish (Windows)");
319 reflabels.emplace_back("Hebrew (Windows)");
320 reflabels.emplace_back("Arabic (Windows)");
321 reflabels.emplace_back("Baltic (Windows)");
322 reflabels.emplace_back("Vietnamese (Windows)");
323 reflabels.emplace_back("Thai (Windows)");
324 reflabels.emplace_back("Chinese Traditional (Big5)");
325 reflabels.emplace_back("Chinese Simplified (GBK)");
326 reflabels.emplace_back("Japanese (Shift-JIS)");
327 reflabels.emplace_back("Korean");
328 reflabels.emplace_back("Hong Kong (Big5-HKSCS)");
329
330 std::vector<std::string> varlabels = g_charsetConverter.getCharsetLabels();
331 ASSERT_EQ(reflabels.size(), varlabels.size());
332
333 size_t pos = 0;
334 for (const auto& it : varlabels)
335 {
336 EXPECT_STREQ((reflabels.at(pos++)).c_str(), it.c_str());
337 }
338}
339
340TEST_F(TestCharsetConverter, getCharsetLabelByName)
341{
342 std::string varstr =
343 g_charsetConverter.getCharsetLabelByName("ISO-8859-1");
344 EXPECT_STREQ("Western Europe (ISO)", varstr.c_str());
345 varstr.clear();
346 varstr = g_charsetConverter.getCharsetLabelByName("Bogus");
347 EXPECT_STREQ("", varstr.c_str());
348}
349
350TEST_F(TestCharsetConverter, getCharsetNameByLabel)
351{
352 std::string varstr =
353 g_charsetConverter.getCharsetNameByLabel("Western Europe (ISO)");
354 EXPECT_STREQ("ISO-8859-1", varstr.c_str());
355 varstr.clear();
356 varstr = g_charsetConverter.getCharsetNameByLabel("Bogus");
357 EXPECT_STREQ("", varstr.c_str());
358}
359
360TEST_F(TestCharsetConverter, unknownToUTF8_1)
361{
362 refstra1 = "test_unknownToUTF8";
363 varstra1 = "test_unknownToUTF8";
364 g_charsetConverter.unknownToUTF8(varstra1);
365 EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
366}
367
368TEST_F(TestCharsetConverter, unknownToUTF8_2)
369{
370 refstra1 = "test_unknownToUTF8";
371 varstra1.clear();
372 g_charsetConverter.unknownToUTF8(refstra1, varstra1);
373 EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
374}
375
376TEST_F(TestCharsetConverter, toW)
377{
378 refstra1 = "test_toW:_charset_UTF-16LE";
379 refstrw1 = L"\xBDEF\xEF94\x85BD\xBDEF\xEF93\x94BD\xBCEF\xEFBF"
380 L"\x94BD\xBDEF\xEF8F\xB7BC\xBCEF\xEF9A\xBFBC\xBDEF"
381 L"\xEF83\x88BD\xBDEF\xEF81\x92BD\xBDEF\xEF93\x85BD"
382 L"\xBDEF\xEF94\xBFBC\xBCEF\xEFB5\xB4BC\xBCEF\xEFA6"
383 L"\x8DBC\xBCEF\xEF91\x96BC\xBCEF\xEFAC\xA5BC";
384 varstrw1.clear();
385 g_charsetConverter.toW(refstra1, varstrw1, "UTF-16LE");
386 EXPECT_STREQ(refstrw1.c_str(), varstrw1.c_str());
387}
388
389TEST_F(TestCharsetConverter, fromW)
390{
391 refstrw1 = L"\xBDEF\xEF94\x85BD\xBDEF\xEF93\x94BD\xBCEF\xEFBF"
392 L"\x86BD\xBDEF\xEF92\x8FBD\xBDEF\xEF8D\xB7BC\xBCEF"
393 L"\xEF9A\xBFBC\xBDEF\xEF83\x88BD\xBDEF\xEF81\x92BD"
394 L"\xBDEF\xEF93\x85BD\xBDEF\xEF94\xBFBC\xBCEF\xEFB5"
395 L"\xB4BC\xBCEF\xEFA6\x8DBC\xBCEF\xEF91\x96BC\xBCEF"
396 L"\xEFAC\xA5BC";
397 refstra1 = "test_fromW:_charset_UTF-16LE";
398 varstra1.clear();
399 g_charsetConverter.fromW(refstrw1, varstra1, "UTF-16LE");
400 EXPECT_STREQ(refstra1.c_str(), varstra1.c_str());
401}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/Crc32.h"
10
11#include <gtest/gtest.h>
12
13static const char refdata[] = "abcdefghijklmnopqrstuvwxyz"
14 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
15 "01234567890!@#$%^&*()";
16
17TEST(TestCrc32, Compute_1)
18{
19 Crc32 a;
20 uint32_t varcrc;
21 a.Compute(refdata, sizeof(refdata) - 1);
22 varcrc = a;
23 EXPECT_EQ(0xa4eb60e3, varcrc);
24}
25
26TEST(TestCrc32, Compute_2)
27{
28 uint32_t varcrc;
29 std::string s = refdata;
30 varcrc = Crc32::Compute(s);
31 EXPECT_EQ(0xa4eb60e3, varcrc);
32}
33
34TEST(TestCrc32, ComputeFromLowerCase)
35{
36 std::string s = refdata;
37 uint32_t varcrc = Crc32::ComputeFromLowerCase(s);
38 EXPECT_EQ((uint32_t)0x7f045b3e, varcrc);
39}
40
41TEST(TestCrc32, Reset)
42{
43 Crc32 a;
44 uint32_t varcrc;
45 std::string s = refdata;
46 a.Compute(s.c_str(), s.length());
47 a.Reset();
48 varcrc = a;
49 EXPECT_EQ(0xffffffff, varcrc);
50}
diff --git a/xbmc/utils/test/TestCryptThreading.cpp b/xbmc/utils/test/TestCryptThreading.cpp
new file mode 100644
index 0000000..949bd6f
--- /dev/null
+++ b/xbmc/utils/test/TestCryptThreading.cpp
@@ -0,0 +1,79 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/CryptThreading.h"
10#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
11#include "threads/SingleLock.h"
12
13#include <atomic>
14#include <set>
15#include <thread>
16#include <vector>
17
18#include <gtest/gtest.h>
19
20TEST(TestCryptThreadingInitializer, General)
21{
22 std::cout << "g_cryptThreadingInitializer address: " <<
23 testing::PrintToString(&g_cryptThreadingInitializer) << "\n";
24}
25
26#define PVTID_NUM_THREADS 10
27
28TEST(TestCryptThreadingInitializer, ProducesValidThreadIds)
29{
30 std::thread testThreads[PVTID_NUM_THREADS];
31
32 std::vector<unsigned long> gatheredIds;
33 CCriticalSection gatheredIdsMutex;
34
35 std::atomic<unsigned long> threadsWaiting{0};
36 std::atomic<bool> gate{false};
37
38 for (int i = 0; i < PVTID_NUM_THREADS; i++)
39 {
40 testThreads[i] = std::thread([&gatheredIds, &gatheredIdsMutex, &threadsWaiting, &gate]() {
41 threadsWaiting++;
42
43 while (!gate);
44
45 unsigned long myTid = g_cryptThreadingInitializer.GetCurrentCryptThreadId();
46
47 {
48 CSingleLock gatheredIdsLock(gatheredIdsMutex);
49 gatheredIds.push_back(myTid);
50 }
51 });
52 }
53
54 gate = true;
55
56 for (int i = 0; i < PVTID_NUM_THREADS; i++)
57 // This is somewhat dangerous but C++ doesn't have a join with timeout or a way to check
58 // if a thread is still running.
59 testThreads[i].join();
60
61 // Verify that all of the thread id's are unique, and that there are 10 of them, and that none
62 // of them is zero
63 std::set<unsigned long> checkIds;
64 for (std::vector<unsigned long>::const_iterator i = gatheredIds.begin(); i != gatheredIds.end(); ++i)
65 {
66 unsigned long curId = *i;
67 // Thread ID isn't zero (since the sequence is pre-incremented and starts at 0)
68 ASSERT_TRUE(curId != 0);
69
70 // Make sure the ID isn't duplicated
71 ASSERT_TRUE(checkIds.find(curId) == checkIds.end());
72 checkIds.insert(curId);
73 }
74
75 // Make sure there's exactly PVTID_NUM_THREADS of them
76 ASSERT_EQ(PVTID_NUM_THREADS, gatheredIds.size());
77 ASSERT_EQ(PVTID_NUM_THREADS, checkIds.size());
78}
79#endif
diff --git a/xbmc/utils/test/TestDatabaseUtils.cpp b/xbmc/utils/test/TestDatabaseUtils.cpp
new file mode 100644
index 0000000..41e20bd
--- /dev/null
+++ b/xbmc/utils/test/TestDatabaseUtils.cpp
@@ -0,0 +1,1376 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "dbwrappers/qry_dat.h"
10#include "music/MusicDatabase.h"
11#include "utils/DatabaseUtils.h"
12#include "utils/StringUtils.h"
13#include "utils/Variant.h"
14#include "video/VideoDatabase.h"
15
16#include <gtest/gtest.h>
17
18class TestDatabaseUtilsHelper
19{
20public:
21 TestDatabaseUtilsHelper()
22 {
23 album_idAlbum = CMusicDatabase::album_idAlbum;
24 album_strAlbum = CMusicDatabase::album_strAlbum;
25 album_strArtists = CMusicDatabase::album_strArtists;
26 album_strGenres = CMusicDatabase::album_strGenres;
27 album_strMoods = CMusicDatabase::album_strMoods;
28 album_strReleaseDate = CMusicDatabase::album_strReleaseDate;
29 album_strOrigReleaseDate = CMusicDatabase::album_strOrigReleaseDate;
30 album_strStyles = CMusicDatabase::album_strStyles;
31 album_strThemes = CMusicDatabase::album_strThemes;
32 album_strReview = CMusicDatabase::album_strReview;
33 album_strLabel = CMusicDatabase::album_strLabel;
34 album_strType = CMusicDatabase::album_strType;
35 album_fRating = CMusicDatabase::album_fRating;
36 album_iVotes = CMusicDatabase::album_iVotes;
37 album_iUserrating = CMusicDatabase::album_iUserrating;
38 album_dtDateAdded = CMusicDatabase::album_dateAdded;
39
40 song_idSong = CMusicDatabase::song_idSong;
41 song_strTitle = CMusicDatabase::song_strTitle;
42 song_iTrack = CMusicDatabase::song_iTrack;
43 song_iDuration = CMusicDatabase::song_iDuration;
44 song_strReleaseDate = CMusicDatabase::song_strReleaseDate;
45 song_strOrigReleaseDate = CMusicDatabase::song_strOrigReleaseDate;
46 song_strFileName = CMusicDatabase::song_strFileName;
47 song_iTimesPlayed = CMusicDatabase::song_iTimesPlayed;
48 song_iStartOffset = CMusicDatabase::song_iStartOffset;
49 song_iEndOffset = CMusicDatabase::song_iEndOffset;
50 song_lastplayed = CMusicDatabase::song_lastplayed;
51 song_rating = CMusicDatabase::song_rating;
52 song_votes = CMusicDatabase::song_votes;
53 song_userrating = CMusicDatabase::song_userrating;
54 song_comment = CMusicDatabase::song_comment;
55 song_strAlbum = CMusicDatabase::song_strAlbum;
56 song_strPath = CMusicDatabase::song_strPath;
57 song_strGenres = CMusicDatabase::song_strGenres;
58 song_strArtists = CMusicDatabase::song_strArtists;
59 }
60
61 int album_idAlbum;
62 int album_strAlbum;
63 int album_strArtists;
64 int album_strGenres;
65 int album_strMoods;
66 int album_strReleaseDate;
67 int album_strOrigReleaseDate;
68 int album_strStyles;
69 int album_strThemes;
70 int album_strReview;
71 int album_strLabel;
72 int album_strType;
73 int album_fRating;
74 int album_iVotes;
75 int album_iUserrating;
76 int album_dtDateAdded;
77
78 int song_idSong;
79 int song_strTitle;
80 int song_iTrack;
81 int song_iDuration;
82 int song_strReleaseDate;
83 int song_strOrigReleaseDate;
84 int song_strFileName;
85 int song_iTimesPlayed;
86 int song_iStartOffset;
87 int song_iEndOffset;
88 int song_lastplayed;
89 int song_rating;
90 int song_votes;
91 int song_userrating;
92 int song_comment;
93 int song_strAlbum;
94 int song_strPath;
95 int song_strGenres;
96 int song_strArtists;
97};
98
99TEST(TestDatabaseUtils, GetField_None)
100{
101 std::string refstr, varstr;
102
103 refstr = "";
104 varstr = DatabaseUtils::GetField(FieldNone, MediaTypeNone,
105 DatabaseQueryPartSelect);
106 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
107
108 varstr = DatabaseUtils::GetField(FieldNone, MediaTypeMovie,
109 DatabaseQueryPartSelect);
110 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
111}
112
113TEST(TestDatabaseUtils, GetField_MediaTypeAlbum)
114{
115 std::string refstr, varstr;
116
117 refstr = "albumview.idAlbum";
118 varstr = DatabaseUtils::GetField(FieldId, MediaTypeAlbum,
119 DatabaseQueryPartSelect);
120 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
121
122 refstr = "albumview.strAlbum";
123 varstr = DatabaseUtils::GetField(FieldAlbum, MediaTypeAlbum,
124 DatabaseQueryPartSelect);
125 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
126
127 refstr = "albumview.strArtists";
128 varstr = DatabaseUtils::GetField(FieldArtist, MediaTypeAlbum,
129 DatabaseQueryPartSelect);
130 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
131
132 refstr = "albumview.strArtists";
133 varstr = DatabaseUtils::GetField(FieldAlbumArtist, MediaTypeAlbum,
134 DatabaseQueryPartSelect);
135 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
136
137 refstr = "albumview.strGenres";
138 varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeAlbum,
139 DatabaseQueryPartSelect);
140 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
141
142 refstr = "albumview.strReleaseDate";
143 varstr = DatabaseUtils::GetField(FieldYear, MediaTypeAlbum,
144 DatabaseQueryPartSelect);
145 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
146
147refstr = "albumview.strOrigReleaseDate";
148 varstr = DatabaseUtils::GetField(FieldOrigYear, MediaTypeAlbum,
149 DatabaseQueryPartSelect);
150 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
151
152 refstr = "albumview.strMoods";
153 varstr = DatabaseUtils::GetField(FieldMoods, MediaTypeAlbum,
154 DatabaseQueryPartSelect);
155 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
156
157 refstr = "albumview.strStyles";
158 varstr = DatabaseUtils::GetField(FieldStyles, MediaTypeAlbum,
159 DatabaseQueryPartSelect);
160 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
161
162 refstr = "albumview.strThemes";
163 varstr = DatabaseUtils::GetField(FieldThemes, MediaTypeAlbum,
164 DatabaseQueryPartSelect);
165 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
166
167 refstr = "albumview.strReview";
168 varstr = DatabaseUtils::GetField(FieldReview, MediaTypeAlbum,
169 DatabaseQueryPartSelect);
170 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
171
172 refstr = "albumview.strLabel";
173 varstr = DatabaseUtils::GetField(FieldMusicLabel, MediaTypeAlbum,
174 DatabaseQueryPartSelect);
175 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
176
177 refstr = "albumview.strType";
178 varstr = DatabaseUtils::GetField(FieldAlbumType, MediaTypeAlbum,
179 DatabaseQueryPartSelect);
180 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
181
182 refstr = "albumview.fRating";
183 varstr = DatabaseUtils::GetField(FieldRating, MediaTypeAlbum,
184 DatabaseQueryPartSelect);
185 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
186
187 refstr = "albumview.iVotes";
188 varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeAlbum,
189 DatabaseQueryPartSelect);
190 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
191
192 refstr = "albumview.iUserrating";
193 varstr = DatabaseUtils::GetField(FieldUserRating, MediaTypeAlbum,
194 DatabaseQueryPartSelect);
195 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
196
197 refstr = "albumview.dateAdded";
198 varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeAlbum,
199 DatabaseQueryPartSelect);
200 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
201
202 refstr = "";
203 varstr = DatabaseUtils::GetField(FieldNone, MediaTypeAlbum,
204 DatabaseQueryPartSelect);
205 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
206
207 refstr = "albumview.strAlbum";
208 varstr = DatabaseUtils::GetField(FieldAlbum, MediaTypeAlbum,
209 DatabaseQueryPartWhere);
210 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
211
212 varstr = DatabaseUtils::GetField(FieldAlbum, MediaTypeAlbum,
213 DatabaseQueryPartOrderBy);
214 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
215}
216
217TEST(TestDatabaseUtils, GetField_MediaTypeSong)
218{
219 std::string refstr, varstr;
220
221 refstr = "songview.idSong";
222 varstr = DatabaseUtils::GetField(FieldId, MediaTypeSong,
223 DatabaseQueryPartSelect);
224 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
225
226 refstr = "songview.strTitle";
227 varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeSong,
228 DatabaseQueryPartSelect);
229 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
230
231 refstr = "songview.iTrack";
232 varstr = DatabaseUtils::GetField(FieldTrackNumber, MediaTypeSong,
233 DatabaseQueryPartSelect);
234 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
235
236 refstr = "songview.iDuration";
237 varstr = DatabaseUtils::GetField(FieldTime, MediaTypeSong,
238 DatabaseQueryPartSelect);
239 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
240
241 refstr = "songview.strFilename";
242 varstr = DatabaseUtils::GetField(FieldFilename, MediaTypeSong,
243 DatabaseQueryPartSelect);
244 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
245
246 refstr = "songview.iTimesPlayed";
247 varstr = DatabaseUtils::GetField(FieldPlaycount, MediaTypeSong,
248 DatabaseQueryPartSelect);
249 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
250
251 refstr = "songview.iStartOffset";
252 varstr = DatabaseUtils::GetField(FieldStartOffset, MediaTypeSong,
253 DatabaseQueryPartSelect);
254 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
255
256 refstr = "songview.iEndOffset";
257 varstr = DatabaseUtils::GetField(FieldEndOffset, MediaTypeSong,
258 DatabaseQueryPartSelect);
259 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
260
261 refstr = "songview.lastPlayed";
262 varstr = DatabaseUtils::GetField(FieldLastPlayed, MediaTypeSong,
263 DatabaseQueryPartSelect);
264 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
265
266 refstr = "songview.rating";
267 varstr = DatabaseUtils::GetField(FieldRating, MediaTypeSong,
268 DatabaseQueryPartSelect);
269 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
270
271 refstr = "songview.votes";
272 varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeSong,
273 DatabaseQueryPartSelect);
274 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
275
276 refstr = "songview.userrating";
277 varstr = DatabaseUtils::GetField(FieldUserRating, MediaTypeSong,
278 DatabaseQueryPartSelect);
279 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
280
281 refstr = "songview.comment";
282 varstr = DatabaseUtils::GetField(FieldComment, MediaTypeSong,
283 DatabaseQueryPartSelect);
284 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
285
286 refstr = "songview.strReleaseDate";
287 varstr = DatabaseUtils::GetField(FieldYear, MediaTypeSong,
288 DatabaseQueryPartSelect);
289 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
290
291 refstr = "songview.strOrigReleaseDate";
292 varstr = DatabaseUtils::GetField(FieldOrigYear, MediaTypeSong,
293 DatabaseQueryPartSelect);
294 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
295
296 refstr = "songview.strAlbum";
297 varstr = DatabaseUtils::GetField(FieldAlbum, MediaTypeSong,
298 DatabaseQueryPartSelect);
299 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
300
301 refstr = "songview.strPath";
302 varstr = DatabaseUtils::GetField(FieldPath, MediaTypeSong,
303 DatabaseQueryPartSelect);
304 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
305
306 refstr = "songview.strArtists";
307 varstr = DatabaseUtils::GetField(FieldArtist, MediaTypeSong,
308 DatabaseQueryPartSelect);
309 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
310
311 refstr = "songview.strArtists";
312 varstr = DatabaseUtils::GetField(FieldAlbumArtist, MediaTypeSong,
313 DatabaseQueryPartSelect);
314 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
315
316 refstr = "songview.strGenres";
317 varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeSong,
318 DatabaseQueryPartSelect);
319 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
320
321 refstr = "songview.dateAdded";
322 varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeSong,
323 DatabaseQueryPartSelect);
324 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
325
326 refstr = "songview.strPath";
327 varstr = DatabaseUtils::GetField(FieldPath, MediaTypeSong,
328 DatabaseQueryPartWhere);
329 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
330
331 varstr = DatabaseUtils::GetField(FieldPath, MediaTypeSong,
332 DatabaseQueryPartOrderBy);
333 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
334}
335
336TEST(TestDatabaseUtils, GetField_MediaTypeMusicVideo)
337{
338 std::string refstr, varstr;
339
340 refstr = "musicvideo_view.idMVideo";
341 varstr = DatabaseUtils::GetField(FieldId, MediaTypeMusicVideo,
342 DatabaseQueryPartSelect);
343 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
344
345 refstr = StringUtils::Format("musicvideo_view.c%02d",VIDEODB_ID_MUSICVIDEO_TITLE);
346 varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeMusicVideo,
347 DatabaseQueryPartSelect);
348 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
349
350 refstr = StringUtils::Format("musicvideo_view.c%02d",VIDEODB_ID_MUSICVIDEO_RUNTIME);
351 varstr = DatabaseUtils::GetField(FieldTime, MediaTypeMusicVideo,
352 DatabaseQueryPartSelect);
353 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
354
355 refstr = StringUtils::Format("musicvideo_view.c%02d",VIDEODB_ID_MUSICVIDEO_DIRECTOR);
356 varstr = DatabaseUtils::GetField(FieldDirector, MediaTypeMusicVideo,
357 DatabaseQueryPartSelect);
358 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
359
360 refstr = StringUtils::Format("musicvideo_view.c%02d",VIDEODB_ID_MUSICVIDEO_STUDIOS);
361 varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeMusicVideo,
362 DatabaseQueryPartSelect);
363 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
364
365 refstr = StringUtils::Format("musicvideo_view.c%02d",VIDEODB_ID_MUSICVIDEO_PLOT);
366 varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeMusicVideo,
367 DatabaseQueryPartSelect);
368 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
369
370 refstr = StringUtils::Format("musicvideo_view.c%02d",VIDEODB_ID_MUSICVIDEO_ALBUM);
371 varstr = DatabaseUtils::GetField(FieldAlbum, MediaTypeMusicVideo,
372 DatabaseQueryPartSelect);
373 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
374
375 refstr = StringUtils::Format("musicvideo_view.c%02d",VIDEODB_ID_MUSICVIDEO_ARTIST);
376 varstr = DatabaseUtils::GetField(FieldArtist, MediaTypeMusicVideo,
377 DatabaseQueryPartSelect);
378 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
379
380 refstr = StringUtils::Format("musicvideo_view.c%02d",VIDEODB_ID_MUSICVIDEO_GENRE);
381 varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeMusicVideo,
382 DatabaseQueryPartSelect);
383 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
384
385 refstr = StringUtils::Format("musicvideo_view.c%02d",VIDEODB_ID_MUSICVIDEO_TRACK);
386 varstr = DatabaseUtils::GetField(FieldTrackNumber, MediaTypeMusicVideo,
387 DatabaseQueryPartSelect);
388 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
389
390 refstr = "musicvideo_view.strFilename";
391 varstr = DatabaseUtils::GetField(FieldFilename, MediaTypeMusicVideo,
392 DatabaseQueryPartSelect);
393 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
394
395 refstr = "musicvideo_view.strPath";
396 varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMusicVideo,
397 DatabaseQueryPartSelect);
398 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
399
400 refstr = "musicvideo_view.playCount";
401 varstr = DatabaseUtils::GetField(FieldPlaycount, MediaTypeMusicVideo,
402 DatabaseQueryPartSelect);
403 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
404
405 refstr = "musicvideo_view.lastPlayed";
406 varstr = DatabaseUtils::GetField(FieldLastPlayed, MediaTypeMusicVideo,
407 DatabaseQueryPartSelect);
408 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
409
410 refstr = "musicvideo_view.dateAdded";
411 varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeMusicVideo,
412 DatabaseQueryPartSelect);
413 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
414
415 refstr = "";
416 varstr = DatabaseUtils::GetField(FieldVideoResolution, MediaTypeMusicVideo,
417 DatabaseQueryPartSelect);
418 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
419
420 refstr = "musicvideo_view.strPath";
421 varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMusicVideo,
422 DatabaseQueryPartWhere);
423 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
424
425 refstr = "musicvideo_view.strPath";
426 varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMusicVideo,
427 DatabaseQueryPartOrderBy);
428 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
429
430 refstr = "musicvideo_view.userrating";
431 varstr = DatabaseUtils::GetField(FieldUserRating, MediaTypeMusicVideo,
432 DatabaseQueryPartSelect);
433 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
434}
435
436TEST(TestDatabaseUtils, GetField_MediaTypeMovie)
437{
438 std::string refstr, varstr;
439
440 refstr = "movie_view.idMovie";
441 varstr = DatabaseUtils::GetField(FieldId, MediaTypeMovie,
442 DatabaseQueryPartSelect);
443 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
444
445 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_TITLE);
446 varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeMovie,
447 DatabaseQueryPartSelect);
448 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
449
450 refstr = StringUtils::Format("CASE WHEN length(movie_view.c%02d) > 0 THEN movie_view.c%02d "
451 "ELSE movie_view.c%02d END", VIDEODB_ID_SORTTITLE,
452 VIDEODB_ID_SORTTITLE, VIDEODB_ID_TITLE);
453 varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeMovie,
454 DatabaseQueryPartOrderBy);
455 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
456
457 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_PLOT);
458 varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeMovie,
459 DatabaseQueryPartSelect);
460 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
461
462 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_PLOTOUTLINE);
463 varstr = DatabaseUtils::GetField(FieldPlotOutline, MediaTypeMovie,
464 DatabaseQueryPartSelect);
465 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
466
467 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_TAGLINE);
468 varstr = DatabaseUtils::GetField(FieldTagline, MediaTypeMovie,
469 DatabaseQueryPartSelect);
470 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
471
472 refstr = "movie_view.votes";
473 varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeMovie,
474 DatabaseQueryPartSelect);
475 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
476
477 refstr = "movie_view.rating";
478 varstr = DatabaseUtils::GetField(FieldRating, MediaTypeMovie,
479 DatabaseQueryPartSelect);
480 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
481
482 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_CREDITS);
483 varstr = DatabaseUtils::GetField(FieldWriter, MediaTypeMovie,
484 DatabaseQueryPartSelect);
485 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
486
487 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_SORTTITLE);
488 varstr = DatabaseUtils::GetField(FieldSortTitle, MediaTypeMovie,
489 DatabaseQueryPartSelect);
490 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
491
492 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_RUNTIME);
493 varstr = DatabaseUtils::GetField(FieldTime, MediaTypeMovie,
494 DatabaseQueryPartSelect);
495 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
496
497 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_MPAA);
498 varstr = DatabaseUtils::GetField(FieldMPAA, MediaTypeMovie,
499 DatabaseQueryPartSelect);
500 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
501
502 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_TOP250);
503 varstr = DatabaseUtils::GetField(FieldTop250, MediaTypeMovie,
504 DatabaseQueryPartSelect);
505 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
506
507 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_GENRE);
508 varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeMovie,
509 DatabaseQueryPartSelect);
510 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
511
512 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_DIRECTOR);
513 varstr = DatabaseUtils::GetField(FieldDirector, MediaTypeMovie,
514 DatabaseQueryPartSelect);
515 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
516
517 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_STUDIOS);
518 varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeMovie,
519 DatabaseQueryPartSelect);
520 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
521
522 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_TRAILER);
523 varstr = DatabaseUtils::GetField(FieldTrailer, MediaTypeMovie,
524 DatabaseQueryPartSelect);
525 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
526
527 refstr = StringUtils::Format("movie_view.c%02d", VIDEODB_ID_COUNTRY);
528 varstr = DatabaseUtils::GetField(FieldCountry, MediaTypeMovie,
529 DatabaseQueryPartSelect);
530 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
531
532 refstr = "movie_view.strFilename";
533 varstr = DatabaseUtils::GetField(FieldFilename, MediaTypeMovie,
534 DatabaseQueryPartSelect);
535 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
536
537 refstr = "movie_view.strPath";
538 varstr = DatabaseUtils::GetField(FieldPath, MediaTypeMovie,
539 DatabaseQueryPartSelect);
540 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
541
542 refstr = "movie_view.playCount";
543 varstr = DatabaseUtils::GetField(FieldPlaycount, MediaTypeMovie,
544 DatabaseQueryPartSelect);
545 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
546
547 refstr = "movie_view.lastPlayed";
548 varstr = DatabaseUtils::GetField(FieldLastPlayed, MediaTypeMovie,
549 DatabaseQueryPartSelect);
550 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
551
552 refstr = "movie_view.dateAdded";
553 varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeMovie,
554 DatabaseQueryPartSelect);
555 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
556
557 refstr = "movie_view.userrating";
558 varstr = DatabaseUtils::GetField(FieldUserRating, MediaTypeMovie,
559 DatabaseQueryPartSelect);
560 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
561
562 refstr = "";
563 varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeMovie,
564 DatabaseQueryPartSelect);
565 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
566}
567
568TEST(TestDatabaseUtils, GetField_MediaTypeTvShow)
569{
570 std::string refstr, varstr;
571
572 refstr = "tvshow_view.idShow";
573 varstr = DatabaseUtils::GetField(FieldId, MediaTypeTvShow,
574 DatabaseQueryPartSelect);
575 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
576
577 refstr = StringUtils::Format("CASE WHEN length(tvshow_view.c%02d) > 0 THEN tvshow_view.c%02d "
578 "ELSE tvshow_view.c%02d END", VIDEODB_ID_TV_SORTTITLE,
579 VIDEODB_ID_TV_SORTTITLE, VIDEODB_ID_TV_TITLE);
580 varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeTvShow,
581 DatabaseQueryPartOrderBy);
582 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
583
584 refstr = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_TITLE);
585 varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeTvShow,
586 DatabaseQueryPartSelect);
587 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
588
589 refstr = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_PLOT);
590 varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeTvShow,
591 DatabaseQueryPartSelect);
592 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
593
594 refstr = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_STATUS);
595 varstr = DatabaseUtils::GetField(FieldTvShowStatus, MediaTypeTvShow,
596 DatabaseQueryPartSelect);
597 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
598
599 refstr = "tvshow_view.votes";
600 varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeTvShow,
601 DatabaseQueryPartSelect);
602 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
603
604 refstr = "tvshow_view.rating";
605 varstr = DatabaseUtils::GetField(FieldRating, MediaTypeTvShow,
606 DatabaseQueryPartSelect);
607 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
608
609 refstr = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_PREMIERED);
610 varstr = DatabaseUtils::GetField(FieldYear, MediaTypeTvShow,
611 DatabaseQueryPartSelect);
612 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
613
614 refstr = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_GENRE);
615 varstr = DatabaseUtils::GetField(FieldGenre, MediaTypeTvShow,
616 DatabaseQueryPartSelect);
617 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
618
619 refstr = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_MPAA);
620 varstr = DatabaseUtils::GetField(FieldMPAA, MediaTypeTvShow,
621 DatabaseQueryPartSelect);
622 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
623
624 refstr = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_STUDIOS);
625 varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeTvShow,
626 DatabaseQueryPartSelect);
627 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
628
629 refstr = StringUtils::Format("tvshow_view.c%02d", VIDEODB_ID_TV_SORTTITLE);
630 varstr = DatabaseUtils::GetField(FieldSortTitle, MediaTypeTvShow,
631 DatabaseQueryPartSelect);
632 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
633
634 refstr = "tvshow_view.strPath";
635 varstr = DatabaseUtils::GetField(FieldPath, MediaTypeTvShow,
636 DatabaseQueryPartSelect);
637 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
638
639 refstr = "tvshow_view.dateAdded";
640 varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeTvShow,
641 DatabaseQueryPartSelect);
642 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
643
644 refstr = "tvshow_view.totalSeasons";
645 varstr = DatabaseUtils::GetField(FieldSeason, MediaTypeTvShow,
646 DatabaseQueryPartSelect);
647 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
648
649 refstr = "tvshow_view.totalCount";
650 varstr = DatabaseUtils::GetField(FieldNumberOfEpisodes, MediaTypeTvShow,
651 DatabaseQueryPartSelect);
652 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
653
654 refstr = "tvshow_view.watchedcount";
655 varstr = DatabaseUtils::GetField(FieldNumberOfWatchedEpisodes,
656 MediaTypeTvShow, DatabaseQueryPartSelect);
657 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
658
659 refstr = "tvshow_view.userrating";
660 varstr = DatabaseUtils::GetField(FieldUserRating, MediaTypeTvShow,
661 DatabaseQueryPartSelect);
662 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
663
664 refstr = "";
665 varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeTvShow,
666 DatabaseQueryPartSelect);
667 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
668}
669
670TEST(TestDatabaseUtils, GetField_MediaTypeEpisode)
671{
672 std::string refstr, varstr;
673
674 refstr = "episode_view.idEpisode";
675 varstr = DatabaseUtils::GetField(FieldId, MediaTypeEpisode,
676 DatabaseQueryPartSelect);
677 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
678
679 refstr = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_TITLE);
680 varstr = DatabaseUtils::GetField(FieldTitle, MediaTypeEpisode,
681 DatabaseQueryPartSelect);
682 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
683
684 refstr = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_PLOT);
685 varstr = DatabaseUtils::GetField(FieldPlot, MediaTypeEpisode,
686 DatabaseQueryPartSelect);
687 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
688
689 refstr = "episode_view.votes";
690 varstr = DatabaseUtils::GetField(FieldVotes, MediaTypeEpisode,
691 DatabaseQueryPartSelect);
692 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
693
694 refstr = "episode_view.rating";
695 varstr = DatabaseUtils::GetField(FieldRating, MediaTypeEpisode,
696 DatabaseQueryPartSelect);
697 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
698
699 refstr = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_CREDITS);
700 varstr = DatabaseUtils::GetField(FieldWriter, MediaTypeEpisode,
701 DatabaseQueryPartSelect);
702 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
703
704 refstr = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_AIRED);
705 varstr = DatabaseUtils::GetField(FieldAirDate, MediaTypeEpisode,
706 DatabaseQueryPartSelect);
707 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
708
709 refstr = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_RUNTIME);
710 varstr = DatabaseUtils::GetField(FieldTime, MediaTypeEpisode,
711 DatabaseQueryPartSelect);
712 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
713
714 refstr = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_DIRECTOR);
715 varstr = DatabaseUtils::GetField(FieldDirector, MediaTypeEpisode,
716 DatabaseQueryPartSelect);
717 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
718
719 refstr = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_SEASON);
720 varstr = DatabaseUtils::GetField(FieldSeason, MediaTypeEpisode,
721 DatabaseQueryPartSelect);
722 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
723
724 refstr = StringUtils::Format("episode_view.c%02d", VIDEODB_ID_EPISODE_EPISODE);
725 varstr = DatabaseUtils::GetField(FieldEpisodeNumber, MediaTypeEpisode,
726 DatabaseQueryPartSelect);
727 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
728
729 refstr = "episode_view.strFilename";
730 varstr = DatabaseUtils::GetField(FieldFilename, MediaTypeEpisode,
731 DatabaseQueryPartSelect);
732 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
733
734 refstr = "episode_view.strPath";
735 varstr = DatabaseUtils::GetField(FieldPath, MediaTypeEpisode,
736 DatabaseQueryPartSelect);
737 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
738
739 refstr = "episode_view.playCount";
740 varstr = DatabaseUtils::GetField(FieldPlaycount, MediaTypeEpisode,
741 DatabaseQueryPartSelect);
742 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
743
744 refstr = "episode_view.lastPlayed";
745 varstr = DatabaseUtils::GetField(FieldLastPlayed, MediaTypeEpisode,
746 DatabaseQueryPartSelect);
747 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
748
749 refstr = "episode_view.dateAdded";
750 varstr = DatabaseUtils::GetField(FieldDateAdded, MediaTypeEpisode,
751 DatabaseQueryPartSelect);
752 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
753
754 refstr = "episode_view.strTitle";
755 varstr = DatabaseUtils::GetField(FieldTvShowTitle, MediaTypeEpisode,
756 DatabaseQueryPartSelect);
757 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
758
759 refstr = "episode_view.premiered";
760 varstr = DatabaseUtils::GetField(FieldYear, MediaTypeEpisode,
761 DatabaseQueryPartSelect);
762 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
763
764 refstr = "episode_view.mpaa";
765 varstr = DatabaseUtils::GetField(FieldMPAA, MediaTypeEpisode,
766 DatabaseQueryPartSelect);
767 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
768
769 refstr = "episode_view.strStudio";
770 varstr = DatabaseUtils::GetField(FieldStudio, MediaTypeEpisode,
771 DatabaseQueryPartSelect);
772 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
773
774 refstr = "episode_view.userrating";
775 varstr = DatabaseUtils::GetField(FieldUserRating, MediaTypeEpisode,
776 DatabaseQueryPartSelect);
777 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
778
779 refstr = "";
780 varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeEpisode,
781 DatabaseQueryPartSelect);
782 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
783}
784
785TEST(TestDatabaseUtils, GetField_FieldRandom)
786{
787 std::string refstr, varstr;
788
789 refstr = "";
790 varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeEpisode,
791 DatabaseQueryPartSelect);
792 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
793
794 refstr = "";
795 varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeEpisode,
796 DatabaseQueryPartWhere);
797 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
798
799 refstr = "RANDOM()";
800 varstr = DatabaseUtils::GetField(FieldRandom, MediaTypeEpisode,
801 DatabaseQueryPartOrderBy);
802 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
803}
804
805TEST(TestDatabaseUtils, GetFieldIndex_None)
806{
807 int refindex, varindex;
808
809 refindex = -1;
810 varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeNone);
811 EXPECT_EQ(refindex, varindex);
812
813 varindex = DatabaseUtils::GetFieldIndex(FieldNone, MediaTypeAlbum);
814 EXPECT_EQ(refindex, varindex);
815}
816
817//! @todo Should enums in CMusicDatabase be made public instead?
818TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeAlbum)
819{
820 int refindex, varindex;
821 TestDatabaseUtilsHelper a;
822
823 refindex = a.album_idAlbum;
824 varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeAlbum);
825 EXPECT_EQ(refindex, varindex);
826
827 refindex = a.album_strAlbum;
828 varindex = DatabaseUtils::GetFieldIndex(FieldAlbum, MediaTypeAlbum);
829 EXPECT_EQ(refindex, varindex);
830
831 refindex = a.album_strArtists;
832 varindex = DatabaseUtils::GetFieldIndex(FieldArtist, MediaTypeAlbum);
833 EXPECT_EQ(refindex, varindex);
834
835 refindex = a.album_strArtists;
836 varindex = DatabaseUtils::GetFieldIndex(FieldAlbumArtist, MediaTypeAlbum);
837 EXPECT_EQ(refindex, varindex);
838
839 refindex = a.album_strGenres;
840 varindex = DatabaseUtils::GetFieldIndex(FieldGenre, MediaTypeAlbum);
841 EXPECT_EQ(refindex, varindex);
842
843 refindex = a.album_strReleaseDate;
844 varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeAlbum);
845 EXPECT_EQ(refindex, varindex);
846
847 refindex = a.album_strOrigReleaseDate;
848 varindex = DatabaseUtils::GetFieldIndex(FieldOrigYear, MediaTypeAlbum);
849 EXPECT_EQ(refindex, varindex);
850
851 refindex = a.album_strMoods;
852 varindex = DatabaseUtils::GetFieldIndex(FieldMoods, MediaTypeAlbum);
853 EXPECT_EQ(refindex, varindex);
854
855 refindex = a.album_strStyles;
856 varindex = DatabaseUtils::GetFieldIndex(FieldStyles, MediaTypeAlbum);
857 EXPECT_EQ(refindex, varindex);
858
859 refindex = a.album_strThemes;
860 varindex = DatabaseUtils::GetFieldIndex(FieldThemes, MediaTypeAlbum);
861 EXPECT_EQ(refindex, varindex);
862
863 refindex = a.album_strReview;
864 varindex = DatabaseUtils::GetFieldIndex(FieldReview, MediaTypeAlbum);
865 EXPECT_EQ(refindex, varindex);
866
867 refindex = a.album_strLabel;
868 varindex = DatabaseUtils::GetFieldIndex(FieldMusicLabel, MediaTypeAlbum);
869 EXPECT_EQ(refindex, varindex);
870
871 refindex = a.album_strType;
872 varindex = DatabaseUtils::GetFieldIndex(FieldAlbumType, MediaTypeAlbum);
873 EXPECT_EQ(refindex, varindex);
874
875 refindex = a.album_fRating;
876 varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeAlbum);
877 EXPECT_EQ(refindex, varindex);
878
879 refindex = a.album_dtDateAdded;
880 varindex = DatabaseUtils::GetFieldIndex(FieldDateAdded, MediaTypeAlbum);
881 EXPECT_EQ(refindex, varindex);
882
883 refindex = -1;
884 varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeAlbum);
885 EXPECT_EQ(refindex, varindex);
886}
887
888TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeSong)
889{
890 int refindex, varindex;
891 TestDatabaseUtilsHelper a;
892
893 refindex = a.song_idSong;
894 varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeSong);
895 EXPECT_EQ(refindex, varindex);
896
897 refindex = a.song_strTitle;
898 varindex = DatabaseUtils::GetFieldIndex(FieldTitle, MediaTypeSong);
899 EXPECT_EQ(refindex, varindex);
900
901 refindex = a.song_iTrack;
902 varindex = DatabaseUtils::GetFieldIndex(FieldTrackNumber, MediaTypeSong);
903 EXPECT_EQ(refindex, varindex);
904
905 refindex = a.song_iDuration;
906 varindex = DatabaseUtils::GetFieldIndex(FieldTime, MediaTypeSong);
907 EXPECT_EQ(refindex, varindex);
908
909 refindex = a.song_strReleaseDate;
910 varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeSong);
911 EXPECT_EQ(refindex, varindex);
912
913 refindex = a.song_strFileName;
914 varindex = DatabaseUtils::GetFieldIndex(FieldFilename, MediaTypeSong);
915 EXPECT_EQ(refindex, varindex);
916
917 refindex = a.song_iTimesPlayed;
918 varindex = DatabaseUtils::GetFieldIndex(FieldPlaycount, MediaTypeSong);
919 EXPECT_EQ(refindex, varindex);
920
921 refindex = a.song_iStartOffset;
922 varindex = DatabaseUtils::GetFieldIndex(FieldStartOffset, MediaTypeSong);
923 EXPECT_EQ(refindex, varindex);
924
925 refindex = a.song_iEndOffset;
926 varindex = DatabaseUtils::GetFieldIndex(FieldEndOffset, MediaTypeSong);
927 EXPECT_EQ(refindex, varindex);
928
929 refindex = a.song_lastplayed;
930 varindex = DatabaseUtils::GetFieldIndex(FieldLastPlayed, MediaTypeSong);
931 EXPECT_EQ(refindex, varindex);
932
933 refindex = a.song_rating;
934 varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeSong);
935 EXPECT_EQ(refindex, varindex);
936
937 refindex = a.song_votes;
938 varindex = DatabaseUtils::GetFieldIndex(FieldVotes, MediaTypeSong);
939 EXPECT_EQ(refindex, varindex);
940
941 refindex = a.song_userrating;
942 varindex = DatabaseUtils::GetFieldIndex(FieldUserRating, MediaTypeSong);
943 EXPECT_EQ(refindex, varindex);
944
945 refindex = a.song_comment;
946 varindex = DatabaseUtils::GetFieldIndex(FieldComment, MediaTypeSong);
947 EXPECT_EQ(refindex, varindex);
948
949 refindex = a.song_strAlbum;
950 varindex = DatabaseUtils::GetFieldIndex(FieldAlbum, MediaTypeSong);
951 EXPECT_EQ(refindex, varindex);
952
953 refindex = a.song_strPath;
954 varindex = DatabaseUtils::GetFieldIndex(FieldPath, MediaTypeSong);
955 EXPECT_EQ(refindex, varindex);
956
957 refindex = a.song_strArtists;
958 varindex = DatabaseUtils::GetFieldIndex(FieldArtist, MediaTypeSong);
959 EXPECT_EQ(refindex, varindex);
960
961 refindex = a.song_strGenres;
962 varindex = DatabaseUtils::GetFieldIndex(FieldGenre, MediaTypeSong);
963 EXPECT_EQ(refindex, varindex);
964
965 refindex = -1;
966 varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeSong);
967 EXPECT_EQ(refindex, varindex);
968}
969
970TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeMusicVideo)
971{
972 int refindex, varindex;
973
974 refindex = 0;
975 varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeMusicVideo);
976 EXPECT_EQ(refindex, varindex);
977
978 refindex = VIDEODB_ID_MUSICVIDEO_TITLE + 2;
979 varindex = DatabaseUtils::GetFieldIndex(FieldTitle, MediaTypeMusicVideo);
980 EXPECT_EQ(refindex, varindex);
981
982 refindex = VIDEODB_ID_MUSICVIDEO_RUNTIME + 2;
983 varindex = DatabaseUtils::GetFieldIndex(FieldTime, MediaTypeMusicVideo);
984 EXPECT_EQ(refindex, varindex);
985
986 refindex = VIDEODB_ID_MUSICVIDEO_DIRECTOR + 2;
987 varindex = DatabaseUtils::GetFieldIndex(FieldDirector, MediaTypeMusicVideo);
988 EXPECT_EQ(refindex, varindex);
989
990 refindex = VIDEODB_ID_MUSICVIDEO_STUDIOS + 2;
991 varindex = DatabaseUtils::GetFieldIndex(FieldStudio, MediaTypeMusicVideo);
992 EXPECT_EQ(refindex, varindex);
993
994 refindex = VIDEODB_ID_MUSICVIDEO_PLOT + 2;
995 varindex = DatabaseUtils::GetFieldIndex(FieldPlot, MediaTypeMusicVideo);
996 EXPECT_EQ(refindex, varindex);
997
998 refindex = VIDEODB_ID_MUSICVIDEO_ALBUM + 2;
999 varindex = DatabaseUtils::GetFieldIndex(FieldAlbum, MediaTypeMusicVideo);
1000 EXPECT_EQ(refindex, varindex);
1001
1002 refindex = VIDEODB_ID_MUSICVIDEO_ARTIST + 2;
1003 varindex = DatabaseUtils::GetFieldIndex(FieldArtist, MediaTypeMusicVideo);
1004 EXPECT_EQ(refindex, varindex);
1005
1006 refindex = VIDEODB_ID_MUSICVIDEO_GENRE + 2;
1007 varindex = DatabaseUtils::GetFieldIndex(FieldGenre, MediaTypeMusicVideo);
1008 EXPECT_EQ(refindex, varindex);
1009
1010 refindex = VIDEODB_ID_MUSICVIDEO_TRACK + 2;
1011 varindex = DatabaseUtils::GetFieldIndex(FieldTrackNumber, MediaTypeMusicVideo);
1012 EXPECT_EQ(refindex, varindex);
1013
1014 refindex = VIDEODB_DETAILS_MUSICVIDEO_FILE;
1015 varindex = DatabaseUtils::GetFieldIndex(FieldFilename, MediaTypeMusicVideo);
1016 EXPECT_EQ(refindex, varindex);
1017
1018 refindex = VIDEODB_DETAILS_MUSICVIDEO_PATH;
1019 varindex = DatabaseUtils::GetFieldIndex(FieldPath, MediaTypeMusicVideo);
1020 EXPECT_EQ(refindex, varindex);
1021
1022 refindex = VIDEODB_DETAILS_MUSICVIDEO_PLAYCOUNT;
1023 varindex = DatabaseUtils::GetFieldIndex(FieldPlaycount, MediaTypeMusicVideo);
1024 EXPECT_EQ(refindex, varindex);
1025
1026 refindex = VIDEODB_DETAILS_MUSICVIDEO_LASTPLAYED;
1027 varindex = DatabaseUtils::GetFieldIndex(FieldLastPlayed, MediaTypeMusicVideo);
1028 EXPECT_EQ(refindex, varindex);
1029
1030 refindex = VIDEODB_DETAILS_MUSICVIDEO_DATEADDED;
1031 varindex = DatabaseUtils::GetFieldIndex(FieldDateAdded, MediaTypeMusicVideo);
1032 EXPECT_EQ(refindex, varindex);
1033
1034 refindex = VIDEODB_DETAILS_MUSICVIDEO_USER_RATING;
1035 varindex = DatabaseUtils::GetFieldIndex(FieldUserRating, MediaTypeMusicVideo);
1036 EXPECT_EQ(refindex, varindex);
1037
1038 refindex = VIDEODB_DETAILS_MUSICVIDEO_PREMIERED;
1039 varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeMusicVideo);
1040 EXPECT_EQ(refindex, varindex);
1041
1042 refindex = -1;
1043 varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeMusicVideo);
1044 EXPECT_EQ(refindex, varindex);
1045}
1046
1047TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeMovie)
1048{
1049 int refindex, varindex;
1050
1051 refindex = 0;
1052 varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeMovie);
1053 EXPECT_EQ(refindex, varindex);
1054
1055 refindex = VIDEODB_ID_TITLE + 2;
1056 varindex = DatabaseUtils::GetFieldIndex(FieldTitle, MediaTypeMovie);
1057 EXPECT_EQ(refindex, varindex);
1058
1059 refindex = VIDEODB_ID_SORTTITLE + 2;
1060 varindex = DatabaseUtils::GetFieldIndex(FieldSortTitle, MediaTypeMovie);
1061 EXPECT_EQ(refindex, varindex);
1062
1063 refindex = VIDEODB_ID_PLOT + 2;
1064 varindex = DatabaseUtils::GetFieldIndex(FieldPlot, MediaTypeMovie);
1065 EXPECT_EQ(refindex, varindex);
1066
1067 refindex = VIDEODB_ID_PLOTOUTLINE + 2;
1068 varindex = DatabaseUtils::GetFieldIndex(FieldPlotOutline, MediaTypeMovie);
1069 EXPECT_EQ(refindex, varindex);
1070
1071 refindex = VIDEODB_ID_TAGLINE + 2;
1072 varindex = DatabaseUtils::GetFieldIndex(FieldTagline, MediaTypeMovie);
1073 EXPECT_EQ(refindex, varindex);
1074
1075 refindex = VIDEODB_ID_CREDITS + 2;
1076 varindex = DatabaseUtils::GetFieldIndex(FieldWriter, MediaTypeMovie);
1077 EXPECT_EQ(refindex, varindex);
1078
1079 refindex = VIDEODB_ID_RUNTIME + 2;
1080 varindex = DatabaseUtils::GetFieldIndex(FieldTime, MediaTypeMovie);
1081 EXPECT_EQ(refindex, varindex);
1082
1083 refindex = VIDEODB_ID_MPAA + 2;
1084 varindex = DatabaseUtils::GetFieldIndex(FieldMPAA, MediaTypeMovie);
1085 EXPECT_EQ(refindex, varindex);
1086
1087 refindex = VIDEODB_ID_TOP250 + 2;
1088 varindex = DatabaseUtils::GetFieldIndex(FieldTop250, MediaTypeMovie);
1089 EXPECT_EQ(refindex, varindex);
1090
1091 refindex = VIDEODB_ID_GENRE + 2;
1092 varindex = DatabaseUtils::GetFieldIndex(FieldGenre, MediaTypeMovie);
1093 EXPECT_EQ(refindex, varindex);
1094
1095 refindex = VIDEODB_ID_DIRECTOR + 2;
1096 varindex = DatabaseUtils::GetFieldIndex(FieldDirector, MediaTypeMovie);
1097 EXPECT_EQ(refindex, varindex);
1098
1099 refindex = VIDEODB_ID_STUDIOS + 2;
1100 varindex = DatabaseUtils::GetFieldIndex(FieldStudio, MediaTypeMovie);
1101 EXPECT_EQ(refindex, varindex);
1102
1103 refindex = VIDEODB_ID_TRAILER + 2;
1104 varindex = DatabaseUtils::GetFieldIndex(FieldTrailer, MediaTypeMovie);
1105 EXPECT_EQ(refindex, varindex);
1106
1107 refindex = VIDEODB_ID_COUNTRY + 2;
1108 varindex = DatabaseUtils::GetFieldIndex(FieldCountry, MediaTypeMovie);
1109 EXPECT_EQ(refindex, varindex);
1110
1111 refindex = VIDEODB_DETAILS_MOVIE_FILE + 2;
1112 varindex = DatabaseUtils::GetFieldIndex(FieldFilename, MediaTypeMovie);
1113 EXPECT_EQ(refindex, varindex);
1114
1115 refindex = VIDEODB_DETAILS_MOVIE_PATH;
1116 varindex = DatabaseUtils::GetFieldIndex(FieldPath, MediaTypeMovie);
1117 EXPECT_EQ(refindex, varindex);
1118
1119 refindex = VIDEODB_DETAILS_MOVIE_PLAYCOUNT;
1120 varindex = DatabaseUtils::GetFieldIndex(FieldPlaycount, MediaTypeMovie);
1121 EXPECT_EQ(refindex, varindex);
1122
1123 refindex = VIDEODB_DETAILS_MOVIE_LASTPLAYED;
1124 varindex = DatabaseUtils::GetFieldIndex(FieldLastPlayed, MediaTypeMovie);
1125 EXPECT_EQ(refindex, varindex);
1126
1127 refindex = VIDEODB_DETAILS_MOVIE_DATEADDED;
1128 varindex = DatabaseUtils::GetFieldIndex(FieldDateAdded, MediaTypeMovie);
1129 EXPECT_EQ(refindex, varindex);
1130
1131 refindex = VIDEODB_DETAILS_MOVIE_USER_RATING;
1132 varindex = DatabaseUtils::GetFieldIndex(FieldUserRating, MediaTypeMovie);
1133 EXPECT_EQ(refindex, varindex);
1134
1135 refindex = VIDEODB_DETAILS_MOVIE_VOTES;
1136 varindex = DatabaseUtils::GetFieldIndex(FieldVotes, MediaTypeMovie);
1137 EXPECT_EQ(refindex, varindex);
1138
1139 refindex = VIDEODB_DETAILS_MOVIE_RATING;
1140 varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeMovie);
1141 EXPECT_EQ(refindex, varindex);
1142
1143 refindex = VIDEODB_DETAILS_MOVIE_PREMIERED;
1144 varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeMovie);
1145 EXPECT_EQ(refindex, varindex);
1146
1147 refindex = -1;
1148 varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeMovie);
1149 EXPECT_EQ(refindex, varindex);
1150}
1151
1152TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeTvShow)
1153{
1154 int refindex, varindex;
1155
1156 refindex = 0;
1157 varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeTvShow);
1158 EXPECT_EQ(refindex, varindex);
1159
1160 refindex = VIDEODB_ID_TV_TITLE + 1;
1161 varindex = DatabaseUtils::GetFieldIndex(FieldTitle, MediaTypeTvShow);
1162 EXPECT_EQ(refindex, varindex);
1163
1164 refindex = VIDEODB_ID_TV_SORTTITLE + 1;
1165 varindex = DatabaseUtils::GetFieldIndex(FieldSortTitle, MediaTypeTvShow);
1166 EXPECT_EQ(refindex, varindex);
1167
1168 refindex = VIDEODB_ID_TV_PLOT + 1;
1169 varindex = DatabaseUtils::GetFieldIndex(FieldPlot, MediaTypeTvShow);
1170 EXPECT_EQ(refindex, varindex);
1171
1172 refindex = VIDEODB_ID_TV_STATUS + 1;
1173 varindex = DatabaseUtils::GetFieldIndex(FieldTvShowStatus, MediaTypeTvShow);
1174 EXPECT_EQ(refindex, varindex);
1175
1176 refindex = VIDEODB_ID_TV_PREMIERED + 1;
1177 varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeTvShow);
1178 EXPECT_EQ(refindex, varindex);
1179
1180 refindex = VIDEODB_ID_TV_GENRE + 1;
1181 varindex = DatabaseUtils::GetFieldIndex(FieldGenre, MediaTypeTvShow);
1182 EXPECT_EQ(refindex, varindex);
1183
1184 refindex = VIDEODB_ID_TV_MPAA + 1;
1185 varindex = DatabaseUtils::GetFieldIndex(FieldMPAA, MediaTypeTvShow);
1186 EXPECT_EQ(refindex, varindex);
1187
1188 refindex = VIDEODB_ID_TV_STUDIOS + 1;
1189 varindex = DatabaseUtils::GetFieldIndex(FieldStudio, MediaTypeTvShow);
1190 EXPECT_EQ(refindex, varindex);
1191
1192 refindex = VIDEODB_DETAILS_TVSHOW_PATH;
1193 varindex = DatabaseUtils::GetFieldIndex(FieldPath, MediaTypeTvShow);
1194 EXPECT_EQ(refindex, varindex);
1195
1196 refindex = VIDEODB_DETAILS_TVSHOW_DATEADDED;
1197 varindex = DatabaseUtils::GetFieldIndex(FieldDateAdded, MediaTypeTvShow);
1198 EXPECT_EQ(refindex, varindex);
1199
1200 refindex = VIDEODB_DETAILS_TVSHOW_NUM_EPISODES;
1201 varindex = DatabaseUtils::GetFieldIndex(FieldNumberOfEpisodes, MediaTypeTvShow);
1202 EXPECT_EQ(refindex, varindex);
1203
1204 refindex = VIDEODB_DETAILS_TVSHOW_NUM_WATCHED;
1205 varindex = DatabaseUtils::GetFieldIndex(FieldNumberOfWatchedEpisodes, MediaTypeTvShow);
1206 EXPECT_EQ(refindex, varindex);
1207
1208 refindex = VIDEODB_DETAILS_TVSHOW_NUM_SEASONS;
1209 varindex = DatabaseUtils::GetFieldIndex(FieldSeason, MediaTypeTvShow);
1210 EXPECT_EQ(refindex, varindex);
1211
1212 refindex = VIDEODB_DETAILS_TVSHOW_USER_RATING;
1213 varindex = DatabaseUtils::GetFieldIndex(FieldUserRating, MediaTypeTvShow);
1214 EXPECT_EQ(refindex, varindex);
1215
1216 refindex = VIDEODB_DETAILS_TVSHOW_VOTES;
1217 varindex = DatabaseUtils::GetFieldIndex(FieldVotes, MediaTypeTvShow);
1218 EXPECT_EQ(refindex, varindex);
1219
1220 refindex = VIDEODB_DETAILS_TVSHOW_RATING;
1221 varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeTvShow);
1222 EXPECT_EQ(refindex, varindex);
1223
1224 refindex = -1;
1225 varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeTvShow);
1226 EXPECT_EQ(refindex, varindex);
1227}
1228
1229TEST(TestDatabaseUtils, GetFieldIndex_MediaTypeEpisode)
1230{
1231 int refindex, varindex;
1232
1233 refindex = 0;
1234 varindex = DatabaseUtils::GetFieldIndex(FieldId, MediaTypeEpisode);
1235 EXPECT_EQ(refindex, varindex);
1236
1237 refindex = VIDEODB_ID_EPISODE_TITLE + 2;
1238 varindex = DatabaseUtils::GetFieldIndex(FieldTitle, MediaTypeEpisode);
1239 EXPECT_EQ(refindex, varindex);
1240
1241 refindex = VIDEODB_ID_EPISODE_PLOT + 2;
1242 varindex = DatabaseUtils::GetFieldIndex(FieldPlot, MediaTypeEpisode);
1243 EXPECT_EQ(refindex, varindex);
1244
1245 refindex = VIDEODB_ID_EPISODE_CREDITS + 2;
1246 varindex = DatabaseUtils::GetFieldIndex(FieldWriter, MediaTypeEpisode);
1247 EXPECT_EQ(refindex, varindex);
1248
1249 refindex = VIDEODB_ID_EPISODE_AIRED + 2;
1250 varindex = DatabaseUtils::GetFieldIndex(FieldAirDate, MediaTypeEpisode);
1251 EXPECT_EQ(refindex, varindex);
1252
1253 refindex = VIDEODB_ID_EPISODE_RUNTIME + 2;
1254 varindex = DatabaseUtils::GetFieldIndex(FieldTime, MediaTypeEpisode);
1255 EXPECT_EQ(refindex, varindex);
1256
1257 refindex = VIDEODB_ID_EPISODE_DIRECTOR + 2;
1258 varindex = DatabaseUtils::GetFieldIndex(FieldDirector, MediaTypeEpisode);
1259 EXPECT_EQ(refindex, varindex);
1260
1261 refindex = VIDEODB_ID_EPISODE_SEASON + 2;
1262 varindex = DatabaseUtils::GetFieldIndex(FieldSeason, MediaTypeEpisode);
1263 EXPECT_EQ(refindex, varindex);
1264
1265 refindex = VIDEODB_ID_EPISODE_EPISODE + 2;
1266 varindex = DatabaseUtils::GetFieldIndex(FieldEpisodeNumber, MediaTypeEpisode);
1267 EXPECT_EQ(refindex, varindex);
1268
1269 refindex = VIDEODB_DETAILS_EPISODE_FILE;
1270 varindex = DatabaseUtils::GetFieldIndex(FieldFilename, MediaTypeEpisode);
1271 EXPECT_EQ(refindex, varindex);
1272
1273 refindex = VIDEODB_DETAILS_EPISODE_PATH;
1274 varindex = DatabaseUtils::GetFieldIndex(FieldPath, MediaTypeEpisode);
1275 EXPECT_EQ(refindex, varindex);
1276
1277 refindex = VIDEODB_DETAILS_EPISODE_PLAYCOUNT;
1278 varindex = DatabaseUtils::GetFieldIndex(FieldPlaycount, MediaTypeEpisode);
1279 EXPECT_EQ(refindex, varindex);
1280
1281 refindex = VIDEODB_DETAILS_EPISODE_LASTPLAYED;
1282 varindex = DatabaseUtils::GetFieldIndex(FieldLastPlayed, MediaTypeEpisode);
1283 EXPECT_EQ(refindex, varindex);
1284
1285 refindex = VIDEODB_DETAILS_EPISODE_DATEADDED;
1286 varindex = DatabaseUtils::GetFieldIndex(FieldDateAdded, MediaTypeEpisode);
1287 EXPECT_EQ(refindex, varindex);
1288
1289 refindex = VIDEODB_DETAILS_EPISODE_TVSHOW_NAME;
1290 varindex = DatabaseUtils::GetFieldIndex(FieldTvShowTitle, MediaTypeEpisode);
1291 EXPECT_EQ(refindex, varindex);
1292
1293 refindex = VIDEODB_DETAILS_EPISODE_TVSHOW_STUDIO;
1294 varindex = DatabaseUtils::GetFieldIndex(FieldStudio, MediaTypeEpisode);
1295 EXPECT_EQ(refindex, varindex);
1296
1297 refindex = VIDEODB_DETAILS_EPISODE_TVSHOW_AIRED;
1298 varindex = DatabaseUtils::GetFieldIndex(FieldYear, MediaTypeEpisode);
1299 EXPECT_EQ(refindex, varindex);
1300
1301 refindex = VIDEODB_DETAILS_EPISODE_TVSHOW_MPAA;
1302 varindex = DatabaseUtils::GetFieldIndex(FieldMPAA, MediaTypeEpisode);
1303 EXPECT_EQ(refindex, varindex);
1304
1305 refindex = VIDEODB_DETAILS_EPISODE_USER_RATING;
1306 varindex = DatabaseUtils::GetFieldIndex(FieldUserRating, MediaTypeEpisode);
1307 EXPECT_EQ(refindex, varindex);
1308
1309 refindex = VIDEODB_DETAILS_EPISODE_VOTES;
1310 varindex = DatabaseUtils::GetFieldIndex(FieldVotes, MediaTypeEpisode);
1311 EXPECT_EQ(refindex, varindex);
1312
1313 refindex = VIDEODB_DETAILS_EPISODE_RATING;
1314 varindex = DatabaseUtils::GetFieldIndex(FieldRating, MediaTypeEpisode);
1315 EXPECT_EQ(refindex, varindex);
1316
1317 refindex = -1;
1318 varindex = DatabaseUtils::GetFieldIndex(FieldRandom, MediaTypeEpisode);
1319 EXPECT_EQ(refindex, varindex);
1320}
1321
1322TEST(TestDatabaseUtils, GetSelectFields)
1323{
1324 Fields fields;
1325 FieldList fieldlist;
1326
1327 EXPECT_FALSE(DatabaseUtils::GetSelectFields(fields, MediaTypeAlbum,
1328 fieldlist));
1329
1330 fields.insert(FieldId);
1331 fields.insert(FieldGenre);
1332 fields.insert(FieldAlbum);
1333 fields.insert(FieldArtist);
1334 fields.insert(FieldTitle);
1335 EXPECT_FALSE(DatabaseUtils::GetSelectFields(fields, MediaTypeNone,
1336 fieldlist));
1337 EXPECT_TRUE(DatabaseUtils::GetSelectFields(fields, MediaTypeAlbum,
1338 fieldlist));
1339 EXPECT_FALSE(fieldlist.empty());
1340}
1341
1342TEST(TestDatabaseUtils, GetFieldValue)
1343{
1344 CVariant v_null, v_string;
1345 dbiplus::field_value f_null, f_string("test");
1346
1347 f_null.set_isNull();
1348 EXPECT_TRUE(DatabaseUtils::GetFieldValue(f_null, v_null));
1349 EXPECT_TRUE(v_null.isNull());
1350
1351 EXPECT_TRUE(DatabaseUtils::GetFieldValue(f_string, v_string));
1352 EXPECT_FALSE(v_string.isNull());
1353 EXPECT_TRUE(v_string.isString());
1354}
1355
1356//! @todo Need some way to test this function
1357// TEST(TestDatabaseUtils, GetDatabaseResults)
1358// {
1359// static bool GetDatabaseResults(MediaType mediaType, const FieldList &fields,
1360// const std::unique_ptr<dbiplus::Dataset> &dataset,
1361// DatabaseResults &results);
1362// }
1363
1364TEST(TestDatabaseUtils, BuildLimitClause)
1365{
1366 std::string a = DatabaseUtils::BuildLimitClause(100);
1367 EXPECT_STREQ(" LIMIT 100", a.c_str());
1368}
1369
1370// class DatabaseUtils
1371// {
1372// public:
1373//
1374//
1375// static std::string BuildLimitClause(int end, int start = 0);
1376// };
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 @@
1/*
2 * Copyright (C) 2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/Digest.h"
10
11#include <gtest/gtest.h>
12
13using KODI::UTILITY::CDigest;
14using KODI::UTILITY::TypedDigest;
15
16TEST(TestDigest, Digest_Empty)
17{
18 EXPECT_STREQ(CDigest::Calculate(CDigest::Type::MD5, "").c_str(), "d41d8cd98f00b204e9800998ecf8427e");
19 EXPECT_STREQ(CDigest::Calculate(CDigest::Type::MD5, nullptr, 0).c_str(), "d41d8cd98f00b204e9800998ecf8427e");
20 {
21 CDigest digest{CDigest::Type::MD5};
22 EXPECT_STREQ(digest.Finalize().c_str(), "d41d8cd98f00b204e9800998ecf8427e");
23 }
24 {
25 CDigest digest{CDigest::Type::MD5};
26 digest.Update("");
27 digest.Update(nullptr, 0);
28 EXPECT_STREQ(digest.Finalize().c_str(), "d41d8cd98f00b204e9800998ecf8427e");
29 }
30}
31
32TEST(TestDigest, Digest_Basic)
33{
34 EXPECT_STREQ(CDigest::Calculate(CDigest::Type::MD5, "asdf").c_str(), "912ec803b2ce49e4a541068d495ab570");
35 EXPECT_STREQ(CDigest::Calculate(CDigest::Type::MD5, "asdf", 4).c_str(), "912ec803b2ce49e4a541068d495ab570");
36 {
37 CDigest digest{CDigest::Type::MD5};
38 digest.Update("as");
39 digest.Update("df", 2);
40 EXPECT_STREQ(digest.Finalize().c_str(), "912ec803b2ce49e4a541068d495ab570");
41 }
42}
43
44TEST(TestDigest, Digest_SHA1)
45{
46 EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA1, "").c_str(), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
47 EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA1, "asdf").c_str(), "3da541559918a808c2402bba5012f6c60b27661c");
48}
49
50TEST(TestDigest, Digest_SHA256)
51{
52 EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA256, "").c_str(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
53 EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA256, "asdf").c_str(), "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b");
54}
55
56TEST(TestDigest, Digest_SHA512)
57{
58 EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA512, "").c_str(), "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e");
59 EXPECT_STREQ(CDigest::Calculate(CDigest::Type::SHA512, "asdf").c_str(), "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1");
60}
61
62TEST(TestDigest, TypedDigest_Empty)
63{
64 TypedDigest t1, t2;
65 EXPECT_EQ(t1, t2);
66 EXPECT_EQ(t1.type, CDigest::Type::INVALID);
67 EXPECT_EQ(t1.value, "");
68 EXPECT_TRUE(t1.Empty());
69 t1.type = CDigest::Type::SHA1;
70 EXPECT_TRUE(t1.Empty());
71}
72
73TEST(TestDigest, TypedDigest_SameType)
74{
75 TypedDigest t1{CDigest::Type::SHA1, "da39a3ee5e6b4b0d3255bfef95601890afd80709"};
76 TypedDigest t2{CDigest::Type::SHA1, "da39a3ee5e6b4b0d3255bfef95601890afd80708"};
77 EXPECT_NE(t1, t2);
78 EXPECT_FALSE(t1.Empty());
79}
80
81TEST(TestDigest, TypedDigest_CompareCase)
82{
83 TypedDigest t1{CDigest::Type::SHA1, "da39a3ee5e6b4b0d3255bfef95601890afd80708"};
84 TypedDigest t2{CDigest::Type::SHA1, "da39A3EE5e6b4b0d3255bfef95601890afd80708"};
85 EXPECT_EQ(t1, t2);
86}
87
88TEST(TestDigest, TypedDigest_DifferingType)
89{
90 TypedDigest t1{CDigest::Type::SHA1, "da39a3ee5e6b4b0d3255bfef95601890afd80709"};
91 TypedDigest t2{CDigest::Type::SHA256, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"};
92 // Silence "unused expression" warning
93 bool a;
94 EXPECT_THROW(a = (t1 == t2), std::logic_error);
95 // Silence "unused variable" warning
96 (void)a;
97 EXPECT_THROW(a = (t1 != t2), std::logic_error);
98 (void)a;
99}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/EndianSwap.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestEndianSwap, Endian_Swap16)
14{
15 uint16_t ref, var;
16 ref = 0x00FF;
17 var = Endian_Swap16(0xFF00);
18 EXPECT_EQ(ref, var);
19}
20
21TEST(TestEndianSwap, Endian_Swap32)
22{
23 uint32_t ref, var;
24 ref = 0x00FF00FF;
25 var = Endian_Swap32(0xFF00FF00);
26 EXPECT_EQ(ref, var);
27}
28
29TEST(TestEndianSwap, Endian_Swap64)
30{
31 uint64_t ref, var;
32 ref = UINT64_C(0x00FF00FF00FF00FF);
33 var = Endian_Swap64(UINT64_C(0xFF00FF00FF00FF00));
34 EXPECT_EQ(ref, var);
35}
36
37#ifndef WORDS_BIGENDIAN
38TEST(TestEndianSwap, Endian_SwapLE16)
39{
40 uint16_t ref, var;
41 ref = 0x00FF;
42 var = Endian_SwapLE16(0x00FF);
43 EXPECT_EQ(ref, var);
44}
45
46TEST(TestEndianSwap, Endian_SwapLE32)
47{
48 uint32_t ref, var;
49 ref = 0x00FF00FF;
50 var = Endian_SwapLE32(0x00FF00FF);
51 EXPECT_EQ(ref, var);
52}
53
54TEST(TestEndianSwap, Endian_SwapLE64)
55{
56 uint64_t ref, var;
57 ref = UINT64_C(0x00FF00FF00FF00FF);
58 var = Endian_SwapLE64(UINT64_C(0x00FF00FF00FF00FF));
59 EXPECT_EQ(ref, var);
60}
61
62TEST(TestEndianSwap, Endian_SwapBE16)
63{
64 uint16_t ref, var;
65 ref = 0x00FF;
66 var = Endian_SwapBE16(0xFF00);
67 EXPECT_EQ(ref, var);
68}
69
70TEST(TestEndianSwap, Endian_SwapBE32)
71{
72 uint32_t ref, var;
73 ref = 0x00FF00FF;
74 var = Endian_SwapBE32(0xFF00FF00);
75 EXPECT_EQ(ref, var);
76}
77
78TEST(TestEndianSwap, Endian_SwapBE64)
79{
80 uint64_t ref, var;
81 ref = UINT64_C(0x00FF00FF00FF00FF);
82 var = Endian_SwapBE64(UINT64_C(0xFF00FF00FF00FF00));
83 EXPECT_EQ(ref, var);
84}
85#else
86TEST(TestEndianSwap, Endian_SwapLE16)
87{
88 uint16_t ref, var;
89 ref = 0x00FF;
90 var = Endian_SwapLE16(0xFF00);
91 EXPECT_EQ(ref, var);
92}
93
94TEST(TestEndianSwap, Endian_SwapLE32)
95{
96 uint32_t ref, var;
97 ref = 0x00FF00FF;
98 var = Endian_SwapLE32(0xFF00FF00);
99 EXPECT_EQ(ref, var);
100}
101
102TEST(TestEndianSwap, Endian_SwapLE64)
103{
104 uint64_t ref, var;
105 ref = UINT64_C(0x00FF00FF00FF00FF);
106 var = Endian_SwapLE64(UINT64_C(0xFF00FF00FF00FF00));
107 EXPECT_EQ(ref, var);
108}
109
110TEST(TestEndianSwap, Endian_SwapBE16)
111{
112 uint16_t ref, var;
113 ref = 0x00FF;
114 var = Endian_SwapBE16(0x00FF);
115 EXPECT_EQ(ref, var);
116}
117
118TEST(TestEndianSwap, Endian_SwapBE32)
119{
120 uint32_t ref, var;
121 ref = 0x00FF00FF;
122 var = Endian_SwapBE32(0x00FF00FF);
123 EXPECT_EQ(ref, var);
124}
125
126TEST(TestEndianSwap, Endian_SwapBE64)
127{
128 uint64_t ref, var;
129 ref = UINT64_C(0x00FF00FF00FF00FF);
130 var = Endian_SwapBE64(UINT64_C(0x00FF00FF00FF00FF));
131 EXPECT_EQ(ref, var);
132}
133#endif
diff --git a/xbmc/utils/test/TestFileOperationJob.cpp b/xbmc/utils/test/TestFileOperationJob.cpp
new file mode 100644
index 0000000..cab4125
--- /dev/null
+++ b/xbmc/utils/test/TestFileOperationJob.cpp
@@ -0,0 +1,288 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "filesystem/Directory.h"
10#include "filesystem/File.h"
11#include "test/TestUtils.h"
12#include "utils/FileOperationJob.h"
13#include "utils/URIUtils.h"
14
15#include <gtest/gtest.h>
16
17TEST(TestFileOperationJob, ActionCopy)
18{
19 XFILE::CFile *tmpfile;
20 std::string tmpfilepath, destfile;
21 CFileItemList items;
22 CFileOperationJob job;
23
24 ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE("")));
25 tmpfilepath = XBMC_TEMPFILEPATH(tmpfile);
26 tmpfile->Close();
27
28 CFileItemPtr item(new CFileItem(tmpfilepath));
29 item->SetPath(tmpfilepath);
30 item->m_bIsFolder = false;
31 item->Select(true);
32 items.Add(item);
33
34 std::string destpath = URIUtils::GetDirectory(tmpfilepath);
35 destpath = URIUtils::AddFileToFolder(destpath, "copy");
36 destfile = URIUtils::AddFileToFolder(destpath, URIUtils::GetFileName(tmpfilepath));
37 ASSERT_FALSE(XFILE::CFile::Exists(destfile));
38
39 job.SetFileOperation(CFileOperationJob::ActionCopy, items, destpath);
40 EXPECT_EQ(CFileOperationJob::ActionCopy, job.GetAction());
41
42 EXPECT_TRUE(job.DoWork());
43 EXPECT_TRUE(XFILE::CFile::Exists(tmpfilepath));
44 EXPECT_TRUE(XFILE::CFile::Exists(destfile));
45
46 EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile));
47 EXPECT_TRUE(XFILE::CFile::Delete(destfile));
48 EXPECT_TRUE(XFILE::CDirectory::Remove(destpath));
49}
50
51TEST(TestFileOperationJob, ActionMove)
52{
53 XFILE::CFile *tmpfile;
54 std::string tmpfilepath, destfile;
55 CFileItemList items;
56 CFileOperationJob job;
57
58 ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE("")));
59 tmpfilepath = XBMC_TEMPFILEPATH(tmpfile);
60 tmpfile->Close();
61
62 CFileItemPtr item(new CFileItem(tmpfilepath));
63 item->SetPath(tmpfilepath);
64 item->m_bIsFolder = false;
65 item->Select(true);
66 items.Add(item);
67
68 std::string destpath = URIUtils::GetDirectory(tmpfilepath);
69 destpath = URIUtils::AddFileToFolder(destpath, "move");
70 destfile = URIUtils::AddFileToFolder(destpath, URIUtils::GetFileName(tmpfilepath));
71 ASSERT_FALSE(XFILE::CFile::Exists(destfile));
72 ASSERT_TRUE(XFILE::CDirectory::Create(destpath));
73
74 job.SetFileOperation(CFileOperationJob::ActionMove, items, destpath);
75 EXPECT_EQ(CFileOperationJob::ActionMove, job.GetAction());
76
77 EXPECT_TRUE(job.DoWork());
78 EXPECT_FALSE(XFILE::CFile::Exists(tmpfilepath));
79 EXPECT_TRUE(XFILE::CFile::Exists(destfile));
80
81 EXPECT_TRUE(XFILE::CFile::Delete(destfile));
82 EXPECT_TRUE(XFILE::CDirectory::Remove(destpath));
83}
84
85TEST(TestFileOperationJob, ActionDelete)
86{
87 XFILE::CFile *tmpfile;
88 std::string tmpfilepath, destfile;
89 CFileItemList items;
90 CFileOperationJob job;
91
92 ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE("")));
93 tmpfilepath = XBMC_TEMPFILEPATH(tmpfile);
94 tmpfile->Close();
95
96 CFileItemPtr item(new CFileItem(tmpfilepath));
97 item->SetPath(tmpfilepath);
98 item->m_bIsFolder = false;
99 item->Select(true);
100 items.Add(item);
101
102 std::string destpath = URIUtils::GetDirectory(tmpfilepath);
103 destpath = URIUtils::AddFileToFolder(destpath, "delete");
104 destfile = URIUtils::AddFileToFolder(destpath, URIUtils::GetFileName(tmpfilepath));
105 ASSERT_FALSE(XFILE::CFile::Exists(destfile));
106
107 job.SetFileOperation(CFileOperationJob::ActionCopy, items, destpath);
108 EXPECT_EQ(CFileOperationJob::ActionCopy, job.GetAction());
109
110 EXPECT_TRUE(job.DoWork());
111 EXPECT_TRUE(XFILE::CFile::Exists(tmpfilepath));
112 EXPECT_TRUE(XFILE::CFile::Exists(destfile));
113
114 job.SetFileOperation(CFileOperationJob::ActionDelete, items, "");
115 EXPECT_EQ(CFileOperationJob::ActionDelete, job.GetAction());
116
117 EXPECT_TRUE(job.DoWork());
118 EXPECT_FALSE(XFILE::CFile::Exists(tmpfilepath));
119
120 items.Clear();
121 CFileItemPtr item2(new CFileItem(destfile));
122 item2->SetPath(destfile);
123 item2->m_bIsFolder = false;
124 item2->Select(true);
125 items.Add(item2);
126
127 job.SetFileOperation(CFileOperationJob::ActionDelete, items, "");
128 EXPECT_EQ(CFileOperationJob::ActionDelete, job.GetAction());
129
130 EXPECT_TRUE(job.DoWork());
131 EXPECT_FALSE(XFILE::CFile::Exists(destfile));
132 EXPECT_TRUE(XFILE::CDirectory::Remove(destpath));
133}
134
135TEST(TestFileOperationJob, ActionReplace)
136{
137 XFILE::CFile *tmpfile;
138 std::string tmpfilepath, destfile;
139 CFileItemList items;
140 CFileOperationJob job;
141
142 ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE("")));
143 tmpfilepath = XBMC_TEMPFILEPATH(tmpfile);
144 tmpfile->Close();
145
146 CFileItemPtr item(new CFileItem(tmpfilepath));
147 item->SetPath(tmpfilepath);
148 item->m_bIsFolder = false;
149 item->Select(true);
150 items.Add(item);
151
152 std::string destpath = URIUtils::GetDirectory(tmpfilepath);
153 destpath = URIUtils::AddFileToFolder(destpath, "replace");
154 destfile = URIUtils::AddFileToFolder(destpath, URIUtils::GetFileName(tmpfilepath));
155 ASSERT_FALSE(XFILE::CFile::Exists(destfile));
156
157 job.SetFileOperation(CFileOperationJob::ActionCopy, items, destpath);
158 EXPECT_EQ(CFileOperationJob::ActionCopy, job.GetAction());
159
160 EXPECT_TRUE(job.DoWork());
161 EXPECT_TRUE(XFILE::CFile::Exists(tmpfilepath));
162 EXPECT_TRUE(XFILE::CFile::Exists(destfile));
163
164 job.SetFileOperation(CFileOperationJob::ActionReplace, items, destpath);
165 EXPECT_EQ(CFileOperationJob::ActionReplace, job.GetAction());
166
167 EXPECT_TRUE(job.DoWork());
168 EXPECT_TRUE(XFILE::CFile::Exists(destfile));
169
170 EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile));
171 EXPECT_TRUE(XFILE::CFile::Delete(destfile));
172 EXPECT_TRUE(XFILE::CDirectory::Remove(destpath));
173}
174
175TEST(TestFileOperationJob, ActionCreateFolder)
176{
177 XFILE::CFile *tmpfile;
178 std::string tmpfilepath, destpath;
179 CFileItemList items;
180 CFileOperationJob job;
181
182 ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE("")));
183 tmpfilepath = XBMC_TEMPFILEPATH(tmpfile);
184
185 std::string tmpfiledirectory =
186 CXBMCTestUtils::Instance().TempFileDirectory(tmpfile);
187
188 tmpfile->Close();
189
190 destpath = tmpfilepath;
191 destpath += ".createfolder";
192 ASSERT_FALSE(XFILE::CFile::Exists(destpath));
193
194 CFileItemPtr item(new CFileItem(destpath));
195 item->SetPath(destpath);
196 item->m_bIsFolder = true;
197 item->Select(true);
198 items.Add(item);
199
200 job.SetFileOperation(CFileOperationJob::ActionCreateFolder, items, tmpfiledirectory);
201 EXPECT_EQ(CFileOperationJob::ActionCreateFolder, job.GetAction());
202
203 EXPECT_TRUE(job.DoWork());
204 EXPECT_TRUE(XFILE::CDirectory::Exists(destpath));
205
206 EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile));
207 EXPECT_TRUE(XFILE::CDirectory::Remove(destpath));
208}
209
210// This test will fail until ActionDeleteFolder has a proper implementation
211TEST(TestFileOperationJob, ActionDeleteFolder)
212{
213 XFILE::CFile *tmpfile;
214 std::string tmpfilepath, destpath;
215 CFileItemList items;
216 CFileOperationJob job;
217
218 ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE("")));
219 tmpfilepath = XBMC_TEMPFILEPATH(tmpfile);
220
221 std::string tmpfiledirectory =
222 CXBMCTestUtils::Instance().TempFileDirectory(tmpfile);
223
224 tmpfile->Close();
225
226 destpath = tmpfilepath;
227 destpath += ".deletefolder";
228 ASSERT_FALSE(XFILE::CFile::Exists(destpath));
229
230 CFileItemPtr item(new CFileItem(destpath));
231 item->SetPath(destpath);
232 item->m_bIsFolder = true;
233 item->Select(true);
234 items.Add(item);
235
236 job.SetFileOperation(CFileOperationJob::ActionCreateFolder, items, tmpfiledirectory);
237 EXPECT_EQ(CFileOperationJob::ActionCreateFolder, job.GetAction());
238
239 EXPECT_TRUE(job.DoWork());
240 EXPECT_TRUE(XFILE::CDirectory::Exists(destpath));
241
242 job.SetFileOperation(CFileOperationJob::ActionDeleteFolder, items, tmpfiledirectory);
243 EXPECT_EQ(CFileOperationJob::ActionDeleteFolder, job.GetAction());
244
245 EXPECT_TRUE(job.DoWork());
246 EXPECT_FALSE(XFILE::CDirectory::Exists(destpath));
247
248 EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile));
249}
250
251TEST(TestFileOperationJob, GetFunctions)
252{
253 XFILE::CFile *tmpfile;
254 std::string tmpfilepath, destfile;
255 CFileItemList items;
256 CFileOperationJob job;
257
258 ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE("")));
259 tmpfilepath = XBMC_TEMPFILEPATH(tmpfile);
260 tmpfile->Close();
261
262 CFileItemPtr item(new CFileItem(tmpfilepath));
263 item->SetPath(tmpfilepath);
264 item->m_bIsFolder = false;
265 item->Select(true);
266 items.Add(item);
267
268 std::string destpath = URIUtils::GetDirectory(tmpfilepath);
269 destpath = URIUtils::AddFileToFolder(destpath, "getfunctions");
270 destfile = URIUtils::AddFileToFolder(destpath, URIUtils::GetFileName(tmpfilepath));
271 ASSERT_FALSE(XFILE::CFile::Exists(destfile));
272
273 job.SetFileOperation(CFileOperationJob::ActionCopy, items, destpath);
274 EXPECT_EQ(CFileOperationJob::ActionCopy, job.GetAction());
275
276 EXPECT_TRUE(job.DoWork());
277 EXPECT_TRUE(XFILE::CFile::Exists(tmpfilepath));
278 EXPECT_TRUE(XFILE::CFile::Exists(destfile));
279
280 std::cout << "GetAverageSpeed(): " << job.GetAverageSpeed() << std::endl;
281 std::cout << "GetCurrentOperation(): " << job.GetCurrentOperation() << std::endl;
282 std::cout << "GetCurrentFile(): " << job.GetCurrentFile() << std::endl;
283 EXPECT_FALSE(job.GetItems().IsEmpty());
284
285 EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile));
286 EXPECT_TRUE(XFILE::CFile::Delete(destfile));
287 EXPECT_TRUE(XFILE::CDirectory::Remove(destpath));
288}
diff --git a/xbmc/utils/test/TestFileUtils.cpp b/xbmc/utils/test/TestFileUtils.cpp
new file mode 100644
index 0000000..720e82d
--- /dev/null
+++ b/xbmc/utils/test/TestFileUtils.cpp
@@ -0,0 +1,41 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "filesystem/File.h"
10#include "test/TestUtils.h"
11#include "utils/FileUtils.h"
12
13#include <gtest/gtest.h>
14
15TEST(TestFileUtils, DeleteItem_CFileItemPtr)
16{
17 XFILE::CFile *tmpfile;
18 std::string tmpfilepath;
19
20 ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE("")));
21 tmpfilepath = XBMC_TEMPFILEPATH(tmpfile);
22
23 CFileItemPtr item(new CFileItem(tmpfilepath));
24 item->SetPath(tmpfilepath);
25 item->m_bIsFolder = false;
26 item->Select(true);
27 tmpfile->Close(); //Close tmpfile before we try to delete it
28 EXPECT_TRUE(CFileUtils::DeleteItem(item));
29}
30
31TEST(TestFileUtils, DeleteItemString)
32{
33 XFILE::CFile *tmpfile;
34
35 ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE("")));
36 tmpfile->Close(); //Close tmpfile before we try to delete it
37 EXPECT_TRUE(CFileUtils::DeleteItem(XBMC_TEMPFILEPATH(tmpfile)));
38}
39
40/* Executing RenameFile() requires input from the user */
41// 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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/test/TestGlobalsHandlingPattern1.h"
10
11#include <gtest/gtest.h>
12
13using namespace xbmcutil;
14using namespace test;
15
16bool TestGlobalPattern1::ctorCalled = false;
17bool TestGlobalPattern1::dtorCalled = false;
18
19TEST(TestGlobal, Pattern1)
20{
21 EXPECT_TRUE(TestGlobalPattern1::ctorCalled);
22 {
23 std::shared_ptr<TestGlobalPattern1> ptr = g_testGlobalPattern1Ref;
24 }
25}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#include "utils/GlobalsHandling.h"
12
13#include <iostream>
14
15namespace xbmcutil
16{
17 namespace test
18 {
19 class TestGlobalPattern1
20 {
21 public:
22 static bool ctorCalled;
23 static bool dtorCalled;
24
25 int somethingToAccess = 0;
26
27 TestGlobalPattern1() { ctorCalled = true; }
28 ~TestGlobalPattern1()
29 {
30 std::cout << "Clean shutdown of TestGlobalPattern1" << std::endl << std::flush;
31 dtorCalled = true;
32 }
33
34 void beHappy() { if (somethingToAccess) throw somethingToAccess; }
35 };
36 }
37}
38
39XBMC_GLOBAL_REF(xbmcutil::test::TestGlobalPattern1,g_testGlobalPattern1);
40#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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/HTMLUtil.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestHTMLUtil, RemoveTags)
14{
15 std::string str;
16 str = "<!DOCTYPE html>\n"
17 "<html>\n"
18 " <head class=\"someclass\">\n"
19 " <body>\n"
20 " <p>blah blah blah</p>\n"
21 " </body>\n"
22 " </head>\n"
23 "</html>\n";
24 HTML::CHTMLUtil::RemoveTags(str);
25 EXPECT_STREQ("\n\n \n \n blah blah blah\n \n \n\n",
26 str.c_str());
27}
28
29TEST(TestHTMLUtil, ConvertHTMLToW)
30{
31 std::wstring inw, refstrw, varstrw;
32 inw = L"&aring;&amp;&euro;";
33 refstrw = L"\u00e5&\u20ac";
34 HTML::CHTMLUtil::ConvertHTMLToW(inw, varstrw);
35 EXPECT_STREQ(refstrw.c_str(), varstrw.c_str());
36}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/HttpHeader.h"
10
11#include <string.h>
12
13#include <gtest/gtest.h>
14
15#define CHECK_CNT_TYPE_NAME "Content-Type"
16#define CHECK_CONTENT_TYPE_HTML "text/html"
17#define CHECK_CONTENT_TYPE_HTML_CHRS "text/html; charset=WINDOWS-1251"
18#define CHECK_CONTENT_TYPE_XML_CHRS "text/xml; charset=uTf-8"
19#define CHECK_CONTENT_TYPE_TEXT "text/plain"
20#define CHECK_DATE_NAME "Date"
21#define CHECK_DATE_VALUE1 "Thu, 09 Jan 2014 17:58:30 GMT"
22#define CHECK_DATE_VALUE2 "Thu, 09 Jan 2014 20:21:20 GMT"
23#define CHECK_DATE_VALUE3 "Thu, 09 Jan 2014 20:25:02 GMT"
24#define CHECK_PROT_LINE_200 "HTTP/1.1 200 OK"
25#define CHECK_PROT_LINE_301 "HTTP/1.1 301 Moved Permanently"
26
27#define CHECK_HEADER_SMPL CHECK_PROT_LINE_200 "\r\n" \
28 CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML "\r\n" \
29 "\r\n"
30
31#define CHECK_HEADER_L1 CHECK_PROT_LINE_200 "\r\n" \
32 "Server: nginx/1.4.4\r\n" \
33 CHECK_DATE_NAME ": " CHECK_DATE_VALUE1 "\r\n" \
34 CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML_CHRS "\r\n" \
35 "Transfer-Encoding: chunked\r\n" \
36 "Connection: close\r\n" \
37 "Set-Cookie: PHPSESSID=90857d437518db8f0944ca012761048a; path=/; domain=example.com\r\n" \
38 "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" \
39 "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" \
40 "Pragma: no-cache\r\n" \
41 "Set-Cookie: user_country=ot; expires=Thu, 09-Jan-2014 18:58:30 GMT; path=/; domain=.example.com\r\n" \
42 "\r\n"
43
44#define CHECK_HEADER_R CHECK_PROT_LINE_301 "\r\n" \
45 "Server: nginx/1.4.4\r\n" \
46 CHECK_DATE_NAME ": " CHECK_DATE_VALUE2 "\r\n" \
47 CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML "\r\n" \
48 "Content-Length: 150\r\n" \
49 "Connection: close\r\n" \
50 "Location: http://www.Example.Com\r\n" \
51 "\r\n"
52
53#define CHECK_HEADER_L2 CHECK_PROT_LINE_200 "\r\n" \
54 CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\n" \
55 "Server: Apache/2.4.7 (Unix) mod_wsgi/3.4 Python/2.7.5 OpenSSL/1.0.1e\r\n" \
56 "Last-Modified: Thu, 09 Jan 2014 20:10:28 GMT\r\n" \
57 "ETag: \"9a97-4ef8f335ebd10\"\r\n" \
58 "Accept-Ranges: bytes\r\n" \
59 "Content-Length: 33355\r\n" \
60 "Vary: Accept-Encoding\r\n" \
61 "Cache-Control: max-age=3600\r\n" \
62 "Expires: Thu, 09 Jan 2014 21:25:02 GMT\r\n" \
63 "Connection: close\r\n" \
64 CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_XML_CHRS "\r\n" \
65 "\r\n"
66
67// local helper function: replace substrings
68std::string strReplace(const std::string& str, const std::string& from, const std::string& to)
69{
70 std::string result;
71 size_t prevPos = 0;
72 size_t pos;
73 const size_t len = str.length();
74
75 do
76 {
77 pos = str.find(from, prevPos);
78 result.append(str, prevPos, pos - prevPos);
79 if (pos >= len)
80 break;
81 result.append(to);
82 prevPos = pos + from.length();
83 } while (true);
84
85 return result;
86}
87
88TEST(TestHttpHeader, General)
89{
90 /* check freshly created object */
91 CHttpHeader testHdr;
92 EXPECT_TRUE(testHdr.GetHeader().empty()) << "Newly created object is not empty";
93 EXPECT_TRUE(testHdr.GetProtoLine().empty()) << "Newly created object has non-empty protocol line";
94 EXPECT_TRUE(testHdr.GetMimeType().empty()) << "Newly created object has non-empty MIME-type";
95 EXPECT_TRUE(testHdr.GetCharset().empty()) << "Newly created object has non-empty charset";
96 EXPECT_TRUE(testHdr.GetValue("foo").empty()) << "Newly created object has some parameter";
97 EXPECT_TRUE(testHdr.GetValues("bar").empty()) << "Newly created object has some parameters";
98 EXPECT_FALSE(testHdr.IsHeaderDone()) << "Newly created object has \"parsing finished\" state";
99
100 /* check general functions in simple case */
101 testHdr.Parse(CHECK_HEADER_SMPL);
102 EXPECT_FALSE(testHdr.GetHeader().empty()) << "Parsed header is empty";
103 EXPECT_FALSE(testHdr.GetProtoLine().empty()) << "Parsed header has empty protocol line";
104 EXPECT_FALSE(testHdr.GetMimeType().empty()) << "Parsed header has empty MIME-type";
105 EXPECT_FALSE(testHdr.GetValue(CHECK_CNT_TYPE_NAME).empty()) << "Parsed header has empty \"" CHECK_CNT_TYPE_NAME "\" parameter";
106 EXPECT_FALSE(testHdr.GetValues(CHECK_CNT_TYPE_NAME).empty()) << "Parsed header has no \"" CHECK_CNT_TYPE_NAME "\" parameters";
107 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Parsed header has \"parsing not finished\" state";
108
109 /* check clearing of object */
110 testHdr.Clear();
111 EXPECT_TRUE(testHdr.GetHeader().empty()) << "Cleared object is not empty";
112 EXPECT_TRUE(testHdr.GetProtoLine().empty()) << "Cleared object has non-empty protocol line";
113 EXPECT_TRUE(testHdr.GetMimeType().empty()) << "Cleared object has non-empty MIME-type";
114 EXPECT_TRUE(testHdr.GetCharset().empty()) << "Cleared object has non-empty charset";
115 EXPECT_TRUE(testHdr.GetValue(CHECK_CNT_TYPE_NAME).empty()) << "Cleared object has some parameter";
116 EXPECT_TRUE(testHdr.GetValues(CHECK_CNT_TYPE_NAME).empty()) << "Cleared object has some parameters";
117 EXPECT_FALSE(testHdr.IsHeaderDone()) << "Cleared object has \"parsing finished\" state";
118
119 /* check general functions after object clearing */
120 testHdr.Parse(CHECK_HEADER_R);
121 EXPECT_FALSE(testHdr.GetHeader().empty()) << "Parsed header is empty";
122 EXPECT_FALSE(testHdr.GetProtoLine().empty()) << "Parsed header has empty protocol line";
123 EXPECT_FALSE(testHdr.GetMimeType().empty()) << "Parsed header has empty MIME-type";
124 EXPECT_FALSE(testHdr.GetValue(CHECK_CNT_TYPE_NAME).empty()) << "Parsed header has empty \"" CHECK_CNT_TYPE_NAME "\" parameter";
125 EXPECT_FALSE(testHdr.GetValues(CHECK_CNT_TYPE_NAME).empty()) << "Parsed header has no \"" CHECK_CNT_TYPE_NAME "\" parameters";
126 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Parsed header has \"parsing not finished\" state";
127}
128
129TEST(TestHttpHeader, Parse)
130{
131 CHttpHeader testHdr;
132
133 /* check parsing line-by-line */
134 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n");
135 EXPECT_FALSE(testHdr.IsHeaderDone()) << "Not completed header has \"parsing finished\" state";
136 testHdr.Parse(CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML "\r\n");
137 EXPECT_FALSE(testHdr.IsHeaderDone()) << "Not completed header has \"parsing finished\" state";
138 testHdr.Parse("\r\n");
139 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
140 EXPECT_STREQ(CHECK_PROT_LINE_200, testHdr.GetProtoLine().c_str()) << "Wrong protocol line";
141 EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\"";
142
143 /* check autoclearing when new header is parsed */
144 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n");
145 EXPECT_FALSE(testHdr.IsHeaderDone()) << "Not completed header has \"parsing finished\" state";
146 EXPECT_TRUE(testHdr.GetValues(CHECK_CNT_TYPE_NAME).empty()) << "Cleared header has some parameters";
147 testHdr.Clear();
148 EXPECT_TRUE(testHdr.GetHeader().empty()) << "Cleared object is not empty";
149
150 /* general check parsing */
151 testHdr.Parse(CHECK_HEADER_SMPL);
152 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
153 EXPECT_STRCASEEQ(CHECK_HEADER_SMPL, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header";
154 testHdr.Parse(CHECK_HEADER_L1);
155 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
156 EXPECT_STRCASEEQ(CHECK_HEADER_L1, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header";
157 EXPECT_STREQ("Thu, 09 Jan 2014 17:58:30 GMT", testHdr.GetValue("Date").c_str()); // case-sensitive match of value
158 testHdr.Parse(CHECK_HEADER_L2);
159 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
160 EXPECT_STRCASEEQ(CHECK_HEADER_L2, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header";
161 EXPECT_STREQ("Thu, 09 Jan 2014 20:10:28 GMT", testHdr.GetValue("Last-Modified").c_str()); // case-sensitive match of value
162 testHdr.Parse(CHECK_HEADER_R);
163 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
164 EXPECT_STRCASEEQ(CHECK_HEADER_R, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header";
165 EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("Location").c_str()); // case-sensitive match of value
166
167 /* check support for '\n' line endings */
168 testHdr.Parse(strReplace(CHECK_HEADER_SMPL, "\r\n", "\n"));
169 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
170 EXPECT_STRCASEEQ(CHECK_HEADER_SMPL, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header";
171 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
172 testHdr.Parse(strReplace(CHECK_HEADER_L1, "\r\n", "\n"));
173 EXPECT_STRCASEEQ(CHECK_HEADER_L1, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header";
174 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
175 testHdr.Parse(strReplace(CHECK_HEADER_L2, "\r\n", "\n"));
176 EXPECT_STRCASEEQ(CHECK_HEADER_L2, testHdr.GetHeader().c_str()) << "Parsed header mismatch the original header";
177 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
178 testHdr.Parse(CHECK_PROT_LINE_200 "\n" CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML "\r\n"); // mixed "\n" and "\r\n"
179 testHdr.Parse("\n");
180 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
181 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";
182 EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\"";
183
184 /* check trimming of whitespaces for parameter name and value */
185 testHdr.Clear();
186 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML "\r\n\r\n");
187 EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\"";
188 testHdr.Clear();
189 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_HTML " \r\n\r\n");
190 EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\"";
191 testHdr.Clear();
192 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME ":" CHECK_CONTENT_TYPE_HTML " \r\n\r\n");
193 EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\"";
194 testHdr.Clear();
195 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME ":\t" CHECK_CONTENT_TYPE_HTML " \t \r\n\r\n");
196 EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\"";
197 testHdr.Clear();
198 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME ":\t " CHECK_CONTENT_TYPE_HTML " \t \r\n\r\n");
199 EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\"";
200 testHdr.Clear();
201 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME "\t:" CHECK_CONTENT_TYPE_HTML " \t \r\n\r\n");
202 EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\"";
203 testHdr.Clear();
204 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n" CHECK_CNT_TYPE_NAME " \t : " CHECK_CONTENT_TYPE_HTML " \t \r\n\r\n");
205 EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong value of parameter \"" CHECK_CNT_TYPE_NAME "\"";
206}
207
208TEST(TestHttpHeader, Parse_Multiline)
209{
210 CHttpHeader testHdr;
211
212 /* Check multiline parameter parsing line-by-line */
213 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n");
214 testHdr.Parse(CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\n");
215 testHdr.Parse("X-Comment: This\r\n"); // between singleline parameters
216 testHdr.Parse(" is\r\n");
217 testHdr.Parse(" multi\r\n");
218 testHdr.Parse(" line\r\n");
219 testHdr.Parse(" value\r\n");
220 testHdr.Parse(CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_TEXT "\r\n");
221 testHdr.Parse("\r\n");
222 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
223 EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
224
225 testHdr.Clear();
226 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n");
227 testHdr.Parse("X-Comment: This\r\n"); // first parameter
228 testHdr.Parse(" is\r\n");
229 testHdr.Parse(" multi\r\n");
230 testHdr.Parse(" line\r\n");
231 testHdr.Parse(" value\r\n");
232 testHdr.Parse(CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_TEXT "\r\n");
233 testHdr.Parse("\r\n");
234 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
235 EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
236
237 testHdr.Clear();
238 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n");
239 testHdr.Parse(CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\n");
240 testHdr.Parse("X-Comment: This\r\n"); // last parameter
241 testHdr.Parse(" is\r\n");
242 testHdr.Parse(" multi\r\n");
243 testHdr.Parse(" line\r\n");
244 testHdr.Parse(" value\r\n");
245 testHdr.Parse("\r\n");
246 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
247 EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
248
249 testHdr.Clear();
250 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n");
251 testHdr.Parse("X-Comment: This\r\n"); // the only parameter
252 testHdr.Parse(" is\r\n");
253 testHdr.Parse(" multi\r\n");
254 testHdr.Parse(" line\r\n");
255 testHdr.Parse(" value\r\n");
256 testHdr.Parse("\r\n");
257 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
258 EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
259
260 testHdr.Clear();
261 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n");
262 testHdr.Parse("X-Comment: This\n"); // the only parameter with mixed ending style
263 testHdr.Parse(" is\r\n");
264 testHdr.Parse(" multi\n");
265 testHdr.Parse(" line\r\n");
266 testHdr.Parse(" value\n");
267 testHdr.Parse("\r\n");
268 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
269 EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
270
271 /* Check multiline parameter parsing as one line */
272 testHdr.Clear();
273 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" \
274 CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_TEXT "\r\n\r\n"); // between singleline parameters
275 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
276 EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
277
278 testHdr.Clear();
279 testHdr.Parse(CHECK_PROT_LINE_200 "\r\nX-Comment: This\r\n is\r\n multi\r\n line\r\n value\r\n" \
280 CHECK_CNT_TYPE_NAME ": " CHECK_CONTENT_TYPE_TEXT "\r\n\r\n"); // first parameter
281 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
282 EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
283
284 testHdr.Clear();
285 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
286 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
287 EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
288
289 testHdr.Clear();
290 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
291 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
292 EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
293
294 testHdr.Clear();
295 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
296 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
297 EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
298
299 /* Check multiline parameter parsing as mixed one/many lines */
300 testHdr.Clear();
301 testHdr.Parse(CHECK_PROT_LINE_200 "\r\nX-Comment: This\n is\r\n multi\r\n");
302 testHdr.Parse(" line\n value\r\n\n"); // the only parameter with mixed ending style
303 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
304 EXPECT_STREQ("This is multi line value", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
305
306 /* Check parsing of multiline parameter with ':' in value */
307 testHdr.Clear();
308 testHdr.Parse(CHECK_PROT_LINE_200 "\r\nX-Comment: This\r\n is:\r\n mul:ti\r\n");
309 testHdr.Parse(" :line\r\n valu:e\r\n\n"); // the only parameter
310 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
311 EXPECT_STREQ("This is: mul:ti :line valu:e", testHdr.GetValue("X-Comment").c_str()) << "Wrong multiline value";
312
313 /* Check multiline parameter parsing with trimming */
314 testHdr.Clear();
315 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n");
316 testHdr.Parse(CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\n");
317 testHdr.Parse("Server: Apache/2.4.7 (Unix)\r\n"); // last parameter, line-by-line parsing
318 testHdr.Parse(" mod_wsgi/3.4 \r\n");
319 testHdr.Parse("\tPython/2.7.5\r\n");
320 testHdr.Parse("\t \t \tOpenSSL/1.0.1e\r\n");
321 testHdr.Parse("\r\n");
322 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
323 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";
324 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";
325 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";
326
327 testHdr.Clear();
328 testHdr.Parse(CHECK_PROT_LINE_200 "\r\n");
329 testHdr.Parse(CHECK_DATE_NAME ": " CHECK_DATE_VALUE3 "\r\n");
330 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
331 testHdr.Parse("\tPython/2.7.5\n\t \t \tOpenSSL/1.0.1e\r\n");
332 testHdr.Parse("\r\n");
333 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
334 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";
335 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";
336 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";
337}
338
339TEST(TestHttpHeader, GetValue)
340{
341 CHttpHeader testHdr;
342
343 /* Check that all parameters values can be retrieved */
344 testHdr.Parse(CHECK_HEADER_R);
345 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
346 EXPECT_STREQ("nginx/1.4.4", testHdr.GetValue("Server").c_str()) << "Wrong parameter value";
347 EXPECT_STREQ(CHECK_DATE_VALUE2, testHdr.GetValue(CHECK_DATE_NAME).c_str()) << "Wrong parameter value";
348 EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong parameter value";
349 EXPECT_STREQ("150", testHdr.GetValue("Content-Length").c_str()) << "Wrong parameter value";
350 EXPECT_STREQ("close", testHdr.GetValue("Connection").c_str()) << "Wrong parameter value";
351 EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("Location").c_str()) << "Wrong parameter value";
352 EXPECT_TRUE(testHdr.GetValue("foo").empty()) << "Some value is returned for non-existed parameter";
353
354 /* Check that all parameters values can be retrieved in random order */
355 testHdr.Parse(CHECK_HEADER_R);
356 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Completed header has \"parsing not finished\" state";
357 EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("Location").c_str()) << "Wrong parameter value";
358 EXPECT_STREQ(CHECK_CONTENT_TYPE_HTML, testHdr.GetValue(CHECK_CNT_TYPE_NAME).c_str()) << "Wrong parameter value";
359 EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("Location").c_str()) << "Wrong parameter value";
360 EXPECT_STREQ("close", testHdr.GetValue("Connection").c_str()) << "Wrong parameter value";
361 EXPECT_STREQ("nginx/1.4.4", testHdr.GetValue("Server").c_str()) << "Wrong parameter value";
362 EXPECT_STREQ("150", testHdr.GetValue("Content-Length").c_str()) << "Wrong parameter value";
363 EXPECT_STREQ(CHECK_DATE_VALUE2, testHdr.GetValue(CHECK_DATE_NAME).c_str()) << "Wrong parameter value";
364 EXPECT_STREQ("nginx/1.4.4", testHdr.GetValue("Server").c_str()) << "Wrong parameter value";
365 EXPECT_TRUE(testHdr.GetValue("foo").empty()) << "Some value is returned for non-existed parameter";
366
367 /* Check that parameters name is case-insensitive and value is case-sensitive*/
368 EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("location").c_str()) << "Wrong parameter value for lowercase name";
369 EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("LOCATION").c_str()) << "Wrong parameter value for UPPERCASE name";
370 EXPECT_STREQ("http://www.Example.Com", testHdr.GetValue("LoCAtIOn").c_str()) << "Wrong parameter value for MiXEdcASe name";
371
372 /* Check value of last added parameter with the same name is returned */
373 testHdr.Parse(CHECK_HEADER_L1);
374 EXPECT_STREQ("close", testHdr.GetValue("Connection").c_str()) << "Wrong parameter value";
375 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";
376 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";
377}
378
379TEST(TestHttpHeader, GetValues)
380{
381 CHttpHeader testHdr;
382
383 /* Check that all parameter values can be retrieved and order of values is correct */
384 testHdr.Parse(CHECK_HEADER_L1);
385 EXPECT_EQ(1U, testHdr.GetValues("Server").size()) << "Wrong number of values for parameter \"Server\"";
386 EXPECT_STREQ("nginx/1.4.4", testHdr.GetValues("Server")[0].c_str()) << "Wrong parameter value";
387 EXPECT_EQ(2U, testHdr.GetValues("Set-Cookie").size()) << "Wrong number of values for parameter \"Set-Cookie\"";
388 EXPECT_STREQ("PHPSESSID=90857d437518db8f0944ca012761048a; path=/; domain=example.com", testHdr.GetValues("Set-Cookie")[0].c_str()) << "Wrong parameter value";
389 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";
390 EXPECT_TRUE(testHdr.GetValues("foo").empty()) << "Some values are returned for non-existed parameter";
391}
392
393TEST(TestHttpHeader, AddParam)
394{
395 CHttpHeader testHdr;
396
397 /* General functionality */
398 testHdr.AddParam("server", "Microsoft-IIS/8.0");
399 EXPECT_STREQ("Microsoft-IIS/8.0", testHdr.GetValue("Server").c_str()) << "Wrong parameter value";
400
401 /* Interfere with parsing */
402 EXPECT_FALSE(testHdr.IsHeaderDone()) << "\"AddParam\" set \"parsing finished\" state";
403 testHdr.Parse(CHECK_PROT_LINE_200 "\r\nServer: nginx/1.4.4\r\n\r\n");
404 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Parsed header has \"parsing not finished\" state";
405 EXPECT_STREQ("nginx/1.4.4", testHdr.GetValue("Server").c_str()) << "Wrong parameter value";
406 testHdr.AddParam("server", "Apache/2.4.7");
407 EXPECT_STREQ("Apache/2.4.7", testHdr.GetValue("Server").c_str()) << "Wrong parameter value";
408 EXPECT_EQ(3U, testHdr.GetValues("Server").size()) << "Wrong number of values for parameter \"Server\"";
409
410 /* Multiple values */
411 testHdr.AddParam("X-foo", "bar1");
412 testHdr.AddParam("x-foo", "bar2");
413 testHdr.AddParam("x-fOO", "bar3");
414 EXPECT_EQ(3U, testHdr.GetValues("X-FOO").size()) << "Wrong number of values for parameter \"X-foo\"";
415 EXPECT_STREQ("bar1", testHdr.GetValues("X-FOo")[0].c_str()) << "Wrong parameter value";
416 EXPECT_STREQ("bar2", testHdr.GetValues("X-fOo")[1].c_str()) << "Wrong parameter value";
417 EXPECT_STREQ("bar3", testHdr.GetValues("x-fOo")[2].c_str()) << "Wrong parameter value";
418 EXPECT_STREQ("bar3", testHdr.GetValue("x-foo").c_str()) << "Wrong parameter value";
419
420 /* Overwrite value */
421 EXPECT_TRUE(testHdr.IsHeaderDone()) << "Parsed header has \"parsing not finished\" state";
422 testHdr.AddParam("x-fOO", "superbar", true);
423 EXPECT_EQ(1U, testHdr.GetValues("X-FoO").size()) << "Wrong number of values for parameter \"X-foo\"";
424 EXPECT_STREQ("superbar", testHdr.GetValue("x-foo").c_str()) << "Wrong parameter value";
425
426 /* Check name trimming */
427 testHdr.AddParam("\tx-fOO\t ", "bar");
428 EXPECT_EQ(2U, testHdr.GetValues("X-FoO").size()) << "Wrong number of values for parameter \"X-foo\"";
429 EXPECT_STREQ("bar", testHdr.GetValue("x-foo").c_str()) << "Wrong parameter value";
430 testHdr.AddParam(" SerVer \t ", "fakeSrv", true);
431 EXPECT_EQ(1U, testHdr.GetValues("serveR").size()) << "Wrong number of values for parameter \"Server\"";
432 EXPECT_STREQ("fakeSrv", testHdr.GetValue("Server").c_str()) << "Wrong parameter value";
433
434 /* Check value trimming */
435 testHdr.AddParam("X-TestParam", " testValue1");
436 EXPECT_STREQ("testValue1", testHdr.GetValue("X-TestParam").c_str()) << "Wrong parameter value";
437 testHdr.AddParam("X-TestParam", "\ttestValue2 and more \t ");
438 EXPECT_STREQ("testValue2 and more", testHdr.GetValue("X-TestParam").c_str()) << "Wrong parameter value";
439
440 /* Empty name or value */
441 testHdr.Clear();
442 testHdr.AddParam("X-TestParam", " ");
443 EXPECT_TRUE(testHdr.GetHeader().empty()) << "Parameter with empty value was added";
444 testHdr.AddParam("\t\t", "value");
445 EXPECT_TRUE(testHdr.GetHeader().empty());
446 testHdr.AddParam(" ", "\t");
447 EXPECT_TRUE(testHdr.GetHeader().empty());
448}
449
450TEST(TestHttpHeader, GetMimeType)
451{
452 CHttpHeader testHdr;
453
454 /* General functionality */
455 EXPECT_TRUE(testHdr.GetMimeType().empty()) << "Newly created object has non-empty MIME-type";
456 testHdr.Parse(CHECK_PROT_LINE_200 "\r\nServer: nginx/1.4.4\r\n\r\n");
457 EXPECT_TRUE(testHdr.GetMimeType().empty()) << "Non-empty MIME-type for header without MIME-type";
458 testHdr.Parse(CHECK_HEADER_SMPL);
459 EXPECT_STREQ("text/html", testHdr.GetMimeType().c_str()) << "Wrong MIME-type";
460 testHdr.Parse(CHECK_HEADER_L1);
461 EXPECT_STREQ("text/html", testHdr.GetMimeType().c_str()) << "Wrong MIME-type";
462 testHdr.Parse(CHECK_HEADER_L2);
463 EXPECT_STREQ("text/xml", testHdr.GetMimeType().c_str()) << "Wrong MIME-type";
464 testHdr.Parse(CHECK_HEADER_R);
465 EXPECT_STREQ("text/html", testHdr.GetMimeType().c_str()) << "Wrong MIME-type";
466
467 /* Overwrite by AddParam */
468 testHdr.AddParam(CHECK_CNT_TYPE_NAME, CHECK_CONTENT_TYPE_TEXT);
469 EXPECT_STREQ(CHECK_CONTENT_TYPE_TEXT, testHdr.GetMimeType().c_str()) << "MIME-type was not overwritten by \"AddParam\"";
470
471 /* Correct trimming */
472 testHdr.AddParam(CHECK_CNT_TYPE_NAME, " " CHECK_CONTENT_TYPE_TEXT " \t ;foo=bar");
473 EXPECT_STREQ(CHECK_CONTENT_TYPE_TEXT, testHdr.GetMimeType().c_str()) << "MIME-type is not trimmed correctly";
474}
475
476
477TEST(TestHttpHeader, GetCharset)
478{
479 CHttpHeader testHdr;
480
481 /* General functionality */
482 EXPECT_TRUE(testHdr.GetCharset().empty()) << "Newly created object has non-empty charset";
483 testHdr.Parse(CHECK_PROT_LINE_200 "\r\nServer: nginx/1.4.4\r\n\r\n");
484 EXPECT_TRUE(testHdr.GetCharset().empty()) << "Non-empty charset for header without charset";
485 testHdr.Parse(CHECK_HEADER_SMPL);
486 EXPECT_TRUE(testHdr.GetCharset().empty()) << "Non-empty charset for header without charset";
487 testHdr.Parse(CHECK_HEADER_L1);
488 EXPECT_STREQ("WINDOWS-1251", testHdr.GetCharset().c_str()) << "Wrong charset value";
489 testHdr.Parse(CHECK_HEADER_L2);
490 EXPECT_STREQ("UTF-8", testHdr.GetCharset().c_str()) << "Wrong charset value";
491
492 /* Overwrite by AddParam */
493 testHdr.AddParam(CHECK_CNT_TYPE_NAME, CHECK_CONTENT_TYPE_TEXT "; charset=WINDOWS-1252");
494 EXPECT_STREQ("WINDOWS-1252", testHdr.GetCharset().c_str()) << "Charset was not overwritten by \"AddParam\"";
495
496 /* Correct trimming */
497 testHdr.AddParam(CHECK_CNT_TYPE_NAME, "text/plain;charset=WINDOWS-1251");
498 EXPECT_STREQ("WINDOWS-1251", testHdr.GetCharset().c_str()) << "Wrong charset value";
499 testHdr.AddParam(CHECK_CNT_TYPE_NAME, "text/plain ;\tcharset=US-AScII\t");
500 EXPECT_STREQ("US-ASCII", testHdr.GetCharset().c_str()) << "Wrong charset value";
501 testHdr.AddParam(CHECK_CNT_TYPE_NAME, "text/html ; \tcharset=\"uTF-8\"\t");
502 EXPECT_STREQ("UTF-8", testHdr.GetCharset().c_str()) << "Wrong charset value";
503 testHdr.AddParam(CHECK_CNT_TYPE_NAME, " \ttext/xml\t;\tcharset=uTF-16 ");
504 EXPECT_STREQ("UTF-16", testHdr.GetCharset().c_str()) << "Wrong charset value";
505}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/HttpParser.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestHttpParser, General)
14{
15 HttpParser a;
16 std::string str = "POST /path/script.cgi HTTP/1.0\r\n"
17 "From: amejia@xbmc.org\r\n"
18 "User-Agent: XBMC/snapshot (compatible; MSIE 5.5; Windows NT"
19 " 4.0)\r\n"
20 "Content-Type: application/x-www-form-urlencoded\r\n"
21 "Content-Length: 35\r\n"
22 "\r\n"
23 "home=amejia&favorite+flavor=orange\r\n";
24 std::string refstr, varstr;
25
26 EXPECT_EQ(a.Done, a.addBytes(str.c_str(), str.length()));
27
28 refstr = "POST";
29 varstr = a.getMethod();
30 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
31
32 refstr = "/path/script.cgi";
33 varstr = a.getUri();
34 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
35
36 refstr = "";
37 varstr = a.getQueryString();
38 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
39
40 refstr = "home=amejia&favorite+flavor=orange\r\n";
41 varstr = a.getBody();
42 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
43
44 refstr = "application/x-www-form-urlencoded";
45 varstr = a.getValue("content-type");
46 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
47
48 EXPECT_EQ((unsigned)35, a.getContentLength());
49}
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 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/HttpRangeUtils.h"
10
11#include <gtest/gtest.h>
12
13#define RANGES_START "bytes="
14
15static const uint64_t DefaultFirstPosition = 1;
16static const uint64_t DefaultLastPosition = 0;
17static const uint64_t DefaultLength = 0;
18static const void* DefaultData = NULL;
19
20TEST(TestHttpRange, FirstPosition)
21{
22 const uint64_t expectedFirstPosition = 25;
23
24 CHttpRange range;
25 EXPECT_EQ(DefaultFirstPosition, range.GetFirstPosition());
26
27 range.SetFirstPosition(expectedFirstPosition);
28 EXPECT_EQ(expectedFirstPosition, range.GetFirstPosition());
29}
30
31TEST(TestHttpRange, LastPosition)
32{
33 const uint64_t expectedLastPosition = 25;
34
35 CHttpRange range;
36 EXPECT_EQ(DefaultLastPosition, range.GetLastPosition());
37
38 range.SetLastPosition(expectedLastPosition);
39 EXPECT_EQ(expectedLastPosition, range.GetLastPosition());
40}
41
42TEST(TestHttpRange, Length)
43{
44 const uint64_t expectedFirstPosition = 10;
45 const uint64_t expectedLastPosition = 25;
46 const uint64_t expectedLength = expectedLastPosition - expectedFirstPosition + 1;
47
48 CHttpRange range;
49 EXPECT_EQ(DefaultLength, range.GetLength());
50
51 range.SetFirstPosition(expectedFirstPosition);
52 range.SetLastPosition(expectedLastPosition);
53 EXPECT_EQ(expectedLength, range.GetLength());
54
55 CHttpRange range_length;
56 range.SetFirstPosition(expectedFirstPosition);
57 range.SetLength(expectedLength);
58 EXPECT_EQ(expectedLastPosition, range.GetLastPosition());
59 EXPECT_EQ(expectedLength, range.GetLength());
60}
61
62TEST(TestHttpRange, IsValid)
63{
64 const uint64_t validFirstPosition = 10;
65 const uint64_t validLastPosition = 25;
66 const uint64_t invalidLastPosition = 5;
67
68 CHttpRange range;
69 EXPECT_FALSE(range.IsValid());
70
71 range.SetFirstPosition(validFirstPosition);
72 EXPECT_FALSE(range.IsValid());
73
74 range.SetLastPosition(invalidLastPosition);
75 EXPECT_FALSE(range.IsValid());
76
77 range.SetLastPosition(validLastPosition);
78 EXPECT_TRUE(range.IsValid());
79}
80
81TEST(TestHttpRange, Ctor)
82{
83 const uint64_t validFirstPosition = 10;
84 const uint64_t validLastPosition = 25;
85 const uint64_t invalidLastPosition = 5;
86 const uint64_t validLength = validLastPosition - validFirstPosition + 1;
87
88 CHttpRange range_invalid(validFirstPosition, invalidLastPosition);
89 EXPECT_EQ(validFirstPosition, range_invalid.GetFirstPosition());
90 EXPECT_EQ(invalidLastPosition, range_invalid.GetLastPosition());
91 EXPECT_EQ(DefaultLength, range_invalid.GetLength());
92 EXPECT_FALSE(range_invalid.IsValid());
93
94 CHttpRange range_valid(validFirstPosition, validLastPosition);
95 EXPECT_EQ(validFirstPosition, range_valid.GetFirstPosition());
96 EXPECT_EQ(validLastPosition, range_valid.GetLastPosition());
97 EXPECT_EQ(validLength, range_valid.GetLength());
98 EXPECT_TRUE(range_valid.IsValid());
99}
100
101TEST(TestHttpResponseRange, SetData)
102{
103 const uint64_t validFirstPosition = 1;
104 const uint64_t validLastPosition = 2;
105 const uint64_t validLength = validLastPosition - validFirstPosition + 1;
106 const char* validData = "test";
107 const void* invalidData = DefaultData;
108 const size_t validDataLength = strlen(validData);
109 const size_t invalidDataLength = 1;
110
111 CHttpResponseRange range;
112 EXPECT_EQ(DefaultData, range.GetData());
113 EXPECT_FALSE(range.IsValid());
114
115 range.SetData(invalidData);
116 EXPECT_EQ(invalidData, range.GetData());
117 EXPECT_FALSE(range.IsValid());
118
119 range.SetData(validData);
120 EXPECT_EQ(validData, range.GetData());
121 EXPECT_FALSE(range.IsValid());
122
123 range.SetData(invalidData, 0);
124 EXPECT_EQ(validData, range.GetData());
125 EXPECT_FALSE(range.IsValid());
126
127 range.SetData(invalidData, invalidDataLength);
128 EXPECT_EQ(invalidData, range.GetData());
129 EXPECT_FALSE(range.IsValid());
130
131 range.SetData(validData, validDataLength);
132 EXPECT_EQ(validData, range.GetData());
133 EXPECT_EQ(0U, range.GetFirstPosition());
134 EXPECT_EQ(validDataLength - 1, range.GetLastPosition());
135 EXPECT_EQ(validDataLength, range.GetLength());
136 EXPECT_TRUE(range.IsValid());
137
138 range.SetData(invalidData, 0, 0);
139 EXPECT_EQ(invalidData, range.GetData());
140 EXPECT_FALSE(range.IsValid());
141
142 range.SetData(validData, validFirstPosition, validLastPosition);
143 EXPECT_EQ(validData, range.GetData());
144 EXPECT_EQ(validFirstPosition, range.GetFirstPosition());
145 EXPECT_EQ(validLastPosition, range.GetLastPosition());
146 EXPECT_EQ(validLength, range.GetLength());
147 EXPECT_TRUE(range.IsValid());
148}
149
150TEST(TestHttpRanges, Ctor)
151{
152 CHttpRange range;
153 uint64_t position;
154
155 CHttpRanges ranges_empty;
156
157 EXPECT_EQ(0U, ranges_empty.Size());
158 EXPECT_TRUE(ranges_empty.Get().empty());
159
160 EXPECT_FALSE(ranges_empty.Get(0, range));
161 EXPECT_FALSE(ranges_empty.GetFirst(range));
162 EXPECT_FALSE(ranges_empty.GetLast(range));
163
164 EXPECT_FALSE(ranges_empty.GetFirstPosition(position));
165 EXPECT_FALSE(ranges_empty.GetLastPosition(position));
166 EXPECT_EQ(0U, ranges_empty.GetLength());
167 EXPECT_FALSE(ranges_empty.GetTotalRange(range));
168}
169
170TEST(TestHttpRanges, GetAll)
171{
172 CHttpRange range_0(0, 2);
173 CHttpRange range_1(4, 6);
174 CHttpRange range_2(8, 10);
175
176 HttpRanges ranges_raw;
177 ranges_raw.push_back(range_0);
178 ranges_raw.push_back(range_1);
179 ranges_raw.push_back(range_2);
180
181 CHttpRanges ranges(ranges_raw);
182
183 const HttpRanges& ranges_raw_get = ranges.Get();
184 ASSERT_EQ(ranges_raw.size(), ranges_raw_get.size());
185
186 for (size_t i = 0; i < ranges_raw.size(); ++i)
187 EXPECT_EQ(ranges_raw.at(i), ranges_raw_get.at(i));
188}
189
190TEST(TestHttpRanges, GetIndex)
191{
192 CHttpRange range_0(0, 2);
193 CHttpRange range_1(4, 6);
194 CHttpRange range_2(8, 10);
195
196 HttpRanges ranges_raw;
197 ranges_raw.push_back(range_0);
198 ranges_raw.push_back(range_1);
199 ranges_raw.push_back(range_2);
200
201 CHttpRanges ranges(ranges_raw);
202
203 CHttpRange range;
204 EXPECT_TRUE(ranges.Get(0, range));
205 EXPECT_EQ(range_0, range);
206
207 EXPECT_TRUE(ranges.Get(1, range));
208 EXPECT_EQ(range_1, range);
209
210 EXPECT_TRUE(ranges.Get(2, range));
211 EXPECT_EQ(range_2, range);
212
213 EXPECT_FALSE(ranges.Get(3, range));
214}
215
216TEST(TestHttpRanges, GetFirst)
217{
218 CHttpRange range_0(0, 2);
219 CHttpRange range_1(4, 6);
220 CHttpRange range_2(8, 10);
221
222 HttpRanges ranges_raw;
223 ranges_raw.push_back(range_0);
224 ranges_raw.push_back(range_1);
225 ranges_raw.push_back(range_2);
226
227 CHttpRanges ranges(ranges_raw);
228
229 CHttpRange range;
230 EXPECT_TRUE(ranges.GetFirst(range));
231 EXPECT_EQ(range_0, range);
232}
233
234TEST(TestHttpRanges, GetLast)
235{
236 CHttpRange range_0(0, 2);
237 CHttpRange range_1(4, 6);
238 CHttpRange range_2(8, 10);
239
240 HttpRanges ranges_raw;
241 ranges_raw.push_back(range_0);
242 ranges_raw.push_back(range_1);
243 ranges_raw.push_back(range_2);
244
245 CHttpRanges ranges(ranges_raw);
246
247 CHttpRange range;
248 EXPECT_TRUE(ranges.GetLast(range));
249 EXPECT_EQ(range_2, range);
250}
251
252TEST(TestHttpRanges, Size)
253{
254 CHttpRange range_0(0, 2);
255 CHttpRange range_1(4, 6);
256 CHttpRange range_2(8, 10);
257
258 HttpRanges ranges_raw;
259 ranges_raw.push_back(range_0);
260 ranges_raw.push_back(range_1);
261 ranges_raw.push_back(range_2);
262
263 CHttpRanges ranges_empty;
264 EXPECT_EQ(0U, ranges_empty.Size());
265
266 CHttpRanges ranges(ranges_raw);
267 EXPECT_EQ(ranges_raw.size(), ranges.Size());
268}
269
270TEST(TestHttpRanges, GetFirstPosition)
271{
272 CHttpRange range_0(0, 2);
273 CHttpRange range_1(4, 6);
274 CHttpRange range_2(8, 10);
275
276 HttpRanges ranges_raw;
277 ranges_raw.push_back(range_0);
278 ranges_raw.push_back(range_1);
279 ranges_raw.push_back(range_2);
280
281 CHttpRanges ranges(ranges_raw);
282
283 uint64_t position;
284 EXPECT_TRUE(ranges.GetFirstPosition(position));
285 EXPECT_EQ(range_0.GetFirstPosition(), position);
286}
287
288TEST(TestHttpRanges, GetLastPosition)
289{
290 CHttpRange range_0(0, 2);
291 CHttpRange range_1(4, 6);
292 CHttpRange range_2(8, 10);
293
294 HttpRanges ranges_raw;
295 ranges_raw.push_back(range_0);
296 ranges_raw.push_back(range_1);
297 ranges_raw.push_back(range_2);
298
299 CHttpRanges ranges(ranges_raw);
300
301 uint64_t position;
302 EXPECT_TRUE(ranges.GetLastPosition(position));
303 EXPECT_EQ(range_2.GetLastPosition(), position);
304}
305
306TEST(TestHttpRanges, GetLength)
307{
308 CHttpRange range_0(0, 2);
309 CHttpRange range_1(4, 6);
310 CHttpRange range_2(8, 10);
311 const uint64_t expectedLength = range_0.GetLength() + range_1.GetLength() + range_2.GetLength();
312
313 HttpRanges ranges_raw;
314 ranges_raw.push_back(range_0);
315 ranges_raw.push_back(range_1);
316 ranges_raw.push_back(range_2);
317
318 CHttpRanges ranges(ranges_raw);
319
320 EXPECT_EQ(expectedLength, ranges.GetLength());
321}
322
323TEST(TestHttpRanges, GetTotalRange)
324{
325 CHttpRange range_0(0, 2);
326 CHttpRange range_1(4, 6);
327 CHttpRange range_2(8, 10);
328 CHttpRange range_total_expected(range_0.GetFirstPosition(), range_2.GetLastPosition());
329
330 HttpRanges ranges_raw;
331 ranges_raw.push_back(range_0);
332 ranges_raw.push_back(range_1);
333 ranges_raw.push_back(range_2);
334
335 CHttpRanges ranges(ranges_raw);
336
337 CHttpRange range_total;
338 EXPECT_TRUE(ranges.GetTotalRange(range_total));
339 EXPECT_EQ(range_total_expected, range_total);
340}
341
342TEST(TestHttpRanges, Add)
343{
344 CHttpRange range_0(0, 2);
345 CHttpRange range_1(4, 6);
346 CHttpRange range_2(8, 10);
347
348 CHttpRanges ranges;
349 CHttpRange range;
350
351 ranges.Add(range_0);
352 EXPECT_EQ(1U, ranges.Size());
353 EXPECT_TRUE(ranges.GetFirst(range));
354 EXPECT_EQ(range_0, range);
355 EXPECT_TRUE(ranges.GetLast(range));
356 EXPECT_EQ(range_0, range);
357
358 ranges.Add(range_1);
359 EXPECT_EQ(2U, ranges.Size());
360 EXPECT_TRUE(ranges.GetFirst(range));
361 EXPECT_EQ(range_0, range);
362 EXPECT_TRUE(ranges.GetLast(range));
363 EXPECT_EQ(range_1, range);
364
365 ranges.Add(range_2);
366 EXPECT_EQ(3U, ranges.Size());
367 EXPECT_TRUE(ranges.GetFirst(range));
368 EXPECT_EQ(range_0, range);
369 EXPECT_TRUE(ranges.GetLast(range));
370 EXPECT_EQ(range_2, range);
371}
372
373TEST(TestHttpRanges, Remove)
374{
375 CHttpRange range_0(0, 2);
376 CHttpRange range_1(4, 6);
377 CHttpRange range_2(8, 10);
378
379 HttpRanges ranges_raw;
380 ranges_raw.push_back(range_0);
381 ranges_raw.push_back(range_1);
382 ranges_raw.push_back(range_2);
383
384 CHttpRanges ranges(ranges_raw);
385
386 CHttpRange range;
387 EXPECT_EQ(3U, ranges.Size());
388 EXPECT_TRUE(ranges.Get(0, range));
389 EXPECT_EQ(range_0, range);
390 EXPECT_TRUE(ranges.Get(1, range));
391 EXPECT_EQ(range_1, range);
392 EXPECT_TRUE(ranges.Get(2, range));
393 EXPECT_EQ(range_2, range);
394
395 // remove non-existing range
396 ranges.Remove(ranges.Size());
397 EXPECT_EQ(3U, ranges.Size());
398 EXPECT_TRUE(ranges.Get(0, range));
399 EXPECT_EQ(range_0, range);
400 EXPECT_TRUE(ranges.Get(1, range));
401 EXPECT_EQ(range_1, range);
402 EXPECT_TRUE(ranges.Get(2, range));
403 EXPECT_EQ(range_2, range);
404
405 // remove first range
406 ranges.Remove(0);
407 EXPECT_EQ(2U, ranges.Size());
408 EXPECT_TRUE(ranges.Get(0, range));
409 EXPECT_EQ(range_1, range);
410 EXPECT_TRUE(ranges.Get(1, range));
411 EXPECT_EQ(range_2, range);
412
413 // remove last range
414 ranges.Remove(1);
415 EXPECT_EQ(1U, ranges.Size());
416 EXPECT_TRUE(ranges.Get(0, range));
417 EXPECT_EQ(range_1, range);
418
419 // remove remaining range
420 ranges.Remove(0);
421 EXPECT_EQ(0U, ranges.Size());
422}
423
424TEST(TestHttpRanges, Clear)
425{
426 CHttpRange range_0(0, 2);
427 CHttpRange range_1(4, 6);
428 CHttpRange range_2(8, 10);
429
430 HttpRanges ranges_raw;
431 ranges_raw.push_back(range_0);
432 ranges_raw.push_back(range_1);
433 ranges_raw.push_back(range_2);
434
435 CHttpRanges ranges(ranges_raw);
436
437 CHttpRange range;
438 EXPECT_EQ(3U, ranges.Size());
439 EXPECT_TRUE(ranges.Get(0, range));
440 EXPECT_EQ(range_0, range);
441 EXPECT_TRUE(ranges.Get(1, range));
442 EXPECT_EQ(range_1, range);
443 EXPECT_TRUE(ranges.Get(2, range));
444 EXPECT_EQ(range_2, range);
445
446 ranges.Clear();
447 EXPECT_EQ(0U, ranges.Size());
448}
449
450TEST(TestHttpRanges, ParseInvalid)
451{
452 CHttpRanges ranges;
453
454 // combinations of invalid string and invalid total length
455 EXPECT_FALSE(ranges.Parse(""));
456 EXPECT_FALSE(ranges.Parse("", 0));
457 EXPECT_FALSE(ranges.Parse("", 1));
458 EXPECT_FALSE(ranges.Parse("test", 0));
459 EXPECT_FALSE(ranges.Parse(RANGES_START, 0));
460
461 // empty range definition
462 EXPECT_FALSE(ranges.Parse(RANGES_START));
463 EXPECT_FALSE(ranges.Parse(RANGES_START "-"));
464
465 // bad characters in range definition
466 EXPECT_FALSE(ranges.Parse(RANGES_START "a"));
467 EXPECT_FALSE(ranges.Parse(RANGES_START "1a"));
468 EXPECT_FALSE(ranges.Parse(RANGES_START "1-a"));
469 EXPECT_FALSE(ranges.Parse(RANGES_START "a-a"));
470 EXPECT_FALSE(ranges.Parse(RANGES_START "a-1"));
471 EXPECT_FALSE(ranges.Parse(RANGES_START "--"));
472 EXPECT_FALSE(ranges.Parse(RANGES_START "1--"));
473 EXPECT_FALSE(ranges.Parse(RANGES_START "1--2"));
474 EXPECT_FALSE(ranges.Parse(RANGES_START "--2"));
475
476 // combination of valid and empty range definitions
477 EXPECT_FALSE(ranges.Parse(RANGES_START "0-1,"));
478 EXPECT_FALSE(ranges.Parse(RANGES_START ",0-1"));
479
480 // too big start position
481 EXPECT_FALSE(ranges.Parse(RANGES_START "10-11", 5));
482
483 // end position smaller than start position
484 EXPECT_FALSE(ranges.Parse(RANGES_START "1-0"));
485}
486
487TEST(TestHttpRanges, ParseStartOnly)
488{
489 const uint64_t totalLength = 5;
490 const CHttpRange range0_(0, totalLength - 1);
491 const CHttpRange range2_(2, totalLength - 1);
492
493 CHttpRange range;
494
495 CHttpRanges ranges_all;
496 EXPECT_TRUE(ranges_all.Parse(RANGES_START "0-", totalLength));
497 EXPECT_EQ(1U, ranges_all.Size());
498 EXPECT_TRUE(ranges_all.Get(0, range));
499 EXPECT_EQ(range0_, range);
500
501 CHttpRanges ranges_some;
502 EXPECT_TRUE(ranges_some.Parse(RANGES_START "2-", totalLength));
503 EXPECT_EQ(1U, ranges_some.Size());
504 EXPECT_TRUE(ranges_some.Get(0, range));
505 EXPECT_EQ(range2_, range);
506}
507
508TEST(TestHttpRanges, ParseFromEnd)
509{
510 const uint64_t totalLength = 5;
511 const CHttpRange range_1(totalLength - 1, totalLength - 1);
512 const CHttpRange range_3(totalLength - 3, totalLength - 1);
513
514 CHttpRange range;
515
516 CHttpRanges ranges_1;
517 EXPECT_TRUE(ranges_1.Parse(RANGES_START "-1", totalLength));
518 EXPECT_EQ(1U, ranges_1.Size());
519 EXPECT_TRUE(ranges_1.Get(0, range));
520 EXPECT_EQ(range_1, range);
521
522 CHttpRanges ranges_3;
523 EXPECT_TRUE(ranges_3.Parse(RANGES_START "-3", totalLength));
524 EXPECT_EQ(1U, ranges_3.Size());
525 EXPECT_TRUE(ranges_3.Get(0, range));
526 EXPECT_EQ(range_3, range);
527}
528
529TEST(TestHttpRanges, ParseSingle)
530{
531 const uint64_t totalLength = 5;
532 const CHttpRange range0_0(0, 0);
533 const CHttpRange range0_1(0, 1);
534 const CHttpRange range0_5(0, totalLength - 1);
535 const CHttpRange range1_1(1, 1);
536 const CHttpRange range1_3(1, 3);
537 const CHttpRange range3_4(3, 4);
538 const CHttpRange range4_4(4, 4);
539
540 CHttpRange range;
541
542 CHttpRanges ranges;
543 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0", totalLength));
544 EXPECT_EQ(1U, ranges.Size());
545 EXPECT_TRUE(ranges.Get(0, range));
546 EXPECT_EQ(range0_0, range);
547
548 EXPECT_TRUE(ranges.Parse(RANGES_START "0-1", totalLength));
549 EXPECT_EQ(1U, ranges.Size());
550 EXPECT_TRUE(ranges.Get(0, range));
551 EXPECT_EQ(range0_1, range);
552
553 EXPECT_TRUE(ranges.Parse(RANGES_START "0-5", totalLength));
554 EXPECT_EQ(1U, ranges.Size());
555 EXPECT_TRUE(ranges.Get(0, range));
556 EXPECT_EQ(range0_5, range);
557
558 EXPECT_TRUE(ranges.Parse(RANGES_START "1-1", totalLength));
559 EXPECT_EQ(1U, ranges.Size());
560 EXPECT_TRUE(ranges.Get(0, range));
561 EXPECT_EQ(range1_1, range);
562
563 EXPECT_TRUE(ranges.Parse(RANGES_START "1-3", totalLength));
564 EXPECT_EQ(1U, ranges.Size());
565 EXPECT_TRUE(ranges.Get(0, range));
566 EXPECT_EQ(range1_3, range);
567
568 EXPECT_TRUE(ranges.Parse(RANGES_START "3-4", totalLength));
569 EXPECT_EQ(1U, ranges.Size());
570 EXPECT_TRUE(ranges.Get(0, range));
571 EXPECT_EQ(range3_4, range);
572
573 EXPECT_TRUE(ranges.Parse(RANGES_START "4-4", totalLength));
574 EXPECT_EQ(1U, ranges.Size());
575 EXPECT_TRUE(ranges.Get(0, range));
576 EXPECT_EQ(range4_4, range);
577}
578
579TEST(TestHttpRanges, ParseMulti)
580{
581 const uint64_t totalLength = 6;
582 const CHttpRange range0_0(0, 0);
583 const CHttpRange range0_1(0, 1);
584 const CHttpRange range1_3(1, 3);
585 const CHttpRange range2_2(2, 2);
586 const CHttpRange range4_5(4, 5);
587 const CHttpRange range5_5(5, 5);
588
589 CHttpRange range;
590
591 CHttpRanges ranges;
592 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-2", totalLength));
593 EXPECT_EQ(2U, ranges.Size());
594 EXPECT_TRUE(ranges.Get(0, range));
595 EXPECT_EQ(range0_0, range);
596 EXPECT_TRUE(ranges.Get(1, range));
597 EXPECT_EQ(range2_2, range);
598
599 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-2,4-5", totalLength));
600 EXPECT_EQ(3U, ranges.Size());
601 EXPECT_TRUE(ranges.Get(0, range));
602 EXPECT_EQ(range0_0, range);
603 EXPECT_TRUE(ranges.Get(1, range));
604 EXPECT_EQ(range2_2, range);
605 EXPECT_TRUE(ranges.Get(2, range));
606 EXPECT_EQ(range4_5, range);
607
608 EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,5-5", totalLength));
609 EXPECT_EQ(2U, ranges.Size());
610 EXPECT_TRUE(ranges.Get(0, range));
611 EXPECT_EQ(range0_1, range);
612 EXPECT_TRUE(ranges.Get(1, range));
613 EXPECT_EQ(range5_5, range);
614
615 EXPECT_TRUE(ranges.Parse(RANGES_START "1-3,5-5", totalLength));
616 EXPECT_EQ(2U, ranges.Size());
617 EXPECT_TRUE(ranges.Get(0, range));
618 EXPECT_EQ(range1_3, range);
619 EXPECT_TRUE(ranges.Get(1, range));
620 EXPECT_EQ(range5_5, range);
621}
622
623TEST(TestHttpRanges, ParseOrderedNotOverlapping)
624{
625 const uint64_t totalLength = 5;
626 const CHttpRange range0_0(0, 0);
627 const CHttpRange range0_1(0, 1);
628 const CHttpRange range2_2(2, 2);
629 const CHttpRange range2_(2, totalLength - 1);
630 const CHttpRange range_1(totalLength - 1, totalLength - 1);
631
632 CHttpRange range;
633
634 CHttpRanges ranges;
635 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,-1", totalLength));
636 EXPECT_EQ(2U, ranges.Size());
637 EXPECT_TRUE(ranges.Get(0, range));
638 EXPECT_EQ(range0_0, range);
639 EXPECT_TRUE(ranges.Get(1, range));
640 EXPECT_EQ(range_1, range);
641
642 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-2,-1", totalLength));
643 EXPECT_EQ(3U, ranges.Size());
644 EXPECT_TRUE(ranges.Get(0, range));
645 EXPECT_EQ(range0_0, range);
646 EXPECT_TRUE(ranges.Get(1, range));
647 EXPECT_EQ(range2_2, range);
648 EXPECT_TRUE(ranges.Get(2, range));
649 EXPECT_EQ(range_1, range);
650
651 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-", totalLength));
652 EXPECT_EQ(2U, ranges.Size());
653 EXPECT_TRUE(ranges.Get(0, range));
654 EXPECT_EQ(range0_0, range);
655 EXPECT_TRUE(ranges.Get(1, range));
656 EXPECT_EQ(range2_, range);
657}
658
659TEST(TestHttpRanges, ParseOrderedBackToBack)
660{
661 const uint64_t totalLength = 5;
662 const CHttpRange range0_1(0, 1);
663 const CHttpRange range0_2(0, 2);
664 const CHttpRange range1_2(1, 2);
665 const CHttpRange range0_3(0, 3);
666 const CHttpRange range4_4(4, 4);
667 const CHttpRange range0_4(0, 4);
668 const CHttpRange range3_4(3, 4);
669
670 CHttpRange range;
671
672 CHttpRanges ranges;
673 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,1-1", totalLength));
674 EXPECT_EQ(1U, ranges.Size());
675 EXPECT_TRUE(ranges.Get(0, range));
676 EXPECT_EQ(range0_1, range);
677
678 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,1-1,2-2", totalLength));
679 EXPECT_EQ(1U, ranges.Size());
680 EXPECT_TRUE(ranges.Get(0, range));
681 EXPECT_EQ(range0_2, range);
682
683 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,1-1,2-2,3-3", totalLength));
684 EXPECT_EQ(1U, ranges.Size());
685 EXPECT_TRUE(ranges.Get(0, range));
686 EXPECT_EQ(range0_3, range);
687
688 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,1-1,2-2,3-3,4-4", totalLength));
689 EXPECT_EQ(1U, ranges.Size());
690 EXPECT_TRUE(ranges.Get(0, range));
691 EXPECT_EQ(range0_4, range);
692
693 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,1-1,3-3,4-4", totalLength));
694 EXPECT_EQ(2U, ranges.Size());
695 EXPECT_TRUE(ranges.Get(0, range));
696 EXPECT_EQ(range0_1, range);
697 EXPECT_TRUE(ranges.Get(1, range));
698 EXPECT_EQ(range3_4, range);
699
700 EXPECT_TRUE(ranges.Parse(RANGES_START "1-1,2-2,4-4", totalLength));
701 EXPECT_EQ(2U, ranges.Size());
702 EXPECT_TRUE(ranges.Get(0, range));
703 EXPECT_EQ(range1_2, range);
704 EXPECT_TRUE(ranges.Get(1, range));
705 EXPECT_EQ(range4_4, range);
706}
707
708TEST(TestHttpRanges, ParseOrderedOverlapping)
709{
710 const uint64_t totalLength = 5;
711 const CHttpRange range0_0(0, 0);
712 const CHttpRange range0_1(0, 1);
713 const CHttpRange range0_2(0, 2);
714 const CHttpRange range0_3(0, 3);
715 const CHttpRange range0_4(0, 4);
716 const CHttpRange range2_4(2, 4);
717
718 CHttpRange range;
719
720 CHttpRanges ranges;
721 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,0-1", totalLength));
722 EXPECT_EQ(1U, ranges.Size());
723 EXPECT_TRUE(ranges.Get(0, range));
724 EXPECT_EQ(range0_1, range);
725
726 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,0-1,0-2", totalLength));
727 EXPECT_EQ(1U, ranges.Size());
728 EXPECT_TRUE(ranges.Get(0, range));
729 EXPECT_EQ(range0_2, range);
730
731 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,0-1,1-2", totalLength));
732 EXPECT_EQ(1U, ranges.Size());
733 EXPECT_TRUE(ranges.Get(0, range));
734 EXPECT_EQ(range0_2, range);
735
736 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,0-2,1-3", totalLength));
737 EXPECT_EQ(1U, ranges.Size());
738 EXPECT_TRUE(ranges.Get(0, range));
739 EXPECT_EQ(range0_3, range);
740
741 EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,1-2,2-3,3-4", totalLength));
742 EXPECT_EQ(1U, ranges.Size());
743 EXPECT_TRUE(ranges.Get(0, range));
744 EXPECT_EQ(range0_4, range);
745
746 EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-3,2-4,4-4", totalLength));
747 EXPECT_EQ(2U, ranges.Size());
748 EXPECT_TRUE(ranges.Get(0, range));
749 EXPECT_EQ(range0_0, range);
750 EXPECT_TRUE(ranges.Get(1, range));
751 EXPECT_EQ(range2_4, range);
752}
753
754TEST(TestHttpRanges, ParseUnorderedNotOverlapping)
755{
756 const uint64_t totalLength = 5;
757 const CHttpRange range0_0(0, 0);
758 const CHttpRange range0_1(0, 1);
759 const CHttpRange range2_2(2, 2);
760 const CHttpRange range2_(2, totalLength - 1);
761 const CHttpRange range_1(totalLength - 1, totalLength - 1);
762
763 CHttpRange range;
764
765 CHttpRanges ranges;
766 EXPECT_TRUE(ranges.Parse(RANGES_START "-1,0-0", totalLength));
767 EXPECT_EQ(2U, ranges.Size());
768 EXPECT_TRUE(ranges.Get(0, range));
769 EXPECT_EQ(range0_0, range);
770 EXPECT_TRUE(ranges.Get(1, range));
771 EXPECT_EQ(range_1, range);
772
773 EXPECT_TRUE(ranges.Parse(RANGES_START "2-2,-1,0-0", totalLength));
774 EXPECT_EQ(3U, ranges.Size());
775 EXPECT_TRUE(ranges.Get(0, range));
776 EXPECT_EQ(range0_0, range);
777 EXPECT_TRUE(ranges.Get(1, range));
778 EXPECT_EQ(range2_2, range);
779 EXPECT_TRUE(ranges.Get(2, range));
780 EXPECT_EQ(range_1, range);
781
782 EXPECT_TRUE(ranges.Parse(RANGES_START "2-,0-0", totalLength));
783 EXPECT_EQ(2U, ranges.Size());
784 EXPECT_TRUE(ranges.Get(0, range));
785 EXPECT_EQ(range0_0, range);
786 EXPECT_TRUE(ranges.Get(1, range));
787 EXPECT_EQ(range2_, range);
788}
789
790TEST(TestHttpRanges, ParseUnorderedBackToBack)
791{
792 const uint64_t totalLength = 5;
793 const CHttpRange range0_0(0, 0);
794 const CHttpRange range1_1(1, 1);
795 const CHttpRange range0_1(0, 1);
796 const CHttpRange range2_2(2, 2);
797 const CHttpRange range0_2(0, 2);
798 const CHttpRange range1_2(1, 2);
799 const CHttpRange range3_3(3, 3);
800 const CHttpRange range0_3(0, 3);
801 const CHttpRange range4_4(4, 4);
802 const CHttpRange range0_4(0, 4);
803 const CHttpRange range3_4(3, 4);
804
805 CHttpRange range;
806
807 CHttpRanges ranges;
808 EXPECT_TRUE(ranges.Parse(RANGES_START "1-1,0-0", totalLength));
809 EXPECT_EQ(1U, ranges.Size());
810 EXPECT_TRUE(ranges.Get(0, range));
811 EXPECT_EQ(range0_1, range);
812
813 EXPECT_TRUE(ranges.Parse(RANGES_START "1-1,0-0,2-2", totalLength));
814 EXPECT_EQ(1U, ranges.Size());
815 EXPECT_TRUE(ranges.Get(0, range));
816 EXPECT_EQ(range0_2, range);
817
818 EXPECT_TRUE(ranges.Parse(RANGES_START "2-2,1-1,3-3,0-0", totalLength));
819 EXPECT_EQ(1U, ranges.Size());
820 EXPECT_TRUE(ranges.Get(0, range));
821 EXPECT_EQ(range0_3, range);
822
823 EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,1-1,0-0,2-2,3-3", totalLength));
824 EXPECT_EQ(1U, ranges.Size());
825 EXPECT_TRUE(ranges.Get(0, range));
826 EXPECT_EQ(range0_4, range);
827
828 EXPECT_TRUE(ranges.Parse(RANGES_START "3-3,0-0,4-4,1-1", totalLength));
829 EXPECT_EQ(2U, ranges.Size());
830 EXPECT_TRUE(ranges.Get(0, range));
831 EXPECT_EQ(range0_1, range);
832 EXPECT_TRUE(ranges.Get(1, range));
833 EXPECT_EQ(range3_4, range);
834
835 EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,1-1,2-2", totalLength));
836 EXPECT_EQ(2U, ranges.Size());
837 EXPECT_TRUE(ranges.Get(0, range));
838 EXPECT_EQ(range1_2, range);
839 EXPECT_TRUE(ranges.Get(1, range));
840 EXPECT_EQ(range4_4, range);
841}
842
843TEST(TestHttpRanges, ParseUnorderedOverlapping)
844{
845 const uint64_t totalLength = 5;
846 const CHttpRange range0_0(0, 0);
847 const CHttpRange range0_1(0, 1);
848 const CHttpRange range0_2(0, 2);
849 const CHttpRange range0_3(0, 3);
850 const CHttpRange range0_4(0, 4);
851 const CHttpRange range2_4(2, 4);
852
853 CHttpRange range;
854
855 CHttpRanges ranges;
856 EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,0-0", totalLength));
857 EXPECT_EQ(1U, ranges.Size());
858 EXPECT_TRUE(ranges.Get(0, range));
859 EXPECT_EQ(range0_1, range);
860
861 EXPECT_TRUE(ranges.Parse(RANGES_START "0-2,0-0,0-1", totalLength));
862 EXPECT_EQ(1U, ranges.Size());
863 EXPECT_TRUE(ranges.Get(0, range));
864 EXPECT_EQ(range0_2, range);
865
866 EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,1-2,0-0", totalLength));
867 EXPECT_EQ(1U, ranges.Size());
868 EXPECT_TRUE(ranges.Get(0, range));
869 EXPECT_EQ(range0_2, range);
870
871 EXPECT_TRUE(ranges.Parse(RANGES_START "0-2,0-0,1-3", totalLength));
872 EXPECT_EQ(1U, ranges.Size());
873 EXPECT_TRUE(ranges.Get(0, range));
874 EXPECT_EQ(range0_3, range);
875
876 EXPECT_TRUE(ranges.Parse(RANGES_START "2-3,1-2,0-1,3-4", totalLength));
877 EXPECT_EQ(1U, ranges.Size());
878 EXPECT_TRUE(ranges.Get(0, range));
879 EXPECT_EQ(range0_4, range);
880
881 EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,0-0,2-4,2-3", totalLength));
882 EXPECT_EQ(2U, ranges.Size());
883 EXPECT_TRUE(ranges.Get(0, range));
884 EXPECT_EQ(range0_0, range);
885 EXPECT_TRUE(ranges.Get(1, range));
886 EXPECT_EQ(range2_4, range);
887}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/HttpResponse.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestHttpResponse, General)
14{
15 CHttpResponse a(HTTP::POST, HTTP::OK);
16 std::string response, content, refstr;
17
18 a.AddHeader("date", "Sun, 01 Jul 2012 00:00:00 -0400");
19 a.AddHeader("content-type", "text/html");
20 content = "<html>\r\n"
21 " <body>\r\n"
22 " <h1>XBMC TestHttpResponse Page</h1>\r\n"
23 " <p>blah blah blah</p>\r\n"
24 " </body>\r\n"
25 "</html>\r\n";
26 a.SetContent(content.c_str(), content.length());
27
28 response = a.Create();;
29 EXPECT_EQ((unsigned int)210, response.size());
30
31 refstr = "HTTP/1.1 200 OK\r\n"
32 "date: Sun, 01 Jul 2012 00:00:00 -0400\r\n"
33 "content-type: text/html\r\n"
34 "Content-Length: 106\r\n"
35 "\r\n"
36 "<html>\r\n"
37 " <body>\r\n"
38 " <h1>XBMC TestHttpResponse Page</h1>\r\n"
39 " <p>blah blah blah</p>\r\n"
40 " </body>\r\n"
41 "</html>\r\n";
42 EXPECT_STREQ(refstr.c_str(), response.c_str());
43}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/JSONVariantParser.h"
10#include "utils/Variant.h"
11
12#include <gtest/gtest.h>
13
14TEST(TestJSONVariantParser, CannotParseNullptr)
15{
16 CVariant variant;
17 ASSERT_FALSE(CJSONVariantParser::Parse(nullptr, variant));
18}
19
20TEST(TestJSONVariantParser, CannotParseEmptyString)
21{
22 CVariant variant;
23 ASSERT_FALSE(CJSONVariantParser::Parse("", variant));
24 ASSERT_FALSE(CJSONVariantParser::Parse(std::string(), variant));
25}
26
27TEST(TestJSONVariantParser, CannotParseInvalidJson)
28{
29 CVariant variant;
30 ASSERT_FALSE(CJSONVariantParser::Parse("{", variant));
31 ASSERT_FALSE(CJSONVariantParser::Parse("}", variant));
32 ASSERT_FALSE(CJSONVariantParser::Parse("[", variant));
33 ASSERT_FALSE(CJSONVariantParser::Parse("]", variant));
34 ASSERT_FALSE(CJSONVariantParser::Parse("foo", variant));
35}
36
37TEST(TestJSONVariantParser, CanParseNull)
38{
39 CVariant variant;
40 ASSERT_TRUE(CJSONVariantParser::Parse("null", variant));
41 ASSERT_TRUE(variant.isNull());
42}
43
44TEST(TestJSONVariantParser, CanParseBoolean)
45{
46 CVariant variant;
47 ASSERT_TRUE(CJSONVariantParser::Parse("true", variant));
48 ASSERT_TRUE(variant.isBoolean());
49 ASSERT_TRUE(variant.asBoolean());
50
51 ASSERT_TRUE(CJSONVariantParser::Parse("false", variant));
52 ASSERT_TRUE(variant.isBoolean());
53 ASSERT_FALSE(variant.asBoolean());
54}
55
56TEST(TestJSONVariantParser, CanParseSignedInteger)
57{
58 CVariant variant;
59 ASSERT_TRUE(CJSONVariantParser::Parse("-1", variant));
60 ASSERT_TRUE(variant.isInteger());
61 ASSERT_EQ(-1, variant.asInteger());
62}
63
64TEST(TestJSONVariantParser, CanParseUnsignedInteger)
65{
66 CVariant variant;
67 ASSERT_TRUE(CJSONVariantParser::Parse("0", variant));
68 ASSERT_TRUE(variant.isUnsignedInteger());
69 ASSERT_EQ(0U, variant.asUnsignedInteger());
70
71 ASSERT_TRUE(CJSONVariantParser::Parse("1", variant));
72 ASSERT_TRUE(variant.isUnsignedInteger());
73 ASSERT_EQ(1U, variant.asUnsignedInteger());
74}
75
76TEST(TestJSONVariantParser, CanParseSignedInteger64)
77{
78 CVariant variant;
79 ASSERT_TRUE(CJSONVariantParser::Parse("-4294967296", variant));
80 ASSERT_TRUE(variant.isInteger());
81 ASSERT_EQ(-4294967296, variant.asInteger());
82}
83
84TEST(TestJSONVariantParser, CanParseUnsignedInteger64)
85{
86 CVariant variant;
87 ASSERT_TRUE(CJSONVariantParser::Parse("4294967296", variant));
88 ASSERT_TRUE(variant.isUnsignedInteger());
89 ASSERT_EQ(4294967296U, variant.asUnsignedInteger());
90}
91
92TEST(TestJSONVariantParser, CanParseDouble)
93{
94 CVariant variant;
95 ASSERT_TRUE(CJSONVariantParser::Parse("0.0", variant));
96 ASSERT_TRUE(variant.isDouble());
97 ASSERT_EQ(0.0, variant.asDouble());
98
99 ASSERT_TRUE(CJSONVariantParser::Parse("1.0", variant));
100 ASSERT_TRUE(variant.isDouble());
101 ASSERT_EQ(1.0, variant.asDouble());
102
103 ASSERT_TRUE(CJSONVariantParser::Parse("-1.0", variant));
104 ASSERT_TRUE(variant.isDouble());
105 ASSERT_EQ(-1.0, variant.asDouble());
106}
107
108TEST(TestJSONVariantParser, CanParseString)
109{
110 CVariant variant;
111 ASSERT_TRUE(CJSONVariantParser::Parse("\"\"", variant));
112 ASSERT_TRUE(variant.isString());
113 ASSERT_TRUE(variant.empty());
114
115 ASSERT_TRUE(CJSONVariantParser::Parse("\"foo\"", variant));
116 ASSERT_TRUE(variant.isString());
117 ASSERT_STREQ("foo", variant.asString().c_str());
118
119 ASSERT_TRUE(CJSONVariantParser::Parse("\"foo bar\"", variant));
120 ASSERT_TRUE(variant.isString());
121 ASSERT_STREQ("foo bar", variant.asString().c_str());
122}
123
124TEST(TestJSONVariantParser, CanParseObject)
125{
126 CVariant variant;
127 ASSERT_TRUE(CJSONVariantParser::Parse("{}", variant));
128 ASSERT_TRUE(variant.isObject());
129 ASSERT_TRUE(variant.empty());
130
131 variant.clear();
132 ASSERT_TRUE(CJSONVariantParser::Parse("{ \"foo\": \"bar\" }", variant));
133 ASSERT_TRUE(variant.isObject());
134 ASSERT_TRUE(variant.isMember("foo"));
135 ASSERT_TRUE(variant["foo"].isString());
136 ASSERT_STREQ("bar", variant["foo"].asString().c_str());
137
138 variant.clear();
139 ASSERT_TRUE(CJSONVariantParser::Parse("{ \"foo\": \"bar\", \"bar\": true }", variant));
140 ASSERT_TRUE(variant.isObject());
141 ASSERT_TRUE(variant.isMember("foo"));
142 ASSERT_TRUE(variant["foo"].isString());
143 ASSERT_STREQ("bar", variant["foo"].asString().c_str());
144 ASSERT_TRUE(variant.isMember("bar"));
145 ASSERT_TRUE(variant["bar"].isBoolean());
146 ASSERT_TRUE(variant["bar"].asBoolean());
147
148 variant.clear();
149 ASSERT_TRUE(CJSONVariantParser::Parse("{ \"foo\": { \"sub-foo\": \"bar\" } }", variant));
150 ASSERT_TRUE(variant.isObject());
151 ASSERT_TRUE(variant.isMember("foo"));
152 ASSERT_TRUE(variant["foo"].isObject());
153 ASSERT_TRUE(variant["foo"].isMember("sub-foo"));
154 ASSERT_TRUE(variant["foo"]["sub-foo"].isString());
155 ASSERT_STREQ("bar", variant["foo"]["sub-foo"].asString().c_str());
156}
157
158TEST(TestJSONVariantParser, CanParseArray)
159{
160 CVariant variant;
161 ASSERT_TRUE(CJSONVariantParser::Parse("[]", variant));
162 ASSERT_TRUE(variant.isArray());
163 ASSERT_TRUE(variant.empty());
164
165 variant.clear();
166 ASSERT_TRUE(CJSONVariantParser::Parse("[ true ]", variant));
167 ASSERT_TRUE(variant.isArray());
168 ASSERT_EQ(1U, variant.size());
169 ASSERT_TRUE(variant[0].isBoolean());
170 ASSERT_TRUE(variant[0].asBoolean());
171
172 variant.clear();
173 ASSERT_TRUE(CJSONVariantParser::Parse("[ true, \"foo\" ]", variant));
174 ASSERT_TRUE(variant.isArray());
175 ASSERT_EQ(2U, variant.size());
176 ASSERT_TRUE(variant[0].isBoolean());
177 ASSERT_TRUE(variant[0].asBoolean());
178 ASSERT_TRUE(variant[1].isString());
179 ASSERT_STREQ("foo", variant[1].asString().c_str());
180
181 variant.clear();
182 ASSERT_TRUE(CJSONVariantParser::Parse("[ { \"foo\": \"bar\" } ]", variant));
183 ASSERT_TRUE(variant.isArray());
184 ASSERT_EQ(1U, variant.size());
185 ASSERT_TRUE(variant[0].isObject());
186 ASSERT_TRUE(variant[0].isMember("foo"));
187 ASSERT_TRUE(variant[0]["foo"].isString());
188 ASSERT_STREQ("bar", variant[0]["foo"].asString().c_str());
189}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/JSONVariantWriter.h"
10#include "utils/Variant.h"
11
12#include <gtest/gtest.h>
13
14TEST(TestJSONVariantWriter, CanWriteNull)
15{
16 CVariant variant;
17 std::string str;
18
19 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
20 ASSERT_STREQ("null", str.c_str());
21}
22
23TEST(TestJSONVariantWriter, CanWriteBoolean)
24{
25 CVariant variant(true);
26 std::string str;
27 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
28 ASSERT_STREQ("true", str.c_str());
29
30 variant = false;
31 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
32 ASSERT_STREQ("false", str.c_str());
33}
34
35TEST(TestJSONVariantWriter, CanWriteSignedInteger)
36{
37 CVariant variant(-1);
38 std::string str;
39 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
40 ASSERT_STREQ("-1", str.c_str());
41}
42
43TEST(TestJSONVariantWriter, CanWriteUnsignedInteger)
44{
45 CVariant variant(0);
46 std::string str;
47 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
48 ASSERT_STREQ("0", str.c_str());
49
50 variant = 1;
51 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
52 ASSERT_STREQ("1", str.c_str());
53}
54
55TEST(TestJSONVariantWriter, CanWriteSignedInteger64)
56{
57 CVariant variant(static_cast<int64_t>(-4294967296LL));
58 std::string str;
59 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
60 ASSERT_STREQ("-4294967296", str.c_str());
61}
62
63TEST(TestJSONVariantWriter, CanWriteUnsignedInteger64)
64{
65 CVariant variant(static_cast<int64_t>(4294967296LL));
66 std::string str;
67 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
68 ASSERT_STREQ("4294967296", str.c_str());
69}
70
71TEST(TestJSONVariantWriter, CanWriteDouble)
72{
73 CVariant variant(0.0);
74 std::string str;
75 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
76 ASSERT_STREQ("0.0", str.c_str());
77
78 variant = 1.0;
79 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
80 ASSERT_STREQ("1.0", str.c_str());
81
82 variant = -1.0;
83 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
84 ASSERT_STREQ("-1.0", str.c_str());
85}
86
87TEST(TestJSONVariantWriter, CanWriteString)
88{
89 CVariant variant("");
90 std::string str;
91 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
92 ASSERT_STREQ("\"\"", str.c_str());
93
94 variant = "foo";
95 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
96 ASSERT_STREQ("\"foo\"", str.c_str());
97
98 variant = "foo bar";
99 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
100 ASSERT_STREQ("\"foo bar\"", str.c_str());
101}
102
103TEST(TestJSONVariantWriter, CanWriteObject)
104{
105 CVariant variant(CVariant::VariantTypeObject);
106 std::string str;
107 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
108 ASSERT_STREQ("{}", str.c_str());
109
110 variant.clear();
111 variant["foo"] = "bar";
112 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
113 ASSERT_STREQ("{\n\t\"foo\": \"bar\"\n}", str.c_str());
114
115 variant.clear();
116 variant["foo"] = "bar";
117 variant["bar"] = true;
118 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
119 ASSERT_STREQ("{\n\t\"bar\": true,\n\t\"foo\": \"bar\"\n}", str.c_str());
120
121 variant.clear();
122 variant["foo"]["sub-foo"] = "bar";
123 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
124 ASSERT_STREQ("{\n\t\"foo\": {\n\t\t\"sub-foo\": \"bar\"\n\t}\n}", str.c_str());
125}
126
127TEST(TestJSONVariantWriter, CanWriteArray)
128{
129 CVariant variant(CVariant::VariantTypeArray);
130 std::string str;
131 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
132 ASSERT_STREQ("[]", str.c_str());
133
134 variant.clear();
135 variant.push_back(true);
136 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
137 ASSERT_STREQ("[\n\ttrue\n]", str.c_str());
138
139 variant.clear();
140 variant.push_back(true);
141 variant.push_back("foo");
142 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
143 ASSERT_STREQ("[\n\ttrue,\n\t\"foo\"\n]", str.c_str());
144
145 variant.clear();
146 CVariant obj(CVariant::VariantTypeObject);
147 obj["foo"] = "bar";
148 variant.push_back(obj);
149 ASSERT_TRUE(CJSONVariantWriter::Write(variant, str, false));
150 ASSERT_STREQ("[\n\t{\n\t\t\"foo\": \"bar\"\n\t}\n]", str.c_str());
151}
diff --git a/xbmc/utils/test/TestJobManager.cpp b/xbmc/utils/test/TestJobManager.cpp
new file mode 100644
index 0000000..40165e9
--- /dev/null
+++ b/xbmc/utils/test/TestJobManager.cpp
@@ -0,0 +1,215 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "test/MtTestUtils.h"
10#include "utils/Job.h"
11#include "utils/JobManager.h"
12#include "utils/XTimeUtils.h"
13
14#include <atomic>
15
16#include <gtest/gtest.h>
17
18using namespace ConditionPoll;
19
20struct Flags
21{
22 std::atomic<bool> lingerAtWork{true};
23 std::atomic<bool> started{false};
24 std::atomic<bool> finished{false};
25 std::atomic<bool> wasCanceled{false};
26};
27
28class DummyJob : public CJob
29{
30 Flags* m_flags;
31public:
32 inline DummyJob(Flags* flags) : m_flags(flags)
33 {
34 }
35
36 bool DoWork() override
37 {
38 m_flags->started = true;
39 while (m_flags->lingerAtWork)
40 std::this_thread::yield();
41
42 if (ShouldCancel(0,0))
43 m_flags->wasCanceled = true;
44
45 m_flags->finished = true;
46 return true;
47 }
48};
49
50class ReallyDumbJob : public CJob
51{
52 Flags* m_flags;
53public:
54 inline ReallyDumbJob(Flags* flags) : m_flags(flags) {}
55
56 bool DoWork() override
57 {
58 m_flags->finished = true;
59 return true;
60 }
61};
62
63class TestJobManager : public testing::Test
64{
65protected:
66 TestJobManager() = default;
67
68 ~TestJobManager() override
69 {
70 /* Always cancel jobs test completion */
71 CJobManager::GetInstance().CancelJobs();
72 CJobManager::GetInstance().Restart();
73 }
74};
75
76TEST_F(TestJobManager, AddJob)
77{
78 Flags* flags = new Flags();
79 ReallyDumbJob* job = new ReallyDumbJob(flags);
80 CJobManager::GetInstance().AddJob(job, NULL);
81 ASSERT_TRUE(poll([flags]() -> bool { return flags->finished; }));
82 delete flags;
83}
84
85TEST_F(TestJobManager, CancelJob)
86{
87 unsigned int id;
88 Flags* flags = new Flags();
89 DummyJob* job = new DummyJob(flags);
90 id = CJobManager::GetInstance().AddJob(job, NULL);
91
92 // wait for the worker thread to be entered
93 ASSERT_TRUE(poll([flags]() -> bool { return flags->started; }));
94
95 // cancel the job
96 CJobManager::GetInstance().CancelJob(id);
97
98 // let the worker thread continue
99 flags->lingerAtWork = false;
100
101 // make sure the job finished.
102 ASSERT_TRUE(poll([flags]() -> bool { return flags->finished; }));
103
104 // ... and that it was canceled.
105 EXPECT_TRUE(flags->wasCanceled);
106 delete flags;
107}
108
109namespace
110{
111struct JobControlPackage
112{
113 JobControlPackage()
114 {
115 // We're not ready to wait yet
116 jobCreatedMutex.lock();
117 }
118
119 ~JobControlPackage()
120 {
121 jobCreatedMutex.unlock();
122 }
123
124 bool ready = false;
125 XbmcThreads::ConditionVariable jobCreatedCond;
126 CCriticalSection jobCreatedMutex;
127};
128
129class BroadcastingJob :
130 public CJob
131{
132public:
133
134 BroadcastingJob(JobControlPackage &package) :
135 m_package(package),
136 m_finish(false)
137 {
138 }
139
140 void FinishAndStopBlocking()
141 {
142 CSingleLock lock(m_blockMutex);
143
144 m_finish = true;
145 m_block.notifyAll();
146 }
147
148 const char * GetType() const override
149 {
150 return "BroadcastingJob";
151 }
152
153 bool DoWork() override
154 {
155 {
156 CSingleLock lock(m_package.jobCreatedMutex);
157
158 m_package.ready = true;
159 m_package.jobCreatedCond.notifyAll();
160 }
161
162 CSingleLock blockLock(m_blockMutex);
163
164 // Block until we're told to go away
165 while (!m_finish)
166 m_block.wait(m_blockMutex);
167 return true;
168 }
169
170private:
171
172 JobControlPackage &m_package;
173
174 XbmcThreads::ConditionVariable m_block;
175 CCriticalSection m_blockMutex;
176 bool m_finish;
177};
178
179BroadcastingJob *
180WaitForJobToStartProcessing(CJob::PRIORITY priority, JobControlPackage &package)
181{
182 BroadcastingJob* job = new BroadcastingJob(package);
183 CJobManager::GetInstance().AddJob(job, NULL, priority);
184
185 // We're now ready to wait, wait and then unblock once ready
186 while (!package.ready)
187 package.jobCreatedCond.wait(package.jobCreatedMutex);
188
189 return job;
190}
191}
192
193TEST_F(TestJobManager, PauseLowPriorityJob)
194{
195 JobControlPackage package;
196 BroadcastingJob *job (WaitForJobToStartProcessing(CJob::PRIORITY_LOW_PAUSABLE, package));
197
198 EXPECT_TRUE(CJobManager::GetInstance().IsProcessing(CJob::PRIORITY_LOW_PAUSABLE));
199 CJobManager::GetInstance().PauseJobs();
200 EXPECT_FALSE(CJobManager::GetInstance().IsProcessing(CJob::PRIORITY_LOW_PAUSABLE));
201 CJobManager::GetInstance().UnPauseJobs();
202 EXPECT_TRUE(CJobManager::GetInstance().IsProcessing(CJob::PRIORITY_LOW_PAUSABLE));
203
204 job->FinishAndStopBlocking();
205}
206
207TEST_F(TestJobManager, IsProcessing)
208{
209 JobControlPackage package;
210 BroadcastingJob *job (WaitForJobToStartProcessing(CJob::PRIORITY_LOW_PAUSABLE, package));
211
212 EXPECT_EQ(0, CJobManager::GetInstance().IsProcessing(""));
213
214 job->FinishAndStopBlocking();
215}
diff --git a/xbmc/utils/test/TestLabelFormatter.cpp b/xbmc/utils/test/TestLabelFormatter.cpp
new file mode 100644
index 0000000..0989fec
--- /dev/null
+++ b/xbmc/utils/test/TestLabelFormatter.cpp
@@ -0,0 +1,69 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "FileItem.h"
10#include "ServiceBroker.h"
11#include "filesystem/File.h"
12#include "settings/Settings.h"
13#include "settings/SettingsComponent.h"
14#include "test/TestUtils.h"
15#include "utils/LabelFormatter.h"
16
17#include <gtest/gtest.h>
18
19/* Set default settings used by CLabelFormatter. */
20class TestLabelFormatter : public testing::Test
21{
22protected:
23 TestLabelFormatter() = default;
24
25 ~TestLabelFormatter() override
26 {
27 CServiceBroker::GetSettingsComponent()->GetSettings()->Unload();
28 }
29};
30
31TEST_F(TestLabelFormatter, FormatLabel)
32{
33 XFILE::CFile *tmpfile;
34 std::string tmpfilepath, destpath;
35 LABEL_MASKS labelMasks;
36 CLabelFormatter formatter("", labelMasks.m_strLabel2File);
37
38 ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE("")));
39 tmpfilepath = XBMC_TEMPFILEPATH(tmpfile);
40
41 CFileItemPtr item(new CFileItem(tmpfilepath));
42 item->SetPath(tmpfilepath);
43 item->m_bIsFolder = false;
44 item->Select(true);
45
46 formatter.FormatLabel(item.get());
47
48 EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile));
49}
50
51TEST_F(TestLabelFormatter, FormatLabel2)
52{
53 XFILE::CFile *tmpfile;
54 std::string tmpfilepath, destpath;
55 LABEL_MASKS labelMasks;
56 CLabelFormatter formatter("", labelMasks.m_strLabel2File);
57
58 ASSERT_NE(nullptr, (tmpfile = XBMC_CREATETEMPFILE("")));
59 tmpfilepath = XBMC_TEMPFILEPATH(tmpfile);
60
61 CFileItemPtr item(new CFileItem(tmpfilepath));
62 item->SetPath(tmpfilepath);
63 item->m_bIsFolder = false;
64 item->Select(true);
65
66 formatter.FormatLabel2(item.get());
67
68 EXPECT_TRUE(XBMC_DELETETEMPFILE(tmpfile));
69}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/LangCodeExpander.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestLangCodeExpander, ConvertISO6391ToISO6392B)
14{
15 std::string refstr, varstr;
16
17 refstr = "eng";
18 g_LangCodeExpander.ConvertISO6391ToISO6392B("en", varstr);
19 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
20}
21
22TEST(TestLangCodeExpander, ConvertToISO6392B)
23{
24 std::string refstr, varstr;
25
26 refstr = "eng";
27 g_LangCodeExpander.ConvertToISO6392B("en", varstr);
28 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
29}
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 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/Locale.h"
10#include "utils/StringUtils.h"
11
12#include <gtest/gtest.h>
13
14static const std::string TerritorySeparator = "_";
15static const std::string CodesetSeparator = ".";
16static const std::string ModifierSeparator = "@";
17
18static const std::string LanguageCodeEnglish = "en";
19static const std::string TerritoryCodeBritain = "GB";
20static const std::string CodesetUtf8 = "UTF-8";
21static const std::string ModifierLatin = "latin";
22
23TEST(TestLocale, DefaultLocale)
24{
25 CLocale locale;
26 ASSERT_FALSE(locale.IsValid());
27 ASSERT_STREQ("", locale.GetLanguageCode().c_str());
28 ASSERT_STREQ("", locale.GetTerritoryCode().c_str());
29 ASSERT_STREQ("", locale.GetCodeset().c_str());
30 ASSERT_STREQ("", locale.GetModifier().c_str());
31 ASSERT_STREQ("", locale.ToString().c_str());
32}
33
34TEST(TestLocale, LanguageLocale)
35{
36 CLocale locale(LanguageCodeEnglish);
37 ASSERT_TRUE(locale.IsValid());
38 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str());
39 ASSERT_STREQ("", locale.GetTerritoryCode().c_str());
40 ASSERT_STREQ("", locale.GetCodeset().c_str());
41 ASSERT_STREQ("", locale.GetModifier().c_str());
42 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToString().c_str());
43 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToStringLC().c_str());
44 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortString().c_str());
45 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortStringLC().c_str());
46}
47
48TEST(TestLocale, LanguageTerritoryLocale)
49{
50 const std::string strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain;
51 std::string strLocaleLC = strLocale;
52 StringUtils::ToLower(strLocaleLC);
53
54 CLocale locale(LanguageCodeEnglish, TerritoryCodeBritain);
55 ASSERT_TRUE(locale.IsValid());
56 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str());
57 ASSERT_STREQ(TerritoryCodeBritain.c_str(), locale.GetTerritoryCode().c_str());
58 ASSERT_STREQ("", locale.GetCodeset().c_str());
59 ASSERT_STREQ("", locale.GetModifier().c_str());
60 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
61 ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str());
62 ASSERT_STREQ(strLocale.c_str(), locale.ToShortString().c_str());
63 ASSERT_STREQ(strLocaleLC.c_str(), locale.ToShortStringLC().c_str());
64}
65
66TEST(TestLocale, LanguageCodesetLocale)
67{
68 const std::string strLocale = LanguageCodeEnglish + CodesetSeparator + CodesetUtf8;
69 std::string strLocaleLC = strLocale;
70 StringUtils::ToLower(strLocaleLC);
71
72 CLocale locale(LanguageCodeEnglish, "", CodesetUtf8);
73 ASSERT_TRUE(locale.IsValid());
74 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str());
75 ASSERT_STREQ("", locale.GetTerritoryCode().c_str());
76 ASSERT_STREQ(CodesetUtf8.c_str(), locale.GetCodeset().c_str());
77 ASSERT_STREQ("", locale.GetModifier().c_str());
78 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
79 ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str());
80 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortString().c_str());
81 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortStringLC().c_str());
82}
83
84TEST(TestLocale, LanguageModifierLocale)
85{
86 const std::string strLocale = LanguageCodeEnglish + ModifierSeparator + ModifierLatin;
87 std::string strLocaleLC = strLocale;
88 StringUtils::ToLower(strLocaleLC);
89
90 CLocale locale(LanguageCodeEnglish, "", "", ModifierLatin);
91 ASSERT_TRUE(locale.IsValid());
92 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str());
93 ASSERT_STREQ("", locale.GetTerritoryCode().c_str());
94 ASSERT_STREQ("", locale.GetCodeset().c_str());
95 ASSERT_STREQ(ModifierLatin.c_str(), locale.GetModifier().c_str());
96 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
97 ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str());
98 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortString().c_str());
99 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.ToShortStringLC().c_str());
100}
101
102TEST(TestLocale, LanguageTerritoryCodesetLocale)
103{
104 const std::string strLocaleShort = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain;
105 std::string strLocaleShortLC = strLocaleShort;
106 StringUtils::ToLower(strLocaleShortLC);
107 const std::string strLocale = strLocaleShort + CodesetSeparator + CodesetUtf8;
108 std::string strLocaleLC = strLocale;
109 StringUtils::ToLower(strLocaleLC);
110
111 CLocale locale(LanguageCodeEnglish, TerritoryCodeBritain, CodesetUtf8);
112 ASSERT_TRUE(locale.IsValid());
113 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str());
114 ASSERT_STREQ(TerritoryCodeBritain.c_str(), locale.GetTerritoryCode().c_str());
115 ASSERT_STREQ(CodesetUtf8.c_str(), locale.GetCodeset().c_str());
116 ASSERT_STREQ("", locale.GetModifier().c_str());
117 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
118 ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str());
119 ASSERT_STREQ(strLocaleShort.c_str(), locale.ToShortString().c_str());
120 ASSERT_STREQ(strLocaleShortLC.c_str(), locale.ToShortStringLC().c_str());
121}
122
123TEST(TestLocale, LanguageTerritoryModifierLocale)
124{
125 const std::string strLocaleShort = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain;
126 std::string strLocaleShortLC = strLocaleShort;
127 StringUtils::ToLower(strLocaleShortLC);
128 const std::string strLocale = strLocaleShort + ModifierSeparator + ModifierLatin;
129 std::string strLocaleLC = strLocale;
130 StringUtils::ToLower(strLocaleLC);
131
132 CLocale locale(LanguageCodeEnglish, TerritoryCodeBritain, "", ModifierLatin);
133 ASSERT_TRUE(locale.IsValid());
134 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str());
135 ASSERT_STREQ(TerritoryCodeBritain.c_str(), locale.GetTerritoryCode().c_str());
136 ASSERT_STREQ("", locale.GetCodeset().c_str());
137 ASSERT_STREQ(ModifierLatin.c_str(), locale.GetModifier().c_str());
138 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
139 ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str());
140 ASSERT_STREQ(strLocaleShort.c_str(), locale.ToShortString().c_str());
141 ASSERT_STREQ(strLocaleShortLC.c_str(), locale.ToShortStringLC().c_str());
142}
143
144TEST(TestLocale, LanguageTerritoryCodesetModifierLocale)
145{
146 const std::string strLocaleShort = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain;
147 std::string strLocaleShortLC = strLocaleShort;
148 StringUtils::ToLower(strLocaleShortLC);
149 const std::string strLocale = strLocaleShort + CodesetSeparator + CodesetUtf8 + ModifierSeparator + ModifierLatin;
150 std::string strLocaleLC = strLocale;
151 StringUtils::ToLower(strLocaleLC);
152
153 CLocale locale(LanguageCodeEnglish, TerritoryCodeBritain, CodesetUtf8, ModifierLatin);
154 ASSERT_TRUE(locale.IsValid());
155 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str());
156 ASSERT_STREQ(TerritoryCodeBritain.c_str(), locale.GetTerritoryCode().c_str());
157 ASSERT_STREQ(CodesetUtf8.c_str(), locale.GetCodeset().c_str());
158 ASSERT_STREQ(ModifierLatin.c_str(), locale.GetModifier().c_str());
159 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
160 ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str());
161 ASSERT_STREQ(strLocaleShort.c_str(), locale.ToShortString().c_str());
162 ASSERT_STREQ(strLocaleShortLC.c_str(), locale.ToShortStringLC().c_str());
163}
164
165TEST(TestLocale, FullStringLocale)
166{
167 const std::string strLocaleShort = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain;
168 std::string strLocaleShortLC = strLocaleShort;
169 StringUtils::ToLower(strLocaleShortLC);
170 const std::string strLocale = strLocaleShort + CodesetSeparator + CodesetUtf8 + ModifierSeparator + ModifierLatin;
171 std::string strLocaleLC = strLocale;
172 StringUtils::ToLower(strLocaleLC);
173
174 CLocale locale(strLocale);
175 ASSERT_TRUE(locale.IsValid());
176 ASSERT_STREQ(LanguageCodeEnglish.c_str(), locale.GetLanguageCode().c_str());
177 ASSERT_STREQ(TerritoryCodeBritain.c_str(), locale.GetTerritoryCode().c_str());
178 ASSERT_STREQ(CodesetUtf8.c_str(), locale.GetCodeset().c_str());
179 ASSERT_STREQ(ModifierLatin.c_str(), locale.GetModifier().c_str());
180 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
181 ASSERT_STREQ(strLocaleLC.c_str(), locale.ToStringLC().c_str());
182 ASSERT_STREQ(strLocaleShort.c_str(), locale.ToShortString().c_str());
183 ASSERT_STREQ(strLocaleShortLC.c_str(), locale.ToShortStringLC().c_str());
184}
185
186TEST(TestLocale, FromString)
187{
188 std::string strLocale = "";
189 CLocale locale = CLocale::FromString(strLocale);
190 ASSERT_FALSE(locale.IsValid());
191 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
192
193 strLocale = LanguageCodeEnglish;
194 locale = CLocale::FromString(strLocale);
195 ASSERT_TRUE(locale.IsValid());
196 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
197
198 strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain;
199 locale = CLocale::FromString(strLocale);
200 ASSERT_TRUE(locale.IsValid());
201 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
202
203 strLocale = LanguageCodeEnglish + CodesetSeparator + CodesetUtf8;
204 locale = CLocale::FromString(strLocale);
205 ASSERT_TRUE(locale.IsValid());
206 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
207
208 strLocale = LanguageCodeEnglish + ModifierSeparator + ModifierLatin;
209 locale = CLocale::FromString(strLocale);
210 ASSERT_TRUE(locale.IsValid());
211 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
212
213 strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + CodesetSeparator + CodesetUtf8;
214 locale = CLocale::FromString(strLocale);
215 ASSERT_TRUE(locale.IsValid());
216 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
217
218 strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + ModifierSeparator + ModifierLatin;
219 locale = CLocale::FromString(strLocale);
220 ASSERT_TRUE(locale.IsValid());
221 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
222
223 strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + CodesetSeparator + CodesetUtf8 + ModifierSeparator + ModifierLatin;
224 locale = CLocale::FromString(strLocale);
225 ASSERT_TRUE(locale.IsValid());
226 ASSERT_STREQ(strLocale.c_str(), locale.ToString().c_str());
227}
228
229TEST(TestLocale, EmptyLocale)
230{
231 ASSERT_FALSE(CLocale::Empty.IsValid());
232 ASSERT_STREQ("", CLocale::Empty.GetLanguageCode().c_str());
233 ASSERT_STREQ("", CLocale::Empty.GetTerritoryCode().c_str());
234 ASSERT_STREQ("", CLocale::Empty.GetCodeset().c_str());
235 ASSERT_STREQ("", CLocale::Empty.GetModifier().c_str());
236 ASSERT_STREQ("", CLocale::Empty.ToString().c_str());
237}
238
239TEST(TestLocale, Equals)
240{
241 std::string strLocale = "";
242 CLocale locale;
243 ASSERT_TRUE(locale.Equals(strLocale));
244
245 locale = CLocale(LanguageCodeEnglish);
246 strLocale = LanguageCodeEnglish;
247 ASSERT_TRUE(locale.Equals(strLocale));
248
249 locale = CLocale(LanguageCodeEnglish, TerritoryCodeBritain);
250 strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain;
251 ASSERT_TRUE(locale.Equals(strLocale));
252
253 locale = CLocale(LanguageCodeEnglish, "", CodesetUtf8);
254 strLocale = LanguageCodeEnglish + CodesetSeparator + CodesetUtf8;
255 ASSERT_TRUE(locale.Equals(strLocale));
256
257 locale = CLocale(LanguageCodeEnglish, "", "", ModifierLatin);
258 strLocale = LanguageCodeEnglish + ModifierSeparator + ModifierLatin;
259 ASSERT_TRUE(locale.Equals(strLocale));
260
261 locale = CLocale(LanguageCodeEnglish, TerritoryCodeBritain, CodesetUtf8);
262 strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + CodesetSeparator + CodesetUtf8;
263 ASSERT_TRUE(locale.Equals(strLocale));
264
265 locale = CLocale(LanguageCodeEnglish, TerritoryCodeBritain, "", ModifierLatin);
266 strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + ModifierSeparator + ModifierLatin;
267 ASSERT_TRUE(locale.Equals(strLocale));
268
269 locale = CLocale(LanguageCodeEnglish, TerritoryCodeBritain, CodesetUtf8, ModifierLatin);
270 strLocale = LanguageCodeEnglish + TerritorySeparator + TerritoryCodeBritain + CodesetSeparator + CodesetUtf8 + ModifierSeparator + ModifierLatin;
271 ASSERT_TRUE(locale.Equals(strLocale));
272}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/MathUtils.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestMathUtils, round_int)
14{
15 int refval, varval, i;
16
17 for (i = -8; i < 8; ++i)
18 {
19 double d = 0.25*i;
20 refval = (i < 0) ? (i - 1) / 4 : (i + 2) / 4;
21 varval = MathUtils::round_int(d);
22 EXPECT_EQ(refval, varval);
23 }
24}
25
26TEST(TestMathUtils, truncate_int)
27{
28 int refval, varval, i;
29
30 for (i = -8; i < 8; ++i)
31 {
32 double d = 0.25*i;
33 refval = i / 4;
34 varval = MathUtils::truncate_int(d);
35 EXPECT_EQ(refval, varval);
36 }
37}
38
39TEST(TestMathUtils, abs)
40{
41 int64_t refval, varval;
42
43 refval = 5;
44 varval = MathUtils::abs(-5);
45 EXPECT_EQ(refval, varval);
46}
47
48TEST(TestMathUtils, bitcount)
49{
50 unsigned refval, varval;
51
52 refval = 10;
53 varval = MathUtils::bitcount(0x03FF);
54 EXPECT_EQ(refval, varval);
55
56 refval = 8;
57 varval = MathUtils::bitcount(0x2AD5);
58 EXPECT_EQ(refval, varval);
59}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "FileItem.h"
10#include "utils/Mime.h"
11
12#include <gtest/gtest.h>
13
14TEST(TestMime, GetMimeType_string)
15{
16 EXPECT_STREQ("video/avi", CMime::GetMimeType("avi").c_str());
17 EXPECT_STRNE("video/x-msvideo", CMime::GetMimeType("avi").c_str());
18 EXPECT_STRNE("video/avi", CMime::GetMimeType("xvid").c_str());
19}
20
21TEST(TestMime, GetMimeType_CFileItem)
22{
23 std::string refstr, varstr;
24 CFileItem item("testfile.mp4", false);
25
26 refstr = "video/mp4";
27 varstr = CMime::GetMimeType(item);
28 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
29}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "test/TestUtils.h"
10#include "utils/POUtils.h"
11
12#include <gtest/gtest.h>
13
14
15TEST(TestPOUtils, General)
16{
17 CPODocument a;
18
19 EXPECT_TRUE(a.LoadFile(XBMC_REF_FILE_PATH("xbmc/utils/test/data/language/Spanish/strings.po")));
20
21 EXPECT_TRUE(a.GetNextEntry());
22 EXPECT_EQ(ID_FOUND, a.GetEntryType());
23 EXPECT_EQ((uint32_t)0, a.GetEntryID());
24 a.ParseEntry(false);
25 EXPECT_STREQ("", a.GetMsgctxt().c_str());
26 EXPECT_STREQ("Programs", a.GetMsgid().c_str());
27 EXPECT_STREQ("Programas", a.GetMsgstr().c_str());
28 EXPECT_STREQ("", a.GetPlurMsgstr(0).c_str());
29
30 EXPECT_TRUE(a.GetNextEntry());
31 EXPECT_EQ(ID_FOUND, a.GetEntryType());
32 EXPECT_EQ((uint32_t)1, a.GetEntryID());
33 a.ParseEntry(false);
34 EXPECT_STREQ("", a.GetMsgctxt().c_str());
35 EXPECT_STREQ("Pictures", a.GetMsgid().c_str());
36 EXPECT_STREQ("Imágenes", a.GetMsgstr().c_str());
37 EXPECT_STREQ("", a.GetPlurMsgstr(0).c_str());
38
39 EXPECT_TRUE(a.GetNextEntry());
40 EXPECT_EQ(ID_FOUND, a.GetEntryType());
41 EXPECT_EQ((uint32_t)2, a.GetEntryID());
42 a.ParseEntry(false);
43 EXPECT_STREQ("", a.GetMsgctxt().c_str());
44 EXPECT_STREQ("Music", a.GetMsgid().c_str());
45 EXPECT_STREQ("Música", a.GetMsgstr().c_str());
46 EXPECT_STREQ("", a.GetPlurMsgstr(0).c_str());
47}
diff --git a/xbmc/utils/test/TestRegExp.cpp b/xbmc/utils/test/TestRegExp.cpp
new file mode 100644
index 0000000..3a3df8f
--- /dev/null
+++ b/xbmc/utils/test/TestRegExp.cpp
@@ -0,0 +1,169 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9/** @todo gtest/gtest.h needs to come in before utils/RegExp.h.
10 * Investigate why.
11 */
12#include "CompileInfo.h"
13#include "ServiceBroker.h"
14#include "filesystem/File.h"
15#include "filesystem/SpecialProtocol.h"
16#include "utils/RegExp.h"
17#include "utils/StringUtils.h"
18#include "utils/log.h"
19
20#include <gtest/gtest.h>
21
22TEST(TestRegExp, RegFind)
23{
24 CRegExp regex;
25
26 EXPECT_TRUE(regex.RegComp("^Test.*"));
27 EXPECT_EQ(0, regex.RegFind("Test string."));
28
29 EXPECT_TRUE(regex.RegComp("^string.*"));
30 EXPECT_EQ(-1, regex.RegFind("Test string."));
31}
32
33TEST(TestRegExp, GetReplaceString)
34{
35 CRegExp regex;
36
37 EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\."));
38 EXPECT_EQ(0, regex.RegFind("Test string."));
39 EXPECT_STREQ("string", regex.GetReplaceString("\\2").c_str());
40}
41
42TEST(TestRegExp, GetFindLen)
43{
44 CRegExp regex;
45
46 EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\."));
47 EXPECT_EQ(0, regex.RegFind("Test string."));
48 EXPECT_EQ(12, regex.GetFindLen());
49}
50
51TEST(TestRegExp, GetSubCount)
52{
53 CRegExp regex;
54
55 EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\."));
56 EXPECT_EQ(0, regex.RegFind("Test string."));
57 EXPECT_EQ(2, regex.GetSubCount());
58}
59
60TEST(TestRegExp, GetSubStart)
61{
62 CRegExp regex;
63
64 EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\."));
65 EXPECT_EQ(0, regex.RegFind("Test string."));
66 EXPECT_EQ(0, regex.GetSubStart(0));
67 EXPECT_EQ(0, regex.GetSubStart(1));
68 EXPECT_EQ(5, regex.GetSubStart(2));
69}
70
71TEST(TestRegExp, GetCaptureTotal)
72{
73 CRegExp regex;
74
75 EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\."));
76 EXPECT_EQ(0, regex.RegFind("Test string."));
77 EXPECT_EQ(2, regex.GetCaptureTotal());
78}
79
80TEST(TestRegExp, GetMatch)
81{
82 CRegExp regex;
83
84 EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\."));
85 EXPECT_EQ(0, regex.RegFind("Test string."));
86 EXPECT_STREQ("Test string.", regex.GetMatch(0).c_str());
87 EXPECT_STREQ("Test", regex.GetMatch(1).c_str());
88 EXPECT_STREQ("string", regex.GetMatch(2).c_str());
89}
90
91TEST(TestRegExp, GetPattern)
92{
93 CRegExp regex;
94
95 EXPECT_TRUE(regex.RegComp("^(Test)\\s*(.*)\\."));
96 EXPECT_STREQ("^(Test)\\s*(.*)\\.", regex.GetPattern().c_str());
97}
98
99TEST(TestRegExp, GetNamedSubPattern)
100{
101 CRegExp regex;
102 std::string match;
103
104 EXPECT_TRUE(regex.RegComp("^(?<first>Test)\\s*(?<second>.*)\\."));
105 EXPECT_EQ(0, regex.RegFind("Test string."));
106 EXPECT_TRUE(regex.GetNamedSubPattern("first", match));
107 EXPECT_STREQ("Test", match.c_str());
108 EXPECT_TRUE(regex.GetNamedSubPattern("second", match));
109 EXPECT_STREQ("string", match.c_str());
110}
111
112TEST(TestRegExp, operatorEqual)
113{
114 CRegExp regex, regexcopy;
115 std::string match;
116
117 EXPECT_TRUE(regex.RegComp("^(?<first>Test)\\s*(?<second>.*)\\."));
118 regexcopy = regex;
119 EXPECT_EQ(0, regexcopy.RegFind("Test string."));
120 EXPECT_TRUE(regexcopy.GetNamedSubPattern("first", match));
121 EXPECT_STREQ("Test", match.c_str());
122 EXPECT_TRUE(regexcopy.GetNamedSubPattern("second", match));
123 EXPECT_STREQ("string", match.c_str());
124}
125
126class TestRegExpLog : public testing::Test
127{
128protected:
129 TestRegExpLog() = default;
130 ~TestRegExpLog() override { CServiceBroker::GetLogging().Uninitialize(); }
131};
132
133TEST_F(TestRegExpLog, DumpOvector)
134{
135 CRegExp regex;
136 std::string logfile, logstring;
137 char buf[100];
138 ssize_t bytesread;
139 XFILE::CFile file;
140
141 std::string appName = CCompileInfo::GetAppName();
142 StringUtils::ToLower(appName);
143 logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log";
144 CServiceBroker::GetLogging().Initialize(
145 CSpecialProtocol::TranslatePath("special://temp/").c_str());
146 EXPECT_TRUE(XFILE::CFile::Exists(logfile));
147
148 EXPECT_TRUE(regex.RegComp("^(?<first>Test)\\s*(?<second>.*)\\."));
149 EXPECT_EQ(0, regex.RegFind("Test string."));
150 regex.DumpOvector(LOGDEBUG);
151 CServiceBroker::GetLogging().Uninitialize();
152
153 EXPECT_TRUE(file.Open(logfile));
154 while ((bytesread = file.Read(buf, sizeof(buf) - 1)) > 0)
155 {
156 buf[bytesread] = '\0';
157 logstring.append(buf);
158 }
159 file.Close();
160 EXPECT_FALSE(logstring.empty());
161
162 EXPECT_STREQ("\xEF\xBB\xBF", logstring.substr(0, 3).c_str());
163
164 EXPECT_TRUE(regex.RegComp(".*DEBUG <general>: regexp ovector=\\{\\[0,12\\],\\[0,4\\],"
165 "\\[5,11\\]\\}.*"));
166 EXPECT_GE(regex.RegFind(logstring), 0);
167
168 EXPECT_TRUE(XFILE::CFile::Delete(logfile));
169}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/RingBuffer.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestRingBuffer, General)
14{
15 CRingBuffer a;
16 char data[20];
17 unsigned int i;
18
19 EXPECT_TRUE(a.Create(20));
20 EXPECT_EQ((unsigned int)20, a.getSize());
21 memset(data, 0, sizeof(data));
22 for (i = 0; i < a.getSize(); i++)
23 EXPECT_TRUE(a.WriteData(data, 1));
24 a.Clear();
25
26 memcpy(data, "0123456789", sizeof("0123456789"));
27 EXPECT_TRUE(a.WriteData(data, sizeof("0123456789")));
28 EXPECT_STREQ("0123456789", a.getBuffer());
29
30 memset(data, 0, sizeof(data));
31 EXPECT_TRUE(a.ReadData(data, 5));
32 EXPECT_STREQ("01234", data);
33}
diff --git a/xbmc/utils/test/TestScraperParser.cpp b/xbmc/utils/test/TestScraperParser.cpp
new file mode 100644
index 0000000..50744b4
--- /dev/null
+++ b/xbmc/utils/test/TestScraperParser.cpp
@@ -0,0 +1,26 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "test/TestUtils.h"
10#include "utils/ScraperParser.h"
11
12#include <gtest/gtest.h>
13
14TEST(TestScraperParser, General)
15{
16 CScraperParser a;
17
18 a.Clear();
19 EXPECT_TRUE(
20 a.Load(XBMC_REF_FILE_PATH("/addons/metadata.themoviedb.org/tmdb.xml")));
21
22 EXPECT_STREQ(
23 XBMC_REF_FILE_PATH("/addons/metadata.themoviedb.org/tmdb.xml").c_str(),
24 a.GetFilename().c_str());
25 EXPECT_STREQ("UTF-8", a.GetSearchStringEncoding().c_str());
26}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/ScraperUrl.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestScraperUrl, General)
14{
15 CScraperUrl a;
16 std::string xmlstring;
17
18 xmlstring = "<data spoof=\"blah\" gzip=\"yes\">\n"
19 " <someurl>\n"
20 " </someurl>\n"
21 " <someotherurl>\n"
22 " </someotherurl>\n"
23 "</data>\n";
24 EXPECT_TRUE(a.ParseFromData(xmlstring));
25
26 const auto url = a.GetFirstUrlByType();
27 EXPECT_STREQ("blah", url.m_spoof.c_str());
28 EXPECT_STREQ("someurl", url.m_url.c_str());
29 EXPECT_STREQ("", url.m_cache.c_str());
30 EXPECT_EQ(CScraperUrl::UrlType::General, url.m_type);
31 EXPECT_FALSE(url.m_post);
32 EXPECT_TRUE(url.m_isgz);
33 EXPECT_EQ(-1, url.m_season);
34}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/SortUtils.h"
10#include "utils/Variant.h"
11
12#include <gtest/gtest.h>
13
14TEST(TestSortUtils, Sort_SortBy)
15{
16 SortItems items;
17
18 CVariant variant1("M Artist");
19 SortItemPtr item1(new SortItem());
20 (*item1)[FieldArtist] = variant1;
21 CVariant variant2("B Artist");
22 SortItemPtr item2(new SortItem());
23 (*item2)[FieldArtist] = variant2;
24 CVariant variant3("R Artist");
25 SortItemPtr item3(new SortItem());
26 (*item3)[FieldArtist] = variant3;
27 CVariant variant4("R Artist");
28 SortItemPtr item4(new SortItem());
29 (*item4)[FieldArtist] = variant4;
30 CVariant variant5("I Artist");
31 SortItemPtr item5(new SortItem());
32 (*item5)[FieldArtist] = variant5;
33 CVariant variant6("A Artist");
34 SortItemPtr item6(new SortItem());
35 (*item6)[FieldArtist] = variant6;
36 CVariant variant7("G Artist");
37 SortItemPtr item7(new SortItem());
38 (*item7)[FieldArtist] = variant7;
39
40 items.push_back(item1);
41 items.push_back(item2);
42 items.push_back(item3);
43 items.push_back(item4);
44 items.push_back(item5);
45 items.push_back(item6);
46 items.push_back(item7);
47
48 SortUtils::Sort(SortByArtist, SortOrderAscending, SortAttributeNone, items);
49
50 EXPECT_STREQ("A Artist", (*items.at(0))[FieldArtist].asString().c_str());
51 EXPECT_STREQ("B Artist", (*items.at(1))[FieldArtist].asString().c_str());
52 EXPECT_STREQ("G Artist", (*items.at(2))[FieldArtist].asString().c_str());
53 EXPECT_STREQ("I Artist", (*items.at(3))[FieldArtist].asString().c_str());
54 EXPECT_STREQ("M Artist", (*items.at(4))[FieldArtist].asString().c_str());
55 EXPECT_STREQ("R Artist", (*items.at(5))[FieldArtist].asString().c_str());
56 EXPECT_STREQ("R Artist", (*items.at(6))[FieldArtist].asString().c_str());
57}
58
59TEST(TestSortUtils, Sort_SortDescription)
60{
61 SortItems items;
62
63 CVariant variant1("M Artist");
64 SortItemPtr item1(new SortItem());
65 (*item1)[FieldArtist] = variant1;
66 CVariant variant2("B Artist");
67 SortItemPtr item2(new SortItem());
68 (*item2)[FieldArtist] = variant2;
69 CVariant variant3("R Artist");
70 SortItemPtr item3(new SortItem());
71 (*item3)[FieldArtist] = variant3;
72 CVariant variant4("R Artist");
73 SortItemPtr item4(new SortItem());
74 (*item4)[FieldArtist] = variant4;
75 CVariant variant5("I Artist");
76 SortItemPtr item5(new SortItem());
77 (*item5)[FieldArtist] = variant5;
78 CVariant variant6("A Artist");
79 SortItemPtr item6(new SortItem());
80 (*item6)[FieldArtist] = variant6;
81 CVariant variant7("G Artist");
82 SortItemPtr item7(new SortItem());
83 (*item7)[FieldArtist] = variant7;
84
85 items.push_back(item1);
86 items.push_back(item2);
87 items.push_back(item3);
88 items.push_back(item4);
89 items.push_back(item5);
90 items.push_back(item6);
91 items.push_back(item7);
92
93 SortDescription desc;
94 desc.sortBy = SortByArtist;
95 SortUtils::Sort(desc, items);
96
97 EXPECT_STREQ("A Artist", (*items.at(0))[FieldArtist].asString().c_str());
98 EXPECT_STREQ("B Artist", (*items.at(1))[FieldArtist].asString().c_str());
99 EXPECT_STREQ("G Artist", (*items.at(2))[FieldArtist].asString().c_str());
100 EXPECT_STREQ("I Artist", (*items.at(3))[FieldArtist].asString().c_str());
101 EXPECT_STREQ("M Artist", (*items.at(4))[FieldArtist].asString().c_str());
102 EXPECT_STREQ("R Artist", (*items.at(5))[FieldArtist].asString().c_str());
103 EXPECT_STREQ("R Artist", (*items.at(6))[FieldArtist].asString().c_str());
104}
105
106TEST(TestSortUtils, GetFieldsForSorting)
107{
108 Fields fields;
109
110 fields = SortUtils::GetFieldsForSorting(SortByArtist);
111 Fields::iterator it;
112 it = fields.find(FieldAlbum);
113 EXPECT_EQ(FieldAlbum, *it);
114 it = fields.find(FieldArtist);
115 EXPECT_EQ(FieldArtist, *it);
116 it = fields.find(FieldArtistSort);
117 EXPECT_EQ(FieldArtistSort, *it);
118 it = fields.find(FieldYear);
119 EXPECT_EQ(FieldYear, *it);
120 it = fields.find(FieldTrackNumber);
121 EXPECT_EQ(FieldTrackNumber, *it);
122 EXPECT_EQ((unsigned int)5, fields.size());
123}
diff --git a/xbmc/utils/test/TestStopwatch.cpp b/xbmc/utils/test/TestStopwatch.cpp
new file mode 100644
index 0000000..966afc5
--- /dev/null
+++ b/xbmc/utils/test/TestStopwatch.cpp
@@ -0,0 +1,64 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "threads/Thread.h"
10#include "utils/Stopwatch.h"
11
12#include <gtest/gtest.h>
13
14class CTestStopWatchThread : public CThread
15{
16public:
17 CTestStopWatchThread() :
18 CThread("TestStopWatch"){}
19};
20
21TEST(TestStopWatch, Initialization)
22{
23 CStopWatch a;
24 EXPECT_FALSE(a.IsRunning());
25 EXPECT_EQ(0.0f, a.GetElapsedSeconds());
26 EXPECT_EQ(0.0f, a.GetElapsedMilliseconds());
27}
28
29TEST(TestStopWatch, Start)
30{
31 CStopWatch a;
32 a.Start();
33 EXPECT_TRUE(a.IsRunning());
34}
35
36TEST(TestStopWatch, Stop)
37{
38 CStopWatch a;
39 a.Start();
40 a.Stop();
41 EXPECT_FALSE(a.IsRunning());
42}
43
44TEST(TestStopWatch, ElapsedTime)
45{
46 CStopWatch a;
47 CTestStopWatchThread thread;
48 a.Start();
49 thread.Sleep(1);
50 EXPECT_GT(a.GetElapsedSeconds(), 0.0f);
51 EXPECT_GT(a.GetElapsedMilliseconds(), 0.0f);
52}
53
54TEST(TestStopWatch, Reset)
55{
56 CStopWatch a;
57 CTestStopWatchThread thread;
58 a.StartZero();
59 thread.Sleep(2);
60 EXPECT_GT(a.GetElapsedMilliseconds(), 1);
61 thread.Sleep(3);
62 a.Reset();
63 EXPECT_LT(a.GetElapsedMilliseconds(), 5);
64}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/StreamDetails.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestStreamDetails, General)
14{
15 CStreamDetails a;
16 CStreamDetailVideo *video = new CStreamDetailVideo();
17 CStreamDetailAudio *audio = new CStreamDetailAudio();
18 CStreamDetailSubtitle *subtitle = new CStreamDetailSubtitle();
19
20 video->m_iWidth = 1920;
21 video->m_iHeight = 1080;
22 video->m_fAspect = 2.39f;
23 video->m_iDuration = 30;
24 video->m_strCodec = "h264";
25 video->m_strStereoMode = "left_right";
26 video->m_strLanguage = "eng";
27
28 audio->m_iChannels = 2;
29 audio->m_strCodec = "aac";
30 audio->m_strLanguage = "eng";
31
32 subtitle->m_strLanguage = "eng";
33
34 a.AddStream(video);
35 a.AddStream(audio);
36
37 EXPECT_TRUE(a.HasItems());
38
39 EXPECT_EQ(1, a.GetStreamCount(CStreamDetail::VIDEO));
40 EXPECT_EQ(1, a.GetVideoStreamCount());
41 EXPECT_STREQ("", a.GetVideoCodec().c_str());
42 EXPECT_EQ(0.0f, a.GetVideoAspect());
43 EXPECT_EQ(0, a.GetVideoWidth());
44 EXPECT_EQ(0, a.GetVideoHeight());
45 EXPECT_EQ(0, a.GetVideoDuration());
46 EXPECT_STREQ("", a.GetStereoMode().c_str());
47
48 EXPECT_EQ(1, a.GetStreamCount(CStreamDetail::AUDIO));
49 EXPECT_EQ(1, a.GetAudioStreamCount());
50
51 EXPECT_EQ(0, a.GetStreamCount(CStreamDetail::SUBTITLE));
52 EXPECT_EQ(0, a.GetSubtitleStreamCount());
53
54 a.AddStream(subtitle);
55 EXPECT_EQ(1, a.GetStreamCount(CStreamDetail::SUBTITLE));
56 EXPECT_EQ(1, a.GetSubtitleStreamCount());
57
58 a.DetermineBestStreams();
59 EXPECT_STREQ("h264", a.GetVideoCodec().c_str());
60 EXPECT_EQ(2.39f, a.GetVideoAspect());
61 EXPECT_EQ(1920, a.GetVideoWidth());
62 EXPECT_EQ(1080, a.GetVideoHeight());
63 EXPECT_EQ(30, a.GetVideoDuration());
64 EXPECT_STREQ("left_right", a.GetStereoMode().c_str());
65}
66
67TEST(TestStreamDetails, VideoDimsToResolutionDescription)
68{
69 EXPECT_STREQ("1080",
70 CStreamDetails::VideoDimsToResolutionDescription(1920, 1080).c_str());
71}
72
73TEST(TestStreamDetails, VideoAspectToAspectDescription)
74{
75 EXPECT_STREQ("2.40", CStreamDetails::VideoAspectToAspectDescription(2.39f).c_str());
76}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/StreamUtils.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestStreamUtils, General)
14{
15 EXPECT_EQ(0, StreamUtils::GetCodecPriority(""));
16 EXPECT_EQ(1, StreamUtils::GetCodecPriority("ac3"));
17 EXPECT_EQ(2, StreamUtils::GetCodecPriority("dca"));
18 EXPECT_EQ(3, StreamUtils::GetCodecPriority("eac3"));
19 EXPECT_EQ(4, StreamUtils::GetCodecPriority("dtshd_hra"));
20 EXPECT_EQ(5, StreamUtils::GetCodecPriority("dtshd_ma"));
21 EXPECT_EQ(6, StreamUtils::GetCodecPriority("truehd"));
22 EXPECT_EQ(7, StreamUtils::GetCodecPriority("flac"));
23}
diff --git a/xbmc/utils/test/TestStringUtils.cpp b/xbmc/utils/test/TestStringUtils.cpp
new file mode 100644
index 0000000..0a063cc
--- /dev/null
+++ b/xbmc/utils/test/TestStringUtils.cpp
@@ -0,0 +1,605 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/StringUtils.h"
10
11#include <algorithm>
12
13#include <gtest/gtest.h>
14enum class ECG
15{
16 A,
17 B
18};
19
20enum EG
21{
22 C,
23 D
24};
25
26namespace test_enum
27{
28enum class ECN
29{
30 A = 1,
31 B
32};
33enum EN
34{
35 C = 1,
36 D
37};
38}
39TEST(TestStringUtils, Format)
40{
41 std::string refstr = "test 25 2.7 ff FF";
42
43 std::string varstr =
44 StringUtils::Format("%s %d %.1f %x %02X", "test", 25, 2.743f, 0x00ff, 0x00ff);
45 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
46
47 varstr = StringUtils::Format("", "test", 25, 2.743f, 0x00ff, 0x00ff);
48 EXPECT_STREQ("", varstr.c_str());
49}
50
51TEST(TestStringUtils, FormatEnum)
52{
53 const char* zero = "0";
54 const char* one = "1";
55
56 std::string varstr = StringUtils::Format("{}", ECG::A);
57 EXPECT_STREQ(zero, varstr.c_str());
58
59 varstr = StringUtils::Format("{}", EG::C);
60 EXPECT_STREQ(zero, varstr.c_str());
61
62 varstr = StringUtils::Format("{}", test_enum::ECN::A);
63 EXPECT_STREQ(one, varstr.c_str());
64
65 varstr = StringUtils::Format("{}", test_enum::EN::C);
66 EXPECT_STREQ(one, varstr.c_str());
67}
68
69TEST(TestStringUtils, FormatEnumWidth)
70{
71 const char* one = "01";
72
73 std::string varstr = StringUtils::Format("{:02d}", ECG::B);
74 EXPECT_STREQ(one, varstr.c_str());
75
76 varstr = StringUtils::Format("%02d", EG::D);
77 EXPECT_STREQ(one, varstr.c_str());
78}
79
80TEST(TestStringUtils, ToUpper)
81{
82 std::string refstr = "TEST";
83
84 std::string varstr = "TeSt";
85 StringUtils::ToUpper(varstr);
86 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
87}
88
89TEST(TestStringUtils, ToLower)
90{
91 std::string refstr = "test";
92
93 std::string varstr = "TeSt";
94 StringUtils::ToLower(varstr);
95 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
96}
97
98TEST(TestStringUtils, ToCapitalize)
99{
100 std::string refstr = "Test";
101 std::string varstr = "test";
102 StringUtils::ToCapitalize(varstr);
103 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
104
105 refstr = "Just A Test";
106 varstr = "just a test";
107 StringUtils::ToCapitalize(varstr);
108 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
109
110 refstr = "Test -1;2:3, String For Case";
111 varstr = "test -1;2:3, string for Case";
112 StringUtils::ToCapitalize(varstr);
113 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
114
115 refstr = " JuST Another\t\tTEst:\nWoRKs ";
116 varstr = " juST another\t\ttEst:\nwoRKs ";
117 StringUtils::ToCapitalize(varstr);
118 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
119
120 refstr = "N.Y.P.D";
121 varstr = "n.y.p.d";
122 StringUtils::ToCapitalize(varstr);
123 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
124
125 refstr = "N-Y-P-D";
126 varstr = "n-y-p-d";
127 StringUtils::ToCapitalize(varstr);
128 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
129}
130
131TEST(TestStringUtils, EqualsNoCase)
132{
133 std::string refstr = "TeSt";
134
135 EXPECT_TRUE(StringUtils::EqualsNoCase(refstr, "TeSt"));
136 EXPECT_TRUE(StringUtils::EqualsNoCase(refstr, "tEsT"));
137}
138
139TEST(TestStringUtils, Left)
140{
141 std::string refstr, varstr;
142 std::string origstr = "test";
143
144 refstr = "";
145 varstr = StringUtils::Left(origstr, 0);
146 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
147
148 refstr = "te";
149 varstr = StringUtils::Left(origstr, 2);
150 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
151
152 refstr = "test";
153 varstr = StringUtils::Left(origstr, 10);
154 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
155}
156
157TEST(TestStringUtils, Mid)
158{
159 std::string refstr, varstr;
160 std::string origstr = "test";
161
162 refstr = "";
163 varstr = StringUtils::Mid(origstr, 0, 0);
164 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
165
166 refstr = "te";
167 varstr = StringUtils::Mid(origstr, 0, 2);
168 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
169
170 refstr = "test";
171 varstr = StringUtils::Mid(origstr, 0, 10);
172 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
173
174 refstr = "st";
175 varstr = StringUtils::Mid(origstr, 2);
176 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
177
178 refstr = "st";
179 varstr = StringUtils::Mid(origstr, 2, 2);
180 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
181
182 refstr = "es";
183 varstr = StringUtils::Mid(origstr, 1, 2);
184 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
185}
186
187TEST(TestStringUtils, Right)
188{
189 std::string refstr, varstr;
190 std::string origstr = "test";
191
192 refstr = "";
193 varstr = StringUtils::Right(origstr, 0);
194 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
195
196 refstr = "st";
197 varstr = StringUtils::Right(origstr, 2);
198 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
199
200 refstr = "test";
201 varstr = StringUtils::Right(origstr, 10);
202 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
203}
204
205TEST(TestStringUtils, Trim)
206{
207 std::string refstr = "test test";
208
209 std::string varstr = " test test ";
210 StringUtils::Trim(varstr);
211 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
212}
213
214TEST(TestStringUtils, TrimLeft)
215{
216 std::string refstr = "test test ";
217
218 std::string varstr = " test test ";
219 StringUtils::TrimLeft(varstr);
220 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
221}
222
223TEST(TestStringUtils, TrimRight)
224{
225 std::string refstr = " test test";
226
227 std::string varstr = " test test ";
228 StringUtils::TrimRight(varstr);
229 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
230}
231
232TEST(TestStringUtils, Replace)
233{
234 std::string refstr = "text text";
235
236 std::string varstr = "test test";
237 EXPECT_EQ(StringUtils::Replace(varstr, 's', 'x'), 2);
238 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
239
240 EXPECT_EQ(StringUtils::Replace(varstr, 's', 'x'), 0);
241 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
242
243 varstr = "test test";
244 EXPECT_EQ(StringUtils::Replace(varstr, "s", "x"), 2);
245 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
246
247 EXPECT_EQ(StringUtils::Replace(varstr, "s", "x"), 0);
248 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
249}
250
251TEST(TestStringUtils, StartsWith)
252{
253 std::string refstr = "test";
254
255 EXPECT_FALSE(StringUtils::StartsWithNoCase(refstr, "x"));
256
257 EXPECT_TRUE(StringUtils::StartsWith(refstr, "te"));
258 EXPECT_TRUE(StringUtils::StartsWith(refstr, "test"));
259 EXPECT_FALSE(StringUtils::StartsWith(refstr, "Te"));
260
261 EXPECT_TRUE(StringUtils::StartsWithNoCase(refstr, "Te"));
262 EXPECT_TRUE(StringUtils::StartsWithNoCase(refstr, "TesT"));
263}
264
265TEST(TestStringUtils, EndsWith)
266{
267 std::string refstr = "test";
268
269 EXPECT_FALSE(StringUtils::EndsWithNoCase(refstr, "x"));
270
271 EXPECT_TRUE(StringUtils::EndsWith(refstr, "st"));
272 EXPECT_TRUE(StringUtils::EndsWith(refstr, "test"));
273 EXPECT_FALSE(StringUtils::EndsWith(refstr, "sT"));
274
275 EXPECT_TRUE(StringUtils::EndsWithNoCase(refstr, "sT"));
276 EXPECT_TRUE(StringUtils::EndsWithNoCase(refstr, "TesT"));
277}
278
279TEST(TestStringUtils, Join)
280{
281 std::string refstr, varstr;
282 std::vector<std::string> strarray;
283
284 strarray.emplace_back("a");
285 strarray.emplace_back("b");
286 strarray.emplace_back("c");
287 strarray.emplace_back("de");
288 strarray.emplace_back(",");
289 strarray.emplace_back("fg");
290 strarray.emplace_back(",");
291 refstr = "a,b,c,de,,,fg,,";
292 varstr = StringUtils::Join(strarray, ",");
293 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
294}
295
296TEST(TestStringUtils, Split)
297{
298 std::vector<std::string> varresults;
299
300 // test overload with string as delimiter
301 varresults = StringUtils::Split("g,h,ij,k,lm,,n", ",");
302 EXPECT_STREQ("g", varresults.at(0).c_str());
303 EXPECT_STREQ("h", varresults.at(1).c_str());
304 EXPECT_STREQ("ij", varresults.at(2).c_str());
305 EXPECT_STREQ("k", varresults.at(3).c_str());
306 EXPECT_STREQ("lm", varresults.at(4).c_str());
307 EXPECT_STREQ("", varresults.at(5).c_str());
308 EXPECT_STREQ("n", varresults.at(6).c_str());
309
310 EXPECT_TRUE(StringUtils::Split("", "|").empty());
311
312 EXPECT_EQ(4U, StringUtils::Split("a bc d ef ghi ", " ", 4).size());
313 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";
314 EXPECT_EQ(7U, StringUtils::Split("a bc d ef ghi ", " ").size()) << "Result must be 7 strings including two empty strings";
315 EXPECT_STREQ("bc", StringUtils::Split("a bc d ef ghi ", " ").at(1).c_str());
316 EXPECT_STREQ("", StringUtils::Split("a bc d ef ghi ", " ").at(2).c_str());
317 EXPECT_STREQ("", StringUtils::Split("a bc d ef ghi ", " ").at(6).c_str());
318
319 EXPECT_EQ(2U, StringUtils::Split("a bc d ef ghi ", " ").size());
320 EXPECT_EQ(2U, StringUtils::Split("a bc d ef ghi ", " ", 10).size());
321 EXPECT_STREQ("a bc", StringUtils::Split("a bc d ef ghi ", " ", 10).at(0).c_str());
322
323 EXPECT_EQ(1U, StringUtils::Split("a bc d ef ghi ", " z").size());
324 EXPECT_STREQ("a bc d ef ghi ", StringUtils::Split("a bc d ef ghi ", " z").at(0).c_str());
325
326 EXPECT_EQ(1U, StringUtils::Split("a bc d ef ghi ", "").size());
327 EXPECT_STREQ("a bc d ef ghi ", StringUtils::Split("a bc d ef ghi ", "").at(0).c_str());
328
329 // test overload with char as delimiter
330 EXPECT_EQ(4U, StringUtils::Split("a bc d ef ghi ", ' ', 4).size());
331 EXPECT_STREQ("d ef ghi ", StringUtils::Split("a bc d ef ghi ", ' ', 4).at(3).c_str());
332 EXPECT_EQ(7U, StringUtils::Split("a bc d ef ghi ", ' ').size()) << "Result must be 7 strings including two empty strings";
333 EXPECT_STREQ("bc", StringUtils::Split("a bc d ef ghi ", ' ').at(1).c_str());
334 EXPECT_STREQ("", StringUtils::Split("a bc d ef ghi ", ' ').at(2).c_str());
335 EXPECT_STREQ("", StringUtils::Split("a bc d ef ghi ", ' ').at(6).c_str());
336
337 EXPECT_EQ(1U, StringUtils::Split("a bc d ef ghi ", 'z').size());
338 EXPECT_STREQ("a bc d ef ghi ", StringUtils::Split("a bc d ef ghi ", 'z').at(0).c_str());
339
340 EXPECT_EQ(1U, StringUtils::Split("a bc d ef ghi ", "").size());
341 EXPECT_STREQ("a bc d ef ghi ", StringUtils::Split("a bc d ef ghi ", 'z').at(0).c_str());
342}
343
344TEST(TestStringUtils, FindNumber)
345{
346 EXPECT_EQ(3, StringUtils::FindNumber("aabcaadeaa", "aa"));
347 EXPECT_EQ(1, StringUtils::FindNumber("aabcaadeaa", "b"));
348}
349
350TEST(TestStringUtils, AlphaNumericCompare)
351{
352 int64_t ref, var;
353
354 ref = 0;
355 var = StringUtils::AlphaNumericCompare(L"123abc", L"abc123");
356 EXPECT_LT(var, ref);
357}
358
359TEST(TestStringUtils, TimeStringToSeconds)
360{
361 EXPECT_EQ(77455, StringUtils::TimeStringToSeconds("21:30:55"));
362 EXPECT_EQ(7*60, StringUtils::TimeStringToSeconds("7 min"));
363 EXPECT_EQ(7*60, StringUtils::TimeStringToSeconds("7 min\t"));
364 EXPECT_EQ(154*60, StringUtils::TimeStringToSeconds(" 154 min"));
365 EXPECT_EQ(1*60+1, StringUtils::TimeStringToSeconds("1:01"));
366 EXPECT_EQ(4*60+3, StringUtils::TimeStringToSeconds("4:03"));
367 EXPECT_EQ(2*3600+4*60+3, StringUtils::TimeStringToSeconds("2:04:03"));
368 EXPECT_EQ(2*3600+4*60+3, StringUtils::TimeStringToSeconds(" 2:4:3"));
369 EXPECT_EQ(2*3600+4*60+3, StringUtils::TimeStringToSeconds(" \t\t 02:04:03 \n "));
370 EXPECT_EQ(1*3600+5*60+2, StringUtils::TimeStringToSeconds("01:05:02:04:03 \n "));
371 EXPECT_EQ(0, StringUtils::TimeStringToSeconds("blah"));
372 EXPECT_EQ(0, StringUtils::TimeStringToSeconds("ля-ля"));
373}
374
375TEST(TestStringUtils, RemoveCRLF)
376{
377 std::string refstr, varstr;
378
379 refstr = "test\r\nstring\nblah blah";
380 varstr = "test\r\nstring\nblah blah\n";
381 StringUtils::RemoveCRLF(varstr);
382 EXPECT_STREQ(refstr.c_str(), varstr.c_str());
383}
384
385TEST(TestStringUtils, utf8_strlen)
386{
387 size_t ref, var;
388
389 ref = 9;
390 var = StringUtils::utf8_strlen("test_UTF8");
391 EXPECT_EQ(ref, var);
392}
393
394TEST(TestStringUtils, SecondsToTimeString)
395{
396 std::string ref, var;
397
398 ref = "21:30:55";
399 var = StringUtils::SecondsToTimeString(77455);
400 EXPECT_STREQ(ref.c_str(), var.c_str());
401}
402
403TEST(TestStringUtils, IsNaturalNumber)
404{
405 EXPECT_TRUE(StringUtils::IsNaturalNumber("10"));
406 EXPECT_TRUE(StringUtils::IsNaturalNumber(" 10"));
407 EXPECT_TRUE(StringUtils::IsNaturalNumber("0"));
408 EXPECT_FALSE(StringUtils::IsNaturalNumber(" 1 0"));
409 EXPECT_FALSE(StringUtils::IsNaturalNumber("1.0"));
410 EXPECT_FALSE(StringUtils::IsNaturalNumber("1.1"));
411 EXPECT_FALSE(StringUtils::IsNaturalNumber("0x1"));
412 EXPECT_FALSE(StringUtils::IsNaturalNumber("blah"));
413 EXPECT_FALSE(StringUtils::IsNaturalNumber("120 h"));
414 EXPECT_FALSE(StringUtils::IsNaturalNumber(" "));
415 EXPECT_FALSE(StringUtils::IsNaturalNumber(""));
416}
417
418TEST(TestStringUtils, IsInteger)
419{
420 EXPECT_TRUE(StringUtils::IsInteger("10"));
421 EXPECT_TRUE(StringUtils::IsInteger(" -10"));
422 EXPECT_TRUE(StringUtils::IsInteger("0"));
423 EXPECT_FALSE(StringUtils::IsInteger(" 1 0"));
424 EXPECT_FALSE(StringUtils::IsInteger("1.0"));
425 EXPECT_FALSE(StringUtils::IsInteger("1.1"));
426 EXPECT_FALSE(StringUtils::IsInteger("0x1"));
427 EXPECT_FALSE(StringUtils::IsInteger("blah"));
428 EXPECT_FALSE(StringUtils::IsInteger("120 h"));
429 EXPECT_FALSE(StringUtils::IsInteger(" "));
430 EXPECT_FALSE(StringUtils::IsInteger(""));
431}
432
433TEST(TestStringUtils, SizeToString)
434{
435 std::string ref, var;
436
437 ref = "2.00 GB";
438 var = StringUtils::SizeToString(2147483647);
439 EXPECT_STREQ(ref.c_str(), var.c_str());
440}
441
442TEST(TestStringUtils, EmptyString)
443{
444 EXPECT_STREQ("", StringUtils::Empty.c_str());
445}
446
447TEST(TestStringUtils, FindWords)
448{
449 size_t ref, var;
450
451 ref = 5;
452 var = StringUtils::FindWords("test string", "string");
453 EXPECT_EQ(ref, var);
454 var = StringUtils::FindWords("12345string", "string");
455 EXPECT_EQ(ref, var);
456 var = StringUtils::FindWords("apple2012", "2012");
457 EXPECT_EQ(ref, var);
458 ref = -1;
459 var = StringUtils::FindWords("12345string", "ring");
460 EXPECT_EQ(ref, var);
461 var = StringUtils::FindWords("12345string", "345");
462 EXPECT_EQ(ref, var);
463 var = StringUtils::FindWords("apple2012", "e2012");
464 EXPECT_EQ(ref, var);
465 var = StringUtils::FindWords("apple2012", "12");
466 EXPECT_EQ(ref, var);
467}
468
469TEST(TestStringUtils, FindWords_NonAscii)
470{
471 size_t ref, var;
472
473 ref = 6;
474 var = StringUtils::FindWords("我的视频", "视频");
475 EXPECT_EQ(ref, var);
476 var = StringUtils::FindWords("我的视频", "视");
477 EXPECT_EQ(ref, var);
478 var = StringUtils::FindWords("Apple ple", "ple");
479 EXPECT_EQ(ref, var);
480 ref = 7;
481 var = StringUtils::FindWords("Äpfel.pfel", "pfel");
482 EXPECT_EQ(ref, var);
483}
484
485TEST(TestStringUtils, FindEndBracket)
486{
487 int ref, var;
488
489 ref = 11;
490 var = StringUtils::FindEndBracket("atest testbb test", 'a', 'b');
491 EXPECT_EQ(ref, var);
492}
493
494TEST(TestStringUtils, DateStringToYYYYMMDD)
495{
496 int ref, var;
497
498 ref = 20120706;
499 var = StringUtils::DateStringToYYYYMMDD("2012-07-06");
500 EXPECT_EQ(ref, var);
501}
502
503TEST(TestStringUtils, WordToDigits)
504{
505 std::string ref, var;
506
507 ref = "8378 787464";
508 var = "test string";
509 StringUtils::WordToDigits(var);
510 EXPECT_STREQ(ref.c_str(), var.c_str());
511}
512
513TEST(TestStringUtils, CreateUUID)
514{
515 std::cout << "CreateUUID(): " << StringUtils::CreateUUID() << std::endl;
516}
517
518TEST(TestStringUtils, ValidateUUID)
519{
520 EXPECT_TRUE(StringUtils::ValidateUUID(StringUtils::CreateUUID()));
521}
522
523TEST(TestStringUtils, CompareFuzzy)
524{
525 double ref, var;
526
527 ref = 6.25f;
528 var = StringUtils::CompareFuzzy("test string", "string test");
529 EXPECT_EQ(ref, var);
530}
531
532TEST(TestStringUtils, FindBestMatch)
533{
534 double refdouble, vardouble;
535 int refint, varint;
536 std::vector<std::string> strarray;
537
538 refint = 3;
539 refdouble = 0.5625f;
540 strarray.emplace_back("");
541 strarray.emplace_back("a");
542 strarray.emplace_back("e");
543 strarray.emplace_back("es");
544 strarray.emplace_back("t");
545 varint = StringUtils::FindBestMatch("test", strarray, vardouble);
546 EXPECT_EQ(refint, varint);
547 EXPECT_EQ(refdouble, vardouble);
548}
549
550TEST(TestStringUtils, Paramify)
551{
552 const char *input = "some, very \\ odd \"string\"";
553 const char *ref = "\"some, very \\\\ odd \\\"string\\\"\"";
554
555 std::string result = StringUtils::Paramify(input);
556 EXPECT_STREQ(ref, result.c_str());
557}
558
559TEST(TestStringUtils, sortstringbyname)
560{
561 std::vector<std::string> strarray;
562 strarray.emplace_back("B");
563 strarray.emplace_back("c");
564 strarray.emplace_back("a");
565 std::sort(strarray.begin(), strarray.end(), sortstringbyname());
566
567 EXPECT_STREQ("a", strarray[0].c_str());
568 EXPECT_STREQ("B", strarray[1].c_str());
569 EXPECT_STREQ("c", strarray[2].c_str());
570}
571
572TEST(TestStringUtils, FileSizeFormat)
573{
574 EXPECT_STREQ("0B", StringUtils::FormatFileSize(0).c_str());
575
576 EXPECT_STREQ("999B", StringUtils::FormatFileSize(999).c_str());
577 EXPECT_STREQ("0.98kB", StringUtils::FormatFileSize(1000).c_str());
578
579 EXPECT_STREQ("1.00kB", StringUtils::FormatFileSize(1024).c_str());
580 EXPECT_STREQ("9.99kB", StringUtils::FormatFileSize(10229).c_str());
581
582 EXPECT_STREQ("10.1kB", StringUtils::FormatFileSize(10387).c_str());
583 EXPECT_STREQ("99.9kB", StringUtils::FormatFileSize(102297).c_str());
584
585 EXPECT_STREQ("100kB", StringUtils::FormatFileSize(102400).c_str());
586 EXPECT_STREQ("999kB", StringUtils::FormatFileSize(1023431).c_str());
587
588 EXPECT_STREQ("0.98MB", StringUtils::FormatFileSize(1023897).c_str());
589 EXPECT_STREQ("0.98MB", StringUtils::FormatFileSize(1024000).c_str());
590
591 //Last unit should overflow the 3 digit limit
592 EXPECT_STREQ("5432PB", StringUtils::FormatFileSize(6115888293969133568).c_str());
593}
594
595TEST(TestStringUtils, ToHexadecimal)
596{
597 EXPECT_STREQ("", StringUtils::ToHexadecimal("").c_str());
598 EXPECT_STREQ("616263", StringUtils::ToHexadecimal("abc").c_str());
599 std::string a{"a\0b\n", 4};
600 EXPECT_STREQ("6100620a", StringUtils::ToHexadecimal(a).c_str());
601 std::string nul{"\0", 1};
602 EXPECT_STREQ("00", StringUtils::ToHexadecimal(nul).c_str());
603 std::string ff{"\xFF", 1};
604 EXPECT_STREQ("ff", StringUtils::ToHexadecimal(ff).c_str());
605}
diff --git a/xbmc/utils/test/TestSystemInfo.cpp b/xbmc/utils/test/TestSystemInfo.cpp
new file mode 100644
index 0000000..1f2b0a1
--- /dev/null
+++ b/xbmc/utils/test/TestSystemInfo.cpp
@@ -0,0 +1,326 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "GUIInfoManager.h"
10#include "ServiceBroker.h"
11#include "settings/Settings.h"
12#include "utils/CPUInfo.h"
13#include "utils/SystemInfo.h"
14#if defined(TARGET_WINDOWS)
15#include "platform/win32/CharsetConverter.h"
16#endif
17
18#include <gtest/gtest.h>
19
20class TestSystemInfo : public testing::Test
21{
22protected:
23 TestSystemInfo() { CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo()); }
24 ~TestSystemInfo() { CServiceBroker::UnregisterCPUInfo(); }
25};
26
27TEST_F(TestSystemInfo, Print_System_Info)
28{
29 std::cout << "'GetKernelName(false)': \"" << g_sysinfo.GetKernelName(true) << "\"\n";
30 std::cout << "'GetKernelVersion()': \"" << g_sysinfo.GetKernelVersion() << "\"\n";
31 std::cout << "'GetKernelVersionFull()': \"" << g_sysinfo.GetKernelVersionFull() << "\"\n";
32 std::cout << "'GetOsPrettyNameWithVersion()': \"" << g_sysinfo.GetOsPrettyNameWithVersion() << "\"\n";
33 std::cout << "'GetOsName(false)': \"" << g_sysinfo.GetOsName(false) << "\"\n";
34 std::cout << "'GetOsVersion()': \"" << g_sysinfo.GetOsVersion() << "\"\n";
35 std::cout << "'GetKernelCpuFamily()': \"" << g_sysinfo.GetKernelCpuFamily() << "\"\n";
36 std::cout << "'GetKernelBitness()': \"" << g_sysinfo.GetKernelBitness() << "\"\n";
37 std::cout << "'GetBuildTargetPlatformName()': \"" << g_sysinfo.GetBuildTargetPlatformName() << "\"\n";
38 std::cout << "'GetBuildTargetPlatformVersionDecoded()': \"" << g_sysinfo.GetBuildTargetPlatformVersionDecoded() << "\"\n";
39 std::cout << "'GetBuildTargetPlatformVersion()': \"" << g_sysinfo.GetBuildTargetPlatformVersion() << "\"\n";
40 std::cout << "'GetBuildTargetCpuFamily()': \"" << g_sysinfo.GetBuildTargetCpuFamily() << "\"\n";
41 std::cout << "'GetXbmcBitness()': \"" << g_sysinfo.GetXbmcBitness() << "\"\n";
42 std::cout << "'GetUsedCompilerNameAndVer()': \"" << g_sysinfo.GetUsedCompilerNameAndVer() << "\"\n";
43 std::cout << "'GetManufacturerName()': \"" << g_sysinfo.GetManufacturerName() << "\"\n";
44 std::cout << "'GetModelName()': \"" << g_sysinfo.GetModelName() << "\"\n";
45 std::cout << "'GetUserAgent()': \"" << g_sysinfo.GetUserAgent() << "\"\n";
46}
47
48TEST_F(TestSystemInfo, GetKernelName)
49{
50 EXPECT_FALSE(g_sysinfo.GetKernelName(true).empty()) << "'GetKernelName(true)' must not return empty kernel name";
51 EXPECT_FALSE(g_sysinfo.GetKernelName(false).empty()) << "'GetKernelName(false)' must not return empty kernel name";
52 EXPECT_STRCASENE("Unknown kernel", g_sysinfo.GetKernelName(true).c_str()) << "'GetKernelName(true)' must not return 'Unknown kernel'";
53 EXPECT_STRCASENE("Unknown kernel", g_sysinfo.GetKernelName(false).c_str()) << "'GetKernelName(false)' must not return 'Unknown kernel'";
54#ifndef TARGET_DARWIN
55 EXPECT_EQ(g_sysinfo.GetBuildTargetPlatformName(), g_sysinfo.GetKernelName(true)) << "'GetKernelName(true)' must match GetBuildTargetPlatformName()";
56 EXPECT_EQ(g_sysinfo.GetBuildTargetPlatformName(), g_sysinfo.GetKernelName(false)) << "'GetKernelName(false)' must match GetBuildTargetPlatformName()";
57#endif // !TARGET_DARWIN
58#if defined(TARGET_WINDOWS)
59 EXPECT_NE(std::string::npos, g_sysinfo.GetKernelName(true).find("Windows")) << "'GetKernelName(true)' must contain 'Windows'";
60 EXPECT_NE(std::string::npos, g_sysinfo.GetKernelName(false).find("Windows")) << "'GetKernelName(false)' must contain 'Windows'";
61#elif defined(TARGET_FREEBSD)
62 EXPECT_STREQ("FreeBSD", g_sysinfo.GetKernelName(true).c_str()) << "'GetKernelName(true)' must return 'FreeBSD'";
63 EXPECT_STREQ("FreeBSD", g_sysinfo.GetKernelName(false).c_str()) << "'GetKernelName(false)' must return 'FreeBSD'";
64#elif defined(TARGET_DARWIN)
65 EXPECT_STREQ("Darwin", g_sysinfo.GetKernelName(true).c_str()) << "'GetKernelName(true)' must return 'Darwin'";
66 EXPECT_STREQ("Darwin", g_sysinfo.GetKernelName(false).c_str()) << "'GetKernelName(false)' must return 'Darwin'";
67#elif defined(TARGET_LINUX)
68 EXPECT_STREQ("Linux", g_sysinfo.GetKernelName(true).c_str()) << "'GetKernelName(true)' must return 'Linux'";
69 EXPECT_STREQ("Linux", g_sysinfo.GetKernelName(false).c_str()) << "'GetKernelName(false)' must return 'Linux'";
70#endif
71}
72
73TEST_F(TestSystemInfo, GetKernelVersionFull)
74{
75 EXPECT_FALSE(g_sysinfo.GetKernelVersionFull().empty()) << "'GetKernelVersionFull()' must not return empty string";
76 EXPECT_STRNE("0.0.0", g_sysinfo.GetKernelVersionFull().c_str()) << "'GetKernelVersionFull()' must not return '0.0.0'";
77 EXPECT_STRNE("0.0", g_sysinfo.GetKernelVersionFull().c_str()) << "'GetKernelVersionFull()' must not return '0.0'";
78 EXPECT_EQ(0U, g_sysinfo.GetKernelVersionFull().find_first_of("0123456789")) << "'GetKernelVersionFull()' must not return version not starting from digit";
79}
80
81TEST_F(TestSystemInfo, GetKernelVersion)
82{
83 EXPECT_FALSE(g_sysinfo.GetKernelVersion().empty()) << "'GetKernelVersion()' must not return empty string";
84 EXPECT_STRNE("0.0.0", g_sysinfo.GetKernelVersion().c_str()) << "'GetKernelVersion()' must not return '0.0.0'";
85 EXPECT_STRNE("0.0", g_sysinfo.GetKernelVersion().c_str()) << "'GetKernelVersion()' must not return '0.0'";
86 EXPECT_EQ(0U, g_sysinfo.GetKernelVersion().find_first_of("0123456789")) << "'GetKernelVersion()' must not return version not starting from digit";
87 EXPECT_EQ(std::string::npos, g_sysinfo.GetKernelVersion().find_first_not_of("0123456789.")) << "'GetKernelVersion()' must not return version with not only digits and dots";
88}
89
90TEST_F(TestSystemInfo, GetOsName)
91{
92 EXPECT_FALSE(g_sysinfo.GetOsName(true).empty()) << "'GetOsName(true)' must not return empty OS name";
93 EXPECT_FALSE(g_sysinfo.GetOsName(false).empty()) << "'GetOsName(false)' must not return empty OS name";
94 EXPECT_STRCASENE("Unknown OS", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must not return 'Unknown OS'";
95 EXPECT_STRCASENE("Unknown OS", g_sysinfo.GetOsName(false).c_str()) << "'GetOsName(false)' must not return 'Unknown OS'";
96#if defined(TARGET_WINDOWS)
97 EXPECT_NE(std::string::npos, g_sysinfo.GetOsName(true).find("Windows")) << "'GetOsName(true)' must contain 'Windows'";
98 EXPECT_NE(std::string::npos, g_sysinfo.GetOsName(false).find("Windows")) << "'GetOsName(false)' must contain 'Windows'";
99#elif defined(TARGET_FREEBSD)
100 EXPECT_STREQ("FreeBSD", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must return 'FreeBSD'";
101 EXPECT_STREQ("FreeBSD", g_sysinfo.GetOsName(false).c_str()) << "'GetOsName(false)' must return 'FreeBSD'";
102#elif defined(TARGET_DARWIN_IOS)
103 EXPECT_STREQ("iOS", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must return 'iOS'";
104 EXPECT_STREQ("iOS", g_sysinfo.GetOsName(false).c_str()) << "'GetOsName(false)' must return 'iOS'";
105#elif defined(TARGET_DARWIN_TVOS)
106 EXPECT_STREQ("tvOS", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must return 'tvOS'";
107 EXPECT_STREQ("tvOS", g_sysinfo.GetOsName(false).c_str())
108 << "'GetOsName(false)' must return 'tvOS'";
109#elif defined(TARGET_DARWIN_OSX)
110 EXPECT_STREQ("OS X", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must return 'OS X'";
111 EXPECT_STREQ("OS X", g_sysinfo.GetOsName(false).c_str()) << "'GetOsName(false)' must return 'OS X'";
112#elif defined(TARGET_ANDROID)
113 EXPECT_STREQ("Android", g_sysinfo.GetOsName(true).c_str()) << "'GetOsName(true)' must return 'Android'";
114 EXPECT_STREQ("Android", g_sysinfo.GetOsName(false).c_str()) << "'GetOsName(false)' must return 'Android'";
115#endif
116#ifdef TARGET_DARWIN
117 EXPECT_EQ(g_sysinfo.GetBuildTargetPlatformName(), g_sysinfo.GetOsName(true)) << "'GetOsName(true)' must match GetBuildTargetPlatformName()";
118 EXPECT_EQ(g_sysinfo.GetBuildTargetPlatformName(), g_sysinfo.GetOsName(false)) << "'GetOsName(false)' must match GetBuildTargetPlatformName()";
119#endif // TARGET_DARWIN
120}
121
122TEST_F(TestSystemInfo, DISABLED_GetOsVersion)
123{
124 EXPECT_FALSE(g_sysinfo.GetOsVersion().empty()) << "'GetOsVersion()' must not return empty string";
125 EXPECT_STRNE("0.0.0", g_sysinfo.GetOsVersion().c_str()) << "'GetOsVersion()' must not return '0.0.0'";
126 EXPECT_STRNE("0.0", g_sysinfo.GetOsVersion().c_str()) << "'GetOsVersion()' must not return '0.0'";
127 EXPECT_EQ(0U, g_sysinfo.GetOsVersion().find_first_of("0123456789")) << "'GetOsVersion()' must not return version not starting from digit";
128 EXPECT_EQ(std::string::npos, g_sysinfo.GetOsVersion().find_first_not_of("0123456789.")) << "'GetOsVersion()' must not return version with not only digits and dots";
129}
130
131TEST_F(TestSystemInfo, GetOsPrettyNameWithVersion)
132{
133 EXPECT_FALSE(g_sysinfo.GetOsPrettyNameWithVersion().empty()) << "'GetOsPrettyNameWithVersion()' must not return empty string";
134 EXPECT_EQ(std::string::npos, g_sysinfo.GetOsPrettyNameWithVersion().find("Unknown")) << "'GetOsPrettyNameWithVersion()' must not contain 'Unknown'";
135 EXPECT_EQ(std::string::npos, g_sysinfo.GetOsPrettyNameWithVersion().find("unknown")) << "'GetOsPrettyNameWithVersion()' must not contain 'unknown'";
136#ifdef TARGET_WINDOWS
137 EXPECT_NE(std::string::npos, g_sysinfo.GetOsPrettyNameWithVersion().find("Windows")) << "'GetOsPrettyNameWithVersion()' must contain 'Windows'";
138#else // ! TARGET_WINDOWS
139 EXPECT_NE(std::string::npos, g_sysinfo.GetOsPrettyNameWithVersion().find(g_sysinfo.GetOsVersion())) << "'GetOsPrettyNameWithVersion()' must contain OS version";
140#endif // ! TARGET_WINDOWS
141}
142
143TEST_F(TestSystemInfo, GetManufacturerName)
144{
145 EXPECT_STRCASENE("unknown", g_sysinfo.GetManufacturerName().c_str()) << "'GetManufacturerName()' must return empty string instead of 'Unknown'";
146}
147
148TEST_F(TestSystemInfo, GetModelName)
149{
150 EXPECT_STRCASENE("unknown", g_sysinfo.GetModelName().c_str()) << "'GetModelName()' must return empty string instead of 'Unknown'";
151}
152
153#ifndef TARGET_WINDOWS
154TEST_F(TestSystemInfo, IsAeroDisabled)
155{
156 EXPECT_FALSE(g_sysinfo.IsAeroDisabled()) << "'IsAeroDisabled()' must return 'false'";
157}
158#endif // ! TARGET_WINDOWS
159
160TEST_F(TestSystemInfo, IsWindowsVersion)
161{
162 EXPECT_FALSE(g_sysinfo.IsWindowsVersion(CSysInfo::WindowsVersionUnknown)) << "'IsWindowsVersion()' must return 'false' for 'WindowsVersionUnknown'";
163#ifndef TARGET_WINDOWS
164 EXPECT_FALSE(g_sysinfo.IsWindowsVersion(CSysInfo::WindowsVersionWin7)) << "'IsWindowsVersion()' must return 'false'";
165#endif // ! TARGET_WINDOWS
166}
167
168TEST_F(TestSystemInfo, IsWindowsVersionAtLeast)
169{
170 EXPECT_FALSE(g_sysinfo.IsWindowsVersionAtLeast(CSysInfo::WindowsVersionUnknown)) << "'IsWindowsVersionAtLeast()' must return 'false' for 'WindowsVersionUnknown'";
171 EXPECT_FALSE(g_sysinfo.IsWindowsVersionAtLeast(CSysInfo::WindowsVersionFuture)) << "'IsWindowsVersionAtLeast()' must return 'false' for 'WindowsVersionFuture'";
172#ifndef TARGET_WINDOWS
173 EXPECT_FALSE(g_sysinfo.IsWindowsVersion(CSysInfo::WindowsVersionWin7)) << "'IsWindowsVersionAtLeast()' must return 'false'";
174#endif // ! TARGET_WINDOWS
175}
176
177TEST_F(TestSystemInfo, GetWindowsVersion)
178{
179#ifdef TARGET_WINDOWS
180 EXPECT_NE(CSysInfo::WindowsVersionUnknown, g_sysinfo.GetWindowsVersion()) << "'GetWindowsVersion()' must not return 'WindowsVersionUnknown'";
181 EXPECT_NE(CSysInfo::WindowsVersionFuture, g_sysinfo.GetWindowsVersion()) << "'GetWindowsVersion()' must not return 'WindowsVersionFuture'";
182#else // ! TARGET_WINDOWS
183 EXPECT_EQ(CSysInfo::WindowsVersionUnknown, g_sysinfo.GetWindowsVersion()) << "'GetWindowsVersion()' must return 'WindowsVersionUnknown'";
184#endif // ! TARGET_WINDOWS
185}
186
187TEST_F(TestSystemInfo, GetKernelBitness)
188{
189 EXPECT_TRUE(g_sysinfo.GetKernelBitness() == 32 || g_sysinfo.GetKernelBitness() == 64) << "'GetKernelBitness()' must return '32' or '64', but not '" << g_sysinfo.GetKernelBitness() << "'";
190 EXPECT_LE(g_sysinfo.GetXbmcBitness(), g_sysinfo.GetKernelBitness()) << "'GetKernelBitness()' must be greater or equal to 'GetXbmcBitness()'";
191}
192
193TEST_F(TestSystemInfo, GetKernelCpuFamily)
194{
195 EXPECT_STRNE("unknown CPU family", g_sysinfo.GetKernelCpuFamily().c_str()) << "'GetKernelCpuFamily()' must not return 'unknown CPU family'";
196#if defined(__thumb__) || defined(_M_ARMT) || defined(__arm__) || defined(_M_ARM) || defined (__aarch64__)
197 EXPECT_STREQ("ARM", g_sysinfo.GetKernelCpuFamily().c_str()) << "'GetKernelCpuFamily()' must return 'ARM'";
198#else // ! ARM
199 EXPECT_EQ(g_sysinfo.GetBuildTargetCpuFamily(), g_sysinfo.GetKernelCpuFamily()) << "'GetKernelCpuFamily()' must match 'GetBuildTargetCpuFamily()'";
200#endif // ! ARM
201}
202
203TEST_F(TestSystemInfo, GetXbmcBitness)
204{
205 EXPECT_TRUE(g_sysinfo.GetXbmcBitness() == 32 || g_sysinfo.GetXbmcBitness() == 64) << "'GetXbmcBitness()' must return '32' or '64', but not '" << g_sysinfo.GetXbmcBitness() << "'";
206 EXPECT_GE(g_sysinfo.GetKernelBitness(), g_sysinfo.GetXbmcBitness()) << "'GetXbmcBitness()' must be not greater than 'GetKernelBitness()'";
207}
208
209TEST_F(TestSystemInfo, GetUserAgent)
210{
211 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'";
212 EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find('(')) << "'GetUserAgent()' must contain brackets around second parameter";
213 EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find(')')) << "'GetUserAgent()' must contain brackets around second parameter";
214 EXPECT_EQ(g_sysinfo.GetUserAgent().find(' '), g_sysinfo.GetUserAgent().find(" (")) << "Second parameter in 'GetUserAgent()' string must be in brackets";
215 EXPECT_EQ(g_sysinfo.GetUserAgent().find(" (") + 1, g_sysinfo.GetUserAgent().find('(')) << "'GetUserAgent()' string must not contain any opening brackets before second parameter";
216 EXPECT_GT(g_sysinfo.GetUserAgent().find(')'), g_sysinfo.GetUserAgent().find('(')) << "'GetUserAgent()' string must not contain any closing brackets before second parameter";
217 EXPECT_EQ(g_sysinfo.GetUserAgent().find(") "), g_sysinfo.GetUserAgent().find(')')) << "'GetUserAgent()' string must not contain any closing brackets before end of second parameter";
218#if defined(TARGET_WINDOWS)
219 EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(Windows")) << "Second parameter in 'GetUserAgent()' string must start from `Windows`";
220 EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find("Windows")) << "'GetUserAgent()' must contain 'Windows'";
221#elif defined(TARGET_DARWIN_IOS)
222 EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find("like Mac OS X")) << "'GetUserAgent()' must contain ' like Mac OS X'";
223 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 '";
224#elif defined(TARGET_DARWIN_TVOS)
225 EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find("like Mac OS X"))
226 << "'GetUserAgent()' must contain ' like Mac OS X'";
227 EXPECT_TRUE(g_sysinfo.GetUserAgent().find("CPU TVOS ") != std::string::npos)
228 << "'GetUserAgent()' must contain 'CPU TVOS '";
229#elif defined(TARGET_DARWIN_OSX)
230 EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(Macintosh; ")) << "Second parameter in 'GetUserAgent()' string must start from 'Macintosh; '";
231#elif defined(TARGET_ANDROID)
232 EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(Linux; Android ")) << "Second parameter in 'GetUserAgent()' string must start from 'Linux; Android '";
233#elif defined(TARGET_POSIX)
234 EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(X11; ")) << "Second parameter in 'GetUserAgent()' string must start from 'X11; '";
235#if defined(TARGET_FREEBSD)
236 EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(X11; FreeBSD ")) << "Second parameter in 'GetUserAgent()' string must start from 'X11; FreeBSD '";
237#elif defined(TARGET_LINUX)
238 EXPECT_EQ(g_sysinfo.GetUserAgent().find('('), g_sysinfo.GetUserAgent().find("(X11; Linux ")) << "Second parameter in 'GetUserAgent()' string must start from 'X11; Linux '";
239#endif // defined(TARGET_LINUX)
240#endif // defined(TARGET_POSIX)
241
242#ifdef TARGET_RASPBERRY_PI
243 EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find(" XBMC_HW_RaspberryPi/")) << "'GetUserAgent()' must contain ' XBMC_HW_RaspberryPi/'";
244#endif // TARGET_RASPBERRY_PI
245
246 EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find(" App_Bitness/")) << "'GetUserAgent()' must contain ' App_Bitness/'";
247 EXPECT_NE(std::string::npos, g_sysinfo.GetUserAgent().find(" Version/")) << "'GetUserAgent()' must contain ' Version/'";
248}
249
250TEST_F(TestSystemInfo, GetBuildTargetPlatformName)
251{
252 EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformName().find("Unknown")) << "'GetBuildTargetPlatformName()' must not contain 'Unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformName() << "'";
253 EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformName().find("unknown")) << "'GetBuildTargetPlatformName()' must not contain 'unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformName() << "'";
254}
255
256TEST_F(TestSystemInfo, GetBuildTargetPlatformVersion)
257{
258 EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformVersion().find("Unknown")) << "'GetBuildTargetPlatformVersion()' must not contain 'Unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformVersion() << "'";
259 EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformVersion().find("unknown")) << "'GetBuildTargetPlatformVersion()' must not contain 'unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformVersion() << "'";
260}
261
262TEST_F(TestSystemInfo, GetBuildTargetPlatformVersionDecoded)
263{
264 EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformVersionDecoded().find("Unknown")) << "'GetBuildTargetPlatformVersionDecoded()' must not contain 'Unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformVersion() << "'";
265 EXPECT_EQ(std::string::npos, g_sysinfo.GetBuildTargetPlatformVersionDecoded().find("unknown")) << "'GetBuildTargetPlatformVersionDecoded()' must not contain 'unknown', actual value: '" << g_sysinfo.GetBuildTargetPlatformVersion() << "'";
266#ifdef TARGET_ANDROID
267 EXPECT_STREQ("API level ", g_sysinfo.GetBuildTargetPlatformVersionDecoded().substr(0, 10).c_str()) << "'GetBuildTargetPlatformVersionDecoded()' must start from 'API level '";
268#else
269 EXPECT_STREQ("version ", g_sysinfo.GetBuildTargetPlatformVersionDecoded().substr(0, 8).c_str()) << "'GetBuildTargetPlatformVersionDecoded()' must start from 'version'";
270#endif
271}
272
273TEST_F(TestSystemInfo, GetBuildTargetCpuFamily)
274{
275 EXPECT_STRNE("unknown CPU family", g_sysinfo.GetBuildTargetCpuFamily().c_str()) << "'GetBuildTargetCpuFamily()' must not return 'unknown CPU family'";
276#if defined(__thumb__) || defined(_M_ARMT) || defined(__arm__) || defined(_M_ARM) || defined (__aarch64__)
277 EXPECT_STREQ("ARM", g_sysinfo.GetBuildTargetCpuFamily().substr(0, 3).c_str()) << "'GetKernelCpuFamily()' string must start from 'ARM'";
278#else // ! ARM
279 EXPECT_EQ(g_sysinfo.GetKernelCpuFamily(), g_sysinfo.GetBuildTargetCpuFamily()) << "'GetBuildTargetCpuFamily()' must match 'GetKernelCpuFamily()'";
280#endif // ! ARM
281}
282
283TEST_F(TestSystemInfo, GetUsedCompilerNameAndVer)
284{
285 EXPECT_STRNE("unknown compiler", g_sysinfo.GetUsedCompilerNameAndVer().c_str()) << "'GetUsedCompilerNameAndVer()' must not return 'unknown compiler'";
286}
287
288TEST_F(TestSystemInfo, GetDiskSpace)
289{
290 int iTotal, iTotalFree, iTotalUsed, iPercentFree, iPercentUsed;
291
292 iTotal = iTotalFree = iTotalUsed = iPercentFree = iPercentUsed = 0;
293 EXPECT_TRUE(g_sysinfo.GetDiskSpace("*", iTotal, iTotalFree, iTotalUsed, iPercentFree, iPercentUsed)) << "'GetDiskSpace()' return 'false' for disk '*'";
294 EXPECT_NE(0, iTotal) << "'GetDiskSpace()' return zero total space for disk '*'";
295 EXPECT_EQ(iTotal, iTotalFree + iTotalUsed) << "'GetDiskSpace()' return 'TotalFree + TotalUsed' not equal to 'Total' for disk '*'";
296 EXPECT_EQ(100, iPercentFree + iPercentUsed) << "'GetDiskSpace()' return 'PercentFree + PercentUsed' not equal to '100' for disk '*'";
297
298 iTotal = iTotalFree = iTotalUsed = iPercentFree = iPercentUsed = 0;
299 EXPECT_TRUE(g_sysinfo.GetDiskSpace("", iTotal, iTotalFree, iTotalUsed, iPercentFree, iPercentUsed)) << "'GetDiskSpace()' return 'false' for disk ''";
300 EXPECT_NE(0, iTotal) << "'GetDiskSpace()' return zero total space for disk ''";
301 EXPECT_EQ(iTotal, iTotalFree + iTotalUsed) << "'GetDiskSpace()' return 'TotalFree + TotalUsed' not equal to 'Total' for disk ''";
302 EXPECT_EQ(100, iPercentFree + iPercentUsed) << "'GetDiskSpace()' return 'PercentFree + PercentUsed' not equal to '100' for disk ''";
303
304#ifdef TARGET_WINDOWS
305 using KODI::PLATFORM::WINDOWS::FromW;
306 wchar_t sysDrive[300];
307 DWORD res = GetEnvironmentVariableW(L"SystemDrive", sysDrive, sizeof(sysDrive) / sizeof(wchar_t));
308 std::string sysDriveLtr;
309 if (res != 0 && res <= sizeof(sysDrive) / sizeof(wchar_t))
310 sysDriveLtr.assign(FromW(sysDrive), 0, 1);
311 else
312 sysDriveLtr = "C"; // fallback
313
314 iTotal = iTotalFree = iTotalUsed = iPercentFree = iPercentUsed = 0;
315 EXPECT_TRUE(g_sysinfo.GetDiskSpace(sysDriveLtr, iTotal, iTotalFree, iTotalUsed, iPercentFree, iPercentUsed)) << "'GetDiskSpace()' return 'false' for disk '" << sysDriveLtr << ":'";
316 EXPECT_NE(0, iTotal) << "'GetDiskSpace()' return zero total space for disk '" << sysDriveLtr << ":'";
317 EXPECT_EQ(iTotal, iTotalFree + iTotalUsed) << "'GetDiskSpace()' return 'TotalFree + TotalUsed' not equal to 'Total' for disk '" << sysDriveLtr << ":'";
318 EXPECT_EQ(100, iPercentFree + iPercentUsed) << "'GetDiskSpace()' return 'PercentFree + PercentUsed' not equal to '100' for disk '" << sysDriveLtr << ":'";
319#elif defined(TARGET_POSIX)
320 iTotal = iTotalFree = iTotalUsed = iPercentFree = iPercentUsed = 0;
321 EXPECT_TRUE(g_sysinfo.GetDiskSpace("/", iTotal, iTotalFree, iTotalUsed, iPercentFree, iPercentUsed)) << "'GetDiskSpace()' return 'false' for directory '/'";
322 EXPECT_NE(0, iTotal) << "'GetDiskSpace()' return zero total space for directory '/'";
323 EXPECT_EQ(iTotal, iTotalFree + iTotalUsed) << "'GetDiskSpace()' return 'TotalFree + TotalUsed' not equal to 'Total' for directory '/'";
324 EXPECT_EQ(100, iPercentFree + iPercentUsed) << "'GetDiskSpace()' return 'PercentFree + PercentUsed' not equal to '100' for directory '/'";
325#endif
326}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "ServiceBroker.h"
10#include "URL.h"
11#include "filesystem/MultiPathDirectory.h"
12#include "settings/AdvancedSettings.h"
13#include "settings/SettingsComponent.h"
14#include "utils/URIUtils.h"
15
16#include <utility>
17
18#include <gtest/gtest.h>
19
20using namespace XFILE;
21
22class TestURIUtils : public testing::Test
23{
24protected:
25 TestURIUtils() = default;
26 ~TestURIUtils() override
27 {
28 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pathSubstitutions.clear();
29 }
30};
31
32TEST_F(TestURIUtils, PathHasParent)
33{
34 EXPECT_TRUE(URIUtils::PathHasParent("/path/to/movie.avi", "/path/to/"));
35 EXPECT_FALSE(URIUtils::PathHasParent("/path/to/movie.avi", "/path/2/"));
36}
37
38TEST_F(TestURIUtils, GetDirectory)
39{
40 EXPECT_STREQ("/path/to/", URIUtils::GetDirectory("/path/to/movie.avi").c_str());
41 EXPECT_STREQ("/path/to/", URIUtils::GetDirectory("/path/to/").c_str());
42 EXPECT_STREQ("/path/to/|option=foo", URIUtils::GetDirectory("/path/to/movie.avi|option=foo").c_str());
43 EXPECT_STREQ("/path/to/|option=foo", URIUtils::GetDirectory("/path/to/|option=foo").c_str());
44 EXPECT_STREQ("", URIUtils::GetDirectory("movie.avi").c_str());
45 EXPECT_STREQ("", URIUtils::GetDirectory("movie.avi|option=foo").c_str());
46 EXPECT_STREQ("", URIUtils::GetDirectory("").c_str());
47
48 // Make sure it works when assigning to the same str as the reference parameter
49 std::string var = "/path/to/movie.avi|option=foo";
50 var = URIUtils::GetDirectory(var);
51 EXPECT_STREQ("/path/to/|option=foo", var.c_str());
52}
53
54TEST_F(TestURIUtils, GetExtension)
55{
56 EXPECT_STREQ(".avi",
57 URIUtils::GetExtension("/path/to/movie.avi").c_str());
58}
59
60TEST_F(TestURIUtils, HasExtension)
61{
62 EXPECT_TRUE (URIUtils::HasExtension("/path/to/movie.AvI"));
63 EXPECT_FALSE(URIUtils::HasExtension("/path/to/movie"));
64 EXPECT_FALSE(URIUtils::HasExtension("/path/.to/movie"));
65 EXPECT_FALSE(URIUtils::HasExtension(""));
66
67 EXPECT_TRUE (URIUtils::HasExtension("/path/to/movie.AvI", ".avi"));
68 EXPECT_FALSE(URIUtils::HasExtension("/path/to/movie.AvI", ".mkv"));
69 EXPECT_FALSE(URIUtils::HasExtension("/path/.avi/movie", ".avi"));
70 EXPECT_FALSE(URIUtils::HasExtension("", ".avi"));
71
72 EXPECT_TRUE (URIUtils::HasExtension("/path/movie.AvI", ".avi|.mkv|.mp4"));
73 EXPECT_TRUE (URIUtils::HasExtension("/path/movie.AvI", ".mkv|.avi|.mp4"));
74 EXPECT_FALSE(URIUtils::HasExtension("/path/movie.AvI", ".mpg|.mkv|.mp4"));
75 EXPECT_FALSE(URIUtils::HasExtension("/path.mkv/movie.AvI", ".mpg|.mkv|.mp4"));
76 EXPECT_FALSE(URIUtils::HasExtension("", ".avi|.mkv|.mp4"));
77}
78
79TEST_F(TestURIUtils, GetFileName)
80{
81 EXPECT_STREQ("movie.avi",
82 URIUtils::GetFileName("/path/to/movie.avi").c_str());
83}
84
85TEST_F(TestURIUtils, RemoveExtension)
86{
87 std::string ref, var;
88
89 /* NOTE: CSettings need to be set to find other extensions. */
90 ref = "/path/to/file";
91 var = "/path/to/file.xml";
92 URIUtils::RemoveExtension(var);
93 EXPECT_STREQ(ref.c_str(), var.c_str());
94}
95
96TEST_F(TestURIUtils, ReplaceExtension)
97{
98 std::string ref, var;
99
100 ref = "/path/to/file.xsd";
101 var = URIUtils::ReplaceExtension("/path/to/file.xml", ".xsd");
102 EXPECT_STREQ(ref.c_str(), var.c_str());
103}
104
105TEST_F(TestURIUtils, Split)
106{
107 std::string refpath, reffile, varpath, varfile;
108
109 refpath = "/path/to/";
110 reffile = "movie.avi";
111 URIUtils::Split("/path/to/movie.avi", varpath, varfile);
112 EXPECT_STREQ(refpath.c_str(), varpath.c_str());
113 EXPECT_STREQ(reffile.c_str(), varfile.c_str());
114
115 std::string varpathOptional, varfileOptional;
116
117 refpath = "/path/to/";
118 reffile = "movie?movie.avi";
119 URIUtils::Split("/path/to/movie?movie.avi", varpathOptional, varfileOptional);
120 EXPECT_STREQ(refpath.c_str(), varpathOptional.c_str());
121 EXPECT_STREQ(reffile.c_str(), varfileOptional.c_str());
122
123 refpath = "file:///path/to/";
124 reffile = "movie.avi";
125 URIUtils::Split("file:///path/to/movie.avi?showinfo=true", varpathOptional, varfileOptional);
126 EXPECT_STREQ(refpath.c_str(), varpathOptional.c_str());
127 EXPECT_STREQ(reffile.c_str(), varfileOptional.c_str());
128}
129
130TEST_F(TestURIUtils, SplitPath)
131{
132 std::vector<std::string> strarray;
133
134 strarray = URIUtils::SplitPath("http://www.test.com/path/to/movie.avi");
135
136 EXPECT_STREQ("http://www.test.com/", strarray.at(0).c_str());
137 EXPECT_STREQ("path", strarray.at(1).c_str());
138 EXPECT_STREQ("to", strarray.at(2).c_str());
139 EXPECT_STREQ("movie.avi", strarray.at(3).c_str());
140}
141
142TEST_F(TestURIUtils, SplitPathLocal)
143{
144#ifndef TARGET_LINUX
145 const char *path = "C:\\path\\to\\movie.avi";
146#else
147 const char *path = "/path/to/movie.avi";
148#endif
149 std::vector<std::string> strarray;
150
151 strarray = URIUtils::SplitPath(path);
152
153#ifndef TARGET_LINUX
154 EXPECT_STREQ("C:", strarray.at(0).c_str());
155#else
156 EXPECT_STREQ("", strarray.at(0).c_str());
157#endif
158 EXPECT_STREQ("path", strarray.at(1).c_str());
159 EXPECT_STREQ("to", strarray.at(2).c_str());
160 EXPECT_STREQ("movie.avi", strarray.at(3).c_str());
161}
162
163TEST_F(TestURIUtils, GetCommonPath)
164{
165 std::string ref, var;
166
167 ref = "/path/";
168 var = "/path/2/movie.avi";
169 URIUtils::GetCommonPath(var, "/path/to/movie.avi");
170 EXPECT_STREQ(ref.c_str(), var.c_str());
171}
172
173TEST_F(TestURIUtils, GetParentPath)
174{
175 std::string ref, var;
176
177 ref = "/path/to/";
178 var = URIUtils::GetParentPath("/path/to/movie.avi");
179 EXPECT_STREQ(ref.c_str(), var.c_str());
180
181 var.clear();
182 EXPECT_TRUE(URIUtils::GetParentPath("/path/to/movie.avi", var));
183 EXPECT_STREQ(ref.c_str(), var.c_str());
184}
185
186TEST_F(TestURIUtils, SubstitutePath)
187{
188 std::string from, to, ref, var;
189
190 from = "C:\\My Videos";
191 to = "https://myserver/some%20other%20path";
192 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pathSubstitutions.push_back(std::make_pair(from, to));
193
194 from = "/this/path1";
195 to = "/some/other/path2";
196 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pathSubstitutions.push_back(std::make_pair(from, to));
197
198 from = "davs://otherserver/my%20music%20path";
199 to = "D:\\Local Music\\MP3 Collection";
200 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pathSubstitutions.push_back(std::make_pair(from, to));
201
202 ref = "https://myserver/some%20other%20path/sub%20dir/movie%20name.avi";
203 var = URIUtils::SubstitutePath("C:\\My Videos\\sub dir\\movie name.avi");
204 EXPECT_STREQ(ref.c_str(), var.c_str());
205
206 ref = "C:\\My Videos\\sub dir\\movie name.avi";
207 var = URIUtils::SubstitutePath("https://myserver/some%20other%20path/sub%20dir/movie%20name.avi", true);
208 EXPECT_STREQ(ref.c_str(), var.c_str());
209
210 ref = "D:\\Local Music\\MP3 Collection\\Phil Collins\\Some CD\\01 - Two Hearts.mp3";
211 var = URIUtils::SubstitutePath("davs://otherserver/my%20music%20path/Phil%20Collins/Some%20CD/01%20-%20Two%20Hearts.mp3");
212 EXPECT_STREQ(ref.c_str(), var.c_str());
213
214 ref = "davs://otherserver/my%20music%20path/Phil%20Collins/Some%20CD/01%20-%20Two%20Hearts.mp3";
215 var = URIUtils::SubstitutePath("D:\\Local Music\\MP3 Collection\\Phil Collins\\Some CD\\01 - Two Hearts.mp3", true);
216 EXPECT_STREQ(ref.c_str(), var.c_str());
217
218 ref = "/some/other/path2/to/movie.avi";
219 var = URIUtils::SubstitutePath("/this/path1/to/movie.avi");
220 EXPECT_STREQ(ref.c_str(), var.c_str());
221
222 ref = "/this/path1/to/movie.avi";
223 var = URIUtils::SubstitutePath("/some/other/path2/to/movie.avi", true);
224 EXPECT_STREQ(ref.c_str(), var.c_str());
225
226 ref = "/no/translation path/";
227 var = URIUtils::SubstitutePath(ref);
228 EXPECT_STREQ(ref.c_str(), var.c_str());
229
230 ref = "/no/translation path/";
231 var = URIUtils::SubstitutePath(ref, true);
232 EXPECT_STREQ(ref.c_str(), var.c_str());
233
234 ref = "c:\\no\\translation path";
235 var = URIUtils::SubstitutePath(ref);
236 EXPECT_STREQ(ref.c_str(), var.c_str());
237
238 ref = "c:\\no\\translation path";
239 var = URIUtils::SubstitutePath(ref, true);
240 EXPECT_STREQ(ref.c_str(), var.c_str());
241}
242
243TEST_F(TestURIUtils, IsAddonsPath)
244{
245 EXPECT_TRUE(URIUtils::IsAddonsPath("addons://path/to/addons"));
246}
247
248TEST_F(TestURIUtils, IsSourcesPath)
249{
250 EXPECT_TRUE(URIUtils::IsSourcesPath("sources://path/to/sources"));
251}
252
253TEST_F(TestURIUtils, IsCDDA)
254{
255 EXPECT_TRUE(URIUtils::IsCDDA("cdda://path/to/cdda"));
256}
257
258TEST_F(TestURIUtils, IsDOSPath)
259{
260 EXPECT_TRUE(URIUtils::IsDOSPath("C://path/to/dosfile"));
261}
262
263TEST_F(TestURIUtils, IsDVD)
264{
265 EXPECT_TRUE(URIUtils::IsDVD("dvd://path/in/video_ts.ifo"));
266#if defined(TARGET_WINDOWS)
267 EXPECT_TRUE(URIUtils::IsDVD("dvd://path/in/file"));
268#else
269 EXPECT_TRUE(URIUtils::IsDVD("iso9660://path/in/video_ts.ifo"));
270 EXPECT_TRUE(URIUtils::IsDVD("udf://path/in/video_ts.ifo"));
271 EXPECT_TRUE(URIUtils::IsDVD("dvd://1"));
272#endif
273}
274
275TEST_F(TestURIUtils, IsFTP)
276{
277 EXPECT_TRUE(URIUtils::IsFTP("ftp://path/in/ftp"));
278}
279
280TEST_F(TestURIUtils, IsHD)
281{
282 EXPECT_TRUE(URIUtils::IsHD("/path/to/file"));
283 EXPECT_TRUE(URIUtils::IsHD("file:///path/to/file"));
284 EXPECT_TRUE(URIUtils::IsHD("special://path/to/file"));
285 EXPECT_TRUE(URIUtils::IsHD("stack://path/to/file"));
286 EXPECT_TRUE(URIUtils::IsHD("zip://path/to/file"));
287}
288
289TEST_F(TestURIUtils, IsInArchive)
290{
291 EXPECT_TRUE(URIUtils::IsInArchive("zip://path/to/file"));
292}
293
294TEST_F(TestURIUtils, IsInRAR)
295{
296 EXPECT_TRUE(URIUtils::IsInRAR("rar://path/to/file"));
297}
298
299TEST_F(TestURIUtils, IsInternetStream)
300{
301 CURL url1("http://path/to/file");
302 CURL url2("https://path/to/file");
303 EXPECT_TRUE(URIUtils::IsInternetStream(url1));
304 EXPECT_TRUE(URIUtils::IsInternetStream(url2));
305}
306
307TEST_F(TestURIUtils, IsInZIP)
308{
309 EXPECT_TRUE(URIUtils::IsInZIP("zip://path/to/file"));
310}
311
312TEST_F(TestURIUtils, IsISO9660)
313{
314 EXPECT_TRUE(URIUtils::IsISO9660("iso9660://path/to/file"));
315}
316
317TEST_F(TestURIUtils, IsLiveTV)
318{
319 EXPECT_TRUE(URIUtils::IsLiveTV("whatever://path/to/file.pvr"));
320}
321
322TEST_F(TestURIUtils, IsMultiPath)
323{
324 EXPECT_TRUE(URIUtils::IsMultiPath("multipath://path/to/file"));
325}
326
327TEST_F(TestURIUtils, IsMusicDb)
328{
329 EXPECT_TRUE(URIUtils::IsMusicDb("musicdb://path/to/file"));
330}
331
332TEST_F(TestURIUtils, IsNfs)
333{
334 EXPECT_TRUE(URIUtils::IsNfs("nfs://path/to/file"));
335 EXPECT_TRUE(URIUtils::IsNfs("stack://nfs://path/to/file"));
336}
337
338TEST_F(TestURIUtils, IsOnDVD)
339{
340 EXPECT_TRUE(URIUtils::IsOnDVD("dvd://path/to/file"));
341 EXPECT_TRUE(URIUtils::IsOnDVD("udf://path/to/file"));
342 EXPECT_TRUE(URIUtils::IsOnDVD("iso9660://path/to/file"));
343 EXPECT_TRUE(URIUtils::IsOnDVD("cdda://path/to/file"));
344}
345
346TEST_F(TestURIUtils, IsOnLAN)
347{
348 std::vector<std::string> multiVec;
349 multiVec.emplace_back("smb://path/to/file");
350 EXPECT_TRUE(URIUtils::IsOnLAN(CMultiPathDirectory::ConstructMultiPath(multiVec)));
351 EXPECT_TRUE(URIUtils::IsOnLAN("stack://smb://path/to/file"));
352 EXPECT_TRUE(URIUtils::IsOnLAN("smb://path/to/file"));
353 EXPECT_FALSE(URIUtils::IsOnLAN("plugin://path/to/file"));
354 EXPECT_TRUE(URIUtils::IsOnLAN("upnp://path/to/file"));
355}
356
357TEST_F(TestURIUtils, IsPlugin)
358{
359 EXPECT_TRUE(URIUtils::IsPlugin("plugin://path/to/file"));
360}
361
362TEST_F(TestURIUtils, IsScript)
363{
364 EXPECT_TRUE(URIUtils::IsScript("script://path/to/file"));
365}
366
367TEST_F(TestURIUtils, IsRAR)
368{
369 EXPECT_TRUE(URIUtils::IsRAR("/path/to/rarfile.rar"));
370 EXPECT_TRUE(URIUtils::IsRAR("/path/to/rarfile.cbr"));
371 EXPECT_FALSE(URIUtils::IsRAR("/path/to/file"));
372 EXPECT_FALSE(URIUtils::IsRAR("rar://path/to/file"));
373}
374
375TEST_F(TestURIUtils, IsRemote)
376{
377 EXPECT_TRUE(URIUtils::IsRemote("http://path/to/file"));
378 EXPECT_TRUE(URIUtils::IsRemote("https://path/to/file"));
379 EXPECT_FALSE(URIUtils::IsRemote("addons://user/"));
380 EXPECT_FALSE(URIUtils::IsRemote("sources://video/"));
381 EXPECT_FALSE(URIUtils::IsRemote("videodb://movies/titles"));
382 EXPECT_FALSE(URIUtils::IsRemote("musicdb://genres/"));
383 EXPECT_FALSE(URIUtils::IsRemote("library://video/"));
384 EXPECT_FALSE(URIUtils::IsRemote("androidapp://app"));
385 EXPECT_FALSE(URIUtils::IsRemote("plugin://plugin.video.id"));
386}
387
388TEST_F(TestURIUtils, IsSmb)
389{
390 EXPECT_TRUE(URIUtils::IsSmb("smb://path/to/file"));
391 EXPECT_TRUE(URIUtils::IsSmb("stack://smb://path/to/file"));
392}
393
394TEST_F(TestURIUtils, IsSpecial)
395{
396 EXPECT_TRUE(URIUtils::IsSpecial("special://path/to/file"));
397 EXPECT_TRUE(URIUtils::IsSpecial("stack://special://path/to/file"));
398}
399
400TEST_F(TestURIUtils, IsStack)
401{
402 EXPECT_TRUE(URIUtils::IsStack("stack://path/to/file"));
403}
404
405TEST_F(TestURIUtils, IsUPnP)
406{
407 EXPECT_TRUE(URIUtils::IsUPnP("upnp://path/to/file"));
408}
409
410TEST_F(TestURIUtils, IsURL)
411{
412 EXPECT_TRUE(URIUtils::IsURL("someprotocol://path/to/file"));
413 EXPECT_FALSE(URIUtils::IsURL("/path/to/file"));
414}
415
416TEST_F(TestURIUtils, IsVideoDb)
417{
418 EXPECT_TRUE(URIUtils::IsVideoDb("videodb://path/to/file"));
419}
420
421TEST_F(TestURIUtils, IsZIP)
422{
423 EXPECT_TRUE(URIUtils::IsZIP("/path/to/zipfile.zip"));
424 EXPECT_TRUE(URIUtils::IsZIP("/path/to/zipfile.cbz"));
425 EXPECT_FALSE(URIUtils::IsZIP("/path/to/file"));
426 EXPECT_FALSE(URIUtils::IsZIP("zip://path/to/file"));
427}
428
429TEST_F(TestURIUtils, IsBluray)
430{
431 EXPECT_TRUE(URIUtils::IsBluray("bluray://path/to/file"));
432}
433
434TEST_F(TestURIUtils, AddSlashAtEnd)
435{
436 std::string ref, var;
437
438 ref = "bluray://path/to/file/";
439 var = "bluray://path/to/file/";
440 URIUtils::AddSlashAtEnd(var);
441 EXPECT_STREQ(ref.c_str(), var.c_str());
442}
443
444TEST_F(TestURIUtils, HasSlashAtEnd)
445{
446 EXPECT_TRUE(URIUtils::HasSlashAtEnd("bluray://path/to/file/"));
447 EXPECT_FALSE(URIUtils::HasSlashAtEnd("bluray://path/to/file"));
448}
449
450TEST_F(TestURIUtils, RemoveSlashAtEnd)
451{
452 std::string ref, var;
453
454 ref = "bluray://path/to/file";
455 var = "bluray://path/to/file/";
456 URIUtils::RemoveSlashAtEnd(var);
457 EXPECT_STREQ(ref.c_str(), var.c_str());
458}
459
460TEST_F(TestURIUtils, CreateArchivePath)
461{
462 std::string ref, var;
463
464 ref = "zip://%2fpath%2fto%2f/file";
465 var = URIUtils::CreateArchivePath("zip", CURL("/path/to/"), "file").Get();
466 EXPECT_STREQ(ref.c_str(), var.c_str());
467}
468
469TEST_F(TestURIUtils, AddFileToFolder)
470{
471 std::string ref = "/path/to/file";
472 std::string var = URIUtils::AddFileToFolder("/path/to", "file");
473 EXPECT_STREQ(ref.c_str(), var.c_str());
474
475 ref = "/path/to/file/and/more";
476 var = URIUtils::AddFileToFolder("/path", "to", "file", "and", "more");
477 EXPECT_STREQ(ref.c_str(), var.c_str());
478}
479
480TEST_F(TestURIUtils, HasParentInHostname)
481{
482 EXPECT_TRUE(URIUtils::HasParentInHostname(CURL("zip://")));
483 EXPECT_TRUE(URIUtils::HasParentInHostname(CURL("bluray://")));
484}
485
486TEST_F(TestURIUtils, HasEncodedHostname)
487{
488 EXPECT_TRUE(URIUtils::HasEncodedHostname(CURL("zip://")));
489 EXPECT_TRUE(URIUtils::HasEncodedHostname(CURL("bluray://")));
490 EXPECT_TRUE(URIUtils::HasEncodedHostname(CURL("musicsearch://")));
491}
492
493TEST_F(TestURIUtils, HasEncodedFilename)
494{
495 EXPECT_TRUE(URIUtils::HasEncodedFilename(CURL("shout://")));
496 EXPECT_TRUE(URIUtils::HasEncodedFilename(CURL("dav://")));
497 EXPECT_TRUE(URIUtils::HasEncodedFilename(CURL("rss://")));
498 EXPECT_TRUE(URIUtils::HasEncodedFilename(CURL("davs://")));
499}
500
501TEST_F(TestURIUtils, GetRealPath)
502{
503 std::string ref;
504
505 ref = "/path/to/file/";
506 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath(ref).c_str());
507
508 ref = "path/to/file";
509 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("../path/to/file").c_str());
510 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("./path/to/file").c_str());
511
512 ref = "/path/to/file";
513 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath(ref).c_str());
514 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("/path/to/./file").c_str());
515 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("/./path/to/./file").c_str());
516 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("/path/to/some/../file").c_str());
517 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("/../path/to/some/../file").c_str());
518
519 ref = "/path/to";
520 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("/path/to/some/../file/..").c_str());
521
522#ifdef TARGET_WINDOWS
523 ref = "\\\\path\\to\\file\\";
524 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath(ref).c_str());
525
526 ref = "path\\to\\file";
527 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("..\\path\\to\\file").c_str());
528 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath(".\\path\\to\\file").c_str());
529
530 ref = "\\\\path\\to\\file";
531 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath(ref).c_str());
532 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("\\\\path\\to\\.\\file").c_str());
533 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("\\\\.\\path/to\\.\\file").c_str());
534 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("\\\\path\\to\\some\\..\\file").c_str());
535 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("\\\\..\\path\\to\\some\\..\\file").c_str());
536
537 ref = "\\\\path\\to";
538 EXPECT_STREQ(ref.c_str(), URIUtils::GetRealPath("\\\\path\\to\\some\\..\\file\\..").c_str());
539#endif
540
541 // test rar/zip paths
542 ref = "zip://%2fpath%2fto%2fzip/subpath/to/file";
543 EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath(ref).c_str());
544
545 // test rar/zip paths
546 ref = "zip://%2fpath%2fto%2fzip/subpath/to/file";
547 EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2fzip/../subpath/to/file").c_str());
548 EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2fzip/./subpath/to/file").c_str());
549 EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2fzip/subpath/to/./file").c_str());
550 EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2fzip/subpath/to/some/../file").c_str());
551
552 EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2f.%2fzip/subpath/to/file").c_str());
553 EXPECT_STRCASEEQ(ref.c_str(), URIUtils::GetRealPath("zip://%2fpath%2fto%2fsome%2f..%2fzip/subpath/to/file").c_str());
554
555 // test zip/zip path
556 ref ="zip://zip%3a%2f%2f%252Fpath%252Fto%252Fzip%2fpath%2fto%2fzip/subpath/to/file";
557 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());
558}
559
560TEST_F(TestURIUtils, UpdateUrlEncoding)
561{
562 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";
563 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";
564
565 EXPECT_TRUE(URIUtils::UpdateUrlEncoding(oldUrl));
566 EXPECT_STRCASEEQ(newUrl.c_str(), oldUrl.c_str());
567
568 oldUrl = "zip://%2fpath%2fto%2farchive%2fsome%2darchive%2efile%2ezip/video.avi";
569 newUrl = "zip://%2fpath%2fto%2farchive%2fsome-archive.file.zip/video.avi";
570
571 EXPECT_TRUE(URIUtils::UpdateUrlEncoding(oldUrl));
572 EXPECT_STRCASEEQ(newUrl.c_str(), oldUrl.c_str());
573
574 oldUrl = "/path/to/some/long%2dnamed%2efile";
575 newUrl = "/path/to/some/long%2dnamed%2efile";
576
577 EXPECT_FALSE(URIUtils::UpdateUrlEncoding(oldUrl));
578 EXPECT_STRCASEEQ(newUrl.c_str(), oldUrl.c_str());
579
580 oldUrl = "/path/to/some/long-named.file";
581 newUrl = "/path/to/some/long-named.file";
582
583 EXPECT_FALSE(URIUtils::UpdateUrlEncoding(oldUrl));
584 EXPECT_STRCASEEQ(newUrl.c_str(), oldUrl.c_str());
585}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/UrlOptions.h"
10#include "utils/Variant.h"
11
12#include <gtest/gtest.h>
13
14TEST(TestUrlOptions, Clear)
15{
16 const char *key = "foo";
17
18 CUrlOptions urlOptions;
19 urlOptions.AddOption(key, "bar");
20 EXPECT_TRUE(urlOptions.HasOption(key));
21
22 urlOptions.Clear();
23 EXPECT_FALSE(urlOptions.HasOption(key));
24}
25
26TEST(TestUrlOptions, AddOption)
27{
28 const char *keyChar = "char";
29 const char *keyString = "string";
30 const char *keyEmpty = "empty";
31 const char *keyInt = "int";
32 const char *keyFloat = "float";
33 const char *keyDouble = "double";
34 const char *keyBool = "bool";
35
36 const char *valueChar = "valueChar";
37 const std::string valueString = "valueString";
38 const char *valueEmpty = "";
39 int valueInt = 1;
40 float valueFloat = 1.0f;
41 double valueDouble = 1.0;
42 bool valueBool = true;
43
44 CVariant variantValue;
45
46 CUrlOptions urlOptions;
47 urlOptions.AddOption(keyChar, valueChar);
48 {
49 CVariant variantValue;
50 EXPECT_TRUE(urlOptions.GetOption(keyChar, variantValue));
51 EXPECT_TRUE(variantValue.isString());
52 EXPECT_STREQ(valueChar, variantValue.asString().c_str());
53 }
54
55 urlOptions.AddOption(keyString, valueString);
56 {
57 CVariant variantValue;
58 EXPECT_TRUE(urlOptions.GetOption(keyString, variantValue));
59 EXPECT_TRUE(variantValue.isString());
60 EXPECT_STREQ(valueString.c_str(), variantValue.asString().c_str());
61 }
62
63 urlOptions.AddOption(keyEmpty, valueEmpty);
64 {
65 CVariant variantValue;
66 EXPECT_TRUE(urlOptions.GetOption(keyEmpty, variantValue));
67 EXPECT_TRUE(variantValue.isString());
68 EXPECT_STREQ(valueEmpty, variantValue.asString().c_str());
69 }
70
71 urlOptions.AddOption(keyInt, valueInt);
72 {
73 CVariant variantValue;
74 EXPECT_TRUE(urlOptions.GetOption(keyInt, variantValue));
75 EXPECT_TRUE(variantValue.isInteger());
76 EXPECT_EQ(valueInt, (int)variantValue.asInteger());
77 }
78
79 urlOptions.AddOption(keyFloat, valueFloat);
80 {
81 CVariant variantValue;
82 EXPECT_TRUE(urlOptions.GetOption(keyFloat, variantValue));
83 EXPECT_TRUE(variantValue.isDouble());
84 EXPECT_EQ(valueFloat, variantValue.asFloat());
85 }
86
87 urlOptions.AddOption(keyDouble, valueDouble);
88 {
89 CVariant variantValue;
90 EXPECT_TRUE(urlOptions.GetOption(keyDouble, variantValue));
91 EXPECT_TRUE(variantValue.isDouble());
92 EXPECT_EQ(valueDouble, variantValue.asDouble());
93 }
94
95 urlOptions.AddOption(keyBool, valueBool);
96 {
97 CVariant variantValue;
98 EXPECT_TRUE(urlOptions.GetOption(keyBool, variantValue));
99 EXPECT_TRUE(variantValue.isBoolean());
100 EXPECT_EQ(valueBool, variantValue.asBoolean());
101 }
102}
103
104TEST(TestUrlOptions, AddOptions)
105{
106 std::string ref = "foo=bar&key=value";
107
108 CUrlOptions urlOptions(ref);
109 {
110 CVariant value;
111 EXPECT_TRUE(urlOptions.GetOption("foo", value));
112 EXPECT_TRUE(value.isString());
113 EXPECT_STREQ("bar", value.asString().c_str());
114 }
115 {
116 CVariant value;
117 EXPECT_TRUE(urlOptions.GetOption("key", value));
118 EXPECT_TRUE(value.isString());
119 EXPECT_STREQ("value", value.asString().c_str());
120 }
121
122 ref = "foo=bar&key";
123 urlOptions.Clear();
124 urlOptions.AddOptions(ref);
125 {
126 CVariant value;
127 EXPECT_TRUE(urlOptions.GetOption("foo", value));
128 EXPECT_TRUE(value.isString());
129 EXPECT_STREQ("bar", value.asString().c_str());
130 }
131 {
132 CVariant value;
133 EXPECT_TRUE(urlOptions.GetOption("key", value));
134 EXPECT_TRUE(value.isString());
135 EXPECT_TRUE(value.empty());
136 }
137}
138
139TEST(TestUrlOptions, RemoveOption)
140{
141 const char *key = "foo";
142
143 CUrlOptions urlOptions;
144 urlOptions.AddOption(key, "bar");
145 EXPECT_TRUE(urlOptions.HasOption(key));
146
147 urlOptions.RemoveOption(key);
148 EXPECT_FALSE(urlOptions.HasOption(key));
149}
150
151TEST(TestUrlOptions, HasOption)
152{
153 const char *key = "foo";
154
155 CUrlOptions urlOptions;
156 urlOptions.AddOption(key, "bar");
157 EXPECT_TRUE(urlOptions.HasOption(key));
158 EXPECT_FALSE(urlOptions.HasOption("bar"));
159}
160
161TEST(TestUrlOptions, GetOptions)
162{
163 const char *key1 = "foo";
164 const char *key2 = "key";
165 const char *value1 = "bar";
166 const char *value2 = "value";
167
168 CUrlOptions urlOptions;
169 urlOptions.AddOption(key1, value1);
170 urlOptions.AddOption(key2, value2);
171 const CUrlOptions::UrlOptions &options = urlOptions.GetOptions();
172 EXPECT_FALSE(options.empty());
173 EXPECT_EQ(2U, options.size());
174
175 CUrlOptions::UrlOptions::const_iterator it1 = options.find(key1);
176 EXPECT_TRUE(it1 != options.end());
177 CUrlOptions::UrlOptions::const_iterator it2 = options.find(key2);
178 EXPECT_TRUE(it2 != options.end());
179 EXPECT_FALSE(options.find("wrong") != options.end());
180 EXPECT_TRUE(it1->second.isString());
181 EXPECT_TRUE(it2->second.isString());
182 EXPECT_STREQ(value1, it1->second.asString().c_str());
183 EXPECT_STREQ(value2, it2->second.asString().c_str());
184}
185
186TEST(TestUrlOptions, GetOptionsString)
187{
188 const char *ref = "foo=bar&key";
189
190 CUrlOptions urlOptions(ref);
191 std::string value = urlOptions.GetOptionsString();
192 EXPECT_STREQ(ref, value.c_str());
193}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/Variant.h"
10
11#include <gtest/gtest.h>
12
13TEST(TestVariant, VariantTypeInteger)
14{
15 CVariant a((int)0), b((int64_t)1);
16
17 EXPECT_TRUE(a.isInteger());
18 EXPECT_EQ(CVariant::VariantTypeInteger, a.type());
19 EXPECT_TRUE(b.isInteger());
20 EXPECT_EQ(CVariant::VariantTypeInteger, b.type());
21
22 EXPECT_EQ((int64_t)1, b.asInteger());
23}
24
25TEST(TestVariant, VariantTypeUnsignedInteger)
26{
27 CVariant a((unsigned int)0), b((uint64_t)1);
28
29 EXPECT_TRUE(a.isUnsignedInteger());
30 EXPECT_EQ(CVariant::VariantTypeUnsignedInteger, a.type());
31 EXPECT_TRUE(b.isUnsignedInteger());
32 EXPECT_EQ(CVariant::VariantTypeUnsignedInteger, b.type());
33
34 EXPECT_EQ((uint64_t)1, b.asUnsignedInteger());
35}
36
37TEST(TestVariant, VariantTypeBoolean)
38{
39 CVariant a(true);
40
41 EXPECT_TRUE(a.isBoolean());
42 EXPECT_EQ(CVariant::VariantTypeBoolean, a.type());
43
44 EXPECT_TRUE(a.asBoolean());
45}
46
47TEST(TestVariant, VariantTypeString)
48{
49 CVariant a("VariantTypeString");
50 CVariant b("VariantTypeString2", sizeof("VariantTypeString2") - 1);
51 std::string str("VariantTypeString3");
52 CVariant c(str);
53
54 EXPECT_TRUE(a.isString());
55 EXPECT_EQ(CVariant::VariantTypeString, a.type());
56 EXPECT_TRUE(b.isString());
57 EXPECT_EQ(CVariant::VariantTypeString, b.type());
58 EXPECT_TRUE(c.isString());
59 EXPECT_EQ(CVariant::VariantTypeString, c.type());
60
61 EXPECT_STREQ("VariantTypeString", a.asString().c_str());
62 EXPECT_STREQ("VariantTypeString2", b.asString().c_str());
63 EXPECT_STREQ("VariantTypeString3", c.asString().c_str());
64}
65
66TEST(TestVariant, VariantTypeWideString)
67{
68 CVariant a(L"VariantTypeWideString");
69 CVariant b(L"VariantTypeWideString2", sizeof(L"VariantTypeWideString2") - 1);
70 std::wstring str(L"VariantTypeWideString3");
71 CVariant c(str);
72
73 EXPECT_TRUE(a.isWideString());
74 EXPECT_EQ(CVariant::VariantTypeWideString, a.type());
75 EXPECT_TRUE(b.isWideString());
76 EXPECT_EQ(CVariant::VariantTypeWideString, b.type());
77 EXPECT_TRUE(c.isWideString());
78 EXPECT_EQ(CVariant::VariantTypeWideString, c.type());
79
80 EXPECT_STREQ(L"VariantTypeWideString", a.asWideString().c_str());
81 EXPECT_STREQ(L"VariantTypeWideString2", b.asWideString().c_str());
82 EXPECT_STREQ(L"VariantTypeWideString3", c.asWideString().c_str());
83}
84
85TEST(TestVariant, VariantTypeDouble)
86{
87 CVariant a((float)0.0f), b((double)0.1f);
88
89 EXPECT_TRUE(a.isDouble());
90 EXPECT_EQ(CVariant::VariantTypeDouble, a.type());
91 EXPECT_TRUE(b.isDouble());
92 EXPECT_EQ(CVariant::VariantTypeDouble, b.type());
93
94 EXPECT_EQ((float)0.0f, a.asDouble());
95 EXPECT_EQ((double)0.1f, b.asDouble());
96}
97
98TEST(TestVariant, VariantTypeArray)
99{
100 std::vector<std::string> strarray;
101 strarray.emplace_back("string1");
102 strarray.emplace_back("string2");
103 strarray.emplace_back("string3");
104 strarray.emplace_back("string4");
105 CVariant a(strarray);
106
107 EXPECT_TRUE(a.isArray());
108 EXPECT_EQ(CVariant::VariantTypeArray, a.type());
109}
110
111TEST(TestVariant, VariantTypeObject)
112{
113 CVariant a;
114 a["key"] = "value";
115
116 EXPECT_TRUE(a.isObject());
117 EXPECT_EQ(CVariant::VariantTypeObject, a.type());
118}
119
120TEST(TestVariant, VariantTypeNull)
121{
122 CVariant a;
123
124 EXPECT_TRUE(a.isNull());
125 EXPECT_EQ(CVariant::VariantTypeNull, a.type());
126}
127
128TEST(TestVariant, VariantFromMap)
129{
130 std::map<std::string, std::string> strMap;
131 strMap["key"] = "value";
132 CVariant a = strMap;
133
134 EXPECT_TRUE(a.isObject());
135 EXPECT_TRUE(a.size() == 1);
136 EXPECT_EQ(CVariant::VariantTypeObject, a.type());
137 EXPECT_TRUE(a.isMember("key"));
138 EXPECT_TRUE(a["key"].isString());
139 EXPECT_STREQ(a["key"].asString().c_str(), "value");
140
141 std::map<std::string, CVariant> variantMap;
142 variantMap["key"] = CVariant("value");
143 CVariant b = variantMap;
144
145 EXPECT_TRUE(b.isObject());
146 EXPECT_TRUE(b.size() == 1);
147 EXPECT_EQ(CVariant::VariantTypeObject, b.type());
148 EXPECT_TRUE(b.isMember("key"));
149 EXPECT_TRUE(b["key"].isString());
150 EXPECT_STREQ(b["key"].asString().c_str(), "value");
151}
152
153TEST(TestVariant, operatorTest)
154{
155 std::vector<std::string> strarray;
156 strarray.emplace_back("string1");
157 CVariant a, b, c(strarray), d;
158 a["key"] = "value";
159 b = a;
160 c[0] = "value2";
161 d = c;
162
163 EXPECT_TRUE(a.isObject());
164 EXPECT_EQ(CVariant::VariantTypeObject, a.type());
165 EXPECT_TRUE(b.isObject());
166 EXPECT_EQ(CVariant::VariantTypeObject, b.type());
167 EXPECT_TRUE(c.isArray());
168 EXPECT_EQ(CVariant::VariantTypeArray, c.type());
169 EXPECT_TRUE(d.isArray());
170 EXPECT_EQ(CVariant::VariantTypeArray, d.type());
171
172 EXPECT_TRUE(a == b);
173 EXPECT_TRUE(c == d);
174 EXPECT_FALSE(a == d);
175
176 EXPECT_STREQ("value", a["key"].asString().c_str());
177 EXPECT_STREQ("value2", c[0].asString().c_str());
178}
179
180TEST(TestVariant, push_back)
181{
182 CVariant a, b("variant1"), c("variant2"), d("variant3");
183 a.push_back(b);
184 a.push_back(c);
185 a.push_back(d);
186
187 EXPECT_TRUE(a.isArray());
188 EXPECT_EQ(CVariant::VariantTypeArray, a.type());
189 EXPECT_STREQ("variant1", a[0].asString().c_str());
190 EXPECT_STREQ("variant2", a[1].asString().c_str());
191 EXPECT_STREQ("variant3", a[2].asString().c_str());
192}
193
194TEST(TestVariant, append)
195{
196 CVariant a, b("variant1"), c("variant2"), d("variant3");
197 a.append(b);
198 a.append(c);
199 a.append(d);
200
201 EXPECT_TRUE(a.isArray());
202 EXPECT_EQ(CVariant::VariantTypeArray, a.type());
203 EXPECT_STREQ("variant1", a[0].asString().c_str());
204 EXPECT_STREQ("variant2", a[1].asString().c_str());
205 EXPECT_STREQ("variant3", a[2].asString().c_str());
206}
207
208TEST(TestVariant, c_str)
209{
210 CVariant a("variant");
211
212 EXPECT_STREQ("variant", a.c_str());
213}
214
215TEST(TestVariant, swap)
216{
217 CVariant a((int)0), b("variant");
218
219 EXPECT_TRUE(a.isInteger());
220 EXPECT_TRUE(b.isString());
221
222 a.swap(b);
223 EXPECT_TRUE(b.isInteger());
224 EXPECT_TRUE(a.isString());
225}
226
227TEST(TestVariant, iterator_array)
228{
229 std::vector<std::string> strarray;
230 strarray.emplace_back("string");
231 strarray.emplace_back("string");
232 strarray.emplace_back("string");
233 strarray.emplace_back("string");
234 CVariant a(strarray);
235
236 EXPECT_TRUE(a.isArray());
237 EXPECT_EQ(CVariant::VariantTypeArray, a.type());
238
239 for (auto it = a.begin_array(); it != a.end_array(); it++)
240 {
241 EXPECT_STREQ("string", it->c_str());
242 }
243
244 for (auto const_it = a.begin_array(); const_it != a.end_array(); const_it++)
245 {
246 EXPECT_STREQ("string", const_it->c_str());
247 }
248}
249
250TEST(TestVariant, iterator_map)
251{
252 CVariant a;
253 a["key1"] = "string";
254 a["key2"] = "string";
255 a["key3"] = "string";
256 a["key4"] = "string";
257
258 EXPECT_TRUE(a.isObject());
259 EXPECT_EQ(CVariant::VariantTypeObject, a.type());
260
261 for (auto it = a.begin_map(); it != a.end_map(); it++)
262 {
263 EXPECT_STREQ("string", it->second.c_str());
264 }
265
266 for (auto const_it = a.begin_map(); const_it != a.end_map(); const_it++)
267 {
268 EXPECT_STREQ("string", const_it->second.c_str());
269 }
270}
271
272TEST(TestVariant, size)
273{
274 std::vector<std::string> strarray;
275 strarray.emplace_back("string");
276 strarray.emplace_back("string");
277 strarray.emplace_back("string");
278 strarray.emplace_back("string");
279 CVariant a(strarray);
280
281 EXPECT_EQ((unsigned int)4, a.size());
282}
283
284TEST(TestVariant, empty)
285{
286 std::vector<std::string> strarray;
287 CVariant a(strarray);
288
289 EXPECT_TRUE(a.empty());
290}
291
292TEST(TestVariant, clear)
293{
294 std::vector<std::string> strarray;
295 strarray.emplace_back("string");
296 strarray.emplace_back("string");
297 strarray.emplace_back("string");
298 strarray.emplace_back("string");
299 CVariant a(strarray);
300
301 EXPECT_FALSE(a.empty());
302 a.clear();
303 EXPECT_TRUE(a.empty());
304}
305
306TEST(TestVariant, erase)
307{
308 std::vector<std::string> strarray;
309 strarray.emplace_back("string1");
310 strarray.emplace_back("string2");
311 strarray.emplace_back("string3");
312 strarray.emplace_back("string4");
313 CVariant a, b(strarray);
314 a["key1"] = "string1";
315 a["key2"] = "string2";
316 a["key3"] = "string3";
317 a["key4"] = "string4";
318
319 EXPECT_STREQ("string2", a["key2"].c_str());
320 EXPECT_STREQ("string2", b[1].c_str());
321 a.erase("key2");
322 b.erase(1);
323 EXPECT_FALSE(a["key2"].c_str());
324 EXPECT_STREQ("string3", b[1].c_str());
325}
326
327TEST(TestVariant, isMember)
328{
329 CVariant a;
330 a["key1"] = "string1";
331
332 EXPECT_TRUE(a.isMember("key1"));
333 EXPECT_FALSE(a.isMember("key2"));
334}
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 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "test/TestUtils.h"
10#include "utils/StringUtils.h"
11#include "utils/XBMCTinyXML.h"
12
13#include <gtest/gtest.h>
14
15TEST(TestXBMCTinyXML, ParseFromString)
16{
17 bool retval = false;
18 // scraper results with unescaped &
19 CXBMCTinyXML doc;
20 std::string data("<details><url function=\"ParseTMDBRating\" "
21 "cache=\"tmdb-en-12244.json\">"
22 "http://api.themoviedb.org/3/movie/12244"
23 "?api_key=57983e31fb435df4df77afb854740ea9"
24 "&language=en&#x3f;&#x003F;&#0063;</url></details>");
25 doc.Parse(data);
26 TiXmlNode *root = doc.RootElement();
27 if (root && root->ValueStr() == "details")
28 {
29 TiXmlElement *url = root->FirstChildElement("url");
30 if (url && url->FirstChild())
31 {
32 retval = (url->FirstChild()->ValueStr() == "http://api.themoviedb.org/3/movie/12244?api_key=57983e31fb435df4df77afb854740ea9&language=en???");
33 }
34 }
35 EXPECT_TRUE(retval);
36}
37
38TEST(TestXBMCTinyXML, ParseFromFileHandle)
39{
40 bool retval = false;
41 // scraper results with unescaped &
42 CXBMCTinyXML doc;
43 FILE *f = fopen(XBMC_REF_FILE_PATH("/xbmc/utils/test/CXBMCTinyXML-test.xml").c_str(), "r");
44 ASSERT_NE(nullptr, f);
45 doc.LoadFile(f);
46 fclose(f);
47 TiXmlNode *root = doc.RootElement();
48 if (root && root->ValueStr() == "details")
49 {
50 TiXmlElement *url = root->FirstChildElement("url");
51 if (url && url->FirstChild())
52 {
53 std::string str = url->FirstChild()->ValueStr();
54 retval = (StringUtils::Trim(str) == "http://api.themoviedb.org/3/movie/12244?api_key=57983e31fb435df4df77afb854740ea9&language=en???");
55 }
56 }
57 EXPECT_TRUE(retval);
58}
diff --git a/xbmc/utils/test/TestXMLUtils.cpp b/xbmc/utils/test/TestXMLUtils.cpp
new file mode 100644
index 0000000..1a807ff
--- /dev/null
+++ b/xbmc/utils/test/TestXMLUtils.cpp
@@ -0,0 +1,356 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "XBDateTime.h"
10#include "utils/StringUtils.h"
11#include "utils/XMLUtils.h"
12
13#include <gtest/gtest.h>
14
15TEST(TestXMLUtils, GetHex)
16{
17 CXBMCTinyXML a;
18 uint32_t ref, val;
19
20 a.Parse(std::string("<root><node>0xFF</node></root>"));
21 EXPECT_TRUE(XMLUtils::GetHex(a.RootElement(), "node", val));
22
23 ref = 0xFF;
24 EXPECT_EQ(ref, val);
25}
26
27TEST(TestXMLUtils, GetUInt)
28{
29 CXBMCTinyXML a;
30 uint32_t ref, val;
31
32 a.Parse(std::string("<root><node>1000</node></root>"));
33 EXPECT_TRUE(XMLUtils::GetUInt(a.RootElement(), "node", val));
34
35 ref = 1000;
36 EXPECT_EQ(ref, val);
37}
38
39TEST(TestXMLUtils, GetLong)
40{
41 CXBMCTinyXML a;
42 long ref, val;
43
44 a.Parse(std::string("<root><node>1000</node></root>"));
45 EXPECT_TRUE(XMLUtils::GetLong(a.RootElement(), "node", val));
46
47 ref = 1000;
48 EXPECT_EQ(ref, val);
49}
50
51TEST(TestXMLUtils, GetFloat)
52{
53 CXBMCTinyXML a;
54 float ref, val;
55
56 a.Parse(std::string("<root><node>1000.1f</node></root>"));
57 EXPECT_TRUE(XMLUtils::GetFloat(a.RootElement(), "node", val));
58 EXPECT_TRUE(XMLUtils::GetFloat(a.RootElement(), "node", val, 1000.0f,
59 1000.2f));
60 ref = 1000.1f;
61 EXPECT_EQ(ref, val);
62}
63
64TEST(TestXMLUtils, GetDouble)
65{
66 CXBMCTinyXML a;
67 double val;
68 std::string refstr, valstr;
69
70 a.Parse(std::string("<root><node>1000.1f</node></root>"));
71 EXPECT_TRUE(XMLUtils::GetDouble(a.RootElement(), "node", val));
72
73 refstr = "1000.100000";
74 valstr = StringUtils::Format("%f", val);
75 EXPECT_STREQ(refstr.c_str(), valstr.c_str());
76}
77
78TEST(TestXMLUtils, GetInt)
79{
80 CXBMCTinyXML a;
81 int ref, val;
82
83 a.Parse(std::string("<root><node>1000</node></root>"));
84 EXPECT_TRUE(XMLUtils::GetInt(a.RootElement(), "node", val));
85 EXPECT_TRUE(XMLUtils::GetInt(a.RootElement(), "node", val, 999, 1001));
86
87 ref = 1000;
88 EXPECT_EQ(ref, val);
89}
90
91TEST(TestXMLUtils, GetBoolean)
92{
93 CXBMCTinyXML a;
94 bool ref, val;
95
96 a.Parse(std::string("<root><node>true</node></root>"));
97 EXPECT_TRUE(XMLUtils::GetBoolean(a.RootElement(), "node", val));
98
99 ref = true;
100 EXPECT_EQ(ref, val);
101}
102
103TEST(TestXMLUtils, GetString)
104{
105 CXBMCTinyXML a;
106 std::string ref, val;
107
108 a.Parse(std::string("<root><node>some string</node></root>"));
109 EXPECT_TRUE(XMLUtils::GetString(a.RootElement(), "node", val));
110
111 ref = "some string";
112 EXPECT_STREQ(ref.c_str(), val.c_str());
113}
114
115TEST(TestXMLUtils, GetAdditiveString)
116{
117 CXBMCTinyXML a, b;
118 std::string ref, val;
119
120 a.Parse(std::string("<root>\n"
121 " <node>some string1</node>\n"
122 " <node>some string2</node>\n"
123 " <node>some string3</node>\n"
124 " <node>some string4</node>\n"
125 " <node>some string5</node>\n"
126 "</root>\n"));
127 EXPECT_TRUE(XMLUtils::GetAdditiveString(a.RootElement(), "node", ",", val));
128
129 ref = "some string1,some string2,some string3,some string4,some string5";
130 EXPECT_STREQ(ref.c_str(), val.c_str());
131
132 val.clear();
133 b.Parse(std::string("<root>\n"
134 " <node>some string1</node>\n"
135 " <node>some string2</node>\n"
136 " <node clear=\"true\">some string3</node>\n"
137 " <node>some string4</node>\n"
138 " <node>some string5</node>\n"
139 "</root>\n"));
140 EXPECT_TRUE(XMLUtils::GetAdditiveString(b.RootElement(), "node", ",", val));
141
142 ref = "some string3,some string4,some string5";
143 EXPECT_STREQ(ref.c_str(), val.c_str());
144}
145
146TEST(TestXMLUtils, GetStringArray)
147{
148 CXBMCTinyXML a;
149 std::vector<std::string> strarray;
150
151 a.Parse(std::string("<root>\n"
152 " <node>some string1</node>\n"
153 " <node>some string2</node>\n"
154 " <node>some string3</node>\n"
155 " <node>some string4</node>\n"
156 " <node>some string5</node>\n"
157 "</root>\n"));
158 EXPECT_TRUE(XMLUtils::GetStringArray(a.RootElement(), "node", strarray));
159
160 EXPECT_STREQ("some string1", strarray.at(0).c_str());
161 EXPECT_STREQ("some string2", strarray.at(1).c_str());
162 EXPECT_STREQ("some string3", strarray.at(2).c_str());
163 EXPECT_STREQ("some string4", strarray.at(3).c_str());
164 EXPECT_STREQ("some string5", strarray.at(4).c_str());
165}
166
167TEST(TestXMLUtils, GetPath)
168{
169 CXBMCTinyXML a, b;
170 std::string ref, val;
171
172 a.Parse(std::string("<root><node urlencoded=\"yes\">special://xbmc/</node></root>"));
173 EXPECT_TRUE(XMLUtils::GetPath(a.RootElement(), "node", val));
174
175 ref = "special://xbmc/";
176 EXPECT_STREQ(ref.c_str(), val.c_str());
177
178 val.clear();
179 b.Parse(std::string("<root><node>special://xbmcbin/</node></root>"));
180 EXPECT_TRUE(XMLUtils::GetPath(b.RootElement(), "node", val));
181
182 ref = "special://xbmcbin/";
183 EXPECT_STREQ(ref.c_str(), val.c_str());
184}
185
186TEST(TestXMLUtils, GetDate)
187{
188 CXBMCTinyXML a;
189 CDateTime ref, val;
190
191 a.Parse(std::string("<root><node>2012-07-08</node></root>"));
192 EXPECT_TRUE(XMLUtils::GetDate(a.RootElement(), "node", val));
193 ref.SetDate(2012, 7, 8);
194 EXPECT_TRUE(ref == val);
195}
196
197TEST(TestXMLUtils, GetDateTime)
198{
199 CXBMCTinyXML a;
200 CDateTime ref, val;
201
202 a.Parse(std::string("<root><node>2012-07-08 01:02:03</node></root>"));
203 EXPECT_TRUE(XMLUtils::GetDateTime(a.RootElement(), "node", val));
204 ref.SetDateTime(2012, 7, 8, 1, 2, 3);
205 EXPECT_TRUE(ref == val);
206}
207
208TEST(TestXMLUtils, SetString)
209{
210 CXBMCTinyXML a;
211 std::string ref, val;
212
213 a.Parse(std::string("<root></root>"));
214 XMLUtils::SetString(a.RootElement(), "node", "some string");
215 EXPECT_TRUE(XMLUtils::GetString(a.RootElement(), "node", val));
216
217 ref = "some string";
218 EXPECT_STREQ(ref.c_str(), val.c_str());
219}
220
221TEST(TestXMLUtils, SetAdditiveString)
222{
223 CXBMCTinyXML a;
224 std::string ref, val;
225
226 a.Parse(std::string("<root></root>"));
227 XMLUtils::SetAdditiveString(a.RootElement(), "node", ",",
228 "some string1,some string2,some string3,some string4,some string5");
229 EXPECT_TRUE(XMLUtils::GetAdditiveString(a.RootElement(), "node", ",", val));
230
231 ref = "some string1,some string2,some string3,some string4,some string5";
232 EXPECT_STREQ(ref.c_str(), val.c_str());
233}
234
235TEST(TestXMLUtils, SetStringArray)
236{
237 CXBMCTinyXML a;
238 std::vector<std::string> strarray;
239 strarray.emplace_back("some string1");
240 strarray.emplace_back("some string2");
241 strarray.emplace_back("some string3");
242 strarray.emplace_back("some string4");
243 strarray.emplace_back("some string5");
244
245 a.Parse(std::string("<root></root>"));
246 XMLUtils::SetStringArray(a.RootElement(), "node", strarray);
247 EXPECT_TRUE(XMLUtils::GetStringArray(a.RootElement(), "node", strarray));
248
249 EXPECT_STREQ("some string1", strarray.at(0).c_str());
250 EXPECT_STREQ("some string2", strarray.at(1).c_str());
251 EXPECT_STREQ("some string3", strarray.at(2).c_str());
252 EXPECT_STREQ("some string4", strarray.at(3).c_str());
253 EXPECT_STREQ("some string5", strarray.at(4).c_str());
254}
255
256TEST(TestXMLUtils, SetInt)
257{
258 CXBMCTinyXML a;
259 int ref, val;
260
261 a.Parse(std::string("<root></root>"));
262 XMLUtils::SetInt(a.RootElement(), "node", 1000);
263 EXPECT_TRUE(XMLUtils::GetInt(a.RootElement(), "node", val));
264
265 ref = 1000;
266 EXPECT_EQ(ref, val);
267}
268
269TEST(TestXMLUtils, SetFloat)
270{
271 CXBMCTinyXML a;
272 float ref, val;
273
274 a.Parse(std::string("<root></root>"));
275 XMLUtils::SetFloat(a.RootElement(), "node", 1000.1f);
276 EXPECT_TRUE(XMLUtils::GetFloat(a.RootElement(), "node", val));
277
278 ref = 1000.1f;
279 EXPECT_EQ(ref, val);
280}
281
282TEST(TestXMLUtils, SetBoolean)
283{
284 CXBMCTinyXML a;
285 bool ref, val;
286
287 a.Parse(std::string("<root></root>"));
288 XMLUtils::SetBoolean(a.RootElement(), "node", true);
289 EXPECT_TRUE(XMLUtils::GetBoolean(a.RootElement(), "node", val));
290
291 ref = true;
292 EXPECT_EQ(ref, val);
293}
294
295TEST(TestXMLUtils, SetHex)
296{
297 CXBMCTinyXML a;
298 uint32_t ref, val;
299
300 a.Parse(std::string("<root></root>"));
301 XMLUtils::SetHex(a.RootElement(), "node", 0xFF);
302 EXPECT_TRUE(XMLUtils::GetHex(a.RootElement(), "node", val));
303
304 ref = 0xFF;
305 EXPECT_EQ(ref, val);
306}
307
308TEST(TestXMLUtils, SetPath)
309{
310 CXBMCTinyXML a;
311 std::string ref, val;
312
313 a.Parse(std::string("<root></root>"));
314 XMLUtils::SetPath(a.RootElement(), "node", "special://xbmc/");
315 EXPECT_TRUE(XMLUtils::GetPath(a.RootElement(), "node", val));
316
317 ref = "special://xbmc/";
318 EXPECT_STREQ(ref.c_str(), val.c_str());
319}
320
321TEST(TestXMLUtils, SetLong)
322{
323 CXBMCTinyXML a;
324 long ref, val;
325
326 a.Parse(std::string("<root></root>"));
327 XMLUtils::SetLong(a.RootElement(), "node", 1000);
328 EXPECT_TRUE(XMLUtils::GetLong(a.RootElement(), "node", val));
329
330 ref = 1000;
331 EXPECT_EQ(ref, val);
332}
333
334TEST(TestXMLUtils, SetDate)
335{
336 CXBMCTinyXML a;
337 CDateTime ref, val;
338
339 a.Parse(std::string("<root></root>"));
340 ref.SetDate(2012, 7, 8);
341 XMLUtils::SetDate(a.RootElement(), "node", ref);
342 EXPECT_TRUE(XMLUtils::GetDate(a.RootElement(), "node", val));
343 EXPECT_TRUE(ref == val);
344}
345
346TEST(TestXMLUtils, SetDateTime)
347{
348 CXBMCTinyXML a;
349 CDateTime ref, val;
350
351 a.Parse(std::string("<root></root>"));
352 ref.SetDateTime(2012, 7, 8, 1, 2, 3);
353 XMLUtils::SetDateTime(a.RootElement(), "node", ref);
354 EXPECT_TRUE(XMLUtils::GetDateTime(a.RootElement(), "node", val));
355 EXPECT_TRUE(ref == val);
356}
diff --git a/xbmc/utils/test/Testlog.cpp b/xbmc/utils/test/Testlog.cpp
new file mode 100644
index 0000000..7405c02
--- /dev/null
+++ b/xbmc/utils/test/Testlog.cpp
@@ -0,0 +1,102 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "CompileInfo.h"
10#include "ServiceBroker.h"
11#include "filesystem/File.h"
12#include "filesystem/SpecialProtocol.h"
13#include "test/TestUtils.h"
14#include "utils/RegExp.h"
15#include "utils/StringUtils.h"
16#include "utils/log.h"
17
18#include <stdlib.h>
19
20#include <gtest/gtest.h>
21
22class Testlog : public testing::Test
23{
24protected:
25 Testlog() = default;
26 ~Testlog() override { CServiceBroker::GetLogging().Uninitialize(); }
27};
28
29TEST_F(Testlog, Log)
30{
31 std::string logfile, logstring;
32 char buf[100];
33 ssize_t bytesread;
34 XFILE::CFile file;
35 CRegExp regex;
36
37 std::string appName = CCompileInfo::GetAppName();
38 StringUtils::ToLower(appName);
39 logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log";
40 CServiceBroker::GetLogging().Initialize(
41 CSpecialProtocol::TranslatePath("special://temp/").c_str());
42 EXPECT_TRUE(XFILE::CFile::Exists(logfile));
43
44 CLog::Log(LOGDEBUG, "debug log message");
45 CLog::Log(LOGINFO, "info log message");
46 CLog::Log(LOGNOTICE, "notice log message");
47 CLog::Log(LOGWARNING, "warning log message");
48 CLog::Log(LOGERROR, "error log message");
49 CLog::Log(LOGSEVERE, "severe log message");
50 CLog::Log(LOGFATAL, "fatal log message");
51 CLog::Log(LOGNONE, "none type log message");
52 CServiceBroker::GetLogging().Uninitialize();
53
54 EXPECT_TRUE(file.Open(logfile));
55 while ((bytesread = file.Read(buf, sizeof(buf) - 1)) > 0)
56 {
57 buf[bytesread] = '\0';
58 logstring.append(buf);
59 }
60 file.Close();
61 EXPECT_FALSE(logstring.empty());
62
63 EXPECT_STREQ("\xEF\xBB\xBF", logstring.substr(0, 3).c_str());
64
65 EXPECT_TRUE(regex.RegComp(".*DEBUG <general>: debug log message.*"));
66 EXPECT_GE(regex.RegFind(logstring), 0);
67 EXPECT_TRUE(regex.RegComp(".*INFO <general>: info log message.*"));
68 EXPECT_GE(regex.RegFind(logstring), 0);
69 EXPECT_TRUE(regex.RegComp(".*INFO <general>: notice log message.*"));
70 EXPECT_GE(regex.RegFind(logstring), 0);
71 EXPECT_TRUE(regex.RegComp(".*WARNING <general>: warning log message.*"));
72 EXPECT_GE(regex.RegFind(logstring), 0);
73 EXPECT_TRUE(regex.RegComp(".*ERROR <general>: error log message.*"));
74 EXPECT_GE(regex.RegFind(logstring), 0);
75 EXPECT_TRUE(regex.RegComp(".*FATAL <general>: severe log message.*"));
76 EXPECT_GE(regex.RegFind(logstring), 0);
77 EXPECT_TRUE(regex.RegComp(".*FATAL <general>: fatal log message.*"));
78 EXPECT_GE(regex.RegFind(logstring), 0);
79 EXPECT_TRUE(regex.RegComp(".*OFF <general>: none type log message.*"));
80 EXPECT_GE(regex.RegFind(logstring), 0);
81
82 EXPECT_TRUE(XFILE::CFile::Delete(logfile));
83}
84
85TEST_F(Testlog, SetLogLevel)
86{
87 std::string logfile;
88
89 std::string appName = CCompileInfo::GetAppName();
90 StringUtils::ToLower(appName);
91 logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log";
92 CServiceBroker::GetLogging().Initialize(
93 CSpecialProtocol::TranslatePath("special://temp/").c_str());
94 EXPECT_TRUE(XFILE::CFile::Exists(logfile));
95
96 EXPECT_EQ(LOG_LEVEL_DEBUG, CServiceBroker::GetLogging().GetLogLevel());
97 CServiceBroker::GetLogging().SetLogLevel(LOG_LEVEL_MAX);
98 EXPECT_EQ(LOG_LEVEL_MAX, CServiceBroker::GetLogging().GetLogLevel());
99
100 CServiceBroker::GetLogging().Uninitialize();
101 EXPECT_TRUE(XFILE::CFile::Delete(logfile));
102}
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 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "utils/rfft.h"
10
11#include <gtest/gtest.h>
12
13#if defined(TARGET_WINDOWS) && !defined(_USE_MATH_DEFINES)
14#define _USE_MATH_DEFINES
15#endif
16
17#include <math.h>
18
19
20TEST(TestRFFT, SimpleSignal)
21{
22 const int size = 32;
23 const int freq1 = 5;
24 const int freq2[] = {1,7};
25 std::vector<float> input(2*size);
26 std::vector<float> output(size);
27 for (size_t i=0;i<size;++i)
28 {
29 input[2*i] = cos(freq1*2.0*M_PI*i/size);
30 input[2*i+1] = cos(freq2[0]*2.0*M_PI*i/size)+cos(freq2[1]*2.0*M_PI*i/size);
31 }
32 RFFT transform(size, false);
33
34 transform.calc(&input[0], &output[0]);
35
36 for (int i=0;i<size/2;++i)
37 {
38 EXPECT_NEAR(output[2*i],(i==freq1?1.0:0.0), 1e-7);
39 EXPECT_NEAR(output[2*i+1], ((i==freq2[0]||i==freq2[1])?1.0:0.0), 1e-7);
40 }
41}
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 @@
1# Kodi Media Center language file
2msgid ""
3msgstr ""
4"Project-Id-Version: XBMC Main\n"
5"Report-Msgid-Bugs-To: http://trac.xbmc.org/\n"
6"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
7"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
8"Last-Translator: Kodi Translation Team\n"
9"Language-Team: Spanish (http://www.transifex.com/projects/p/xbmc-main/language/es/)\n"
10"MIME-Version: 1.0\n"
11"Content-Type: text/plain; charset=UTF-8\n"
12"Content-Transfer-Encoding: 8bit\n"
13"Language: es\n"
14"Plural-Forms: nplurals=2; plural=(n != 1);\n"
15
16msgctxt "#0"
17msgid "Programs"
18msgstr "Programas"
19
20msgctxt "#1"
21msgid "Pictures"
22msgstr "Imágenes"
23
24msgctxt "#2"
25msgid "Music"
26msgstr "Música"