вторник, 3 мая 2016 г.

Nautilus Extension for FictionBook2 (FB2)

Расширение для файлового менеджера Nautilus, показывающее в отдельных колонках (табличный режим просмотра) информацию об авторе книги и её названии.

Исходники можно получить https://github.com/yastrov/nautilus_extension_fictionbook2 , а сборка практически тривиальна и описана в README. (Хотя для версии Nautilus отличной от 3, придётся поправить в Makefile пути к расширениям.)

Наконец-то нашёл время, чтобы осуществить старое желание написать полезное расширение.

В процессе создания пришлось погулять по интернету, причём улов был очень скромный. Очень мало примеров, очень скудная документация, особенно когда дело доходит до не совсем классических вещей.
Поэтому результат представляет собой попытку осмыслить в том числе редкие примеры на языке Си, а так же Python. На последнем примеров больше, хотя мне не кажется скриптование подобных вещей, постоянно находящихся в памяти и работающих, хорошим и оптимальным решением.

Не буду останавливаться на описании интерфейсов самого Nautilus. Тут много рутины, и выглядит оно не самым наглядным на мой взгляд образом. (И, что греха таить - было частично заимствовано из примеров. Но не суть.)

На чём стоит остановиться:

Почему не Python?
Причиной, что кроме любви к Си, было желание сделать компактное (в плане потребляемой памяти) и достаточно быстрое решение. А в качестве бонуса - не смотря на отсутствие бинарников на данный момент, ещё и более простое распространение - установку.

По возможности были использованы функции GLib вместо стандартных Си. Потому, что она рекомендована из-за безопасности - т.е. обёртки над функциями стандартной библиотеки языка, которые она предоставляет, уже содержат проверки, в частности указателей на NULL.

Асинхронная обработка оказалась не настолько понятно описанной как в доках, так и в примерах. И это плохо, потому что при работе с блокирующими операциями нужно использовать именно её. Поэтому сложно ручаться, что она получилась оптимальной. Однако основную задачу - асинхронность, она выполняет. (Все виденные примеры, были написаны на Python.)

Долго думал, использовать для отложенного вызова g_timeout_add с секундной задержкой (что популярно, но всё-таки не совсем правильно: может произойти вызов примерно одновременно для множества файлов) или g_idle_add, обещающую вызов тогда, когда снизится нагрузка (про последнюю не вполне понятно, гарантируется ли её вызов в обозримом будущем, или может отложиться на неопределённое время.) Пока победила g_idle_add.

Приведу небольшой фрагмент кода в качестве иллюстрации:

Для того, чтобы можно было использовать асинхронную обработку, необходимо определить примерно следующую структуру:

typedef struct {
    GClosure *update_complete;
    NautilusInfoProvider *provider;
    NautilusFileInfo *file;
    int operation_handle;
    gboolean cancelled;
} UpdateHandle;

А наш callback будет выглядеть:

gint
timeout_plain_fb2_callback(gpointer data);

Посмотрим же основную функцию, которую будет вызывать Nautilus:

static NautilusOperationResult
fb2_extension_update_file_info (NautilusInfoProvider *provider,
                NautilusFileInfo *file,
                GClosure *update_complete,
                NautilusOperationHandle **handle)
{
    if(nautilus_file_info_is_directory(file))
        return NAUTILUS_OPERATION_COMPLETE;
    ...
 
    if (!data) {
        /* Получаем имя файла именно таким способом. */
        char *filename = nautilus_file_info_get_name(file);
        const int len = strlen(filename);
        if(len > 4 && g_strcmp0(&filename[len-4], ".fb2") == 0) {
            /* Заполним структуру, необходимую для асинхронной работы. */
            UpdateHandle *update_handle = g_new0 (UpdateHandle, 1);
            update_handle->update_complete = g_closure_ref(update_complete);
            update_handle->provider = provider;
            update_handle->file = g_object_ref (file);
            // Или так: g_timeout_add (1,
            /* Но лучше так: */
            g_idle_add(
                timeout_plain_fb2_callback,
                update_handle);
            /* Польза данной операции не вполне очевидна... */
            *handle = update_handle;
            g_free(filename);
            /* Сообщаем, что выполнение операции отложено. */
            return NAUTILUS_OPERATION_IN_PROGRESS;    
        } else {
           ...
            }
        }
        g_free(filename);
        return NAUTILUS_OPERATION_COMPLETE;
    }

    return NAUTILUS_OPERATION_COMPLETE;
}

callback Практически из официального примера бородатого года:
В нём мы с помощью g_object_set_data_full кэшируем данные на будущее, и устанавливаем
с помощью nautilus_file_info_add_string_attribute для отображения сейчас:

gint
timeout_plain_fb2_callback(gpointer data)
{
    UpdateHandle *handle = (UpdateHandle*)data;
    /* Не вполне очевидно, что же может отменить операцию, но раз в доках предлагается, оставим. */
    if (!handle->cancelled) {
        char *filename = g_file_get_path(nautilus_file_info_get_location(handle->file));
        ...
        }
        g_free(filename);
    }
 
    nautilus_info_provider_update_complete_invoke
                        (handle->update_complete,
                         handle->provider,
                         (NautilusOperationHandle*)handle,
                         NAUTILUS_OPERATION_COMPLETE);
    /* С handle мы закончили. */
    g_closure_unref (handle->update_complete);
    g_object_unref (handle->file);
    g_free (handle);
    return 0;
}

Вывод:

Если не считать громоздких и не совсем наглядных (особенно когда есть желание сделать один единственный файл - исходник) объявлений интерфейсов самого Nautilus, и бедной документации, в целом процесс легче, чем может показаться, а возможности по кастомизации - большие.

Так же стоит при написании расширения искать исходники и примеры на других языках программирования.

Подборка ссылок:

http://web.archive.org/web/20090418175132/http://www.campd.org/stuff/docs/extending-nautilus/NautilusExtensions.html Официальный Nautilus Extension manual, теперь доступный только из архива:

https://developer.gnome.org/libnautilus-extension/stable/ Nautilus API

https://developer.gnome.org/glib/stable/ - GLib

Комментариев нет:

Отправить комментарий