Merged revisions 82952,82954 via svnmerge from
[python/dscho.git] / Demo / sockets / gopher.py
blobc287319c2ea1869c651ba06886f92c1017d7394a
1 #! /usr/bin/env python
3 # A simple gopher client.
5 # Usage: gopher [ [selector] host [port] ]
7 import sys
8 import os
9 import socket
11 # Default selector, host and port
12 DEF_SELECTOR = ''
13 DEF_HOST = 'gopher.micro.umn.edu'
14 DEF_PORT = 70
16 # Recognized file types
17 T_TEXTFILE = '0'
18 T_MENU = '1'
19 T_CSO = '2'
20 T_ERROR = '3'
21 T_BINHEX = '4'
22 T_DOS = '5'
23 T_UUENCODE = '6'
24 T_SEARCH = '7'
25 T_TELNET = '8'
26 T_BINARY = '9'
27 T_REDUNDANT = '+'
28 T_SOUND = 's'
30 # Dictionary mapping types to strings
31 typename = {'0': '<TEXT>', '1': '<DIR>', '2': '<CSO>', '3': '<ERROR>', \
32 '4': '<BINHEX>', '5': '<DOS>', '6': '<UUENCODE>', '7': '<SEARCH>', \
33 '8': '<TELNET>', '9': '<BINARY>', '+': '<REDUNDANT>', 's': '<SOUND>'}
35 # Oft-used characters and strings
36 CRLF = '\r\n'
37 TAB = '\t'
39 # Open a TCP connection to a given host and port
40 def open_socket(host, port):
41 if not port:
42 port = DEF_PORT
43 elif type(port) == type(''):
44 port = int(port)
45 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
46 s.connect((host, port))
47 return s
49 # Send a selector to a given host and port, return a file with the reply
50 def send_request(selector, host, port):
51 s = open_socket(host, port)
52 s.send(selector + CRLF)
53 s.shutdown(1)
54 return s.makefile('r')
56 # Get a menu in the form of a list of entries
57 def get_menu(selector, host, port):
58 f = send_request(selector, host, port)
59 list = []
60 while 1:
61 line = f.readline()
62 if not line:
63 print('(Unexpected EOF from server)')
64 break
65 if line[-2:] == CRLF:
66 line = line[:-2]
67 elif line[-1:] in CRLF:
68 line = line[:-1]
69 if line == '.':
70 break
71 if not line:
72 print('(Empty line from server)')
73 continue
74 typechar = line[0]
75 parts = line[1:].split(TAB)
76 if len(parts) < 4:
77 print('(Bad line from server: %r)' % (line,))
78 continue
79 if len(parts) > 4:
80 print('(Extra info from server: %r)' % (parts[4:],))
81 parts.insert(0, typechar)
82 list.append(parts)
83 f.close()
84 return list
86 # Get a text file as a list of lines, with trailing CRLF stripped
87 def get_textfile(selector, host, port):
88 list = []
89 get_alt_textfile(selector, host, port, list.append)
90 return list
92 # Get a text file and pass each line to a function, with trailing CRLF stripped
93 def get_alt_textfile(selector, host, port, func):
94 f = send_request(selector, host, port)
95 while 1:
96 line = f.readline()
97 if not line:
98 print('(Unexpected EOF from server)')
99 break
100 if line[-2:] == CRLF:
101 line = line[:-2]
102 elif line[-1:] in CRLF:
103 line = line[:-1]
104 if line == '.':
105 break
106 if line[:2] == '..':
107 line = line[1:]
108 func(line)
109 f.close()
111 # Get a binary file as one solid data block
112 def get_binary(selector, host, port):
113 f = send_request(selector, host, port)
114 data = f.read()
115 f.close()
116 return data
118 # Get a binary file and pass each block to a function
119 def get_alt_binary(selector, host, port, func, blocksize):
120 f = send_request(selector, host, port)
121 while 1:
122 data = f.read(blocksize)
123 if not data:
124 break
125 func(data)
127 # A *very* simple interactive browser
129 # Browser main command, has default arguments
130 def browser(*args):
131 selector = DEF_SELECTOR
132 host = DEF_HOST
133 port = DEF_PORT
134 n = len(args)
135 if n > 0 and args[0]:
136 selector = args[0]
137 if n > 1 and args[1]:
138 host = args[1]
139 if n > 2 and args[2]:
140 port = args[2]
141 if n > 3:
142 raise RuntimeError('too many args')
143 try:
144 browse_menu(selector, host, port)
145 except socket.error as msg:
146 print('Socket error:', msg)
147 sys.exit(1)
148 except KeyboardInterrupt:
149 print('\n[Goodbye]')
151 # Browse a menu
152 def browse_menu(selector, host, port):
153 list = get_menu(selector, host, port)
154 while 1:
155 print('----- MENU -----')
156 print('Selector:', repr(selector))
157 print('Host:', host, ' Port:', port)
158 print()
159 for i in range(len(list)):
160 item = list[i]
161 typechar, description = item[0], item[1]
162 print(repr(i+1).rjust(3) + ':', description, end=' ')
163 if typechar in typename:
164 print(typename[typechar])
165 else:
166 print('<TYPE=' + repr(typechar) + '>')
167 print()
168 while 1:
169 try:
170 str = input('Choice [CR == up a level]: ')
171 except EOFError:
172 print()
173 return
174 if not str:
175 return
176 try:
177 choice = int(str)
178 except ValueError:
179 print('Choice must be a number; try again:')
180 continue
181 if not 0 < choice <= len(list):
182 print('Choice out of range; try again:')
183 continue
184 break
185 item = list[choice-1]
186 typechar = item[0]
187 [i_selector, i_host, i_port] = item[2:5]
188 if typechar in typebrowser:
189 browserfunc = typebrowser[typechar]
190 try:
191 browserfunc(i_selector, i_host, i_port)
192 except (IOError, socket.error):
193 t, v, tb = sys.exc_info()
194 print('***', t, ':', v)
195 else:
196 print('Unsupported object type')
198 # Browse a text file
199 def browse_textfile(selector, host, port):
200 x = None
201 try:
202 p = os.popen('${PAGER-more}', 'w')
203 x = SaveLines(p)
204 get_alt_textfile(selector, host, port, x.writeln)
205 except IOError as msg:
206 print('IOError:', msg)
207 if x:
208 x.close()
209 f = open_savefile()
210 if not f:
211 return
212 x = SaveLines(f)
213 try:
214 get_alt_textfile(selector, host, port, x.writeln)
215 print('Done.')
216 except IOError as msg:
217 print('IOError:', msg)
218 x.close()
220 def raw_input(prompt):
221 sys.stdout.write(prompt)
222 sys.stdout.flush()
223 return sys.stdin.readline()
225 # Browse a search index
226 def browse_search(selector, host, port):
227 while 1:
228 print('----- SEARCH -----')
229 print('Selector:', repr(selector))
230 print('Host:', host, ' Port:', port)
231 print()
232 try:
233 query = input('Query [CR == up a level]: ')
234 except EOFError:
235 print()
236 break
237 query = query.strip()
238 if not query:
239 break
240 if '\t' in query:
241 print('Sorry, queries cannot contain tabs')
242 continue
243 browse_menu(selector + TAB + query, host, port)
245 # "Browse" telnet-based information, i.e. open a telnet session
246 def browse_telnet(selector, host, port):
247 if selector:
248 print('Log in as', repr(selector))
249 if type(port) != type(''):
250 port = repr(port)
251 sts = os.system('set -x; exec telnet ' + host + ' ' + port)
252 if sts:
253 print('Exit status:', sts)
255 # "Browse" a binary file, i.e. save it to a file
256 def browse_binary(selector, host, port):
257 f = open_savefile()
258 if not f:
259 return
260 x = SaveWithProgress(f)
261 get_alt_binary(selector, host, port, x.write, 8*1024)
262 x.close()
264 # "Browse" a sound file, i.e. play it or save it
265 def browse_sound(selector, host, port):
266 browse_binary(selector, host, port)
268 # Dictionary mapping types to browser functions
269 typebrowser = {'0': browse_textfile, '1': browse_menu, \
270 '4': browse_binary, '5': browse_binary, '6': browse_textfile, \
271 '7': browse_search, \
272 '8': browse_telnet, '9': browse_binary, 's': browse_sound}
274 # Class used to save lines, appending a newline to each line
275 class SaveLines:
276 def __init__(self, f):
277 self.f = f
278 def writeln(self, line):
279 self.f.write(line + '\n')
280 def close(self):
281 sts = self.f.close()
282 if sts:
283 print('Exit status:', sts)
285 # Class used to save data while showing progress
286 class SaveWithProgress:
287 def __init__(self, f):
288 self.f = f
289 def write(self, data):
290 sys.stdout.write('#')
291 sys.stdout.flush()
292 self.f.write(data)
293 def close(self):
294 print()
295 sts = self.f.close()
296 if sts:
297 print('Exit status:', sts)
299 # Ask for and open a save file, or return None if not to save
300 def open_savefile():
301 try:
302 savefile = input( \
303 'Save as file [CR == don\'t save; |pipeline or ~user/... OK]: ')
304 except EOFError:
305 print()
306 return None
307 savefile = savefile.strip()
308 if not savefile:
309 return None
310 if savefile[0] == '|':
311 cmd = savefile[1:].strip()
312 try:
313 p = os.popen(cmd, 'w')
314 except IOError as msg:
315 print(repr(cmd), ':', msg)
316 return None
317 print('Piping through', repr(cmd), '...')
318 return p
319 if savefile[0] == '~':
320 savefile = os.path.expanduser(savefile)
321 try:
322 f = open(savefile, 'w')
323 except IOError as msg:
324 print(repr(savefile), ':', msg)
325 return None
326 print('Saving to', repr(savefile), '...')
327 return f
329 # Test program
330 def test():
331 if sys.argv[4:]:
332 print('usage: gopher [ [selector] host [port] ]')
333 sys.exit(2)
334 elif sys.argv[3:]:
335 browser(sys.argv[1], sys.argv[2], sys.argv[3])
336 elif sys.argv[2:]:
337 try:
338 port = int(sys.argv[2])
339 selector = ''
340 host = sys.argv[1]
341 except ValueError:
342 selector = sys.argv[1]
343 host = sys.argv[2]
344 port = ''
345 browser(selector, host, port)
346 elif sys.argv[1:]:
347 browser('', sys.argv[1])
348 else:
349 browser()
351 # Call the test program as a main program
352 test()