/* * undelete: A plugin for the Video Disk Recorder * * See the README file for copyright information and how to reach the author. * * $Id: menuundelete.c 0.5 2005/11/17 21:05:05 hflor Exp $ */ #include "menuundelete.h" #if VDRVERSNUM < 10307 #include "menutext.h" #endif #include "undelete.h" #include "menudispkey.h" #include "i18n.h" #include "vdrtools.h" #include #include #include #ifdef HAVE_ICONPATCH #include #endif #include // --- cMenuRecordingSelectItem -------------------------------------------------------- cMenuRecordingSelectItem::cMenuRecordingSelectItem(cRecording *Recording, int Level) { #ifdef UND_Debug objID = ++cMenuRecordingSelectItem_nextID; #endif filename = strdup(Recording->FileName()); totalEntries = newEntries = 0; start = Recording->start; SetText(Recording->Title('\t', true, Level)); if ((isdir = (*Text() == '\t'))) name = strdup(Text() + 2); else name = strdup(Recording->Name()); level = Level; d2syslog("cMenuRecordingSelectItem::cMenuRecordingSelectItem", "FileName=%s Level=%d Name=%s", filename, level, name); } cMenuRecordingSelectItem::~cMenuRecordingSelectItem() { d2syslog("cMenuRecordingSelectItem::~cMenuRecordingSelectItem", "FileName=%s Name=%s", filename, name); freenull(filename); freenull(name); } #if VDRVERSNUM >= 10315 int cMenuRecordingSelectItem::compare(const cListObject &ListObject) const { cMenuRecordingSelectItem *temp = (cMenuRecordingSelectItem *)&ListObject; if (isdir != temp->isdir) return isdir ? -1 : 1; if (isdir) return strcasecmp(name, temp->name); return start == temp->start ? 0 : start < temp->start ? -1 : 1; } #else bool cMenuRecordingSelectItem::operator< (const cListObject &ListObject) { cMenuRecordingSelectItem *temp = (cMenuRecordingSelectItem *)&ListObject; if (isdir != temp->isdir) return isdir; if (isdir) return strcasecmp(name, temp->name) < 0; return start < temp->start; } #endif void cMenuRecordingSelectItem::IncrementCounter(bool New) { totalEntries++; if (New) newEntries++; char *buffer = NULL; asprintf(&buffer, "%d\t%d\t%s", totalEntries, newEntries, name); SetText(buffer, false); d2syslog("cMenuRecordingSelectItem::IncrementCounter", "FileName=%s New=%s Buffer=%s", filename, New ? "true" : "false", buffer); } void cMenuRecordingSelectItem::RefreshCounter(void) { d2syslog("cMenuRecordingSelectItem::RefeshCounter", "FileName=%s", filename); if (isdir) { char *dirname = DirName(); if (dirname) { totalEntries = 0; newEntries = 0; for (cRecording *recording = DeletedRecordings.First(); recording; recording = DeletedRecordings.Next(recording)) if (!strncmp(recording->FileName(), dirname, strlen(dirname))) if (strchr(recording->FileName() + strlen(dirname), '/')) IncrementCounter(recording->IsNew()); free(dirname); } } } char *cMenuRecordingSelectItem::DirName(bool Parent) { char *temp = NULL; if (isdir || Parent) { temp = strdup(filename); char *last = temp + strlen(VideoDirectory) + 1; for (int i = level; *last && i >= Parent ? -1 : 0; i--) { last = strchr(last, '/'); if (!last) break; last++; } if (last) *last = 0; } d2syslog("cMenuRecordingSelectItem::DirName", "Level=%d Parent=%s DirName=%s", level, Parent ? "yes" : "no", temp ? temp : "(NULL)"); return temp; } // --- cMenuRecordingSelect -------------------------------------------------------- cMenuRecordingSelect::cMenuRecordingSelect(const char *Base, int Level) #if VDRVERSNUM >= 10307 :cOsdMenu(Base ? Base : tr("Deleted Recordings"), 9, 6, 6) #else :cOsdMenu(Base ? Base : tr("Deleted Recordings"), 6, 6, 6) #endif { #ifdef UND_Debug objID = ++cMenuRecordingSelect_nextID; #endif d2syslog("cMenuRecordingSelect::cMenuRecordingSelect", "Base=%s Level=%d", Base, Level); STATUS(tr("Display$prepare recording display...")); base = Base ? strdup(Base) : NULL; level = Level; top_line[0] = top_line[1] = bottom_line[0] = bottom_line[1] = false; Display(); cMenuRecordingSelectItem *LastItem = NULL; char *LastItemText = NULL; for (cRecording *recording = DeletedRecordings.First(); recording; recording = DeletedRecordings.Next(recording)) { if (!Base || (strstr(recording->Name(), Base) == recording->Name() && recording->Name()[strlen(Base)] == '~')) { cMenuRecordingSelectItem *Item = new cMenuRecordingSelectItem(recording, level); if (*Item->Text() && (!LastItem || strcmp(Item->Text(), LastItemText))) { Add(Item); LastItem = Item; freenull(LastItemText); LastItemText = strdup(LastItem->Text()); } else delete Item; if (LastItem) { if (LastItem->IsDirectory()) LastItem->IncrementCounter(recording->IsNew()); } } } freenull(LastItemText); Sort(); AddFunctionLines(); d1listlog("cMenuRecordingSelect::cMenuRecordingSelect"); SetCurrent(First()); SetHelpKeys(); } cMenuRecordingSelect::~cMenuRecordingSelect() { d2syslog("cMenuRecordingSelect::~cMenuRecordingSelect", "Base=%s Level=%d", base, level); freenull(base); } bool cMenuRecordingSelect::IsMenuRecordingSelectItem(cOsdItem *Item) { if (Item) { d1syslog("cMenuRecordingSelect::IsMenuRecordingSelectItem", "Type(Item)=%s Text(Item)=%s", typeid(*Item).name(), Item->Text()); return typeid(*Item) == typeid(cMenuRecordingSelectItem); } d1syslog("cMenuRecordingSelect::IsMenuRecordingSelectItem", "Item=%s", "(NULL)"); return false; } bool cMenuRecordingSelect::IsDirectory(cOsdItem *Item) { if (IsMenuRecordingSelectItem(Item)) return ((cMenuRecordingSelectItem *)Item)->IsDirectory(); return false; } bool cMenuRecordingSelect::IsRecording(cOsdItem *Item) { if (IsMenuRecordingSelectItem(Item)) return !((cMenuRecordingSelectItem *)Item)->IsDirectory(); return false; } bool cMenuRecordingSelect::IsReady(void) { if (WorkFilename) esyslog("%s: Error previous jobs not completed (%s)", plugin_name, WorkFilename); return !WorkFilename; } cRecording *cMenuRecordingSelect::GetRecording(cOsdItem *Item) { if (IsRecording(Item)) { cRecording *recording = DeletedRecordings.GetByName(((cMenuRecordingSelectItem *)Item)->FileName()); if (!recording) ERROR(tr("Error while accessing recording!")); return recording; } else return NULL; } void cMenuRecordingSelect::AddFunctionLines(void) { d1syslog("cMenuRecordingSelect::AddFunctionLines", "Count=%d purge=%s lines=%d salvage=%s lines=%d", Count(), FunctionLine[functionline_b_e[0].u], functionline_num[0].u, FunctionLine[functionline_b_e[1].u], functionline_num[1].u); for (int i = 0; i < 2; i++) { if (functionline_b_e[i].u != 0x0 && Count()) { if (functionline_b_e[i].u & 0x1) top_line[i] = true; if ((functionline_b_e[i].u & 0x2) && (!top_line[i] || Count() >= functionline_num[i].u)) bottom_line[i] = true; } } if (firstsalvage.u) { if (top_line[0]) Ins(new cOsdItem(tr("Display$Purge all records ..."), osUser2)); if (top_line[1]) Ins(new cOsdItem(tr("Display$Salavge all records ..."), osUser3)); if (bottom_line[0]) Add(new cOsdItem(tr("Display$Purge all records ..."), osUser2)); if (bottom_line[1]) Add(new cOsdItem(tr("Display$Salavge all records ..."), osUser3)); } else { if (top_line[1]) Ins(new cOsdItem(tr("Display$Salavge all records ..."), osUser3)); if (top_line[0]) Ins(new cOsdItem(tr("Display$Purge all records ..."), osUser2)); if (bottom_line[1]) Add(new cOsdItem(tr("Display$Salavge all records ..."), osUser3)); if (bottom_line[0]) Add(new cOsdItem(tr("Display$Purge all records ..."), osUser2)); } d1syslog("cMenuRecordingSelect::AddFunctionLines", "new top(purge)=%s top(salavge)=%s bottom(purge)=%s bottom(salvage)=%s", top_line[0] ? "yes" : "no", top_line[1] ? "yes" : "no", bottom_line[0] ? "yes" : "no", bottom_line[1] ? "yes" : "no"); } void cMenuRecordingSelect::DelFunctionLines(void) { if (top_line[0] || top_line[1]) Del(0); if (top_line[0] && top_line[1]) Del(0); if (bottom_line[0] || bottom_line[1]) Del(Count() - 1); if (bottom_line[0] && bottom_line[1]) Del(Count() - 1); top_line[0] = top_line[1] = bottom_line[0] = bottom_line[1] = false; } void cMenuRecordingSelect::SetHelpKeys(void) { const char *helptext[4] = { NULL, NULL, NULL, NULL }; cOsdItem *item = Get(Current()); for (int i = 2; i < MaxFunctionName; i++) { bool functionavailable = false; switch (i) { case 2: case 3: functionavailable = IsMenuRecordingSelectItem(item); break; case 4: case 5: functionavailable = Count(); break; case 6: functionavailable = IsDirectory(item); break; case 7: if (IsRecording(item)) { cRecording *recording = GetRecording(item); #if VDRVERSNUM >= 10325 functionavailable = (recording && recording->Info() && recording->Info()->Description() && *recording->Info()->Description()); #else functionavailable = (recording && recording->Summary() && *recording->Summary()); #endif } break; case 8: functionavailable = KeyState != 1 || functionkey[8].u != functionkey[9].u; break; case 9: functionavailable = KeyState != 2 || functionkey[8].u != functionkey[9].u; break; case 10: functionavailable = true; break; } if (functionavailable) switch (functionkey[i].u) { case 1: case 3: case 5: case 7: if (KeyState == 1) helptext[functionkey[i].u >> 1] = FunctionHotKey[i]; break; case 2: case 4: case 6: case 8: if (KeyState == 2) helptext[(functionkey[i].u - 1) >> 1] = FunctionHotKey[i]; break; } } SetHelp(helptext[0], helptext[1], helptext[2], helptext[3]); d2syslog("cMenuRecordingSelect::SetHelpKeys", "KeyState=%d Item=%s red=%s green=%s yellow=%s blue=%s", KeyState, item ? item->Text() : "(NULL)", helptext[0] ? helptext[0] : "(NULL)", helptext[1] ? helptext[1] : "(NULL)", helptext[2] ? helptext[2] : "(NULL)", helptext[3] ? helptext[3] : "(NULL)"); } eOSState cMenuRecordingSelect::FunctionCall(int FunctionNumber) { eOSState state = osUnknown; cOsdItem *item = Get(Current()); d2syslog("cMenuRecordingSelect::FunctionCall", "FunctionNumber=%d HasSubMenu=%s Item=%s", FunctionNumber, HasSubMenu() ? "yes" : "no", item ? item->Text() : "(NULL)"); cMenuRecordingSelectItem *ri = NULL; cRecording *recording = NULL; if (!item || HasSubMenu()) return osContinue; switch (FunctionNumber) { // open/summary case 1: if (IsDirectory(item)) state = FunctionCall(6); else if (IsRecording(item)) state = FunctionCall(7); break; // purge case 2: state = osContinue; if (IsMenuRecordingSelectItem(item) && IsReady()) { ri = (cMenuRecordingSelectItem *)item; if ((!ri->IsDirectory() && (!confirmpurge.u || Interface->Confirm(tr("Question$Purge recording?")))) || (ri->IsDirectory() && (!confirmpurgedir.u || Interface->Confirm(tr("Question$Purge recordings in diretory?"))))) { cRemote::Put((eKeys)kPurgeRec); if (ri->IsDirectory()) WorkFilename = ri->DirName(); // WorkFilename is NULL see IsReady() else WorkFilename = strdup(ri->FileName()); } } break; // salvage case 3: state = osContinue; if (IsMenuRecordingSelectItem(item) && IsReady()) { ri = (cMenuRecordingSelectItem *)item; if ((!ri->IsDirectory() && (!confirmsalvage.u || Interface->Confirm(tr("Question$Salvage recording?")))) || (ri->IsDirectory() && (!confirmsalvagedir.u || Interface->Confirm(tr("Question$Salvage recordings in directory?"))))) { cRemote::Put((eKeys)kSalvageRec); if (ri->IsDirectory()) WorkFilename = ri->DirName(); // WorkFilename is NULL see IsReady() else WorkFilename = strdup(ri->FileName()); } } break; // purge all case 4: state = osContinue; if (IsReady() && (!confirmpurgeall.u || Interface->Confirm(tr("Question$Purge all recordings?")))) { cRemote::Put((eKeys)kPurgeRec); for (item = First(); !WorkFilename && item; item = Next(item)) if (IsMenuRecordingSelectItem(item)) WorkFilename = ((cMenuRecordingSelectItem *)item)->DirName(true); Clear(); top_line[0] = top_line[1] = bottom_line[0] = bottom_line[1] = false; } break; // salvage all case 5: state = osContinue; if (IsReady() && (!confirmsalvageall.u || Interface->Confirm(tr("Question$Salvage all recordings?")))) { cRemote::Put((eKeys)kSalvageRec); for (item = First(); !WorkFilename && item; item = Next(item)) if (IsMenuRecordingSelectItem(item)) WorkFilename = ((cMenuRecordingSelectItem *)item)->DirName(true); Clear(); top_line[0] = top_line[1] = bottom_line[0] = bottom_line[1] = false; } break; // open case 6: if (IsDirectory(item)) { ri = (cMenuRecordingSelectItem *)item; const char *t = ri->Name(); char *buffer = NULL; if (base) { asprintf(&buffer, "%s~%s", base, t); t = buffer; } state = AddSubMenu(new cMenuRecordingSelect(t, level + 1)); freenull(buffer); } break; // summary case 7: recording = GetRecording(item); #if VDRVERSNUM >= 10325 if (recording && recording->Info() && recording->Info()->Description() && *recording->Info()->Description()) state = AddSubMenu(new cMenuText(tr("Summary"), recording->Info()->Description())); #else if (recording && recording->Summary() && *recording->Summary()) state = AddSubMenu(new cMenuText(tr("Summary"), recording->Summary())); #endif break; // switch hotkeys to 1 case 8: if (functionkey[8].u == functionkey[9].u && KeyState == 1) KeyState = 2; else KeyState = 1; cRemote::Put((eKeys)kHelpKeys); state = osContinue; break; // switch hotkeys to 2 case 9: KeyState = 2; cRemote::Put((eKeys)kHelpKeys); state = osContinue; break; // Choise$Display keys case 10: state = AddSubMenu(new cMenuDispKey()); break; } d2syslog("cMenuRecordingSelect::FunctionCall", "FunctionNumber=%d returned OSState=%s WorkFilename=%s KeyState=%d", FunctionNumber, OSStateName(state), WorkFilename ? WorkFilename : "(NULL)", KeyState); return state; } eOSState cMenuRecordingSelect::ProcessKey(eKeys Key) { bool hSubMenu = HasSubMenu(); cOsdItem *item = NULL; int keynumber; d3ProcessKey1("cMenuRecordingSelect::ProcessKey"); eOSState state = cOsdMenu::ProcessKey(Key); d3ProcessKey2("cMenuRecordingSelect::ProcessKey"); if (hSubMenu && !HasSubMenu()) { if (state == osContinue) { for (item = First(); item; item = Next(item)) if (IsDirectory(item)) ((cMenuRecordingSelectItem *)item)->RefreshCounter(); Display(); } } switch (state) { case osUnknown: keynumber = KeyState - 2; // KeyState is 1 or 2 switch (Key) { case kBlue: keynumber += 2; // no break! case kYellow: keynumber += 2; // no break! case kGreen: keynumber += 2; // no break! case kRed: keynumber += 2; // keynumber red=1/2 green=3/4 yellow=5/6 blue=7/8 break; case k0...k9: keynumber = Key - k0 + 9; break; case kUser1...kUser9: keynumber = Key - kUser1 + 19; break; case kOk: state = FunctionCall(okkey.u); break; case kDelLine: if (Count()) Del(Current()); item = Get(Current()); if (item && !IsMenuRecordingSelectItem(item)) { item = Prev(item); if (!IsMenuRecordingSelectItem(item)) item = NULL; } DelFunctionLines(); AddFunctionLines(); if (item) SetCurrent(item); if (Count()) { Display(); state = osContinue; cRemote::Put((eKeys)kHelpKeys); } else { state = osBack; cRemote::Put((eKeys)kDelLine); } break; default: break; } if (keynumber > 0) for (int i = 0; i < MaxFunctionName && state == osUnknown; i++) if (functionkey[i].u == keynumber) state = FunctionCall(i); if (state == osUnknown && Key >= k0 && Key <= k9) { item = Get((Key == k0 ? 10 : Key - k0) + (top_line[0] ? 1 : 0) + (top_line[1] ? 1 : 0) - 1); if (IsMenuRecordingSelectItem(item)) { SetCurrent(item); Display(); state = FunctionCall(numkey.u); } } break; case osUser2: state = FunctionCall(4); break; case osUser3: state = FunctionCall(5); break; default: break; } if (!HasSubMenu() && Key != kNone && !WorkFilename) { SetHelpKeys(); item = Get(Current()); if (IsDirectory(item)) { ((cMenuRecordingSelectItem *)item)->RefreshCounter(); RefreshCurrent(); } } #ifdef UND_Debug1 if (state != osUnknown && !HasSubMenu()) { d1syslog("cMenuRecordingSelect::ProcessKey", "base=%s", base ? base : "(NULL)"); d1listlog("cMenuRecordingSelect::ProcessKey"); } #endif d3ProcessKey3("cMenuRecordingSelect::ProcessKey"); return state; } // --- cMenuUndelete -------------------------------------------------------- cMenuUndelete::cMenuUndelete(void) :cOsdMenu(tr("Deleted Recordings"), 6, 6) { #ifdef UND_Debug objID = ++cMenuUndelete_nextID; #endif d2syslog("cMenuUndelete::cMenuUndelete", "%s", ""); MenuIsOpen = true; PurgeRecording = false; SalvageRecording = false; FillMenu(); } cMenuUndelete::~cMenuUndelete() { d2syslog("cMenuUndelete::~cMenuUndelete", "%s", ""); if (PurgeRecording && !oRemoveThread.ActiveWithCancel()) oRemoveThread.Start(); DeletedRecordings.Clear(); #if VDRVERSNUM >= 10311 if (SalvageRecording) #if VDRVERSNUM >= 10333 Recordings.Update(); #else Recordings.TriggerUpdate(); #endif #endif MenuIsOpen = false; } void cMenuUndelete::FillMenu(void) { STATUS(tr("Display$prepare recording display...")); DeletedRecordings.Clear(); #if VDRVERSNUM >= 10311 DeletedRecordings.Load(); #else DeletedRecordings.Load(true); #endif DeletedRecordings.Sort(); KeyState = 1; AddSubMenu(new cMenuRecordingSelect); } eOSState cMenuUndelete::ProcessKey(eKeys Key) { d3ProcessKey1("cMenuUndelete::ProcessKey"); eOSState state = cOsdMenu::ProcessKey(Key); d3ProcessKey2("cMenuUndelete::ProcessKey"); #ifdef UND_Debug3 if (!noneKey || state != osUnknown) d3syslog("cMenuUndelete::ProcessKey", "WorkFilename=%s", WorkFilename ? WorkFilename : "(NULL)"); #endif if (state == osUnknown) { cRecording *recording = NULL; bool processerror = false; bool processrecording = false; if (WorkFilename && (Key == kPurgeRec || Key == kSalvageRec)) STATUS(tr("Display$please wait ...")); switch (Key) { case kPurgeRec: state = osContinue; if (WorkFilename) { for (recording = DeletedRecordings.First(); recording; recording = DeletedRecordings.Next(recording)) if (!strncmp(recording->FileName(), WorkFilename, strlen(WorkFilename)) && (*(WorkFilename + strlen(WorkFilename) - 1) != '/' || strchr(recording->FileName() + strlen(WorkFilename), '/'))) { if (verbose.u) isyslog("%s: purge deleted recording (%s)", plugin_name, recording->FileName()); if (!RemoveVideoFile(recording->FileName())) { esyslog("%s: Error while remove deleted recording (%s)", plugin_name, recording->FileName()); processerror = true; } PurgeRecording = true; cRemote::Put(Key); DeletedRecordings.Del(recording); processrecording = true; break; } } break; case kSalvageRec: state = osContinue; if (WorkFilename) { for (recording = DeletedRecordings.First(); recording; recording = DeletedRecordings.Next(recording)) if (!strncmp(recording->FileName(), WorkFilename, strlen(WorkFilename)) && (*(WorkFilename + strlen(WorkFilename) - 1) != '/' || strchr(recording->FileName() + strlen(WorkFilename), '/'))) { if (verbose.u) isyslog("%s: undelete recording (%s)", plugin_name, recording->FileName()); char *NewName = strdup(recording->FileName()); char *ext = strrchr(NewName, '.'); if (!strcmp(ext, DELEXT)) { strncpy(ext, RECEXT, strlen(ext)); if (!access(NewName, F_OK)) { if (verbose.u) isyslog("%s: Recording with the same name exists (%s)", plugin_name, NewName); ERROR(tr("Error$Recording with the same name exists!")); processerror = true; } else { if (!RenameVideoFile(recording->FileName(), NewName)) { esyslog("%s: Error while rename deleted recording (%s) to (%s)", plugin_name, recording->FileName(), NewName); processerror = true; } SalvageRecording = true; } } free(NewName); cRemote::Put(Key); DeletedRecordings.Del(recording); processrecording = true; break; } } break; case kHelpKeys: if (!HasSubMenu()) state = osBack; default: break; } if (WorkFilename && (!processrecording || processerror)) { freenull(WorkFilename); if (processerror) { CloseSubMenu(); Clear(); FillMenu(); } else cRemote::Put((eKeys)kDelLine); STATUS(NULL); } } if (!WorkFilename && !HasSubMenu()) state = osBack; d3ProcessKey3("cMenuUndelete::ProcessKey"); return state; }