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