Liblicense tutorial
Contents
Python and GTK
With the release of liblicense (ll) 0.3 we've introduced the beginnings of desktop integration. With this brief guide I hope to drive further integration into other apps. While I'll be using the python bindings for simplicity, the C bindings are very similar. In this section I will show how the integration through nautilus-python is done. Here is a screenshot from nautilus.
The first step of writing any plugin is figuring out the relevant APIs. In this case, the relevant APIs are that of liblicense and nautilus-python. The former's API is familiar to me because I wrote it but for consistencies sake the documentation is on the wiki. The nautilus-python documentation is pretty sparse as is the nautilus extension C documentation. However, I found this documentation file on the gnome svn. There are two primary integrations which I'll talk about. The first is the license emblem shown in the screenshot above. The second is a license properties tab. We'll start with the emblem because it uses the read function of liblicense.
First off to have nautilus load the plugin we place the python file in either /usr/lib/nautilus/extensions-1.0/python or ~/.nautilus/python-extensions/. Our name for the file is nautilus-liblicense.py. We'll put both aspects of the integration in this file. Next we'll import the necessary modules.
import liblicense import urllib import gtk import nautilus from liblicense.gui_gtk import *
Urllib is used to replace things such as %20 (for space) with their correct character. Gtk can be ignored for now as we'll see it in the second part. The nautilus import provides the classes which we'll subclass to provide the desired functionality. Lastly, the two liblicense imports provide us with the library functions and the gtk selector widget which we'll use later.
In order to process the files upon load we'll subclass the InfoProvider class from nautilus-python (imported as nautilus). Within this class we need to override one function, update_file_info, which takes on argument, a FileInfo object, in addition to self.
The class so far:
class LicenseInfoProvider(nautilus.InfoProvider): def __init__(self): pass def update_file_info(self, f): pass
There is nothing we need to do upon init but we'll leave it there. In a nautilus-python example they did similar. So now in the update_file_info function we'll need to read the license of the file and assign a corresponding emblem. Currently, two emblems are used, one for Creative Commons licenses and another for anything else. Here is this final code of the update_file_info function.
def update_file_info(self, f): if f.get_uri()[:7]=="file://": license = liblicense.read(urllib.unquote(f.get_uri()[7:])) if license: if "Creative Commons" in liblicense.get_attribute(license,"http://purl.org/dc/elements/1.1/creator",False): f.add_emblem("cc") else: f.add_emblem("licensed")
The first check that is done makes sure that the argument is a local file by check that it begins with "file://". Once that is confirmed, we then read the license of the file by calling liblicense.read(filename). Here urllib is used to convert url encoded characters, such as %20 for a space, so that we have an accurate file path. This is all done on everything after the first seven characters of the uri which get_uri() returned.
After the license is read we check that it is not null. If its not then we use liblicense.get_attribute to get the creator of the license to check if it is Creative Commons or not. We use python's keyword in because get_attribute returns a list of values. The arguments for get_attribute are the license uri, the desired attributes uri (the predicate in RDF terms) and finally a boolean toggling the locale awareness of the processing. Once all of this is done we add the corresponding emblem using the FileInfo object's add_emblem function which takes the emblem keyword. Upon install of liblicense these two icons are added. The keyword is taken from the filename (emblem-keyword.{png,svg}) not the .icon file. That explains all of the first part. If you have further questions visit #cc on irc.freenode.net or email cc-devel@lists.ibiblio.org.
The second part of this section and of the nautilus liblicense integration is the properties tab. Through this tab one can see the exact license the file is currently under and they can also modify the license. This is done by utilizing the packaged LicenseChooser liblicense gtk widget. Lets have a look.
First lets set up the nautilus API for a property page:
class LicensePropertyPage(nautilus.PropertyPageProvider): def __init__(self): pass def get_property_pages(self, files): pass
Again, similar to the first section we're subclassing a nautilus class. And again, we need to subclass one function. This time we need to subclass the get_property_pages method. This method takes in a list of FileInfo objects and returns a sequence of PropertyPages.
Alright, lets get started. While the first iteration of this integration only supported a single file, we'll jump straight to the multiple file version.
def get_property_pages(self, files): self.files = files self.files = filter(lambda f: f.get_uri_scheme() == 'file' and not f.is_directory(),self.files) self.files = map(lambda f: urllib.unquote(f.get_uri()[7:]),self.files) if len(self.files)==0: return
In this first bit we do some python wizardry. First we store the files list in the object itself, we'll need it later. Now we filter out the files with a different uri scheme and those which are directories. This leaves us with a list of FileInfo objects which have uris in the "file:///path/filename.ext" form. So to get a list of files in the liblicense form we use map and urllib.unquote like we did in the previous part. Finally, before we continue, we make sure we have not filtered out all of the selected items.
Now that we have a list of relevant files we need to do one of two things, if there is only one file selected we start the license chooser displaying the appropriate license or we display no license if multiple files are selected. get_property_pages continued:
self.property_label = gtk.Label('License') self.property_label.show() if len(files) == 1: license = liblicense.read(self.files[0]) if license==None: license = liblicense.get_default() else: license = None self.box = LicenseWidget(license)
First, we create the label for the property tab. After this we see how many files we are dealing with. If its one, we call read on the first and only element of the list and proceed to check whether it exists (in other words, is None or not). When the license is None we default to the system default license. If there is multiple files we simply set the license to None. Finally, we create the LicenseWidget (which is a subclass of gtk.HBox) passing the desired starting license uri in.
The last task we must complete is to write the license to the selected file(s) when the properties window is closed. To do this we attach a callback to the destroy signal of the LicenseWidget.
self.box.connect("destroy",self.license_chosen) self.box.show() return nautilus.PropertyPage("NautilusPython::license", self.property_label, self.box),
The above should be self explanatory. We simply use the PyGTK widget method connect to attach a method to the signal. Then we return a sequence (note the ending comma) of PropertyPages created in the return statement. I copied this from the nautilus-python example and assume the arguments are an identifier, a label and a widget to be packed in the tab.
Lastly, the callback method is pretty simple.
def license_chosen(self, widget): license = self.box.get_license() if license: for f in self.files: liblicense.write(f,license)
Like all GTK callbacks the first argument is the widget which emitted the signal along with self. Upon receiving this signal we get the current license of the widget by calling the LicenseWidget's get_license method which returns the uri for the selected license or None if no license is selected. Then we check to see if a license was selected and if so iterate through the selected files writing the license info to each one. This writing is done using the liblicense write method which takes the file path and the license uri. It utilizes liblicense modules which write to a variety of different file formats.
Well, thats the end of the liblicense nautilus integration. The entire file can be seen at Liblicense svn browse.
--Scott Shawcroft 17:03, 29 July 2007 (EDT)
License Chooser
Liblicense aims to make integration of license selection into applications seamless. The license chooser API allows for selecting licenses based on what the license permits, requires, and/or prohibits. For example, the license chooser API makes an excellent backend to the following:
Graphical License Chooser
I'll start with a simple example that uses the Python bindings:
from liblicense import LicenseChooser import liblicense as ll attributes = ["http://creativecommons.org/ns#Distribution", "http://creativecommons.org/ns#DerivativeWorks", "http://creativecommons.org/ns#Attribution", "http://creativecommons.org/ns#Notice"] chooser = LicenseChooser(None,attributes) chooser.get_licenses(permits=chooser.attribute_flag("http://creativecommons.org/ns#Distribution") | chooser.attribute_flag("http://creativecommons.org/ns#DerivativeWorks"), requires=ll.UNSPECIFIED, prohibits=ll.UNSPECIFIED) >>> ['http://creativecommons.org/licenses/publicdomain/']
LicenseChooser takes two arguments:
- The first is a string representing the jurisdiction of licenses to return. In this case, we specify None to only return Unported licenses. For example, "us" or "uk" would be acceptable input, for United States and United Kingdom licenses, respectively.
- The second is a list of attributes to search on. Note: this is not Creative Commons specific -- you may specify any attributes in this list.
Now that we have a LicenseChooser, we can query it for licenses based on flags. There's a bit of voodoo here, but I hope that you can follow by example. Let's start with this:
chooser.attribute_flag("http://creativecommons.org/ns#Distribution") | chooser.attribute_flag("http://creativecommons.org/ns#DerivativeWorks")
These are the permits flags from the above example. What this says is that we want licenses that permit Distribution and DerivativeWorks. Notice that if we want to search based on multiple attributes, the calls to chooser.attribute_flag("attribute string") can be OR'ed together.
Flags we don't care about (in this example, requires and prohibits), we set to ll.UNSPECIFIED. Alternatively, they can be left out altogether.
Because we also specified the Attribution and Notice attributes, the above query will return licenses that do not permit, require, or prohibit these attributes. This particular query returns one license: Public Domain.
PyGTK Widget
Now that we have the basics for how the LicenseChooser works, we are going to build the following license selection widget using PyGTK. Because it is included with Liblicense, I'll only explain the relevant parts of the code. The full source is available here: http://cctools.svn.sourceforge.net/viewvc/cctools/liblicense/trunk/src/gnome/gui_gtk.py.in.
My hope is that this short tutorial will help in creating license choosers using other graphical toolkits and other languages.
First off, I'll assume you've built the above GUI. You should have something like the following:
import gtk import gobject import liblicense class LicenseWidget(gtk.VBox): __gsignals__ = {"changed":(gobject.SIGNAL_RUN_FIRST,gobject.TYPE_NONE,(gobject.TYPE_STRING,))} def __init__(self): # Build your GUI here
You should have 5 gtk.CheckButton's.
- self.ash: Allow Sharing
- self.ar : Allow Remixing
- self.pcw: Prohibit Commercial Works
- self.sa : Require Share-Alike
- self.by : Require Attribution
And 2 gtk.Entry's.
- self.uri : Displays the URI of the matching license
- self.license : Displays the name of the matching license
Note that you could use any attributes and any number of attributes you would like. These 5 fit well with Creative Commons licenses.
We will also create the license chooser in __init__.
attributes = ["http://creativecommons.org/ns#Attribution", "http://creativecommons.org/ns#Distribution", "http://creativecommons.org/ns#DerivativeWorks", "http://creativecommons.org/ns#CommercialUse", "http://creativecommons.org/ns#ShareAlike"] self.ll_chooser = liblicense.LicenseChooser(None,attributes)
Now let's connect the five buttons up.
self.by.connect("toggled",self.checkbox_toggled,0) self.ash.connect("toggled",self.checkbox_toggled,1) self.ar.connect("toggled",self.checkbox_toggled,2) self.pcw.connect("toggled",self.checkbox_toggled,3) self.sa.connect("toggled",self.checkbox_toggled,4)
Here's what we'll do when a checkbox is clicked.
def checkbox_toggled(self,button,flag): self.update_license() def update_license(self): permits = liblicense.UNSPECIFIED requires = liblicense.UNSPECIFIED prohibits = liblicense.UNSPECIFIED if self.ash.get_active(): permits |= self.ll_chooser.attribute_flag("http://creativecommons.org/ns#Distribution") if self.ar.get_active(): permits |= self.ll_chooser.attribute_flag("http://creativecommons.org/ns#DerivativeWorks") if self.by.get_active(): requires |= self.ll_chooser.attribute_flag("http://creativecommons.org/ns#Attribution") if self.sa.get_active(): requires |= self.ll_chooser.attribute_flag("http://creativecommons.org/ns#ShareAlike") if self.pcw.get_active(): prohibits |= self.ll_chooser.attribute_flag("http://creativecommons.org/ns#CommercialUse") licenses = self.ll_chooser.get_licenses(permits=permits, requires=requires, prohibits=prohibits) if licenses: uri = licenses[0] self.emit("changed",uri) self.uri.set_text(uri) v = liblicense.get_version(uri) self.license.set_text("%s - %d.%d.%d" % (liblicense.get_name(u), v[0], v[1], v[2])) else: self.uri.set_text("") self.license.set_text("none")
I'll break this down.
licenses = self.ll_chooser.get_licenses( permits=(self.ash.get_active()<<1) | (self.ar.get_active()<<2), requires=(self.by.get_active()<<0) | (self.sa.get_active()<<4), prohibits=(self.pcw.get_active()<<3))
Here are those voodoo flags again. The important thing to realize is that the above numbers correspond to the index in the attributes array. If get_active() returns False, then that component drops out of the expression and is effectively ignored. Otherwise, the correct bit is set.
if licenses: uri = licenses[0] self.emit("changed",uri) self.uri.set_text(uri) v = liblicense.get_version(uri) self.license.set_text("%s - %d.%d.%d" % (liblicense.get_name(u), v[0], v[1], v[2]))
A license matched, so we'll use liblicense to get the name and version of the license to display, along with the URI. Note that the LicenseChooser returned a list of licenses. For simplicity, we'll only display the first in the list. We'll also emit the "changed" signal so that the application is notified of the license change.
else: self.uri.set_text("") self.license.set_text("none")
No license matched so we do a bit of cleanup.
And that's about it. I won't cover the details of jurisdiction at the moment, but this can easily be accomplished by creating a new LicenseChooser with the appropriate jurisdiction whenever a new jurisdiction is selected.
C bindings
Here is a complete, standalone example using the C bindings. Integration into applications follows similarly to the Python bindings above.
#include <liblicense.h> #include <stdio.h> int main() { ll_init(); char *attributes[] = { "http://creativecommons.org/ns#Distribution", "http://creativecommons.org/ns#CommercialUse", "http://creativecommons.org/ns#DerivativeWorks", "http://creativecommons.org/ns#ShareAlike", "http://creativecommons.org/ns#Attribution", NULL }; ll_license_chooser_t *chooser = ll_new_license_chooser(NULL,attributes); int permits, requires, prohibits; permits = ll_attribute_flag("http://creativecommons.org/ns#Distribution") | ll_attribute_flag("http://creativecommons.org/ns#DerivativeWorks"); requires = ll_attribute_flag("http://creativecommons.org/ns#ShareAlike") | ll_attribute_flag("http://creativecommons.org/ns#Attribution"); prohibits = ll_attribute_flag("http://creativecommons.org/ns#CommercialUse"); const ll_license_list_t *licenses = ll_get_licenses_from_flags(chooser,permits,requires,prohibits); while (licenses) { printf("%s\n",licenses->license); licenses = licenses->next; } ll_free_license_chooser(chooser); ll_stop(); return 0; }
Command-line Interface
Incomplete Insert non-formatted text hereHSAENZL EN WEBFile:Example.jpgYAHOOFile:Example.jpgHSAENZ == Headline text ==Media:Example.ogg