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