root / trunk / twisted / conch / scripts / cftp.py

Revision 25487, 26.7 kB (checked in by mwh, 7 months ago)

Merge cftp-put-over-longer-file-2519: fix bugs in cftp when uploading shorter files over longer

Fix two places in the conch sftp client where it failed to truncate a file that
was being overwritten by an upload. Fix is from the reporter, tests are from
mwh.

Author: johnnypops, mwh
Reviewer: therve
Fixes: #2519

Line 
1 # -*- test-case-name: twisted.conch.test.test_cftp -*-
2 #
3 # Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4 # See LICENSE for details.
5
6 #
7 # $Id: cftp.py,v 1.65 2004/03/11 00:29:14 z3p Exp $
8
9 #""" Implementation module for the `cftp` command.
10 #"""
11 from twisted.conch.client import agent, connect, default, options
12 from twisted.conch.error import ConchError
13 from twisted.conch.ssh import connection, common
14 from twisted.conch.ssh import channel, filetransfer
15 from twisted.protocols import basic
16 from twisted.internet import reactor, stdio, defer, utils
17 from twisted.python import log, usage, failure
18
19 import os, sys, getpass, struct, tty, fcntl, base64, signal, stat, errno
20 import fnmatch, pwd, time, glob
21
22 class ClientOptions(options.ConchOptions):
23    
24     synopsis = """Usage:   cftp [options] [user@]host
25          cftp [options] [user@]host[:dir[/]]
26          cftp [options] [user@]host[:file [localfile]]
27 """
28    
29     optParameters = [
30                     ['buffersize', 'B', 32768, 'Size of the buffer to use for sending/receiving.'],
31                     ['batchfile', 'b', None, 'File to read commands from, or \'-\' for stdin.'],
32                     ['requests', 'R', 5, 'Number of requests to make before waiting for a reply.'],
33                     ['subsystem', 's', 'sftp', 'Subsystem/server program to connect to.']]
34     zsh_altArgDescr = {"buffersize":"Size of send/receive buffer (default: 32768)"}
35     #zsh_multiUse = ["foo", "bar"]
36     #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
37     #zsh_actions = {"foo":'_files -g "*.foo"', "bar":"(one two three)"}
38     #zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
39     zsh_extras = ['2::localfile:{if [[ $words[1] == *:* ]]; then; _files; fi}']
40
41     def parseArgs(self, host, localPath=None):
42         self['remotePath'] = ''
43         if ':' in host:
44             host, self['remotePath'] = host.split(':', 1)
45             self['remotePath'].rstrip('/')
46         self['host'] = host
47         self['localPath'] = localPath
48
49 def run():
50 #    import hotshot
51 #    prof = hotshot.Profile('cftp.prof')
52 #    prof.start()
53     args = sys.argv[1:]
54     if '-l' in args: # cvs is an idiot
55         i = args.index('-l')
56         args = args[i:i+2]+args
57         del args[i+2:i+4]
58     options = ClientOptions()
59     try:
60         options.parseOptions(args)
61     except usage.UsageError, u:
62         print 'ERROR: %s' % u
63         sys.exit(1)
64     if options['log']:
65         realout = sys.stdout
66         log.startLogging(sys.stderr)
67         sys.stdout = realout
68     else:
69         log.discardLogs()
70     doConnect(options)
71     reactor.run()
72 #    prof.stop()
73 #    prof.close()
74
75 def handleError():
76     from twisted.python import failure
77     global exitStatus
78     exitStatus = 2
79     try:
80         reactor.stop()
81     except: pass
82     log.err(failure.Failure())
83     raise
84
85 def doConnect(options):
86 #    log.deferr = handleError # HACK
87     if '@' in options['host']:
88         options['user'], options['host'] = options['host'].split('@',1)
89     host = options['host']
90     if not options['user']:
91         options['user'] = getpass.getuser()
92     if not options['port']:
93         options['port'] = 22
94     else:
95         options['port'] = int(options['port'])
96     host = options['host']
97     port = options['port']
98     conn = SSHConnection()
99     conn.options = options
100     vhk = default.verifyHostKey
101     uao = default.SSHUserAuthClient(options['user'], options, conn)
102     connect.connect(host, port, options, vhk, uao).addErrback(_ebExit)
103
104 def _ebExit(f):
105     #global exitStatus
106     if hasattr(f.value, 'value'):
107         s = f.value.value
108     else:
109         s = str(f)
110     print s
111     #exitStatus = "conch: exiting with error %s" % f
112     try:
113         reactor.stop()
114     except: pass
115
116 def _ignore(*args): pass
117
118 class FileWrapper:
119
120     def __init__(self, f):
121         self.f = f
122         self.total = 0.0
123         f.seek(0, 2) # seek to the end
124         self.size = f.tell()
125
126     def __getattr__(self, attr):
127         return getattr(self.f, attr)
128
129 class StdioClient(basic.LineReceiver):
130
131     ps = 'cftp> '
132     delimiter = '\n'
133
134     def __init__(self, client, f = None):
135         self.client = client
136         self.currentDirectory = ''
137         self.file = f
138         self.useProgressBar = (not f and 1) or 0
139
140     def connectionMade(self):
141         self.client.realPath('').addCallback(self._cbSetCurDir)
142
143     def _cbSetCurDir(self, path):
144         self.currentDirectory = path
145         self._newLine()
146
147     def lineReceived(self, line):
148         if self.client.transport.localClosed:
149             return
150         log.msg('got line %s' % repr(line))
151         line = line.lstrip()
152         if not line:
153             self._newLine()
154             return
155         if self.file and line.startswith('-'):
156             self.ignoreErrors = 1
157             line = line[1:]
158         else:
159             self.ignoreErrors = 0
160         if ' ' in line:
161             command, rest = line.split(' ', 1)
162             rest = rest.lstrip()
163         else:
164             command, rest = line, ''
165         if command.startswith('!'): # command
166             f = self.cmd_EXEC
167             rest = (command[1:] + ' ' + rest).strip()
168         else:
169             command = command.upper()
170             log.msg('looking up cmd %s' % command)
171             f = getattr(self, 'cmd_%s' % command, None)
172         if f is not None:
173             d = defer.maybeDeferred(f, rest)
174             d.addCallback(self._cbCommand)
175             d.addErrback(self._ebCommand)
176         else:
177             self._ebCommand(failure.Failure(NotImplementedError(
178                 "No command called `%s'" % command)))
179             self._newLine()
180
181     def _printFailure(self, f):
182         log.msg(f)
183         e = f.trap(NotImplementedError, filetransfer.SFTPError, OSError, IOError)
184         if e == NotImplementedError:
185             self.transport.write(self.cmd_HELP(''))
186         elif e == filetransfer.SFTPError:
187             self.transport.write("remote error %i: %s\n" %
188                     (f.value.code, f.value.message))
189         elif e in (OSError, IOError):
190             self.transport.write("local error %i: %s\n" %
191                     (f.value.errno, f.value.strerror))
192
193     def _newLine(self):
194         if self.client.transport.localClosed:
195             return
196         self.transport.write(self.ps)
197         self.ignoreErrors = 0
198         if self.file:
199             l = self.file.readline()
200             if not l:
201                 self.client.transport.loseConnection()
202             else:
203                 self.transport.write(l)
204                 self.lineReceived(l.strip())
205
206     def _cbCommand(self, result):
207         if result is not None:
208             self.transport.write(result)
209             if not result.endswith('\n'):
210                 self.transport.write('\n')
211         self._newLine()
212
213     def _ebCommand(self, f):
214         self._printFailure(f)
215         if self.file and not self.ignoreErrors:
216             self.client.transport.loseConnection()
217         self._newLine()
218
219     def cmd_CD(self, path):
220         path, rest = self._getFilename(path)
221         if not path.endswith('/'):
222             path += '/'
223         newPath = path and os.path.join(self.currentDirectory, path) or ''
224         d = self.client.openDirectory(newPath)
225         d.addCallback(self._cbCd)
226         d.addErrback(self._ebCommand)
227         return d
228
229     def _cbCd(self, directory):
230         directory.close()
231         d = self.client.realPath(directory.name)
232         d.addCallback(self._cbCurDir)
233         return d
234
235     def _cbCurDir(self, path):
236         self.currentDirectory = path
237
238     def cmd_CHGRP(self, rest):
239         grp, rest = rest.split(None, 1)
240         path, rest = self._getFilename(rest)
241         grp = int(grp)
242         d = self.client.getAttrs(path)
243         d.addCallback(self._cbSetUsrGrp, path, grp=grp)
244         return d
245    
246     def cmd_CHMOD(self, rest):
247         mod, rest = rest.split(None, 1)
248         path, rest = self._getFilename(rest)
249         mod = int(mod, 8)
250         d = self.client.setAttrs(path, {'permissions':mod})
251         d.addCallback(_ignore)
252         return d
253    
254     def cmd_CHOWN(self, rest):
255         usr, rest = rest.split(None, 1)
256         path, rest = self._getFilename(rest)
257         usr = int(usr)
258         d = self.client.getAttrs(path)
259         d.addCallback(self._cbSetUsrGrp, path, usr=usr)
260         return d
261    
262     def _cbSetUsrGrp(self, attrs, path, usr=None, grp=None):
263         new = {}
264         new['uid'] = (usr is not None) and usr or attrs['uid']
265         new['gid'] = (grp is not None) and grp or attrs['gid']
266         d = self.client.setAttrs(path, new)
267         d.addCallback(_ignore)
268         return d
269
270     def cmd_GET(self, rest):
271         remote, rest = self._getFilename(rest)
272         if '*' in remote or '?' in remote: # wildcard
273             if rest:
274                 local, rest = self._getFilename(rest)
275                 if not os.path.isdir(local):
276                     return "Wildcard get with non-directory target."
277             else:
278                 local = ''
279             d = self._remoteGlob(remote)
280             d.addCallback(self._cbGetMultiple, local)
281             return d
282         if rest:
283             local, rest = self._getFilename(rest)
284         else:
285             local = os.path.split(remote)[1]
286         log.msg((remote, local))
287         lf = file(local, 'w', 0)
288         path = os.path.join(self.currentDirectory, remote)
289         d = self.client.openFile(path, filetransfer.FXF_READ, {})
290         d.addCallback(self._cbGetOpenFile, lf)
291         d.addErrback(self._ebCloseLf, lf)
292         return d
293
294     def _cbGetMultiple(self, files, local):
295         #if self._useProgressBar: # one at a time
296         # XXX this can be optimized for times w/o progress bar
297         return self._cbGetMultipleNext(None, files, local)
298
299     def _cbGetMultipleNext(self, res, files, local):
300         if isinstance(res, failure.Failure):
301             self._printFailure(res)
302         elif res:
303             self.transport.write(res)
304             if not res.endswith('\n'):
305                 self.transport.write('\n')
306         if not files:
307             return
308         f = files.pop(0)[0]
309         lf = file(os.path.join(local, os.path.split(f)[1]), 'w', 0)
310         path = os.path.join(self.currentDirectory, f)
311         d = self.client.openFile(path, filetransfer.FXF_READ, {})
312         d.addCallback(self._cbGetOpenFile, lf)
313         d.addErrback(self._ebCloseLf, lf)
314         d.addBoth(self._cbGetMultipleNext, files, local)
315         return d
316
317     def _ebCloseLf(self, f, lf):
318         lf.close()
319         return f
320
321     def _cbGetOpenFile(self, rf, lf):
322         return rf.getAttrs().addCallback(self._cbGetFileSize, rf, lf)
323
324     def _cbGetFileSize(self, attrs, rf, lf):
325         if not stat.S_ISREG(attrs['permissions']):
326             rf.close()
327             lf.close()
328             return "Can't get non-regular file: %s" % rf.name
329         rf.size = attrs['size']
330         bufferSize = self.client.transport.conn.options['buffersize']
331         numRequests = self.client.transport.conn.options['requests']
332         rf.total = 0.0
333         dList = []
334         chunks = []
335         startTime = time.time()
336         for i in range(numRequests):           
337             d = self._cbGetRead('', rf, lf, chunks, 0, bufferSize, startTime)
338             dList.append(d)
339         dl = defer.DeferredList(dList, fireOnOneErrback=1)
340         dl.addCallback(self._cbGetDone, rf, lf)
341         return dl
342
343     def _getNextChunk(self, chunks):
344         end = 0
345         for chunk in chunks:
346             if end == 'eof':
347                 return # nothing more to get
348             if end != chunk[0]:
349                 i = chunks.index(chunk)
350                 chunks.insert(i, (end, chunk[0]))
351                 return (end, chunk[0] - end)
352             end = chunk[1]
353         bufSize = int(self.client.transport.conn.options['buffersize'])
354         chunks.append((end, end + bufSize))
355         return (end, bufSize)
356    
357     def _cbGetRead(self, data, rf, lf, chunks, start, size, startTime):
358         if data and isinstance(data, failure.Failure):
359             log.msg('get read err: %s' % data)
360             reason = data
361             reason.trap(EOFError)
362             i = chunks.index((start, start + size))
363             del chunks[i]
364             chunks.insert(i, (start, 'eof'))
365         elif data:
366             log.msg('get read data: %i' % len(data))
367             lf.seek(start)
368             lf.write(data)
369             if len(data) != size:
370                 log.msg('got less than we asked for: %i < %i' %
371                         (len(data), size))
372                 i = chunks.index((start, start + size))
373                 del chunks[i]
374                 chunks.insert(i, (start, start + len(data)))
375             rf.total += len(data)
376         if self.useProgressBar:
377             self._printProgessBar(rf, startTime)
378         chunk = self._getNextChunk(chunks)
379         if not chunk:
380             return
381         else:
382             start, length = chunk
383         log.msg('asking for %i -> %i' % (start, start+length))
384         d = rf.readChunk(start, length)
385         d.addBoth(self._cbGetRead, rf, lf, chunks, start, length, startTime)
386         return d
387
388     def _cbGetDone(self, ignored, rf, lf):
389         log.msg('get done')
390         rf.close()
391         lf.close()
392         if self.useProgressBar:
393             self.transport.write('\n')
394         return "Transferred %s to %s" % (rf.name, lf.name)
395    
396     def cmd_PUT(self, rest):
397         local, rest = self._getFilename(rest)
398         if '*' in local or '?' in local: # wildcard
399             if rest:
400                 remote, rest = self._getFilename(rest)
401                 path = os.path.join(self.currentDirectory, remote)
402                 d = self.client.getAttrs(path)
403                 d.addCallback(self._cbPutTargetAttrs, remote, local)
404                 return d
405             else:
406                 remote = ''
407                 files = glob.glob(local)
408                 return self._cbPutMultipleNext(None, files, remote)
409         if rest:
410             remote, rest = self._getFilename(rest)
411         else:
412             remote = os.path.split(local)[1]
413         lf = file(local, 'r')
414         path = os.path.join(self.currentDirectory, remote)
415         flags = filetransfer.FXF_WRITE|filetransfer.FXF_CREAT|filetransfer.FXF_TRUNC
416         d = self.client.openFile(path, flags, {})
417         d.addCallback(self._cbPutOpenFile, lf)
418         d.addErrback(self._ebCloseLf, lf)
419         return d
420
421     def _cbPutTargetAttrs(self, attrs, path, local):
422         if not stat.S_ISDIR(attrs['permissions']):
423             return "Wildcard put with non-directory target."
424         return self._cbPutMultipleNext(None, files, path)
425
426     def _cbPutMultipleNext(self, res, files, path):
427         if isinstance(res, failure.Failure):
428             self._printFailure(res)
429         elif res:
430             self.transport.write(res)
431             if not res.endswith('\n'):
432                 self.transport.write('\n')
433         f = None
434         while files and not f:
435             try:
436                 f = files.pop(0)
437                 lf = file(f, 'r')
438             except:
439                 self._printFailure(failure.Failure())
440                 f = None
441         if not f:
442             return
443         name = os.path.split(f)[1]
444         remote = os.path.join(self.currentDirectory, path, name)
445         log.msg((name, remote, path))
446         flags = filetransfer.FXF_WRITE|filetransfer.FXF_CREAT|filetransfer.FXF_TRUNC
447         d = self.client.openFile(remote, flags, {})
448         d.addCallback(self._cbPutOpenFile, lf)
449         d.addErrback(self._ebCloseLf, lf)
450         d.addBoth(self._cbPutMultipleNext, files, path)
451         return d
452
453     def _cbPutOpenFile(self, rf, lf):
454         numRequests = self.client.transport.conn.options['requests']
455         if self.useProgressBar:
456             lf = FileWrapper(lf)
457         dList = []
458         chunks = []
459         startTime = time.time()
460         for i in range(numRequests):
461             d = self._cbPutWrite(None, rf, lf, chunks, startTime)
462             if d:
463                 dList.append(d)
464         dl = defer.DeferredList(dList, fireOnOneErrback=1)
465         dl.addCallback(self._cbPutDone, rf, lf)
466         return dl
467
468     def _cbPutWrite(self, ignored, rf, lf, chunks, startTime):
469         chunk = self._getNextChunk(chunks)
470         start, size = chunk
471         lf.seek(start)
472         data = lf.read(size)
473         if self.useProgressBar:
474             lf.total += len(data)
475             self._printProgessBar(lf, startTime)
476         if data:
477             d = rf.writeChunk(start, data)
478             d.addCallback(self._cbPutWrite, rf, lf, chunks, startTime)
479             return d
480         else:
481             return
482
483     def _cbPutDone(self, ignored, rf, lf):
484         lf.close()
485         rf.close()
486         if self.useProgressBar:
487             self.transport.write('\n')
488         return 'Transferred %s to %s' % (lf.name, rf.name)
489
490     def cmd_LCD(self, path):
491         os.chdir(path)
492
493     def cmd_LN(self, rest):
494         linkpath, rest = self._getFilename(rest)
495         targetpath, rest = self._getFilename(rest)
496         linkpath, targetpath = map(
497                 lambda x: os.path.join(self.currentDirectory, x),
498                 (linkpath, targetpath))
499         return self.client.makeLink(linkpath, targetpath).addCallback(_ignore)
500
501     def cmd_LS(self, rest):
502         # possible lines:
503         # ls                    current directory
504         # ls name_of_file       that file
505         # ls name_of_directory  that directory
506         # ls some_glob_string   current directory, globbed for that string
507         options = []
508         rest = rest.split()
509         while rest and rest[0] and rest[0][0] == '-':
510             opts = rest.pop(0)[1:]
511             for o in opts:
512                 if o == 'l':
513                     options.append('verbose')
514                 elif o == 'a':
515                     options.append('all')
516         rest = ' '.join(rest)
517         path, rest = self._getFilename(rest)
518         if not path:
519             fullPath = self.currentDirectory + '/'
520         else:
521             fullPath = os.path.join(self.currentDirectory, path)
522         d = self._remoteGlob(fullPath)
523         d.addCallback(self._cbDisplayFiles, options)
524         return d
525
526     def _cbDisplayFiles(self, files, options):
527         files.sort()
528         if 'all' not in options:
529             files = [f for f in files if not f[0].startswith('.')]
530         if 'verbose' in options:
531             lines = [f[1] for f in files]
532         else:
533             lines = [f[0] for f in files]
534         if not lines:
535             return None
536         else:
537             return '\n'.join(lines)
538
539     def cmd_MKDIR(self, path):
540         path, rest = self._getFilename(path)
541         path = os.path.join(self.currentDirectory, path)
542         return self.client.makeDirectory(path, {}).addCallback(_ignore)
543
544     def cmd_RMDIR(self, path):
545         path, rest = self._getFilename(path)
546         path = os.path.join(self.currentDirectory, path)
547         return self.client.removeDirectory(path).addCallback(_ignore)
548
549     def cmd_LMKDIR(self, path):
550         os.system("mkdir %s" % path)
551
552     def cmd_RM(self, path):
553         path, rest = self._getFilename(path)
554         path = os.path.join(self.currentDirectory, path)
555         return self.client.removeFile(path).addCallback(_ignore)
556
557     def cmd_LLS(self, rest):
558         os.system("ls %s" % rest)
559
560     def cmd_RENAME(self, rest):
561         oldpath, rest = self._getFilename(rest)
562         newpath, rest = self._getFilename(rest)
563         oldpath, newpath = map (
564                 lambda x: os.path.join(self.currentDirectory, x),
565                 (oldpath, newpath))
566         return self.client.renameFile(oldpath, newpath).addCallback(_ignore)
567
568     def cmd_EXIT(self, ignored):
569         self.client.transport.loseConnection()
570
571     cmd_QUIT = cmd_EXIT
572
573     def cmd_VERSION(self, ignored):
574         return "SFTP version %i" % self.client.version
575    
576     def cmd_HELP(self, ignored):
577         return """Available commands:
578 cd path                         Change remote directory to 'path'.
579 chgrp gid path                  Change gid of 'path' to 'gid'.
580 chmod mode path                 Change mode of 'path' to 'mode'.
581 chown uid path                  Change uid of 'path' to 'uid'.
582 exit                            Disconnect from the server.
583 get remote-path [local-path]    Get remote file.
584 help                            Get a list of available commands.
585 lcd path                        Change local directory to 'path'.
586 lls [ls-options] [path]         Display local directory listing.
587 lmkdir path                     Create local directory.
588 ln linkpath targetpath          Symlink remote file.
589 lpwd                            Print the local working directory.
590 ls [-l] [path]                  Display remote directory listing.
591 mkdir path                      Create remote directory.
592 progress                        Toggle progress bar.
593 put local-path [remote-path]    Put local file.
594 pwd                             Print the remote working directory.
595 quit                            Disconnect from the server.
596 rename oldpath newpath          Rename remote file.
597 rmdir path                      Remove remote directory.
598 rm path                         Remove remote file.
599 version                         Print the SFTP version.
600 ?                               Synonym for 'help'.
601 """
602
603     def cmd_PWD(self, ignored):
604         return self.currentDirectory
605
606     def cmd_LPWD(self, ignored):
607         return os.getcwd()
608
609     def cmd_PROGRESS(self, ignored):
610         self.useProgressBar = not self.useProgressBar
611         return "%ssing progess bar." % (self.useProgressBar and "U" or "Not u")
612
613     def cmd_EXEC(self, rest):
614         shell = pwd.getpwnam(getpass.getuser())[6]
615         print repr(rest)
616         if rest:
617             cmds = ['-c', rest]
618             return utils.getProcessOutput(shell, cmds, errortoo=1)
619         else:
620             os.system(shell)
621
622     # accessory functions
623
624     def _remoteGlob(self, fullPath):
625         log.msg('looking up %s' % fullPath)
626         head, tail = os.path.split(fullPath)
627         if '*' in tail or '?' in tail:
628             glob = 1
629         else:
630             glob = 0
631         if tail and not glob: # could be file or directory
632            # try directory first
633            d = self.client.openDirectory(fullPath)
634            d.addCallback(self._cbOpenList, '')
635            d.addErrback(self._ebNotADirectory, head, tail)
636         else:
637             d = self.client.openDirectory(head)
638             d.addCallback(self._cbOpenList, tail)
639         return d
640
641     def _cbOpenList(self, directory, glob):
642         files = []
643         d = directory.read()
644         d.addBoth(self._cbReadFile, files, directory, glob)
645         return d
646
647     def _ebNotADirectory(self, reason, path, glob):
648         d = self.client.openDirectory(path)
649         d.addCallback(self._cbOpenList, glob)
650         return d
651
652     def _cbReadFile(self, files, l, directory, glob):
653         if not isinstance(files, failure.Failure):
654             if glob:
655                 l.extend([f for f in files if fnmatch.fnmatch(f[0], glob)])
656             else:
657                 l.extend(files)
658             d = directory.read()
659             d.addBoth(self._cbReadFile, l, directory, glob)
660             return d
661         else:
662             reason = files
663             reason.trap(EOFError)
664             directory.close()
665             return l
666
667     def _abbrevSize(self, size):
668         # from http://mail.python.org/pipermail/python-list/1999-December/018395.html
669         _abbrevs = [
670             (1<<50L, 'PB'),
671             (1<<40L, 'TB'),
672             (1<<30L, 'GB'),
673             (1<<20L, 'MB'),
674             (1<<10L, 'kb'),
675             (1, '')
676             ]
677
678         for factor, suffix in _abbrevs:
679             if size > factor:
680                 break
681         return '%.1f' % (size/factor) + suffix
682
683     def _abbrevTime(self, t):
684         if t > 3600: # 1 hour
685             hours = int(t / 3600)
686             t -= (3600 * hours)
687             mins = int(t / 60)
688             t -= (60 * mins)
689             return "%i:%02i:%02i" % (hours, mins, t)
690         else:
691             mins = int(t/60)
692             t -= (60 * mins)
693             return "%02i:%02i" % (mins, t)
694
695     def _printProgessBar(self, f, startTime):
696         diff = time.time() - startTime
697         total = f.total
698         try:
699             winSize = struct.unpack('4H',
700                 fcntl.ioctl(0, tty.TIOCGWINSZ, '12345679'))
701         except IOError:
702             winSize = [None, 80]
703         speed = total/diff
704         if speed:
705             timeLeft = (f.size - total) / speed
706         else:
707             timeLeft = 0
708         front = f.name
709         back = '%3i%% %s %sps %s ' % ((total/f.size)*100, self._abbrevSize(total),
710                 self._abbrevSize(total/diff), self._abbrevTime(timeLeft))
711         spaces = (winSize[1] - (len(front) + len(back) + 1)) * ' '
712         self.transport.write('\r%s%s%s' % (front, spaces, back))
713
714     def _getFilename(self, line):
715         line.lstrip()
716         if not line:
717             return None, ''
718         if line[0] in '\'"':
719             ret = []
720             line = list(line)
721             try:
722                 for i in range(1,len(line)):
723                     c = line[i]
724                     if c == line[0]:
725                         return ''.join(ret), ''.join(line[i+1:]).lstrip()
726                     elif c == '\\': # quoted character
727                         del line[i]
728                         if line[i] not in '\'"\\':
729                             raise IndexError, "bad quote: \\%s" % line[i]
730                         ret.append(line[i])
731                     else:
732                         ret.append(line[i])
733             except IndexError:
734                 raise IndexError, "unterminated quote"
735         ret = line.split(None, 1)
736         if len(ret) == 1:
737             return ret[0], ''
738         else:
739             return ret
740
741 StdioClient.__dict__['cmd_?'] = StdioClient.cmd_HELP
742
743 class SSHConnection(connection.SSHConnection):
744     def serviceStarted(self):
745         self.openChannel(SSHSession())
746
747 class SSHSession(channel.SSHChannel):
748
749     name = 'session'
750
751     def channelOpen(self, foo):
752         log.msg('session %s open' % self.id)
753         if self.conn.options['subsystem'].startswith('/'):
754             request = 'exec'
755         else:
756             request = 'subsystem'
757         d = self.conn.sendRequest(self, request, \
758             common.NS(self.conn.options['subsystem']), wantReply=1)
759         d.addCallback(self._cbSubsystem)
760         d.addErrback(_ebExit)
761
762     def _cbSubsystem(self, result):
763         self.client = filetransfer.FileTransferClient()
764         self.client.makeConnection(self)
765         self.dataReceived = self.client.dataReceived
766         f = None
767         if self.conn.options['batchfile']:
768             fn = self.conn.options['batchfile']
769             if fn != '-':
770                 f = file(fn)
771         self.stdio = stdio.StandardIO(StdioClient(self.client, f))
772
773     def extReceived(self, t, data):
774         if t==connection.EXTENDED_DATA_STDERR:
775             log.msg('got %s stderr data' % len(data))
776             sys.stderr.write(data)
777             sys.stderr.flush()
778
779     def eofReceived(self):
780         log.msg('got eof')
781         self.stdio.closeStdin()
782    
783     def closeReceived(self):
784         log.msg('remote side closed %s' % self)
785         self.conn.sendClose(self)
786
787     def closed(self):
788         try:
789             reactor.stop()
790         except:
791             pass
792
793     def stopWriting(self):
794         self.stdio.pauseProducing()
795
796     def startWriting(self):
797         self.stdio.resumeProducing()
798
799 if __name__ == '__main__':
800     run()
801
Note: See TracBrowser for help on using the browser.