diff --git a/Makefile b/Makefile index 70c1078c..44232f02 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ ifeq ($(HAVE_PCRE2),1) endif # -Wno-deprecated-declarations .. get rid of warning: ‘template class std::auto_ptr’ is deprecated -CXXFLAGS += -std=c++14 -Wfatal-errors -Wundef -Wno-deprecated-declarations +CXXFLAGS += -std=c++17 -Wfatal-errors -Wundef -Wno-deprecated-declarations ### export all vars for sub-makes, using absolute paths LIBDIR := $(abspath $(LIBDIR)) diff --git a/README b/README index e381ec65..8d3c5169 100644 --- a/README +++ b/README @@ -36,10 +36,10 @@ Requirements: VDR >= 2.4.0 -gcc >= 4.8.1, must support -std=c++14 +gcc >= v8, must support -std=c++17 PCRE2 >= 10.38 - https://github.com/PhilipHazel/pcre2/releases -Tntnet >= 1.5.3 - http://www.tntnet.org/download.hms -Cxxtools >= 1.4.3 - http://www.tntnet.org/download.hms +Tntnet >= 2.2.1 - http://www.tntnet.org/download.hms // https://web.archive.org/web/20160314183814/http://www.tntnet.org/download.html +Cxxtools >= 2.2.1 - http://www.tntnet.org/download.hms // https://web.archive.org/web/20160314183814/http://www.tntnet.org/download.html Tntnet provides basic webserver functions for live and needs cxxtools. Boost provides some data structures we need. While currently relying on the diff --git a/StringMatch.cpp b/StringMatch.cpp index affd45ff..5fb1ad5f 100644 --- a/StringMatch.cpp +++ b/StringMatch.cpp @@ -8,14 +8,14 @@ #include -StringMatch::StringMatch(std::string Pattern) : re(nullptr), match_data(nullptr) { +StringMatch::StringMatch(cSv Pattern) : re(nullptr), match_data(nullptr) { PCRE2_SIZE erroroffset; int errorcode; if (not Pattern.empty()) { re = pcre2_compile( - (PCRE2_SPTR) Pattern.c_str(), - PCRE2_ZERO_TERMINATED, + (PCRE2_SPTR) Pattern.data(), + (PCRE2_SIZE) Pattern.length(), 0 | PCRE2_CASELESS // Do caseless matching | PCRE2_DUPNAMES // Allow duplicate names for subpatterns @@ -39,14 +39,14 @@ StringMatch::~StringMatch() { pcre2_code_free((pcre2_code*)re); } -bool StringMatch::Matches(std::string s) { +bool StringMatch::Matches(cSv s) { if ((re == nullptr) or (match_data == nullptr)) return false; int rc = pcre2_match( (pcre2_code*)re, // the compiled pattern - (PCRE2_SPTR) s.c_str(), // the subject string - s.size(), // the length of the subject + (PCRE2_SPTR) s.data(), // the subject string + (PCRE2_SIZE) s.length(), // the length of the subject 0, // start at offset 0 in the subject 0, // default options (pcre2_match_data*)match_data, // block for storing the result diff --git a/StringMatch.h b/StringMatch.h index ad3b4a58..dd775811 100644 --- a/StringMatch.h +++ b/StringMatch.h @@ -1,14 +1,16 @@ #pragma once #ifdef HAVE_PCRE2 #include +#include +#include "stringhelpers.h" class StringMatch { private: void* re; void* match_data; public: - StringMatch(std::string Pattern); + StringMatch(cSv Pattern); ~StringMatch(); - bool Matches(std::string s); + bool Matches(cSv s); }; #endif diff --git a/epg_events.cpp b/epg_events.cpp index dae82c74..54c22415 100644 --- a/epg_events.cpp +++ b/epg_events.cpp @@ -534,7 +534,7 @@ bool appendEpgItem(cLargeString &epg_item, RecordingsItemPtr &recItem, const cEv epg_item.append("[\""); // [0] : EPG ID (without event_) - epg_item.append(EpgEvents::EncodeDomId(Channel->GetChannelID(), Event->EventID()).c_str() + 6); + epg_item.appendS(EpgEvents::EncodeDomId(Channel->GetChannelID(), Event->EventID()).c_str() + 6); epg_item.append("\",\""); // [1] : Timer ID const cTimer* timer = LiveTimerManager().GetTimer(Event->EventID(), Channel->GetChannelID() ); diff --git a/epgsearch.cpp b/epgsearch.cpp index 19c8f69e..8ae12e23 100644 --- a/epgsearch.cpp +++ b/epgsearch.cpp @@ -441,7 +441,7 @@ void SearchTimers::TriggerUpdate() bool SearchTimer::BlacklistSelected(int id) const { for(unsigned int i=0; i(m_blacklistIDs[i]) == id) return true; return false; } @@ -474,7 +474,7 @@ void ExtEPGInfo::ParseValues( std::string const& data ) bool ExtEPGInfo::Selected(unsigned int index, std::string const& values) { if (index >= m_values.size()) return false; - std::string extepgvalue = StringTrim(m_values[index]); + std::string extepgvalue(StringTrim(m_values[index])); std::vector parts; parts = StringSplit( values, ',' ); diff --git a/largeString.h b/largeString.h index 516c719a..e756069e 100644 --- a/largeString.h +++ b/largeString.h @@ -3,8 +3,10 @@ #include #include +#include #include #include +#include "stringhelpers.h" class cLargeString { private: @@ -29,8 +31,8 @@ class cLargeString { public: cLargeString(const cLargeString& o) = delete; cLargeString &operator= (const cLargeString &) = delete; -// cLargeString(cLargeString&& o) = default; // default is wrong, explicit definition required -// cLargeString &operator= (cLargeString &&) = default; // default is wrong, explicit definition required +// cLargeString(cLargeString&& o) = default; // default is wrong, explicit definition required +// cLargeString &operator= (cLargeString &&) = default; // default is wrong, explicit definition required template cLargeString(const char (&name)[N], size_t initialSize = 0, size_t increaseSize = 0, bool debugBufferSize = false) { m_nameData = name; @@ -61,9 +63,10 @@ class cLargeString { cLargeString &append(char c); cLargeString &append(const char *s, size_t len); cLargeString &append(const std::string &s) { return append(s.c_str(), s.length()); } + cLargeString &append(std::string_view s) { return append(s.data(), s.length()); } cLargeString &append(int i); cLargeString &appendS(const char *s); - template cLargeString &appendFormated(char const* format, Args&&... args) { + template cLargeString &appendFormated(const char *format, Args&&... args) { size_t avail = m_buffer_end - m_string_end; size_t numNeeded = snprintf(m_string_end, avail, format, std::forward(args)...); if (numNeeded >= avail) { @@ -79,8 +82,10 @@ class cLargeString { void clear(); inline size_t length() const { return m_string_end - m_s; } inline bool empty() const { return m_string_end == m_s; } - cLargeString &erase(size_t index = 0) { setMaxSize(); m_string_end = std::min(m_string_end, m_s + index); return *this;} + std::string substr(size_t pos, size_t count = std::string::npos) const; // as std::string.substr(), but replace 0 with % +// cLargeString &erase(size_t index = 0) { setMaxSize(); m_string_end = std::min(m_string_end, m_s + index); return *this;} char operator[](size_t i) const { return *(m_s + i); } + operator cSv() const { return cSv(m_s, m_string_end - m_s); } const char *nameData() const { return m_nameData; } int nameLen() const { return m_nameLen; } }; diff --git a/live.cpp b/live.cpp index cd47114b..cba2f124 100644 --- a/live.cpp +++ b/live.cpp @@ -106,7 +106,7 @@ class cLiveImageProviderImp: public cLiveImageProvider { m_errorMessages = false; return fullPath?imagePath:LiveSetup().GetTvscraperImageDir() + imagePath; } - return LiveSetup().GetServerUrlImages() + (fullPath?ScraperImagePath2Live(imagePath):imagePath); + return concatenate(LiveSetup().GetServerUrlImages(), (fullPath?ScraperImagePath2Live(imagePath):imagePath)); } virtual ~cLiveImageProviderImp() {} private: diff --git a/live/js/live/createHtml.js b/live/js/live/createHtml.js index 82c851f5..08ab06b6 100644 --- a/live/js/live/createHtml.js +++ b/live/js/live/createHtml.js @@ -179,7 +179,7 @@ function clearCheckboxes(form) { } } } -function execute(url) { +async function execute(url) { /* * Input: * Url: url to the page triggering the execution of the function @@ -191,17 +191,26 @@ function execute(url) { * - bool success * - string error (only if success == false). Human readable text */ + const response = await fetch(encodeURI(url + '&async=1'), { + method: "GET", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }); + const req_responseXML = new window.DOMParser().parseFromString(await response.text(), "text/xml"); +/* var req = new XMLHttpRequest(); req.open('POST', encodeURI(url + '&async=1'), false); req.overrideMimeType("text/xml"); req.send(); +*/ var ret_object = new Object(); ret_object.success = false; - if (!req.responseXML) { + if (!req_responseXML) { ret_object.error = "invalid xml, no responseXML"; return ret_object; } - var response_array = req.responseXML.getElementsByTagName("response"); + var response_array = req_responseXML.getElementsByTagName("response"); if (response_array.length != 1) { ret_object.error = "invalid xml, no response tag or several response tags"; return ret_object; @@ -220,7 +229,7 @@ function execute(url) { return ret_object; } - var error_array = req.responseXML.getElementsByTagName("error"); + var error_array = req_responseXML.getElementsByTagName("error"); if (error_array.length != 1) { ret_object.error = "invalid xml, no error tag or several error tags"; return ret_object; @@ -233,9 +242,9 @@ function execute(url) { ret_object.error = error_child_nodes[0].nodeValue; return ret_object; } -function delete_rec_back(recid, history_num_back) +async function delete_rec_back(recid, history_num_back) { - var ret_object = execute('delete_recording.html?param=' + recid); + var ret_object = await execute('delete_recording.html?param=' + recid); if (!ret_object.success) alert (ret_object.error); history.go(-history_num_back); } @@ -246,3 +255,71 @@ function back_depending_referrer(back_epginfo, back_others) { history.go(-back_others); } } +function RecordingsSt(s, level, displayFolder, data) { + var recs_param = ''; + for (obj_i of data) { + if (typeof recs[obj_i] === 'undefined') { + if (recs_param.length == 0) { + recs_param += 'r='; + } else { + recs_param += '&r='; + } + recs_param += obj_i; + } + } + if (recs_param.length == 0) { + RecordingsSt_int(s, level, displayFolder, data); + } else { + const request = new XMLHttpRequest(); + request.open("POST", "get_recordings.html", false); + request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + request.send(recs_param); + eval(request.response); + RecordingsSt_int(s, level, displayFolder, data); + } +} +async function RecordingsSt_a(s, level, displayFolder, data) { + var recs_param = ''; + for (obj_i of data) { + if (typeof recs[obj_i] === 'undefined') { + recs_param += '&r='; + recs_param += obj_i; + } + } + if (recs_param.length == 0) { + RecordingsSt_int(s, level, displayFolder, data); + } else { + var recs_param_a = 'vdr_start='; + recs_param_a += vdr_start; + recs_param_a += '&recordings_tree_creation='; + recs_param_a += recordings_tree_creation; + recs_param_a += recs_param; + const response = await fetch("get_recordings.html", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: recs_param_a, + }); + const new_recs = await response.text(); + eval(new_recs); + if (vdr_restart) { + location.reload(); + } else { + RecordingsSt_int(s, level, displayFolder, data); + } + } +} +async function rec_string_d_a(rec_ids) { + const st = Object.create(null) + st.a = "" + await RecordingsSt_a(st, rec_ids[0], rec_ids[1], rec_ids[2]) + return st.a +} + +function rec_string_d(rec_ids) { + const st = Object.create(null) + st.a = "" + RecordingsSt_int(st, rec_ids[0], rec_ids[1], rec_ids[2]) + return st.a +} diff --git a/live/js/live/infowin.js b/live/js/live/infowin.js index ef0ea8e9..288f4321 100644 --- a/live/js/live/infowin.js +++ b/live/js/live/infowin.js @@ -174,8 +174,8 @@ var InfoWin = new Class({ var confirm_del = this.winBody.getElementById('confirm_' + id); if (confirm_del && id.startsWith("del_") ) { confirm_del.onclick = null; - confirm_del.addEvent('click', function(event){ - var err = execute('delete_recording.html?param=' + id.substring(4) ); + confirm_del.addEvent('click', async function(event) { + var err = await execute('delete_recording.html?param=' + id.substring(4) ); if (!err.success) alert (err.error); if (history_num_back > 0) { history.go(-history_num_back); } else { location.reload(); } diff --git a/live/js/live/treeview.js b/live/js/live/treeview.js index 00083a36..f24d2f90 100644 --- a/live/js/live/treeview.js +++ b/live/js/live/treeview.js @@ -49,7 +49,7 @@ class Treeview { this.folder_closed = folder_closed; } - Toggle(node, node_id) { + async Toggle(node, node_id) { // Unfold the branch if it isn't visible const sibling = findSibling(node, "UL"); if (sibling == null) return; @@ -57,7 +57,7 @@ class Treeview { if (sibling.style.display == 'none') { setImages(node, this.minus, this.folder_open); if (rec_ids[node_id] != null && rec_ids[node_id].length > 0) { - sibling.insertAdjacentHTML("beforeend", rec_string_d(rec_ids[node_id])); + sibling.insertAdjacentHTML("beforeend", await rec_string_d_a(rec_ids[node_id])); rec_ids[node_id] = []; if (typeof liveEnhanced !== 'undefined') liveEnhanced.domReadySetup(); imgLoad(); @@ -102,7 +102,7 @@ openNodes = openNodes.join(","); createCookie( cookieNameRec, openNodes, 14 ); } -function openNodesOnPageLoad() +async function openNodesOnPageLoad() { var openNodes = readCookie( cookieNameRec ); var domChanges = 0; @@ -115,7 +115,7 @@ for (var z=0; z 0) { - ul.insertAdjacentHTML("beforeend", rec_string_d(rec_ids[openNodes[z]])); + ul.insertAdjacentHTML("beforeend", await rec_string_d_a(rec_ids[openNodes[z]])); rec_ids[openNodes[z]] = []; domChanges = 1; } @@ -140,7 +140,7 @@ function filterRecordings(filter, currentSort, currentFlat) { window.location.href = "recordings.html?sort=" + currentSort + "&flat=" + currentFlat + "&filter=" + encodeURIComponent(filter.value); } -function ExpandAll() +async function ExpandAll() { var openNodes = ""; var domChanges = 0; @@ -150,7 +150,7 @@ function ExpandAll() recordingNodes[idx].style.display = 'block'; openNodes += recordingNodes[idx].id + ","; if (rec_ids[recordingNodes[idx].id] != null && rec_ids[recordingNodes[idx].id].length > 0) { - recordingNodes[idx].insertAdjacentHTML("beforeend", rec_string_d(rec_ids[recordingNodes[idx].id])); + recordingNodes[idx].insertAdjacentHTML("beforeend", await rec_string_d_a(rec_ids[recordingNodes[idx].id])); rec_ids[recordingNodes[idx].id] = []; domChanges = 1; } diff --git a/pages/Makefile b/pages/Makefile index f7e544c6..1f029641 100644 --- a/pages/Makefile +++ b/pages/Makefile @@ -10,15 +10,16 @@ DEFINES += -DDISABLE_TEMPLATES_COLLIDING_WITH_STL INCLUDES += -I$(VDRDIR)/include -I.. ### The object files (add further files here): -OBJS := menu.o recordings.o schedule.o multischedule.o screenshot.o \ +OBJS := get_recordings.o delete_recording.o recordings.o pageelems.o \ + epginfo.o schedule.o multischedule.o screenshot.o \ timers.o whats_on.o switch_channel.o keypress.o remote.o \ - channels_widget.o edit_timer.o error.o pageelems.o tooltip.o \ + channels_widget.o edit_timer.o error.o tooltip.o \ searchtimers.o edit_searchtimer.o searchresults.o \ searchepg.o login.o ibox.o xmlresponse.o play_recording.o \ pause_recording.o stop_recording.o ffw_recording.o \ - rwd_recording.o setup.o content.o epginfo.o timerconflicts.o \ + rwd_recording.o setup.o content.o timerconflicts.o \ recstream.o users.o edit_user.o edit_recording.o osd.o \ - playlist.o stream.o stream_data.o delete_recording.o + playlist.o stream.o stream_data.o menu.o SRCS := $(patsubst %.o,%.cpp,$(OBJS)) ESRCS := $(patsubst %.o,%.ecpp,$(OBJS)) ESRCS_DEPS := $(patsubst %.o,.%.edep,$(OBJS)) diff --git a/pages/edit_recording.ecpp b/pages/edit_recording.ecpp index 608f826e..5b99ca84 100644 --- a/pages/edit_recording.ecpp +++ b/pages/edit_recording.ecpp @@ -195,11 +195,11 @@ if (recording && recording->Info()->Aux()) { getAutoTimerReason.recording_in = recording; getAutoTimerReason.requestRecording = false; if (getAutoTimerReason.call(LiveSetup().GetPluginScraper()) && getAutoTimerReason.createdByTvscraper) { - AppendHtmlEscapedAndCorrectNonUTF8(aux_data, getAutoTimerReason.reason.c_str() ); + AppendHtmlEscapedAndCorrectNonUTF8(aux_data, getAutoTimerReason.reason); } else { - std::string epgsearchinfo = GetXMLValue(recording->Info()->Aux(), "epgsearch"); + cSv epgsearchinfo = partInXmlTag(recording->Info()->Aux(), "epgsearch"); if (!epgsearchinfo.empty()) { - AppendHtmlEscapedAndCorrectNonUTF8(aux_data, GetXMLValue(epgsearchinfo, "searchtimer").c_str() ); + AppendHtmlEscapedAndCorrectNonUTF8(aux_data, partInXmlTag(epgsearchinfo, "searchtimer") ); } } if (!aux_data.empty()) { diff --git a/pages/get_recordings.ecpp b/pages/get_recordings.ecpp new file mode 100644 index 00000000..7d2c789e --- /dev/null +++ b/pages/get_recordings.ecpp @@ -0,0 +1,71 @@ +<%pre> + +#include +#include +#include + +using namespace vdrlive; + + +<%args> + int r[]; + time_t vdr_start = 0; + time_t recordings_tree_creation = 0; + +<%cpp> + reply.setContentType("text/javascript"); + bool error = false; + if (vdr_start != 0 && vdr_start != LiveSetup().GetVdrStart()) { + esyslog("live ERROR, get_recordings.ecpp, vdr restarted"); + error = true; + } + +vdr_restart = <$ error?"true":"false"$>; + <%cpp> + if (!error) { + bool recordings_tree_re_created = false; + RecordingsTreePtr recordingsTree(LiveRecordingsManager()->GetRecordingsTree()); + if (recordings_tree_creation != 0 && recordings_tree_creation != recordingsTree->getCreationTimestamp()) { + isyslog("live INFO, get_recordings.ecpp, recordings tree re-created"); + recordings_tree_re_created = true; + } + +recordings_tree_re_created = <$ recordings_tree_re_created?"true":"false"$>; + <%cpp> + /* + esyslog("live, get_recordings.ecpp, qparam as url = %s", qparam.getUrl().c_str() ); + if(qparam.paramcount()) + esyslog("live, get_recordings.ecpp, qparam[0] = %s", qparam[0].c_str() ); + + esyslog("live, get_recordings.ecpp, r size = %d", (int)r.size()); + if (r.size() > 0) + esyslog("live, get_recordings.ecpp, r[0] = %d", r[0]); + + requires later tntnet + std::vector names; + qparam.getNames(std::back_inserter(names)); + + for (const std::string &n: names) { + std::vector values; + qparam.getValues(n, std::back_inserter(values)); + esyslog("live, get_recordings.ecpp, parameter name %s size = %d", n.c_str(), (int)values.size()); + if (values.size() > 0) + esyslog("live, get_recordings.ecpp, %s[0] = %s", n.c_str(), values[0].c_str()); + + } + */ + + cLargeString recoring_item("recoring_item_get_recordings.ecpp", 5000); + char buffer_i[21]; + buffer_i[20] = 0; + for (const RecordingsItemPtr &rPtr: *recordingsTree->allRecordings()) { + for (int recording: r) if (recording == rPtr->IdI() ) { + recoring_item.clear(); + rPtr->AppendAsJSArray(recoring_item, true); + + recs[<$concat::addCharsIbe(buffer_i+20, rPtr->IdI())$>]=[<$$ recoring_item.c_str() $>] + <%cpp> + } + } + } + diff --git a/pages/pageelems.ecpp b/pages/pageelems.ecpp index bf79761b..4a6be48f 100644 --- a/pages/pageelems.ecpp +++ b/pages/pageelems.ecpp @@ -347,7 +347,7 @@ function existingRecordingString(col_span, bottomrow, id, archiveDescr, imdb_id, // if (LiveSetup().GetShowIMDb()) { if (s_IMDB_ID.empty() ) { - } else { } @@ -1020,6 +1020,8 @@ if (scraperMovieOrTv.found) { <%cpp> const int num_cols = 4; int num = 1; + std::string loc_name = std::locale("").name().substr(0,2); + if (loc_name.length() != 2 || loc_name[0] == 'C' || loc_name[0] == '*') loc_name = "en"; // use en as default std::string actor_names = ""; for (const auto &character:scraperCharacters) { @@ -1035,8 +1037,9 @@ if (scraperMovieOrTv.found) { <%cpp> - actor_names.append(""); - actor_names.append(character->getPersonName()); + stringAppend(actor_names, "getPersonName(), " ", "_")), "\" target=\"_blank\">", + character->getPersonName(), ""); actor_names.append(":
"); actor_names.append(character->getCharacterName()); actor_names.append(""); diff --git a/pages/recordings.ecpp b/pages/recordings.ecpp index 920bd90f..1c543997 100644 --- a/pages/recordings.ecpp +++ b/pages/recordings.ecpp @@ -1,11 +1,15 @@ <%pre> +#include +#include #include #include #include #include #include #include +#include +#include #include #ifdef HAVE_PCRE2 @@ -14,7 +18,6 @@ #include #define MB_PER_MINUTE 25.75 // this is just an estimate! -#include using namespace vdrlive; @@ -98,11 +101,7 @@ for (auto it = deletions.begin(); it != deletions.end(); ++it) { deletions.clear(); int FreeMB, UsedMB; -#if APIVERSNUM > 20101 int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB); -#else -int Percent = VideoDiskSpace(&FreeMB, &UsedMB); -#endif int Minutes = int(double(FreeMB) / MB_PER_MINUTE); int Hours = Minutes / 60; Minutes %= 60; @@ -112,11 +111,21 @@ const char *TNT_ARRAY = "[]"; #else const char *TNT_ARRAY = ""; #endif -const char *lf; -if (LiveSetup().GetUseAjax()) lf = "
"; -else lf = " "; +const char *lf = "
"; +RecordingsTreePtr recordingsTree(LiveRecordingsManager()->GetRecordingsTree()); +char buffer_i[21]; +buffer_i[20] = 0; + +// get data for all open nodes +std::vector available_recs; +std::string openTreeNodes; +if (request.hasCookie("VDR-Live-Recordings-Tree-Open-Nodes")) { + openTreeNodes = request.getCookie("VDR-Live-Recordings-Tree-Open-Nodes"); + isyslog("live: VDR-Live-Recordings-Tree-Open-Nodes = %s", openTreeNodes.c_str()); +} +cLargeString recoring_item("recoring_item", 25000); <& pageelems.doc_type &> @@ -128,18 +137,19 @@ else lf = " "; <& pageelems.ajax_js &> @@ -301,14 +304,10 @@ if (!deleteResult.empty()) {
<$ std::string(tr("List of recordings")) + " (" + diskinfo + ")" $>
<%cpp> int rec_cnt; -#if VDRVERSNUM >= 20301 { LOCK_RECORDINGS_READ; rec_cnt = Recordings->Count(); // Access VDRs global cRecordings Recordings instance. } -#else - rec_cnt = Recordings.Count(); // Access VDRs global cRecordings Recordings instance. -#endif if (rec_cnt == 0) { <$ tr("No recordings found") $> @@ -317,11 +316,14 @@ if (!deleteResult.empty()) { uintptr_t irecoring_item = (uintptr_t)&recoring_item; RecordingsItemPtr recItemRoot = recordingsTree->getRoot(); uintptr_t iRecItemRoot = (uintptr_t)&recItemRoot; + uintptr_t iRecordingsTree = (uintptr_t)&recordingsTree; + uintptr_t iOpenTreeNodes = (uintptr_t)&openTreeNodes; + uintptr_t iAvailable_recs = (uintptr_t)&available_recs;
    - <& recordings.recordings_item iRecItem=(iRecItemRoot) irecoring_item=(irecoring_item) &> + <& recordings.recordings_item iRecItem=(iRecItemRoot) irecoring_item=(irecoring_item) iRecordingsTree=(iRecordingsTree) iOpenTreeNodes=(iOpenTreeNodes) iAvailable_recs=(iAvailable_recs) &>
@@ -347,6 +349,9 @@ if (!deleteResult.empty()) { <%args> uintptr_t iRecItem = 0; uintptr_t irecoring_item = 0; + uintptr_t iRecordingsTree = 0; + uintptr_t iOpenTreeNodes = 0; + uintptr_t iAvailable_recs = 0; std::string parentIdHash; <%cpp> @@ -361,8 +366,12 @@ if (currentSort.compare(0, 10, "duplicates", 10) == 0) sortOrder = eSortOrder::d if (currentSort == "datedesc" || currentSort == "namedesc") reverse = true; // get rec item of this (current) dir RecordingsItemPtr recItemThisDir = *(static_cast((void *)iRecItem) ); -int level = recItemThisDir->Level(); +RecordingsTreePtr recordingsTree = *(static_cast((void *)iRecordingsTree) ); cLargeString *recoring_item = static_cast((void *)irecoring_item ); +std::string openTreeNodes= *(static_cast((void *)iOpenTreeNodes) ); +std::vector available_recs = *(static_cast *>((void *)iAvailable_recs) ); +int level = recItemThisDir->Level(); + std::vector recItems; const std::vector *recItemsC = NULL; @@ -426,7 +435,7 @@ if (currentFlat != "true") { <%cpp> @@ -437,7 +446,6 @@ if (currentFlat != "true") { } // end if (currentSort != "duplicates") } else { // here we prepare the items in case of a flat list, no folders - RecordingsTreePtr recordingsTree(LiveRecordingsManager()->GetRecordingsTree()); if (currentSort != "duplicates") { recItemsC = recordingsTree->allRecordings(sortOrder); } else { @@ -447,6 +455,21 @@ if (currentFlat != "true") { addDuplicateRecordingsNoSd(recItems, recordingsTree); recItemsC = &recItems; } + char buffer_i[21]; + buffer_i[20] = 0; + + + <%cpp> } if (currentSort != "duplicates" || currentFlat == "true") { if (recItemsC->empty() ) { @@ -459,19 +482,40 @@ if (currentSort != "duplicates" || currentFlat == "true") { const char *displayFolder = "1"; recoring_item->clear(); RecordingsItemRec::AppendAsJSArray(*recoring_item, recItemsC->begin(), recItemsC->end(), first, currentFilter, reverse); - if (level == 0) { + - <%cpp> } else { - - <%cpp> } + <%cpp> } } else { // duplicates - RecordingsTreePtr recordingsTree(LiveRecordingsManager()->GetRecordingsTree()); for (int i = 0; i < 3; i++) { recItems.clear(); std::string nodeName; @@ -672,8 +716,10 @@ if (recording && recording->Info() ) { int duration_deviation = getScraperVideo.m_scraperVideo->getDurationDeviation(); int video_SD_HD = getScraperVideo.m_scraperVideo->getHD(); char sdhd = video_SD_HD == 0 ? 's': video_SD_HD == 1 ? 'h': 'u'; - CONVERT(b_recordingErrors, recordingErrors, toCharsI); - CONVERT(b_duration_deviation, duration_deviation, toCharsI); + char b_recordingErrors[21]; + char b_duration_deviation[21]; + b_recordingErrors[20] = 0; + b_duration_deviation[20] = 0; std::string duration; if (recording->FileName() ) AppendDuration(duration, tr("%d:%02d"), recording->LengthInSeconds() ); @@ -681,7 +727,7 @@ if (recording && recording->Info() ) { <%cpp> } diff --git a/pages/searchepg.ecpp b/pages/searchepg.ecpp index 4685024b..443ea55d 100644 --- a/pages/searchepg.ecpp +++ b/pages/searchepg.ecpp @@ -100,8 +100,8 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); searchtimer.SetUseTime(usetime); if (usetime) { - searchtimer.SetStartTime(StringToInt(start_h) * 100 + StringToInt(start_m)); - searchtimer.SetStopTime(StringToInt(stop_h) * 100 + StringToInt(stop_m)); + searchtimer.SetStartTime(parse_int(start_h) * 100 + parse_int(start_m)); + searchtimer.SetStopTime(parse_int(stop_h) * 100 + parse_int(stop_m)); } searchtimer.SetUseDuration(useduration); if (useduration) diff --git a/pages/setup.ecpp b/pages/setup.ecpp index 624575eb..31789ffb 100644 --- a/pages/setup.ecpp +++ b/pages/setup.ecpp @@ -20,7 +20,6 @@ using namespace vdrlive; std::string theme; std::string localnetmask; std::string showLogo; - std::string useAjax; std::string showInfoBox; std::string useStreamdev; std::string markNewRec; @@ -69,10 +68,7 @@ if (!cUser::CurrentUserHasRightTo(UR_EDITSETUP)) LiveSetup().SetStartScreen(startscreen); LiveSetup().SetTheme(theme); LiveSetup().SetShowLogo(!showLogo.empty()); - LiveSetup().SetUseAjax(!useAjax.empty()); - if (LiveSetup().GetUseAjax()) { - LiveSetup().SetShowInfoBox(!showInfoBox.empty()); - } + LiveSetup().SetShowInfoBox(!showInfoBox.empty()); LiveSetup().SetUseStreamdev(!useStreamdev.empty()); LiveSetup().SetStreamdevPort(streamdevport.empty() ? 3000 : lexical_cast(streamdevport)); LiveSetup().SetStreamdevType(streamdevtype.empty() ? "PES" : streamdevtype); @@ -109,7 +105,6 @@ if (!cUser::CurrentUserHasRightTo(UR_EDITSETUP)) theme = LiveSetup().GetTheme(); localnetmask = LiveSetup().GetLocalNetMask(); showLogo = LiveSetup().GetShowLogo() ? "1" : ""; - useAjax = LiveSetup().GetUseAjax() ? "1" : ""; showInfoBox = LiveSetup().GetShowInfoBox() ? "1" : ""; useStreamdev = LiveSetup().GetUseStreamdev() ? "1" : ""; streamdevport = lexical_cast(LiveSetup().GetStreamdevPort()); @@ -134,7 +129,6 @@ if (!cUser::CurrentUserHasRightTo(UR_EDITSETUP)) function initform() { changeduseauth(document.getElementById("useauth")); - changeduseajax(document.getElementById("useAjax")); changedusestreamdev(document.getElementById("useStreamdev")); if (document.getElementById("message").value != "") alert(document.getElementById("message").value); @@ -147,10 +141,6 @@ if (!cUser::CurrentUserHasRightTo(UR_EDITSETUP)) { document.getElementById("authchanged").value = 1; } - function changeduseajax(selection) - { - document.getElementById("ajaxsection").style.display = (selection.checked ? "block" : "none"); - } function changedusestreamdev(selection) { document.getElementById("streamdevsection").style.display = (selection.checked ? "block" : "none"); @@ -201,17 +191,9 @@ if (!cUser::CurrentUserHasRightTo(UR_EDITSETUP)) -
<$ tr("Use ajax technology") $>:
+
<$ tr("Show dynamic VDR information box") $>:
- CHECKIF(!useAjax.empty()); onclick="changeduseajax(this)"/> - + CHECKIF(!showInfoBox.empty()); /> diff --git a/pages/stream.ecpp b/pages/stream.ecpp index b02acc94..de44b6f8 100644 --- a/pages/stream.ecpp +++ b/pages/stream.ecpp @@ -159,8 +159,10 @@ if (Event) { timeSpan = headTime; startDate = headDate; } - std::string longDescription = StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), LiveSetup().GetMaxTooltipChars())) - + "

" + tr("Click to view details."); + std::string longDescription = concatenate( + StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), LiveSetup().GetMaxTooltipChars())), + "

", + tr("Click to view details.")); diff --git a/pages/timerconflicts.ecpp b/pages/timerconflicts.ecpp index adabe2ed..adc72d0a 100644 --- a/pages/timerconflicts.ecpp +++ b/pages/timerconflicts.ecpp @@ -109,12 +109,15 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); #endif if (timer->Event()) { epgEvent = EpgEvents::CreateEpgInfo(timer->Channel(), timer->Event()); - longDescription = StringEscapeAndBreak(SortedTimers::GetTimerInfo(*timer)) + "
" - + StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), LiveSetup().GetMaxTooltipChars())) - + "

" + tr("Click to view details."); - AppendHtmlEscapedAndCorrectNonUTF8(searchTimName, SortedTimers::SearchTimerInfo(*timer, "searchtimer").c_str()); + longDescription = concatenate( + StringEscapeAndBreak(SortedTimers::GetTimerInfo(*timer)), + "
", + StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), LiveSetup().GetMaxTooltipChars())), + "

", + tr("Click to view details.")); + AppendHtmlEscapedAndCorrectNonUTF8(searchTimName, SortedTimers::SearchTimerInfo(*timer, "searchtimer") ); searchTimId = SortedTimers::SearchTimerInfo(*timer, "s-id"); - if (searchTimName.empty() && searchTimId.empty() ) AppendHtmlEscapedAndCorrectNonUTF8(searchTimName, SortedTimers::TvScraperTimerInfo(*timer, recID, recName).c_str() ); + if (searchTimName.empty() && searchTimId.empty() ) AppendHtmlEscapedAndCorrectNonUTF8(searchTimName, SortedTimers::TvScraperTimerInfo(*timer, recID, recName) ); title = epgEvent->Title(); } std::string timerFile; diff --git a/pages/timers.ecpp b/pages/timers.ecpp index 24c0a7d7..15e22a64 100644 --- a/pages/timers.ecpp +++ b/pages/timers.ecpp @@ -111,13 +111,13 @@ using namespace vdrlive; longDescription += LiveSetup().GetUseAjax()?"
":lf; longDescription += StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), LiveSetup().GetMaxTooltipChars(), truncated), lf); if (truncated) longDescription += "..."; - AppendHtmlEscapedAndCorrectNonUTF8(searchTimName, SortedTimers::SearchTimerInfo(*timer, "searchtimer").c_str()); + AppendHtmlEscapedAndCorrectNonUTF8(searchTimName, SortedTimers::SearchTimerInfo(*timer, "searchtimer")); searchTimId = SortedTimers::SearchTimerInfo(*timer, "s-id"); - if (searchTimName.empty() && searchTimId.empty() ) AppendHtmlEscapedAndCorrectNonUTF8(searchTimName, SortedTimers::TvScraperTimerInfo(*timer, recID, recName).c_str()); + if (searchTimName.empty() && searchTimId.empty() ) AppendHtmlEscapedAndCorrectNonUTF8(searchTimName, SortedTimers::TvScraperTimerInfo(*timer, recID, recName) ); } std::string currentDay = SortedTimers::GetTimerDays(timer); - const cTimer *nextTimer = NULL; - if (i < (sortedTimers.Size() - 1)) nextTimer = sortedTimers[i + 1]; + const cTimer *nextTimer = NULL; + if (i < (sortedTimers.Size() - 1)) nextTimer = sortedTimers[i + 1]; bool bottom = false; if (i == sortedTimers.Size() - 1) bottom = true; else { diff --git a/pages/whats_on.ecpp b/pages/whats_on.ecpp index e53855b5..b0077802 100644 --- a/pages/whats_on.ecpp +++ b/pages/whats_on.ecpp @@ -192,7 +192,7 @@ if (type == "now") { EpgInfoPtr epgEvent = *i; bool truncated = false; - std::string truncDescription = StringWordTruncate(epgEvent->LongDescr(), maximumTruncDescriptionLength, truncated); + std::string truncDescription(StringWordTruncate(epgEvent->LongDescr(), maximumTruncDescriptionLength, truncated)); std::string longDescription = StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), LiveSetup().GetMaxTooltipChars())) + "

" + tr("Click to view details."); const cChannel* Channel = epgEvent->Channel(); diff --git a/recman.cpp b/recman.cpp index 0b23b640..7b101898 100644 --- a/recman.cpp +++ b/recman.cpp @@ -30,12 +30,8 @@ namespace vdrlive { */ std::weak_ptr RecordingsManager::m_recMan; std::shared_ptr RecordingsManager::m_recTree; -#if VDRVERSNUM >= 20301 cStateKey RecordingsManager::m_recordingsStateKey; -#else - int RecordingsManager::m_recordingsState = 0; -#endif - time_t scraperLastRecordingsUpdate; + time_t scraperLastRecordingsUpdate; // The RecordingsManager holds a VDR lock on the // Recordings. Additionally the singleton instance of @@ -53,12 +49,7 @@ namespace vdrlive { // themselfs. This way the use of LIVE::recordings is straight // forward and does hide the locking needs from the user. -#if VDRVERSNUM >= 20301 RecordingsManager::RecordingsManager() -#else - RecordingsManager::RecordingsManager() : - m_recordingsLock(&Recordings) -#endif { } @@ -66,6 +57,7 @@ namespace vdrlive { { RecordingsManagerPtr recMan = EnsureValidData(); if (! recMan) { + esyslog("live, ERROR, recMan == nullptr after call to EnsureValidData"); return RecordingsTreePtr(recMan, std::shared_ptr()); } return RecordingsTreePtr(recMan, m_recTree); @@ -76,15 +68,11 @@ namespace vdrlive { return "recording_" + MD5Hash(recording->FileName()); } - cRecording const * RecordingsManager::GetByMd5Hash(std::string const & hash) const + cRecording const * RecordingsManager::GetByMd5Hash(cSv hash) const { if (!hash.empty()) { -#if VDRVERSNUM >= 20301 LOCK_RECORDINGS_READ; for (cRecording* rec = (cRecording *)Recordings->First(); rec; rec = (cRecording *)Recordings->Next(rec)) { -#else - for (cRecording* rec = Recordings.First(); rec; rec = Recordings.Next(rec)) { -#endif if (hash == Md5Hash(rec)) return rec; } @@ -92,14 +80,14 @@ namespace vdrlive { return 0; } - bool RecordingsManager::MoveRecording(cRecording const * recording, std::string const & name, bool copy) const + bool RecordingsManager::MoveRecording(cRecording const * recording, cSv name, bool copy) const { if (!recording) return false; // Check for injections that try to escape from the video dir. if (name.compare(0, 3, "../") == 0 || name.find("/..") != std::string::npos) { - esyslog("live: renaming failed: new name invalid \"%s\"", name.c_str()); + esyslog("live: renaming failed: new name invalid \"%.*s\"", (int)name.length(), name.data()); return false; } @@ -109,27 +97,17 @@ namespace vdrlive { if (found == std::string::npos) return false; -#if APIVERSNUM > 20101 - std::string newname = std::string(cVideoDirectory::Name()) + "/" + name + oldname.substr(found); -#else - std::string newname = std::string(VideoDirectory) + "/" + name + oldname.substr(found); -#endif + std::string newname = concatenate(cVideoDirectory::Name(), "/", name, cSv(oldname).substr(found)); - if (!MoveDirectory(oldname.c_str(), newname.c_str(), copy)) { - esyslog("live: renaming failed from '%s' to '%s'", oldname.c_str(), newname.c_str()); + if (!MoveDirectory(oldname, newname, copy)) { + esyslog("live: renaming failed from '%.*s' to '%s'", (int)oldname.length(), oldname.data(), newname.c_str()); return false; } -#if VDRVERSNUM >= 20301 LOCK_RECORDINGS_WRITE; if (!copy) Recordings->DelByName(oldname.c_str()); Recordings->AddByName(newname.c_str()); -#else - if (!copy) - Recordings.DelByName(oldname.c_str()); - Recordings.AddByName(newname.c_str()); -#endif cRecordingUserCommand::InvokeCommand(*cString::sprintf("rename \"%s\"", *strescape(oldname.c_str(), "\\\"$'")), newname.c_str()); return true; @@ -169,12 +147,8 @@ namespace vdrlive { std::string name(recording->FileName()); const_cast(recording)->Delete(); -#if VDRVERSNUM >= 20301 LOCK_RECORDINGS_WRITE; Recordings->DelByName(name.c_str()); -#else - Recordings.DelByName(name.c_str()); -#endif } int RecordingsManager::GetArchiveType(cRecording const * recording) @@ -244,7 +218,6 @@ namespace vdrlive { return archived; } -#if VDRVERSNUM >= 20301 bool RecordingsManager::StateChanged () { bool result = false; @@ -257,7 +230,6 @@ namespace vdrlive { return result; } -#endif RecordingsManagerPtr RecordingsManager::EnsureValidData() { @@ -274,19 +246,15 @@ namespace vdrlive { // StateChanged must be executed every time, so not part of // the short cut evaluation in the if statement below. -#if VDRVERSNUM >= 20301 bool stateChanged = StateChanged(); -#else - bool stateChanged = Recordings.StateChanged(m_recordingsState); -#endif // check: changes on scraper data? - cGetScraperUpdateTimes scraperUpdateTimes; - if (scraperUpdateTimes.call(LiveSetup().GetPluginScraper()) ) { - if (scraperUpdateTimes.m_recordingsUpdateTime > scraperLastRecordingsUpdate) { - scraperLastRecordingsUpdate = scraperUpdateTimes.m_recordingsUpdateTime; - stateChanged = true; - } - } + cGetScraperUpdateTimes scraperUpdateTimes; + if (scraperUpdateTimes.call(LiveSetup().GetPluginScraper()) ) { + if (scraperUpdateTimes.m_recordingsUpdateTime > scraperLastRecordingsUpdate) { + scraperLastRecordingsUpdate = scraperUpdateTimes.m_recordingsUpdateTime; + stateChanged = true; + } + } if (stateChanged || (!m_recTree) ) { if (stateChanged) { m_recTree.reset(); @@ -303,19 +271,18 @@ namespace vdrlive { return recMan; } - - ShortTextDescription::ShortTextDescription(const char * ShortText, const char * Description) : - m_short_text(ShortText), - m_description(Description) - { } - wint_t ShortTextDescription::getNextNonPunctChar() { - wint_t result; - do { - if( m_short_text && *m_short_text ) result = getNextUtfCodepoint(m_short_text); - else result = getNextUtfCodepoint(m_description); - } while (result && iswpunct(result)); - return towlower(result); - } + ShortTextDescription::ShortTextDescription(const char * ShortText, const char * Description): + m_short_text(ShortText), + m_description(Description) + { } + wint_t ShortTextDescription::getNextNonPunctChar() { + wint_t result; + do { + if( m_short_text && *m_short_text ) result = getNextUtfCodepoint(m_short_text); + else result = getNextUtfCodepoint(m_description); + } while (result && iswpunct(result)); + return towlower(result); + } /** * Implemetation of class RecordingsItemPtrCompare @@ -493,7 +460,7 @@ bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vector::iterator iter = std::lower_bound(m_subdirs.begin(), m_subdirs.end(), dirName); if (iter != m_subdirs.end() && !(dirName < *iter) ) return *iter; RecordingsItemPtr recPtr (new RecordingsItemDir(dirName, Level() + 1)); @@ -571,7 +539,7 @@ bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vectorcheckNew() ) return true; return false; } - void RecordingsItem::addDirList(std::vector &dirs, const std::string &basePath) + void RecordingsItem::addDirList(std::vector &dirs, cSv basePath) { if (!IsDir() ) return; - std::string basePath0 = basePath; - if (basePath.empty() ) dirs.push_back(""); - else basePath0.append("/"); + std::string basePath0(basePath); + if (basePath.empty() ) dirs.push_back(""); + else basePath0.append("/"); + size_t basePath0_len = basePath0.length(); for (const auto &subdir:m_subdirs) { - std::string thisPath = basePath0 + subdir->m_name; - dirs.push_back(thisPath); - subdir->addDirList(dirs, thisPath); - } + basePath0.erase(basePath0_len); + basePath0.append(subdir->m_name); + dirs.push_back(basePath0); + subdir->addDirList(dirs, basePath0); + } } void RecordingsItem::setTvShow(const cRecording* recording) { - if (m_cmp_dir) return; - m_cmp_dir = RecordingsItemPtrCompare::BySeason; - getScraperData(recording, cImageLevels(eImageLevel::tvShowCollection, eImageLevel::anySeasonCollection) ); + if (m_cmp_dir) return; + m_cmp_dir = RecordingsItemPtrCompare::BySeason; + getScraperData(recording, cImageLevels(eImageLevel::tvShowCollection, eImageLevel::anySeasonCollection) ); } void RecordingsItem::getScraperData(const cRecording* recording, const cImageLevels &imageLevels, std::string *collectionName) { @@ -693,7 +663,7 @@ bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vector(cLargeString & return m_video_SD_HD; } -void AppendScraperData(cLargeString &target, const std::string &s_IMDB_ID, const cTvMedia &s_image, tvType s_videoType, const std::string &s_title, int s_season_number, int s_episode_number, const std::string &s_episode_name, int s_runtime, const std::string &s_release_date) { +void AppendScraperData(cLargeString &target, cSv s_IMDB_ID, const cTvMedia &s_image, tvType s_videoType, cSv s_title, int s_season_number, int s_episode_number, cSv s_episode_name, int s_runtime, cSv s_release_date) { bool scraperDataAvailable = s_videoType == tMovie || s_videoType == tSeries; target.append("\""); // [2] IMDB ID @@ -868,7 +838,7 @@ void AppendScraperData(cLargeString &target, const std::string &s_IMDB_ID, const if (s_image.width <= s_image.height) target.append("pt"); target.append("\",\""); // [5] : title (scraper) - if (scraperDataAvailable) AppendHtmlEscapedAndCorrectNonUTF8(target, s_title.c_str() ); + if (scraperDataAvailable) AppendHtmlEscapedAndCorrectNonUTF8(target, s_title.data(), s_title.data() + s_title.length() ); target.append("\",\""); if (s_videoType == tSeries && (s_episode_number != 0 || s_season_number != 0)) { // [6] : season/episode/episode name (scraper) @@ -876,7 +846,7 @@ void AppendScraperData(cLargeString &target, const std::string &s_IMDB_ID, const target.append('E'); target.append(s_episode_number); target.append(' '); - AppendHtmlEscapedAndCorrectNonUTF8(target, s_episode_name.c_str() ); + AppendHtmlEscapedAndCorrectNonUTF8(target, s_episode_name.data(), s_episode_name.data() + s_episode_name.length() ); } target.append("\",\""); // [7] : runtime (scraper) @@ -920,7 +890,7 @@ void AppendScraperData(cLargeString &target, const std::string &s_IMDB_ID, const AppendHtmlEscapedAndCorrectNonUTF8(target, RecInfo()->ChannelName() ); target.append("\", \""); // [13] NewR() - target.append(NewR() ); + target.appendS(NewR() ); target.append("\", \""); // [14] Name AppendHtmlEscapedAndCorrectNonUTF8(target, Name().c_str() ); @@ -938,7 +908,7 @@ void AppendScraperData(cLargeString &target, const std::string &s_IMDB_ID, const target.append(","); // if(displayFolder) { target.append("\""); - if( *(const char *)Recording()->Folder() ) AppendHtmlEscapedAndCorrectNonUTF8(target, (const char *)Recording()->Folder() ); + AppendHtmlEscapedAndCorrectNonUTF8(target, (const char *)Recording()->Folder() ); target.append("\""); // } // [19] duration @@ -952,9 +922,9 @@ void AppendScraperData(cLargeString &target, const std::string &s_IMDB_ID, const int fileSizeMB = FileSizeMB(); if(fileSizeMB >= 0) if (fileSizeMB >= 1000) - AppendFormated(target, tr("%.1f GB"), (double)fileSizeMB / 1000.); + stringAppendFormated(target, tr("%.1f GB"), (double)fileSizeMB / 1000.); else - AppendFormated(target, tr("%'d MB"), fileSizeMB); + stringAppendFormated(target, tr("%'d MB"), fileSizeMB); else target.append(" "); target.append("\","); @@ -962,7 +932,7 @@ void AppendScraperData(cLargeString &target, const std::string &s_IMDB_ID, const target.append(NumberTsFiles() ); } -void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vector::const_iterator recIterFirst, std::vector::const_iterator recIterLast, bool &first, const std::string &filter, bool reverse) { +void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vector::const_iterator recIterFirst, std::vector::const_iterator recIterLast, bool &first, cSv filter, bool reverse) { if (reverse) { for (; recIterFirst != recIterLast;) { --recIterLast; @@ -987,7 +957,7 @@ void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vectorTitle() )), + RecordingsItem(event->Title() ), m_event(event) { if (scraperVideo) { @@ -1003,8 +973,8 @@ void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vectorName(); } - bool operator< (const RecordingsItemPtr &a, const std::string &b) { return a->Name() < b; } + bool operator< (cSv a, const RecordingsItemPtr &b) { return a < b->Name(); } + bool operator< (const RecordingsItemPtr &a, cSv b) { return a->Name() < b; } bool operator< (int a, const RecordingsItemPtr &b) { return *b > a; } bool operator< (const RecordingsItemPtr &a, int b) { return *a < b; } @@ -1015,8 +985,10 @@ void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vector begin = std::chrono::high_resolution_clock::now(); // check availability of scraper data + m_creation_timestamp = time(0); cGetScraperVideo getScraperVideo; bool scraperDataAvailable = getScraperVideo.call(LiveSetup().GetPluginScraper()); RecordingsItemPtr recPtrTvShows (new RecordingsItemDir(tr("TV shows"), 1)); @@ -1036,72 +1008,66 @@ void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vector= 20301 LOCK_RECORDINGS_READ; for (cRecording* recording = (cRecording *)Recordings->First(); recording; recording = (cRecording *)Recordings->Next(recording)) { - idI = recording->Id(); -#else - for (cRecording* recording = Recordings.First(); recording; recording = Recordings.Next(recording)) { - idI++; -#endif - if (scraperDataAvailable) m_maxLevel = std::max(m_maxLevel, recording->HierarchyLevels() + 1); - else m_maxLevel = std::max(m_maxLevel, recording->HierarchyLevels() ); + if (scraperDataAvailable) m_maxLevel = std::max(m_maxLevel, recording->HierarchyLevels() + 1); + else m_maxLevel = std::max(m_maxLevel, recording->HierarchyLevels() ); - RecordingsItemPtr dir = m_rootFileSystem; - std::string name(recording->Name()); + RecordingsItemPtr dir = m_rootFileSystem; + std::string_view name(recording->Name()); - size_t index = 0; - size_t pos = 0; - do { - pos = name.find('~', index); - if (pos != std::string::npos) { + size_t index = 0; + size_t pos = 0; + do { + pos = name.find('~', index); + if (pos != std::string::npos) { // note: the dir returned is the added (or existing) subdir named name.substr(index, pos - index) - dir = dir->addDirIfNotExists(name.substr(index, pos - index) ); - index = pos + 1; - // esyslog("live: DH: current dir: '%s'", dir->Name().c_str()); - } - else { - std::string recName(name.substr(index, name.length() - index)); - RecordingsItemPtr recPtr (new RecordingsItemRec(idI, recMan->Md5Hash(recording), recName, recording)); - dir->m_entries.push_back(recPtr); - m_allRecordings.push_back(recPtr); - // esyslog("live: DH: added rec: '%s'", recName.c_str()); - if (scraperDataAvailable) { - switch (recPtr->scraperVideoType() ) { - case tMovie: - if (recPtr->m_s_collection_id == 0) break; - dir = recPtrMovieCollections->addDirCollectionIfNotExists(recPtr->m_s_collection_id, recording); - dir->m_entries.push_back(recPtr); - break; - case tSeries: - dir = recPtrTvShows->addDirIfNotExists(recPtr->scraperName() ); - dir->setTvShow(recording); - if (recPtr->scraperSeasonNumber() != 0 || recPtr->scraperEpisodeNumber() != 0) { - dir = dir->addDirSeasonIfNotExists(recPtr->scraperSeasonNumber(), recording); - } - dir->m_entries.push_back(recPtr); - break; - default: // do nothing - break; - } - } - } - } while (pos != std::string::npos); + dir = dir->addDirIfNotExists(name.substr(index, pos - index) ); + index = pos + 1; + // esyslog("live: DH: current dir: '%s'", dir->Name().c_str()); + } + else { + std::string_view recName(name.substr(index, name.length() - index)); + RecordingsItemPtr recPtr (new RecordingsItemRec(recording->Id(), recMan->Md5Hash(recording), recName, recording)); + dir->m_entries.push_back(recPtr); +m_allRecordings.push_back(recPtr); + // esyslog("live: DH: added rec: '%.*s'", (int)recName.length(), recName.data()); + if (scraperDataAvailable) { + switch (recPtr->scraperVideoType() ) { + case tMovie: + if (recPtr->m_s_collection_id == 0) break; + dir = recPtrMovieCollections->addDirCollectionIfNotExists(recPtr->m_s_collection_id, recording); + dir->m_entries.push_back(recPtr); + break; + case tSeries: + dir = recPtrTvShows->addDirIfNotExists(recPtr->scraperName() ); + dir->setTvShow(recording); + if (recPtr->scraperSeasonNumber() != 0 || recPtr->scraperEpisodeNumber() != 0) { + dir = dir->addDirSeasonIfNotExists(recPtr->scraperSeasonNumber(), recording); + } + dir->m_entries.push_back(recPtr); + break; + default: // do nothing + break; + } + } + } + } while (pos != std::string::npos); } - if (scraperDataAvailable) { - for (auto it = recPtrTvShows->m_subdirs.begin(); it != recPtrTvShows->m_subdirs.end(); ) { - if ((*it)->numberOfRecordings() < 2) it = recPtrTvShows->m_subdirs.erase(it); - else ++it; - } - for (auto it = recPtrMovieCollections->m_subdirs.begin(); it != recPtrMovieCollections->m_subdirs.end(); ) { - if ((*it)->numberOfRecordings() < 2) it = recPtrMovieCollections->m_subdirs.erase(it); - else ++it; - } + if (scraperDataAvailable) { + for (auto it = recPtrTvShows->m_subdirs.begin(); it != recPtrTvShows->m_subdirs.end(); ) { + if ((*it)->numberOfRecordings() < 2) it = recPtrTvShows->m_subdirs.erase(it); + else ++it; + } + for (auto it = recPtrMovieCollections->m_subdirs.begin(); it != recPtrMovieCollections->m_subdirs.end(); ) { + if ((*it)->numberOfRecordings() < 2) it = recPtrMovieCollections->m_subdirs.erase(it); + else ++it; + } } m_root->finishRecordingsTree(); - // esyslog("live: DH: ------ RecordingsTree::RecordingsTree() --------"); + std::chrono::duration timeNeeded = std::chrono::high_resolution_clock::now() - begin; + esyslog("live: DH: ------ RecordingsTree::RecordingsTree() --------, required time: %9.5f", timeNeeded.count() ); } RecordingsTree::~RecordingsTree() diff --git a/recman.h b/recman.h index e164a5d9..803a0b51 100644 --- a/recman.h +++ b/recman.h @@ -1,24 +1,27 @@ #ifndef VDR_LIVE_RECORDINGS_H #define VDR_LIVE_RECORDINGS_H -#include -#include "stdext.h" -#include "setup.h" -#include "largeString.h" -#include "tools.h" - // STL headers need to be before VDR tools.h (included by ) #include #include #include +#include #include #include #if TNTVERSION >= 30000 - #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) + #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif +#include +#include "stdext.h" +#include "setup.h" + #include +#include +#include "stringhelpers.h" +#include "largeString.h" +#include "tools.h" #include "services.h" namespace vdrlive { @@ -40,8 +43,8 @@ namespace vdrlive { typedef std::weak_ptr RecordingsItemWeakPtr; bool operator< (const RecordingsItemPtr &a, const RecordingsItemPtr &b); - bool operator< (const std::string &a, const RecordingsItemPtr &b); - bool operator< (const RecordingsItemPtr &a, const std::string &b); + bool operator< (cSv a, const RecordingsItemPtr &b); + bool operator< (const RecordingsItemPtr &a, cSv b); bool operator< (int a, const RecordingsItemPtr &b); bool operator< (const RecordingsItemPtr &a, int b); @@ -75,13 +78,13 @@ namespace vdrlive { * fetches a cRecording from VDR's Recordings collection. Returns * NULL if recording was not found */ - cRecording const* GetByMd5Hash(std::string const & hash) const; + cRecording const* GetByMd5Hash(cSv hash) const; /** * Move a recording with the given hash according to * VDRs recording mechanisms. */ - bool MoveRecording(cRecording const * recording, std::string const & name, bool copy = false) const; + bool MoveRecording(cRecording const * recording, cSv name, bool copy = false) const; /** * Delete recording resume with the given hash according to @@ -119,18 +122,12 @@ namespace vdrlive { private: RecordingsManager(); -#if VDRVERSNUM >= 20301 static bool StateChanged(); -#endif static RecordingsManagerPtr EnsureValidData(); static std::weak_ptr m_recMan; static std::shared_ptr m_recTree; -#if VDRVERSNUM >= 20301 static cStateKey m_recordingsStateKey; -#else - static int m_recordingsState; -#endif cThreadLock m_recordingsLock; }; @@ -182,7 +179,7 @@ namespace vdrlive { friend class RecordingsTree; protected: - RecordingsItem(const std::string& name); + RecordingsItem(cSv name); public: virtual ~RecordingsItem(); @@ -207,19 +204,19 @@ template void finishRecordingsTree(); // sort recursively, Order: m_cmp_rec (if defined. Otherwise: no sort) // dirs: Order: m_cmp_dir (if defined. Otherwise: m_name_for_sort) virtual bool operator< (const RecordingsItemPtr &sec) const { return m_name < sec->m_name; } - virtual bool operator< (const std::string &sec) const { return m_name < sec; } - virtual bool operator> (const std::string &sec) const { return m_name > sec; } + virtual bool operator< (cSv sec) const { return m_name < sec; } + virtual bool operator> (cSv sec) const { return m_name > sec; } virtual bool operator< (int sec) const { return false; } virtual bool operator> (int sec) const { return false; } virtual int Level() { return 0; } int numberOfRecordings(); - RecordingsItemPtr addDirIfNotExists(const std::string &dirName); + RecordingsItemPtr addDirIfNotExists(cSv dirName); RecordingsItemPtr addDirCollectionIfNotExists(int collectionId, const cRecording* recording); RecordingsItemPtr addDirSeasonIfNotExists(int collectionId, const cRecording* recording); const std::vector *getRecordings(eSortOrder sortOrder); const std::vector *getDirs() { return &m_subdirs; } bool checkNew(); - void addDirList(std::vector &dirs, const std::string &basePath); + void addDirList(std::vector &dirs, cSv basePath); void setTvShow(const cRecording* recording); void getScraperData(const cRecording* recording, const cImageLevels &imageLevels, std::string *collectionName = NULL); @@ -236,7 +233,7 @@ template int CompareStD(const RecordingsItemPtr &second, int *numEqualChars=NULL) const; bool orderDuplicates(const RecordingsItemPtr &second, bool alwaysShortText, bool lang = false) const; // To display the recording on the UI - bool matchesFilter(const std::string &filter); + bool matchesFilter(cSv filter); virtual const int IsArchived() const { return 0 ; } virtual const std::string ArchiveDescr() const { return "" ; } virtual const char *NewR() const { return "" ; } @@ -247,7 +244,7 @@ template bool dirEntriesSorted() { return m_cmp_dir != NULL; } private: - std::string GetNameForSearch(std::string const & name); + std::string GetNameForSearch(cSv name); protected: int m_idI = -1; std::string m_name; @@ -285,7 +282,7 @@ template class RecordingsItemDir : public RecordingsItem { public: - RecordingsItemDir(const std::string& name, int level); + RecordingsItemDir(cSv name, int level); virtual ~RecordingsItemDir(); @@ -306,8 +303,8 @@ template virtual ~RecordingsItemDirSeason(); virtual bool operator< (const RecordingsItemPtr &sec) const { return m_s_season_number < sec->scraperSeasonNumber(); } - virtual bool operator< (const std::string &sec) const { return false; } - virtual bool operator> (const std::string &sec) const { return false; } + virtual bool operator< (cSv sec) const { return false; } + virtual bool operator> (cSv sec) const { return false; } virtual bool operator< (int sec) const { return m_s_season_number < sec; } virtual bool operator> (int sec) const { return m_s_season_number > sec; } @@ -320,8 +317,8 @@ template virtual ~RecordingsItemDirCollection(); virtual bool operator< (const RecordingsItemPtr &sec) const { return m_s_collection_id < sec->scraperCollectionId(); } - virtual bool operator< (const std::string &sec) const { return false; } - virtual bool operator> (const std::string &sec) const { return false; } + virtual bool operator< (cSv sec) const { return false; } + virtual bool operator> (cSv sec) const { return false; } virtual bool operator< (int sec) const { return m_s_collection_id < sec; } virtual bool operator> (int sec) const { return m_s_collection_id > sec; } @@ -336,7 +333,7 @@ template class RecordingsItemRec : public RecordingsItem { public: - RecordingsItemRec(int idI, const std::string& id, const std::string& name, const cRecording* recording); + RecordingsItemRec(int idI, cSv id, cSv name, const cRecording* recording); virtual ~RecordingsItemRec(); @@ -364,7 +361,7 @@ template virtual int SD_HD(); virtual void AppendAsJSArray(cLargeString &target, bool displayFolder); - static void AppendAsJSArray(cLargeString &target, std::vector::const_iterator recIterFirst, std::vector::const_iterator recIterLast, bool &first, const std::string &filter, bool reverse); + static void AppendAsJSArray(cLargeString &target, std::vector::const_iterator recIterFirst, std::vector::const_iterator recIterLast, bool &first, cSv filter, bool reverse); private: const cRecording *m_recording; @@ -412,6 +409,7 @@ template const std::vector *allRecordings(eSortOrder sortOrder); int MaxLevel() const { return m_maxLevel; } + time_t getCreationTimestamp() { return m_creation_timestamp; } private: int m_maxLevel; @@ -421,6 +419,7 @@ template bool m_allRecordingsSorted = false; std::vector m_allRecordings_other_sort; eSortOrder m_sortOrder = (eSortOrder)-1; + time_t m_creation_timestamp = 0; }; @@ -459,7 +458,7 @@ template a.swap(b); } -void AppendScraperData(cLargeString &target, const std::string &s_IMDB_ID, const cTvMedia &s_image, tvType s_videoType, const std::string &s_title, int s_season_number, int s_episode_number, const std::string &s_episode_name, int s_runtime, const std::string &s_release_date); +void AppendScraperData(cLargeString &target, cSv s_IMDB_ID, const cTvMedia &s_image, tvType s_videoType, cSv s_title, int s_season_number, int s_episode_number, cSv s_episode_name, int s_runtime, cSv s_release_date); std::string recordingErrorsHtml(int recordingErrors); diff --git a/setup.cpp b/setup.cpp index acb20bee..660899b5 100644 --- a/setup.cpp +++ b/setup.cpp @@ -39,7 +39,6 @@ Setup::Setup(): m_lastsortingmode("nameasc"), m_tntnetloglevel("WARN"), m_showLogo(1), - m_useAjax(1), m_showInfoBox(1), m_useStreamdev(1), m_streamdevPort(3000), @@ -60,6 +59,7 @@ Setup::Setup(): { m_adminPasswordMD5 = "4:" + MD5Hash("live"); liveplugin = cPluginManager::GetPlugin("live"); + m_vdr_start = time(0); } bool Setup::ParseCommandLine( int argc, char* argv[] ) @@ -155,7 +155,6 @@ bool Setup::ParseSetupEntry( char const* name, char const* value ) else if ( strcmp( name, "LastWhatsOnListMode" ) == 0 ) { m_lastwhatsonlistmode = value; } else if ( strcmp( name, "LastSortingMode" ) == 0 ) { m_lastsortingmode = value; } else if ( strcmp( name, "ShowLogo" ) == 0 ) { m_showLogo = atoi(value); } - else if ( strcmp( name, "UseAjax" ) == 0 ) { m_useAjax = atoi(value); } else if ( strcmp( name, "ShowInfoBox" ) == 0 ) { m_showInfoBox = atoi(value); } else if ( strcmp( name, "UseStreamdev" ) == 0 ) { m_useStreamdev = atoi(value); } else if ( strcmp( name, "StreamdevPort" ) == 0 ) { m_streamdevPort = atoi(value); } @@ -357,7 +356,6 @@ bool Setup::SaveSetup() liveplugin->SetupStore("LastWhatsOnListMode", m_lastwhatsonlistmode.c_str()); liveplugin->SetupStore("LastSortingMode", m_lastsortingmode.c_str()); liveplugin->SetupStore("ShowLogo", m_showLogo); - liveplugin->SetupStore("UseAjax", m_useAjax); liveplugin->SetupStore("ShowInfoBox", m_showInfoBox); liveplugin->SetupStore("UseStreamdev", m_useStreamdev); liveplugin->SetupStore("StreamdevPort", m_streamdevPort); diff --git a/setup.h b/setup.h index 5edc8864..9743ecf0 100644 --- a/setup.h +++ b/setup.h @@ -60,7 +60,7 @@ class Setup std::string const GetLastSortingMode() const { return m_lastsortingmode; } std::string const GetTntnetLogLevel() const { return m_tntnetloglevel; } bool GetShowLogo() const { return m_showLogo != 0; } - bool GetUseAjax() const { return m_useAjax != 0; } + bool GetUseAjax() const { return true; } bool GetShowInfoBox() const { return m_showInfoBox != 0; } bool GetUseStreamdev() const { return m_useStreamdev != 0; } int GetStreamdevPort() const { return m_streamdevPort; } @@ -95,7 +95,6 @@ class Setup void SetLastWhatsOnListMode(std::string const & mode) { m_lastwhatsonlistmode = mode; SaveSetup(); } void SetLastSortingMode(std::string const & mode) { m_lastsortingmode = mode; SaveSetup(); } void SetShowLogo(bool show) { m_showLogo = show ? 1 : 0; } - void SetUseAjax(bool use) { m_useAjax = use ? 1 : 0; } void SetShowInfoBox(bool show) { m_showInfoBox = show ? 1 : 0; } void SetUseStreamdev(bool use) { m_useStreamdev = use ? 1 : 0; } void SetStreamdevPort(int port) { m_streamdevPort = port; } @@ -118,6 +117,7 @@ class Setup bool ParseSetupEntry( char const* name, char const* value ); bool CheckLocalNet(std::string const & ip); + time_t GetVdrStart() { return m_vdr_start; } private: @@ -154,15 +154,14 @@ class Setup std::string m_scheduleDuration; std::string m_startscreen; std::string m_theme; - std::string m_themedLinkPrefix; - std::string m_themedLinkPrefixImg; + std::string m_themedLinkPrefix; + std::string m_themedLinkPrefixImg; std::string m_localnetmask; bool m_islocalnet; std::string m_lastwhatsonlistmode; std::string m_lastsortingmode; std::string m_tntnetloglevel; int m_showLogo; - int m_useAjax; int m_showInfoBox; int m_useStreamdev; int m_streamdevPort; @@ -180,6 +179,7 @@ class Setup bool CheckServerPort(); bool CheckServerIps(); bool CheckServerSslPort(); + time_t m_vdr_start = 0; }; Setup& LiveSetup(); diff --git a/stringhelpers.h b/stringhelpers.h new file mode 100644 index 00000000..b39fc709 --- /dev/null +++ b/stringhelpers.h @@ -0,0 +1,1015 @@ +/* + * version 0.9.2 + * general stringhelper functions + * + * only depends on g++ -std=c++17 std:: standard headers and on esyslog (from VDR) + * no other dependencies, so it can be easily included in any other header + * + * +*/ +#ifndef __STRINGHELPERS_H +#define __STRINGHELPERS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONVERT(result, from, fn) \ +char result[fn(NULL, from) + 1]; \ +fn(result, from); + +#define CV_VA_NUM_ARGS_HELPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N +#define CV_VA_NUM_ARGS(...) CV_VA_NUM_ARGS_HELPER(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define CAT2( A, B ) A ## B +#define SELECT( NAME, NUM ) CAT2( NAME ## _, NUM ) +#define VA_SELECT( NAME, ... ) SELECT( NAME, CV_VA_NUM_ARGS(__VA_ARGS__) )(__VA_ARGS__) + +// concatenate macro based, and fastest =============== +#define CONCATENATE_START_2(result, s1, s2) \ +int result##concatenate_lvls = 0; \ +int result##concatenate_lvl1 = concat::numChars(s1); \ +result##concatenate_lvls += result##concatenate_lvl1; \ +int result##concatenate_lvl2 = concat::numChars(s2); \ +result##concatenate_lvls += result##concatenate_lvl2; + +#define CONCATENATE_START_3(result, s1, s2, s3) \ +CONCATENATE_START_2(result, s1, s2) \ +int result##concatenate_lvl3 = concat::numChars(s3); \ +result##concatenate_lvls += result##concatenate_lvl3; + +#define CONCATENATE_START_4(result, s1, s2, s3, s4) \ +CONCATENATE_START_3(result, s1, s2, s3) \ +int result##concatenate_lvl4 = concat::numChars(s4); \ +result##concatenate_lvls += result##concatenate_lvl4; + +#define CONCATENATE_START_5(result, s1, s2, s3, s4, s5) \ +CONCATENATE_START_4(result, s1, s2, s3, s4) \ +int result##concatenate_lvl5 = concat::numChars(s5); \ +result##concatenate_lvls += result##concatenate_lvl5; + +#define CONCATENATE_START_6(result, s1, s2, s3, s4, s5, s6) \ +CONCATENATE_START_5(result, s1, s2, s3, s4, s5) \ +int result##concatenate_lvl6 = concat::numChars(s6); \ +result##concatenate_lvls += result##concatenate_lvl6; + +#define CONCATENATE_START_7(result, s1, s2, s3, s4, s5, s6, s7) \ +CONCATENATE_START_6(result, s1, s2, s3, s4, s5, s6) \ +int result##concatenate_lvl7 = concat::numChars(s7); \ +result##concatenate_lvls += result##concatenate_lvl7; + +#define CONCATENATE_START_8(result, s1, s2, s3, s4, s5, s6, s7, s8) \ +CONCATENATE_START_7(result, s1, s2, s3, s4, s5, s6, s7) \ +int result##concatenate_lvl8 = concat::numChars(s8); \ +result##concatenate_lvls += result##concatenate_lvl8; + +#define CONCATENATE_START_9(result, s1, s2, s3, s4, s5, s6, s7, s8, s9) \ +CONCATENATE_START_8(result, s1, s2, s3, s4, s5, s6, s7, s8) \ +int result##concatenate_lvl9 = concat::numChars(s9); \ +result##concatenate_lvls += result##concatenate_lvl9; + +#define CONCATENATE_END_ADDCHARS_B(result_concatenate_buf, lvl, s) \ +concat::addChars(result_concatenate_buf, lvl, s); \ +result_concatenate_buf += lvl; + +#define CONCATENATE_END_2(result, s1, s2) \ +char *result##concatenate_buf = result; \ +CONCATENATE_END_ADDCHARS_B(result##concatenate_buf, result##concatenate_lvl1, s1); \ +CONCATENATE_END_ADDCHARS_B(result##concatenate_buf, result##concatenate_lvl2, s2); + +#define CONCATENATE_END_3(result, s1, s2, s3) \ +CONCATENATE_END_2(result, s1, s2) \ +CONCATENATE_END_ADDCHARS_B(result##concatenate_buf, result##concatenate_lvl3, s3); + +#define CONCATENATE_END_4(result, s1, s2, s3, s4) \ +CONCATENATE_END_3(result, s1, s2, s3) \ +CONCATENATE_END_ADDCHARS_B(result##concatenate_buf, result##concatenate_lvl4, s4); + +#define CONCATENATE_END_5(result, s1, s2, s3, s4, s5) \ +CONCATENATE_END_4(result, s1, s2, s3, s4) \ +CONCATENATE_END_ADDCHARS_B(result##concatenate_buf, result##concatenate_lvl5, s5); + +#define CONCATENATE_END_6(result, s1, s2, s3, s4, s5, s6) \ +CONCATENATE_END_5(result, s1, s2, s3, s4, s5) \ +CONCATENATE_END_ADDCHARS_B(result##concatenate_buf, result##concatenate_lvl6, s6); + +#define CONCATENATE_END_7(result, s1, s2, s3, s4, s5, s6, s7) \ +CONCATENATE_END_6(result, s1, s2, s3, s4, s5, s6) \ +CONCATENATE_END_ADDCHARS_B(result##concatenate_buf, result##concatenate_lvl7, s7); + +#define CONCATENATE_END_8(result, s1, s2, s3, s4, s5, s6, s7, s8) \ +CONCATENATE_END_7(result, s1, s2, s3, s4, s5, s6, s7) \ +CONCATENATE_END_ADDCHARS_B(result##concatenate_buf, result##concatenate_lvl8, s8); + +#define CONCATENATE_END_9(result, s1, s2, s3, s4, s5, s6, s7, s8, s9) \ +CONCATENATE_END_8(result, s1, s2, s3, s4, s5, s6, s7, s8) \ +CONCATENATE_END_ADDCHARS_B(result##concatenate_buf, result##concatenate_lvl9, s9); + +#define CONCATENATE(result, ...) \ +SELECT( CONCATENATE_START, CV_VA_NUM_ARGS(__VA_ARGS__) )(result, __VA_ARGS__) \ +char result[result##concatenate_lvls + 1]; \ +result[result##concatenate_lvls] = 0; \ +SELECT( CONCATENATE_END, CV_VA_NUM_ARGS(__VA_ARGS__) )(result, __VA_ARGS__) \ +*result##concatenate_buf = 0; + +// ========================================================= +// methods for char *s, make sure that s==NULL is just an empty string +// ========================================================= +inline std::string charPointerToString(const unsigned char *s) { + return s?reinterpret_cast(s):""; +} +inline std::string_view charPointerToStringView(const unsigned char *s) { + return s?reinterpret_cast(s):std::string_view(); +} +inline std::string_view charPointerToStringView(const char *s) { + return s?s:std::string_view(); +} +inline std::string charPointerToString(const char *s) { + return s?s:""; +} +// challange: +// method with importing parameter cSv called with const char * = nullptr +// undifined behavior, as cSv(nullptr) is undefined. +// solution: +// a) be very carefull, check const char * for nullptr before calling a method with cSv as import parameter +// b) replace all cSv with cSv +// very small performance impact if such a method if called with cSv +// convert nullptr to empty cSv if called with const char * + +class cSv: public std::string_view { + public: + cSv(): std::string_view() {} + cSv(const char *s): std::string_view(charPointerToStringView(s)) {} + cSv(const char *s, size_t l): std::string_view(s, l) {} + cSv(std::string_view sv): std::string_view(sv) {} + cSv(const std::string &s): std::string_view(s) {} + cSv substr_csv(size_t pos = 0) { return (length() > pos)?cSv(data() + pos, length() - pos):cSv(); } + cSv substr_csv(size_t pos, size_t count) { return (length() > pos)?cSv(data() + pos, std::min(length() - pos, count) ):cSv(); } +}; +inline bool stringEqual(const char *s1, const char *s2) { +// return true if texts are identical (or both texts are NULL) + if (s1 && s2) return strcmp(s1, s2) == 0; + if (!s1 && !s2 ) return true; + if (!s1 && !*s2 ) return true; + if (!*s1 && !s2 ) return true; + return false; +} + +// ========================================================= +// ========================================================= +// Chapter 0: UTF8 string utilities **************** +// ========================================================= +// ========================================================= + +inline int AppendUtfCodepoint(char *&target, wint_t codepoint){ + if (codepoint <= 0x7F) { + if (target) { + *(target++) = (char) (codepoint); + *target = 0; + } + return 1; + } + if (codepoint <= 0x07FF) { + if (target) { + *(target++) =( (char) (0xC0 | (codepoint >> 6 ) ) ); + *(target++) =( (char) (0x80 | (codepoint & 0x3F)) ); + *target = 0; + } + return 2; + } + if (codepoint <= 0xFFFF) { + if (target) { + *(target++) =( (char) (0xE0 | ( codepoint >> 12)) ); + *(target++) =( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); + *(target++) =( (char) (0x80 | ( codepoint & 0x3F)) ); + *target = 0; + } + return 3; + } + if (target) { + *(target++) =( (char) (0xF0 | ((codepoint >> 18) & 0x07)) ); + *(target++) =( (char) (0x80 | ((codepoint >> 12) & 0x3F)) ); + *(target++) =( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); + *(target++) =( (char) (0x80 | ( codepoint & 0x3F)) ); + *target = 0; + } + return 4; +} + +inline void stringAppendUtfCodepoint(std::string &target, wint_t codepoint){ + if (codepoint <= 0x7F){ + target.push_back( (char) (codepoint) ); + return; + } + if (codepoint <= 0x07FF) { + target.push_back( (char) (0xC0 | (codepoint >> 6 ) ) ); + target.push_back( (char) (0x80 | (codepoint & 0x3F)) ); + return; + } + if (codepoint <= 0xFFFF) { + target.push_back( (char) (0xE0 | ( codepoint >> 12)) ); + target.push_back( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); + target.push_back( (char) (0x80 | ( codepoint & 0x3F)) ); + return; + } + target.push_back( (char) (0xF0 | ((codepoint >> 18) & 0x07)) ); + target.push_back( (char) (0x80 | ((codepoint >> 12) & 0x3F)) ); + target.push_back( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); + target.push_back( (char) (0x80 | ( codepoint & 0x3F)) ); + return; +} + +inline int utf8CodepointIsValid(const char *p){ +// In case of invalid UTF8, return 0 +// otherwise, return number of characters for this UTF codepoint + static const uint8_t LEN[] = {2,2,2,2,3,3,4,0}; + + int len = ((*p & 0xC0) == 0xC0) * LEN[(*p >> 3) & 7] + ((*p | 0x7F) == 0x7F); + for (int k=1; k < len; k++) if ((p[k] & 0xC0) != 0x80) len = 0; + return len; +} + +inline wint_t Utf8ToUtf32(const char *&p, int len) { +// assumes, that uft8 validity checks have already been done. len must be provided. call utf8CodepointIsValid first +// change p to position of next codepoint (p = p + len) + static const uint8_t FF_MSK[] = {0xFF >>0, 0xFF >>0, 0xFF >>3, 0xFF >>4, 0xFF >>5, 0xFF >>0, 0xFF >>0, 0xFF >>0}; + wint_t val = *p & FF_MSK[len]; + const char *q = p + len; + for (p++; p < q; p++) val = (val << 6) | (*p & 0x3F); + return val; +} + +inline wint_t getUtfCodepoint(const char *p) { +// get next codepoint +// 0 is returned at end of string + if(!p || !*p) return 0; + int l = utf8CodepointIsValid(p); + if( l == 0 ) return '?'; + const char *s = p; + return Utf8ToUtf32(s, l); +} + +inline wint_t getNextUtfCodepoint(const char *&p){ +// get next codepoint, and increment p +// 0 is returned at end of string, and p will point to the end of the string (0) + if(!p || !*p) return 0; + int l = utf8CodepointIsValid(p); + if( l == 0 ) { p++; return '?'; } + return Utf8ToUtf32(p, l); +} + +// ========================================================= +// ========================================================= +// Chapter 1: Parse char* / string_view / string +// ========================================================= +// ========================================================= + +// ========================================================= +// whitespace ============================================== +// ========================================================= +inline bool my_isspace(char c) { +// 0.0627, fastest + return (c == ' ') || (c >= 0x09 && c <= 0x0d); +// (0x09, '\t'), (0x0a, '\n'), (0x0b, '\v'), (0x0c, '\f'), (0x0d, '\r') +} +inline void StringRemoveTrailingWhitespace(std::string &str) { + const char* whitespaces = " \t\f\v\n\r"; + + std::size_t found = str.find_last_not_of(whitespaces); + if (found!=std::string::npos) + str.erase(found+1); + else + str.clear(); // str is all whitespace +} + +inline int StringRemoveTrailingWhitespace(const char *str, int len) { +// return "new" len of string, without whitespaces at the end + if (!str) return 0; + for (; len; len--) if (!my_isspace(str[len - 1])) return len; + return 0; +} + +inline cSv remove_leading_whitespace(cSv sv) { +// return a string_view with leading whitespace from sv removed +// for performance: +// avoid changing sv: cSv &sv is much slower than cSv sv +// don't use std::isspace or isspace: this is really slow ... 0.055 <-> 0.037 +// also avoid find_first_not_of(" \t\f\v\n\r";): way too slow ... +// definition of whitespace: +// (0x20, ' '), (0x09, '\t'), (0x0a, '\n'), (0x0b, '\v'), (0x0c, '\f'), (0x0d, '\r') +// or: (c == ' ') || (c >= 0x09 && c <= 0x0d); +// best performance: use find_first_not_of for ' ': + for (size_t i = 0; i < sv.length(); ++i) { + i = sv.find_first_not_of(' ', i); + if (i == std::string_view::npos) return cSv(); // only ' ' + if (sv[i] > 0x0d || sv[i] < 0x09) return sv.substr(i); // non whitespace found at i + } + return cSv(); + +/* + for (size_t i = 0; i < sv.length(); ++i) if (!my_isspace(sv[i])) return sv.substr(i); + return cSv(); +*/ +/* +// same performance as for with if in for loop. +// prefer if in for loop for shorter code and better readability + size_t i = 0; + for (; i < sv.length() && my_isspace(sv[i]); ++i); + return sv.substr(i); +*/ +} +// ========================================================= +// parse string_view for int +// ========================================================= + +template inline T parse_unsigned_internal(cSv sv) { + T val = 0; + for (size_t start = 0; start < sv.length() && std::isdigit(sv[start]); ++start) val = val*10 + (sv[start]-'0'); + return val; +} +template inline T parse_int(cSv sv) { + if (sv.empty() ) return 0; + if (!std::isdigit(sv[0]) && sv[0] != '-') { + sv = remove_leading_whitespace(sv); + if (sv.empty() ) return 0; + } + if (sv[0] != '-') return parse_unsigned_internal(sv); + return -parse_unsigned_internal(sv.substr(1)); +} + +template inline T parse_unsigned(cSv sv) { + if (sv.empty() ) return 0; + if (!std::isdigit(sv[0])) sv = remove_leading_whitespace(sv); + return parse_unsigned_internal(sv); +} + +// ========================================================= +// parse string_view for xml +// ========================================================= + +template cSv partInXmlTag(cSv sv, const char (&tag)[N], bool *exists = nullptr) { +// very simple XML parser +// if sv contains ..., ... is returned (part between the outermost XML tags is returned). +// otherwise, cSv() is returned. This is also returned if the tags are there, but there is nothing between the tags ... +// there is no error checking, like is more often in sv than , ... + if (exists) *exists = false; +// N == strlen(tag) + 1. It includes the 0 terminator ... +// strlen(startTag) = N+1; strlen(endTag) = N+2. Sums to 2N+3 + if (N < 1 || sv.length() < 2*N+3) return cSv(); +// create + char tagD[N + 2]; + memcpy(tagD + 2, tag, N - 1); + tagD[N + 1] = '>'; +// find + tagD[1] = '<'; + size_t pos_start = sv.find(tagD + 1, 0, N + 1); + if (pos_start == std::string_view::npos) return cSv(); + pos_start += N + 1; // start of ... between tags +// rfind + tagD[0] = '<'; + tagD[1] = '/'; +// std::cout << "tagD[0] " << cSv(tagD, N + 2) << "\n"; + size_t len = sv.substr(pos_start).rfind(tagD, std::string_view::npos, N + 2); + if (len == std::string_view::npos) return cSv(); + if (exists) *exists = true; + return sv.substr(pos_start, len); +} + +// ========================================================= +// =========== search in char* +// ========================================================= + +inline const char* removePrefix(const char *s, const char *prefix) { +// if s starts with prefix, return s + strlen(prefix) (string with prefix removed) +// otherwise, return NULL + if (!s || !prefix) return NULL; + size_t len = strlen(prefix); + if (strncmp(s, prefix, len) != 0) return NULL; + return s+len; +} + +inline const char *strnstr(const char *haystack, const char *needle, size_t len) { +// if len > 0: use only len characters of needle +// if len == 0: use all (strlen(needle)) characters of needle + + if (len == 0) return strstr(haystack, needle); + for (;(haystack = strchr(haystack, needle[0])); haystack++) + if (!strncmp(haystack, needle, len)) return haystack; + return 0; +} + +inline const char *strstr_word (const char *haystack, const char *needle, size_t len = 0) { +// as strstr, but needle must be a word (surrounded by non-alphanumerical characters) +// if len > 0: use only len characters of needle +// if len == 0: use strlen(needle) characters of needle + if (!haystack || !needle || !(*needle) ) return NULL; + size_t len2 = (len == 0) ? strlen(needle) : len; + if (len2 == 0) return NULL; + for (const char *f = strnstr(haystack, needle, len); f && *(f+1); f = strnstr (f + 1, needle, len) ) { + if (f != haystack && isalpha(*(f-1) )) continue; + if (f[len2] != 0 && isalpha(f[len2]) ) continue; + return f; + } + return NULL; +} + +inline cSv textAttributeValue(const char *text, const char *attributeName) { + if (!text || !attributeName) return cSv(); + const char *found = strstr(text, attributeName); + if (!found) return cSv(); + const char *avs = found + strlen(attributeName); + const char *ave = strchr(avs, '\n'); + if (!ave) return cSv(); + return cSv(avs, ave-avs); +} + +inline bool splitString(cSv str, cSv delim, size_t minLengh, cSv &first, cSv &second) { +// true if delim is part of str, and length of first & second >= minLengh + std::size_t found = str.find(delim); + size_t first_len = 0; + while (found != std::string::npos) { + first_len = StringRemoveTrailingWhitespace(str.data(), found); + if (first_len >= minLengh) break; + found = str.find(delim, found + 1); + } +// std::cout << "first_len " << first_len << " found " << found << "\n"; + if(first_len < minLengh) return false; // nothing found + + std::size_t ssnd; + for(ssnd = found + delim.length(); ssnd < str.length() && str[ssnd] == ' '; ssnd++); + if(str.length() - ssnd < minLengh) return false; // nothing found, second part to short + + second = str.substr(ssnd); + first = str.substr(0, first_len); + return true; +} + +inline bool splitString(cSv str, char delimiter, size_t minLengh, cSv &first, cSv &second) { + using namespace std::literals::string_view_literals; + if (delimiter == '-') return splitString(str, " - "sv, minLengh, first, second); + if (delimiter == ':') return splitString(str, ": "sv, minLengh, first, second); + std::string delim(1, delimiter); + return splitString(str, delim, minLengh, first, second); +} + +inline cSv SecondPart(cSv str, cSv delim, size_t minLengh) { +// return second part of split string if delim is part of str, and length of first & second >= minLengh +// otherwise, return "" + cSv first, second; + if (splitString(str, delim, minLengh, first, second)) return second; + else return ""; +} + +inline cSv SecondPart(cSv str, cSv delim) { +// Return part of str after first occurence of delim +// if delim is not in str, return "" + size_t found = str.find(delim); + if (found == std::string::npos) return cSv(); + std::size_t ssnd; + for(ssnd = found + delim.length(); ssnd < str.length() && str[ssnd] == ' '; ssnd++); + return str.substr(ssnd); +} + +inline int StringRemoveLastPartWithP(const char *str, int len) { +// remove part with (...) +// return -1 if nothing can be removed +// otherwise length of string without () + len = StringRemoveTrailingWhitespace(str, len); + if (len < 3) return -1; + if (str[len -1] != ')') return -1; + for (int i = len -2; i; i--) { + if (!isdigit(str[i]) && str[i] != '/') { + if (str[i] != '(') return -1; + int len2 = StringRemoveLastPartWithP(str, i); + if (len2 == -1 ) return StringRemoveTrailingWhitespace(str, i); + return len2; + } + } + return -1; +} + +inline bool StringRemoveLastPartWithP(std::string &str) { +// remove part with (...) + int len = StringRemoveLastPartWithP(str.c_str(), str.length() ); + if (len < 0) return false; + str.erase(len); + return true; +} +inline cSv removeLastPartWithP(cSv str) { + int l = StringRemoveLastPartWithP(str.data(), str.length() ); + if (l < 0) return str; + return cSv(str.data(), l); +} + +inline int NumberInLastPartWithPS(cSv str) { +// return number in last part with (./.), 0 if not found / invalid + if (str.length() < 3 ) return 0; + if (str[str.length() - 1] != ')') return 0; + std::size_t found = str.find_last_of("("); + if (found == std::string::npos) return 0; + for (std::size_t i = found + 1; i < str.length() - 1; i ++) { + if (!isdigit(str[i]) && str[i] != '/') return 0; // we ignore (asw), and return only number with digits only + } + return parse_unsigned_internal(str.substr_csv(found + 1)); +} +inline int NumberInLastPartWithP(cSv str) { +// return number in last part with (...), 0 if not found / invalid + if (str.length() < 3 ) return 0; + if (str[str.length() - 1] != ')') return 0; + std::size_t found = str.find_last_of("("); + if (found == std::string::npos) return 0; + for (std::size_t i = found + 1; i < str.length() - 1; i ++) { + if (!isdigit(str[i])) return 0; // we ignore (asw), and return only number with digits only + } + return parse_unsigned_internal(str.substr_csv(found + 1)); +} + +inline int seasonS(cSv description_part, const char *S) { +// return season, if found at the beginning of description_part +// otherwise, return -1 +// std::cout << "seasonS " << description_part << "\n"; + size_t s_len = strlen(S); + if (description_part.length() <= s_len || + !isdigit(description_part[s_len]) || + description_part.compare(0, s_len, S) != 0) return -1; + return parse_unsigned(description_part.substr(s_len)); +} +inline bool episodeSEp(int &season, int &episode, cSv description, const char *S, const char *Ep) { +// search pattern S Ep +// return true if episode was found. +// set season = -1 if season was not found +// set episode = 0 if episode was not found +// find Ep[digit] + season = -1; + episode = 0; + size_t Ep_len = strlen(Ep); + size_t ep_pos = 0; + do { + ep_pos = description.find(Ep, ep_pos); + if (ep_pos == std::string_view::npos || ep_pos + Ep_len >= description.length() ) return false; // no Ep[digit] + ep_pos += Ep_len; +// std::cout << "ep_pos = " << ep_pos << "\n"; + } while (!isdigit(description[ep_pos])); +// Ep[digit] found +// std::cout << "ep found at " << description.substr(ep_pos) << "\n"; + episode = parse_unsigned(description.substr(ep_pos)); + if (ep_pos - Ep_len >= 3) season = seasonS(description.substr(ep_pos - Ep_len - 3), S); + if (season < 0 && ep_pos - Ep_len >= 4) season = seasonS(description.substr(ep_pos - Ep_len - 4), S); + return true; +} + +// ========================================================= +// ========================================================= +// Chapter 2: change string: mainly: append to string +// ========================================================= +// ========================================================= + +// ========================================================= +// some performance improvemnt, to get string presentation for channel +// you can also use channelID.ToString() +// ========================================================= + +inline int stringAppendAllASCIICharacters(std::string &target, const char *str) { +// append all characters > 31 (signed !!!!). Unsigned: 31 < character < 128 +// return number of appended characters + int i = 0; +// for (const signed char *strs = reinterpret_cast(str); strs[i] > 31; i++); + for (; reinterpret_cast(str)[i] > 31; i++); + target.append(str, i); + return i; +} +inline void stringAppendRemoveControlCharacters(std::string &target, const char *str) { + for(;;) { + str += stringAppendAllASCIICharacters(target, str); + wint_t cp = getNextUtfCodepoint(str); + if (cp == 0) { StringRemoveTrailingWhitespace(target); return; } + if (cp > 31) stringAppendUtfCodepoint(target, cp); + else target.append(" "); + } +} +inline void stringAppendRemoveControlCharactersKeepNl(std::string &target, const char *str) { + for(;;) { + str += stringAppendAllASCIICharacters(target, str); + wint_t cp = getNextUtfCodepoint(str); + if (cp == 0) { StringRemoveTrailingWhitespace(target); return; } + if (cp == ' ' && str[1] == '\n') target.append("\n"); + else if (cp > 31 || cp == '\n') stringAppendUtfCodepoint(target, cp); + else target.append(" "); + } +} + +// ========================================================= +// =========== concatenate ================================= +// ========================================================= + +inline std::string concatenate(cSv s1, cSv s2) { + std::string result; + result.reserve(s1.length() + s2.length()); + result.append(s1); + result.append(s2); + return result; +} + +inline std::string concatenate(cSv s1, cSv s2, cSv s3) { + std::string result; + result.reserve(s1.length() + s2.length() + s3.length() ); + result.append(s1); + result.append(s2); + result.append(s3); + return result; +} + +inline std::string concatenate(const char *s1, const char *s2) { + if (!s1 && !s2) return std::string(); + if (!s1) return s2; + if (!s2) return s1; + std::string result; + result.reserve(strlen(s1) + strlen(s2)); + result.append(s1); + result.append(s2); + return result; +} + +inline std::string concatenate(const char *s1, const char *s2, const char *s3) { + if (!s1) return concatenate(s2, s3); + if (!s2) return concatenate(s1, s3); + if (!s3) return concatenate(s1, s2); + std::string result; + result.reserve(strlen(s1) + strlen(s2) + strlen(s3) ); + result.append(s1); + result.append(s2); + result.append(s3); + return result; +} + +namespace concat { + template inline int numCharsUg0(T i) { +// note: i must be > 0!!!! + int numChars; + for (numChars = 0; i; i /= 10) numChars++; + return numChars; + } + template inline int numCharsU(T i) { +// note: i must be >= 0!!!! + if (i < 10) return 1; + return numCharsUg0(i); + } + inline int numChars(cSv s) { return s.length(); } + inline int numChars(std::string_view s) { return s.length(); } + inline int numChars(const std::string &s) { return s.length(); } + inline int numChars(const char *s) { return s?strlen(s):0; } + inline int numChars(int i) { + if (i == 0) return 1; + if (i > 0 ) return numCharsUg0(i); + return numCharsUg0(-i) + 1; + } + template inline char *addCharsUg0be(char *be, T i) { +// i > 0 must be ensured before calling! +// make sure to have a large enough buffer size (20 + zero terminator if required) +// be is buffer end. Last character is written to be-1 +// no zero terminator is written! You can make buffer large enough and set *be=0 before calling +// position of first char is returned +// length is be - returned value + for (; i; i /= 10) *(--be) = '0' + (i%10); + return be; + } + template inline char *addCharsIbe(char *be, T i) { +// i can be any integer like type (signed, unsigned, ...) +// make sure to have a large enough buffer size (20 + zero terminator if required) +// be is buffer end. Last character is written to be-1 +// no zero terminator is written! You can make buffer large enough and set *be=0 before calling +// position of first char is returned +// length is be - returned value +// Example: +// char buffer_i[21]; +// buffer_i[20] = 0; +// std::cout << "\"" << concat::addCharsIbe(buffer_i+20, 5) << "\"\n"; +// Example 2: +// char buffer2_i[20]; +// char *result = concat::addCharsIbe(buffer2_i+20, 6); +// std::cout << "\"" << cSv(result, buffer2_i + 20 - result) << "\"\n"; + + if (i > 0) return addCharsUg0be(be, i); + if (i == 0) { + *(--be) = '0'; + return be; + } + be = addCharsUg0be(be, -i); + *(--be) = '-'; + return be; + } + template inline cSv addCharsIbeSc(char *be, T i) { + char *result = concat::addCharsIbe(be, i); + return cSv(result, be - result); + } + inline void addChars(char *b, int l, int i) { addCharsIbe(b+l, i); } + inline void addChars(char *b, int l, const std::string_view &s) { memcpy(b, s.data(), l); } + inline void addChars(char *b, int l, const cSv &s) { memcpy(b, s.data(), l); } + inline void addChars(char *b, int l, const std::string &s) { memcpy(b, s.data(), l); } + inline void addChars(char *b, int l, const char *s) { memcpy(b, s, l); } + +// methods to append to std::strings ======================== + template + inline void stringAppendU(std::string &str, T i) { +// for integer types i >= 0 !!!! This is not checked !!!!! + if (i == 0) { str.append(1, '0'); return; } + char buf[20]; // unsigned int 64: max. 20. (18446744073709551615) signed int64: max. 19 (+ sign) + char *bufe = buf+20; + char *bufs = addCharsUg0be(bufe, i); + str.append(bufs, bufe-bufs); + } + template + inline void stringAppendI(std::string &str, T i) { +// for integer types i + char buf[20]; + char *bufe = buf+20; + char *bufs = addCharsIbe(bufe, i); + str.append(bufs, bufe-bufs); + } +} +inline void stringAppend(std::string &str, unsigned int i) { concat::stringAppendU(str, i); } +inline void stringAppend(std::string &str, unsigned long int i) { concat::stringAppendU(str, i); } +inline void stringAppend(std::string &str, unsigned long long int i) { concat::stringAppendU(str, i); } + +inline void stringAppend(std::string &str, int i) { concat::stringAppendI(str, i); } +inline void stringAppend(std::string &str, long int i) { concat::stringAppendI(str, i); } +inline void stringAppend(std::string &str, long long int i) { concat::stringAppendI(str, i); } + +// strings +inline void stringAppend(std::string &str, const char *s) { if(s) str.append(s); } +inline void stringAppend(std::string &str, const std::string &s) { str.append(s); } +inline void stringAppend(std::string &str, std::string_view s) { str.append(s); } +inline void stringAppend(std::string &str, cSv s) { str.append(s); } + +template +void stringAppend(std::string &str, const T &n, const Args&... args) { + stringAppend(str, n); + stringAppend(str, args...); +} +template +std::string concatenate(const Args&... args) { + std::string result; + result.reserve(200); +//stringAppend(result, std::forward(args)...); + stringAppend(result, args...); + return result; +} + +// __attribute__ ((format (printf, 2, 3))) can not be used, but should work starting with gcc 13.1 +template +void stringAppendFormated(std::string &str, const char *fmt, Args&&... args) { + size_t size = 1024; + char buf[size]; + int needed = snprintf (buf, size, fmt, std::forward(args)...); + if (needed < 0) return; // error in snprintf + if ((size_t)needed < size) { + str.append(buf); + } else { + char buf2[needed + 1]; + sprintf (buf2, fmt, args...); + str.append(buf2); + } +} +class cConcatenate +{ + public: + cConcatenate(size_t buf_size = 0) { m_data.reserve(buf_size>0?buf_size:200); } + cConcatenate(const cConcatenate&) = delete; + cConcatenate &operator= (const cConcatenate &) = delete; + template + cConcatenate & operator<<(const T &i) { stringAppend(m_data, i); return *this; } + std::string moveStr() { return std::move(m_data); } + const std::string &getStrRef() { return m_data; } + const char *getCharS() { return m_data.c_str(); } + private: + std::string m_data; +}; + +// ========================================================= +// ========================================================= +// Chapter 3: containers +// convert containers to strings, and strings to containers +// ========================================================= +// ========================================================= + +template +void push_back_new(std::vector &vec, const T &str) { +// add str to vec, but only if str is not yet in vec and if str is not empty + if (str.empty() ) return; + if (find (vec.begin(), vec.end(), str) != vec.end() ) return; + vec.push_back(str); +} + +template +void stringToVector(std::vector &vec, const char *str) { +// if str does not start with '|', don't split, just add str to vec +// otherwise, split str at '|', and add each part to vec + if (!str || !*str) return; + if (str[0] != '|') { vec.push_back(str); return; } + const char *lDelimPos = str; + for (const char *rDelimPos = strchr(lDelimPos + 1, '|'); rDelimPos != NULL; rDelimPos = strchr(lDelimPos + 1, '|') ) { + push_back_new(vec, T(lDelimPos + 1, rDelimPos - lDelimPos - 1)); + lDelimPos = rDelimPos; + } +} + +inline std::string vectorToString(const std::vector &vec) { + if (vec.size() == 0) return ""; + if (vec.size() == 1) return vec[0]; + std::string result("|"); + for (const std::string &str: vec) { result.append(str); result.append("|"); } + return result; +} + +inline std::string objToString(const int &i) { return std::to_string(i); } +inline std::string objToString(const std::string &i) { return i; } + +template> +std::string getStringFromSet(const C &in, char delim = ';') { + if (in.size() == 0) return ""; + std::string result; + if (delim == '|') result.append(1, delim); + for (const T &i: in) { + result.append(objToString(i)); + result.append(1, delim); + } + return result; +} + +template T stringToObj(const char *s, size_t len) { + esyslog("tvscraper: ERROR: template T stringToObj called"); + return 5; } +template<> inline int stringToObj(const char *s, size_t len) { return atoi(s); } +template<> inline std::string stringToObj(const char *s, size_t len) { return std::string(s, len); } +template<> inline cSv stringToObj(const char *s, size_t len) { return cSv(s, len); } + +template void insertObject(std::vector &cont, const T &obj) { cont.push_back(obj); } +template void insertObject(std::set &cont, const T &obj) { cont.insert(obj); } + +template> +C getSetFromString(const char *str, char delim = ';') { +// split str at delim';', and add each part to result + C result; + if (!str || !*str) return result; + const char *lStartPos = str; + if (delim == '|' && lStartPos[0] == delim) lStartPos++; + for (const char *rDelimPos = strchr(lStartPos, delim); rDelimPos != NULL; rDelimPos = strchr(lStartPos, delim) ) { + insertObject(result, stringToObj(lStartPos, rDelimPos - lStartPos)); + lStartPos = rDelimPos + 1; + } + const char *rDelimPos = strchr(lStartPos, 0); + if (rDelimPos != lStartPos) insertObject(result, stringToObj(lStartPos, rDelimPos - lStartPos)); + return result; +} + +inline const char *strchr_se(const char *ss, const char *se, char ch) { + for (const char *sc = ss; sc < se; sc++) if (*sc == ch) return sc; + return NULL; +} + +template> +C getSetFromString(cSv str, char delim, const std::set &ignoreWords, int max_len = 0) { +// split str at delim, and add each part to result + C result; + if (str.empty() ) return result; + const char *lCurrentPos = str.data(); + const char *lEndPos = str.data() + str.length(); + const char *lSoftEndPos = max_len == 0?lEndPos:str.data() + max_len; + for (const char *rDelimPos = strchr_se(lCurrentPos, lEndPos, delim); rDelimPos != NULL; rDelimPos = strchr_se(lCurrentPos, lEndPos, delim) ) { + if (ignoreWords.find(cSv(lCurrentPos, rDelimPos - lCurrentPos)) == ignoreWords.end() ) + insertObject(result, stringToObj(lCurrentPos, rDelimPos - lCurrentPos)); + lCurrentPos = rDelimPos + 1; + if (lCurrentPos >= lSoftEndPos) break; + } + if (lCurrentPos < lEndPos && lCurrentPos < lSoftEndPos) insertObject(result, stringToObj(lCurrentPos, lEndPos - lCurrentPos)); + return result; +} + +class cSplit { + public: + cSplit(cSv sv, char delim): m_sv(sv), m_delim(delim), m_end(cSv(), m_delim) {} + cSplit(const char *s, char delim): m_sv(charPointerToStringView(s)), m_delim(delim), m_end(cSv(), m_delim) {} + cSplit(const cSplit&) = delete; + cSplit &operator= (const cSplit &) = delete; + class iterator { + cSv m_remainingParts; + char m_delim; + size_t m_next_delim; + public: + using iterator_category = std::forward_iterator_tag; + using value_type = cSv; + using difference_type = int; + using pointer = const cSv*; + using reference = cSv; + + explicit iterator(cSv r, char delim): m_delim(delim) { + if (!r.empty() && r[0] == delim) m_remainingParts = r.substr(1); + else m_remainingParts = r; + m_next_delim = m_remainingParts.find(m_delim); + } + iterator& operator++() { + if (m_next_delim == std::string_view::npos) { + m_remainingParts = cSv(); + } else { + m_remainingParts = m_remainingParts.substr(m_next_delim + 1); + m_next_delim = m_remainingParts.find(m_delim); + } + return *this; + } + bool operator!=(iterator other) const { return m_remainingParts != other.m_remainingParts; } + bool operator==(iterator other) const { return m_remainingParts == other.m_remainingParts; } + cSv operator*() const { + if (m_next_delim == std::string_view::npos) return m_remainingParts; + else return m_remainingParts.substr(0, m_next_delim); + } + }; + iterator begin() { return iterator(m_sv, m_delim); } + const iterator &end() { return m_end; } + iterator find(cSv sv) { + if (m_sv.find(sv) == std::string_view::npos) return m_end; + return std::find(begin(), end(), sv); + } + private: + const cSv m_sv; + const char m_delim; + const iterator m_end; +}; + +class cContainer { + public: + cContainer(char delim = '|'): m_delim(delim) { } + cContainer(const cContainer&) = delete; + cContainer &operator= (const cContainer &) = delete; + bool find(cSv sv) { + if (!sv.empty() ) { + size_t f = m_buffer.find(sv); + if (f == std::string_view::npos || f== 0 || f + sv.length() == m_buffer.length() ) return false; + if (m_buffer[f-1] == m_delim && m_buffer[f+sv.length()] == m_delim) return true; + } + CONCATENATE(ns, "|", sv, "|"); + size_t f = m_buffer.find(ns); + return f != std::string_view::npos; + } + bool insert(cSv sv) { +// true, if already in buffer (will not insert again ...) +// else: false + if (m_buffer.empty() ) { + m_buffer.reserve(300); + m_buffer.append(1, m_delim); + } else if (find(sv)) return true; + m_buffer.append(sv); + m_buffer.append(1, m_delim); + return false; + } + bool insert(const char *s) { + if (!s) return true; + return insert(cSv(s)); + } + std::string moveBuffer() { return std::move(m_buffer); } + const std::string &getBufferRef() { return m_buffer; } + private: + char m_delim; + std::string m_buffer; +}; + +// ========================================================= +// Utility to measure times (performance) **************** +// ========================================================= +class cMeasureTime { + public: + void start() { begin = std::chrono::high_resolution_clock::now(); } + void stop() { + std::chrono::duration timeNeeded = std::chrono::high_resolution_clock::now() - begin; + maxT = std::max(maxT, timeNeeded); + sumT += timeNeeded; + ++numCalls; + } + void reset() { + sumT = std::chrono::duration(0); + maxT = std::chrono::duration(0); + numCalls = 0; + } + void add(const cMeasureTime &other) { + maxT = std::max(maxT, other.maxT); + sumT += other.sumT; + numCalls += other.numCalls; + } + void print(const char *context) const { + if (numCalls == 0) return; + if (!context) context = "cMeasureTime"; + esyslog("tvscraper: %s num = %5i, time = %9.5f, average %f, max = %f", context, numCalls, sumT.count(), sumT.count()/numCalls, maxT.count()); + } + + private: + int numCalls = 0; + std::chrono::duration sumT = std::chrono::duration(0.); + std::chrono::duration maxT = std::chrono::duration(0.); + std::chrono::time_point begin; +}; + +#endif // __STRINGHELPERS_H diff --git a/timers.cpp b/timers.cpp index f86e329c..bce23bec 100644 --- a/timers.cpp +++ b/timers.cpp @@ -18,12 +18,7 @@ namespace vdrlive { static char const* const TIMER_DELETE = "DELETE"; static char const* const TIMER_TOGGLE = "TOGGLE"; - SortedTimers::SortedTimers() -#if VDRVERSNUM < 20301 - : m_state( 0 ) -#endif - { - } + SortedTimers::SortedTimers() { } std::string SortedTimers::GetTimerId( cTimer const& timer ) { @@ -41,17 +36,13 @@ namespace vdrlive { return 0; } -#if VDRVERSNUM >= 20301 - #ifdef DEBUG_LOCK +#ifdef DEBUG_LOCK dsyslog("live: timers.cpp SortedTimers::GetByTimerId() LOCK_TIMERS_READ"); dsyslog("live: timers.cpp SortedTimers::GetByTimerId() LOCK_CHANNELS_READ"); - #endif +#endif LOCK_TIMERS_READ LOCK_CHANNELS_READ; cChannel* channel = (cChannel *)Channels->GetByChannelID( tChannelID::FromString( parts[0].c_str() ) ); -#else - cChannel* channel = Channels.GetByChannelID( tChannelID::FromString( parts[0].c_str() ) ); -#endif if ( channel == 0 ) { esyslog("live: GetByTimerId: no channel %s", parts[0].c_str() ); return 0; @@ -77,7 +68,6 @@ namespace vdrlive { return 0; } - std::string SortedTimers::EncodeDomId(std::string const& timerid) { std::string tId("timer_"); @@ -113,21 +103,19 @@ namespace vdrlive { if (timer.Aux()) { - std::string epgsearchinfo = GetXMLValue(timer.Aux(), "epgsearch"); + cSv epgsearchinfo = partInXmlTag(timer.Aux(), "epgsearch"); if (!epgsearchinfo.empty()) { - std::string searchtimer = GetXMLValue(epgsearchinfo, "searchtimer"); + cSv searchtimer = partInXmlTag(epgsearchinfo, "searchtimer"); if (!searchtimer.empty()) info << tr("Searchtimer") << ": " << searchtimer << std::endl; } } -#if VDRVERSNUM >= 20400 - if (timer.Local()) { - info << trVDR("Record on") << ": " << trVDR(" ") << std::endl; - } else { - info << trVDR("Record on") << ": " << timer.Remote() << std::endl; - } -#endif + if (timer.Local()) { + info << trVDR("Record on") << ": " << trVDR(" ") << std::endl; + } else { + info << trVDR("Record on") << ": " << timer.Remote() << std::endl; + } return info.str(); } @@ -140,38 +128,19 @@ namespace vdrlive { if (!getAutoTimerReason.createdByTvscraper) return ""; if (getAutoTimerReason.recording) { recID = "recording_" + MD5Hash(getAutoTimerReason.recording->FileName() ); - recName = getAutoTimerReason.recordingName; - return getAutoTimerReason.reason; + recName = std::move(getAutoTimerReason.recordingName); + return std::move(getAutoTimerReason.reason); } - return getAutoTimerReason.reason + " " + getAutoTimerReason.recordingName; + return concatenate(getAutoTimerReason.reason, " ", getAutoTimerReason.recordingName); } // fallback information, if this tvscraper method is not available - std::string tvScraperInfo = GetXMLValue(timer.Aux(), "tvscraper"); + cSv tvScraperInfo = partInXmlTag(timer.Aux(), "tvscraper"); if (tvScraperInfo.empty()) return ""; - std::string data = GetXMLValue(tvScraperInfo, "reason"); + cSv data = partInXmlTag(tvScraperInfo, "reason"); if (data.empty() ) return ""; - data.append(": "); - data.append(GetXMLValue(tvScraperInfo, "causedBy")); - return data; + return concatenate(data, " ", partInXmlTag(tvScraperInfo, "causedBy")); } - std::string SortedTimers::SearchTimerInfo(cTimer const& timer, std::string const& value) - { - std::stringstream info; - if (timer.Aux()) - { - std::string epgsearchinfo = GetXMLValue(timer.Aux(), "epgsearch"); - if (!epgsearchinfo.empty()) - { - std::string data = GetXMLValue(epgsearchinfo, value); - if (!data.empty()) - info << data; - } - } - return info.str(); - } - -#if VDRVERSNUM >= 20301 bool SortedTimers::Modified() { bool modified = false; @@ -184,7 +153,6 @@ namespace vdrlive { return modified; } -#endif TimerManager::TimerManager() : m_reloadTimers(true) diff --git a/timers.h b/timers.h index 3af0cd07..d388e958 100644 --- a/timers.h +++ b/timers.h @@ -6,10 +6,11 @@ #include #if TNTVERSION >= 30000 - #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) + #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif #include +#include "stringhelpers.h" namespace vdrlive { @@ -25,15 +26,14 @@ namespace vdrlive { static std::string EncodeDomId(std::string const& timerid); static std::string DecodeDomId(std::string const &timerDomId); -#if VDRVERSNUM >= 20301 bool Modified(); -#else - bool Modified() { return Timers.Modified(m_state); } -#endif static std::string GetTimerDays(cTimer const *timer); static std::string GetTimerInfo(cTimer const& timer); - static std::string SearchTimerInfo(cTimer const& timer, std::string const& value); +template + static cSv SearchTimerInfo(cTimer const& timer, const char (&value)[N] ) { + return partInXmlTag(partInXmlTag(timer.Aux(), "epgsearch"), value); + } static std::string TvScraperTimerInfo(cTimer const& timer, std::string &recID, std::string &recName); private: @@ -41,13 +41,7 @@ namespace vdrlive { SortedTimers( SortedTimers const& ); cMutex m_mutex; - -#if VDRVERSNUM >= 20301 cStateKey m_TimersStateKey; -#else - int m_state; -#endif - }; class TimerManager: public cMutex diff --git a/tools.cpp b/tools.cpp index 3c368cbd..65054fd0 100644 --- a/tools.cpp +++ b/tools.cpp @@ -109,84 +109,13 @@ template template void AppendHtmlEscapedAndCorrectNonUTF8(std::string &target, const char* s, const char *end, bool tooltip); template void AppendHtmlEscapedAndCorrectNonUTF8(cLargeString &target, const char* s, const char *end, bool tooltip); - void AppendCorrectNonUTF8(std::string &target, const char* s){ -// append c-string s to target -// replace invalid UTF8 characters with ? - if(!s) return; - int l = 0; // length of current utf8 codepoint - size_t i = 0; // number of not yet appended chars - const char* notAppended = s; // position of the first character which is not yet appended - for (const char* current = s; *current; current+=l) { - l = utf8CodepointIsValid(current); - if( l > 0) { i += l; continue; } -// invalid UTF8 - target.append(notAppended, i); - target.append("?"); - notAppended = notAppended + i + 1; - i = 0; - l = 1; - } - target.append(notAppended, i); - } - - wint_t getNextUtfCodepoint(const char *&p) { -// get next codepoint, and increment p -// 0 is returned at end of string, and p will point to the end of the string (0) - if(!p || !*p) return 0; - int l = utf8CodepointIsValid(p); - if( l == 0 ) { p++; return '?'; } - return Utf8ToUtf32(p, l); - } - - int utf8CodepointIsValid(const char *p){ -// In case of invalid UTF8, return 0 -// otherwise, return number of characters for this UTF codepoint - static const uint8_t LEN[] = {2,2,2,2,3,3,4,0}; - - int len = ((*p & 0xC0) == 0xC0) * LEN[(*p >> 3) & 7] + ((*p | 0x7F) == 0x7F); - for (int k=1; k < len; k++) if ((p[k] & 0xC0) != 0x80) len = 0; - return len; - } - - wint_t Utf8ToUtf32(const char *&p, int len) { - // assumes, that uft8 validity checks have already been done. len must be provided. call utf8CodepointIsValid first - // change p to position of next codepoint (p = p + len) - static const uint8_t FF_MSK[] = {0xFF >>0, 0xFF >>0, 0xFF >>3, 0xFF >>4, 0xFF >>5, 0xFF >>0, 0xFF >>0, 0xFF >>0}; - wint_t val = *p & FF_MSK[len]; - const char *q = p + len; - for (p++; p < q; p++) val = (val << 6) | (*p & 0x3F); - return val; - } - void AppendUtfCodepoint(std::string &target, wint_t codepoint){ - if (codepoint <= 0x7F){ - target.push_back( (char) (codepoint) ); - return; - } - if (codepoint <= 0x07FF) { - target.push_back( (char) (0xC0 | (codepoint >> 6 ) ) ); - target.push_back( (char) (0x80 | (codepoint & 0x3F)) ); - return; - } - if (codepoint <= 0xFFFF) { - target.push_back( (char) (0xE0 | ( codepoint >> 12)) ); - target.push_back( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); - target.push_back( (char) (0x80 | ( codepoint & 0x3F)) ); - return; - } - target.push_back( (char) (0xF0 | ((codepoint >> 18) & 0x07)) ); - target.push_back( (char) (0x80 | ((codepoint >> 12) & 0x3F)) ); - target.push_back( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); - target.push_back( (char) (0x80 | ( codepoint & 0x3F)) ); - return; - } - template void AppendDuration(T &target, char const* format, int duration) { int minutes = (duration + 30) / 60; int hours = minutes / 60; minutes %= 60; - AppendFormated(target, format, hours, minutes); + stringAppendFormated(target, format, hours, minutes); } template void AppendDuration(std::string &target, char const* format, int duration); template void AppendDuration(cLargeString &target, char const* format, int duration); @@ -234,9 +163,9 @@ template void AppendDuration(cLargeString &target, char const* for return result; } - std::string StringReplace( std::string const& text, std::string const& substring, std::string const& replacement ) + std::string StringReplace(cSv text, cSv substring, cSv replacement) { - std::string result = text; + std::string result(text); size_t pos = 0; while ( ( pos = result.find( substring, pos ) ) != std::string::npos ) { result.replace( pos, substring.length(), replacement ); @@ -245,29 +174,20 @@ template void AppendDuration(cLargeString &target, char const* for return result; } - std::vector StringSplit( std::string const& text, char delimiter ) + std::vector StringSplit(cSv text, char delimiter ) { std::vector result; size_t last = 0, pos; while ( ( pos = text.find( delimiter, last ) ) != std::string::npos ) { - result.push_back( text.substr( last, pos - last ) ); + result.emplace_back( text.substr( last, pos - last ) ); last = pos + 1; } if ( last < text.length() ) - result.push_back( text.substr( last ) ); + result.emplace_back( text.substr( last ) ); return result; } - int StringToInt( std::string const& string, int base ) - { - char* end; - int result = strtol( string.c_str(), &end, base ); - if ( *end == '\0' ) - return result; - return 0; - } - - std::string StringWordTruncate(const std::string& input, size_t maxLen, bool& truncated) + cSv StringWordTruncate(cSv input, size_t maxLen, bool& truncated) { if (input.length() <= maxLen) { @@ -275,42 +195,34 @@ template void AppendDuration(cLargeString &target, char const* for return input; } truncated = true; - std::string result = input.substr(0, maxLen); + cSv result = input.substr_csv(0, maxLen); size_t pos = result.find_last_of(" \t,;:.\n?!'\"/\\()[]{}*+-"); - return result.substr(0, pos); + return result.substr_csv(0, pos); } - std::string StringFormatBreak(std::string const& input) + std::string StringFormatBreak(cSv input) { - return StringReplace( input, "\n", "
" ); + return StringReplace(input, "\n", "
" ); } - std::string StringEscapeAndBreak(std::string const& input, const char* nl) + std::string StringEscapeAndBreak(cSv input, const char* nl) { std::stringstream plainBuilder; - HtmlEscOstream builder(plainBuilder); + HtmlEscOstream builder(plainBuilder); // see https://web.archive.org/web/20151208133551/http://www.tntnet.org/apidoc_master/html/classtnt_1_1HtmlEscOstream.html builder << input; return StringReplace(plainBuilder.str(), "\n", nl); } - std::string StringTrim(std::string const& str) + cSv StringTrim(cSv str) { - std::string res = str; - size_t pos = res.find_last_not_of(' '); - if(pos != std::string::npos) { - res.erase(pos + 1); - pos = res.find_first_not_of(' '); - if(pos != std::string::npos) res.erase(0, pos); - } - else res.erase(res.begin(), res.end()); - return res; - } - + size_t pos = str.find_last_not_of(' '); + if (pos == std::string::npos) return cSv(); + cSv trailBlankRemoved = str.substr_csv(0, pos); + pos = trailBlankRemoved.find_first_not_of(' '); + if (pos == std::string::npos) return cSv(); + return trailBlankRemoved.substr_csv(pos); + } -const char *getText(const char *shortText, const char *description) { - if (shortText && *shortText) return shortText; - return description; -} // Spielfilm Thailand / Deutschland / Großbritannien 2015 (Rak ti Khon Kaen) #define MAX_LEN_ST 70 template @@ -352,13 +264,6 @@ template template void AppendTextTruncateOnWord(std::string &target, const char *text, int max_len, bool tooltip); template void AppendTextTruncateOnWord(cLargeString &target, const char *text, int max_len, bool tooltip); - std::string ZeroPad(int number) - { - std::stringstream os; - os << std::setw(2) << std::setfill('0') << number; - return os.str(); - } - std::string MD5Hash(std::string const& str) { char* szInput = strdup(str.c_str()); @@ -370,10 +275,10 @@ template void AppendTextTruncateOnWord(cLargeString &target, const } - std::string xxHash32(std::string const& str) + std::string xxHash32(cSv str) { char res[9]; - uint32_t result = XXHash32::hash(str.c_str(), str.length(), 20); + uint32_t result = XXHash32::hash(str.data(), str.length(), 20); res[8] = 0; for (int i = 7; i >= 0; i--) { int dig = result % 16; @@ -453,15 +358,15 @@ template void AppendTextTruncateOnWord(cLargeString &target, const } }; - std::string StringUrlEncode( std::string const& input ) + std::string StringUrlEncode(cSv input) { std::stringstream ostr; - for_each( input.begin(), input.end(), urlencoder( ostr ) ); + std::for_each (input.begin(), input.end(), urlencoder( ostr ) ); return ostr.str(); } // return the time value as time_t from formatted with - time_t GetDateFromDatePicker(std::string const& datestring, std::string const& format) + time_t GetDateFromDatePicker(cSv datestring, cSv format) { if (datestring.empty()) return 0; @@ -478,10 +383,10 @@ template void AppendTextTruncateOnWord(cLargeString &target, const } // format is in datepicker format ('mm' for month, 'dd' for day, 'yyyy' for year) - std::string DatePickerToC(time_t date, std::string const& format) + std::string DatePickerToC(time_t date, cSv format) { if (date == 0) return ""; - std::string cformat = format; + std::string cformat(format); cformat = StringReplace(cformat, "mm", "%m"); cformat = StringReplace(cformat, "dd", "%d"); cformat = StringReplace(cformat, "yyyy", "%Y"); @@ -511,45 +416,46 @@ template void AppendTextTruncateOnWord(cLargeString &target, const intToTimeString(t, tm); return t; } - std::string charToString(const char *s) { - if (!s) return ""; - return s; - } - std::string EncodeDomId(std::string const & toEncode, char const * from, char const * to) + std::string EncodeDomId(cSv toEncode, char const * from, char const * to) { - std::string encoded = toEncode; + std::string encoded(toEncode); for (; *from && *to; from++, to++) { replace(encoded.begin(), encoded.end(), *from, *to); } return encoded; } - std::string DecodeDomId(std::string const & toDecode, char const * from, char const * to) + std::string DecodeDomId(cSv toDecode, char const * from, char const * to) { - std::string decoded = toDecode; + std::string decoded(toDecode); for (; *from && *to; from++, to++) { replace(decoded.begin(), decoded.end(), *from, *to); } return decoded; } - std::string FileSystemExchangeChars(std::string const & s, bool ToFileSystem) + std::string FileSystemExchangeChars(cSv s, bool ToFileSystem) { - char *str = strdup(s.c_str()); + if (s.empty()) return std::string(); + char *str = reinterpret_cast(std::malloc(s.length() + 1)); // vdr ExchangeChars needs a pointer to data allocated with malloc + if (!str) { + esyslog("live, ERROR: out of memory in FileSystemExchangeChars"); + return std::string(s); + } + std::memcpy(str, s.data(), s.length()); + str[s.length()] = 0; str = ExchangeChars(str, ToFileSystem); std::string data = str; - if (str) { - free(str); - } + std::free(str); return data; } - bool MoveDirectory(std::string const & sourceDir, std::string const & targetDir, bool copy) + bool MoveDirectory(cSv sourceDir, cSv targetDir, bool copy) { const char* delim = "/"; - std::string source = sourceDir; - std::string target = targetDir; + std::string source(sourceDir); + std::string target(targetDir); // add missing directory delimiters if (source.compare(source.size() - 1, 1, delim) != 0) { @@ -572,13 +478,9 @@ template void AppendTextTruncateOnWord(cLargeString &target, const struct stat st1, st2; stat(source.c_str(), &st1); - stat(target.c_str(),&st2); + stat(target.c_str(), &st2); if (!copy && (st1.st_dev == st2.st_dev)) { -#if APIVERSNUM > 20101 if (!cVideoDirectory::RenameVideoFile(source.c_str(), target.c_str())) { -#else - if (!RenameVideoFile(source.c_str(), target.c_str())) { -#endif esyslog("live: rename failed from %s to %s", source.c_str(), target.c_str()); return false; } @@ -674,11 +576,7 @@ template void AppendTextTruncateOnWord(cLargeString &target, const size_t found = source.find_last_of(delim); if (found != std::string::npos) { source = source.substr(0, found); -#if APIVERSNUM > 20101 while (source != cVideoDirectory::Name()) { -#else - while (source != VideoDirectory) { -#endif found = source.find_last_of(delim); if (found == std::string::npos) break; @@ -695,11 +593,7 @@ template void AppendTextTruncateOnWord(cLargeString &target, const size_t found = target.find_last_of(delim); if (found != std::string::npos) { target = target.substr(0, found); -#if APIVERSNUM > 20101 while (target != cVideoDirectory::Name()) { -#else - while (target != VideoDirectory) { -#endif found = target.find_last_of(delim); if (found == std::string::npos) break; @@ -716,40 +610,14 @@ template void AppendTextTruncateOnWord(cLargeString &target, const return true; } - template int toCharsU(char *buffer, T i0) { -// notes: -// i0 must be unsigned !!!! -// return number of characters written to buffer (don't count 0 terminator) -// if buffer==NULL: don't write anything, just return the number -// sizeof(buffer) must be >= this return value + 1 !! This is not checked .... - int numChars; - int i = i0; - if (i < 10) numChars = 1; - else for (numChars = 0; i; i /= 10) numChars++; - if (!buffer) return numChars; - char *bufferEnd = buffer + numChars; - i = i0; - *bufferEnd = 0; - if (i < 10) *(--bufferEnd) = '0' + i; - else for (; i; i /= 10) *(--bufferEnd) = '0' + (i%10); - return numChars; - } - template int toCharsU(char *buffer, unsigned int i0); - template int toCharsI(char *buffer, T i) { - if (i >= 0) return toCharsU(buffer, i); - if (buffer) *(buffer++) = '-'; - return toCharsU(buffer, -1*i) + 1; - } - template int toCharsI(char *buffer, int i); - - std::string ScraperImagePath2Live(const std::string &path){ + cSv ScraperImagePath2Live(cSv path) { int tvscraperImageDirLength = LiveSetup().GetTvscraperImageDir().length(); - if (tvscraperImageDirLength == 0) return ""; + if (tvscraperImageDirLength == 0) return cSv(); if (path.compare(0, tvscraperImageDirLength, LiveSetup().GetTvscraperImageDir()) != 0) { - esyslog("live: ERROR, image path %s does not start with %s", path.c_str(), LiveSetup().GetTvscraperImageDir().c_str()); - return ""; + esyslog("live: ERROR, image path %.*s does not start with %s", (int)path.length(), path.data(), LiveSetup().GetTvscraperImageDir().c_str()); + return cSv(); } - return path.substr(tvscraperImageDirLength); + return path.substr_csv(tvscraperImageDirLength); } bool ScraperCallService(const char *Id, void *Data) { @@ -757,18 +625,4 @@ template void AppendTextTruncateOnWord(cLargeString &target, const if (!pScraper) return false; return pScraper->Service(Id, Data); } -// XML tools ******************************** - -// returns the content of ... - std::string GetXMLValue( std::string const& xml, std::string const& element ) - { - std::string start = "<" + element + ">"; - std::string end = ""; - size_t startPos = xml.find(start); - if (startPos == std::string::npos) return ""; - size_t endPos = xml.find(end); - if (endPos == std::string::npos) return ""; - return xml.substr(startPos + start.size(), endPos - startPos - start.size()); - } - } // namespace vdrlive diff --git a/tools.h b/tools.h index 99c22df8..3c08aa9d 100644 --- a/tools.h +++ b/tools.h @@ -4,7 +4,6 @@ // uncomment to debug lock sequence // #define DEBUG_LOCK -#include "largeString.h" // STL headers need to be before VDR tools.h (included by ) #include #include @@ -12,8 +11,8 @@ #include #if TNTVERSION >= 30000 - #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) - #include "cxxtools/serializationinfo.h" + #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) + #include "cxxtools/serializationinfo.h" #endif #ifndef DISABLE_TEMPLATES_COLLIDING_WITH_STL @@ -21,10 +20,8 @@ #define DISABLE_TEMPLATES_COLLIDING_WITH_STL #endif #include - -#define CONVERT(result, from, fn) \ -char result[fn(NULL, from) + 1]; \ -fn(result, from); +#include "stringhelpers.h" +#include "largeString.h" std::istream& operator>>( std::istream& is, tChannelID& ret ); @@ -53,6 +50,10 @@ namespace cxxtools #endif +template void stringAppendFormated(cLargeString &target, const char *format, Args&&... args) { + target.appendFormated(format, std::forward(args)...); +} + namespace vdrlive { extern const std::locale g_locale; extern const std::collate& g_collate_char; @@ -61,32 +62,16 @@ namespace vdrlive { template void AppendHtmlEscapedAndCorrectNonUTF8(T &target, const char* s, const char *end = NULL, bool tooltip = false); template - void AppendTextTruncateOnWord(T &target, const char *text, int max_len, bool tooltip = false); - void AppendCorrectNonUTF8(std::string &target, const char* s); - - wint_t getNextUtfCodepoint(const char *&p); - int utf8CodepointIsValid(const char *p); // In case of invalid UTF8, return 0. Otherwise: Number of characters - wint_t Utf8ToUtf32(const char *&p, int len); // assumes, that uft8 validity checks have already been done. len must be provided. call utf8CodepointIsValid first - void AppendUtfCodepoint(std::string &target, wint_t codepoint); - - template void AppendFormated(std::string &target, char const* format, Args&&... args) { - char result[30]; - size_t numNeeded = snprintf(result, sizeof(result), format, std::forward(args)...); - - if (numNeeded < sizeof(result)) { - target.append(result); - } else { - char buf2[numNeeded+1]; - sprintf(buf2, format, std::forward(args)...); - target.append(buf2); - } + inline void AppendHtmlEscapedAndCorrectNonUTF8(T &target, cSv s, bool tooltip = false) { + AppendHtmlEscapedAndCorrectNonUTF8(target, s.data(), s.data() + s.length(), tooltip); } - template void AppendFormated(cLargeString &target, char const* format, Args&&... args) { - target.appendFormated(format, std::forward(args)...); - } - template std::string Format(char const* format, Args&&... args) { + +template + void AppendTextTruncateOnWord(T &target, const char *text, int max_len, bool tooltip = false); + + template std::string Format(const char *format, Args&&... args) { std::string result; - AppendFormated(result, format, std::forward(args)...); + stringAppendFormated(result, format, std::forward(args)...); return result; } @@ -97,56 +82,40 @@ template void AppendDateTime(std::string &target, char const* format, time_t time ); std::string FormatDateTime( char const* format, time_t time ); - std::string StringReplace( std::string const& text, std::string const& substring, std::string const& replacement ); - - std::vector StringSplit( std::string const& text, char delimiter ); + std::string StringReplace(cSv text, cSv substring, cSv replacement ); - int StringToInt( std::string const& string, int base = 10 ); + std::vector StringSplit(cSv text, char delimiter ); - std::string StringWordTruncate(const std::string& input, size_t maxLen, bool& truncated); - inline std::string StringWordTruncate(const std::string& input, size_t maxLen) { bool dummy; return StringWordTruncate(input, maxLen, dummy); } + cSv StringWordTruncate(cSv input, size_t maxLen, bool& truncated); + inline cSv StringWordTruncate(cSv input, size_t maxLen) { bool dummy; return StringWordTruncate(input, maxLen, dummy); } - std::string StringEscapeAndBreak(std::string const& input, const char* nl = "
"); + std::string StringEscapeAndBreak(cSv input, const char* nl = "
"); + std::string StringFormatBreak(cSv input); + cSv StringTrim(cSv str); - std::string StringFormatBreak(std::string const& input); - - std::string StringTrim(const std::string& str); - std::string ZeroPad(int number); - - const char *getText(const char *shortText, const char *description); template void AppendTextMaxLen(T &target, const char *text); std::string MD5Hash(std::string const& str); - std::string xxHash32(std::string const& str); + std::string xxHash32(cSv str); - time_t GetTimeT(std::string timestring); + time_t GetTimeT(std::string timestring); // timestring in HH:MM std::string ExpandTimeString(std::string timestring); - std::string StringUrlEncode( std::string const& input ); + std::string StringUrlEncode(cSv input); - std::string GetXMLValue( std::string const& xml, std::string const& element ); - - time_t GetDateFromDatePicker(std::string const& datestring, std::string const& format); - std::string DatePickerToC(time_t date, std::string const& format); + time_t GetDateFromDatePicker(cSv datestring, cSv format); + std::string DatePickerToC(time_t date, cSv format); std::string intToTimeString(int tm); int timeStringToInt(const char *t); int timeStringToInt(const std::string &t); - std::string charToString(const char *s); - - std::string EncodeDomId(std::string const & toEncode, char const * from = ".-:", char const * to = "pmc"); - std::string DecodeDomId(std::string const & toDecode, char const * from = "pmc", char const * to = ".-:"); - std::string FileSystemExchangeChars(std::string const & s, bool ToFileSystem); + std::string EncodeDomId(cSv toEncode, char const * from = ".-:", char const * to = "pmc"); + std::string DecodeDomId(cSv toDecode, char const * from = "pmc", char const * to = ".-:"); - bool MoveDirectory(std::string const & sourceDir, std::string const & targetDir, bool copy = false); + std::string FileSystemExchangeChars(cSv s, bool ToFileSystem); - template int toCharsU(char *buffer, T i); - template int toCharsI(char *buffer, T i); -// notes: -// return number of characters written to buffer (don't count 0 terminator) -// if buffer==NULL: don't write anything, just return the number -// sizeof(buffer) must be >= this return value + 1 !! This is not checked .... + bool MoveDirectory(cSv sourceDir, cSv targetDir, bool copy = false); struct bad_lexical_cast: std::runtime_error { @@ -213,13 +182,13 @@ template // tool for images returned by tvscraper or scraper2vdr: // convert path (valid in local file system) to path which can be used by live (in browser) to access the image // Note: final browser path is: "/tvscraper/" + ScraperImagePath2Live(...) - std::string ScraperImagePath2Live(const std::string &path); + cSv ScraperImagePath2Live(cSv path); // call the service Id // return false if there is no scraper plugin, or if the serrvice does not exist // otherwise, return true // can be called with Data == Null to check is the service exits - bool ScraperCallService(const char *Id, void *Data); + bool ScraperCallService(const char *Id, void *Data); } // namespace vdrlive #endif // VDR_LIVE_TOOLS_H