PyQt and a SSH upload droplet
Modern GUIs need Drag and Drop
The following is an example for a drag & drop action with PyQt4. It uses paramiko for SSH interactions. I'm well aware that it won't work on Windows that way. But that's a Windows problem. I'm also well aware that there's a password in this file. Give it a try.
The source is at GitHub. The indention seems to be broken there. But that's a GitHub problem. It seems to be broken here, too. But that's a Drupal problem. ;). Actually it isn't even a problem.
Just the imports. The os module is necessary if you want paramiko to use your private ssh-key. The sys module is needed due argv:
- #ff7700;font-weight:bold;">import #dc143c;">sys
- #ff7700;font-weight:bold;">import paramiko #808080; font-style: italic;"># for ssh
- #ff7700;font-weight:bold;">import #dc143c;">os
- #ff7700;font-weight:bold;">from PyQt4 #ff7700;font-weight:bold;">import QtGui, QtCore
- #ff7700;font-weight:bold;">from mainwindow #ff7700;font-weight:bold;">import Ui_MainWindow
I pretty much guess that's self-explaining now. That's the standard header to initiate the form:
- #ff7700;font-weight:bold;">class gui_does(QtGui.QMainWindow):
- #ff7700;font-weight:bold;">def #0000cd;">__init__(#008000;">self):
- QtGui.QMainWindow.#0000cd;">__init__(#008000;">self)
- #008000;">self.ui = Ui_MainWindow()
- #008000;">self.ui.setupUi(#008000;">self)
- #008000;">self.setWindowTitle(#483d8b;">'To-SSH Droplet')
- #808080; font-style: italic;"># to accept drops
- #008000;">self.setAcceptDrops(#008000;">True)
- #008000;">self.ui.lineEdit.setText(#483d8b;">"/home/wishi/")
- #008000;">self.statusBar().showMessage(#483d8b;">'Ready')
To make an application accept drop-acrions simply define this in an __init__. Compared to Cocoa e. g. this is really trivial.
- #ff7700;font-weight:bold;">def dragEnterEvent(#008000;">self, event):
- #ff7700;font-weight:bold;">if event.mimeData().hasUrls():
- event.acceptProposedAction()
- #483d8b;">""" the initial drop action is scp """
- #ff7700;font-weight:bold;">def dropEvent(#008000;">self, event):
- filelist = (#483d8b;">'#000099; font-weight: bold;">\r#000099; font-weight: bold;">\n'.join([#008000;">str(url.toString()) #ff7700;font-weight:bold;">for url #ff7700;font-weight:bold;">in event.mimeData().urls()]))
- #008000;">self.ui.textEdit.setText(filelist)
- splitted_filelist = filelist.split(#483d8b;">"#000099; font-weight: bold;">\r#000099; font-weight: bold;">\n")
- destination_path = #008000;">self.ui.lineEdit.text()
- That's it to define the event-actions. If a user drops a file, handle it:
- #808080; font-style: italic;">#privatekeyfile = os.path.expanduser('~/.ssh/id_rsa')
- #808080; font-style: italic;">#mykey = paramiko.RSAKey.from_private_key_file(privatekeyfile)
- username = #483d8b;">'wishi'
- password = #483d8b;">'bmljZSB0cnkgaWRpb3Q='
- host = #483d8b;">'crazylazy.info'
- port = #ff4500;">22
- error = #008000;">False
So... paramiko is able to use your private key, as stated in the comment. It also supports passwords. Surely you know, that Base64 isn't used in SSH ;). However in this case it makes sense.
- #ff7700;font-weight:bold;">try:
- #008000;">self.statusBar().showMessage(#483d8b;">"Uploading")
- ssh = paramiko.SSHClient()
- ssh.load_system_host_keys()
- ssh.connect(host, username = username, password = password)
- sftp = ssh.open_sftp()
- #ff7700;font-weight:bold;">for #008000;">file #ff7700;font-weight:bold;">in splitted_filelist:
- #008000;">file = #008000;">file[#ff4500;">7:]
- patharray = #008000;">file.split(#483d8b;">"/")
- destination_path = destination_path + patharray[#008000;">len(patharray)-#ff4500;">1]
- sftp.put(#008000;">file, #008000;">str(destination_path))
- sftp.close()
- ssh.close()
- #ff7700;font-weight:bold;">except:
- error = #008000;">True
- #ff7700;font-weight:bold;">if (error):
- #008000;">self.statusBar().showMessage(#483d8b;">"Error")
- #ff7700;font-weight:bold;">else:
- #008000;">self.statusBar().showMessage(#483d8b;">"Ready")
- #ff7700;font-weight:bold;">if __name__==#483d8b;">"__main__":
- app = QtGui.QApplication(#dc143c;">sys.argv)
- window = gui_does()
- window.show()
- #dc143c;">sys.exit(app.exec_())
The interesting point here are the text conversions, which are necessary. You see that str(destionation_path) converts self.ui.lineEdit.text(). That's confusing at the beginning, but Qt handles text as unicode. Most Python installation don't.
Paramiko wants an ASCII string. So you convert this into a standard Python string-type. Furthermore you see that the string-operations introduced in nearly every Python tutorial I ever read are more than essential. Even in GUI programming they're central.
If you take a look at the for-loop: destination_path = destination_path + patharray[len(patharray)-1] -> makes sftp.put keep the filename and put it into the destination_path. You need the last element, which is len(element)-1.
The URI string normally starts with "file://". You don't want to pass these identifier-string to sftp.put. So you start at th 7th (file = file[7:]) position. That converts the URI into a general Unix path.
The rest of the app is trivial. Starting Qt Designer, clicking: the GUI just needs a lineEdit and a textEdit. The whole project including the QT Designer files are at GitHub. Surely you can enhance it with a progress bar, a menu-bar, preferences and extend it into a full featured sftp client.
This is just a general snippet on how to implement Drag and Drop actions with Qt and Python. If you want to dive into it, enhance it and:
Have fun,
wishi

Post new comment