Saturday, March 27, 2010

Whiskyfestival

Some notes from the "WFNN" (Whisky Festival Noord Nederland, der Aa-Kerk, Groningen):

  • Have coffee. Note that other visitors appear to be generally over 40 and male. Make note to skip WFNN in favour of beach volleyball tournament next year.

  • First taste: a Linkwood 12 yrs. (Douglas Laing).

  • Compared a standard Glenlivet (12 years) with an 18 years old one. The 18 years old is fuller.

  • A Clynelish: fresh, salty. Worthy of a recommendation.

  • Had a taste of Ben Riachs: Rum cask vs. Madeira cask.

  • Compared a Highland Park with a MacAllen. Highland Park won, probably because the MacAllen's more subtle tones were lost on my diminishing senses.

  • Compared Dalmore 12 yr. with 15 yr. Both a bit too sweet for me at that time.

  • Finally, a Lagavulin. Superb, as always. (Barman made us eat a mint first?!)

  • Resisted urge to pay 15 euros for a glass of *extremely* rare Lagavulin.

  • Laphroiag tastes a bit thin after that, but still a great taste.

  • Failed to act upon recommendation of rare Aberlour by ex-colleague. ("You want the bottle that's almost empty.")

  • Compared Ardbeg 10 years old with an Ardbeg Corryvreckan. (Corryvreckan won.)

  • 23:00: Closing time. Down to the pub for a two shilling ale evaluation and a final round of beers.

Thursday, March 25, 2010

Manipulating PDF files

A customer wanted a product that adds a watermark or stamp to a PDF document. My colleague Kim Chee found out that stamping and watermarking can easily be done with pdftk. In our product, we call the OS's pdftk to manipulate the pdf files.

To generate a dynamic stamp or watermark which includes the username and time, we use reportlab.

One hurdle was to read the PDF file (in order to write that to a temporary file on the filesystem). The PDF files in our product are contained in a custom content type, in an array field of files. In order to correctly read the PDF from a field, i had to call str(field).

Below is some code from our product's wfsubscribers.py. This file and the code framework was generated by ArchGenXML, where we created a workflow transition script.

##code-section module-header #fill in your manual code here
from DateTime import DateTime
import os, sys
from Products.CMFCore.utils import getToolByName
import tempfile
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.units import cm
##/code-section module-header

...

def approveDocument(obj, event):
    """generated workflow subscriber."""
    # do only change the code section inside this function.
    if not event.transition \
       or event.transition.id not in ['approve'] \
       or obj != event.object:
        return
    ##code-section approveDocument #fill in your manual code here

    # create stamp file
    tmp = tempfile.mkdtemp()
    stamppath = "%s/stamp.pdf" % tmp
    canvas = Canvas(stamppath)
    canvas.setFontSize(0.5*cm)
    canvas.setFillColorRGB(1,0.75,0, alpha=0.75)
    user_id = event.status.get('actor')
    mtool = getToolByName(obj, 'portal_membership')
    member = mtool.getMemberById(user_id)
    fullname = member.getProperty('fullname')
    now = DateTime()
    message = "Approved by %s [%s] on %s" % (fullname, user_id,
            now.strftime('%Y/%m/%d %H:%M') )
    canvas.drawString(1*cm, 1*cm, message)
    canvas.showPage()
    canvas.save()

    # iterate over documents
    fields = obj.getField('documents').Schema().fields()
    file_fields = [field for field in fields if field.type == 'file']
    for i, field in enumerate(file_fields):
        # check if document is a pdf
        content_type = field.getContentType(obj)
        if content_type != 'application/pdf':
            obj.plone_log("File in field %s is not a PDF, skipping." % field)
            return

        # get raw file
        document = obj.getDocuments()[i]
        assert document.filename == field.getFilename(obj)
        file = str(field.getRaw(obj))

        # write pdf document to tmp file
        infile_name = tmp + '/infile.pdf'
        infile = open(infile_name, 'wb')
        infile.write(str(file))
        infile.close()

        # create stamped version of PDF
        outfile_name = tmp + '/outfile.pdf'
        command_line = 'pdftk %s stamp %s output %s' % (infile_name, 
                stamppath, outfile_name)
        args = shlex.split(command_line)
        stampresult = subprocess.Popen(args)
        # wait for stamping to finish before going further
        sts = os.waitpid(stampresult.pid, 0)[1]

        # read stamped pdf from file
        outfile = open(outfile_name,'r')
        stamped = outfile.read()
        outfile.close()

        # replace existing PDF with stamped version
        document.update_data(stamped)
        
    # clean up
    command_line = 'rm -rf %s' % tmp
    args = shlex.split(command_line)
    p = subprocess.Popen(args)

    return

    ##/code-section approveDocument

Tuesday, March 23, 2010

Laptop battery remaining time vs. uptime

Question: Why is it that after 15 minutes' uptime, Ubuntu thinks my laptop battery will last another 3 hours, but all it ever manages in total is 2:55 (at most)?

Here's a shell script which writes the current uptime and the estimated remaining time in a file, every minute. Hopefully this can shed some light on the matter. At least we can draw a nice graph afterwards!

You may need to sudo apt-get install acpi first.
#!/bin/bash

filename=~/.battery_data.csv
/bin/echo "up, remaining" > $filename

while true; do
# extract h:mm from uptime output:
# try to find (h)h:mm first, then try to find 'xx min' and print as 0:xx
up=`/usr/bin/uptime | sed 's/^.*up \+\([0-9]\+:[0-9]\{2\}\).*/\1/g' | \
sed 's/^.*up \+\([0-9]\+\) \+min.*/0:\1/g'`

# extract h:mm from acpi 'hh:mm:ss' output
remaining=`/usr/bin/acpi | sed 's/^.* \+[0-9]\([0-9]:[0-9]\{2\}\):[0-9]\{2\}.*/\1/'`

# write output to file and screen
echo $up, $remaining | tee -a $filename

# interval between polls (in seconds)
sleep 60

done


Update: Here's a graph:

Uptime on X axis, remaining time (blue) and total time (orange) on the Y axis. Polls every minute. You can see the prognosis is optimistic at the beginning.

Wednesday, March 10, 2010

Creating workflow transition scripts with ArchGenXML and ArgoUML

To add a script that should be executed on a workflow transition, do the following:
* On the transition's tab, click the "New call action" icon, which looks like this: a()
* Enter an id for the action
* Save UML, regenerate, a file wfsubscribers.py will be created in your product's root folder, containing an empty script.
* Below ##code-section {action_id} #fill in your manual code here, insert your code

Creating worklists using ArchGenXML

Once you've created a workflow in a UML model, you can add a revision list (worklist) as follows:
* in the workflow diagram, select a state
* add a tagged value "worklist=finalize_queue"
* add a tagged value "review=Manager,Reviewer"
The finalize_queue is just a name for the list. I've named it after the transition that would be the logical next step, which is "Finalize". The "review" value defines which roles get the "Review portal content" permission on the object in this state, so which roles get to see the items in their review list.

How to remove a portlet

To delete Plone's default portlets in your product, just add an assignment (to profiles/default/portlets.xml) with the "remove" attribute:
<assignment
manager="plone.leftcolumn"
category="context"
key="/"
type="portlets.Navigation"
name="navigation"
remove=""
/>

You can find the names, types and managers of the portlets in Product.CMFPlone's portlets.xml. Moving a portlet from one column to the other is as simple as removing it in one place and deleting it in the other.

Monday, March 8, 2010

Dutch translations for Plone roles

This is how Plone's roles are currently translated (in Plone 3.3.4):
Contributor: Medewerker
Editor: Schrijver
Member: Gebruiker
Reader: Lezer
Reviewer: Redacteur
Manager: Beheerder

Friday, March 5, 2010

How to set a property programmatically

You read a property (from OFS.PropertyManager.PropertyManager) with getProperty, but setting it is done through _setProperty. Note the underscore.

Convert ArgoUML to XMI

If you want to use ArchGenXML on UML models generated with ArgoUML >= 0.28, you'll have to export them to .xmi first. This is a quick way to do that automatically before running agx.

A shell script:
#!/bin/bash
# argouml2xmi.sh
# Creates a .xmi file from an ArgoUML-generated .uml file.
# Usage: ./argouml2xmi.sh filename.uml
# A file called filename.xmi will be created.

if [ ! -n "$1" ]
then
echo "Usage: `basename $0` input_file"
exit
fi

XMI_FILE=`echo $1 | sed 's/\.uml/\.xmi/'`
echo '<?xml version = "1.0" encoding = "UTF-8" ?>' > $XMI_FILE
sed -n '/<XMI*/,/<\/XMI>/p' $1 >> $XMI_FILE


Let the Makefile in your uml/ directory read:
default:
./argouml2xmi.sh MyProject.uml
archgenxml MyProject.xmi ../

and you'll only have to do is run make -C uml/

Tuesday, March 2, 2010

Why run ArchGenXML on a UML file instead of zargo?

Short answer: there's no reason.

The original version of this post comes from an error i got from ArchGenXML when running it on a UML file, instead of the generally recommended zipped (zargo) version. Why did i want to use UML instead of zargo?

The reason was version control: As a binary (zipped) file, zargo files will be replaced whole, while UML files can be diff'ed. This results in smaller diffs, and diffs that can be scanned for useful information.

However, the difference between two UML files, as output by the diff command, is hardly human-readable: Adding one field to a content class yields a 146 line diff that is almost completely unreadable. Also, the difference in size for changes is small: the patch is about 6k, while the zargo file is 36k. (UML size is 388k.)

So my arguments for running AGX on UML are invalid, i'll use zargo from now on.

(update 2010-07-06)

Original post: ArchGenXML: xml.parsers.expat.ExpatError: unbound prefix



I was working on a workflow in ArgoUML. After i'd added a transition, I got this error:
Traceback (most recent call last):
...
File "/usr/lib/python2.6/xml/dom/minidom.py", line 1918, in parse
return expatbuilder.parse(file)
File "/usr/lib/python2.6/xml/dom/expatbuilder.py", line 924, in parse
result = builder.parseFile(fp)
File "/usr/lib/python2.6/xml/dom/expatbuilder.py", line 207, in parseFile
parser.Parse(buffer, 0)
xml.parsers.expat.ExpatError: unbound prefix: line 3843, column 6
make: *** [default] Error 1
make: Leaving directory `...'


The offending XML is this:

<argouml:pathitem figname="Fig2.1"
classname="org.argouml.uml.diagram.ui.PathItemPlacement"
figclassname="org.argouml.uml.diagram.ui.FigNameWithAbstract"
ownerhref="127-0-1-1--3a86f936:1271ed6383a:-8000:000000000000121D"
angle="270.0"
offset="10" />


As it turns out, this only happens with ArgoUML 0.28.1, with 0.26.2 all goes well.

The answer is that ArgoUML adds the "<argouml:... />" tag since 0.28, and archgenxml doesn't (yet?) know how to deal with it. However, you can export the model as an xmi profile, and let archgenxml work with that.

See also this part about generating the .xmi file automatically from the .uml.