change blue button to read "upload" when focus is on local files
[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(_("Upload"))
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                 self["key_blue"].setText(_("Upload"))
223
224         def setRemote(self):
225                 self.currlist = "remote"
226                 self["key_blue"].setText(_("Download"))
227
228         def okQuestion(self, res = None):
229                 if res:
230                         self.ok(force = True)
231
232         def getRemoteFile(self):
233                 remoteFile = self["remote"].getSelection()
234                 if not remoteFile or not remoteFile[0]:
235                         return None, None, None
236
237                 absRemoteFile = remoteFile[0]
238                 if remoteFile[1]:
239                         fileName = absRemoteFile.split('/')[-2]
240                 else:
241                         fileName = absRemoteFile.split('/')[-1]
242
243                 if len(remoteFile) == 3:
244                         fileSize = remoteFile[2]
245                 else:
246                         fileSize = 0
247
248                 return absRemoteFile, fileName, fileSize
249
250         def getLocalFile(self):
251                 # XXX: isn't this supposed to be an absolute filename? well, it's not for me :-/
252                 localFile = self["local"].getSelection()
253                 if not localFile:
254                         return None, None
255
256                 if localFile[1]:
257                         absLocalFile = localFile[0]
258                         fileName = absLocalFile.split('/')[-2]
259                 else:
260                         fileName = localFile[0]
261                         absLocalFile = self["local"].getCurrentDirectory() + fileName
262
263                 return absLocalFile, fileName
264
265         def renameCallback(self, newName = None):
266                 if not newName:
267                         return
268
269                 if self.currlist == "remote":
270                         absRemoteFile, fileName, fileSize = self.getRemoteFile()
271                         if not fileName:
272                                 return
273
274                         directory = self["remote"].getCurrentDirectory()
275                         sep = '/' if directory != '/' else ''
276                         newRemoteFile = directory + sep + newName
277
278                         def callback(ret = None):
279                                 AddPopup(_("Renamed %s to %s.") % (fileName, newName), MessageBox.TYPE_INFO, -1)
280                         def errback(ret = None):
281                                 AddPopup(_("Could not rename %s.") % (fileName), MessageBox.TYPE_ERROR, -1)
282
283                         self.ftpclient.rename(absRemoteFile, newRemoteFile).addCallback(callback).addErrback(errback)
284                 else:
285                         assert(self.currlist == "local")
286                         absLocalFile, fileName = self.getLocalFile()
287                         if not fileName:
288                                 return
289
290                         directory = self["local"].getCurrentDirectory()
291                         newLocalFile = os_path.join(directory, newName)
292
293                         try:
294                                 os_rename(absLocalFile, newLocalFile)
295                         except OSError, ose:
296                                 AddPopup(_("Could not rename %s.") % (fileName), MessageBox.TYPE_ERROR, -1)
297                         else:
298                                 AddPopup(_("Renamed %s to %s.") % (fileName, newName), MessageBox.TYPE_INFO, -1)
299
300         def rename(self):
301                 if not self.ftpclient or self.queue:
302                         return
303
304                 if self.currlist == "remote":
305                         absRemoteFile, fileName, fileSize = self.getRemoteFile()
306                         if not fileName:
307                                 return
308                 else:
309                         assert(self.currlist == "local")
310                         absLocalFile, fileName = self.getLocalFile()
311                         if not fileName:
312                                 return
313
314                 self.session.openWithCallback(
315                         self.renameCallback,
316                         NTIVirtualKeyBoard,
317                         title = _("Enter new filename:"),
318                         text = fileName,
319                 )
320
321         def deleteConfirmed(self, ret):
322                 if not ret:
323                         return
324
325                 if self.currlist == "remote":
326                         absRemoteFile, fileName, fileSize = self.getRemoteFile()
327                         if not fileName:
328                                 return
329
330                         def callback(ret = None):
331                                 AddPopup(_("Removed %s.") % (fileName), MessageBox.TYPE_INFO, -1)
332                         def errback(ret = None):
333                                 AddPopup(_("Could not delete %s.") % (fileName), MessageBox.TYPE_ERROR, -1)
334
335                         self.ftpclient.removeFile(absRemoteFile).addCallback(callback).addErrback(errback)
336                 else:
337                         assert(self.currlist == "local")
338                         absLocalFile, fileName = self.getLocalFile()
339                         if not fileName:
340                                 return
341
342                         try:
343                                 os_unlink(absLocalFile)
344                         except OSError, oe:
345                                 AddPopup(_("Could not delete %s.") % (fileName), MessageBox.TYPE_ERROR, -1)
346                         else:
347                                 AddPopup(_("Removed %s.") % (fileName), MessageBox.TYPE_INFO, -1)
348
349         def delete(self):
350                 if not self.ftpclient or self.queue:
351                         return
352
353                 if self.currlist == "remote":
354                         if self["remote"].canDescent():
355                                 self.session.open(
356                                         MessageBox,
357                                         _("Removing directories is not supported."),
358                                         MessageBox.TYPE_WARNING
359                                 )
360                                 return
361
362                         absRemoteFile, fileName, fileSize = self.getRemoteFile()
363                         if not fileName:
364                                 return
365                 else:
366                         assert(self.currlist == "local")
367                         if self["local"].canDescent():
368                                 self.session.open(
369                                         MessageBox,
370                                         _("Removing directories is not supported."),
371                                         MessageBox.TYPE_WARNING
372                                 )
373                                 return
374
375                         absLocalFile, fileName = self.getLocalFile()
376                         if not fileName:
377                                 return
378
379                 self.session.openWithCallback(
380                         self.deleteConfirmed,
381                         MessageBox,
382                         _("Are you sure you want to delete %s?") % (fileName)
383                 )
384
385         def transferListRcvd(self, res, filelist):
386                 remoteDirectory, _, _ = self.getRemoteFile()
387                 localDirectory = self["local"].getCurrentDirectory()
388
389                 self.queue = [(True, remoteDirectory + file["filename"], localDirectory + file["filename"], file["size"]) for file in filelist.files if file["filetype"] == "-"]
390                 self.nextQueue()
391         
392         def nextQueue(self):
393                 if self.queue:
394                         top = self.queue[0]
395                         self.queue = self.queue[1:]
396                         if top[0]:
397                                 self.getFile(*top[1:])
398                         else:
399                                 self.putFile(*top[1:])
400
401         def transferListFailed(self, res = None):
402                 self.queue = None
403                 AddPopup(_("Could not obtain list of files."), MessageBox.TYPE_ERROR, -1)
404
405         def transfer(self):
406                 if not self.ftpclient or self.queue:
407                         return
408
409                 if self.currlist == "remote":
410                         # single file transfer is implemented in self.ok
411                         if not self["remote"].canDescent():
412                                 return self.ok()
413                         else:
414                                 absRemoteFile, fileName, fileSize = self.getRemoteFile()
415                                 if not fileName:
416                                         return
417
418                                 filelist = FTPFileListProtocol()
419                                 d = self.ftpclient.list(absRemoteFile, filelist)
420                                 d.addCallback(self.transferListRcvd, filelist).addErrback(self.transferListFailed)
421                 else:
422                         assert(self.currlist == "local")
423                         # single file transfer is implemented in self.ok
424                         if not self["local"].canDescent():
425                                 return self.ok()
426                         else:
427                                 localDirectory, _ = self.getLocalFile()
428                                 remoteDirectory = self["remote"].getCurrentDirectory()
429
430                                 def remoteFileExists(absName):
431                                         for file in self["remote"].getFileList():
432                                                 if file[0][0] == absName:
433                                                         return True
434                                         return False
435
436                                 self.queue = [(False, remoteDirectory + file, localDirectory + file, remoteFileExists(remoteDirectory + file)) for file in os_listdir(localDirectory) if os_path.isfile(localDirectory + file)]
437                                 self.nextQueue()
438
439
440         def getFileCallback(self, ret, absRemoteFile, absLocalFile, fileSize):
441                 if not ret:
442                         self.nextQueue()
443                 else:
444                         self.getFile(absRemoteFile, absLocalFile, fileSize, force=True)
445
446         def getFile(self, absRemoteFile, absLocalFile, fileSize, force=False):
447                 if not force and os_path.exists(absLocalFile):
448                         fileName = absRemoteFile.split('/')[-1]
449                         AddNotificationWithCallback(
450                                 lambda ret: self.getFileCallback(ret, absRemoteFile, absLocalFile, fileSize),
451                                 MessageBox,
452                                 _("A file with this name (%s) already exists locally.\nDo you want to overwrite it?") % (fileName),
453                         )
454                 else:
455                         self.currentLength = 0
456                         self.lastLength = 0
457                         self.lastTime = 0
458                         self.lastApprox = 0
459                         self.fileSize = fileSize
460
461                         try:
462                                 self.file = open(absLocalFile, 'w')
463                         except IOError, ie:
464                                 # TODO: handle this
465                                 raise ie
466                         else:
467                                 d = self.ftpclient.retrieveFile(absRemoteFile, self, offset = 0)
468                                 d.addCallback(self.getFinished).addErrback(self.getFailed)
469
470         def putFileCallback(self, ret, absRemoteFile, absLocalFile, remoteFileExists):
471                 if not ret:
472                         self.nextQueue()
473                 else:
474                         self.putFile(absRemoteFile, absLocalFile, remoteFileExists, force=True)
475
476         def putFile(self, absRemoteFile, absLocalFile, remoteFileExists, force=False):
477                 if not force and remoteFileExists:
478                         fileName = absRemoteFile.split('/')[-1]
479                         AddNotificationWithCallback(
480                                 lambda ret: self.putFileCallback(ret, absRemoteFile, absLocalFile, remoteFileExists),
481                                 MessageBox,
482                                 _("A file with this name (%s) already exists on the remote host.\nDo you want to overwrite it?") % (fileName),
483                         )
484                 else:
485                         self.currentLength = 0
486                         self.lastLength = 0
487                         self.lastTime = 0
488                         self.lastApprox = 0
489
490                         def sendfile(consumer, fileObj):
491                                 FileSender().beginFileTransfer(fileObj, consumer, transform = self.putProgress).addCallback(  
492                                         lambda _: consumer.finish()).addCallback(
493                                         self.putComplete).addErrback(self.putFailed)
494
495                         try:
496                                 self.fileSize = int(os_path.getsize(absLocalFile))
497                                 self.file = open(absLocalFile, 'rb')
498                         except (IOError, OSError), e:
499                                 # TODO: handle this
500                                 raise e
501                         else:
502                                 dC, dL = self.ftpclient.storeFile(absRemoteFile)
503                                 dC.addCallback(sendfile, self.file)
504
505         def ok(self, force = False):
506                 if self.queue:
507                         return
508
509                 if self.currlist == "remote":
510                         if not self.ftpclient:
511                                 return
512
513                         # Get file/change directory
514                         if self["remote"].canDescent():
515                                 self["remote"].descent()
516                         else:
517                                 if self.file:
518                                         self.session.open(
519                                                 MessageBox,
520                                                 _("There already is an active transfer."),
521                                                 type = MessageBox.TYPE_WARNING
522                                         )
523                                         return
524
525                                 absRemoteFile, fileName, fileSize = self.getRemoteFile()
526                                 if not fileName:
527                                         return
528
529                                 absLocalFile = self["local"].getCurrentDirectory() + fileName
530
531                                 self.getFile(absRemoteFile, absLocalFile, fileSize)
532                 else:
533                         # Put file/change directory
534                         assert(self.currlist == "local")
535                         if self["local"].canDescent():
536                                 self["local"].descent()
537                         else:
538                                 if not self.ftpclient:
539                                         return
540
541                                 if self.file:
542                                         self.session.open(
543                                                 MessageBox,
544                                                 _("There already is an active transfer."),
545                                                 type = MessageBox.TYPE_WARNING
546                                         )
547                                         return
548
549                                 if not self["remote"].isValid:
550                                         return
551
552                                 absLocalFile, fileName = self.getLocalFile()
553                                 if not fileName:
554                                         return
555
556                                 directory = self["remote"].getCurrentDirectory()
557                                 sep = '/' if directory != '/' else ''
558                                 absRemoteFile = directory + sep + fileName
559
560                                 def remoteFileExists(absName):
561                                         for file in self["remote"].getFileList():
562                                                 if file[0][0] == absName:
563                                                         return True
564                                         return False
565
566                                 self.putFile(absRemoteFile, absLocalFile, remoteFileExists(absRemoteFile))
567
568         def transferFinished(self, msg, type, toRefresh):
569                 AddPopup(msg, type, -1)
570
571                 self["eta"].setText("")
572                 self["speed"].setText("")
573                 self["progress"].invalidate()
574                 self[toRefresh].refresh()
575                 self.file.close()
576                 self.file = None
577
578         def putComplete(self, *args):
579                 if self.queue:
580                         self.file.close()
581                         self.file = None
582
583                         self.nextQueue()
584                 else:
585                         self.transferFinished(
586                                 _("Upload finished."),
587                                 MessageBox.TYPE_INFO,
588                                 "remote"
589                         )
590
591         def putFailed(self, *args):
592                 # NOTE: we continue uploading but notify the user of every error though
593                 # we only display one success notification
594                 self.transferFinished(
595                         _("Error during download."),
596                         MessageBox.TYPE_ERROR,
597                         "remote"
598                 )
599                 if self.queue:
600                         self.nextQueue()
601
602         def getFinished(self, *args):
603                 if self.queue:
604                         self.file.close()
605                         self.file = None
606
607                         self.nextQueue()
608                 else:
609                         self.transferFinished(
610                                 _("Download finished."),
611                                 MessageBox.TYPE_INFO,
612                                 "local"
613                         )
614
615         def getFailed(self, *args):
616                 # NOTE: we continue downloading but notify the user of every error though
617                 # we only display one success notification
618                 self.transferFinished(
619                         _("Error during download."),
620                         MessageBox.TYPE_ERROR,
621                         "local"
622                 )
623                 if self.queue:
624                         self.nextQueue()
625
626         def putProgress(self, chunk):
627                 self.currentLength += len(chunk)
628                 self.gotProgress(self.currentLength, self.fileSize)
629                 return chunk
630
631         def gotProgress(self, pos, max):
632                 self["progress"].writeValues(pos, max)
633
634                 newTime = time()
635                 # Check if we're called the first time (got total)
636                 lastTime = self.lastTime
637                 if lastTime == 0:
638                         self.lastTime = newTime
639
640                 # 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)
641                 elif int(newTime - lastTime) >= 2:
642                         lastApprox = round(((pos - self.lastLength) / (newTime - lastTime) / 1024), 2)
643
644                         secLen = int(round(((max-pos) / 1024) / lastApprox))
645                         self["eta"].setText(_("ETA %d:%02d min") % (secLen / 60, secLen % 60))
646                         self["speed"].setText(_("%d kb/s") % (lastApprox))
647
648                         self.lastApprox = lastApprox
649                         self.lastLength = pos
650                         self.lastTime = newTime
651
652         def dataReceived(self, data):
653                 if not self.file:
654                         return
655
656                 self.currentLength += len(data)
657                 self.gotProgress(self.currentLength, self.fileSize)
658
659                 try:
660                         self.file.write(data)
661                 except IOError, ie:
662                         # TODO: handle this
663                         self.file = None
664                         raise ie
665
666         def cancelQuestion(self, res = None):
667                 res = res and res[1]
668                 if res:
669                         if res == 1:
670                                 self.file.close()
671                                 self.file = None
672                                 self.disconnect()
673                         self.close()
674
675         def cancel(self):
676                 if self.file is not None:
677                         self.session.openWithCallback(
678                                 self.cancelQuestion,
679                                 ChoiceBox,
680                                 title = _("A transfer is currently in progress.\nWhat do you want to do?"),
681                                 list = (
682                                         (_("Run in Background"), 2),
683                                         (_("Abort transfer"), 1),
684                                         (_("Cancel"), 0)
685                                 )
686                         )
687                         return
688
689                 self.disconnect()
690                 self.close()
691
692         def up(self):
693                 self[self.currlist].up()
694
695         def down(self):
696                 self[self.currlist].down()
697
698         def left(self):
699                 self[self.currlist].pageUp()
700
701         def right(self):
702                 self[self.currlist].pageDown()
703
704         def disconnect(self):
705                 if self.ftpclient:
706                         # XXX: according to the docs we should wait for the servers answer to our quit request, we just hope everything goes well here
707                         self.ftpclient.quit()
708                         self.ftpclient = None
709                         self["remote"].ftpclient = None
710                 self["remoteText"].setText(_("Remote (not connected)"))
711
712         def connectWrapper(self, ret):
713                 if ret:
714                         self.connect(ret[1])
715
716         def connect(self, server):
717                 self.disconnect()
718
719                 self.server = server
720
721                 if not server:
722                         return
723
724                 username = server.getUsername()
725                 if not username:
726                         username = 'anonymous'
727                         password = 'my@email.com'
728                 else:
729                         password = server.getPassword()
730
731                 host = server.getAddress()
732                 passive = server.getPassive()
733                 port = server.getPort()
734                 timeout = 30 # TODO: make configurable
735
736                 # 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
737
738                 creator = ClientCreator(reactor, FTPClient, username, password, passive = passive)
739                 creator.connectTCP(host, port, timeout).addCallback(self.controlConnectionMade).addErrback(self.connectionFailed)
740
741         def controlConnectionMade(self, ftpclient):
742                 print "[FTPBrowser] connection established"
743                 self.ftpclient = ftpclient
744                 self["remote"].ftpclient = ftpclient
745                 self["remoteText"].setText(_("Remote"))
746
747                 self["remote"].changeDir(self.server.getPath())
748
749         def connectionFailed(self, *args):
750                 print "[FTPBrowser] connection failed", args
751
752                 self.server = None
753                 self["remoteText"].setText(_("Remote (not connected)"))
754                 self.session.open(
755                                 MessageBox,
756                                 _("Could not connect to ftp server!"),
757                                 type = MessageBox.TYPE_ERROR,
758                                 timeout = 3,
759                 )
760