Home

Dokumentation

Impressum

Dokumentation VDR
 

Main Page   Class Hierarchy   Alphabetical List   Data Structures   File List   Data Fields   Globals  

recording.c

Go to the documentation of this file.
00001 
00010 #include "recording.h"
00011 #include <errno.h>
00012 #include <fcntl.h>
00013 #include <stdio.h>
00014 #include <string.h>
00015 #include <sys/stat.h>
00016 #include <unistd.h>
00017 #include "channels.h"
00018 #include "i18n.h"
00019 #include "interface.h"
00020 #include "remux.h" //XXX+ I_FRAME
00021 #include "tools.h"
00022 #include "videodir.h"
00023 
00024 #define RECEXT       ".rec"
00025 #define DELEXT       ".del"
00026 /* This was the original code, which works fine in a Linux only environment.
00027    Unfortunately, because of windows and its brain dead file system, we have
00028    to use a more complicated approach, in order to allow users who have enabled
00029    the VFAT compile time option to see their recordings even if they forget to
00030    enable VFAT when compiling a new version of VDR... Gee, do I hate Windows.
00031    (kls 2002-07-27)
00032 #define DATAFORMAT   "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
00033 #define NAMEFORMAT   "%s/%s/" DATAFORMAT
00034 */
00038 #define DATAFORMAT   "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
00039 #ifdef VFAT
00040 #define nameFORMAT   "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT
00041 #else
00042 #define nameFORMAT   "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
00043 #endif
00044 #define NAMEFORMAT   "%s/%s/" nameFORMAT
00045 // end of implementation for brain dead systems
00046 
00047 #define RESUMEFILESUFFIX  "/resume.vdr"
00048 #define SUMMARYFILESUFFIX "/summary.vdr"
00049 #define MARKSFILESUFFIX   "/marks.vdr"
00050 
00051 #define FINDCMD      "find %s -follow -type d -name '%s' 2> /dev/null"
00052 
00056 #define MINDISKSPACE 1024 // MB
00057 
00061 #define DELETEDLIFETIME     1 // hours after which a deleted recording will be actually removed
00062 
00066 #define REMOVECHECKDELTA 3600 // seconds between checks for removing deleted files
00067 #define DISKCHECKDELTA    100 // seconds between checks for free disk space
00068 #define REMOVELATENCY      10 // seconds to wait until next check after removing a file
00069 
00070 #define TIMERMACRO_TITLE    "TITLE"
00071 #define TIMERMACRO_EPISODE  "EPISODE"
00072 
00073 void RemoveDeletedRecordings(void)
00074 {
00075   static time_t LastRemoveCheck = 0;
00076   if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
00077      // Make sure only one instance of VDR does this:
00078      cLockFile LockFile(VideoDirectory);
00079      if (!LockFile.Lock())
00080         return;
00081      // Remove the oldest file that has been "deleted":
00082      cRecordings Recordings;
00083      if (Recordings.Load(true)) {
00084         cRecording *r = Recordings.First();
00085         cRecording *r0 = r;
00086         while (r) {
00087               if (r->start < r0->start)
00088                  r0 = r;
00089               r = Recordings.Next(r);
00090               }
00091         if (r0 && time(NULL) - r0->start > DELETEDLIFETIME * 60) {
00092            r0->Remove();
00093            RemoveEmptyVideoDirectories();
00094            LastRemoveCheck += REMOVELATENCY;
00095            return;
00096            }
00097         }
00098      LastRemoveCheck = time(NULL);
00099      }
00100 }
00101 
00102 void AssertFreeDiskSpace(int Priority)
00103 {
00109   static time_t LastFreeDiskCheck = 0;
00110   if (time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA) {
00111      if (!VideoFileSpaceAvailable(MINDISKSPACE)) {
00112         // Make sure only one instance of VDR does this:
00113         cLockFile LockFile(VideoDirectory);
00114         if (!LockFile.Lock())
00115            return;
00116         // Remove the oldest file that has been "deleted":
00117         isyslog("low disk space while recording, trying to remove a deleted recording...");
00118         cRecordings Recordings;
00119         if (Recordings.Load(true)) {
00120            cRecording *r = Recordings.First();
00121            cRecording *r0 = r;
00122            while (r) {
00123                  if (r->start < r0->start)
00124                     r0 = r;
00125                  r = Recordings.Next(r);
00126                  }
00127            if (r0 && r0->Remove()) {
00128               LastFreeDiskCheck += REMOVELATENCY;
00129               return;
00130               }
00131            }
00132         // No "deleted" files to remove, so let's see if we can delete a recording:
00133         isyslog("...no deleted recording found, trying to delete an old recording...");
00134         if (Recordings.Load(false)) {
00135            cRecording *r = Recordings.First();
00136            cRecording *r0 = NULL;
00137            while (r) {
00138                  if (r->lifetime < MAXLIFETIME) { // recordings with MAXLIFETIME live forever
00139                     if ((r->lifetime == 0 && Priority > r->priority) || // the recording has no guaranteed lifetime and the new recording has higher priority
00140                         (time(NULL) - r->start) / SECSINDAY > r->lifetime) { // the recording's guaranteed lifetime has expired
00141                        if (r0) {
00142                           if (r->priority < r0->priority || (r->priority == r0->priority && r->start < r0->start))
00143                              r0 = r; // in any case we delete the one with the lowest priority (or the older one in case of equal priorities)
00144                           }
00145                        else
00146                           r0 = r;
00147                        }
00148                     }
00149                  r = Recordings.Next(r);
00150                  }
00151            if (r0 && r0->Delete())
00152               return;
00153            }
00154         // Unable to free disk space, but there's nothing we can do about that...
00155         isyslog("...no old recording found, giving up");
00156         Interface->Confirm(tr("Low disk space!"), 30);
00157         }
00158      LastFreeDiskCheck = time(NULL);
00159      }
00160 }
00161 
00166 cResumeFile::cResumeFile(const char *FileName)
00167 {
00168   fileName = MALLOC(char, strlen(FileName) + strlen(RESUMEFILESUFFIX) + 1);
00169   if (fileName) {
00170      strcpy(fileName, FileName);
00171      strcat(fileName, RESUMEFILESUFFIX);
00172      }
00173   else
00174      esyslog("ERROR: can't allocate memory for resume file name");
00175 }
00176 
00177 cResumeFile::~cResumeFile()
00178 {
00179   free(fileName);
00180 }
00181 
00182 int cResumeFile::Read(void)
00183 {
00184   int resume = -1;
00185   if (fileName) {
00186      int f = open(fileName, O_RDONLY);
00187      if (f >= 0) {
00188         if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) {
00189            resume = -1;
00190            LOG_ERROR_STR(fileName);
00191            }
00192         close(f);
00193         }
00194      else if (errno != ENOENT)
00195         LOG_ERROR_STR(fileName);
00196      }
00197   return resume;
00198 }
00199 
00200 bool cResumeFile::Save(int Index)
00201 {
00202   if (fileName) {
00203      int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
00204      if (f >= 0) {
00205         if (safe_write(f, &Index, sizeof(Index)) < 0)
00206            LOG_ERROR_STR(fileName);
00207         close(f);
00208         return true;
00209         }
00210      }
00211   return false;
00212 }
00213 
00214 void cResumeFile::Delete(void)
00215 {
00216   if (fileName) {
00217      if (remove(fileName) < 0 && errno != ENOENT)
00218         LOG_ERROR_STR(fileName);
00219      }
00220 }
00221 
00226 #define RESUME_NOT_INITIALIZED (-2)
00227 
00228 struct tCharExchange { char a; char b; };
00229 tCharExchange CharExchange[] = {
00230   { '~',  '/'    },
00231   { ' ',  '_'    },
00232   { '\'', '\x01' },
00233   { '/',  '\x02' },
00234   { 0, 0 }
00235   };
00236 
00237 static char *ExchangeChars(char *s, bool ToFileSystem)
00238 {
00239   char *p = s;
00240   while (*p) {
00241 #ifdef VFAT
00242         // The VFAT file system can't handle all characters, so we
00243         // have to take extra efforts to encode/decode them:
00244         if (ToFileSystem) {
00245            switch (*p) {
00246                   // characters that can be used "as is":
00247                   case '!':
00248                   case '@':
00249                   case '$':
00250                   case '%':
00251                   case '&':
00252                   case '(':
00253                   case ')':
00254                   case '+':
00255                   case ',':
00256                   case '-':
00257                   case ';':
00258                   case '=':
00259                   case '0' ... '9':
00260                   case 'a' ... 'z':
00261                   case 'A' ... 'Z':
00262                   case 'ä': case 'Ä':
00263                   case 'ö': case 'Ö':
00264                   case 'ü': case 'Ü':
00265                   case 'ß':
00266                        break;
00267                   // characters that can be mapped to other characters:
00268                   case ' ': *p = '_'; break;
00269                   case '~': *p = '/'; break;
00270                   // characters that have to be encoded:
00271                   default:
00272                     if (*p != '.' || !*(p + 1) || *(p + 1) == '~') { // Windows can't handle '.' at the end of directory names
00273                        int l = p - s;
00274                        s = (char *)realloc(s, strlen(s) + 10);
00275                        p = s + l;
00276                        char buf[4];
00277                        sprintf(buf, "#%02X", (unsigned char)*p);
00278                        memmove(p + 2, p, strlen(p) + 1);
00279                        strncpy(p, buf, 3);
00280                        p += 2;
00281                        }
00282                   }
00283            }
00284         else {
00285            switch (*p) {
00286              // mapped characters:
00287              case '_': *p = ' '; break;
00288              case '/': *p = '~'; break;
00289              // encodes characters:
00290              case '#': {
00291                   if (strlen(p) > 2) {
00292                      char buf[3];
00293                      sprintf(buf, "%c%c", *(p + 1), *(p + 2));
00294                      unsigned char c = strtol(buf, NULL, 16);
00295                      *p = c;
00296                      memmove(p + 1, p + 3, strlen(p) - 2);
00297                      }
00298                   }
00299                   break;
00300              // backwards compatibility:
00301              case '\x01': *p = '\''; break;
00302              case '\x02': *p = '/';  break;
00303              case '\x03': *p = ':';  break;
00304              }
00305            }
00306 #else
00307         for (struct tCharExchange *ce = CharExchange; ce->a && ce->b; ce++) {
00308             if (*p == (ToFileSystem ? ce->a : ce->b)) {
00309                *p = ToFileSystem ? ce->b : ce->a;
00310                break;
00311                }
00312             }
00313 #endif
00314         p++;
00315         }
00316   return s;
00317 }
00318 
00319 cRecording::cRecording(cTimer *Timer, const char *Title, const char *Subtitle, const char *Summary)
00320 {
00321   resume = RESUME_NOT_INITIALIZED;
00322   titleBuffer = NULL;
00323   sortBuffer = NULL;
00324   fileName = NULL;
00325   name = NULL;
00326   // set up the actual name:
00327   if (isempty(Title))
00328      Title = Timer->Channel()->Name();
00329   if (isempty(Subtitle))
00330      Subtitle = " ";
00331   char *macroTITLE   = strstr(Timer->File(), TIMERMACRO_TITLE);
00332   char *macroEPISODE = strstr(Timer->File(), TIMERMACRO_EPISODE);
00333   if (macroTITLE || macroEPISODE) {
00334      name = strdup(Timer->File());
00335      name = strreplace(name, TIMERMACRO_TITLE, Title);
00336      name = strreplace(name, TIMERMACRO_EPISODE, Subtitle);
00337      if (Timer->IsSingleEvent()) {
00338         Timer->SetFile(name); // this was an instant recording, so let's set the actual data
00339         Timers.Save();
00340         }
00341      }
00342   else if (Timer->IsSingleEvent() || !Setup.UseSubtitle)
00343      name = strdup(Timer->File());
00344   else
00345      asprintf(&name, "%s~%s", Timer->File(), Subtitle);
00346   // substitute characters that would cause problems in file names:
00347   strreplace(name, '\n', ' ');
00348   start = Timer->StartTime();
00349   priority = Timer->Priority();
00350   lifetime = Timer->Lifetime();
00351   // handle summary:
00352   summary = !isempty(Timer->Summary()) ? strdup(Timer->Summary()) : NULL;
00353   if (!summary) {
00354      if (isempty(Subtitle))
00355         Subtitle = "";
00356      if (isempty(Summary))
00357         Summary = "";
00358      if (*Subtitle || *Summary)
00359         asprintf(&summary, "%s\n\n%s%s%s", Title, Subtitle, (*Subtitle && *Summary) ? "\n\n" : "", Summary);
00360      }
00361 }
00362 
00363 cRecording::cRecording(const char *FileName)
00364 {
00365   resume = RESUME_NOT_INITIALIZED;
00366   titleBuffer = NULL;
00367   sortBuffer = NULL;
00368   fileName = strdup(FileName);
00369   FileName += strlen(VideoDirectory) + 1;
00370   char *p = strrchr(FileName, '/');
00371 
00372   name = NULL;
00373   summary = NULL;
00374   if (p) {
00375      time_t now = time(NULL);
00376      struct tm tm_r;
00377      struct tm t = *localtime_r(&now, &tm_r); // this initializes the time zone in 't'
00378      t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
00379      if (7 == sscanf(p + 1, DATAFORMAT, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) {
00380         t.tm_year -= 1900;
00381         t.tm_mon--;
00382         t.tm_sec = 0;
00383         start = mktime(&t);
00384         name = MALLOC(char, p - FileName + 1);
00385         strncpy(name, FileName, p - FileName);
00386         name[p - FileName] = 0;
00387         name = ExchangeChars(name, false);
00388         }
00389      // read an optional summary file:
00390      char *SummaryFileName = NULL;
00391      asprintf(&SummaryFileName, "%s%s", fileName, SUMMARYFILESUFFIX);
00392      int f = open(SummaryFileName, O_RDONLY);
00393      if (f >= 0) {
00394         struct stat buf;
00395         if (fstat(f, &buf) == 0) {
00396            int size = buf.st_size;
00397            summary = MALLOC(char, size + 1); // +1 for terminating 0
00398            if (summary) {
00399               int rbytes = safe_read(f, summary, size);
00400               if (rbytes >= 0) {
00401                  summary[rbytes] = 0;
00402                  if (rbytes != size)
00403                     esyslog("%s: expected %d bytes but read %d", SummaryFileName, size, rbytes);
00404                  }
00405               else {
00406                  LOG_ERROR_STR(SummaryFileName);
00407                  free(summary);
00408                  summary = NULL;
00409                  }
00410 
00411               }
00412            else
00413               esyslog("can't allocate %d byte of memory for summary file '%s'", size + 1, SummaryFileName);
00414            close(f);
00415            }
00416         else
00417            LOG_ERROR_STR(SummaryFileName);
00418         }
00419      else if (errno != ENOENT)
00420         LOG_ERROR_STR(SummaryFileName);
00421      free(SummaryFileName);
00422      }
00423 }
00424 
00425 cRecording::~cRecording()
00426 {
00427   free(titleBuffer);
00428   free(sortBuffer);
00429   free(fileName);
00430   free(name);
00431   free(summary);
00432 }
00433 
00434 char *cRecording::StripEpisodeName(char *s)
00435 {
00436   char *t = s, *s1 = NULL, *s2 = NULL;
00437   while (*t) {
00438         if (*t == '/') {
00439            if (s1) {
00440               if (s2)
00441                  s1 = s2;
00442               s2 = t;
00443               }
00444            else
00445               s1 = t;
00446            }
00447         t++;
00448         }
00449   if (s1 && s2)
00450      memmove(s1 + 1, s2, t - s2 + 1);
00451   return s;
00452 }
00453 
00454 char *cRecording::SortName(void)
00455 {
00456   if (!sortBuffer) {
00457      char *s = StripEpisodeName(strdup(FileName() + strlen(VideoDirectory) + 1));
00458      int l = strxfrm(NULL, s, 0);
00459      sortBuffer = MALLOC(char, l);
00460      strxfrm(sortBuffer, s, l);
00461      free(s);
00462      }
00463   return sortBuffer;
00464 }
00465 
00466 int cRecording::GetResume(void)
00467 {
00468   if (resume == RESUME_NOT_INITIALIZED) {
00469      cResumeFile ResumeFile(FileName());
00470      resume = ResumeFile.Read();
00471      }
00472   return resume;
00473 }
00474 
00475 bool cRecording::operator< (const cListObject &ListObject)
00476 {
00477   cRecording *r = (cRecording *)&ListObject;
00478   return strcasecmp(SortName(), r->SortName()) < 0;
00479 }
00480 
00481 const char *cRecording::FileName(void)
00482 {
00483   if (!fileName) {
00484      struct tm tm_r;
00485      struct tm *t = localtime_r(&start, &tm_r);
00486      name = ExchangeChars(name, true);
00487      asprintf(&fileName, NAMEFORMAT, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, priority, lifetime);
00488      name = ExchangeChars(name, false);
00489      }
00490   return fileName;
00491 }
00492 
00493 const char *cRecording::Title(char Delimiter, bool NewIndicator, int Level)
00494 {
00495   char New = NewIndicator && IsNew() ? '*' : ' ';
00496   free(titleBuffer);
00497   titleBuffer = NULL;
00498   if (Level < 0 || Level == HierarchyLevels()) {
00499      struct tm tm_r;
00500      struct tm *t = localtime_r(&start, &tm_r);
00501      char *s;
00502      if (Level > 0 && (s = strrchr(name, '~')) != NULL)
00503         s++;
00504      else
00505         s = name;
00506      asprintf(&titleBuffer, "%02d.%02d%c%02d:%02d%c%c%s",
00507                             t->tm_mday,
00508                             t->tm_mon + 1,
00509                             Delimiter,
00510                             t->tm_hour,
00511                             t->tm_min,
00512                             New,
00513                             Delimiter,
00514                             s);
00515      // let's not display a trailing '~':
00516      stripspace(titleBuffer);
00517      s = &titleBuffer[strlen(titleBuffer) - 1];
00518      if (*s == '~')
00519         *s = 0;
00520      }
00521   else if (Level < HierarchyLevels()) {
00522      const char *s = name;
00523      const char *p = s;
00524      while (*++s) {
00525            if (*s == '~') {
00526               if (Level--)
00527                  p = s + 1;
00528               else
00529                  break;
00530               }
00531            }
00532      titleBuffer = MALLOC(char, s - p + 3);
00533      *titleBuffer = Delimiter;
00534      *(titleBuffer + 1) = Delimiter;
00535      strn0cpy(titleBuffer + 2, p, s - p + 1);
00536      }
00537   else
00538      return "";
00539   return titleBuffer;
00540 }
00541 
00542 const char *cRecording::PrefixFileName(char Prefix)
00543 {
00544   const char *p = PrefixVideoFileName(FileName(), Prefix);
00545   if (p) {
00546      free(fileName);
00547      fileName = strdup(p);
00548      return fileName;
00549      }
00550   return NULL;
00551 }
00552 
00553 int cRecording::HierarchyLevels(void)
00554 {
00555   const char *s = name;
00556   int level = 0;
00557   while (*++s) {
00558         if (*s == '~')
00559            level++;
00560         }
00561   return level;
00562 }
00563 
00564 bool cRecording::WriteSummary(void)
00565 {
00566   if (summary) {
00567      char *SummaryFileName = NULL;
00568      asprintf(&SummaryFileName, "%s%s", fileName, SUMMARYFILESUFFIX);
00569      FILE *f = fopen(SummaryFileName, "w");
00570      if (f) {
00571         if (fputs(summary, f) < 0)
00572            LOG_ERROR_STR(SummaryFileName);
00573         fclose(f);
00574         }
00575      else
00576         LOG_ERROR_STR(SummaryFileName);
00577      free(SummaryFileName);
00578      }
00579   return true;
00580 }
00581 
00582 bool cRecording::Delete(void)
00583 {
00584   bool result = true;
00585   char *NewName = strdup(FileName());
00586   char *ext = strrchr(NewName, '.');
00587   if (strcmp(ext, RECEXT) == 0) {
00588      strncpy(ext, DELEXT, strlen(ext));
00589      if (access(NewName, F_OK) == 0) {
00590         // the new name already exists, so let's remove that one first:
00591         isyslog("removing recording %s", NewName);
00592         RemoveVideoFile(NewName);
00593         }
00594      isyslog("deleting recording %s", FileName());
00595      result = RenameVideoFile(FileName(), NewName);
00596      }
00597   free(NewName);
00598   return result;
00599 }
00600 
00601 bool cRecording::Remove(void)
00602 {
00603   // let's do a final safety check here:
00604   if (!endswith(FileName(), DELEXT)) {
00605      esyslog("attempt to remove recording %s", FileName());
00606      return false;
00607      }
00608   isyslog("removing recording %s", FileName());
00609   return RemoveVideoFile(FileName());
00610 }
00611 
00612 // --- cRecordings -----------------------------------------------------------
00613 
00614 bool cRecordings::Load(bool Deleted)
00615 {
00616   Clear();
00617   bool result = false;
00618   char *cmd = NULL;
00619   asprintf(&cmd, FINDCMD, VideoDirectory, Deleted ? "*" DELEXT : "*" RECEXT);
00620   FILE *p = popen(cmd, "r");
00621   if (p) {
00622      char *s;
00623      while ((s = readline(p)) != NULL) {
00624            cRecording *r = new cRecording(s);
00625            if (r->Name())
00626               Add(r);
00627            else
00628               delete r;
00629            }
00630      pclose(p);
00631      Sort();
00632      result = Count() > 0;
00633      }
00634   else
00635      Interface->Error("Error while opening pipe!");
00636   free(cmd);
00637   return result;
00638 }
00639 
00640 cRecording *cRecordings::GetByName(const char *FileName)
00641 {
00642   for (cRecording *recording = First(); recording; recording = Next(recording)) {
00643       if (strcmp(recording->FileName(), FileName) == 0)
00644          return recording;
00645       }
00646   return NULL;
00647 }
00648 
00649 // --- cMark -----------------------------------------------------------------
00650 
00651 char *cMark::buffer = NULL;
00652 
00653 cMark::cMark(int Position, const char *Comment)
00654 {
00655   position = Position;
00656   comment = Comment ? strdup(Comment) : NULL;
00657 }
00658 
00659 cMark::~cMark()
00660 {
00661   free(comment);
00662 }
00663 
00664 const char *cMark::ToText(void)
00665 {
00666   free(buffer);
00667   asprintf(&buffer, "%s%s%s\n", IndexToHMSF(position, true), comment ? " " : "", comment ? comment : "");
00668   return buffer;
00669 }
00670 
00671 bool cMark::Parse(const char *s)
00672 {
00673   free(comment);
00674   comment = NULL;
00675   position = HMSFToIndex(s);
00676   const char *p = strchr(s, ' ');
00677   if (p) {
00678      p = skipspace(p);
00679      if (*p) {
00680         comment = strdup(p);
00681         comment[strlen(comment) - 1] = 0; // strips trailing newline
00682         }
00683      }
00684   return true;
00685 }
00686 
00687 bool cMark::Save(FILE *f)
00688 {
00689   return fprintf(f, ToText()) > 0;
00690 }
00691 
00692 // --- cMarks ----------------------------------------------------------------
00693 
00694 bool cMarks::Load(const char *RecordingFileName)
00695 {
00696   const char *MarksFile = AddDirectory(RecordingFileName, MARKSFILESUFFIX);
00697   if (cConfig<cMark>::Load(MarksFile)) {
00698      Sort();
00699      return true;
00700      }
00701   return false;
00702 }
00703 
00704 void cMarks::Sort(void)
00705 {
00706   for (cMark *m1 = First(); m1; m1 = Next(m1)) {
00707       for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
00708           if (m2->position < m1->position) {
00709              swap(m1->position, m2->position);
00710              swap(m1->comment, m2->comment);
00711              }
00712           }
00713       }
00714 }
00715 
00716 cMark *cMarks::Add(int Position)
00717 {
00718   cMark *m = Get(Position);
00719   if (!m) {
00720      cConfig<cMark>::Add(m = new cMark(Position));
00721      Sort();
00722      }
00723   return m;
00724 }
00725 
00726 cMark *cMarks::Get(int Position)
00727 {
00728   for (cMark *mi = First(); mi; mi = Next(mi)) {
00729       if (mi->position == Position)
00730          return mi;
00731       }
00732   return NULL;
00733 }
00734 
00735 cMark *cMarks::GetPrev(int Position)
00736 {
00737   for (cMark *mi = Last(); mi; mi = Prev(mi)) {
00738       if (mi->position < Position)
00739          return mi;
00740       }
00741   return NULL;
00742 }
00743 
00744 cMark *cMarks::GetNext(int Position)
00745 {
00746   for (cMark *mi = First(); mi; mi = Next(mi)) {
00747       if (mi->position > Position)
00748          return mi;
00749       }
00750   return NULL;
00751 }
00752 
00753 // --- cRecordingUserCommand -------------------------------------------------
00754 
00755 const char *cRecordingUserCommand::command = NULL;
00756 
00757 void cRecordingUserCommand::InvokeCommand(const char *State, const char *RecordingFileName)
00758 {
00759   if (command) {
00760      char *cmd;
00761      asprintf(&cmd, "%s %s \"%s\"", command, State, strescape(RecordingFileName, "\"$"));
00762      isyslog("executing '%s'", cmd);
00763      SystemExec(cmd);
00764      free(cmd);
00765      }
00766 }
00767 
00768 // --- XXX+
00769 
00770 //XXX+ somewhere else???
00771 // --- cIndexFile ------------------------------------------------------------
00772 
00773 #define INDEXFILESUFFIX     "/index.vdr"
00774 
00775 // The maximum time to wait before giving up while catching up on an index file:
00776 #define MAXINDEXCATCHUP   2 // seconds
00777 
00778 // The minimum age of an index file for considering it no longer to be written:
00779 #define MININDEXAGE      10 // seconds
00780 
00781 cIndexFile::cIndexFile(const char *FileName, bool Record)
00782 :resumeFile(FileName)
00783 {
00784   f = -1;
00785   fileName = NULL;
00786   size = 0;
00787   last = -1;
00788   index = NULL;
00789   if (FileName) {
00790      fileName = MALLOC(char, strlen(FileName) + strlen(INDEXFILESUFFIX) + 1);
00791      if (fileName) {
00792         strcpy(fileName, FileName);
00793         char *pFileExt = fileName + strlen(fileName);
00794         strcpy(pFileExt, INDEXFILESUFFIX);
00795         int delta = 0;
00796         if (access(fileName, R_OK) == 0) {
00797            struct stat buf;
00798            if (stat(fileName, &buf) == 0) {
00799               delta = buf.st_size % sizeof(tIndex);
00800               if (delta) {
00801                  delta = sizeof(tIndex) - delta;
00802                  esyslog("ERROR: invalid file size (%ld) in '%s'", buf.st_size, fileName);
00803                  }
00804               last = (buf.st_size + delta) / sizeof(tIndex) - 1;
00805               if (!Record && last >= 0) {
00806                  size = last + 1;
00807                  index = MALLOC(tIndex, size);
00808                  if (index) {
00809                     f = open(fileName, O_RDONLY);
00810                     if (f >= 0) {
00811                        if ((int)safe_read(f, index, buf.st_size) != buf.st_size) {
00812                           esyslog("ERROR: can't read from file '%s'", fileName);
00813                           free(index);
00814                           index = NULL;
00815                           close(f);
00816                           f = -1;
00817                           }
00818                        // we don't close f here, see CatchUp()!
00819                        }
00820                     else
00821                        LOG_ERROR_STR(fileName);
00822                     }
00823                  else
00824                     esyslog("ERROR: can't allocate %d bytes for index '%s'", size * sizeof(tIndex), fileName);
00825                  }
00826               }
00827            else
00828               LOG_ERROR;
00829            }
00830         else if (!Record)
00831            isyslog("missing index file %s", fileName);
00832         if (Record) {
00833            if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) >= 0) {
00834               if (delta) {
00835                  esyslog("ERROR: padding index file with %d '0' bytes", delta);
00836                  while (delta--)
00837                        writechar(f, 0);
00838                  }
00839               }
00840            else
00841               LOG_ERROR_STR(fileName);
00842            }
00843         }
00844      else
00845         esyslog("ERROR: can't copy file name '%s'", FileName);
00846      }
00847 }
00848 
00849 cIndexFile::~cIndexFile()
00850 {
00851   if (f >= 0)
00852      close(f);
00853   free(fileName);
00854   free(index);
00855 }
00856 
00857 bool cIndexFile::CatchUp(int Index)
00858 {
00859   if (index && f >= 0) {
00860      for (int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >= last); i++) {
00861          struct stat buf;
00862          if (fstat(f, &buf) == 0) {
00863             if (time(NULL) - buf.st_mtime > MININDEXAGE) {
00864                // apparently the index file is not being written any more
00865                close(f);
00866                f = -1;
00867                return false;
00868                }
00869             int newLast = buf.st_size / sizeof(tIndex) - 1;
00870             if (newLast > last) {
00871                if (size <= newLast) {
00872                   size *= 2;
00873                   if (size <= newLast)
00874                      size = newLast + 1;
00875                   }
00876                index = (tIndex *)realloc(index, size * sizeof(tIndex));
00877                if (index) {
00878                   int offset = (last + 1) * sizeof(tIndex);
00879                   int delta = (newLast - last) * sizeof(tIndex);
00880                   if (lseek(f, offset, SEEK_SET) == offset) {
00881                      if (safe_read(f, &index[last + 1], delta) != delta) {
00882                         esyslog("ERROR: can't read from index");
00883                         free(index);
00884                         index = NULL;
00885                         close(f);
00886                         f = -1;
00887                         break;
00888                         }
00889                      last = newLast;
00890                      }
00891                   else
00892                      LOG_ERROR_STR(fileName);
00893                   }
00894                else
00895                   esyslog("ERROR: can't realloc() index");
00896                }
00897             }
00898          else
00899             LOG_ERROR_STR(fileName);
00900          if (Index >= last)
00901             sleep(1);
00902          else
00903             return true;
00904          }
00905      }
00906   return false;
00907 }
00908 
00909 bool cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset)
00910 {
00911   if (f >= 0) {
00912      tIndex i = { FileOffset, PictureType, FileNumber, 0 };
00913      if (safe_write(f, &i, sizeof(i)) < 0) {
00914         LOG_ERROR_STR(fileName);
00915         close(f);
00916         f = -1;
00917         return false;
00918         }
00919      last++;
00920      }
00921   return f >= 0;
00922 }
00923 
00924 bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType, int *Length)
00925 {
00926   if (index) {
00927      CatchUp(Index);
00928      if (Index >= 0 && Index < last) {
00929         *FileNumber = index[Index].number;
00930         *FileOffset = index[Index].offset;
00931         if (PictureType)
00932            *PictureType = index[Index].type;
00933         if (Length) {
00934            int fn = index[Index + 1].number;
00935            int fo = index[Index + 1].offset;
00936            if (fn == *FileNumber)
00937               *Length = fo - *FileOffset;
00938            else
00939               *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly)
00940            }
00941         return true;
00942         }
00943      }
00944   return false;
00945 }
00946 
00947 int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length, bool StayOffEnd)
00948 {
00949   if (index) {
00950      CatchUp();
00951      int d = Forward ? 1 : -1;
00952      for (;;) {
00953          Index += d;
00954          if (Index >= 0 && Index < last - ((Forward && StayOffEnd) ? 100 : 0)) {
00955             if (index[Index].type == I_FRAME) {
00956                if (FileNumber)
00957                   *FileNumber = index[Index].number;
00958                else
00959                   FileNumber = &index[Index].number;
00960                if (FileOffset)
00961                   *FileOffset = index[Index].offset;
00962                else
00963                   FileOffset = &index[Index].offset;
00964                if (Length) {
00965                   // all recordings end with a non-I_FRAME, so the following should be safe:
00966                   int fn = index[Index + 1].number;
00967                   int fo = index[Index + 1].offset;
00968                   if (fn == *FileNumber)
00969                      *Length = fo - *FileOffset;
00970                   else {
00971                      esyslog("ERROR: 'I' frame at end of file #%d", *FileNumber);
00972                      *Length = -1;
00973                      }
00974                   }
00975                return Index;
00976                }
00977             }
00978          else
00979             break;
00980          }
00981      }
00982   return -1;
00983 }
00984 
00985 int cIndexFile::Get(uchar FileNumber, int FileOffset)
00986 {
00987   if (index) {
00988      CatchUp();
00989      //TODO implement binary search!
00990      int i;
00991      for (i = 0; i < last; i++) {
00992          if (index[i].number > FileNumber || (index[i].number == FileNumber) && index[i].offset >= FileOffset)
00993             break;
00994          }
00995      return i;
00996      }
00997   return -1;
00998 }
00999 
01000 // --- cFileName -------------------------------------------------------------
01001 
01002 #include <errno.h>
01003 #include <unistd.h>
01004 #include "videodir.h"
01005 
01006 #define MAXFILESPERRECORDING 255
01007 #define RECORDFILESUFFIX    "/%03d.vdr"
01008 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
01009 
01010 cFileName::cFileName(const char *FileName, bool Record, bool Blocking)
01011 {
01012   file = -1;
01013   fileNumber = 0;
01014   record = Record;
01015   blocking = Blocking;
01016   // Prepare the file name:
01017   fileName = MALLOC(char, strlen(FileName) + RECORDFILESUFFIXLEN);
01018   if (!fileName) {
01019      esyslog("ERROR: can't copy file name '%s'", fileName);
01020      return;
01021      }
01022   strcpy(fileName, FileName);
01023   pFileNumber = fileName + strlen(fileName);
01024   SetOffset(1);
01025 }
01026 
01027 cFileName::~cFileName()
01028 {
01029   Close();
01030   free(fileName);
01031 }
01032 
01033 int cFileName::Open(void)
01034 {
01035   if (file < 0) {
01036      int BlockingFlag = blocking ? 0 : O_NONBLOCK;
01037      if (record) {
01038         dsyslog("recording to '%s'", fileName);
01039         file = OpenVideoFile(fileName, O_RDWR | O_CREAT | BlockingFlag);
01040         if (file < 0)
01041            LOG_ERROR_STR(fileName);
01042         }
01043      else {
01044         if (access(fileName, R_OK) == 0) {
01045            dsyslog("playing '%s'", fileName);
01046            file = open(fileName, O_RDONLY | BlockingFlag);
01047            if (file < 0)
01048               LOG_ERROR_STR(fileName);
01049            }
01050         else if (errno != ENOENT)
01051            LOG_ERROR_STR(fileName);
01052         }
01053      }
01054   return file;
01055 }
01056 
01057 void cFileName::Close(void)
01058 {
01059   if (file >= 0) {
01060      if ((record && CloseVideoFile(file) < 0) || (!record && close(file) < 0))
01061         LOG_ERROR_STR(fileName);
01062      file = -1;
01063      }
01064 }
01065 
01066 int cFileName::SetOffset(int Number, int Offset)
01067 {
01068   if (fileNumber != Number)
01069      Close();
01070   if (0 < Number && Number <= MAXFILESPERRECORDING) {
01071      fileNumber = Number;
01072      sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber);
01073      if (record) {
01074         if (access(fileName, F_OK) == 0) // file exists, let's try next suffix
01075            return SetOffset(Number + 1);
01076         else if (errno != ENOENT) { // something serious has happened
01077            LOG_ERROR_STR(fileName);
01078            return -1;
01079            }
01080         // found a non existing file suffix
01081         }
01082      if (Open() >= 0) {
01083         if (!record && Offset >= 0 && lseek(file, Offset, SEEK_SET) != Offset) {
01084            LOG_ERROR_STR(fileName);
01085            return -1;
01086            }
01087         }
01088      return file;
01089      }
01090   esyslog("ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING);
01091   return -1;
01092 }
01093 
01094 int cFileName::NextFile(void)
01095 {
01096   return SetOffset(fileNumber + 1);
01097 }
01098 
01099 // --- Index stuff -----------------------------------------------------------
01100 
01101 const char *IndexToHMSF(int Index, bool WithFrame)
01102 {
01103   static char buffer[16];
01104   int f = (Index % FRAMESPERSEC) + 1;
01105   int s = (Index / FRAMESPERSEC);
01106   int m = s / 60 % 60;
01107   int h = s / 3600;
01108   s %= 60;
01109   snprintf(buffer, sizeof(buffer), WithFrame ? "%d:%02d:%02d.%02d" : "%d:%02d:%02d", h, m, s, f);
01110   return buffer;
01111 }
01112 
01113 int HMSFToIndex(const char *HMSF)
01114 {
01115   int h, m, s, f = 0;
01116   if (3 <= sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f))
01117      return (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1;
01118   return 0;
01119 }
01120 
01121 int SecondsToFrames(int Seconds)
01122 {
01123   return Seconds * FRAMESPERSEC;
01124 }
01125 
01126 // --- ReadFrame -------------------------------------------------------------
01127 
01128 int ReadFrame(int f, uchar *b, int Length, int Max)
01129 {
01130   if (Length == -1)
01131      Length = Max; // this means we read up to EOF (see cIndex)
01132   else if (Length > Max) {
01133      esyslog("ERROR: frame larger than buffer (%d > %d)", Length, Max);
01134      Length = Max;
01135      }
01136   int r = safe_read(f, b, Length);
01137   if (r < 0)
01138      LOG_ERROR;
01139   return r;
01140 }
01141 
01142 

Generated on Wed Feb 5 23:30:11 2003 for VDR by doxygen1.3-rc2