/*
 * See the README file for copyright information and how to reach the author.
 */

#include <sys/statvfs.h>
#include <string>
#include <sstream>
#include <iomanip>
#include <fstream>
#include <typeinfo>
#include <vdr/interface.h>
#include <vdr/videodir.h>
#include <vdr/status.h>
#include <vdr/osdbase.h>
#include <vdr/plugin.h>
#include <vdr/cutter.h>
#include <vdr/menu.h>
#include <vdr/menuitems.h>
#include <vdr/recording.h>
#include "myreplaycontrol.h"
#include "mymenu.h"
#include "mysetup.h"
#include "mytools.h"

#define DISKSPACECHEK     5 // seconds between disk space checks
#define RECEXT       ".rec"
#define DELEXT       ".del"

#define osUserRecRenamed osUser1
#define osUserRecMoved   osUser2
#define osUserRecRemoved osUser3
#define osUserRecEmpty   osUser4
#define osUserRecCopied  osUser5
#define osUserBraked     osUser6

int Ebene = 0;

using namespace std;

static bool UndeleteRecording(const cRecording *Recording)
{
  bool result = true;
  cString NewName = Recording->FileName();
  char *ext = (char *)strrchr(NewName, '.');
  if (strcmp(ext, DELEXT) == 0) {
     strncpy(ext, RECEXT, strlen(ext));
     while (access(NewName, F_OK) == 0) {
        // the new name already exists
        cString p = cVideoDirectory::PrefixVideoFileName(NewName, '!');
        if (*p)
           NewName = p;
        }
     isyslog("restoring deleted recording %s", Recording->FileName());
     if (access(Recording->FileName(), F_OK) == 0) {
        result = cVideoDirectory::RenameVideoFile(Recording->FileName(), NewName);
        if (result) {
           LOCK_RECORDINGS_WRITE;
           Recordings->AddByName(NewName);
           }
        }
     else {
        isyslog("deleted recording '%s' vanished", Recording->FileName());
        result = false;
        }
     }
  return result;
}

// --- myMenuFolderItem ------------------------------------------------------

class myMenuFolderItem : public cOsdItem {
private:
  cNestedItem *folder;
public:
  virtual void Set(void);
  myMenuFolderItem(cNestedItem *Folder);
  cNestedItem *Folder(void) { return folder; }
  };

myMenuFolderItem::myMenuFolderItem(cNestedItem *Folder)
:cOsdItem(Folder->Text())
{
  folder = Folder;
  Set();
}

void myMenuFolderItem::Set(void)
{
  if (folder->SubItems() && folder->SubItems()->Count())
     SetText(cString::sprintf("%s...", folder->Text()));
  else
     SetText(folder->Text());
}

// --- myMenuEditFolder ------------------------------------------------------

class myMenuEditFolder : public cOsdMenu {
private:
  cList<cNestedItem> *list;
  cNestedItem *folder;
  char name[PATH_MAX];
  eOSState Confirm(void);
public:
  myMenuEditFolder(const char *Dir, cList<cNestedItem> *List, cNestedItem *Folder = NULL);
  cString GetFolder(void);
  virtual eOSState ProcessKey(eKeys Key);
  };

myMenuEditFolder::myMenuEditFolder(const char *Dir, cList<cNestedItem> *List, cNestedItem *Folder)
:cOsdMenu(Folder ? trVDR("Edit folder") : trVDR("New folder"), 12)
{
  SetMenuCategory(mcFolder);
  list = List;
  folder = Folder;
  if (folder)
     strn0cpy(name, folder->Text(), sizeof(name));
  else {
     *name = 0;
     cRemote::Put(kRight, true); // go right into string editing mode
     }
  if (!isempty(Dir)) {
     cOsdItem *DirItem = new cOsdItem(Dir);
     DirItem->SetSelectable(false);
     Add(DirItem);
     }
  Add(new cMenuEditStrItem( trVDR("Name"), name, sizeof(name)));
}

cString myMenuEditFolder::GetFolder(void)
{
  return folder ? folder->Text() : "";
}

eOSState myMenuEditFolder::Confirm(void)
{
  if (!folder || strcmp(folder->Text(), name) != 0) {
     // each name may occur only once in a folder list
     for (cNestedItem *Folder = list->First(); Folder; Folder = list->Next(Folder)) {
        if (strcmp(Folder->Text(), name) == 0) {
           Skins.Message(mtError, trVDR("Folder name already exists!"));
           return osContinue;
           }
        }
     char *p = strpbrk(name, "\\{}#~"); // FOLDERDELIMCHAR
     if (p) {
        Skins.Message(mtError, cString::sprintf(trVDR("Folder name must not contain '%c'!"), *p));
        return osContinue;
        }
     }
  if (folder)
     folder->SetText(name);
  else
     list->Add(folder = new cNestedItem(name));
  return osEnd;
}

eOSState myMenuEditFolder::ProcessKey(eKeys Key)
{
  eOSState state = cOsdMenu::ProcessKey(Key);

  if (state == osUnknown) {
     switch (Key) {
       case kOk:     return Confirm();
       case kRed:
       case kGreen:
       case kYellow:
       case kBlue:   return osContinue;
       default: break;
       }
     }
  return state;
}

// --- myMenuFolder -----------------------------------------------------------

myMenuFolder::myMenuFolder(const char *Title, cNestedItemList *NestedItemList, const char *Path, int WhatToDo)
:cOsdMenu(Title)
{
  SetMenuCategory(mcFolder);
  list = nestedItemList = NestedItemList;
  firstFolder = NULL;
  editing = false;
  helpKeys = -1;
  whatToDo = WhatToDo;
  Set();
  DescendPath(Path);
  Display();
  SetHelpKeys();
}

myMenuFolder::myMenuFolder(const char *Title, cList<cNestedItem> *List, cNestedItemList *NestedItemList, const char *Dir, const char *Path, int WhatToDo)
:cOsdMenu(Title)
{
  SetMenuCategory(mcFolder);
  list = List;
  nestedItemList = NestedItemList;
  dir = Dir;
  firstFolder = NULL;
  editing = false;
  helpKeys = -1;
  whatToDo = WhatToDo;
  Set();
  DescendPath(Path);
  Display();
  SetHelpKeys();
}

void myMenuFolder::SetHelpKeys(void)
{
  if (HasSubMenu())
     return;
  int NewHelpKeys = 0;
  if (firstFolder)
     NewHelpKeys = 1;
  if (NewHelpKeys != helpKeys) {
     helpKeys = NewHelpKeys;
     if (whatToDo == 3) // Copy
        SetHelp(tr("Button$Cancel"),
                trVDR("Button$New"),
                firstFolder ? trVDR("Button$Delete")
                            : NULL,
                firstFolder ? tr("Button$Copy")
                            : NULL);
     else if (whatToDo == 2) // Move
        SetHelp(tr("Button$Cancel"),
                trVDR("Button$New"),
                firstFolder ? trVDR("Button$Delete")
                            : NULL,
                firstFolder ? tr("Button$Move")
                            : NULL);
     else // Name
        SetHelp(NewHelpKeys > 0 ? trVDR("Button$Open")
                                : NULL,
                trVDR("Button$New"),
                firstFolder ? trVDR("Button$Delete")
                            : NULL,
                firstFolder ? trVDR("Button$Edit")
                            : NULL);
     }
}

#define FOLDERDELIMCHARSUBST 0x01
static void AddRecordingFolders(const cRecordings *Recordings, cList<cNestedItem> *List, char *Path)
{
  if (Path) {
     char *p = strchr(Path, FOLDERDELIMCHARSUBST);
     if (p)
        *p++ = 0;
     cNestedItem *Folder;
     for (Folder = List->First(); Folder; Folder = List->Next(Folder)) {
        if (strcmp(Path, Folder->Text()) == 0)
           break;
        }
     if (!Folder)
        List->Add(Folder = new cNestedItem(Path));
     if (p) {
        Folder->SetSubItems(true);
        AddRecordingFolders(Recordings, Folder->SubItems(), p);
        }
     }
  else {
     cStringList Dirs;
     for (const cRecording *Recording = Recordings->First(); Recording; Recording = Recordings->Next(Recording)) {
        cString Folder = Recording->Folder();
        strreplace((char *)*Folder, FOLDERDELIMCHAR, FOLDERDELIMCHARSUBST); // makes sure parent folders come before subfolders
        if (Dirs.Find(Folder) < 0)
           Dirs.Append(strdup(Folder));
        }
     Dirs.Sort();
     for (int i = 0; i < Dirs.Size(); i++) {
        if (char *s = Dirs[i])
           AddRecordingFolders(Recordings, &Folders, s);
        }
     }
}

void myMenuFolder::Set(const char *CurrentFolder)
{
  static cStateKey RecordingsStateKey;
  if (list == &Folders) {
     if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(RecordingsStateKey)) {
        AddRecordingFolders(Recordings, &Folders, NULL);
        RecordingsStateKey.Remove();
        }
     }
  firstFolder = NULL;
  Clear();
  if (!isempty(dir)) {
     cOsdItem *DirItem = new cOsdItem(dir);
     DirItem->SetSelectable(false);
     Add(DirItem);
     }
  list->Sort();
  for (cNestedItem *Folder = list->First(); Folder; Folder = list->Next(Folder)) {
     cOsdItem *FolderItem = new myMenuFolderItem(Folder);
     Add(FolderItem, CurrentFolder ? strcmp(Folder->Text(), CurrentFolder) == 0 : false);
     if (!firstFolder)
        firstFolder = FolderItem;
     }
}

void myMenuFolder::DescendPath(const char *Path)
{
  if (Path) {
     const char *p = strchr(Path, FOLDERDELIMCHAR);
     if (p) {
        for (myMenuFolderItem *Folder = (myMenuFolderItem *)firstFolder; Folder; Folder = (myMenuFolderItem *)Next(Folder)) {
           if (strncmp(Folder->Folder()->Text(), Path, p - Path) == 0) {
              SetCurrent(Folder);
              if (Folder->Folder()->SubItems() && strchr(p + 1, FOLDERDELIMCHAR))
                 AddSubMenu(new myMenuFolder(Title(), Folder->Folder()->SubItems(), nestedItemList, !isempty(dir) ? *cString::sprintf("%s%c%s", *dir, FOLDERDELIMCHAR, Folder->Folder()->Text()) : Folder->Folder()->Text(), p + 1, whatToDo));
              break;
              }
           }
        }
     }
}

eOSState myMenuFolder::Select(bool Open)
{
  if (firstFolder) {
     myMenuFolderItem *Folder = (myMenuFolderItem *)Get(Current());
     if (Folder) {
        if (Open) {
           Folder->Folder()->SetSubItems(true);
           return AddSubMenu(new myMenuFolder(Title(), Folder->Folder()->SubItems(), nestedItemList, !isempty(dir) ? *cString::sprintf("%s%c%s", *dir, FOLDERDELIMCHAR, Folder->Folder()->Text()) : Folder->Folder()->Text(), NULL, whatToDo));
           }
        else
           return osEnd;
        }
     }
  return osContinue;
}

eOSState myMenuFolder::New(void)
{
  editing = true;
  return AddSubMenu(new myMenuEditFolder(dir, list));
}

eOSState myMenuFolder::Delete(void)
{
  if (!HasSubMenu() && firstFolder) {
     myMenuFolderItem *Folder = (myMenuFolderItem *)Get(Current());
     if (Folder && Interface->Confirm(Folder->Folder()->SubItems() ? trVDR("Delete folder and all sub folders?")
                                                                   : trVDR("Delete folder?"))) {
        list->Del(Folder->Folder());
        Del(Folder->Index());
        firstFolder = Get(isempty(dir) ? 0 : 1);
        Display();
        SetHelpKeys();
        nestedItemList->Save();
        }
     }
  return osContinue;
}

eOSState myMenuFolder::Edit(void)
{
  if (!HasSubMenu() && firstFolder) {
     myMenuFolderItem *Folder = (myMenuFolderItem *)Get(Current());
     if (Folder) {
        editing = true;
        return AddSubMenu(new myMenuEditFolder(dir, list, Folder->Folder()));
        }
     }
  return osContinue;
}

eOSState myMenuFolder::SetFolder(void)
{
  if (myMenuEditFolder *mef = dynamic_cast<myMenuEditFolder *>(SubMenu())) {
     Set(mef->GetFolder());
     SetHelpKeys();
     Display();
     nestedItemList->Save();
     }
  return CloseSubMenu();
}

cString myMenuFolder::GetFolder(void)
{
  if (firstFolder) {
     myMenuFolderItem *Folder = (myMenuFolderItem *)Get(Current());
     if (Folder) {
        if (myMenuFolder *mf = dynamic_cast<myMenuFolder *>(SubMenu()))
           return cString::sprintf("%s%c%s", Folder->Folder()->Text(), FOLDERDELIMCHAR, *mf->GetFolder());
        return Folder->Folder()->Text();
        }
     }
  return "";
}

eOSState myMenuFolder::ProcessKey(eKeys Key)
{
  if (!HasSubMenu())
     editing = false;
  eOSState state = cOsdMenu::ProcessKey(Key);

  if (state == osUnknown) {
     switch (Key) {
       case kOk:     return Select(true);
       case kRed:    return osUserBraked;
       case kGreen:  return New();
       case kYellow: return Delete();
       case kBlue:   return Select(false);
       default:      state = osContinue;
       }
     }
  else if (state == osEnd && HasSubMenu() && editing)
     state = SetFolder();
  SetHelpKeys();
  return state;
}

// --- myMenuPathEdit ---------------------------------------------------------

myMenuPathEdit::myMenuPathEdit(const char *Path, int WhatTodoPath)
:cOsdMenu("", 12)
{
  SetMenuCategory(mcRecordingEdit);
  path = Path;
  *folder = 0;
  *name = 0;
  whatTodoPath = WhatTodoPath;
  buttonAction = NULL;
  actionCancel = NULL;
  buttonFolder = NULL;
  doCopy = (whatTodoPath ==3) ? "True" : NULL;
  doMove = (whatTodoPath ==2) ? "True" : NULL;
  doName = (whatTodoPath ==1) ? "True" : NULL;
  doAll  = (whatTodoPath ==0) ? "True" : NULL;
  extraAction = false;
  const char *s = strrchr(path, FOLDERDELIMCHAR);
  if (s) {
     strn0cpy(folder, cString(path, s), sizeof(folder));
     s++;
     }
  else
     s = path;
  strn0cpy(name, s, sizeof(name));
  {
  LOCK_RECORDINGS_READ;
  pathIsInUse = Recordings->PathIsInUse(path);
  }
  oldFolder = folder;
  cOsdItem *p;
  if (doAll || doMove || doCopy) {
     Add(p = folderItem = new cMenuEditStrItem(trVDR("Folder"), folder, sizeof(folder)));
     p->SetSelectable(!pathIsInUse);
     }
  if (doAll || doName) {
     Add(p = new cMenuEditStrItem(trVDR("Name"), name, sizeof(name)));
     p->SetSelectable(!pathIsInUse);
     }
  if (pathIsInUse) {
     Add(new cOsdItem("", osUnknown, false));
     Add(new cOsdItem(trVDR("This folder is currently in use - no changes are possible!"), osUnknown, false));
     }
  if (!pathIsInUse && !doAll && (doMove || doCopy)) {
     cRemote::Put(kRed, false);
     }
  else if (!pathIsInUse && doName) {
     cRemote::Put(kRight, false);
     }
  else {
     Display();
     SetHelpKeys();
     }
}

void myMenuPathEdit::SetHelpKeys(void)
{
  if (whatTodoPath >= 2) // Move or Copy
     return;
  if (doAll)
     buttonFolder = !pathIsInUse ? trVDR("Button$Folder") : NULL;
  buttonAction = NULL;
  actionCancel = NULL;
  doCopy = NULL;
  doMove = NULL;
//  if ((pathIsInUse & ruMove) != 0)
//     buttonAction = actionCancel = ((pathIsInUse & ruPending) != 0) ? tr("Button$Cancel moving") : tr("Button$Stop moving");
//  else if ((pathIsInUse & ruCopy) != 0)
//     buttonAction = actionCancel = ((pathIsInUse & ruPending) != 0) ? tr("Button$Cancel copying") : tr("Button$Stop copying");
  if (doAll)
     if (extraAction)
        buttonAction = doCopy = tr("Button$Move");
     else
        buttonAction = doMove = tr("Button$Copy");
  SetTitle(cString::sprintf("%s %s%s%s", trVDR("Edit path"), (doCopy || doMove) ? "(" : "", (pathIsInUse || actionCancel) ? "" : doCopy ? tr("Button$Copy") : tr("Button$Move"), (doCopy || doMove) ? ")" : ""));
//  SetTitle((pathIsInUse || doAll) ? trVDR("Edit path") : doCopy ? tr("Edit path (Copy)") : tr("Edit path (Move)"));
  Display();
  SetHelp(buttonFolder,
          NULL,
          NULL,
          (pathIsInUse || doName) ? NULL
                                  : buttonAction);
}

eOSState myMenuPathEdit::SetFolder(void)
{
  if (myMenuFolder *mf = dynamic_cast<myMenuFolder *>(SubMenu())) {
     strn0cpy(folder, mf->GetFolder(), sizeof(folder));
     SetCurrent(folderItem);
     Display();
     }
  return CloseSubMenu();
}

eOSState myMenuPathEdit::Folder(void)
{
  return AddSubMenu(new myMenuFolder(trVDR("Select folder"), &Folders, path, whatTodoPath));
}

eOSState myMenuPathEdit::ApplyChanges(void)
{
  if (!*name) {
     *name = ' '; // name must not be empty!
     name[1] = 0;
     }
  cString NewPath = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name;
  NewPath.CompactChars(FOLDERDELIMCHAR);
  if (strcmp(NewPath, path)) {
     int NumRecordings = 0;
     {
     LOCK_RECORDINGS_READ;
     NumRecordings = Recordings->GetNumRecordingsInPath(path);
     }
     if (NumRecordings > 1 && !Interface->Confirm(doCopy ? cString::sprintf(tr("Copy entire folder containing %d recordings?"), NumRecordings)
                                                         : cString::sprintf(trVDR("Move entire folder containing %d recordings?"), NumRecordings)))
        return osContinue;
     bool Error = false;
     if (doCopy) {
#ifdef COPYRECORDING
        {
          LOCK_RECORDINGS_WRITE;
          Recordings->SetExplicitModify();
          Error = !Recordings->CopyRecordings(path, NewPath);
          if (!Error)
             Recordings->SetModified();
        }
#endif
        if (Error) {
           Skins.Message(mtError, tr("Error while copying folder!"));
           return osContinue;
           }
        }
     else {
        {
          LOCK_RECORDINGS_WRITE;
          Recordings->SetExplicitModify();
          Error = !Recordings->MoveRecordings(path, NewPath);
          if (!Error)
             Recordings->SetModified();
        }
        if (Error) {
           Skins.Message(mtError, trVDR("Error while moving folder!"));
           return osContinue;
           }
        }
     if (doCopy)
        return osUserRecCopied;
     if (strcmp(folder, oldFolder))
        return osUserRecMoved;
     return osUserRecRenamed;
     }
  return osBack;
}

eOSState myMenuPathEdit::ProcessKey(eKeys Key)
{
  eOSState state = cOsdMenu::ProcessKey(Key);
  if (state == osUnknown) {
     if (!pathIsInUse) {
        switch (Key) {
          case kRed:  return !doName ? Folder() : osContinue;
          case kBlue: extraAction = !extraAction; SetHelpKeys(); return osContinue;
          case kOk:   return ApplyChanges();
          case kYellow:
          case kGreen:
          default: break;
          }
        }
     else if (Key == kOk)
        return osBack;
     }
  else if (state == osEnd && HasSubMenu()) {
     state = SetFolder();
     if (doMove || doCopy)
        return ApplyChanges();
     }
  else if ((state == osUserBraked && Key == kRed) || (!HasSubMenu() && Key == kBack)) {
     return osBack;
     }
  return state;
}

// --- myMenuRecordingEdit ----------------------------------------------------

myMenuRecordingEdit::myMenuRecordingEdit(const cRecording *Recording, int WhatTodoRec)
:cOsdMenu("", 12)
{
  SetMenuCategory(mcRecordingEdit);
  recording = Recording;
  originalFileName = recording->FileName();
  strn0cpy(folder, recording->Folder(), sizeof(folder));
  strn0cpy(name, recording->BaseName(), sizeof(name));
  strn0cpy(old_name, name, sizeof(name));
  strn0cpy(start_name, name, sizeof(name));
  const cRecordingInfo *info = recording->Info();
  strn0cpy(title, "", sizeof(title)); // empty default
  strn0cpy(shorttext, "", sizeof(shorttext)); // empty default
  strn0cpy(title_shorttext, "", sizeof(title_shorttext)); // empty default
  if (info) {
     if (!isempty(info->Title())) {
        strn0cpy(title, info->Title(), sizeof(title));
        strn0cpy(title_shorttext, info->Title(), sizeof(title_shorttext)); // default
        }
     if (!isempty(info->ShortText())) {
        strn0cpy(shorttext, info->ShortText(), sizeof(shorttext));
        strn0cpy(title_shorttext, info->ShortText(), sizeof(title_shorttext)); // default
        }
     if (!isempty(title) && !isempty(shorttext)) {
        snprintf(title_shorttext, sizeof(title_shorttext), "%s - %s", title, shorttext); // concenate
        }
     description = info->Description();
  }
  old_name_0_percent = name_0_percent = (name[0] == '%');
  menuRename[0] = tr("Manual");
  menuRename[1] = "EPG T";
  menuRename[2] = "EPG S";
  menuRename[3] = "EPG T+S";
  priority = recording->Priority();
  lifetime = recording->Lifetime();
  whatTodoRec = WhatTodoRec;
  folderItem = NULL;
  nameItem = NULL;
  buttonFolder = NULL;
  buttonAction = NULL;
  buttonExtraAction = NULL;
  buttonDeleteMarks = NULL;
  actionCancel = NULL;
  doCut = NULL;
  doRest = (whatTodoRec == 4) ? "True" : NULL;
  doCopy = (whatTodoRec == 3) ? "True" : NULL;
  doMove = (whatTodoRec == 2) ? "True" : NULL;
  doName = (whatTodoRec == 1) ? "True" : NULL;
  doAll  = (whatTodoRec == 0) ? "True" : NULL;
  extraAction = false;
  recordingIsInUse = ruNone;
  Set();
  if (!recordingIsInUse && doName)
     cRemote::Put(kRight);
}

void myMenuRecordingEdit::Set(void)
{
  int current = Current();
  Clear();
  recordingIsInUse = recording->IsInUse();
  cOsdItem *p;
  if (doAll || doMove || doCopy) {
     Add(p = folderItem = new cMenuEditStrItem(trVDR("Folder"), folder, sizeof(folder)));
     p->SetSelectable(!recordingIsInUse);
     }
  if (doAll || doName) {
     Add(p = nameItem = new cMenuEditStrItem(trVDR("Name"), name, sizeof(name)));
     p->SetSelectable(!recordingIsInUse);
     Add(new cOsdItem("", osUnknown, false));
     Add(new cOsdItem(tr("To use the other options, exit the edit mode with \"OK\" first."), osUnknown, false));
     Add(new cOsdItem("", osUnknown, false));
     Add(new cOsdItem(tr("Renaming options:"), osUnknown, false));
     Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("EPG T"), isempty(title) ? tr("(empty)") : title), osUnknown, false));
     Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("EPG S"), isempty(shorttext) ? tr("(empty)") : shorttext), osUnknown, false));
     Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("EPG T+S"), isempty(title_shorttext) ? tr("(empty)") : title_shorttext), osUnknown, false));
     Add(p = new cMenuEditStraItem(tr("Rename"), &name_preselect, 4, menuRename));
     p->SetSelectable(!recordingIsInUse);
     name_0_percent = (name[0] == '%');
     Add(p = new cMenuEditBoolItem(tr("Add %"), &name_0_percent));
     p->SetSelectable(!recordingIsInUse);
     }
  if (doAll || doRest) {
     Add(new cOsdItem(tr("Recording:"), osUnknown, false));
     Add(p = new cMenuEditIntItem(trVDR("Priority"), &priority, 0, MAXPRIORITY));
     p->SetSelectable(!recordingIsInUse);
     Add(p = new cMenuEditIntItem(trVDR("Lifetime"), &lifetime, 0, MAXLIFETIME));
     p->SetSelectable(!recordingIsInUse);
     Add(new cOsdItem("", osUnknown, false));
     Add(new cOsdItem(tr("EPG Info:"), osUnknown, false));
#if VDRVERSNUM >= 20502 || defined EPGRENAME
     Add(p = new cMenuEditStrItem(tr("Title"), title, sizeof(title)));
     p->SetSelectable(!recordingIsInUse);
     Add(p = new cMenuEditStrItem(tr("Shorttext"), shorttext, sizeof(shorttext)));
     p->SetSelectable(!recordingIsInUse);
#else
     Add(new cOsdItem(cString::sprintf("%s:\t%s",tr("Title"), title), osUnknown, false));
     Add(new cOsdItem(cString::sprintf("%s:\t%s",tr("Shorttext"), shorttext), osUnknown, false));
#endif
     Add(new cOsdItem("", osUnknown, false));
     Add(new cOsdItem(tr("EPG Description:"), osUnknown, false));
     Add(new cOsdItem(description, osUnknown, false));
     }
  if (recordingIsInUse) {
     Add(new cOsdItem("", osUnknown, false));
     Add(new cOsdItem(trVDR("This recording is currently in use - no changes are possible!"), osUnknown, false));
     }
  SetCurrent(Get(current));
  if (!recordingIsInUse && !doAll && (doMove || doCopy)) {
     Folder();
     }
  else {
     Display();
     SetHelpKeys();
     }
}

void myMenuRecordingEdit::SetHelpKeys(void)
{
  if (doAll)
     buttonFolder = !recordingIsInUse ? trVDR("Button$Folder") : NULL;
  buttonAction = NULL;
  buttonExtraAction = NULL;
  buttonDeleteMarks = NULL;
  actionCancel = NULL;
  doCut = NULL;
  doCopy = NULL;
  doMove = NULL;
  if ((recordingIsInUse & ruCut) != 0)
     buttonAction = actionCancel = ((recordingIsInUse & ruPending) != 0) ? trVDR("Button$Cancel cutting") : trVDR("Button$Stop cutting");
  else if ((recordingIsInUse & ruMove) != 0)
     buttonAction = actionCancel = ((recordingIsInUse & ruPending) != 0) ? trVDR("Button$Cancel moving") : trVDR("Button$Stop moving");
  else if ((recordingIsInUse & ruCopy) != 0)
     buttonAction = actionCancel = ((recordingIsInUse & ruPending) != 0) ? trVDR("Button$Cancel copying") : trVDR("Button$Stop copying");
  else if (recording->HasMarks()) {
     buttonAction = doCut = trVDR("Button$Cut");
     buttonDeleteMarks = trVDR("Button$Delete marks");
     }
#if VDRVERSNUM >= 20502 || defined EPGRENAME
  if (doRest) {
     if (recording->Info()->Description()) {
        if (!isempty(recording->Info()->Description()))
           buttonExtraAction = tr("Button$Clear EPG description");
        }
     }
#endif
  if (doAll)
     if (extraAction)
        buttonExtraAction = doCopy = tr("Button$Move");
     else
        buttonExtraAction = doMove = tr("Button$Copy");
  SetTitle(cString::sprintf("%s %s%s%s", trVDR("Edit recording"), (doCopy || doMove) ? "(" : "", (actionCancel || recordingIsInUse || doName || doRest) ? "" : doCopy ? tr("Button$Copy") : tr("Button$Move"), (doCopy || doMove) ? ")" : ""));
  Display();
  SetHelp(buttonFolder,
          (doName) ? NULL : buttonAction,
          (doName) ? NULL : buttonDeleteMarks,
          (actionCancel || doName) ? NULL
                                   : buttonExtraAction);
}

bool myMenuRecordingEdit::RefreshRecording(void)
{
  if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(recordingsStateKey)) {
     if ((recording = Recordings->GetByName(originalFileName)) != NULL)
        Set();
     else {
        recordingsStateKey.Remove();
        Skins.Message(mtWarning, trVDR("Recording vanished!"));
        return false;
        }
     recordingsStateKey.Remove();
     }
  return true;
}

eOSState myMenuRecordingEdit::SetFolder(void)
{
  if (myMenuFolder *mf = dynamic_cast<myMenuFolder *>(SubMenu())) {
     strn0cpy(folder, mf->GetFolder(), sizeof(folder));
     SetCurrent(folderItem);
     Display();
     }
  return CloseSubMenu();
}

eOSState myMenuRecordingEdit::Folder(void)
{
  return AddSubMenu(new myMenuFolder(trVDR("Select folder"), &Folders, recording->Name(), whatTodoRec));
}

eOSState myMenuRecordingEdit::Action(void)
{
  if (actionCancel)
     RecordingsHandler.Del(recording->FileName());
  else if (doCut) {
     if (access(cCutter::EditedFileName(recording->FileName()), F_OK) != 0 || Interface->Confirm(trVDR("Edited version already exists - overwrite?"))) {
        if (!RecordingsHandler.Add(ruCut, recording->FileName()))
           Skins.Message(mtError, trVDR("Error while queueing recording for cutting!"));
        }
     }
  recordingIsInUse = recording->IsInUse();
  RefreshRecording();
  SetHelpKeys();
  return osContinue;
}

eOSState myMenuRecordingEdit::RemoveName(void)
{
  if (Get(Current()) == nameItem) {
     if (Interface->Confirm(trVDR("Rename recording to folder name?"))) {
        char *s = strrchr(folder, FOLDERDELIMCHAR);
        if (s)
           *s++ = 0;
        else
           s = folder;
        strn0cpy(name, s, sizeof(name));
        if (s == folder)
           *s = 0;
        Set();
        }
     }
  return osContinue;
}

eOSState myMenuRecordingEdit::SetName(void)
{
  char PresetName[NAME_MAX] = ""; //default
  int offset = 0;
  if (name_preselect == 0 && (name[0] == '%')) {
     if (name_0_percent == false) {
        strshift(name, 1);
        if (!*name) {
           *name = ' '; // name must not be empty!
           name[1] = 0;
           }
        }
     }
  else if (name_preselect != 0 || (name[0] != '%')) {
     if (name_0_percent == true) {
        PresetName[0] = '%';
        PresetName[1] = '0';
        offset = 1;
        }
     switch (name_preselect) {
       case 0:
          strn0cpy(PresetName + offset, name, NAME_MAX - 1);
          break;
       case 1:
          if (isempty(title)) {
             Skins.Message(mtError, tr("EPG title is empty, can't rename!"));
             return osContinue;
             };
          strn0cpy(PresetName + offset, title, NAME_MAX - 1);
          dsyslog("extrecmenung: rename recording according to 'EPG title'");
          break;
       case 2:
          if (isempty(shorttext)) {
             Skins.Message(mtError, tr("EPG shorttext is empty, can't rename!"));
             return osContinue;
             };
          strn0cpy(PresetName + offset, shorttext, NAME_MAX - 1);
          dsyslog("extrecmenung: rename recording according to 'EPG shorttext'");
          break;
       case 3:
          if (isempty(title_shorttext)) {
             Skins.Message(mtError, tr("EPG title+shorttext is empty, can't rename!"));
             return osContinue;
             };
          if (strlen(title_shorttext) >= NAME_MAX - 1) {
             Skins.Message(mtError, tr("EPG title+shorttext is too long, can't rename!"));
             return osContinue;
             };
          strn0cpy(PresetName + offset, title_shorttext, NAME_MAX - 1);
          dsyslog("extrecmenung: rename recording according to 'EPG title+shorttext'");
          break;
        }
     strn0cpy(name, PresetName, NAME_MAX);
     }
  Set();
  return osContinue;
}

#if VDRVERSNUM >= 20502 || defined EPGRENAME
eOSState myMenuRecordingEdit::ClearEpgDescription(void)
{
  if (buttonExtraAction && Interface->Confirm(tr("Clear EPG description of this recording?"))) {
     dsyslog("extrecmenung: clear EPG description");
     recording->Info()->SetData(NULL, NULL, ""); // clear description, NULL leave title+shorttext unchanged
     description_clear = true;
     Set();
     SetHelpKeys();
     }
  return osContinue;
}
#endif

eOSState myMenuRecordingEdit::DeleteMarks(void)
{
  if (buttonDeleteMarks && Interface->Confirm(trVDR("Delete editing marks for this recording?"))) {
     if (cMarks::DeleteMarksFile(recording))
        SetHelpKeys();
     else
        Skins.Message(mtError, trVDR("Error while deleting editing marks!"));
     }
  return osContinue;
}

eOSState myMenuRecordingEdit::ApplyChanges(void)
{
  cStateKey StateKey;
  cRecordings *Recordings = cRecordings::GetRecordingsWrite(StateKey);
  cRecording *Recording = Recordings->GetByName(recording->FileName());
  if (!Recording) {
     StateKey.Remove(false);
     Skins.Message(mtWarning, trVDR("Recording vanished!"));
     return osBack;
     }
  bool Modified = false;
  if (priority != recording->Priority() || lifetime != recording->Lifetime()) {
     if (!Recording->ChangePriorityLifetime(priority, lifetime)) {
        StateKey.Remove(Modified);
        Skins.Message(mtError, trVDR("Error while changing priority/lifetime!"));
        return osContinue;
        }
     Modified = true;
     }
#if VDRVERSNUM >= 20502 || defined EPGRENAME
  cRecordingInfo *info = recording->Info();
  char OldTitle[NAME_MAX] = ""; // default
  char OldShorttext[NAME_MAX] = ""; //default
  bool InfoModified = false;
  if (info) {
     if (info->Title()) {
        strn0cpy(OldTitle, info->Title(), sizeof(OldTitle));
        }
     if (info->ShortText()) {
        strn0cpy(OldShorttext, info->ShortText(), sizeof(OldShorttext));
        }
     }
  if (strcmp(OldTitle, title)) {
     InfoModified = true;
     dsyslog("extrecmenung: EPG title change: '%s' -> '%s'", OldTitle, title);
     if (strlen(title) > 0) {
        info->SetData(title, NULL, NULL); // NULL leave shorttext+description unchanged
        }
     else {
        info->SetData("", NULL, NULL); // clear title, NULL leave shorttext+description unchanged
        }
     }
  if (strcmp(OldShorttext, shorttext)) {
     InfoModified = true;
     dsyslog("extrecmenung: EPG shorttext change: '%s' -> '%s'", OldShorttext, shorttext);
     if (strlen(shorttext) > 0) {
        info->SetData(NULL, shorttext, NULL); // NULL leave title+description unchanged
        }
     else {
        info->SetData(NULL, "", NULL); // clear shorttext, NULL leave title+description unchanged
        }
     }
  if (description_clear == true) {
     InfoModified = true;
     }
  if (InfoModified) {
     if (!Recording->WriteInfo()) {
        Skins.Message(mtError, tr("Error while writing updated 'info' file!"));
        esyslog("extrecmenung: recording 'info' update problem caused by EPG title/shorttext/description change");
        }
     else {
        Skins.QueueMessage(mtInfo, tr("Successfully updated 'info' file"));
        dsyslog("extrecmenung: recording 'info' file updated because of EPG title/shorttext/description change");
        }
     }
#endif
  if (!*name) {
     *name = ' '; // name must not be empty!
     name[1] = 0;
     }
  cString OldFolder = Recording->Folder();
  cString NewName = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name;
  NewName.CompactChars(FOLDERDELIMCHAR);
  if (strcmp(NewName, Recording->Name())) {
     dsyslog("extrecmenung: rename recording from: '%s'", *Recording->BaseName());
     dsyslog("extrecmenung: rename recording to  : '%s'", name);
     if (doCopy) {
#ifdef COPYRECORDING
        if (!Recording->CopyName(NewName)) {
           StateKey.Remove(Modified);
           Skins.Message(mtError, tr("Error while copying folder/name!"));
           return osContinue;
           }
#endif
        }
     else {
        if (!Recording->ChangeName(NewName)) {
           StateKey.Remove(Modified);
           Skins.Message(mtError, trVDR("Error while changing folder/name!"));
           return osContinue;
           }
        }
     Modified = true;
     }
  if (Modified) {
     eOSState  state = osUserRecRenamed;
     if (doCopy)
        state = osUserRecCopied;
     if (strcmp(Recording->Folder(), OldFolder))
        state = osUserRecMoved;
     Recordings->TouchUpdate();
     StateKey.Remove(Modified);
     return state;
     }
  StateKey.Remove(Modified);
  return osBack;
}

eOSState myMenuRecordingEdit::ProcessKey(eKeys Key)
{
  if (!HasSubMenu()) {
     if (!RefreshRecording())
        return osBack; // the recording has vanished, so close this menu
     }
  eOSState state = cOsdMenu::ProcessKey(Key);
  if (Key != kNone && (strcmp(old_name, name) || name_0_percent != old_name_0_percent || name_preselect != old_name_preselect)) {
     if (strcmp(old_name, name)) {
        name_0_percent = (name[0] == '%');
        }
     if (name_preselect != old_name_preselect && name_preselect == 0)
        strn0cpy(name, start_name, sizeof(start_name));
     SetName();
     strn0cpy(old_name, name, sizeof(name));
     old_name_0_percent = name_0_percent;
     old_name_preselect = name_preselect;
     }
  if (state == osUnknown) {
     switch (Key) {
       case k0:      return RemoveName();
       case kRed:    return buttonFolder ? Folder() : osContinue;
       case kGreen:  return buttonAction ? Action() : osContinue;
       case kYellow: return buttonDeleteMarks ? DeleteMarks() : osContinue;
#if VDRVERSNUM >= 20502 || defined EPGRENAME
       case kBlue:   if (buttonExtraAction) { return ClearEpgDescription(); } else { extraAction = !extraAction; SetHelpKeys(); return osContinue; };
#else
       case kBlue:   extraAction = !extraAction; SetHelpKeys(); return osContinue;
#endif
       case kOk:     return !recordingIsInUse ? ApplyChanges() : osBack;
       default: break;
       }
     }
  else if (state == osEnd && HasSubMenu()) {
     state = SetFolder();
     if (doMove || doCopy)
        return !recordingIsInUse ? ApplyChanges() : osBack;
     }
  else if ((state == osUserBraked && Key == kRed) || (!HasSubMenu() && Key == kBack)) {
     return osBack; // close the edit menu
     }
  return state;
}

// --- myMenuRecording -------------------------------------------------------
myMenuRecording::myMenuRecording(const cRecording *Recording, bool WithButtons, bool DeletedRecording)
:cOsdMenu(trVDR("Recording info"))
{
  SetMenuCategory(mcRecordingInfo);
  if (cRecordings::GetRecordingsRead(recordingsStateKey)) // initializes recordingsStateKey, so we don't call Display() unnecessarily
     recordingsStateKey.Remove();
  recording = Recording;
  originalFileName = recording->FileName();
  withButtons = WithButtons;
  deletedRecording = DeletedRecording;
  if (withButtons)
    SetHelp(trVDR("Button$Play"), trVDR("Button$Rewind"), tr("Button$Details"), tr("Button$Back"));
  else
    SetHelp(tr("Button$RECORDINGS"), NULL, NULL, tr("Button$Back"));
}

bool myMenuRecording::RefreshRecording(void)
{
  if (const cRecordings *Recordings = deletedRecording ? cRecordings::GetDeletedRecordingsRead(recordingsStateKey)
                                                       : cRecordings::GetRecordingsRead(recordingsStateKey)) {
     if ((recording = Recordings->GetByName(originalFileName)) != NULL) {
        if (!mysetup.UseVDRsRecInfoMenu)
           recordingsStateKey.Remove();
        Display();
        }
     else {
        recordingsStateKey.Remove();
        Skins.Message(mtWarning, trVDR("Recording vanished!"));
        return false;
        }
     if (mysetup.UseVDRsRecInfoMenu)
        recordingsStateKey.Remove();
     }
  return true;
}

void myMenuRecording::Display(void)
{
  if (HasSubMenu()) {
     SubMenu()->Display();
     return;
     }
  cOsdMenu::Display();

  if (!recording)
     return;

#ifdef USE_GRAPHTFT
  cStatus::MsgOsdSetRecording(recording);
#endif

  if (mysetup.UseVDRsRecInfoMenu) {
     DisplayMenu()->SetRecording(recording);
     if (recording->Info()->Description())
        cStatus::MsgOsdTextItem(recording->Info()->Description());
     }
  else {
     const cRecordingInfo *Info = recording->Info();
     stringstream text;
     time_t start = recording->Start();
     text << *DateString(start) << ", " << *TimeString(start) << "\n\n";

     if (!isempty(Info->Title())) {
        text << Info->Title();
        if (!isempty(Info->ShortText())) {
           text << " - " << Info->ShortText();
           }
        text << "\n\n";
        if (Info->Description())
           text << Info->Description() << "\n\n";
        }

    string recname = recording->Name();
    string::size_type i = recname.rfind('~');
    if (i != string::npos)
       text << trVDR("Name") << ": " << recname.substr(i + 1, recname.length()) << "\n"
            << tr("Path") << ": " << recname.substr(0, i) << "\n";
    else
       text << trVDR("Name") << ": " << recname << "\n";

    if (Info->ChannelName() && (strlen(Info->ChannelName()) > 0)) {
        // take use of ChannelName provided by 'info'
        text << trVDR("Channel") << ": " << Info->ChannelName() << "\n";
    } else {
        // fallback: lookup ChannelName by ChannelID in active channels
	{
        LOCK_CHANNELS_READ;
        if (const cChannel *channel = Channels->GetByChannelID(((cRecordingInfo*)Info)->ChannelID()))
           text << trVDR("Channel") << ": " << *ChannelString(channel, 0) << "\n";
	}
    }

    int recmb = DirSizeMB(recording->FileName());
    if (recmb < 0)
       recmb = 0;
    if (recmb > 1023)
       text << tr("Size") << ": " << setprecision(3) << recmb / 1024.0 << " GB\n";
    else
       text << tr("Size") << ": " << recmb << " MB\n";

    int prio = recording->Priority();
    int lft = recording->Lifetime();
    text << trVDR("Priority") << ": " << prio << "\n";
    text << trVDR("Lifetime") << ": " << lft << "\n";

#if (APIVERSNUM >= 20605)
    // Draw video format
    if (!isempty(Info->FrameParams())) {
       text << std::endl << tr("Frame Parameters") << ": " << *Info->FrameParams();
    }
#endif

    const char *temp = strdup(*cString::sprintf("%s", text.str().c_str()));
    DisplayMenu()->SetText(temp, false);
    cStatus::MsgOsdTextItem(text.str().c_str());
    }
}

eOSState myMenuRecording::ProcessKey(eKeys Key)
{
  if (HasSubMenu())
     return cOsdMenu::ProcessKey(Key);
  else if (!RefreshRecording())
     return osBack; // the recording has vanished, so close this menu
  switch (int(Key)) {
    case kUp|k_Repeat:
    case kUp:
    case kDown|k_Repeat:
    case kDown:
    case kLeft|k_Repeat:
    case kLeft:
    case kRight|k_Repeat:
    case kRight:
                  DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight);
                  cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft);
                  return osContinue;
    case kInfo:   return osBack;
    default: break;
    }

  eOSState state = cOsdMenu::ProcessKey(Key);

  if (state == osUnknown) {
    switch (Key) {
      case kRed:    if (withButtons) {
                       Key = kOk; // will play the recording, even if recording commands are defined
                       cRemote::Put(Key, true);
                       // continue with osBack to close the info menu and process the key
                       return osBack;
                    } else {
                       break;
                    }
      case kGreen:  if (withButtons) {
                       cRemote::Put(Key, true);
                       // continue with osBack to close the info menu and process the key
                       return osBack;
                    } else {
                       break;
                    }
      case kYellow: if (withButtons) {
                       return AddSubMenu(new myMenuRecordingEdit(recording, 4));
                    } else {
                       break;
                    }
      case kBlue:   return osBack;
      case kOk:     return osBack;
      default: break;
      }
    }
  return state;
}

// --- myMenuRecordingItem ---------------------------------------------------
myMenuRecordingItem::myMenuRecordingItem(const cRecording *Recording, int Level)
{
  recording = Recording;
  level = Level;
  name = NULL;
  totalEntries = newEntries = 0;
  isdvd = false;
  ishdd = false;
  dirismoving = false; //true;
  filename = Recording->FileName();
  int savedlevel = level;
  // get the level of this recording
  level = 0;
  const char *s = Recording->Name();
  while (*++s) {
     if (*s == '~')
        level++;
     }
  // create the title of this item
  if (Level < level) { // directory entry
     s = Recording->Name();
     const char *p = s;
     while (*++s) {
        if (*s == '~') {
           if (Level--)
              p = s + 1;
           else
              break;
           }
        }
     title = MALLOC(char, s - p + 1);
     strn0cpy(title, p, s - p +1);
     name = strdup(title);
     }
  else {
     if (Level == level) { // recording entry
        string buffer;
        stringstream titlebuffer;
        stringstream idbuffer;
        // date and time of recording
        struct tm tm_r;
        time_t start = Recording->Start();
        struct tm *t=localtime_r(&start,&tm_r);
        // display symbol
        buffer = filename;
#ifdef ARCHIV_DVD_HDD
        isdvd = Recording->IsDVD();
        ishdd = Recording->IsHDD();
#else
        if (Recording->IsPesRecording())
           buffer += "/001.vdr";
        else
           buffer += "/00001.ts";
        if (access(buffer.c_str(), R_OK)) {
           buffer = filename;
           buffer += "/dvd.vdr";
           isdvd =! access(buffer.c_str(), R_OK);
           buffer = filename;
           buffer += "/hdd.vdr";
           ishdd =! access(buffer.c_str(), R_OK);
           }
#endif /* ARCHIV_DVD_HDD */
        int Usage = Recording->IsInUse();
        if ((Usage & ruSrc) != 0 && (Usage & ruMove) != 0)
           titlebuffer << Icons::MovingRecording(); // moving recording
        else if (ishdd)
           titlebuffer << Icons::HDD(); // archive hdd
        else if (isdvd)
           titlebuffer << Icons::DVD(); // archive dvd
        else if ((Usage & ruSrc) != 0 && (Usage & ruCut) != 0)
           titlebuffer << Icons::Scissor(); // cutting recording
        else if(Recording->IsNew() && !mysetup.PatchNew)
           titlebuffer << '*';
        else if(!Recording->IsNew() && mysetup.PatchNew)
           titlebuffer << Icons::Continue(); // alternative to new marker / rewind / continue
        else titlebuffer << ' '; // no icon
        titlebuffer << '\t';
        // loop all columns and write each output to ostringstream
        for (int i = 0; i < MAX_RECLIST_COLUMNS; i++) {
           ostringstream sbuffer;

           if      (mysetup.RecListColumn[i].Type == COLTYPE_DATE) {
              sbuffer << setw(2) << setfill('0') << t->tm_mday << '.'
                      << setw(2) << setfill('0') << t->tm_mon+1 << '.'
                      << setw(2) << setfill('0') << t->tm_year%100;
              }

           else if (mysetup.RecListColumn[i].Type == COLTYPE_TIME) {
              sbuffer << setw(2) << setfill('0') << t->tm_hour << '.'
                      << setw(2) << setfill('0') << t->tm_min;
              }

           else if (mysetup.RecListColumn[i].Type == COLTYPE_DATETIME) {
              sbuffer << setw(2) << setfill('0') << t->tm_mday << '.'
                      << setw(2) << setfill('0') << t->tm_mon+1 << '.'
                      << setw(2) << setfill('0') << t->tm_year%100;
              sbuffer << Icons::FixedBlank();
              sbuffer << setw(2) << setfill('0') << t->tm_hour << '.'
                      << setw(2) << setfill('0') << t->tm_min;
              }

           else if (mysetup.RecListColumn[i].Type == COLTYPE_LENGTH) {
               // get recording length from Recording
              int iLenSeconds = Recording->LengthInSeconds();
              if (iLenSeconds > 0)
                 sbuffer << (int)( (iLenSeconds + 30) / 60 ) << "'";
              else {
                 // get recording length from index size
                 buffer = filename;
                 if (Recording->IsPesRecording())
                    buffer += "/index.vdr";
                 else
                    buffer += "/index";
                 struct stat statbuf;
                 if (!stat(buffer.c_str(), &statbuf)) {
                    sbuffer << (int)( (statbuf.st_size / 8 / Recording->FramesPerSecond()) + 30 / 60 ) << "'";
                    }
                 else {
                    // get recording length from file 'length.vdr'
                    buffer = filename;
                    buffer += "/length.vdr";

                    ifstream in(buffer.c_str());
                    if (in) {
                       if (!in.eof())
                          getline(in,buffer);
                       sbuffer << buffer << "'";
                       in.close();
                       }
                    }
                 }
              }
#if (APIVERSNUM >= 20505)
           else if (mysetup.RecListColumn[i].Type == COLTYPE_ERROR) {
              const cRecordingInfo *Info = Recording->Info();
              int error = Info->Errors();
              if (error >= mysetup.ErrorCount)
                 sbuffer << "!";
              else
                 sbuffer << " ";
              }
#endif
           else if (mysetup.RecListColumn[i].Type == COLTYPE_RATING) {
              int rated = -1;
#ifdef VDR_RATING_PATCH
              // get recording rating from Recording
              rated = Recording->Rating();
#endif /* VDR_RATING_PATCH */
              if (rated == -1) {
                 // get recording rating from file 'rated.vdr'
                 buffer = filename;
                 buffer += "/rated.vdr";
                 ifstream in(buffer.c_str());
                 if (in) {
                    if (!in.eof())
                       getline(in,buffer);
                    rated = atoi(buffer.c_str());
                    in.close();
                    }
                 }
              if (rated > 10)
                 rated = 10;
              if (rated > 0) {
                 while (rated > 1) {
                    sbuffer << Icons::StarFull();
                    rated = rated - 2;
                    }
                 if (rated > 0) {
                    sbuffer << Icons::StarHalf();
                    rated--;
                    }
                 }
              else {
                 int watched = -1;
#ifdef VDR_WATCHSTATE_PATCH
                 // get watch state from Recording
                 watched = Recording->Watchstate();
#endif /* VDR_WATCHSTATE_PATCH */
                 if (watched == -1) {
                    // get watch state from file 'watched.vdr'
                    buffer = filename;
                    buffer += "/watched.vdr";
                    ifstream in(buffer.c_str());
                    if(in) {
                       if(!in.eof())
                          getline(in,buffer);
                       watched = atoi(buffer.c_str());
                       in.close();
                       }
                    }
                 if (watched == 1) {
                    sbuffer << Icons::EyeHalf();
                    }
                 else if (watched == 2) {
                    sbuffer << Icons::EyeClosed();
                    }
                 else if (watched == 3) {
                    sbuffer << Icons::EyeFull();
                    }
                 }
              }

           else if (mysetup.RecListColumn[i].Type == COLTYPE_FILE || mysetup.RecListColumn[i].Type == COLTYPE_FILETHENCOMMAND) {
              string sRes = "";
#if defined (VDR_YEAR_PATCH) || defined (VDR_COUNTRY_PATCH)
              // get contents from VDR Recording
              if (!strcasecmp( mysetup.RecListColumn[i].Op1, "year.vdr" ))
                 sRes = Recording->Year();
              else if (!strcasecmp( mysetup.RecListColumn[i].Op1, "country.vdr" ))
                 sRes = Recording->Country();
#endif
              if (sRes.length() == 0) {
                 // get content from file
                 buffer = filename;
                 buffer += "/";
                 buffer += mysetup.RecListColumn[i].Op1;
                 ifstream in(buffer.c_str());
                 if (in) {
                    if (!in.eof())
                       getline(in, sRes);
                    in.close();
                    }
                 }
              // cut to maximum width
              sRes = sRes.substr(0, mysetup.RecListColumn[i].Width);
              if (sRes.length() > 0)
                 sbuffer << sRes;
              else {
                 if (mysetup.RecListColumn[i].Type == COLTYPE_FILETHENCOMMAND) {
                    // execute the command given by Op2
                    char result [1024];
                    strcpy(result, mysetup.RecListColumn[i].Op2);
                    strcat(result, " \"");
                    strcat(result, filename);
                    strcat(result, "\"");
                    FILE *fp = popen(result, "r");
                    int read = fread(result, 1, sizeof(result), fp);
                    pclose (fp);
                    if (read > 0) {
                       // use output of command
                       // strip trailing whitespaces
                       result[read] = 0;
                       while (strlen(result) > 0 && (result[strlen(result) - 1] == 0x0a || result[strlen(result) - 1] == 0x0d || result[strlen(result) - 1] == ' ')) {
                          result[strlen(result) - 1] = 0;
                          }
                       result[mysetup.RecListColumn[i].Width] = 0;
                       sbuffer << result;
                       }
                    else {
                       // retry reading the file (useful when the execution of the command created the file)
                       buffer = filename;
                       buffer += "/";
                       buffer += mysetup.RecListColumn[i].Op1;
                       ifstream in(buffer.c_str());
                       if (in) {
                          if (!in.eof())
                             getline(in, buffer);
                          // cut to maximum width
                          buffer = buffer.substr(0, mysetup.RecListColumn[i].Width);
                          sbuffer << buffer;
                          in.close();
                          }
                       }
                    }
                 }
              }

           // adjust alignment
           int iLeftBlanks = 0;
           switch (mysetup.RecListColumn[i].Align) {
              case 1:
                 // center alignment
                 iLeftBlanks = (mysetup.RecListColumn[i].Width - strlen(sbuffer.str().c_str())) / 2; // sbuffer.width()) / 2;
                 break;
              case 2:
                 // right alignment
                 iLeftBlanks = (mysetup.RecListColumn[i].Width - strlen(sbuffer.str().c_str())); // sbuffer.width());
                 break;
              default:
                 // left alignment
                 break;
              }
           if (iLeftBlanks > 0) {
              for (int j = 0; j < iLeftBlanks; j++) {
                 titlebuffer << Icons::FixedBlank();
                 }
              }
           titlebuffer << sbuffer.str() << '\t';
           } // loop all columns
        // recording title
        string _s = Recording->Name();
        string::size_type i = _s.rfind('~');
        if (i != string::npos) {
           titlebuffer << _s.substr(i + 1,_s.length() - i);
           }
        else {
           titlebuffer << _s;
           }
        title = strdup(titlebuffer.str().c_str());
        }
     else {
        if (Level > level) // any other
           title = strdup("");
        }
     }
  level = savedlevel;
  SetText(title);
  int Usage = Recording->IsInUse();
  if (((Usage & ruSrc) != 0 && (Usage & ruMove) != 0) || ((Usage & ruDst) != 0 && (Usage & (ruMove | ruCopy)) != 0))
//  if ((Usage & ruDst) != 0 && (Usage & (ruMove | ruCopy)) != 0)
     SetSelectable(false);
}

myMenuRecordingItem::~myMenuRecordingItem()
{
  free(title);
  free(name);
}

void myMenuRecordingItem::IncrementCounter(bool New)
{
  totalEntries++;
  if (New)
     newEntries++;

  ostringstream entries;
  entries << setw(mysetup.RecsPerDir + 1) << totalEntries;

//  const char *Icon = GetDirIsMoving() ? Icons::MovingDirectory() : Icons::Directory() : " ";

  if (mysetup.ShowNewRecs) {
     SetText(cString::sprintf("%s\t%s\t(%d)\t\t\t%s",
                            GetDirIsMoving() ? Icons::MovingDirectory() : Icons::Directory(),
                            // replace leading spaces with fixed blank (right align)
                            myStrReplace(entries.str(), ' ', Icons::FixedBlank()).c_str(),
                            newEntries,
                            name));
     }
  else {
     SetText(cString::sprintf("%s\t%s\t\t\t\t%s",
                            GetDirIsMoving() ? Icons::MovingDirectory() : Icons::Directory(),
                            // replace leading spaces with fixed blank (right align)
                            myStrReplace(entries.str(), ' ', Icons::FixedBlank()).c_str(),
                            name));
     }
}

void myMenuRecordingItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable)
{
  if (!DisplayMenu->SetItemRecording(recording, Index, Current, Selectable, level, totalEntries, newEntries))
    DisplayMenu->SetItem(Text(), Index, Current, Selectable);
}

// --- myMenuRecordings -------------------------------------------------------
#define MB_PER_MINUTE 25.75 // this is just an estimate! (taken over from VDR)

bool myMenuRecordings::wasdvd;
bool myMenuRecordings::washdd;
dev_t myMenuRecordings::fsid = 0;
time_t myMenuRecordings::lastDiskSpaceCheck = 0;
int myMenuRecordings::lastFreeMB = -1;
cString myMenuRecordings::path;
cString myMenuRecordings::fileName;

myMenuRecordings::myMenuRecordings(const char *Base, int Level, bool OpenSubMenus, const cRecordingFilter *Filter, bool DelRecMenu)
:cOsdMenu("")
{
  if (mysetup.SetRecordingCat)
     SetMenuCategory(mcRecording);
  int c[MAX_RECLIST_COLUMNS],i=0;

  for (i=0; i<MAX_RECLIST_COLUMNS; i++) {
     c[i] = 1;
     if ((mysetup.RecListColumn[i].Type != COLTYPE_NONE) &&
         (mysetup.RecListColumn[i].Width > 0)) {
        c[i] = mysetup.RecListColumn[i].Width+1;
        }
     }

  // widen the first column if there isn't enough space for the number of recordings in a direcory
  if (c[0] < mysetup.RecsPerDir+1) {
     c[0] = mysetup.RecsPerDir+1;
  }
  // widen the second column if the number of new recordings should be displayed and
  // there isn't enough space for the number of new recordings in a direcory
  if (mysetup.ShowNewRecs && c[1] < mysetup.RecsPerDir+3) {
     c[1] = mysetup.RecsPerDir+3;
  }

  SetCols(2, c[0], c[1], c[2], c[3]);

  base = Base ? strdup(Base) : NULL;
  level = Setup.RecordingDirs ? Level : -1;
  filter = Filter;
  helpkeys = -1;
  editMenu = EditMenu = false;
  timeOut = 0;
  delRecMenu = DelRecMenu;
  deletedRec = false;
  buttonRed = NULL;
  buttonGreen = NULL;
  buttonYellow = NULL;
  buttonBlue = NULL;
  hascutter = false;
  hasmovecopy = false;
  counter = 0;
  Ebene++;
  ebene = Ebene;

  Display(); // this keeps the higher level menus from showing up briefly when pressing 'Back' during replay

  if (wasdvd && !cControl::Control()) {
     char *cmd = NULL;
     if (-1 != asprintf(&cmd, "dvdarchive.sh umount \"%s\"", *strescape(myReplayControl::LastReplayed(), "'\\\"$"))) {
        isyslog("[extrecmenu] calling %s to unmount dvd", cmd);
        int result = SystemExec(cmd);
        if (result) {
           result = result / 256;
           if (result == 1)
              Skins.Message(mtError, tr("Error while mounting DVD!"));
           }
        isyslog("[extrecmenu] dvdarchive.sh returns %d", result);
        free(cmd);
        }

     wasdvd = false;
     }
  if (washdd &&! cControl::Control()) {
     char *cmd=NULL;
     if (-1 != asprintf(&cmd, "hddarchive.sh umount \"%s\"", *strescape(myReplayControl::LastReplayed(), "'\\\"$"))) {
        isyslog("[extrecmenu] calling %s to unmount Archive-HDD", cmd);
        int result = SystemExec(cmd);
        if (result) {
           result = result / 256;
           if (result == 1)
              Skins.Message(mtError, tr("Error while mounting Archive-HDD!"));
           }
        isyslog("[extrecmenu] hddarchive.sh returns %d", result);
        free(cmd);
        }

     washdd = false;
     }

  Set();
  if (Current() < 0)
     SetCurrent(First());
  else if ((mysetup.GoLastReplayed || mysetup.ReturnToRec) && (myReplayControl::LastReplayed() || *path || *fileName)) {
     if (!*path || Level < strcountchr(path, FOLDERDELIMCHAR)) {
        if (Open(true))
           return;
        }
     }
  mysetup.ReturnToRec = false;

  SetHelpKeys();
}

myMenuRecordings::~myMenuRecordings()
{
/*  if (myMenuRecordingItem *ri = (myMenuRecordingItem *)Get(Current())) {
     if (!ri->IsDirectory())
        SetRecording(ri->Recording()->FileName());
     } */
  free(base);
  Ebene--;
}

int myMenuRecordings::FreeMB()
{
  if (lastFreeMB <= 0 || (time(NULL) - lastDiskSpaceCheck) > DISKSPACECHEK) {
     int freediskspace = 0;
     if (mysetup.FileSystemFreeMB == 2) {
        string path = cVideoDirectory::Name();
        path += "/";
        char *tmpbase = base ? ExchangeChars(strdup(base), true) : NULL;
        if (tmpbase)
           path += tmpbase;

        struct stat statdir;
        if (!stat(path.c_str(), &statdir)) {
           if (statdir.st_dev != fsid) {
              fsid = statdir.st_dev;
              struct statvfs fsstat;
              if (!statvfs(path.c_str(), &fsstat)) {
                 freediskspace = int((double)fsstat.f_bavail / (double)(1024.0 * 1024.0 / fsstat.f_bsize));
                 LOCK_DELETEDRECORDINGS_READ
                 for (const cRecording *rec = DeletedRecordings->First(); rec; rec = DeletedRecordings->Next(rec)) {
                    if (!stat(rec->FileName(), &statdir)) {
                       if (statdir.st_dev == fsid) {
                          int ds = DirSizeMB(rec->FileName());
                          if (ds > 0)
                             freediskspace += ds;
                          else
                             esyslog("[extrecmenu] DirSizeMB(%s) failed!", rec->FileName());
                          }
                       }
                    }
                 }
              else {
                 esyslog("[extrecmenu] error while getting filesystem size - statvfs (%s): %s", path.c_str(), strerror(errno));
                 freediskspace = 0;
                 }
              }
           else {
              freediskspace = lastFreeMB;
              }
           }
        else {
           esyslog("[extrecmenu] error while getting filesystem size - stat (%s): %s", path.c_str(), strerror(errno));
           freediskspace = 0;
           }
        free(tmpbase);
        }
     else {
        cVideoDirectory::VideoDiskSpace(&freediskspace);
        }
     lastFreeMB = freediskspace;
     lastDiskSpaceCheck = time(NULL);
     }
  return lastFreeMB;
}

void myMenuRecordings::Title()
{
  if (mysetup.FileSystemFreeMB > 0) {
     int freemb = FreeMB();
     int minutes = 0;
     {
     LOCK_RECORDINGS_READ
     double MBperMinute = Recordings->MBperMinute();
     minutes = int(double(freemb) / (MBperMinute > 0 ? MBperMinute : MB_PER_MINUTE));
     }
     SetTitle(cString::sprintf("%s%s%s (%i:%02i %s)",
                               hasmovecopy ? Icons::MovingRecording()
                                           : hascutter ? Icons::Scissor()
                                                       : "",
                               (hasmovecopy || hascutter) ? " " : "",
                               base ? base
                                    : delRecMenu ? tr("Deleted Recordings")
                                                 : trVDR("Recordings"),
                               minutes/60, minutes%60, trVDR("free")));
     }
  else
     SetTitle(cString::sprintf("%s%s%s",
                               hasmovecopy ? Icons::MovingRecording()
                                           : hascutter ? Icons::Scissor()
                                                       : "",
                               (hasmovecopy || hascutter) ? " " : "",
                               base ? base
                                    : delRecMenu ? tr("Deleted Recordings")
                                                 : trVDR("Recordings")));
}

void myMenuRecordings::SetHelpKeys(void)
{
  bool deletedrec = false;
  int Usage = 0;
  bool commands = false;
  buttonRed = NULL;
  buttonGreen = NULL;
  buttonYellow = NULL;
  buttonBlue = NULL;
//  actionCancel = NULL;
  if (!HasSubMenu()) {
     int newhelpkeys = 0;
     myMenuRecordingItem *ri = (myMenuRecordingItem *)Get(Current());
     if (ri) {
        Usage = ri->Recording()->IsInUse();

        if (ri->IsDirectory()) {
           commands = RecordingDirCommands.Count() ? true : false;
           if (!ri->GetDirIsMoving())
              newhelpkeys = 1;
           else
              newhelpkeys = 2;
           }
        else {
           commands = RecordingCommands.Count() ? true : false;
           newhelpkeys = 3;
           if (ri->Recording()) {
              if ((mysetup.UseVDRsRecInfoMenu) && (!ri->Recording()->Info()->Title())) {
                 newhelpkeys = 5;
                 }
              if (((Usage & ruSrc) != 0 && (Usage & ruMove) != 0) || ((Usage & ruSrc) != 0 && (Usage & ruCut) != 0))
                 newhelpkeys++; // => 4 or 6
              }
           }
        {
        LOCK_DELETEDRECORDINGS_READ;
        if (!commands || !mysetup.UndeleteTimeOut || (!mysetup.ButtonFirst && timeOut < mysetup.UndeleteTimeOut) || (mysetup.ButtonFirst && timeOut >= mysetup.UndeleteTimeOut))
           deletedrec = DeletedRecordings->Count() ? true : false;
        }
        if (delRecMenu)
           switch (newhelpkeys) {
              case 0: SetHelp(NULL); break;
              case 1: // 1 - 2 are for directory
              case 2: SetHelp(tr("Button$RECORDINGS")); break;
              case 3: // 3 - 6 are for recording
              case 4:
              case 5:
              case 6: SetHelp(tr("Button$RECORDINGS"), tr("Button$Undelete"), tr("Button$Destroy"), trVDR("Button$Info"));
              }
        else if (newhelpkeys != helpkeys  || EditMenu != editMenu || deletedrec != deletedRec) {
           switch (newhelpkeys) {
              case 1: // Directory
                      buttonRed    = editMenu ? tr("Button$Rename")
                                              : deletedrec ? tr("Button$UNDELETE")
                                                           : RecordingDirCommands.Count() ? tr("Button$Commands")
                                                                                          : trVDR("Button$Open");
                      buttonGreen  = editMenu ? tr("Button$Move")
                                              : NULL;
                      buttonYellow = editMenu ? NULL
                                              : trVDR("Button$Edit");
#ifdef COPYRECORDING
                      buttonBlue   = editMenu ? tr("Button$Copy")
                                              : NULL;
#else
                      buttonBlue   = NULL;
#endif
                      break;
              case 2: // Directory + Cancel
                      buttonRed    = deletedrec ? tr("Button$UNDELETE")
                                                : trVDR("Button$Open");
                      buttonGreen  = NULL;
                      buttonYellow = tr("Button$Cancel");
                      buttonBlue   = NULL;
                      break;
              case 3: // File
                      buttonRed    = editMenu ? tr("Button$Rename")
                                              : deletedrec ? tr("Button$UNDELETE")
                                                           : RecordingCommands.Count() ? tr("Button$Commands")
                                                                                       : trVDR("Button$Play");
                      buttonGreen  = editMenu ? tr("Button$Move")
                                              : trVDR("Button$Rewind");
                      buttonYellow = editMenu ? trVDR("Button$Delete")
                                              : trVDR("Button$Edit");
#ifdef COPYRECORDING
                      buttonBlue   = editMenu ? tr("Button$Copy")
                                              : trVDR("Button$Info");
#else
                      buttonBlue   = editMenu ? tr("Button$Details")
                                              : trVDR("Button$Info");
#endif
                      break;
              case 4: // File      + Cancel
                      buttonRed    = editMenu ? tr("Button$Rename")
                                              : deletedrec ? tr("Button$UNDELETE")
                                                           : RecordingCommands.Count() ? tr("Button$Commands")
                                                                                       : trVDR("Button$Play");
                      buttonGreen  = trVDR("Button$Rewind");
                      buttonYellow = tr("Button$Cancel");
                      buttonBlue   = trVDR("Button$Info");
                      break;
              case 5: // File      + Original Info
                      buttonRed    = editMenu ? tr("Button$Rename")
                                              : deletedrec ? tr("Button$UNDELETE")
                                                           : RecordingCommands.Count() ? tr("Button$Commands")
                                                                                       : trVDR("Button$Play");
                      buttonGreen  = editMenu ? tr("Button$Move")
                                              : trVDR("Button$Rewind");
                      buttonYellow = editMenu ? trVDR("Button$Delete")
                                              : trVDR("Button$Edit");
                      buttonBlue   = NULL;
                      break;
              case 6: // File      + Original Info + Cancel
                      buttonRed    = editMenu ? tr("Button$Rename")
                                              : deletedrec ? tr("Button$UNDELETE")
                                                           : RecordingCommands.Count() ? tr("Button$Commands")
                                                                                       : trVDR("Button$Play");
                      buttonGreen  = trVDR("Button$Rewind");
                      buttonYellow = tr("Button$Cancel");
                      buttonBlue   = NULL;
                      break;
              }
           SetHelp(buttonRed, buttonGreen, buttonYellow, buttonBlue);
           }
        helpkeys = newhelpkeys;
        deletedRec = deletedrec;
        EditMenu = editMenu;
        }
     }
}

// create the menu list
void myMenuRecordings::Set(bool Refresh)
{
  if ((ebene == Ebene) && delRecMenu ? cRecordings::GetDeletedRecordingsRead(recordingsStateKey) : cRecordings::GetRecordingsRead(recordingsStateKey)) {
     recordingsStateKey.Remove();
     const char *CurrentRecording = *fileName ? *fileName : myReplayControl::LastReplayed();
     cRecordings *Recordings = delRecMenu ? cRecordings::GetDeletedRecordingsWrite(recordingsStateKey) : cRecordings::GetRecordingsWrite(recordingsStateKey); // write access is necessary for sorting!
     myMenuRecordingItem *LastItem = NULL;
     hascutter = false;
     hasmovecopy = false;
     fsid = 0;
     if (myMenuRecordingItem *ri = (myMenuRecordingItem*)Get(Current()))
        CurrentRecording = ri->Recording()->FileName();
     int current = Current();
     Clear();
     GetRecordingsSortMode(DirectoryName());
     Recordings->Sort();
#ifdef USE_PINPLUGIN
     bool hidepinprotectedrecs = false;
     if (cPlugin *pinplugin = cPluginManager::GetPlugin("pin"))
        hidepinprotectedrecs = pinplugin->SetupParse("hideProtectedRecordings","1");
#endif
     for (const cRecording *Recording = Recordings->First(); Recording; Recording = Recordings->Next(Recording)) {
        if ((!filter || filter->Filter(Recording)) && (!base || (strstr(Recording->Name(), base) == Recording->Name() && Recording->Name()[strlen(base)] == FOLDERDELIMCHAR))) {
           myMenuRecordingItem *Item = new myMenuRecordingItem(Recording, level);
           myMenuRecordingItem *LastDir = NULL;
           if (Item->IsDirectory()) {
              // Sorting may ignore non-alphanumeric characters, so we need to explicitly handle directories in case they only differ in such characters:
              for (myMenuRecordingItem *p = LastItem; p; p = dynamic_cast<myMenuRecordingItem *>(p->Prev())) {
                 if (p->Name() && strcmp(p->Name(), Item->Name()) == 0) {
                    LastDir = p;
                    break;
                    }
                 }
              }
           if (*Item->Text() && !LastDir) {
#ifdef USE_PINPLUGIN
              if (!(hidepinprotectedrecs && cStatus::MsgReplayProtected(Recording, Item->Name(), base, Item->IsDirectory(), true))) {
#endif
                 Add(Item);
                 LastItem = Item;
                 if (Item->IsDirectory())
                    LastDir = Item;
#ifdef USE_PINPLUGIN
                 }
              else
                 LastItem = NULL;
#endif
              }
           else
              delete Item;
           if (LastItem || LastDir) {
              if (LastItem) {
                 int Usage = Recording->IsInUse();
                 if ((Usage & (ruSrc | ruDst)) != 0 && (Usage & (ruMove | ruCopy)) != 0) {
                    LastItem->SetDirIsMoving(true);
                    hasmovecopy = true;
                    }
                 if ((Usage & ruSrc) != 0 && (Usage & ruCut) != 0)
                    hascutter = true;
                 }
              if (*path) {
                 if (strcmp(path, Recording->Folder()) == 0)
                    SetCurrent(LastDir ? LastDir : LastItem);
                 }
              else if (CurrentRecording && strcmp(CurrentRecording, Recording->FileName()) == 0) {
                 SetCurrent(LastDir ? LastDir : LastItem);
                 if (Item && !Item->IsDirectory() && (Item->IsDVD() || Item->IsHDD()) && !cControl::Control())
                    cReplayControl::ClearLastReplayed(cReplayControl::LastReplayed());
                 }
              }
           if (LastDir)
              LastDir->IncrementCounter(Recording->IsNew());
           }
        }
     if (Current() < 0)
        SetCurrent(Get(current)); // last resort, in case the recording was deleted
     SetMenuSortMode(RecordingsSortMode == rsmName ? msmName : msmTime);
     recordingsStateKey.Remove(false); // sorting doesn't count as a real modification
     ForceFreeMbUpdate();
     Title();
     if (Refresh) {
        SetHelpKeys();
        Display();
        }
     }
}

void myMenuRecordings::SetPath(const char *Path)
{
  path = Path;
}

void myMenuRecordings::SetRecording(const char *FileName)
{
  fileName = FileName;
}

cString myMenuRecordings::DirectoryName(void)
{
  cString d(cVideoDirectory::Name());
  if (base) {
     char *s = ExchangeChars(strdup(base), true);
     d = AddDirectory(d, s);
     free(s);
     }
  return d;
}

// opens a subdirectory
bool myMenuRecordings::Open(bool OpenSubMenus)
{
  myMenuRecordingItem *ri = (myMenuRecordingItem*)Get(Current());
  if (ri && ri->IsDirectory() && (!*path || strcountchr(path, FOLDERDELIMCHAR) > 0)) {
     const char *t = ri->Name();
     cString buffer;
     if (base) {
        buffer = cString::sprintf("%s%c%s", base, FOLDERDELIMCHAR, t);
        t = buffer;
        }
     AddSubMenu(new myMenuRecordings(t, level + 1, OpenSubMenus, filter, delRecMenu));
     return true;
     }
  return false;
}

// plays a recording
eOSState myMenuRecordings::Play(void)
{
  if (myMenuRecordingItem *ri = (myMenuRecordingItem*)Get(Current())) {
#ifdef USE_PINPLUGIN
     if (cStatus::MsgReplayProtected(ri->Recording(), ri->Name(), base, ri->IsDirectory()) == true)
        return osContinue;
#endif
     if (ri->IsDirectory())
        Open();
     else {
        if (ri->Recording()) {
           if (ri->IsHDD()) {
              if (!MountHDD(ri->Recording()->FileName()))
                 return osContinue;
              }
           else if (ri->IsDVD()) {
              int i = 0;
              i = (MountDVD(ri->Recording()->FileName()));
              if (i == 0) return osContinue;
              else if (i == 2) return osEnd;
              }
           myReplayControl::SetRecording(ri->Recording()->FileName());
           cControl::Shutdown();
           isyslog("[extrecmenu] starting replay of recording");
           cControl::Launch(new myReplayControl());
           return osEnd;
           }
        }
     }
  return osContinue;
}

bool myMenuRecordings::MountHDD(const char *Name)
{
  cString msg;
  const char *name = NULL;
  char path[MaxFileName];
  char hddnr[BUFSIZ];
  cString buffer;
  FILE *f;

  buffer = cString::sprintf("%s/hdd.vdr", Name);
  if ((f = fopen(buffer, "r")) != NULL) {
     // get the hdd id
     if (fgets(hddnr, sizeof(hddnr), f)) {
        char *p = strchr(hddnr, '\n');
        if (p)
           *p = 0;
        }
     fclose(f);
     }
  if (Interface->Confirm(cString::sprintf(tr("Please attach Archive-HDD %s"), hddnr))) {
     // recording is an archive hdd
     strcpy(path, Name);
     name = strrchr(path, '/') + 1;
     msg = cString::sprintf("hddarchive.sh mount \"%s\" '%s'", *strescape(path, "'"), *strescape(name, "'\\\"$"));
     isyslog("[extrecmenu] calling %s to mount Archive-HDD", *msg);
     int result = SystemExec(msg);
     isyslog("[extrecmenu] hddarchive.sh returns %d", result);
     if (result) {
        result = result/256;
        if (result == 1)
           Skins.Message(mtError, tr("Error while mounting Archive-HDD!"));
        if (result == 3)
           Skins.Message(mtError, tr("Recording not found on Archive-HDD!"));
        if (result == 4)
           Skins.Message(mtError, tr("Error while linking [0-9]*.vdr!"));
        if (result == 5)
           Skins.Message(mtError, tr("sudo or mount --bind / umount error (vfat system)"));
        if (result == 127)
           Skins.Message(mtError, tr("Script 'hddarchive.sh' not found!"));
        return false;
        }
     washdd = true;
     }
  else {
     return false;
     }
  return true;
}

int myMenuRecordings::MountDVD(const char *Name)
{
  cString msg;
  const char *name = NULL;
  char path[MaxFileName];
  bool isvideodvd = false;
  char dvdnr[BUFSIZ];
  cString buffer;
  FILE *f;

  buffer = cString::sprintf("%s/dvd.vdr", Name);
  if ((f = fopen(buffer, "r")) != NULL) {
     // get the dvd id
     if (fgets(dvdnr, sizeof(dvdnr), f)) {
        char *p = strchr(dvdnr, '\n');
        if (p)
           *p = 0;
        }
     // determine if dvd is a video dvd
     char tmp[BUFSIZ];
     if (fgets(tmp, sizeof(dvdnr) ,f))
        isvideodvd = true;
     fclose(f);
     }
  if (Interface->Confirm(cString::sprintf(tr("Please insert DVD %s"), dvdnr))) {
     // recording is a video dvd
     if (isvideodvd) {
        cPlugin *plugin = cPluginManager::GetPlugin("dvd");
        if (plugin) {
           cOsdObject *osd=plugin->MainMenuAction();
           delete osd;
           osd = NULL;
           return 2;
           }
        else {
           Skins.Message(mtError, tr("DVD plugin is not installed!"));
           return false;
           }
        }
     // recording is an archive dvd
     else {
        strcpy(path, Name);
        name = strrchr(path, '/') + 1;
        msg = cString::sprintf("dvdarchive.sh mount \"%s\" '%s'", *strescape(path, "'"), *strescape(name, "'\\\"$"));
        isyslog("[extrecmenu] calling %s to mount dvd", *msg);
        int result = SystemExec(msg);
        isyslog("[extrecmenu] dvdarchive.sh returns %d", result);
        if (result) {
           result=result/256;
           if (result == 1)
              Skins.Message(mtError, tr("Error while mounting DVD!"));
           if (result == 2)
              Skins.Message(mtError, tr("No DVD in drive!"));
           if (result == 3)
              Skins.Message(mtError, tr("Recording not found on DVD!"));
           if (result == 4)
              Skins.Message(mtError, tr("Error while linking [0-9]*.vdr!"));
           if (result == 5)
              Skins.Message(mtError, tr("sudo or mount --bind / umount error (vfat system)"));
           if (result == 127)
              Skins.Message(mtError, tr("Script 'dvdarchive.sh' not found!"));
           return false;
           }
        wasdvd = true;
        }
     }
  else {
     return false;
     }
  return true;
}

// plays a recording from the beginning
eOSState myMenuRecordings::Rewind(void)
{
  if (HasSubMenu() || Count() == 0)
     return osContinue;
  myMenuRecordingItem *ri = (myMenuRecordingItem*)Get(Current());
  if (ri && !ri->IsDirectory()) {
     cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording
     cResumeFile ResumeFile(ri->Recording()->FileName(), ri->Recording()->IsPesRecording());
     ResumeFile.Delete();
     return Play();
     }
  return osContinue;
}

// Undelete a recording
eOSState myMenuRecordings::Undelete(void)
{
  if (HasSubMenu() || Count() == 0)
     return osContinue;
  if (myMenuRecordingItem *ri = (myMenuRecordingItem *)Get(Current())) {
     if (ri->IsDirectory())
        return osContinue;
     else {
        if (Interface->Confirm(tr("Restore recording?"))) {
           cRecording *recording = ((cRecording *)ri->Recording());
           if (recording) {
              if (UndeleteRecording(recording)) {
                 cOsdMenu::Del(Current());
                 if (cRecordings *DeletedRecordings = cRecordings::GetDeletedRecordingsWrite(recordingsStateKey)) { // write access is necessary for sorting!
                    DeletedRecordings->Del(recording);
                    recordingsStateKey.Remove(true);
                    }
                 else
                    Skins.Message(mtError, tr("Error while removing recording!"));
                 Display();
                 if (!Count())
                    return osUserRecEmpty;
                 return osUserRecRemoved;
                 }
              else
                 Skins.Message(mtError, tr("Error while restoring recording!"));
              }
           }
        }
     }
  return osContinue;
}

// delete a deleted recording
eOSState myMenuRecordings::Erase(void)
{
  if (HasSubMenu() || Count() == 0)
     return osContinue;
  if (myMenuRecordingItem *ri = (myMenuRecordingItem *)Get(Current())) {
     if (ri->IsDirectory())
        return osContinue;
     else {
        if (Interface->Confirm(trVDR("Delete recording?"))) {
           cRecording *recording = ((cRecording *)ri->Recording());
           if (recording) {
              if (recording->Remove()) {
                 cOsdMenu::Del(Current());
                 if (cRecordings *DeletedRecordings = cRecordings::GetDeletedRecordingsWrite(recordingsStateKey)) { // write access is necessary for sorting!
                    DeletedRecordings->Del(recording);
                    recordingsStateKey.Remove(true);
                    }
                 else
                    Skins.Message(mtError, tr("Error while removing recording!"));
                 Display();
                 if (!Count())
                    return osUserRecEmpty;
                 return osUserRecRemoved;
                 }
              else
                 Skins.Message(mtError, tr("Error while removing recording!"));
              }
           }
        }
     }
  return osContinue;
}

// delete a recording
eOSState myMenuRecordings::Delete(void)
{
  editMenu = false;
  if (HasSubMenu() || Count()==0)
    return osContinue;
  myMenuRecordingItem *ri = (myMenuRecordingItem*)Get(Current());
  if (ri && !ri->IsDirectory()) {
     if (Interface->Confirm(trVDR("Delete recording?"))) {
        if (cRecordControl *rc = cRecordControls::GetRecordControl(ri->Recording()->FileName())) {
           if (Interface->Confirm(trVDR("Timer still recording - really delete?"))) {
              if (cTimer *Timer = rc->Timer()) {
                 LOCK_TIMERS_WRITE;
                 Timer->Skip();
                 cRecordControls::Process(Timers, time(NULL));
                 if (Timer->IsSingleEvent()) {
                    Timers->Del(Timer);
                    isyslog("deleting timer %s", *Timer->ToDescr());
                    }
                 }
              }
           else
              return osContinue;
           }
        cString FileName;
        {
        LOCK_RECORDINGS_READ;
        if (const cRecording *Recording = Recordings->GetByName(ri->Recording()->FileName())) {
           FileName = Recording->FileName();
           if (RecordingsHandler.GetUsage(FileName)) {
              if (!Interface->Confirm(trVDR("Recording is being edited - really delete?")))
                 return osContinue;
              }
           }
        }
        RecordingsHandler.Del(FileName); // must do this w/o holding a lock, because the cleanup section in cDirCopier::Action() might request one!
        if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), FileName) == 0)
           cControl::Shutdown();
        cRecordings *Recordings = cRecordings::GetRecordingsWrite(recordingsStateKey);
        Recordings->SetExplicitModify();
        cRecording *Recording = Recordings->GetByName(FileName);
        if (!Recording || Recording->Delete()) {
           myReplayControl::ClearLastReplayed(FileName);
           Recordings->DelByName(FileName);
           cOsdMenu::Del(Current());
           SetHelpKeys();
           ForceFreeMbUpdate();
           Recordings->SetModified();
           recordingsStateKey.Remove();
           Display();
           if (!Count()) {
              return osUserRecEmpty;
              }
           return osUserRecRemoved;
           }
        else
           Skins.Message(mtError, trVDR("Error while deleting recording!"));
        recordingsStateKey.Remove();
        }
     }
  SetHelpKeys();
  return osContinue;
}

// renames a recording
eOSState myMenuRecordings::MoveRename(int i)
{
  editMenu = false;
#ifdef COPYRECORDING
  if (HasSubMenu() || Count() == 0)
#else
  if (HasSubMenu() || Count() == 0 || i == 3) // i==3 ignores CopyRecording if COPYRECORDING is not defined
#endif
     return osContinue;
  if (myMenuRecordingItem *ri = (myMenuRecordingItem*)Get(Current())) {
     if (ri->IsDirectory())
        return AddSubMenu(new myMenuPathEdit(cString(ri->Recording()->Name(), strchrn(ri->Recording()->Name(), FOLDERDELIMCHAR, ri->Level() + 1)), i));
     else if (ri->Recording())
        return AddSubMenu(new myMenuRecordingEdit(ri->Recording(), i));
  }
  return osContinue;
}

// edit details of a recording
eOSState myMenuRecordings::Details(void)
{
  editMenu = false;
  if (HasSubMenu() || Count() == 0)
     return osContinue;
  if (myMenuRecordingItem *ri = (myMenuRecordingItem*)Get(Current())) {
     if (ri->Recording())
        return AddSubMenu(new myMenuRecordingEdit(ri->Recording(), 4));
     }
  return osContinue;
}

// opens an info screen to a recording
eOSState myMenuRecordings::Info(void)
{
  if (HasSubMenu() || Count()==0)
    return osContinue;
  if (myMenuRecordingItem *ri = (myMenuRecordingItem*)Get(Current())) {
     if (ri->IsDirectory())
        return osContinue;
     else {
        if (mysetup.UseVDRsRecInfoMenu && (!ri->Recording() || (ri->Recording() && !ri->Recording()->Info()->Title())))
           return osContinue;
        else
           return AddSubMenu(new myMenuRecording(ri->Recording(), !delRecMenu, delRecMenu));
        }
     }
  return osContinue;
}

// execute a command for a recording
eOSState myMenuRecordings::Commands(eKeys Key)
{
  if (HasSubMenu() || Count()==0)
    return osContinue;

  if (myMenuRecordingItem *ri = (myMenuRecordingItem*)Get(Current())) {
     char *parameter = NULL;
     if (ri->IsDirectory()) {
        char *strBase = base ? ExchangeChars(strdup(base), true) : NULL;
        char *strName = ExchangeChars(strdup(ri->Name()), true);
        if (-1 == asprintf(&parameter, "\"%s/%s/%s\"", cVideoDirectory::Name(), strBase ? strBase : "", strName))
           parameter = NULL;
        free(strBase);
        free(strName);
        }
     else {
        if (-1 == asprintf(&parameter, "\"%s\"", *strescape(ri->Recording()->FileName(), "\\\"$")))
           parameter=NULL;
        }
     cMenuCommands *menu;
     eOSState state = AddSubMenu(menu = new cMenuCommands(trVDR("Recording commands"), ri->IsDirectory() ? &RecordingDirCommands : &RecordingCommands, parameter ? parameter : ""));
     free(parameter);
     if (Key != kNone)
        state = menu->ProcessKey(Key);
     return state;
     }
  return osContinue;
}

eOSState myMenuRecordings::Sort(void)
{
  if (HasSubMenu())
     return osContinue;
  IncRecordingsSortMode(DirectoryName());
  recordingsStateKey.Reset();
  Set(true);
  return osContinue;
}

eOSState myMenuRecordings::ProcessKey(eKeys Key)
{
  bool EditingActive = RecordingsHandler.Active();

  eOSState state = cOsdMenu::ProcessKey(Key);

  if (state == osUnknown || state == osBack) {
     myMenuRecordingItem *ri = (myMenuRecordingItem*)Get(Current());

     if (editMenu) {
        if (Key == kRed || Key == kGreen || Key == kYellow || (!ri->IsDVD() && !ri->IsHDD() && Key == kBlue) || Key == kBack)
           helpkeys = -1;
        }

     int newhelpkeys = 0;
     if (ri) {
        int Usage = ri->Recording()->IsInUse();

        if (ri->IsDirectory()) {
           if (!ri->GetDirIsMoving())
              newhelpkeys = 1;
           else
              newhelpkeys = 2;
           }
        else {
           newhelpkeys = 3;
           if (ri->Recording()) {
              if ((mysetup.UseVDRsRecInfoMenu) && (!ri->Recording()->Info()->Title())) {
                 newhelpkeys = 5;
                 }
              if (((Usage & ruSrc) != 0 && (Usage & ruMove) != 0) || ((Usage & ruSrc) != 0 && (Usage & ruCut) != 0))
                 newhelpkeys++; // => 4 or 6
              }
           }
        }

     switch(Key) {
       case kPlayPause:
       case kPlay:
       case kOk:     if (delRecMenu) {Open(); return osContinue;}
                     else return editMenu ? osContinue
                                          : Play();
       case kRed:    if (delRecMenu) {CloseSubMenu(false); delRecMenu = false; if (base) return state;
                                                                               else return osBack;}
                     else {
                        return editMenu ? MoveRename(1) // Name
                                        : deletedRec ? AddSubMenu(new myMenuRecordings(NULL, 0, false, NULL, true))
                                                     : (helpkeys > 0 && ri &&
                                                        ((ri->IsDirectory() && RecordingDirCommands.Count()) ||
                                                         (!ri->IsDirectory() && RecordingCommands.Count()))
                                                       ) ? Commands()
                                                         : Play();
                        }
       case kGreen:  return delRecMenu ? Undelete()
                                       : editMenu ? MoveRename(2) // Move
                                                  : Rewind();
       case kYellow: if (delRecMenu) return Erase();
                     else
                        if (editMenu) {
                           return (newhelpkeys == 1 || newhelpkeys == 2) ? osContinue
                                                                         : Delete();
                           }
                        else
                           if (newhelpkeys == 2 || newhelpkeys == 4 || newhelpkeys == 6) { // in progress
                              if (Interface->Confirm(tr("Really cancel?")))
                                 RecordingsHandler.Del(ri->Recording()->FileName()); // abort processing
                              return osContinue;
                              }
                           else { // switch to editMenu
                              editMenu = true; SetHelpKeys(); return osContinue;
                              }
       case kInfo:   return Info();
       case kBlue:   return delRecMenu ? Info()
#ifdef COPYRECORDING
                                       : editMenu ? MoveRename(3) // Copy
#else
                                       : editMenu ? MoveRename(4) // Rest
#endif
                                                  : Info(); // even recordings on DVD or HDD have Info()
       case k0:      return Sort();
       case k1...k9: return (delRecMenu || editMenu) ? osContinue
                                                     : Commands(Key);
       case kBack:   if (editMenu) {editMenu = false; SetHelpKeys(); Key = kNone; state = osContinue;}
       default: break;
       }
     }
  else if (state == osUserRecRenamed) {
     // a recording or path was renamed, so let's refresh the menu
     CloseSubMenu(false);  // this is the myMenuRecordingEdit/myMenuPathEdit
     path = NULL;
     fileName = NULL;
     state = osContinue;
     }
  else if (state == osUserRecCopied) {
     CloseSubMenu(false); // this is the myMenuRecordingEdit/myMenuPathEdit
     path = NULL;
     fileName = NULL;
     Set(); // the recording might have been copied into a new subfolder of this folder
     Display();
     state = osContinue;
     }
  else if (state == osUserRecMoved) {
     // a recording was moved to a different folder, so let's delete the old item
     CloseSubMenu(false); // this is the myMenuRecordingEdit/myMenuPathEdit
     path = NULL;
     fileName = NULL;
     cOsdMenu::Del(Current());
     Set(); // the recording might have been moved into a new subfolder of this folder
     if (!Count())
        return osUserRecEmpty;
     Display();
     state = osUserRecRemoved;
     }
  else if (state == osUserRecRemoved) {
     // a recording was removed from a sub folder, so update the current item
     if (cOsdMenu *m = SubMenu()) {
        if (myMenuRecordingItem *ri = (myMenuRecordingItem *)Get(Current())) {
           if (myMenuRecordingItem *riSub = (myMenuRecordingItem *)m->Get(m->Current()))
              ri->SetRecording(riSub->Recording());
           }
        }
     // no state change here, this report goes upstream!
     }
  else if (state == osUserRecEmpty) {
     // a subfolder became empty, so let's go back up
     CloseSubMenu(false); // this is the now empty submenu
     cOsdMenu::Del(Current()); // the menu entry of the now empty subfolder
     Set(); // in case a recording was moved into a new subfolder of this folder
     if ((base || delRecMenu) && !Count()) // base: don't go up beyond the top level Recordings menu
        return state;
     Display();
     state = osContinue;
     }
  if (!HasSubMenu()) {
     Set(true);
     // a subfolder became empty, so let's go back up
     if (base && !Count()) { // base: don't go up beyond the top level Recordings menu
        CloseSubMenu(false); // this is the now empty submenu
        if (!Count())
           return osBack;
     }
     if (Key != kNone) {
        timeOut = 0;
        SetHelpKeys();
        }
     if (EditingActive) {
        if (mysetup.RecRefreshRate > 0 && counter == mysetup.RecRefreshRate) {
           counter = 0;
           LOCK_RECORDINGS_WRITE; // to trigger a state change
           }
        counter++;
        }
     if (!delRecMenu && mysetup.UndeleteTimeOut) {
        int delRecordings;
        {
        LOCK_DELETEDRECORDINGS_READ;
        delRecordings = DeletedRecordings->Count();
        }
        if (delRecordings && timeOut < mysetup.UndeleteTimeOut) {
           timeOut++;
           SetHelpKeys();
           }
        }
     }
  return state;
}
