From a87e7625ddf38cd2e653a256899e9119505e92ac Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sun, 17 Feb 2002 13:05:05 +0100 Subject: [PATCH] Implemented the 'First day' parameter for repeating timers --- FORMATS | 11 ++++- HISTORY | 8 +++- MANUAL | 17 ++++++-- config.c | 69 +++++++++++++++++++++++++------ config.h | 13 +++--- i18n.c | 22 +++++++++- menu.c | 121 +++++++++++++++++++++++++++++++++++++++++++++---------- tools.c | 22 +++++++--- tools.h | 3 +- 9 files changed, 232 insertions(+), 54 deletions(-) diff --git a/FORMATS b/FORMATS index d7d4293c..3e1ff868 100644 --- a/FORMATS +++ b/FORMATS @@ -50,7 +50,7 @@ Video Disk Recorder File Formats and recognize if the user has modified them. When a user modifes an active timer the 'active' field will be explicitly set to '1'. - Program number of the channel to record - - Day of recording, either one or more of + - Day of recording (in case of a repeating timer), either one or more of M------ = Monday -T----- = Tuesday --W---- = Wednesday @@ -61,7 +61,14 @@ Video Disk Recorder File Formats (any combination is possible, for example MTWTF--, and the days may be indicated by any characters except '-', so for example ABC---- would set a timer that records on monday, tuesday and wednesday) or the "day of month" - (1..31) + (1..31) in case of a single shot timer. + The day definition of a repeating timer may be followed by the date when that + timer shall hit for the first time. The format for this is @YYYY-MM-DD, + so a complete definition could look like this: MTWTF--@2002-02-18. This + "first day" feature can be used to disable a repeating timer for a couple + of days, or for instance to define a new Mon...Fri timer on wednesday, which + actually starts "monday next week". The "first day" date given need not be + that of a day when the timer would actually hit. - Start time (first two digits for the hour, second two digits for the minutes) - End time (first two digits for the hour, second two digits for the minutes) - Priority (from 0 to 99, 0 = lowest prioity, 99 = highest priority) diff --git a/HISTORY b/HISTORY index 5ce51874..f2ac0d44 100644 --- a/HISTORY +++ b/HISTORY @@ -979,7 +979,7 @@ Video Disk Recorder Revision History - Only reporting the 'EPG bugfix statistics' if there really were any fixes. - Added Finnish language texts (thanks to Hannu Savolainen). - Reverted to the previous way of searching for the EPG record of the current - recording in case of a periodic timer (i.e. taking the one that is in the + recording in case of a repeating timer (i.e. taking the one that is in the middle between start and end time). - Added a typedef for 'in_addr_t' to make it work with glibc < 2.2 (thanks to Jürgen Schmidt). @@ -997,7 +997,7 @@ Video Disk Recorder Revision History - If a recording has no episode title, the trailing '~' is no longer shown in the progress display. -2002-02-16: Version 1.0.0pre1 +2002-02-17: Version 1.0.0pre1 - Added scanning for EPG data for another 4 days on channels that support this (thanks to Oleg Assovski). @@ -1005,3 +1005,7 @@ Video Disk Recorder Revision History - Fixed the "Low disk space!" message (thanks to Sergei Haller). - Added the TPID to Hessen-3 in 'channels.conf' (thanks to Sergei Haller). - Fixed a crash when replaying with DEBUG_OSD=1 (thanks to Stefan Huelswitt). +- Implemented the "First day" parameter for repeating timers. See FORMATS for + information about the enhanced 'timers.conf' file format, and MANUAL for + a description of the new item in the "Edit Timer" menu and the enhanced + functionality of the "Blue" button in the "Timers" menu. diff --git a/MANUAL b/MANUAL index d0ce7a2c..f8d746ae 100644 --- a/MANUAL +++ b/MANUAL @@ -41,8 +41,15 @@ Video Disk Recorder User's Manual any changes that might have been made in the current menu. In the "Timers" menu, the current timer can be enabled or disabled with - the "Right" or "Left" key, respectively (enabled timers are marked with '>', - timers that are currently recording are marked with '#'). + the "Blue" key (this is only possible if the "Timers" list is sorted, + otherwise the "Blue" key is used to mark a timer in order to move it to + another position in the list). Enabled timers are marked with '>', timers + that are currently recording are marked with '#'. If a timer has the + "First day" set so that it will start recording only on the given date, + it is marked with '!'. The "Blue" key toggles through the "enabled" and + "disabled" states, and for repeating timers that are currently recording + also a state that ends this recording prematurely and sets the "First day" + date so that it will record again the next time the timer hits. "Ok" here opens the "Edit timer" menu. Textual options, like channel names or recording file names, can be edited @@ -160,7 +167,7 @@ Video Disk Recorder User's Manual list with the "Up" and "Down" button and press "Ok" (or the "Red" button) to start playback. New recordings are marked with an '*'. If the Setup parameter RecordingDirs has been set and there are recordings - from periodic timers organized in a subdirectory structure, only the + from repeating timers organized in a subdirectory structure, only the directory is displayed and it can be opened by pressing "Ok" (or the "Red" button). A directory entry displays the total number of recordings within that directory (and any possible subdirectory thereof) as well as the total @@ -170,7 +177,7 @@ Video Disk Recorder User's Manual If the setup parameter UseSubtitle was turned on when a recording took place, VDR adds the "subtitle" (which is usually the name of the episode in case of a series) to the recording's name. The "Recordings" menu then displays all - recordings of a periodic timer in chronological order, since these are + recordings of a repeating timer in chronological order, since these are usually the individual episodes of a series, which you may want to view in the order in which they were broadcast. @@ -321,6 +328,8 @@ Video Disk Recorder User's Manual of this timer being collected in a common subdirectory. If this field is left blank, the channel name will be used to form the name of the recording. + First day: The date of the first day when this timer shall start recording + (only available for repeating timers). A timer can also be programmed by pressing the "Red" button on the "Schedule", "Now", "Next" or "Event" menus. diff --git a/config.c b/config.c index 8b5c454f..53b70262 100644 --- a/config.c +++ b/config.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.c 1.83 2002/02/10 11:39:00 kls Exp $ + * $Id: config.c 1.84 2002/02/17 11:37:05 kls Exp $ */ #include "config.h" @@ -338,6 +338,7 @@ cTimer::cTimer(bool Instant) priority = Setup.DefaultPriority; lifetime = Setup.DefaultLifetime; *file = 0; + firstday = 0; summary = NULL; if (Instant && ch) snprintf(file, sizeof(file), "%s%s", Setup.MarkInstantRecord ? "@" : "", *Setup.NameInstantRecord ? Setup.NameInstantRecord : ch->name); @@ -367,6 +368,7 @@ cTimer::cTimer(const cEventInfo *EventInfo) const char *Title = EventInfo->GetTitle(); if (!isempty(Title)) strn0cpy(file, EventInfo->GetTitle(), sizeof(file)); + firstday = 0; summary = NULL; } @@ -395,7 +397,7 @@ const char *cTimer::ToText(cTimer *Timer) delete buffer; strreplace(Timer->file, ':', '|'); strreplace(Timer->summary, '\n', '|'); - asprintf(&buffer, "%d:%d:%s:%04d:%04d:%d:%d:%s:%s\n", Timer->active, Timer->channel, PrintDay(Timer->day), Timer->start, Timer->stop, Timer->priority, Timer->lifetime, Timer->file, Timer->summary ? Timer->summary : ""); + asprintf(&buffer, "%d:%d:%s:%04d:%04d:%d:%d:%s:%s\n", Timer->active, Timer->channel, PrintDay(Timer->day, Timer->firstday), Timer->start, Timer->stop, Timer->priority, Timer->lifetime, Timer->file, Timer->summary ? Timer->summary : ""); strreplace(Timer->summary, '|', '\n'); strreplace(Timer->file, '|', ':'); return buffer; @@ -411,20 +413,38 @@ int cTimer::TimeToInt(int t) return (t / 100 * 60 + t % 100) * 60; } -int cTimer::ParseDay(const char *s) +int cTimer::ParseDay(const char *s, time_t *FirstDay) { char *tail; int d = strtol(s, &tail, 10); + if (FirstDay) + *FirstDay = 0; if (tail && *tail) { d = 0; if (tail == s) { - if (strlen(s) == 7) { + const char *first = strchr(s, '@'); + int l = first ? first - s : strlen(s); + if (l == 7) { for (const char *p = s + 6; p >= s; p--) { - d <<= 1; - d |= (*p != '-'); - } + d <<= 1; + d |= (*p != '-'); + } d |= 0x80000000; } + if (FirstDay && first) { + ++first; + if (strlen(first) == 10) { + struct tm tm_r; + if (3 == sscanf(first, "%d-%d-%d", &tm_r.tm_year, &tm_r.tm_mon, &tm_r.tm_mday)) { + tm_r.tm_year -= 1900; + tm_r.tm_mon--; + tm_r.tm_hour = tm_r.tm_min = tm_r.tm_sec = 0; + *FirstDay = mktime(&tm_r); + } + } + else + d = 0; + } } } else if (d < 1 || d > 31) @@ -432,24 +452,40 @@ int cTimer::ParseDay(const char *s) return d; } -const char *cTimer::PrintDay(int d) +const char *cTimer::PrintDay(int d, time_t FirstDay) { - static char buffer[8]; +#define DAYBUFFERSIZE 32 + static char buffer[DAYBUFFERSIZE]; if ((d & 0x80000000) != 0) { char *b = buffer; const char *w = tr("MTWTFSS"); - *b = 0; while (*w) { *b++ = (d & 1) ? *w : '-'; d >>= 1; w++; } + if (FirstDay) { + struct tm tm_r; + localtime_r(&FirstDay, &tm_r); + b += strftime(b, DAYBUFFERSIZE - (b - buffer), "@%Y-%m-%d", &tm_r); + } + *b = 0; } else sprintf(buffer, "%d", d); return buffer; } +const char *cTimer::PrintFirstDay(void) +{ + if (firstday) { + const char *s = PrintDay(day, firstday); + if (strlen(s) == 18) + return s + 8; + } + return ""; // not NULL, so the caller can always use the result +} + bool cTimer::Parse(const char *s) { char *buffer1 = NULL; @@ -477,7 +513,7 @@ bool cTimer::Parse(const char *s) summary = NULL; } //TODO add more plausibility checks - day = ParseDay(buffer1); + day = ParseDay(buffer1, &firstday); strn0cpy(file, buffer2, MaxFileName); strreplace(file, '|', ':'); strreplace(summary, '|', '\n'); @@ -563,13 +599,17 @@ bool cTimer::Matches(time_t t) if (DayMatches(t0)) { time_t a = SetTime(t0, begin); time_t b = a + length; - if (t <= b) { + if ((!firstday || a >= firstday) && t <= b) { startTime = a; stopTime = b; + if (t >= firstday) + firstday = 0; break; } } } + if (!startTime) + startTime = firstday; // just to have something that's more than a week in the future return active && startTime <= t && t < stopTime; // must stop *before* stopTime to allow adjacent timers } @@ -598,6 +638,11 @@ void cTimer::SetPending(bool Pending) pending = Pending; } +void cTimer::SkipToday(void) +{ + firstday = IncDay(SetTime(recording ? StartTime() : time(NULL), 0), 1); +} + // --- cCommand ------------------------------------------------------------- char *cCommand::result = NULL; diff --git a/config.h b/config.h index e2e2897f..60cfafde 100644 --- a/config.h +++ b/config.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.95 2002/02/10 15:44:40 kls Exp $ + * $Id: config.h 1.96 2002/02/17 12:17:29 kls Exp $ */ #ifndef __CONFIG_H @@ -135,6 +135,7 @@ public: int priority; int lifetime; char file[MaxFileName]; + time_t firstday; char *summary; cTimer(bool Instant = false); cTimer(const cEventInfo *EventInfo); @@ -148,17 +149,19 @@ public: int GetMDay(time_t t); int GetWDay(time_t t); bool DayMatches(time_t t); - time_t IncDay(time_t t, int Days); - time_t SetTime(time_t t, int SecondsFromMidnight); + static time_t IncDay(time_t t, int Days); + static time_t SetTime(time_t t, int SecondsFromMidnight); char *SetFile(const char *File); bool Matches(time_t t = 0); time_t StartTime(void); time_t StopTime(void); void SetRecording(bool Recording); void SetPending(bool Pending); + void SkipToday(void); + const char *PrintFirstDay(void); static int TimeToInt(int t); - static int ParseDay(const char *s); - static const char *PrintDay(int d); + static int ParseDay(const char *s, time_t *FirstDay = NULL); + static const char *PrintDay(int d, time_t FirstDay = 0); }; class cCommand : public cListObject { diff --git a/i18n.c b/i18n.c index f2c414f0..487ece85 100644 --- a/i18n.c +++ b/i18n.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: i18n.c 1.53 2002/02/10 15:07:46 kls Exp $ + * $Id: i18n.c 1.54 2002/02/17 12:36:19 kls Exp $ * * Slovenian translations provided by Miha Setina * Italian translations provided by Alberto Carraro @@ -261,6 +261,16 @@ const tPhrase Phrases[] = { "Marker", "Merkitse", }, + { "On/Off", + "Ein/Aus", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, { "Record", "Aufnehmen", "Posnemi", @@ -684,6 +694,16 @@ const tPhrase Phrases[] = { "Filnavn", "Tiedosto", }, + { "First day", + "Erster Tag", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, // Error messages: { "Channel is being used by a timer!", "Kanal wird von einem Timer benutzt!", diff --git a/menu.c b/menu.c index a7a98ec3..a48a1968 100644 --- a/menu.c +++ b/menu.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 1.152 2002/02/10 11:52:34 kls Exp $ + * $Id: menu.c 1.153 2002/02/17 13:00:13 kls Exp $ */ #include "menu.h" @@ -254,6 +254,62 @@ eOSState cMenuEditDayItem::ProcessKey(eKeys Key) return osContinue; } +// --- cMenuEditDateItem ----------------------------------------------------- + +class cMenuEditDateItem : public cMenuEditItem { +protected: + time_t *value; + virtual void Set(void); +public: + cMenuEditDateItem(const char *Name, time_t *Value); + virtual eOSState ProcessKey(eKeys Key); + }; + +cMenuEditDateItem::cMenuEditDateItem(const char *Name, time_t *Value) +:cMenuEditItem(Name) +{ + value = Value; + Set(); +} + +void cMenuEditDateItem::Set(void) +{ +#define DATEBUFFERSIZE 32 + char buf[DATEBUFFERSIZE]; + if (*value) { + struct tm tm_r; + localtime_r(value, &tm_r); + strftime(buf, DATEBUFFERSIZE, "%Y-%m-%d ", &tm_r); + strcat(buf, WeekDayName(tm_r.tm_wday)); + } + else + *buf = 0; + SetValue(buf); +} + +eOSState cMenuEditDateItem::ProcessKey(eKeys Key) +{ + eOSState state = cMenuEditItem::ProcessKey(Key); + + if (state == osUnknown) { + if (NORMALKEY(Key) == kLeft) { // TODO might want to increase the delta if repeated quickly? + *value -= SECSINDAY; + if (*value < time(NULL)) + *value = 0; + } + else if (NORMALKEY(Key) == kRight) { + if (!*value) + *value = cTimer::SetTime(time(NULL), 0); + *value += SECSINDAY; + } + else + return state; + Set(); + state = osContinue; + } + return state; +} + // --- cMenuEditTimeItem ----------------------------------------------------- class cMenuEditTimeItem : public cMenuEditItem { @@ -905,6 +961,8 @@ class cMenuEditTimer : public cOsdMenu { private: cTimer *timer; cTimer data; + cMenuEditDateItem *firstday; + void SetFirstDayItem(void); public: cMenuEditTimer(int Index, bool New = false); virtual eOSState ProcessKey(eKeys Key); @@ -913,6 +971,7 @@ public: cMenuEditTimer::cMenuEditTimer(int Index, bool New) :cOsdMenu(tr("Edit Timer"), 12) { + firstday = NULL; timer = Timers.Get(Index); if (timer) { data = *timer; @@ -927,6 +986,21 @@ cMenuEditTimer::cMenuEditTimer(int Index, bool New) Add(new cMenuEditIntItem( tr("Priority"), &data.priority, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Lifetime"), &data.lifetime, 0, MAXLIFETIME)); Add(new cMenuEditStrItem( tr("File"), data.file, sizeof(data.file), FileNameChars)); + SetFirstDayItem(); + } +} + +void cMenuEditTimer::SetFirstDayItem(void) +{ + if (!firstday && !data.IsSingleEvent()) { + Add(firstday = new cMenuEditDateItem(tr("First day"), &data.firstday)); + Display(); + } + else if (firstday && data.IsSingleEvent()) { + Del(firstday->Index()); + firstday = NULL; + data.firstday = 0; + Display(); } } @@ -953,6 +1027,8 @@ eOSState cMenuEditTimer::ProcessKey(eKeys Key) default: break; } } + if (Key != kNone) + SetFirstDayItem(); return state; } @@ -983,7 +1059,7 @@ void cMenuTimerItem::Set(void) { char *buffer = NULL; asprintf(&buffer, "%c\t%d\t%s\t%02d:%02d\t%02d:%02d\t%s", - timer->active ? timer->recording ? '#' : '>' : ' ', + !timer->active ? ' ' : timer->firstday ? '!' : timer->recording ? '#' : '>', timer->channel, timer->PrintDay(timer->day), timer->start / 100, @@ -998,10 +1074,10 @@ void cMenuTimerItem::Set(void) class cMenuTimers : public cOsdMenu { private: - eOSState Activate(bool On); eOSState Edit(void); eOSState New(void); eOSState Del(void); + eOSState OnOff(void); virtual void Move(int From, int To); eOSState Summary(void); cTimer *CurrentTimer(void); @@ -1022,7 +1098,7 @@ cMenuTimers::cMenuTimers(void) } if (Setup.SortTimers) Sort(); - SetHelp(tr("Edit"), tr("New"), tr("Delete"), Setup.SortTimers ? NULL : tr("Mark")); + SetHelp(tr("Edit"), tr("New"), tr("Delete"), Setup.SortTimers ? tr("On/Off") : tr("Mark")); } cTimer *cMenuTimers::CurrentTimer(void) @@ -1031,14 +1107,27 @@ cTimer *cMenuTimers::CurrentTimer(void) return item ? item->Timer() : NULL; } -eOSState cMenuTimers::Activate(bool On) +eOSState cMenuTimers::OnOff(void) { cTimer *timer = CurrentTimer(); - if (timer && timer->active != On) { - timer->active = On; + if (timer) { + if (timer->IsSingleEvent()) + timer->active = !timer->active; + else if (timer->firstday) { + timer->firstday = 0; + timer->active = false; + } + else if (timer->active) + timer->SkipToday(); + else + timer->active = true; + timer->Matches(); // refresh start and end time RefreshCurrent(); DisplayCurrent(true); - isyslog(LOG_INFO, "timer %d %sactivated", timer->Index() + 1, timer->active ? "" : "de"); + if (timer->firstday) + isyslog(LOG_INFO, "timer %d first day set to %s", timer->Index() + 1, timer->PrintFirstDay()); + else + isyslog(LOG_INFO, "timer %d %sactivated", timer->Index() + 1, timer->active ? "" : "de"); Timers.Save(); } return osContinue; @@ -1106,27 +1195,17 @@ eOSState cMenuTimers::Summary(void) eOSState cMenuTimers::ProcessKey(eKeys Key) { - // Must do these before calling cOsdMenu::ProcessKey() because cOsdMenu - // uses them to page up/down: - if (!HasSubMenu()) { - switch (Key) { - case kLeft: - case kRight: return Activate(Key == kRight); - default: break; - } - } - eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { - case kLeft: - case kRight: return Activate(Key == kRight); case kOk: return Summary(); case kRed: return Edit(); case kGreen: return New(); case kYellow: return Del(); - case kBlue: if (!Setup.SortTimers) + case kBlue: if (Setup.SortTimers) + OnOff(); + else Mark(); break; default: break; diff --git a/tools.c b/tools.c index 1e60011f..3764a644 100644 --- a/tools.c +++ b/tools.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.c 1.58 2002/02/16 12:41:44 kls Exp $ + * $Id: tools.c 1.59 2002/02/17 12:57:23 kls Exp $ */ #include "tools.h" @@ -462,6 +462,20 @@ bool SpinUpDisk(const char *FileName) return false; } +const char *WeekDayName(int WeekDay) +{ + static char buffer[4]; + WeekDay = WeekDay == 0 ? 6 : WeekDay - 1; // we start with monday==0! + if (0 <= WeekDay && WeekDay <= 6) { + const char *day = tr("MonTueWedThuFriSatSun"); + day += WeekDay * 3; + strncpy(buffer, day, 3); + return buffer; + } + else + return "???"; +} + const char *DayDateTime(time_t t) { static char buffer[32]; @@ -469,11 +483,7 @@ const char *DayDateTime(time_t t) time(&t); struct tm tm_r; tm *tm = localtime_r(&t, &tm_r); - int weekday = tm->tm_wday == 0 ? 6 : tm->tm_wday - 1; // we start with monday==0! - const char *day = tr("MonTueWedThuFriSatSun"); - day += weekday * 3; - strncpy(buffer, day, 3); - snprintf(buffer + 3, sizeof(buffer) - 3, " %2d.%02d %02d:%02d", tm->tm_mday, tm->tm_mon + 1, tm->tm_hour, tm->tm_min); + snprintf(buffer, sizeof(buffer), "%s %2d.%02d %02d:%02d", WeekDayName(tm->tm_wday), tm->tm_mday, tm->tm_mon + 1, tm->tm_hour, tm->tm_min); return buffer; } diff --git a/tools.h b/tools.h index 9663a8ed..08a56723 100644 --- a/tools.h +++ b/tools.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.h 1.41 2002/02/03 12:36:25 kls Exp $ + * $Id: tools.h 1.42 2002/02/17 12:57:44 kls Exp $ */ #ifndef __TOOLS_H @@ -66,6 +66,7 @@ bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks = false); bool RemoveEmptyDirectories(const char *DirName, bool RemoveThis = false); char *ReadLink(const char *FileName); bool SpinUpDisk(const char *FileName); +const char *WeekDayName(int WeekDay); // returns a statically allocated string! const char *DayDateTime(time_t t = 0); // returns a statically allocated string! class cFile {