Пишем полезную программу для KDE4 на питоне за два часа

    Появилось на работе пара свободных часов и решил я себе сделать жизнь удобнее.
    По роду деятельности(а работаю я программистом) приходится много чего делать на удалённых серверах, доступ на которые имеется только по ssh. А писать и отлаживать программы удобнее всего локально, и только потом ставить на рабочую машину. Посему удобно использовать sshfs. Однако, набирать в консоли каждый раз команду на монтирование я устал, писать скрипт на баше — лень. Потому захотелось иметь графический менеджер sshfs маунтов, да ко всему прочему в KDE4.


    Альтернативы


    Естественно, от написания своего сопротивлялся до последнего. Гугл вскрылся выдавать мне ответы. Но ничего подходящего я не нашёл.
    ksshfs заработал почему-то только в KDE3
    sshfsgui тоже не захотел работать, ссылаясь на какие-то явовские ошибки. Перепробовал несколько разных версий и реализаций явамашин — не помогло.

    Итак придётся самому.

    Читаем


    Для начала, как вообще создавать приложения для KDE4? Это знание я подчерпнул из статьи «Программируем для КДЕ4».
    В остальном помогла документация.

    Каркас


    В плане интерфейса я ориентировался на sshfsgui. С этого и начнём.
    Первым делом берём каркас для прилжения:
    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 = "сайт программы"
    bugEmail = "email для общения с автором на тему ошибок"

    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_()


    Скажу одно — это уже будет запускаться, и мысль эта душу мне согревает. Выглядит вот так:
    Пустое приложение Qt4

    Интерфейс


    Поскольку программка за два часа, да и вообще простая, то всё относящееся к интерфейсу я поместил в метод __init__. Не мучаем себя Qt Designer'ом, а просто пишем код.

    Выглядит это так:
    def __init__(self, parent = None): #конструктор

            KMainWindow.__init__(self, parent) #call parent constructor

            hbox = KHBox( self ) # создаём горизонтальный слой
            hbox.setMargin(10) # отступы 10 пикселей
            self.setCentralWidget(hbox) #делаем его главным

            #два вертикальных слоя внутри главного горизонтального
            vbox_left = KVBox( hbox )
            vbox_right = KVBox( hbox )

            # выравниваем правый слой по верху
            hbox.layout().setAlignment( vbox_right, QtCore.Qt.AlignTop )

            # поля для ввода данных для монтирования
            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 = QLabel( 'Username:', vbox_right )
            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 )

            #кнопки монтирования и размонтирования
            #для них создаём отдельный слой
            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( 'Disconnect' ) )

            #список для сохранённых профилей
            saved_list_label = QLabel( 'Stored connections:', vbox_left )
            self.saved_list = KListWidget( vbox_left )
            self.saved_list.setMaximumWidth( 150 )

            #кнопки сохранения и удаления профилей
            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' ) )

    Итак, после всего этого имеем программу, отображающую нам формочку:
    Форма, интерфейс программы qt

    Обработка событий


    Теперь надо вдохнуть жизнь в каркас нашей программы.
    Поскольку все действия пользователь(т.е. я) будет совершать посредством нажатия на кнопки и выбора профиля в списке сохранённых профилей, то надо установить обработчики событий на эти элементы. В этом нам поможет механизм сигналов и слотов.
    Всё просто:
    #привязка обработчиков событий к кнопкам
    #здесь save_btn — переменная, содержащая объект кнопки сохранения
    # QtCore.SIGNAL('clicked()') — сигнал «клик по кнопке»
    # self.onSave — метод, вызываемый для обработки клика
    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 )

    #самым сложным было найти в документации как называется сигнал «кликнули по элементу в списке»
    self.connect( self.saved_list, QtCore.SIGNAL( 'itemClicked (QListWidgetItem *)' ), self.onSelectServer )


    Сохранение профиля

    Теперь дело за малым — написать собственно обработчики. Начнём по порядку: сохранение профиля и удаление профиля.
    Хранить профили будем в домашней директории пользователя в ~/.pyksshfs/hosts/.
    Один файл на один профиль.Имя файла — то, что в форме называется «Name».
    Логично, что при запуске программа должна проверять, есть ли такой каталог и создавать его в случае отсутствия.
    Для этого добавим после описания программы следующий немудрёный код:
    config_path = os.getenv( 'HOME' )+'/.pyksshfs/hosts'
    if not os.path.isdir( config_path ):
        os.makedirs( config_path, 0700 )


    А в начало файла с программой import os
    Раздумывая над тем как лучше хранить значения полей формы в файле, я подумал, что в питоне наверняка есть уже готовый модуль для хранения конфигов. Так и вышло.
    Минутное гугление тут же дало результат: import ConfigParser
    Итак, метод onSave:
    def onSave( self ):
        '''
        save settings
        '
    ''
        if self.entry_name.text(): #Если есть ли имя профиля
            config = ConfigParser.RawConfigParser() # то создадим и заполним конфиг
            config.add_section( 'Connection' )
            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 )    #сохраним конфиг
            file.close()
            self.selected_name = self.entry_name.text()
            self.listServers() # обновим список профилей


    Список профилей

    В конце написания метода приходит идея, что хорошо бы новый профиль сразу появлялся в списке, да и при открытии программы тоже надо отображать список сохранённых профилей.
    Так что пишем сразу метод получения и вывода списка и всталяем его вызов в конец __init__ и onSave.
    def listServers( self ):
        self.saved_list.clear()
        hosts = os.listdir( self.config_path )
        self.saved_list.insertItems( O, hosts ) #этим вызовом добавляем список файлов в виджет «список»
        if self.selected_name: #Если мы уже выбирали какой-то профиль, то его надо выделить в списке
            item = self.saved_list.findItems( self.selected_name, QtCore.Qt.MatchExactly )
            self.saved_list.setItemSelected( item[O], True )

    (Почему-то хабр не хочет отображать 0 в коде, заменил на прописную букву О).

    Размонтирование

    Поехали дальше. Метод для размонтирования удалённой директории. Тут объяснять в-общем-то нечего.

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


    Монтирование

    Монтирование гораздо интереснее. Эту часть я мучал дольше всего. Скажу по секрету, что именно из-за этого метода я провозился гораздо больше двух часов. Но на самом деле проблемы были такого характера, что знал бы я о них раньше, то вполне уложился бы в срок, приведённый в заголовке.
    В чём заключается проблема: комманда монтирования директории через ssh интерактивная и требует ввода пароля от пользователя. Но в случае, если сделана авторизация по ключам, не требует. Соответственно надо сформировать комманду, выполнить, узнать спрашивают ли пароль, затем спросить его у пользователя. А если пароль не нужен, то пользователя не трогать.
    У комманды sshfs есть параметр, позволяющий передать пароль с stdin. Но тогда придётся пользователя спросить заранее, что не очень хорошо, когда пароль не нужен.
    Есть ещё одна тонкость. Если мы ни разу не заходили на сервер по ssh, нас спросят — «а доверяем ли мы ему?» и надо будет ввести yes.

    В-общем, нам надо как-то обработать эти случаи. Для решения такого рода задач существует модуль pexpect ( import pexpect ). С его помощью можно работать с интерактивными программами( например telnet, ftp, ssh ). Что ж, пора показать код.
    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)
        elif i == 2:
            #Any problem
            print "Error found: %s" % sshfs.before
        elif i == 3:
            #Timeout
            print "Timeout: %s" % sshfs.before
        print sshfs.before

    Часть кода я взял из проекта linux-volume-manager-fuse-kde4, т.к. сначала мой код не хотел работать, а после того как мой код заработал, решил оставить всё же этот, т.к. он обрабатывает больше вариантов.
    Для получения пароля от пользователя я использовал программу ksshaskpass. Во-первых, чтобы не писать, во-вторых, она умеет сохранять/получать пароль из kwalletd, что весьма удобно.
    Первоначальный код никак не работал из-за того, что по документации ksshaskpass, должен возвращать пароль, а вместо этого в дополнение к паролю возвращает ещё какую-то отладочную строчку. Её пришлось отфильтровать вот так
    password = pexpect.run( 'ksshaskpass' ).split( '\n' )[1]
    Кстати, если вдруг отладочная строчки исчезнет, программа перестанет работать.

    Загрузка профиля

    Почти всё готово. Осталось последнее действие: загрузить профиль, когда пользователь выберет его из списка. Сразу код.
    def onSelectServer( self, item ):
        """
        get settings from file, when item selected in seved_list
        "
    ""
        name = item.text() # имя файла
        self.selected_name = name #запоминаем выбор

        config = ConfigParser.RawConfigParser()
        config.readfp( open( self.config_path+'/'+name ) ) #открываем конфиг

        # заполняем поля формы из конфига
        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' ) )


    Результат


    Вот и всё. За каких-то пару часов я, владея только синтаксисом питона, гуглом и чёрным поясом по копипасту сделал вполне рабочую программку, которую намерен теперь использовать.
    Возможно, в статье я упустил какую-то часть кода.
    Так что лучше всего будет скачать полный рабочий варинат pyKSshfs.

    Напоследок скриншот:
    Готовая программа

    Планы


    К середине написания программы я подумал, что она была бы удобнее в виде плазма-аплета. И выглядеть он должен как аплет монтирования флешек. Но так-как возился с ksshaskpass, решил отложить. Может быть скоро я займусь этим. А может быть кто-то из вас меня опередит — буду только рад.

    Ссылки


    1. Скачать pyKSshfs.
    2. «Программируем для КДЕ4»
    3. Документация по pyQt
    4. Kommander-скрипт ksshfs
    5. Java-программа sshfsgui
    6. Часть кода была взята тут


    Спасибо за внимание!


    Спасибо всем, кто смог это всё прочитать, знаю это было непросто. =)
    Всем удачи!
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 52

      +9
      Отличный пост, спасибо.
        +2
        Отличная статья. Теперь доведите прогу до ума окончательно (тестирование и документация), и выкладывайте в репозитории или на фреш мит, что бы сообщество могло пользоваться
          0
          да, да аналог Smb4k для ftp/ssh очень бы не помешал.
          ввел параметры соединения, и у тебя создалась папка ~/remotesite/www.sampe.com куда все смонтировалось
        • НЛО прилетело и опубликовало эту надпись здесь
            +17
            какой волшебный никс, ну надо же!!!
              +3
              то есть в винде на С# такое сделать за пару часиков нельзя? При том что есть куча готовых модулей для работы с чем угодно на codeproject, итп сайтах. Извините, не хочу холивар начинать, просто вырвалось…
                0
                В Винде пара часов уйдёт только на то, чтобы скачать Visual C# Express Edition и поставить её :-) Очень сильно удалена разработка от «простых смертных». Это, понятно, одновременно преимущество и недостаток…
                  +4
                  Надеюсь, с вами ничего нехорошего не случится от шока, который вы испытаете, узнав, что в винде тоже можно писать на Питоне…
                    0
                    В комплект поставки он не входит и на даже на DVD его нет. В любом случае нужно где-то что-то искать.
                      0
                      Да бросьте. Кому надо это и кто умеет — тот знает (python.org). И в Linux кому не надо не найдет.
                      +2
                      А Вы пробовали? Причём не просто «на Питоне», а с использованием библиотек, не идущих в комплекте? Я как-то провёл пару незабываемых часов «нужна такая-то либа — гугль — чёрт, она только для 2.6, а у меня 2.5 — надо обновить питон — нужна ещё одна либа — гугль — блин, а эту для 2.6 ещё не упаковали в инсталлятор! — придётся руками класть куда надо — да я знать не знаю (и не желаю, если честно), где находится это „куда надо“! — блин, а эта либа вообще только для 2.4 есть, и автор, похоже, забил на неё»…

                      При этом для Линукса всё нужное находилось лёгким движением «yum search » без каких-либо напрягов.

                      P.S.: не холивара ради, а справедливости для.
                        +2
                        Не только пробовали, а в основном так и пишем. А описываемый вами негативный экспериенс был как раз под линуксом, а не под виндами, в которых почему-то всё находилось и ставилось без проблем. А в линуксе, пока требуемое есть в репозитории, тоже легко, но уж если нет — циркулярные депенденси всю душу вынут, пока чего-то достигнешь…
                          0
                          easy_install решает часть проблем. Правда не все. Оставшуюся часть решает перекомпиляция питона под свой компилятор с тем, чтобы ставить нескомпиленные либы через python install, благо сделать это нетрудно.
                    • НЛО прилетело и опубликовало эту надпись здесь
                        +3
                        Такое можно сделать даже на винде. Но это не отменяет тот факт что Linux более открытая и дружелюбная _программисту_ система. Это и не удивительно, Linux писали как бы «для своих».
                    • НЛО прилетело и опубликовало эту надпись здесь
                        0
                        а что у него с пеп8? по-моему нормально всё
                        • НЛО прилетело и опубликовало эту надпись здесь
                          +1
                          Это же не просто прочитать надо, но усвоить, запомнить и не забывать применять.
                          В-общем, требует времени.
                          Почитаю. «Ну надо так надо» =).
                          • НЛО прилетело и опубликовало эту надпись здесь
                          +1
                          Все больше и больше порываюсь разобраться с Питоном. Хорошая статья, спасибо.
                            0
                            тыкнуть в дельфине правой кнопкой по панели быстрого доступа к папкам и добавить туда sshfs не канает?
                            +1
                            Мне кажется что использование PyKDE4 в даном случае не очень оправдано.
                              0
                              Разумеется если только задача не стоит принудительно использовать PyKDE4.
                              А для такой программы лучше б подошла «чистая» PyQt4.
                                +1
                                Когда я начинал писать мне очень хотелось как можно быстрее получить результат.
                                Я особо не задумывался что делаю, и как делаю.
                                Теперь вижу, что «это надо переписать» :-)
                                Дело в том, что если бы я сразу начал думать на pyKDE или pyQt делать, то не собрался бы оочень долго.
                              0
                              Хорошая статья, спасибо!
                              А вот Вы со связкой PyQt4 и WebKit не работали, случайно?
                              0
                              Чего только не выдумают, чтоб нормальным редактором (ну, например emacs :) ) не пользоваться…
                                0
                                Код написан в vim :-)
                                  0
                                  В vim нету аналогa емаксовского tramp?
                                    0
                                    Не знаю. Сходу ничего не нашёл. Да, вроде и не надо…
                                      +1
                                      Ну, вобщем нагуглил довольно легко — netrw
                                        0
                                        Интересно, попробую. Однако, я больше привык пользоваться внешними файлменеджерами, а редактор использую только для редактирования. М.б. этот плагин будет удобен, а может и нет.
                                0
                                Пишем велосипед!
                                Теперь главный вопрос — а зачем нам оно и чем оно лучше встроенного средства в кде?:D
                                  0
                                  А это уже где-то обсуждалось. Попробуйте воспроизвести фильм так.
                                  fish — это далеко не то же самое, что sshfs.
                                    –1
                                    это монтирование в /media/…
                                      0
                                      А я вижу, что это fish и в /media/ как-то ничего нового не наблюдается.
                                      На всякий случай: debian lenny, KDE4.2 из experimental
                                      0
                                      ну вообще почти одно и тоже, только sshfs виртуализирует протокол для ядра и всех userspace-программ. Принцип же работы у обоих прост, только fish куда более древняя реализация, и куда менее приятная нежели чем sshfs.

                                      Фильмы смотреть через sshfs — это уже вообще — зачем? sftp и вперёд.
                                    0
                                    а вот бы еще подобное почитать про меню и панели инструментов из pykde4. очень было бы интересно.
                                      0
                                      я для такого сделал просто набор скриптов и положил их в папку с быстрым доступом
                                      для меня это удобнее. например — можно очень быстро поменять логин и пароль для доступа к какой-нибудь сети
                                        –7
                                        спасибо афторам за такие статьи.
                                        все больше убеждаюсь что питон — это унылое говно типа бейсика :D :D :D и что делфи и фрипаскаль намного лутше ево :D
                                          0
                                          Ну наконец-то новая статья о Питоне.

                                          На Хабре я не нашел упоминания об этом замечательном сайте showmedo.com — там очень много всего о Python и не только. Раньше не мог опубликовать, потому как кармы мало.
                                          Пользуйтесь :)
                                            0
                                            Извените, какие-то проблемы — немогу вставить ссылку :(
                                              +2
                                              Похоже, что написание поста заняло больше времени, чем программа :) Спасибо.
                                                +1
                                                Так и есть. :-)
                                                0
                                                Написал что-то подобное на mono с gtk. Если кому интересно могу выложить сырцы.
                                                Теперь по теме.
                                                Я столкнулся с проблемой, что если примонтированная директория длительное время не посещается она может зависнуть из за обрыва SSH соединения.
                                                Решил проблему регулярным, раз в 5 минут, чтением списка файлов из подключенных директорий.
                                                  0
                                                  У sshfs есть опция reconnect.
                                                  Если соединение оборвалось, то при обращении к каталогу куда смонтировали, sshfs попытается восстановить соединение. Правда при этом обычно спрашивает пароль. Просто пароль, непонятно от чего. Каждый раз меня это вводит в задумчивость — что оно от меня хочет.

                                                  В случае KDE (и 3 и 4), должен выскакивать(если разрешён доступ в kwallet, то сразу введёт пароль сам, ничего не показывая пользователю) ksshaskpass(если он установлен), он может просто взять пароль из kwallet. Но на одном компьютере у меня так работает, а на втором нет.

                                                  Поэтому лучше всего настроить авторизацию по ключам.
                                                  –1
                                                  А чем не подошли системы контроля версий?
                                                  По-моему задача как раз для них.

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                  Самое читаемое