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