diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | recdone_thread.c | 174 | ||||
-rw-r--r-- | recdone_thread.h | 40 | ||||
-rw-r--r-- | recstatus.c | 138 | ||||
-rw-r--r-- | recstatus.h | 14 |
6 files changed, 236 insertions, 134 deletions
@@ -1,5 +1,7 @@ /createcats /.dependencies +/.dependencies_doc +/.doc_stmp /MANUAL /README.DE /doc/ @@ -132,7 +132,7 @@ ifeq ($(WITHOUT_QUICKSEARCH), 0) ALL += libvdr-$(PLUGIN4).so endif -OBJS = afuzzy.o blacklist.o changrp.o confdloader.o conflictcheck.o conflictcheck_thread.o distance.o $(PLUGIN).o epgsearchcats.o epgsearchcfg.o epgsearchext.o epgsearchsetup.o epgsearchsvdrp.o epgsearchtools.o mail.o md5.o menu_announcelist.o menu_blacklistedit.o menu_blacklists.o menu_commands.o menu_conflictcheck.o menu_deftimercheckmethod.o menu_dirselect.o menu_event.o menu_favorites.o menu_main.o menu_myedittimer.o menu_quicksearch.o menu_recsdone.o menu_search.o menu_searchactions.o menu_searchedit.o menu_searchresults.o menu_searchtemplate.o menu_switchtimers.o menu_templateedit.o menu_timersdone.o menu_whatson.o noannounce.o pending_notifications.o rcfile.o recdone.o recstatus.o searchtimer_thread.o services.o switchtimer.o switchtimer_thread.o templatefile.o timer_thread.o timerdone.o timerstatus.o uservars.o varparser.o +OBJS = afuzzy.o blacklist.o changrp.o confdloader.o conflictcheck.o conflictcheck_thread.o distance.o $(PLUGIN).o epgsearchcats.o epgsearchcfg.o epgsearchext.o epgsearchsetup.o epgsearchsvdrp.o epgsearchtools.o mail.o md5.o menu_announcelist.o menu_blacklistedit.o menu_blacklists.o menu_commands.o menu_conflictcheck.o menu_deftimercheckmethod.o menu_dirselect.o menu_event.o menu_favorites.o menu_main.o menu_myedittimer.o menu_quicksearch.o menu_recsdone.o menu_search.o menu_searchactions.o menu_searchedit.o menu_searchresults.o menu_searchtemplate.o menu_switchtimers.o menu_templateedit.o menu_timersdone.o menu_whatson.o noannounce.o pending_notifications.o rcfile.o recdone.o recdone_thread.o recstatus.o searchtimer_thread.o services.o switchtimer.o switchtimer_thread.o templatefile.o timer_thread.o timerdone.o timerstatus.o uservars.o varparser.o ifeq ($(REGEXLIB), pcre) LIBS += $(shell pcre-config --libs-posix) diff --git a/recdone_thread.c b/recdone_thread.c new file mode 100644 index 0000000..0f77ab2 --- /dev/null +++ b/recdone_thread.c @@ -0,0 +1,174 @@ +/* -*- c++ -*- +Copyright (C) 2004-2013 Christian Wieninger 2017 Johann Friedrichs + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +*/ + +#include <string> +#include <list> +#ifdef __FreeBSD__ +#include <netinet/in.h> +#endif +#include "recdone_thread.h" +#include "recdone.h" + +#include <vdr/tools.h> +#include <vdr/plugin.h> +#define ALLOWED_BREAK_INSECS 2 + +extern int updateForced; + +cRecdoneThread::cRecdoneThread() +: cThread("EPGSearch: recdone") +{ +} + +cRecdoneThread::~cRecdoneThread() { +} + +bool cRecdoneThread::IsPesRecording(const cRecording *pRecording) +{ + return pRecording && pRecording->IsPesRecording(); +} + +#define LOC_INDEXFILESUFFIX "/index" + +struct tIndexTs { + uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!) + int reserved:7; // reserved for future use + int independent:1; // marks frames that can be displayed by themselves (for trick modes) + uint16_t number:16; // up to 64K files per recording + tIndexTs(off_t Offset, bool Independent, uint16_t Number) + { + offset = Offset; + reserved = 0; + independent = Independent; + number = Number; + } +}; + +int cRecdoneThread::RecLengthInSecs(const cRecording *pRecording) +{ + struct stat buf; + cString fullname = cString::sprintf("%s%s", pRecording->FileName(), IsPesRecording(pRecording) ? LOC_INDEXFILESUFFIX ".vdr" : LOC_INDEXFILESUFFIX); + if (pRecording->FileName() && *fullname && access(fullname, R_OK) == 0 && stat(fullname, &buf) == 0) + { + double frames = buf.st_size ? (buf.st_size - 1) / sizeof(tIndexTs) + 1 : 0; + double Seconds = 0; + modf((frames + 0.5) / pRecording->FramesPerSecond(), &Seconds); + return Seconds; + } + return -1; +} + +void cRecdoneThread::Action(void) +{ + LogFile.Log(1,"started recdone_thread with %s",m_filename); + cMutexLock RecsDoneLock(&RecsDone); + time_t now = time(NULL); + // remove timers that finished recording from TimersRecording + // incomplete recordings are kept for a while, perhaps they will be resumed + cMutexLock TimersRecordingLock(&TimersRecording); + cRecDoneTimerObj *tiR = TimersRecording.First(); + while(tiR) + { + // check if timer still exists + bool found = false; + + LOCK_TIMERS_READ; + for (const cTimer *ti = Timers->First(); ti; ti = Timers->Next(ti)) + if (ti == tiR->timer) + { + found = true; + break; + } + + if (found && !tiR->timer->Recording()) + { + if (tiR->recDone) + { + cSearchExt* search = SearchExts.GetSearchFromID(tiR->recDone->searchID); + if (!search) return; + // check if recording has ended before timer end + + bool complete = true; + const cRecording *pRecording; + { + LOCK_RECORDINGS_READ; + pRecording = Recordings->GetByName(m_filename); + } + long timerLengthSecs = tiR->timer->StopTime()-tiR->timer->StartTime(); + int recFraction = 100; + if (pRecording && timerLengthSecs) + { + int recLen = RecLengthInSecs(pRecording); + recFraction = double(recLen) * 100 / timerLengthSecs; + } + bool vpsUsed = tiR->timer->HasFlags(tfVps) && tiR->timer->Event() && tiR->timer->Event()->Vps(); + if ((!vpsUsed && now < tiR->timer->StopTime()) || recFraction < (vpsUsed ? 90: 98)) // assure timer has reached its end or at least 98% were recorded + { + complete = false; + LogFile.Log(1,"finished: '%s' (not complete! - recorded only %d%%); search timer: '%s'; VPS used: %s", tiR->timer->File(), recFraction, search->search, vpsUsed ? "Yes": "No"); + } + else + { + LogFile.Log(1,"finished: '%s'; search timer: '%s'; VPS used: %s", tiR->timer->File(), search->search, vpsUsed ? "Yes": "No"); + if (recFraction < 100) + LogFile.Log(2,"recorded %d%%'", recFraction); + } + if (complete) + { + RecsDone.Add(tiR->recDone); + LogFile.Log(1,"added rec done for '%s~%s';%s", tiR->recDone->title?tiR->recDone->title:"unknown title", + tiR->recDone->shortText?tiR->recDone->shortText:"unknown subtitle", + search->search); + RecsDone.Save(); + tiR->recDone = NULL; // prevent deletion + tiR->lastBreak = 0; + + // check for search timers to delete automatically + SearchExts.CheckForAutoDelete(search); + + // trigger a search timer update (skip running events) + search->skipRunningEvents = true; + updateForced = 1; + } + else if (tiR->lastBreak == 0) // store first break + tiR->lastBreak = now; + } + if (tiR->lastBreak == 0 || (now - tiR->lastBreak) > ALLOWED_BREAK_INSECS) + { // remove finished recordings or those with an unallowed break + if (tiR->recDone) delete tiR->recDone; // clean up + cRecDoneTimerObj *tiRNext = TimersRecording.Next(tiR); + TimersRecording.Del(tiR); + tiR = tiRNext; + continue; + } + break; + } + if (!found) + { + if (tiR->recDone) delete tiR->recDone; // clean up + cRecDoneTimerObj *tiRNext = TimersRecording.Next(tiR); + TimersRecording.Del(tiR); + tiR = tiRNext; + continue; + } + tiR = TimersRecording.Next(tiR); + } + if (m_filename) free(m_filename); + LogFile.Log(1,"recdone_thread ended"); +} diff --git a/recdone_thread.h b/recdone_thread.h new file mode 100644 index 0000000..adb65f9 --- /dev/null +++ b/recdone_thread.h @@ -0,0 +1,40 @@ +/* -*- c++ -*- +Copyright (C) 2004-2013 Christian Wieninger 2017 Johann Friedrichs + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + +*/ + +#ifndef VDR_RECDONE_THREAD_H +#define VDR_RECDONE_THREAD_H + +#include <vdr/thread.h> +#include "recstatus.h" + +class cRecdoneThread: public cThread { +private: + char * m_filename; +public: + virtual void Action(void); + cRecdoneThread(void); + virtual ~cRecdoneThread(); + void SetFilename(const char *FileName) { + m_filename = strdup(FileName); }; + int RecLengthInSecs(const cRecording *pRecording); + bool IsPesRecording(const cRecording *pRecording); +}; + +#endif diff --git a/recstatus.c b/recstatus.c index 794ba27..0b382fd 100644 --- a/recstatus.c +++ b/recstatus.c @@ -23,15 +23,15 @@ The project's page is at http://winni.vdr-developer.org/epgsearch #include "epgsearchtools.h" #include "recstatus.h" -#include "recdone.h" +#include "recdone_thread.h" #include "conflictcheck_thread.h" #include "epgsearchcfg.h" #include <math.h> #define ALLOWED_BREAK_INSECS 2 -extern int updateForced; extern int gl_InfoConflict; - +cTimersRecording TimersRecording; +cRecdoneThread RecdoneThread; cRecStatusMonitor* gl_recStatusMonitor = NULL; @@ -54,6 +54,7 @@ void cRecStatusMonitor::Recording(const cDevice *Device, const char *Name, const { // check if this is a new entry cRecDoneTimerObj *tiRFound = NULL; + cMutexLock TimersRecordingLock(&TimersRecording); for (cRecDoneTimerObj *tiR = TimersRecording.First(); tiR; tiR = TimersRecording.Next(tiR)) if (tiR->timer == ti) { @@ -105,139 +106,18 @@ void cRecStatusMonitor::Recording(const cDevice *Device, const char *Name, const if (!On) { - cMutexLock RecsDoneLock(&RecsDone); - // remove timers that finished recording from TimersRecording - // incomplete recordings are kept for a while, perhaps they will be resumed - cRecDoneTimerObj *tiR = TimersRecording.First(); - while(tiR) - { - // check if timer still exists - bool found = false; - - LOCK_TIMERS_READ; - for (const cTimer *ti = Timers->First(); ti; ti = Timers->Next(ti)) - if (ti == tiR->timer) - { - found = true; - break; - } - - if (found && !tiR->timer->Recording()) - { - if (tiR->recDone) - { - cSearchExt* search = SearchExts.GetSearchFromID(tiR->recDone->searchID); - if (!search) return; - - // check if recording has ended before timer end - - bool complete = true; - const cRecording *pRecording; - { - LOCK_RECORDINGS_READ; - pRecording = Recordings->GetByName(Filename); - } - long timerLengthSecs = tiR->timer->StopTime()-tiR->timer->StartTime(); - int recFraction = 100; - if (pRecording && timerLengthSecs) - { - int recLen = RecLengthInSecs(pRecording); - recFraction = double(recLen) * 100 / timerLengthSecs; - } - bool vpsUsed = tiR->timer->HasFlags(tfVps) && tiR->timer->Event() && tiR->timer->Event()->Vps(); - if ((!vpsUsed && now < tiR->timer->StopTime()) || recFraction < (vpsUsed ? 90: 98)) // assure timer has reached its end or at least 98% were recorded - { - complete = false; - LogFile.Log(1,"finished: '%s' (not complete! - recorded only %d%%); search timer: '%s'; VPS used: %s", tiR->timer->File(), recFraction, search->search, vpsUsed ? "Yes": "No"); - } - else - { - LogFile.Log(1,"finished: '%s'; search timer: '%s'; VPS used: %s", tiR->timer->File(), search->search, vpsUsed ? "Yes": "No"); - if (recFraction < 100) - LogFile.Log(2,"recorded %d%%'", recFraction); - } - if (complete) - { - RecsDone.Add(tiR->recDone); - LogFile.Log(1,"added rec done for '%s~%s';%s", tiR->recDone->title?tiR->recDone->title:"unknown title", - tiR->recDone->shortText?tiR->recDone->shortText:"unknown subtitle", - search->search); - RecsDone.Save(); - tiR->recDone = NULL; // prevent deletion - tiR->lastBreak = 0; - - // check for search timers to delete automatically - SearchExts.CheckForAutoDelete(search); - - // trigger a search timer update (skip running events) - search->skipRunningEvents = true; - updateForced = 1; - } - else if (tiR->lastBreak == 0) // store first break - tiR->lastBreak = now; - } - if (tiR->lastBreak == 0 || (now - tiR->lastBreak) > ALLOWED_BREAK_INSECS) - { // remove finished recordings or those with an unallowed break - if (tiR->recDone) delete tiR->recDone; // clean up - cRecDoneTimerObj *tiRNext = TimersRecording.Next(tiR); - TimersRecording.Del(tiR); - tiR = tiRNext; - continue; - } - break; - } - if (!found) - { - if (tiR->recDone) delete tiR->recDone; // clean up - cRecDoneTimerObj *tiRNext = TimersRecording.Next(tiR); - TimersRecording.Del(tiR); - tiR = tiRNext; - continue; - } - tiR = TimersRecording.Next(tiR); - } + // must be done in a different thread because we hold timer and scheduling lock here + while (RecdoneThread.Active()) cCondWait::SleepMs(100); // wait before changing filename + RecdoneThread.SetFilename(Filename); + RecdoneThread.Start(); } } int cRecStatusMonitor::TimerRecDevice(const cTimer* timer) { if (!timer) return 0; + cMutexLock TimersRecordingLock(&TimersRecording); for (cRecDoneTimerObj *tiR = TimersRecording.First(); tiR; tiR = TimersRecording.Next(tiR)) if (tiR->timer == timer && timer->Recording()) return tiR->deviceNr+1; return 0; } - -bool cRecStatusMonitor::IsPesRecording(const cRecording *pRecording) -{ - return pRecording && pRecording->IsPesRecording(); -} - -#define LOC_INDEXFILESUFFIX "/index" - -struct tIndexTs { - uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!) - int reserved:7; // reserved for future use - int independent:1; // marks frames that can be displayed by themselves (for trick modes) - uint16_t number:16; // up to 64K files per recording - tIndexTs(off_t Offset, bool Independent, uint16_t Number) - { - offset = Offset; - reserved = 0; - independent = Independent; - number = Number; - } - }; - -int cRecStatusMonitor::RecLengthInSecs(const cRecording *pRecording) -{ - struct stat buf; - cString fullname = cString::sprintf("%s%s", pRecording->FileName(), IsPesRecording(pRecording) ? LOC_INDEXFILESUFFIX ".vdr" : LOC_INDEXFILESUFFIX); - if (pRecording->FileName() && *fullname && access(fullname, R_OK) == 0 && stat(fullname, &buf) == 0) - { - double frames = buf.st_size ? (buf.st_size - 1) / sizeof(tIndexTs) + 1 : 0; - double Seconds = 0; - modf((frames + 0.5) / pRecording->FramesPerSecond(), &Seconds); - return Seconds; - } - return -1; -} diff --git a/recstatus.h b/recstatus.h index c0104f9..dcc6fec 100644 --- a/recstatus.h +++ b/recstatus.h @@ -21,6 +21,9 @@ The author can be reached at cwieninger@gmx.de The project's page is at http://winni.vdr-developer.org/epgsearch */ +#ifndef __RECSTATUS_H +#define __RECSTATUS_H + #include <vdr/status.h> #include "recdone.h" #include "epgsearchtools.h" @@ -36,17 +39,20 @@ public: ~cRecDoneTimerObj() { timer = NULL; recDone = NULL; } // do not delete anything! }; +class cTimersRecording: public cList<cRecDoneTimerObj>, public cMutex { +}; + +extern cTimersRecording TimersRecording; + class cRecStatusMonitor : public cStatus { -public: - cList<cRecDoneTimerObj> TimersRecording; protected: virtual void Recording(const cDevice *Device, const char *Name, const char*, bool On); public: cRecStatusMonitor(); int TimerRecDevice(const cTimer*); - bool IsPesRecording(const cRecording *pRecording); - int RecLengthInSecs(const cRecording *pRecording); }; extern cRecStatusMonitor* gl_recStatusMonitor; + +#endif |