diff --git a/largeString.cpp b/largeString.cpp index 7997e008..198c1759 100644 --- a/largeString.cpp +++ b/largeString.cpp @@ -21,36 +21,6 @@ void cLargeString::init(size_t initialSize, size_t increaseSize, bool debugBuffe m_buffer_end = m_s + initialSize; m_string_end = m_s; } -void cLargeString::loadFile(const char *filename, bool *exists) { - if (exists) *exists = false; - if (!filename) return; - struct stat buffer; - if (stat(filename, &buffer) != 0) return; - -// file exists, length buffer.st_size - if (exists) *exists = true; - if (buffer.st_size == 0) return; // empty file - m_s = (char *) malloc((size_t)(buffer.st_size + 1) * sizeof(char)); // add one. So we can add the 0 string terminator - if (!m_s) { - esyslog("cLargeString::loadFile, ERROR out of memory, name = %.*s, filename = %s, requested size = %zu", nameLen(), nameData(), filename, (size_t)(buffer.st_size + 1)); - throw std::runtime_error("cLargeString::cLargeString, ERROR out of memory (file)"); - return; - } - m_buffer_end = m_s + buffer.st_size + 1; - m_string_end = m_s; - FILE *f = fopen(filename, "rb"); - if (!f) { - esyslog("cLargeString::loadFile, ERROR: stat OK, fopen fails, filename %s", filename); - return; - } - size_t num_read = fread (m_s, 1, (size_t)buffer.st_size, f); - fclose(f); - m_string_end = m_s + num_read; - if (num_read != (size_t)buffer.st_size) { - esyslog("cLargeString::loadFile, ERROR: num_read = %zu, buffer.st_size = %zu, ferror %i, name = %.*s, filename %s", num_read, (size_t)buffer.st_size, ferror(f), nameLen(), nameData(), filename); - m_string_end = m_s + std::min(num_read, (size_t)buffer.st_size); - } -} cLargeString::~cLargeString() { if (m_s == m_s_initial) return; @@ -152,3 +122,10 @@ void cLargeString::enlarge(size_t increaseSize) { m_buffer_end = m_s + newSize; m_string_end = m_s + stringLength; } + +std::string cLargeString::substr(size_t pos, size_t count) const { + if (pos >= length() ) return ""; + std::string result(m_s + pos, std::min(length() - pos, count) ); + for (char &si: result) if (si == 0) si = '%'; + return result; +} diff --git a/largeString.h b/largeString.h index e756069e..197f4f46 100644 --- a/largeString.h +++ b/largeString.h @@ -27,7 +27,6 @@ class cLargeString { } void setMaxSize() { m_maxSize = std::max(m_maxSize, (size_t)(m_string_end - m_s)); } void init(size_t initialSize, size_t increaseSize, bool debugBufferSize); - void loadFile(const char *filename, bool *exists); public: cLargeString(const cLargeString& o) = delete; cLargeString &operator= (const cLargeString &) = delete; @@ -39,12 +38,6 @@ class cLargeString { m_nameLen = static_cast(N) - 1; init(initialSize, increaseSize, debugBufferSize); } - template - cLargeString(const char (&name)[N], const char *filename, bool *exists = NULL) { - m_nameData = name; - m_nameLen = static_cast(N) - 1; - loadFile(filename, exists); - } ~cLargeString(); char *data() { *m_string_end = 0; return m_s; } const char *c_str() const { *m_string_end = 0; return m_s; } diff --git a/live.cpp b/live.cpp index cba2f124..3f14cf98 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 concatenate(LiveSetup().GetServerUrlImages(), (fullPath?ScraperImagePath2Live(imagePath):imagePath)); + return concat(LiveSetup().GetServerUrlImages(), (fullPath?ScraperImagePath2Live(imagePath):imagePath)); } virtual ~cLiveImageProviderImp() {} private: diff --git a/pages/ibox.ecpp b/pages/ibox.ecpp index 15a08c0a..d804995b 100644 --- a/pages/ibox.ecpp +++ b/pages/ibox.ecpp @@ -48,9 +48,10 @@ TimerConflictNotifier timerNotifier(); LOCK_RECORDINGS_READ; cRecording *recording = (cRecording *)Recordings->GetByName(NowReplaying); if (recording) { - epgEvent = EpgEvents::CreateEpgInfo(recManager->Md5Hash(recording), - recording, - tr("playing recording")); + epgEvent = EpgEvents::CreateEpgInfo( + concat("recording_", cToSvXxHash128(XXH3_128bits(NowReplaying, strlen(NowReplaying))) ), + recording, + tr("playing recording")); } } else { diff --git a/pages/recordings.ecpp b/pages/recordings.ecpp index 6da69fb7..f7a96e99 100644 --- a/pages/recordings.ecpp +++ b/pages/recordings.ecpp @@ -138,17 +138,6 @@ const recs = []; const vdr_start = <$ cSv(cToSvInt(LiveSetup().GetVdrStart() )) $>; const recordings_tree_creation = <$ cSv(cToSvInt(recordingsTree->getCreationTimestamp() )) $>; const param_name_recs = '&r<$ TNT_ARRAY $>='; -<%cpp> -/* -for (const RecordingsItemPtr &rPtr: *recordingsTree->allRecordings()) { - recoring_item.clear(); - rPtr->AppendAsJSArray(recoring_item, currentFlat == "true" || currentSort == "duplicates"); - - recs[<$cSv(cToSvInt(rPtr->IdI()))$>]=[<$$ recoring_item.c_str() $>] -<%cpp> -} -*/ - const rec_ids = Object.create(null) rec_ids["T"] = [1, 2] @@ -211,11 +200,11 @@ function RecordingsSt_int(s, level, displayFolder, data) { s.a += '" />' } s.a += '' // end of recording_imgs -<%cpp> if (!LiveSetup().GetTvscraperImageDir().empty() ) { +% if (!LiveSetup().GetTvscraperImageDir().empty() ) { s.a += '' addScraperImageTitle(s, obj[3], obj[4], obj[5], obj[6], obj[7], obj[8], '<$$lf$>'); s.a += '' -<%cpp> } +% } s.a += '' if (obj[1].length != 0) { @@ -446,7 +432,28 @@ if (currentFlat != "true") { } else { // here we prepare the items in case of a flat list, no folders if (currentSort != "duplicates") { - recItemsC = recordingsTree->allRecordings(sortOrder); + recItems.clear(); + size_t max_items = 100; + if (reverse) { + reverse = false; + std::vector::const_iterator recIterFirst = recordingsTree->allRecordings(sortOrder)->begin(); + std::vector::const_iterator recIterLast = recordingsTree->allRecordings(sortOrder)->end(); + for (; recIterFirst != recIterLast;) { + --recIterLast; + RecordingsItemPtr recItem = *recIterLast; + if (!recItem->matchesFilter(currentFilter)) continue; + recItems.push_back(recItem); + if (recItems.size() >= max_items) break; + } + } else { + for (const RecordingsItemPtr &rPtr: *recordingsTree->allRecordings(sortOrder) ) { + if (!rPtr->matchesFilter(currentFilter)) continue; + recItems.push_back(rPtr); + if (recItems.size() >= max_items) break; + } + } +// recItemsC = recordingsTree->allRecordings(sortOrder); + recItemsC = &recItems; } else { recItems.clear(); addDuplicateRecordingsSd(recItems, recordingsTree); @@ -658,7 +665,7 @@ if (currentSort.compare(0, current.length(), current) == 0) { class="acti
-
<$ Format(tr("Delete recording \"%s\"?"), recording->Name()?recording->Name():"No recording name available") $>
+
<$ cSv(cToSvFormated(tr("Delete recording \"%s\"?"), recording->Name()?recording->Name():"No recording name available")) $>
diff --git a/pages/stream.ecpp b/pages/stream.ecpp index de44b6f8..19aacfc2 100644 --- a/pages/stream.ecpp +++ b/pages/stream.ecpp @@ -159,7 +159,7 @@ if (Event) { timeSpan = headTime; startDate = headDate; } - std::string longDescription = concatenate( + std::string longDescription = concat( StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), LiveSetup().GetMaxTooltipChars())), "

", tr("Click to view details.")); diff --git a/pages/timerconflicts.ecpp b/pages/timerconflicts.ecpp index 70b78e36..b5a54f8a 100644 --- a/pages/timerconflicts.ecpp +++ b/pages/timerconflicts.ecpp @@ -109,7 +109,7 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); #endif if (timer->Event()) { epgEvent = EpgEvents::CreateEpgInfo(timer->Channel(), timer->Event()); - longDescription = concatenate( + longDescription = concat( StringEscapeAndBreak(SortedTimers::GetTimerInfo(*timer)), "
", StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), LiveSetup().GetMaxTooltipChars())), diff --git a/recman.cpp b/recman.cpp index c35fba4e..d1314f8e 100644 --- a/recman.cpp +++ b/recman.cpp @@ -63,25 +63,15 @@ namespace vdrlive { return RecordingsTreePtr(recMan, m_recTree); } - std::string RecordingsManager::Md5Hash(cRecording const * recording) const - { - m_timeMd5Hash.start(); - std::string result; - result.reserve(42); - result.append("recording_"); - stringAppend_xxHash128(result, recording->FileName()); - m_timeMd5Hash.stop(); - return result; - } - cRecording const * RecordingsManager::GetByMd5Hash(cSv hash) const { if (hash.length() != 42) return 0; if (hash.compare(0, 10, "recording_") != 0) return 0; - cSv xxh = hash.substr_csv(10); + XXH128_hash_t xxh = parse_hex_128(hash.substr_csv(10)); LOCK_RECORDINGS_READ; for (cRecording* rec = (cRecording *)Recordings->First(); rec; rec = (cRecording *)Recordings->Next(rec)) { - if (compare_xxHash128(xxh, rec->FileName())) return rec; + XXH128_hash_t xxh_rec = XXH3_128bits(rec->FileName(), strlen(rec->FileName())); + if (xxh_rec.high64 == xxh.high64 && xxh_rec.low64 == xxh.low64) return rec; } return 0; } @@ -103,7 +93,7 @@ namespace vdrlive { if (found == std::string::npos) return false; - std::string newname = concatenate(cVideoDirectory::Name(), "/", name, cSv(oldname).substr(found)); + std::string newname = concat(cVideoDirectory::Name(), "/", name, cSv(oldname).substr_csv(found)); if (!MoveDirectory(oldname, newname, copy)) { esyslog("live: renaming failed from '%.*s' to '%s'", (int)oldname.length(), oldname.data(), newname.c_str()); @@ -162,6 +152,7 @@ namespace vdrlive { // 1: on dvd // 2: on hdd // 0: "normal" VDR recording + if (!LiveSetup().GetUseArchive() ) return 0; if (!recording || !recording->FileName() ) return 0; size_t folder_length = strlen(recording->FileName()); char file[folder_length + 9]; // "/dvd.vdr" + 0 terminator -> 9 @@ -591,33 +582,46 @@ bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vectorstart(); + m_imageLevels = imageLevels; cGetScraperVideo getScraperVideo(NULL, recording); if (m_timeIdentify) m_timeIdentify->start(); bool scraper_available = getScraperVideo.call(LiveSetup().GetPluginScraper()); if (m_timeIdentify) m_timeIdentify->stop(); if (scraper_available) { - m_s_videoType = getScraperVideo.m_scraperVideo->getVideoType(); - m_s_dbid = getScraperVideo.m_scraperVideo->getDbId(); + m_scraperVideo.swap(getScraperVideo.m_scraperVideo); + m_s_videoType = m_scraperVideo->getVideoType(); // sort duplicates + m_s_dbid = m_scraperVideo->getDbId(); // sort duplicates if (m_s_videoType == tSeries || m_s_videoType == tMovie) { + m_s_episode_number = m_scraperVideo->getEpisodeNumber(); // sort duplicates + m_s_season_number = m_scraperVideo->getSeasonNumber(); // sort duplicates + m_language = m_scraperVideo->getLanguage(); // sort duplicates + m_duration_deviation = m_scraperVideo->getDurationDeviation(); // sort errors if (m_timeOverview) m_timeOverview->start(); - getScraperVideo.m_scraperVideo->getOverview(&m_s_title, &m_s_episode_name, &m_s_release_date, &m_s_runtime, &m_s_IMDB_ID, &m_s_collection_id, collectionName); + m_scraperVideo->getOverview(&m_s_title, &m_s_episode_name, &m_s_release_date, &m_s_runtime, &m_s_IMDB_ID, &m_s_collection_id, collectionName); // m_s_collection_id is required for sort if (m_timeOverview) m_timeOverview->stop(); - m_s_episode_number = getScraperVideo.m_scraperVideo->getEpisodeNumber(); - m_s_season_number = getScraperVideo.m_scraperVideo->getSeasonNumber(); - m_duration_deviation = getScraperVideo.m_scraperVideo->getDurationDeviation(); - m_language = getScraperVideo.m_scraperVideo->getLanguage(); - m_video_SD_HD = getScraperVideo.m_scraperVideo->getHD(); +// the following is not required for sort, but only for display: + m_video_SD_HD = m_scraperVideo->getHD(); } if (m_timeImage) m_timeImage->start(); - m_s_image = getScraperVideo.m_scraperVideo->getImage(imageLevels, cOrientations(eOrientation::landscape, eOrientation::portrait, eOrientation::banner), false); +// m_s_image = m_scraperVideo->getImage(imageLevels, cOrientations(eOrientation::landscape, eOrientation::portrait, eOrientation::banner), false); // not required for sort if (m_timeImage) m_timeImage->stop(); } else { // service "GetScraperVideo" is not available, just get the image m_s_videoType = tNone; - EpgEvents::PosterTvscraper(m_s_image, NULL, recording); + m_s_image_requested = true; + EpgEvents::PosterTvscraper(m_s_image, NULL, recording); // we need this here: image also for dirs, but m_recording is only available for recs. So, the call cannot be moved to scraperImage } if (m_timeDurationDeviation) m_timeDurationDeviation->stop(); } + const cTvMedia &RecordingsItem::scraperImage() const { + if (!m_s_image_requested) { + m_s_image_requested = true; + if (m_scraperVideo) + m_s_image = m_scraperVideo->getImage(m_imageLevels, cOrientations(eOrientation::landscape, eOrientation::portrait, eOrientation::banner), false); + } + return m_s_image; + } + int RecordingsItem::CompareTexts(const RecordingsItemPtr &second, int *numEqualChars) const // Compare NameForSearch + ShortText + Description @@ -741,15 +745,15 @@ bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vectorFileName(), strlen(recording->FileName()) )), m_isArchived(RecordingsManager::GetArchiveType(m_recording) ) { // dsyslog("live: REC: C: rec %s -> %s", name.c_str(), parent->Name().c_str()); timeItemRec->start(); - m_idI = idI; + m_idI = recording->Id(); m_timeIdentify = timeIdentify; m_timeOverview = timeOverview; m_timeImage = timeImage; @@ -758,9 +762,11 @@ bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vectorstart(); m_number_ts_files = GetNumberOfTsFiles(recording); timeNumTsFiles->stop(); +*/ timeItemRec->stop(); } @@ -775,7 +781,8 @@ template { if (! RecInfo() ) return; const char *text = ShortText(); - if (!text || Name() == text ) text = RecInfo()->Description(); + cSv name(Name() ); + if (!text || name == text || (name.substr(0, 1) == "%" && name.substr(1) == text)) text = RecInfo()->Description(); AppendTextMaxLen(target, text); } template void RecordingsItem::AppendShortTextOrDesc(std::string &target) const; @@ -891,13 +898,13 @@ void AppendScraperData(cLargeString &target, cSv s_IMDB_ID, const cTvMedia &s_im void RecordingsItemRec::AppendAsJSArray(cLargeString &target, bool displayFolder){ target.append("\""); // [0] : ID - target.appendS(Id().c_str() + 10); + target.append(cToSvXxHash128(IdHash())); target.append("\",\""); // [1] : ArchiveDescr() if (IsArchived()) AppendHtmlEscapedAndCorrectNonUTF8(target, ArchiveDescr().c_str() ); target.append("\","); // scraper data - AppendScraperData(target, m_s_IMDB_ID, m_s_image, m_s_videoType, m_s_title, m_s_season_number, m_s_episode_number, m_s_episode_name, m_s_runtime, m_s_release_date); + AppendScraperData(target, m_s_IMDB_ID, scraperImage(), m_s_videoType, m_s_title, m_s_season_number, m_s_episode_number, m_s_episode_name, m_s_runtime, m_s_release_date); // [9] : Day, time target.append(",\""); AppendDateTime(target, tr("%a,"), StartTime()); // day of week @@ -1017,15 +1024,7 @@ void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vectorm_timeMd5Hash.reset(); cMeasureTime timeRecs, timeIdentify, timeOverview, timeImage, timeDurationDeviation, timeNumTsFiles, timeItemRec; - timeRecs.reset(); - timeIdentify.reset(); - timeOverview.reset(); - timeImage.reset(); - timeDurationDeviation.reset(); - timeNumTsFiles.reset(); - timeItemRec.reset(); std::chrono::time_point begin = std::chrono::high_resolution_clock::now(); // check availability of scraper data @@ -1071,10 +1070,10 @@ void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vectorId(), recMan->Md5Hash(recording), recName, recording, &timeIdentify, &timeOverview, &timeImage, &timeDurationDeviation, &timeNumTsFiles, &timeItemRec)); + RecordingsItemPtr recPtr (new RecordingsItemRec(recName, recording, &timeIdentify, &timeOverview, &timeImage, &timeDurationDeviation, &timeNumTsFiles, &timeItemRec)); timeRecs.stop(); dir->m_entries.push_back(recPtr); -m_allRecordings.push_back(recPtr); + m_allRecordings.push_back(recPtr); // esyslog("live: DH: added rec: '%.*s'", (int)recName.length(), recName.data()); if (scraperDataAvailable) { switch (recPtr->scraperVideoType() ) { @@ -1111,8 +1110,6 @@ m_allRecordings.push_back(recPtr); m_root->finishRecordingsTree(); std::chrono::duration timeNeeded = std::chrono::high_resolution_clock::now() - begin; dsyslog("live: DH: ------ RecordingsTree::RecordingsTree() --------, required time: %9.5f", timeNeeded.count() ); -/* - recMan->m_timeMd5Hash.print("live: time Hash "); timeRecs.print("live: timeRecs "); timeItemRec.print("live: ItemRec "); timeIdentify.print("live: Identify "); @@ -1120,7 +1117,6 @@ m_allRecordings.push_back(recPtr); timeImage.print("live: Image "); timeDurationDeviation.print("live: Scraper "); timeNumTsFiles.print("live: NumTsFiles"); -*/ } RecordingsTree::~RecordingsTree() diff --git a/recman.h b/recman.h index 2bcb4adf..73f324f5 100644 --- a/recman.h +++ b/recman.h @@ -68,13 +68,6 @@ namespace vdrlive { */ RecordingsTreePtr GetRecordingsTree() const; - /** - * generates a Md5 hash from a cRecording entry. It can be used - * to reidentify a recording. - */ - std::string Md5Hash(cRecording const * recording) const; - mutable cMeasureTime m_timeMd5Hash; - /** * fetches a cRecording from VDR's Recordings collection. Returns * NULL if recording was not found @@ -194,7 +187,7 @@ namespace vdrlive { virtual const std::string& NameForSearch() const { return m_name_for_search; } virtual const char * ShortText() const { return RecInfo()? RecInfo()->ShortText():0; } virtual const char * Description() const { return RecInfo()? RecInfo()->Description():0; } - virtual const std::string Id() const = 0; +// virtual const std::string Id() const = 0; int IdI() const { return m_idI;} template void AppendShortTextOrDesc(T &target) const; @@ -228,7 +221,7 @@ template int scraperSeasonNumber() const { return m_s_season_number; } const std::string &scraperName() const { return m_s_title; } const std::string &scraperReleaseDate() const { return m_s_release_date; } - const cTvMedia &scraperImage() const { return m_s_image; } + const cTvMedia &scraperImage() const; int language() const { return m_language; } int CompareTexts(const RecordingsItemPtr &second, int *numEqualChars=NULL) const; int CompareStD(const RecordingsItemPtr &second, int *numEqualChars=NULL) const; @@ -263,13 +256,16 @@ template bool (*m_cmp_dir)(const RecordingsItemPtr &itemPtr1, const RecordingsItemPtr &itemPtr2) = NULL; bool (*m_cmp_rec)(const RecordingsItemPtr &itemPtr1, const RecordingsItemPtr &itemPtr2) = NULL; // scraper data + std::unique_ptr m_scraperVideo; tvType m_s_videoType = tNone; int m_s_dbid = 0; std::string m_s_title = ""; std::string m_s_episode_name = ""; std::string m_s_IMDB_ID = ""; std::string m_s_release_date = ""; - cTvMedia m_s_image; + mutable cTvMedia m_s_image; + mutable bool m_s_image_requested = false; + cImageLevels m_imageLevels; int m_s_runtime = 0; int m_s_collection_id = 0; int m_s_episode_number = 0; @@ -295,7 +291,7 @@ template virtual time_t StartTime() const { return 0; } virtual int Duration() const { return -1; } virtual bool IsDir() const { return true; } - virtual std::string const Id() const { return ""; } +// virtual std::string const Id() const { return ""; } virtual int Level() { return m_level; } private: @@ -339,7 +335,7 @@ template class RecordingsItemRec : public RecordingsItem { public: - RecordingsItemRec(int idI, cSv id, cSv name, const cRecording* recording, cMeasureTime *timeIdentify, cMeasureTime *timeOverview, cMeasureTime *timeImage, cMeasureTime *timeDurationDeviation, cMeasureTime *timeNumTsFiles, cMeasureTime *timeItemRec); + RecordingsItemRec(cSv name, const cRecording* recording, cMeasureTime *timeIdentify, cMeasureTime *timeOverview, cMeasureTime *timeImage, cMeasureTime *timeDurationDeviation, cMeasureTime *timeNumTsFiles, cMeasureTime *timeItemRec); virtual ~RecordingsItemRec(); @@ -348,7 +344,7 @@ template virtual int DurationDeviation() const { return m_duration_deviation; } // duration deviation in seconds virtual int FileSizeMB() const { return m_recording->FileName() ? m_recording->FileSizeMB() : -1; } // file size in MB virtual bool IsDir() const { return false; } - virtual const std::string Id() const { return m_id; } + virtual const XXH128_hash_t IdHash() const { return m_hash; } virtual const cRecording* Recording() const { return m_recording; } virtual const cRecordingInfo* RecInfo() const { return m_recording->Info(); } @@ -362,7 +358,10 @@ template #else virtual const int RecordingErrors() const { return -1; } #endif - int NumberTsFiles() const { return m_number_ts_files; } + int NumberTsFiles() const { + if (m_number_ts_files == -2) m_number_ts_files = GetNumberOfTsFiles(m_recording); + return m_number_ts_files; + } void AppendRecordingErrorsStr(std::string &target) const; virtual int SD_HD(); @@ -371,9 +370,9 @@ template private: const cRecording *m_recording; - const std::string m_id; + const XXH128_hash_t m_hash; const int m_isArchived; - int m_number_ts_files; + mutable int m_number_ts_files = -2; }; /** @@ -390,7 +389,7 @@ template virtual time_t StartTime() const { return m_event->StartTime(); } virtual int Duration() const { return m_event->Duration() / 60; } // duration in minutes virtual bool IsDir() const { return false; } - virtual std::string const Id() const { return ""; } +// virtual std::string const Id() const { return ""; } private: const cEvent *m_event; }; diff --git a/setup.h b/setup.h index ebfd69ef..11ecabe5 100644 --- a/setup.h +++ b/setup.h @@ -61,6 +61,7 @@ class Setup std::string const GetTntnetLogLevel() const { return m_tntnetloglevel; } bool GetShowLogo() const { return m_showLogo != 0; } bool GetUseAjax() const { return true; } + bool GetUseArchive() const { return false; } bool GetShowInfoBox() const { return m_showInfoBox != 0; } bool GetUseStreamdev() const { return m_useStreamdev != 0; } int GetStreamdevPort() const { return m_streamdevPort; } diff --git a/stringhelpers.h b/stringhelpers.h index 4ecdcfbe..d347ae77 100644 --- a/stringhelpers.h +++ b/stringhelpers.h @@ -146,10 +146,14 @@ class cSv: public std::string_view { cSv(const char *s): std::string_view(charPointerToStringView(s)) {} cSv(const unsigned char *s): std::string_view(charPointerToStringView(reinterpret_cast(s))) {} cSv(const char *s, size_t l): std::string_view(s, l) {} + cSv(const unsigned char *s, size_t l): std::string_view(reinterpret_cast(s), l) {} cSv(std::string_view sv): std::string_view(sv) {} + cSv(const cSv &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(); } + cSv substr(size_t pos = 0) const { return (length() > pos)?cSv(data() + pos, length() - pos):cSv(); } + cSv substr_csv(size_t pos = 0) const { return (length() > pos)?cSv(data() + pos, length() - pos):cSv(); } + cSv substr(size_t pos, size_t count) const { return (length() > pos)?cSv(data() + pos, std::min(length() - pos, count) ):cSv(); } + cSv substr_csv(size_t pos, size_t count) const { return (length() > pos)?cSv(data() + pos, std::min(length() - pos, count) ):cSv(); } private: static std::string_view charPointerToStringView(const char *s) { return s?std::string_view(s, strlen(s)):std::string_view(); @@ -344,6 +348,36 @@ template inline T parse_unsigned(cSv sv) { return parse_unsigned_internal(sv); } +template inline T parse_hex(cSv sv, size_t *num_digits = 0) { + static const signed char hex_values[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + T value = 0; + const unsigned char *data = reinterpret_cast(sv.data()); + const unsigned char *data_e = data + sv.length(); + for (; data < data_e; ++data) { + signed char val = hex_values[*data]; + if (val == -1) break; + value = value*16 + val; + } + if (num_digits) *num_digits = data - reinterpret_cast(sv.data()); + return value; +} // ========================================================= // parse string_view for xml // ========================================================= @@ -494,16 +528,15 @@ inline void stringAppendRemoveControlCharactersKeepNl(std::string &target, const // =========== concatenate ================================= // ========================================================= -inline std::string concatenate(cSv s1, cSv s2) { +inline std::string concat(cSv s1, cSv s2) { std::string result; result.reserve(s1.length() + s2.length()); result.append(s1); result.append(s2); -// dsyslog("\nconcatenate 2 result: %s\n", result.c_str() ); return result; } -inline std::string concatenate(cSv s1, cSv s2, cSv s3) { +inline std::string concat(cSv s1, cSv s2, cSv s3) { std::string result; result.reserve(s1.length() + s2.length() + s3.length() ); result.append(s1); @@ -512,7 +545,7 @@ inline std::string concatenate(cSv s1, cSv s2, cSv s3) { return result; } -inline std::string concatenate(cSv s1, cSv s2, cSv s3, cSv s4) { +inline std::string concat(cSv s1, cSv s2, cSv s3, cSv s4) { std::string result; result.reserve(s1.length() + s2.length() + s3.length() + s4.length() ); result.append(s1); @@ -522,6 +555,29 @@ inline std::string concatenate(cSv s1, cSv s2, cSv s3, cSv s4) { return result; } +inline std::string concat(cSv s1, cSv s2, cSv s3, cSv s4, cSv s5) { + std::string result; + result.reserve(s1.length() + s2.length() + s3.length() + s4.length() + s5.length() ); + result.append(s1); + result.append(s2); + result.append(s3); + result.append(s4); + result.append(s5); + return result; +} + +inline std::string concat(cSv s1, cSv s2, cSv s3, cSv s4, cSv s5, cSv s6) { + std::string result; + result.reserve(s1.length() + s2.length() + s3.length() + s4.length() + s5.length() + s6.length() ); + result.append(s1); + result.append(s2); + result.append(s3); + result.append(s4); + result.append(s5); + result.append(s6); + return result; +} + namespace ns_concat { template inline int numCharsUg0(T i) { // note: i must be > 0!!!! @@ -582,26 +638,27 @@ namespace ns_concat { 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) { if(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); + template inline T addCharsHex(char *buffer, size_t num_chars, T value) { +// sizeof(buffer) must be >= num_chars. This is not checked !!! +// value must be >= 0. This is not checked !!! +// value is written with num_chars chars +// if value is too small -> left values filled with 0 +// if value is too high -> the highest numbers are not written. This is not checked! +// but, you can check: if the returnde value is != 0, some chars are not written + const char *hex_chars = "0123456789ABCDEF"; + for (char *be = buffer + num_chars -1; be >= buffer; --be, value /= 16) *be = hex_chars[value%16]; + return value; } } - +inline unsigned addCharsHex(char *buffer, size_t num_chars, unsigned value) { + return ns_concat::addCharsHex(buffer, num_chars, value); +} +inline unsigned long addCharsHex(char *buffer, size_t num_chars, unsigned long value) { + return ns_concat::addCharsHex(buffer, num_chars, value); +} +inline unsigned long long addCharsHex(char *buffer, size_t num_chars, unsigned long long value) { + return ns_concat::addCharsHex(buffer, num_chars, value); +} /* class cToSvInt_sik: public cSv { // decided against this implementation. @@ -628,6 +685,7 @@ class cToSv { cToSv() {} cToSv(const cToSv&) = delete; cToSv &operator= (const cToSv &) = delete; + virtual ~cToSv() {} virtual operator cSv() const = 0; }; class cToSvInt: public cToSv { @@ -639,21 +697,44 @@ class cToSvInt: public cToSv { cToSvInt &operator= (const cToSvInt &) = delete; operator cSv() const { return cSv(m_result, m_buffer + 20 - m_result); } private: - char m_buffer[20]; + char m_buffer[20]; // unsigned int 64: max. 20. (18446744073709551615) signed int64: max. 19 (+ sign) char *m_result; }; +template +class cToSvHex: public cToSv { + public: +// T must be an unsigned type, like long long unsigned, ... + template cToSvHex(T value) { + addCharsHex(m_buffer, N, value); + } + cToSvHex(const cToSvHex&) = delete; + cToSvHex &operator= (const cToSvHex &) = delete; + operator cSv() const { return cSv(m_buffer, N); } + protected: + cToSvHex() { } + char m_buffer[N]; +}; -template inline std::string toStringI(T i) { -// for integer types i - return std::string(cToSvInt(i)); +namespace ns_concat { +// 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); + } } + inline void stringAppend(std::string &str, unsigned int i) { ns_concat::stringAppendU(str, i); } inline void stringAppend(std::string &str, unsigned long int i) { ns_concat::stringAppendU(str, i); } inline void stringAppend(std::string &str, unsigned long long int i) { ns_concat::stringAppendU(str, i); } -inline void stringAppend(std::string &str, int i) { ns_concat::stringAppendI(str, i); } -inline void stringAppend(std::string &str, long int i) { ns_concat::stringAppendI(str, i); } -inline void stringAppend(std::string &str, long long int i) { ns_concat::stringAppendI(str, i); } +inline void stringAppend(std::string &str, int i) { str.append(cToSvInt(i)); } +inline void stringAppend(std::string &str, long int i) { str.append(cToSvInt(i)); } +inline void stringAppend(std::string &str, long long int i) { str.append(cToSvInt(i)); } // strings inline void stringAppend(std::string &str, const char *s) { if(s) str.append(s); } @@ -683,8 +764,8 @@ inline std::string concatenate(const Args&... args) { class cToSvFile: public cToSv { public: - cToSvFile(const char *filename) { load(filename); } - cToSvFile(const std::string &filename) { load(filename.c_str() ); } + cToSvFile(const char *filename, size_t max_length = 0) { load(filename, max_length); } + cToSvFile(const std::string &filename, size_t max_length = 0) { load(filename.c_str(), max_length ); } cToSvFile(const cToSvFile&) = delete; cToSvFile &operator= (const cToSvFile &) = delete; operator cSv() const { return m_result; } @@ -692,7 +773,7 @@ class cToSvFile: public cToSv { bool exists() const { return m_exists; } ~cToSvFile() { std::free(m_s); } private: - void load(const char *filename) { + void load(const char *filename, size_t max_length) { if (!filename) return; int fd = open(filename, O_RDONLY); if (fd == -1) { @@ -710,16 +791,18 @@ class cToSvFile: public cToSv { // file exists, length buffer.st_size m_exists = true; if (buffer.st_size == 0) { close(fd); return; } // empty file - m_s = (char *) malloc((size_t)(buffer.st_size + 1) * sizeof(char)); // add one. So we can add the 0 string terminator + size_t length = buffer.st_size; + if (max_length != 0 && length > max_length) length = max_length; + m_s = (char *) malloc((length + 1) * sizeof(char)); // add one. So we can add the 0 string terminator if (!m_s) { - esyslog("cToSvFile::load, ERROR out of memory, filename = %s, requested size = %zu\n", filename, (size_t)(buffer.st_size + 1)); + esyslog("cToSvFile::load, ERROR out of memory, filename = %s, requested size = %zu\n", filename, length + 1); close(fd); return; } size_t num_read = 0; ssize_t num_read1 = 1; - for (; num_read1 > 0 && num_read < (size_t)buffer.st_size; num_read += num_read1) { - num_read1 = read(fd, m_s + num_read, (size_t)buffer.st_size - num_read); + for (; num_read1 > 0 && num_read < length; num_read += num_read1) { + num_read1 = read(fd, m_s + num_read, length - num_read); if (num_read1 == -1) { esyslog("cToSvFile::load, ERROR: read fails, errno %d, filename %s\n", errno, filename); close(fd); @@ -730,14 +813,15 @@ class cToSvFile: public cToSv { close(fd); m_result = cSv(m_s, num_read); m_s[num_read] = 0; // so data returns a 0 terminated string - if (num_read != (size_t)buffer.st_size) { - esyslog("cToSvFile::load, ERROR: num_read = %zu, buffer.st_size = %zu, filename %s\n", num_read, (size_t)buffer.st_size, filename); + if (num_read != length) { + esyslog("cToSvFile::load, ERROR: num_read = %zu, length = %zu, filename %s\n", num_read, length, filename); } } bool m_exists = false; char *m_s = nullptr; cSv m_result; }; + class cToSvFormated: public cToSv { public: // __attribute__ ((format (printf, 2, 3))) can not be used, but should work starting with gcc 13.1 @@ -796,6 +880,15 @@ void stringAppendFormated(std::string &str, const char *fmt, Args&&... args) { str.append(buf2); } } + +inline std::string zeroToPercent(cSv sv) { +// rapidjson, inplace -> there are 0 in data +// to print such data, we replace 0 with % + std::string result(sv); + for (char &si: result) if (si == 0) si = '%'; + return result; +} + // ========================================================= // ========================================================= // Chapter 3: containers diff --git a/timers.cpp b/timers.cpp index 1d80314a..55500089 100644 --- a/timers.cpp +++ b/timers.cpp @@ -131,14 +131,14 @@ namespace vdrlive { recName = std::move(getAutoTimerReason.recordingName); return std::move(getAutoTimerReason.reason); } - return concatenate(getAutoTimerReason.reason, " ", getAutoTimerReason.recordingName); + return concat(getAutoTimerReason.reason, " ", getAutoTimerReason.recordingName); } // fallback information, if this tvscraper method is not available cSv tvScraperInfo = partInXmlTag(timer.Aux(), "tvscraper"); if (tvScraperInfo.empty()) return ""; cSv data = partInXmlTag(tvScraperInfo, "reason"); if (data.empty() ) return ""; - return concatenate(data, " ", partInXmlTag(tvScraperInfo, "causedBy")); + return concat(data, " ", partInXmlTag(tvScraperInfo, "causedBy")); } bool SortedTimers::Modified() diff --git a/tools.cpp b/tools.cpp index a9ce6b4b..c30be76d 100644 --- a/tools.cpp +++ b/tools.cpp @@ -300,9 +300,47 @@ template void toHex(char *buf, int chars, T value) { inline void xxHash128_buf(char *buf, cSv str) { // sizeof buf must be >= 32. This is not checked! XXH128_hash_t result = XXH3_128bits(str.data(), str.length()); - toHex(buf+16, 16, result.low64); toHex(buf , 16, result.high64); + toHex(buf+16, 16, result.low64); } + XXH128_hash_t parse_hex_128(cSv str) { + XXH128_hash_t result; + if (str.length() != 32) { + esyslog("live: ERROR in parse_hex_128, hex = \"%.*s\" is not 32 chars long", (int)str.length(), str.data()); + result.low64 = 0; + result.high64 = 0; + return result; + } + size_t parsed_chars_h, parsed_chars_l; + result.high64 = parse_hex(str.substr(0, 16), &parsed_chars_h); + result.low64 = parse_hex(str.substr(16), &parsed_chars_l); + if (parsed_chars_l == 16 && parsed_chars_h == 16) return result; + esyslog("live: ERROR in parse_hex_128, hex = \"%.*s\" contains invalid characters", (int)str.length(), str.data()); + result.low64 = 0; + result.high64 = 0; + return result; + } +/* + cToSvXxHash128::cToSvXxHash128(cSv str, bool alreadyHashed) { + if (alreadyHashed) { + m_hash = parse_hex_128(str); + if (str != cSv(*this)) + esyslog("live: ERROR in cToSvXxHash128, str = \"%.*s\" != buffer = \"%.*s\"", (int)str.length(), str.data(), 32, m_buffer); + } else { + m_hash = XXH3_128bits(str.data(), str.length()); + } + } + + cToSvXxHash128::operator cSv() const { + if (!m_buffer_filled) { + toHex(m_buffer+16, 16, m_hash.low64); + toHex(m_buffer , 16, m_hash.high64); + m_buffer_filled = true; + } + return cSv(m_buffer, 32); + } +*/ + void stringAppend_xxHash128(std::string &target, cSv str) { char buf[32]; xxHash128_buf(buf, str); diff --git a/tools.h b/tools.h index a6f1d3dd..301b0ef0 100644 --- a/tools.h +++ b/tools.h @@ -19,6 +19,7 @@ // To get rid of the swap definition in vdr/tools.h #define DISABLE_TEMPLATES_COLLIDING_WITH_STL #endif +#include "xxhash.h" #include #include "stringhelpers.h" #include "largeString.h" @@ -69,12 +70,6 @@ template 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; - stringAppendFormated(result, format, std::forward(args)...); - return result; - } - template void AppendDuration(T &target, char const* format, int duration); std::string FormatDuration( char const* format, int duration ); @@ -101,9 +96,38 @@ template void toHex(char *buf, int chars, T value); std::string xxHash32(cSv str); std::string xxHash64(cSv str); std::string xxHash128(cSv str); + XXH128_hash_t parse_hex_128(cSv str); void stringAppend_xxHash128(std::string &target, cSv str); bool compare_xxHash128(cSv str1, cSv str2); // return str1 == xxHash128(str2) + class cToSvXxHash128: public cToSvHex<32> { + public: + cToSvXxHash128(XXH128_hash_t value): cToSvHex<32>::cToSvHex() { + addCharsHex(m_buffer, 16, value.high64); + addCharsHex(m_buffer+16, 16, value.low64); + } + cToSvXxHash128(const cToSvXxHash128&) = delete; + cToSvXxHash128 &operator= (const cToSvXxHash128 &) = delete; + }; + +/* + class cToSvXxHash128: public cToSv { + friend bool operator==(const cToSvXxHash128 &h1, const cToSvXxHash128 &h2); + public: + cToSvXxHash128(cSv str, bool alreadyHashed = false); + cToSvXxHash128(const cToSvXxHash128&) = delete; + cToSvXxHash128 &operator= (const cToSvXxHash128 &) = delete; + operator cSv() const; + private: + XXH128_hash_t m_hash; + mutable char m_buffer[32]; + mutable bool m_buffer_filled = false; + }; + inline bool operator==(const cToSvXxHash128 &h1, const cToSvXxHash128 &h2) { + return h1.m_hash.low64 == h2.m_hash.low64 && h1.m_hash.high64 == h2.m_hash.high64; + } +*/ + time_t GetTimeT(std::string timestring); // timestring in HH:MM std::string ExpandTimeString(std::string timestring);