add pretty basic queue manager (currently only displays queue in a not very readable...
[enigma2-plugins.git] / ftpbrowser / src / FTPBrowser.py
1 # for localized messages
2 from . import _
3
4 # Core
5 from enigma import RT_HALIGN_LEFT, eListboxPythonMultiContent
6
7 # Tools
8 from Tools.Directories import SCOPE_SKIN_IMAGE, resolveFilename
9 from Tools.LoadPixmap import LoadPixmap
10 from Tools.Notifications import AddPopup, AddNotificationWithCallback
11
12 # GUI (Screens)
13 from Screens.Screen import Screen
14 from Screens.HelpMenu import HelpableScreen
15 from Screens.MessageBox import MessageBox
16 from Screens.ChoiceBox import ChoiceBox
17 from Screens.InfoBarGenerics import InfoBarNotifications
18 from FTPServerManager import FTPServerManager
19 from FTPQueueManager import FTPQueueManager
20 from NTIVirtualKeyBoard import NTIVirtualKeyBoard
21
22 # GUI (Components)
23 from Components.ActionMap import ActionMap, HelpableActionMap
24 from Components.Label import Label
25 from Components.FileList import FileList, FileEntryComponent, EXTENSIONS
26 from Components.Button import Button
27 from VariableProgressSource import VariableProgressSource
28
29 # FTP Client
30 from twisted.internet import reactor, defer
31 from twisted.internet.protocol import Protocol, ClientCreator
32 from twisted.protocols.ftp import FTPClient, FTPFileListProtocol
33 from twisted.protocols.basic import FileSender
34
35 # System
36 from os import path as os_path, unlink as os_unlink, rename as os_rename, \
37                 listdir as os_listdir
38 from time import time
39
40 def FTPFileEntryComponent(file, directory):
41         isDir = True if file['filetype'] == 'd' else False
42         name = file['filename']
43         absolute = directory + name
44         if isDir:
45                 absolute += '/'
46
47         res = [
48                 (absolute, isDir, file['size']),
49                 (eListboxPythonMultiContent.TYPE_TEXT, 35, 1, 470, 20, 0, RT_HALIGN_LEFT, name)
50         ]
51         if isDir:
52                 png = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, "extensions/directory.png"))
53         else:
54                 extension = name.split('.')
55                 extension = extension[-1].lower()
56                 if EXTENSIONS.has_key(extension):
57                         png = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, "extensions/" + EXTENSIONS[extension] + ".png"))
58                 else:
59                         png = None
60         if png is not None:
61                 res.append((eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, 10, 2, 20, 20, png))
62
63         return res
64
65 class FTPFileList(FileList):
66         def __init__(self):
67                 self.ftpclient = None
68                 self.select = None
69                 self.isValid = False
70                 FileList.__init__(self, "/")
71
72         def changeDir(self, directory, select = None):
73                 if not directory:
74                         return
75
76                 if self.ftpclient is None:
77                         self.list = []
78                         self.l.setList(self.list)
79                         return
80
81                 self.current_directory = directory
82                 self.select = select
83
84                 self.filelist = FTPFileListProtocol()
85                 d = self.ftpclient.list(directory, self.filelist)
86                 d.addCallback(self.listRcvd).addErrback(self.listFailed)
87
88         def listRcvd(self, *args):
89                 # TODO: is any of the 'advanced' features useful (and more of all can they be implemented) here?
90                 list = [FTPFileEntryComponent(file, self.current_directory) for file in self.filelist.files]
91                 list.sort(key = lambda x: (not x[0][1], x[0][0]))
92                 if self.current_directory != "/":
93                         list.insert(0, FileEntryComponent(name = "<" +_("Parent Directory") + ">", absolute = '/'.join(self.current_directory.split('/')[:-2]) + '/', isDir = True))
94
95                 self.isValid = True
96                 self.l.setList(list)
97                 self.list = list
98
99                 select = self.select
100                 if select is not None:
101                         i = 0
102                         self.moveToIndex(0)
103                         for x in list:
104                                 p = x[0][0]
105
106                                 if p == select:
107                                         self.moveToIndex(i)
108                                         break
109                                 i += 1
110
111         def listFailed(self, *args):
112                 # XXX: we might end up here if login fails, we might want to add some check for this (e.g. send a dummy command before doing actual work)
113                 if self.current_directory != "/":
114                         self.list = [
115                                 FileEntryComponent(name = "<" +_("Parent Directory") + ">", absolute = '/'.join(self.current_directory.split('/')[:-2]) + '/', isDir = True),
116                                 FileEntryComponent(name = "<" + _("Error") + ">", absolute = None, isDir = False),
117                         ]
118                 else:
119                         self.list = [
120                                 FileEntryComponent(name = "<" + _("Error") + ">", absolute = None, isDir = False),
121                         ]
122
123                 self.isValid = False
124                 self.l.setList(self.list)
125
126 class FTPBrowser(Screen, Protocol, InfoBarNotifications, HelpableScreen):
127         skin = """
128                 <screen name="FTPBrowser" position="center,center" size="560,440" title="FTP Browser">
129                         <widget name="localText" position="20,10" size="200,20" font="Regular;18" />
130                         <widget name="local" position="20,40" size="255,320" scrollbarMode="showOnDemand" />
131                         <widget name="remoteText" position="285,10" size="200,20" font="Regular;18" />
132                         <widget name="remote" position="285,40" size="255,320" scrollbarMode="showOnDemand" />
133                         <widget name="eta" position="20,360" size="200,30" font="Regular;23" />
134                         <widget name="speed" position="330,360" size="200,30" halign="right" font="Regular;23" />
135                         <widget source="progress" render="Progress" position="20,390" size="520,10" />
136                         <ePixmap name="green" position="10,400" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
137                         <ePixmap name="yellow" position="180,400" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
138                         <ePixmap name="blue" position="350,400" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
139                         <widget name="key_green" position="10,400" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
140                         <widget name="key_yellow" position="180,400" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
141                         <widget name="key_blue" position="350,400" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
142                         <ePixmap position="515,408" zPosition="1" size="35,25" pixmap="skin_default/buttons/key_menu.png" alphatest="on" />
143                 </screen>"""
144
145         def __init__(self, session):
146                 Screen.__init__(self, session)
147                 HelpableScreen.__init__(self)
148                 InfoBarNotifications.__init__(self)
149                 self.ftpclient = None
150                 self.queueManagerInstance = None
151                 self.file = None
152                 self.queue = None
153                 self.currlist = "local"
154
155                 # Init what we need for dl progress
156                 self.currentLength = 0
157                 self.lastLength = 0
158                 self.lastTime = 0
159                 self.lastApprox = 0
160                 self.fileSize = 0
161
162                 self["localText"] = Label(_("Local"))
163                 self["local"] = FileList("/media/hdd/", showMountpoints = False)
164                 self["remoteText"] = Label(_("Remote (not connected)"))
165                 self["remote"] = FTPFileList()
166                 self["eta"] = Label("")
167                 self["speed"] = Label("")
168                 self["progress"] = VariableProgressSource()
169                 self["key_red"] = Button(_("Exit"))
170                 self["key_green"] = Button(_("Rename"))
171                 self["key_yellow"] = Button(_("Delete"))
172                 self["key_blue"] = Button(_("Upload"))
173
174                 self.server = None
175
176                 self["ftpbrowserBaseActions"] = HelpableActionMap(self, "ftpbrowserBaseActions",
177                         {
178                                 "ok": (self.ok, _("enter directory/get file/put file")),
179                                 "cancel": (self.cancel , _("close")),
180                                 "menu": (self.menu, _("open menu")),
181                         }, -2)
182
183                 self["ftpbrowserListActions"] = HelpableActionMap(self, "ftpbrowserListActions",
184                         {
185                                 "channelUp": (self.setLocal, _("Select local file list")),
186                                 "channelDown": (self.setRemote, _("Select remote file list")),
187                         })
188
189                 self["actions"] = ActionMap(["ftpbrowserDirectionActions", "ColorActions"],
190                         {
191                                 "up": self.up,
192                                 "down": self.down,
193                                 "left": self.left,
194                                 "right": self.right,
195                                 "green": self.rename,
196                                 "yellow": self.delete,
197                                 "blue": self.transfer,
198                         }, -2)
199
200                 self.onExecBegin.append(self.reinitialize)
201
202         def reinitialize(self):
203                 # NOTE: this will clear the remote file list if we are not currently connected. this behavior is intended.
204                 # XXX: but do we also want to do this when we just returned from a notification?
205                 self["remote"].refresh()
206                 self["local"].refresh()
207
208                 if not self.ftpclient:
209                         self.connect(self.server)
210                 # XXX: Actually everything else should be taken care of... recheck this!
211
212         def serverManagerCallback(self, uri):
213                 if uri:
214                         self.connect(uri)
215
216         def serverManager(self):
217                 self.session.openWithCallback(
218                         self.serverManagerCallback,
219                         FTPServerManager,
220                 )
221
222         def queueManagerCallback(self):
223                 self.queueManagerInstance = None
224
225         def queueManager(self):
226                 self.queueManagerInstance = self.session.openWithCallback(
227                         self.queueManagerCallback,
228                         FTPQueueManager,
229                         self.queue,
230                 )
231
232         def menuCallback(self, ret):
233                 ret and ret[1]()
234
235         def menu(self):
236                 self.session.openWithCallback(
237                         self.menuCallback,
238                         ChoiceBox,
239                         list = [
240                                 (_("Server Manager"), self.serverManager),
241                                 (_("Queue Manager"), self.queueManager),
242                         ]
243                 )
244
245         def setLocal(self):
246                 self.currlist = "local"
247                 self["key_blue"].setText(_("Upload"))
248
249         def setRemote(self):
250                 self.currlist = "remote"
251                 self["key_blue"].setText(_("Download"))
252
253         def okQuestion(self, res = None):
254                 if res:
255                         self.ok(force = True)
256
257         def getRemoteFile(self):
258                 remoteFile = self["remote"].getSelection()
259                 if not remoteFile or not remoteFile[0]:
260                         return None, None, None
261
262                 absRemoteFile = remoteFile[0]
263                 if remoteFile[1]:
264                         fileName = absRemoteFile.split('/')[-2]
265                 else:
266                         fileName = absRemoteFile.split('/')[-1]
267
268                 if len(remoteFile) == 3:
269                         fileSize = remoteFile[2]
270                 else:
271                         fileSize = 0
272
273                 return absRemoteFile, fileName, fileSize
274
275         def getLocalFile(self):
276                 # XXX: isn't this supposed to be an absolute filename? well, it's not for me :-/
277                 localFile = self["local"].getSelection()
278                 if not localFile:
279                         return None, None
280
281                 if localFile[1]:
282                         absLocalFile = localFile[0]
283                         fileName = absLocalFile.split('/')[-2]
284                 else:
285                         fileName = localFile[0]
286                         absLocalFile = self["local"].getCurrentDirectory() + fileName
287
288                 return absLocalFile, fileName
289
290         def renameCallback(self, newName = None):
291                 if not newName:
292                         return
293
294                 if self.currlist == "remote":
295                         absRemoteFile, fileName, fileSize = self.getRemoteFile()
296                         if not fileName:
297                                 return
298
299                         directory = self["remote"].getCurrentDirectory()
300                         sep = '/' if directory != '/' else ''
301                         newRemoteFile = directory + sep + newName
302
303                         def callback(ret = None):
304                                 AddPopup(_("Renamed %s to %s.") % (fileName, newName), MessageBox.TYPE_INFO, -1)
305                         def errback(ret = None):
306                                 AddPopup(_("Could not rename %s.") % (fileName), MessageBox.TYPE_ERROR, -1)
307
308                         self.ftpclient.rename(absRemoteFile, newRemoteFile).addCallback(callback).addErrback(errback)
309                 else:
310                         assert(self.currlist == "local")
311                         absLocalFile, fileName = self.getLocalFile()
312                         if not fileName:
313                                 return
314
315                         directory = self["local"].getCurrentDirectory()
316                         newLocalFile = os_path.join(directory, newName)
317
318                         try:
319                                 os_rename(absLocalFile, newLocalFile)
320                         except OSError, ose:
321                                 AddPopup(_("Could not rename %s.") % (fileName), MessageBox.TYPE_ERROR, -1)
322                         else:
323                                 AddPopup(_("Renamed %s to %s.") % (fileName, newName), MessageBox.TYPE_INFO, -1)
324
325         def rename(self):
326                 if not self.ftpclient or self.queue:
327                         return
328
329                 if self.currlist == "remote":
330                         absRemoteFile, fileName, fileSize = self.getRemoteFile()
331                         if not fileName:
332                                 return
333                 else:
334                         assert(self.currlist == "local")
335                         absLocalFile, fileName = self.getLocalFile()
336                         if not fileName:
337                                 return
338
339                 self.session.openWithCallback(
340                         self.renameCallback,
341                         NTIVirtualKeyBoard,
342                         title = _("Enter new filename:"),
343                         text = fileName,
344                 )
345
346         def deleteConfirmed(self, ret):
347                 if not ret:
348                         return
349
350                 if self.currlist == "remote":
351                         absRemoteFile, fileName, fileSize = self.getRemoteFile()
352                         if not fileName:
353                                 return
354
355                         def callback(ret = None):
356                                 AddPopup(_("Removed %s.") % (fileName), MessageBox.TYPE_INFO, -1)
357                         def errback(ret = None):
358                                 AddPopup(_("Could not delete %s.") % (fileName), MessageBox.TYPE_ERROR, -1)
359
360                         self.ftpclient.removeFile(absRemoteFile).addCallback(callback).addErrback(errback)
361                 else:
362                         assert(self.currlist == "local")
363                         absLocalFile, fileName = self.getLocalFile()
364                         if not fileName:
365                                 return
366
367                         try:
368                                 os_unlink(absLocalFile)
369                         except OSError, oe:
370                                 AddPopup(_("Could not delete %s.") % (fileName), MessageBox.TYPE_ERROR, -1)
371                         else:
372                                 AddPopup(_("Removed %s.") % (fileName), MessageBox.TYPE_INFO, -1)
373
374         def delete(self):
375                 if not self.ftpclient or self.queue:
376                         return
377
378                 if self.currlist == "remote":
379                         if self["remote"].canDescent():
380                                 self.session.open(
381                                         MessageBox,
382                                         _("Removing directories is not supported."),
383                                         MessageBox.TYPE_WARNING
384                                 )
385                                 return
386
387                         absRemoteFile, fileName, fileSize = self.getRemoteFile()
388                         if not fileName:
389                                 return
390                 else:
391                         assert(self.currlist == "local")
392                         if self["local"].canDescent():
393                                 self.session.open(
394                                         MessageBox,
395                                         _("Removing directories is not supported."),
396                                         MessageBox.TYPE_WARNING
397                                 )
398                                 return
399
400                         absLocalFile, fileName = self.getLocalFile()
401                         if not fileName:
402                                 return
403
404                 self.session.openWithCallback(
405                         self.deleteConfirmed,
406                         MessageBox,
407                         _("Are you sure you want to delete %s?") % (fileName)
408                 )
409
410         def transferListRcvd(self, res, filelist):
411                 remoteDirectory, _, _ = self.getRemoteFile()
412                 localDirectory = self["local"].getCurrentDirectory()
413
414                 self.queue = [(True, remoteDirectory + file["filename"], localDirectory + file["filename"], file["size"]) for file in filelist.files if file["filetype"] == "-"]
415                 self.nextQueue()
416         
417         def nextQueue(self):
418                 if self.queue:
419                         # NOTE: put this transfer back if there already is an active one,
420                         # it will be picked up again when the active transfer is done
421                         if self.file:
422                                 return
423
424                         top = self.queue[0]
425                         del self.queue[0]
426                         if top[0]:
427                                 self.getFile(*top[1:])
428                         else:
429                                 self.putFile(*top[1:])
430                 elif self.queue is not None:
431                         self.queue = None
432                         AddPopup(_("Queue processed."), MessageBox.TYPE_INFO, -1)
433
434                 if self.queueManagerInstance:
435                         self.queueManagerInstance.updateList(self.queue)
436
437         def transferListFailed(self, res = None):
438                 self.queue = None
439                 AddPopup(_("Could not obtain list of files."), MessageBox.TYPE_ERROR, -1)
440
441         def transfer(self):
442                 if not self.ftpclient or self.queue:
443                         return
444
445                 if self.currlist == "remote":
446                         # single file transfer is implemented in self.ok
447                         if not self["remote"].canDescent():
448                                 return self.ok()
449                         else:
450                                 absRemoteFile, fileName, fileSize = self.getRemoteFile()
451                                 if not fileName:
452                                         return
453
454                                 filelist = FTPFileListProtocol()
455                                 d = self.ftpclient.list(absRemoteFile, filelist)
456                                 d.addCallback(self.transferListRcvd, filelist).addErrback(self.transferListFailed)
457                 else:
458                         assert(self.currlist == "local")
459                         # single file transfer is implemented in self.ok
460                         if not self["local"].canDescent():
461                                 return self.ok()
462                         else:
463                                 localDirectory, _ = self.getLocalFile()
464                                 remoteDirectory = self["remote"].getCurrentDirectory()
465
466                                 def remoteFileExists(absName):
467                                         for file in self["remote"].getFileList():
468                                                 if file[0][0] == absName:
469                                                         return True
470                                         return False
471
472                                 self.queue = [(False, remoteDirectory + file, localDirectory + file, remoteFileExists(remoteDirectory + file)) for file in os_listdir(localDirectory) if os_path.isfile(localDirectory + file)]
473                                 self.nextQueue()
474
475
476         def getFileCallback(self, ret, absRemoteFile, absLocalFile, fileSize):
477                 if not ret:
478                         self.nextQueue()
479                 else:
480                         self.getFile(absRemoteFile, absLocalFile, fileSize, force=True)
481
482         def getFile(self, absRemoteFile, absLocalFile, fileSize, force=False):
483                 if not force and os_path.exists(absLocalFile):
484                         fileName = absRemoteFile.split('/')[-1]
485                         AddNotificationWithCallback(
486                                 lambda ret: self.getFileCallback(ret, absRemoteFile, absLocalFile, fileSize),
487                                 MessageBox,
488                                 _("A file with this name (%s) already exists locally.\nDo you want to overwrite it?") % (fileName),
489                         )
490                 else:
491                         self.currentLength = 0
492                         self.lastLength = 0
493                         self.lastTime = 0
494                         self.lastApprox = 0
495                         self.fileSize = fileSize
496
497                         try:
498                                 self.file = open(absLocalFile, 'w')
499                         except IOError, ie:
500                                 # TODO: handle this
501                                 raise ie
502                         else:
503                                 d = self.ftpclient.retrieveFile(absRemoteFile, self, offset = 0)
504                                 d.addCallback(self.getFinished).addErrback(self.getFailed)
505
506         def putFileCallback(self, ret, absRemoteFile, absLocalFile, remoteFileExists):
507                 if not ret:
508                         self.nextQueue()
509                 else:
510                         self.putFile(absRemoteFile, absLocalFile, remoteFileExists, force=True)
511
512         def putFile(self, absRemoteFile, absLocalFile, remoteFileExists, force=False):
513                 if not force and remoteFileExists:
514                         fileName = absRemoteFile.split('/')[-1]
515                         AddNotificationWithCallback(
516                                 lambda ret: self.putFileCallback(ret, absRemoteFile, absLocalFile, remoteFileExists),
517                                 MessageBox,
518                                 _("A file with this name (%s) already exists on the remote host.\nDo you want to overwrite it?") % (fileName),
519                         )
520                 else:
521                         self.currentLength = 0
522                         self.lastLength = 0
523                         self.lastTime = 0
524                         self.lastApprox = 0
525
526                         def sendfile(consumer, fileObj):
527                                 FileSender().beginFileTransfer(fileObj, consumer, transform = self.putProgress).addCallback(  
528                                         lambda _: consumer.finish()).addCallback(
529                                         self.putComplete).addErrback(self.putFailed)
530
531                         try:
532                                 self.fileSize = int(os_path.getsize(absLocalFile))
533                                 self.file = open(absLocalFile, 'rb')
534                         except (IOError, OSError), e:
535                                 # TODO: handle this
536                                 raise e
537                         else:
538                                 dC, dL = self.ftpclient.storeFile(absRemoteFile)
539                                 dC.addCallback(sendfile, self.file)
540
541         def ok(self, force = False):
542                 if self.queue:
543                         return
544
545                 if self.currlist == "remote":
546                         if not self.ftpclient:
547                                 return
548
549                         # Get file/change directory
550                         if self["remote"].canDescent():
551                                 self["remote"].descent()
552                         else:
553                                 if self.file:
554                                         self.session.open(
555                                                 MessageBox,
556                                                 _("There already is an active transfer."),
557                                                 type = MessageBox.TYPE_WARNING
558                                         )
559                                         return
560
561                                 absRemoteFile, fileName, fileSize = self.getRemoteFile()
562                                 if not fileName:
563                                         return
564
565                                 absLocalFile = self["local"].getCurrentDirectory() + fileName
566
567                                 self.getFile(absRemoteFile, absLocalFile, fileSize)
568                 else:
569                         # Put file/change directory
570                         assert(self.currlist == "local")
571                         if self["local"].canDescent():
572                                 self["local"].descent()
573                         else:
574                                 if not self.ftpclient:
575                                         return
576
577                                 if self.file:
578                                         self.session.open(
579                                                 MessageBox,
580                                                 _("There already is an active transfer."),
581                                                 type = MessageBox.TYPE_WARNING
582                                         )
583                                         return
584
585                                 if not self["remote"].isValid:
586                                         return
587
588                                 absLocalFile, fileName = self.getLocalFile()
589                                 if not fileName:
590                                         return
591
592                                 directory = self["remote"].getCurrentDirectory()
593                                 sep = '/' if directory != '/' else ''
594                                 absRemoteFile = directory + sep + fileName
595
596                                 def remoteFileExists(absName):
597                                         for file in self["remote"].getFileList():
598                                                 if file[0][0] == absName:
599                                                         return True
600                                         return False
601
602                                 self.putFile(absRemoteFile, absLocalFile, remoteFileExists(absRemoteFile))
603
604         def transferFinished(self, msg, type, toRefresh):
605                 AddPopup(msg, type, -1)
606
607                 self["eta"].setText("")
608                 self["speed"].setText("")
609                 self["progress"].invalidate()
610                 self[toRefresh].refresh()
611                 self.file.close()
612                 self.file = None
613
614         def putComplete(self, *args):
615                 if self.queue is not None:
616                         self.file.close()
617                         self.file = None
618
619                         self.nextQueue()
620                 else:
621                         self.transferFinished(
622                                 _("Upload finished."),
623                                 MessageBox.TYPE_INFO,
624                                 "remote"
625                         )
626
627         def putFailed(self, *args):
628                 # NOTE: we continue uploading but notify the user of every error though
629                 # we only display one success notification
630                 self.transferFinished(
631                         _("Error during download."),
632                         MessageBox.TYPE_ERROR,
633                         "remote"
634                 )
635                 if self.queue is not None:
636                         self.nextQueue()
637
638         def getFinished(self, *args):
639                 if self.queue is not None:
640                         self.file.close()
641                         self.file = None
642
643                         self.nextQueue()
644                 else:
645                         self.transferFinished(
646                                 _("Download finished."),
647                                 MessageBox.TYPE_INFO,
648                                 "local"
649                         )
650
651         def getFailed(self, *args):
652                 # NOTE: we continue downloading but notify the user of every error though
653                 # we only display one success notification
654                 self.transferFinished(
655                         _("Error during download."),
656                         MessageBox.TYPE_ERROR,
657                         "local"
658                 )
659                 if self.queue is not None:
660                         self.nextQueue()
661
662         def putProgress(self, chunk):
663                 self.currentLength += len(chunk)
664                 self.gotProgress(self.currentLength, self.fileSize)
665                 return chunk
666
667         def gotProgress(self, pos, max):
668                 self["progress"].writeValues(pos, max)
669
670                 newTime = time()
671                 # Check if we're called the first time (got total)
672                 lastTime = self.lastTime
673                 if lastTime == 0:
674                         self.lastTime = newTime
675
676                 # We dont want to update more often than every two sec (could be done by a timer, but this should give a more accurate result though it might lag)
677                 elif int(newTime - lastTime) >= 2:
678                         lastApprox = round(((pos - self.lastLength) / (newTime - lastTime) / 1024), 2)
679
680                         secLen = int(round(((max-pos) / 1024) / lastApprox))
681                         self["eta"].setText(_("ETA %d:%02d min") % (secLen / 60, secLen % 60))
682                         self["speed"].setText(_("%d kb/s") % (lastApprox))
683
684                         self.lastApprox = lastApprox
685                         self.lastLength = pos
686                         self.lastTime = newTime
687
688         def dataReceived(self, data):
689                 if not self.file:
690                         return
691
692                 self.currentLength += len(data)
693                 self.gotProgress(self.currentLength, self.fileSize)
694
695                 try:
696                         self.file.write(data)
697                 except IOError, ie:
698                         # TODO: handle this
699                         self.file = None
700                         raise ie
701
702         def cancelQuestion(self, res = None):
703                 res = res and res[1]
704                 if res:
705                         if res == 1:
706                                 self.file.close()
707                                 self.file = None
708                                 self.disconnect()
709                         self.close()
710
711         def cancel(self):
712                 if self.file is not None:
713                         self.session.openWithCallback(
714                                 self.cancelQuestion,
715                                 ChoiceBox,
716                                 title = _("A transfer is currently in progress.\nWhat do you want to do?"),
717                                 list = (
718                                         (_("Run in Background"), 2),
719                                         (_("Abort transfer"), 1),
720                                         (_("Cancel"), 0)
721                                 )
722                         )
723                         return
724
725                 self.disconnect()
726                 self.close()
727
728         def up(self):
729                 self[self.currlist].up()
730
731         def down(self):
732                 self[self.currlist].down()
733
734         def left(self):
735                 self[self.currlist].pageUp()
736
737         def right(self):
738                 self[self.currlist].pageDown()
739
740         def disconnect(self):
741                 if self.ftpclient:
742                         # XXX: according to the docs we should wait for the servers answer to our quit request, we just hope everything goes well here
743                         self.ftpclient.quit()
744                         self.ftpclient = None
745                         self["remote"].ftpclient = None
746                 self["remoteText"].setText(_("Remote (not connected)"))
747
748         def connectWrapper(self, ret):
749                 if ret:
750                         self.connect(ret[1])
751
752         def connect(self, server):
753                 self.disconnect()
754
755                 self.server = server
756
757                 if not server:
758                         return
759
760                 username = server.getUsername()
761                 if not username:
762                         username = 'anonymous'
763                         password = 'my@email.com'
764                 else:
765                         password = server.getPassword()
766
767                 host = server.getAddress()
768                 passive = server.getPassive()
769                 port = server.getPort()
770                 timeout = 30 # TODO: make configurable
771
772                 # XXX: we might want to add a guard so we don't try to connect to another host while a previous attempt is not timed out
773
774                 creator = ClientCreator(reactor, FTPClient, username, password, passive = passive)
775                 creator.connectTCP(host, port, timeout).addCallback(self.controlConnectionMade).addErrback(self.connectionFailed)
776
777         def controlConnectionMade(self, ftpclient):
778                 print "[FTPBrowser] connection established"
779                 self.ftpclient = ftpclient
780                 self["remote"].ftpclient = ftpclient
781                 self["remoteText"].setText(_("Remote"))
782
783                 self["remote"].changeDir(self.server.getPath())
784
785         def connectionFailed(self, *args):
786                 print "[FTPBrowser] connection failed", args
787
788                 self.server = None
789                 self["remoteText"].setText(_("Remote (not connected)"))
790                 self.session.open(
791                                 MessageBox,
792                                 _("Could not connect to ftp server!"),
793                                 type = MessageBox.TYPE_ERROR,
794                                 timeout = 3,
795                 )
796