initial checkin of a (still) very basic ftp browser
[enigma2-plugins.git] / ftpbrowser / src / FTPBrowser.py
1 # for localized messages
2 from . import _
3
4 # GUI (Screens)
5 from Screens.Screen import Screen
6 from Screens.MessageBox import MessageBox
7
8 # GUI (Components)
9 from Components.ActionMap import ActionMap, HelpableActionMap
10 from Components.Label import Label
11 from Components.FileList import FileList, FileEntryComponent
12 from Components.Button import Button
13
14 # FTP Client
15 from twisted.internet import reactor, defer
16 from twisted.internet.protocol import Protocol, ClientCreator
17 from twisted.protocols.ftp import FTPClient, FTPFileListProtocol
18
19 # For new and improved _parse
20 from urlparse import urlparse, urlunparse
21
22 def _parse(url, defaultPort = None):
23         url = url.strip()
24         parsed = urlparse(url)
25         scheme = parsed[0]
26         path = urlunparse(('','')+parsed[2:])
27
28         if defaultPort is None:
29                 if scheme == 'https':
30                         defaultPort = 443
31                 elif scheme == 'ftp':
32                         defaultPort = 21
33                 else:
34                         defaultPort = 80
35
36         host, port = parsed[1], defaultPort
37
38         if '@' in host:
39                 username, host = host.split('@')
40                 if ':' in username:
41                         username, password = username.split(':')
42                 else:
43                         password = ""
44         else:
45                 username = ""
46                 password = ""
47
48         if ':' in host:
49                 host, port = host.split(':')
50                 port = int(port)
51
52         if path == "":
53                 path = "/"
54
55         return scheme, host, port, path, username, password
56
57 class FTPFileList(FileList):
58         def __init__(self):
59                 self.ftpclient = None
60                 self.select = None
61                 FileList.__init__(self, "/")
62
63         def changeDir(self, directory, select = None):
64                 if self.ftpclient is None:
65                         self.list = []
66                         self.l.setList(self.list)
67                         return
68
69                 self.current_directory = directory
70                 self.select = select
71
72                 self.filelist = FTPFileListProtocol()
73                 d = self.ftpclient.list(directory, self.filelist)
74                 d.addCallback(self.listRcvd).addErrback(self.listFailed)
75
76         def listRcvd(self, *args):
77                 # XXX: we might want to sort this list and/or implement any other feature than 'list directories'
78                 self.list = [FileEntryComponent(name = file['filename'], absolute = self.current_directory + '/' + file['filename'], isDir = True if file['filetype'] == 'd' else False) for file in self.filelist.files]
79                 if self.current_directory != "/":
80                         self.list.insert(0, FileEntryComponent(name = "<" +_("Parent Directory") + ">", absolute = '/'.join(self.current_directory.split('/')[:-1]) + '/', isDir = True))
81                 self.l.setList(self.list)
82
83         def listFailed(self, *args):
84                 if self.current_directory != "/":
85                         self.list = [FileEntryComponent(name = "<" +_("Parent Directory") + ">", absolute = '/'.join(self.current_directory.split('/')[:-1]) + '/', isDir = True)]
86                 else:
87                         self.list = []
88                 self.l.setList(self.list)
89
90 class FTPBrowser(Screen):
91         skin = """
92                 <screen name="FTPBrowser" position="100,100" size="550,400" title="FTP Browser" >
93                         <widget name="local" position="20,10" size="220,350" scrollbarMode="showOnDemand" />
94                         <widget name="remote" position="245,10" size="220,350" scrollbarMode="showOnDemand" />
95                         <ePixmap name="red" position="0,360" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
96                         <ePixmap name="green" position="140,360" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
97                         <ePixmap name="yellow" position="280,360" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
98                         <ePixmap name="blue" position="420,360" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
99                         <widget name="key_red" position="0,360" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
100                         <widget name="key_green" position="140,360" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
101                         <widget name="key_yellow" position="280,360" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
102                         <widget name="key_blue" position="420,360" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
103                 </screen>"""
104
105         def __init__(self, session):
106                 Screen.__init__(self, session)
107                 self.ftpclient = None
108                 self.currlist = "remote"
109                 self["local"] = FileList("/media/hdd")
110                 self["remote"] = FTPFileList()
111                 self["key_red"] = Button("")
112                 self["key_green"] = Button("")
113                 self["key_yellow"] = Button("")
114                 self["key_blue"] = Button("")
115
116                 URI = "ftp://root@localhost:21" # TODO: make configurable
117                 self.connect(URI)
118
119                 self["OkCancelActions"] = HelpableActionMap(self, "OkCancelActions", 
120                         {
121                                 "ok": (self.ok, _("enter directory/get file/put file")),
122                                 "cancel": (self.cancel , _("close")),
123                         }, -2)
124
125                 self["ChannelSelectBaseActions"] = HelpableActionMap(self, "ChannelSelectBaseActions",
126                         {
127                                 "nextBouquet": (self.setLocal, _("Select local file list")),
128                                 "prevBouquet": (self.setRemote, _("Select remote file list")),
129                         })
130
131                 self["actions"] = ActionMap(["DirectionActions"],
132                         {
133                                 "up": self.up,
134                                 "down": self.down,
135                                 "left": self.left,
136                                 "right": self.right,
137                         }, -2)
138
139         def setLocal(self):
140                 self.currlist = "local"
141
142         def setRemote(self):
143                 self.currlist = "remote"
144
145         def ok(self):
146                 if self.currlist == "remote":
147                         # Get file/change directory
148                         if self["remote"].canDescent():
149                                 self["remote"].descent()
150                         else:
151                                 # XXX: implement
152                                 pass
153                 else:
154                         # Put file/change directory
155                         assert(self.currlist == "local")
156                         if self["local"].canDescent():
157                                 self["local"].descent()
158                         else:
159                                 # XXX: implement
160                                 pass
161
162         def cancel(self):
163                 # TODO: anything else?
164                 self.close()
165
166         def up(self):
167                 self[self.currlist].up()
168
169         def down(self):
170                 self[self.currlist].down()
171
172         def left(self):
173                 self[self.currlist].pageUp()
174
175         def right(self):
176                 self[self.currlist].pageDown()
177
178         def connect(self, address):
179                 self.ftpclient = None
180                 self["remote"].ftpclient = None
181
182                 scheme, host, port, path, username, password = _parse(address)
183                 if not username:
184                         username = 'anonymous'
185                         password = 'my@email.com'
186
187                 timeout = 30 # TODO: make configurable
188                 passive = True # TODO: make configurable
189                 creator = ClientCreator(reactor, FTPClient, username, password, passive = passive)
190
191                 creator.connectTCP(host, port, timeout).addCallback(self.controlConnectionMade).addErrback(self.connectionFailed)
192
193         def controlConnectionMade(self, ftpclient):
194                 print "[FTPBrowser] connection established"
195                 self.ftpclient = ftpclient
196                 self["remote"].ftpclient = ftpclient
197                 self["remote"].changeDir("/")
198
199         def connectionFailed(self, *args):
200                 print "[FTPBrowser] connection failed", args
201                 if args:
202                         print args[0]
203                 self.session.open(
204                                 MessageBox,
205                                 _("Could not connect to ftp server!\nClosing."),
206                                 type = MessageBox.TYPE_ERROR,
207                                 timeout = 3,
208                 )
209