Saturday 22 October 2011

My own debian repository

I've created my own debian repository.
I'll put only auteur and openmolar related packages into this, and currently I just have the latest stable in there.

I'm using repropro to add packages and handle signing, and think it is wonderful in it's simplicity.

to add this to a debian based distro do the following.

~$ sudo su
~# echo "deb http://openmolar.com/repos/apt/debian squeeze main" >> /etc/apt/sources.list
~# wget -O - http://www.openmolar.com/rowinggolfer.gpg.key| apt-key add -
~# apt-get update

Thursday 6 October 2011

Ecryptfs and Dropbox - a match made in heaven for secure backup

Dropbox is a wonderful service, but there's the problem that they (Dropbox) have a copy of your data, so it is smart to encrypt anything sensitive you store there.

The problem is, of course, that this adds complexity, and get's in the way of one's workflow.

However, I realised yesterday that there's an easy way to do this.

~$ ecryptfs-setup-private
~$ ln -s ~/.Private ~/Dropbox/Private

Note the "." in the first path

Now anything you put into ~/Private will be automatically synced up to Dropbox. Very, very convenient way of doing this IMHO.

These instructions are very, very simplified, and the ecryptfs setup can be done in many other ways than using the setup-private script. Using a common encryption passphrase across your boxes, or not encrypting filenames, or using public/private key for the ecrtyptfs mount all should be investigated.

But the main point here is the symlink idea to the hidden encrypted folder. I don't recall hearing anyone suggest this before.

Tuesday 23 August 2011

toggling /etc/hosts file via a simple gui

Like many folks who play at web development, I have local running instances of all my websites to enable offline development and/or testing.

To switch between the two, I have a launcher to a little gui I've written.

The Interface
The Dialog raised on request.

works great.

the code is below.

Incidentally, as /etc/hosts required root permissions, I call the write using gksu. KDE users would need to change this.

Here's the full code.
#! /usr/bin/env python

'''
This script modifies the /etc/hosts file so that I utilise this machines
web server rather than doing a dns lookup and going to the online 
sites.
It's for testing purposes. 

my /etc/hosts file has this text present.

127.0.0.1   localhost 
127.0.0.1   rowinggolfer.org 
127.0.0.1   openmolar.com 
127.0.0.1   academydental.com 
127.0.1.1    slim-maroon

and when I want to use the "cloud" servers, the sites listed in variable SITES
are commented out.

'''

from PyQt4 import QtGui, QtCore
import re, subprocess, sys, tempfile

SITES = ["rowinggolfer.org", "academydental.com", "openmolar.com",]


class Dialog(QtGui.QDialog):
    def __init__(self, parent=None):
        QtGui.QDialog.__init__(self, parent)
    
        self.setWindowTitle("toggle /etc/hosts file")
        
        self.local_rb = QtGui.QRadioButton("Using localhost")
        self.global_rb = QtGui.QRadioButton("Using remote host(s)")
        
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.local_rb)
        layout.addWidget(self.global_rb)
        
        self.global_rb.setChecked(True)
        self.local_rb.setChecked(self.check_state)
        
        self.local_rb.toggled.connect(self.apply_changes)
    
    def sizeHint(self):
        return QtCore.QSize(200,50)
    
    @property
    def check_state(self):
        '''
        checks /etc/hosts to see if it is set to use the local instance of 
        apache2 for the SITES
        '''
        local = False
        
        f = open("/etc/hosts", "r")
        for line in f:
            for site in SITES:
                if re.match("127\.0\.0\.1[ \t]*%s"% site, line):
                    local = True
        
        return local
        
    def apply_changes(self, use_local):
        host = "localhost" if use_local else "remote host(s)"
        
        message = '''modify <em>/etc/hosts</em> so that 
            <b>%s</b> is used for the following sites?<ul>{sites}</ul>'''% host
        
        sites = ""
        for site in SITES:
            sites += "<li>%s</li>"% site
            
        message = message.replace("{sites}", sites)
        
        result = QtGui.QMessageBox.question(self, "confirm", message,
        QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel,
        QtGui.QMessageBox.Ok) == QtGui.QMessageBox.Ok
        
        if result:
            self.write_file(use_local)
        
        
        success = use_local == self.check_state
        
        if success:
            QtGui.QMessageBox.inforamtion(self, "success", 
                "changes applied sucessfully")
        
        self.local_rb.toggled.disconnect(self.apply_changes)
        self.local_rb.setChecked(self.check_state)
        self.local_rb.toggled.connect(self.apply_changes)
        
        
        
    def write_file(self, local):
    
        def repl(arg):
            if local:
                return re.sub("\n#*", "\n", arg.group())
            else:
                return arg.group().replace("\n", "\n#")
    
        f = open("/etc/hosts", "r")
        data = f.read()
        f.close()
        if not local:
            for site in SITES:
                data = re.sub(  "(\n127\.0\.0\.1[ \t]*%s)"% site, 
                                repl, data)
        else:
            for site in SITES:
                data = re.sub(  "\n#*127\.0\.0\.1[ \t]*(%s)"% site, 
                                repl, data)
    
        t = tempfile.NamedTemporaryFile("w", delete=False)
        t.write(data)
        t.close()
        
        p = subprocess.Popen(["gksu", "mv", t.name, "/etc/hosts"]) 
        assert p.wait()==0
        
        
if __name__ == "__main__":
    app = QtGui.QApplication([])
    dl = Dialog()
    dl.exec_()
    
    

Monday 22 August 2011

A VPS from Bitfolk

I've had a VPS from amazon up and running for a few months, but I simply couldn't get my head around their billing tariffs. I am sure they are great and really flexible, but they weren't working out as a good deal for me.
(on average I was paying $75 a month). On top of that, on Friday, I was having trouble logging in (probably my own fault).


So I finally got around to switching provider.
and as http://bitfolk.com/ are well known to the UK floss community as a sponsor of http://oggcamp.org/ , and patronised by the internet's very own "Alan (popey) Pope".

Bitfolk have been an absolute joy to deal with, I had my server up and running within a couple of hours of my enquiry.

I have now moved both www.openmolar.com and www.rowinggolfer.org onto that server and they seem fast and stable.
 How much for this incredible service? Under 20% of the cost of the amazon box (which is now cancelled), and easy to understand.


Thanks Bitfolk, and a 5 star rating from yours truly.

Tuesday 16 August 2011

Screencasting on linux

There are lots of good screen capturing options for linux.

On #oggcastplanet on irc.freenode.net,  K4k asked about streaming the output, with a webcam shot of the speaker somewhere on the screen.

here's one way to do that.

1st, start the webcam in a non-intrusive frame. mplayer fits the bill here.

~$ mplayer -cache 128 -tv driver=v4l2:width=320:height=240 -vo xv tv:// 

when that window appears, resize it, place it where you find least intrusive, and set it to remain "on top".

in another terminal, do this

~$ ffmpeg -f oss -i /dev/dsp -f x11grab -s xga -r 15 -i :0.0 out.mp4

then record your screencast.

hit q in the second terminal when you are finished.

If this works.. replace out.mp4 with whatever format or stream you wish to create.

Below is the video I created with this method (on ubuntu 10.04).


Thursday 11 August 2011

Customised Business Software or Software Customised Businesses?

So I am reading Bookeeping for Dummies at the moment (because double-entry booking is a complete mystery to me).

This paragraph caught my eye, when the author discusses software.




Chapter 6
Surveying Computer Options.



...... we must mention the fact that in a significant number of business situations no accounting software package does exactly what you did before in the same way. Every accounting package is a compromise because it offers the features that someone else decided are important for the majority of businesses. The decision you may have to make is whether to modify your business processes to fall into line with the new accounting software or have some bespoke modification to the accounting package you decide to buy. The simplest and cheapest solution is to fall into line with your chosen accounting software.


Get the gist?
The choice is.. change how you do things, or make the software do what you need it to, and the author suggests the former.

I believe it is a shame that ANYONE holds this view. Would we buy shoes that don't fit our feet? Why then do we have to use software that doesn't fit our needs?

Get an open source solution that is the closest fit, then get it modified.

Wednesday 15 June 2011

Screw you launchpad

I've moved my code hosting to http://code.google.com/p/openmolar/, switching from bzr to hg.

Why?
https://bugs.launchpad.net/launchpad/+bug/220082

I'm absolutely bloody furious.

However, I feel this move was absolutely necessary.
Launchpad is falling behind other code hosting sites from a my perspective as a coder. Only time will tell if I am right.


p.s. as a dentist.. should i really be using hg?

Monday 13 June 2011

PyQt QSysTrayIcon Example

Following A question in #pyqt on irc.freenode.net, I had a play with QSysTrayIcon.

"Hugo__" wanted a tray icon in Gnome which showed a different menu for left and right clicking.

Here's one way to achieve such with PyQt4.

Note - I use QtGui.QIcon.fromTheme here.. and do NOT supply a fallback icon.
Hence, the icons will only show on freedesktop standards compliant environments (gnome, KDE etc..)

see
http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
for details.







#! /usr/bin/env python
from PyQt4 import QtGui, QtCore

class RightClickMenu(QtGui.QMenu):
    def __init__(self, parent=None):
        QtGui.QMenu.__init__(self, "Edit", parent)

        icon = QtGui.QIcon.fromTheme("edit-cut")
        self.addAction(QtGui.QAction(icon, "&Cut", self))

        icon = QtGui.QIcon.fromTheme("edit-copy")
        self.addAction(QtGui.QAction(icon, "Copy (&X)", self))

        icon = QtGui.QIcon.fromTheme("edit-paste")
        self.addAction(QtGui.QAction(icon, "&Paste", self))
    
class LeftClickMenu(QtGui.QMenu):
    def __init__(self, parent=None):
        QtGui.QMenu.__init__(self, "File", parent)

        icon = QtGui.QIcon.fromTheme("document-new")
        self.addAction(QtGui.QAction(icon, "&New", self))

        icon = QtGui.QIcon.fromTheme("document-open")
        self.addAction(QtGui.QAction(icon, "&Open", self))

        icon = QtGui.QIcon.fromTheme("document-save")
        self.addAction(QtGui.QAction(icon, "&Save", self))


class SystemTrayIcon(QtGui.QSystemTrayIcon):
    def __init__(self, parent=None):
        QtGui.QSystemTrayIcon.__init__(self, parent)
        self.setIcon(QtGui.QIcon.fromTheme("document-save"))

        self.right_menu = RightClickMenu()
        self.setContextMenu(self.right_menu)

        self.left_menu = LeftClickMenu()
    
        self.activated.connect(self.click_trap)

    def click_trap(self, value):
        if value == self.Trigger: #left click!
            self.left_menu.exec_(QtGui.QCursor.pos())

    def welcome(self):
        self.showMessage("Hello", "I should be aware of both buttons")
        
    def show(self):
        QtGui.QSystemTrayIcon.show(self)
        QtCore.QTimer.singleShot(100, self.welcome)

if __name__ == "__main__":
    app = QtGui.QApplication([])

    tray = SystemTrayIcon()
    tray.show()
    
    #set the exec loop going
    app.exec_()

Making PyQt experimentation easier

Over the past few months, I've encountered the same issue. namely, I want to do *SOMETHING* with the openmolar project be it:
  1. document with sphinx
  2. write unittests (I know... I should have written them 1st..)
  3. make a deb or rpm
  4. make a windows executable
  5. convert to python 3 with 2to3 etc..

Rather than risk breaking my main codebase however, I believe it always helps to attempt this stuff with a smaller application first I think. So I've written one. It is trivial enough to keep the codebase simple, but complex enough to be realistic.

It raises dialogs, stores data, etc.

I've tried to follow a lot of my coding style choices (which you may hate), but on checking the modules with pylint, most get a 10/10, only decreasing when PyQt4 own naming conventions dictate.

anyways, to cut to the chase.. the code is here is anyone wants a play. This is in the public domain, so do with it as you wish.
download it from my dropbox account http://dl.dropbox.com/u/1989100/example_pyqt_app.tar.bz2

My current focus is writing unittests (something I am ashamed to admit I have never done before), and it is proving VERY interesting.

for example. dialogs are interesting, in that the exec_ function needs to be called to ensure correct code coverage. Calling that during a test run causes a pause in the procedings.. not good.

So what is the best way to call accept() or reject() on such a dialog?
I am experimenting with installEventFilter into QApplication itself, and accepting the dialog when the focus event is encountered seems to be working really well.

Tuesday 7 June 2011

Python Class - Am I a subclass?

Problem...
in #python on freenode, a question was asked about identifying a subclass during the __init__ method.
I came up with this example. 
class Example(object):
    def __init__(self):
        if self.__class__ != Example.mro()[0]:
            print "I am an instance of a subclass of Example"
        else:
            print "I am an instance of Example"

class ExampleDeritive(Example):
    ''' the most basic of subclasses! '''
    pass


>>> Example()
I am an instance of Example

>>> ExampleDeritive()
I am an instance of a subclass of Example

Monday 23 May 2011

ubuntu-dental doppleganger

my dental life and open source advocacy seldom collide.

however, I'm struck by the likeness between these two women, who are leaders in each field.



This is Marilou-Ciantar, arguably Scotland's most eminent periodontal specialist.


and this is Jane Silber, CEO of canonical (the company behind ubuntu)

uncanny in my opinion.

Friday 8 April 2011

Hiding the MenuBar in a PyQt application.

I've long been a fan of the firefox plugin "tiny menu" where screen real estate is preserved by compressing the application's menubar down into a single menu button.

Here's one way to achieve the same effect in pyqt4.






 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
#! /usr/bin/env python
# -*- coding: utf-8 -*-
'''
A simple application with a shrinkable menu bar.
(similar functionality to firefox4)

At any point either the tiny menu or menubar should be visible.
Therefore if the toolbar is hidden, the menu will re-appear.

to experience this... either 

    click View>Tiny Menu or hit F11
'''

from PyQt4 import QtGui, QtCore

class DockableMenuBar(QtGui.QMenuBar):
    def __init__(self, parent=None):
        QtGui.QMenuBar.__init__(self, parent)
        
        self.toggleViewAction = QtGui.QAction(_("Tiny &Menu"), parent)
        self.toggleViewAction.setShortcut('f11')
        self.toggleViewAction.setCheckable(True)
        self.toggleViewAction.triggered.connect(self.toggle_visability)
        
        self.menu_view = QtGui.QMenu(_("&View"), self)
        self.menu_view.addAction(self.toggleViewAction)
        self.addMenu(self.menu_view)
        
        self._menu_button = None
        
    @property
    def menu_button(self):
        self._menu_button = QtGui.QToolButton()
        self._menu_button.setPopupMode(QtGui.QToolButton.InstantPopup)
        self._menu_button.setText(_("Menu"))
        self._menu_button.setMenu(self.mini_menu)        
        self._menu_button.setToolButtonStyle(
            QtCore.Qt.ToolButtonFollowStyle)
        return self._menu_button

    @property
    def mini_menu(self):
        self._mini_menu = QtGui.QMenu()
        for action in self.actions():
            self._mini_menu.addAction(action)
        return self._mini_menu
        
    def addViewOption(self, action):
        '''
        add an action to the 'view' category of the toolbar
        ''' 
        self.menu_view.addAction(action)
        
    def addMenu(self, *args):
        try:
            return self.insertMenu(self.actions()[0], *args)
        except IndexError:
            return QtGui.QMenuBar.addMenu(self, *args)
            
    def addAction(self, *args):
        try:
            return self.insertAction(self.actions()[0], *args)
        except IndexError:
            return QtGui.QMenuBar.addAction(self, *args)        

    def toggle_visability(self, set_visible):
        self.setVisible(not set_visible)
        if set_visible:
            self.emit(QtCore.SIGNAL("mini menu required"), self.menu_button)
        else:
            self.emit(QtCore.SIGNAL("hide mini menu"))
            
    def setNotVisible(self, menu_bar_visible):
        '''
        make sure that we don't end up with neither menu visible!
        ''' 
        if not menu_bar_visible:
            self.setVisible(True)
        
class DockAwareToolBar(QtGui.QToolBar):
    def __init__(self, parent=None):
        QtGui.QToolBar.__init__(self, parent)
        self.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
        self.setObjectName("DockAwareToolbar") #for QSettings
        
        # this should happen by default IMO.
        self.toggleViewAction().setText(_("&ToolBar"))

    def add_mini_menu(self, menu_button):
        self._menu_button = menu_button
        if self.actions():
            self.insertWidget(self.actions()[0], menu_button)
        else:
            self.addWidget(menu_button)
        self.show()
    
    def clear_mini_menu(self):
        self._menu_button.hide()
        self._menu_button.setParent(None)
        self._menu_button.deleteLater()
        self._menu_button = None
              
class TestMainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        
        ## initiate instances of our classes
        
        self.toolbar = DockAwareToolBar()        
        menu_bar = DockableMenuBar(self)
        
        ## the menu bar needs this action adding
        menu_bar.addViewOption(self.toolbar.toggleViewAction())
        
        ## add them to the app
        self.addToolBar(QtCore.Qt.TopToolBarArea, self.toolbar)
        self.setMenuBar(menu_bar)
        
        ## make them aware of one another
        self.connect(self.menuBar(), QtCore.SIGNAL("mini menu required"),
            self.toolbar.add_mini_menu)
        self.connect(self.menuBar(), QtCore.SIGNAL("hide mini menu"),
            self.toolbar.clear_mini_menu)
        self.toolbar.toggleViewAction().triggered.connect(
            self.menuBar().setNotVisible)
        
        ## some arbitrary stuff to make the app more realistic
        file_action = QtGui.QAction("&File", self)
        self.menuBar().addAction(file_action)
        
        line_edit = QtGui.QLineEdit("http://google.com")
        self.toolbar.addWidget(line_edit)
        
        go_but = QtGui.QPushButton("Go!")
        go_but.setFixedWidth(60)
        self.toolbar.addWidget(go_but)
        
        te = QtGui.QTextEdit()
        self.setCentralWidget(te)
        te.setText(__doc__)
        

if __name__ == "__main__":
    import gettext
    gettext.install("")
    
    app = QtGui.QApplication([])
    mw = TestMainWindow()
    mw.show()
    app.exec_()




Thursday 7 April 2011

Python Class Attributes. A Quiz!

So take a look at this code.

#! /usr/bin/env python
'''
A simple class demonstrating attributes
'''
ID_COUNTER = 0

class Person(object):
    genus = "homo sapien"

    def __init__(self, name, sex="M"):
        global ID_COUNTER
    
        assert sex in ("M", "F"), 'INVALID SEX "%s" must be "M" or "F"'% sex
        ID_COUNTER += 1
        self.id = ID_COUNTER
        self.name = name
        self._sex = sex
        self._profession = None

    @property
    def sex(self):
        return "Male" if self._sex=="M" else "Female"

    @property
    def profession(self):
        if self._profession is None:
           return "unknown"
        return self._profession

    def set_profession(self, profession):
        self._profession = profession

    def __repr__(self):
        return "Person %03d:\n\t%s\n\tGenus\t(%s)\n\t%s\n\t%s"% (
            self.id,
            self.name,
            self.genus,
            self.sex,
            self.profession)

if __name__ == "__main__":
    person1 = Person("Neil")
    person1.set_profession("dentist")

    person2 = Person("Joan", "F")
    person2.genus = "Neanderthal"

    person3 = Person("Timrit", "M")
    person3.set_profession("refrigeration")
    
    for object_ in (Person.genus, person1, person2, person3):
        print object_
        print

which gives the following output

homo sapien

Person 001:
 Neil
 Genus (homo sapien)
 Male
 dentist

Person 002:
 Joan
 Genus (Neanderthal)
 Female
 unknown

Person 003:
 Timrit
 Genus (homo sapien)
 Male
 refrigeration


And here are your questions.
1. Why is the Global statement used on line 11?
2. is there a better way of implementing a unique serial ID for these objects?
3. What would happen if I tried to create an instance with the following call?
person4 = Person("ArtV61", "unknown")
4. is genus a "class attribute" or an "instance attribute"?
5. what is the difference between a "class attribute" or an "instance attribute"?
6. is Person an old or new style class?
7. what would need to change in this code to make it run under python3?
8. what is the __repr__ function for, and what would be the output if it were deleted?
9. what namespace is the __name__ variable found in?
10. why is the trailing underscore used for object_ on the penultimate line of code?


Answers, as always to linc AT thelinuxlink.net, quoting "QUIZ" in the subject field.

Saturday 15 January 2011

using pyqt4 to validate XML Schemas

Follow up to the problem solved in the last posting.
lxml worked great, but I don't want to burden folks with yet another 3rd party module.

so I once again looked to pyqt4 for help....
and..

from PyQt4.QtXmlPatterns import QXmlSchemaValidator, QXmlSchema

#open up the xsd file - and load it into QXMLSchema
f=open("foo.xsd", "r")
xsd = f.read()
f.close()

schema = QXmlSchema()
schema.load(xsd)

#now the xml itself.
f = open("foo.xml", "r")
xml = f.read()
f.close()

validator = QXmlSchemaValidator(schema)
print (validator.validate(xml))

#Returns True :)

validating xml with python

THE XML

I wanted to validate the XML sheets produced by auteur
example XML produced by auteur.

<?xml version="1.0" encoding="UTF-8"?>
    
<auteur>
  <source>
    <location>/home/neil/Videos/2010_04_canada/M2U00547.MPG</location>
      <timestamp pos="11" />
      <clip end="7.5" id="0001" start="1.5" />
      <clip end="13.6" id="0002" start="7.5" />
      <clip end="1.5" id="0003" start="0.0" />
  </source>
  
  <source>
    <location>/home/neil/Videos/2010_04_canada/M2U00549.MPG</location>
    <timestamp pos="2.678" />
  </source>

</auteur>


The Schema

and here's the schema I wrote to check the validity of that data.
(saved as foo.xsd)

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:annotation>
    <xs:documentation>
       The rules in this schema will be used to validate 
       the content of an auteur project file.
    </xs:documentation>
    <xs:appinfo source="http://www.auteur-project.org" > </xs:appinfo>
</xs:annotation>
<!--RULES START HERE -->

<!-- ROOT NODE -->
<xs:element name="auteur" >
<xs:complexType>
<xs:sequence>

  
  <!-- SOURCES -->
  <xs:element name="source" minOccurs="0" maxOccurs="unbounded">
  
    <xs:complexType>
    <xs:sequence>
        <!-- only 1 location allowed per source -->
        <xs:element name = "location"  type="xs:string" minOccurs="1" maxOccurs="1"/>
        
        <!-- TIMESTAMPS -->
        <xs:element name = "timestamp" minOccurs="0" maxOccurs="unbounded" >
          <xs:complexType>
          <xs:attribute name="pos" type="xs:decimal" use="required" />
          </xs:complexType>
        </xs:element>
          
        <!-- CLIPS -->
        <xs:element name="clip" minOccurs="0" maxOccurs="unbounded" >
            <xs:complexType>
            <xs:attribute name = "id" type="xs:string"  use="required" />
            <xs:attribute name = "start" type="xs:decimal" />
            <xs:attribute name = "end" type="xs:decimal" />
            </xs:complexType>
        </xs:element>
        
    
    </xs:sequence>
    </xs:complexType>  
    
  </xs:element>

</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

THE PYTHON to put it all together

a short python script to check foo.xml's validity with foo.xsd

(note lxml is NOT in python standard lib - a real pity!)

#! /usr/bin/env python
from lxml import etree

try:
    doc = etree.parse("foo.xml")    
    xsd = etree.parse("foo.xsd")

    xmlschema = etree.XMLSchema(xsd)
    xmlschema.assertValid(doc)
    
    print ("document validates!")

except etree.XMLSyntaxError as e:
    print ("PARSING ERROR", e)
    
except AssertionError as e:
    print ("INVALID DOCUMENT", e)