Merge branch 'master' into future
[enigma2-plugins.git] / emission / src / EmissionOverview.py
1 # -*- coding: utf-8 -*-
2
3 # GUI (Screens)
4 from Screens.Screen import Screen
5 from Screens.ChoiceBox import ChoiceBox
6 from Screens.HelpMenu import HelpableScreen
7 from Screens.LocationBox import LocationBox
8 from Screens.MessageBox import MessageBox
9
10 # GUI (Components)
11 from Components.ActionMap import HelpableActionMap
12 from Components.FileList import FileList
13 from Components.Pixmap import Pixmap
14 from Components.Sources.List import List
15 from Components.Sources.StaticText import StaticText
16
17 # Configuration
18 from Components.config import config
19
20 from enigma import eTimer
21
22 from transmissionrpc import Client, TransmissionError
23
24 from . import EmissionBandwidth
25 from . import EmissionDetailview
26 from . import EmissionSetup
27
28 LIST_TYPE_ALL = 0
29 LIST_TYPE_DOWNLOADING = 1
30 LIST_TYPE_SEEDING = 2
31
32 SORT_TYPE_TIME = 0
33 SORT_TYPE_PROGRESS = 1
34 SORT_TYPE_ADDED = 2
35 SORT_TYPE_SPEED = 3
36
37 class TorrentLocationBox(LocationBox):
38         def __init__(self, session):
39                 # XXX: implement bookmarks
40                 LocationBox.__init__(self, session)
41
42                 self.skinName = [ "TorrentLocationBox", "LocationBox" ]
43
44                 # non-standard filelist which shows .tor(rent) files
45                 self["filelist"] = FileList(None, showDirectories = True, showFiles = True, matchingPattern = "^.*\.tor(rent)?")
46
47         def ok(self):
48                 # changeDir in booklist and only select path
49                 if self.currList == "filelist":
50                         if self["filelist"].canDescent():
51                                 self["filelist"].descent()
52                                 self.updateTarget()
53                         else:
54                                 self.select()
55                 else:
56                         self["filelist"].changeDir(self["booklist"].getCurrent())
57
58         def selectConfirmed(self, ret):
59                 if ret:
60                         dir = self["filelist"].getCurrentDirectory()
61                         cur = self["filelist"].getSelection()
62                         ret = dir and cur and dir + cur[0]
63                         if self.realBookmarks:
64                                 if self.autoAdd and not ret in self.bookmarks:
65                                         self.bookmarks.append(self.getPreferredFolder())
66                                         self.bookmarks.sort()
67
68                                 if self.bookmarks != self.realBookmarks.value:
69                                         self.realBookmarks.value = self.bookmarks
70                                         self.realBookmarks.save()
71                         self.close(ret)
72
73         def select(self):
74                 # only go to work if a file is selected
75                 if self.currList == "filelist":
76                         if not self["filelist"].canDescent():
77                                 self.selectConfirmed(True)
78
79 class EmissionOverview(Screen, HelpableScreen):
80         skin = """<screen name="EmissionOverview" title="Torrent Overview" position="75,135" size="565,330">
81                 <ePixmap position="0,0" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
82                 <ePixmap position="140,0" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
83                 <ePixmap position="280,0" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
84                 <ePixmap position="420,0" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
85                 <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" />
86                 <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" />
87                 <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" />
88                 <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" />
89                 <widget size="320,25" alphatest="on" position="5,45" zPosition="1" name="all_sel" pixmap="skin_default/epg_now.png" />
90                 <widget valign="center" transparent="1" size="108,22" backgroundColor="#25062748" position="5,47" zPosition="2" source="all_text" render="Label" halign="center" font="Regular;18" />
91                 <widget size="320,25" alphatest="on" position="5,45" zPosition="1" name="downloading_sel" pixmap="skin_default/epg_next.png" />
92                 <widget valign="center" transparent="1" size="108,22" backgroundColor="#25062748" position="111,47" zPosition="2" source="downloading_text" render="Label" halign="center" font="Regular;18" />
93                 <widget size="320,25" alphatest="on" position="5,45" zPosition="1" name="seeding_sel" pixmap="skin_default/epg_more.png" />
94                 <widget valign="center" transparent="1" size="108,22" backgroundColor="#25062748" position="212,47" zPosition="2" source="seeding_text" render="Label" halign="center" font="Regular;18" />
95                 <widget source="torrents" render="Label" size="240,22" position="320,47" halign="right" font="Regular;18" />
96                 <!--ePixmap size="550,230" alphatest="on" position="5,65" pixmap="skin_default/border_epg.png" /-->
97                 <widget source="list" render="Listbox" position="5,70" size="550,225" scrollbarMode="showAlways">
98                         <convert type="TemplatedMultiContent">
99                                 {"template": [
100                                                 MultiContentEntryText(pos=(2,2), size=(555,22), text = 1, font = 0, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER),
101                                                 MultiContentEntryText(pos=(2,26), size=(555,18), text = 2, font = 1, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER),
102                                                 (eListboxPythonMultiContent.TYPE_PROGRESS, 0, 44, 537, 6, -3),
103                                         ],
104                                   "fonts": [gFont("Regular", 20),gFont("Regular", 16)],
105                                   "itemHeight": 51
106                                  }
107                         </convert>
108                 </widget>
109                 <widget source="upspeed" render="Label" size="150,20" position="5,300" halign="left" font="Regular;18" />
110                 <widget source="downspeed" render="Label" size="150,20" position="410,300" halign="right" font="Regular;18" />
111         </screen>"""
112
113         def __init__(self, session):
114                 Screen.__init__(self, session)
115                 HelpableScreen.__init__(self)
116
117                 try:
118                         self.transmission = Client(
119                                 address = config.plugins.emission.hostname.value,
120                                 port = config.plugins.emission.port.value,
121                                 user = config.plugins.emission.username.value,
122                                 password = config.plugins.emission.password.value
123                         )
124                 except TransmissionError as te:
125                         self.transmission = None
126
127                 self["SetupActions"] = HelpableActionMap(self, "SetupActions",
128                 {
129                         "ok": (self.ok, _("show details")),
130                         "cancel": (self.close, _("close")),
131                 })
132
133                 self["ColorActions"] = HelpableActionMap(self, "ColorActions",
134                 {
135                         "green": (self.bandwidth, _("open bandwidth settings")),
136                         "yellow": (self.prevlist, _("show previous list")),
137                         "blue": (self.nextlist, _("show next list")),
138                 })
139
140                 self["MenuActions"] = HelpableActionMap(self, "MenuActions",
141                 {
142                         "menu": (self.menu, _("open context menu")),
143                 })
144
145                 self["key_red"] = StaticText(_("Close"))
146                 self["key_green"] = StaticText(_("Bandwidth"))
147                 self["key_yellow"] = StaticText("")
148                 self["key_blue"] = StaticText("")
149
150                 self["all_text"] = StaticText(_("All"))
151                 self["downloading_text"] = StaticText(_("DL"))
152                 self["seeding_text"] = StaticText(_("UL"))
153                 self["upspeed"] = StaticText("")
154                 self["downspeed"] = StaticText("")
155                 self["torrents"] = StaticText("")
156
157                 self["all_sel"] = Pixmap()
158                 self["downloading_sel"] = Pixmap()
159                 self["seeding_sel"] = Pixmap()
160
161                 self['list'] = List([])
162
163                 self.list_type = config.plugins.emission.last_tab.value
164                 self.sort_type = config.plugins.emission.last_sort.value
165                 self.showHideSetTextMagic()
166
167                 self.timer = eTimer()
168                 self.timer.callback.append(self.updateList)
169                 self.timer.start(0, 1)
170
171         def bandwidthCallback(self, ret = None):
172                 if self.transmission is not None and ret:
173                         try:
174                                 self.transmission.set_session(**ret)
175                         except TransmissionError as te:
176                                 self.session.open(
177                                         MessageBox,
178                                         _("Error communicating with transmission-daemon: %s.") % (te),
179                                         type = MessageBox.TYPE_ERROR,
180                                         timeout = 5
181                                 )
182                 self.updateList()
183
184         def menuCallback(self, ret = None):
185                 ret and ret[1]()
186
187         def newDlCallback(self, ret = None):
188                 if self.transmission is not None and ret:
189                         try:
190                                 res = self.transmission.add_url(ret)
191                         except TransmissionError as te:
192                                 self.session.open(
193                                         MessageBox,
194                                         _("Error communicating with transmission-daemon: %s.") % (te),
195                                         type = MessageBox.TYPE_ERROR,
196                                         timeout = 5
197                                 )
198                         else:
199                                 if not res:
200                                         self.session.open(
201                                                 MessageBox,
202                                                 _("Torrent could not be scheduled not download!"),
203                                                 type = MessageBox.TYPE_ERROR,
204                                                 timeout = 5
205                                         )
206                 self.updateList()
207
208         def newDl(self):
209                 self.timer.stop()
210                 self.session.openWithCallback(
211                         self.newDlCallback,
212                         TorrentLocationBox
213                 )
214
215         def sortCallback(self, ret = None):
216                 if ret is not None:
217                         self.sort_type = config.plugins.emission.last_sort.value = ret[1]
218                         config.plugins.emission.last_sort.save()
219                 self.updateList()
220
221         def sort(self):
222                 self.timer.stop()
223                 self.session.openWithCallback(
224                         self.sortCallback,
225                         ChoiceBox,
226                         _("Which sorting method do you prefer?"),
227                         [(_("by eta") ,SORT_TYPE_TIME),
228                         (_("by progress") ,SORT_TYPE_PROGRESS),
229                         (_("by age") ,SORT_TYPE_ADDED),
230                         (_("by speed") ,SORT_TYPE_SPEED)]
231                 )
232
233         def pauseShown(self):
234                 if self.transmission is not None:
235                         self.transmission.stop([x[0].id for x in self.list])
236
237         def unpauseShown(self):
238                 if self.transmission is not None:
239                         self.transmission.start([x[0].id for x in self.list])
240
241         def pauseAll(self):
242                 if self.transmission is None:
243                         return
244
245                 try:
246                         self.transmission.stop([x.id for x in self.transmission.list().values()])
247                 except TransmissionError as te:
248                         self.session.open(
249                                 MessageBox,
250                                 _("Error communicating with transmission-daemon: %s.") % (te),
251                                 type = MessageBox.TYPE_ERROR,
252                                 timeout = 5
253                         )
254
255         def unpauseAll(self):
256                 if self.transmission is None:
257                         return
258
259                 try:
260                         self.transmission.start([x.id for x in self.transmission.list().values()])
261                 except TransmissionError as te:
262                         self.session.open(
263                                 MessageBox,
264                                 _("Error communicating with transmission-daemon: %s.") % (te),
265                                 type = MessageBox.TYPE_ERROR,
266                                 timeout = 5
267                         )
268
269         def configure(self):
270                 #reload(EmissionSetup)
271                 self.timer.stop()
272                 self.session.openWithCallback(
273                         self.configureCallback,
274                         EmissionSetup.EmissionSetup
275                 )
276
277         def menu(self):
278                 self.session.openWithCallback(
279                         self.menuCallback,
280                         ChoiceBox,
281                         _("What do you want to do?"),
282                         [(_("Configure connection"), self.configure),
283                         (_("Change sorting"), self.sort),
284                         (_("Add new download"), self.newDl),
285                         (_("Pause shown"), self.pauseShown),
286                         (_("Unpause shown"), self.unpauseShown),
287                         (_("Pause all"), self.pauseAll),
288                         (_("Unpause all"), self.unpauseAll)],
289                 )
290
291         def showHideSetTextMagic(self):
292                 list_type = self.list_type
293                 if list_type == LIST_TYPE_ALL:
294                         self["all_sel"].show()
295                         self["downloading_sel"].hide()
296                         self["seeding_sel"].hide()
297                         self["key_yellow"].setText(_("Seeding"))
298                         self["key_blue"].setText(_("Download"))
299                 elif list_type == LIST_TYPE_DOWNLOADING:
300                         self["all_sel"].hide()
301                         self["downloading_sel"].show()
302                         self["seeding_sel"].hide()
303                         self["key_yellow"].setText(_("All"))
304                         self["key_blue"].setText(_("Seeding"))
305                 else: #if list_type == LIST_TYPE_SEEDING:
306                         self["all_sel"].hide()
307                         self["downloading_sel"].hide()
308                         self["seeding_sel"].show()
309                         self["key_yellow"].setText(_("Download"))
310                         self["key_blue"].setText(_("All"))
311
312         def prevlist(self):
313                 self.timer.stop()
314                 list_type = self.list_type
315                 if list_type == LIST_TYPE_ALL:
316                         self.list_type = LIST_TYPE_SEEDING
317                 elif list_type == LIST_TYPE_DOWNLOADING:
318                         self.list_type = LIST_TYPE_ALL
319                 else: #if list_type == LIST_TYPE_SEEDING:
320                         self.list_type = LIST_TYPE_DOWNLOADING
321                 self.showHideSetTextMagic()
322                 self.updateList()
323
324         def nextlist(self):
325                 self.timer.stop()
326                 list_type = self.list_type
327                 if list_type == LIST_TYPE_ALL:
328                         self.list_type = LIST_TYPE_DOWNLOADING
329                 elif list_type == LIST_TYPE_DOWNLOADING:
330                         self.list_type = LIST_TYPE_SEEDING
331                 else: #if list_type == LIST_TYPE_SEEDING:
332                         self.list_type = LIST_TYPE_ALL
333                 self.showHideSetTextMagic()
334                 self.updateList()
335
336         def prevItem(self):
337                 self['list'].selectPrevious()
338                 cur = self['list'].getCurrent()
339                 return cur and cur[0]
340
341         def nextItem(self):
342                 self['list'].selectNext()
343                 cur = self['list'].getCurrent()
344                 return cur and cur[0]
345
346         def bandwidth(self):
347                 if self.transmission is None:
348                         return
349
350                 #reload(EmissionBandwidth)
351                 self.timer.stop()
352                 try:
353                         sess = self.transmission.get_session()
354                         rpc_version = self.transmission.rpc_version
355                 except TransmissionError as te:
356                         self.session.open(
357                                 MessageBox,
358                                 _("Error communicating with transmission-daemon: %s.") % (te),
359                                 type = MessageBox.TYPE_ERROR,
360                                 timeout = 5
361                         )
362                         # XXX: this seems silly but cleans the gui and restarts the timer :-)
363                         self.updateList()
364                 else:
365                         self.session.openWithCallback(
366                                 self.bandwidthCallback,
367                                 EmissionBandwidth.EmissionBandwidth,
368                                 sess,
369                                 False,
370                                 rpc_version
371                         )
372
373         def configureCallback(self):
374                 try:
375                         self.transmission = Client(
376                                 address = config.plugins.emission.hostname.value,
377                                 port = config.plugins.emission.port.value,
378                                 user = config.plugins.emission.username.value,
379                                 password = config.plugins.emission.password.value
380                         )
381                 except TransmissionError as te:
382                         self.transmission = None
383                         self.session.open(
384                                 MessageBox,
385                                 _("Error communicating with transmission-daemon: %s.") % (te),
386                                 type = MessageBox.TYPE_ERROR,
387                                 timeout = 5
388                         )
389                 else:
390                         self.updateList()
391
392         def updateList(self, *args, **kwargs):
393                 # XXX: if we are not connected do NOT restart timer, it's useless anyway
394                 if self.transmission is None:
395                         return
396
397                 try:
398                         lst = list(self.transmission.list().values())
399                         session = self.transmission.session_stats()
400                 except TransmissionError:
401                         # XXX: some hint in gui would be nice
402                         self['list'].setList([])
403                         self["torrents"].setText("")
404                         self["upspeed"].setText("")
405                         self["downspeed"].setText("")
406                 else:
407                         sort_type = self.sort_type
408                         if sort_type == SORT_TYPE_TIME:
409                                 def cmp_func(x, y):
410                                         x_eta = x.fields['eta']
411                                         y_eta = y.fields['eta']
412                                         if x_eta > -1 and y_eta < 0:
413                                                 return 1
414                                         if x_eta < 0 and y_eta > -1:
415                                                 return -1
416                                         # note: cmp call inversed because lower eta is "better"
417                                         return cmp(y_eta, x_eta) or cmp(x.progress, y.progress)
418
419                                 lst.sort(cmp = cmp_func, reverse = True)
420                         elif sort_type == SORT_TYPE_PROGRESS:
421                                 lst.sort(key = lambda x: x.progress, reverse = True)
422                         elif sort_type == SORT_TYPE_SPEED:
423                                 lst.sort(key = lambda x: (x.rateDownload, x.rateUpload), reverse = True)
424                         # SORT_TYPE_ADDED is what we already have
425
426                         list_type = self.list_type
427                         if list_type == LIST_TYPE_ALL:
428                                 lst = [
429                                         (x, x.name.encode('utf-8', 'ignore'),
430                                         str(x.eta or '?:??:??').encode('utf-8'),
431                                         int(x.progress))
432                                         for x in lst
433                                 ]
434                         elif list_type == LIST_TYPE_DOWNLOADING:
435                                 lst = [
436                                         (x, x.name.encode('utf-8', 'ignore'),
437                                         str(x.eta or '?:??:??').encode('utf-8'),
438                                         int(x.progress))
439                                         for x in lst if x.status == "downloading"
440                                 ]
441                         else: #if list_type == LIST_TYPE_SEEDING:
442                                 lst = [
443                                         (x, x.name.encode('utf-8', 'ignore'),
444                                         str(x.eta or '?:??:??').encode('utf-8'),
445                                         int(x.progress))
446                                         for x in lst if x.status == "seeding"
447                                 ]
448
449                         self["torrents"].setText(_("Active Torrents: %d/%d") % (session.activeTorrentCount, session.torrentCount))
450                         self["upspeed"].setText(_("UL: %d kb/s") % (session.uploadSpeed/1024))
451                         self["downspeed"].setText(_("DL: %d kb/s") % (session.downloadSpeed/1024))
452
453                         # XXX: this is a little ugly but this way we have the least
454                         # visible distortion :-)
455                         index = min(self['list'].index, len(lst)-1)
456                         self['list'].setList(lst)
457                         self['list'].index = index
458
459                         self.list = lst
460                 self.timer.startLongTimer(10)
461
462         def ok(self):
463                 cur = self['list'].getCurrent()
464                 if self.transmission is not None and cur:
465                         #reload(EmissionDetailview)
466                         self.timer.stop()
467                         self.session.openWithCallback(
468                                 self.updateList,
469                                 EmissionDetailview.EmissionDetailview,
470                                 self.transmission,
471                                 cur[0],
472                                 self.prevItem,
473                                 self.nextItem,
474                         )
475
476         def close(self):
477                 self.timer.stop()
478                 config.plugins.emission.last_tab.value = self.list_type
479                 config.plugins.emission.last_tab.save()
480                 Screen.close(self)
481
482 __all__ = ['LIST_TYPE_ALL', 'LIST_TYPE_DOWNLOADING', \
483         'LIST_TYPE_SEEDING', 'EmissionOverview', 'SORT_TYPE_TIME', \
484         'SORT_TYPE_PROGRESS', 'SORT_TYPE_ADDED', 'SORT_TYPE_SPEED']
485