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

Revision 32921, 27.5 KB (checked in by teratorn, 7 months ago)

Merge make-zshcomp-dynamic-3078-6: New tab-completion system
Author: teratorn
Reviewer: glyph, exarkun
Fixes: #3078

Deprecates t.p.zshcomp in favor of a tab-completion system in t.p.usage - completion matches are generated dynamically at tab-press time.

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