Merge branch 'master' of https://github.com/OpenPLi/enigma2
[openblackhole/openblackhole-enigma2.git] / lib / python / Screens / MovieSelection.py
1 from Screen import Screen
2 from Components.Button import Button
3 from Components.ActionMap import HelpableActionMap, ActionMap, NumberActionMap
4 from Components.ChoiceList import ChoiceList, ChoiceEntryComponent
5 from Components.MenuList import MenuList
6 from Components.MovieList import MovieList, resetMoviePlayState, AUDIO_EXTENSIONS, DVD_EXTENSIONS, IMAGE_EXTENSIONS, moviePlayState
7 from Components.DiskInfo import DiskInfo
8 from Components.Pixmap import Pixmap, MultiPixmap
9 from Components.Label import Label
10 from Components.PluginComponent import plugins
11 from Components.config import config, ConfigSubsection, ConfigText, ConfigInteger, ConfigLocations, ConfigSet, ConfigYesNo, ConfigSelection, getConfigListEntry
12 from Components.ConfigList import ConfigListScreen
13 from Components.ServiceEventTracker import ServiceEventTracker, InfoBarBase
14 from Components.Sources.ServiceEvent import ServiceEvent
15 from Components.Sources.StaticText import StaticText
16 import Components.Harddisk
17 from Components.UsageConfig import preferredTimerPath
18
19 from Plugins.Plugin import PluginDescriptor
20
21 from Screens.MessageBox import MessageBox
22 from Screens.ChoiceBox import ChoiceBox
23 from Screens.LocationBox import MovieLocationBox
24 from Screens.HelpMenu import HelpableScreen
25 from Screens.InputBox import PinInput
26 import Screens.InfoBar
27
28 from Tools import NumericalTextInput
29 from Tools.Directories import resolveFilename, SCOPE_HDD
30 from Tools.BoundFunction import boundFunction
31 import Tools.Trashcan
32 import NavigationInstance
33 import RecordTimer
34
35 from enigma import eServiceReference, eServiceCenter, eTimer, eSize, iPlayableService, iServiceInformation, getPrevAsciiCode, eRCInput
36 import os
37 import time
38 import cPickle as pickle
39
40 config.movielist = ConfigSubsection()
41 config.movielist.moviesort = ConfigInteger(default=MovieList.SORT_GROUPWISE)
42 config.movielist.listtype = ConfigInteger(default=MovieList.LISTTYPE_MINIMAL)
43 config.movielist.description = ConfigInteger(default=MovieList.SHOW_DESCRIPTION)
44 config.movielist.last_videodir = ConfigText(default=resolveFilename(SCOPE_HDD))
45 config.movielist.last_timer_videodir = ConfigText(default=resolveFilename(SCOPE_HDD))
46 config.movielist.videodirs = ConfigLocations(default=[resolveFilename(SCOPE_HDD)])
47 config.movielist.last_selected_tags = ConfigSet([], default=[])
48 config.movielist.play_audio_internal = ConfigYesNo(default=True)
49 config.movielist.settings_per_directory = ConfigYesNo(default=True)
50 config.movielist.root = ConfigSelection(default="/media", choices=["/","/media","/media/hdd","/media/hdd/movie","/media/usb","/media/usb/movie"])
51 config.movielist.hide_extensions = ConfigYesNo(default=False)
52 config.movielist.stop_service = ConfigYesNo(default=True)
53
54 userDefinedButtons = None
55 last_selected_dest = []
56 preferredTagEditor = None
57
58 # this kludge is needed because ConfigSelection only takes numbers
59 # and someone appears to be fascinated by 'enums'.
60 l_moviesort = [
61         (str(MovieList.SORT_GROUPWISE), _("default") , '02/01 & A-Z'),
62         (str(MovieList.SORT_RECORDED), _("by date"), '03/02/01'),
63         (str(MovieList.SORT_ALPHANUMERIC), _("alphabetic"), 'A-Z'),
64         (str(MovieList.SORT_ALPHANUMERIC_FLAT), _("flat alphabetic"), 'A-Z Flat'),
65         (str(MovieList.SHUFFLE), _("shuffle"), '?'),
66         (str(MovieList.SORT_RECORDED_REVERSE), _("reverse by date"), '01/02/03'),
67         (str(MovieList.SORT_ALPHANUMERIC_REVERSE), _("alphabetic reverse"), 'Z-A'),
68         (str(MovieList.SORT_ALPHANUMERIC_FLAT_REVERSE), _("flat alphabetic reverse"), 'Z-A Flat')]
69 l_listtype = [(str(MovieList.LISTTYPE_ORIGINAL), _("list style default")),
70         (str(MovieList.LISTTYPE_COMPACT_DESCRIPTION), _("list style compact with description")),
71         (str(MovieList.LISTTYPE_COMPACT), _("list style compact")),
72         (str(MovieList.LISTTYPE_MINIMAL), _("list style single line"))]
73
74 def defaultMoviePath():
75         result = config.usage.default_path.value
76         if not os.path.isdir(result):
77                 from Tools import Directories
78                 return Directories.defaultRecordingLocation()
79         return result
80
81 def setPreferredTagEditor(te):
82         global preferredTagEditor
83         if preferredTagEditor is None:
84                 preferredTagEditor = te
85                 print "Preferred tag editor changed to", preferredTagEditor
86         else:
87                 print "Preferred tag editor already set to", preferredTagEditor, "ignoring", te
88
89 def getPreferredTagEditor():
90         global preferredTagEditor
91         return preferredTagEditor
92
93 def isTrashFolder(ref):
94         if not config.usage.movielist_trashcan.value or not ref.flags & eServiceReference.mustDescent:
95                 return False
96         path = os.path.realpath(ref.getPath())
97         return path.endswith('.Trash') and path.startswith(Tools.Trashcan.getTrashFolder(path))
98
99 def isInTrashFolder(ref):
100         if not config.usage.movielist_trashcan.value or not ref.flags & eServiceReference.mustDescent:
101                 return False
102         path = os.path.realpath(ref.getPath())
103         return path.startswith(Tools.Trashcan.getTrashFolder(path))
104
105 def isSimpleFile(item):
106         if not item:
107                 return False
108         if not item[0] or not item[1]:
109                 return False
110         return (item[0].flags & eServiceReference.mustDescent) == 0
111
112 def isFolder(item):
113         if not item:
114                 return False
115         if not item[0] or not item[1]:
116                 return False
117         return (item[0].flags & eServiceReference.mustDescent) != 0
118
119
120 def canMove(item):
121         if not item:
122                 return False
123         if not item[0] or not item[1]:
124                 return False
125         if item[0].flags & eServiceReference.mustDescent:
126                 return not isTrashFolder(item[0])
127         return True
128
129 canDelete = canMove
130
131 def canCopy(item):
132         if not item:
133                 return False
134         if not item[0] or not item[1]:
135                 return False
136         if item[0].flags & eServiceReference.mustDescent:
137                 return False
138         return True
139
140 def createMoveList(serviceref, dest):
141         #normpath is to remove the trailing '/' from directories
142         src = isinstance(serviceref, str) and serviceref + ".ts" or os.path.normpath(serviceref.getPath())
143         srcPath, srcName = os.path.split(src)
144         if os.path.normpath(srcPath) == dest:
145                 # move file to itself is allowed, so we have to check it
146                 raise Exception, "Refusing to move to the same directory"
147         # Make a list of items to move
148         moveList = [(src, os.path.join(dest, srcName))]
149         if isinstance(serviceref, str) or not serviceref.flags & eServiceReference.mustDescent:
150                 # Real movie, add extra files...
151                 srcBase = os.path.splitext(src)[0]
152                 baseName = os.path.split(srcBase)[1]
153                 eitName =  srcBase + '.eit'
154                 if os.path.exists(eitName):
155                         moveList.append((eitName, os.path.join(dest, baseName+'.eit')))
156                 baseName = os.path.split(src)[1]
157                 for ext in ('.ap', '.cuts', '.meta', '.sc'):
158                         candidate = src + ext
159                         if os.path.exists(candidate):
160                                 moveList.append((candidate, os.path.join(dest, baseName+ext)))
161         return moveList
162
163 def moveServiceFiles(serviceref, dest, name=None, allowCopy=True):
164         moveList = createMoveList(serviceref, dest)
165         # Try to "atomically" move these files
166         movedList = []
167         try:
168                 try:
169                         for item in moveList:
170                                 os.rename(item[0], item[1])
171                                 movedList.append(item)
172                 except OSError, e:
173                         if e.errno == 18 and allowCopy:
174                                 print "[MovieSelection] cannot rename across devices, trying slow move"
175                                 import CopyFiles
176                                 # start with the smaller files, do the big one later.
177                                 moveList.reverse()
178                                 if name is None:
179                                         name = os.path.split(moveList[-1][0])[1]
180                                 CopyFiles.moveFiles(moveList, name)
181                                 print "[MovieSelection] Moving in background..."
182                         else:
183                                 raise
184         except Exception, e:
185                 print "[MovieSelection] Failed move:", e
186                 for item in movedList:
187                         try:
188                                 os.rename(item[1], item[0])
189                         except:
190                                 print "[MovieSelection] Failed to undo move:", item
191                 # rethrow exception
192                 raise
193
194 def copyServiceFiles(serviceref, dest, name=None):
195         # current should be 'ref' type, dest a simple path string
196         moveList = createMoveList(serviceref, dest)
197         # Try to "atomically" move these files
198         movedList = []
199         try:
200                 for item in moveList:
201                         os.link(item[0], item[1])
202                         movedList.append(item)
203                 # this worked, we're done
204                 return
205         except Exception, e:
206                 print "[MovieSelection] Failed copy using link:", e
207                 for item in movedList:
208                         try:
209                                 os.unlink(item[1])
210                         except:
211                                 print "[MovieSelection] Failed to undo copy:", item
212         #Link failed, really copy.
213         import CopyFiles
214         # start with the smaller files, do the big one later.
215         moveList.reverse()
216         if name is None:
217                 name = os.path.split(moveList[-1][0])[1]
218         CopyFiles.copyFiles(moveList, name)
219         print "[MovieSelection] Copying in background..."
220
221 # Appends possible destinations to the bookmarks object. Appends tuples
222 # in the form (description, path) to it.
223 def buildMovieLocationList(bookmarks):
224         inlist = []
225         for d in config.movielist.videodirs.value:
226                 d = os.path.normpath(d)
227                 bookmarks.append((d,d))
228                 inlist.append(d)
229         for p in Components.Harddisk.harddiskmanager.getMountedPartitions():
230                 d = os.path.normpath(p.mountpoint)
231                 if d in inlist:
232                         # improve shortcuts to mountpoints
233                         try:
234                                 bookmarks[bookmarks.index((d,d))] = (p.tabbedDescription(), d)
235                         except:
236                                 pass # When already listed as some "friendly" name
237                 else:
238                         bookmarks.append((p.tabbedDescription(), d))
239                 inlist.append(d)
240
241 class MovieBrowserConfiguration(ConfigListScreen,Screen):
242         skin = """
243 <screen position="center,center" size="560,400" title="Movie Browser Configuration" >
244         <ePixmap name="red"    position="0,0"   zPosition="2" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
245         <ePixmap name="green"  position="140,0" zPosition="2" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
246
247         <widget name="key_red" position="0,0" size="140,40" valign="center" halign="center" zPosition="4"  foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
248         <widget name="key_green" position="140,0" size="140,40" valign="center" halign="center" zPosition="4"  foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
249         <widget name="config" position="10,40" size="540,340" scrollbarMode="showOnDemand" />
250
251         <ePixmap alphatest="on" pixmap="skin_default/icons/clock.png" position="480,383" size="14,14" zPosition="3"/>
252         <widget font="Regular;18" halign="left" position="505,380" render="Label" size="55,20" source="global.CurrentTime" transparent="1" valign="center" zPosition="3">
253                 <convert type="ClockToText">Default</convert>
254         </widget>
255 </screen>"""
256
257         def __init__(self, session, args = 0):
258                 self.session = session
259                 self.setup_title = _("Movie list configuration")
260                 Screen.__init__(self, session)
261                 cfg = ConfigSubsection()
262                 self.cfg = cfg
263                 cfg.moviesort = ConfigSelection(default=str(config.movielist.moviesort.value), choices = l_moviesort)
264                 cfg.listtype = ConfigSelection(default=str(config.movielist.listtype.value), choices = l_listtype)
265                 cfg.description = ConfigYesNo(default=(config.movielist.description.value != MovieList.HIDE_DESCRIPTION))
266                 configList = [
267                         getConfigListEntry(_("Sort"), cfg.moviesort),
268                         getConfigListEntry(_("Show extended description"), cfg.description),
269                         getConfigListEntry(_("Type"), cfg.listtype),
270                         getConfigListEntry(_("Use individual settings for each directory"), config.movielist.settings_per_directory),
271                         getConfigListEntry(_("Allow quit movieplayer with exit"), config.usage.leave_movieplayer_onExit),
272                         getConfigListEntry(_("Behavior when a movie reaches the end"), config.usage.on_movie_eof),
273                         getConfigListEntry(_("Stop service on return to movie list"), config.movielist.stop_service),
274                         getConfigListEntry(_("Load length of movies in movie list"), config.usage.load_length_of_movies_in_moviellist),
275                         getConfigListEntry(_("Show status icons in movie list"), config.usage.show_icons_in_movielist),
276                         getConfigListEntry(_("Show icon for new/unseen items"), config.usage.movielist_unseen),
277                         getConfigListEntry(_("Play audio in background"), config.movielist.play_audio_internal),
278                         getConfigListEntry(_("Root directory"), config.movielist.root),
279                         getConfigListEntry(_("Hide known extensions"), config.movielist.hide_extensions),
280                         ]
281                 for btn in ('red', 'green', 'yellow', 'blue', 'TV', 'Radio', 'Text', 'F1', 'F2', 'F3'):
282                         configList.append(getConfigListEntry(_(btn), userDefinedButtons[btn]))
283                 ConfigListScreen.__init__(self, configList, session=session, on_change = self.changedEntry)
284                 self["key_red"] = Button(_("Cancel"))
285                 self["key_green"] = Button(_("Ok"))
286                 self["setupActions"] = ActionMap(["SetupActions", "ColorActions",  "MenuActions"],
287                 {
288                         "red": self.cancel,
289                         "green": self.save,
290                         "save": self.save,
291                         "cancel": self.cancel,
292                         "ok": self.save,
293                         "menu": self.cancel,
294                 }, -2)
295                 self.onChangedEntry = []
296                 self.onLayoutFinish.append(self.layoutFinished)
297
298         def layoutFinished(self):
299                 self.setTitle(self.setup_title)
300
301         # for summary:
302         def changedEntry(self):
303                 for x in self.onChangedEntry:
304                         x()
305         def getCurrentEntry(self):
306                 return self["config"].getCurrent()[0]
307         def getCurrentValue(self):
308                 return str(self["config"].getCurrent()[1].getText())
309         def createSummary(self):
310                 from Screens.Setup import SetupSummary
311                 return SetupSummary
312
313         def save(self):
314                 self.saveAll()
315                 cfg = self.cfg
316                 config.movielist.moviesort.value = int(cfg.moviesort.value)
317                 config.movielist.listtype.value = int(cfg.listtype.value)
318                 if cfg.description.value:
319                         config.movielist.description.value = MovieList.SHOW_DESCRIPTION
320                 else:
321                         config.movielist.description.value = MovieList.HIDE_DESCRIPTION
322                 if not config.movielist.settings_per_directory.value:
323                         config.movielist.moviesort.save()
324                         config.movielist.listtype.save()
325                         config.movielist.description.save()
326                         config.usage.on_movie_eof.save()
327                 self.close(True)
328
329         def cancel(self):
330                 if self["config"].isChanged():
331                         self.session.openWithCallback(self.cancelCallback, MessageBox, _("Really close without saving settings?"))
332                 else:
333                         self.cancelCallback(True)
334
335         def cancelCallback(self, answer):
336                 if answer:
337                         for x in self["config"].list:
338                                 x[1].cancel()
339                         self.close(False)
340
341 class MovieContextMenuSummary(Screen):
342         def __init__(self, session, parent):
343                 Screen.__init__(self, session, parent = parent)
344                 self["selected"] = StaticText("")
345                 self.onShow.append(self.__onShow)
346                 self.onHide.append(self.__onHide)
347
348         def __onShow(self):
349                 self.parent["menu"].onSelectionChanged.append(self.selectionChanged)
350                 self.selectionChanged()
351
352         def __onHide(self):
353                 self.parent["menu"].onSelectionChanged.remove(self.selectionChanged)
354
355         def selectionChanged(self):
356                 item = self.parent["menu"].getCurrent()
357                 self["selected"].text = item[0][0]
358
359 from Screens.ParentalControlSetup import ProtectedScreen
360
361 class MovieContextMenu(Screen, ProtectedScreen):
362         # Contract: On OK returns a callable object (e.g. delete)
363         def __init__(self, session, csel, service):
364                 Screen.__init__(self, session)
365                 self.csel = csel
366                 ProtectedScreen.__init__(self)
367
368                 self["actions"] = ActionMap(["OkCancelActions", "ColorActions", "NumberActions", "MenuActions"],
369                         {
370                                 "ok": self.okbuttonClick,
371                                 "cancel": self.cancelClick,
372                                 "yellow": self.do_showNetworkSetup,
373                                 "menu": self.do_configure,
374                                 "2": self.do_rename,
375                                 "5": self.do_copy,
376                                 "6": self.do_move,
377                                 "7": self.do_createdir,
378                                 "8": self.do_delete
379                         })
380
381                 def append_to_menu(menu, args, key=""):
382                         menu.append(ChoiceEntryComponent(key, args))
383
384                 menu = []
385                 if service:
386                         if (service.flags & eServiceReference.mustDescent) and isTrashFolder(service):
387                                 append_to_menu(menu, (_("Permanently remove all deleted items"), csel.purgeAll), key="8")
388                         else:
389                                 append_to_menu(menu, (_("Delete"), csel.do_delete), key="8")
390                                 append_to_menu(menu, (_("Move"), csel.do_move), key="6")
391                                 append_to_menu(menu, (_("Rename"), csel.do_rename), key="2")
392                                 if not (service.flags & eServiceReference.mustDescent):
393                                         append_to_menu(menu, (_("Copy"), csel.do_copy), key="5")
394                                         if self.isResetable():
395                                                 append_to_menu(menu, (_("Reset playback position"), csel.do_reset))
396                                         if service.getPath().endswith('.ts'):
397                                                 append_to_menu(menu, (_("Start offline decode"), csel.do_decode))
398                                 if config.ParentalControl.hideBlacklist.value and config.ParentalControl.storeservicepin.value != "never":
399                                         from Components.ParentalControl import parentalControl
400                                         if not parentalControl.sessionPinCached:
401                                                 append_to_menu(menu, (_("Unhide parental control services"), csel.unhideParentalServices))
402                                 # Plugins expect a valid selection, so only include them if we selected a non-dir
403                                 if not(service.flags & eServiceReference.mustDescent):
404                                         for p in plugins.getPlugins(PluginDescriptor.WHERE_MOVIELIST):
405                                                 append_to_menu( menu, (p.description, boundFunction(p, session, service)), key="bullet")
406                 if csel.exist_bookmark():
407                         append_to_menu(menu, (_("Remove bookmark"), csel.do_addbookmark))
408                 else:
409                         append_to_menu(menu, (_("Add bookmark"), csel.do_addbookmark))
410                 append_to_menu(menu, (_("create directory"), csel.do_createdir), key="7")
411                 append_to_menu(menu, (_("Sort by") + "...", csel.selectSortby))
412                 append_to_menu(menu, (_("Network") + "...", csel.showNetworkSetup), key="yellow")
413                 append_to_menu(menu, (_("Settings") + "...", csel.configure), key="menu")
414
415                 self["menu"] = ChoiceList(menu)
416
417         def isProtected(self):
418                 return self.csel.protectContextMenu and config.ParentalControl.setuppinactive.value and config.ParentalControl.config_sections.context_menus.value
419
420         def isResetable(self):
421                 item = self.csel.getCurrentSelection()
422                 return not(item[1] and moviePlayState(item[0].getPath() + ".cuts", item[0], item[1].getLength(item[0])) is None)
423
424         def pinEntered(self, answer):
425                 if answer:
426                         self.csel.protectContextMenu = False
427                 ProtectedScreen.pinEntered(self, answer)
428
429         def createSummary(self):
430                 return MovieContextMenuSummary
431
432         def okbuttonClick(self):
433                 self.close(self["menu"].getCurrent()[0][1])
434
435         def do_rename(self):
436                 self.close(self.csel.do_rename())
437         def do_copy(self):
438                 self.close(self.csel.do_copy())
439         def do_move(self):
440                 self.close(self.csel.do_move())
441         def do_createdir(self):
442                 self.close(self.csel.do_createdir())
443         def do_delete(self):
444                 self.close(self.csel.do_delete())
445         def do_configure(self):
446                 self.close(self.csel.configure())
447         def do_showNetworkSetup(self):
448                 self.close(self.csel.showNetworkSetup())
449
450         def cancelClick(self):
451                 self.close(None)
452
453 class SelectionEventInfo:
454         def __init__(self):
455                 self["Service"] = ServiceEvent()
456                 self.list.connectSelChanged(self.__selectionChanged)
457                 self.timer = eTimer()
458                 self.timer.callback.append(self.updateEventInfo)
459                 self.onShown.append(self.__selectionChanged)
460
461         def __selectionChanged(self):
462                 if self.execing and self.settings["description"] == MovieList.SHOW_DESCRIPTION:
463                         self.timer.start(100, True)
464
465         def updateEventInfo(self):
466                 serviceref = self.getCurrent()
467                 self["Service"].newService(serviceref)
468
469 class MovieSelectionSummary(Screen):
470         # Kludgy component to display current selection on LCD. Should use
471         # parent.Service as source for everything, but that seems to have a
472         # performance impact as the MovieSelection goes through hoops to prevent
473         # this when the info is not selected
474         def __init__(self, session, parent):
475                 Screen.__init__(self, session, parent = parent)
476                 self["name"] = StaticText("")
477                 self.onShow.append(self.__onShow)
478                 self.onHide.append(self.__onHide)
479
480         def __onShow(self):
481                 self.parent.list.connectSelChanged(self.selectionChanged)
482                 self.selectionChanged()
483
484         def __onHide(self):
485                 self.parent.list.disconnectSelChanged(self.selectionChanged)
486
487         def selectionChanged(self):
488                 item = self.parent.getCurrentSelection()
489                 if item and item[0]:
490                         data = item[3]
491                         if (data is not None) and (data != -1):
492                                 name = data.txt
493                         elif not item[1]:
494                                 # special case, one up
495                                 name = ".."
496                         else:
497                                 name = item[1].getName(item[0])
498                         if (item[0].flags & eServiceReference.mustDescent):
499                                 if len(name) > 12:
500                                         name = os.path.split(os.path.normpath(name))[1]
501                                 name = "> " + name
502                         self["name"].text = name
503                 else:
504                         self["name"].text = ""
505
506 class MovieSelection(Screen, HelpableScreen, SelectionEventInfo, InfoBarBase, ProtectedScreen):
507         # SUSPEND_PAUSES actually means "please call my pauseService()"
508         ALLOW_SUSPEND = Screen.SUSPEND_PAUSES
509
510         def __init__(self, session, selectedmovie = None, timeshiftEnabled = False):
511                 Screen.__init__(self, session)
512                 HelpableScreen.__init__(self)
513                 if not timeshiftEnabled:
514                         InfoBarBase.__init__(self) # For ServiceEventTracker
515                 ProtectedScreen.__init__(self)
516                 self.protectContextMenu = True
517
518                 self.initUserDefinedActions()
519                 self.tags = {}
520                 if selectedmovie:
521                         self.selected_tags = config.movielist.last_selected_tags.value
522                 else:
523                         self.selected_tags = None
524                 self.selected_tags_ele = None
525                 self.nextInBackground = None
526
527                 self.movemode = False
528                 self.bouquet_mark_edit = False
529
530                 self.feedbackTimer = None
531                 self.pathselectEnabled = False
532
533                 self.numericalTextInput = NumericalTextInput.NumericalTextInput(mapping=NumericalTextInput.MAP_SEARCH_UPCASE)
534                 self["chosenletter"] = Label("")
535                 self["chosenletter"].visible = False
536
537                 self["waitingtext"] = Label(_("Please wait... Loading list..."))
538
539                 # create optional description border and hide immediately
540                 self["DescriptionBorder"] = Pixmap()
541                 self["DescriptionBorder"].hide()
542
543                 if config.ParentalControl.servicepinactive.value:
544                         from Components.ParentalControl import parentalControl
545                         if not parentalControl.sessionPinCached and config.movielist.last_videodir.value and [x for x in config.movielist.last_videodir.value[1:].split("/") if x.startswith(".") and not x.startswith(".Trash")]:
546                                 config.movielist.last_videodir.value = ""
547                 if not os.path.isdir(config.movielist.last_videodir.value):
548                         config.movielist.last_videodir.value = defaultMoviePath()
549                         config.movielist.last_videodir.save()
550                 self.setCurrentRef(config.movielist.last_videodir.value)
551
552                 self.settings = {\
553                         "listtype": config.movielist.listtype.value,
554                         "moviesort": config.movielist.moviesort.value,
555                         "description": config.movielist.description.value,
556                         "movieoff": config.usage.on_movie_eof.value
557                 }
558                 self.movieOff = self.settings["movieoff"]
559
560                 self["list"] = MovieList(None, list_type=self.settings["listtype"], sort_type=self.settings["moviesort"], descr_state=self.settings["description"])
561
562                 self.loadLocalSettings()
563
564                 self.list = self["list"]
565                 self.selectedmovie = selectedmovie
566
567                 self.playGoTo = None #1 - preview next item / -1 - preview previous
568
569                 title = _("Movie selection")
570                 self.setTitle(title)
571
572                 # Need list for init
573                 SelectionEventInfo.__init__(self)
574
575                 self["key_red"] = Button("")
576                 self["key_green"] = Button("")
577                 self["key_yellow"] = Button("")
578                 self["key_blue"] = Button("")
579                 self._updateButtonTexts()
580
581                 self["movie_off"] = MultiPixmap()
582                 self["movie_off"].hide()
583
584                 self["movie_sort"] = MultiPixmap()
585                 self["movie_sort"].hide()
586
587                 self["freeDiskSpace"] = self.diskinfo = DiskInfo(config.movielist.last_videodir.value, DiskInfo.FREE, update=False)
588
589                 self["InfobarActions"] = HelpableActionMap(self, "InfobarActions",
590                         {
591                                 "showMovies": (self.doPathSelect, _("Select the movie path")),
592                                 "showRadio": (self.btn_radio, boundFunction(self.getinitUserDefinedActionsDescription, "btn_radio")),
593                                 "showTv": (self.btn_tv, boundFunction(self.getinitUserDefinedActionsDescription, "btn_tv")),
594                                 "showText": (self.btn_text, boundFunction(self.getinitUserDefinedActionsDescription, "btn_text")),
595                         })
596
597                 self["NumberActions"] =  NumberActionMap(["NumberActions", "InputAsciiActions"],
598                         {
599                                 "gotAsciiCode": self.keyAsciiCode,
600                                 "0": self.keyNumberGlobal,
601                                 "1": self.keyNumberGlobal,
602                                 "2": self.keyNumberGlobal,
603                                 "3": self.keyNumberGlobal,
604                                 "4": self.keyNumberGlobal,
605                                 "5": self.keyNumberGlobal,
606                                 "6": self.keyNumberGlobal,
607                                 "7": self.keyNumberGlobal,
608                                 "8": self.keyNumberGlobal,
609                                 "9": self.keyNumberGlobal
610                         })
611
612                 self["playbackActions"] = HelpableActionMap(self, "MoviePlayerActions",
613                         {
614                                 "leavePlayer": (self.playbackStop, _("Stop")),
615                                 "moveNext": (self.playNext, _("Play next")),
616                                 "movePrev": (self.playPrev, _("Play previous")),
617                                 "channelUp": (self.moveToFirstOrFirstFile, _("Go to first movie or top of list")),
618                                 "channelDown": (self.moveToLastOrFirstFile, _("Go to first movie or last item")),
619                         })
620                 self["MovieSelectionActions"] = HelpableActionMap(self, "MovieSelectionActions",
621                         {
622                                 "contextMenu": (self.doContext, _("Menu")),
623                                 "showEventInfo": (self.showEventInformation, _("Show event details")),
624                         })
625
626                 self["ColorActions"] = HelpableActionMap(self, "ColorActions",
627                         {
628                                 "red": (self.btn_red, boundFunction(self.getinitUserDefinedActionsDescription, "btn_red")),
629                                 "green": (self.btn_green, boundFunction(self.getinitUserDefinedActionsDescription, "btn_green")),
630                                 "yellow": (self.btn_yellow, boundFunction(self.getinitUserDefinedActionsDescription, "btn_yellow")),
631                                 "blue": (self.btn_blue, boundFunction(self.getinitUserDefinedActionsDescription, "btn_blue")),
632                         })
633                 self["FunctionKeyActions"] = HelpableActionMap(self, "FunctionKeyActions",
634                         {
635                                 "f1": (self.btn_F1, boundFunction(self.getinitUserDefinedActionsDescription, "btn_F1")),
636                                 "f2": (self.btn_F2, boundFunction(self.getinitUserDefinedActionsDescription, "btn_F2")),
637                                 "f3": (self.btn_F3, boundFunction(self.getinitUserDefinedActionsDescription, "btn_F3")),
638                         })
639                 self["OkCancelActions"] = HelpableActionMap(self, "OkCancelActions",
640                         {
641                                 "cancel": (self.abort, _("Exit movie list")),
642                                 "ok": (self.itemSelected, _("Select movie")),
643                         })
644                 self["DirectionActions"] = HelpableActionMap(self, "DirectionActions",
645                         {
646                                 "up": (self.keyUp, _("Go up the list")),
647                                 "down": (self.keyDown, _("Go down the list"))
648                         }, prio = -2)
649
650                 tPreview = _("Preview")
651                 tFwd = _("skip forward") + " (" + tPreview +")"
652                 tBack= _("skip backward") + " (" + tPreview +")"
653                 sfwd = lambda: self.seekRelative(1, config.seek.selfdefined_46.value * 90000)
654                 ssfwd = lambda: self.seekRelative(1, config.seek.selfdefined_79.value * 90000)
655                 sback = lambda: self.seekRelative(-1, config.seek.selfdefined_46.value * 90000)
656                 ssback = lambda: self.seekRelative(-1, config.seek.selfdefined_79.value * 90000)
657                 self["SeekActions"] = HelpableActionMap(self, "InfobarSeekActions",
658                         {
659                                 "playpauseService": (self.preview, _("Preview")),
660                                 "seekFwd": (sfwd, tFwd),
661                                 "seekFwdManual": (ssfwd, tFwd),
662                                 "seekBack": (sback, tBack),
663                                 "seekBackManual": (ssback, tBack),
664                         }, prio=5)
665                 self.onShown.append(self.onFirstTimeShown)
666                 self.onLayoutFinish.append(self.saveListsize)
667                 self.list.connectSelChanged(self.updateButtons)
668                 self.onClose.append(self.__onClose)
669                 NavigationInstance.instance.RecordTimer.on_state_change.append(self.list.updateRecordings)
670                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
671                         {
672                                 #iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged,
673                                 iPlayableService.evStart: self.__serviceStarted,
674                                 iPlayableService.evEOF: self.__evEOF,
675                                 #iPlayableService.evSOF: self.__evSOF,
676                         })
677                 try:
678                         vumachine = file("/proc/stb/info/vumodel").read().strip()
679                         if vumachine == "ultimo" or vumachine == "zero":
680                                 self.onExecBegin.append(self.asciiOff)
681                         else:
682                                 self.onExecBegin.append(self.asciiOn)
683                 except:
684                         self.onExecBegin.append(self.asciiOn)
685                 config.misc.standbyCounter.addNotifier(self.standbyCountChanged, initial_call=False)
686
687         def isProtected(self):
688                 return config.ParentalControl.setuppinactive.value and config.ParentalControl.config_sections.movie_list.value
689
690         def standbyCountChanged(self, value):
691                 path = self.getTitle().split(" /", 1)
692                 if path and len(path) > 1:
693                         if [x for x in path[1].split("/") if x.startswith(".") and not x.startswith(".Trash")]:
694                                 moviepath = defaultMoviePath()
695                                 if moviepath:
696                                         config.movielist.last_videodir.value = defaultMoviePath()
697                                         self.close(None)
698
699         def unhideParentalServices(self):
700                 if self.protectContextMenu:
701                         self.session.openWithCallback(self.unhideParentalServicesCallback, PinInput, pinList=[config.ParentalControl.servicepin[0].value], triesEntry=config.ParentalControl.retries.servicepin, title=_("Enter the service pin"), windowTitle=_("Enter pin code"))
702                 else:
703                         self.unhideParentalServicesCallback(True)
704
705         def unhideParentalServicesCallback(self, answer):
706                 if answer:
707                         from Components.ParentalControl import parentalControl
708                         parentalControl.setSessionPinCached()
709                         parentalControl.hideBlacklist()
710                         self.reloadList()
711                 elif answer is not None:
712                         self.session.openWithCallback(self.close, MessageBox, _("The pin code you entered is wrong."), MessageBox.TYPE_ERROR)
713
714         def asciiOn(self):
715                 rcinput = eRCInput.getInstance()
716                 rcinput.setKeyboardMode(rcinput.kmAscii)
717
718         def asciiOff(self):
719                 rcinput = eRCInput.getInstance()
720                 rcinput.setKeyboardMode(rcinput.kmNone)
721
722         def initUserDefinedActions(self):
723                 global userDefinedButtons, userDefinedActions, config
724                 if userDefinedButtons is None:
725                         userDefinedActions = {
726                                 'delete': _("Delete"),
727                                 'move': _("Move"),
728                                 'copy': _("Copy"),
729                                 'reset': _("Reset"),
730                                 'tags': _("Tags"),
731                                 'addbookmark': _("Add bookmark"),
732                                 'bookmarks': _("Location"),
733                                 'rename': _("Rename"),
734                                 'gohome': _("Home"),
735                                 'sort': _("Sort"),
736                                 'sortby': _("Sort by"),
737                                 'listtype': _("List type"),
738                                 'preview': _("Preview"),
739                                 'movieoff': _("On end of movie"),
740                                 'movieoff_menu': _("On end of movie (as menu)")
741                         }
742                         for p in plugins.getPlugins(PluginDescriptor.WHERE_MOVIELIST):
743                                 userDefinedActions['@' + p.name] = p.description
744                         locations = []
745                         buildMovieLocationList(locations)
746                         prefix = _("Goto") + ": "
747                         for d,p in locations:
748                                 if p and p.startswith('/'):
749                                         userDefinedActions[p] = prefix + d
750                         config.movielist.btn_red = ConfigSelection(default='delete', choices=userDefinedActions)
751                         config.movielist.btn_green = ConfigSelection(default='move', choices=userDefinedActions)
752                         config.movielist.btn_yellow = ConfigSelection(default='bookmarks', choices=userDefinedActions)
753                         config.movielist.btn_blue = ConfigSelection(default='sort', choices=userDefinedActions)
754                         config.movielist.btn_radio = ConfigSelection(default='tags', choices=userDefinedActions)
755                         config.movielist.btn_tv = ConfigSelection(default='gohome', choices=userDefinedActions)
756                         config.movielist.btn_text = ConfigSelection(default='movieoff', choices=userDefinedActions)
757                         config.movielist.btn_F1 = ConfigSelection(default='movieoff_menu', choices=userDefinedActions)
758                         config.movielist.btn_F2 = ConfigSelection(default='preview', choices=userDefinedActions)
759                         config.movielist.btn_F3 = ConfigSelection(default='/media', choices=userDefinedActions)
760                         userDefinedButtons ={
761                                 'red': config.movielist.btn_red,
762                                 'green': config.movielist.btn_green,
763                                 'yellow': config.movielist.btn_yellow,
764                                 'blue': config.movielist.btn_blue,
765                                 'Radio': config.movielist.btn_radio,
766                                 'TV': config.movielist.btn_tv,
767                                 'Text': config.movielist.btn_text,
768                                 'F1': config.movielist.btn_F1,
769                                 'F2': config.movielist.btn_F2,
770                                 'F3': config.movielist.btn_F3
771                         }
772
773         def getinitUserDefinedActionsDescription(self, key):
774                 return _(userDefinedActions.get(eval("config.movielist." + key + ".value"), _("Not Defined")))
775
776         def _callButton(self, name):
777                 if name.startswith('@'):
778                         item = self.getCurrentSelection()
779                         if isSimpleFile(item):
780                                 name = name[1:]
781                                 for p in plugins.getPlugins(PluginDescriptor.WHERE_MOVIELIST):
782                                         if name == p.name:
783                                                 p(self.session, item[0])
784                 elif name.startswith('/'):
785                         self.gotFilename(name)
786                 else:
787                         try:
788                                 a = getattr(self, 'do_' + name)
789                         except Exception:
790                                 # Undefined action
791                                 return
792                         a()
793
794         def btn_red(self):
795                 self._callButton(config.movielist.btn_red.value)
796         def btn_green(self):
797                 self._callButton(config.movielist.btn_green.value)
798         def btn_yellow(self):
799                 self._callButton(config.movielist.btn_yellow.value)
800         def btn_blue(self):
801                 self._callButton(config.movielist.btn_blue.value)
802         def btn_radio(self):
803                 self._callButton(config.movielist.btn_radio.value)
804         def btn_tv(self):
805                 self._callButton(config.movielist.btn_tv.value)
806         def btn_text(self):
807                 self._callButton(config.movielist.btn_text.value)
808         def btn_F1(self):
809                 self._callButton(config.movielist.btn_F1.value)
810         def btn_F2(self):
811                 self._callButton(config.movielist.btn_F2.value)
812         def btn_F3(self):
813                 self._callButton(config.movielist.btn_F3.value)
814
815         def keyUp(self):
816                 if self["list"].getCurrentIndex() < 1:
817                         self["list"].moveToLast()
818                 else:
819                         self["list"].moveUp()
820
821         def keyDown(self):
822                 if self["list"].getCurrentIndex() == len(self["list"]) - 1:
823                         self["list"].moveToFirst()
824                 else:
825                         self["list"].moveDown()
826
827         def moveToFirstOrFirstFile(self):
828                 if self.list.getCurrentIndex() <= self.list.firstFileEntry: #selection above or on first movie
829                         if self.list.getCurrentIndex() < 1:
830                                 self.list.moveToLast()
831                         else:
832                                 self.list.moveToFirst()
833                 else:
834                         self.list.moveToFirstMovie()
835
836         def moveToLastOrFirstFile(self):
837                 if self.list.getCurrentIndex() >= self.list.firstFileEntry or self.list.firstFileEntry == len(self.list): #selection below or on first movie or no files
838                         if self.list.getCurrentIndex() == len(self.list) - 1:
839                                 self.list.moveToFirst()
840                         else:
841                                 self.list.moveToLast()
842                 else:
843                         self.list.moveToFirstMovie()
844
845         def keyNumberGlobal(self, number):
846                 unichar = self.numericalTextInput.getKey(number)
847                 charstr = unichar.encode("utf-8")
848                 if len(charstr) == 1:
849                         self.list.moveToChar(charstr[0], self["chosenletter"])
850
851         def keyAsciiCode(self):
852                 unichar = unichr(getPrevAsciiCode())
853                 charstr = unichar.encode("utf-8")
854                 if len(charstr) == 1:
855                         self.list.moveToString(charstr[0], self["chosenletter"])
856
857         def isItemPlayable(self, index):
858                 item = self.list.getItem(index)
859                 if item:
860                         path = item.getPath()
861                         if not item.flags & eServiceReference.mustDescent:
862                                 ext = os.path.splitext(path)[1].lower()
863                                 if ext in IMAGE_EXTENSIONS:
864                                         return False
865                                 else:
866                                         return True
867                 return False
868
869         def goToPlayingService(self):
870                 service = self.session.nav.getCurrentlyPlayingServiceOrGroup()
871                 if service:
872                         path = service.getPath()
873                         if path:
874                                 path = os.path.split(os.path.normpath(path))[0]
875                                 if not path.endswith('/'):
876                                         path += '/'
877                                 self.gotFilename(path, selItem = service)
878                                 return True
879                 return False
880
881         def playNext(self):
882                 if self.list.playInBackground:
883                         if self.list.moveTo(self.list.playInBackground):
884                                 if self.isItemPlayable(self.list.getCurrentIndex() + 1):
885                                         self.list.moveDown()
886                                         self.callLater(self.preview)
887                         else:
888                                 self.playGoTo = 1
889                                 self.goToPlayingService()
890                 else:
891                         self.preview()
892
893         def playPrev(self):
894                 if self.list.playInBackground:
895                         if self.list.moveTo(self.list.playInBackground):
896                                 if self.isItemPlayable(self.list.getCurrentIndex() - 1):
897                                         self.list.moveUp()
898                                         self.callLater(self.preview)
899                         else:
900                                 self.playGoTo = -1
901                                 self.goToPlayingService()
902
903         def __onClose(self):
904                 config.misc.standbyCounter.removeNotifier(self.standbyCountChanged)
905                 try:
906                         NavigationInstance.instance.RecordTimer.on_state_change.remove(self.list.updateRecordings)
907                 except Exception, e:
908                         print "[ML] failed to unsubscribe:", e
909                         pass
910
911         def createSummary(self):
912                 return MovieSelectionSummary
913
914         def updateDescription(self):
915                 if self.settings["description"] == MovieList.SHOW_DESCRIPTION:
916                         self["DescriptionBorder"].show()
917                         self["list"].instance.resize(eSize(self.listWidth, self.listHeight-self["DescriptionBorder"].instance.size().height()))
918                 else:
919                         self["Service"].newService(None)
920                         self["DescriptionBorder"].hide()
921                         self["list"].instance.resize(eSize(self.listWidth, self.listHeight))
922
923         def pauseService(self):
924                 # Called when pressing Power button (go to standby)
925                 self.playbackStop()
926                 self.session.nav.stopService()
927
928         def unPauseService(self):
929                 # When returning from standby. It might have been a while, so
930                 # reload the list.
931                 self.reloadList()
932
933         def can_delete(self, item):
934                 if not item:
935                         return False
936                 return canDelete(item) or isTrashFolder(item[0])
937         def can_move(self, item):
938                 return canMove(item)
939         def can_default(self, item):
940                 # returns whether item is a regular file
941                 return isSimpleFile(item)
942         def can_sort(self, item):
943                 return True
944         def can_listtype(self, item):
945                 return True
946         def can_preview(self, item):
947                 return isSimpleFile(item)
948
949         def _updateButtonTexts(self):
950                 for k in ('red', 'green', 'yellow', 'blue'):
951                         btn = userDefinedButtons[k]
952                         self['key_' + k].setText(userDefinedActions[btn.value])
953
954         def updateButtons(self):
955                 item = self.getCurrentSelection()
956                 for name in ('red', 'green', 'yellow', 'blue'):
957                         action = userDefinedButtons[name].value
958                         if action.startswith('@'):
959                                 check = self.can_default
960                         elif action.startswith('/'):
961                                 check = self.can_gohome
962                         else:
963                                 try:
964                                         check = getattr(self, 'can_' + action)
965                                 except:
966                                         check = self.can_default
967                         gui = self["key_" + name]
968                         if check(item):
969                                 gui.show()
970                         else:
971                                 gui.hide()
972
973         def showEventInformation(self):
974                 from Screens.EventView import EventViewSimple
975                 from ServiceReference import ServiceReference
976                 evt = self["list"].getCurrentEvent()
977                 if evt:
978                         self.session.open(EventViewSimple, evt, ServiceReference(self.getCurrent()))
979
980         def saveListsize(self):
981                         listsize = self["list"].instance.size()
982                         self.listWidth = listsize.width()
983                         self.listHeight = listsize.height()
984                         self.updateDescription()
985
986         def onFirstTimeShown(self):
987                 self.onShown.remove(self.onFirstTimeShown) # Just once, not after returning etc.
988                 self.show()
989                 self.reloadList(self.selectedmovie, home=True)
990                 del self.selectedmovie
991
992         def getCurrent(self):
993                 # Returns selected serviceref (may be None)
994                 return self["list"].getCurrent()
995
996         def getCurrentSelection(self):
997                 # Returns None or (serviceref, info, begin, len)
998                 return self["list"].l.getCurrentSelection()
999
1000         def playAsDVD(self, path):
1001                 try:
1002                         from Screens import DVD
1003                         if path.endswith('VIDEO_TS/'):
1004                                 # strip away VIDEO_TS/ part
1005                                 path = os.path.split(path.rstrip('/'))[0]
1006                         self.session.open(DVD.DVDPlayer, dvd_filelist=[path])
1007                         return True
1008                 except Exception, e:
1009                         print "[ML] DVD Player not installed:", e
1010
1011         def __serviceStarted(self):
1012                 if not self.list.playInBackground:
1013                         return
1014                 ref = self.session.nav.getCurrentService()
1015                 cue = ref.cueSheet()
1016                 if not cue:
1017                         return
1018                 # disable writing the stop position
1019                 cue.setCutListEnable(2)
1020                 # find "resume" position
1021                 cuts = cue.getCutList()
1022                 if not cuts:
1023                         return
1024                 for (pts, what) in cuts:
1025                         if what == 3:
1026                                 last = pts
1027                                 break
1028                 else:
1029                         # no resume, jump to start of program (first marker)
1030                         last = cuts[0][0]
1031                 self.doSeekTo = last
1032                 self.callLater(self.doSeek)
1033
1034         def doSeek(self, pts = None):
1035                 if pts is None:
1036                         pts = self.doSeekTo
1037                 seekable = self.getSeek()
1038                 if seekable is None:
1039                         return
1040                 seekable.seekTo(pts)
1041
1042         def getSeek(self):
1043                 service = self.session.nav.getCurrentService()
1044                 if service is None:
1045                         return None
1046                 seek = service.seek()
1047                 if seek is None or not seek.isCurrentlySeekable():
1048                         return None
1049                 return seek
1050
1051         def callLater(self, function):
1052                 self.previewTimer = eTimer()
1053                 self.previewTimer.callback.append(function)
1054                 self.previewTimer.start(10, True)
1055
1056         def __evEOF(self):
1057                 playInBackground = self.list.playInBackground
1058                 if not playInBackground:
1059                         print "Not playing anything in background"
1060                         return
1061                 self.session.nav.stopService()
1062                 self.list.playInBackground = None
1063                 if config.movielist.play_audio_internal.value:
1064                         index = self.list.findService(playInBackground)
1065                         if index is None:
1066                                 return # Not found?
1067                         next = self.list.getItem(index + 1)
1068                         if not next:
1069                                 return
1070                         path = next.getPath()
1071                         ext = os.path.splitext(path)[1].lower()
1072                         print "Next up:", path
1073                         if ext in AUDIO_EXTENSIONS:
1074                                 self.nextInBackground = next
1075                                 self.callLater(self.preview)
1076                                 self["list"].moveToIndex(index+1)
1077
1078         def preview(self):
1079                 current = self.getCurrent()
1080                 if current is not None:
1081                         path = current.getPath()
1082                         if current.flags & eServiceReference.mustDescent:
1083                                 self.gotFilename(path)
1084                         else:
1085                                 Screens.InfoBar.InfoBar.instance.checkTimeshiftRunning(self.previewCheckTimeshiftCallback)
1086
1087         def startPreview(self):
1088                 if self.nextInBackground is not None:
1089                         current = self.nextInBackground
1090                         self.nextInBackground = None
1091                 else:
1092                         current = self.getCurrent()
1093                 playInBackground = self.list.playInBackground
1094                 if playInBackground:
1095                         self.list.playInBackground = None
1096                         self.session.nav.stopService()
1097                         if playInBackground != current:
1098                                 # come back to play the new one
1099                                 self.callLater(self.preview)
1100                 else:
1101                         self.list.playInBackground = current
1102                         self.session.nav.playService(current)
1103
1104         def previewCheckTimeshiftCallback(self, answer):
1105                 if answer:
1106                         self.startPreview()
1107
1108         def seekRelative(self, direction, amount):
1109                 if self.list.playInBackground:
1110                         seekable = self.getSeek()
1111                         if seekable is None:
1112                                 return
1113                         seekable.seekRelative(direction, amount)
1114
1115         def playbackStop(self):
1116                 if self.list.playInBackground:
1117                         self.list.playInBackground = None
1118                         self.session.nav.stopService()
1119
1120         def itemSelected(self, answer = True):
1121                 current = self.getCurrent()
1122                 if current is not None:
1123                         path = current.getPath()
1124                         if current.flags & eServiceReference.mustDescent:
1125                                 if path.endswith("VIDEO_TS/") or os.path.exists(os.path.join(path, 'VIDEO_TS.IFO')):
1126                                         #force a DVD extention
1127                                         Screens.InfoBar.InfoBar.instance.checkTimeshiftRunning(boundFunction(self.itemSelectedCheckTimeshiftCallback, ".iso", path))
1128                                         return
1129                                 self.gotFilename(path)
1130                         else:
1131                                 ext = os.path.splitext(path)[1].lower()
1132                                 if config.movielist.play_audio_internal.value and (ext in AUDIO_EXTENSIONS):
1133                                         self.preview()
1134                                         return
1135                                 if self.list.playInBackground:
1136                                         # Stop preview, come back later
1137                                         self.session.nav.stopService()
1138                                         self.list.playInBackground = None
1139                                         self.callLater(self.itemSelected)
1140                                         return
1141                                 if ext in IMAGE_EXTENSIONS:
1142                                         try:
1143                                                 from Plugins.Extensions.PicturePlayer import ui
1144                                                 # Build the list for the PicturePlayer UI
1145                                                 filelist = []
1146                                                 index = 0
1147                                                 for item in self.list.list:
1148                                                         p = item[0].getPath()
1149                                                         if p == path:
1150                                                                 index = len(filelist)
1151                                                         if os.path.splitext(p)[1].lower() in IMAGE_EXTENSIONS:
1152                                                                 filelist.append(((p,False), None))
1153                                                 self.session.open(ui.Pic_Full_View, filelist, index, path)
1154                                         except Exception, ex:
1155                                                 print "[ML] Cannot display", str(ex)
1156                                         return
1157                                 Screens.InfoBar.InfoBar.instance.checkTimeshiftRunning(boundFunction(self.itemSelectedCheckTimeshiftCallback, ext, path))
1158
1159         def itemSelectedCheckTimeshiftCallback(self, ext, path, answer):
1160                 if answer:
1161                         if ext in DVD_EXTENSIONS:
1162                                 if self.playAsDVD(path):
1163                                         return
1164                         self.movieSelected()
1165
1166         # Note: DVDBurn overrides this method, hence the itemSelected indirection.
1167         def movieSelected(self):
1168                 current = self.getCurrent()
1169                 if current is not None:
1170                         self.saveconfig()
1171                         self.close(current)
1172
1173         def doContext(self):
1174                 current = self.getCurrent()
1175                 if current is not None:
1176                         self.session.openWithCallback(self.doneContext, MovieContextMenu, self, current)
1177
1178         def doneContext(self, action):
1179                 if action is not None:
1180                         action()
1181
1182         def saveLocalSettings(self):
1183                 if config.movielist.settings_per_directory.value:
1184                         try:
1185                                 path = os.path.join(config.movielist.last_videodir.value, ".e2settings.pkl")
1186                                 pickle.dump(self.settings, open(path, "wb"))
1187                         except Exception, e:
1188                                 print "Failed to save settings to %s: %s" % (path, e)
1189                 # Also set config items, in case the user has a read-only disk
1190                 config.movielist.moviesort.value = self.settings["moviesort"]
1191                 config.movielist.listtype.value = self.settings["listtype"]
1192                 config.movielist.description.value = self.settings["description"]
1193                 config.usage.on_movie_eof.value = self.settings["movieoff"]
1194                 # save moviesort and movieeof values for using by hotkeys
1195                 config.movielist.moviesort.save()
1196                 config.usage.on_movie_eof.save()
1197
1198         def loadLocalSettings(self):
1199                 'Load settings, called when entering a directory'
1200                 if config.movielist.settings_per_directory.value:
1201                         try:
1202                                 path = os.path.join(config.movielist.last_videodir.value, ".e2settings.pkl")
1203                                 updates = pickle.load(open(path, "rb"))
1204                                 self.applyConfigSettings(updates)
1205                         except IOError, e:
1206                                 updates = {
1207                                         "listtype": config.movielist.listtype.default,
1208                                         "moviesort": config.movielist.moviesort.default,
1209                                         "description": config.movielist.description.default,
1210                                         "movieoff": config.usage.on_movie_eof.default
1211                                 }
1212                                 self.applyConfigSettings(updates)
1213                                 pass # ignore fail to open errors
1214                         except Exception, e:
1215                                 print "Failed to load settings from %s: %s" % (path, e)
1216                 else:
1217                         updates = {
1218                                 "listtype": config.movielist.listtype.value,
1219                                 "moviesort": config.movielist.moviesort.value,
1220                                 "description": config.movielist.description.value,
1221                                 "movieoff": config.usage.on_movie_eof.value
1222                                 }
1223                         self.applyConfigSettings(updates)
1224
1225         def applyConfigSettings(self, updates):
1226                 needUpdate = ("description" in updates) and (updates["description"] != self.settings["description"])
1227                 self.settings.update(updates)
1228                 if needUpdate:
1229                         self["list"].setDescriptionState(self.settings["description"])
1230                         self.updateDescription()
1231                 if self.settings["listtype"] != self["list"].list_type:
1232                         self["list"].setListType(self.settings["listtype"])
1233                         needUpdate = True
1234                 if self.settings["moviesort"] != self["list"].sort_type:
1235                         self["list"].setSortType(self.settings["moviesort"])
1236                         needUpdate = True
1237                 if self.settings["movieoff"] != self.movieOff:
1238                         self.movieOff = self.settings["movieoff"]
1239                         needUpdate = True
1240                 config.movielist.moviesort.value = self.settings["moviesort"]
1241                 config.movielist.listtype.value = self.settings["listtype"]
1242                 config.movielist.description.value = self.settings["description"]
1243                 config.usage.on_movie_eof.value = self.settings["movieoff"]
1244                 return needUpdate
1245
1246         def sortBy(self, newType):
1247                 self.settings["moviesort"] = newType
1248                 self.saveLocalSettings()
1249                 self.setSortType(newType)
1250                 self.reloadList()
1251
1252         def listType(self, newType):
1253                 self.settings["listtype"] = newType
1254                 self.saveLocalSettings()
1255                 self.setListType(newType)
1256                 self.reloadList()
1257
1258         def showDescription(self, newType):
1259                 self.settings["description"] = newType
1260                 self.saveLocalSettings()
1261                 self.setDescriptionState(newType)
1262                 self.updateDescription()
1263
1264         def abort(self):
1265                 global playlist
1266                 del playlist[:]
1267                 if self.list.playInBackground:
1268                         self.list.playInBackground = None
1269                         self.session.nav.stopService()
1270                         self.callLater(self.abort)
1271                         return
1272                 self.saveconfig()
1273                 self.close(None)
1274
1275         def saveconfig(self):
1276                 config.movielist.last_selected_tags.value = self.selected_tags
1277
1278         def configure(self):
1279                 self.session.openWithCallback(self.configureDone, MovieBrowserConfiguration)
1280
1281         def configureDone(self, result):
1282                 if result:
1283                         self.applyConfigSettings({\
1284                                 "listtype": config.movielist.listtype.value,
1285                                 "moviesort": config.movielist.moviesort.value,
1286                                 "description": config.movielist.description.value,
1287                                 "movieoff": config.usage.on_movie_eof.value})
1288                         self.saveLocalSettings()
1289                         self._updateButtonTexts()
1290                         self.reloadList()
1291
1292         def can_sortby(self, item):
1293                 return True
1294
1295         def do_sortby(self):
1296                 self.selectSortby()
1297
1298         def selectSortby(self):
1299                 menu = []
1300                 index = 0
1301                 used = 0
1302                 for x in l_moviesort:
1303                         if int(x[0]) == int(config.movielist.moviesort.value):
1304                                 used = index
1305                         menu.append((_(x[1]), x[0], "%d" % index))
1306                         index += 1
1307                 self.session.openWithCallback(self.sortbyMenuCallback, ChoiceBox, title=_("Sort list:"), list=menu, selection = used)
1308
1309         def sortbyMenuCallback(self, choice):
1310                 if choice is None:
1311                         return
1312                 self.sortBy(int(choice[1]))
1313                 self["movie_sort"].setPixmapNum(int(choice[1])-1)
1314
1315         def getTagDescription(self, tag):
1316                 # TODO: access the tag database
1317                 return tag
1318
1319         def updateTags(self):
1320                 # get a list of tags available in this list
1321                 self.tags = self["list"].tags
1322
1323         def setListType(self, type):
1324                 self["list"].setListType(type)
1325
1326         def setDescriptionState(self, val):
1327                 self["list"].setDescriptionState(val)
1328
1329         def setSortType(self, type):
1330                 self["list"].setSortType(type)
1331
1332         def setCurrentRef(self, path):
1333                 self.current_ref = eServiceReference("2:0:1:0:0:0:0:0:0:0:" + path)
1334                 # Magic: this sets extra things to show
1335                 self.current_ref.setName('16384:jpg 16384:png 16384:gif 16384:bmp')
1336
1337         def reloadList(self, sel = None, home = False):
1338                 self.reload_sel = sel
1339                 self.reload_home = home
1340                 self["waitingtext"].visible = True
1341                 self.pathselectEnabled = False
1342                 self.callLater(self.reloadWithDelay)
1343
1344         def reloadWithDelay(self):
1345                 if not os.path.isdir(config.movielist.last_videodir.value):
1346                         path = defaultMoviePath()
1347                         config.movielist.last_videodir.value = path
1348                         config.movielist.last_videodir.save()
1349                         self.setCurrentRef(path)
1350                         self["freeDiskSpace"].path = path
1351                 if self.reload_sel is None:
1352                         self.reload_sel = self.getCurrent()
1353                 self["list"].reload(self.current_ref, self.selected_tags)
1354                 self.updateTags()
1355                 title = _("Recorded files...")
1356                 if config.usage.setup_level.index >= 2: # expert+
1357                         title += "  " + config.movielist.last_videodir.value
1358                 if self.selected_tags:
1359                         title += " - " + ','.join(self.selected_tags)
1360                 self.setTitle(title)
1361                 self.displayMovieOffStatus()
1362                 self.displaySortStatus()
1363                 if not (self.reload_sel and self["list"].moveTo(self.reload_sel)):
1364                         if self.reload_home:
1365                                 self["list"].moveToFirstMovie()
1366                 self["freeDiskSpace"].update()
1367                 self["waitingtext"].visible = False
1368                 self.createPlaylist()
1369                 if self.playGoTo:
1370                         if self.isItemPlayable(self.list.getCurrentIndex() + 1):
1371                                 if self.playGoTo > 0:
1372                                         self.list.moveDown()
1373                                 else:
1374                                         self.list.moveUp()
1375                                 self.playGoTo = None
1376                                 self.callLater(self.preview)
1377                 self.callLater(self.enablePathSelect)
1378
1379         def enablePathSelect(self):
1380                 self.pathselectEnabled = True
1381
1382         def doPathSelect(self):
1383                 if self.pathselectEnabled:
1384                         self.session.openWithCallback(
1385                                 self.gotFilename,
1386                                 MovieLocationBox,
1387                                 _("Please select the movie path..."),
1388                                 config.movielist.last_videodir.value
1389                         )
1390
1391         def gotFilename(self, res, selItem=None):
1392                 def servicePinEntered(res, selItem, result):
1393                         if result:
1394                                 from Components.ParentalControl import parentalControl
1395                                 parentalControl.setSessionPinCached()
1396                                 parentalControl.hideBlacklist()
1397                                 self.gotFilename(res, selItem)
1398                         elif result == False:
1399                                 self.session.open(MessageBox, _("The pin code you entered is wrong."), MessageBox.TYPE_INFO, timeout=3)
1400                 if not res:
1401                         return
1402                 # serviceref must end with /
1403                 if not res.endswith('/'):
1404                         res += '/'
1405                 currentDir = config.movielist.last_videodir.value
1406                 if res != currentDir:
1407                         if os.path.isdir(res):
1408                                 baseName = os.path.basename(res[:-1])
1409                                 if config.ParentalControl.servicepinactive.value and baseName.startswith(".") and not baseName.startswith(".Trash"):
1410                                         from Components.ParentalControl import parentalControl
1411                                         if not parentalControl.sessionPinCached:
1412                                                 self.session.openWithCallback(boundFunction(servicePinEntered, res, selItem), PinInput, pinList=[x.value for x in config.ParentalControl.servicepin], triesEntry=config.ParentalControl.retries.servicepin, title=_("Please enter the correct pin code"), windowTitle=_("Enter pin code"))
1413                                                 return
1414                                 config.movielist.last_videodir.value = res
1415                                 config.movielist.last_videodir.save()
1416                                 self.loadLocalSettings()
1417                                 self.setCurrentRef(res)
1418                                 self["freeDiskSpace"].path = res
1419                                 if selItem:
1420                                         self.reloadList(home = True, sel = selItem)
1421                                 else:
1422                                         self.reloadList(home = True, sel = eServiceReference("2:0:1:0:0:0:0:0:0:0:" + currentDir))
1423                         else:
1424                                 self.session.open(MessageBox, _("Directory %s does not exist.") % (res), type=MessageBox.TYPE_ERROR, timeout=5)
1425
1426         def showAll(self):
1427                 self.selected_tags_ele = None
1428                 self.selected_tags = None
1429                 self.reloadList(home = True)
1430
1431         def showTagsN(self, tagele):
1432                 if not self.tags:
1433                         self.showTagWarning()
1434                 elif not tagele or (self.selected_tags and tagele.value in self.selected_tags) or not tagele.value in self.tags:
1435                         self.showTagsMenu(tagele)
1436                 else:
1437                         self.selected_tags_ele = tagele
1438                         self.selected_tags = self.tags[tagele.value]
1439                         self.reloadList(home = True)
1440
1441         def showTagsFirst(self):
1442                 self.showTagsN(config.movielist.first_tags)
1443
1444         def showTagsSecond(self):
1445                 self.showTagsN(config.movielist.second_tags)
1446
1447         def can_tags(self, item):
1448                 return self.tags
1449         def do_tags(self):
1450                 self.showTagsN(None)
1451
1452         def tagChosen(self, tag):
1453                 if tag is not None:
1454                         if tag[1] is None: # all
1455                                 self.showAll()
1456                                 return
1457                         # TODO: Some error checking maybe, don't wanna crash on KeyError
1458                         self.selected_tags = self.tags[tag[0]]
1459                         if self.selected_tags_ele:
1460                                 self.selected_tags_ele.value = tag[0]
1461                                 self.selected_tags_ele.save()
1462                         self.reloadList(home = True)
1463
1464         def showTagsMenu(self, tagele):
1465                 self.selected_tags_ele = tagele
1466                 lst = [(_("show all tags"), None)] + [(tag, self.getTagDescription(tag)) for tag in sorted(self.tags)]
1467                 self.session.openWithCallback(self.tagChosen, ChoiceBox, title=_("Please select tag to filter..."), list = lst)
1468
1469         def showTagWarning(self):
1470                 self.session.open(MessageBox, _("No tags are set on these movies."), MessageBox.TYPE_ERROR)
1471
1472         def selectMovieLocation(self, title, callback):
1473                 bookmarks = [("("+_("Other")+"...)", None)]
1474                 buildMovieLocationList(bookmarks)
1475                 self.onMovieSelected = callback
1476                 self.movieSelectTitle = title
1477                 self.session.openWithCallback(self.gotMovieLocation, ChoiceBox, title=title, list = bookmarks)
1478
1479         def gotMovieLocation(self, choice):
1480                 if not choice:
1481                         # cancelled
1482                         self.onMovieSelected(None)
1483                         del self.onMovieSelected
1484                         return
1485                 if isinstance(choice, tuple):
1486                         if choice[1] is None:
1487                                 # Display full browser, which returns string
1488                                 self.session.openWithCallback(
1489                                         self.gotMovieLocation,
1490                                         MovieLocationBox,
1491                                         self.movieSelectTitle,
1492                                         config.movielist.last_videodir.value
1493                                 )
1494                                 return
1495                         choice = choice[1]
1496                 choice = os.path.normpath(choice)
1497                 self.rememberMovieLocation(choice)
1498                 self.onMovieSelected(choice)
1499                 del self.onMovieSelected
1500
1501         def rememberMovieLocation(self, where):
1502                 if where in last_selected_dest:
1503                         last_selected_dest.remove(where)
1504                 last_selected_dest.insert(0, where)
1505                 if len(last_selected_dest) > 5:
1506                         del last_selected_dest[-1]
1507
1508         def can_bookmarks(self, item):
1509                 return True
1510         def do_bookmarks(self):
1511                 self.selectMovieLocation(title=_("Please select the movie path..."), callback=self.gotFilename)
1512
1513         def can_addbookmark(self, item):
1514                 return True
1515         def exist_bookmark(self):
1516                 path = config.movielist.last_videodir.value
1517                 if path in config.movielist.videodirs.value:
1518                         return True
1519                 return False
1520         def do_addbookmark(self):
1521                 path = config.movielist.last_videodir.value
1522                 if path in config.movielist.videodirs.value:
1523                         if len(path) > 40:
1524                                 path = '...' + path[-40:]
1525                         self.session.openWithCallback(self.removeBookmark, MessageBox, _("Do you really want to remove your bookmark of %s?") % path)
1526                 else:
1527                         config.movielist.videodirs.value += [path]
1528                         config.movielist.videodirs.save()
1529         def removeBookmark(self, yes):
1530                 if not yes:
1531                         return
1532                 path = config.movielist.last_videodir.value
1533                 bookmarks = config.movielist.videodirs.value
1534                 bookmarks.remove(path)
1535                 config.movielist.videodirs.value = bookmarks
1536                 config.movielist.videodirs.save()
1537
1538         def can_createdir(self, item):
1539                 return True
1540         def do_createdir(self):
1541                 from Screens.VirtualKeyBoard import VirtualKeyBoard
1542                 self.session.openWithCallback(self.createDirCallback, VirtualKeyBoard,
1543                         title = _("Please enter name of the new directory"),
1544                         text = "")
1545         def createDirCallback(self, name):
1546                 if not name:
1547                         return
1548                 msg = None
1549                 try:
1550                         path = os.path.join(config.movielist.last_videodir.value, name)
1551                         os.mkdir(path)
1552                         if not path.endswith('/'):
1553                                 path += '/'
1554                         self.reloadList(sel = eServiceReference("2:0:1:0:0:0:0:0:0:0:" + path))
1555                 except OSError, e:
1556                         print "Error %s:" % e.errno, e
1557                         if e.errno == 17:
1558                                 msg = _("The path %s already exists.") % name
1559                         else:
1560                                 msg = _("Error") + '\n' + str(e)
1561                 except Exception, e:
1562                         print "[ML] Unexpected error:", e
1563                         msg = _("Error") + '\n' + str(e)
1564                 if msg:
1565                         self.session.open(MessageBox, msg, type = MessageBox.TYPE_ERROR, timeout = 5)
1566
1567         def can_rename(self, item):
1568                 return canMove(item)
1569         def do_rename(self):
1570                 item = self.getCurrentSelection()
1571                 if not canMove(item):
1572                         return
1573                 if isFolder(item):
1574                         p = os.path.split(item[0].getPath())
1575                         if not p[1]:
1576                                 # if path ends in '/', p is blank.
1577                                 p = os.path.split(p[0])
1578                         name = p[1]
1579                 else:
1580                         info = item[1]
1581                         name = info.getName(item[0])
1582                 from Screens.VirtualKeyBoard import VirtualKeyBoard
1583                 self.session.openWithCallback(self.renameCallback, VirtualKeyBoard,
1584                         title = _("Rename"),
1585                         text = name)
1586
1587         def do_decode(self):
1588                 from ServiceReference import ServiceReference
1589                 item = self.getCurrentSelection()
1590                 info = item[1]
1591                 filepath = item[0].getPath()
1592                 if not filepath.endswith('.ts'):
1593                         return
1594                 serviceref = ServiceReference(None, reftype = eServiceReference.idDVB, path = filepath)
1595                 name = info.getName(item[0]) + ' - decoded'
1596                 description = info.getInfoString(item[0], iServiceInformation.sDescription)
1597                 recording = RecordTimer.RecordTimerEntry(serviceref, int(time.time()), int(time.time()) + 3600, name, description, 0, dirname = preferredTimerPath())
1598                 recording.dontSave = True
1599                 recording.autoincrease = True
1600                 recording.setAutoincreaseEnd()
1601                 self.session.nav.RecordTimer.record(recording, ignoreTSC = True)
1602
1603         def renameCallback(self, name):
1604                 if not name:
1605                         return
1606                 name = name.strip()
1607                 item = self.getCurrentSelection()
1608                 if item and item[0]:
1609                         try:
1610                                 path = item[0].getPath().rstrip('/')
1611                                 meta = path + '.meta'
1612                                 if os.path.isfile(meta):
1613                                         metafile = open(meta, "r+")
1614                                         sid = metafile.readline()
1615                                         oldtitle = metafile.readline()
1616                                         rest = metafile.read()
1617                                         metafile.seek(0)
1618                                         metafile.write("%s%s\n%s" %(sid, name, rest))
1619                                         metafile.truncate()
1620                                         metafile.close()
1621                                         index = self.list.getCurrentIndex()
1622                                         info = self.list.list[index]
1623                                         if hasattr(info[3], 'txt'):
1624                                                 info[3].txt = name
1625                                         else:
1626                                                 self.list.invalidateCurrentItem()
1627                                         return
1628                                 pathname,filename = os.path.split(path)
1629                                 newpath = os.path.join(pathname, name)
1630                                 msg = None
1631                                 print "[ML] rename", path, "to", newpath
1632                                 os.rename(path, newpath)
1633                                 self.reloadList(sel = eServiceReference("2:0:1:0:0:0:0:0:0:0:" + newpath))
1634                         except OSError, e:
1635                                 print "Error %s:" % e.errno, e
1636                                 if e.errno == 17:
1637                                         msg = _("The path %s already exists.") % name
1638                                 else:
1639                                         msg = _("Error") + '\n' + str(e)
1640                         except Exception, e:
1641                                 import traceback
1642                                 print "[ML] Unexpected error:", e
1643                                 traceback.print_exc()
1644                                 msg = _("Error") + '\n' + str(e)
1645                         if msg:
1646                                 self.session.open(MessageBox, msg, type = MessageBox.TYPE_ERROR, timeout = 5)
1647
1648         def do_reset(self):
1649                 current = self.getCurrent()
1650                 if current:
1651                         resetMoviePlayState(current.getPath() + ".cuts", current)
1652                         self["list"].invalidateCurrentItem() # trigger repaint
1653
1654         def do_move(self):
1655                 item = self.getCurrentSelection()
1656                 if canMove(item):
1657                         current = item[0]
1658                         info = item[1]
1659                         if info is None:
1660                                 # Special case
1661                                 return
1662                         name = info and info.getName(current) or _("this recording")
1663                         path = os.path.normpath(current.getPath())
1664                         # show a more limited list of destinations, no point
1665                         # in showing mountpoints.
1666                         title = _("Select destination for:") + " " + name
1667                         bookmarks = [("("+_("Other")+"...)", None)]
1668                         inlist = []
1669                         # Subdirs
1670                         try:
1671                                 base = os.path.split(path)[0]
1672                                 for fn in os.listdir(base):
1673                                         if not fn.startswith('.'): # Skip hidden things
1674                                                 d = os.path.join(base, fn)
1675                                                 if os.path.isdir(d) and (d not in inlist):
1676                                                         bookmarks.append((fn,d))
1677                                                         inlist.append(d)
1678                         except Exception, e :
1679                                 print "[MovieSelection]", e
1680                         # Last favourites
1681                         for d in last_selected_dest:
1682                                 if d not in inlist:
1683                                         bookmarks.append((d,d))
1684                         # Other favourites
1685                         for d in config.movielist.videodirs.value:
1686                                 d = os.path.normpath(d)
1687                                 bookmarks.append((d,d))
1688                                 inlist.append(d)
1689                         for p in Components.Harddisk.harddiskmanager.getMountedPartitions():
1690                                 d = os.path.normpath(p.mountpoint)
1691                                 if d not in inlist:
1692                                         bookmarks.append((p.description, d))
1693                                         inlist.append(d)
1694                         self.onMovieSelected = self.gotMoveMovieDest
1695                         self.movieSelectTitle = title
1696                         self.session.openWithCallback(self.gotMovieLocation, ChoiceBox, title=title, list=bookmarks)
1697
1698         def gotMoveMovieDest(self, choice):
1699                 if not choice:
1700                         return
1701                 dest = os.path.normpath(choice)
1702                 try:
1703                         item = self.getCurrentSelection()
1704                         current = item[0]
1705                         if item[1] is None:
1706                                 name = None
1707                         else:
1708                                 name = item[1].getName(current)
1709                         moveServiceFiles(current, dest, name)
1710                         self["list"].removeService(current)
1711                 except Exception, e:
1712                         self.session.open(MessageBox, str(e), MessageBox.TYPE_ERROR)
1713
1714         def can_copy(self, item):
1715                 return canCopy(item)
1716         def do_copy(self):
1717                 item = self.getCurrentSelection()
1718                 if canMove(item):
1719                         current = item[0]
1720                         info = item[1]
1721                         if info is None:
1722                                 # Special case
1723                                 return
1724                         name = info and info.getName(current) or _("this recording")
1725                         self.selectMovieLocation(title=_("Select copy destination for:") + " " + name, callback=self.gotCopyMovieDest)
1726
1727         def gotCopyMovieDest(self, choice):
1728                 if not choice:
1729                         return
1730                 dest = os.path.normpath(choice)
1731                 try:
1732                         item = self.getCurrentSelection()
1733                         current = item[0]
1734                         if item[1] is None:
1735                                 name = None
1736                         else:
1737                                 name = item[1].getName(current)
1738                         copyServiceFiles(current, dest, name)
1739                 except Exception, e:
1740                         self.session.open(MessageBox, str(e), MessageBox.TYPE_ERROR)
1741
1742         def stopTimer(self, timer):
1743                 if timer.isRunning():
1744                         if timer.repeated:
1745                                 if not timer.disabled:
1746                                         timer.enable()
1747                                 timer.processRepeated(findRunningEvent=False)
1748                                 self.session.nav.RecordTimer.doActivate(timer)
1749                         else:
1750                                 timer.afterEvent = RecordTimer.AFTEREVENT.NONE
1751                                 NavigationInstance.instance.RecordTimer.removeEntry(timer)
1752
1753         def onTimerChoice(self, choice):
1754                 if isinstance(choice, tuple) and choice[1]:
1755                         choice, timer = choice[1]
1756                         if not choice:
1757                                 # cancel
1758                                 return
1759                         if "s" in choice:
1760                                 self.stopTimer(timer)
1761                         if "d" in choice:
1762                                 self.delete(True)
1763
1764         def do_delete(self):
1765                 self.delete()
1766
1767         def delete(self, *args):
1768                 if args and (not args[0]):
1769                         # cancelled by user (passing any arg means it's a dialog return)
1770                         return
1771                 item = self.getCurrentSelection()
1772                 if not canDelete(item):
1773                         if item and isTrashFolder(item[0]):
1774                                 # Red button to empty trashcan...
1775                                 self.purgeAll()
1776                         return
1777                 current = item[0]
1778                 info = item[1]
1779                 cur_path = os.path.realpath(current.getPath())
1780                 st = os.stat(cur_path)
1781                 name = info and info.getName(current) or _("this recording")
1782                 are_you_sure = _("Do you really want to delete %s?") % (name)
1783                 if current.flags & eServiceReference.mustDescent:
1784                         files = 0
1785                         subdirs = 0
1786                         if args:
1787                                 # already confirmed...
1788                                 # but not implemented yet...
1789                                 msg = ''
1790                                 if config.usage.movielist_trashcan.value:
1791                                         try:
1792                                                 # Move the files to the trash can in a way that their CTIME is
1793                                                 # set to "now". A simple move would not correctly update the
1794                                                 # ctime, and hence trigger a very early purge.
1795                                                 trash = Tools.Trashcan.createTrashFolder(cur_path)
1796                                                 trash = os.path.join(trash, os.path.split(cur_path)[1])
1797                                                 os.mkdir(trash)
1798                                                 for root, dirnames, filenames in os.walk(cur_path):
1799                                                         trashroot = os.path.join(trash, root[len(cur_path)+1:])
1800                                                         for fn in filenames:
1801                                                                 print "Move %s -> %s" % (os.path.join(root, fn), os.path.join(trashroot, fn))
1802                                                                 os.rename(os.path.join(root, fn), os.path.join(trashroot, fn))
1803                                                         for dn in dirnames:
1804                                                                 print "MkDir", os.path.join(trashroot, dn)
1805                                                                 os.mkdir(os.path.join(trashroot, dn))
1806                                                 # second pass to remove the empty directories
1807                                                 for root, dirnames, filenames in os.walk(cur_path, topdown=False):
1808                                                         for dn in dirnames:
1809                                                                 print "rmdir", os.path.join(trashroot, dn)
1810                                                                 os.rmdir(os.path.join(root, dn))
1811                                                 os.rmdir(cur_path)
1812                                                 self["list"].removeService(current)
1813                                                 self.showActionFeedback(_("Deleted") + " " + name)
1814                                                 # Files were moved to .Trash, ok.
1815                                                 return
1816                                         except OSError, e:
1817                                                 print "[MovieSelection] Cannot move to trash", e
1818                                                 if e.errno == 18:
1819                                                         # This occurs when moving across devices
1820                                                         msg = _("Cannot move files on a different disk or system to the trash can") + ". "
1821                                                 else:
1822                                                         msg = _("Cannot move to trash can") + ".\n" + str(e) + "\n"
1823                                         except Exception, e:
1824                                                 print "[MovieSelection] Weird error moving to trash", e
1825                                                 # Failed to create trash or move files.
1826                                                 msg = _("Cannot move to trash can") + "\n" + str(e) + "\n"
1827                                 msg += _("Sorry, deleting directories can (for now) only be done through the trash can.")
1828                                 self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR)
1829                                 return
1830                         for fn in os.listdir(cur_path):
1831                                 if (fn != '.') and (fn != '..'):
1832                                         ffn = os.path.join(cur_path, fn)
1833                                         if os.path.isdir(ffn):
1834                                                 subdirs += 1
1835                                         else:
1836                                                 files += 1
1837                         if files or subdirs:
1838                                 msg = _("Directory contains %s and %s.") % (ngettext("%d file", "%d files", files) % files, ngettext("%d subdirectory", "%d subdirectories", subdirs) % subdirs) + '\n' + are_you_sure
1839                                 if isInTrashFolder(current):
1840                                         # Red button to empty trashcan item or subdir
1841                                         msg = _("Deleted items") + "\n" + msg
1842                                         callback = self.purgeConfirmed
1843                                 else:
1844                                         callback = self.delete
1845                                 self.session.openWithCallback(callback, MessageBox, msg)
1846                                 return
1847                         else:
1848                                 try:
1849                                         os.rmdir(cur_path)
1850                                 except Exception, e:
1851                                         print "[MovieSelection] Failed delete", e
1852                                         self.session.open(MessageBox, _("Delete failed!") + "\n" + str(e), MessageBox.TYPE_ERROR)
1853                                 else:
1854                                         self["list"].removeService(current)
1855                                         self.showActionFeedback(_("Deleted") + " " + name)
1856                 else:
1857                         if not args:
1858                                 rec_filename = os.path.split(current.getPath())[1]
1859                                 if rec_filename.endswith(".ts"): rec_filename = rec_filename[:-3]
1860                                 for timer in NavigationInstance.instance.RecordTimer.timer_list:
1861                                         if timer.isRunning() and not timer.justplay and rec_filename in timer.Filename:
1862                                                 choices = [
1863                                                         (_("Cancel"), None),
1864                                                         (_("Stop recording"), ("s", timer)),
1865                                                         (_("Stop recording and delete"), ("sd", timer))]
1866                                                 self.session.openWithCallback(self.onTimerChoice, ChoiceBox, title=_("Recording in progress") + ":\n%s" % name, list=choices)
1867                                                 return
1868                                 if time.time() - st.st_mtime < 5:
1869                                         if not args:
1870                                                 self.session.openWithCallback(self.delete, MessageBox, _("File appears to be busy.\n") + are_you_sure)
1871                                                 return
1872                         if config.usage.movielist_trashcan.value:
1873                                 try:
1874                                         trash = Tools.Trashcan.createTrashFolder(cur_path)
1875                                         # Also check whether we're INSIDE the trash, then it's a purge.
1876                                         if cur_path.startswith(trash):
1877                                                 msg = _("Deleted items") + "\n"
1878                                         else:
1879                                                 moveServiceFiles(current, trash, name, allowCopy=False)
1880                                                 self["list"].removeService(current)
1881                                                 # Files were moved to .Trash, ok.
1882                                                 from Screens.InfoBarGenerics import delResumePoint
1883                                                 delResumePoint(current)
1884                                                 self.showActionFeedback(_("Deleted") + " " + name)
1885                                                 return
1886                                 except OSError, e:
1887                                         print "[MovieSelection] Cannot move to trash", e
1888                                         if e.errno == 18:
1889                                                 # This occurs when moving across devices
1890                                                 msg = _("Cannot move files on a different disk or system to the trash can") + ". "
1891                                         else:
1892                                                 msg = _("Cannot move to trash can") + ".\n" + str(e) + "\n"
1893                                 except Exception, e:
1894                                         print "[MovieSelection] Weird error moving to trash", e
1895                                         # Failed to create trash or move files.
1896                                         msg = _("Cannot move to trash can") + "\n" + str(e) + "\n"
1897                         else:
1898                                 msg = ''
1899                         self.session.openWithCallback(self.deleteConfirmed, MessageBox, msg + are_you_sure)
1900
1901         def deleteConfirmed(self, confirmed):
1902                 if not confirmed:
1903                         return
1904                 item = self.getCurrentSelection()
1905                 if item is None:
1906                         return # huh?
1907                 current = item[0]
1908                 info = item[1]
1909                 name = info and info.getName(current) or _("this recording")
1910                 serviceHandler = eServiceCenter.getInstance()
1911                 offline = serviceHandler.offlineOperations(current)
1912                 try:
1913                         if offline is None:
1914                                 from enigma import eBackgroundFileEraser
1915                                 eBackgroundFileEraser.getInstance().erase(os.path.realpath(current.getPath()))
1916                         else:
1917                                 if offline.deleteFromDisk(0):
1918                                         raise Exception, "Offline delete failed"
1919                         self["list"].removeService(current)
1920                         from Screens.InfoBarGenerics import delResumePoint
1921                         delResumePoint(current)
1922                         self.showActionFeedback(_("Deleted") + " " + name)
1923                 except Exception, ex:
1924                         self.session.open(MessageBox, _("Delete failed!") + "\n" + name + "\n" + str(ex), MessageBox.TYPE_ERROR)
1925
1926
1927         def purgeAll(self):
1928                 recordings = self.session.nav.getRecordings()
1929                 next_rec_time = -1
1930                 msg = _("Permanently delete all recordings in the trash can?")
1931                 if not recordings:
1932                         next_rec_time = self.session.nav.RecordTimer.getNextRecordingTime()
1933                 if recordings or (next_rec_time > 0 and (next_rec_time - time.time()) < 120):
1934                         msg += "\n" + _("Recording(s) are in progress or coming up in few seconds!")
1935                 self.session.openWithCallback(self.purgeConfirmed, MessageBox, msg)
1936
1937         def purgeConfirmed(self, confirmed):
1938                 if not confirmed:
1939                         return
1940                 item = self.getCurrentSelection()
1941                 current = item[0]
1942                 cur_path = os.path.realpath(current.getPath())
1943                 Tools.Trashcan.cleanAll(cur_path)
1944
1945         def showNetworkSetup(self):
1946                 import NetworkSetup
1947                 self.session.open(NetworkSetup.NetworkAdapterSelection)
1948
1949         def showActionFeedback(self, text):
1950                 if self.feedbackTimer is None:
1951                         self.feedbackTimer = eTimer()
1952                         self.feedbackTimer.callback.append(self.hideActionFeedback)
1953                 else:
1954                         self.feedbackTimer.stop()
1955                 self.feedbackTimer.start(3000, 1)
1956                 self.diskinfo.setText(text)
1957
1958         def hideActionFeedback(self):
1959                 print "[ML] hide feedback"
1960                 self.diskinfo.update()
1961
1962         def can_gohome(self, item):
1963                 return True
1964
1965         def do_gohome(self):
1966                 self.gotFilename(defaultMoviePath())
1967
1968         def do_sort(self):
1969                 index = 0
1970                 for index, item in enumerate(l_moviesort):
1971                         if int(item[0]) == int(config.movielist.moviesort.value):
1972                                 break
1973                 if index >= len(l_moviesort) - 1:
1974                         index = 0
1975                 else:
1976                         index += 1
1977                 #descriptions in native languages too long...
1978                 sorttext = l_moviesort[index][2]
1979                 if config.movielist.btn_red.value == "sort": self['key_red'].setText(sorttext)
1980                 if config.movielist.btn_green.value == "sort": self['key_green'].setText(sorttext)
1981                 if config.movielist.btn_yellow.value == "sort": self['key_yellow'].setText(sorttext)
1982                 if config.movielist.btn_blue.value == "sort": self['key_blue'].setText(sorttext)
1983                 self.sorttimer = eTimer()
1984                 self.sorttimer.callback.append(self._updateButtonTexts)
1985                 self.sorttimer.start(1500, True) #time for displaying sorting type just applied
1986                 self.sortBy(int(l_moviesort[index][0]))
1987                 self["movie_sort"].setPixmapNum(int(l_moviesort[index][0])-1)
1988
1989         def do_listtype(self):
1990                 index = 0
1991                 for index, item in enumerate(l_listtype):
1992                         if int(item[0]) == int(config.movielist.listtype.value):
1993                                 break
1994                 if index >= len(l_listtype) - 1:
1995                         index = 0
1996                 else:
1997                         index += 1
1998                 self.listType(int(l_listtype[index][0]))
1999
2000         def do_preview(self):
2001                 self.preview()
2002
2003         def displaySortStatus(self):
2004                 self["movie_sort"].setPixmapNum(int(config.movielist.moviesort.value)-1)
2005                 self["movie_sort"].show()
2006
2007         def can_movieoff(self, item):
2008                 return True
2009
2010         def do_movieoff(self):
2011                 self.setNextMovieOffStatus()
2012                 self.displayMovieOffStatus()
2013
2014         def displayMovieOffStatus(self):
2015                 self["movie_off"].setPixmapNum(config.usage.on_movie_eof.getIndex())
2016                 self["movie_off"].show()
2017
2018         def setNextMovieOffStatus(self):
2019                 config.usage.on_movie_eof.selectNext()
2020                 self.settings["movieoff"] = config.usage.on_movie_eof.value
2021                 self.saveLocalSettings()
2022
2023         def can_movieoff_menu(self, item):
2024                 return True
2025
2026         def do_movieoff_menu(self):
2027                 current_movie_eof = config.usage.on_movie_eof.value
2028                 menu = []
2029                 for x in config.usage.on_movie_eof.choices:
2030                         config.usage.on_movie_eof.value = x
2031                         menu.append((config.usage.on_movie_eof.getText(), x))
2032                 config.usage.on_movie_eof.value = current_movie_eof
2033                 used = config.usage.on_movie_eof.getIndex()
2034                 self.session.openWithCallback(self.movieoffMenuCallback, ChoiceBox, title = _("On end of movie"), list = menu, selection = used)
2035
2036         def movieoffMenuCallback(self, choice):
2037                 if choice is None:
2038                         return
2039                 self.settings["movieoff"] = choice[1]
2040                 self.saveLocalSettings()
2041                 self.displayMovieOffStatus()
2042
2043         def createPlaylist(self):
2044                 global playlist
2045                 items = playlist
2046                 del items[:]
2047                 for index, item in enumerate(self["list"]):
2048                         if item:
2049                                 item = item[0]
2050                                 path = item.getPath()
2051                                 if not item.flags & eServiceReference.mustDescent:
2052                                         ext = os.path.splitext(path)[1].lower()
2053                                         if ext in IMAGE_EXTENSIONS:
2054                                                 continue
2055                                         else:
2056                                                 items.append(item)
2057
2058 playlist = []