diff options
| author | manuel <manuel@mausz.at> | 2020-10-19 00:52:24 +0200 |
|---|---|---|
| committer | manuel <manuel@mausz.at> | 2020-10-19 00:52:24 +0200 |
| commit | be933ef2241d79558f91796cc5b3a161f72ebf9c (patch) | |
| tree | fe3ab2f130e20c99001f2d7a81d610c78c96a3f4 /xbmc/utils | |
| parent | 5f8335c1e49ce108ef3481863833c98efa00411b (diff) | |
| download | kodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.tar.gz kodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.tar.bz2 kodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.zip | |
sync with upstream
Diffstat (limited to 'xbmc/utils')
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 | |||
| 15 | using namespace Actor; | ||
| 16 | |||
| 17 | void 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 | |||
| 40 | bool 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 | |||
| 76 | Protocol::~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 | |||
| 88 | Message *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 | |||
| 113 | void Protocol::ReturnMessage(Message *msg) | ||
| 114 | { | ||
| 115 | CSingleLock lock(criticalSection); | ||
| 116 | |||
| 117 | freeMessageQueue.push(msg); | ||
| 118 | } | ||
| 119 | |||
| 120 | bool 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 | |||
| 152 | bool 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 | |||
| 174 | bool 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 | |||
| 206 | bool 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 | |||
| 228 | bool 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 | |||
| 260 | bool 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 | |||
| 291 | bool 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 | |||
| 304 | bool 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 | |||
| 318 | void Protocol::Purge() | ||
| 319 | { | ||
| 320 | Message *msg; | ||
| 321 | |||
| 322 | while (ReceiveInMessage(&msg)) | ||
| 323 | msg->Release(); | ||
| 324 | |||
| 325 | while (ReceiveOutMessage(&msg)) | ||
| 326 | msg->Release(); | ||
| 327 | } | ||
| 328 | |||
| 329 | void 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 | |||
| 351 | void 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 | |||
| 18 | class CEvent; | ||
| 19 | |||
| 20 | namespace Actor | ||
| 21 | { | ||
| 22 | |||
| 23 | class CPayloadWrapBase | ||
| 24 | { | ||
| 25 | public: | ||
| 26 | virtual ~CPayloadWrapBase() = default; | ||
| 27 | }; | ||
| 28 | |||
| 29 | template<typename Payload> | ||
| 30 | class CPayloadWrap : public CPayloadWrapBase | ||
| 31 | { | ||
| 32 | public: | ||
| 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();}; | ||
| 37 | protected: | ||
| 38 | std::unique_ptr<Payload> m_pPayload; | ||
| 39 | }; | ||
| 40 | |||
| 41 | class Protocol; | ||
| 42 | |||
| 43 | class Message | ||
| 44 | { | ||
| 45 | friend class Protocol; | ||
| 46 | |||
| 47 | static constexpr size_t MSG_INTERNAL_BUFFER_SIZE = 32; | ||
| 48 | |||
| 49 | public: | ||
| 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 | |||
| 66 | private: | ||
| 67 | explicit Message(Protocol &_origin) noexcept | ||
| 68 | :origin(_origin) {} | ||
| 69 | }; | ||
| 70 | |||
| 71 | class Protocol | ||
| 72 | { | ||
| 73 | public: | ||
| 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 | |||
| 105 | protected: | ||
| 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 | |||
| 23 | using namespace KODI::MESSAGING; | ||
| 24 | |||
| 25 | CAlarmClock::CAlarmClock() : CThread("AlarmClock") | ||
| 26 | { | ||
| 27 | } | ||
| 28 | |||
| 29 | CAlarmClock::~CAlarmClock() = default; | ||
| 30 | |||
| 31 | void 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 | |||
| 74 | void 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 | |||
| 127 | void 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 | |||
| 18 | struct SAlarmClockEvent | ||
| 19 | { | ||
| 20 | CStopWatch watch; | ||
| 21 | double m_fSecs; | ||
| 22 | std::string m_strCommand; | ||
| 23 | bool m_loop; | ||
| 24 | }; | ||
| 25 | |||
| 26 | class CAlarmClock : public CThread | ||
| 27 | { | ||
| 28 | public: | ||
| 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; | ||
| 59 | private: | ||
| 60 | std::map<std::string,SAlarmClockEvent> m_event; | ||
| 61 | CCriticalSection m_events; | ||
| 62 | |||
| 63 | bool m_bIsRunning = false; | ||
| 64 | }; | ||
| 65 | |||
| 66 | extern 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 | |||
| 19 | bool 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 | |||
| 43 | void 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 | |||
| 13 | bool IsAliasShortcut(const std::string& path, bool isdirectory); | ||
| 14 | void 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 | |||
| 25 | using 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 | |||
| 32 | CArchive::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 | |||
| 51 | CArchive::~CArchive() | ||
| 52 | { | ||
| 53 | FlushBuffer(); | ||
| 54 | } | ||
| 55 | |||
| 56 | void CArchive::Close() | ||
| 57 | { | ||
| 58 | FlushBuffer(); | ||
| 59 | } | ||
| 60 | |||
| 61 | bool CArchive::IsLoading() const | ||
| 62 | { | ||
| 63 | return (m_iMode == load); | ||
| 64 | } | ||
| 65 | |||
| 66 | bool CArchive::IsStoring() const | ||
| 67 | { | ||
| 68 | return (m_iMode == store); | ||
| 69 | } | ||
| 70 | |||
| 71 | CArchive& CArchive::operator<<(float f) | ||
| 72 | { | ||
| 73 | return streamout(&f, sizeof(f)); | ||
| 74 | } | ||
| 75 | |||
| 76 | CArchive& CArchive::operator<<(double d) | ||
| 77 | { | ||
| 78 | return streamout(&d, sizeof(d)); | ||
| 79 | } | ||
| 80 | |||
| 81 | CArchive& CArchive::operator<<(short int s) | ||
| 82 | { | ||
| 83 | return streamout(&s, sizeof(s)); | ||
| 84 | } | ||
| 85 | |||
| 86 | CArchive& CArchive::operator<<(unsigned short int us) | ||
| 87 | { | ||
| 88 | return streamout(&us, sizeof(us)); | ||
| 89 | } | ||
| 90 | |||
| 91 | CArchive& CArchive::operator<<(int i) | ||
| 92 | { | ||
| 93 | return streamout(&i, sizeof(i)); | ||
| 94 | } | ||
| 95 | |||
| 96 | CArchive& CArchive::operator<<(unsigned int ui) | ||
| 97 | { | ||
| 98 | return streamout(&ui, sizeof(ui)); | ||
| 99 | } | ||
| 100 | |||
| 101 | CArchive& CArchive::operator<<(long int l) | ||
| 102 | { | ||
| 103 | return streamout(&l, sizeof(l)); | ||
| 104 | } | ||
| 105 | |||
| 106 | CArchive& CArchive::operator<<(unsigned long int ul) | ||
| 107 | { | ||
| 108 | return streamout(&ul, sizeof(ul)); | ||
| 109 | } | ||
| 110 | |||
| 111 | CArchive& CArchive::operator<<(long long int ll) | ||
| 112 | { | ||
| 113 | return streamout(&ll, sizeof(ll)); | ||
| 114 | } | ||
| 115 | |||
| 116 | CArchive& CArchive::operator<<(unsigned long long int ull) | ||
| 117 | { | ||
| 118 | return streamout(&ull, sizeof(ull)); | ||
| 119 | } | ||
| 120 | |||
| 121 | CArchive& CArchive::operator<<(bool b) | ||
| 122 | { | ||
| 123 | return streamout(&b, sizeof(b)); | ||
| 124 | } | ||
| 125 | |||
| 126 | CArchive& CArchive::operator<<(char c) | ||
| 127 | { | ||
| 128 | return streamout(&c, sizeof(c)); | ||
| 129 | } | ||
| 130 | |||
| 131 | CArchive& 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 | |||
| 142 | CArchive& 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 | |||
| 154 | CArchive& CArchive::operator<<(const KODI::TIME::SystemTime& time) | ||
| 155 | { | ||
| 156 | return streamout(&time, sizeof(KODI::TIME::SystemTime)); | ||
| 157 | } | ||
| 158 | |||
| 159 | CArchive& CArchive::operator<<(IArchivable& obj) | ||
| 160 | { | ||
| 161 | obj.Archive(*this); | ||
| 162 | |||
| 163 | return *this; | ||
| 164 | } | ||
| 165 | |||
| 166 | CArchive& 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 | |||
| 211 | CArchive& 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 | |||
| 224 | CArchive& 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 | |||
| 237 | CArchive& 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 | |||
| 252 | CArchive& 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 | |||
| 267 | CArchive& CArchive::operator>>(KODI::TIME::SystemTime& time) | ||
| 268 | { | ||
| 269 | return streamin(&time, sizeof(KODI::TIME::SystemTime)); | ||
| 270 | } | ||
| 271 | |||
| 272 | CArchive& CArchive::operator>>(IArchivable& obj) | ||
| 273 | { | ||
| 274 | obj.Archive(*this); | ||
| 275 | |||
| 276 | return *this; | ||
| 277 | } | ||
| 278 | |||
| 279 | CArchive& 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 | |||
| 364 | CArchive& 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 | |||
| 379 | CArchive& 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 | |||
| 394 | void 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 | |||
| 408 | CArchive &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 | |||
| 423 | void 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 | |||
| 436 | CArchive &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 | |||
| 19 | namespace XFILE | ||
| 20 | { | ||
| 21 | class CFile; | ||
| 22 | } | ||
| 23 | class CVariant; | ||
| 24 | class IArchivable; | ||
| 25 | |||
| 26 | class CArchive | ||
| 27 | { | ||
| 28 | public: | ||
| 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 | |||
| 139 | protected: | ||
| 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 | |||
| 177 | private: | ||
| 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 | |||
| 13 | const std::string Base64::m_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
| 14 | "abcdefghijklmnopqrstuvwxyz" | ||
| 15 | "0123456789+/"; | ||
| 16 | |||
| 17 | void 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 | |||
| 50 | std::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 | |||
| 58 | void Base64::Encode(const std::string &input, std::string &output) | ||
| 59 | { | ||
| 60 | Encode(input.c_str(), input.size(), output); | ||
| 61 | } | ||
| 62 | |||
| 63 | std::string Base64::Encode(const std::string &input) | ||
| 64 | { | ||
| 65 | std::string output; | ||
| 66 | Encode(input, output); | ||
| 67 | |||
| 68 | return output; | ||
| 69 | } | ||
| 70 | |||
| 71 | void 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 | |||
| 105 | std::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 | |||
| 113 | void 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 | |||
| 122 | std::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 | |||
| 13 | class Base64 | ||
| 14 | { | ||
| 15 | public: | ||
| 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 | |||
| 25 | private: | ||
| 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 | |||
| 23 | enum { | ||
| 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 | |||
| 40 | enum { | ||
| 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 | |||
| 68 | enum { | ||
| 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 | */ | ||
| 104 | static 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 | |||
| 114 | static 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; | ||
| 129 | next_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 | |||
| 164 | static 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 | ||
| 170 | static 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 | |||
| 180 | static 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 | |||
| 221 | static 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 | |||
| 229 | static 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 | ///////////////////////////////////////////////////////////////////////////////////////////// | ||
| 259 | CBitstreamParser::CBitstreamParser() = default; | ||
| 260 | |||
| 261 | CBitstreamParser::~CBitstreamParser() = default; | ||
| 262 | |||
| 263 | void CBitstreamParser::Close() | ||
| 264 | { | ||
| 265 | } | ||
| 266 | |||
| 267 | bool 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 | ///////////////////////////////////////////////////////////////////////////////////////////// | ||
| 312 | CBitstreamConverter::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 | |||
| 328 | CBitstreamConverter::~CBitstreamConverter() | ||
| 329 | { | ||
| 330 | Close(); | ||
| 331 | } | ||
| 332 | |||
| 333 | bool 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 | |||
| 492 | void 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 | |||
| 513 | bool 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 | |||
| 623 | uint8_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 | |||
| 631 | int 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 | |||
| 639 | uint8_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 | } | ||
| 646 | int CBitstreamConverter::GetExtraSize() const | ||
| 647 | { | ||
| 648 | if(m_convert_bitstream) | ||
| 649 | return m_sps_pps_context.size; | ||
| 650 | else | ||
| 651 | return m_extrasize; | ||
| 652 | } | ||
| 653 | |||
| 654 | void CBitstreamConverter::ResetStartDecode(void) | ||
| 655 | { | ||
| 656 | m_start_decode = false; | ||
| 657 | } | ||
| 658 | |||
| 659 | bool CBitstreamConverter::CanStartDecode() const | ||
| 660 | { | ||
| 661 | return m_start_decode; | ||
| 662 | } | ||
| 663 | |||
| 664 | bool 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 | |||
| 721 | pps: | ||
| 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 | |||
| 746 | bool 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 | |||
| 829 | bool 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 | |||
| 844 | bool 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 | |||
| 870 | bool 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 | |||
| 951 | fail: | ||
| 952 | av_free(*poutbuf), *poutbuf = NULL; | ||
| 953 | *poutbuf_size = 0; | ||
| 954 | return false; | ||
| 955 | } | ||
| 956 | |||
| 957 | void 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 | |||
| 989 | int 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 | |||
| 1012 | int 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 | |||
| 1026 | int 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 | ///////////////////////////////////////////////////////////////////////////////////////////// | ||
| 1094 | bool 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 | |||
| 13 | extern "C" { | ||
| 14 | #include <libavutil/avutil.h> | ||
| 15 | #include <libavformat/avformat.h> | ||
| 16 | #include <libavfilter/avfilter.h> | ||
| 17 | #include <libavcodec/avcodec.h> | ||
| 18 | } | ||
| 19 | |||
| 20 | typedef struct | ||
| 21 | { | ||
| 22 | const uint8_t *data; | ||
| 23 | const uint8_t *end; | ||
| 24 | int head; | ||
| 25 | uint64_t cache; | ||
| 26 | } nal_bitstream; | ||
| 27 | |||
| 28 | typedef 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 | |||
| 38 | typedef 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 | |||
| 72 | class CBitstreamParser | ||
| 73 | { | ||
| 74 | public: | ||
| 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 | |||
| 83 | class CBitstreamConverter | ||
| 84 | { | ||
| 85 | public: | ||
| 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 | |||
| 102 | protected: | ||
| 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 | |||
| 11 | CBitstreamReader::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 | |||
| 20 | uint32_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 | |||
| 31 | void 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 | |||
| 41 | uint32_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 | |||
| 68 | const 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 | |||
| 13 | class CBitstreamReader | ||
| 14 | { | ||
| 15 | public: | ||
| 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 | |||
| 21 | private: | ||
| 22 | const uint8_t *buffer, *start; | ||
| 23 | int offbits, length, oflow; | ||
| 24 | }; | ||
| 25 | |||
| 26 | const 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 | */ | ||
| 40 | constexpr uint32_t BS_RB24(const uint8_t* x) | ||
| 41 | { | ||
| 42 | return (x[0] << 16) | (x[1] << 8) | x[2]; | ||
| 43 | } | ||
| 44 | |||
| 45 | constexpr 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 | |||
| 13 | int64_t BitstreamStats::m_tmFreq; | ||
| 14 | |||
| 15 | BitstreamStats::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 | |||
| 29 | void BitstreamStats::AddSampleBytes(unsigned int nBytes) | ||
| 30 | { | ||
| 31 | AddSampleBits(nBytes*8); | ||
| 32 | } | ||
| 33 | |||
| 34 | void BitstreamStats::AddSampleBits(unsigned int nBits) | ||
| 35 | { | ||
| 36 | m_nBitCount += nBits; | ||
| 37 | if (m_nBitCount >= m_nEstimatedBitrate) | ||
| 38 | CalculateBitrate(); | ||
| 39 | } | ||
| 40 | |||
| 41 | void BitstreamStats::Start() | ||
| 42 | { | ||
| 43 | m_nBitCount = 0; | ||
| 44 | m_tmStart = CurrentHostCounter(); | ||
| 45 | } | ||
| 46 | |||
| 47 | void 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 | |||
| 13 | class BitstreamStats final | ||
| 14 | { | ||
| 15 | public: | ||
| 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 | |||
| 31 | private: | ||
| 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 | |||
| 11 | CBitstreamWriter::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 | |||
| 20 | void 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 | |||
| 79 | void 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 | |||
| 89 | void 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 | |||
| 13 | class CBitstreamWriter | ||
| 14 | { | ||
| 15 | public: | ||
| 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 | |||
| 21 | private: | ||
| 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 | |||
| 15 | bool 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 | |||
| 43 | bool 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 | |||
| 108 | bool 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 | |||
| 17 | typedef enum { | ||
| 18 | BooleanLogicOperationOr = 0, | ||
| 19 | BooleanLogicOperationAnd | ||
| 20 | } BooleanLogicOperation; | ||
| 21 | |||
| 22 | class CBooleanLogicValue : public IXmlDeserializable | ||
| 23 | { | ||
| 24 | public: | ||
| 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 | |||
| 39 | protected: | ||
| 40 | std::string m_value; | ||
| 41 | bool m_negated; | ||
| 42 | }; | ||
| 43 | |||
| 44 | typedef std::shared_ptr<CBooleanLogicValue> CBooleanLogicValuePtr; | ||
| 45 | typedef std::vector<CBooleanLogicValuePtr> CBooleanLogicValues; | ||
| 46 | |||
| 47 | class CBooleanLogicOperation; | ||
| 48 | typedef std::shared_ptr<CBooleanLogicOperation> CBooleanLogicOperationPtr; | ||
| 49 | typedef std::vector<CBooleanLogicOperationPtr> CBooleanLogicOperations; | ||
| 50 | |||
| 51 | class CBooleanLogicOperation : public IXmlDeserializable | ||
| 52 | { | ||
| 53 | public: | ||
| 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 | |||
| 67 | protected: | ||
| 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 | |||
| 76 | class CBooleanLogic : public IXmlDeserializable | ||
| 77 | { | ||
| 78 | protected: | ||
| 79 | /* make sure nobody deletes a pointer to this class */ | ||
| 80 | ~CBooleanLogic() override = default; | ||
| 81 | |||
| 82 | public: | ||
| 83 | bool Deserialize(const TiXmlNode *node) override; | ||
| 84 | |||
| 85 | const CBooleanLogicOperationPtr& Get() const { return m_operation; } | ||
| 86 | CBooleanLogicOperationPtr Get() { return m_operation; } | ||
| 87 | |||
| 88 | protected: | ||
| 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 | |||
| 19 | std::unique_ptr<CBufferObject> CBufferObject::GetBufferObject(bool needsCreateBySize) | ||
| 20 | { | ||
| 21 | return CBufferObjectFactory::CreateBufferObject(needsCreateBySize); | ||
| 22 | } | ||
| 23 | |||
| 24 | int CBufferObject::GetFd() | ||
| 25 | { | ||
| 26 | return m_fd; | ||
| 27 | } | ||
| 28 | |||
| 29 | uint32_t CBufferObject::GetStride() | ||
| 30 | { | ||
| 31 | return m_stride; | ||
| 32 | } | ||
| 33 | |||
| 34 | uint64_t CBufferObject::GetModifier() | ||
| 35 | { | ||
| 36 | return 0; // linear | ||
| 37 | } | ||
| 38 | |||
| 39 | void 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 | |||
| 51 | void 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 | */ | ||
| 21 | class CBufferObject : public IBufferObject | ||
| 22 | { | ||
| 23 | public: | ||
| 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 | |||
| 40 | protected: | ||
| 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 | |||
| 11 | std::list<std::function<std::unique_ptr<CBufferObject>()>> CBufferObjectFactory::m_bufferObjects; | ||
| 12 | |||
| 13 | std::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 | |||
| 33 | void CBufferObjectFactory::RegisterBufferObject( | ||
| 34 | std::function<std::unique_ptr<CBufferObject>()> createFunc) | ||
| 35 | { | ||
| 36 | m_bufferObjects.emplace_front(createFunc); | ||
| 37 | } | ||
| 38 | |||
| 39 | void 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 | */ | ||
| 21 | class CBufferObjectFactory | ||
| 22 | { | ||
| 23 | public: | ||
| 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 | |||
| 46 | protected: | ||
| 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 @@ | |||
| 1 | set(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 | |||
| 76 | set(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 | |||
| 171 | if(XSLT_FOUND) | ||
| 172 | list(APPEND SOURCES XSLTUtils.cpp) | ||
| 173 | list(APPEND HEADERS XSLTUtils.h) | ||
| 174 | endif() | ||
| 175 | if(EGL_FOUND) | ||
| 176 | list(APPEND SOURCES EGLUtils.cpp | ||
| 177 | EGLFence.cpp) | ||
| 178 | list(APPEND HEADERS EGLUtils.h | ||
| 179 | EGLFence.h) | ||
| 180 | endif() | ||
| 181 | |||
| 182 | # The large map trips the clang optimizer | ||
| 183 | if(CMAKE_CXX_COMPILER_ID STREQUAL Clang) | ||
| 184 | set_source_files_properties(Mime.cpp PROPERTIES COMPILE_FLAGS -O0) | ||
| 185 | endif() | ||
| 186 | |||
| 187 | if(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) | ||
| 190 | endif() | ||
| 191 | |||
| 192 | if(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() | ||
| 219 | endif() | ||
| 220 | |||
| 221 | core_add_library(utils) | ||
| 222 | |||
| 223 | if(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() | ||
| 227 | endif() | ||
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 | |||
| 13 | bool 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 | |||
| 24 | const 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 | |||
| 37 | std::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 | |||
| 18 | enum 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 | |||
| 34 | struct 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 | |||
| 43 | class CCPUInfo | ||
| 44 | { | ||
| 45 | public: | ||
| 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 | |||
| 97 | protected: | ||
| 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 | |||
| 73 | enum SpecialCharset | ||
| 74 | { | ||
| 75 | NotSpecialCharset = 0, | ||
| 76 | SystemCharset, | ||
| 77 | UserCharset /* locale.charset */, | ||
| 78 | SubtitleCharset /* subtitles.charset */, | ||
| 79 | }; | ||
| 80 | |||
| 81 | class CConverterType : public CCriticalSection | ||
| 82 | { | ||
| 83 | public: | ||
| 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 | |||
| 99 | private: | ||
| 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 | |||
| 110 | CConverterType::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 | |||
| 120 | CConverterType::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 | |||
| 130 | CConverterType::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 | |||
| 140 | CConverterType::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 | |||
| 150 | CConverterType::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 | |||
| 160 | CConverterType::~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 | |||
| 168 | iconv_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 | |||
| 191 | void 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 | |||
| 207 | void 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 | |||
| 226 | std::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 | |||
| 242 | enum 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 */ | ||
| 266 | class CCharsetConverter::CInnerConverter | ||
| 267 | { | ||
| 268 | public: | ||
| 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 */ | ||
| 288 | const int CCharsetConverter::m_Utf8CharMinSize = 1; | ||
| 289 | const int CCharsetConverter::m_Utf8CharMaxSize = 4; | ||
| 290 | |||
| 291 | CConverterType 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 | |||
| 311 | CCriticalSection CCharsetConverter::CInnerConverter::m_critSectionFriBiDi; | ||
| 312 | |||
| 313 | template<class INPUT,class OUTPUT> | ||
| 314 | bool 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 | |||
| 329 | template<class INPUT,class OUTPUT> | ||
| 330 | bool 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 */ | ||
| 352 | struct 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 | |||
| 363 | template<class INPUT,class OUTPUT> | ||
| 364 | bool 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 | |||
| 469 | bool 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 | |||
| 534 | static 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 | |||
| 566 | CCharsetConverter::CCharsetConverter() = default; | ||
| 567 | |||
| 568 | void 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 | |||
| 580 | void CCharsetConverter::clear() | ||
| 581 | { | ||
| 582 | } | ||
| 583 | |||
| 584 | std::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 | |||
| 593 | std::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 | |||
| 604 | std::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 | |||
| 615 | void CCharsetConverter::reset(void) | ||
| 616 | { | ||
| 617 | for (CConverterType& conversion : CInnerConverter::m_stdConversion) | ||
| 618 | conversion.Reset(); | ||
| 619 | } | ||
| 620 | |||
| 621 | void CCharsetConverter::resetSystemCharset(void) | ||
| 622 | { | ||
| 623 | CInnerConverter::m_stdConversion[Utf8ToSystem].Reset(); | ||
| 624 | CInnerConverter::m_stdConversion[SystemToUtf8].Reset(); | ||
| 625 | } | ||
| 626 | |||
| 627 | void 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 | |||
| 635 | void CCharsetConverter::resetSubtitleCharset(void) | ||
| 636 | { | ||
| 637 | CInnerConverter::m_stdConversion[SubtitleCharsetToUtf8].Reset(); | ||
| 638 | } | ||
| 639 | |||
| 640 | void CCharsetConverter::reinitCharsetsFromSettings(void) | ||
| 641 | { | ||
| 642 | resetUserCharset(); // this will also reinit Subtitle charsets | ||
| 643 | } | ||
| 644 | |||
| 645 | bool CCharsetConverter::utf8ToUtf32(const std::string& utf8StringSrc, std::u32string& utf32StringDst, bool failOnBadChar /*= true*/) | ||
| 646 | { | ||
| 647 | return CInnerConverter::stdConvert(Utf8ToUtf32, utf8StringSrc, utf32StringDst, failOnBadChar); | ||
| 648 | } | ||
| 649 | |||
| 650 | std::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 | |||
| 657 | bool 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 | |||
| 670 | bool CCharsetConverter::utf32ToUtf8(const std::u32string& utf32StringSrc, std::string& utf8StringDst, bool failOnBadChar /*= true*/) | ||
| 671 | { | ||
| 672 | return CInnerConverter::stdConvert(Utf32ToUtf8, utf32StringSrc, utf8StringDst, failOnBadChar); | ||
| 673 | } | ||
| 674 | |||
| 675 | std::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 | |||
| 682 | bool 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 | |||
| 692 | bool 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 | |||
| 703 | bool 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 | ||
| 714 | bool 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 | |||
| 734 | bool CCharsetConverter::subtitleCharsetToUtf8(const std::string& stringSrc, std::string& utf8StringDst) | ||
| 735 | { | ||
| 736 | return CInnerConverter::stdConvert(SubtitleCharsetToUtf8, stringSrc, utf8StringDst, false); | ||
| 737 | } | ||
| 738 | |||
| 739 | bool 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 | |||
| 745 | bool 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 | |||
| 751 | bool CCharsetConverter::utf8ToStringCharset(const std::string& utf8StringSrc, std::string& stringDst) | ||
| 752 | { | ||
| 753 | return CInnerConverter::stdConvert(Utf8ToUserCharset, utf8StringSrc, stringDst); | ||
| 754 | } | ||
| 755 | |||
| 756 | bool CCharsetConverter::utf8ToStringCharset(std::string& stringSrcDst) | ||
| 757 | { | ||
| 758 | std::string strSrc(stringSrcDst); | ||
| 759 | return utf8ToStringCharset(strSrc, stringSrcDst); | ||
| 760 | } | ||
| 761 | |||
| 762 | bool 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 | |||
| 773 | bool 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 | |||
| 784 | bool 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 | |||
| 789 | bool 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 | |||
| 794 | bool CCharsetConverter::unknownToUTF8(std::string& stringSrcDst) | ||
| 795 | { | ||
| 796 | std::string source(stringSrcDst); | ||
| 797 | return unknownToUTF8(source, stringSrcDst); | ||
| 798 | } | ||
| 799 | |||
| 800 | bool 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 | |||
| 811 | bool CCharsetConverter::wToUTF8(const std::wstring& wStringSrc, std::string& utf8StringDst, bool failOnBadChar /*= false*/) | ||
| 812 | { | ||
| 813 | return CInnerConverter::stdConvert(WtoUtf8, wStringSrc, utf8StringDst, failOnBadChar); | ||
| 814 | } | ||
| 815 | |||
| 816 | bool CCharsetConverter::utf16BEtoUTF8(const std::u16string& utf16StringSrc, std::string& utf8StringDst) | ||
| 817 | { | ||
| 818 | return CInnerConverter::stdConvert(Utf16BEtoUtf8, utf16StringSrc, utf8StringDst); | ||
| 819 | } | ||
| 820 | |||
| 821 | bool CCharsetConverter::utf16LEtoUTF8(const std::u16string& utf16StringSrc, | ||
| 822 | std::string& utf8StringDst) | ||
| 823 | { | ||
| 824 | return CInnerConverter::stdConvert(Utf16LEtoUtf8, utf16StringSrc, utf8StringDst); | ||
| 825 | } | ||
| 826 | |||
| 827 | bool CCharsetConverter::ucs2ToUTF8(const std::u16string& ucs2StringSrc, std::string& utf8StringDst) | ||
| 828 | { | ||
| 829 | return CInnerConverter::stdConvert(Ucs2CharsetToUtf8, ucs2StringSrc,utf8StringDst); | ||
| 830 | } | ||
| 831 | |||
| 832 | bool CCharsetConverter::utf16LEtoW(const std::u16string& utf16String, std::wstring& wString) | ||
| 833 | { | ||
| 834 | return CInnerConverter::stdConvert(Utf16LEtoW, utf16String, wString); | ||
| 835 | } | ||
| 836 | |||
| 837 | bool CCharsetConverter::utf32ToStringCharset(const std::u32string& utf32StringSrc, std::string& stringDst) | ||
| 838 | { | ||
| 839 | return CInnerConverter::stdConvert(Utf32ToUserCharset, utf32StringSrc, stringDst); | ||
| 840 | } | ||
| 841 | |||
| 842 | bool CCharsetConverter::utf8ToSystem(std::string& stringSrcDst, bool failOnBadChar /*= false*/) | ||
| 843 | { | ||
| 844 | std::string strSrc(stringSrcDst); | ||
| 845 | return CInnerConverter::stdConvert(Utf8ToSystem, strSrc, stringSrcDst, failOnBadChar); | ||
| 846 | } | ||
| 847 | |||
| 848 | bool CCharsetConverter::systemToUtf8(const std::string& sysStringSrc, std::string& utf8StringDst, bool failOnBadChar /*= false*/) | ||
| 849 | { | ||
| 850 | return CInnerConverter::stdConvert(SystemToUtf8, sysStringSrc, utf8StringDst, failOnBadChar); | ||
| 851 | } | ||
| 852 | |||
| 853 | bool 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 | |||
| 863 | void 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 | |||
| 18 | class CSetting; | ||
| 19 | struct StringSettingOption; | ||
| 20 | |||
| 21 | class CCharsetConverter : public ISettingCallback | ||
| 22 | { | ||
| 23 | public: | ||
| 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); | ||
| 160 | private: | ||
| 161 | static void resetUserCharset(void); | ||
| 162 | static void resetSubtitleCharset(void); | ||
| 163 | |||
| 164 | static const int m_Utf8CharMinSize, m_Utf8CharMaxSize; | ||
| 165 | class CInnerConverter; | ||
| 166 | }; | ||
| 167 | |||
| 168 | XBMC_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 */ | ||
| 22 | const 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 */ | ||
| 26 | const 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 */ | ||
| 30 | const std::string CCharsetDetection::m_HtmlWhitespaceChars("\x09\x0A\x0C\x0D\x20"); // tab, LF, FF, CR and space | ||
| 31 | |||
| 32 | std::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 | |||
| 59 | bool 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 | |||
| 168 | bool 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 '&'-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 | |||
| 245 | bool 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 | |||
| 267 | bool 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 | |||
| 348 | bool 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 | |||
| 420 | bool 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 | |||
| 439 | std::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 | |||
| 511 | size_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 | |||
| 587 | std::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 | |||
| 633 | inline 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 | |||
| 14 | class CCharsetDetection | ||
| 15 | { | ||
| 16 | public: | ||
| 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 | |||
| 71 | private: | ||
| 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 | |||
| 13 | namespace UTILS | ||
| 14 | { | ||
| 15 | |||
| 16 | typedef uint32_t Color; | ||
| 17 | |||
| 18 | namespace 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 | |||
| 15 | UTILS::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 | |||
| 13 | class 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 | |||
| 13 | uint32_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 | |||
| 81 | Crc32::Crc32() | ||
| 82 | { | ||
| 83 | Reset(); | ||
| 84 | } | ||
| 85 | |||
| 86 | void Crc32::Reset() | ||
| 87 | { | ||
| 88 | m_crc = 0xFFFFFFFF; | ||
| 89 | } | ||
| 90 | |||
| 91 | void 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 | |||
| 97 | uint32_t Crc32::Compute(const std::string& strValue) | ||
| 98 | { | ||
| 99 | Crc32 crc; | ||
| 100 | crc.Compute(strValue.c_str(), strValue.size()); | ||
| 101 | return crc; | ||
| 102 | } | ||
| 103 | |||
| 104 | uint32_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 | |||
| 14 | class Crc32 | ||
| 15 | { | ||
| 16 | public: | ||
| 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 | |||
| 28 | private: | ||
| 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 | |||
| 17 | namespace | ||
| 18 | { | ||
| 19 | |||
| 20 | CCriticalSection* getlock(int index) | ||
| 21 | { | ||
| 22 | return g_cryptThreadingInitializer.GetLock(index); | ||
| 23 | } | ||
| 24 | |||
| 25 | void 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 | |||
| 33 | unsigned 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 | |||
| 43 | void 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 | |||
| 51 | CryptThreadingInitializer::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 | |||
| 60 | CryptThreadingInitializer::~CryptThreadingInitializer() | ||
| 61 | { | ||
| 62 | CSingleLock l(m_locksLock); | ||
| 63 | CRYPTO_set_locking_callback(nullptr); | ||
| 64 | m_locks.clear(); | ||
| 65 | } | ||
| 66 | |||
| 67 | CCriticalSection* 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 | |||
| 79 | unsigned 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 | |||
| 21 | class CryptThreadingInitializer | ||
| 22 | { | ||
| 23 | std::vector<std::unique_ptr<CCriticalSection>> m_locks; | ||
| 24 | CCriticalSection m_locksLock; | ||
| 25 | |||
| 26 | public: | ||
| 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 | |||
| 37 | private: | ||
| 38 | CryptThreadingInitializer(const CryptThreadingInitializer &rhs) = delete; | ||
| 39 | CryptThreadingInitializer& operator=(const CryptThreadingInitializer&) = delete; | ||
| 40 | }; | ||
| 41 | |||
| 42 | XBMC_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 | |||
| 22 | namespace | ||
| 23 | { | ||
| 24 | |||
| 25 | std::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 | |||
| 31 | static const char* DMA_HEAP_PATH; | ||
| 32 | |||
| 33 | } // namespace | ||
| 34 | |||
| 35 | std::unique_ptr<CBufferObject> CDMAHeapBufferObject::Create() | ||
| 36 | { | ||
| 37 | return std::make_unique<CDMAHeapBufferObject>(); | ||
| 38 | } | ||
| 39 | |||
| 40 | void 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 | |||
| 65 | CDMAHeapBufferObject::~CDMAHeapBufferObject() | ||
| 66 | { | ||
| 67 | ReleaseMemory(); | ||
| 68 | DestroyBufferObject(); | ||
| 69 | |||
| 70 | close(m_dmaheapfd); | ||
| 71 | m_dmaheapfd = -1; | ||
| 72 | } | ||
| 73 | |||
| 74 | bool 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 | |||
| 99 | bool 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 | |||
| 137 | void 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 | |||
| 152 | uint8_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 | |||
| 175 | void 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 | |||
| 16 | class CDMAHeapBufferObject : public CBufferObject | ||
| 17 | { | ||
| 18 | public: | ||
| 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 | |||
| 34 | private: | ||
| 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 | |||
| 20 | MediaType 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 | |||
| 47 | std::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 | |||
| 279 | int 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 | |||
| 287 | int 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 | |||
| 295 | bool 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 | |||
| 336 | bool 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 | |||
| 385 | bool 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 | |||
| 477 | std::string DatabaseUtils::BuildLimitClause(int end, int start /* = 0 */) | ||
| 478 | { | ||
| 479 | return " LIMIT " + BuildLimitClauseOnly(end, start); | ||
| 480 | } | ||
| 481 | |||
| 482 | std::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 | |||
| 502 | size_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 | |||
| 516 | int 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 | |||
| 19 | class CVariant; | ||
| 20 | |||
| 21 | namespace dbiplus | ||
| 22 | { | ||
| 23 | class Dataset; | ||
| 24 | class field_value; | ||
| 25 | } | ||
| 26 | |||
| 27 | typedef 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 | |||
| 150 | typedef std::set<Field> Fields; | ||
| 151 | typedef std::vector<Field> FieldList; | ||
| 152 | |||
| 153 | typedef enum { | ||
| 154 | DatabaseQueryPartSelect, | ||
| 155 | DatabaseQueryPartWhere, | ||
| 156 | DatabaseQueryPartOrderBy, | ||
| 157 | } DatabaseQueryPart; | ||
| 158 | |||
| 159 | typedef std::map<Field, CVariant> DatabaseResult; | ||
| 160 | typedef std::vector<DatabaseResult> DatabaseResults; | ||
| 161 | |||
| 162 | class DatabaseUtils | ||
| 163 | { | ||
| 164 | public: | ||
| 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 | |||
| 179 | private: | ||
| 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 | |||
| 18 | namespace KODI | ||
| 19 | { | ||
| 20 | namespace UTILITY | ||
| 21 | { | ||
| 22 | |||
| 23 | namespace | ||
| 24 | { | ||
| 25 | |||
| 26 | EVP_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 | |||
| 45 | std::ostream& operator<<(std::ostream& os, TypedDigest const& digest) | ||
| 46 | { | ||
| 47 | return os << "{" << CDigest::TypeToString(digest.type) << "}" << digest.value; | ||
| 48 | } | ||
| 49 | |||
| 50 | std::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 | |||
| 69 | CDigest::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 | |||
| 95 | void CDigest::MdCtxDeleter::operator()(EVP_MD_CTX* context) | ||
| 96 | { | ||
| 97 | EVP_MD_CTX_destroy(context); | ||
| 98 | } | ||
| 99 | |||
| 100 | CDigest::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 | |||
| 109 | void CDigest::Update(std::string const& data) | ||
| 110 | { | ||
| 111 | Update(data.c_str(), data.size()); | ||
| 112 | } | ||
| 113 | |||
| 114 | void 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 | |||
| 127 | std::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 | |||
| 149 | std::string CDigest::Finalize() | ||
| 150 | { | ||
| 151 | return StringUtils::ToHexadecimal(FinalizeRaw()); | ||
| 152 | } | ||
| 153 | |||
| 154 | std::string CDigest::Calculate(Type type, std::string const& data) | ||
| 155 | { | ||
| 156 | CDigest digest{type}; | ||
| 157 | digest.Update(data); | ||
| 158 | return digest.Finalize(); | ||
| 159 | } | ||
| 160 | |||
| 161 | std::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 | |||
| 20 | namespace KODI | ||
| 21 | { | ||
| 22 | namespace UTILITY | ||
| 23 | { | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Utility class for calculating message digests/hashes, currently using OpenSSL | ||
| 27 | */ | ||
| 28 | class CDigest | ||
| 29 | { | ||
| 30 | public: | ||
| 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 | |||
| 93 | private: | ||
| 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 | |||
| 104 | struct 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 | |||
| 121 | inline 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 | |||
| 130 | inline bool operator!=(TypedDigest const& left, TypedDigest const& right) | ||
| 131 | { | ||
| 132 | return !(left == right); | ||
| 133 | } | ||
| 134 | |||
| 135 | std::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 | |||
| 20 | using namespace KODI::WINDOWING::GBM; | ||
| 21 | |||
| 22 | std::unique_ptr<CBufferObject> CDumbBufferObject::Create() | ||
| 23 | { | ||
| 24 | return std::make_unique<CDumbBufferObject>(); | ||
| 25 | } | ||
| 26 | |||
| 27 | void CDumbBufferObject::Register() | ||
| 28 | { | ||
| 29 | CBufferObjectFactory::RegisterBufferObject(CDumbBufferObject::Create); | ||
| 30 | } | ||
| 31 | |||
| 32 | CDumbBufferObject::CDumbBufferObject() | ||
| 33 | { | ||
| 34 | auto winSystem = static_cast<CWinSystemGbmEGLContext*>(CServiceBroker::GetWinSystem()); | ||
| 35 | |||
| 36 | m_device = winSystem->GetDrm()->GetFileDescriptor(); | ||
| 37 | } | ||
| 38 | |||
| 39 | CDumbBufferObject::~CDumbBufferObject() | ||
| 40 | { | ||
| 41 | ReleaseMemory(); | ||
| 42 | DestroyBufferObject(); | ||
| 43 | } | ||
| 44 | |||
| 45 | bool 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 | |||
| 89 | void 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 | |||
| 104 | uint8_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 | |||
| 148 | void 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 | |||
| 16 | class CDumbBufferObject : public CBufferObject | ||
| 17 | { | ||
| 18 | public: | ||
| 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 | |||
| 33 | private: | ||
| 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 | |||
| 14 | using namespace KODI::UTILS::EGL; | ||
| 15 | |||
| 16 | CEGLFence::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 | |||
| 24 | void 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 | |||
| 34 | void 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 | |||
| 49 | bool 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 | |||
| 14 | namespace KODI | ||
| 15 | { | ||
| 16 | namespace UTILS | ||
| 17 | { | ||
| 18 | namespace EGL | ||
| 19 | { | ||
| 20 | |||
| 21 | class CEGLFence | ||
| 22 | { | ||
| 23 | public: | ||
| 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 | |||
| 32 | private: | ||
| 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 | |||
| 16 | namespace | ||
| 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) | ||
| 56 | std::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 | |||
| 104 | CEGLImage::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 | |||
| 112 | bool 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 | |||
| 189 | void CEGLImage::UploadImage(GLenum textureTarget) | ||
| 190 | { | ||
| 191 | m_glEGLImageTargetTexture2DOES(textureTarget, m_image); | ||
| 192 | } | ||
| 193 | |||
| 194 | void 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 | |||
| 19 | class CEGLImage | ||
| 20 | { | ||
| 21 | public: | ||
| 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 | |||
| 51 | private: | ||
| 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 | |||
| 22 | namespace | ||
| 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) | ||
| 39 | std::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 | |||
| 77 | std::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 | |||
| 98 | std::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) | ||
| 114 | void 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 | |||
| 135 | std::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 | |||
| 147 | std::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 | |||
| 159 | bool CEGLUtils::HasExtension(EGLDisplay eglDisplay, const std::string& name) | ||
| 160 | { | ||
| 161 | auto exts = GetExtensions(eglDisplay); | ||
| 162 | return (exts.find(name) != exts.end()); | ||
| 163 | } | ||
| 164 | |||
| 165 | bool CEGLUtils::HasClientExtension(const std::string& name) | ||
| 166 | { | ||
| 167 | auto exts = GetClientExtensions(); | ||
| 168 | return (exts.find(name) != exts.end()); | ||
| 169 | } | ||
| 170 | |||
| 171 | void 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 | |||
| 185 | CEGLContextUtils::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 | |||
| 207 | bool CEGLContextUtils::IsPlatformSupported() const | ||
| 208 | { | ||
| 209 | return m_platformSupported; | ||
| 210 | } | ||
| 211 | |||
| 212 | CEGLContextUtils::~CEGLContextUtils() | ||
| 213 | { | ||
| 214 | Destroy(); | ||
| 215 | } | ||
| 216 | |||
| 217 | bool 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 | |||
| 234 | bool 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 | |||
| 268 | bool 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 | |||
| 300 | bool 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 | |||
| 394 | EGLint 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 | |||
| 402 | bool 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 | |||
| 450 | bool 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 | |||
| 467 | void 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 | |||
| 486 | void 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 | |||
| 494 | bool 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 | |||
| 529 | bool 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 | |||
| 564 | void 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 | |||
| 576 | void 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 | |||
| 586 | void 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 | |||
| 597 | bool 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 | |||
| 607 | bool 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 | |||
| 19 | class CEGLUtils | ||
| 20 | { | ||
| 21 | public: | ||
| 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 | |||
| 38 | private: | ||
| 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 | */ | ||
| 48 | class CEGLAttributesVec | ||
| 49 | { | ||
| 50 | public: | ||
| 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 | |||
| 86 | private: | ||
| 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 | */ | ||
| 100 | template<std::size_t AttributeCount> | ||
| 101 | class CEGLAttributes | ||
| 102 | { | ||
| 103 | public: | ||
| 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 | |||
| 161 | private: | ||
| 162 | std::array<EGLint, AttributeCount * 2 + 1> m_attributes; | ||
| 163 | int m_writePosition{}; | ||
| 164 | }; | ||
| 165 | |||
| 166 | class CEGLContextUtils final | ||
| 167 | { | ||
| 168 | public: | ||
| 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 | |||
| 222 | private: | ||
| 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 | |||
| 13 | EmbeddedArtInfo::EmbeddedArtInfo(size_t size, | ||
| 14 | const std::string &mime, const std::string& type) | ||
| 15 | { | ||
| 16 | Set(size, mime, type); | ||
| 17 | } | ||
| 18 | |||
| 19 | void 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 | |||
| 26 | void EmbeddedArtInfo::Clear() | ||
| 27 | { | ||
| 28 | m_mime.clear(); | ||
| 29 | m_size = 0; | ||
| 30 | } | ||
| 31 | |||
| 32 | bool EmbeddedArtInfo::Empty() const | ||
| 33 | { | ||
| 34 | return m_size == 0; | ||
| 35 | } | ||
| 36 | |||
| 37 | bool 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 | |||
| 44 | void 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 | |||
| 60 | EmbeddedArt::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 | |||
| 66 | void 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 | |||
| 17 | class EmbeddedArtInfo : public IArchivable | ||
| 18 | { | ||
| 19 | public: | ||
| 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 | |||
| 38 | class EmbeddedArt : public EmbeddedArtInfo | ||
| 39 | { | ||
| 40 | public: | ||
| 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 */ | ||
| 12 | void 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 | ||
| 23 | extern "C" { | ||
| 24 | #endif | ||
| 25 | |||
| 26 | #if defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) | ||
| 27 | static __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 | |||
| 35 | static __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 | ||
| 45 | static __inline__ uint16_t Endian_Swap16(uint16_t x) { | ||
| 46 | return((x<<8)|(x>>8)); | ||
| 47 | } | ||
| 48 | |||
| 49 | static __inline__ uint32_t Endian_Swap32(uint32_t x) { | ||
| 50 | return((x<<24)|((x<<8)&0x00FF0000)|((x>>8)&0x0000FF00)|(x>>24)); | ||
| 51 | } | ||
| 52 | #endif | ||
| 53 | |||
| 54 | static __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 | |||
| 68 | void 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 | |||
| 21 | template<typename Event> | ||
| 22 | class CEventStream | ||
| 23 | { | ||
| 24 | public: | ||
| 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 | |||
| 58 | protected: | ||
| 59 | std::vector<std::shared_ptr<detail::ISubscription<Event>>> m_subscriptions; | ||
| 60 | CCriticalSection m_criticalSection; | ||
| 61 | }; | ||
| 62 | |||
| 63 | |||
| 64 | template<typename Event> | ||
| 65 | class CEventSource : public CEventStream<Event> | ||
| 66 | { | ||
| 67 | public: | ||
| 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 | |||
| 83 | private: | ||
| 84 | CJobQueue m_queue; | ||
| 85 | }; | ||
| 86 | |||
| 87 | template<typename Event> | ||
| 88 | class CBlockingEventSource : public CEventStream<Event> | ||
| 89 | { | ||
| 90 | public: | ||
| 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 | |||
| 14 | namespace detail | ||
| 15 | { | ||
| 16 | |||
| 17 | template<typename Event> | ||
| 18 | class ISubscription | ||
| 19 | { | ||
| 20 | public: | ||
| 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 | |||
| 27 | template<typename Event, typename Owner> | ||
| 28 | class CSubscription : public ISubscription<Event> | ||
| 29 | { | ||
| 30 | public: | ||
| 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 | |||
| 37 | private: | ||
| 38 | Owner* m_owner; | ||
| 39 | Fn m_eventHandler; | ||
| 40 | CCriticalSection m_criticalSection; | ||
| 41 | }; | ||
| 42 | |||
| 43 | template<typename Event, typename Owner> | ||
| 44 | CSubscription<Event, Owner>::CSubscription(Owner* owner, Fn fn) | ||
| 45 | : m_owner(owner), m_eventHandler(fn) | ||
| 46 | {} | ||
| 47 | |||
| 48 | template<typename Event, typename Owner> | ||
| 49 | bool CSubscription<Event, Owner>::IsOwnedBy(void* obj) | ||
| 50 | { | ||
| 51 | CSingleLock lock(m_criticalSection); | ||
| 52 | return obj != nullptr && obj == m_owner; | ||
| 53 | } | ||
| 54 | |||
| 55 | template<typename Event, typename Owner> | ||
| 56 | void CSubscription<Event, Owner>::Cancel() | ||
| 57 | { | ||
| 58 | CSingleLock lock(m_criticalSection); | ||
| 59 | m_owner = nullptr; | ||
| 60 | } | ||
| 61 | |||
| 62 | template<typename Event, typename Owner> | ||
| 63 | void 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 | |||
| 19 | const unsigned int CFanart::max_fanart_colors=3; | ||
| 20 | |||
| 21 | |||
| 22 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 23 | /// CFanart Functions | ||
| 24 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 25 | |||
| 26 | CFanart::CFanart() = default; | ||
| 27 | |||
| 28 | void 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 | |||
| 45 | void 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 | |||
| 54 | void CFanart::Clear() | ||
| 55 | { | ||
| 56 | m_fanart.clear(); | ||
| 57 | m_xml.clear(); | ||
| 58 | } | ||
| 59 | |||
| 60 | bool 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 | |||
| 98 | std::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 | |||
| 106 | std::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 | |||
| 114 | const 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 | |||
| 124 | bool 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 | |||
| 137 | unsigned int CFanart::GetNumFanarts() const | ||
| 138 | { | ||
| 139 | return m_fanart.size(); | ||
| 140 | } | ||
| 141 | |||
| 142 | bool 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. | ||
| 30 | class CFanart | ||
| 31 | { | ||
| 32 | public: | ||
| 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; | ||
| 81 | private: | ||
| 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 | |||
| 19 | using namespace ADDON; | ||
| 20 | |||
| 21 | const std::vector<TYPE> ADDON_TYPES = { | ||
| 22 | ADDON_VFS, | ||
| 23 | ADDON_IMAGEDECODER, | ||
| 24 | ADDON_AUDIODECODER | ||
| 25 | }; | ||
| 26 | |||
| 27 | CFileExtensionProvider::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 | |||
| 36 | CFileExtensionProvider::~CFileExtensionProvider() | ||
| 37 | { | ||
| 38 | m_addonManager.Events().Unsubscribe(this); | ||
| 39 | |||
| 40 | m_advancedSettings.reset(); | ||
| 41 | m_addonExtensions.clear(); | ||
| 42 | } | ||
| 43 | |||
| 44 | std::string CFileExtensionProvider::GetDiscStubExtensions() const | ||
| 45 | { | ||
| 46 | return m_advancedSettings->m_discStubExtensions; | ||
| 47 | } | ||
| 48 | |||
| 49 | std::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 | |||
| 58 | std::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 | |||
| 67 | std::string CFileExtensionProvider::GetSubtitleExtensions() const | ||
| 68 | { | ||
| 69 | std::string extensions(m_advancedSettings->m_subtitlesExtensions); | ||
| 70 | extensions += '|' + GetAddonExtensions(ADDON_VFS); | ||
| 71 | |||
| 72 | return extensions; | ||
| 73 | } | ||
| 74 | |||
| 75 | std::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 | |||
| 85 | std::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 | |||
| 95 | std::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 | |||
| 104 | std::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 | |||
| 113 | void CFileExtensionProvider::SetAddonExtensions() | ||
| 114 | { | ||
| 115 | for (auto const type : ADDON_TYPES) | ||
| 116 | { | ||
| 117 | SetAddonExtensions(type); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | void 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 | |||
| 158 | void 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 | |||
| 179 | bool 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 | |||
| 15 | namespace ADDON | ||
| 16 | { | ||
| 17 | class CAddonMgr; | ||
| 18 | } | ||
| 19 | |||
| 20 | class CFileExtensionProvider | ||
| 21 | { | ||
| 22 | public: | ||
| 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 | |||
| 61 | private: | ||
| 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 | |||
| 25 | using namespace XFILE; | ||
| 26 | |||
| 27 | CFileOperationJob::CFileOperationJob() | ||
| 28 | : m_items(), | ||
| 29 | m_strDestFile(), | ||
| 30 | m_avgSpeed(), | ||
| 31 | m_currentOperation(), | ||
| 32 | m_currentFile() | ||
| 33 | { } | ||
| 34 | |||
| 35 | CFileOperationJob::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 | |||
| 52 | void 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 | |||
| 62 | bool 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 | |||
| 89 | bool 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 | |||
| 107 | bool 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 | |||
| 141 | bool 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 | |||
| 199 | CFileOperationJob::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 | |||
| 206 | struct DataHolder | ||
| 207 | { | ||
| 208 | CFileOperationJob *base; | ||
| 209 | double current; | ||
| 210 | double opWeight; | ||
| 211 | }; | ||
| 212 | |||
| 213 | std::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 | |||
| 243 | bool CFileOperationJob::CFileOperation::ExecuteOperation(CFileOperationJob *base, double ¤t, 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 | |||
| 296 | inline 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 | |||
| 313 | bool 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 | |||
| 331 | bool 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 | |||
| 18 | class CFileOperationJob : public CProgressJob | ||
| 19 | { | ||
| 20 | public: | ||
| 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 | |||
| 54 | private: | ||
| 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 ¤t, 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 | |||
| 36 | using namespace XFILE; | ||
| 37 | |||
| 38 | bool 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 | |||
| 47 | bool 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 | |||
| 65 | bool 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 | |||
| 96 | bool 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 | |||
| 166 | CDateTime 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 | |||
| 175 | CDateTime 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 | |||
| 252 | bool 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 | |||
| 15 | class CFileUtils | ||
| 16 | { | ||
| 17 | public: | ||
| 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 | |||
| 17 | using namespace KODI::WINDOWING::GBM; | ||
| 18 | |||
| 19 | std::unique_ptr<CBufferObject> CGBMBufferObject::Create() | ||
| 20 | { | ||
| 21 | return std::make_unique<CGBMBufferObject>(); | ||
| 22 | } | ||
| 23 | |||
| 24 | void CGBMBufferObject::Register() | ||
| 25 | { | ||
| 26 | CBufferObjectFactory::RegisterBufferObject(CGBMBufferObject::Create); | ||
| 27 | } | ||
| 28 | |||
| 29 | CGBMBufferObject::CGBMBufferObject() | ||
| 30 | { | ||
| 31 | m_device = static_cast<CWinSystemGbmEGLContext*>(CServiceBroker::GetWinSystem())->GetGBMDevice(); | ||
| 32 | } | ||
| 33 | |||
| 34 | CGBMBufferObject::~CGBMBufferObject() | ||
| 35 | { | ||
| 36 | ReleaseMemory(); | ||
| 37 | DestroyBufferObject(); | ||
| 38 | } | ||
| 39 | |||
| 40 | bool 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 | |||
| 58 | void 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 | |||
| 69 | uint8_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 | |||
| 81 | void 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 | |||
| 91 | uint64_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 | |||
| 16 | struct gbm_bo; | ||
| 17 | struct gbm_device; | ||
| 18 | |||
| 19 | class CGBMBufferObject : public CBufferObject | ||
| 20 | { | ||
| 21 | public: | ||
| 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 | |||
| 39 | private: | ||
| 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 | |||
| 22 | namespace | ||
| 23 | { | ||
| 24 | |||
| 25 | #define X(VAL) std::make_pair(VAL, #VAL) | ||
| 26 | std::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 | |||
| 42 | std::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 | |||
| 55 | std::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 | |||
| 69 | std::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 | |||
| 83 | void 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 | |||
| 110 | static 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 | |||
| 120 | void _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 | |||
| 154 | void 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 | |||
| 238 | int 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 | |||
| 24 | namespace KODI | ||
| 25 | { | ||
| 26 | namespace UTILS | ||
| 27 | { | ||
| 28 | namespace GL | ||
| 29 | { | ||
| 30 | |||
| 31 | void GlErrorCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam); | ||
| 32 | |||
| 33 | } | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | void _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 | |||
| 44 | void LogGraphicsInfo(); | ||
| 45 | |||
| 46 | int 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 | |||
| 22 | template <typename T> class CPointGen | ||
| 23 | { | ||
| 24 | public: | ||
| 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 | |||
| 88 | template<typename T> | ||
| 89 | constexpr bool operator==(const CPointGen<T> &point1, const CPointGen<T> &point2) noexcept | ||
| 90 | { | ||
| 91 | return (point1.x == point2.x && point1.y == point2.y); | ||
| 92 | } | ||
| 93 | |||
| 94 | template<typename T> | ||
| 95 | constexpr bool operator!=(const CPointGen<T> &point1, const CPointGen<T> &point2) noexcept | ||
| 96 | { | ||
| 97 | return !(point1 == point2); | ||
| 98 | } | ||
| 99 | |||
| 100 | using CPoint = CPointGen<float>; | ||
| 101 | using 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 | */ | ||
| 112 | template <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 | |||
| 130 | public: | ||
| 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 | |||
| 230 | template<typename T> | ||
| 231 | inline bool operator==(const CSizeGen<T>& size1, const CSizeGen<T>& size2) noexcept | ||
| 232 | { | ||
| 233 | return (size1.Width() == size2.Width() && size1.Height() == size2.Height()); | ||
| 234 | } | ||
| 235 | |||
| 236 | template<typename T> | ||
| 237 | inline bool operator!=(const CSizeGen<T>& size1, const CSizeGen<T>& size2) noexcept | ||
| 238 | { | ||
| 239 | return !(size1 == size2); | ||
| 240 | } | ||
| 241 | |||
| 242 | using CSize = CSizeGen<float>; | ||
| 243 | using CSizeInt = CSizeGen<int>; | ||
| 244 | |||
| 245 | |||
| 246 | template <typename T> class CRectGen | ||
| 247 | { | ||
| 248 | public: | ||
| 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{}; | ||
| 464 | private: | ||
| 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 | |||
| 471 | template<typename T> | ||
| 472 | constexpr 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 | |||
| 477 | template<typename T> | ||
| 478 | constexpr bool operator!=(const CRectGen<T> &rect1, const CRectGen<T> &rect2) noexcept | ||
| 479 | { | ||
| 480 | return !(rect1 == rect2); | ||
| 481 | } | ||
| 482 | |||
| 483 | using CRect = CRectGen<float>; | ||
| 484 | using CRectInt = CRectGen<int>; | ||
diff --git a/xbmc/utils/GlobalsHandling.h b/xbmc/utils/GlobalsHandling.h new file mode 100644 index 0000000..a51cc08 --- /dev/null +++ b/xbmc/utils/GlobalsHandling.h | |||
| @@ -0,0 +1,202 @@ | |||
| 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 | |||
| 86 | namespace 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 | |||
| 21 | using SetMap = std::map<int, std::set<CFileItemPtr> >; | ||
| 22 | |||
| 23 | bool 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 | |||
| 29 | bool 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 | |||
| 147 | bool 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 | |||
| 13 | class CFileItemList; | ||
| 14 | |||
| 15 | // can be used as a flag | ||
| 16 | typedef enum { | ||
| 17 | GroupByNone = 0x0, | ||
| 18 | GroupBySet = 0x1 | ||
| 19 | } GroupBy; | ||
| 20 | |||
| 21 | typedef enum { | ||
| 22 | GroupAttributeNone = 0x0, | ||
| 23 | GroupAttributeIgnoreSingleItems = 0x1 | ||
| 24 | } GroupAttribute; | ||
| 25 | |||
| 26 | class GroupUtils | ||
| 27 | { | ||
| 28 | public: | ||
| 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 | |||
| 15 | using namespace HTML; | ||
| 16 | |||
| 17 | CHTMLUtil::CHTMLUtil(void) = default; | ||
| 18 | |||
| 19 | CHTMLUtil::~CHTMLUtil(void) = default; | ||
| 20 | |||
| 21 | void 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 | |||
| 41 | typedef struct | ||
| 42 | { | ||
| 43 | const wchar_t* html; | ||
| 44 | const wchar_t w; | ||
| 45 | } HTMLMapping; | ||
| 46 | |||
| 47 | static const HTMLMapping mappings[] = | ||
| 48 | {{L"&", 0x0026}, | ||
| 49 | {L"'", 0x0027}, | ||
| 50 | {L"´", 0x00B4}, | ||
| 51 | {L"à", 0x00E0}, | ||
| 52 | {L"á", 0x00E1}, | ||
| 53 | {L"â", 0x00E2}, | ||
| 54 | {L"ã", 0x00E3}, | ||
| 55 | {L"ä", 0x00E4}, | ||
| 56 | {L"å", 0x00E5}, | ||
| 57 | {L"æ", 0x00E6}, | ||
| 58 | {L"À", 0x00C0}, | ||
| 59 | {L"Á", 0x00C1}, | ||
| 60 | {L"Â", 0x00C2}, | ||
| 61 | {L"Ã", 0x00C3}, | ||
| 62 | {L"Ä", 0x00C4}, | ||
| 63 | {L"Å", 0x00C5}, | ||
| 64 | {L"Æ", 0x00C6}, | ||
| 65 | {L"„", 0x201E}, | ||
| 66 | {L"¦", 0x00A6}, | ||
| 67 | {L"•", 0x2022}, | ||
| 68 | {L"•", 0x2022}, | ||
| 69 | {L"¢", 0x00A2}, | ||
| 70 | {L"ˆ", 0x02C6}, | ||
| 71 | {L"¤", 0x00A4}, | ||
| 72 | {L"©", 0x00A9}, | ||
| 73 | {L"¸", 0x00B8}, | ||
| 74 | {L"Ç", 0x00C7}, | ||
| 75 | {L"ç", 0x00E7}, | ||
| 76 | {L"†", 0x2020}, | ||
| 77 | {L"°", 0x00B0}, | ||
| 78 | {L"÷", 0x00F7}, | ||
| 79 | {L"‡", 0x2021}, | ||
| 80 | {L"è", 0x00E8}, | ||
| 81 | {L"é", 0x00E9}, | ||
| 82 | {L"ê", 0x00EA}, | ||
| 83 | {L" ", 0x2003}, | ||
| 84 | {L" ", 0x2002}, | ||
| 85 | {L"ë", 0x00EB}, | ||
| 86 | {L"ð", 0x00F0}, | ||
| 87 | {L"€", 0x20AC}, | ||
| 88 | {L"È", 0x00C8}, | ||
| 89 | {L"É", 0x00C9}, | ||
| 90 | {L"Ê", 0x00CA}, | ||
| 91 | {L"Ë", 0x00CB}, | ||
| 92 | {L"Ð", 0x00D0}, | ||
| 93 | {L""", 0x0022}, | ||
| 94 | {L"⁄", 0x2044}, | ||
| 95 | {L"¼", 0x00BC}, | ||
| 96 | {L"½", 0x00BD}, | ||
| 97 | {L"¾", 0x00BE}, | ||
| 98 | {L">", 0x003E}, | ||
| 99 | {L"…", 0x2026}, | ||
| 100 | {L"¡", 0x00A1}, | ||
| 101 | {L"¿", 0x00BF}, | ||
| 102 | {L"ì", 0x00EC}, | ||
| 103 | {L"í", 0x00ED}, | ||
| 104 | {L"î", 0x00EE}, | ||
| 105 | {L"ï", 0x00EF}, | ||
| 106 | {L"Ì", 0x00CC}, | ||
| 107 | {L"Í", 0x00CD}, | ||
| 108 | {L"Î", 0x00CE}, | ||
| 109 | {L"Ï", 0x00CF}, | ||
| 110 | {L"‎", 0x200E}, | ||
| 111 | {L"<", 0x003C}, | ||
| 112 | {L"«", 0x00AB}, | ||
| 113 | {L"“", 0x201C}, | ||
| 114 | {L"‹", 0x2039}, | ||
| 115 | {L"‘", 0x2018}, | ||
| 116 | {L"¯", 0x00AF}, | ||
| 117 | {L"µ", 0x00B5}, | ||
| 118 | {L"·", 0x00B7}, | ||
| 119 | {L"—", 0x2014}, | ||
| 120 | {L" ", 0x00A0}, | ||
| 121 | {L"–", 0x2013}, | ||
| 122 | {L"ñ", 0x00F1}, | ||
| 123 | {L"¬", 0x00AC}, | ||
| 124 | {L"Ñ", 0x00D1}, | ||
| 125 | {L"ª", 0x00AA}, | ||
| 126 | {L"º", 0x00BA}, | ||
| 127 | {L"œ", 0x0153}, | ||
| 128 | {L"ò", 0x00F2}, | ||
| 129 | {L"ó", 0x00F3}, | ||
| 130 | {L"ô", 0x00F4}, | ||
| 131 | {L"õ", 0x00F5}, | ||
| 132 | {L"ö", 0x00F6}, | ||
| 133 | {L"ø", 0x00F8}, | ||
| 134 | {L"Œ", 0x0152}, | ||
| 135 | {L"Ò", 0x00D2}, | ||
| 136 | {L"Ó", 0x00D3}, | ||
| 137 | {L"Ô", 0x00D4}, | ||
| 138 | {L"Õ", 0x00D5}, | ||
| 139 | {L"Ö", 0x00D6}, | ||
| 140 | {L"Ø", 0x00D8}, | ||
| 141 | {L"¶", 0x00B6}, | ||
| 142 | {L"‰", 0x2030}, | ||
| 143 | {L"±", 0x00B1}, | ||
| 144 | {L"£", 0x00A3}, | ||
| 145 | {L"»", 0x00BB}, | ||
| 146 | {L"”", 0x201D}, | ||
| 147 | {L"®", 0x00AE}, | ||
| 148 | {L"‏", 0x200F}, | ||
| 149 | {L"›", 0x203A}, | ||
| 150 | {L"’", 0x2019}, | ||
| 151 | {L"‚", 0x201A}, | ||
| 152 | {L"š", 0x0161}, | ||
| 153 | {L"§", 0x00A7}, | ||
| 154 | {L"­", 0x00AD}, | ||
| 155 | {L"¹", 0x00B9}, | ||
| 156 | {L"²", 0x00B2}, | ||
| 157 | {L"³", 0x00B3}, | ||
| 158 | {L"ß", 0x00DF}, | ||
| 159 | {L"Š", 0x0160}, | ||
| 160 | {L" ", 0x2009}, | ||
| 161 | {L"þ", 0x00FE}, | ||
| 162 | {L"˜", 0x02DC}, | ||
| 163 | {L"×", 0x00D7}, | ||
| 164 | {L"™", 0x2122}, | ||
| 165 | {L"Þ", 0x00DE}, | ||
| 166 | {L"¨", 0x00A8}, | ||
| 167 | {L"ù", 0x00F9}, | ||
| 168 | {L"ú", 0x00FA}, | ||
| 169 | {L"û", 0x00FB}, | ||
| 170 | {L"ü", 0x00FC}, | ||
| 171 | {L"Ù", 0x00D9}, | ||
| 172 | {L"Ú", 0x00DA}, | ||
| 173 | {L"Û", 0x00DB}, | ||
| 174 | {L"Ü", 0x00DC}, | ||
| 175 | {L"¥", 0x00A5}, | ||
| 176 | {L"ÿ", 0x00FF}, | ||
| 177 | {L"ý", 0x00FD}, | ||
| 178 | {L"Ý", 0x00DD}, | ||
| 179 | {L"Ÿ", 0x0178}, | ||
| 180 | {L"‍", 0x200D}, | ||
| 181 | {L"‌", 0x200C}, | ||
| 182 | {NULL, L'\0'}}; | ||
| 183 | |||
| 184 | void 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 | |||
| 13 | namespace HTML | ||
| 14 | { | ||
| 15 | class CHTMLUtil | ||
| 16 | { | ||
| 17 | public: | ||
| 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 | ||
| 14 | const char* const CHttpHeader::m_whitespaceChars = " \t"; | ||
| 15 | |||
| 16 | |||
| 17 | CHttpHeader::CHttpHeader() | ||
| 18 | { | ||
| 19 | m_headerdone = false; | ||
| 20 | } | ||
| 21 | |||
| 22 | CHttpHeader::~CHttpHeader() = default; | ||
| 23 | |||
| 24 | void 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 | |||
| 69 | bool 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 | |||
| 94 | void 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 | |||
| 123 | std::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 | |||
| 131 | std::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 | |||
| 143 | std::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 | |||
| 157 | std::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 | |||
| 171 | std::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 | |||
| 181 | std::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 | |||
| 233 | void 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 | |||
| 15 | class CHttpHeader | ||
| 16 | { | ||
| 17 | public: | ||
| 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 | |||
| 43 | protected: | ||
| 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 | |||
| 16 | HttpParser::~HttpParser() = default; | ||
| 17 | |||
| 18 | void | ||
| 19 | HttpParser::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 | |||
| 132 | bool | ||
| 133 | HttpParser::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 | |||
| 149 | HttpParser::status_t | ||
| 150 | HttpParser::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 | |||
| 178 | const char* | ||
| 179 | HttpParser::getMethod() const | ||
| 180 | { | ||
| 181 | return &_data[0]; | ||
| 182 | } | ||
| 183 | |||
| 184 | const char* | ||
| 185 | HttpParser::getUri() const | ||
| 186 | { | ||
| 187 | return &_data[_uriIndex]; | ||
| 188 | } | ||
| 189 | |||
| 190 | const char* | ||
| 191 | HttpParser::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 | |||
| 204 | const char* | ||
| 205 | HttpParser::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. | ||
| 215 | const char* | ||
| 216 | HttpParser::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 | |||
| 231 | unsigned | ||
| 232 | HttpParser::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 | |||
| 42 | class HttpParser | ||
| 43 | { | ||
| 44 | public: | ||
| 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 | |||
| 63 | private: | ||
| 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 | |||
| 31 | CHttpRange::CHttpRange(uint64_t firstPosition, uint64_t lastPosition) | ||
| 32 | : m_first(firstPosition), | ||
| 33 | m_last(lastPosition) | ||
| 34 | { } | ||
| 35 | |||
| 36 | bool 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 | |||
| 42 | bool CHttpRange::operator==(const CHttpRange &other) const | ||
| 43 | { | ||
| 44 | return m_first == other.m_first && m_last == other.m_last; | ||
| 45 | } | ||
| 46 | |||
| 47 | bool CHttpRange::operator!=(const CHttpRange &other) const | ||
| 48 | { | ||
| 49 | return !(*this == other); | ||
| 50 | } | ||
| 51 | |||
| 52 | uint64_t CHttpRange::GetLength() const | ||
| 53 | { | ||
| 54 | if (!IsValid()) | ||
| 55 | return 0; | ||
| 56 | |||
| 57 | return m_last - m_first + 1; | ||
| 58 | } | ||
| 59 | |||
| 60 | void CHttpRange::SetLength(uint64_t length) | ||
| 61 | { | ||
| 62 | m_last = m_first + length - 1; | ||
| 63 | } | ||
| 64 | |||
| 65 | bool CHttpRange::IsValid() const | ||
| 66 | { | ||
| 67 | return m_last >= m_first; | ||
| 68 | } | ||
| 69 | |||
| 70 | CHttpResponseRange::CHttpResponseRange() | ||
| 71 | : CHttpRange(), | ||
| 72 | m_data(NULL) | ||
| 73 | { } | ||
| 74 | |||
| 75 | CHttpResponseRange::CHttpResponseRange(uint64_t firstPosition, uint64_t lastPosition) | ||
| 76 | : CHttpRange(firstPosition, lastPosition), | ||
| 77 | m_data(NULL) | ||
| 78 | { } | ||
| 79 | |||
| 80 | CHttpResponseRange::CHttpResponseRange(const void* data, uint64_t firstPosition, uint64_t lastPosition) | ||
| 81 | : CHttpRange(firstPosition, lastPosition), | ||
| 82 | m_data(data) | ||
| 83 | { } | ||
| 84 | |||
| 85 | CHttpResponseRange::CHttpResponseRange(const void* data, uint64_t length) | ||
| 86 | : CHttpRange(0, length - 1), | ||
| 87 | m_data(data) | ||
| 88 | { } | ||
| 89 | |||
| 90 | bool 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 | |||
| 98 | bool CHttpResponseRange::operator!=(const CHttpResponseRange &other) const | ||
| 99 | { | ||
| 100 | return !(*this == other); | ||
| 101 | } | ||
| 102 | |||
| 103 | void 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 | |||
| 114 | void CHttpResponseRange::SetData(const void* data, uint64_t firstPosition, uint64_t lastPosition) | ||
| 115 | { | ||
| 116 | SetData(data); | ||
| 117 | SetFirstPosition(firstPosition); | ||
| 118 | SetLastPosition(lastPosition); | ||
| 119 | } | ||
| 120 | |||
| 121 | bool CHttpResponseRange::IsValid() const | ||
| 122 | { | ||
| 123 | if (!CHttpRange::IsValid()) | ||
| 124 | return false; | ||
| 125 | |||
| 126 | return m_data != NULL; | ||
| 127 | } | ||
| 128 | |||
| 129 | CHttpRanges::CHttpRanges() | ||
| 130 | : m_ranges() | ||
| 131 | { } | ||
| 132 | |||
| 133 | CHttpRanges::CHttpRanges(const HttpRanges& httpRanges) | ||
| 134 | : m_ranges(httpRanges) | ||
| 135 | { | ||
| 136 | SortAndCleanup(); | ||
| 137 | } | ||
| 138 | |||
| 139 | bool 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 | |||
| 148 | bool 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 | |||
| 157 | bool 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 | |||
| 166 | bool 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 | |||
| 175 | bool 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 | |||
| 184 | uint64_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 | |||
| 193 | bool 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 | |||
| 208 | void CHttpRanges::Add(const CHttpRange& range) | ||
| 209 | { | ||
| 210 | if (!range.IsValid()) | ||
| 211 | return; | ||
| 212 | |||
| 213 | m_ranges.push_back(range); | ||
| 214 | |||
| 215 | SortAndCleanup(); | ||
| 216 | } | ||
| 217 | |||
| 218 | void CHttpRanges::Remove(size_t index) | ||
| 219 | { | ||
| 220 | if (index >= Size()) | ||
| 221 | return; | ||
| 222 | |||
| 223 | m_ranges.erase(m_ranges.begin() + index); | ||
| 224 | } | ||
| 225 | |||
| 226 | void CHttpRanges::Clear() | ||
| 227 | { | ||
| 228 | m_ranges.clear(); | ||
| 229 | } | ||
| 230 | |||
| 231 | bool CHttpRanges::Parse(const std::string& header) | ||
| 232 | { | ||
| 233 | return Parse(header, std::numeric_limits<uint64_t>::max()); | ||
| 234 | } | ||
| 235 | |||
| 236 | bool 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 | |||
| 319 | void 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 | |||
| 344 | std::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 | |||
| 352 | std::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 | |||
| 362 | std::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 | |||
| 376 | std::string HttpRangeUtils::GenerateMultipartBoundaryContentType(const std::string& multipartBoundary) | ||
| 377 | { | ||
| 378 | if (multipartBoundary.empty()) | ||
| 379 | return ""; | ||
| 380 | |||
| 381 | return "multipart/byteranges; boundary=" + multipartBoundary; | ||
| 382 | } | ||
| 383 | |||
| 384 | std::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 | |||
| 396 | std::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 | |||
| 404 | std::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 | |||
| 416 | std::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 | |||
| 15 | class CHttpRange | ||
| 16 | { | ||
| 17 | public: | ||
| 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 | |||
| 36 | protected: | ||
| 37 | uint64_t m_first = 1; | ||
| 38 | uint64_t m_last = 0; | ||
| 39 | }; | ||
| 40 | |||
| 41 | typedef std::vector<CHttpRange> HttpRanges; | ||
| 42 | |||
| 43 | class CHttpResponseRange : public CHttpRange | ||
| 44 | { | ||
| 45 | public: | ||
| 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 | |||
| 62 | protected: | ||
| 63 | const void* m_data; | ||
| 64 | }; | ||
| 65 | |||
| 66 | typedef std::vector<CHttpResponseRange> HttpResponseRanges; | ||
| 67 | |||
| 68 | class CHttpRanges final | ||
| 69 | { | ||
| 70 | public: | ||
| 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 | |||
| 97 | protected: | ||
| 98 | void SortAndCleanup(); | ||
| 99 | |||
| 100 | HttpRanges m_ranges; | ||
| 101 | }; | ||
| 102 | |||
| 103 | class HttpRangeUtils | ||
| 104 | { | ||
| 105 | public: | ||
| 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 | |||
| 19 | std::map<HTTP::StatusCode, std::string> CHttpResponse::m_statusCodeText = CHttpResponse::createStatusCodes(); | ||
| 20 | |||
| 21 | CHttpResponse::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 | |||
| 31 | void 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 | |||
| 39 | void 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 | |||
| 49 | std::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 | |||
| 106 | std::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 | |||
| 16 | namespace 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 | |||
| 104 | class CHttpResponse | ||
| 105 | { | ||
| 106 | public: | ||
| 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 | |||
| 114 | private: | ||
| 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 | |||
| 11 | class CArchive; | ||
| 12 | |||
| 13 | class IArchivable | ||
| 14 | { | ||
| 15 | protected: | ||
| 16 | /* make sure nobody deletes a pointer to this class */ | ||
| 17 | ~IArchivable() = default; | ||
| 18 | |||
| 19 | public: | ||
| 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 | */ | ||
| 39 | class IBufferObject | ||
| 40 | { | ||
| 41 | public: | ||
| 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 | |||
| 14 | class ILocalizer | ||
| 15 | { | ||
| 16 | public: | ||
| 17 | virtual ~ILocalizer() = default; | ||
| 18 | |||
| 19 | virtual std::string Localize(std::uint32_t code) const = 0; | ||
| 20 | |||
| 21 | protected: | ||
| 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 | ||
| 16 | using spdlog_filename_t = std::wstring; | ||
| 17 | #else | ||
| 18 | using spdlog_filename_t = std::string; | ||
| 19 | #endif | ||
| 20 | |||
| 21 | namespace spdlog | ||
| 22 | { | ||
| 23 | namespace sinks | ||
| 24 | { | ||
| 25 | template<typename Mutex> | ||
| 26 | class dist_sink; | ||
| 27 | } | ||
| 28 | } // namespace spdlog | ||
| 29 | |||
| 30 | class IPlatformLog | ||
| 31 | { | ||
| 32 | public: | ||
| 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 | |||
| 13 | typedef uint32_t character_t; | ||
| 14 | typedef std::vector<character_t> vecText; | ||
| 15 | |||
| 16 | class IRssObserver | ||
| 17 | { | ||
| 18 | protected: | ||
| 19 | /* make sure nobody deletes a pointer to this class */ | ||
| 20 | ~IRssObserver() = default; | ||
| 21 | |||
| 22 | public: | ||
| 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 | |||
| 11 | class IScreenshotSurface | ||
| 12 | { | ||
| 13 | public: | ||
| 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 | |||
| 31 | protected: | ||
| 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 | |||
| 11 | class CVariant; | ||
| 12 | |||
| 13 | class ISerializable | ||
| 14 | { | ||
| 15 | protected: | ||
| 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 | |||
| 15 | class ISortable | ||
| 16 | { | ||
| 17 | protected: | ||
| 18 | /* make sure nobody deletes a pointer to this class */ | ||
| 19 | ~ISortable() = default; | ||
| 20 | |||
| 21 | public: | ||
| 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 | |||
| 11 | class TiXmlNode; | ||
| 12 | |||
| 13 | class IXmlDeserializable | ||
| 14 | { | ||
| 15 | public: | ||
| 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 | |||
| 15 | CInfoLoader::CInfoLoader(unsigned int timeToRefresh) | ||
| 16 | { | ||
| 17 | m_refreshTime = 0; | ||
| 18 | m_timeToRefresh = timeToRefresh; | ||
| 19 | m_busy = false; | ||
| 20 | } | ||
| 21 | |||
| 22 | CInfoLoader::~CInfoLoader() = default; | ||
| 23 | |||
| 24 | void CInfoLoader::OnJobComplete(unsigned int jobID, bool success, CJob *job) | ||
| 25 | { | ||
| 26 | m_refreshTime = CTimeUtils::GetFrameTime() + m_timeToRefresh; | ||
| 27 | m_busy = false; | ||
| 28 | } | ||
| 29 | |||
| 30 | std::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 | |||
| 45 | std::string CInfoLoader::BusyInfo(int info) const | ||
| 46 | { | ||
| 47 | return g_localizeStrings.Get(503); | ||
| 48 | } | ||
| 49 | |||
| 50 | std::string CInfoLoader::TranslateInfo(int info) const | ||
| 51 | { | ||
| 52 | return ""; | ||
| 53 | } | ||
| 54 | |||
| 55 | void 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 | |||
| 15 | class CInfoLoader : public IJobCallback | ||
| 16 | { | ||
| 17 | public: | ||
| 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; | ||
| 25 | protected: | ||
| 26 | virtual CJob *GetJob() const=0; | ||
| 27 | virtual std::string TranslateInfo(int info) const; | ||
| 28 | virtual std::string BusyInfo(int info) const; | ||
| 29 | private: | ||
| 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 | |||
| 13 | class CJSONVariantParserHandler | ||
| 14 | { | ||
| 15 | public: | ||
| 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 | |||
| 33 | private: | ||
| 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 | |||
| 59 | CJSONVariantParserHandler::CJSONVariantParserHandler(CVariant& parsedObject) | ||
| 60 | : m_parsedObject(parsedObject), | ||
| 61 | m_parse(), | ||
| 62 | m_key(), | ||
| 63 | m_status(PARSE_STATUS::Variable) | ||
| 64 | { } | ||
| 65 | |||
| 66 | bool CJSONVariantParserHandler::Null() | ||
| 67 | { | ||
| 68 | PushObject(CVariant::ConstNullVariant); | ||
| 69 | PopObject(); | ||
| 70 | |||
| 71 | return true; | ||
| 72 | } | ||
| 73 | |||
| 74 | bool CJSONVariantParserHandler::Bool(bool b) | ||
| 75 | { | ||
| 76 | return Primitive(b); | ||
| 77 | } | ||
| 78 | |||
| 79 | bool CJSONVariantParserHandler::Int(int i) | ||
| 80 | { | ||
| 81 | return Primitive(i); | ||
| 82 | } | ||
| 83 | |||
| 84 | bool CJSONVariantParserHandler::Uint(unsigned u) | ||
| 85 | { | ||
| 86 | return Primitive(u); | ||
| 87 | } | ||
| 88 | |||
| 89 | bool CJSONVariantParserHandler::Int64(int64_t i) | ||
| 90 | { | ||
| 91 | return Primitive(i); | ||
| 92 | } | ||
| 93 | |||
| 94 | bool CJSONVariantParserHandler::Uint64(uint64_t u) | ||
| 95 | { | ||
| 96 | return Primitive(u); | ||
| 97 | } | ||
| 98 | |||
| 99 | bool CJSONVariantParserHandler::Double(double d) | ||
| 100 | { | ||
| 101 | return Primitive(d); | ||
| 102 | } | ||
| 103 | |||
| 104 | bool CJSONVariantParserHandler::RawNumber(const char* str, rapidjson::SizeType length, bool copy) | ||
| 105 | { | ||
| 106 | return Primitive(str, length); | ||
| 107 | } | ||
| 108 | |||
| 109 | bool CJSONVariantParserHandler::String(const char* str, rapidjson::SizeType length, bool copy) | ||
| 110 | { | ||
| 111 | return Primitive(str, length); | ||
| 112 | } | ||
| 113 | |||
| 114 | bool CJSONVariantParserHandler::StartObject() | ||
| 115 | { | ||
| 116 | PushObject(CVariant::VariantTypeObject); | ||
| 117 | |||
| 118 | return true; | ||
| 119 | } | ||
| 120 | |||
| 121 | bool 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 | |||
| 128 | bool CJSONVariantParserHandler::EndObject(rapidjson::SizeType memberCount) | ||
| 129 | { | ||
| 130 | PopObject(); | ||
| 131 | |||
| 132 | return true; | ||
| 133 | } | ||
| 134 | |||
| 135 | bool CJSONVariantParserHandler::StartArray() | ||
| 136 | { | ||
| 137 | PushObject(CVariant::VariantTypeArray); | ||
| 138 | |||
| 139 | return true; | ||
| 140 | } | ||
| 141 | |||
| 142 | bool CJSONVariantParserHandler::EndArray(rapidjson::SizeType elementCount) | ||
| 143 | { | ||
| 144 | PopObject(); | ||
| 145 | |||
| 146 | return true; | ||
| 147 | } | ||
| 148 | |||
| 149 | void 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 | |||
| 173 | void 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 | |||
| 197 | bool 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 | |||
| 214 | bool 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 | |||
| 15 | class CJSONVariantParser | ||
| 16 | { | ||
| 17 | public: | ||
| 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 | |||
| 17 | template<class TWriter> | ||
| 18 | bool 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 | |||
| 71 | bool 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 | |||
| 13 | class CVariant; | ||
| 14 | |||
| 15 | class CJSONVariantWriter | ||
| 16 | { | ||
| 17 | public: | ||
| 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 | |||
| 11 | class 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 | */ | ||
| 30 | class IJobCallback | ||
| 31 | { | ||
| 32 | public: | ||
| 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 | |||
| 69 | class 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 | */ | ||
| 90 | class CJob | ||
| 91 | { | ||
| 92 | public: | ||
| 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; | ||
| 157 | private: | ||
| 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 | |||
| 19 | bool 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 | |||
| 26 | CJobWorker::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 | |||
| 32 | CJobWorker::~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 | |||
| 42 | void 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 | |||
| 65 | void CJobQueue::CJobPointer::CancelJob() | ||
| 66 | { | ||
| 67 | CJobManager::GetInstance().CancelJob(m_id); | ||
| 68 | m_id = 0; | ||
| 69 | } | ||
| 70 | |||
| 71 | CJobQueue::CJobQueue(bool lifo, unsigned int jobsAtOnce, CJob::PRIORITY priority) | ||
| 72 | : m_jobsAtOnce(jobsAtOnce), m_priority(priority), m_lifo(lifo) | ||
| 73 | { | ||
| 74 | } | ||
| 75 | |||
| 76 | CJobQueue::~CJobQueue() | ||
| 77 | { | ||
| 78 | CancelJobs(); | ||
| 79 | } | ||
| 80 | |||
| 81 | void 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 | |||
| 92 | void 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 | |||
| 110 | bool 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 | |||
| 130 | void 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 | |||
| 142 | void 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 | |||
| 151 | bool CJobQueue::IsProcessing() const | ||
| 152 | { | ||
| 153 | return CJobManager::GetInstance().m_running && (!m_processing.empty() || !m_jobQueue.empty()); | ||
| 154 | } | ||
| 155 | |||
| 156 | bool CJobQueue::QueueEmpty() const | ||
| 157 | { | ||
| 158 | CSingleLock lock(m_section); | ||
| 159 | return m_jobQueue.empty(); | ||
| 160 | } | ||
| 161 | |||
| 162 | CJobManager &CJobManager::GetInstance() | ||
| 163 | { | ||
| 164 | static CJobManager sJobManager; | ||
| 165 | return sJobManager; | ||
| 166 | } | ||
| 167 | |||
| 168 | CJobManager::CJobManager() | ||
| 169 | { | ||
| 170 | m_jobCounter = 0; | ||
| 171 | m_running = true; | ||
| 172 | m_pauseJobs = false; | ||
| 173 | } | ||
| 174 | |||
| 175 | void 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 | |||
| 184 | void 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 | |||
| 209 | unsigned 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 | |||
| 229 | void 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 | |||
| 250 | void 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 | |||
| 269 | CJob *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 | |||
| 293 | void CJobManager::PauseJobs() | ||
| 294 | { | ||
| 295 | CSingleLock lock(m_section); | ||
| 296 | m_pauseJobs = true; | ||
| 297 | } | ||
| 298 | |||
| 299 | void CJobManager::UnPauseJobs() | ||
| 300 | { | ||
| 301 | CSingleLock lock(m_section); | ||
| 302 | m_pauseJobs = false; | ||
| 303 | } | ||
| 304 | |||
| 305 | bool 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 | |||
| 320 | int 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 | |||
| 336 | CJob *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 | |||
| 362 | bool 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 | |||
| 380 | void 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 | |||
| 408 | void 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 | |||
| 417 | unsigned 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 | |||
| 19 | class CJobManager; | ||
| 20 | |||
| 21 | class CJobWorker : public CThread | ||
| 22 | { | ||
| 23 | public: | ||
| 24 | explicit CJobWorker(CJobManager *manager); | ||
| 25 | ~CJobWorker() override; | ||
| 26 | |||
| 27 | void Process() override; | ||
| 28 | private: | ||
| 29 | CJobManager *m_jobManager; | ||
| 30 | }; | ||
| 31 | |||
| 32 | template<typename F> | ||
| 33 | class CLambdaJob : public CJob | ||
| 34 | { | ||
| 35 | public: | ||
| 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 | }; | ||
| 46 | private: | ||
| 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 | */ | ||
| 63 | class 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 | }; | ||
| 88 | public: | ||
| 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 | |||
| 160 | protected: | ||
| 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 | |||
| 167 | private: | ||
| 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 | */ | ||
| 192 | class 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 | |||
| 227 | public: | ||
| 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 | |||
| 312 | protected: | ||
| 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 | |||
| 344 | private: | ||
| 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 | |||
| 30 | using 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 | |||
| 107 | CLabelFormatter::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 | |||
| 116 | std::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 | |||
| 138 | void 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 | |||
| 147 | void CLabelFormatter::FormatLabel2(CFileItem *item) const | ||
| 148 | { | ||
| 149 | item->SetLabel2(GetContent(1, item)); | ||
| 150 | } | ||
| 151 | |||
| 152 | std::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 | |||
| 381 | void 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 | |||
| 397 | void 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 | |||
| 421 | bool 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 | |||
| 439 | void 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 | |||
| 14 | namespace MUSIC_INFO | ||
| 15 | { | ||
| 16 | class CMusicInfoTag; | ||
| 17 | } | ||
| 18 | |||
| 19 | class CFileItem; // forward | ||
| 20 | |||
| 21 | struct 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 | |||
| 35 | class CLabelFormatter | ||
| 36 | { | ||
| 37 | public: | ||
| 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 | |||
| 50 | private: | ||
| 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 | |||
| 21 | typedef struct LCENTRY | ||
| 22 | { | ||
| 23 | long code; | ||
| 24 | const char* name; | ||
| 25 | } LCENTRY; | ||
| 26 | |||
| 27 | extern const std::array<struct LCENTRY, 186> g_iso639_1; | ||
| 28 | extern const std::array<struct LCENTRY, 540> g_iso639_2; | ||
| 29 | |||
| 30 | struct ISO639 | ||
| 31 | { | ||
| 32 | const char* iso639_1; | ||
| 33 | const char* iso639_2b; | ||
| 34 | const char* iso639_2t; | ||
| 35 | const char* win_id; | ||
| 36 | }; | ||
| 37 | |||
| 38 | struct ISO3166_1 | ||
| 39 | { | ||
| 40 | const char* alpha2; | ||
| 41 | const char* alpha3; | ||
| 42 | }; | ||
| 43 | |||
| 44 | // declared as extern to allow forward declaration | ||
| 45 | extern const std::array<ISO639, 190> LanguageCodes; | ||
| 46 | extern const std::array<ISO3166_1, 245> RegionCodes; | ||
| 47 | |||
| 48 | CLangCodeExpander::CLangCodeExpander() = default; | ||
| 49 | |||
| 50 | CLangCodeExpander::~CLangCodeExpander() = default; | ||
| 51 | |||
| 52 | void CLangCodeExpander::Clear() | ||
| 53 | { | ||
| 54 | m_mapUser.clear(); | ||
| 55 | } | ||
| 56 | |||
| 57 | void 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 | |||
| 84 | bool 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 | |||
| 126 | bool 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 | |||
| 136 | bool 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 | |||
| 166 | bool 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 | |||
| 215 | bool 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 | |||
| 236 | bool 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 | ||
| 250 | bool 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 | |||
| 271 | bool 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 | |||
| 292 | bool 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 | |||
| 356 | bool 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 | |||
| 393 | bool 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 | |||
| 413 | bool 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 | |||
| 450 | void 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 | |||
| 464 | bool 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 | |||
| 485 | std::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 | |||
| 504 | bool 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 | |||
| 520 | std::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 | |||
| 535 | std::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 | ||
| 551 | const 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 | ||
| 743 | const 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 | ||
| 1292 | const 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 | ||
| 1491 | const 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 | |||
| 15 | class TiXmlElement; | ||
| 16 | |||
| 17 | class CLangCodeExpander | ||
| 18 | { | ||
| 19 | public: | ||
| 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); | ||
| 111 | protected: | ||
| 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 | |||
| 143 | extern CLangCodeExpander g_LangCodeExpander; | ||
diff --git a/xbmc/utils/LegacyPathTranslation.cpp b/xbmc/utils/LegacyPathTranslation.cpp new file mode 100644 index 0000000..0069339 --- /dev/null +++ b/xbmc/utils/LegacyPathTranslation.cpp | |||
| @@ -0,0 +1,105 @@ | |||
| 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 | |||
| 14 | typedef 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() | ||
| 21 | static 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() | ||
| 55 | static 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 | |||
| 72 | std::string CLegacyPathTranslation::TranslateVideoDbPath(const CURL &legacyPath) | ||
| 73 | { | ||
| 74 | return TranslatePath(legacyPath.Get(), s_videoDbTranslator, VideoDbTranslatorSize); | ||
| 75 | } | ||
| 76 | |||
| 77 | std::string CLegacyPathTranslation::TranslateMusicDbPath(const CURL &legacyPath) | ||
| 78 | { | ||
| 79 | return TranslatePath(legacyPath.Get(), s_musicDbTranslator, MusicDbTranslatorSize); | ||
| 80 | } | ||
| 81 | |||
| 82 | std::string CLegacyPathTranslation::TranslateVideoDbPath(const std::string &legacyPath) | ||
| 83 | { | ||
| 84 | return TranslatePath(legacyPath, s_videoDbTranslator, VideoDbTranslatorSize); | ||
| 85 | } | ||
| 86 | |||
| 87 | std::string CLegacyPathTranslation::TranslateMusicDbPath(const std::string &legacyPath) | ||
| 88 | { | ||
| 89 | return TranslatePath(legacyPath, s_musicDbTranslator, MusicDbTranslatorSize); | ||
| 90 | } | ||
| 91 | |||
| 92 | std::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 | |||
| 13 | typedef struct Translator Translator; | ||
| 14 | |||
| 15 | class 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 | */ | ||
| 24 | class CLegacyPathTranslation | ||
| 25 | { | ||
| 26 | public: | ||
| 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 | |||
| 45 | private: | ||
| 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 | |||
| 11 | constexpr unsigned long long int operator"" _kib (unsigned long long int val) | ||
| 12 | { | ||
| 13 | return val * 1024ull; | ||
| 14 | } | ||
| 15 | |||
| 16 | constexpr unsigned long long int operator"" _kb (unsigned long long int val) | ||
| 17 | { | ||
| 18 | return val * 1000ull; | ||
| 19 | } | ||
| 20 | |||
| 21 | constexpr unsigned long long int operator"" _mib (unsigned long long int val) | ||
| 22 | { | ||
| 23 | return val * 1024ull * 1024ull; | ||
| 24 | } | ||
| 25 | |||
| 26 | constexpr 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 | |||
| 13 | const CLocale CLocale::Empty; | ||
| 14 | |||
| 15 | CLocale::CLocale() | ||
| 16 | : m_language(), | ||
| 17 | m_territory(), | ||
| 18 | m_codeset(), | ||
| 19 | m_modifier() | ||
| 20 | { } | ||
| 21 | |||
| 22 | CLocale::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 | |||
| 31 | CLocale::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 | |||
| 40 | CLocale::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 | |||
| 49 | CLocale::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 | |||
| 58 | CLocale::~CLocale() = default; | ||
| 59 | |||
| 60 | CLocale CLocale::FromString(const std::string& locale) | ||
| 61 | { | ||
| 62 | return CLocale(locale); | ||
| 63 | } | ||
| 64 | |||
| 65 | bool 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 | |||
| 77 | std::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 | |||
| 93 | std::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 | |||
| 104 | std::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 | |||
| 117 | std::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 | |||
| 128 | bool CLocale::Equals(const std::string& locale) const | ||
| 129 | { | ||
| 130 | CLocale other = FromString(locale); | ||
| 131 | |||
| 132 | return *this == other; | ||
| 133 | } | ||
| 134 | |||
| 135 | bool 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 | |||
| 156 | std::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 | |||
| 178 | std::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 | |||
| 200 | bool 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 | |||
| 209 | bool 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 | |||
| 254 | void 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 | |||
| 264 | int 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 | */ | ||
| 18 | class CLocale | ||
| 19 | { | ||
| 20 | public: | ||
| 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 | |||
| 147 | private: | ||
| 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 | */ | ||
| 44 | namespace 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 | |||
| 14 | namespace KODI | ||
| 15 | { | ||
| 16 | namespace MEMORY | ||
| 17 | { | ||
| 18 | struct MemoryStatus | ||
| 19 | { | ||
| 20 | unsigned int memoryLoad; | ||
| 21 | |||
| 22 | uint64_t totalPhys; | ||
| 23 | uint64_t availPhys; | ||
| 24 | }; | ||
| 25 | |||
| 26 | void* AlignedMalloc(size_t s, size_t alignTo); | ||
| 27 | void AlignedFree(void* p); | ||
| 28 | void 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 | |||
| 21 | const 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 | |||
| 516 | std::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 | |||
| 534 | std::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 | |||
| 545 | std::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 | |||
| 583 | CMime::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 | |||
| 632 | CMime::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 | |||
| 667 | bool 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 | |||
| 14 | class CURL; | ||
| 15 | |||
| 16 | class CFileItem; | ||
| 17 | |||
| 18 | class CMime | ||
| 19 | { | ||
| 20 | public: | ||
| 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 | |||
| 42 | private: | ||
| 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 | |||
| 16 | Observable &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 | |||
| 26 | bool 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 | |||
| 32 | void Observable::RegisterObserver(Observer *obs) | ||
| 33 | { | ||
| 34 | CSingleLock lock(m_obsCritSection); | ||
| 35 | if (!IsObserving(*obs)) | ||
| 36 | { | ||
| 37 | m_observers.push_back(obs); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | void 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 | |||
| 49 | void 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 | |||
| 59 | void Observable::SetChanged(bool SetTo) | ||
| 60 | { | ||
| 61 | m_bObservableChanged = SetTo; | ||
| 62 | } | ||
| 63 | |||
| 64 | void 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 | |||
| 16 | class Observable; | ||
| 17 | class ObservableMessageJob; | ||
| 18 | |||
| 19 | typedef enum | ||
| 20 | { | ||
| 21 | ObservableMessageNone, | ||
| 22 | ObservableMessagePeripheralsChanged, | ||
| 23 | ObservableMessageSettingsChanged, | ||
| 24 | ObservableMessageButtonMapsChanged, | ||
| 25 | } ObservableMessage; | ||
| 26 | |||
| 27 | class Observer | ||
| 28 | { | ||
| 29 | public: | ||
| 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 | |||
| 40 | class Observable | ||
| 41 | { | ||
| 42 | friend class ObservableMessageJob; | ||
| 43 | |||
| 44 | public: | ||
| 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 | |||
| 80 | protected: | ||
| 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 | |||
| 17 | CPODocument::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 | |||
| 26 | CPODocument::~CPODocument() = default; | ||
| 27 | |||
| 28 | bool 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 | |||
| 63 | bool 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 | |||
| 101 | void 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 | |||
| 164 | const 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 | |||
| 175 | std::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 | |||
| 226 | bool 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 | |||
| 238 | bool 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 | |||
| 254 | void 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 | |||
| 278 | void 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 | |||
| 15 | typedef 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 | |||
| 22 | enum | ||
| 23 | { | ||
| 24 | ISSOURCELANG=true | ||
| 25 | }; | ||
| 26 | |||
| 27 | // Struct to hold current position and text of the string field in the main PO entry. | ||
| 28 | struct CStrEntry | ||
| 29 | { | ||
| 30 | size_t Pos; | ||
| 31 | std::string Str; | ||
| 32 | }; | ||
| 33 | |||
| 34 | // Struct to collect all important data of the current processed entry. | ||
| 35 | struct 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 | |||
| 47 | class CPODocument | ||
| 48 | { | ||
| 49 | public: | ||
| 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 | |||
| 114 | protected: | ||
| 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 | |||
| 20 | CProgressJob::CProgressJob() | ||
| 21 | : m_progress(NULL), | ||
| 22 | m_progressDialog(NULL) | ||
| 23 | { } | ||
| 24 | |||
| 25 | CProgressJob::CProgressJob(CGUIDialogProgressBarHandle* progressBar) | ||
| 26 | : m_progress(progressBar), | ||
| 27 | m_progressDialog(NULL) | ||
| 28 | { } | ||
| 29 | |||
| 30 | CProgressJob::~CProgressJob() | ||
| 31 | { | ||
| 32 | MarkFinished(); | ||
| 33 | |||
| 34 | m_progress = NULL; | ||
| 35 | m_progressDialog = NULL; | ||
| 36 | } | ||
| 37 | |||
| 38 | bool 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 | |||
| 48 | bool 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 | |||
| 73 | void 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 | |||
| 84 | void 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 | |||
| 95 | void 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 | |||
| 110 | void 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 | |||
| 125 | void 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 | |||
| 147 | void 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 | |||
| 158 | void 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 | |||
| 174 | bool CProgressJob::IsCancelled() const | ||
| 175 | { | ||
| 176 | if (m_progressDialog != NULL) | ||
| 177 | return m_progressDialog->IsCanceled(); | ||
| 178 | |||
| 179 | return false; | ||
| 180 | } | ||
| 181 | |||
| 182 | bool 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 | |||
| 15 | class CGUIDialogProgress; | ||
| 16 | class 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 | */ | ||
| 22 | class CProgressJob : public CJob | ||
| 23 | { | ||
| 24 | public: | ||
| 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 | |||
| 53 | protected: | ||
| 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 | |||
| 156 | private: | ||
| 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 | |||
| 14 | namespace KODI | ||
| 15 | { | ||
| 16 | namespace UTILS | ||
| 17 | { | ||
| 18 | template<class TIterator> | ||
| 19 | void 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 | |||
| 35 | CRecentlyAddedJob::CRecentlyAddedJob(int flag) | ||
| 36 | { | ||
| 37 | m_flag = flag; | ||
| 38 | } | ||
| 39 | |||
| 40 | bool 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 | |||
| 193 | bool 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 | |||
| 317 | bool 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 | |||
| 377 | bool 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 | |||
| 13 | enum ERecentlyAddedFlag | ||
| 14 | { | ||
| 15 | Audio = 0x1, | ||
| 16 | Video = 0x2, | ||
| 17 | Totals = 0x4 | ||
| 18 | }; | ||
| 19 | |||
| 20 | class CRecentlyAddedJob : public CJob | ||
| 21 | { | ||
| 22 | public: | ||
| 23 | explicit CRecentlyAddedJob(int flag); | ||
| 24 | static bool UpdateVideo(); | ||
| 25 | static bool UpdateMusic(); | ||
| 26 | static bool UpdateTotal(); | ||
| 27 | bool DoWork() override; | ||
| 28 | private: | ||
| 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 | |||
| 19 | using 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 | |||
| 40 | int CRegExp::m_Utf8Supported = -1; | ||
| 41 | int CRegExp::m_UcpSupported = -1; | ||
| 42 | int CRegExp::m_JitSupported = -1; | ||
| 43 | |||
| 44 | |||
| 45 | CRegExp::CRegExp(bool caseless /*= false*/, CRegExp::utf8Mode utf8 /*= asciiOnly*/) | ||
| 46 | { | ||
| 47 | InitValues(caseless, utf8); | ||
| 48 | } | ||
| 49 | |||
| 50 | void 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 | |||
| 75 | CRegExp::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 | |||
| 84 | bool 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 | |||
| 136 | inline 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 | |||
| 169 | bool 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 | |||
| 225 | CRegExp::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 | |||
| 235 | CRegExp& 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 | |||
| 262 | CRegExp::~CRegExp() | ||
| 263 | { | ||
| 264 | Cleanup(); | ||
| 265 | } | ||
| 266 | |||
| 267 | bool 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 | |||
| 320 | int CRegExp::RegFind(const char *str, unsigned int startoffset /*= 0*/, int maxNumberOfCharsToTest /*= -1*/) | ||
| 321 | { | ||
| 322 | return PrivateRegFind(strlen(str), str, startoffset, maxNumberOfCharsToTest); | ||
| 323 | } | ||
| 324 | |||
| 325 | int 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 | |||
| 413 | int 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 | |||
| 421 | std::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 | |||
| 471 | int CRegExp::GetSubStart(int iSub) const | ||
| 472 | { | ||
| 473 | if (!IsValidSubNumber(iSub)) | ||
| 474 | return -1; | ||
| 475 | |||
| 476 | return m_iOvector[iSub*2] + m_offset; | ||
| 477 | } | ||
| 478 | |||
| 479 | int CRegExp::GetSubStart(const std::string& subName) const | ||
| 480 | { | ||
| 481 | return GetSubStart(GetNamedSubPatternNumber(subName.c_str())); | ||
| 482 | } | ||
| 483 | |||
| 484 | int 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 | |||
| 492 | int CRegExp::GetSubLength(const std::string& subName) const | ||
| 493 | { | ||
| 494 | return GetSubLength(GetNamedSubPatternNumber(subName.c_str())); | ||
| 495 | } | ||
| 496 | |||
| 497 | std::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 | |||
| 510 | std::string CRegExp::GetMatch(const std::string& subName) const | ||
| 511 | { | ||
| 512 | return GetMatch(GetNamedSubPatternNumber(subName.c_str())); | ||
| 513 | } | ||
| 514 | |||
| 515 | bool 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 | |||
| 525 | int CRegExp::GetNamedSubPatternNumber(const char* strName) const | ||
| 526 | { | ||
| 527 | return pcre_get_stringnumber(m_re, strName); | ||
| 528 | } | ||
| 529 | |||
| 530 | void 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 | |||
| 548 | void 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 | |||
| 571 | inline bool CRegExp::IsValidSubNumber(int iSub) const | ||
| 572 | { | ||
| 573 | return iSub >= 0 && iSub <= m_iMatchCount && iSub <= m_MaxNumOfBackrefrences; | ||
| 574 | } | ||
| 575 | |||
| 576 | |||
| 577 | bool 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 | |||
| 588 | bool 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 | |||
| 601 | bool 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 | |||
| 631 | bool 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 | |||
| 21 | namespace PCRE { | ||
| 22 | struct real_pcre_jit_stack; // forward declaration for PCRE without JIT | ||
| 23 | typedef struct real_pcre_jit_stack pcre_jit_stack; | ||
| 24 | #include <pcre.h> | ||
| 25 | } | ||
| 26 | |||
| 27 | class CRegExp | ||
| 28 | { | ||
| 29 | public: | ||
| 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 | |||
| 136 | private: | ||
| 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 | |||
| 164 | typedef 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 */ | ||
| 18 | CRingBuffer::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 */ | ||
| 28 | CRingBuffer::~CRingBuffer() | ||
| 29 | { | ||
| 30 | Destroy(); | ||
| 31 | } | ||
| 32 | |||
| 33 | /* Create a ring buffer with the specified 'size' */ | ||
| 34 | bool 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 */ | ||
| 47 | void 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 */ | ||
| 62 | void 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 | */ | ||
| 73 | bool 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 | */ | ||
| 101 | bool 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 | */ | ||
| 124 | bool 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 | */ | ||
| 152 | bool 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 */ | ||
| 172 | bool 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 */ | ||
| 201 | bool 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 */ | ||
| 207 | bool CRingBuffer::Copy(CRingBuffer &rBuf) | ||
| 208 | { | ||
| 209 | Clear(); | ||
| 210 | return Append(rBuf); | ||
| 211 | } | ||
| 212 | |||
| 213 | /* Our various 'get' methods */ | ||
| 214 | char *CRingBuffer::getBuffer() | ||
| 215 | { | ||
| 216 | return m_buffer; | ||
| 217 | } | ||
| 218 | |||
| 219 | unsigned int CRingBuffer::getSize() | ||
| 220 | { | ||
| 221 | CSingleLock lock(m_critSection); | ||
| 222 | return m_size; | ||
| 223 | } | ||
| 224 | |||
| 225 | unsigned int CRingBuffer::getReadPtr() const | ||
| 226 | { | ||
| 227 | return m_readPtr; | ||
| 228 | } | ||
| 229 | |||
| 230 | unsigned int CRingBuffer::getWritePtr() | ||
| 231 | { | ||
| 232 | CSingleLock lock(m_critSection); | ||
| 233 | return m_writePtr; | ||
| 234 | } | ||
| 235 | |||
| 236 | unsigned int CRingBuffer::getMaxReadSize() | ||
| 237 | { | ||
| 238 | CSingleLock lock(m_critSection); | ||
| 239 | return m_fillCount; | ||
| 240 | } | ||
| 241 | |||
| 242 | unsigned 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 | |||
| 13 | class 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; | ||
| 21 | public: | ||
| 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 | |||
| 29 | using namespace XFILE; | ||
| 30 | using namespace KODI::MESSAGING; | ||
| 31 | |||
| 32 | |||
| 33 | CRssManager::CRssManager() | ||
| 34 | { | ||
| 35 | m_bActive = false; | ||
| 36 | } | ||
| 37 | |||
| 38 | CRssManager::~CRssManager() | ||
| 39 | { | ||
| 40 | Stop(); | ||
| 41 | } | ||
| 42 | |||
| 43 | CRssManager& CRssManager::GetInstance() | ||
| 44 | { | ||
| 45 | static CRssManager sRssManager; | ||
| 46 | return sRssManager; | ||
| 47 | } | ||
| 48 | |||
| 49 | void CRssManager::OnSettingsLoaded() | ||
| 50 | { | ||
| 51 | Load(); | ||
| 52 | } | ||
| 53 | |||
| 54 | void CRssManager::OnSettingsUnloaded() | ||
| 55 | { | ||
| 56 | Clear(); | ||
| 57 | } | ||
| 58 | |||
| 59 | void 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 | |||
| 77 | void CRssManager::Start() | ||
| 78 | { | ||
| 79 | m_bActive = true; | ||
| 80 | } | ||
| 81 | |||
| 82 | void 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 | |||
| 94 | bool 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 | |||
| 160 | bool CRssManager::Reload() | ||
| 161 | { | ||
| 162 | Stop(); | ||
| 163 | if (!Load()) | ||
| 164 | return false; | ||
| 165 | Start(); | ||
| 166 | |||
| 167 | return true; | ||
| 168 | } | ||
| 169 | |||
| 170 | void 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 | ||
| 177 | bool 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 | |||
| 19 | class CRssReader; | ||
| 20 | class IRssObserver; | ||
| 21 | |||
| 22 | typedef struct | ||
| 23 | { | ||
| 24 | bool rtl; | ||
| 25 | std::vector<int> interval; | ||
| 26 | std::vector<std::string> url; | ||
| 27 | } RssSet; | ||
| 28 | typedef std::map<int, RssSet> RssUrls; | ||
| 29 | |||
| 30 | class CRssManager : public ISettingCallback, public ISettingsHandler | ||
| 31 | { | ||
| 32 | public: | ||
| 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 | |||
| 50 | protected: | ||
| 51 | CRssManager(); | ||
| 52 | ~CRssManager() override; | ||
| 53 | |||
| 54 | private: | ||
| 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 | |||
| 31 | using namespace XFILE; | ||
| 32 | |||
| 33 | ////////////////////////////////////////////////////////////////////// | ||
| 34 | // Construction/Destruction | ||
| 35 | ////////////////////////////////////////////////////////////////////// | ||
| 36 | |||
| 37 | CRssReader::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 | |||
| 47 | CRssReader::~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 | |||
| 56 | void CRssReader::Create(IRssObserver* aObserver, const std::vector<std::string>& aUrls, const std::vector<int> ×, 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 | |||
| 80 | void CRssReader::requestRefresh() | ||
| 81 | { | ||
| 82 | m_requestRefresh = true; | ||
| 83 | } | ||
| 84 | |||
| 85 | void 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 | |||
| 98 | void CRssReader::OnExit() | ||
| 99 | { | ||
| 100 | m_bIsRunning = false; | ||
| 101 | } | ||
| 102 | |||
| 103 | int CRssReader::GetQueueSize() | ||
| 104 | { | ||
| 105 | CSingleLock lock(m_critical); | ||
| 106 | return m_vecQueue.size(); | ||
| 107 | } | ||
| 108 | |||
| 109 | void 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 | |||
| 200 | void 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 | |||
| 219 | void CRssReader::AddTag(const std::string &aString) | ||
| 220 | { | ||
| 221 | m_tagSet.push_back(aString); | ||
| 222 | } | ||
| 223 | |||
| 224 | void 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 | |||
| 242 | void 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 | |||
| 308 | bool 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 | |||
| 318 | bool 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 | |||
| 374 | void CRssReader::SetObserver(IRssObserver *observer) | ||
| 375 | { | ||
| 376 | m_pObserver = observer; | ||
| 377 | } | ||
| 378 | |||
| 379 | void 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 | |||
| 394 | void 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 | |||
| 21 | class CRssReader : public CThread | ||
| 22 | { | ||
| 23 | public: | ||
| 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 | |||
| 38 | private: | ||
| 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 | |||
| 31 | void 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 | |||
| 11 | class CBookmark; | ||
| 12 | class CFileItem; | ||
| 13 | |||
| 14 | class CSaveFileState | ||
| 15 | { | ||
| 16 | public: | ||
| 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 | |||
| 13 | namespace KODI | ||
| 14 | { | ||
| 15 | namespace 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 | */ | ||
| 26 | template<typename Handle, Handle invalid, typename Deleter> | ||
| 27 | class CScopeGuard | ||
| 28 | { | ||
| 29 | |||
| 30 | public: | ||
| 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 | |||
| 105 | private: | ||
| 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 | |||
| 27 | using namespace ADDON; | ||
| 28 | using namespace XFILE; | ||
| 29 | |||
| 30 | CScraperParser::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 | |||
| 39 | CScraperParser::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 | |||
| 49 | CScraperParser &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 | |||
| 66 | CScraperParser::~CScraperParser() | ||
| 67 | { | ||
| 68 | Clear(); | ||
| 69 | } | ||
| 70 | |||
| 71 | void CScraperParser::Clear() | ||
| 72 | { | ||
| 73 | m_pRootElement = NULL; | ||
| 74 | delete m_document; | ||
| 75 | |||
| 76 | m_document = NULL; | ||
| 77 | m_strFile.clear(); | ||
| 78 | } | ||
| 79 | |||
| 80 | bool 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 | |||
| 99 | bool 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 | |||
| 140 | void 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 | |||
| 183 | void 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 | |||
| 330 | void 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 | |||
| 352 | TiXmlElement *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 | |||
| 366 | TiXmlElement *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 | |||
| 380 | void 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 | |||
| 451 | const 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 | |||
| 474 | void 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 | |||
| 542 | void 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 | |||
| 570 | void CScraperParser::ClearBuffers() | ||
| 571 | { | ||
| 572 | //clear all m_param strings | ||
| 573 | for (std::string& param : m_param) | ||
| 574 | param.clear(); | ||
| 575 | } | ||
| 576 | |||
| 577 | void 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 | |||
| 594 | void 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 | |||
| 607 | void 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 | |||
| 16 | namespace ADDON | ||
| 17 | { | ||
| 18 | class CScraper; | ||
| 19 | } | ||
| 20 | |||
| 21 | class TiXmlElement; | ||
| 22 | class CXBMCTinyXML; | ||
| 23 | |||
| 24 | class CScraperSettings; | ||
| 25 | |||
| 26 | class CScraperParser | ||
| 27 | { | ||
| 28 | public: | ||
| 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 | |||
| 47 | private: | ||
| 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 | |||
| 30 | CScraperUrl::CScraperUrl() : m_relevance(0.0), m_parsed(false) | ||
| 31 | { | ||
| 32 | } | ||
| 33 | |||
| 34 | CScraperUrl::CScraperUrl(std::string strUrl) : CScraperUrl() | ||
| 35 | { | ||
| 36 | ParseFromData(std::move(strUrl)); | ||
| 37 | } | ||
| 38 | |||
| 39 | CScraperUrl::CScraperUrl(const TiXmlElement* element) : CScraperUrl() | ||
| 40 | { | ||
| 41 | ParseAndAppendUrl(element); | ||
| 42 | } | ||
| 43 | |||
| 44 | CScraperUrl::~CScraperUrl() = default; | ||
| 45 | |||
| 46 | void CScraperUrl::Clear() | ||
| 47 | { | ||
| 48 | m_urls.clear(); | ||
| 49 | m_data.clear(); | ||
| 50 | m_relevance = 0.0; | ||
| 51 | m_parsed = false; | ||
| 52 | } | ||
| 53 | |||
| 54 | void CScraperUrl::SetData(std::string data) | ||
| 55 | { | ||
| 56 | m_data = std::move(data); | ||
| 57 | m_parsed = false; | ||
| 58 | } | ||
| 59 | |||
| 60 | const 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 | |||
| 71 | const 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 | |||
| 83 | unsigned 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 | |||
| 95 | std::string CScraperUrl::GetFirstThumbUrl() const | ||
| 96 | { | ||
| 97 | if (m_urls.empty()) | ||
| 98 | return {}; | ||
| 99 | |||
| 100 | return GetThumbUrl(m_urls.front()); | ||
| 101 | } | ||
| 102 | |||
| 103 | void 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 | |||
| 123 | bool 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 | |||
| 133 | bool 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 | |||
| 162 | bool 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) | ||
| 212 | bool 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 | |||
| 242 | void 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 | |||
| 290 | std::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 | |||
| 298 | bool 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 | |||
| 15 | class TiXmlElement; | ||
| 16 | namespace XFILE | ||
| 17 | { | ||
| 18 | class CCurlFile; | ||
| 19 | } | ||
| 20 | |||
| 21 | class CScraperUrl | ||
| 22 | { | ||
| 23 | public: | ||
| 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 | |||
| 116 | private: | ||
| 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 | |||
| 25 | using namespace XFILE; | ||
| 26 | |||
| 27 | std::vector<std::function<std::unique_ptr<IScreenshotSurface>()>> CScreenShot::m_screenShotSurfaces; | ||
| 28 | |||
| 29 | void CScreenShot::Register(std::function<std::unique_ptr<IScreenshotSurface>()> createFunc) | ||
| 30 | { | ||
| 31 | m_screenShotSurfaces.emplace_back(createFunc); | ||
| 32 | } | ||
| 33 | |||
| 34 | void 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 | |||
| 86 | void 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 | |||
| 18 | class CScreenShot | ||
| 19 | { | ||
| 20 | public: | ||
| 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 | |||
| 26 | private: | ||
| 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 | |||
| 21 | std::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 | |||
| 47 | std::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 | |||
| 55 | std::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 | |||
| 62 | std::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 | |||
| 67 | std::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 | |||
| 75 | std::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 | |||
| 80 | std::string ByDate(SortAttribute attributes, const SortItem &values) | ||
| 81 | { | ||
| 82 | return values.at(FieldDate).asString() + " " + ByLabel(attributes, values); | ||
| 83 | } | ||
| 84 | |||
| 85 | std::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 | |||
| 90 | std::string BySize(SortAttribute attributes, const SortItem &values) | ||
| 91 | { | ||
| 92 | return StringUtils::Format("%" PRId64, values.at(FieldSize).asInteger()); | ||
| 93 | } | ||
| 94 | |||
| 95 | std::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 | |||
| 100 | std::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 | |||
| 108 | std::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 | |||
| 123 | std::string ByAlbumType(SortAttribute attributes, const SortItem &values) | ||
| 124 | { | ||
| 125 | return values.at(FieldAlbumType).asString() + " " + ByLabel(attributes, values); | ||
| 126 | } | ||
| 127 | |||
| 128 | std::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 | |||
| 151 | std::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 | |||
| 178 | std::string ByTrackNumber(SortAttribute attributes, const SortItem &values) | ||
| 179 | { | ||
| 180 | return StringUtils::Format("%i", (int)values.at(FieldTrackNumber).asInteger()); | ||
| 181 | } | ||
| 182 | |||
| 183 | std::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 | } | ||
| 188 | std::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 | |||
| 199 | std::string ByProgramCount(SortAttribute attributes, const SortItem &values) | ||
| 200 | { | ||
| 201 | return StringUtils::Format("%i", (int)values.at(FieldProgramCount).asInteger()); | ||
| 202 | } | ||
| 203 | |||
| 204 | std::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 | |||
| 210 | std::string ByGenre(SortAttribute attributes, const SortItem &values) | ||
| 211 | { | ||
| 212 | return ArrayToString(attributes, values.at(FieldGenre)); | ||
| 213 | } | ||
| 214 | |||
| 215 | std::string ByCountry(SortAttribute attributes, const SortItem &values) | ||
| 216 | { | ||
| 217 | return ArrayToString(attributes, values.at(FieldCountry)); | ||
| 218 | } | ||
| 219 | |||
| 220 | std::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 | |||
| 242 | std::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 | |||
| 260 | std::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 | |||
| 272 | std::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 | |||
| 277 | std::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 | |||
| 282 | std::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 | |||
| 287 | std::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 | |||
| 292 | std::string ByMPAA(SortAttribute attributes, const SortItem &values) | ||
| 293 | { | ||
| 294 | return values.at(FieldMPAA).asString() + " " + ByLabel(attributes, values); | ||
| 295 | } | ||
| 296 | |||
| 297 | std::string ByStudio(SortAttribute attributes, const SortItem &values) | ||
| 298 | { | ||
| 299 | return ArrayToString(attributes, values.at(FieldStudio)); | ||
| 300 | } | ||
| 301 | |||
| 302 | std::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 | |||
| 330 | std::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 | |||
| 340 | std::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 | |||
| 345 | std::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 | |||
| 350 | std::string ByTvShowStatus(SortAttribute attributes, const SortItem &values) | ||
| 351 | { | ||
| 352 | return values.at(FieldTvShowStatus).asString() + " " + ByLabel(attributes, values); | ||
| 353 | } | ||
| 354 | |||
| 355 | std::string ByTvShowTitle(SortAttribute attributes, const SortItem &values) | ||
| 356 | { | ||
| 357 | return values.at(FieldTvShowTitle).asString() + " " + ByLabel(attributes, values); | ||
| 358 | } | ||
| 359 | |||
| 360 | std::string ByProductionCode(SortAttribute attributes, const SortItem &values) | ||
| 361 | { | ||
| 362 | return values.at(FieldProductionCode).asString(); | ||
| 363 | } | ||
| 364 | |||
| 365 | std::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 | |||
| 370 | std::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 | |||
| 375 | std::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 | |||
| 380 | std::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 | |||
| 385 | std::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 | |||
| 390 | std::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 | |||
| 395 | std::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 | |||
| 400 | std::string ByBitrate(SortAttribute attributes, const SortItem &values) | ||
| 401 | { | ||
| 402 | return StringUtils::Format("%" PRId64, values.at(FieldBitrate).asInteger()); | ||
| 403 | } | ||
| 404 | |||
| 405 | std::string ByListeners(SortAttribute attributes, const SortItem &values) | ||
| 406 | { | ||
| 407 | return StringUtils::Format("%" PRId64, values.at(FieldListeners).asInteger()); | ||
| 408 | } | ||
| 409 | |||
| 410 | std::string ByRandom(SortAttribute attributes, const SortItem &values) | ||
| 411 | { | ||
| 412 | return StringUtils::Format("%i", CUtil::GetRandomNumber()); | ||
| 413 | } | ||
| 414 | |||
| 415 | std::string ByChannel(SortAttribute attributes, const SortItem &values) | ||
| 416 | { | ||
| 417 | return values.at(FieldChannelName).asString(); | ||
| 418 | } | ||
| 419 | |||
| 420 | std::string ByChannelNumber(SortAttribute attributes, const SortItem &values) | ||
| 421 | { | ||
| 422 | return values.at(FieldChannelNumber).asString(); | ||
| 423 | } | ||
| 424 | |||
| 425 | std::string ByClientChannelOrder(SortAttribute attributes, const SortItem& values) | ||
| 426 | { | ||
| 427 | return values.at(FieldClientChannelOrder).asString(); | ||
| 428 | } | ||
| 429 | |||
| 430 | std::string ByDateTaken(SortAttribute attributes, const SortItem &values) | ||
| 431 | { | ||
| 432 | return values.at(FieldDateTaken).asString(); | ||
| 433 | } | ||
| 434 | |||
| 435 | std::string ByRelevance(SortAttribute attributes, const SortItem &values) | ||
| 436 | { | ||
| 437 | return StringUtils::Format("%i", (int)values.at(FieldRelevance).asInteger()); | ||
| 438 | } | ||
| 439 | |||
| 440 | std::string ByInstallDate(SortAttribute attributes, const SortItem &values) | ||
| 441 | { | ||
| 442 | return values.at(FieldInstallDate).asString(); | ||
| 443 | } | ||
| 444 | |||
| 445 | std::string ByLastUpdated(SortAttribute attributes, const SortItem &values) | ||
| 446 | { | ||
| 447 | return values.at(FieldLastUpdated).asString(); | ||
| 448 | } | ||
| 449 | |||
| 450 | std::string ByLastUsed(SortAttribute attributes, const SortItem &values) | ||
| 451 | { | ||
| 452 | return values.at(FieldLastUsed).asString(); | ||
| 453 | } | ||
| 454 | |||
| 455 | std::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 | |||
| 461 | bool 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 | |||
| 527 | bool 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 | |||
| 537 | bool 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 | |||
| 547 | bool 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 | |||
| 557 | bool 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 | |||
| 567 | bool SorterIndirectAscending(const SortItemPtr &left, const SortItemPtr &right) | ||
| 568 | { | ||
| 569 | return SorterAscending(*left, *right); | ||
| 570 | } | ||
| 571 | |||
| 572 | bool SorterIndirectDescending(const SortItemPtr &left, const SortItemPtr &right) | ||
| 573 | { | ||
| 574 | return SorterDescending(*left, *right); | ||
| 575 | } | ||
| 576 | |||
| 577 | bool SorterIndirectIgnoreFoldersAscending(const SortItemPtr &left, const SortItemPtr &right) | ||
| 578 | { | ||
| 579 | return SorterIgnoreFoldersAscending(*left, *right); | ||
| 580 | } | ||
| 581 | |||
| 582 | bool SorterIndirectIgnoreFoldersDescending(const SortItemPtr &left, const SortItemPtr &right) | ||
| 583 | { | ||
| 584 | return SorterIgnoreFoldersDescending(*left, *right); | ||
| 585 | } | ||
| 586 | |||
| 587 | // clang-format off | ||
| 588 | std::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 | |||
| 654 | std::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 | |||
| 748 | std::map<SortBy, SortUtils::SortPreparator> SortUtils::m_preparators = fillPreparators(); | ||
| 749 | std::map<SortBy, Fields> SortUtils::m_sortingFields = fillSortingFields(); | ||
| 750 | |||
| 751 | void 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 | |||
| 933 | void 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 | |||
| 972 | void 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 | |||
| 1011 | void SortUtils::Sort(const SortDescription &sortDescription, DatabaseResults& items) | ||
| 1012 | { | ||
| 1013 | Sort(sortDescription.sortBy, sortDescription.sortOrder, sortDescription.sortAttributes, items, sortDescription.limitEnd, sortDescription.limitStart); | ||
| 1014 | } | ||
| 1015 | |||
| 1016 | void SortUtils::Sort(const SortDescription &sortDescription, SortItems& items) | ||
| 1017 | { | ||
| 1018 | Sort(sortDescription.sortBy, sortDescription.sortOrder, sortDescription.sortAttributes, items, sortDescription.limitEnd, sortDescription.limitStart); | ||
| 1019 | } | ||
| 1020 | |||
| 1021 | bool 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 | |||
| 1042 | const 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 | |||
| 1051 | SortUtils::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 | |||
| 1059 | SortUtils::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 | |||
| 1067 | const 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 | |||
| 1076 | std::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 | |||
| 1088 | typedef struct | ||
| 1089 | { | ||
| 1090 | SortBy sort; | ||
| 1091 | SORT_METHOD old; | ||
| 1092 | SortAttribute flags; | ||
| 1093 | int label; | ||
| 1094 | } sort_map; | ||
| 1095 | |||
| 1096 | // clang-format off | ||
| 1097 | const 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 | |||
| 1169 | SORT_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 | |||
| 1187 | SortDescription 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 | |||
| 1202 | int 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 | |||
| 1212 | template<typename T> | ||
| 1213 | T 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 | |||
| 1222 | template<typename T> | ||
| 1223 | const 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 | */ | ||
| 1243 | const 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 | |||
| 1301 | SortBy SortUtils::SortMethodFromString(const std::string& sortMethod) | ||
| 1302 | { | ||
| 1303 | return TypeFromString<SortBy>(sortMethods, sortMethod, SortByNone); | ||
| 1304 | } | ||
| 1305 | |||
| 1306 | const std::string& SortUtils::SortMethodToString(SortBy sortMethod) | ||
| 1307 | { | ||
| 1308 | return TypeToString<SortBy>(sortMethods, sortMethod); | ||
| 1309 | } | ||
| 1310 | |||
| 1311 | const std::map<std::string, SortOrder> sortOrders = { | ||
| 1312 | { "ascending", SortOrderAscending }, | ||
| 1313 | { "descending", SortOrderDescending } | ||
| 1314 | }; | ||
| 1315 | |||
| 1316 | SortOrder SortUtils::SortOrderFromString(const std::string& sortOrder) | ||
| 1317 | { | ||
| 1318 | return TypeFromString<SortOrder>(sortOrders, sortOrder, SortOrderNone); | ||
| 1319 | } | ||
| 1320 | |||
| 1321 | const 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 | |||
| 20 | typedef enum { | ||
| 21 | SortOrderNone = 0, | ||
| 22 | SortOrderAscending, | ||
| 23 | SortOrderDescending | ||
| 24 | } SortOrder; | ||
| 25 | |||
| 26 | typedef enum { | ||
| 27 | SortAttributeNone = 0x0, | ||
| 28 | SortAttributeIgnoreArticle = 0x1, | ||
| 29 | SortAttributeIgnoreFolders = 0x2, | ||
| 30 | SortAttributeUseArtistSortName = 0x4, | ||
| 31 | SortAttributeIgnoreLabel = 0x8 | ||
| 32 | } SortAttribute; | ||
| 33 | |||
| 34 | typedef 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 | ///@{ | ||
| 49 | typedef 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 | |||
| 167 | typedef 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 | |||
| 175 | typedef struct GUIViewSortDetails | ||
| 176 | { | ||
| 177 | SortDescription m_sortDescription; | ||
| 178 | int m_buttonLabel; | ||
| 179 | LABEL_MASKS m_labelMasks; | ||
| 180 | } GUIViewSortDetails; | ||
| 181 | |||
| 182 | typedef DatabaseResult SortItem; | ||
| 183 | typedef std::shared_ptr<SortItem> SortItemPtr; | ||
| 184 | typedef std::vector<SortItemPtr> SortItems; | ||
| 185 | |||
| 186 | class SortUtils | ||
| 187 | { | ||
| 188 | public: | ||
| 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 | |||
| 217 | private: | ||
| 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 | |||
| 16 | CSpeed::CSpeed() | ||
| 17 | { | ||
| 18 | m_value = 0.0; | ||
| 19 | m_valid = false; | ||
| 20 | } | ||
| 21 | |||
| 22 | CSpeed::CSpeed(const CSpeed& speed) | ||
| 23 | { | ||
| 24 | m_value = speed.m_value; | ||
| 25 | m_valid = speed.m_valid; | ||
| 26 | } | ||
| 27 | |||
| 28 | CSpeed::CSpeed(double value) | ||
| 29 | { | ||
| 30 | m_value = value; | ||
| 31 | m_valid = true; | ||
| 32 | } | ||
| 33 | |||
| 34 | bool 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 | |||
| 48 | bool CSpeed::operator >=(const CSpeed& right) const | ||
| 49 | { | ||
| 50 | return operator >(right) || operator ==(right); | ||
| 51 | } | ||
| 52 | |||
| 53 | bool 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 | |||
| 67 | bool CSpeed::operator <=(const CSpeed& right) const | ||
| 68 | { | ||
| 69 | return operator <(right) || operator ==(right); | ||
| 70 | } | ||
| 71 | |||
| 72 | bool 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 | |||
| 86 | bool CSpeed::operator !=(const CSpeed& right) const | ||
| 87 | { | ||
| 88 | return !operator ==(right.m_value); | ||
| 89 | } | ||
| 90 | |||
| 91 | CSpeed& CSpeed::operator =(const CSpeed& right) | ||
| 92 | { | ||
| 93 | m_valid = right.m_valid; | ||
| 94 | m_value = right.m_value; | ||
| 95 | return *this; | ||
| 96 | } | ||
| 97 | |||
| 98 | const 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 | |||
| 107 | const 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 | |||
| 116 | const 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 | |||
| 125 | const 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 | |||
| 134 | CSpeed 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 | |||
| 149 | CSpeed 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 | |||
| 163 | CSpeed 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 | |||
| 176 | CSpeed 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 | |||
| 189 | CSpeed& CSpeed::operator ++() | ||
| 190 | { | ||
| 191 | assert(IsValid()); | ||
| 192 | |||
| 193 | m_value++; | ||
| 194 | return *this; | ||
| 195 | } | ||
| 196 | |||
| 197 | CSpeed& CSpeed::operator --() | ||
| 198 | { | ||
| 199 | assert(IsValid()); | ||
| 200 | |||
| 201 | m_value--; | ||
| 202 | return *this; | ||
| 203 | } | ||
| 204 | |||
| 205 | CSpeed CSpeed::operator ++(int) | ||
| 206 | { | ||
| 207 | assert(IsValid()); | ||
| 208 | |||
| 209 | CSpeed temp(*this); | ||
| 210 | m_value++; | ||
| 211 | return temp; | ||
| 212 | } | ||
| 213 | |||
| 214 | CSpeed CSpeed::operator --(int) | ||
| 215 | { | ||
| 216 | assert(IsValid()); | ||
| 217 | |||
| 218 | CSpeed temp(*this); | ||
| 219 | m_value--; | ||
| 220 | return temp; | ||
| 221 | } | ||
| 222 | |||
| 223 | bool CSpeed::operator >(double right) const | ||
| 224 | { | ||
| 225 | assert(IsValid()); | ||
| 226 | |||
| 227 | if (!IsValid()) | ||
| 228 | return false; | ||
| 229 | |||
| 230 | return (m_value > right); | ||
| 231 | } | ||
| 232 | |||
| 233 | bool CSpeed::operator >=(double right) const | ||
| 234 | { | ||
| 235 | return operator >(right) || operator ==(right); | ||
| 236 | } | ||
| 237 | |||
| 238 | bool CSpeed::operator <(double right) const | ||
| 239 | { | ||
| 240 | assert(IsValid()); | ||
| 241 | |||
| 242 | if (!IsValid()) | ||
| 243 | return false; | ||
| 244 | |||
| 245 | return (m_value < right); | ||
| 246 | } | ||
| 247 | |||
| 248 | bool CSpeed::operator <=(double right) const | ||
| 249 | { | ||
| 250 | return operator <(right) || operator ==(right); | ||
| 251 | } | ||
| 252 | |||
| 253 | bool CSpeed::operator ==(double right) const | ||
| 254 | { | ||
| 255 | if (!IsValid()) | ||
| 256 | return false; | ||
| 257 | |||
| 258 | return (m_value == right); | ||
| 259 | } | ||
| 260 | |||
| 261 | bool CSpeed::operator !=(double right) const | ||
| 262 | { | ||
| 263 | return !operator ==(right); | ||
| 264 | } | ||
| 265 | |||
| 266 | const CSpeed& CSpeed::operator +=(double right) | ||
| 267 | { | ||
| 268 | assert(IsValid()); | ||
| 269 | |||
| 270 | m_value += right; | ||
| 271 | return *this; | ||
| 272 | } | ||
| 273 | |||
| 274 | const CSpeed& CSpeed::operator -=(double right) | ||
| 275 | { | ||
| 276 | assert(IsValid()); | ||
| 277 | |||
| 278 | m_value -= right; | ||
| 279 | return *this; | ||
| 280 | } | ||
| 281 | |||
| 282 | const CSpeed& CSpeed::operator *=(double right) | ||
| 283 | { | ||
| 284 | assert(IsValid()); | ||
| 285 | |||
| 286 | m_value *= right; | ||
| 287 | return *this; | ||
| 288 | } | ||
| 289 | |||
| 290 | const CSpeed& CSpeed::operator /=(double right) | ||
| 291 | { | ||
| 292 | assert(IsValid()); | ||
| 293 | |||
| 294 | m_value /= right; | ||
| 295 | return *this; | ||
| 296 | } | ||
| 297 | |||
| 298 | CSpeed CSpeed::operator +(double right) const | ||
| 299 | { | ||
| 300 | assert(IsValid()); | ||
| 301 | |||
| 302 | CSpeed temp(*this); | ||
| 303 | temp.m_value += right; | ||
| 304 | return temp; | ||
| 305 | } | ||
| 306 | |||
| 307 | CSpeed CSpeed::operator -(double right) const | ||
| 308 | { | ||
| 309 | assert(IsValid()); | ||
| 310 | |||
| 311 | CSpeed temp(*this); | ||
| 312 | temp.m_value -= right; | ||
| 313 | return temp; | ||
| 314 | } | ||
| 315 | |||
| 316 | CSpeed CSpeed::operator *(double right) const | ||
| 317 | { | ||
| 318 | assert(IsValid()); | ||
| 319 | |||
| 320 | CSpeed temp(*this); | ||
| 321 | temp.m_value *= right; | ||
| 322 | return temp; | ||
| 323 | } | ||
| 324 | |||
| 325 | CSpeed CSpeed::operator /(double right) const | ||
| 326 | { | ||
| 327 | assert(IsValid()); | ||
| 328 | |||
| 329 | CSpeed temp(*this); | ||
| 330 | temp.m_value /= right; | ||
| 331 | return temp; | ||
| 332 | } | ||
| 333 | |||
| 334 | CSpeed CSpeed::CreateFromKilometresPerHour(double value) | ||
| 335 | { | ||
| 336 | return CSpeed(value / 3.6); | ||
| 337 | } | ||
| 338 | |||
| 339 | CSpeed CSpeed::CreateFromMetresPerMinute(double value) | ||
| 340 | { | ||
| 341 | return CSpeed(value / 60.0); | ||
| 342 | } | ||
| 343 | |||
| 344 | CSpeed CSpeed::CreateFromMetresPerSecond(double value) | ||
| 345 | { | ||
| 346 | return CSpeed(value); | ||
| 347 | } | ||
| 348 | |||
| 349 | CSpeed CSpeed::CreateFromFeetPerHour(double value) | ||
| 350 | { | ||
| 351 | return CreateFromFeetPerMinute(value / 60.0); | ||
| 352 | } | ||
| 353 | |||
| 354 | CSpeed CSpeed::CreateFromFeetPerMinute(double value) | ||
| 355 | { | ||
| 356 | return CreateFromFeetPerSecond(value / 60.0); | ||
| 357 | } | ||
| 358 | |||
| 359 | CSpeed CSpeed::CreateFromFeetPerSecond(double value) | ||
| 360 | { | ||
| 361 | return CSpeed(value / 3.280839895); | ||
| 362 | } | ||
| 363 | |||
| 364 | CSpeed CSpeed::CreateFromMilesPerHour(double value) | ||
| 365 | { | ||
| 366 | return CSpeed(value / 2.236936292); | ||
| 367 | } | ||
| 368 | |||
| 369 | CSpeed CSpeed::CreateFromKnots(double value) | ||
| 370 | { | ||
| 371 | return CSpeed(value / 1.943846172); | ||
| 372 | } | ||
| 373 | |||
| 374 | CSpeed 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 | |||
| 404 | CSpeed CSpeed::CreateFromInchPerSecond(double value) | ||
| 405 | { | ||
| 406 | return CSpeed(value / 39.37007874); | ||
| 407 | } | ||
| 408 | |||
| 409 | CSpeed CSpeed::CreateFromYardPerSecond(double value) | ||
| 410 | { | ||
| 411 | return CSpeed(value / 1.093613298); | ||
| 412 | } | ||
| 413 | |||
| 414 | CSpeed CSpeed::CreateFromFurlongPerFortnight(double value) | ||
| 415 | { | ||
| 416 | return CSpeed(value / 6012.885613871); | ||
| 417 | } | ||
| 418 | |||
| 419 | void 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 | |||
| 433 | bool CSpeed::IsValid() const | ||
| 434 | { | ||
| 435 | return m_valid; | ||
| 436 | } | ||
| 437 | |||
| 438 | double CSpeed::ToKilometresPerHour() const | ||
| 439 | { | ||
| 440 | return m_value * 3.6; | ||
| 441 | } | ||
| 442 | |||
| 443 | double CSpeed::ToMetresPerMinute() const | ||
| 444 | { | ||
| 445 | return m_value * 60.0; | ||
| 446 | } | ||
| 447 | |||
| 448 | double CSpeed::ToMetresPerSecond() const | ||
| 449 | { | ||
| 450 | return m_value; | ||
| 451 | } | ||
| 452 | |||
| 453 | double CSpeed::ToFeetPerHour() const | ||
| 454 | { | ||
| 455 | return ToFeetPerMinute() * 60.0; | ||
| 456 | } | ||
| 457 | |||
| 458 | double CSpeed::ToFeetPerMinute() const | ||
| 459 | { | ||
| 460 | return ToFeetPerSecond() * 60.0; | ||
| 461 | } | ||
| 462 | |||
| 463 | double CSpeed::ToFeetPerSecond() const | ||
| 464 | { | ||
| 465 | return m_value * 3.280839895; | ||
| 466 | } | ||
| 467 | |||
| 468 | double CSpeed::ToMilesPerHour() const | ||
| 469 | { | ||
| 470 | return m_value * 2.236936292; | ||
| 471 | } | ||
| 472 | |||
| 473 | double CSpeed::ToKnots() const | ||
| 474 | { | ||
| 475 | return m_value * 1.943846172; | ||
| 476 | } | ||
| 477 | |||
| 478 | double 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 | |||
| 508 | double CSpeed::ToInchPerSecond() const | ||
| 509 | { | ||
| 510 | return m_value * 39.37007874; | ||
| 511 | } | ||
| 512 | |||
| 513 | double CSpeed::ToYardPerSecond() const | ||
| 514 | { | ||
| 515 | return m_value * 1.093613298; | ||
| 516 | } | ||
| 517 | |||
| 518 | double CSpeed::ToFurlongPerFortnight() const | ||
| 519 | { | ||
| 520 | return m_value * 6012.885613871; | ||
| 521 | } | ||
| 522 | |||
| 523 | double 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 | ||
| 576 | std::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 | |||
| 15 | class CSpeed : public IArchivable | ||
| 16 | { | ||
| 17 | public: | ||
| 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 | |||
| 108 | protected: | ||
| 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 | |||
| 14 | Logger CStaticLoggerBase::s_logger; | ||
| 15 | |||
| 16 | CStaticLoggerBase::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 | |||
| 15 | class CStaticLoggerBase | ||
| 16 | { | ||
| 17 | protected: | ||
| 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 | |||
| 18 | CStopWatch::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 | |||
| 36 | CStopWatch::~CStopWatch() = default; | ||
| 37 | |||
| 38 | int64_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 | |||
| 13 | class CStopWatch | ||
| 14 | { | ||
| 15 | public: | ||
| 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 | |||
| 93 | private: | ||
| 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 | |||
| 20 | const float VIDEOASPECT_EPSILON = 0.025f; | ||
| 21 | |||
| 22 | CStreamDetailVideo::CStreamDetailVideo() : | ||
| 23 | CStreamDetail(CStreamDetail::VIDEO) | ||
| 24 | { | ||
| 25 | } | ||
| 26 | |||
| 27 | CStreamDetailVideo::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 | |||
| 39 | void 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 | } | ||
| 62 | void 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 | |||
| 73 | bool 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 | |||
| 83 | CStreamDetailAudio::CStreamDetailAudio() : | ||
| 84 | CStreamDetail(CStreamDetail::AUDIO) | ||
| 85 | { | ||
| 86 | } | ||
| 87 | |||
| 88 | CStreamDetailAudio::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 | |||
| 96 | void 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 | } | ||
| 111 | void CStreamDetailAudio::Serialize(CVariant& value) const | ||
| 112 | { | ||
| 113 | value["codec"] = m_strCodec; | ||
| 114 | value["language"] = m_strLanguage; | ||
| 115 | value["channels"] = m_iChannels; | ||
| 116 | } | ||
| 117 | |||
| 118 | bool 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 | |||
| 134 | CStreamDetailSubtitle::CStreamDetailSubtitle() : | ||
| 135 | CStreamDetail(CStreamDetail::SUBTITLE) | ||
| 136 | { | ||
| 137 | } | ||
| 138 | |||
| 139 | CStreamDetailSubtitle::CStreamDetailSubtitle(const SubtitleStreamInfo &info) : | ||
| 140 | CStreamDetail(CStreamDetail::SUBTITLE), | ||
| 141 | m_strLanguage(info.language) | ||
| 142 | { | ||
| 143 | } | ||
| 144 | |||
| 145 | void CStreamDetailSubtitle::Archive(CArchive& ar) | ||
| 146 | { | ||
| 147 | if (ar.IsStoring()) | ||
| 148 | { | ||
| 149 | ar << m_strLanguage; | ||
| 150 | } | ||
| 151 | else | ||
| 152 | { | ||
| 153 | ar >> m_strLanguage; | ||
| 154 | } | ||
| 155 | } | ||
| 156 | void CStreamDetailSubtitle::Serialize(CVariant& value) const | ||
| 157 | { | ||
| 158 | value["language"] = m_strLanguage; | ||
| 159 | } | ||
| 160 | |||
| 161 | bool 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 | |||
| 175 | CStreamDetailSubtitle& 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 | |||
| 185 | CStreamDetails& 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 | |||
| 212 | bool 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 | |||
| 248 | bool CStreamDetails::operator !=(const CStreamDetails &right) const | ||
| 249 | { | ||
| 250 | if (this == &right) return false; | ||
| 251 | |||
| 252 | return !(*this == right); | ||
| 253 | } | ||
| 254 | |||
| 255 | CStreamDetail *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 | |||
| 277 | std::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 | |||
| 286 | int 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 | |||
| 295 | int CStreamDetails::GetVideoStreamCount(void) const | ||
| 296 | { | ||
| 297 | return GetStreamCount(CStreamDetail::VIDEO); | ||
| 298 | } | ||
| 299 | |||
| 300 | int CStreamDetails::GetAudioStreamCount(void) const | ||
| 301 | { | ||
| 302 | return GetStreamCount(CStreamDetail::AUDIO); | ||
| 303 | } | ||
| 304 | |||
| 305 | int CStreamDetails::GetSubtitleStreamCount(void) const | ||
| 306 | { | ||
| 307 | return GetStreamCount(CStreamDetail::SUBTITLE); | ||
| 308 | } | ||
| 309 | |||
| 310 | CStreamDetails::CStreamDetails(const CStreamDetails &that) | ||
| 311 | { | ||
| 312 | m_pBestVideo = nullptr; | ||
| 313 | m_pBestAudio = nullptr; | ||
| 314 | m_pBestSubtitle = nullptr; | ||
| 315 | *this = that; | ||
| 316 | } | ||
| 317 | |||
| 318 | void CStreamDetails::AddStream(CStreamDetail *item) | ||
| 319 | { | ||
| 320 | item->m_pParent = this; | ||
| 321 | m_vecItems.emplace_back(item); | ||
| 322 | } | ||
| 323 | |||
| 324 | void CStreamDetails::Reset(void) | ||
| 325 | { | ||
| 326 | m_pBestVideo = nullptr; | ||
| 327 | m_pBestAudio = nullptr; | ||
| 328 | m_pBestSubtitle = nullptr; | ||
| 329 | |||
| 330 | m_vecItems.clear(); | ||
| 331 | } | ||
| 332 | |||
| 333 | const 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 | |||
| 365 | std::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 | |||
| 374 | float 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 | |||
| 383 | int 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 | |||
| 392 | int 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 | |||
| 401 | int 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 | |||
| 410 | void 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 | |||
| 417 | std::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 | |||
| 426 | std::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 | |||
| 435 | std::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 | |||
| 444 | int 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 | |||
| 453 | std::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 | |||
| 462 | void 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 | } | ||
| 496 | void 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 | |||
| 523 | void 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 | |||
| 555 | std::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 | |||
| 584 | std::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 | |||
| 614 | bool 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 | |||
| 18 | class CStreamDetails; | ||
| 19 | class CVariant; | ||
| 20 | struct VideoStreamInfo; | ||
| 21 | struct AudioStreamInfo; | ||
| 22 | struct SubtitleStreamInfo; | ||
| 23 | |||
| 24 | class CStreamDetail : public IArchivable, public ISerializable | ||
| 25 | { | ||
| 26 | public: | ||
| 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 | |||
| 39 | protected: | ||
| 40 | CStreamDetails *m_pParent; | ||
| 41 | friend class CStreamDetails; | ||
| 42 | }; | ||
| 43 | |||
| 44 | class CStreamDetailVideo final : public CStreamDetail | ||
| 45 | { | ||
| 46 | public: | ||
| 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 | |||
| 62 | class CStreamDetailAudio final : public CStreamDetail | ||
| 63 | { | ||
| 64 | public: | ||
| 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 | |||
| 76 | class CStreamDetailSubtitle final : public CStreamDetail | ||
| 77 | { | ||
| 78 | public: | ||
| 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 | |||
| 89 | class CStreamDetails final : public IArchivable, public ISerializable | ||
| 90 | { | ||
| 91 | public: | ||
| 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); | ||
| 131 | private: | ||
| 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 | |||
| 11 | int 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 | |||
| 13 | class StreamUtils | ||
| 14 | { | ||
| 15 | public: | ||
| 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 | |||
| 56 | static constexpr const char* ADDON_GUID_RE = "^(\\{){0,1}[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}(\\}){0,1}$"; | ||
| 57 | |||
| 58 | /* empty string for use in returns by ref */ | ||
| 59 | const 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 | |||
| 68 | static 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 | |||
| 145 | static 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 | |||
| 223 | std::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 | |||
| 267 | std::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 | |||
| 312 | int 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 | |||
| 321 | wchar_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 | |||
| 330 | wchar_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 | |||
| 339 | void StringUtils::ToUpper(std::string &str) | ||
| 340 | { | ||
| 341 | std::transform(str.begin(), str.end(), str.begin(), ::toupper); | ||
| 342 | } | ||
| 343 | |||
| 344 | void StringUtils::ToUpper(std::wstring &str) | ||
| 345 | { | ||
| 346 | transform(str.begin(), str.end(), str.begin(), toupperUnicode); | ||
| 347 | } | ||
| 348 | |||
| 349 | void StringUtils::ToLower(std::string &str) | ||
| 350 | { | ||
| 351 | transform(str.begin(), str.end(), str.begin(), ::tolower); | ||
| 352 | } | ||
| 353 | |||
| 354 | void StringUtils::ToLower(std::wstring &str) | ||
| 355 | { | ||
| 356 | transform(str.begin(), str.end(), str.begin(), tolowerUnicode); | ||
| 357 | } | ||
| 358 | |||
| 359 | void 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 | |||
| 367 | void 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 | |||
| 384 | bool 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 | |||
| 393 | bool StringUtils::EqualsNoCase(const std::string &str1, const char *s2) | ||
| 394 | { | ||
| 395 | return EqualsNoCase(str1.c_str(), s2); | ||
| 396 | } | ||
| 397 | |||
| 398 | bool 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 | |||
| 411 | int 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 | |||
| 416 | int 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 | |||
| 432 | std::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 | |||
| 438 | std::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 | |||
| 451 | std::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 | |||
| 457 | std::string& StringUtils::Trim(std::string &str) | ||
| 458 | { | ||
| 459 | TrimLeft(str); | ||
| 460 | return TrimRight(str); | ||
| 461 | } | ||
| 462 | |||
| 463 | std::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 | ||
| 471 | static int isspace_c(char c) | ||
| 472 | { | ||
| 473 | return (c & 0x80) == 0 && ::isspace(c); | ||
| 474 | } | ||
| 475 | |||
| 476 | std::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 | |||
| 482 | std::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 | |||
| 489 | std::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 | |||
| 495 | std::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 | |||
| 502 | int 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 | |||
| 513 | std::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 | |||
| 540 | int 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 | |||
| 555 | int 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 | |||
| 573 | int 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 | |||
| 591 | bool StringUtils::StartsWith(const std::string &str1, const std::string &str2) | ||
| 592 | { | ||
| 593 | return str1.compare(0, str2.size(), str2) == 0; | ||
| 594 | } | ||
| 595 | |||
| 596 | bool StringUtils::StartsWith(const std::string &str1, const char *s2) | ||
| 597 | { | ||
| 598 | return StartsWith(str1.c_str(), s2); | ||
| 599 | } | ||
| 600 | |||
| 601 | bool 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 | |||
| 613 | bool StringUtils::StartsWithNoCase(const std::string &str1, const std::string &str2) | ||
| 614 | { | ||
| 615 | return StartsWithNoCase(str1.c_str(), str2.c_str()); | ||
| 616 | } | ||
| 617 | |||
| 618 | bool StringUtils::StartsWithNoCase(const std::string &str1, const char *s2) | ||
| 619 | { | ||
| 620 | return StartsWithNoCase(str1.c_str(), s2); | ||
| 621 | } | ||
| 622 | |||
| 623 | bool 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 | |||
| 635 | bool 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 | |||
| 642 | bool 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 | |||
| 650 | bool 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 | |||
| 666 | bool 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 | |||
| 682 | std::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 | |||
| 689 | std::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 | |||
| 696 | std::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 | |||
| 703 | std::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. | ||
| 757 | int 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 | ||
| 773 | static 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 | |||
| 792 | static 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 | |||
| 811 | static 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 | |||
| 830 | static 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 | |||
| 849 | static 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 | |||
| 868 | static 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 | |||
| 887 | static 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 | |||
| 906 | static 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 | |||
| 925 | static 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 | |||
| 944 | static 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 | |||
| 963 | static 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 | |||
| 982 | static 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 | |||
| 1010 | static 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 | ||
| 1028 | int64_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 | */ | ||
| 1148 | static 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 | */ | ||
| 1194 | int 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 | |||
| 1311 | int 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 | |||
| 1324 | std::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 | |||
| 1363 | long 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 | |||
| 1385 | std::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 | |||
| 1424 | bool 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 | |||
| 1439 | bool 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 | |||
| 1456 | int StringUtils::asciidigitvalue(char chr) | ||
| 1457 | { | ||
| 1458 | if (!isasciidigit(chr)) | ||
| 1459 | return -1; | ||
| 1460 | |||
| 1461 | return chr - '0'; | ||
| 1462 | } | ||
| 1463 | |||
| 1464 | int 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 | |||
| 1478 | void StringUtils::RemoveCRLF(std::string& strLine) | ||
| 1479 | { | ||
| 1480 | StringUtils::TrimRight(strLine, "\n\r"); | ||
| 1481 | } | ||
| 1482 | |||
| 1483 | std::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 | |||
| 1512 | std::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 | |||
| 1535 | std::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. | ||
| 1546 | int 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 | |||
| 1575 | size_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 | ||
| 1620 | int 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 | |||
| 1638 | void 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 | |||
| 1656 | std::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 | |||
| 1669 | bool 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 | |||
| 1676 | double 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 | |||
| 1681 | int 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 | |||
| 1700 | bool 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 | |||
| 1710 | size_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 | |||
| 1721 | std::string StringUtils::Paramify(const std::string ¶m) | ||
| 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 | |||
| 1733 | std::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 | |||
| 1740 | void 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 | |||
| 1756 | std::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 | |||
| 1763 | void 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 | |||
| 1779 | uint64_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 | |||
| 1787 | std::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 | |||
| 1805 | const 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 | |||
| 45 | The following macros can be used to stringify definitions to | ||
| 46 | C style strings. | ||
| 47 | |||
| 48 | Example: | ||
| 49 | |||
| 50 | #define foo 4 | ||
| 51 | DEF_TO_STR_NAME(foo) // outputs "foo" | ||
| 52 | DEF_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 | |||
| 59 | template<typename T, std::enable_if_t<!std::is_enum<T>::value, int> = 0> | ||
| 60 | constexpr auto&& EnumToInt(T&& arg) noexcept | ||
| 61 | { | ||
| 62 | return arg; | ||
| 63 | } | ||
| 64 | template<typename T, std::enable_if_t<std::is_enum<T>::value, int> = 0> | ||
| 65 | constexpr auto EnumToInt(T&& arg) noexcept | ||
| 66 | { | ||
| 67 | return static_cast<int>(arg); | ||
| 68 | } | ||
| 69 | |||
| 70 | class StringUtils | ||
| 71 | { | ||
| 72 | public: | ||
| 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 ¶m); | ||
| 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 | |||
| 389 | private: | ||
| 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 | |||
| 397 | struct 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 | |||
| 13 | bool StringValidation::IsInteger(const std::string &input, void *data) | ||
| 14 | { | ||
| 15 | return StringUtils::IsInteger(input); | ||
| 16 | } | ||
| 17 | |||
| 18 | bool StringValidation::IsPositiveInteger(const std::string &input, void *data) | ||
| 19 | { | ||
| 20 | return StringUtils::IsNaturalNumber(input); | ||
| 21 | } | ||
| 22 | |||
| 23 | bool 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 | |||
| 13 | class StringValidation | ||
| 14 | { | ||
| 15 | public: | ||
| 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 | |||
| 23 | private: | ||
| 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 | |||
| 41 | using namespace winrt::Windows::ApplicationModel; | ||
| 42 | using namespace winrt::Windows::Security::ExchangeActiveSyncProvisioning; | ||
| 43 | using namespace winrt::Windows::System; | ||
| 44 | using 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 | |||
| 82 | using namespace XFILE; | ||
| 83 | |||
| 84 | #ifdef TARGET_WINDOWS_DESKTOP | ||
| 85 | static 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) | ||
| 110 | static 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 | |||
| 192 | enum lsb_rel_info_type | ||
| 193 | { | ||
| 194 | lsb_rel_distributor, | ||
| 195 | lsb_rel_description, | ||
| 196 | lsb_rel_release, | ||
| 197 | lsb_rel_codename | ||
| 198 | }; | ||
| 199 | |||
| 200 | static 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 | |||
| 245 | CSysInfo g_sysinfo; | ||
| 246 | |||
| 247 | CSysInfoJob::CSysInfoJob() = default; | ||
| 248 | |||
| 249 | bool 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 | |||
| 263 | const CSysData &CSysInfoJob::GetData() const | ||
| 264 | { | ||
| 265 | return m_info; | ||
| 266 | } | ||
| 267 | |||
| 268 | CSysData::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 | |||
| 277 | std::string CSysInfoJob::GetMACAddress() | ||
| 278 | { | ||
| 279 | CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); | ||
| 280 | if (iface) | ||
| 281 | return iface->GetMacAddress(); | ||
| 282 | |||
| 283 | return ""; | ||
| 284 | } | ||
| 285 | |||
| 286 | std::string CSysInfoJob::GetVideoEncoder() | ||
| 287 | { | ||
| 288 | return "GPU: " + CServiceBroker::GetRenderSystem()->GetRenderRenderer(); | ||
| 289 | } | ||
| 290 | |||
| 291 | std::string CSysInfoJob::GetBatteryLevel() | ||
| 292 | { | ||
| 293 | return StringUtils::Format("%d%%", CServiceBroker::GetPowerManager().BatteryLevel()); | ||
| 294 | } | ||
| 295 | |||
| 296 | bool 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 | |||
| 313 | std::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 | |||
| 351 | std::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 | |||
| 379 | void CSysInfo::Reset() | ||
| 380 | { | ||
| 381 | m_info.Reset(); | ||
| 382 | } | ||
| 383 | |||
| 384 | CSysInfo::CSysInfo(void) : CInfoLoader(15 * 1000) | ||
| 385 | { | ||
| 386 | memset(MD5_Sign, 0, sizeof(MD5_Sign)); | ||
| 387 | m_iSystemTimeTotalUp = 0; | ||
| 388 | } | ||
| 389 | |||
| 390 | CSysInfo::~CSysInfo() = default; | ||
| 391 | |||
| 392 | bool 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 | |||
| 404 | bool 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 | |||
| 422 | const 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 | |||
| 430 | bool 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 | |||
| 475 | std::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 | |||
| 504 | std::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 | |||
| 536 | std::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 | |||
| 550 | std::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 | |||
| 585 | std::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 | |||
| 622 | std::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 | |||
| 710 | std::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 | |||
| 740 | std::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 | |||
| 776 | bool 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 | |||
| 789 | CSysInfo::WindowsVersion CSysInfo::m_WinVer = WindowsVersionUnknown; | ||
| 790 | |||
| 791 | bool CSysInfo::IsWindowsVersion(WindowsVersion ver) | ||
| 792 | { | ||
| 793 | if (ver == WindowsVersionUnknown) | ||
| 794 | return false; | ||
| 795 | return GetWindowsVersion() == ver; | ||
| 796 | } | ||
| 797 | |||
| 798 | bool CSysInfo::IsWindowsVersionAtLeast(WindowsVersion ver) | ||
| 799 | { | ||
| 800 | if (ver == WindowsVersionUnknown) | ||
| 801 | return false; | ||
| 802 | return GetWindowsVersion() >= ver; | ||
| 803 | } | ||
| 804 | |||
| 805 | CSysInfo::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 | |||
| 834 | int 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 | |||
| 895 | const 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 | |||
| 945 | int CSysInfo::GetXbmcBitness(void) | ||
| 946 | { | ||
| 947 | return static_cast<int>(sizeof(void*) * 8); | ||
| 948 | } | ||
| 949 | |||
| 950 | bool 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 | |||
| 957 | std::string CSysInfo::GetHddSpaceInfo(int drive, bool shortText) | ||
| 958 | { | ||
| 959 | int percent; | ||
| 960 | return GetHddSpaceInfo( percent, drive, shortText); | ||
| 961 | } | ||
| 962 | |||
| 963 | std::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 | |||
| 1014 | std::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 | |||
| 1163 | std::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. | ||
| 1178 | std::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 | |||
| 1186 | std::string CSysInfo::GetVersion() | ||
| 1187 | { | ||
| 1188 | return GetVersionShort() + " (" + CCompileInfo::GetVersionCode() + ")" + | ||
| 1189 | " Git:" + CCompileInfo::GetSCMID(); | ||
| 1190 | } | ||
| 1191 | |||
| 1192 | std::string CSysInfo::GetVersionCode() | ||
| 1193 | { | ||
| 1194 | return CCompileInfo::GetVersionCode(); | ||
| 1195 | } | ||
| 1196 | |||
| 1197 | std::string CSysInfo::GetVersionGit() | ||
| 1198 | { | ||
| 1199 | return CCompileInfo::GetSCMID(); | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | std::string CSysInfo::GetBuildDate() | ||
| 1203 | { | ||
| 1204 | return CCompileInfo::GetBuildDate(); | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | std::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 | |||
| 1232 | std::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 | |||
| 1257 | std::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 | |||
| 1302 | std::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 | |||
| 1322 | std::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 | |||
| 1349 | std::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 | |||
| 1366 | CSysInfo::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 | |||
| 1386 | CJob *CSysInfo::GetJob() const | ||
| 1387 | { | ||
| 1388 | return new CSysInfoJob(); | ||
| 1389 | } | ||
| 1390 | |||
| 1391 | void 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 | |||
| 24 | class CSysData | ||
| 25 | { | ||
| 26 | public: | ||
| 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 | |||
| 48 | class CSysInfoJob : public CJob | ||
| 49 | { | ||
| 50 | public: | ||
| 51 | CSysInfoJob(); | ||
| 52 | |||
| 53 | bool DoWork() override; | ||
| 54 | const CSysData &GetData() const; | ||
| 55 | |||
| 56 | static CSysData::INTERNET_STATE GetInternetState(); | ||
| 57 | private: | ||
| 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 | |||
| 67 | class CSysInfo : public CInfoLoader, public ISubSettings | ||
| 68 | { | ||
| 69 | public: | ||
| 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 | |||
| 142 | protected: | ||
| 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 | |||
| 147 | private: | ||
| 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 | |||
| 155 | extern 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 | |||
| 16 | CTemperature::CTemperature() | ||
| 17 | { | ||
| 18 | m_value=0.0f; | ||
| 19 | m_valid=false; | ||
| 20 | } | ||
| 21 | |||
| 22 | CTemperature::CTemperature(const CTemperature& temperature) | ||
| 23 | { | ||
| 24 | m_value=temperature.m_value; | ||
| 25 | m_valid=temperature.m_valid; | ||
| 26 | } | ||
| 27 | |||
| 28 | CTemperature::CTemperature(double value) | ||
| 29 | { | ||
| 30 | m_value=value; | ||
| 31 | m_valid=true; | ||
| 32 | } | ||
| 33 | |||
| 34 | bool 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 | |||
| 48 | bool CTemperature::operator >=(const CTemperature& right) const | ||
| 49 | { | ||
| 50 | return operator >(right) || operator ==(right); | ||
| 51 | } | ||
| 52 | |||
| 53 | bool 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 | |||
| 67 | bool CTemperature::operator <=(const CTemperature& right) const | ||
| 68 | { | ||
| 69 | return operator <(right) || operator ==(right); | ||
| 70 | } | ||
| 71 | |||
| 72 | bool 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 | |||
| 86 | bool CTemperature::operator !=(const CTemperature& right) const | ||
| 87 | { | ||
| 88 | return !operator ==(right.m_value); | ||
| 89 | } | ||
| 90 | |||
| 91 | CTemperature& CTemperature::operator =(const CTemperature& right) | ||
| 92 | { | ||
| 93 | m_valid=right.m_valid; | ||
| 94 | m_value=right.m_value; | ||
| 95 | return *this; | ||
| 96 | } | ||
| 97 | |||
| 98 | const 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 | |||
| 107 | const 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 | |||
| 116 | const 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 | |||
| 125 | const 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 | |||
| 134 | CTemperature 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 | |||
| 149 | CTemperature 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 | |||
| 163 | CTemperature 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 | |||
| 176 | CTemperature 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 | |||
| 189 | CTemperature& CTemperature::operator ++() | ||
| 190 | { | ||
| 191 | assert(IsValid()); | ||
| 192 | |||
| 193 | m_value++; | ||
| 194 | return *this; | ||
| 195 | } | ||
| 196 | |||
| 197 | CTemperature& CTemperature::operator --() | ||
| 198 | { | ||
| 199 | assert(IsValid()); | ||
| 200 | |||
| 201 | m_value--; | ||
| 202 | return *this; | ||
| 203 | } | ||
| 204 | |||
| 205 | CTemperature CTemperature::operator ++(int) | ||
| 206 | { | ||
| 207 | assert(IsValid()); | ||
| 208 | |||
| 209 | CTemperature temp(*this); | ||
| 210 | m_value++; | ||
| 211 | return temp; | ||
| 212 | } | ||
| 213 | |||
| 214 | CTemperature CTemperature::operator --(int) | ||
| 215 | { | ||
| 216 | assert(IsValid()); | ||
| 217 | |||
| 218 | CTemperature temp(*this); | ||
| 219 | m_value--; | ||
| 220 | return temp; | ||
| 221 | } | ||
| 222 | |||
| 223 | bool CTemperature::operator >(double right) const | ||
| 224 | { | ||
| 225 | assert(IsValid()); | ||
| 226 | |||
| 227 | if (!IsValid()) | ||
| 228 | return false; | ||
| 229 | |||
| 230 | return (m_value>right); | ||
| 231 | } | ||
| 232 | |||
| 233 | bool CTemperature::operator >=(double right) const | ||
| 234 | { | ||
| 235 | return operator >(right) || operator ==(right); | ||
| 236 | } | ||
| 237 | |||
| 238 | bool CTemperature::operator <(double right) const | ||
| 239 | { | ||
| 240 | assert(IsValid()); | ||
| 241 | |||
| 242 | if (!IsValid()) | ||
| 243 | return false; | ||
| 244 | |||
| 245 | return (m_value<right); | ||
| 246 | } | ||
| 247 | |||
| 248 | bool CTemperature::operator <=(double right) const | ||
| 249 | { | ||
| 250 | return operator <(right) || operator ==(right); | ||
| 251 | } | ||
| 252 | |||
| 253 | bool CTemperature::operator ==(double right) const | ||
| 254 | { | ||
| 255 | if (!IsValid()) | ||
| 256 | return false; | ||
| 257 | |||
| 258 | return (m_value==right); | ||
| 259 | } | ||
| 260 | |||
| 261 | bool CTemperature::operator !=(double right) const | ||
| 262 | { | ||
| 263 | return !operator ==(right); | ||
| 264 | } | ||
| 265 | |||
| 266 | const CTemperature& CTemperature::operator +=(double right) | ||
| 267 | { | ||
| 268 | assert(IsValid()); | ||
| 269 | |||
| 270 | m_value+=right; | ||
| 271 | return *this; | ||
| 272 | } | ||
| 273 | |||
| 274 | const CTemperature& CTemperature::operator -=(double right) | ||
| 275 | { | ||
| 276 | assert(IsValid()); | ||
| 277 | |||
| 278 | m_value-=right; | ||
| 279 | return *this; | ||
| 280 | } | ||
| 281 | |||
| 282 | const CTemperature& CTemperature::operator *=(double right) | ||
| 283 | { | ||
| 284 | assert(IsValid()); | ||
| 285 | |||
| 286 | m_value*=right; | ||
| 287 | return *this; | ||
| 288 | } | ||
| 289 | |||
| 290 | const CTemperature& CTemperature::operator /=(double right) | ||
| 291 | { | ||
| 292 | assert(IsValid()); | ||
| 293 | |||
| 294 | m_value/=right; | ||
| 295 | return *this; | ||
| 296 | } | ||
| 297 | |||
| 298 | CTemperature CTemperature::operator +(double right) const | ||
| 299 | { | ||
| 300 | assert(IsValid()); | ||
| 301 | |||
| 302 | CTemperature temp(*this); | ||
| 303 | temp.m_value+=right; | ||
| 304 | return temp; | ||
| 305 | } | ||
| 306 | |||
| 307 | CTemperature CTemperature::operator -(double right) const | ||
| 308 | { | ||
| 309 | assert(IsValid()); | ||
| 310 | |||
| 311 | CTemperature temp(*this); | ||
| 312 | temp.m_value-=right; | ||
| 313 | return temp; | ||
| 314 | } | ||
| 315 | |||
| 316 | CTemperature CTemperature::operator *(double right) const | ||
| 317 | { | ||
| 318 | assert(IsValid()); | ||
| 319 | |||
| 320 | CTemperature temp(*this); | ||
| 321 | temp.m_value*=right; | ||
| 322 | return temp; | ||
| 323 | } | ||
| 324 | |||
| 325 | CTemperature CTemperature::operator /(double right) const | ||
| 326 | { | ||
| 327 | assert(IsValid()); | ||
| 328 | |||
| 329 | CTemperature temp(*this); | ||
| 330 | temp.m_value/=right; | ||
| 331 | return temp; | ||
| 332 | } | ||
| 333 | |||
| 334 | CTemperature CTemperature::CreateFromFahrenheit(double value) | ||
| 335 | { | ||
| 336 | return CTemperature(value); | ||
| 337 | } | ||
| 338 | |||
| 339 | CTemperature CTemperature::CreateFromReaumur(double value) | ||
| 340 | { | ||
| 341 | return CTemperature(value*2.25f+32.0f); | ||
| 342 | } | ||
| 343 | |||
| 344 | CTemperature CTemperature::CreateFromRankine(double value) | ||
| 345 | { | ||
| 346 | return CTemperature(value-459.67f); | ||
| 347 | } | ||
| 348 | |||
| 349 | CTemperature CTemperature::CreateFromRomer(double value) | ||
| 350 | { | ||
| 351 | return CTemperature((value-7.5f)*24.0f/7.0f+32.0f); | ||
| 352 | } | ||
| 353 | |||
| 354 | CTemperature CTemperature::CreateFromDelisle(double value) | ||
| 355 | { | ||
| 356 | CTemperature temp(212.0f - value * 1.2f); | ||
| 357 | return temp; | ||
| 358 | } | ||
| 359 | |||
| 360 | CTemperature CTemperature::CreateFromNewton(double value) | ||
| 361 | { | ||
| 362 | return CTemperature(value*60.0f/11.0f+32.0f); | ||
| 363 | } | ||
| 364 | |||
| 365 | CTemperature CTemperature::CreateFromCelsius(double value) | ||
| 366 | { | ||
| 367 | return CTemperature(value*1.8f+32.0f); | ||
| 368 | } | ||
| 369 | |||
| 370 | CTemperature CTemperature::CreateFromKelvin(double value) | ||
| 371 | { | ||
| 372 | return CTemperature((value - 273.15) * 1.8 + 32.0); | ||
| 373 | } | ||
| 374 | |||
| 375 | void 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 | |||
| 389 | bool CTemperature::IsValid() const | ||
| 390 | { | ||
| 391 | return m_valid; | ||
| 392 | } | ||
| 393 | |||
| 394 | double CTemperature::ToFahrenheit() const | ||
| 395 | { | ||
| 396 | return m_value; | ||
| 397 | } | ||
| 398 | |||
| 399 | double CTemperature::ToKelvin() const | ||
| 400 | { | ||
| 401 | return (m_value+459.67F)/1.8f; | ||
| 402 | } | ||
| 403 | |||
| 404 | double CTemperature::ToCelsius() const | ||
| 405 | { | ||
| 406 | return (m_value-32.0f)/1.8f; | ||
| 407 | } | ||
| 408 | |||
| 409 | double CTemperature::ToReaumur() const | ||
| 410 | { | ||
| 411 | return (m_value-32.0f)/2.25f; | ||
| 412 | } | ||
| 413 | |||
| 414 | double CTemperature::ToRankine() const | ||
| 415 | { | ||
| 416 | return m_value+459.67f; | ||
| 417 | } | ||
| 418 | |||
| 419 | double CTemperature::ToRomer() const | ||
| 420 | { | ||
| 421 | return (m_value-32.0f)*7.0f/24.0f+7.5f; | ||
| 422 | } | ||
| 423 | |||
| 424 | double CTemperature::ToDelisle() const | ||
| 425 | { | ||
| 426 | return (212.f-m_value)*5.0f/6.0f; | ||
| 427 | } | ||
| 428 | |||
| 429 | double CTemperature::ToNewton() const | ||
| 430 | { | ||
| 431 | return (m_value-32.0f)*11.0f/60.0f; | ||
| 432 | } | ||
| 433 | |||
| 434 | double 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 | ||
| 475 | std::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 | |||
| 15 | class CTemperature : public IArchivable | ||
| 16 | { | ||
| 17 | public: | ||
| 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 | |||
| 97 | protected: | ||
| 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 | |||
| 13 | CTextSearch::CTextSearch(const std::string &strSearchTerms, bool bCaseSensitive /* = false */, TextSearchDefault defaultSearchMode /* = SEARCH_DEFAULT_OR */) | ||
| 14 | { | ||
| 15 | m_bCaseSensitive = bCaseSensitive; | ||
| 16 | ExtractSearchTerms(strSearchTerms, defaultSearchMode); | ||
| 17 | } | ||
| 18 | |||
| 19 | bool CTextSearch::IsValid(void) const | ||
| 20 | { | ||
| 21 | return m_AND.size() > 0 || m_OR.size() > 0 || m_NOT.size() > 0; | ||
| 22 | } | ||
| 23 | |||
| 24 | bool 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 | |||
| 64 | void 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 | |||
| 87 | void 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 | |||
| 14 | typedef enum TextSearchDefault | ||
| 15 | { | ||
| 16 | SEARCH_DEFAULT_AND = 0, | ||
| 17 | SEARCH_DEFAULT_OR, | ||
| 18 | SEARCH_DEFAULT_NOT | ||
| 19 | } TextSearchDefault; | ||
| 20 | |||
| 21 | class CTextSearch final | ||
| 22 | { | ||
| 23 | public: | ||
| 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 | |||
| 29 | private: | ||
| 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 | |||
| 23 | int64_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 | |||
| 42 | int64_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 | |||
| 55 | unsigned int CTimeUtils::frameTime = 0; | ||
| 56 | |||
| 57 | void 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 | |||
| 70 | unsigned int CTimeUtils::GetFrameTime() | ||
| 71 | { | ||
| 72 | return frameTime; | ||
| 73 | } | ||
| 74 | |||
| 75 | CDateTime 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 | |||
| 98 | std::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 | |||
| 15 | class CDateTime; | ||
| 16 | |||
| 17 | int64_t CurrentHostCounter(void); | ||
| 18 | int64_t CurrentHostFrequency(void); | ||
| 19 | |||
| 20 | class CTimeUtils | ||
| 21 | { | ||
| 22 | public: | ||
| 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); | ||
| 42 | private: | ||
| 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 | |||
| 25 | class TransformMatrix | ||
| 26 | { | ||
| 27 | public: | ||
| 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 | |||
| 237 | inline 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 | |||
| 243 | inline 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 | |||
| 19 | namespace | ||
| 20 | { | ||
| 21 | |||
| 22 | const auto PAGESIZE = getpagesize(); | ||
| 23 | |||
| 24 | int RoundUp(int num, int factor) | ||
| 25 | { | ||
| 26 | return num + factor - 1 - (num - 1) % factor; | ||
| 27 | } | ||
| 28 | |||
| 29 | } // namespace | ||
| 30 | |||
| 31 | std::unique_ptr<CBufferObject> CUDMABufferObject::Create() | ||
| 32 | { | ||
| 33 | return std::make_unique<CUDMABufferObject>(); | ||
| 34 | } | ||
| 35 | |||
| 36 | void 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 | |||
| 51 | CUDMABufferObject::~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 | |||
| 64 | bool 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 | |||
| 89 | bool 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 | |||
| 146 | void 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 | |||
| 167 | uint8_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 | |||
| 190 | void 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 | |||
| 16 | class CUDMABufferObject : public CBufferObject | ||
| 17 | { | ||
| 18 | public: | ||
| 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 | |||
| 34 | private: | ||
| 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 | |||
| 33 | using namespace PVR; | ||
| 34 | using namespace XFILE; | ||
| 35 | |||
| 36 | const CAdvancedSettings* URIUtils::m_advancedSettings = nullptr; | ||
| 37 | |||
| 38 | void URIUtils::RegisterAdvancedSettings(const CAdvancedSettings& advancedSettings) | ||
| 39 | { | ||
| 40 | m_advancedSettings = &advancedSettings; | ||
| 41 | } | ||
| 42 | |||
| 43 | void URIUtils::UnregisterAdvancedSettings() | ||
| 44 | { | ||
| 45 | m_advancedSettings = nullptr; | ||
| 46 | } | ||
| 47 | |||
| 48 | /* returns filename extension including period of filename */ | ||
| 49 | std::string URIUtils::GetExtension(const CURL& url) | ||
| 50 | { | ||
| 51 | return URIUtils::GetExtension(url.GetFileName()); | ||
| 52 | } | ||
| 53 | |||
| 54 | std::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 | |||
| 69 | bool 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 | |||
| 81 | bool URIUtils::HasExtension(const CURL& url, const std::string& strExtensions) | ||
| 82 | { | ||
| 83 | return HasExtension(url.GetFileName(), strExtensions); | ||
| 84 | } | ||
| 85 | |||
| 86 | bool 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 | |||
| 119 | void 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 | |||
| 155 | std::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 | |||
| 180 | std::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*/ | ||
| 187 | std::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 | |||
| 200 | void 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 | |||
| 239 | std::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 | |||
| 262 | void 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 | |||
| 278 | bool 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 | |||
| 286 | bool URIUtils::HasEncodedHostname(const CURL& url) | ||
| 287 | { | ||
| 288 | return HasParentInHostname(url) | ||
| 289 | || url.IsProtocol("musicsearch") | ||
| 290 | || url.IsProtocol( "image"); | ||
| 291 | } | ||
| 292 | |||
| 293 | bool 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 | |||
| 302 | std::string URIUtils::GetParentPath(const std::string& strPath) | ||
| 303 | { | ||
| 304 | std::string strReturn; | ||
| 305 | GetParentPath(strPath, strReturn); | ||
| 306 | return strReturn; | ||
| 307 | } | ||
| 308 | |||
| 309 | bool 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 | |||
| 423 | std::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 | |||
| 445 | std::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 | |||
| 454 | std::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 | |||
| 463 | std::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 | |||
| 493 | CURL URIUtils::SubstitutePath(const CURL& url, bool reverse /* = false */) | ||
| 494 | { | ||
| 495 | const std::string pathToUrl = url.Get(); | ||
| 496 | return CURL(SubstitutePath(pathToUrl, reverse)); | ||
| 497 | } | ||
| 498 | |||
| 499 | std::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 | |||
| 528 | bool URIUtils::IsProtocol(const std::string& url, const std::string &type) | ||
| 529 | { | ||
| 530 | return StringUtils::StartsWithNoCase(url, type + "://"); | ||
| 531 | } | ||
| 532 | |||
| 533 | bool 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 | |||
| 553 | bool 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 | |||
| 570 | bool 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 | |||
| 620 | bool 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 | |||
| 644 | bool 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 | |||
| 673 | static 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 | |||
| 680 | bool 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 | |||
| 720 | bool URIUtils::IsMultiPath(const std::string& strPath) | ||
| 721 | { | ||
| 722 | return IsProtocol(strPath, "multipath"); | ||
| 723 | } | ||
| 724 | |||
| 725 | bool 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 | |||
| 741 | bool 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 | |||
| 767 | bool URIUtils::IsStack(const std::string& strFile) | ||
| 768 | { | ||
| 769 | return IsProtocol(strFile, "stack"); | ||
| 770 | } | ||
| 771 | |||
| 772 | bool 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 | |||
| 788 | bool 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 | |||
| 796 | bool URIUtils::IsInAPK(const std::string& strFile) | ||
| 797 | { | ||
| 798 | CURL url(strFile); | ||
| 799 | |||
| 800 | return url.IsProtocol("apk") && !url.GetFileName().empty(); | ||
| 801 | } | ||
| 802 | |||
| 803 | bool 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 | |||
| 816 | bool 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 | |||
| 829 | bool URIUtils::IsAPK(const std::string& strFile) | ||
| 830 | { | ||
| 831 | return HasExtension(strFile, ".apk"); | ||
| 832 | } | ||
| 833 | |||
| 834 | bool URIUtils::IsZIP(const std::string& strFile) // also checks for comic books! | ||
| 835 | { | ||
| 836 | return HasExtension(strFile, ".zip|.cbz"); | ||
| 837 | } | ||
| 838 | |||
| 839 | bool URIUtils::IsArchive(const std::string& strFile) | ||
| 840 | { | ||
| 841 | return HasExtension(strFile, ".zip|.rar|.apk|.cbz|.cbr"); | ||
| 842 | } | ||
| 843 | |||
| 844 | bool 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 | |||
| 852 | bool URIUtils::IsPlugin(const std::string& strFile) | ||
| 853 | { | ||
| 854 | CURL url(strFile); | ||
| 855 | return url.IsProtocol("plugin"); | ||
| 856 | } | ||
| 857 | |||
| 858 | bool URIUtils::IsScript(const std::string& strFile) | ||
| 859 | { | ||
| 860 | CURL url(strFile); | ||
| 861 | return url.IsProtocol("script"); | ||
| 862 | } | ||
| 863 | |||
| 864 | bool URIUtils::IsAddonsPath(const std::string& strFile) | ||
| 865 | { | ||
| 866 | CURL url(strFile); | ||
| 867 | return url.IsProtocol("addons"); | ||
| 868 | } | ||
| 869 | |||
| 870 | bool URIUtils::IsSourcesPath(const std::string& strPath) | ||
| 871 | { | ||
| 872 | CURL url(strPath); | ||
| 873 | return url.IsProtocol("sources"); | ||
| 874 | } | ||
| 875 | |||
| 876 | bool URIUtils::IsCDDA(const std::string& strFile) | ||
| 877 | { | ||
| 878 | return IsProtocol(strFile, "cdda"); | ||
| 879 | } | ||
| 880 | |||
| 881 | bool URIUtils::IsISO9660(const std::string& strFile) | ||
| 882 | { | ||
| 883 | return IsProtocol(strFile, "iso9660"); | ||
| 884 | } | ||
| 885 | |||
| 886 | bool 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 | |||
| 901 | bool URIUtils::IsURL(const std::string& strFile) | ||
| 902 | { | ||
| 903 | return strFile.find("://") != std::string::npos; | ||
| 904 | } | ||
| 905 | |||
| 906 | bool 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 | |||
| 922 | bool 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 | |||
| 938 | bool 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 | |||
| 946 | bool 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 | |||
| 954 | bool 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 | |||
| 962 | bool 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 | |||
| 970 | bool 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 | |||
| 978 | bool 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 | |||
| 986 | bool 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 | |||
| 1002 | bool URIUtils::IsInternetStream(const std::string &path, bool bStrictCheck /* = false */) | ||
| 1003 | { | ||
| 1004 | const CURL pathToUrl(path); | ||
| 1005 | return IsInternetStream(pathToUrl, bStrictCheck); | ||
| 1006 | } | ||
| 1007 | |||
| 1008 | bool 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 | |||
| 1038 | bool URIUtils::IsUPnP(const std::string& strFile) | ||
| 1039 | { | ||
| 1040 | return IsProtocol(strFile, "upnp"); | ||
| 1041 | } | ||
| 1042 | |||
| 1043 | bool 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 | |||
| 1055 | bool 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 | |||
| 1064 | bool URIUtils::IsPVRRecordingFileOrFolder(const std::string& strFile) | ||
| 1065 | { | ||
| 1066 | return StringUtils::StartsWith(strFile, "pvr://recordings"); | ||
| 1067 | } | ||
| 1068 | |||
| 1069 | bool URIUtils::IsMusicDb(const std::string& strFile) | ||
| 1070 | { | ||
| 1071 | return IsProtocol(strFile, "musicdb"); | ||
| 1072 | } | ||
| 1073 | |||
| 1074 | bool 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 | |||
| 1089 | bool URIUtils::IsVideoDb(const std::string& strFile) | ||
| 1090 | { | ||
| 1091 | return IsProtocol(strFile, "videodb"); | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | bool URIUtils::IsBluray(const std::string& strFile) | ||
| 1095 | { | ||
| 1096 | return IsProtocol(strFile, "bluray"); | ||
| 1097 | } | ||
| 1098 | |||
| 1099 | bool URIUtils::IsAndroidApp(const std::string &path) | ||
| 1100 | { | ||
| 1101 | return IsProtocol(path, "androidapp"); | ||
| 1102 | } | ||
| 1103 | |||
| 1104 | bool URIUtils::IsLibraryFolder(const std::string& strFile) | ||
| 1105 | { | ||
| 1106 | CURL url(strFile); | ||
| 1107 | return url.IsProtocol("library"); | ||
| 1108 | } | ||
| 1109 | |||
| 1110 | bool 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 | |||
| 1118 | bool 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 | |||
| 1130 | std::string URIUtils::AppendSlash(std::string strFolder) | ||
| 1131 | { | ||
| 1132 | AddSlashAtEnd(strFolder); | ||
| 1133 | return strFolder; | ||
| 1134 | } | ||
| 1135 | |||
| 1136 | void 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 | |||
| 1160 | bool 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 | |||
| 1177 | void 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 | |||
| 1202 | bool 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 | |||
| 1211 | std::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 | |||
| 1241 | std::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 | |||
| 1274 | std::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 | |||
| 1306 | std::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 | |||
| 1322 | CURL 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 | |||
| 1345 | std::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 | |||
| 1357 | std::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 | |||
| 1403 | bool 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 | |||
| 14 | class CURL; | ||
| 15 | class CAdvancedSettings; | ||
| 16 | |||
| 17 | class URIUtils | ||
| 18 | { | ||
| 19 | public: | ||
| 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 | |||
| 223 | private: | ||
| 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 | |||
| 15 | CUrlOptions::CUrlOptions() = default; | ||
| 16 | |||
| 17 | CUrlOptions::CUrlOptions(const std::string &options, const char *strLead /* = "" */) | ||
| 18 | : m_strLead(strLead) | ||
| 19 | { | ||
| 20 | AddOptions(options); | ||
| 21 | } | ||
| 22 | |||
| 23 | CUrlOptions::~CUrlOptions() = default; | ||
| 24 | |||
| 25 | std::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 | |||
| 49 | void 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 | |||
| 57 | void 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 | |||
| 65 | void CUrlOptions::AddOption(const std::string &key, int value) | ||
| 66 | { | ||
| 67 | if (key.empty()) | ||
| 68 | return; | ||
| 69 | |||
| 70 | m_options[key] = value; | ||
| 71 | } | ||
| 72 | |||
| 73 | void CUrlOptions::AddOption(const std::string &key, float value) | ||
| 74 | { | ||
| 75 | if (key.empty()) | ||
| 76 | return; | ||
| 77 | |||
| 78 | m_options[key] = value; | ||
| 79 | } | ||
| 80 | |||
| 81 | void CUrlOptions::AddOption(const std::string &key, double value) | ||
| 82 | { | ||
| 83 | if (key.empty()) | ||
| 84 | return; | ||
| 85 | |||
| 86 | m_options[key] = value; | ||
| 87 | } | ||
| 88 | |||
| 89 | void CUrlOptions::AddOption(const std::string &key, bool value) | ||
| 90 | { | ||
| 91 | if (key.empty()) | ||
| 92 | return; | ||
| 93 | |||
| 94 | m_options[key] = value; | ||
| 95 | } | ||
| 96 | |||
| 97 | void 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 | |||
| 135 | void CUrlOptions::AddOptions(const CUrlOptions &options) | ||
| 136 | { | ||
| 137 | m_options.insert(options.m_options.begin(), options.m_options.end()); | ||
| 138 | } | ||
| 139 | |||
| 140 | void 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 | |||
| 150 | bool 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 | |||
| 158 | bool 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 | |||
| 16 | class CUrlOptions | ||
| 17 | { | ||
| 18 | public: | ||
| 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 | |||
| 43 | protected: | ||
| 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 | |||
| 12 | CUtf8Utils::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 | |||
| 38 | size_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 | |||
| 55 | size_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 | |||
| 74 | inline 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 | ||
| 84 | inline 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 | |||
| 13 | class CUtf8Utils | ||
| 14 | { | ||
| 15 | public: | ||
| 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); | ||
| 40 | private: | ||
| 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 | |||
| 13 | enum | ||
| 14 | { | ||
| 15 | VC1_PROFILE_SIMPLE, | ||
| 16 | VC1_PROFILE_MAIN, | ||
| 17 | VC1_PROFILE_RESERVED, | ||
| 18 | VC1_PROFILE_ADVANCED, | ||
| 19 | VC1_PROFILE_NOPROFILE | ||
| 20 | }; | ||
| 21 | |||
| 22 | enum | ||
| 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 | |||
| 37 | enum | ||
| 38 | { | ||
| 39 | VC1_FRAME_PROGRESSIVE = 0x0, | ||
| 40 | VC1_FRAME_INTERLACE = 0x10, | ||
| 41 | VC1_FIELD_INTERLACE = 0x11 | ||
| 42 | }; | ||
| 43 | |||
| 44 | CVC1BitstreamParser::CVC1BitstreamParser() | ||
| 45 | { | ||
| 46 | Reset(); | ||
| 47 | } | ||
| 48 | |||
| 49 | void CVC1BitstreamParser::Reset() | ||
| 50 | { | ||
| 51 | m_Profile = VC1_PROFILE_NOPROFILE; | ||
| 52 | } | ||
| 53 | |||
| 54 | bool CVC1BitstreamParser::IsRecoveryPoint(const uint8_t *buf, int buf_size) | ||
| 55 | { | ||
| 56 | return vc1_parse_frame(buf, buf + buf_size, true); | ||
| 57 | }; | ||
| 58 | |||
| 59 | bool CVC1BitstreamParser::IsIFrame(const uint8_t *buf, int buf_size) | ||
| 60 | { | ||
| 61 | return vc1_parse_frame(buf, buf + buf_size, false); | ||
| 62 | }; | ||
| 63 | |||
| 64 | bool 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 | |||
| 13 | class CVC1BitstreamParser | ||
| 14 | { | ||
| 15 | public: | ||
| 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 | |||
| 24 | protected: | ||
| 25 | bool vc1_parse_frame(const uint8_t *buf, const uint8_t *buf_end, bool sequenceOnly); | ||
| 26 | private: | ||
| 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 | |||
| 31 | std::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 | |||
| 42 | std::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 | |||
| 53 | int64_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 | |||
| 64 | int64_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 | |||
| 75 | uint64_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 | |||
| 86 | uint64_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 | |||
| 97 | double 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 | |||
| 108 | double 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 | |||
| 119 | CVariant::CVariant() | ||
| 120 | : CVariant(VariantTypeNull) | ||
| 121 | { | ||
| 122 | } | ||
| 123 | |||
| 124 | CVariant CVariant::ConstNullVariant = CVariant::VariantTypeConstNull; | ||
| 125 | CVariant::VariantArray CVariant::EMPTY_ARRAY; | ||
| 126 | CVariant::VariantMap CVariant::EMPTY_MAP; | ||
| 127 | |||
| 128 | CVariant::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 | |||
| 166 | CVariant::CVariant(int integer) | ||
| 167 | { | ||
| 168 | m_type = VariantTypeInteger; | ||
| 169 | m_data.integer = integer; | ||
| 170 | } | ||
| 171 | |||
| 172 | CVariant::CVariant(int64_t integer) | ||
| 173 | { | ||
| 174 | m_type = VariantTypeInteger; | ||
| 175 | m_data.integer = integer; | ||
| 176 | } | ||
| 177 | |||
| 178 | CVariant::CVariant(unsigned int unsignedinteger) | ||
| 179 | { | ||
| 180 | m_type = VariantTypeUnsignedInteger; | ||
| 181 | m_data.unsignedinteger = unsignedinteger; | ||
| 182 | } | ||
| 183 | |||
| 184 | CVariant::CVariant(uint64_t unsignedinteger) | ||
| 185 | { | ||
| 186 | m_type = VariantTypeUnsignedInteger; | ||
| 187 | m_data.unsignedinteger = unsignedinteger; | ||
| 188 | } | ||
| 189 | |||
| 190 | CVariant::CVariant(double value) | ||
| 191 | { | ||
| 192 | m_type = VariantTypeDouble; | ||
| 193 | m_data.dvalue = value; | ||
| 194 | } | ||
| 195 | |||
| 196 | CVariant::CVariant(float value) | ||
| 197 | { | ||
| 198 | m_type = VariantTypeDouble; | ||
| 199 | m_data.dvalue = (double)value; | ||
| 200 | } | ||
| 201 | |||
| 202 | CVariant::CVariant(bool boolean) | ||
| 203 | { | ||
| 204 | m_type = VariantTypeBoolean; | ||
| 205 | m_data.boolean = boolean; | ||
| 206 | } | ||
| 207 | |||
| 208 | CVariant::CVariant(const char *str) | ||
| 209 | { | ||
| 210 | m_type = VariantTypeString; | ||
| 211 | m_data.string = new std::string(str); | ||
| 212 | } | ||
| 213 | |||
| 214 | CVariant::CVariant(const char *str, unsigned int length) | ||
| 215 | { | ||
| 216 | m_type = VariantTypeString; | ||
| 217 | m_data.string = new std::string(str, length); | ||
| 218 | } | ||
| 219 | |||
| 220 | CVariant::CVariant(const std::string &str) | ||
| 221 | { | ||
| 222 | m_type = VariantTypeString; | ||
| 223 | m_data.string = new std::string(str); | ||
| 224 | } | ||
| 225 | |||
| 226 | CVariant::CVariant(std::string &&str) | ||
| 227 | { | ||
| 228 | m_type = VariantTypeString; | ||
| 229 | m_data.string = new std::string(std::move(str)); | ||
| 230 | } | ||
| 231 | |||
| 232 | CVariant::CVariant(const wchar_t *str) | ||
| 233 | { | ||
| 234 | m_type = VariantTypeWideString; | ||
| 235 | m_data.wstring = new std::wstring(str); | ||
| 236 | } | ||
| 237 | |||
| 238 | CVariant::CVariant(const wchar_t *str, unsigned int length) | ||
| 239 | { | ||
| 240 | m_type = VariantTypeWideString; | ||
| 241 | m_data.wstring = new std::wstring(str, length); | ||
| 242 | } | ||
| 243 | |||
| 244 | CVariant::CVariant(const std::wstring &str) | ||
| 245 | { | ||
| 246 | m_type = VariantTypeWideString; | ||
| 247 | m_data.wstring = new std::wstring(str); | ||
| 248 | } | ||
| 249 | |||
| 250 | CVariant::CVariant(std::wstring &&str) | ||
| 251 | { | ||
| 252 | m_type = VariantTypeWideString; | ||
| 253 | m_data.wstring = new std::wstring(std::move(str)); | ||
| 254 | } | ||
| 255 | |||
| 256 | CVariant::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 | |||
| 265 | CVariant::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 | |||
| 273 | CVariant::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 | |||
| 279 | CVariant::CVariant(const CVariant &variant) | ||
| 280 | { | ||
| 281 | m_type = VariantTypeNull; | ||
| 282 | *this = variant; | ||
| 283 | } | ||
| 284 | |||
| 285 | CVariant::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 | |||
| 294 | CVariant::~CVariant() | ||
| 295 | { | ||
| 296 | cleanup(); | ||
| 297 | } | ||
| 298 | |||
| 299 | void 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 | |||
| 328 | bool CVariant::isInteger() const | ||
| 329 | { | ||
| 330 | return isSignedInteger() || isUnsignedInteger(); | ||
| 331 | } | ||
| 332 | |||
| 333 | bool CVariant::isSignedInteger() const | ||
| 334 | { | ||
| 335 | return m_type == VariantTypeInteger; | ||
| 336 | } | ||
| 337 | |||
| 338 | bool CVariant::isUnsignedInteger() const | ||
| 339 | { | ||
| 340 | return m_type == VariantTypeUnsignedInteger; | ||
| 341 | } | ||
| 342 | |||
| 343 | bool CVariant::isBoolean() const | ||
| 344 | { | ||
| 345 | return m_type == VariantTypeBoolean; | ||
| 346 | } | ||
| 347 | |||
| 348 | bool CVariant::isDouble() const | ||
| 349 | { | ||
| 350 | return m_type == VariantTypeDouble; | ||
| 351 | } | ||
| 352 | |||
| 353 | bool CVariant::isString() const | ||
| 354 | { | ||
| 355 | return m_type == VariantTypeString; | ||
| 356 | } | ||
| 357 | |||
| 358 | bool CVariant::isWideString() const | ||
| 359 | { | ||
| 360 | return m_type == VariantTypeWideString; | ||
| 361 | } | ||
| 362 | |||
| 363 | bool CVariant::isArray() const | ||
| 364 | { | ||
| 365 | return m_type == VariantTypeArray; | ||
| 366 | } | ||
| 367 | |||
| 368 | bool CVariant::isObject() const | ||
| 369 | { | ||
| 370 | return m_type == VariantTypeObject; | ||
| 371 | } | ||
| 372 | |||
| 373 | bool CVariant::isNull() const | ||
| 374 | { | ||
| 375 | return m_type == VariantTypeNull || m_type == VariantTypeConstNull; | ||
| 376 | } | ||
| 377 | |||
| 378 | CVariant::VariantType CVariant::type() const | ||
| 379 | { | ||
| 380 | return m_type; | ||
| 381 | } | ||
| 382 | |||
| 383 | int64_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 | |||
| 404 | int32_t CVariant::asInteger32(int32_t fallback) const | ||
| 405 | { | ||
| 406 | return static_cast<int32_t>(asInteger(fallback)); | ||
| 407 | } | ||
| 408 | |||
| 409 | uint64_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 | |||
| 430 | uint32_t CVariant::asUnsignedInteger32(uint32_t fallback) const | ||
| 431 | { | ||
| 432 | return static_cast<uint32_t>(asUnsignedInteger(fallback)); | ||
| 433 | } | ||
| 434 | |||
| 435 | double 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 | |||
| 456 | float 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 | |||
| 477 | bool 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 | |||
| 504 | std::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 | |||
| 525 | std::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 | |||
| 546 | CVariant &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 | |||
| 560 | const 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 | |||
| 569 | CVariant &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 | |||
| 577 | const 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 | |||
| 585 | CVariant &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 | |||
| 627 | CVariant& 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 | |||
| 655 | bool 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 | |||
| 685 | void 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 | |||
| 696 | void 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 | |||
| 708 | void 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 | |||
| 720 | void CVariant::append(const CVariant &variant) | ||
| 721 | { | ||
| 722 | push_back(variant); | ||
| 723 | } | ||
| 724 | |||
| 725 | void CVariant::append(CVariant&& variant) | ||
| 726 | { | ||
| 727 | push_back(std::move(variant)); | ||
| 728 | } | ||
| 729 | |||
| 730 | const 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 | |||
| 738 | void 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 | |||
| 750 | CVariant::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 | |||
| 758 | CVariant::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 | |||
| 766 | CVariant::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 | |||
| 774 | CVariant::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 | |||
| 782 | CVariant::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 | |||
| 790 | CVariant::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 | |||
| 798 | CVariant::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 | |||
| 806 | CVariant::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 | |||
| 814 | unsigned 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 | |||
| 828 | bool 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 | |||
| 844 | void 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 | |||
| 856 | void 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 | |||
| 867 | void 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 | |||
| 879 | bool 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 | |||
| 17 | int64_t str2int64(const std::string &str, int64_t fallback = 0); | ||
| 18 | int64_t str2int64(const std::wstring &str, int64_t fallback = 0); | ||
| 19 | uint64_t str2uint64(const std::string &str, uint64_t fallback = 0); | ||
| 20 | uint64_t str2uint64(const std::wstring &str, uint64_t fallback = 0); | ||
| 21 | double str2double(const std::string &str, double fallback = 0.0); | ||
| 22 | double 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 | |||
| 29 | class CVariant | ||
| 30 | { | ||
| 31 | public: | ||
| 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 | |||
| 115 | private: | ||
| 116 | typedef std::vector<CVariant> VariantArray; | ||
| 117 | typedef std::map<std::string, CVariant> VariantMap; | ||
| 118 | |||
| 119 | public: | ||
| 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 | |||
| 146 | private: | ||
| 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 | |||
| 13 | CVector& CVector::operator+=(const CVector &other) | ||
| 14 | { | ||
| 15 | x += other.x; | ||
| 16 | y += other.y; | ||
| 17 | |||
| 18 | return *this; | ||
| 19 | } | ||
| 20 | |||
| 21 | CVector& CVector::operator-=(const CVector &other) | ||
| 22 | { | ||
| 23 | x -= other.x; | ||
| 24 | y -= other.y; | ||
| 25 | |||
| 26 | return *this; | ||
| 27 | } | ||
| 28 | |||
| 29 | float 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 | |||
| 11 | class CVector | ||
| 12 | { | ||
| 13 | public: | ||
| 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 | |||
| 23 | CXBMCTinyXML::CXBMCTinyXML() | ||
| 24 | : TiXmlDocument() | ||
| 25 | { | ||
| 26 | } | ||
| 27 | |||
| 28 | CXBMCTinyXML::CXBMCTinyXML(const char *documentName) | ||
| 29 | : TiXmlDocument(documentName) | ||
| 30 | { | ||
| 31 | } | ||
| 32 | |||
| 33 | CXBMCTinyXML::CXBMCTinyXML(const std::string& documentName) | ||
| 34 | : TiXmlDocument(documentName) | ||
| 35 | { | ||
| 36 | } | ||
| 37 | |||
| 38 | CXBMCTinyXML::CXBMCTinyXML(const std::string& documentName, const std::string& documentCharset) | ||
| 39 | : TiXmlDocument(documentName), m_SuggestedCharset(documentCharset) | ||
| 40 | { | ||
| 41 | StringUtils::ToUpper(m_SuggestedCharset); | ||
| 42 | } | ||
| 43 | |||
| 44 | bool CXBMCTinyXML::LoadFile(TiXmlEncoding encoding) | ||
| 45 | { | ||
| 46 | return LoadFile(value, encoding); | ||
| 47 | } | ||
| 48 | |||
| 49 | bool CXBMCTinyXML::LoadFile(const char *_filename, TiXmlEncoding encoding) | ||
| 50 | { | ||
| 51 | return LoadFile(std::string(_filename), encoding); | ||
| 52 | } | ||
| 53 | |||
| 54 | bool 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 | |||
| 84 | bool 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 | |||
| 91 | bool 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 | |||
| 102 | bool CXBMCTinyXML::SaveFile(const char *_filename) const | ||
| 103 | { | ||
| 104 | return SaveFile(std::string(_filename)); | ||
| 105 | } | ||
| 106 | |||
| 107 | bool 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 | |||
| 123 | bool 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 | |||
| 130 | bool 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 | |||
| 195 | bool 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 | |||
| 225 | bool CXBMCTinyXML::InternalParse(const std::string& rawdata, TiXmlEncoding encoding /*= TIXML_DEFAULT_ENCODING */) | ||
| 226 | { | ||
| 227 | // Preprocess string, replacing '&' with '& 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 | |||
| 244 | bool 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???</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 | |||
| 33 | class CXBMCTinyXML : public TiXmlDocument | ||
| 34 | { | ||
| 35 | public: | ||
| 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(); | ||
| 52 | protected: | ||
| 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 | |||
| 13 | bool 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 | |||
| 21 | bool 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 | |||
| 29 | bool 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 | |||
| 40 | bool 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 | |||
| 48 | bool 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 | |||
| 56 | bool 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 | |||
| 67 | bool 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 | |||
| 75 | bool 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 | |||
| 83 | bool 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 | |||
| 94 | bool 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 | |||
| 111 | bool 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 | |||
| 129 | std::string XMLUtils::GetString(const TiXmlNode* pRootNode, const char* strTag) | ||
| 130 | { | ||
| 131 | std::string temp; | ||
| 132 | GetString(pRootNode, strTag, temp); | ||
| 133 | return temp; | ||
| 134 | } | ||
| 135 | |||
| 136 | bool 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 | |||
| 144 | bool 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 | */ | ||
| 175 | bool 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 | |||
| 210 | bool 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 | |||
| 228 | bool 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 | |||
| 240 | bool 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 | |||
| 252 | std::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 | |||
| 263 | void 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 | |||
| 270 | void 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 | |||
| 276 | TiXmlNode* 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 | |||
| 288 | TiXmlNode* 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 | |||
| 294 | void 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 | |||
| 300 | TiXmlNode* 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 | |||
| 306 | TiXmlNode* 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 | |||
| 312 | void XMLUtils::SetBoolean(TiXmlNode* pRootNode, const char *strTag, bool value) | ||
| 313 | { | ||
| 314 | SetString(pRootNode, strTag, value ? "true" : "false"); | ||
| 315 | } | ||
| 316 | |||
| 317 | void 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 | |||
| 323 | void 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 | |||
| 335 | void XMLUtils::SetDate(TiXmlNode* pRootNode, const char *strTag, const CDateTime& date) | ||
| 336 | { | ||
| 337 | SetString(pRootNode, strTag, date.IsValid() ? date.GetAsDBDate() : ""); | ||
| 338 | } | ||
| 339 | |||
| 340 | void 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 | |||
| 17 | class CDateTime; | ||
| 18 | |||
| 19 | class XMLUtils | ||
| 20 | { | ||
| 21 | public: | ||
| 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 | ||
| 19 | void 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 | |||
| 28 | XSLTUtils::XSLTUtils() | ||
| 29 | { | ||
| 30 | // initialize libxslt | ||
| 31 | xmlSubstituteEntitiesDefault(1); | ||
| 32 | xmlLoadExtDtdDefaultValue = 0; | ||
| 33 | xsltSetGenericErrorFunc(NULL, err); | ||
| 34 | } | ||
| 35 | |||
| 36 | XSLTUtils::~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 | |||
| 46 | bool 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 | |||
| 72 | bool 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 | |||
| 80 | bool 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 | |||
| 16 | class XSLTUtils | ||
| 17 | { | ||
| 18 | public: | ||
| 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 | |||
| 46 | private: | ||
| 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 | |||
| 24 | namespace KODI | ||
| 25 | { | ||
| 26 | namespace TIME | ||
| 27 | { | ||
| 28 | struct 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 | |||
| 40 | struct 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 | |||
| 51 | constexpr int KODI_TIME_ZONE_ID_INVALID{-1}; | ||
| 52 | constexpr int KODI_TIME_ZONE_ID_UNKNOWN{0}; | ||
| 53 | constexpr int KODI_TIME_ZONE_ID_STANDARD{1}; | ||
| 54 | constexpr int KODI_TIME_ZONE_ID_DAYLIGHT{2}; | ||
| 55 | |||
| 56 | struct FileTime | ||
| 57 | { | ||
| 58 | unsigned int lowDateTime; | ||
| 59 | unsigned int highDateTime; | ||
| 60 | }; | ||
| 61 | |||
| 62 | void GetLocalTime(SystemTime* systemTime); | ||
| 63 | uint32_t GetTimeZoneInformation(TimeZoneInformation* timeZoneInformation); | ||
| 64 | |||
| 65 | void Sleep(uint32_t milliSeconds); | ||
| 66 | |||
| 67 | int FileTimeToLocalFileTime(const FileTime* fileTime, FileTime* localFileTime); | ||
| 68 | int SystemTimeToFileTime(const SystemTime* systemTime, FileTime* fileTime); | ||
| 69 | long CompareFileTime(const FileTime* fileTime1, const FileTime* fileTime2); | ||
| 70 | int FileTimeToSystemTime(const FileTime* fileTime, SystemTime* systemTime); | ||
| 71 | int LocalFileTimeToFileTime(const FileTime* LocalFileTime, FileTime* fileTime); | ||
| 72 | |||
| 73 | int FileTimeToTimeT(const FileTime* localFileTime, time_t* pTimeT); | ||
| 74 | int 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 | |||
| 14 | using namespace XUTILS; | ||
| 15 | |||
| 16 | auto_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 | |||
| 27 | auto_buffer::~auto_buffer() | ||
| 28 | { | ||
| 29 | free(p); | ||
| 30 | } | ||
| 31 | |||
| 32 | auto_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 | |||
| 45 | auto_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 | |||
| 58 | auto_buffer& auto_buffer::clear(void) | ||
| 59 | { | ||
| 60 | free(p); | ||
| 61 | p = 0; | ||
| 62 | s = 0; | ||
| 63 | return *this; | ||
| 64 | } | ||
| 65 | |||
| 66 | auto_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 | |||
| 77 | void* 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 | |||
| 13 | namespace 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 | |||
| 37 | static constexpr unsigned char Utf8Bom[3] = {0xEF, 0xBB, 0xBF}; | ||
| 38 | static const std::string LogFileExtension = ".log"; | ||
| 39 | static const std::string LogPattern = "%Y-%m-%d %T.%e T:%-5t %7l <%n>: %v"; | ||
| 40 | |||
| 41 | CLog::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 | |||
| 65 | void 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 | |||
| 72 | void 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 | |||
| 85 | void 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 | |||
| 131 | void 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 | |||
| 154 | void 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 | |||
| 175 | bool 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 | |||
| 185 | bool 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 | |||
| 193 | void 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 | |||
| 227 | Logger 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 | |||
| 236 | CLog& CLog::GetInstance() | ||
| 237 | { | ||
| 238 | return CServiceBroker::GetLogging(); | ||
| 239 | } | ||
| 240 | |||
| 241 | spdlog::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 | |||
| 267 | Logger 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 | |||
| 278 | void 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 | |||
| 27 | namespace spdlog | ||
| 28 | { | ||
| 29 | namespace sinks | ||
| 30 | { | ||
| 31 | template<typename Mutex> | ||
| 32 | class basic_file_sink; | ||
| 33 | |||
| 34 | template<typename Mutex> | ||
| 35 | class dist_sink; | ||
| 36 | } // namespace sinks | ||
| 37 | } // namespace spdlog | ||
| 38 | |||
| 39 | class CLog : public ISettingsHandler, public ISettingCallback | ||
| 40 | { | ||
| 41 | public: | ||
| 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 | |||
| 148 | private: | ||
| 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 | |||
| 206 | namespace XbmcUtils | ||
| 207 | { | ||
| 208 | class LogImplementation : public XbmcCommons::ILogger | ||
| 209 | { | ||
| 210 | public: | ||
| 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 | |||
| 13 | namespace spdlog | ||
| 14 | { | ||
| 15 | class logger; | ||
| 16 | } | ||
| 17 | |||
| 18 | using Logger = std::shared_ptr<spdlog::logger>; | ||
diff --git a/xbmc/utils/params_check_macros.h b/xbmc/utils/params_check_macros.h new file mode 100644 index 0000000..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 | |||
| 16 | RFFT::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 | |||
| 22 | RFFT::~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 | |||
| 31 | void 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 | |||
| 68 | void 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. | ||
| 16 | class RFFT | ||
| 17 | { | ||
| 18 | public: | ||
| 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); | ||
| 31 | protected: | ||
| 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 @@ | |||
| 1 | set(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 | |||
| 47 | set(HEADERS TestGlobalsHandlingPattern1.h) | ||
| 48 | |||
| 49 | if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore) | ||
| 50 | list(APPEND SOURCES TestCryptThreading.cpp) | ||
| 51 | endif() | ||
| 52 | |||
| 53 | core_add_test_library(utils_test) | ||
diff --git a/xbmc/utils/test/CXBMCTinyXML-test.xml b/xbmc/utils/test/CXBMCTinyXML-test.xml new file mode 100644 index 0000000..9444dc8 --- /dev/null +++ b/xbmc/utils/test/CXBMCTinyXML-test.xml | |||
| @@ -0,0 +1,6 @@ | |||
| 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??? | ||
| 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 | |||
| 13 | TEST(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 | |||
| 18 | TEST(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 | |||
| 53 | TEST(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 | |||
| 21 | class TestArchive : public testing::Test | ||
| 22 | { | ||
| 23 | protected: | ||
| 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 | |||
| 35 | TEST_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 | |||
| 44 | TEST_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 | |||
| 53 | TEST_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 | |||
| 70 | TEST_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 | |||
| 87 | TEST_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 | |||
| 104 | TEST_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 | |||
| 121 | TEST_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 | |||
| 138 | TEST_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 | |||
| 155 | TEST_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 | |||
| 172 | TEST_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 | |||
| 189 | TEST_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 | |||
| 206 | TEST_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 | |||
| 223 | TEST_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 | |||
| 241 | TEST_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 | |||
| 259 | TEST_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 | |||
| 277 | TEST_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 | |||
| 301 | TEST_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 | |||
| 325 | TEST_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 | |||
| 13 | static 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 | |||
| 20 | static const char refbase64data[] = "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY" | ||
| 21 | "GRobHB0eHyAhIiMkJSYnKCkqKywtLi8w"; | ||
| 22 | |||
| 23 | TEST(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 | |||
| 30 | TEST(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 | |||
| 37 | TEST(TestBase64, Encode_3) | ||
| 38 | { | ||
| 39 | std::string a; | ||
| 40 | Base64::Encode(refdata, a); | ||
| 41 | EXPECT_STREQ(refbase64data, a.c_str()); | ||
| 42 | } | ||
| 43 | |||
| 44 | TEST(TestBase64, Encode_4) | ||
| 45 | { | ||
| 46 | std::string a; | ||
| 47 | a = Base64::Encode(refdata); | ||
| 48 | EXPECT_STREQ(refbase64data, a.c_str()); | ||
| 49 | } | ||
| 50 | |||
| 51 | TEST(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 | |||
| 58 | TEST(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 | |||
| 65 | TEST(TestBase64, Decode_3) | ||
| 66 | { | ||
| 67 | std::string a; | ||
| 68 | Base64::Decode(refbase64data, a); | ||
| 69 | EXPECT_STREQ(refdata, a.c_str()); | ||
| 70 | } | ||
| 71 | |||
| 72 | TEST(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 | |||
| 17 | class CTestBitstreamStatsThread : public CThread | ||
| 18 | { | ||
| 19 | public: | ||
| 20 | CTestBitstreamStatsThread() : | ||
| 21 | CThread("TestBitstreamStats"){} | ||
| 22 | |||
| 23 | }; | ||
| 24 | |||
| 25 | TEST(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 | |||
| 22 | struct TestCPUInfo : public ::testing::Test | ||
| 23 | { | ||
| 24 | TestCPUInfo() { CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo()); } | ||
| 25 | |||
| 26 | ~TestCPUInfo() { CServiceBroker::UnregisterCPUInfo(); } | ||
| 27 | }; | ||
| 28 | |||
| 29 | TEST_F(TestCPUInfo, GetUsedPercentage) | ||
| 30 | { | ||
| 31 | EXPECT_GE(CServiceBroker::GetCPUInfo()->GetUsedPercentage(), 0); | ||
| 32 | } | ||
| 33 | |||
| 34 | TEST_F(TestCPUInfo, GetCPUCount) | ||
| 35 | { | ||
| 36 | EXPECT_GT(CServiceBroker::GetCPUInfo()->GetCPUCount(), 0); | ||
| 37 | } | ||
| 38 | |||
| 39 | TEST_F(TestCPUInfo, GetCPUFrequency) | ||
| 40 | { | ||
| 41 | EXPECT_GE(CServiceBroker::GetCPUInfo()->GetCPUFrequency(), 0.f); | ||
| 42 | } | ||
| 43 | |||
| 44 | #if defined(TARGET_WINDOWS) | ||
| 45 | TEST_F(TestCPUInfo, DISABLED_GetTemperature) | ||
| 46 | #else | ||
| 47 | TEST_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 | |||
| 56 | TEST_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 | |||
| 63 | TEST_F(TestCPUInfo, GetCoresUsageString) | ||
| 64 | { | ||
| 65 | EXPECT_STRNE("", CServiceBroker::GetCPUInfo()->GetCoresUsageString().c_str()); | ||
| 66 | } | ||
| 67 | |||
| 68 | TEST_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 | ||
| 18 | static const uint16_t refutf16LE1[] = { 0xff54, 0xff45, 0xff53, 0xff54, | ||
| 19 | 0xff3f, 0xff55, 0xff54, 0xff46, | ||
| 20 | 0xff11, 0xff16, 0xff2c, 0xff25, | ||
| 21 | 0xff54, 0xff4f, 0xff57, 0x0 }; | ||
| 22 | |||
| 23 | static 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 | |||
| 36 | static 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 | ||
| 42 | static 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 | |||
| 48 | static 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 | |||
| 65 | static 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 | |||
| 71 | static 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 | |||
| 77 | class TestCharsetConverter : public testing::Test | ||
| 78 | { | ||
| 79 | protected: | ||
| 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 | |||
| 115 | TEST_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 | |||
| 136 | TEST_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 | |||
| 146 | TEST_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 | |||
| 154 | TEST_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 | |||
| 162 | TEST_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 | |||
| 170 | TEST_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 | /* | ||
| 179 | TEST_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 | |||
| 211 | TEST_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 | |||
| 219 | TEST_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 | |||
| 226 | TEST_F(TestCharsetConverter, isValidUtf8_2) | ||
| 227 | { | ||
| 228 | refstr1 = refutf16LE3; | ||
| 229 | EXPECT_FALSE(CUtf8Utils::isValidUtf8(refstr1)); | ||
| 230 | } | ||
| 231 | |||
| 232 | TEST_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 | |||
| 239 | TEST_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 | |||
| 251 | TEST_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 | |||
| 287 | TEST_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 | |||
| 302 | TEST_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 | |||
| 340 | TEST_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 | |||
| 350 | TEST_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 | |||
| 360 | TEST_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 | |||
| 368 | TEST_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 | |||
| 376 | TEST_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 | |||
| 389 | TEST_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 | |||
| 13 | static const char refdata[] = "abcdefghijklmnopqrstuvwxyz" | ||
| 14 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
| 15 | "01234567890!@#$%^&*()"; | ||
| 16 | |||
| 17 | TEST(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 | |||
| 26 | TEST(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 | |||
| 34 | TEST(TestCrc32, ComputeFromLowerCase) | ||
| 35 | { | ||
| 36 | std::string s = refdata; | ||
| 37 | uint32_t varcrc = Crc32::ComputeFromLowerCase(s); | ||
| 38 | EXPECT_EQ((uint32_t)0x7f045b3e, varcrc); | ||
| 39 | } | ||
| 40 | |||
| 41 | TEST(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 | |||
| 20 | TEST(TestCryptThreadingInitializer, General) | ||
| 21 | { | ||
| 22 | std::cout << "g_cryptThreadingInitializer address: " << | ||
| 23 | testing::PrintToString(&g_cryptThreadingInitializer) << "\n"; | ||
| 24 | } | ||
| 25 | |||
| 26 | #define PVTID_NUM_THREADS 10 | ||
| 27 | |||
| 28 | TEST(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 | |||
| 18 | class TestDatabaseUtilsHelper | ||
| 19 | { | ||
| 20 | public: | ||
| 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 | |||
| 99 | TEST(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 | |||
| 113 | TEST(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 | |||
| 147 | refstr = "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 | |||
| 217 | TEST(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 | |||
| 336 | TEST(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 | |||
| 436 | TEST(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 | |||
| 568 | TEST(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 | |||
| 670 | TEST(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 | |||
| 785 | TEST(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 | |||
| 805 | TEST(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? | ||
| 818 | TEST(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 | |||
| 888 | TEST(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 | |||
| 970 | TEST(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 | |||
| 1047 | TEST(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 | |||
| 1152 | TEST(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 | |||
| 1229 | TEST(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 | |||
| 1322 | TEST(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 | |||
| 1342 | TEST(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 | |||
| 1364 | TEST(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 | |||
| 13 | using KODI::UTILITY::CDigest; | ||
| 14 | using KODI::UTILITY::TypedDigest; | ||
| 15 | |||
| 16 | TEST(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 | |||
| 32 | TEST(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 | |||
| 44 | TEST(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 | |||
| 50 | TEST(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 | |||
| 56 | TEST(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 | |||
| 62 | TEST(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 | |||
| 73 | TEST(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 | |||
| 81 | TEST(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 | |||
| 88 | TEST(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 | |||
| 13 | TEST(TestEndianSwap, Endian_Swap16) | ||
| 14 | { | ||
| 15 | uint16_t ref, var; | ||
| 16 | ref = 0x00FF; | ||
| 17 | var = Endian_Swap16(0xFF00); | ||
| 18 | EXPECT_EQ(ref, var); | ||
| 19 | } | ||
| 20 | |||
| 21 | TEST(TestEndianSwap, Endian_Swap32) | ||
| 22 | { | ||
| 23 | uint32_t ref, var; | ||
| 24 | ref = 0x00FF00FF; | ||
| 25 | var = Endian_Swap32(0xFF00FF00); | ||
| 26 | EXPECT_EQ(ref, var); | ||
| 27 | } | ||
| 28 | |||
| 29 | TEST(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 | ||
| 38 | TEST(TestEndianSwap, Endian_SwapLE16) | ||
| 39 | { | ||
| 40 | uint16_t ref, var; | ||
| 41 | ref = 0x00FF; | ||
| 42 | var = Endian_SwapLE16(0x00FF); | ||
| 43 | EXPECT_EQ(ref, var); | ||
| 44 | } | ||
| 45 | |||
| 46 | TEST(TestEndianSwap, Endian_SwapLE32) | ||
| 47 | { | ||
| 48 | uint32_t ref, var; | ||
| 49 | ref = 0x00FF00FF; | ||
| 50 | var = Endian_SwapLE32(0x00FF00FF); | ||
| 51 | EXPECT_EQ(ref, var); | ||
| 52 | } | ||
| 53 | |||
| 54 | TEST(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 | |||
| 62 | TEST(TestEndianSwap, Endian_SwapBE16) | ||
| 63 | { | ||
| 64 | uint16_t ref, var; | ||
| 65 | ref = 0x00FF; | ||
| 66 | var = Endian_SwapBE16(0xFF00); | ||
| 67 | EXPECT_EQ(ref, var); | ||
| 68 | } | ||
| 69 | |||
| 70 | TEST(TestEndianSwap, Endian_SwapBE32) | ||
| 71 | { | ||
| 72 | uint32_t ref, var; | ||
| 73 | ref = 0x00FF00FF; | ||
| 74 | var = Endian_SwapBE32(0xFF00FF00); | ||
| 75 | EXPECT_EQ(ref, var); | ||
| 76 | } | ||
| 77 | |||
| 78 | TEST(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 | ||
| 86 | TEST(TestEndianSwap, Endian_SwapLE16) | ||
| 87 | { | ||
| 88 | uint16_t ref, var; | ||
| 89 | ref = 0x00FF; | ||
| 90 | var = Endian_SwapLE16(0xFF00); | ||
| 91 | EXPECT_EQ(ref, var); | ||
| 92 | } | ||
| 93 | |||
| 94 | TEST(TestEndianSwap, Endian_SwapLE32) | ||
| 95 | { | ||
| 96 | uint32_t ref, var; | ||
| 97 | ref = 0x00FF00FF; | ||
| 98 | var = Endian_SwapLE32(0xFF00FF00); | ||
| 99 | EXPECT_EQ(ref, var); | ||
| 100 | } | ||
| 101 | |||
| 102 | TEST(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 | |||
| 110 | TEST(TestEndianSwap, Endian_SwapBE16) | ||
| 111 | { | ||
| 112 | uint16_t ref, var; | ||
| 113 | ref = 0x00FF; | ||
| 114 | var = Endian_SwapBE16(0x00FF); | ||
| 115 | EXPECT_EQ(ref, var); | ||
| 116 | } | ||
| 117 | |||
| 118 | TEST(TestEndianSwap, Endian_SwapBE32) | ||
| 119 | { | ||
| 120 | uint32_t ref, var; | ||
| 121 | ref = 0x00FF00FF; | ||
| 122 | var = Endian_SwapBE32(0x00FF00FF); | ||
| 123 | EXPECT_EQ(ref, var); | ||
| 124 | } | ||
| 125 | |||
| 126 | TEST(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 | |||
| 17 | TEST(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 | |||
| 51 | TEST(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 | |||
| 85 | TEST(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 | |||
| 135 | TEST(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 | |||
| 175 | TEST(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 | ||
| 211 | TEST(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 | |||
| 251 | TEST(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 | |||
| 15 | TEST(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 | |||
| 31 | TEST(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 | |||
| 13 | using namespace xbmcutil; | ||
| 14 | using namespace test; | ||
| 15 | |||
| 16 | bool TestGlobalPattern1::ctorCalled = false; | ||
| 17 | bool TestGlobalPattern1::dtorCalled = false; | ||
| 18 | |||
| 19 | TEST(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 | |||
| 15 | namespace 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 | |||
| 39 | XBMC_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 | |||
| 13 | TEST(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 | |||
| 29 | TEST(TestHTMLUtil, ConvertHTMLToW) | ||
| 30 | { | ||
| 31 | std::wstring inw, refstrw, varstrw; | ||
| 32 | inw = L"å&€"; | ||
| 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 | ||
| 68 | std::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 | |||
| 88 | TEST(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 | |||
| 129 | TEST(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 | |||
| 208 | TEST(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 | |||
| 339 | TEST(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 | |||
| 379 | TEST(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 | |||
| 393 | TEST(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 | |||
| 450 | TEST(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 | |||
| 477 | TEST(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 | |||
| 13 | TEST(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 | |||
| 15 | static const uint64_t DefaultFirstPosition = 1; | ||
| 16 | static const uint64_t DefaultLastPosition = 0; | ||
| 17 | static const uint64_t DefaultLength = 0; | ||
| 18 | static const void* DefaultData = NULL; | ||
| 19 | |||
| 20 | TEST(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 | |||
| 31 | TEST(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 | |||
| 42 | TEST(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 | |||
| 62 | TEST(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 | |||
| 81 | TEST(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 | |||
| 101 | TEST(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 | |||
| 150 | TEST(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 | |||
| 170 | TEST(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 | |||
| 190 | TEST(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 | |||
| 216 | TEST(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 | |||
| 234 | TEST(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 | |||
| 252 | TEST(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 | |||
| 270 | TEST(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 | |||
| 288 | TEST(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 | |||
| 306 | TEST(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 | |||
| 323 | TEST(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 | |||
| 342 | TEST(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 | |||
| 373 | TEST(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 | |||
| 424 | TEST(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 | |||
| 450 | TEST(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 | |||
| 487 | TEST(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 | |||
| 508 | TEST(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 | |||
| 529 | TEST(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 | |||
| 579 | TEST(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 | |||
| 623 | TEST(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 | |||
| 659 | TEST(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 | |||
| 708 | TEST(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 | |||
| 754 | TEST(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 | |||
| 790 | TEST(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 | |||
| 843 | TEST(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 | |||
| 13 | TEST(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 | |||
| 14 | TEST(TestJSONVariantParser, CannotParseNullptr) | ||
| 15 | { | ||
| 16 | CVariant variant; | ||
| 17 | ASSERT_FALSE(CJSONVariantParser::Parse(nullptr, variant)); | ||
| 18 | } | ||
| 19 | |||
| 20 | TEST(TestJSONVariantParser, CannotParseEmptyString) | ||
| 21 | { | ||
| 22 | CVariant variant; | ||
| 23 | ASSERT_FALSE(CJSONVariantParser::Parse("", variant)); | ||
| 24 | ASSERT_FALSE(CJSONVariantParser::Parse(std::string(), variant)); | ||
| 25 | } | ||
| 26 | |||
| 27 | TEST(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 | |||
| 37 | TEST(TestJSONVariantParser, CanParseNull) | ||
| 38 | { | ||
| 39 | CVariant variant; | ||
| 40 | ASSERT_TRUE(CJSONVariantParser::Parse("null", variant)); | ||
| 41 | ASSERT_TRUE(variant.isNull()); | ||
| 42 | } | ||
| 43 | |||
| 44 | TEST(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 | |||
| 56 | TEST(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 | |||
| 64 | TEST(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 | |||
| 76 | TEST(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 | |||
| 84 | TEST(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 | |||
| 92 | TEST(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 | |||
| 108 | TEST(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 | |||
| 124 | TEST(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 | |||
| 158 | TEST(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 | |||
| 14 | TEST(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 | |||
| 23 | TEST(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 | |||
| 35 | TEST(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 | |||
| 43 | TEST(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 | |||
| 55 | TEST(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 | |||
| 63 | TEST(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 | |||
| 71 | TEST(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 | |||
| 87 | TEST(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 | |||
| 103 | TEST(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 | |||
| 127 | TEST(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 | |||
| 18 | using namespace ConditionPoll; | ||
| 19 | |||
| 20 | struct 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 | |||
| 28 | class DummyJob : public CJob | ||
| 29 | { | ||
| 30 | Flags* m_flags; | ||
| 31 | public: | ||
| 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 | |||
| 50 | class ReallyDumbJob : public CJob | ||
| 51 | { | ||
| 52 | Flags* m_flags; | ||
| 53 | public: | ||
| 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 | |||
| 63 | class TestJobManager : public testing::Test | ||
| 64 | { | ||
| 65 | protected: | ||
| 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 | |||
| 76 | TEST_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 | |||
| 85 | TEST_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 | |||
| 109 | namespace | ||
| 110 | { | ||
| 111 | struct 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 | |||
| 129 | class BroadcastingJob : | ||
| 130 | public CJob | ||
| 131 | { | ||
| 132 | public: | ||
| 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 | |||
| 170 | private: | ||
| 171 | |||
| 172 | JobControlPackage &m_package; | ||
| 173 | |||
| 174 | XbmcThreads::ConditionVariable m_block; | ||
| 175 | CCriticalSection m_blockMutex; | ||
| 176 | bool m_finish; | ||
| 177 | }; | ||
| 178 | |||
| 179 | BroadcastingJob * | ||
| 180 | WaitForJobToStartProcessing(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 | |||
| 193 | TEST_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 | |||
| 207 | TEST_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. */ | ||
| 20 | class TestLabelFormatter : public testing::Test | ||
| 21 | { | ||
| 22 | protected: | ||
| 23 | TestLabelFormatter() = default; | ||
| 24 | |||
| 25 | ~TestLabelFormatter() override | ||
| 26 | { | ||
| 27 | CServiceBroker::GetSettingsComponent()->GetSettings()->Unload(); | ||
| 28 | } | ||
| 29 | }; | ||
| 30 | |||
| 31 | TEST_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 | |||
| 51 | TEST_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 | |||
| 13 | TEST(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 | |||
| 22 | TEST(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 | |||
| 14 | static const std::string TerritorySeparator = "_"; | ||
| 15 | static const std::string CodesetSeparator = "."; | ||
| 16 | static const std::string ModifierSeparator = "@"; | ||
| 17 | |||
| 18 | static const std::string LanguageCodeEnglish = "en"; | ||
| 19 | static const std::string TerritoryCodeBritain = "GB"; | ||
| 20 | static const std::string CodesetUtf8 = "UTF-8"; | ||
| 21 | static const std::string ModifierLatin = "latin"; | ||
| 22 | |||
| 23 | TEST(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 | |||
| 34 | TEST(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 | |||
| 48 | TEST(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 | |||
| 66 | TEST(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 | |||
| 84 | TEST(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 | |||
| 102 | TEST(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 | |||
| 123 | TEST(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 | |||
| 144 | TEST(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 | |||
| 165 | TEST(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 | |||
| 186 | TEST(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 | |||
| 229 | TEST(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 | |||
| 239 | TEST(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 | |||
| 13 | TEST(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 | |||
| 26 | TEST(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 | |||
| 39 | TEST(TestMathUtils, abs) | ||
| 40 | { | ||
| 41 | int64_t refval, varval; | ||
| 42 | |||
| 43 | refval = 5; | ||
| 44 | varval = MathUtils::abs(-5); | ||
| 45 | EXPECT_EQ(refval, varval); | ||
| 46 | } | ||
| 47 | |||
| 48 | TEST(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 | |||
| 14 | TEST(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 | |||
| 21 | TEST(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 | |||
| 15 | TEST(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 | |||
| 22 | TEST(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 | |||
| 33 | TEST(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 | |||
| 42 | TEST(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 | |||
| 51 | TEST(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 | |||
| 60 | TEST(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 | |||
| 71 | TEST(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 | |||
| 80 | TEST(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 | |||
| 91 | TEST(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 | |||
| 99 | TEST(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 | |||
| 112 | TEST(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 | |||
| 126 | class TestRegExpLog : public testing::Test | ||
| 127 | { | ||
| 128 | protected: | ||
| 129 | TestRegExpLog() = default; | ||
| 130 | ~TestRegExpLog() override { CServiceBroker::GetLogging().Uninitialize(); } | ||
| 131 | }; | ||
| 132 | |||
| 133 | TEST_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 | |||
| 13 | TEST(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 | |||
| 14 | TEST(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 | |||
| 13 | TEST(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 | |||
| 14 | TEST(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 | |||
| 59 | TEST(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 | |||
| 106 | TEST(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 | |||
| 14 | class CTestStopWatchThread : public CThread | ||
| 15 | { | ||
| 16 | public: | ||
| 17 | CTestStopWatchThread() : | ||
| 18 | CThread("TestStopWatch"){} | ||
| 19 | }; | ||
| 20 | |||
| 21 | TEST(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 | |||
| 29 | TEST(TestStopWatch, Start) | ||
| 30 | { | ||
| 31 | CStopWatch a; | ||
| 32 | a.Start(); | ||
| 33 | EXPECT_TRUE(a.IsRunning()); | ||
| 34 | } | ||
| 35 | |||
| 36 | TEST(TestStopWatch, Stop) | ||
| 37 | { | ||
| 38 | CStopWatch a; | ||
| 39 | a.Start(); | ||
| 40 | a.Stop(); | ||
| 41 | EXPECT_FALSE(a.IsRunning()); | ||
| 42 | } | ||
| 43 | |||
| 44 | TEST(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 | |||
| 54 | TEST(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 | |||
| 13 | TEST(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 | |||
| 67 | TEST(TestStreamDetails, VideoDimsToResolutionDescription) | ||
| 68 | { | ||
| 69 | EXPECT_STREQ("1080", | ||
| 70 | CStreamDetails::VideoDimsToResolutionDescription(1920, 1080).c_str()); | ||
| 71 | } | ||
| 72 | |||
| 73 | TEST(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 | |||
| 13 | TEST(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> | ||
| 14 | enum class ECG | ||
| 15 | { | ||
| 16 | A, | ||
| 17 | B | ||
| 18 | }; | ||
| 19 | |||
| 20 | enum EG | ||
| 21 | { | ||
| 22 | C, | ||
| 23 | D | ||
| 24 | }; | ||
| 25 | |||
| 26 | namespace test_enum | ||
| 27 | { | ||
| 28 | enum class ECN | ||
| 29 | { | ||
| 30 | A = 1, | ||
| 31 | B | ||
| 32 | }; | ||
| 33 | enum EN | ||
| 34 | { | ||
| 35 | C = 1, | ||
| 36 | D | ||
| 37 | }; | ||
| 38 | } | ||
| 39 | TEST(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 | |||
| 51 | TEST(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 | |||
| 69 | TEST(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 | |||
| 80 | TEST(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 | |||
| 89 | TEST(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 | |||
| 98 | TEST(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 | |||
| 131 | TEST(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 | |||
| 139 | TEST(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 | |||
| 157 | TEST(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 | |||
| 187 | TEST(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 | |||
| 205 | TEST(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 | |||
| 214 | TEST(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 | |||
| 223 | TEST(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 | |||
| 232 | TEST(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 | |||
| 251 | TEST(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 | |||
| 265 | TEST(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 | |||
| 279 | TEST(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 | |||
| 296 | TEST(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 | |||
| 344 | TEST(TestStringUtils, FindNumber) | ||
| 345 | { | ||
| 346 | EXPECT_EQ(3, StringUtils::FindNumber("aabcaadeaa", "aa")); | ||
| 347 | EXPECT_EQ(1, StringUtils::FindNumber("aabcaadeaa", "b")); | ||
| 348 | } | ||
| 349 | |||
| 350 | TEST(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 | |||
| 359 | TEST(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 | |||
| 375 | TEST(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 | |||
| 385 | TEST(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 | |||
| 394 | TEST(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 | |||
| 403 | TEST(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 | |||
| 418 | TEST(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 | |||
| 433 | TEST(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 | |||
| 442 | TEST(TestStringUtils, EmptyString) | ||
| 443 | { | ||
| 444 | EXPECT_STREQ("", StringUtils::Empty.c_str()); | ||
| 445 | } | ||
| 446 | |||
| 447 | TEST(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 | |||
| 469 | TEST(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 | |||
| 485 | TEST(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 | |||
| 494 | TEST(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 | |||
| 503 | TEST(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 | |||
| 513 | TEST(TestStringUtils, CreateUUID) | ||
| 514 | { | ||
| 515 | std::cout << "CreateUUID(): " << StringUtils::CreateUUID() << std::endl; | ||
| 516 | } | ||
| 517 | |||
| 518 | TEST(TestStringUtils, ValidateUUID) | ||
| 519 | { | ||
| 520 | EXPECT_TRUE(StringUtils::ValidateUUID(StringUtils::CreateUUID())); | ||
| 521 | } | ||
| 522 | |||
| 523 | TEST(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 | |||
| 532 | TEST(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 | |||
| 550 | TEST(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 | |||
| 559 | TEST(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 | |||
| 572 | TEST(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 | |||
| 595 | TEST(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 | |||
| 20 | class TestSystemInfo : public testing::Test | ||
| 21 | { | ||
| 22 | protected: | ||
| 23 | TestSystemInfo() { CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo()); } | ||
| 24 | ~TestSystemInfo() { CServiceBroker::UnregisterCPUInfo(); } | ||
| 25 | }; | ||
| 26 | |||
| 27 | TEST_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 | |||
| 48 | TEST_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 | |||
| 73 | TEST_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 | |||
| 81 | TEST_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 | |||
| 90 | TEST_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 | |||
| 122 | TEST_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 | |||
| 131 | TEST_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 | |||
| 143 | TEST_F(TestSystemInfo, GetManufacturerName) | ||
| 144 | { | ||
| 145 | EXPECT_STRCASENE("unknown", g_sysinfo.GetManufacturerName().c_str()) << "'GetManufacturerName()' must return empty string instead of 'Unknown'"; | ||
| 146 | } | ||
| 147 | |||
| 148 | TEST_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 | ||
| 154 | TEST_F(TestSystemInfo, IsAeroDisabled) | ||
| 155 | { | ||
| 156 | EXPECT_FALSE(g_sysinfo.IsAeroDisabled()) << "'IsAeroDisabled()' must return 'false'"; | ||
| 157 | } | ||
| 158 | #endif // ! TARGET_WINDOWS | ||
| 159 | |||
| 160 | TEST_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 | |||
| 168 | TEST_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 | |||
| 177 | TEST_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 | |||
| 187 | TEST_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 | |||
| 193 | TEST_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 | |||
| 203 | TEST_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 | |||
| 209 | TEST_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 | |||
| 250 | TEST_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 | |||
| 256 | TEST_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 | |||
| 262 | TEST_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 | |||
| 273 | TEST_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 | |||
| 283 | TEST_F(TestSystemInfo, GetUsedCompilerNameAndVer) | ||
| 284 | { | ||
| 285 | EXPECT_STRNE("unknown compiler", g_sysinfo.GetUsedCompilerNameAndVer().c_str()) << "'GetUsedCompilerNameAndVer()' must not return 'unknown compiler'"; | ||
| 286 | } | ||
| 287 | |||
| 288 | TEST_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 | |||
| 20 | using namespace XFILE; | ||
| 21 | |||
| 22 | class TestURIUtils : public testing::Test | ||
| 23 | { | ||
| 24 | protected: | ||
| 25 | TestURIUtils() = default; | ||
| 26 | ~TestURIUtils() override | ||
| 27 | { | ||
| 28 | CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pathSubstitutions.clear(); | ||
| 29 | } | ||
| 30 | }; | ||
| 31 | |||
| 32 | TEST_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 | |||
| 38 | TEST_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 | |||
| 54 | TEST_F(TestURIUtils, GetExtension) | ||
| 55 | { | ||
| 56 | EXPECT_STREQ(".avi", | ||
| 57 | URIUtils::GetExtension("/path/to/movie.avi").c_str()); | ||
| 58 | } | ||
| 59 | |||
| 60 | TEST_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 | |||
| 79 | TEST_F(TestURIUtils, GetFileName) | ||
| 80 | { | ||
| 81 | EXPECT_STREQ("movie.avi", | ||
| 82 | URIUtils::GetFileName("/path/to/movie.avi").c_str()); | ||
| 83 | } | ||
| 84 | |||
| 85 | TEST_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 | |||
| 96 | TEST_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 | |||
| 105 | TEST_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 | |||
| 130 | TEST_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 | |||
| 142 | TEST_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 | |||
| 163 | TEST_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 | |||
| 173 | TEST_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 | |||
| 186 | TEST_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 | |||
| 243 | TEST_F(TestURIUtils, IsAddonsPath) | ||
| 244 | { | ||
| 245 | EXPECT_TRUE(URIUtils::IsAddonsPath("addons://path/to/addons")); | ||
| 246 | } | ||
| 247 | |||
| 248 | TEST_F(TestURIUtils, IsSourcesPath) | ||
| 249 | { | ||
| 250 | EXPECT_TRUE(URIUtils::IsSourcesPath("sources://path/to/sources")); | ||
| 251 | } | ||
| 252 | |||
| 253 | TEST_F(TestURIUtils, IsCDDA) | ||
| 254 | { | ||
| 255 | EXPECT_TRUE(URIUtils::IsCDDA("cdda://path/to/cdda")); | ||
| 256 | } | ||
| 257 | |||
| 258 | TEST_F(TestURIUtils, IsDOSPath) | ||
| 259 | { | ||
| 260 | EXPECT_TRUE(URIUtils::IsDOSPath("C://path/to/dosfile")); | ||
| 261 | } | ||
| 262 | |||
| 263 | TEST_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 | |||
| 275 | TEST_F(TestURIUtils, IsFTP) | ||
| 276 | { | ||
| 277 | EXPECT_TRUE(URIUtils::IsFTP("ftp://path/in/ftp")); | ||
| 278 | } | ||
| 279 | |||
| 280 | TEST_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 | |||
| 289 | TEST_F(TestURIUtils, IsInArchive) | ||
| 290 | { | ||
| 291 | EXPECT_TRUE(URIUtils::IsInArchive("zip://path/to/file")); | ||
| 292 | } | ||
| 293 | |||
| 294 | TEST_F(TestURIUtils, IsInRAR) | ||
| 295 | { | ||
| 296 | EXPECT_TRUE(URIUtils::IsInRAR("rar://path/to/file")); | ||
| 297 | } | ||
| 298 | |||
| 299 | TEST_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 | |||
| 307 | TEST_F(TestURIUtils, IsInZIP) | ||
| 308 | { | ||
| 309 | EXPECT_TRUE(URIUtils::IsInZIP("zip://path/to/file")); | ||
| 310 | } | ||
| 311 | |||
| 312 | TEST_F(TestURIUtils, IsISO9660) | ||
| 313 | { | ||
| 314 | EXPECT_TRUE(URIUtils::IsISO9660("iso9660://path/to/file")); | ||
| 315 | } | ||
| 316 | |||
| 317 | TEST_F(TestURIUtils, IsLiveTV) | ||
| 318 | { | ||
| 319 | EXPECT_TRUE(URIUtils::IsLiveTV("whatever://path/to/file.pvr")); | ||
| 320 | } | ||
| 321 | |||
| 322 | TEST_F(TestURIUtils, IsMultiPath) | ||
| 323 | { | ||
| 324 | EXPECT_TRUE(URIUtils::IsMultiPath("multipath://path/to/file")); | ||
| 325 | } | ||
| 326 | |||
| 327 | TEST_F(TestURIUtils, IsMusicDb) | ||
| 328 | { | ||
| 329 | EXPECT_TRUE(URIUtils::IsMusicDb("musicdb://path/to/file")); | ||
| 330 | } | ||
| 331 | |||
| 332 | TEST_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 | |||
| 338 | TEST_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 | |||
| 346 | TEST_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 | |||
| 357 | TEST_F(TestURIUtils, IsPlugin) | ||
| 358 | { | ||
| 359 | EXPECT_TRUE(URIUtils::IsPlugin("plugin://path/to/file")); | ||
| 360 | } | ||
| 361 | |||
| 362 | TEST_F(TestURIUtils, IsScript) | ||
| 363 | { | ||
| 364 | EXPECT_TRUE(URIUtils::IsScript("script://path/to/file")); | ||
| 365 | } | ||
| 366 | |||
| 367 | TEST_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 | |||
| 375 | TEST_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 | |||
| 388 | TEST_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 | |||
| 394 | TEST_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 | |||
| 400 | TEST_F(TestURIUtils, IsStack) | ||
| 401 | { | ||
| 402 | EXPECT_TRUE(URIUtils::IsStack("stack://path/to/file")); | ||
| 403 | } | ||
| 404 | |||
| 405 | TEST_F(TestURIUtils, IsUPnP) | ||
| 406 | { | ||
| 407 | EXPECT_TRUE(URIUtils::IsUPnP("upnp://path/to/file")); | ||
| 408 | } | ||
| 409 | |||
| 410 | TEST_F(TestURIUtils, IsURL) | ||
| 411 | { | ||
| 412 | EXPECT_TRUE(URIUtils::IsURL("someprotocol://path/to/file")); | ||
| 413 | EXPECT_FALSE(URIUtils::IsURL("/path/to/file")); | ||
| 414 | } | ||
| 415 | |||
| 416 | TEST_F(TestURIUtils, IsVideoDb) | ||
| 417 | { | ||
| 418 | EXPECT_TRUE(URIUtils::IsVideoDb("videodb://path/to/file")); | ||
| 419 | } | ||
| 420 | |||
| 421 | TEST_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 | |||
| 429 | TEST_F(TestURIUtils, IsBluray) | ||
| 430 | { | ||
| 431 | EXPECT_TRUE(URIUtils::IsBluray("bluray://path/to/file")); | ||
| 432 | } | ||
| 433 | |||
| 434 | TEST_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 | |||
| 444 | TEST_F(TestURIUtils, HasSlashAtEnd) | ||
| 445 | { | ||
| 446 | EXPECT_TRUE(URIUtils::HasSlashAtEnd("bluray://path/to/file/")); | ||
| 447 | EXPECT_FALSE(URIUtils::HasSlashAtEnd("bluray://path/to/file")); | ||
| 448 | } | ||
| 449 | |||
| 450 | TEST_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 | |||
| 460 | TEST_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 | |||
| 469 | TEST_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 | |||
| 480 | TEST_F(TestURIUtils, HasParentInHostname) | ||
| 481 | { | ||
| 482 | EXPECT_TRUE(URIUtils::HasParentInHostname(CURL("zip://"))); | ||
| 483 | EXPECT_TRUE(URIUtils::HasParentInHostname(CURL("bluray://"))); | ||
| 484 | } | ||
| 485 | |||
| 486 | TEST_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 | |||
| 493 | TEST_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 | |||
| 501 | TEST_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 | |||
| 560 | TEST_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 | |||
| 14 | TEST(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 | |||
| 26 | TEST(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 | |||
| 104 | TEST(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 | |||
| 139 | TEST(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 | |||
| 151 | TEST(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 | |||
| 161 | TEST(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 | |||
| 186 | TEST(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 | |||
| 13 | TEST(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 | |||
| 25 | TEST(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 | |||
| 37 | TEST(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 | |||
| 47 | TEST(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 | |||
| 66 | TEST(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 | |||
| 85 | TEST(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 | |||
| 98 | TEST(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 | |||
| 111 | TEST(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 | |||
| 120 | TEST(TestVariant, VariantTypeNull) | ||
| 121 | { | ||
| 122 | CVariant a; | ||
| 123 | |||
| 124 | EXPECT_TRUE(a.isNull()); | ||
| 125 | EXPECT_EQ(CVariant::VariantTypeNull, a.type()); | ||
| 126 | } | ||
| 127 | |||
| 128 | TEST(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 | |||
| 153 | TEST(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 | |||
| 180 | TEST(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 | |||
| 194 | TEST(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 | |||
| 208 | TEST(TestVariant, c_str) | ||
| 209 | { | ||
| 210 | CVariant a("variant"); | ||
| 211 | |||
| 212 | EXPECT_STREQ("variant", a.c_str()); | ||
| 213 | } | ||
| 214 | |||
| 215 | TEST(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 | |||
| 227 | TEST(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 | |||
| 250 | TEST(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 | |||
| 272 | TEST(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 | |||
| 284 | TEST(TestVariant, empty) | ||
| 285 | { | ||
| 286 | std::vector<std::string> strarray; | ||
| 287 | CVariant a(strarray); | ||
| 288 | |||
| 289 | EXPECT_TRUE(a.empty()); | ||
| 290 | } | ||
| 291 | |||
| 292 | TEST(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 | |||
| 306 | TEST(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 | |||
| 327 | TEST(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 | |||
| 15 | TEST(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???</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 | |||
| 38 | TEST(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 | |||
| 15 | TEST(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 | |||
| 27 | TEST(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 | |||
| 39 | TEST(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 | |||
| 51 | TEST(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 | |||
| 64 | TEST(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 | |||
| 78 | TEST(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 | |||
| 91 | TEST(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 | |||
| 103 | TEST(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 | |||
| 115 | TEST(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 | |||
| 146 | TEST(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 | |||
| 167 | TEST(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 | |||
| 186 | TEST(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 | |||
| 197 | TEST(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 | |||
| 208 | TEST(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 | |||
| 221 | TEST(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 | |||
| 235 | TEST(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 | |||
| 256 | TEST(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 | |||
| 269 | TEST(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 | |||
| 282 | TEST(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 | |||
| 295 | TEST(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 | |||
| 308 | TEST(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 | |||
| 321 | TEST(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 | |||
| 334 | TEST(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 | |||
| 346 | TEST(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 | |||
| 22 | class Testlog : public testing::Test | ||
| 23 | { | ||
| 24 | protected: | ||
| 25 | Testlog() = default; | ||
| 26 | ~Testlog() override { CServiceBroker::GetLogging().Uninitialize(); } | ||
| 27 | }; | ||
| 28 | |||
| 29 | TEST_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 | |||
| 85 | TEST_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 | |||
| 20 | TEST(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 | ||
| 2 | msgid "" | ||
| 3 | msgstr "" | ||
| 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 | |||
| 16 | msgctxt "#0" | ||
| 17 | msgid "Programs" | ||
| 18 | msgstr "Programas" | ||
| 19 | |||
| 20 | msgctxt "#1" | ||
| 21 | msgid "Pictures" | ||
| 22 | msgstr "Imágenes" | ||
| 23 | |||
| 24 | msgctxt "#2" | ||
| 25 | msgid "Music" | ||
| 26 | msgstr "Música" | ||
