We write a useful program for KDE4 in python in two hours

    A couple of free hours appeared at work and I decided to make life easier for myself.
    By type of activity (and I work as a programmer) there is a lot to do on remote servers, access to which is available only via ssh. And it is most convenient to write and debug programs locally, and only then put them on a working machine. Therefore, it is convenient to use sshfs. However, I’m tired of typing the mount command every time, writing a script on the tower is too lazy. So I wanted to have a sshfs mounts graphical manager, and everything else in KDE4.


    Alternatives


    Naturally, from writing his own, he resisted to the last. Google opened up to give me answers. But I did not find anything suitable.
    ksshfs earned for some reason only in KDE3
    sshfsgui also did not want to work, citing some Java errors. I tried several different versions and implementations of yavamashin - it did not help.

    So you have to do it yourself.

    Read


    For starters, how do you create applications for KDE4? I learned this knowledge from the article “Programming for KDE4” .
    The rest was helped by the documentation .

    Frame


    In terms of interface, I focused on sshfsgui. This is where we start.
    First of all, we take the framework for the application:
    from PyKDE4.kdeui import KApplication, KMainWindow, KPushButton, KHBox, KVBox, KLineEdit, KListWidget
    from PyKDE4.kdecore import i18n, ki18n, KAboutData, KCmdLineArgs
    from PyQt4 import QtCore
    from PyQt4.QtGui import Qlabel
    import sys

    class pyksshfsWindow(KMainWindow):

        selected_name = False

    def __init__(self, parent = None): #конструктор
            KMainWindow.__init__(self, parent) #call parent constructor

    appName = "pyksshfs"
    catalog = ""
    programName = ki18n("PyKSshfs")
    version = "0.1"
    description = ki18n ("Gui application for using sshfs")
    license = KAboutData.License_GPL
    copyright = ki18n ("© Akademic")
    text = ki18n ("none") homePage = "email
    site for the
    bugEmail =" program email errors "

    aboutData = KAboutData (appName, catalog, programName, version, description, license, copyright, text, homePage, bugEmail)
    KCmdLineArgs.init (sys.argv, aboutData)

    app = KApplication ()
    w = pyksshfsWindow ()
    w.show ( )
    app.exec_ ()


    I will say one thing - it will already be launched, and this thought warms my soul. It looks like this:
    Qt4 blank application

    Interface


    Since the program is in two hours, and indeed simple, I placed everything related to the interface in the __init__ method. Do not torture yourself with Qt Designer, but just write code.

    It looks like this:
    def __init __ (self, parent = None): # constructor

            KMainWindow .__ init __ (self, parent) #call parent constructor

            hbox = KHBox (self) # create a horizontal layer
            hbox.setMargin (10) #
            indent 10 pixels self.setCentralWidget (hbox) # make it main

            # two vertical layers inside the main horizontal
            vbox_left = KVBox (hbox)
            vbox_right = KVBox (hbox)

            # align the right layer on top of
            hbox.layout (). setAlignment (vbox_right, QtCore.Qt.AlignTop)

            # data entry fields to mount
            entry_name_label = QLabel ('Name:', vbox_right)
            self.entry_name = KLineEdit (vbox_right)
            server_address_label = QLabel ('Server address:', vbox_right)
            self.server_address = KLineEdit (vbox_right)

            server_port_label = QLabel ('Server port:', vbox_right)
            self.server_port = KLineEdit (vbox_right)

            user_name_label = QLab )
            self.user_name = KLineEdit (vbox_right)

            remote_path_label QLabel = ( 'Remote path:', vbox_right)
            self.remote_path = KLineEdit (vbox_right)

            local_path_label QLabel = ( 'Local path:', vbox_right)
            self.local_path = KLineEdit (vbox_right)

            # mount and unmount buttons
            # create a separate layer for them
            btn_hbox_right = KHBox (vbox_right)
            connect_btn = KPushButton (btn_hbox_right)
            connect_btn.setText (i18n ('Connect'))

            disconnect_btn = KPushButton (btn_hbox_right)
            disconnect_btn.setText (i18n)'         ellist_ellist_elconn_list

            saved
    connections Stored: ', vbox_left)
            self.saved_list = KListWidget (vbox_left)
            self.saved_list.setMaximumWidth (150)

            # save and delete buttons profiles
            btn_hbox_left = KHBox (vbox_left)
            save_btn = KPushButton (btn_hbox_left)
            save_btn.setText (i18n (' save ' )))

            delete_btn = KPushButton (btn_hbox_left)
            delete_btn.setText (i18n ('Delete'))

    So, after all this, we have a program that displays us a form:
    Qt form, interface

    Event handling


    Now we need to breathe life into the framework of our program.
    Since the user (i.e., I) will perform all actions by clicking on the buttons and selecting a profile in the list of saved profiles, it is necessary to install event handlers on these elements. The mechanism of signals and slots will help us with this.
    It's simple:

    #binding event handlers to buttons #here save_btn is a variable containing the save button object
    # QtCore.SIGNAL ('clicked ()') is a “click on a button” signal
    # self.onSave is a method called to process a click
    self.connect (save_btn , QtCore.SIGNAL ('clicked ()'), self.onSave)
    self.connect (delete_btn, QtCore.SIGNAL ('clicked ()'), self.onDelete)
    self.connect (connect_btn, QtCore.SIGNAL ('clicked ( ) '), self.onConnect)
    self.connect (disconnect_btn, QtCore.SIGNAL (' clicked () '), self.onDisconnect)

    # the most difficult thing was to find in the documentation the name of the signal “clicked on an item in the list”
    self.connect ( self.saved_list, QtCore.SIGNAL ('itemClicked (QListWidgetItem *)'), self.onSelectServer)


    Saving Profile

    Now it's up to you to write the handlers themselves. Let's start in order: saving a profile and deleting a profile.
    We will store the profiles in the user's home directory in ~ / .pyksshfs / hosts /.
    One file per profile. The file name is what is called “Name” in the form.
    It is logical that at startup, the program should check whether there is such a directory and create it in case of absence.
    To do this, add the following non-sophisticated code after the program description:
    config_path = os.getenv ('HOME') + '/. pyksshfs / hosts'
    if not os.path.isdir (config_path):
        os.makedirs (config_path, 0700)


    And to the beginning of the file with the import os program.
    Thinking about how best to store the values ​​of the form fields in the file, I thought that in python there probably is a ready-made module for storing configs. And so it happened.
    A minute googling immediately gave the result: import ConfigParser
    So, the onSave method:
    def onSave (self):
        '' '
        save settings
        ' ''
        if self.entry_name.text (): # If there is a profile name
            config = ConfigParser.RawConfigParser () # then create and fill in the
            config.add_section ('Connection') config
            config.set ('Connection', 'host', self.server_address.text ())
            config.set ('Connection', 'port', self.server_port.text ())
            config.set ('Connection', 'user_name ', self.user_name.text ())
            config.set (' Connection ',' remote_path ', self.remote_path.text ())
            config.set (' Connection ',' local_path ', self.local_path.text ())

            if self.selected_name:
                os.unlink (self.config_path + '/' + self.selected_name)

            path = self.config_path + '/' + self.entry_name.text ()
            file = open (path, 'w')
            config.write (file) # save the config
            file.close ()
            self.selected_name = self.entry_name.text ()
            self.listServers () # update the list of profiles


    Profiles List

    At the end of writing the method, the idea comes that it would be nice if the new profile appeared immediately in the list, and when you open the program, you also need to display a list of saved profiles.
    So we write immediately the method of receiving and listing the output and insert its call at the end of __init__ and onSave.
    def listServers (self):
        self.saved_list.clear ()
        hosts = os.listdir (self.config_path)
        self.saved_list.insertItems (O, hosts) # with this call we add the list of files to the list widget
        if self.selected_name: # If we have already selected a profile, then it should be selected in the list
            item = self.saved_list.findItems (self.selected_name, QtCore.Qt.MatchExactly)
            self.saved_list.setItemSelected (item [O], True)

    (For some reason, the Habr does not want to display 0 in the code, replaced it with a capital letter O).

    Unmounting

    Let's go further. Method for unmounting a remote directory. There is basically nothing to explain.

    def onDisconnect (self):
        if (self.local_path.text ()):
            os.system ('fusermount -u' + str (self.local_path.text ()))


    Mounting

    Mounting is much more interesting. I tormented this part the longest. I’ll tell you a secret that it was because of this method that I spent much more than two hours. But in fact, the problems were of such a nature that if I had known about them before, I would have completely met the deadline given in the heading.
    What is the problem: the command to mount the directory through ssh is interactive and requires a password from the user. But in case authorization by keys is done, it does not require. Accordingly, it is necessary to form a command, execute, find out if the password is being asked, then ask the user. And if the password is not needed, then do not touch the user.
    The sshfs command has a parameter that allows you to pass a password with stdin. But then the user will have to ask in advance what is not very good when the password is not needed.
    There is another subtlety. If we have never visited the server via ssh, we will be asked - “do we trust him?” And you will need to enter yes.

    In general, we need to somehow handle these cases. To solve such problems, there is a pexpect module (import pexpect). With it, you can work with interactive programs (for example telnet, ftp, ssh). Well, it's time to show the code.
    def onConnect (self):
        command = 'sshfs'
        if self.user_name.text ():
            command + = self.user_name.text () + '@'
        command + = self.server_address.text ()
        if self.remote_path.text ():
            command + = ':' + self.remote_path.text ()
        else :
            command + = ': /'

        if self.server_port.text ():
            command + = '-p' + self.server_port.text ()

        command + = '' + self.local_path.text ()

        sshfs = pexpect.spawn (str (command), env = { 'SSH_ASKPASS': '/ dev / null' } )
        ssh_newkey = 'Are you sure you want to continue connecting ''
        i = sshfs.expect ([ssh_newkey, 'assword:', pexpect.EOF, pexpect.TIMEOUT])

        if i == 0:
            sshfs.sendline ('yes')
            i = sshfs.expect ([ssh_newkey, 'assword:' , pexpect.EOF])
        if i == 1:
            #If no password ask for it
            askpasscmd = 'ksshaskpass% s'% self.entry_name.text ()
            password = pexpect.run (askpasscmd) .split ('\ n') [1]
            sshfs.sendline (password)
            j = sshfs.expect ([pexpect.EOF, 'assword:'])
            if j == 1:
                #Password incorrect, force the connection close
                print "Password incorrect"
                sshfs.close (True )
        #p.terminate (True)
        elifi == 2:
            #Any problem
            print "Error found:% s"% sshfs.before
        elif i == 3:
            #Timeout
            print "Timeout:% s"% sshfs.before
        print sshfs.before

    I took part of the code from the linux-volume-manager-fuse-kde4 project , because At first, my code didn’t want to work, but after my code worked, I decided to leave this one, because It handles more options.
    To get the password from the user, I used the ksshaskpass program. Firstly, in order not to write, and secondly, she knows how to save / receive a password from kwalletd, which is very convenient.
    The original code didn’t work at all due to the fact that according to the ksshaskpass documentation, it should return a password, but instead of that, in addition to the password, it returns some other debugging line. It had to be filtered out like this:
    password = pexpect.run ('ksshaskpass') .split ('\ n') [1]
    By the way, if the debugging line suddenly disappears, the program will stop working.

    Profile upload

    Almost everything is ready. The last action remains: load the profile when the user selects it from the list. Immediately the code.
    def onSelectServer (self, item):
        "" "
        get settings from file, when item selected in seved_list
        " ""
        name = item.text () # file name
        self.selected_name = name # remember the choice

        config = ConfigParser.RawConfigParser ()
        config .readfp (open (self.config_path + '/' + name)) # open the config

        # fill the form fields from the
        config self.entry_name.setText (name)
        self.server_address.setText (config.get ('Connection', 'host') )
        self.server_port.setText (config.get ('Connection', 'port'))
        self.user_name.setText (config.get ('Connection', 'user_name'))
        self.remote_path.setText (config.get ('Connection', 'remote_path'))
        self.local_path.setText (config.get ('Connection', 'local_path'))


    Result


    That's all. In just a couple of hours, using only python syntax, google and a black belt, I made quite a working program for copy-paste, which I now intend to use.
    Perhaps in the article I missed some part of the code.
    So it is best to download the full working pyKSshfs version .

    Finally a screenshot:
    Finished program

    Plans


    By the middle of writing the program, I thought that it would be more convenient as a plasma applet. And it should look like an applet for mounting flash drives. But as I was busy with ksshaskpass, I decided to postpone it. Maybe soon I will do it. Or maybe one of you will get ahead of me - I will only be glad.

    References


    1. Download pyKSshfs .
    2. "Programming for KDE4"
    3. PyQt documentation
    4. Kommander script ksshfs
    5. Sshfsgui java program
    6. Part of the code was taken here.


    Thanks for attention!


    Thanks to everyone who could read all this, I know it was not easy. =)
    Good luck to everyone!

    Also popular now: