Hi all
<div><br></div><div>I&#39;ve looked around for SFTP client doco, but it&#39;s come up pretty thin on the ground. I&#39;ve seen <a href="http://twistedmatrix.com/trac/wiki/TwistedConch" target="_blank">http://twistedmatrix.com/trac/wiki/TwistedConch</a> and everything under it. With it I&#39;ve been able to connect via SSH to a server and run a command (<i>a la</i> the &#39;cat&#39; example from tutorial page and from <a href="http://twistedmatrix.com/documents/current/conch/examples/sshsimpleclient.py" shape="rect" target="_blank">sshsimpleclient.py</a>).</div>

<div><br></div><div>On the other hand, plenty of posts recommend checking out cftp.py in scripts/. That uses the FileTransferClient, and appears to be more along the lines of what is needed for a persistent SFTP connection and file transfers.</div>
<div><br></div><div>What seems to be happening with the below is that the connection is being created OK, but I haven&#39;t been able to utilise the _remoteGlob method (taken from cftp.py). It simply hangs.</div><div><br>
</div><div>My sandbox code is below.</div><div><br></div><div>Any help greatly appreciated.</div><div>Brad</div><div><br></div><div>&lt;code&gt;</div><div><div>import os</div><div>from twisted.python import failure</div></div>
<div><div>from twisted.internet import reactor, protocol, defer</div><div>from twisted.conch.ssh import transport, connection, userauth, channel, common, filetransfer</div><div>import logging</div><div>from twisted.python import log</div>
<div>import sys</div></div><div><br></div><div><div>class SimpleTransport(transport.SSHClientTransport):</div><div>    def verifyHostKey(self, hostKey, fingerprint):</div><div>        print &#39;host key fingerprint: %s&#39; % fingerprint</div>
<div>        return defer.succeed(1) </div><div><br></div><div>    def connectionSecure(self):</div><div>        &#39;&#39;&#39;</div><div>        called when the encryption is set up and other services can be run</div><div>
        &#39;&#39;&#39;</div><div>        self.requestService(SimpleUserAuth(USERNAME,SimpleConnection()))</div><div><br></div><div>class SimpleConnection(connection.SSHConnection):</div><div>    def serviceStarted(self):</div>
<div>        self.openChannel(SSHSession())</div><div>        </div><div>class SimpleUserAuth(userauth.SSHUserAuthClient):</div><div>    def getPassword(self):</div><div>        return defer.succeed(PASS)</div></div><div>
<br></div><div><div>class SSHSession(channel.SSHChannel):</div><div>    &#39;&#39;&#39;</div><div>    Things to do within the active SSH session.</div><div>    &#39;&#39;&#39;</div><div>    name = &#39;session&#39;</div><div>
    </div><div>    def __init__(self, interface_handler=None, *args, **kwargs):</div><div>        channel.SSHChannel.__init__(self, *args, **kwargs)</div><div>        self.interface_handler = interface_handler or SftpClient(self)</div>
<div>    </div><div>    def channelOpen(self, ignoredData):</div><div>        self.data = &#39;&#39;</div><div>        # Create an sftp connection (stays open)</div><div>        d = self.conn.sendRequest(self, &#39;subsystem&#39;, common.NS(&#39;sftp&#39;), wantReply=1)</div>
<div>        d.addCallback(self._cbSubsystem)</div><div>    </div><div>    def _cbSubsystem(self, result):</div><div>        self.interface_handler.getDirectoryContents(&#39;/tmp&#39;)</div><div>        </div><div>    def closeReceived(self):</div>
<div>        <a href="http://logging.info">logging.info</a>(&#39;remote side closed %s&#39; % self)</div><div>        self.conn.sendClose(self)</div><div>        reactor.stop()</div><div><br></div><div><div>class SftpClient(object):</div>
<div>    </div><div>    def __init__(self, transport, *args, **kwargs):</div><div>        super(SftpClient, self).__init__(*args, **kwargs)</div><div>        self.transport = transport</div><div>        self._client = filetransfer.FileTransferClient()</div>
<div><br></div><div>    @property</div><div>    def client(self):</div><div>        if not self._client.connected:</div><div>            self._client.makeConnection(self.transport)</div><div>            logging.debug(&quot;Made &#39;connection&#39; with transport class&quot;)</div>
<div>        return self._client</div><div>    </div><div>    def getDirectoryContents(self, path):</div><div>        d = self._remoteGlob(path)</div><div>        </div><div>        def gotit(files):</div><div>            print &quot;Got %s: %s&quot; % (type(files), files)</div>
<div>        d.addCallback(gotit)</div><div>        return d</div><div>        </div><div>    # Accessory methods.</div><div>    # These are stolen from <a href="http://twisted.conch.scripts.cftp.py">twisted.conch.scripts.cftp.py</a>. We can&#39;t</div>
<div>    # import that module as it contains unix-dependent imports.</div><div>    def _remoteGlob(self, fullPath):</div><div>        logging.debug(&#39;looking up %s&#39; % fullPath)</div><div>        head, tail = os.path.split(fullPath)</div>
<div>        if &#39;*&#39; in tail or &#39;?&#39; in tail:</div><div>            glob = 1</div><div>        else:</div><div>            glob = 0</div><div>        if tail and not glob: # could be file or directory</div><div>
            # try directory first</div><div>            logging.debug(&quot;Opening dir&quot;)</div><div>            d = self.client.openDirectory(fullPath)</div><div>            d.addCallback(self._cbOpenList, &#39;&#39;)</div>
<div>            d.addErrback(self._ebNotADirectory, head, tail)</div><div>        else:</div><div>            d = self.client.openDirectory(head)</div><div>            d.addCallback(self._cbOpenList, tail)</div><div>        return d</div>
<div><br></div><div>    def _cbOpenList(self, directory, glob):</div><div>        logging.debug(&quot;Got dir&quot;)</div><div>        files = []</div><div>        d = directory.read()</div><div>        d.addBoth(self._cbReadFile, files, directory, glob)</div>
<div>        return d</div><div><br></div><div>    def _ebNotADirectory(self, reason, path, glob):</div><div>        logging.debug(&quot;Not a dir&quot;)</div><div>        d = self.client.openDirectory(path)</div><div>        d.addCallback(self._cbOpenList, glob)</div>
<div>        return d</div><div><br></div><div>    def _cbReadFile(self, files, l, directory, glob):</div><div>        logging.debug(&quot;Reading file&quot;)</div><div>        if not isinstance(files, failure.Failure):</div>
<div>            if glob:</div><div>                raise NotImplementedError(&quot;don&#39;t have fnmatch available to use on Windows so have commented this bit out&quot;)</div><div>#                l.extend([f for f in files if fnmatch.fnmatch(f[0], glob)])</div>
<div>            else:</div><div>                l.extend(files)</div><div>            d = directory.read()</div><div>            d.addBoth(self._cbReadFile, l, directory, glob)</div><div>            return d</div><div>        else:</div>
<div>            reason = files</div><div>            reason.trap(EOFError)</div><div>            directory.close()</div><div>            return l</div></div><div>        </div><div>if __name__==&#39;__main__&#39;:</div><div>
    protocol.ClientCreator(reactor, SimpleTransport).connectTCP(HOST, 22)</div><div>    log.startLogging(sys.stdout, setStdout=0)</div><div>    reactor.run()</div></div><div><br></div><div>&lt;/code&gt;</div>