From 7f66e1573e056b26598cb8d37b5f65fe7276eb25 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Fri, 8 Feb 2013 09:24:55 +0100 Subject: [PATCH] New command line option --dirnames --- HISTORY | 10 +++- INSTALL | 6 ++- UPDATE-2.0.0 | 6 +++ config.c | 4 +- config.h | 6 +-- recording.c | 140 ++++++++++++++++++++++++++++++++++++++++++++------- recording.h | 6 ++- timers.c | 26 +--------- timers.h | 4 +- vdr.1 | 14 ++++-- vdr.c | 58 +++++++++++++++++++-- 11 files changed, 219 insertions(+), 61 deletions(-) diff --git a/HISTORY b/HISTORY index dfcf831d..3bf23d1e 100644 --- a/HISTORY +++ b/HISTORY @@ -7534,7 +7534,7 @@ Video Disk Recorder Revision History - Reduced the number of retries in cTransfer::Receive() to avoid blocking recordings in case the primary device can't handle the current live signal. -2013-02-03: Version 1.7.37 +2013-02-08: Version 1.7.37 - Now also using FindHeader() in cMpeg2Fixer::AdjTref() (pointed out by Sören Moch). - Added missing template for DVBDIR to Make.config.template (reported by Derek Kelly). @@ -7570,3 +7570,11 @@ Video Disk Recorder Revision History recommended that plugins that implement an interface to any kind of remote controls also use the parameters Setup.RcRepeatDelay and Setup.RcRepeatDelta for the desired purpose, and remove any setup options they might have that serve the same purpose. +- cTimer no longer does any special "VFAT" handling to shorten directory names to 40 + characters. When a string is used as a directory name for a recording, the maximum + length of the directory path, as well as the individual directory names, is now + limited to the values specified by the new command line option --dirnames (see + man vdr(1) for details). For backwards compatibility the option --vfat is still + available and has the same effect as --dirnames=250,40,1. +- The macro MaxFileName is now obsolete and may be removed in future versions. Use + NAME_MAX directly instead. diff --git a/INSTALL b/INSTALL index 7654c2a9..2cf6be33 100644 --- a/INSTALL +++ b/INSTALL @@ -58,8 +58,10 @@ Alternatively you can use the '--lirc' option at runtime. This option accepts an optional path to the remote control device, the default of which can be set via the LIRC_DEVICE macro. -If your video directory will be on a VFAT partition, you can call VDR with -the command line option '--vfat'. +If you want to make your video directory available to other machines that +have limitations on directory name lengths and/or allowed characters in +directory names, you can call VDR with the command line option '--dirnames' +(see man vdr(1) for details). When running, the 'vdr' program writes status information into the system log file, which is usually /var/log/messages (or /var/log/user.log, diff --git a/UPDATE-2.0.0 b/UPDATE-2.0.0 index 02087812..6ecfcbb2 100644 --- a/UPDATE-2.0.0 +++ b/UPDATE-2.0.0 @@ -369,6 +369,12 @@ Recordings: recording, even if there is no mark set at that point. - The new option "Setup/Replay/Pause on mark set" can be used to activate automatically going into Pause mode if an editing mark is set during replay. +- Timers no longer do any special "VFAT" handling to shorten directory names to 40 + characters. When a string is used as a directory name for a recording, the maximum + length of the directory path, as well as the individual directory names, is now + limited to the values specified by the new command line option --dirnames (see + man vdr(1) for details). For backwards compatibility the option --vfat is still + available and has the same effect as --dirnames=250,40,1. SVDRP: diff --git a/config.c b/config.c index 3335dfc2..b4228931 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 2.33 2013/02/02 13:44:33 kls Exp $ + * $Id: config.c 2.34 2013/02/05 11:16:08 kls Exp $ */ #include "config.h" @@ -587,7 +587,7 @@ bool cSetup::Parse(const char *Name, const char *Value) else if (!strcasecmp(Name, "MenuScrollWrap")) MenuScrollWrap = atoi(Value); else if (!strcasecmp(Name, "MenuKeyCloses")) MenuKeyCloses = atoi(Value); else if (!strcasecmp(Name, "MarkInstantRecord")) MarkInstantRecord = atoi(Value); - else if (!strcasecmp(Name, "NameInstantRecord")) Utf8Strn0Cpy(NameInstantRecord, Value, MaxFileName); + else if (!strcasecmp(Name, "NameInstantRecord")) Utf8Strn0Cpy(NameInstantRecord, Value, sizeof(NameInstantRecord)); else if (!strcasecmp(Name, "InstantRecordTime")) InstantRecordTime = atoi(Value); else if (!strcasecmp(Name, "LnbSLOF")) LnbSLOF = atoi(Value); else if (!strcasecmp(Name, "LnbFrequLo")) LnbFrequLo = atoi(Value); diff --git a/config.h b/config.h index 5a0f71d1..00b2d68e 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 2.63 2013/02/02 13:45:19 kls Exp $ + * $Id: config.h 2.64 2013/02/05 11:19:20 kls Exp $ */ #ifndef __CONFIG_H @@ -49,7 +49,7 @@ #define MINOSDHEIGHT 324 #define MAXOSDHEIGHT 1200 -#define MaxFileName 256 +#define MaxFileName NAME_MAX // obsolete - use NAME_MAX directly instead! #define MaxSkinName 16 #define MaxThemeName 16 @@ -257,7 +257,7 @@ public: int MenuScrollWrap; int MenuKeyCloses; int MarkInstantRecord; - char NameInstantRecord[MaxFileName]; + char NameInstantRecord[NAME_MAX]; int InstantRecordTime; int LnbSLOF; int LnbFrequLo; diff --git a/recording.c b/recording.c index fcc99424..57248a07 100644 --- a/recording.c +++ b/recording.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.c 2.85 2013/01/25 14:33:16 kls Exp $ + * $Id: recording.c 2.86 2013/02/08 09:02:07 kls Exp $ */ #include "recording.h" @@ -64,11 +64,11 @@ #define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks #define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written -#define MAX_SUBTITLE_LENGTH 40 - #define MAX_LINK_LEVEL 6 -bool VfatFileSystem = false; +int DirectoryPathMax = PATH_MAX; +int DirectoryNameMax = NAME_MAX; +bool DirectoryEncoding = false; int InstanceId = 0; cRecordings DeletedRecordings(true); @@ -540,22 +540,30 @@ tCharExchange CharExchange[] = { { 0, 0 } }; +const char *InvalidChars = "\"\\/:*?|<>#"; + +bool NeedsConversion(const char *p) +{ + return DirectoryEncoding && + (strchr(InvalidChars, *p) // characters that can't be part of a Windows file/directory name + || *p == '.' && (!*(p + 1) || *(p + 1) == FOLDERDELIMCHAR)); // Windows can't handle '.' at the end of file/directory names +} + char *ExchangeChars(char *s, bool ToFileSystem) { char *p = s; while (*p) { - if (VfatFileSystem) { - // The VFAT file system can't handle all characters, so we + if (DirectoryEncoding) { + // Some file systems can't handle all characters, so we // have to take extra efforts to encode/decode them: if (ToFileSystem) { - const char *InvalidChars = "\"\\/:*?|<>#"; switch (*p) { // characters that can be mapped to other characters: case ' ': *p = '_'; break; case FOLDERDELIMCHAR: *p = '/'; break; // characters that have to be encoded: default: - if (strchr(InvalidChars, *p) || *p == '.' && (!*(p + 1) || *(p + 1) == FOLDERDELIMCHAR)) { // Windows can't handle '.' at the end of file/directory names + if (NeedsConversion(p)) { int l = p - s; if (char *NewBuffer = (char *)realloc(s, strlen(s) + 10)) { s = NewBuffer; @@ -610,6 +618,107 @@ char *ExchangeChars(char *s, bool ToFileSystem) return s; } +char *LimitNameLengths(char *s, int PathMax, int NameMax) +{ + // Limits the total length of the directory path in 's' to PathMax, and each + // individual directory name to NameMax. The lengths of characters that need + // conversion when using 's' as a file name are taken into account accordingly. + // If a directory name exceeds NameMax, it will be truncated. If the whole + // directory path exceeds PathMax, individual directory names will be shortened + // (from right to left) until the limit is met, or until the currently handled + // directory name consists of only a single character. All operations are performed + // directly on the given 's', which may become shorter (but never longer) than + // the original value. + // Returns a pointer to 's'. + int Length = strlen(s); + int PathLength = 0; + // Collect the resulting lengths of each character: + bool NameTooLong = false; + int8_t a[Length]; + int n = 0; + int NameLength = 0; + for (char *p = s; *p; p++) { + if (*p == FOLDERDELIMCHAR) { + a[n] = -1; // FOLDERDELIMCHAR is a single character, neg. sign marks it + NameTooLong |= NameLength > NameMax; + NameLength = 0; + PathLength += 1; + } + else if (NeedsConversion(p)) { + a[n] = 3; // "#xx" + NameLength += 3; + PathLength += 3; + } + else { + int8_t l = Utf8CharLen(p); + a[n] = l; + NameLength += l; + PathLength += l; + while (l-- > 1) { + a[++n] = 0; + p++; + } + } + n++; + } + NameTooLong |= NameLength > NameMax; + // Limit names to NameMax: + if (NameTooLong) { + while (n > 0) { + // Calculate the length of the current name: + int NameLength = 0; + int i = n; + int b = i; + while (i-- > 0 && a[i] >= 0) { + NameLength += a[i]; + b = i; + } + // Shorten the name if necessary: + if (NameLength > NameMax) { + int l = 0; + i = n; + while (i-- > 0 && a[i] >= 0) { + l += a[i]; + if (NameLength - l <= NameMax) { + memmove(s + i, s + n, Length - n + 1); + memmove(a + i, a + n, Length - n + 1); + Length -= n - i; + PathLength -= l; + break; + } + } + } + // Switch to the next name: + n = b - 1; + } + } + // Limit path to PathMax: + n = Length; + while (PathLength > PathMax && n > 0) { + // Calculate how much to cut off the current name: + int i = n; + int b = i; + int l = 0; + while (--i > 0 && a[i - 1] >= 0) { + if (a[i] > 0) { + l += a[i]; + b = i; + if (PathLength - l <= PathMax) + break; + } + } + // Shorten the name if necessary: + if (l > 0) { + memmove(s + b, s + n, Length - n + 1); + Length -= n - b; + PathLength -= l; + } + // Switch to the next name: + n = i - 1; + } + return s; +} + cRecording::cRecording(cTimer *Timer, const cEvent *Event) { resume = RESUME_NOT_INITIALIZED; @@ -628,16 +737,10 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event) // set up the actual name: const char *Title = Event ? Event->Title() : NULL; const char *Subtitle = Event ? Event->ShortText() : NULL; - char SubtitleBuffer[MAX_SUBTITLE_LENGTH]; if (isempty(Title)) Title = Timer->Channel()->Name(); if (isempty(Subtitle)) Subtitle = " "; - else if (strlen(Subtitle) > MAX_SUBTITLE_LENGTH) { - // let's make sure the Subtitle doesn't produce too long a file name: - Utf8Strn0Cpy(SubtitleBuffer, Subtitle, MAX_SUBTITLE_LENGTH); - Subtitle = SubtitleBuffer; - } const char *macroTITLE = strstr(Timer->File(), TIMERMACRO_TITLE); const char *macroEPISODE = strstr(Timer->File(), TIMERMACRO_EPISODE); if (macroTITLE || macroEPISODE) { @@ -872,9 +975,12 @@ const char *cRecording::FileName(void) const const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS; int ch = isPesRecording ? priority : channel; int ri = isPesRecording ? lifetime : instanceId; - name = ExchangeChars(name, true); - fileName = strdup(cString::sprintf(fmt, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri)); - name = ExchangeChars(name, false); + char *Name = LimitNameLengths(strdup(name), DirectoryPathMax - strlen(VideoDirectory) - 1 - 42, DirectoryNameMax); // 42 = length of an actual recording directory name (generated with DATAFORMATTS) plus some reserve + if (strcmp(Name, name) != 0) + dsyslog("recording file name '%s' truncated to '%s'", name, Name); + Name = ExchangeChars(Name, true); + fileName = strdup(cString::sprintf(fmt, VideoDirectory, Name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri)); + free(Name); } return fileName; } diff --git a/recording.h b/recording.h index f555024c..037f2046 100644 --- a/recording.h +++ b/recording.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.h 2.41 2012/12/23 15:11:53 kls Exp $ + * $Id: recording.h 2.42 2013/02/07 13:42:17 kls Exp $ */ #ifndef __RECORDING_H @@ -22,7 +22,9 @@ #define TIMERMACRO_TITLE "TITLE" #define TIMERMACRO_EPISODE "EPISODE" -extern bool VfatFileSystem; +extern int DirectoryPathMax; +extern int DirectoryNameMax; +extern bool DirectoryEncoding; extern int InstanceId; void RemoveDeletedRecordings(void); diff --git a/timers.c b/timers.c index ab8e4778..16cf8cef 100644 --- a/timers.c +++ b/timers.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: timers.c 2.15 2012/12/07 13:14:00 kls Exp $ + * $Id: timers.c 2.16 2013/02/05 11:13:20 kls Exp $ */ #include "timers.h" @@ -17,8 +17,6 @@ #include "remote.h" #include "status.h" -#define VFAT_MAX_FILENAME 40 // same as MAX_SUBTITLE_LENGTH in recording.c - // IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d' // format characters in order to allow any number of blanks after a numeric // value! @@ -80,11 +78,6 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel) lifetime = Pause ? Setup.PauseLifetime : Setup.DefaultLifetime; if (Instant && channel) snprintf(file, sizeof(file), "%s%s", Setup.MarkInstantRecord ? "@" : "", *Setup.NameInstantRecord ? Setup.NameInstantRecord : channel->Name()); - if (VfatFileSystem && (Utf8StrLen(file) > VFAT_MAX_FILENAME)) { - dsyslog("timer file name too long for VFAT file system: '%s'", file); - file[Utf8SymChars(file, VFAT_MAX_FILENAME)] = 0; - dsyslog("timer file name truncated to '%s'", file); - } } cTimer::cTimer(const cEvent *Event) @@ -120,11 +113,6 @@ cTimer::cTimer(const cEvent *Event) const char *Title = Event->Title(); if (!isempty(Title)) Utf8Strn0Cpy(file, Event->Title(), sizeof(file)); - if (VfatFileSystem && (Utf8StrLen(file) > VFAT_MAX_FILENAME)) { - dsyslog("timer file name too long for VFAT file system: '%s'", file); - file[Utf8SymChars(file, VFAT_MAX_FILENAME)] = 0; - dsyslog("timer file name truncated to '%s'", file); - } SetEvent(Event); } @@ -332,18 +320,6 @@ bool cTimer::Parse(const char *s) } //TODO add more plausibility checks result = ParseDay(daybuffer, day, weekdays); - if (VfatFileSystem) { - char *p = strrchr(filebuffer, FOLDERDELIMCHAR); - if (p) - p++; - else - p = filebuffer; - if (Utf8StrLen(p) > VFAT_MAX_FILENAME) { - dsyslog("timer file name too long for VFAT file system: '%s'", p); - p[Utf8SymChars(p, VFAT_MAX_FILENAME)] = 0; - dsyslog("timer file name truncated to '%s'", p); - } - } Utf8Strn0Cpy(file, filebuffer, sizeof(file)); strreplace(file, '|', ':'); if (isnumber(channelbuffer)) diff --git a/timers.h b/timers.h index cde8b3bd..adc55096 100644 --- a/timers.h +++ b/timers.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: timers.h 2.5 2012/12/07 13:13:40 kls Exp $ + * $Id: timers.h 2.6 2013/02/05 11:23:24 kls Exp $ */ #ifndef __TIMERS_H @@ -39,7 +39,7 @@ private: int stop; int priority; int lifetime; - mutable char file[MaxFileName]; + mutable char file[NAME_MAX * 2]; // *2 to be able to hold 'title' and 'episode', which can each be up to 255 characters long char *aux; const cEvent *event; public: diff --git a/vdr.1 b/vdr.1 index d22c4e91..f7cb109f 100644 --- a/vdr.1 +++ b/vdr.1 @@ -8,7 +8,7 @@ .\" License as specified in the file COPYING that comes with the .\" vdr distribution. .\" -.\" $Id: vdr.1 2.9 2012/09/01 13:40:49 kls Exp $ +.\" $Id: vdr.1 2.10 2013/02/07 14:40:46 kls Exp $ .\" .TH vdr 1 "10 Feb 2008" "1.6" "Video Disk Recorder" .SH NAME @@ -57,6 +57,15 @@ Run in daemon mode (implies \-\-no\-kbd). Use only the given DVB device (\fInum\fR = 0, 1, 2...). There may be several \fB\-D\fR options (by default all DVB devices will be used). .TP +.BI \-\-dirnames= path[,name[,enc]] +Set the maximum directory path length to \fIpath\fR (default is the maximum value +allowed on the system). If \fIname\fR is also given, it defines the maximum directory +name length (default is the maximum value allowed on the system). The optional +\fIenc\fR can be 0 or 1, and controls whether special characters in directory names +are encoded as hex values (default: 0). +The length of the video directory name and that of the actual recording directory is +subtracted from \fIpath\fR, to make sure the directory path will never become too long. +.TP .BI \-\-edit= rec Edit the given recording. \fIrec\fR must be the full path name of an existing recording. @@ -188,8 +197,7 @@ operation. allow coredumps if -u is given (only for debugging). .TP .BI \-\-vfat -Encode special characters in recording names to avoid problems -with VFAT file systems. +For backwards compatibility (same as \-\-dirnames= 250,40,1. .TP .BI \-v\ dir ,\ \-\-video= dir Use \fIdir\fR as video directory. diff --git a/vdr.c b/vdr.c index 752575d6..af5a7cfe 100644 --- a/vdr.c +++ b/vdr.c @@ -22,7 +22,7 @@ * * The project's page is at http://www.tvdr.de * - * $Id: vdr.c 2.46 2013/01/29 11:17:45 kls Exp $ + * $Id: vdr.c 2.47 2013/02/07 13:53:01 kls Exp $ */ #include @@ -227,6 +227,7 @@ int main(int argc, char *argv[]) { "config", required_argument, NULL, 'c' }, { "daemon", no_argument, NULL, 'd' }, { "device", required_argument, NULL, 'D' }, + { "dirnames", required_argument, NULL, 'd' | 0x100 }, { "edit", required_argument, NULL, 'e' | 0x100 }, { "epgfile", required_argument, NULL, 'E' }, { "filesize", required_argument, NULL, 'f' | 0x100 }, @@ -277,6 +278,44 @@ int main(int argc, char *argv[]) fprintf(stderr, "vdr: invalid DVB device number: %s\n", optarg); return 2; break; + case 'd' | 0x100: { + char *s = optarg; + int n = strtol(s, &s, 10); + if (n <= 0 || n >= PATH_MAX) { + fprintf(stderr, "vdr: invalid directory path length: %s\n", optarg); + return 2; + } + DirectoryPathMax = n; + if (!*s) + break; + if (*s++ != ',') { + fprintf(stderr, "vdr: invalid delimiter: %s\n", optarg); + return 2; + } + n = strtol(s, &s, 10); + if (n <= 0 || n >= NAME_MAX) { + fprintf(stderr, "vdr: invalid directory name length: %s\n", optarg); + return 2; + } + DirectoryNameMax = n; + if (!*s) + break; + if (*s++ != ',') { + fprintf(stderr, "vdr: invalid delimiter: %s\n", optarg); + return 2; + } + n = strtol(s, &s, 10); + if (n != 0 && n != 1) { + fprintf(stderr, "vdr: invalid directory encoding: %s\n", optarg); + return 2; + } + DirectoryEncoding = n; + if (*s) { + fprintf(stderr, "vdr: unexpected data: %s\n", optarg); + return 2; + } + } + break; case 'e' | 0x100: return CutRecording(optarg) ? 0 : 2; case 'E': EpgDataFileName = (*optarg != '-' ? optarg : NULL); @@ -384,7 +423,9 @@ int main(int argc, char *argv[]) case 'V': DisplayVersion = true; break; case 'v' | 0x100: - VfatFileSystem = true; + DirectoryPathMax = 250; + DirectoryNameMax = 40; + DirectoryEncoding = true; break; case 'v': VideoDirectory = optarg; while (optarg && *optarg && optarg[strlen(optarg) - 1] == '/') @@ -435,6 +476,13 @@ int main(int argc, char *argv[]) " -D NUM, --device=NUM use only the given DVB device (NUM = 0, 1, 2...)\n" " there may be several -D options (default: all DVB\n" " devices will be used)\n" + " --dirnames=PATH[,NAME[,ENC]]\n" + " set the maximum directory path length to PATH\n" + " (default: %d); if NAME is also given, it defines\n" + " the maximum directory name length (default: %d);\n" + " the optional ENC can be 0 or 1, and controls whether\n" + " special characters in directory names are encoded as\n" + " hex values (default: 0)\n" " --edit=REC cut recording REC and exit\n" " -E FILE, --epgfile=FILE write the EPG data into the given FILE (default is\n" " '%s' in the video directory)\n" @@ -477,13 +525,15 @@ int main(int argc, char *argv[]) " --userdump allow coredumps if -u is given (debugging)\n" " -v DIR, --video=DIR use DIR as video directory (default: %s)\n" " -V, --version print version information and exit\n" - " --vfat encode special characters in recording names to\n" - " avoid problems with VFAT file systems\n" + " --vfat for backwards compatibility (same as\n" + " --dirnames=250,40,1\n" " -w SEC, --watchdog=SEC activate the watchdog timer with a timeout of SEC\n" " seconds (default: %d); '0' disables the watchdog\n" "\n", DEFAULTCACHEDIR, DEFAULTCONFDIR, + PATH_MAX, + NAME_MAX, DEFAULTEPGDATAFILENAME, MAXVIDEOFILESIZEDEFAULT, DEFAULTPLUGINDIR,