Разработка сценариев для рабочих столов Linux: Часть 2. Разработка сценариев для файлового менеджера Nautilus

Использование Python для расширения функциональности файлового менеджера Nautilus

В данной статье рассказывается, как использовать язык Python для создания функциональности, расширяющей возможности файлового менеджера Nautilus для интеграции с рабочим столом GNOME.

Пол Феррилл, технический директор, ATAC

Пол Феррилл (Paul Ferrill) уже более 20 лет пишет статьи, посвященные различным аспектам информационных технологий. Пол начинал с написания обзоров сетевых продуктов, таких как LANtastic и ранние версии Novell Netware, для журнала PC Magazine. Пол обладает степенями магистра и бакалавра в области электротехники и имеет опыт разработки ПО для самых разнообразных платформ и архитектур.



24.01.2012

Вероятно, чаще всего пользователи рабочего стола GNOME используют программу Nautilus. Эта программа берет на себя все рутинные операции с файлами: копирование, перемещение и переименование и имеет доступный графический интерфейс пользователя. На первый взгляд может показаться, что для Nautilus нет невыполнимых задач, связанных с файлами, но только если не вспоминать, какими возможностями в этом отношении обладает стандартная оболочка shell и её сценарии.

Разработчики Nautilus предлагают несколько способов добавления новой функциональности без вмешательства в основной код приложения. Самый простой способ – использовать сценарий оболочки для запуска серии команд, которые обычно выполняются в окне терминала. Этот метод позволяет проверить команды до начала их использования, чтобы еще раз убедиться, что они работают правильно. Также можно использовать и другие языки, включая язык C Scripting Language, Gnome Basic, Perl и Python. В этой статье для расширения возможностей Nautilus используется язык Python, поэтому предполагается, что пользователь уже обладает базовыми знаниями этого языка и библиотеки Python Standard Library.

Создание сценариев для Nautilus

Первый метод расширения возможностей Nautilus использует специальный каталог .gnome2/nautilus-scripts, располагающийся в каталоге /home. Любой исполняемый файл, помещенный в этот каталог, будет показан в подменю Scripts (Сценарии) в контекстном меню, появляющемся при щелчке правой кнопкой мыши по файлу или каталогу. Также можно выделить несколько файлов и каталогов и передать их в сценарий при помощи этого же контекстного меню.

Файловый менеджер Nautilus при вызове сценария делает доступными для него несколько переменных среды, включая текущий каталог и список выбранных файлов. Список доступных переменных среды приведен в таблице 1.

Таблица 1. Переменные окружения, доступные в Nautilus.
Переменная средыОписание
NAUTILUS_SCRIPT_SELECTED_FILE_PATHSсписок путей к выбранным файлам, разделенный символами конца строки (только для локальной файловой системы)
NAUTILUS_SCRIPT_SELECTED_URISсписок URI для выбранных файлов, разделенный символами конца строки
NAUTILUS_SCRIPT_CURRENT_URIтекущее местоположение в файловой системе
NAUTILUS_SCRIPT_WINDOW_GEOMETRYпозиция и размер текущего окна

В языке Python значения этих переменных можно получить, вызвав функцию os.environ.get, как показано ниже:

selected = os.environ.get('NAUTILUS_SCRIPT_SELECTED_FILE_PATHS,'')

Этот вызов вернет строку, содержащую пути ко всем выбранным файлам, разделенные символом конца строки. Python позволяет легко превратить эту строку в итерируемый список с помощью следующей команды:

targets = selected.splitlines()

Сейчас, возможно, настало время обсудить вопросы взаимодействия с пользователем. С того момента, как управление было передано из Nautilus в сценарий, для него в принципе не существует ограничений, что можно делать, а чего делать нельзя. В зависимости от задачи сценария ему, возможно, даже не потребуется обратная связь с пользователем, за исключением вопросов завершения работы или вывода сообщения об ошибке, которые можно решить с помощью простого окна для вывода сообщений. Так как Nautilus написан на основе библиотеки графического интерфейса пользователя gtk, стоит использовать именно эту реализацию, хотя это и не обязательно. Также можно использовать TkInter или wxPython.

В данной статье будет использоваться gtk. Создание простого диалогового окна для вывода отчета о выполнении операции потребует всего нескольких строк кода. Для удобства восприятия будет полезно создать простую функцию для генерации сообщения. Это потребует всего четырех строк кода, как показано ниже:

def alert(msg):
    dialog = gtk.MessageDialog()
    dialog.set_markup(msg)
	dialog.run()

Пример: Создание простого сценария, возвращающего количество выбранных файлов

Первый пример собирает представленные ранее фрагменты в простой сценарий, возвращающий количество выбранных файлов. Этот сценарий может работать с отдельными файлами или каталогами. В нем используется еще одна функция из библиотеки Python – os.walk, которая рекурсивно строит список файлов в каждом каталоге. Для создания такой утилиты потребуются всего 38 строк кода, приведенные в листинге 1.

Листинг 1. Исходный код Python для сценария Filecount
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
import os

def alert(msg):
    """открывает диалоговое окно с простым сообщением."""

    dialog = gtk.MessageDialog()
    dialog.set_markup(msg)
    dialog.run()

def main():
    selected = os.environ.get('NAUTILUS_SCRIPT_SELECTED_URIS', '')
    curdir = os.environ.get('NAUTILUS_SCRIPT_CURRENT_URI', os.curdir)
    
    if selected:
        targets = selected.splitlines()
    else:
        targets = [curdir]
    
    files = []
    directories = []
    
    for target in targets:
        if target.startswith('file:///'):
            target = target[7:]
        for dirname, dirnames, filenames in os.walk(target):
            for dirname in dirnames:
                directories.append(dirname)
            for filename in filenames:
                files.append(filename)

    alert('%s directories and %s files' %
          (len(directories),len(files)))

if __name__ == "__main__":
    main()

На рисунке 1 показано контекстное меню Nautilus, открывающееся при щелчке правой кнопки мыши по отдельному файлу или выбранной группе файлов. Подменю Scripts содержит все исполняемые файлы, находящиеся в каталоге .gnome2/nautilus-scripts, а также опцию для открытия этого каталога. Выбор одного из файлов приведет к запуску соответствующего сценария.

Рисунок 1. Контекстное меню, открывающееся при выборе файла в Nautilus
Рисунок 1. Контекстное меню, открывающееся при выборе файла в Nautilus

На рисунке 2 показан результат запуска сценария Filecount.py.

Рисунок 2. Результат запуска сценария Filecount.py
Рисунок 2. Результат запуска сценария Filecount.py

Стоит упомянуть несколько полезных моментов, касающихся отладки сценариев Nautilus. Во-первых, необходимо закрыть все запущенные экземпляры Nautilus, чтобы он смог полностью перезагрузиться и найти новые сценарии или расширения. Это можно сделать с помощью следующей команды:

nautilus -q

Другая полезная команда позволяет запустить Nautilus, не открывая файла с настройками или профилем пользователя. Это поможет сэкономить несколько шагов позже, в случае если сценарий или расширение непреднамеренно повредит что-либо. Это можно сделать с помощью следующей команды:

nautilus -no-desktop

На последнем шаге, чтобы открыть доступ к утилите filecount из Nautilus, потребуется скопировать сценарий в каталог ~/.gnome2/nautilus-scripts и изменить его параметры, сделав его исполняемым. Для этого используется следующая команда:

chmod +x Filecount.py

Пример: Создание утилиты для удаления временных файлов

В качестве второго примера будет разработана утилита для удаления файлов, выполняющая поиск временных файлов, созданных текстовыми редакторами, такими как Vim или EMACS. Этот же подход можно использовать и для удаления каталога с определенными файлами, просто изменив функцию check. Этот код относится к категории "молчаливых" - т.е. он будет выполняться без взаимодействия с пользователем.

Функция main, использующаяся в данном сценарии, похожа на аналогичную функцию из прошлого примера, за несколькими исключениями. В представленном коде с помощью рекурсии основная функция вызывается многократно, пока не будет исследован последний каталог. Без помощи рекурсии эту задачу можно было бы решить с помощью функции os.walk. Проверка файлов выполняется в функции check, которая просто ищет файлы, названия которых заканчиваются символами ~ (тильда) или # (решетка) или начинаются с символа # или имеют расширение .pyc. В этом примере используется несколько функций из модуля os стандартной библиотеки Python. Также этот код служит хорошим примером мультиплатформенной реализации действий с именами файлов и каталогов и выполнения других операций с файлами. Полный код данного сценария приведен в листинге 2.

Листинг 2. Код Python-сценария для удаления временных файлов
#!/usr/bin/env python

import pygtk
pygtk.require('2.0')
import gtk
import os

def check(path):
    """функция возвращает true, если файл должен быть удален."""
    
    if path.endswith('~'):
        return True
    if path.startswith('#') and basename.endswith('#'):
        return True
    if path.endswith('.pyc'):
        return True
    return False

def walk(dirname=None):
    selected = os.environ.get('NAUTILUS_SCRIPT_SELECTED_FILE_PATHS', '')
    curdir = os.environ.get('NAUTILUS_SCRIPT_CURRENT_URI', os.curdir)
    
    if dirname is not None:
        targets = [dirname]
    elif selected:
        targets = selected.splitlines()
    else:
        targets = [curdir]
    
    for target in targets:
        if target.startswith('file:///'):
            target = target[7:]
        if not os.path.isdir(target): continue
        for dirname, dirnames, files in os.walk(target):
            for dir in dirnames:
                dir = os.path.join(dirname, dir)
                walk(dir)
            for file in files:
                file = os.path.join(dirname, file)
                if check(file):
                    os.remove(file)

if __name__ == '__main__':
    walk()

Создание расширений для Nautilus

Второй способ добавления новой функциональности в Nautilus основан на создании расширений. Этот способ сложнее, чем рассматривавшийся ранее, но у него имеются дополнительные преимущества. Расширения Nautilus могут быть встроены в окно отображения файлов, поэтому можно написать расширение, которое добавит столбец с информацией, не отображавшейся ранее. Но сначала потребуется установить пакет с расширениями python-nautilus с помощью следующей команды:

sudo apt-get install python-nautilus

Эта команда загрузит и установит все необходимые файлы, включая документацию и примеры. Исходный код примеров можно найти в папке /usr/share/doc/python-nautilus/examples. После установки пакета для разработки программ можно будет использовать классы и провайдеры Nautilus, список которых приведен в таблице 2.

Таблица 2. Классы и провайдеры Nautilus
Класс или провайдерОписание
nautilus.ColumnСсылка на объект column (столбец) в Nautilus
nautilus.FileInfoСсылка на объект fileinfo (информация о файле) в Nautilus
nautilus.MenuСсылка на объект menu (меню) в Nautilus
nautilus.MenuItemСсылка на объект menuitem (элемент меню) в Nautilus
nautilus.PropertyPageСсылка на объект propertypage (окно свойств) в Nautilus
nautilus.ColumnProviderОбеспечивает вывод информации в колонку в главном окне Nautilus
nautilus.InfoProviderПредоставляет доступ к информации о файле
nautilus.LocationWidgetProviderОтображает местоположение в файловой системе
nautilus.MenuProviderДобавление новой функциональности в контекстное меню, вызываемое правой кнопкой мыши
nautilus.PropertyPageProviderДобавляет информацию в окно свойств

В примерах, представленных на Web-сайте gnome.org, демонстрируется использование классов MenuProvider (background-image.py и open-terminal.py), ColumnProvider и InfoProvider (block-size-column.py), PropertyPageProvider (md5sum-property-page.py). В примере, посвященном ColumnProvider, содержится 13 строк исполняемого кода на языке Python, который добавляют в Nautilus новый столбец. Для того чтобы получить доступ к новой опции, необходимо положить сценарий в соответствующий каталог (~/.nautilus/python-extensions) и перезапустить Nautilus. После этого новый столбец Block Size можно будет сделать видимым, выбрав соответствующий переключатель в меню View (Представление) -> Visible Columns (Видимые столбцы), что приведет к вызову следующего метода Python-библиотеки:

str(os.stat(filename).st_blksize))

Примечание: Опция Visible Columns станет доступна, только после переключения представления в режим List (Список).

Стандартный подход к реализации любого Python-расширения для Nautilus состоит в наследовании существующего базового класса Nautilus-провайдера и последующем выполнении набора инструкций, возвращающего соответствующий Nautilus-объект. В примере block-size-column.py возвращается объект типа nautilus.Column. Создаваемый Nautilus-объект должен содержать четыре параметра: название, атрибут, метку и описание, как показано ниже:

return nautilus.Column("NautilusPython::block_size_column", 
                       "block_size", 
                       "Block size", 
                       "Get the block size")

При создании нового расширения необходимая информация наследуется из определенного базового класса. В примере block-size-column.py в определении класса перечислены два класса: nautilus.ColumnProvider и nautilus.InfoProvider, поэтому новый класс одновременно наследует этим двум классам. Далее требуется переопределить необходимые методы из базового класса или классов, чтобы заполнить новый столбец требуемой информацией. Для этого в примере block-size-column.py переопределяются методы get_columns и update_file_info.

Информация в расширения Nautilus передается немного по-другому, чем в сценарии. Как было сказано, Nautilus запускает новый процесс для выполнения сценария и устанавливает переменные среды для передачи информации. Расширения выполняются в том же процессе, что и сам Nautilus, и поэтому имеют доступ ко всем его объектам, методам и атрибутам. Информация о файлах передается через объект nautilus.FileInfo, включая такие параметры, как file_type (тип файла), location (местоположение), name (имя), uri и mime_type (MIME-тип). Для добавления информации в объект FileInfo необходимо вызвать метод add_string_attribute. В представленном примере для добавления атрибутов в объект FileInfo используется именно такой способ.

Пример: Вывод количества строк в файле

В данном примере метод класса PropertyPageProvider используется для вывода количества строк и символов. Эта информация выводится, если правой кнопкой мыши открыть контекстное меню файла и выбрать опцию Properties (Свойства). Основная задача этого расширения – посчитать количество строк и символов в файле и вывести результат на новой вкладке в окне свойств файла. Расширения имеют непосредственный доступ к структурам данных Nautilus, включая объект file. Единственное, что требуется сделать, - это извлечь название файла с помощью функции urllib.unquote, входящей в стандартную библиотеку:

filename = urllib.unquote(file.get_uri()[7:]

Для выполнения остальной работы по подсчету количества строк и символов потребуется всего несколько строк кода. В примере создается функция count, считывающая весь файл в одну большую строку, а затем подсчитывающая общее число символов и количество символов конца строки. Так как окно свойств может отображаться сразу для нескольких выбранных файлов или каталогов, то нужно предусмотреть возможность обработки нескольких файлов. Затем остается только вывести результаты на новой странице в окне свойств. В листинге 3 для этого создается простой объект gtk.Hbox, куда затем добавляется несколько текстовых элементов с собранной информацией.

Листинг 3. Исходный код расширения Linecountextension.py
import nautilus
import urllib
import gtk
import os

types = ['.py','.js','.html','.css','.txt','.rst','.cgi']
exceptions = ('MochiKit.js',)

class LineCountPropertyPage(nautilus.PropertyPageProvider):
    def __init__(self):
        pass
    
    def count(self, filename):
        s = open(filename).read()
        return s.count('\n'), len(s)
    
    def get_property_pages(self, files):
        if not len(files):
            return
        
        lines = 0
        chars = 0
        
        for file in files:
            if not file.is_directory():
                result = self.count(urllib.unquote(file.get_uri()[7:]))
                lines += result[0]
                chars += result[1]

        self.property_label = gtk.Label('Linecount')
        self.property_label.show()

        self.hbox = gtk.HBox(0, False)
        self.hbox.show()

        label = gtk.Label('Lines:')
        label.show()
        self.hbox.pack_start(label)

        self.value_label = gtk.Label()
        self.hbox.pack_start(self.value_label)

        self.value_label.set_text(str(lines))
        self.value_label.show()
        
        self.chars_label = gtk.Label('Characters:')
        self.chars_label.show()
        self.hbox.pack_start(self.chars_label)
        
        self.chars_value = gtk.Label()
        self.hbox.pack_start(self.chars_value)
        self.chars_value.set_text(str(chars))
        self.chars_value.show()
        
        return nautilus.PropertyPage("NautilusPython::linecount",
                                     self.property_label, self.hbox),

На рисунке 3 показано окно свойств файла с открытой вкладкой Linecount, куда был выведен результат запуска этого расширения. Важно отметить, что эта функциональность работает как с отдельными файлами, так и с несколькими выбранными файлами и каталогами. Выведенное число отражает суммарное количество строк во всех выбранных файлах.

Рисунок 3. Вкладка Linecount с информацией о количестве строк в файле
Рисунок 3. Вкладка Linecount с информацией о количестве строк в файле

Теперь можно модифицировать расширение таким образом, чтобы информация выводилась в столбец вместо вкладки в окне свойств. Для этого потребуется совсем немного изменений, хотя класс расширения в данном случае должен будет наследовать классам nautilus.ColumnProvider и nautilus.InfoProvider. Также потребуется переопределить методы get_columns и pdate_file_info. Метод get_columns должен возвращать информацию, собранную методом count.

Для расширения, основанного на классе nautilus.ColumnProvider, потребуется внести изменения в метод count. С помощью процедуры readlines из стандартной библиотеки Python всех строки файла считываются в список объектов типа string. Теперь, чтобы посчитать количество строк в файле, достаточно просто посчитать количество элементов в списке, которое можно получить с помощью инструкции len(s). В обоих случаях используется одинаковая проверка типа файла, так как считать количество строк имеет смысл только для тех файлов, где они есть. Список допустимых расширений файлов определяется с помощью следующей инструкции:

types = ['.py','.js','.html','.css','.txt','.rst','.cgi']

Второй список содержит исключаемые файлы, которые не нужно обрабатывать. Например, для исключения конкретного файла можно воспользоваться следующей строкой кода:

exceptions = ['MochiKit.js']

Оба эти списка затем используются для включения или исключения файлов из обработки с помощью следующих строк кода:

if ext not in types or basename in exceptions:
    return 0

В общей сложности наше расширение состоит из 26 строк. При желании можно модифицировать добавляющие или исключающие списки, добавив туда интересующие файлы. Исходный код расширения приведен в листинге 4.

Листинг 4. Исходный код расширения Linecountcolumn
import nautilus
import urllib
import os

types = ['.py','.js','.html','.css','.txt','.rst','.cgi']
exceptions = ['MochiKit.js']

class LineCountExtension(nautilus.ColumnProvider, nautilus.InfoProvider):
    def __init__(self):
        pass
    
    def count(self, filename):
        ext = os.path.splitext(filename)[1]
        basename = os.path.basename(filename)
        if ext not in types or basename in exceptions:
            return 0

        s = open(filename).readlines()
        return len(s)
    
    def get_columns(self):
        return nautilus.Column("NautilusPython::linecount",
                               "linecount",
                               "Line Count",
                               "The number of lines of code"),

    def update_file_info(self, file):
        if file.is_directory():
            lines = 'n/a'
        else:
            lines = self.count(urllib.unquote(file.get_uri()[7:]))
        
        file.add_string_attribute('linecount', str(lines))

На рисунке 4 показано окно Nautilus с активированным столбцом Line Count (количество строк). Для каждого файла выводится количество содержащихся в нем строк. Если с помощью данного метода потребуется выводить общее количество строк для нескольких файлов, то его можно будет дополнить арифметическими выражениями.

Рисунок 4. Столбец Line Count в окне Nautilus
Рисунок 4. Столбец Line Count в окне Nautilus

Заключение

Как было показано, расширить возможности Nautilus с помощью Python – это довольно простая задача. Простота и элегантность Python и его стандартной библиотеки позволяют создавать эффективный и удобный для восприятия код. Изучение примеров и документации на Web-сайте gnome.org хотя и может доставить определенные трудности, но вряд ли является непреодолимой преградой. Дополнительные примеры легко отыскать с помощью Google или любого другого поискового портала. Примеры, приведенные в данной статье, дают представления о различных способах расширения функциональности Nautilus. Так что при хорошем уровне владения языком Python вам не составит труда выбрать подходящее решение и реализовать его.

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=788804
ArticleTitle=Разработка сценариев для рабочих столов Linux: Часть 2. Разработка сценариев для файлового менеджера Nautilus
publish-date=01242012