úterý 22. května 2012

Nautilus Extension API z Pythonu

Je víc možností, jak rozšířit funkčnost správce souborů Nautilus. Kontextové menu můžete vylepšit o nějakou novou funkci nejjednodušeji pomocí externích skriptů, jak už jsem tu psal. Můžete využít plných možností API Nautila a psát rozšíření (extensions) přímo v C, ale můžete také využít rošíření Nautilus Python a psát je v Pythonu. Poslední zmíněné řešení vám oproti tomu prvnímu dává daleko víc možností a oproti druhému s tím budete mít daleko méně práce. Méně práce to je moje a tak se snad moc nepředřu, když se budu snažit fungování Nautilus Extension API trochu přiblížit. Přidám i odkaz na repozitář s pár hotovými rozšířeními připravenými rovnou k instalaci.


Nautilus Python je rozšíření Nautila napsané v C a zpřístupňuje jeho API z Pythonu. Nainstalujete příkazem:

sudo apt-get install python-nautilus

Skripty rozšíření se umisťují do následujících adresářů, podle toho, zda je chcete aplikovat všem uživatelům, nebo jen jednotlivcům:

/usr/share/nautilus-python/extensions/
~/.local/share/nautilus-python/extensions/

Příklady rozšíření, které můžete studovat, najdete i ve vašem počítači zde:

/usr/share/doc/python-nautilus/examples/

V kódu vašeho rozšíření pak importujete modul Nautilus takto:

from gi.repository import Nautilus

Konkrétní kód jednoho rozšíření trochu rozeberu dál.

API Nautila zahrnuje tyto třídy:


Nautilus.Column - Instance této třídy jsou očekávány od rozšíření založeného na Nautilus.ColumnProvider
Nautilus.FileInfo - Instance této třídy vytváří Nautilus pro každý soubor, který zrovna uživatele zajímá (nachází se v právě otevřeném adresáři) a rozšíření založená na interfejsu Nautilus.InfoProvider tyto objekty doplňují o další atributy.
Nautilus.Menu - Submenu pro strukturování Nautilus.MenuItems.
Nautilus.MenuItem - Seznam instancí této třídy je očekáván od rozšíření založeného na Nautilus.MenuProvider
Nautilus.PropertyPage - Seznam instancí této třídy je očekáván od rozšíření založeného na Nautilus.PropertyPageProvider.
Nautilus.InfoProvider — Poskytuje interface, který umožňuje doplnit objekty Nautilus.FileInfo o další informace o souborech, včetně emblémů. Pro představu, emblémy, které indikují stav synchronizace souborů, takto řeší třeba Ubuntu One, nebo Dropbox.

Nautilus.ColumnProvider — Poskytuje interface pro přidávání sloupců pro režim zobrazení Seznam (List). Rozšíření na něm založená nejčastěji zahrnují i Nautilus.FileInfoProvider k získání potřebných dat. Můžete třeba přidat sloupec s délkou hudebních souborů apod. Vrací seznam instancí třídy Nautilus.Column.
Nautilus.MenuProvider — Asi nejpoužívanější interface, umožňuje přidat funkce do kontextové nabídky souborů. Vrací seznam instancí třídy Nautilus.MenuItem
Nautilus.PropertyPageProvider — Umožňuje přidávat do okna Vlastnosti (Properties) vlastní stránky (taby). Vrací seznam objektů Nautilus.PropertyPage
Nautilus.LocationWidgetProvider — S pomocí tohoto interfejsu můžete nad výpis souborů v okně Nautila přidat vlastní Gtk widget. To umožňuje například ty nabídky na otevření adresáře ve video přehrávači, které Nautilus přidává do okna, když má za to, že vidí DVD, nebo import fotografií z externích médií. Rozšíření vrací objekt odvozený od Gtk. Widget a je Nautilem voláno ještě před zobrazením vlastních souborů s argumenty v podobě aktuální URI a instance okna.

Sice píšu "vrací seznam ...", ale vracet ho musí váš kód, vaše metody, které dopíšete do vaší třídy odvozené od GObject.GObject a jedné nebo více výše zmíněných tříd  Nautilus.....Provider. Viz. dále.

Dokumentace k výše uvedenému i s příklady je zde:
http://projects.gnome.org/nautilus-python/documentation/html/index.html

Jak to funguje


Když Nautilus otevře nějaký adresář, zavolá Nautilus.LocationWidgetProvidery, předá jim URI aktuálního adresáře a ti případně dodají Gtk widget, jenž se zobrazí v okně nad výpisem souborů. Dále vytvoří pro každý soubor (včetně adresářů), který v adresáři najde, instanci třídy Nautilus.FileInfo a naplní ji informacemi, které soubor charakterizují. Základní info jako mime type a GnomeVFSFileInfo, dodá sám a pak objekt postupně předá všem aktivním rozšířením založeným na Nautilus.InfoProvider, aby doplnily, co potřebují jejich klienti. Mezi data, která může tento objekt obsahovat patří i emblémy, které pak Nautilus 'lepí' na ikonu daného souboru. Pokud se nějaký soubor změní, nebo je vyžadována aktualizace informací o souboru z jiného důvodu, vyzve Nautilus InfoProvidery k aktualizaci. Pokud InfoProvideři potřebují k získání potřebných dat větší než zanedbatelný čas, například spouští další programy, komunikují s diskem, měli by fungovat v asynchronním režimu - tedy vrátí Nautilu OperationResult.IN_PROGRESS a úkol vyřeší v jiném vlákně. Nautilus zatím pokračuje a až se rozšíření přihlásí s výsledkem, aktualizuje, co je třeba. Jelikož kód rozšíření běží v hlavním vlákně Nautila, může zabržděný InfoProvider, který nepoužije asynchronní řešení Nautilus totálně odrovnat co se rychlosti zobrazování souborů v adresáři týká.
Když kliknete v okně Nautila na soubor pravým myšítkem, zavolá Nautilus příslušné metody MenuProviderů, aby dodali své položky do menu. Pokud pak kliknete na položku Vlastnosti (Properties), zavolá zas PropertyPageProvidery, aby dodali stránky do okna Vlastností. V obou případech předá Nautilus Providerům seznam vybraných souborů a/nebo aktuální adresář v podobě seznamu instancí Nautilus.FileInfo. Na základě vlastností souborů se Provider může rozhodnout, zda uživateli vůbec něco nabídne, případně vytvoří obsah a vrátí ho ve formě objektů, které Nautilus od daného providera očekává.

Pro příklad vezmeme rozšíření kontextové nabídky:
Kliknete pravým myšítkem na soubor, nebo výběr souborů, v ten okamžik Nautilus obejde všechny extensions založené na Nautilus.MenuProvider a zavolá předepsané metody tohoto interfejsu s patřičnými parametry. Rozšíření typu MenuProvider musí obsahovat alespoň jednu ze zvýrazněných metod:

class Nautilus.MenuProvider:
   
def get_file_items(window, files)
   def get_file_items_full(provider, window, files)
   def get_background_items(window, folder)
   def get_background_items_full(provider, window, folder)

   def Nautilus.menu_provider_emit_items_updated_signal(provider)


V podstatě jde o dvě základní možnosti -
get_file_items dostane jako argument seznam s vybranými soubory a get_background_items dostane aktuální adresář, jehož obsah právě Nautilus zobrazuje.
Případně jejich _full verze, které mezi argumenty dostanou i instanci aktuálního MenuProvidera.
Mezi argumenty je i window, což je instance gtk.Window aktuálního okna Nautila. Momentálně mě nenapadá, jak bych okno využil, ale teoreticky můžete třeba změnit jeho velikost.
Vaše rozšíření může zkontrolovat zda vyhovuje počet vybraných souborů, zda typově odpovídají záměru a pokud testem projdou, vrátí Nautilovi v seznamu MenuItem, která po kliknutí zavolá metodu, jenž provede vlastní akci. Položek v seznamu může být více, můžete je i strukturovat do submenu.

Konkrétní příklad


Pro názornost si můžeme ukázat oblíbené rozšíření kontextového menu Otevři jako root. Tento konkrétní skript je z tohoto repozitáře:
https://launchpad.net/~nae-team/+archive/ppa (je tam celá řada dalších rozšíření, která můžete nainstalovat najednou, nebo jednotlivě, co se vám bude hodit)
Jen jsem ho trochu okomentoval.

#nautilus open as root 
#dr3mro@gmail.com
#version 0.2

from gi.repository import Nautilus, GObject
import subprocess,os
import urllib
import re

# třída rozšíření je vždy odvozena od GObjectu a zvoleného interfejsu
class OpenAsRoot(GObject.GObject, Nautilus.MenuProvider):

    def __init__(self):
        pass

    def url2path(self,url):
        fileuri=url.get_activation_uri()
        arg_uri=fileuri[7:]
        path=urllib.url2pathname(arg_uri)
        return re.escape(path)

    # metoda, která bude volána po kliknutí na položku v menu Nautila
    def run(self, menu,url):  # v url bude adresa vybraného souboru, viz dále
        path=self.url2path(url) # přežvýká url na lokální cestu ^^
        args="gvfs-open %s" %path  # příprava příkazu pro shell
                                   # gvfs-open umí otevírat cílový soubor programem
                                   # registrovaným pro daný typ souboru
        subprocess.Popen(["gksu",args]) # spustí se nezávislý subshell 'pod rootem'
                                        # a v něm připravený příkaz
                                        # konec práce tohoto rozšíření

    # tato metoda bude volána Nautilem po otevření kontextového menu
    def get_file_items(self, window, files):
        # pokud je vybrán jiný počet souborů, než jeden, nebude se tím zabývat
        if len(files) != 1:
            return

        # v opačném případě dodá patřičnou položku do menu
        file = files[0]
        item = Nautilus.MenuItem(
                name="MenuItem:OpenAsRoot", # jedinečný identifikátor
                label="Open as Root...",    # text položky v menu
                tip="Open as Root !" # toto se zobrazí v statusbaru po najetí myši
            )

        item.connect('activate', self.run,file) # po aktivaci položky
                       # se spustí metoda 'self.run' s parametrem 'file'
        return [item]  # položku do menu je třeba předat v seznamu,
                       # i když je jen jedna


Závěrem


Rozšíření Nautila umějí být velmi užitečná, ale také ho mohou pěkně poptopit. Nebezpečí je v tom, že jejich kód běží v hlavním vlákně Nautila a pokud se zasekne nějaké rozšíření, zasekne se celý Nautilus.

Typicky jsem třeba viděl rozšíření typu MenuProvider, kde autor ve chvíli, kdy měl dodat položku do menu, rekurzivně projel všechny vybrané adresáře, aby všem souborům zkontroloval koncovky v názvech a ty co vyhovovaly nacpal do seznamu. Když nakonec nebyl seznam prázdný, vrátil Nautilovi, který celou tu dobu čekal, položku do menu. Čekání se mohlo podle počtu souborů opravdu protáhnout. Pokud jste pak na jeho položku v menu klikli, spustil externí aplikaci v shellu, které seznam předal jako argument a zase použil nevhodný způsob jejího spuštění, který Nautila zastavil, dokud aplikace soubory nezpracovala. Ha, našel jsem ten kód, můžete se na tu hrůzu kouknout tady. Je to ještě pro Nautila v GNOME 2, ale rozdíl je defakto jen v rozdílném importu GNOME modulů.

Takže až budete nadávat na to, že je Nautilus pomalý, zkontrolujte si rozšíření, která jste doinstalovali, nebo dokonce sami napsali.

Je jisté, že v některých případech je potřeba dělat kompromisy, málokdo vymění svižnost file manageru za nějaké parádičky, ale často se jen stačí trochu zamyslet, případně něco dostudovat.

Třeba o Nautilus.InfoProvider by se hodilo napsat více, ale já už jsem zas vyčerpaný, tak třeba zase příště.. a máte google. a kachny. a seznam internátů ;)

Pokud se vám zdá text zmatený, nebude to náhoda, jsem rozený spisovatel a terminologii mám v malíčku, o který mě připravila velikonoční lidožravá ovečka.

Odkazy k tématu:


http://projects.gnome.org/nautilus-python/
http://projects.gnome.org/nautilus-python/documentation/html/index.html
http://taschenorakel.de/svn/repos/bulldozer/trunk/documentation/NautilusExtensions.html - není přímo k pythonu, ani nejnovější, ale je tam mnoho k pochopení fungování celého systému rozšíření
http://saravananthirumuruganathan.wordpress.com/2010/08/29/extending-nautilus-context-menus-using-nautilus-actions-scripts-and-python-extensions/ - o rozšíření kontextového menu způsoby


Žádné komentáře:

Okomentovat

Zkuste prosím při komentováni používat místo volby Anonymní volbu Název/adresa URL, kde vyplníte nějakou přezdívku, adresu zadávat nemusíte. Vědět, které příspěvky jsou od jednoho člověka, je fajn. Díky.

Pokud by se vám náhodou odeslaný komentář na stránce nezobrazil, vytáhnu ho z koše hned jak si toho všimnu. I Google spam filter se občas sekne.