Don't crash before tags-patch is in place
[enigma2-plugins.git] / tageditor / src / plugin.py
1 from Plugins.Plugin import PluginDescriptor
2 from Screens.Screen import Screen
3 from Screens.InputBox import InputBox
4 from Screens.ChoiceBox import ChoiceBox
5 from Screens.MessageBox import MessageBox
6 from Components.config import config
7 from Components.ActionMap import NumberActionMap
8 from Components.Button import Button
9 from Components.SelectionList import SelectionList
10 from enigma import eServiceReference, eServiceCenter, iServiceInformation
11 import os
12
13 def main(session, service, **kwargs):
14         session.open(MovieTagEditor, service, session.current_dialog, **kwargs)
15
16 def Plugins(**kwargs):
17         try:
18                 from Screens.MovieSelection import setPreferredTagEditor
19                 setPreferredTagEditor(TagEditor)
20         except:
21                 pass
22         return PluginDescriptor(name="TagEditor", description=_("Edit tags..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)
23
24
25 class TagEditor(Screen):
26         skin = """
27         <screen name="TagEditor" title="Edit Tags" position="75,155" size="565,280">
28                 <widget name="list" position="5,5" size="555,225" scrollbarMode="showOnDemand" />
29                 <ePixmap position="0,235" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
30                 <ePixmap position="140,235" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
31                 <ePixmap position="280,235" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
32                 <ePixmap position="420,235" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
33                 <widget name="key_red" position="0,235" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
34                 <widget name="key_green" position="140,235" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
35                 <widget name="key_yellow" position="280,235" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
36                 <widget name="key_blue" position="420,235" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
37         </screen>"""
38
39         def __init__(self, session, tags, txt = None, args = 0):
40                 Screen.__init__(self, session, args)
41
42                 # Initialize Buttons
43                 self["key_red"] = Button(_("Cancel"))
44                 self["key_green"] = Button(_("OK"))
45                 self["key_yellow"] = Button(_("New"))
46                 self["key_blue"] = Button(_("Load"))
47
48                 self.tags = self.loadTagsFile()
49                 self.origtags = self.tags + []
50                 self.ghosttags = self.tags + []
51                 self.joinTags(self.tags, tags)
52                 
53                 self["list"] = SelectionList()
54                 self.ghostlist = tags + []
55                 self.updateMenuList(self.tags, tags)
56
57                 # Define Actions
58                 self["actions"] = NumberActionMap(["OkCancelActions", "ColorActions", "MenuActions"],
59                 {
60                         "ok": self["list"].toggleSelection,
61                         "cancel": self.cancel,
62                         "red": self.cancel,
63                         "green": self.accept,
64                         "yellow": self.addCustom,
65                         "blue": self.loadFromHdd,
66                         "menu": self.showMenu
67                 }, -1)
68
69         def addCustom(self):
70                 self.session.openWithCallback(
71                         self.addCustomCallback,
72                         InputBox,
73                         title = _("Please enter the new tag")
74                 )
75
76         def addCustomCallback(self, ret):
77                 ret = ret and ret.strip().replace(" ","_").capitalize()
78                 if ret and ret not in self.tags:
79                         self.tags.append(ret)
80                         self.updateMenuList(self.tags, [ret])
81
82         def loadTagsFile(self):
83                 try:
84                         file = open("/etc/enigma2/movietags")
85                         tags = [x.rstrip() for x in file.readlines()]
86                         while "" in tags:
87                                 tags.remove("")
88                         file.close()
89                 except IOError, ioe:
90                         tags = []
91                 return tags
92
93         def saveTagsFile(self, tags):
94                 try:
95                         file = open("/etc/enigma2/movietags", "w")
96                         file.write("\n".join(tags)+"\n")
97                         file.close()
98                 except IOError, ioe:
99                         pass
100
101         def joinTags(self, taglist, newtags):
102                 for tag in newtags:
103                         if not tag in taglist:
104                                 taglist.append(tag)
105
106         def setTimerTags(self, timer, tags):
107                 if hasattr(timer, "tags") and timer.tags != tags:
108                         timer.tags = tags
109                         self.timerdirty = True
110
111         def setMovieTags(self, ref, tags):
112                 file = ref.getPath()
113                 if file.endswith(".ts") is True:
114                         file = file + ".meta"
115                 else:
116                         file = file + ".ts.meta"
117                 if os.path.exists(file):
118                         metafile = open(file, "r")
119                         sid = metafile.readline()
120                         title = metafile.readline()
121                         descr = metafile.readline()
122                         time = metafile.readline()
123                         oldtags = metafile.readline().rstrip()
124                         metafile.close()
125                         tags = " ".join(tags)
126                         if tags != oldtags:
127                                 metafile = open(file, "w")
128                                 metafile.write("%s%s%s%s%s" %(sid, title, descr, time, tags))
129                                 metafile.close()
130
131         def foreachTimerTags(self, func):
132                 self.timerdirty = False
133                 for timer in self.session.nav.RecordTimer.timer_list + self.session.nav.RecordTimer.processed_timers:
134                         if hasattr(timer, "tags") and timer.tags:
135                                 func(timer, timer.tags+[])
136                 if self.timerdirty:
137                         self.session.nav.RecordTimer.saveTimer()
138
139         def foreachMovieTags(self, func):
140                 serviceHandler = eServiceCenter.getInstance()
141                 for dir in config.movielist.videodirs.value:
142                         if os.path.isdir(dir):
143                                 root = eServiceReference("2:0:1:0:0:0:0:0:0:0:" + dir)
144                                 list = serviceHandler.list(root)
145                                 if list is None:
146                                         continue
147                                 while 1:
148                                         serviceref = list.getNext()
149                                         if not serviceref.valid():
150                                                 break
151                                         if (serviceref.flags & eServiceReference.mustDescent):
152                                                 continue
153                                         info = serviceHandler.info(serviceref)
154                                         if info is None:
155                                                 continue
156                                         tags = info.getInfoString(serviceref, iServiceInformation.sTags).split(' ')
157                                         if tags == ['']:
158                                                 tags = []
159                                         if tags:
160                                                 func(serviceref, tags)
161
162         def updateMenuList(self, tags, extrasel = []):
163                 seltags = [x[1] for x in self["list"].getSelectionsList()] + extrasel
164                 tags.sort()
165                 self["list"].setList([])
166                 for tag in tags:
167                         self["list"].addSelection(tag, tag, 0, tag in seltags)
168                 
169         def loadFromHdd(self):
170                 tags = self.tags + []
171                 self.foreachTimerTags(lambda t, tg: self.joinTags(tags, tg))
172                 self.foreachMovieTags(lambda r, tg: self.joinTags(tags, tg))
173                 self.updateMenuList(tags)
174                 self.tags = tags
175
176         def removeUnused(self):
177                 tags = [x[1] for x in self["list"].getSelectionsList()]
178                 self.foreachTimerTags(lambda t, tg: self.joinTags(tags, tg))
179                 self.foreachMovieTags(lambda r, tg: self.joinTags(tags, tg))
180                 self.updateMenuList(tags)
181                 self.tags = tags
182
183         def listReplace(self, lst, fr, to=None):
184                 if fr in lst:
185                         lst.remove(fr)
186                         if to != None and not to in lst:
187                                 lst.append(to)
188                                 lst.sort()
189                 return lst
190
191         def renameTag(self):
192                 self.thistag = self["list"].list[self["list"].getSelectedIndex()][0]
193                 self.session.openWithCallback(
194                         self.renameTagCallback,
195                         InputBox,
196                         title = _("Replace tag \"%s\" everywhere with:   (Note that 'Cancel' will not undo this!)") % (self.thistag[1]),
197                         text = self.thistag[1]
198                 )
199
200         def renameTagCallback(self, res):
201                 res = res and res.strip().replace(" ", "_").capitalize()
202                 if res and len(res) and res != self.thistag[1]:
203                         thistag = self.thistag[1]
204                         self.foreachTimerTags(lambda t, tg: (thistag in tg) and self.setTimerTags(t, self.listReplace(tg, thistag, res)))
205                         self.foreachMovieTags(lambda r, tg: (thistag in tg) and self.setMovieTags(r, self.listReplace(tg, thistag, res)))
206                         self.listReplace(self.tags, thistag, res)
207                         self.listReplace(self.ghosttags, thistag, res)
208                         self.listReplace(self.ghostlist, thistag, res)
209                         self.updateMenuList(self.tags, self.thistag[3] and [res] or [])
210
211         def removeTag(self):
212                 self.thistag = self["list"].list[self["list"].getSelectedIndex()][0]
213                 self.session.openWithCallback(
214                         self.removeTagCallback,
215                         MessageBox,
216                         _("Do you really want to delete tag \"%s\" everywhere?\n(Note that 'Cancel' will not undo this!)") % (self.thistag[1])
217                 )
218
219         def removeTagCallback(self, res):
220                 if res:
221                         thistag = self.thistag[1]
222                         self.foreachTimerTags(lambda t, tg: (thistag in tg) and self.setTimerTags(t, self.listReplace(tg, thistag)))
223                         self.foreachMovieTags(lambda r, tg: (thistag in tg) and self.setMovieTags(r, self.listReplace(tg, thistag)))
224                         self.listReplace(self.tags, thistag)
225                         self.listReplace(self.ghosttags, thistag)
226                         self.listReplace(self.ghostlist, thistag)
227                         self.updateMenuList(self.tags)
228
229         def removeAll(self):
230                 self.session.openWithCallback(
231                         self.removeAllCallback,
232                         MessageBox,
233                         _("Do you really want to delete all tags everywhere?\n(Note that 'Cancel' will not undo this!)")
234                 )
235
236         def removeAllCallback(self, res):
237                 if res:
238                         self.foreachTimerTags(lambda t, tg: tg and self.setTimerTags(t, []))
239                         self.foreachMovieTags(lambda r, tg: tg and self.setMovieTags(r, []))
240                         self.tags = []
241                         self.ghosttags = []
242                         self.ghostlist = []
243                         self.updateMenuList(self.tags)
244
245         def showMenu(self):
246                 menu = []
247                 menu.append((_("Add new tag"), self.addCustom))
248                 menu.append((_("Rename this tag"), self.renameTag))
249                 menu.append((_("Delete this tag"), self.removeTag))
250                 menu.append((_("Delete unused tags"), self.removeUnused))
251                 menu.append((_("Delete all tags"), self.removeAll))
252                 self.session.openWithCallback(self.menuCallback, ChoiceBox, title="", list=menu)
253
254         def menuCallback(self, choice):
255                 if choice is None:
256                         return
257                 choice[1]()
258
259         def cancel(self):
260                 if not self.origtags == self.ghosttags:
261                         self.saveTagsFile(self.ghosttags)
262                         self.close(self.ghostlist)
263                 else:
264                         self.close(None)
265
266         def accept(self):
267                 list = [x[1] for x in self["list"].getSelectionsList()]
268                 if not self.origtags == self.tags:
269                         self.saveTagsFile(self.tags)
270                 self.close(list)
271
272 class MovieTagEditor(TagEditor):
273         def __init__(self, session, service, parent, args = 0):
274                 self.service = service
275                 self.parentscreen = parent
276                 serviceHandler = eServiceCenter.getInstance()
277                 info = serviceHandler.info(self.service)
278                 self.path = self.service.getPath()
279                 if self.path.endswith(".ts") is True:
280                         self.path = self.path[:-3]
281                 tags = info.getInfoString(self.service, iServiceInformation.sTags)
282                 if tags:
283                         self.tags = tags.split(' ')
284                 else:
285                         self.tags = []
286                 TagEditor.__init__(self, session, self.tags, args)
287
288         def saveTags(self, file, tags):
289                 if os.path.exists(file + ".ts.meta"):
290                         metafile = open(file + ".ts.meta", "r")
291                         sid = metafile.readline()
292                         title = metafile.readline()
293                         descr = metafile.readline()
294                         time = metafile.readline()
295                         oldtags = metafile.readline().rstrip()
296                         metafile.close()
297                         tags = " ".join(tags)
298                         if tags != oldtags:
299                                 metafile = open(file + ".ts.meta", "w")
300                                 metafile.write("%s%s%s%s%s" %(sid, title, descr, time, tags))
301                                 metafile.close()
302
303         def cancel(self):
304                 if not self.origtags == self.ghosttags:
305                         self.saveTagsFile(self.ghosttags)
306                         self.exitDialog()
307                 else:
308                         self.close()
309
310         def accept(self):
311                 list = [x[1] for x in self["list"].getSelectionsList()]
312                 if not self.origtags == self.tags:
313                         self.saveTagsFile(self.tags)
314                 self.saveTags(self.path, list)
315                 self.exitDialog()
316
317         def exitDialog(self):
318                 self.close()
319                 # This will try to get back to an updated movie list.
320                 # A proper way to do this should be provided in enigma2.
321                 try:
322                         self.parentscreen.csel.reloadList()
323                         self.parentscreen.close()
324                 except AttributeError:
325                         pass