serienfilm: don't crash on dead symlink in folder
[enigma2-plugins.git] / serienfilm / src / MovieList.py
1 # -*- coding: utf-8 -*-
2
3 from Components.GUIComponent import GUIComponent
4 from Tools.FuzzyDate import FuzzyTime
5 from ServiceReference import ServiceReference
6 from Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest
7 from Components.config import config
8 from Tools.Directories import fileExists
9 from Tools.LoadPixmap import LoadPixmap
10 from Components.UsageConfig import preferredPath, defaultMoviePath
11 from enigma import eEnv
12 from glob import glob
13 import copy
14 import os.path
15
16 from enigma import eListboxPythonMultiContent, eListbox, gFont, iServiceInformation, \
17         RT_HALIGN_LEFT, RT_HALIGN_RIGHT, eServiceReference, eServiceCenter
18
19 class MovieList(GUIComponent):
20         SORT_ALPHANUMERIC = 1
21         SORT_RECORDED = 2
22
23         LISTTYPE_ORIGINAL = 0x10
24         LISTTYPE_COMPACT_TAGS = 0x20
25         LISTTYPE_COMPACT_SERVICE = 0x40
26         LISTTYPE_MINIMAL = 0x80
27         LISTTYPE_COMPACT = LISTTYPE_COMPACT_TAGS | LISTTYPE_COMPACT_SERVICE
28
29         HIDE_DESCRIPTION = 1
30         SHOW_DESCRIPTION = 2
31
32         SHOW_NO_TIMES = 0
33         SHOW_RECORDINGTIME = 1
34         SHOW_DURATION = 2
35         SHOW_DIRECTORIES = 4
36
37         # tinfo types:
38         REAL_DIR = 1
39         REAL_UP = 2
40         VIRT_DIR = 4
41         VIRT_UP = 8
42         VIRT_ENTRY = (VIRT_DIR | VIRT_UP)
43
44         MAXTIME = 0x7fffffff
45
46         gsflists = []
47
48
49         def __init__(self, root, list_type=None, sort_type=None, show_times=None, sftitle_episode_separator = None, MovieSelectionSelf = None):
50                 GUIComponent.__init__(self)
51 #               print "[SF-Plugin] class SF:MovieList init, lstt=%x, srt=%x, sht=%s, sft=>%s<, root=%s" % ( list_type, sort_type, show_times, str(sftitle_episode_separator), str(root))
52                 self.list_type = list_type or self.LISTTYPE_MINIMAL
53                 self.show_times = show_times or self.SHOW_DURATION | self.SHOW_DIRECTORIES
54                 self.sort_type = sort_type or self.SORT_RECORDED
55                 self.sftitle_episode_separator = sftitle_episode_separator
56
57                 self.l = eListboxPythonMultiContent()
58
59                 self.tags = set()
60                 self.list = None
61                 self.sflists = None
62                 self.MovieSelectionSelf = MovieSelectionSelf
63                 self.MselTitle = ""
64
65                 if root is not None:
66                         self.reload(root)
67
68                 self.pdirIcon = LoadPixmap(cached=True, path=eEnv.resolve('${libdir}/enigma2/python/Plugins/Extensions/SerienFilm/icons/folder_20.png'))
69                 self.rdirIcon = LoadPixmap(cached=True, path=eEnv.resolve('${libdir}/enigma2/python/Plugins/Extensions/SerienFilm/icons/folder_red.png'))
70                 self.fupIcon = LoadPixmap(cached=True, path=eEnv.resolve('${libdir}/enigma2/python/Plugins/Extensions/SerienFilm/icons/folderup_20.png'))
71                 self.pdirMap = MultiContentEntryPixmapAlphaTest(pos=(0,0), size=(20,20), png=self.pdirIcon)
72                 self.rdirMap = MultiContentEntryPixmapAlphaTest(pos=(0,0), size=(20,20), png=self.rdirIcon)
73                 self.fupMap = MultiContentEntryPixmapAlphaTest(pos=(0,0), size=(20,20), png=self.fupIcon)
74
75                 self.redrawList()
76                 self.l.setBuildFunc(self.buildMovieListEntry)
77
78                 self.onSelectionChanged = [ ]
79
80         def connectSelChanged(self, fnc):
81                 if not fnc in self.onSelectionChanged:
82                         self.onSelectionChanged.append(fnc)
83
84         def disconnectSelChanged(self, fnc):
85                 if fnc in self.onSelectionChanged:
86                         self.onSelectionChanged.remove(fnc)
87
88         def selectionChanged(self):
89                 for x in self.onSelectionChanged:
90 #                       print "[SF-Plugin] MovieList.selectionChanged: " + str(x)
91                         x()
92
93         def setListType(self, type):
94                 self.list_type = type
95                 self.redrawList()
96                 self.l.setList(self.list)                               # redraw
97
98         def setShowTimes(self, val):
99                 self.show_times = val
100
101         def setSortType(self, type):
102                 self.sort_type = type
103
104         def setTitleEpiSep(self, sftitle_episode_separator):
105                 self.sftitle_episode_separator = sftitle_episode_separator
106
107
108         def redrawList(self):
109                 if self.list_type & MovieList.LISTTYPE_ORIGINAL:
110                         self.l.setFont(0, gFont("Regular", 22))
111                         self.l.setFont(1, gFont("Regular", 18))
112                         self.l.setFont(2, gFont("Regular", 16))
113                         self.l.setItemHeight(75)
114                         if self.sflists and self.sflists[0] != self.list:
115                                 self.l.setItemHeight(41)
116                 elif self.list_type & MovieList.LISTTYPE_COMPACT:
117                         self.l.setFont(0, gFont("Regular", 20))
118                         self.l.setFont(1, gFont("Regular", 16))
119                         self.l.setItemHeight(39)
120 #                       self.l.setFont(1, gFont("Regular", 14))
121 #                       self.l.setItemHeight(37)
122                 else:
123                         self.l.setFont(0, gFont("Regular", 20)) # MINIMAL
124                         self.l.setFont(1, gFont("Regular", 16))
125                         self.l.setItemHeight(25)
126
127         #
128         # | name of movie              |
129         #
130         def buildMovieListEntry(self, serviceref, info, begin, tinfo):
131 #               print "[SF-Plugin] SF:MovieList.buildMovieListEntry, lst_type=%x, show_tims=%x" % (self.list_type, self.show_times)
132
133                 width = self.l.getItemSize().width()
134                 len = tinfo[5]                  #tinfo = [type, pixmap, txt, description, service, len]
135
136                 if len <= 0: #recalc len when not already done
137                         cur_idx = self.l.getCurrentSelectionIndex()
138                         x = self.list[cur_idx]
139                         if config.usage.load_length_of_movies_in_moviellist.value:
140                                 len = x[1].getLength(x[0]) #recalc the movie length...
141                         else:
142                                 len = 0         #dont recalc movielist to speedup loading the list
143                         self.list[cur_idx][3][5] = len  #update entry in list... so next time we don't need to recalc
144
145                 if len > 0:
146                         len = "%d:%02d" % (len / 60, len % 60)
147                 else:
148                         len = ""
149
150                 res = [ None ]
151                 begin_string = ""
152                 date_string = ""
153
154                 pixmap = tinfo[1]
155                 typ = tinfo[0]
156                 service = None
157                 if typ & (self.VIRT_UP | self.REAL_UP):
158                         txt = tinfo[3]  # [2] == " " for alpha-sort to top
159                 else:
160                         txt = tinfo[2]
161                         if begin > 0:
162                                 t = FuzzyTime(begin)
163                                 begin_string = t[0] + ", " + t[1]
164                                 date_string = t[0]
165                 if not typ & (self.REAL_DIR | self.VIRT_ENTRY):
166                         service = tinfo[4]
167                 description = tinfo[3]
168                 tags = self.tags and info.getInfoString(serviceref, iServiceInformation.sTags)
169
170                 if isinstance(pixmap, str):
171                         pixmap = MultiContentEntryText(pos=(0, 0), size=(25, 20), font = 0, flags = RT_HALIGN_LEFT, text = pixmap)
172                 if pixmap is not None:
173                         res.append(pixmap)
174
175                 XPOS = 25
176
177                 if self.list_type & MovieList.LISTTYPE_ORIGINAL:
178                         res.append(MultiContentEntryText(pos=(XPOS, 0), size=(width, 30), font = 0, flags = RT_HALIGN_LEFT, text=txt))
179                         line2 = 20
180                         if self.list == self.sflists[0]:
181                                 line2 = 50
182                                 if not typ & (self.REAL_DIR | self.VIRT_ENTRY):
183                                         res.append(MultiContentEntryText(pos=(XPOS, 30), size=(width, 20), font=1, flags=RT_HALIGN_LEFT, text=description))
184                         res.append(MultiContentEntryText(pos=(XPOS, line2), size=(150, 20), font=1, flags=RT_HALIGN_LEFT, text=begin_string))
185                         if service:
186                                 res.append(MultiContentEntryText(pos=(XPOS+150, line2), size=(180, 20), font = 2, flags = RT_HALIGN_RIGHT, text = service))
187                         if tags:
188                                 res.append(MultiContentEntryText(pos=(width - 250, line2), size=(180, 20), font = 2, flags = RT_HALIGN_RIGHT, text = tags))
189                         if not typ & (self.REAL_DIR | self.VIRT_ENTRY):
190                                 res.append(MultiContentEntryText(pos=(width-60, line2), size=(60, 20), font=2, flags=RT_HALIGN_RIGHT, text=len))
191                         return res
192
193                 tslen = 80
194                 if self.show_times & self.SHOW_RECORDINGTIME:
195                         tslen += 50
196                         date_string = begin_string
197                 dusz = 0
198                 if self.show_times & self.SHOW_DURATION and not tinfo[0] & (self.VIRT_ENTRY | self.REAL_UP):
199                         dusz = 57
200
201                 if self.list_type  & MovieList.LISTTYPE_COMPACT:
202                         res.append(MultiContentEntryText(pos=(XPOS, 4), size=(tslen-5, 20), font=1, flags=RT_HALIGN_RIGHT, text=date_string))
203                         res.append(MultiContentEntryText(pos=(XPOS + tslen, 0), size=(width-XPOS-tslen, 20), font = 0, flags = RT_HALIGN_LEFT, text = txt))
204                         other = None
205                         if self.list_type & MovieList.LISTTYPE_COMPACT_TAGS:
206                                 if tags:
207                                         res.append(MultiContentEntryText(pos=(width-dusz-185, 20), size=(180, 17), font=1, flags=RT_HALIGN_RIGHT, text=tags))
208                                 otherend = dusz+185
209                                 other = service
210                         else:
211                                 if service:
212                                         res.append(MultiContentEntryText(pos=(width-dusz-155, 20), size=(153, 17), font=1, flags=RT_HALIGN_RIGHT, text=service))
213                                 otherend = dusz+160
214                                 other = tags
215                         if self.list == self.sflists[0]:
216                                 if not typ & (self.REAL_DIR | self.VIRT_ENTRY):
217                                         res.append(MultiContentEntryText(pos=(XPOS, 20), size=(width-(XPOS+otherend), 17), font=1, flags=RT_HALIGN_LEFT, text=description))
218                                 elif other:
219                                         res.append(MultiContentEntryText(pos=(XPOS, 20), size=(width-(XPOS+otherend), 17), font=1, flags=RT_HALIGN_LEFT, text=other))
220                         if dusz:
221                                 res.append(MultiContentEntryText(pos=(width-dusz, 20), size=(dusz-2, 20), font=1, flags=RT_HALIGN_RIGHT, text=len))
222                 else:
223 #                       assert(self.list_type == MovieList.LISTTYPE_MINIMAL)
224                         res.append(MultiContentEntryText(pos=(XPOS, 3), size=(tslen-5, 20), font=1, flags=RT_HALIGN_RIGHT, text=date_string))
225                         res.append(MultiContentEntryText(pos=(XPOS + tslen, 0), size=(width-XPOS-tslen-dusz, 20), font = 0, flags = RT_HALIGN_LEFT, text = txt))
226                         if dusz:
227                                 res.append(MultiContentEntryText(pos=(width-dusz, 3), size=(dusz, 20), font=1, flags=RT_HALIGN_RIGHT, text=len))
228
229                 return res
230
231         def moveToIndex(self, index):
232                 if index <0:
233                         index += len(self.list)                 # standard python list behaviour
234                 self.instance.moveSelectionTo(index)
235
236         def getCurrentIndex(self):
237                 return self.instance.getCurrentIndex()
238
239         def getCurrentEvent(self):
240                 l = self.l.getCurrentSelection()
241                 return l and l[0] and l[1] and l[1].getEvent(l[0])
242
243         def getCurrent(self):
244                 l = self.l.getCurrentSelection()
245                 return l and l[0]
246
247         GUI_WIDGET = eListbox
248
249         def postWidgetCreate(self, instance):
250                 instance.setContent(self.l)
251                 self.selectionChanged_conn = instance.selectionChanged.connect(self.selectionChanged)
252
253         def preWidgetRemove(self, instance):
254                 instance.setContent(None)
255                 self.selectionChanged_conn = None
256
257         def reload(self, root = None, filter_tags = None):
258                 if root is not None:
259                         self.load(root, filter_tags)
260                 else:
261                         self.load(self.root, filter_tags)
262                 self.l.setList(self.list)
263
264         def removeService(self, service):
265                 for l in self.list[:]:
266                         repnr = tinfo = None
267                         if l[0] == service:
268                                 tinfo = l[3]
269                                 if not service.flags & eServiceReference.canDescent and isinstance(tinfo[1], str) and tinfo[1][0] == "#":
270                                         repnr = int(tinfo[1][1:])
271                                 self.list.remove(l)
272                                 break
273                 self.l.setList(self.list)
274                 if len(self.list) == 1 and self.list[0][3][0] & self.VIRT_UP:   # last movie of a series is gone
275                         service = self.list[0][0]
276                         self.moveTo(service, True)
277                         assert service.flags == eServiceReference.canDescent
278                         self.removeService(service)
279                         return
280                 if repnr is None:
281                         return
282                 repeats = 0             # update repeatcount "#x" of surviving movies
283                 ele0 = 0
284 #               print "[SF-Plugin] removeService: searching " + tinfo[2]
285                 for i in range(1, len(self.list)):
286                         m = self.list[i]
287                         t = m[3]
288 #                       print "[SF-Plugin] removeService try: %x, %s -- %s" % (m[0].flags,  str(t[1]), str(t[2]))
289                         if not m[0].flags & eServiceReference.canDescent and t[2] == tinfo[2] and isinstance(t[1], str) and t[1][0] == "#":
290                                 repeats += 1
291                                 rc = int(t[1][1:])
292                                 if rc > repnr:
293                                         rc -= 1
294                                         t[1] = "#" + str(rc)
295 #                                       print "[SF-Plugin] removeService: %s --> %s" % (t[2], t[1])
296                                 if rc == 0:
297                                         ele0 = i
298                 if ele0 > 0 and repeats == 1:
299                         self.list[ele0][3][1] = None    # remove "#0" from only lonely surviving movie
300 #                       print "[SF-Plugin] removeService: remove #0 from " + self.list[ele0][3][2]
301
302
303         def __len__(self):
304                 return len(self.list)
305
306
307         def playDirectory(self, serviceref):
308                 if serviceref.type == (eServiceReference.idUser | eServiceReference.idDVB) and serviceref.flags == eServiceReference.canDescent:
309                         self.moveTo(serviceref)         # virtual Directory
310                         return ""
311                 if serviceref.flags & eServiceReference.mustDescent:
312                         info = self.serviceHandler.info(serviceref)
313                         if info is None:
314                                 name = ""
315                         else:
316                                 name = info.getName(serviceref)
317 #                       print "[SF-Plugin] MovieList.playDirectory: %s nicht spielbar" ,(name)
318                         return name
319
320         def realDirUp(self, root):
321                 parent = None
322                 info = self.serviceHandler.info(root)
323                 pwd = info and info.getName(root)
324                 defM = defaultMoviePath()
325                 print "[SF-Plugin] MovieList.realDirUp: pwd = >%s<" % (str(pwd))
326                 if pwd and os.path.exists(pwd) and os.path.exists(defM) and not os.path.samefile(pwd, defM):
327                         parentdir = pwd[:pwd.rfind("/", 0, -1)] + "/"
328                         parent = eServiceReference("2:0:1:0:0:0:0:0:0:0:" + parentdir)
329                         info = self.serviceHandler.info(parent)
330                         if info is not None:
331                                 txt = info.getName(parent)                                                                                                                              # Titel
332                                 service = ServiceReference(info.getInfoString(parent, iServiceInformation.sServiceref)).getServiceName()        # Sender
333                                 description = info.getInfoString(parent, iServiceInformation.sDescription)                              # Beschreibung
334 #                               begin = info.getInfo(root, iServiceInformation.sTimeCreate)
335                                 begin = self.MAXTIME
336                                 parent.flags = eServiceReference.flagDirectory | eServiceReference.sort1
337                                 tinfo = [self.REAL_DIR | self.REAL_UP, self.fupMap, "  0", txt, service, 1]     # "  0" sorts before VIRT_UP
338                                 return ((parent, info, begin, tinfo))
339
340
341
342         def load(self, root, filter_tags):
343                 # this lists our root service, then building a 
344                 # nice list
345
346                 self.serviceHandler = eServiceCenter.getInstance()
347                 parentLstEntry = self.realDirUp(root)
348
349                 self.rootlst = [ ]
350
351                 self.root = root
352                 list = self.serviceHandler.list(root)
353                 if list is None:
354                         print "[SF-Plugin] listing of movies failed"
355                         list = [ ]      
356                         return
357                 tags = set()
358
359                 rootinfo = self.serviceHandler.info(root)
360                 pwd = rootinfo and rootinfo.getName(root)
361
362                 while 1:
363                         serviceref = list.getNext()
364                         if not serviceref.valid():
365                                 break
366                         pixmap = None
367                         type = 0
368                         if serviceref.flags & eServiceReference.mustDescent:
369                                 if not self.show_times & self.SHOW_DIRECTORIES:
370                                         continue                                # hide Directories
371                                 type = self.REAL_DIR            # show Directories
372                                 pixmap = self.rdirMap
373
374                         info = self.serviceHandler.info(serviceref)
375                         if info is None:
376                                 continue
377                         txt = info.getName(serviceref)
378                         if serviceref.flags & eServiceReference.mustDescent:
379                                 files = [ f for f in glob(os.path.join(txt, "*")) if fileExists(f) ]
380                                 # skip empty directories
381                                 if not files:
382                                         continue
383                                 # use mtime of latest recording
384                                 begin = sorted((os.path.getmtime(x) for x in files))[-1]
385                                 if pwd and txt.startswith(pwd):
386                                         txt = txt[len(pwd):]
387                                 if txt.endswith(os.path.sep):
388                                         txt = txt[:-len(os.path.sep)]
389                         else:
390                                 begin = info.getInfo(serviceref, iServiceInformation.sTimeCreate)
391                         this_tags = info.getInfoString(serviceref, iServiceInformation.sTags).split(' ')
392
393                         # convert space-seperated list of tags into a set
394                         if this_tags == ['']:
395                                 this_tags = []
396                         this_tags = set(this_tags)
397                         tags |= this_tags
398
399                         # filter_tags is either None (which means no filter at all), or 
400                         # a set. In this case, all elements of filter_tags must be present,
401                         # otherwise the entry will be dropped.                  
402                         if filter_tags is not None and not this_tags.issuperset(filter_tags):
403                                 continue
404
405                         service = ServiceReference(info.getInfoString(serviceref, iServiceInformation.sServiceref)).getServiceName()    # Sender
406                         description = info.getInfoString(serviceref, iServiceInformation.sDescription)
407                         tinfo = [type, pixmap, txt, description, service, -1]
408
409                         self.rootlst.append((serviceref, info, begin, tinfo))
410
411                 self.rootlst.sort(key=lambda x: -x[2])                                          # movies of same name stay sortet by time
412                 self.rootlst.sort(key=lambda x: (x[3][2]+x[3][3]).lower())
413                 self.list = self.rootlst
414                 self.createSublists()
415
416
417                 if self.sort_type == self.SORT_RECORDED:
418                         self.sortLists()
419
420                 # finally, store a list of all tags which were found. these can be presented
421                 # to the user to filter the list
422                 self.tags = tags
423                 if parentLstEntry:
424 #                       print "[SF-Plugin] SF:MovieList.load: parentLstEntry %s" % (self.debPrtEref(parentLstEntry[0]))
425                         self.list.insert(0, parentLstEntry)
426
427         def moveTo(self, serviceref, descend_virtdirs=True, search_all_lists=True):
428                 count = 0
429                 for x in self.list:
430                         if x[0] == serviceref:
431
432                                 if descend_virtdirs:
433                                         l = self.list[count]
434                                         tinfo = l[3]
435                                         if tinfo[0] & self.VIRT_ENTRY:
436                                                 assert tinfo[4][:6] == "SFLIDX"
437                                                 self.list = self.sflists[int(tinfo[4][6:])]
438                                                 self.l.setList(self.list)
439                                                 self.MovieSelectionSelf.setTitle(self.MselTitle)
440                                                 self.redrawList()
441                                         if tinfo[0] & self.VIRT_DIR:
442                                                 count = 0                                                       # select VIRT_UP in sublist
443                                                 self.MovieSelectionSelf.setTitle("%s: %s" % (_("Series"), tinfo[2]))
444                                         elif tinfo[0] & self.VIRT_UP:
445                                                 rv = self.moveTo(serviceref, False)
446                                                 return rv
447
448                                 self.instance.moveSelectionTo(count)
449                                 return True
450                         count += 1
451         # InfoBar:leavePlayerConfirmed(movielist) should find movies in virtual directories
452                 if search_all_lists and descend_virtdirs and self.sflists:
453                         savelist = self.list
454                         for l in self.sflists:
455                                 if l == savelist:
456                                         continue
457                                 self.list = l
458                                 self.l.setList(l)
459                                 if self.moveTo(serviceref, descend_virtdirs=True, search_all_lists=False):
460                                         return True
461                         self.list = savelist
462                         self.l.setList(self.list)
463
464 # enigmas list:         (serviceref, info, begin, len)  # len is replaced by tinfo
465 # tinfo:                        [type, pixmap, txt, description, service, len]
466
467 # pixmap:                       pixmap (DIR_UP...) or String (#0, #1 ... for multiple recordings)
468 # SFLIDX0...999         entry# in serlst, 0 == rootlist
469
470
471         def serflm(self, film, episode):
472                 fdate = film[2]
473                 tinfo = film[3]
474                 dsc = tinfo[3]
475                 service = tinfo[4]
476                 epi = len(episode) == 2 and episode[1]
477                 if epi:
478                         txt = ": ".join((epi, dsc))
479                 else:
480                         txt = dsc or service
481                 if self.serdate < fdate:
482                         self.serdate = fdate
483                 tinfo[2] = txt
484                 tinfo[3] = dsc
485                 return film
486
487         def update_repcnt(self, serlst, repcnt):
488                 for i in range(repcnt + 1):
489                         serlst[-( i+1 )][3][1] =  "#" + str(i)
490
491         def createSublists(self):
492                 self.serdate = 0
493                 serie = serlst = None
494                 self.sflists = [self.rootlst]
495                 txt = ("", "")
496                 rootlidx = repcnt = 0
497                 global gsflists
498                 sflidx = 0
499                 if self.sftitle_episode_separator:
500                         splitTitle = lambda s: s.split(self.sftitle_episode_separator, 1)
501                 else:
502                         splitTitle = lambda s: [s]
503 #               print "[SF-Plugin] MovieList.createSublists: self.sftitle_episode_separator = %d = >%s<" % (len(self.sftitle_episode_separator), self.sftitle_episode_separator)
504                 for tinfo in self.rootlst[:]:
505 #                       ts = tinfo[3][2].split(": ", 1)
506                         ts = splitTitle(tinfo[3][2])
507                         if txt[0] == ts[0]:
508                                 if txt[0] != serie:                             # neue Serie
509                                         sflidx += 1
510                                         serie = txt[0]
511                                         ser_serviceref = eServiceReference(eServiceReference.idUser | eServiceReference.idDVB, 
512                                                         eServiceReference.canDescent, "SFLIDX" + str(sflidx))
513                                         ser_info = self.serviceHandler.info(ser_serviceref)
514                                         # VIRT_UP should sort first, but after REAL_UP: MAXTIME-1 resp. "  1"
515                                         serlst = [(ser_serviceref, ser_info, MovieList.MAXTIME-1,
516                                                 [self.VIRT_UP, self.fupMap, "  1", txt[0], "SFLIDX0", 1])]
517                                         self.sflists.append(serlst)
518                                         serlst.append(self.serflm(self.rootlst[rootlidx-1], txt))
519                                         parent_list_index = rootlidx-1
520                                 film = self.rootlst.pop(rootlidx)
521                                 rootlidx -= 1
522                                 film = self.serflm(film, ts)
523                                 samefilm = False
524                                 if serlst:
525                                         if serlst and film[3][3] != "" and film[3][2] == serlst[-1][3][2]:              # perhaps same Movie?
526                                                 event1 = film[1].getEvent(film[0])
527                                                 event2 = serlst[-1][1].getEvent(serlst[-1][0])
528                                                 if event1 and event2 and event1.getExtendedDescription() == event2.getExtendedDescription():
529                                                         samefilm = True
530                                         if samefilm:
531                                                 repcnt += 1
532                                         elif repcnt:
533                                                 self.update_repcnt(serlst, repcnt)
534                                                 repcnt = 0
535                                         serlst.append(film)
536                         elif serlst:
537                                 self.rootlst[parent_list_index] = (ser_serviceref, ser_info, self.serdate, 
538                                         [self.VIRT_DIR, self.pdirMap, txt[0], "", "SFLIDX" + str(sflidx), 1])
539                                 self.serdate = 0
540                                 if repcnt:
541                                         self.update_repcnt(serlst, repcnt)
542                                         repcnt = 0
543                                 serlst = None
544                         rootlidx += 1
545                         txt = ts
546                 if serlst:
547                         self.rootlst[parent_list_index] = (ser_serviceref, ser_info, self.serdate, 
548                                 [self.VIRT_DIR, self.pdirMap, txt[0], "", "SFLIDX" + str(sflidx), None, 1])
549                         if repcnt:
550                                 self.update_repcnt(serlst, repcnt)
551 #               print "[SF-Plugin] sflist has %d entries" % (len(self.sflists))
552                 gsflists = self.sflists
553
554
555
556
557         def sortLists(self):
558                 if self.sort_type == self.SORT_ALPHANUMERIC:
559                         key = lambda x: (x[3][2]+x[3][3]).lower()
560                 else: key=lambda x: -x[2]
561                 if self.sflists:
562                         for list in self.sflists:
563                                 list.sort(key=key)
564                         return True
565
566         def toggleSort(self):
567                 save_list = self.list
568                 current = self.getCurrent()
569                 self.sort_type ^= (self.SORT_ALPHANUMERIC | self.SORT_RECORDED)
570                 self.sortLists()
571                 self.list = save_list
572                 self.l.setList(self.list)                               # redraw
573                 self.moveTo(current, False)
574
575 #       def toggleTags(self, toggle):
576 #               if toggle and self.list_type & (MovieList.LISTTYPE_COMPACT | MovieList.LISTTYPE_MINIMAL):
577 #                       self.showtags ^= MovieList.LISTTYPE_COMPACT
578 #               else:
579 #                       self.showtags = 0
580 #               self.redrawList()
581 #               self.l.setList(self.list)                               # redraw
582
583         def saveTitle(self, title):
584                 self.MselTitle = title
585
586         def getVirtDirList(self, name):
587                 return name[:6] == "SFLIDX" and self.sflists[int(name[6:])]
588
589         @staticmethod
590         def getVirtDirStatistics(name):
591                 if name[:6] == "SFLIDX":
592                         list = gsflists[int(name[6:])]
593                         repcnt = 0
594                         for l in list:
595                                 if isinstance(l[3][1], str) and l[3][1][0] == "#" and l[3][1] != "#0":
596                                         repcnt += 1
597                         s = "%d %s" % (len(list)-1, _("Movies"))
598                         if repcnt:
599                                 s += ", %d %s" % (repcnt, _("duplicated"))
600                         return s
601
602