[meego-commits] 10659: Changes to devel:contentfw/libcontentaction

Mishra Maitrey no_reply at build.meego.com
Tue Dec 7 14:04:43 UTC 2010


Hi,
I have made the following changes to libcontentaction in project devel:contentfw. Please review and accept ASAP.

Thank You,
Mishra Maitrey

[This message was auto-generated]

---

Request #10659:

  submit:   home:maimishr/libcontentaction(r14) -> devel:contentfw/libcontentaction


Message:
    Feature number - 10498
* Tue Dec 07 2010 Mishra Maitrey <maitrey.mishra at nokia.com> - 0.1.35
- Feature number - 10498
- API addition: Action::actionsForString(const QString&)
- API addition: Action::defaultActionForString(const QString&)
- Enabling defining regexps specialCaseOf="another-regexp"
- Configuration update: regexp for phone number
- Configuration update: added feed-url, ftp-url, ovi-store-url
- Fixes 2 digits numbers or more than 20 digits number are recognized as tel links
  in Text Message Viewer / IM Viewer
- QT_NO_KEYWORDS
- Don't use QDBusInterface when triggering D-Bus actions
- Doc update
- Making regexps case-insensitive
- Storing regexps in order; enables prioritizing specific ones over generic ones
- Added missing _aegis file for tests
- Configuration update: regexp for ovi music urls
- Re-reading mimeinfo.cache
- Configuration update: e-mail address regexps
- API file contains interfaces that are frozen
- Fixes regular expression for urls in libcontentaction
- Fixes regular expression for emails in libcontentaction
- Added 2 Action::launcherAction functions
- Merging the highlighter tool hl1 into lca-tool (lca-tool --highlight)
- libcontentaction declared 'scope: Platform' in the API file
- Default actions are defined for the content with the '#' in the filename
- Numbers with area code enclosed in "()" are parsed, and not intrepreted as
  two distinct set of numbers. 
- Numbers seperated by '.' are parsed, not intepreted as distinct set of numbers
- lca doesn't get confused if defaults.list contains nonexistent applications
- Fixes Text Message Viewer / IM Message Viewer - phone number link support

State:   new          2010-12-07T06:04:42 maimishr
Comment: None



changes files:
--------------
--- libcontentaction.changes
+++ libcontentaction.changes
@@ -0,0 +1,45 @@
+* Tue Dec 07 2010 Mishra Maitrey <maitrey.mishra at nokia.com> - 0.1.35
+- Feature number - 10498
+- API addition: Action::actionsForString(const QString&)
+- API addition: Action::defaultActionForString(const QString&)
+- Enabling defining regexps specialCaseOf="another-regexp"
+- Configuration update: regexp for phone number
+- Configuration update: added feed-url, ftp-url, ovi-store-url
+- Fixes 2 digits numbers or more than 20 digits number are recognized as tel links
+  in Text Message Viewer / IM Viewer
+- QT_NO_KEYWORDS
+- Don't use QDBusInterface when triggering D-Bus actions
+- Doc update
+- Making regexps case-insensitive
+- Storing regexps in order; enables prioritizing specific ones over generic ones
+- Added missing _aegis file for tests
+- Configuration update: regexp for ovi music urls
+- Re-reading mimeinfo.cache
+- Configuration update: e-mail address regexps
+- API file contains interfaces that are frozen
+- Fixes regular expression for urls in libcontentaction
+- Fixes regular expression for emails in libcontentaction
+- Added 2 Action::launcherAction functions
+- Merging the highlighter tool hl1 into lca-tool (lca-tool --highlight)
+- libcontentaction declared 'scope: Platform' in the API file
+- Default actions are defined for the content with the '#' in the filename
+- Numbers with area code enclosed in "()" are parsed, and not intrepreted as
+  two distinct set of numbers. 
+- Numbers seperated by '.' are parsed, not intepreted as distinct set of numbers
+- lca doesn't get confused if defaults.list contains nonexistent applications
+- Fixes Text Message Viewer / IM Message Viewer - phone number link support 
+- Fixes Links, phone numbers not highlighted, nor recognised in MMS Viewer
+- Fixing double %-escaping of special characters
+- Defaults.list update
+- Configuration update: http-url regexp
+- Configuration update
+- Fixes music suite service interface changes
+- Small doc fix: .desktop keys were out of date
+- Remove duplicate actions: now it's ok to declare both x-maemo-nepomuk
+  mime types and the corresponding real mime types in a .desktop file
+- Fixes libcontentaction passes a wrong parameter to the music-suite service interface
+- Assume "file" scheme if none given
+- Fixes Method mimeForFile is not returning the mime-type of given file
+- Add autoconf to build-deps
+- Return Icon and Name even for invalid actions
+

old:
----
  fix_glib_ftbfs.patch
  libcontentaction-0.1.15.tar.gz

new:
----
  0001-Fix-python-version-for-tests.patch
  libcontentaction-0.1.35.tar.gz

spec files:
-----------
--- libcontentaction.spec
+++ libcontentaction.spec
@@ -1,20 +1,20 @@
 # 
 # Do NOT Edit the Auto-generated Part!
-# Generated by: spectacle version 0.18
+# Generated by: spectacle version 0.20
 # 
 # >> macros
 # << macros
 
 Name:       libcontentaction
 Summary:    Library for associating content with actions
-Version:    0.1.15
+Version:    0.1.35
 Release:    1
 Group:      System/Desktop
 License:    LGPLv2.1
 URL:        http://maemo.gitorious.org/maemo-af/libcontentaction
 Source0:    %{name}-%{version}.tar.gz
 Source100:  libcontentaction.yaml
-Patch0:     fix_glib_ftbfs.patch
+Patch0:     0001-Fix-python-version-for-tests.patch
 Requires(post): /sbin/ldconfig
 Requires(postun): /sbin/ldconfig
 BuildRequires:  pkgconfig(glib-2.0) >= 2.12.0
@@ -54,7 +54,7 @@
 %prep
 %setup -q -n %{name}-%{version}
 
-# fix_glib_ftbfs.patch
+# 0001-Fix-python-version-for-tests.patch
 %patch0 -p1
 # >> setup
 # << setup

other changes:
--------------

++++++ 0001-Fix-python-version-for-tests.patch (new)
--- 0001-Fix-python-version-for-tests.patch
+++ 0001-Fix-python-version-for-tests.patch
+diff -Naur ../libcontentaction-0.1.35//t/applications/ubermeego.desktop libcontentaction-0.1.35//t/applications/ubermeego.desktop
+--- ../libcontentaction-0.1.35//t/applications/ubermeego.desktop	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/applications/ubermeego.desktop	2010-12-03 10:58:55.329381001 +0200
+@@ -6,7 +6,7 @@
+ GenericName=Uber Program
+ Icon=ubericon
+ Comment=View ANYTHING
+-Exec=/usr/bin/python2.5 -c "import sys; print \"uberalles:\", sys.argv[1:]"
++Exec=/usr/bin/python -c "import sys; print \"uberalles:\", sys.argv[1:]"
+ Terminal=false
+ Type=Application
+ MimeType=text/plain;
+diff -Naur ../libcontentaction-0.1.35//t/applications/ubermimeopen.desktop libcontentaction-0.1.35//t/applications/ubermimeopen.desktop
+--- ../libcontentaction-0.1.35//t/applications/ubermimeopen.desktop	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/applications/ubermimeopen.desktop	2010-12-03 10:26:33.517381001 +0200
+@@ -6,7 +6,7 @@
+ GenericName=Uber Program
+ Icon=mimeopenicon
+ Comment=View ANYTHING
+-Exec=/usr/bin/python2.5 -c "import sys; print \"uberalles:\", sys.argv[1:]"
++Exec=/usr/bin/python2.6 -c "import sys; print \"uberalles:\", sys.argv[1:]"
+ Terminal=false
+ Type=Application
+ MimeType=text/plain;
+diff -Naur ../libcontentaction-0.1.35//t/cltool.py libcontentaction-0.1.35//t/cltool.py
+--- ../libcontentaction-0.1.35//t/cltool.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/cltool.py	2010-12-03 10:59:39.013381001 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ ##
+ ## Copyright (C) 2009 Nokia. All rights reserved.
+ ##
+diff -Naur ../libcontentaction-0.1.35//t/gallery.py libcontentaction-0.1.35//t/gallery.py
+--- ../libcontentaction-0.1.35//t/gallery.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/gallery.py	2010-12-03 10:58:15.173381002 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ import dbus, dbus.service, dbus.mainloop.glib
+ import gobject
+ from sys import stdout, argv
+diff -Naur ../libcontentaction-0.1.35//t/sandbox.sh libcontentaction-0.1.35//t/sandbox.sh
+--- ../libcontentaction-0.1.35//t/sandbox.sh	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/sandbox.sh	2010-12-07 14:24:21.761380992 +0200
+@@ -13,7 +13,6 @@
+ 
+ case "$1" in
+         --start)
+-                exec >/tmp/lca-sandbox-start.log 2>&1
+                 echo "Starting"
+                 echo "Importing data to tracker"
+                 # reset tracker
+diff -Naur ../libcontentaction-0.1.35//t/servicemapper.py libcontentaction-0.1.35//t/servicemapper.py
+--- ../libcontentaction-0.1.35//t/servicemapper.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/servicemapper.py	2010-12-03 11:00:16.101381002 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ import os
+ import dbus, dbus.service, dbus.mainloop.glib
+ import gobject
+diff -Naur ../libcontentaction-0.1.35//t/test-defaults.py libcontentaction-0.1.35//t/test-defaults.py
+--- ../libcontentaction-0.1.35//t/test-defaults.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/test-defaults.py	2010-12-03 10:55:25.365381002 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ ##
+ ## Copyright (C) 2008, 2009 Nokia. All rights reserved.
+ ##
+diff -Naur ../libcontentaction-0.1.35//t/test-desktop-launching.py libcontentaction-0.1.35//t/test-desktop-launching.py
+--- ../libcontentaction-0.1.35//t/test-desktop-launching.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/test-desktop-launching.py	2010-12-03 10:57:08.537381000 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ ##
+ ## Copyright (C) 2010 Nokia. All rights reserved.
+ ##
+diff -Naur ../libcontentaction-0.1.35//t/test-fixed-params.py libcontentaction-0.1.35//t/test-fixed-params.py
+--- ../libcontentaction-0.1.35//t/test-fixed-params.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/test-fixed-params.py	2010-12-03 10:57:18.317381001 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ ##
+ ## Copyright (C) 2010 Nokia. All rights reserved.
+ ##
+diff -Naur ../libcontentaction-0.1.35//t/test-invalid-defaults.py libcontentaction-0.1.35//t/test-invalid-defaults.py
+--- ../libcontentaction-0.1.35//t/test-invalid-defaults.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/test-invalid-defaults.py	2010-12-03 10:59:53.953381001 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ ##
+ ## Copyright (C) 2008, 2009 Nokia. All rights reserved.
+ ##
+diff -Naur ../libcontentaction-0.1.35//t/test-invoking.py libcontentaction-0.1.35//t/test-invoking.py
+--- ../libcontentaction-0.1.35//t/test-invoking.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/test-invoking.py	2010-12-03 10:57:31.385381003 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ ##
+ ## Copyright (C) 2008, 2009 Nokia. All rights reserved.
+ ##
+diff -Naur ../libcontentaction-0.1.35//t/test-mimes.py libcontentaction-0.1.35//t/test-mimes.py
+--- ../libcontentaction-0.1.35//t/test-mimes.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/test-mimes.py	2010-12-03 10:56:54.669381001 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ ##
+ ## Copyright (C) 2010 Nokia. All rights reserved.
+ ##
+diff -Naur ../libcontentaction-0.1.35//t/test-regexps.py libcontentaction-0.1.35//t/test-regexps.py
+--- ../libcontentaction-0.1.35//t/test-regexps.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/test-regexps.py	2010-12-03 10:58:04.013381002 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ ##
+ ## Copyright (C) 2008-2010 Nokia. All rights reserved.
+ ##
+diff -Naur ../libcontentaction-0.1.35//t/test-servicefw-signals.py libcontentaction-0.1.35//t/test-servicefw-signals.py
+--- ../libcontentaction-0.1.35//t/test-servicefw-signals.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/test-servicefw-signals.py	2010-12-03 11:00:31.981381002 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ ##
+ ## Copyright (C) 2008, 2009 Nokia. All rights reserved.
+ ##
+diff -Naur ../libcontentaction-0.1.35//t/test-special-chars.py libcontentaction-0.1.35//t/test-special-chars.py
+--- ../libcontentaction-0.1.35//t/test-special-chars.py	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/test-special-chars.py	2010-12-03 11:00:04.933381000 +0200
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python2.5
++#!/usr/bin/python
+ ##
+ ## Copyright (C) 2008, 2009 Nokia. All rights reserved.
+ ##
+diff -Naur ../libcontentaction-0.1.35//t/tests.xml libcontentaction-0.1.35//t/tests.xml
+--- ../libcontentaction-0.1.35//t/tests.xml	2010-12-01 15:13:57.000000000 +0200
++++ libcontentaction-0.1.35//t/tests.xml	2010-12-07 14:24:00.345381001 +0200
+@@ -4,10 +4,9 @@
+     <set name="libcontentaction" description="Tests for libcontentaction">
+       <pre_steps>
+ 	<step expected_result="0">
+-          . /tmp/session_bus_address.user; \
+-	  echo "[General]" > /home/user/.config/tracker/tracker-store.cfg; \
+-	  echo "Verbosity=3" >> /home/user/.config/tracker/tracker-store.cfg; \
+-	  echo "LowMemoryMode=false" >> /home/user/.config/tracker/tracker-store.cfg; \
++	  echo "[General]" > /home/meego/.config/tracker/tracker-store.cfg; \
++	  echo "Verbosity=3" >> /home/meego/.config/tracker/tracker-store.cfg; \
++	  echo "LowMemoryMode=false" >> /home/meego/.config/tracker/tracker-store.cfg; \
+           cd /usr/share/libcontentaction-tests; \
+           ./sandbox.sh --start
+         </step>
+@@ -15,84 +14,72 @@
+ 
+       <case name="test-actions" description="A test of questionable value.">
+         <step expected_result="0">
+-          . /tmp/session_bus_address.user; \
+           cd /usr/share/libcontentaction-tests; \
+           PATH=.:$PATH XDG_DATA_HOME=/usr/share/libcontentaction-tests ./test-actions.sh
+         </step>
+       </case>
+       <case name="test-mimetypes" description="A test of questionable value.">
+         <step expected_result="0">
+-          . /tmp/session_bus_address.user; \
+           cd /usr/share/libcontentaction-tests; \
+           PATH=.:$PATH XDG_DATA_HOME=/usr/share/libcontentaction-tests ./test-mimetypes.sh
+         </step>
+       </case>
+       <case name="test-invoking" description="A test of questionable value.">
+         <step expected_result="0">
+-          . /tmp/session_bus_address.user; \
+           cd /usr/share/libcontentaction-tests; \
+           PATH=.:$PATH XDG_DATA_HOME=/usr/share/libcontentaction-tests ./test-invoking.py
+         </step>
+       </case>
+       <case name="test-defaults" description="A test of questionable value.">
+         <step expected_result="0">
+-          . /tmp/session_bus_address.user; \
+           cd /usr/share/libcontentaction-tests; \
+           PATH=.:$PATH XDG_DATA_HOME=/usr/share/libcontentaction-tests ./test-defaults.py
+         </step>
+       </case>
+       <case name="test-servicefw-signals" description="A test of questionable value.">
+         <step expected_result="0">
+-          . /tmp/session_bus_address.user; \
+           cd /usr/share/libcontentaction-tests; \
+           PATH=.:/usr/lib/libcontentaction-tests:$PATH XDG_DATA_HOME=/usr/share/libcontentaction-tests ./test-servicefw-signals.py
+         </step>
+       </case>
+       <case name="test-mimes" description="A test of questionable value.">
(65 more lines skipped)

++++++ libcontentaction-0.1.15.tar.gz -> libcontentaction-0.1.35.tar.gz
--- .git
+++ .git
+(directory)
--- .git/HEAD
+++ .git/HEAD
+ref: refs/heads/m-branch
--- .git/branches
+++ .git/branches
+(directory)
--- .git/config
+++ .git/config
+[core]
+	repositoryformatversion = 0
+	filemode = true
+	bare = false
+	logallrefupdates = true
+[remote "origin"]
+	fetch = +refs/heads/*:refs/remotes/origin/*
+	url = git at gitorious.org:maemo-af/libcontentaction.git
+[branch "master"]
+	remote = origin
+	merge = refs/heads/master
--- .git/description
+++ .git/description
+Unnamed repository; edit this file 'description' to name the repository.
--- .git/hooks
+++ .git/hooks
+(directory)
--- .git/hooks/applypatch-msg.sample
+++ .git/hooks/applypatch-msg.sample
+#!/bin/sh
+#
+# An example hook script to check the commit log message taken by
+# applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.  The hook is
+# allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "applypatch-msg".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/commit-msg" &&
+	exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+:
--- .git/hooks/commit-msg.sample
+++ .git/hooks/commit-msg.sample
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by "git commit" with one argument, the name of the file
+# that has the commit message.  The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit.  The hook is allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "commit-msg".
+
+# Uncomment the below to add a Signed-off-by line to the message.
+# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
+# hook is more suited to it.
+#
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
+	echo >&2 Duplicate Signed-off-by lines.
+	exit 1
+}
--- .git/hooks/post-commit.sample
+++ .git/hooks/post-commit.sample
+#!/bin/sh
+#
+# An example hook script that is called after a successful
+# commit is made.
+#
+# To enable this hook, rename this file to "post-commit".
+
+: Nothing
--- .git/hooks/post-receive.sample
+++ .git/hooks/post-receive.sample
+#!/bin/sh
+#
+# An example hook script for the "post-receive" event.
+#
+# The "post-receive" script is run after receive-pack has accepted a pack
+# and the repository has been updated.  It is passed arguments in through
+# stdin in the form
+#  <oldrev> <newrev> <refname>
+# For example:
+#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
+#
+# see contrib/hooks/ for a sample, or uncomment the next line and
+# rename the file to "post-receive".
+
+#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
--- .git/hooks/post-update.sample
+++ .git/hooks/post-update.sample
+#!/bin/sh
+#
+# An example hook script to prepare a packed repository for use over
+# dumb transports.
+#
+# To enable this hook, rename this file to "post-update".
+
+exec git update-server-info
--- .git/hooks/pre-applypatch.sample
+++ .git/hooks/pre-applypatch.sample
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed
+# by applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-applypatch".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/pre-commit" &&
+	exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+:
--- .git/hooks/pre-commit.sample
+++ .git/hooks/pre-commit.sample
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git commit" with no arguments.  The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-commit".
+
+if git rev-parse --verify HEAD >/dev/null 2>&1
+then
+	against=HEAD
+else
+	# Initial commit: diff against an empty tree object
+	against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
+# If you want to allow non-ascii filenames set this variable to true.
+allownonascii=$(git config hooks.allownonascii)
+
+# Cross platform projects tend to avoid non-ascii filenames; prevent
+# them from being added to the repository. We exploit the fact that the
+# printable range starts at the space character and ends with tilde.
+if [ "$allownonascii" != "true" ] &&
+	# Note that the use of brackets around a tr range is ok here, (it's
+	# even required, for portability to Solaris 10's /usr/bin/tr), since
+	# the square bracket bytes happen to fall in the designated range.
+	test "$(git diff --cached --name-only --diff-filter=A -z $against |
+	  LC_ALL=C tr -d '[ -~]\0')"
+then
+	echo "Error: Attempt to add a non-ascii file name."
+	echo
+	echo "This can cause problems if you want to work"
+	echo "with people on other platforms."
+	echo
+	echo "To be portable it is advisable to rename the file ..."
+	echo
+	echo "If you know what you are doing you can disable this"
+	echo "check using:"
+	echo
+	echo "  git config hooks.allownonascii true"
+	echo
+	exit 1
+fi
+
+exec git diff-index --check --cached $against --
--- .git/hooks/pre-rebase.sample
+++ .git/hooks/pre-rebase.sample
+#!/bin/sh
+#
+# Copyright (c) 2006, 2008 Junio C Hamano
+#
+# The "pre-rebase" hook is run just before "git rebase" starts doing
+# its job, and can prevent the command from running by exiting with
+# non-zero status.
+#
+# The hook is called with the following parameters:
+#
+# $1 -- the upstream the series was forked from.
+# $2 -- the branch being rebased (or empty when rebasing the current branch).
+#
+# This sample shows how to prevent topic branches that are already
+# merged to 'next' branch from getting rebased, because allowing it
+# would result in rebasing already published history.
+
+publish=next
+basebranch="$1"
+if test "$#" = 2
+then
+	topic="refs/heads/$2"
+else
+	topic=`git symbolic-ref HEAD` ||
+	exit 0 ;# we do not interrupt rebasing detached HEAD
+fi
+
+case "$topic" in
+refs/heads/??/*)
+	;;
+*)
+	exit 0 ;# we do not interrupt others.
+	;;
+esac
+
+# Now we are dealing with a topic branch being rebased
+# on top of master.  Is it OK to rebase it?
+
+# Does the topic really exist?
+git show-ref -q "$topic" || {
+	echo >&2 "No such branch $topic"
+	exit 1
+}
+
+# Is topic fully merged to master?
+not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
+if test -z "$not_in_master"
+then
+	echo >&2 "$topic is fully merged to master; better remove it."
+	exit 1 ;# we could allow it, but there is no point.
+fi
+
+# Is topic ever merged to next?  If so you should not be rebasing it.
+only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
+only_next_2=`git rev-list ^master           ${publish} | sort`
+if test "$only_next_1" = "$only_next_2"
+then
+	not_in_topic=`git rev-list "^$topic" master`
+	if test -z "$not_in_topic"
+	then
+		echo >&2 "$topic is already up-to-date with master"
+		exit 1 ;# we could allow it, but there is no point.
+	else
+		exit 0
+	fi
+else
+	not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
+	/usr/bin/perl -e '
+		my $topic = $ARGV[0];
+		my $msg = "* $topic has commits already merged to public branch:\n";
+		my (%not_in_next) = map {
+			/^([0-9a-f]+) /;
+			($1 => 1);
+		} split(/\n/, $ARGV[1]);
+		for my $elem (map {
+				/^([0-9a-f]+) (.*)$/;
+				[$1 => $2];
+			} split(/\n/, $ARGV[2])) {
+			if (!exists $not_in_next{$elem->[0]}) {
+				if ($msg) {
+					print STDERR $msg;
+					undef $msg;
+				}
+				print STDERR " $elem->[1]\n";
+			}
+		}
+	' "$topic" "$not_in_next" "$not_in_master"
+	exit 1
+fi
+
+exit 0
+
+################################################################
+
+This sample hook safeguards topic branches that have been
+published from being rewound.
+
+The workflow assumed here is:
+
+ * Once a topic branch forks from "master", "master" is never
+   merged into it again (either directly or indirectly).
+
+ * Once a topic branch is fully cooked and merged into "master",
+   it is deleted.  If you need to build on top of it to correct
+   earlier mistakes, a new topic branch is created by forking at
+   the tip of the "master".  This is not strictly necessary, but
+   it makes it easier to keep your history simple.
+
+ * Whenever you need to test or publish your changes to topic
+   branches, merge them into "next" branch.
+
+The script, being an example, hardcodes the publish branch name
+to be "next", but it is trivial to make it configurable via
+$GIT_DIR/config mechanism.
+
+With this workflow, you would want to know:
+
+(1) ... if a topic branch has ever been merged to "next".  Young
+    topic branches can have stupid mistakes you would rather
+    clean up before publishing, and things that have not been
+    merged into other branches can be easily rebased without
+    affecting other people.  But once it is published, you would
+    not want to rewind it.
+
+(2) ... if a topic branch has been fully merged to "master".
+    Then you can delete it.  More importantly, you should not
+    build on top of it -- other people may already want to
+    change things related to the topic as patches against your
+    "master", so if you need further changes, it is better to
+    fork the topic (perhaps with the same name) afresh from the
+    tip of "master".
+
+Let's look at this example:
+
+		   o---o---o---o---o---o---o---o---o---o "next"
+		  /       /           /           /
+		 /   a---a---b A     /           /
+		/   /               /           /
+	       /   /   c---c---c---c B         /
+	      /   /   /             \         /
+	     /   /   /   b---b C     \       /
+	    /   /   /   /             \     /
+    ---o---o---o---o---o---o---o---o---o---o---o "master"
+
+
+A, B and C are topic branches.
+
+ * A has one fix since it was merged up to "next".
+
+ * B has finished.  It has been fully merged up to "master" and "next",
+   and is ready to be deleted.
+
+ * C has not merged to "next" at all.
+
+We would want to allow C to be rebased, refuse A, and encourage
+B to be deleted.
+
+To compute (1):
+
+	git rev-list ^master ^topic next
+	git rev-list ^master        next
+
+	if these match, topic has not merged in next at all.
+
+To compute (2):
+
+	git rev-list master..topic
+
+	if this is empty, it is fully merged to "master".
--- .git/hooks/prepare-commit-msg.sample
+++ .git/hooks/prepare-commit-msg.sample
+#!/bin/sh
+#
+# An example hook script to prepare the commit log message.
+# Called by "git commit" with the name of the file that has the
+# commit message, followed by the description of the commit
+# message's source.  The hook's purpose is to edit the commit
+# message file.  If the hook fails with a non-zero status,
+# the commit is aborted.
+#
+# To enable this hook, rename this file to "prepare-commit-msg".
+
+# This hook includes three examples.  The first comments out the
+# "Conflicts:" part of a merge commit.
+#
+# The second includes the output of "git diff --name-status -r"
+# into the message, just before the "git status" output.  It is
+# commented because it doesn't cope with --amend or with squashed
+# commits.
+#
+# The third example adds a Signed-off-by line to the message, that can
+# still be edited.  This is rarely a good idea.
+
+case "$2,$3" in
+  merge,)
+    /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
+
+# ,|template,)
+#   /usr/bin/perl -i.bak -pe '
+#      print "\n" . `git diff --cached --name-status -r`
+#	 if /^#/ && $first++ == 0' "$1" ;;
+
+  *) ;;
+esac
+
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
--- .git/hooks/update.sample
+++ .git/hooks/update.sample
+#!/bin/sh
+#
+# An example hook script to blocks unannotated tags from entering.
+# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
+#
+# To enable this hook, rename this file to "update".
+#
+# Config
+# ------
+# hooks.allowunannotated
+#   This boolean sets whether unannotated tags will be allowed into the
+#   repository.  By default they won't be.
+# hooks.allowdeletetag
+#   This boolean sets whether deleting tags will be allowed in the
+#   repository.  By default they won't be.
+# hooks.allowmodifytag
+#   This boolean sets whether a tag may be modified after creation. By default
+#   it won't be.
+# hooks.allowdeletebranch
+#   This boolean sets whether deleting branches will be allowed in the
+#   repository.  By default they won't be.
+# hooks.denycreatebranch
+#   This boolean sets whether remotely creating branches will be denied
+#   in the repository.  By default this is allowed.
+#
+
+# --- Command line
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# --- Safety check
+if [ -z "$GIT_DIR" ]; then
+	echo "Don't run this script from the command line." >&2
+	echo " (if you want, you could supply GIT_DIR then run" >&2
+	echo "  $0 <ref> <oldrev> <newrev>)" >&2
+	exit 1
+fi
+
+if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
+	echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
+	exit 1
+fi
+
+# --- Config
+allowunannotated=$(git config --bool hooks.allowunannotated)
+allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
+denycreatebranch=$(git config --bool hooks.denycreatebranch)
+allowdeletetag=$(git config --bool hooks.allowdeletetag)
+allowmodifytag=$(git config --bool hooks.allowmodifytag)
+
+# check for no description
+projectdesc=$(sed -e '1q' "$GIT_DIR/description")
+case "$projectdesc" in
+"Unnamed repository"* | "")
+	echo "*** Project description file hasn't been set" >&2
+	exit 1
+	;;
+esac
+
+# --- Check types
+# if $newrev is 0000...0000, it's a commit to delete a ref.
+zero="0000000000000000000000000000000000000000"
+if [ "$newrev" = "$zero" ]; then
+	newrev_type=delete
+else
+	newrev_type=$(git cat-file -t $newrev)
+fi
+
+case "$refname","$newrev_type" in
+	refs/tags/*,commit)
+		# un-annotated tag
+		short_refname=${refname##refs/tags/}
+		if [ "$allowunannotated" != "true" ]; then
+			echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
+			echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
+			exit 1
+		fi
+		;;
+	refs/tags/*,delete)
+		# delete tag
+		if [ "$allowdeletetag" != "true" ]; then
+			echo "*** Deleting a tag is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	refs/tags/*,tag)
+		# annotated tag
+		if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
+		then
+			echo "*** Tag '$refname' already exists." >&2
+			echo "*** Modifying a tag is not allowed in this repository." >&2
+			exit 1
+		fi
+		;;
+	refs/heads/*,commit)
+		# branch
+		if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
+			echo "*** Creating a branch is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	refs/heads/*,delete)
+		# delete branch
+		if [ "$allowdeletebranch" != "true" ]; then
+			echo "*** Deleting a branch is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	refs/remotes/*,commit)
+		# tracking branch
+		;;
+	refs/remotes/*,delete)
+		# delete tracking branch
+		if [ "$allowdeletebranch" != "true" ]; then
+			echo "*** Deleting a tracking branch is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	*)
+		# Anything else (is there anything else?)
+		echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
+		exit 1
+		;;
+esac
+
+# --- Finished
+exit 0
--- .git/info
+++ .git/info
+(directory)
--- .git/info/exclude
+++ .git/info/exclude
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
--- .git/logs
+++ .git/logs
+(directory)
--- .git/logs/HEAD
+++ .git/logs/HEAD
+0000000000000000000000000000000000000000 d28a7bcfd3d8cbf6094d67b6a38c8ec50be517ea maimishr <ext-maitrey.mishra at nokia.com> 1291209237 +0200	clone: from git at gitorious.org:maemo-af/libcontentaction.git
+d28a7bcfd3d8cbf6094d67b6a38c8ec50be517ea 479e2f295059b9b0a470ce6013ebf401555030d7 maimishr <ext-maitrey.mishra at nokia.com> 1291209468 +0200	checkout: moving from master to m-branch
--- .git/logs/refs
+++ .git/logs/refs
+(directory)
--- .git/logs/refs/heads
+++ .git/logs/refs/heads
+(directory)
--- .git/logs/refs/heads/m-branch
+++ .git/logs/refs/heads/m-branch
+0000000000000000000000000000000000000000 479e2f295059b9b0a470ce6013ebf401555030d7 maimishr <ext-maitrey.mishra at nokia.com> 1291209468 +0200	branch: Created from 0.1.35
--- .git/logs/refs/heads/master
+++ .git/logs/refs/heads/master
+0000000000000000000000000000000000000000 d28a7bcfd3d8cbf6094d67b6a38c8ec50be517ea maimishr <ext-maitrey.mishra at nokia.com> 1291209237 +0200	clone: from git at gitorious.org:maemo-af/libcontentaction.git
--- .git/objects
+++ .git/objects
+(directory)
--- .git/objects/info
+++ .git/objects/info
+(directory)
--- .git/objects/pack
+++ .git/objects/pack
+(directory)
--- .git/packed-refs
+++ .git/packed-refs
+# pack-refs with: peeled 
+2ab603557b496780fb53ebe3c5f49af613eba38b refs/tags/0.1.9
+b8fbd559e0a3ef693fe1b5e022997a399adf1152 refs/tags/0.1.8
+0660deb5336db0ea84d7f6b4bff4f2d05982e025 refs/tags/0.1.7
+0997bad02dc931206787e0ae19c558a22314f6d5 refs/tags/0.1.6
+686b3cae97935f99bf3d1482e5e4c207c0cd66d2 refs/tags/0.1.5
+40a7f0f7c014b844623a06731977ce5daed58fbc refs/tags/0.1.4
+^b5c8c72803c5bf6bfa4e48e99f42d772955d7464
+479e2f295059b9b0a470ce6013ebf401555030d7 refs/tags/0.1.35
+695c26acfa3565d868bd1bf813f1c3028a9d5633 refs/tags/0.1.34
+c4f5dbf61e4d761a54a02cf4a975ec3be61410ae refs/tags/0.1.33
+b53bcea1e80414a5f79977d5a383cf4f188b417b refs/tags/0.1.32
+848ccfd86690f0a8f0597768ec6e8c06b5cb8ecb refs/tags/0.1.31
+9b79e321c2fb3a9c9d30069daa4219597db85b57 refs/tags/0.1.30
+679712b44dd5981ce343add9d4aa50d7b809fae7 refs/tags/0.1.3
+c988686f6359dd7d7d38ca4a01e1501bb4650842 refs/tags/0.1.29
+dc333aefdb8299d037160c6fb146d483ee920eca refs/tags/0.1.28
+752d37bfa15fe0d6012b063fd9a57f7e5dc2e6cf refs/tags/0.1.27
+4f640f9f57a8f4feafa9c67c264519edade16a1c refs/tags/0.1.26
+c5c6009dc2af778eb2c42ee64803c6c8556d2f65 refs/tags/0.1.25
+db7c3ff3339d5360526643df36d0857b3f09e0fa refs/tags/0.1.24
+dec2f7ba527a11ffc8a74d177b31f4ce455ed8bd refs/tags/0.1.23
+6696e362a5b77124efe059445244b94469f95afc refs/tags/0.1.22
+270d91bb97addc1f1af9848ba9fea7aac9dc979b refs/tags/0.1.21
+1f8e24f9fd6354b06fc14b49d304b7f9e07191b3 refs/tags/0.1.20
+4cf6dc9eddc5ecce9f606b887a8c5c386a29d855 refs/tags/0.1.2
+29f9105b2e8599b33ec2eb8ed1fb4c4422ae727e refs/tags/0.1.19
+59bc44d59f486d71ba77bf9e676dd7058d92531b refs/tags/0.1.18
+8920565ee41a613cf3b4ece9f358543075ee5045 refs/tags/0.1.17
+25e1ba874a2f3f91593aa793863128334753553c refs/tags/0.1.16
+2e0943652ad1251bfa3cba2a0348328271348476 refs/tags/0.1.15
+1bb6aafbc98ff26a1f70edd093e2daf18591d4e2 refs/tags/0.1.14
+508e671adb9774448c4d2c36547fad5f9404e3c0 refs/tags/0.1.13
+ae61655069cad8b06d5f3a49cbf560aefe53fa4a refs/tags/0.1.12
+da1e01931cb28ad4894f1ae86fd7daf878cdb71f refs/tags/0.1.11
+86d704801eea7a62750d30c26fe1366f05308430 refs/tags/0.1.10
+25ce56bf7b7ffbf7667aaa5a73aac3856fca3a94 refs/tags/0.1.1
+^65042c8e68f7b909354bfd3791486bcc62b79e0b
+09fe661ede23b6d7edcd26fa24361e9b89d9bfb8 refs/tags/0.1.0
+dbfb8e3277d39d4a8f325018724de587aed632a8 refs/tags/0.0.8.1
+^61e925b5b0879bf7d104d416a10575b6d6699840
+80b66f2da037f4489a6b5fb7709baf6253820532 refs/tags/0.0.8
+05f8524c613352d10622c087c472d5352a2c7263 refs/tags/0.0.7
+f3e3c5ffea6f412e7875fa28b2185acb87d39ba2 refs/tags/0.0.6
+7795c8d41c087af0309ed6c9be61dd7fc9f92f2f refs/tags/0.0.5
+1863d0f1fcca9d4b388ac4f96a198dc2d67ac176 refs/tags/0.0.4
+79e944cc0ccb34da669f8b85399039c3cda94297 refs/tags/0.0.3
+0f03a794f7eabe70df769ae92e00f91239caa599 refs/tags/0.0.2
+36f748db9cc7c4956b6ef3728747e5fe89971b04 refs/tags/0.0.1
+d28a7bcfd3d8cbf6094d67b6a38c8ec50be517ea refs/remotes/origin/master
+027f591e6104b846513fe9e96dd9c31638cb58f7 refs/remotes/origin/docs
--- .git/refs
+++ .git/refs
+(directory)
--- .git/refs/heads
+++ .git/refs/heads
+(directory)
--- .git/refs/heads/m-branch
+++ .git/refs/heads/m-branch
+479e2f295059b9b0a470ce6013ebf401555030d7
--- .git/refs/heads/master
+++ .git/refs/heads/master
+d28a7bcfd3d8cbf6094d67b6a38c8ec50be517ea
--- .git/refs/remotes
+++ .git/refs/remotes
+(directory)
--- .git/refs/remotes/origin
+++ .git/refs/remotes/origin
+(directory)
--- .git/refs/remotes/origin/HEAD
+++ .git/refs/remotes/origin/HEAD
+ref: refs/remotes/origin/master
--- .git/refs/tags
+++ .git/refs/tags
+(directory)
--- .gitignore
+++ .gitignore
@@ -33,6 +33,7 @@
 t/hldemo
 t/hl1
 t/l10ntest
+t/*.pyo
 
 t/test-l10n-data/test_en.qm
 t/test-l10n-data/test_hu.qm
--- configure.ac
+++ configure.ac
@@ -1,5 +1,5 @@
 AC_PREREQ([2.61])
-AC_INIT([libcontentaction], [0.1.15], [marius.vollmer at nokia.com], libcontentaction)
+AC_INIT([libcontentaction], [0.1.35], [marius.vollmer at nokia.com], libcontentaction)
 
 AC_CONFIG_SRCDIR([Makefile.am])
 AM_INIT_AUTOMAKE([-Wall -Werror foreign])
@@ -8,7 +8,7 @@
 
 AC_LANG([C++])
 AC_PROG_CXX
-CXXFLAGS="$CXXFLAGS -Wall -Werror"
+CXXFLAGS="$CXXFLAGS -Wall -Werror -DQT_NO_KEYWORDS"
 AC_DISABLE_STATIC
 AC_PROG_LIBTOOL
 AC_PROG_QT_MOC
--- data/Makefile.am
+++ data/Makefile.am
@@ -3,7 +3,9 @@
 	tracker1.xml \
 	highlight1.xml
 
-EXTRA_DIST = \
+testdatadir = $(datadir)/libcontentaction-tests/data
+dist_testdata_DATA = \
+	highlight1.xml \
 	hl-examples.xml
 
 applicationsdir = $(datadir)/applications
--- data/defaults.list
+++ data/defaults.list
@@ -5,6 +5,7 @@
 application/msword=office-tools.desktop
 application/pdf=office-tools.desktop
 application/rss+xml=feedreader.desktop
+application/smil=musicsuiteplaylistinterface.desktop
 application/vnd.ms-powerpoint=office-tools.desktop
 application/vnd.oasis.opendocument.presentation=office-tools.desktop
 application/vnd.oasis.opendocument.spreadsheet=office-tools.desktop
@@ -17,28 +18,32 @@
 application/vnd.openxmlformats-officedocument.wordprocessingml.document=office-tools.desktop
 application/vnd.openxmlformats-officedocument.wordprocessingml.template=office-tools.desktop
 application/x-mplayer2=musicsuiteinterface.desktop
+application/x-smil=musicsuiteplaylistinterface.desktop
 application/x-vnd.oasis.opendocument.presentation=office-tools.desktop
 application/x-vnd.oasis.opendocument.spreadsheet=office-tools.desktop
 application/x-vnd.oasis.opendocument.text=office-tools.desktop
 audio/AMR-WB=musicsuiteinterface.desktop
 audio/AMR=musicsuiteinterface.desktop
+audio/aac=musicsuiteinterface.desktop
 audio/mp2=musicsuiteinterface.desktop
 audio/mp3=musicsuiteinterface.desktop
 audio/mp4=musicsuiteinterface.desktop
 audio/mpeg=musicsuiteinterface.desktop
-audio/mpegurl=musicsuiteinterface.desktop
+audio/mpegurl=musicsuiteplaylistinterface.desktop
+audio/playlist=musicsuiteplaylistinterface.desktop
 audio/wav=musicsuiteinterface.desktop
 audio/x-amr=musicsuiteinterface.desktop
 audio/x-m4a=musicsuiteinterface.desktop
 audio/x-mp2=musicsuiteinterface.desktop
 audio/x-mpeg=musicsuiteinterface.desktop
-audio/x-mpegurl=musicsuiteinterface.desktop
+audio/x-mpegurl=musicsuiteplaylistinterface.desktop
 audio/x-ms-asx=musicsuiteinterface.desktop
 audio/x-ms-wax=musicsuiteinterface.desktop
 audio/x-ms-wma=musicsuiteinterface.desktop
-audio/x-scpls=musicsuiteinterface.desktop
+audio/x-scpls=musicsuiteplaylistinterface.desktop
 audio/x-wav=musicsuiteinterface.desktop
 message/rfc822=mail.desktop
+text/html=fennec.desktop
 text/plain=office-tools.desktop
 video/3gpp2=videosuiteinterface.desktop
 video/3gpp=videosuiteinterface.desktop
@@ -50,6 +55,11 @@
 video/x-ms-wmv=videosuiteinterface.desktop
 video/x-msvideo=videosuiteinterface.desktop
 video/x-xvid=videosuiteinterface.desktop
+x-maemo-highlight/http-url=fennec.desktop
 x-maemo-nepomuk/contact=opencontactcard.desktop
 x-maemo-nepomuk/email=mail.desktop
+x-maemo-urischeme/file=fennec.desktop
+x-maemo-urischeme/ftp=fennec.desktop
+x-maemo-urischeme/http=fennec.desktop
+x-maemo-urischeme/https=fennec.desktop
 x-maemo-urischeme/mailto=mail.desktop
--- data/highlight1.xml
+++ data/highlight1.xml
@@ -1,8 +1,10 @@
 <?xml version="1.0"?>
 <actions>
-
-  <highlight regexp="[^\s@]+@([^\s.]+)(\.[^\s.]+)*" name="email-address"/>
-  <highlight regexp="\+?\d+([- ]\d+)*" name="phone-number"/>
-  <highlight regexp="http://(\w+((:\w+)?)@)?(\w+\.)+\w+(:\d+)?(/[\w ./?%&=]*)?" name="http-url"/>
-
+  <highlight regexp="(mailto:)?[\w!#$%&'*+/=?^`{|}~-]+(\.[\w!#$%&'*+/=?^`{|}~-]+)*@([\w-]+\.)+[\w-]+" name="email-address"/>
+  <highlight regexp="((\+ ?)|\b|\(\d+\))(\(\d+\) ?|\d[-.pwxPWX# ]?){2,19}\d(?!\d)" name="phone-number"/>
+  <highlight regexp="(https?://)?music\.ovi\.com(/[\w/?%:;@&=+$,\-.!~*'()]*)?" name="ovi-music-url" specialCaseOf="http-url"/>
+  <highlight regexp="(https?://)?store\.ovi\.(com|mobi)(.cn)?(/[\w/?%:;@&=+$,\-.!~*'()]*)?" name="ovi-store-url" specialCaseOf="http-url"/>
+  <highlight regexp="(https?://|www\.)(\w+((:\w+)?)@)?([\w\-]+\.)+[\w\-]+(:\d+)?(/[\w/?%:;@&=+$,\-.!~*'()]*)?" name="http-url"/>
+  <highlight regexp="ftp://(\w+((:\w+)?)@)?([\w\-]+\.)+[\w\-]+(:\d+)?(/[\w/?%:;@&=+$,\-.!~*'()]*)?" name="ftp-url"/>
+  <highlight regexp="feed:(//)?(\w+((:\w+)?)@)?([\w\-]+\.)+[\w\-]+(:\d+)?(/[\w/?%:;@&=+$,\-.!~*'()]*)?" name="feed-url"/>
 </actions>
--- data/hl-examples.xml
+++ data/hl-examples.xml
@@ -2,9 +2,21 @@
 <actions>
   <!-- the following highlight rules are just examples -->
 
-  <highlight regexp="[^\s@]+@([^\s.]+)(\.[^\s.]+)*" name="email"/>
-  <highlight regexp="\+?\d+([- ]\d+)*" name="phone"/>
-  <highlight regexp="http://(\w+((:\w+)?)@)?(\w+\.)+\w+(:\d+)?(/[\w ./?%&=]*)?" name="url"/>
-  <highlight regexp="http://(\w+((:\w+)?)@)?example.com(:\d+)?(/[\w ./?%&=]*)?" name="ovoda"/>
+  <highlight regexp="http://(\w+((:\w+)?)@)?example.com(:\d+)?(/[\w ./?%&=]*)?" name="special-url"/>
+
+  <!-- testing the special case relationships: general case + 2 specializations -->
+  <highlight regexp="foobar\w*" name="special-1a" specialCaseOf="general-1"/>
+  <highlight regexp="foo\w*" name="general-1"/>
+  <highlight regexp="foobaz\w*" name="special-1b" specialCaseOf="general-1"/>
+
+  <!-- testing the special case relationships: general case + specialization + its specialization -->
+  <highlight regexp="cat\w*" name="general-2"/>
+  <highlight regexp="catdogzebra\w*" name="superspecial-2" specialCaseOf="special-2"/>
+  <highlight regexp="catdog\w*" name="special-2" specialCaseOf="general-2"/>
+
+  <!-- testing the special case relationships: even the circular case shouldn't confuse the computation -->
+  <highlight regexp="circ" name="circular-1" specialCaseOf="circular-2"/>
+  <highlight regexp="circ2" name="circular-2" specialCaseOf="circular-1"/>
+
 
 </actions>
--- data/tracker1.xml
+++ data/tracker1.xml
@@ -70,11 +70,6 @@
     { ?uri a nmo:IMMessage . }
   </tracker-condition>
 
-  <!-- dont use this, use music-piece instead -->
-  <tracker-condition name="musicpiece"> 
-    { ?uri a nmm:MusicPiece . }
-  </tracker-condition>
-
   <tracker-condition name="music-piece"> 
     { ?uri a nmm:MusicPiece . }
   </tracker-condition>
--- debian/api
+++ debian/api
@@ -1,6 +1,8 @@
 interface: libcontentaction
 type: library
-scope: Nokia MeeGo
-state: experimental
+scope: Platform
+state: stable
 libs-pkg: libcontentaction0
 dev-pkg: libcontentaction-dev
+doc-type: doxygen
+doxygen-conf: doc/doxy.cfg
--- debian/changelog
+++ debian/changelog
@@ -1,3 +1,147 @@
+libcontentaction (0.1.35) unstable; urgency=low
+
+  * API addition: Action::actionsForString(const QString&)
+  * API addition: Action::defaultActionForString(const QString&)
+  * Enabling defining regexps specialCaseOf="another-regexp"
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Wed, 01 Dec 2010 14:02:49 +0200
+
+libcontentaction (0.1.34) unstable; urgency=low
+
+  * Configuration update: regexp for phone number
+  * Configuration update: added feed-url, ftp-url, ovi-store-url
+  * Fixes: NB#191300
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Mon, 29 Nov 2010 12:00:30 +0200
+
+libcontentaction (0.1.33) unstable; urgency=low
+
+  * QT_NO_KEYWORDS
+  * Configuration update: regexp for phone number
+  * Fixes: NB#191300
+  * Don't use QDBusInterface when triggering D-Bus actions (workaround, QTBUG-14485)
+  
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Thu, 28 Oct 2010 13:59:32 +0300
+
+libcontentaction (0.1.32) unstable; urgency=low
+
+  * Doc update.
+  * Making regexps case-insensitive.
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Thu, 07 Oct 2010 10:23:24 +0300
+
+libcontentaction (0.1.31) unstable; urgency=low
+
+  * Storing regexps in order; enables prioritizing specific ones over generic ones.
+  * Added missing _aegis file for tests
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Wed, 29 Sep 2010 09:18:23 +0300
+
+libcontentaction (0.1.30) unstable; urgency=low
+
+  * Configuration update: regexp for ovi music urls
+  * Re-reading mimeinfo.cache
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Fri, 24 Sep 2010 14:53:02 +0300
+
+libcontentaction (0.1.29) unstable; urgency=low
+
+  * Fixes: NB#191392
+  * Fixes: NB#191945
+  * Configuration update: e-mail address regexps
+  * Fixes: NB#191957
+  
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Thu, 23 Sep 2010 10:37:25 +0300
+
+libcontentaction (0.1.28) unstable; urgency=low
+
+  * Added 2 Action::launcherAction functions.
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Fri, 17 Sep 2010 13:17:13 +0300
+
+libcontentaction (0.1.27) unstable; urgency=low
+
+  * Fixes: NB#192179
+  * Merging the highlighter tool hl1 into lca-tool (lca-tool --highlight)
+  
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Thu, 16 Sep 2010 11:53:09 +0300
+
+libcontentaction (0.1.26) unstable; urgency=low
+
+  * Fixes: NB#191945
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Wed, 15 Sep 2010 10:14:26 +0300
+
+libcontentaction (0.1.25) unstable; urgency=low
+
+  * Fixes: NB#191791
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Tue, 14 Sep 2010 12:10:12 +0300
+
+libcontentaction (0.1.24) unstable; urgency=low
+
+  * Configuration update: regexps
+  * Fixes: NB#191491
+  * Fixes: NB#191290
+  * Fixes: NB#179385
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Mon, 13 Sep 2010 13:04:21 +0300
+
+libcontentaction (0.1.23) unstable; urgency=low
+
+  * Fixes: NB#190808
+  * lca doesn't get confused if defaults.list contains nonexistent applications
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Mon, 13 Sep 2010 09:36:06 +0300
+
+libcontentaction (0.1.22) unstable; urgency=low
+
+  * Fixing double %-escaping of special characters.
+  * Defaults.list update
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Mon, 06 Sep 2010 10:12:26 +0300
+
+libcontentaction (0.1.21) unstable; urgency=low
+
+  * Configuration update: http-url regexp.
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Fri, 03 Sep 2010 12:19:00 +0300
+
+libcontentaction (0.1.20) unstable; urgency=low
+
+  * Configuration update.
+  * Fixes: NB#189612
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Wed, 01 Sep 2010 12:43:48 +0300
+
+libcontentaction (0.1.19) unstable; urgency=low
+
+  * Small doc fix: .desktop keys were out of date.
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Wed, 01 Sep 2010 11:50:39 +0300
+
+libcontentaction (0.1.18) unstable; urgency=low
+
+  * Remove duplicate actions: now it's ok to declare both x-maemo-nepomuk
+    mime types and the corresponding real mime types in a .desktop file.
+  * Fixes: NB#186435
+
+ -- Marja Hassinen <marja.hassinen at nokia.com>  Mon, 30 Aug 2010 10:39:56 +0300
+
+libcontentaction (0.1.17) unstable; urgency=low
+
+  * Assume "file" scheme if none given.
+  * Fixes: NB#180448
+  * Fixes: NB#187474
+
+ -- Akos PASZTORY <ext-akos.pasztory at nokia.com>  Thu, 26 Aug 2010 10:38:29 +0300
+
+libcontentaction (0.1.16) unstable; urgency=low
+
+  * Return Icon and Name even for invalid actions.
+
+ -- Akos PASZTORY <ext-akos.pasztory at nokia.com>  Wed, 18 Aug 2010 12:49:36 +0300
+
 libcontentaction (0.1.15) unstable; urgency=low
 
   * Added an API file, whatever it is.
--- debian/control
+++ debian/control
@@ -7,7 +7,9 @@
                libglib2.0-dev (>= 2.12.0),
                libmeegotouch-dev,
                doxygen (>= 1.5.9),
-               pkg-config
+               pkg-config,
+               autoconf, automake, libtool,
+               aegis-builder (>= 1.4)
 Standards-Version: 3.8.0
 
 Package: libcontentaction0
--- debian/libcontentaction-tests.aegis
+++ debian/libcontentaction-tests.aegis
+<aegis>
+   <request>
+      <credential name="TrackerReadAccess" />
+      <for path="/usr/lib/libcontentaction-tests/servicetest" />
+   </request>
+</aegis>
--- debian/libcontentaction0.aegis
+++ debian/libcontentaction0.aegis
+<aegis>
+   <request>
+      <credential name="TrackerReadAccess" />
+      <for path="/usr/bin/lca-tool" />
+   </request>
+</aegis>
--- debian/rules
+++ debian/rules
@@ -63,6 +63,8 @@
 	dh_gencontrol -a
 	dh_md5sums -a
 	dh_builddeb -a
+	aegis-deb-add -control debian/libcontentaction0/DEBIAN/control .. debian/libcontentaction0.aegis=_aegis
+	aegis-deb-add -control debian/libcontentaction-tests/DEBIAN/control .. debian/libcontentaction-tests.aegis=_aegis
 
 binary: binary-indep binary-arch
 .PHONY: build clean binary-indep binary-arch binary install
--- src/config.cpp
+++ src/config.cpp
@@ -36,7 +36,10 @@
 using namespace ContentAction::Internal;
 
 // mime type -> regexp
-static QHash<QString, QString> Highlighter_cfg;
+static QList<QPair<QString, QString> > Highlighter_cfg;
+// raw data for Highlighter_cfg
+static QHash<QString, QString> mimeToRegexp;
+static QHash<QString, QString> mimeToParent;
 // friendly tracker condition name => sparql snippet
 static QHash<QString, QString> Tracker_cfg;
 
@@ -101,7 +104,10 @@
             QString mime = atts.value("name").trimmed();
             if (mime.isEmpty())
                 fail("expected a nonempy mimetype");
-            Highlighter_cfg[mime.prepend(HighlighterMimeClass)] = regexp;
+            mimeToRegexp.insert(mime, regexp);
+            QString parentRegexp = atts.value("specialCaseOf");
+            if (!parentRegexp.isEmpty())
+                mimeToParent.insert(mime, parentRegexp);
         }
         else if (qname == "tracker-condition") {
             state = inTrackerCondition;
@@ -155,6 +161,39 @@
 
 #undef fail
 
+// Constructs Highlighter_cfg from mimeToRegexp and mimeToParent.  Sorts the
+// regexps topologically so that the special cases appear before the general
+// cases.
+static void sortRegexps()
+{
+    // Insert the regexps in the wrong order (parent first, parent is the more
+    // general regexp).  But always prepend, so the list will be in the right
+    // order (special case before the general case).
+    QString toInsert;
+    QString original;
+    while (!mimeToRegexp.isEmpty()) {
+        // Take any regexp
+        toInsert = mimeToRegexp.begin().key();
+        original = toInsert; // store the starting point (for loop detection)
+        while (mimeToParent.contains(toInsert)
+               && mimeToRegexp.contains(mimeToParent.value(toInsert))) {
+            // There is a parent and parent not yet inserted
+            toInsert = mimeToParent.value(toInsert);
+
+            if (toInsert == original) {
+                LCA_WARNING << "Loop in regexp specialization:" << toInsert;
+                // a loop
+                break;
+            }
+        }
+        // Insert, and also remove from mimeToRegexp to note it has been
+        // inserted.
+        Highlighter_cfg.prepend(
+            qMakePair(QString(HighlighterMimeClass) + toInsert,
+                      mimeToRegexp.take(toInsert)));
+    }
+}
+
 static void readConfig()
 {
     static bool read = false;
@@ -170,7 +209,7 @@
     }
     dir.setNameFilters(QStringList("*.xml"));
     QStringList confFiles = dir.entryList(QDir::Files);
-    foreach (const QString& confFile, confFiles) {
+    Q_FOREACH (const QString& confFile, confFiles) {
         QFile file(dir.filePath(confFile));
 
         ConfigReader handler;
@@ -182,13 +221,19 @@
             continue;
         }
     }
+
+    // Sort the regexps topologially: each regexp (e.g., a specialized url)
+    // before its parent (e.g., a more general url)
+    sortRegexps();
+    mimeToRegexp.clear();
+    mimeToParent.clear();
 }
 
 } // end anon namespace
 
 /// Returns the highlighter configuration map of (mimetype, regexp) read from
 /// the configuration files.
-const QHash<QString, QString>& ContentAction::Internal::highlighterConfig()
+const QList<QPair<QString, QString> >& ContentAction::Internal::highlighterConfig()
 {
     readConfig();
     return Highlighter_cfg;
--- src/contentaction.cpp
+++ src/contentaction.cpp
@@ -95,19 +95,19 @@
     LCA_WARNING << "triggered an invalid action, not doing anything.";
 }
 
-DefaultPrivate::DefaultPrivate(MDesktopEntry* desktopEntry, const QStringList& params)
-    : desktopEntry(desktopEntry), params(params)
+DefaultPrivate::DefaultPrivate(QSharedPointer<MDesktopEntry> desktopEntry,
+                               const QStringList& params, bool valid)
+    : desktopEntry(desktopEntry), params(params), valid(valid)
 {
 }
 
 DefaultPrivate::~DefaultPrivate()
 {
-    delete desktopEntry;
 }
 
 bool DefaultPrivate::isValid() const
 {
-    return true;
+    return valid;
 }
 
 QString DefaultPrivate::name() const
@@ -146,9 +146,20 @@
 {
 }
 
-Action createAction(const QString& desktopFile, const QStringList& params)
+/// Creates an Action object which will launch the application defined by \a
+/// desktopFilePath with the given \a params when triggered.
+Action createAction(const QString& desktopFilePath, const QStringList& params)
+{
+    QSharedPointer<MDesktopEntry> desktopEntry(new MDesktopEntry(
+                                                   desktopFilePath));
+    return createAction(desktopEntry, params);
+}
+
+/// Creates an Action object which will launch the application defined by \a
+/// desktopEntry with the given \a params when triggered.
+Action createAction(QSharedPointer<MDesktopEntry> desktopEntry,
+                    const QStringList& params)
 {
-    MDesktopEntry* desktopEntry = new MDesktopEntry(desktopFile);
     if (desktopEntry->contains(XMaemoMethodKey) &&
         !desktopEntry->contains(XMaemoServiceKey)) {
         return Action(new ServiceFwPrivate(desktopEntry, params));
@@ -161,9 +172,8 @@
         return Action(new ExecPrivate(desktopEntry, params));
     }
     else {
-        delete desktopEntry;
         // We don't know how to launch
-        return Action(new ActionPrivate());
+        return Action(new DefaultPrivate(desktopEntry, params, false));
     }
 }
 
@@ -205,4 +215,25 @@
     return d->icon();
 }
 
+/// Creates an action that will launch the given application (specified by
+/// .desktop file name) with the \a params the way the application specifies in
+/// their .desktop file. \a app is the name of the .desktop file (with the
+/// .desktop extension). It is looked for in the standard locations.
+Action Action::launcherAction(const QString& app, const QStringList& params)
+{
+    QString appDesktop = findDesktopFile(app);
+    if (!appDesktop.isEmpty()) {
+        return createAction(appDesktop, params);
+    }
+    return Action();
+}
+
+/// Creates an action that will launch the given application (specified by
+/// MDesktopEntry) with the \a params the way the application specifies in
+/// their .desktop file.
+Action Action::launcherAction(QSharedPointer<MDesktopEntry> mDesktop, const QStringList& params)
+{
+    return createAction(mDesktop, params);
+}
+
 } // end namespace
--- src/contentaction.h
+++ src/contentaction.h
@@ -37,6 +37,7 @@
 #endif
 
 class MLabel;
+class MDesktopEntry;
 
 namespace ContentAction
 {
@@ -57,12 +58,18 @@
     static Action defaultActionForFile(const QUrl& fileUri);
     static Action defaultActionForFile(const QUrl& fileUri, const QString& mimeType);
     static Action defaultActionForScheme(const QString& uri);
+    static Action defaultActionForString(const QString& param);
 
     static QList<Action> actions(const QString& uri);
     static QList<Action> actions(const QStringList& uris);
     static QList<Action> actionsForFile(const QUrl& fileUri);
     static QList<Action> actionsForFile(const QUrl& fileUri, const QString& mimeType);
     static QList<Action> actionsForScheme(const QString& uri);
+    static QList<Action> actionsForString(const QString& param);
+
+    static Action launcherAction(const QString& app, const QStringList& params);
+    static Action launcherAction(QSharedPointer<MDesktopEntry>,
+                                 const QStringList& params);
 
     static QList<Match> highlight(const QString& text);
 
@@ -78,7 +85,10 @@
 
     QSharedPointer<ActionPrivate> d;
 
-    friend Action createAction(const QString& desktopFileId, const QStringList& params);
+    friend Action createAction(const QString& desktopFilePath,
+                               const QStringList& params);
+    friend Action createAction(QSharedPointer<MDesktopEntry> desktopEntry,
+                               const QStringList& params);
 };
 
 struct LCA_EXPORT Match {
--- src/dbus.cpp
+++ src/dbus.cpp
@@ -24,7 +24,8 @@
 #include <MDesktopEntry>
 
 #include <QVariantList>
-#include <QDBusInterface>
+#include <QDBusConnection>
+#include <QDBusMessage>
 #include <QDBusPendingCall>
 
 using namespace ContentAction::Internal;
@@ -33,7 +34,8 @@
 
 const QString XMaemoFixedArgsKey("Desktop Entry/X-Maemo-Fixed-Args");
 
-DBusPrivate::DBusPrivate(MDesktopEntry* desktopEntry, const QStringList& _params)
+DBusPrivate::DBusPrivate(QSharedPointer<MDesktopEntry> desktopEntry,
+                         const QStringList& _params)
     : DefaultPrivate(desktopEntry, _params), varArgs(false)
 {
     // mime_open  X-Osso-Service
@@ -81,18 +83,26 @@
 
 void DBusPrivate::trigger() const
 {
+    // Call a D-Bus function asynchronously.  Don't use a QDBusInterface because
+    // it creates a blocking Introspect call, see
+    // http://bugreports.qt.nokia.com/browse/QTBUG-14485
+
     if (varArgs) {
         // Call a D-Bus function with a variable length argument list
         QVariantList vargs;
-        foreach (const QString& param, params)
+        Q_FOREACH (const QString& param, params)
             vargs << param;
-        QDBusInterface launcher(busName, objectPath, iface);
-        launcher.callWithArgumentList(QDBus::NoBlock, method, vargs);
+        QDBusMessage message = QDBusMessage::createMethodCall(busName, objectPath, iface, method);
+        message.setArguments(vargs);
+        QDBusConnection::sessionBus().asyncCall(message);
     }
     else {
         // Call a D-Bus function with a string list
-        QDBusInterface launcher(busName, objectPath, iface);
-        launcher.asyncCall(method, params);
+
+        QDBusMessage message = QDBusMessage::createMethodCall(busName, objectPath, iface, method);
+        message.setArguments(QVariantList() << params);
+        QDBusConnection::sessionBus().asyncCall(message);
+
         // FIXME: What if we're launching a non-meegotouch desktop file, and we don't
         // have any func taking a string list; only a func taking nothing?
     }
--- src/doc.h
+++ src/doc.h
@@ -2,142 +2,195 @@
 
 \mainpage libcontentaction
 
-\brief a library for associating URIs to the set of applicable
-ContentAction::Action objects and triggering the actions.
+\brief This library associates files, Tracker URIs, and text snippets to a
+set of applicable ContentAction::Action objects and triggers the actions.
 
-\section Overview
+\section overview Overview
 
-The libcontentaction library retrieves associated actions
-(ContentAction::Action objects) for files, objects stored in Tracker and
-regular expressions in free text.  It may be used to query the default
-action as well.  The Action object can be used to trigger() the action.
+The libcontentaction library retrieves associated actions (ContentAction::Action
+objects) for files, objects stored in Tracker, and regular expressions (regexp)
+in free text.  It may also be used to query the default action.  The Action
+object can be used to trigger() the action.
 
-Actions correspond to .desktop files and they can be installed independently
+Actions correspond to .desktop files, and they can be installed independently
 from libcontentaction.
 
-In case of file URIs, the library finds out the mime type and uses that as a
-key of the association.  For objects stored in Tracker, the ontology classes
-are used for this purpose.  Finally, the library provides
-ContentAction::highlightLabel() for adding highlighters to a MLabel
-based on the available actions handling regexps.
-
-\section providing_actions Providing actions
+For file URIs, the library finds out the MIME type and uses it as a key of the
+association.  For objects stored in Tracker, the library adds the custom MIME
+types that target them.  Finally, the library provides
+ContentAction::highlightLabel(), which adds highlighters to an MLabel based on
+the actions associated with regular expressions (such as phone numbers and
+e-mail addresses).
 
 Actions can target one of the following:
 
--# mime types (\c image/jpeg)
+-# MIME types (\c image/jpeg)
 -# Tracker-query based conditions (\c x-maemo-nepomuk/contact)
 -# regular expressions (\c x-maemo-highlight/phonenumber)
 -# URI schemes (\c x-maemo-urischeme/mailto)
 
-The library supports launching actions with one of the following methods:
+\section detaileddescription Detailed description
 
--# launch a MeeGoTouch based application using the MApplication D-Bus interface
--# call a D-Bus method specified in the .desktop file
--# call a Maemo Service Framework method
--# execute a binary
--# call the legacy mime_open method
+libcontentaction = \ref xdgmimehandling "XDG MIME type handling"
+		 + \ref customlaunch
+		 + \ref custommime "Custom MIME types"
+		 + \ref highlighter
 
-All D-Bus based invocation methods (except mime_open) require the receiver
-to accept an array of strings.
+The following sections describe how libcontentaction extends the XDG MIME
+type handling system.
 
-To define a new action, drop a .desktop file in /usr/share/applications (or
-in one of the directories in $XDG_DATA_DIRS).
+\section xdgmimehandling XDG MIME type handling
 
-When you install a debian package which puts a file in /usr/share/applications, debian triggers take
-care of calling update-desktop-database which gathers information about handled mime types to
-/usr/share/mimeinfo.cache.  When experimenting, you might want to call update-desktop-database
-manually.
+XDG MIME type handling associates files with applications which can open them.
+Each file has an associated MIME type, and applications define which MIME types
+they can handle.
 
-\subsection writing_desktopfile Writing .desktop files for actions
+Applications define which MIME types they handle in the .desktop file
+installed to /usr/share/applications.  (See also \c $XDG_DATA_DIRS and \c
+$XDG_DATA_HOME in the XDG Base Directory specification.)
 
-Let's take an image viewer for example.
+Example of a .desktop file called /usr/share/applications/myimageviewer.desktop:
 
 \verbatim
 [Desktop Entry]
 Type=Application
-Icon=gallery.png
-;; This is needed to prevent this action appearing in the launcher.
-NotShowIn=X-MeeGo;
+Name=MyImageViewer
+Name[en]=My Image Viewer
+Name[fi]=Kuvankatselu
+Exec=/usr/bin/myimgview --view %U
+Icon=myimgview.png
+MimeType=image/jpeg;image/png;
+\endverbatim
 
-;; Defining a localization method:
-;; 1. MeeGoTouch-based
-X-MeeGo-translation-catalog=gallery_catalog
-X-MeeGo-logical-id=view_logical_id
-;; 2. XDG style
-Name=View in gallery
-Name[fi]=Näytä galleriassa
+The \c MimeType line contains the list of MIME types that the application
+handles.  Wildcards (such as \c image/*) are allowed.
 
-;; Defining when this action applies:
-;; 1. ordinary mimetypes
-MimeType=image/*;text/plain;
-;; 2. Tracker-query based conditions
-MimeType=x-maemo-nepomuk/contact;
-;; 3. pre-defined regexps for the highlighter
-MimeType=x-maemo-highlight/phonenumber;
-;; 4. URI schemes
-MimeType=x-maemo-urischeme/mailto;
+The \c Exec line defines how to execute the application.  When the action is
+launched, \%U is replaced with a list of image URIs to open (such as
+file:///home/me/myimage.jpg).
+
+The update-desktop-database tool gathers information on which application
+handles which MIME type into /usr/share/applications/mimeinfo.cache.  When a
+.desktop file is installed by a debian package, it is automatically called by a
+dpkg trigger.
 
-;; Defining how to trigger the action:
-;; 1. invoke a MApplication based program, by calling
-;; the com.nokia.MApplicationIf.launch method
-X-Maemo-Service=org.maemo.gallery_service
-;; 2. general D-Bus invocation
-X-Maemo-Service=org.maemo.gallery_service
-X-Maemo-Method=org.maemo.galleryinterface.viewimage
-;; It is possible to specify an optional object path, defaults to '/'.
-X-Maemo-Object-Path=/the/object/path
-;; Also an action may have optional, fixed string arguments, which get
-;; prepended to normal arguments.
-X-Maemo-Fixed-Args=foo;bar;baz;
-;; 3. Maemo Service Framework based action
-;; NOTE in this case you must not define X-Maemo-Service!
-X-Maemo-Method=com.nokia.imageviewerinterface.showImage
-;; 4. Plain old exec
-Exec=/usr/bin/gallery %U
-;; 5. the legacy mime_open method, provided only to wrap old programs into
-;; actions, don't use it in new applications
-X-Osso-Service=org.maemo.gallery_service
+Example of mimeinfo.cache:
+\verbatim
+[MIME Cache]
+image/jpeg=myimageviewer.desktop;someotherviewer.desktop;
+image/png=myimageviewer.desktop
 \endverbatim
 
-Additionally:
+Default applications for each MIME type are declared in
+/usr/share/applications/defaults.list.  Each user might also have their own
+defaults.list, overriding the global one.
 
-- All D-Bus methods must accept a list of strings and no other parameters
-  (in D-Bus terms, signature="as").  When your function is called, the
-  strings will be the URIs (or the free-text snippet) used to construct the
-  Action.  (You may have a return value; we'll ignore it.)
-
-- If you use a the Maemo Service Framework based invocation, then you must
-  declare your application to be an implementor of that interface.  For
-  this, it's enough to add a "Interface: " line to your D-Bus .service file.
-  You might also want to publish your interface in the maemo-services
-  package, but it's not needed for libcontentaction.
+Example of defaults.list:
+\verbatim
+[Default Applications]
+image/jpeg=someotherviewer.desktop
+image/png=myimageviewer.desktop
+\endverbatim
 
-\section tracker_conditions Defining new Tracker-based conditions
+The defaults.list is installed by the package libcontentaction-data.
 
-\attention
-The following section may be subject to changes!
+The libcontentaction library is compatible with XDG MIME type handling.  If you
+install the example file myimageviewer.desktop into /usr/share/applications,
+MyImageViewer appears as an action for jpeg and png images.  For most cases,
+this is all you need.  However, libcontentaction also provides you with custom
+launch methods. For more information, see \ref customlaunch.
 
-\attention
-The configuration file directory has changed from /etc/contentaction to /usr/share/contentaction.
+More information:
 
-Conditions are defined with XML files in \c /usr/share/contentaction/ (this
-location is overridden by $CONTENTACTION_ACTIONS).  A condition is described
-by a \a <tracker-condition> element, whose \c name attribute is appended to
-\c "x-maemo-nepomuk/" to get the corresponding mime type.  The element's
-text contains the SparQL snippet used to evaluate the condition by
-substituting it into the following query:
+<a href="http://en.wikipedia.org/wiki/Internet_media_type">MIME types</a>
 
-\code
-SELECT 1 {
-   SNIPPET
-   FILTER(?uri = <the-uri-to-verify-against>)
-}
-\endcode
+<a href="http://standards.freedesktop.org/desktop-entry-spec/latest/">Desktop entry specification</a>
 
-The condition applies if the query returns non-zero rows.
+<a href="http://freedesktop.org/wiki/Software/desktop-file-utils">Desktop file utils (including update-desktop-database)</a>
+
+<a href="http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html"> XDG Base Directory specification</a>
+
+\section customlaunch Custom launch methods
+
+In addition to the Exec line, .desktop files can specify custom ways to launch
+the application.  The launch methods supported by libcontentaction are:
+
+-# launching a MeeGoTouch based application using the MApplication D-Bus interface
+  - Define: \c X-Maemo-Service=my.bus.name
+  - Optionally: \c X-Maemo-Fixed-Args=my;fixed;args
+-# calling a D-Bus method specified in the .desktop file
+  - Define: \c X-Maemo-Service=my.bus.name
+  - Define: \c X-Maemo-Method=com.my.interface.Method
+  - Optionally: \c X-Maemo-Object-Path=/the/object/path (default: /)
+  - Optionally: \c X-Maemo-Fixed-Args=my;fixed;args
+-# calling a MeeGo Service Framework method
+  - Define: \c X-Maemo-Method=com.my.interface.Method
+  - Optionally: \c X-Maemo-Fixed-Args=my;fixed;args
+  - Note: Do not define \c X-Maemo-Service
+-# executing a binary
+  - Define: \c Exec=command-to-execute fixedarg1 \%U fixedarg2
+  - No need for \c X-Maemo-Fixed-Args
+-# calling the legacy mime_open method
+  - Define: \c X-Osso-Service=my.bus.name
 
-Example:
+All D-Bus based invocation methods (except for mime_open) require the receiver
+to accept an array of strings (in D-Bus terms, signature="as").  When your
+function is called, the strings are the URIs (or the free-text snippet)
+used to construct the Action, as well as the fixed parameters you may have
+specified.  If there is a return value, it is ignored.
+
+If you use a MeeGo Service Framework based invocation,
+declare your application to be an implementor of the interface. You just need to
+add an "Interface: " line to your D-Bus .service file.  You can also publish
+your interface in the meego-services package, but it is not needed for
+libcontentaction.
+
+If your application is running, D-Bus based launching delivers the
+function call to the running instance.  If your application is not running,
+D-Bus can autolaunch it as defined in your .service file.  This makes
+it possible to have only one instance of your application running without any
+extra code.
+
+For more information, see \ref exampledesktop.
+
+More information:
+
+<a href="http://www.freedesktop.org/wiki/Software/dbus">D-Bus</a>
+
+<a href="http://dbus.freedesktop.org/doc/dbus-specification.html">Message Bus Starting Services</a>
+
+<a href="http://apidocs.meego.com/mtf/index.html">MeeGoTouch</a>
+
+\section custommime Custom MIME types for Tracker URIs
+
+\attention
+The following section may be subject to changes!
+
+The XDG MIME type handling associates files with applications that can open
+them. However, some objects are not represented as files.  For example, contacts
+are represented as objects in a semantic data store, Tracker.  It is often
+necessary to define actions to a subset of certain objects.  For example, the
+"call" action is only applicable to contacts which have an associated phone
+number.
+
+To this end, libcontentaction can find actions for Tracker URIs which are ids
+for objects in the Tracker data store.
+
+If a URI represents a normal file (for instance, an image), the actions found
+through XDG MIME type handling are added to the list of applicable actions. This
+makes libcontentaction compatible with XDG MIME type handling.  The applications
+that declare actions for normal files do not need to know about Tracker URIs.
+
+The libcontentaction library defines the "tracker conditions" which determine
+whether an URI is of interest.  The conditions are mapped into custom MIME types
+(\c "x-maemo-nepomuk/...").
+
+The conditions are defined in .xml files installed in /usr/share/contentaction
+(or a location specified by $CONTENTACTION_ACTIONS).  Each condition defines a
+SparQL snippet for querying whether a given URI satisfies the condition.
+
+For instance, the following .xml adds new MIME types \c x-maemo-nepomuk/image, \c
+x-maemo-nepomuk/contact and \c x-maemo-nepomuk/contact-with-phone-number.
 
 \code
 <actions>
@@ -156,38 +209,169 @@
 </actions>
 \endcode
 
-\section highlighter Free-text highlighter
+A condition is evaluated by executing the following SparQL query:
 
-Passing a string to highlight() can be used to discover interesting parts of
-the text.  It returns Match objects identifying the position of the match
-and the possible actions.
+\code
+SELECT 1 {
+   SNIPPET
+   FILTER(?uri = <the-uri-to-verify-against>)
+}
+\endcode
 
-\note
-These actions have different semantics than ordinary Actions.  When
-triggered, they call the method with a single element list containing the
-matched text (as UTF-8).  These are very likely _not_ valid URIs!
+The condition applies if the query returns non-zero rows.
+
+An application can now define in its .desktop file that it handles a custom
+MIME type.  When launched, the application receives a parameter which is the
+Tracker URI of the object to be opened.  The application needs to query all
+the needed information (for example, the name and phone number for a contact)
+from Tracker.
+
+If your application wants to receive Tracker URIs instead of file URIs as
+parameters, a Tracker condition can overlap normal MIME types.  For example,
+applications handling the \c x-maemo-nepomuk/image MIME type get Tracker
+URIs and applications handling \c image/jpeg get file URIs as parameters.
+Both applications appear as applicable actions for images.
+
+More information:
+
+<a href="http://live.gnome.org/Tracker/Documentation">Tracker</a>
+
+<a href="http://www.semanticdesktop.org/ontologies/">Nepomuk ontologies</a>
+
+<a href="http://www.w3.org/TR/rdf-sparql-query/">SparQL, the query language</a>
+
+\section custommimescheme Custom MIME types for URI schemes
+
+The libcontentaction library is also able to dispatch URIs based on the scheme
+(ContentAction::Action::actionsForScheme).  To this end, applications can
+define that they handle a custom MIME type, for example,
+\c x-maemo-urischeme/http.  When actionsForScheme("http://www.example.com") is
+called, applications declaring \c x-maemo-urischeme/http appear in the list of
+applicable actions.  When launched, an action gets the string
+"http://www.example.com" as a parameter.
 
-\subsection defining_regexps Defining new regexps for the highlighter
+\section highlighter Free-text highlighter
 
 \attention
 The following section may be subject to changes!
 
-Similarly to Tracker-query based conditions, regexps are also defined in XML
-files residing in \c /etc/contentaction:
+Passing an MLabel* to ContentAction::Action::highlightLabel() adds a
+MLabelHighlighter which highlights interesting elements inside the label.  When
+the user clicks a highlighted element, the default action for it is launched.
+When the user long-clicks a highlighted element, a pop-up menu containing the
+applicable actions is shown.  When the user clicks an item in the menu, the
+corresponding action is launched.
+
+These actions have different semantics than ordinary Actions.  When
+triggered, they call the method with a single element list containing the
+matched text (as UTF-8).  Note that these are very likely invalid URIs.
+
+Similarly to Tracker conditions, regexps are also defined in .xml files
+located in \c /usr/share/contentaction (unless overridden with
+$CONTENTACTION_ACTIONS).
+
+For example, the following .xml adds new MIME types \c
+x-maemo-highlight/email-address and \c x-maemo-nepomuk/phone-number.
 
 \code
 <actions>
-  <highlight regexp="[^\s@]+@([^\s.]+)(\.[^\s.]+)*" name="email"/>
-  <highlight regexp="\+?\d+([- ]\d+)*" name="phone"/>
+  <highlight regexp="[^\s@]+@([^\s.]+)(\.[^\s.]+)*" name="email-address"/>
+  <highlight regexp="\+?\d+([- ]\d+)*" name="phone-number"/>
 </actions>
 \endcode
 
-To form the mimetype used in the action .desktop files, \c
-x-maemo-highlight/ is prepended to the \a name attribute.
+Applications can now define in their .desktop files that they handle these
+custom MIME types.  When launched, they get a string which matches the regular
+expression as a parameter. For example, an application handling
+\c x-maemo-highlight/phone-number might get "+ 123 456-789" as a parameter.
+
+More information:
+
+<a href="http://apidocs.meego.com/mtf/class_m_label.html">MLabel documentation</a>
 
-\section default_actions Default actions
+\section exampledesktop An example .desktop file
 
-If you want your application to be the default application for some mime
-types, contact the libcontentaction implementors.
+The following example illustrates the interesting .desktop file fields from the
+libcontentaction point of view:
+
+\verbatim
+[Desktop Entry]
+Type=Application
+Icon=gallery.png
+;; This is needed to prevent this action from appearing in the launcher.
+NotShowIn=X-MeeGo;
+
+;; Defining a localisation method:
+;; 1. MeeGoTouch-based
+X-MeeGo-Translation-Catalog=gallery_catalog
+X-MeeGo-Logical-Id=view_logical_id
+;; 2. XDG style
+Name=View in gallery
+Name[fi]=Avaa galleriassa
+
+;; Defining when this action applies:
+;; 1. ordinary mimetypes
+MimeType=image/*;text/plain;
+;; 2. Tracker-query based conditions
+MimeType=x-maemo-nepomuk/contact;
+;; 3. pre-defined regexps for the highlighter
+MimeType=x-maemo-highlight/phonenumber;
+;; 4. URI schemes
+MimeType=x-maemo-urischeme/mailto;
+
+;; Defining how to trigger the action:
+;; 1. invoke a MApplication based program by calling
+;; the com.nokia.MApplicationIf.launch method
+X-Maemo-Service=org.maemo.gallery_service
+;; 2. general D-Bus invocation; define a bus name and a function
+X-Maemo-Service=org.maemo.gallery_service
+X-Maemo-Method=org.maemo.galleryinterface.viewimage
+;; It is possible to specify an optional object path, defaults to '/'.
+X-Maemo-Object-Path=/the/object/path
+;; Also an action may have optional, fixed string arguments, which get
+;; prepended to normal arguments. (This applies to all launch methods.)
+X-Maemo-Fixed-Args=foo;bar;baz;
+;; 3. MeeGo Service Framework based action
+;; NOTE in this case you must not define X-Maemo-Service!
+X-Maemo-Method=com.nokia.imageviewerinterface.showImage
+;; 4. Plain old exec
+Exec=/usr/bin/gallery %U
+;; 5. the legacy mime_open method, provided only to wrap old programs into
+;; actions, do not use it in new applications
+X-Osso-Service=org.maemo.gallery_service
+\endverbatim
+
+More information:
+
+<a href="http://apidocs.meego.com/mtf/i18n.html#translationsystem">MeeGoTouch translation system</a>
+
+\section lcatool lca-tool
+
+The lca-tool is a command-line utility which can be used as a development and
+testing tool by action implementors.
+
+Use the lca-tool to:
+- list the applicable actions for a file, Tracker URI or a string with a scheme.
+- trigger an applicable action for a file, Tracker URI or a string with a scheme.
+- launch an application (given its .desktop file) with parameters.
+- search for interesting items to highlight in free text.
+
+The lca-tool is installed by the libcontentaction0 package.  To display the help
+message, run lca-tool without parameters.
+
+Example workflow for testing that an image viewer has successfully
+declared the \c image/jpeg MIME type:
+
+\verbatim
+$ cp myimageviewer.desktop /usr/share/applications
+$ update-desktop-database /usr/share/applications
+$ lca-tool --file --printmimes file:///home/me/myimage.jpg
+image/jpeg
+$ lca-tool --file --print file:///home/me/myimage.jpg
+someotherviewer
+myimageviewer
+$ lca-tool --file --trigger myimageviewer file:///home/me/myimage.jpg
+(MyImageViewer should launch)
+\endverbatim
 
 */
--- src/exec.cpp
+++ src/exec.cpp
@@ -31,11 +31,17 @@
 
 namespace ContentAction {
 
-ExecPrivate::ExecPrivate(MDesktopEntry* desktopEntry, const QStringList& params)
+ExecPrivate::ExecPrivate(QSharedPointer<MDesktopEntry> desktopEntry,
+                         const QStringList& params)
     : DefaultPrivate(desktopEntry, params)
 {
     g_type_init();
-    appInfo = G_APP_INFO(g_desktop_app_info_new_from_filename(desktopEntry->fileName().toLocal8Bit().constData()));
+    appInfo = G_APP_INFO(
+        g_desktop_app_info_new_from_filename(
+            desktopEntry->fileName().toLocal8Bit().constData()));
+    if (appInfo == 0) {
+        LCA_WARNING << "invalid desktop file" << desktopEntry->fileName();
+    }
 }
 
 ExecPrivate::~ExecPrivate()
@@ -48,7 +54,7 @@
     GError *error = 0;
     GList *uris = NULL;
 
-    foreach (const QString& param, params)
+    Q_FOREACH (const QString& param, params)
         uris = g_list_append(uris, g_strdup(param.toAscii().constData()));
 
     g_app_info_launch_uris(appInfo, uris, NULL, &error);
--- src/highlight.cpp
+++ src/highlight.cpp
@@ -45,7 +45,7 @@
 public:
     LCALabelHighlighter(const MimesAndRegexps &mars_,
                         QObject *parent = 0);
-private slots:
+private Q_SLOTS:
     void doDefaultAction(const QString& match);
     void doPopupActions(const QString& match);
     void doAction(const QModelIndex& ix);
@@ -58,14 +58,14 @@
 {
     QString re("(?:");
     bool first = true;
-    foreach (const MimeAndRegexp &mr, mars) {
+    Q_FOREACH (const MimeAndRegexp &mr, mars) {
         if (!first)
             re += '|';
         re += mr.second.pattern();
         first = false;
     }
     re += ")";
-    return QRegExp(re);
+    return QRegExp(re, Qt::CaseInsensitive);
 }
 
 LCALabelHighlighter::LCALabelHighlighter(const MimesAndRegexps &mars_,
@@ -84,7 +84,7 @@
 QStringList LCALabelHighlighter::matchingMimes(const QString &str) const
 {
     QStringList ret;
-    foreach (const MimeAndRegexp &mr, mars)
+    Q_FOREACH (const MimeAndRegexp &mr, mars)
         if (mr.second.exactMatch(str))
             ret.append(mr.first);
     return ret;
@@ -94,18 +94,21 @@
 {
     QStringList mimes = matchingMimes(match);
     QString app;
-    foreach (const QString &mime, mimes) {
-        app = defaultAppForContentType(mime);
+    Q_FOREACH (const QString &mime, mimes) {
+        app = findDesktopFile(defaultAppForContentType(mime));
         if (!app.isEmpty()) {
-            createAction(findDesktopFile(app), QStringList() << match).trigger();
+            createAction(app, QStringList() << match).trigger();
             return;
         }
     }
-    foreach (const QString &mime, mimes) {
+    Q_FOREACH (const QString &mime, mimes) {
         QStringList apps = appsForContentType(mime);
-        if (!apps.isEmpty()) {
-            createAction(findDesktopFile(apps[0]), QStringList() << match).trigger();
-            return;
+        Q_FOREACH (const QString& appid, apps) {
+            app = findDesktopFile(appid);
+            if (!app.isEmpty()) {
+                createAction(app, QStringList() << match).trigger();
+                return;
+            }
         }
     }
 }
@@ -152,9 +155,13 @@
     qRegisterMetaType<Action>();
     QList<Action> alist;
     QStringList mimes = matchingMimes(match);
-    foreach (const QString &mime, mimes) {
-        foreach (const QString &app, appsForContentType(mime))
-            alist << createAction(findDesktopFile(app), QStringList() << match);
+    QString app;
+    Q_FOREACH (const QString &mime, mimes) {
+        Q_FOREACH (const QString &appid, appsForContentType(mime)) {
+            app = findDesktopFile(appid);
+            if (!app.isEmpty())
+                alist << createAction(app, QStringList() << match);
+        }
     }
 
     MPopupList *popuplist = new MPopupList();
@@ -200,25 +207,29 @@
 void ContentAction::highlightLabel(MLabel *label)
 {
     MimesAndRegexps mars;
-    QHashIterator<QString, QString> iter(highlighterConfig());
+    QListIterator<QPair<QString, QString> > iter(highlighterConfig());
     while (iter.hasNext()) {
-        iter.next();
-        // iter.key == mime type, iter.value == regexp
-        if (!appsForContentType(iter.key()).isEmpty())
-            mars += qMakePair(iter.key(), QRegExp(iter.value()));
+        QPair<QString, QString> mar = iter.next();
+        if (!appsForContentType(mar.first).isEmpty())
+            mars += qMakePair(mar.first, QRegExp(mar.second));
     }
     hiLabel(label, mars);
 }
 
 /// Similar to highlightLabel() but allows specifying which regexp-types to
-/// highlight (e.g. only \c "x-maemo-highlight/mailto").
+/// highlight (e.g. only \c "x-maemo-highlight/mailto"). The order of the \a
+/// typesOfHighlight is honoured; the regexps appearing first get the priority
+/// when deciding the default action and the order of the actions.
 void ContentAction::highlightLabel(MLabel *label,
                                    QStringList typesToHighlight)
 {
     MimesAndRegexps mars;
-    const QHash<QString, QString>& cfg = highlighterConfig();
-    foreach (const QString& k, typesToHighlight) {
-        QString re(cfg.value(k, QString()));
+    const QList<QPair<QString, QString> >& cfgList = highlighterConfig();
+    QMap<QString, QString> cfgMap;
+    for (int i = 0; i < cfgList.size(); ++i)
+        cfgMap[cfgList[i].first] = cfgList[i].second;
+    Q_FOREACH (const QString& k, typesToHighlight) {
+        QString re(cfgMap.value(k, QString()));
         if (re.isEmpty())
             continue;
         if (!appsForContentType(k).isEmpty())
--- src/highlighter.cpp
+++ src/highlighter.cpp
@@ -34,12 +34,12 @@
 /// list of Match objects.
 QList<Match> Action::highlight(const QString& text)
 {
-    const QHash<QString, QString>& cfg = highlighterConfig();
+    const QList<QPair<QString, QString> >& cfg = highlighterConfig();
     QList<Match> result;
 
-    foreach (const QString& mime, cfg.keys()) {
-        QRegExp re(cfg[mime]);
-        QStringList apps = appsForContentType(mime);
+    for (int i = 0; i < cfg.size(); ++i) {
+        QRegExp re(cfg[i].second, Qt::CaseInsensitive);
+        QStringList apps = appsForContentType(cfg[i].first);
         int pos = 0;
         while ((pos = re.indexIn(text, pos)) != -1) {
             int l = re.matchedLength();
@@ -47,11 +47,13 @@
             m.start = pos;
             m.end = pos + l;
 
-            foreach (const QString& app, apps) {
+            Q_FOREACH (const QString& app, apps) {
                 m.actions << createAction(findDesktopFile(app), QStringList() << re.cap());
             }
             result << m;
             pos += l;
+            if (l == 0)
+                ++pos;
         }
     }
     return result;
--- src/internal.h
+++ src/internal.h
@@ -30,25 +30,30 @@
 
 struct DefaultPrivate : public ActionPrivate
 {
-    DefaultPrivate(MDesktopEntry* desktopEntry, const QStringList& params);
+    DefaultPrivate(QSharedPointer<MDesktopEntry> desktopEntry,
+                   const QStringList& params,
+        bool valid = true);
     virtual ~DefaultPrivate();
     virtual bool isValid() const;
     virtual QString name() const;
     virtual QString localizedName() const;
     virtual QString icon() const;
-    MDesktopEntry* desktopEntry;
+    QSharedPointer<MDesktopEntry> desktopEntry;
     QStringList params;
+    bool valid;
 };
 
 struct ServiceFwPrivate : public DefaultPrivate {
-    ServiceFwPrivate(MDesktopEntry* desktopEntry, const QStringList& params);
+    ServiceFwPrivate(QSharedPointer<MDesktopEntry> desktopEntry,
+                     const QStringList& params);
     virtual void trigger() const;
 
     QString serviceFwMethod;
 };
 
 struct DBusPrivate : public DefaultPrivate {
-    DBusPrivate(MDesktopEntry* desktopEntry, const QStringList& params);
+    DBusPrivate(QSharedPointer<MDesktopEntry> desktopEntry,
+                const QStringList& params);
     virtual void trigger() const;
 
     QString busName;
@@ -59,14 +64,18 @@
 };
 
 struct ExecPrivate : public DefaultPrivate {
-    ExecPrivate(MDesktopEntry* desktopEntry, const QStringList& params);
+    ExecPrivate(QSharedPointer<MDesktopEntry> desktopEntry,
+                const QStringList& params);
     virtual ~ExecPrivate();
     virtual void trigger() const;
 
     GAppInfo *appInfo;
 };
 
-Action createAction(const QString& desktopFileId, const QStringList& params);
+Action createAction(const QString& desktopFilePath,
+                    const QStringList& params);
+Action createAction(QSharedPointer<MDesktopEntry> desktopEntry,
+                    const QStringList& params);
 
 // our pseudo mimetype classes
 extern const QString OntologyMimeClass;
@@ -91,9 +100,10 @@
 LCA_EXPORT QString mimeForScheme(const QString& uri);
 LCA_EXPORT QString mimeForFile(const QUrl& fileUri);
 LCA_EXPORT QStringList mimeForTrackerObject(const QString& uri);
+LCA_EXPORT QStringList mimeForString(const QString& param);
 
 const QHash<QString, QStringList>& mimeApps();
-const QHash<QString, QString>& highlighterConfig();
+const QList<QPair<QString, QString> >& highlighterConfig();
 const QHash<QString, QString>& trackerConditions();
 
 } // end namespace Internal
--- src/lca-tool.cpp
+++ src/lca-tool.cpp
@@ -33,24 +33,25 @@
 using namespace ContentAction::Internal;
 
 static const char help[] = \
-"Usage: lca-tool [OPTIONS] MODE MODALCOMMAND URIS\n"
+"Usage: lca-tool [OPTIONS] MODE MODALCOMMAND PARAMS\n"
 "       lca-tool [OPTIONS] OTHERCOMMAND ARGS\n"
 "OPTION can be:\n"
 "  --l10n              use localized names when printing actions\n"
 "\n"
 "MODE is one of:\n"
-"  --tracker           URIS are representing objects stored in Tracker,\n"
+"  --tracker           PARAMS are representing objects stored in Tracker,\n"
 "                      dispatched using Tracker-query based conditions\n"
-"  --file              URI is a file (or other resource), dispatched based on\n"
+"  --file              PARAMS is a file (or other resource), dispatched based on\n"
 "                      its content type\n"
-"  --scheme            URIS are dispatched based on their scheme only\n"
+"  --scheme            PARAMS are dispatched based on their scheme only\n"
+"  --string            PARAMS are dispatched based on regexp matches\n"
 "\n"
 "In modal use, the following commands are available:\n"
-"  --print             prints actions applicable to URIS\n"
-"  --trigger ACTION    trigger ACTION with the given URIS\n"
-"  --printdefault      print the default action for URIS\n"
-"  --triggerdefault    trigger the default action for the given URIS\n"
-"  --printmimes        print the (pseudo) mimetypes of URIS\n"
+"  --print             prints actions applicable to PARAMS\n"
+"  --trigger ACTION    trigger ACTION with the given PARAMS\n"
+"  --printdefault      print the default action for PARAMS\n"
+"  --triggerdefault    trigger the default action for the given PARAMS\n"
+"  --printmimes        print the (pseudo) mimetypes of PARAMS\n"
 "\n"
 "ACTION is the basename of the action's .desktop file (both when printing and\n"
 "when invoking).\n"
@@ -62,6 +63,9 @@
 "                                  mimetype\n"
 "  --setmimedefault MIME ACTION    set ACTION as default for the given mimetype\n"
 "  --resetmimedefault MIME         remove the user-defined default from the given mimetype\n"
+"  --highlight                     will read text from stdin and find actions for items in it\n"
+"  --triggerdesktop DESKTOPFILE PARAMS   will launch the application defined by DESKTOPFILE with the given PARAMS\n"
+
 "\n"
 "Return values:\n"
 "  0   success\n"
@@ -69,18 +73,23 @@
 "  2   problems with the arguments\n"
 "  3   triggered an action not applicable to the given URIS\n"
 "  4   no default action exists for the given URIS\n"
+"  5   desktop file not found\n"
 "\n"
 "Examples:\n"
 "  $ lca-tool --tracker --triggerdefault urn:1246934-4213\n"
 "  $ lca-tool --file --print file://$HOME/plaintext\n"
 "  $ lca-tool --scheme --triggerdefault mailto:someone at example.com\n"
-"  $ lca-tool --setmimedefault image/jpeg imageviewer\n";
+"  $ lca-tool --string print \"myaddress at email.com\""
+"  $ lca-tool --setmimedefault image/jpeg imageviewer\n"
+"  $ lca-tool --highlight < myinput.txt\n"
+"  $ lca-tool --triggerdesktop myapp.desktop param1 param2\n";
 
 enum UriMode {
     NoMode = 0,
     TrackerMode,
     FileMode,
     SchemeMode,
+    StringMode,
 };
 
 enum ActionToDo {
@@ -98,6 +107,8 @@
     PrintMimeDefault,
     SetMimeDefault,
     ResetMimeDefault,
+    Highlight,
+    TriggerDesktop
 };
 
 #define NEEDARG(errmsg)                         \
@@ -108,6 +119,72 @@
         }                                       \
     } while (0)
 
+// The highlighter part:
+
+QDebug operator<<(QDebug dbg, const Match& m) {
+    dbg.nospace() << "match at ("
+                  << m.start << ", " << m.end << "): ";
+    Q_FOREACH (const Action& a, m.actions) {
+        dbg.space() << a.name();
+    }
+    dbg.nospace() << "\n";
+    return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QList<Match>& ms) {
+    Q_FOREACH (const Match& m, ms) {
+        dbg.space() << m;
+    }
+    return dbg;
+}
+
+/*
+ * Reads text from the standard input, highlight rules from the usual place
+ * (ie. xml files in $CONTENTACTION_ACTIONS) and then prints match results on
+ * the standard output.  If the terminal is a tty, the results are printed on
+ * stderr, and on stdout a beautifully colored version of the text is shown.
+ */
+void doHighlight()
+{
+    QTextStream out(isatty(1) ? stderr : stdout);
+    QTextStream textout(isatty(1) > 0 ? stdout : fopen("/dev/null", "w"));
+    QTextStream in(stdin);
+    QString text = in.readAll();
+
+    QList<Match> ms = Action::highlight(text);
+    Q_FOREACH (const Match& m, ms) {
+        QStringList actions;
+        Q_FOREACH (const Action& a, m.actions)
+            actions << a.name();
+        out << QString("%1 %2 '%3' %4\n").arg(QString::number(m.start),
+                                              QString::number(m.end),
+                                              text.mid(m.start, m.end - m.start),
+                                              actions.join(" "));
+    }
+    QString hltext(text);
+    if (isatty(1)) {
+        qSort(ms.begin(), ms.end());
+        QString color[] = {
+            "\e[1;37;41m",
+            "\e[1;37;42m",
+            "\e[1;37;43m",
+            "\e[1;37;44m",
+            "\e[1;37;45m",
+            "\e[1;37;46m",
+        };
+        int i = 0;
+        int d = 0;
+        Q_FOREACH (const Match& m, ms) {
+            hltext.insert(d + m.start, color[i]);
+            d += color[i].length();
+            i = (i + 1) % (sizeof(color) / sizeof(color[0]));
+            hltext.insert(d + m.end, "\e[0m");
+            d += 4;
+        }
+        textout << hltext;
+    }
+}
+
 int main(int argc, char **argv)
 {
     QCoreApplication app(argc, argv);
@@ -120,7 +197,7 @@
 
     char *l10npaths = getenv("CONTENTACTION_L10N_PATH");
     if (l10npaths) {
-        foreach (const QString& p, QString::fromLocal8Bit(l10npaths).split(':')) {
+        Q_FOREACH (const QString& p, QString::fromLocal8Bit(l10npaths).split(':')) {
             qDebug() << "adding path:" << p;
             MLocale::addTranslationPath(p);
         }
@@ -155,6 +232,8 @@
             newmode = FileMode;
         else if (arg == "--scheme")
             newmode = SchemeMode;
+        else if (arg == "--string")
+            newmode = StringMode;
         if (newmode != NoMode) {
             if (mode != NoMode) {
                 err << "only a single MODE may be specified" << endl;
@@ -192,6 +271,14 @@
             NEEDARG("a MIME must be given when using --setmimedefault");
             mime = args.takeFirst();
         }
+        else if (arg == "--highlight") {
+            todo = Highlight;
+        }
+        else if (arg == "--triggerdesktop") {
+            todo = TriggerDesktop;
+            NEEDARG("a DESKTOPFILE must be given when using --triggerdesktop");
+            actionName = args.takeFirst();
+        }
         // modal actions
         else if (arg == "--print") {
             todo = PrintActions;
@@ -222,7 +309,7 @@
     // handle modeless actions first
     switch (todo) {
     case PrintActionsForMime:
-        foreach (const Action& a, actionsForMime(mime)) {
+        Q_FOREACH (const Action& a, actionsForMime(mime)) {
             out << a.name() << endl;
         }
         return 0;
@@ -239,6 +326,18 @@
         resetMimeDefault(mime);
         return 0;
         break;
+    case Highlight:
+        doHighlight();
+        return 0;
+        break;
+    case TriggerDesktop:
+    {
+        Action a = Action::launcherAction(actionName, args);
+        if (!a.isValid())
+            return 5;
+        a.trigger();
+        return 0;
+    }
     default:
         break;
     }
@@ -265,34 +364,51 @@
     case FileMode:
     {
         QFileInfo fileInfo(args[0]);
-        if (fileInfo.exists())
-            args[0] = fileInfo.absoluteFilePath().prepend("file://");
-        actions = Action::actionsForFile(QUrl(args[0]));
-        defAction = Action::defaultActionForFile(QUrl(args[0]));
+        QUrl fileUrl;
+        if (fileInfo.exists()) {
+            // the user gave: /home/me/somefile#canhavespecialchars.txt
+            fileUrl = QUrl::fromLocalFile(fileInfo.absoluteFilePath());
+        }
+        else {
+            // the user gave: file:///home/me/mustbe%23escapedproperly.txt
+            // (hopefully)
+            fileUrl = QUrl::fromEncoded(args[0].toLatin1());
+            QFileInfo fileInfo2(fileUrl.toLocalFile());
+            if (!fileInfo2.exists()) {
+                err << "possibly incorrect file uri: " << args[0] << endl;
+                err << "input a file uri (file:///abs/path/...) %-escaped" << endl;
+            }
+        }
+        actions = Action::actionsForFile(fileUrl);
+        defAction = Action::defaultActionForFile(fileUrl);
     }
     break;
     case SchemeMode:
         actions = Action::actionsForScheme(args[0]);
         defAction = Action::defaultActionForScheme(args[0]);
         break;
+    case StringMode:
+        actions = Action::actionsForString(args[0]);
+        defAction = Action::defaultActionForString(args[0]);
+        break;
     default:
         break;
     }
 
     switch (todo) {
     case PrintActions:
-        foreach (const Action& a, actions) {
+        Q_FOREACH (const Action& a, actions) {
             out << (use_l10n ? a.localizedName() : a.name()) << endl;
         }
         break;
     case TriggerAction:
-        foreach (const Action& a, actions) {
+        Q_FOREACH (const Action& a, actions) {
             if (a.name() == actionName) {
                 a.trigger();
                 return 0;
             }
         }
-        err << actionName << "is not applicable" << endl;
+        err << actionName << " is not applicable" << endl;
         return 3;
         break;
     case PrintDefaultAction:
@@ -300,7 +416,7 @@
         break;
     case TriggerDefaultAction:
         if (!defAction.isValid()) {
-            err << "no default action for the given URIs" << endl;
+            err << "no default action for the given PARAMS" << endl;
             return 4;
         }
         defAction.trigger();
@@ -309,7 +425,7 @@
     case PrintMimes: {
         switch (mode) {
         case TrackerMode:
-            foreach (const QString& mime, mimeForTrackerObject(args[0])) {
+            Q_FOREACH (const QString& mime, mimeForTrackerObject(args[0])) {
                 out << mime << endl;
             }
             break;
@@ -319,6 +435,11 @@
         case SchemeMode:
             out << mimeForScheme(args[0]) << endl;
             break;
+        case StringMode:
+            Q_FOREACH (const QString& mime, mimeForString(args[0])) {
+                out << mime << endl;
+            }
+            break;
         default:
             break;
         }
--- src/mime.cpp
+++ src/mime.cpp
@@ -32,6 +32,7 @@
 #include <QDir>
 #include <QFile>
 #include <QFileInfo>
+#include <QDateTime>
 #include <QTextStream>
 #include <QDebug>
 #include <QFile>
@@ -64,9 +65,13 @@
 
 /// Returns the content type of the given file, or an empty string if it cannot
 /// be retrieved.
-QString Internal::mimeForFile(const QUrl& fileUri)
+QString Internal::mimeForFile(const QUrl& uri)
 {
     g_type_init();
+    // assume "file" scheme if the uri had nothing
+    QUrl fileUri(uri);
+    if (fileUri.scheme().isEmpty())
+        fileUri.setScheme("file");
     QByteArray filename = fileUri.toEncoded();
     GFile *file = g_file_new_for_uri(filename.constData());
 
@@ -205,6 +210,8 @@
 // ("something.desktop"), and returns the first hit.
 QString Internal::findDesktopFile(const QString& id)
 {
+    if (id.isEmpty())
+        return QString();
     QStringList dirs = xdgDataDirs();
     for (int i = 0; i < dirs.size(); ++i) {
         QFile f(dirs[i] + "/applications/" + id);
@@ -241,14 +248,28 @@
 }
 
 // Reads the mimeinfo.cache files and returns the mapping from mime types to
-// desktop entries.
+// desktop entries. Tries to decide cleverly whether to re-read the files if
+// they might have changed.
 const QHash<QString, QStringList>& Internal::mimeApps()
 {
-    static bool read = false;
     static QHash<QString, QStringList> mimecache;
+    // When have we last consider reading various mimeinfo.cache files
+    static uint lastTime = 0;
+    // What were the "last modified" times of them when they were read
+    static QHash<QString, uint> lastModified;
+
+    // Read each mimeinfo.cache if 1) it has never been read or 2) 1 min has
+    // passed since our previous timestamp check && timestamp shows it has
+    // changed.
+
+    // This is a hack but so is trying to use QFileSystemWatcher from a library
+    // without a LifeTimeManager object. Deleting the QFileSystemWatcher at the
+    // right moment is tricky and cannot be enforced by the library.
 
-    if (read)
+    uint currentTime = QDateTime::currentDateTime().toTime_t();
+    if (currentTime - lastTime < 60)
         return mimecache;
+    lastTime = currentTime;
 
     QStringList dirs = xdgDataDirs();
     QHash<QString, QString> temp;
@@ -256,14 +277,20 @@
         QFile f(dirs[i] + "/applications/mimeinfo.cache");
         if (!f.exists())
             continue;
+        // Check the "last modified" time of the file
+        uint lm = QFileInfo(f.fileName()).lastModified().toTime_t();
+        if (lastModified.contains(dirs[i]) &&
+            lm == lastModified[dirs[i]])
+            continue;
+
         readKeyValues(f, temp);
+        lastModified[dirs[i]] = lm;
     }
     QHashIterator<QString, QString> it(temp);
     while (it.hasNext()) {
         it.next();
         mimecache.insert(it.key(), it.value().split(";", QString::SkipEmptyParts));
     }
-    read = true;
     return mimecache;
 }
 
@@ -314,10 +341,11 @@
     // actually) is to launch the application it describes.
     if (mimeType == DesktopFileMimeType)
         return createAction(fileUri.toLocalFile(), QStringList());
-    QString appid = defaultAppForContentType(mimeType);
-    if (!appid.isEmpty())
-        return createAction(findDesktopFile(appid),
-                        QStringList() << fileUri.toEncoded());
+    QString app = findDesktopFile(defaultAppForContentType(mimeType));
+    if (!app.isEmpty()) {
+        return createAction(app,
+                            QStringList() << fileUri.toEncoded());
+    }
     // Fall back to one of the existing actions (if there are some)
     QList<Action> acts = actionsForUri(fileUri.toEncoded(), mimeType);
     if (acts.size() >= 1)
@@ -333,9 +361,11 @@
         return result << createAction(uri, QStringList());
 
     QStringList appIds = appsForContentType(mimeType);
-    foreach (const QString& id, appIds) {
-        result << createAction(findDesktopFile(id),
-                               QStringList() << uri);
+    Q_FOREACH (const QString& id, appIds) {
+        QString app = findDesktopFile(id);
+        if (!app.isEmpty())
+            result << createAction(app,
+                                   QStringList() << uri);
     }
     return result;
 }
@@ -371,14 +401,28 @@
     return mime;
 }
 
+/// Returns the pseudo-mimetypes of the \a param string.  Mime types are found
+/// based on exact matching against regexps in the highlighter configuration.
+QStringList Internal::mimeForString(const QString& param)
+{
+    QStringList mimes;
+    const QList<QPair<QString, QString> >& cfgList = highlighterConfig();
+    for (int i = 0; i < cfgList.size(); ++i) {
+        if (QRegExp(cfgList[i].second, Qt::CaseInsensitive).exactMatch(param)) {
+            mimes << cfgList[i].first;
+        }
+    }
+    return mimes;
+}
+
 /// Returns the default action for handling the scheme of the passed \a uri.
 /// \sa actionsForScheme().
 Action Action::defaultActionForScheme(const QString& uri)
 {
     QString mimeType = mimeForScheme(uri);
-    QString defApp = defaultAppForContentType(mimeType);
+    QString defApp = findDesktopFile(defaultAppForContentType(mimeType));
     if (!defApp.isEmpty())
-        return createAction(findDesktopFile(defApp), QStringList() << uri);
+        return createAction(defApp, QStringList() << uri);
 
     // Fall back to one of the existing actions (if there are some)
     QList<Action> acts = actionsForUri(uri, mimeType);
@@ -395,17 +439,54 @@
 QList<Action> Action::actionsForScheme(const QString& uri)
 {
     QList<Action> result;
-    foreach (const QString& app, appsForContentType(mimeForScheme(uri))) {
+    Q_FOREACH (const QString& app, appsForContentType(mimeForScheme(uri))) {
         result << createAction(findDesktopFile(app), QStringList() << uri);
     }
     return result;
 }
 
+/// Returns the default action for handling the passed \a param.
+/// \sa actionsForString().
+Action Action::defaultActionForString(const QString& param)
+{
+    QStringList mimeTypes = mimeForString(param);
+    Q_FOREACH (const QString& mimeType, mimeTypes) {
+        QString def = findDesktopFile(defaultAppForContentType(mimeType));
+        if (!def.isEmpty())
+            return createAction(def,
+                                QStringList() << param);
+    }
+    // Fall back to one of the existing actions (if there are some)
+    QList<Action> acts = actionsForString(param);
+    if (acts.size() >= 1)
+        return acts[0];
+    return Action();
+}
+
+/// Returns all actions handling the given string \a param.  Dispatching is done
+/// based on exact matching against the regexpx of highlighter configuration.
+QList<Action> Action::actionsForString(const QString& param)
+{
+    QStringList mimeTypes = mimeForString(param);
+    QList<Action> result;
+    Q_FOREACH (const QString& mimeType, mimeTypes) {
+        QStringList apps = appsForContentType(mimeType);
+        Q_FOREACH (const QString& appid, apps) {
+            QString app = findDesktopFile(appid);
+            if (!app.isEmpty()) {
+                result << createAction(app,
+                                       QStringList() << param);
+            }
+        }
+    }
+    return result;
+}
+
 QList<Action> actionsForMime(const QString& mimeType)
 {
     QList<Action> result;
     QStringList appIds = appsForContentType(mimeType);
-    foreach (const QString& id, appIds) {
+    Q_FOREACH (const QString& id, appIds) {
         result << createAction(findDesktopFile(id),
                                QStringList());
     }
@@ -414,9 +495,9 @@
 
 Action defaultActionForMime(const QString& mimeType)
 {
-    QString appid = defaultAppForContentType(mimeType);
-    if (!appid.isEmpty())
-        return createAction(findDesktopFile(appid),
+    QString app = findDesktopFile(defaultAppForContentType(mimeType));
+    if (!app.isEmpty())
+        return createAction(app,
                             QStringList());
     return Action();
 }
--- src/service.cpp
+++ src/service.cpp
@@ -33,7 +33,8 @@
 namespace ContentAction
 {
 
-ServiceFwPrivate::ServiceFwPrivate(MDesktopEntry* desktopEntry, const QStringList& params)
+ServiceFwPrivate::ServiceFwPrivate(QSharedPointer<MDesktopEntry> desktopEntry,
+                                   const QStringList& params)
     : DefaultPrivate(desktopEntry, params),
       serviceFwMethod(desktopEntry->value(XMaemoMethodKey))
 {
@@ -72,7 +73,7 @@
 
 ServiceResolver::~ServiceResolver()
 {
-    foreach (QDBusInterface* proxy, proxies)
+    Q_FOREACH (QDBusInterface* proxy, proxies)
         delete proxy;
     proxies.clear();
     delete mapperProxy;
@@ -102,7 +103,7 @@
 {
     // Check which interfaces now become unusable
     QStringList interfaces = resolved.keys(implementor);
-    foreach (const QString& interface, interfaces)
+    Q_FOREACH (const QString& interface, interfaces)
         resolved.remove(interface);
     if (proxies.contains(implementor))
         delete proxies.take(implementor);
--- src/service.h
+++ src/service.h
@@ -39,7 +39,7 @@
     QDBusInterface* implementor(const QString& interface);
     QDBusInterface* implementorForAction(const QString& action, QString& method);
 
-private slots:
+private Q_SLOTS:
     void onServiceAvailable(QString, QString);
     void onServiceUnavailable(QString);
 
--- src/tracker.cpp
+++ src/tracker.cpp
@@ -76,14 +76,14 @@
 static bool mimeAndUriFromTracker(const QStringList& uris, QStringList &urlsAndMimes)
 {
     QString query("SELECT ");
-    foreach (const QString& uri, uris)
+    Q_FOREACH (const QString& uri, uris)
         query += QString("nie:url(<%1>) nie:mimeType(<%1>) ").arg(uri);
     query += " {}";
     QDBusReply<QVector<QStringList> > reply = tracker()->call(SparqlQuery, query);
     if (!reply.isValid())
         return false;
     urlsAndMimes = reply.value()[0];
-    foreach (const QString& x, urlsAndMimes)
+    Q_FOREACH (const QString& x, urlsAndMimes)
         if (x.isEmpty()) return false;
     return true;
 }
@@ -94,14 +94,14 @@
 static bool hactionFromTracker(const QStringList& uris, QStringList &urls)
 {
     QString query("SELECT ");
-    foreach (const QString& uri, uris)
+    Q_FOREACH (const QString& uri, uris)
         query += QString("tracker:coalesce(nfo:bookmarks(<%1>), nfo:uri(<%1>)) ").arg(uri);
     query += " {}";
     QDBusReply<QVector<QStringList> > reply = tracker()->call(SparqlQuery, query);
     if (!reply.isValid())
         return false;
     urls = reply.value()[0];
-    foreach (const QString& x, urls)
+    Q_FOREACH (const QString& x, urls)
         if (x.isEmpty()) return false;
     return true;
 }
@@ -126,7 +126,7 @@
     QStringList mimeTypes;
     QHash<QString, QString> conditions = trackerConditions();
     // TODO: evaluate them at once.
-    foreach (const QString& mimeType, conditions.keys()) {
+    Q_FOREACH (const QString& mimeType, conditions.keys()) {
         // Don't consider mime types for which nobody defines an action,
         // except if it is `software-application' which is special case.
         QString pseudoMimeType(OntologyMimeClass + mimeType);
@@ -145,7 +145,7 @@
 static QList<QStringList> mimeTypesForUris(const QStringList& uris)
 {
     QList<QStringList> allMimeTypes;
-    foreach (const QString& uri, uris) {
+    Q_FOREACH (const QString& uri, uris) {
         if (!isValidIRI(uri)) return QList<QStringList>();
         allMimeTypes << mimeForTrackerObject(uri);
     }
@@ -182,12 +182,12 @@
     if (!isValidIRI(uri)) return Action();
     QStringList mimeTypes = mimeForTrackerObject(uri);
     LCA_DEBUG << "pseudo-mimes" << mimeTypes;
-    foreach (const QString& mimeType, mimeTypes) {
+    Q_FOREACH (const QString& mimeType, mimeTypes) {
         if (mimeType == SoftwareApplicationMimeType)
             return createSoftwareAction(uri);
-        QString def = defaultAppForContentType(mimeType);
+        QString def = findDesktopFile(defaultAppForContentType(mimeType));
         if (!def.isEmpty())
-            return createAction(findDesktopFile(def),
+            return createAction(def,
                                 QStringList() << uri);
     }
     // If the resource is a file-based one, query its url and mimetype, and
@@ -195,7 +195,9 @@
     QStringList urlAndMime;
     if (mimeAndUriFromTracker(QStringList() << uri, urlAndMime)) {
         LCA_DEBUG << "real url and mimetype" << urlAndMime;
-        return defaultActionForFile(urlAndMime[0], urlAndMime[1]);
+        // Tracker has the filename %-encoded, don't encode it again
+        QUrl fileUrl = QUrl::fromEncoded(urlAndMime[0].toUtf8());
+        return defaultActionForFile(fileUrl, urlAndMime[1]);
     }
 
     // FIXME: this is a hack for converting nfo:Bookmark and nfo:WebHistory into
@@ -236,7 +238,7 @@
     QSet<QString> defApps;
     for (int i = 0; i < mimeTypes.size(); ++i) {
         QSet<QString> defs;
-        foreach (const QString& mimeType, mimeTypes[i]) {
+        Q_FOREACH (const QString& mimeType, mimeTypes[i]) {
             QString def = defaultAppForContentType(mimeType);
             if (!def.isEmpty())
                 defs << def;
@@ -249,8 +251,11 @@
     LCA_DEBUG << "defApps" << defApps;
     // If there are multiple possible default applications, the choice is
     // arbitrary.
-    if (!defApps.empty())
-        return createAction(findDesktopFile(*defApps.begin()), uris);
+    Q_FOREACH (const QString& appid, defApps) {
+        QString app = findDesktopFile(appid);
+        if (!app.isEmpty())
+            return createAction(app, uris);
+    }
 
     // Try mimetype based handlers for real things.
     QStringList urlsAndMimes;
@@ -269,8 +274,9 @@
             }
         }
         LCA_DEBUG << "defApp" << defApp;
-        if (!defApp.isEmpty())
-            return createAction(findDesktopFile(defApp), fileUris);
+        QString app = findDesktopFile(defApp);
+        if (app.isEmpty())
+            return createAction(app, fileUris);
     }
     // Fall back to one of the existing actions (if there are some)
     LCA_DEBUG << "fallback to actions()";
@@ -296,21 +302,35 @@
 
     QStringList mimeTypes = mimeForTrackerObject(uri);
     LCA_DEBUG << "pseudo mimes" << mimeTypes;
-    foreach (const QString& mimeType, mimeTypes) {
+    QSet<QString> blackList; // for adding each action only once
+    Q_FOREACH (const QString& mimeType, mimeTypes) {
         QStringList apps = appsForContentType(mimeType);
         if (mimeType == SoftwareApplicationMimeType)
             result << createSoftwareAction(uri);
-        foreach (const QString& app, apps) {
-            result << createAction(findDesktopFile(app),
-                                   QStringList() << uri);
+        Q_FOREACH (const QString& appid, apps) {
+            QString app = findDesktopFile(appid);
+            if (!app.isEmpty()) {
+                result << createAction(app,
+                                       QStringList() << uri);
+                blackList.insert(result.last().name());
+            }
         }
     }
     // Construct additional actions based on nie:mimeType(uri), passing
-    // nie:url(uri) as argument.
+    // nie:url(uri) as argument. However, don't add actions for those
+    // applications which already are in the list (because they declare handling
+    // the x-maemo-nepomuk mime type).
     QStringList urlAndMime;
     if (mimeAndUriFromTracker(QStringList() << uri, urlAndMime)) {
         LCA_DEBUG << "real url and mimetype" << urlAndMime;
-        result << actionsForFile(urlAndMime[0], urlAndMime[1]);
+        // Tracker has the filename %-encoded, don't encode it again
+        QUrl fileUrl = QUrl::fromEncoded(urlAndMime[0].toUtf8());
+        QList<Action> actions = actionsForFile(fileUrl, urlAndMime[1]);
+        Q_FOREACH (const Action& a, actions) {
+            if (!blackList.contains(a.name())) {
+                result << a;
+            }
+        }
     }
     // And still others, if it happens to be a nfo:Bookmark or nfo:WebHistory.
     // FIXME: this is a hack for converting nfo:Bookmark and nfo:WebHistory into
@@ -348,13 +368,13 @@
     QStringList commonApps;
     for (int i = 0; i < mimeTypes.size(); ++i) {
         QStringList apps;
-        foreach (const QString& mime, mimeTypes[i])
+        Q_FOREACH (const QString& mime, mimeTypes[i])
             apps += appsForContentType(mime);
         if (i == 0) {
             commonApps = apps;
         } else {
             QStringList intersection;
-            foreach (const QString& commonApp, commonApps) {
+            Q_FOREACH (const QString& commonApp, commonApps) {
                 if (apps.contains(commonApp))
                     intersection << commonApp;
             }
@@ -362,8 +382,14 @@
         }
     }
     LCA_DEBUG << "commonApps" << commonApps;
-    foreach (const QString& app, commonApps)
-        result << createAction(findDesktopFile(app), uris);
+    QSet<QString> blackList; // for adding each action only once
+    Q_FOREACH (const QString& appid, commonApps) {
+        QString app = findDesktopFile(appid);
+        if (!app.isEmpty()) {
+            result << createAction(app, uris);
+            blackList.insert(result.last().name());
+        }
+    }
 
     QStringList urlsAndMimes;
     if (mimeAndUriFromTracker(uris, urlsAndMimes)) {
@@ -377,7 +403,7 @@
                 commonApps = apps;
             } else {
                 QStringList intersection;
-                foreach (const QString& commonApp, commonApps) {
+                Q_FOREACH (const QString& commonApp, commonApps) {
                     if (apps.contains(commonApp))
                         intersection << commonApp;
                 }
@@ -385,8 +411,15 @@
             }
         }
         LCA_DEBUG << "real-mime commonApps" << commonApps;
-        foreach (const QString& app, commonApps)
-            result << createAction(findDesktopFile(app), fileUris);
+        Q_FOREACH (const QString& appid, commonApps) {
+            QString app = findDesktopFile(appid);
+            if (app.isEmpty())
+                continue;
+            Action a = createAction(app, fileUris);
+            if (!blackList.contains(a.name())) {
+                result << a;
+            }
+        }
     }
     // TODO: sort the result
     return result;
--- t/Makefile.am
+++ t/Makefile.am
@@ -31,7 +31,10 @@
 	test-desktop-launching.py \
 	test-l10n.sh \
 	test-fixed-params.py \
-	test-schemes.sh
+	test-schemes.sh \
+	test-special-chars.py \
+	test-invalid-defaults.py \
+	test-regexps.py
 
 noinst_SCRIPTS = \
 	start-sandbox.sh \
@@ -43,11 +46,11 @@
 	service.map \
 	testdata.ttl \
 	plaintext \
+	empty.pdf \
 	launchme.desktop
 
 check_PROGRAMS = \
-	linktest \
-	hl1
+	linktest
 
 noinst_PROGRAMS = hldemo
 hldemo_SOURCES = hldemo.cpp
@@ -64,11 +67,13 @@
 testhelper_PROGRAMS = \
 	servicetest
 
-hl1_SOURCES = hl1.cpp
 linktest_SOURCES = linktest.cpp
 linktest_CPPFLAGS = \
 	$(AM_CPPFLAGS) \
-	$(GIO_CFLAGS)
+	$(GIO_CFLAGS) \
+	$(MT_CFLAGS)
+linktest_LDADD = \
+	$(MT_LIBS)
 
 servicetest_SOURCES = servicelistener.cpp
 
@@ -85,6 +90,7 @@
 	$(noinst_SCRIPTS) \
 	hlinput.txt \
 	plaintext \
+	empty.pdf \
 	launchme.desktop
 
 # NOTE: we depend on automake executing $(TESTS) in order.  Should this
@@ -103,6 +109,9 @@
 	test-l10n.sh \
 	test-fixed-params.py \
 	test-schemes.sh \
+	test-special-chars.py \
+	test-invalid-defaults.py \
+	test-regexps.py \
 	stop-sandbox.sh
 
 clean-local:
--- t/applications/Makefile.am
+++ t/applications/Makefile.am
@@ -16,4 +16,9 @@
 	fixedparams.desktop \
 	plainimageviewer.desktop \
 	trackerimageviewer.desktop \
-	plainmusicplayer.desktop
+	plainmusicplayer.desktop \
+	complexmusic.desktop \
+	uriprinter.desktop \
+	gallerywithfilename.desktop \
+	browser.desktop \
+	special-browser.desktop
--- t/applications/addcontact.desktop
+++ t/applications/addcontact.desktop
@@ -5,4 +5,4 @@
 Terminal=false
 Type=Application
 NotShowIn=X-MeeGo;
-MimeType=x-maemo-highlight/phone;
+MimeType=x-maemo-highlight/phone-number;
\ No newline at end of file
--- t/applications/browser.desktop
+++ t/applications/browser.desktop
+[Desktop Entry]
+Encoding=UTF-8
+Name=browser
+Exec=echo "browser with params:" %U
+Terminal=false
+Type=Application
+NotShowIn=X-MeeGo;
+MimeType=x-maemo-highlight/http-url;x-maemo-highlight/ftp-url;x-maemo-highlight/feed-url;x-maemo-highlight/ftp-url;
--- t/applications/caller.desktop
+++ t/applications/caller.desktop
@@ -5,4 +5,4 @@
 Terminal=false
 Type=Application
 NotShowIn=X-MeeGo;
-MimeType=x-maemo-highlight/phone;x-maemo-highlight/url;
+MimeType=x-maemo-highlight/phone-number;
--- t/applications/complexmusic.desktop
+++ t/applications/complexmusic.desktop
+[Desktop Entry]
+Encoding=UTF-8
+Name=complex_musicplayer
+GenericName=A complex music player
+Icon=execicon
+Comment=Views audio
+Exec=echo complex_musicplayer %U
+Terminal=false
+Type=Application
+MimeType=x-maemo-nepomuk/music-piece;audio/mpeg;
+NotShowIn=X-MeeGo;
--- t/applications/defaults.list
+++ t/applications/defaults.list
@@ -4,5 +4,7 @@
 x-maemo-highlight/url=caller.desktop
 x-maemo-highlight/ovoda=ovoda.desktop
 x-maemo-nepomuk/image=galleryserviceinterface.desktop
+x-maemo-nepomuk/calendar-event=nonexistent.desktop
+application/pdf=nonexistent.desktop
 image/*=plainimageviewer.desktop
 audio/*=plainmusicplayer.desktop
--- t/applications/gallerywithfilename.desktop
+++ t/applications/gallerywithfilename.desktop
+[Desktop Entry]
+Encoding=UTF-8
+Name=fake gallery
+Terminal=false
+Type=Application
+MimeType=image/png;
+NotShowIn=X-MeeGo;
+X-Maemo-Service=just.a.gallery
+X-Maemo-Method=com.nokia.galleryserviceinterface.showImage
--- t/applications/mimeinfo.cache
+++ t/applications/mimeinfo.cache
@@ -1,12 +1,17 @@
 [MIME Cache]
 x-maemo-nepomuk/person-contact=contacthandler.desktop
-x-maemo-highlight/url=caller.desktop
 text/*=fixedparams.desktop
-x-maemo-highlight/ovoda=ovoda.desktop
+image/png=gallerywithfilename.desktop;uriprinter.desktop;
 image/*=plainimageviewer.desktop
 x-maemo-nepomuk/image=galleryserviceinterface.desktop;trackerimageviewer.desktop;other.desktop;upload.desktop;show.desktop;
-x-maemo-highlight/phone=addcontact.desktop;caller.desktop;
 text/plain=uberexec.desktop;ubermeego.desktop;ubermimeopen.desktop;
+audio/mpeg=complexmusic.desktop
 audio/*=plainmusicplayer.desktop
-x-maemo-highlight/email=emailer.desktop
+x-maemo-nepomuk/music-piece=complexmusic.desktop
 x-maemo-urischeme/mailto=emailer.desktop
+x-maemo-highlight/email-address=emailer.desktop;
+x-maemo-highlight/http-url=browser.desktop;
+x-maemo-highlight/ftp-url=browser.desktop;
+x-maemo-highlight/feed-url=browser.desktop;
+x-maemo-highlight/phone-number=caller.desktop;
+x-maemo-highlight/special-url=special-browser.desktop;
--- t/applications/special-browser.desktop
+++ t/applications/special-browser.desktop
+[Desktop Entry]
+Encoding=UTF-8
+Name=browser
+Exec=echo "special browser with params:" %U
+Terminal=false
+Type=Application
+NotShowIn=X-MeeGo;
+MimeType=x-maemo-highlight/special-url;
--- t/applications/uriprinter.desktop
+++ t/applications/uriprinter.desktop
+[Desktop Entry]
+Encoding=UTF-8
+Name=UriPrinter
+Comment=Just prints the params into a file
+Exec=python -c "f2 = open('./executedAction', 'w'); f2.write(\"%U\\n\");"
+Terminal=false
+Type=Application
+MimeType=image/png;
+NotShowIn=X-MeeGo;
--- t/hl1.cpp
+++ t/hl1.cpp
-/*
- * Reads text from the standard input, highlight rules from the usual place
- * (ie. xml files in $CONTENTACTION_ACTIONS) and then prints match results on
- * the standard output.  If the terminal is a tty, the results are printed on
- * stderr, and on stdout a beautifully colored version of the text is shown.
- */
-#include <stdio.h>
-#include <unistd.h>
-#include <contentaction.h>
-#include <QDebug>
-
-using namespace ContentAction;
-
-QDebug operator<<(QDebug dbg, const Match& m) {
-    dbg.nospace() << "match at ("
-                  << m.start << ", " << m.end << "): ";
-    foreach (const Action& a, m.actions) {
-        dbg.space() << a.name();
-    }
-    dbg.nospace() << "\n";
-    return dbg;
-}
-
-QDebug operator<<(QDebug dbg, const QList<Match>& ms) {
-    foreach (const Match& m, ms) {
-        dbg.space() << m;
-    }
-    return dbg;
-}
-
-int main(void)
-{
-    QTextStream out(isatty(1) ? stderr : stdout);
-    QTextStream textout(isatty(1) > 0 ? stdout : fopen("/dev/null", "w"));
-    QTextStream in(stdin);
-    QString text = in.readAll();
-
-    QList<Match> ms = Action::highlight(text);
-    foreach (const Match& m, ms) {
-        QStringList actions;
-        foreach (const Action& a, m.actions)
-            actions << a.name();
-        out << QString("%1 %2 '%3' %4\n").arg(QString::number(m.start),
-                                              QString::number(m.end),
-                                              text.mid(m.start, m.end - m.start),
-                                              actions.join(" "));
-    }
-    QString hltext(text);
-    if (isatty(1)) {
-        qSort(ms.begin(), ms.end());
-        QString color[] = {
-            "\e[1;37;41m",
-            "\e[1;37;42m",
-            "\e[1;37;43m",
-            "\e[1;37;44m",
-            "\e[1;37;45m",
-            "\e[1;37;46m",
-        };
-        int i = 0;
-        int d = 0;
-        foreach (const Match& m, ms) {
-            hltext.insert(d + m.start, color[i]);
-            d += color[i].length();
-            i = (i + 1) % (sizeof(color) / sizeof(color[0]));
-            hltext.insert(d + m.end, "\e[0m");
-            d += 4;
-        }
-        textout << hltext;
-    }
-    return 0;
-}
--- t/hldemo.cpp
+++ t/hldemo.cpp
@@ -23,7 +23,7 @@
     Us();
     ~Us();
 
-private slots:
+private Q_SLOTS:
     void doHilite();
     void doUnHilite();
 
--- t/hlinput.txt
+++ t/hlinput.txt
@@ -3,4 +3,58 @@
 or 911
 ooaoa+foo at motherland.ru
 normal uri http://foo.bar.ru
-ovoda http://example.com/quux/323
+special url http://example.com/quux/323
+
+These should be valid phone numbers:
+number 1234567 is here
+number (303)499-7111 is here
+number +13034997111 is here
+number 303-499-7111 is here
+number +1(303)499-7111 is here
+number 303.499.7111, is here
+number (3034997111) is here
+number +48 123.12-3 123 is here
+number +481234p12345 is here
+number +481234#12345 is here
+number +358 (9) 123 456 is here
+
+These should be valid web addresses:
+http://us.m.yahoo.com
+http://www.google.com/m
+www.google.com/m
+<http://us.m.yahoo.com>
+http://maps.google.com/maps?f=d&source=s_d&saddr=Nieznana+droga&daddr=krak%C3%B3w&hl&geocode=Fa5h9wIdlq87Aq%3BFQrt-wIdFFYwASnRGE41wEQWRzG_ikd2tbZrtA&mra=ls&sll=49.915862,20.323334&sspn=0.505808,1.234589&ie=UTF8&t=h&z=10
+http://host-with-dash.com/path?%:@&=+$,-!~*'with(special)chars
+
+And other browsable urls:
+feed://browsefeed.com
+feed:browsefeed.com
+ftp://browseftp.com
+
+These should be valid e-mail addresses:
+Address with an extra dot: john.doe at att.com.
+<mailto:john_doe2 at att.com>
+
+These are suspicious phone numbers:
+sometextbefore333222111444sometextafter
++(((((
++358+7777
+i bought 13 bananas
+too short phone number: 14
+too long phone number: 151515151515151515151
+but these are ok: 141 and 14141414141414141414
+
+These are suspicious e-mail addresses:
+double at atsign@bar.com
+onlyuser at andhostname
+onlyuser2 at andhostname2.
+USERNAME at ALLUPPERCASE.COM
+
+These are suspicious web addresses:
+http://http://double.http.com
+
+These are some previous error cases:
+this is an url www.myurl.com/foo/bar and e-mail first at home.com
+1112223338;email at domain.org;4445556668;email2 at domain.org
+Http://www.capital.com
+hTTp://www.capital.com
--- t/linktest.cpp
+++ t/linktest.cpp
@@ -3,6 +3,8 @@
 #include "contentaction.h"
 #include "internal.h"
 
+#include <MDesktopEntry>
+
 using namespace ContentAction;
 using namespace ContentAction::Internal;
 
@@ -29,6 +31,11 @@
     setMimeDefault("foo", "bar");
     QList<Action> list = actionsForMime("foo");
     setMimeDefault("foo", list[0]);
+
+    QSharedPointer<MDesktopEntry> m(new MDesktopEntry("some.desktop"));
+    Action d = Action::launcherAction(m, QStringList() << "param1" << "param2");
+
+    Action e = Action::launcherAction("some.desktop", QStringList() << "param1" << "param2");
 }
 
 int main(int argc, char **argv)
--- t/servicelistener.cpp
+++ t/servicelistener.cpp
@@ -7,7 +7,7 @@
 class Dummy : public QObject
 {
     Q_OBJECT
-public slots:
+public Q_SLOTS:
     void timeout()
     {
         static int n = 0;
--- t/test-desktop-launching.py
+++ t/test-desktop-launching.py
@@ -55,6 +55,15 @@
         os.remove("./launchedAction")
         self.assert_(content.find("I was launched") != -1)
 
+    def testLaunchWithParams(self):
+        (status, output) = getstatusoutput("lca-tool --triggerdesktop uriprinter.desktop param1 param2 param3")
+        f = open("./executedAction")
+        content = f.read()
+        f.close()
+        os.remove("./executedAction")
+        self.assert_(status == 0)
+        self.assert_(content.find("'param1' 'param2' 'param3'") != -1)
+
 def runTests():
     suite = unittest.TestLoader().loadTestsFromTestCase(Launching)
     result = unittest.TextTestRunner(verbosity=2).run(suite)
--- t/test-highlight.sh
+++ t/test-highlight.sh
@@ -8,9 +8,77 @@
     expr "$1" : "$2" >/dev/null;
 }
 
-res=$(./hl1 < $srcdir/hlinput.txt)
+res=$(lca-tool --highlight < $srcdir/hlinput.txt)
 
 strstr "$res" ".*61 73 '+44 433 2236' caller" || exit 1
-strstr "$res" ".*77 80 '911' caller" || exit 1 
+strstr "$res" ".*77 80 '911' caller" || exit 1
 strstr "$res" ".*15 33 'email at address.here' emailer" || exit 1 
 strstr "$res" ".*81 104 'ooaoa+foo at motherland.ru' emailer" || exit 1
+
+# phone numbers
+
+strstr "$res" ".*'1234567' caller" || exit 2
+strstr "$res" ".*'(303)499-7111' caller" || exit 2
+strstr "$res" ".*'+13034997111' caller" || exit 2
+strstr "$res" ".*'303-499-7111' caller" || exit 2
+strstr "$res" ".*'+1(303)499-7111' caller" || exit 2
+strstr "$res" ".*'303.499.7111' caller" || exit 2
+strstr "$res" ".*'3034997111' caller" || exit 2
+strstr "$res" ".*'+48 123.12-3 123' caller" || exit 2
+strstr "$res" ".*'+481234p12345' caller" || exit 2
+strstr "$res" ".*'+481234#12345' caller" || exit 2
+strstr "$res" ".*'+358 (9) 123 456' caller" || exit 2
+
+# web addresses
+
+strstr "$res" ".*'http://us.m.yahoo.com' browser" || exit 3
+strstr "$res" ".*'http://www.google.com/m' browser" || exit 3
+strstr "$res" ".*'www.google.com/m' browser" || exit 3
+strstr "$res" ".*'http://us.m.yahoo.com' browser" || exit 3
+strstr "$res" ".*'http://maps.google.com/maps?f=d&source=s_d&saddr=Nieznana+droga&daddr=krak%C3%B3w&hl&geocode=Fa5h9wIdlq87Aq%3BFQrt-wIdFFYwASnRGE41wEQWRzG_ikd2tbZrtA&mra=ls&sll=49.915862,20.323334&sspn=0.505808,1.234589&ie=UTF8&t=h&z=10' browser" || exit 3
+strstr "$res" ".*'http://host-with-dash.com/path?%:@&=+\\$,-!~\\*'with(special)chars' browser" || exit 3
+
+strstr "$res" ".*feed://browsefeed.com' browser" || exit 3
+strstr "$res" ".*feed:browsefeed.com' browser" || exit 3
+strstr "$res" ".*ftp://browseftp.com' browser" || exit 3
+
+# email addresses
+strstr "$res" ".*'john.doe at att.com' emailer" || exit 4
+strstr "$res" ".*'mailto:john_doe2 at att.com' emailer" || exit 4
+
+# invalid and almost-invalid cases
+strstr "$res" ".*'333222111444' caller" && exit 5
+strstr "$res" ".*'+((((' caller" && exit 5
+#+358 and +7777 will be recognized separately, though
+strstr "$res" ".*'+358+7777' caller" && exit 5
+#strstr "$res" ".*'+358' caller" && exit 5
+#strstr "$res" ".*'+7777' caller" && exit 5
+strstr "$res" ".*'13' caller" && exit 5
+strstr "$res" ".*'14' caller" && exit 5
+# a long phone number
+strstr "$res" ".*'151515151515151515151' caller" && exit 5
+# also: it's prefix shouldn't be recognized
+strstr "$res" ".*'15151515151515151515' caller" && exit 5
+strstr "$res" ".*'141' caller" || exit 5
+strstr "$res" ".*'14141414141414141414' caller" || exit 5
+
+strstr "$res" ".*'double at atsign@bar.com' emailer" && exit 5
+strstr "$res" ".*'onlyuser at andhostname' emailer" && exit 5
+strstr "$res" ".*'onlyuser2 at andhostname2.' emailer" && exit 5
+strstr "$res" ".*'USERNAME at ALLUPPERCASE.COM' emailer" || exit 5
+
+strstr "$res" ".*'http://http://double.http.com' browser" && exit 6
+strstr "$res" ".*'http://double.http.com' browser" || exit 6
+
+# previous bugs
+strstr "$res" ".*'www.myurl.com/foo/bar' browser" || exit 7
+strstr "$res" ".*'first at home.com' emailer" || exit 7
+strstr "$res" ".*'1112223338' caller" || exit 7
+strstr "$res" ".*'email at domain.org' emailer" || exit 7
+strstr "$res" ".*'4445556668' caller" || exit 7
+strstr "$res" ".*'email2 at domain.org' emailer" || exit 7
+
+strstr "$res" ".*'Http://www.capital.com' browser" || exit 8
+strstr "$res" ".*'hTTp://www.capital.com' browser" || exit 8
+
+exit 0
--- t/test-invalid-defaults.py
+++ t/test-invalid-defaults.py
+#!/usr/bin/python2.5
+##
+## Copyright (C) 2008, 2009 Nokia. All rights reserved.
+##
+## Contact: Marius Vollmer <marius.vollmer at nokia.com>
+##
+## This library is free software; you can redistribute it and/or
+## modify it under the terms of the GNU Lesser General Public License
+## version 2.1 as published by the Free Software Foundation.
+##
+## This library is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public
+## License along with this library; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+## 02110-1301 USA
+
+import sys
+import os
+# Otherwise env.py won't be found when running tests inside a VPATH build dir
+sys.path.insert(0, os.getcwd())
+
+try: import env
+except: pass
+
+import unittest
+from commands import getstatusoutput
+from cltool import CLTool
+
+# this controls where the test files are
+testfiles_dir = os.path.abspath('.')
+if 'MIME_TEST_DIR' in os.environ:
+    testfiles_dir = os.path.abspath(os.environ['MIME_TEST_DIR'])
+
+class InvalidDefaults(unittest.TestCase):
+    def testInvalidDefaultForUri(self):
+        # this uri is a ncal:Event, and x-maemo-nepomuk/calendar-event has a
+        # defaults.list entry pointing to a nonexistent application.
+        (status, output) = getstatusoutput("lca-tool --tracker --printdefault urn:test:calendarevent")
+        self.assert_(status == 0)
+        self.assert_(output.find("Invalid action") != -1)
+
+    def testInvalidDefaultForFile(self):
+        filename = "file://" + testfiles_dir + "/empty.pdf"
+        (status, output) = getstatusoutput("lca-tool --file --printdefault " + filename)
+        self.assert_(status == 0)
+        self.assert_(output.find("Invalid action") != -1)
+
+def runTests():
+    suite = unittest.TestLoader().loadTestsFromTestCase(InvalidDefaults)
+    result = unittest.TextTestRunner(verbosity=2).run(suite)
+    return len(result.errors + result.failures)
+
+if __name__ == "__main__":
+    sys.exit(runTests())
--- t/test-regexps.py
+++ t/test-regexps.py
+#!/usr/bin/python2.5
+##
+## Copyright (C) 2008-2010 Nokia. All rights reserved.
+##
+## Contact: Marius Vollmer <marius.vollmer at nokia.com>
+##
+## This library is free software; you can redistribute it and/or
+## modify it under the terms of the GNU Lesser General Public License
+## version 2.1 as published by the Free Software Foundation.
+##
+## This library is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public
+## License along with this library; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+## 02110-1301 USA
+
+import sys
+import os
+# Otherwise env.py won't be found when running tests inside a VPATH build dir
+sys.path.insert(0, os.getcwd())
+
+try: import env
+except: pass
+
+import unittest
+from commands import getstatusoutput
+from cltool import CLTool
+from tempfile import mkdtemp
+
+class Regexps(unittest.TestCase):
+    def setUp(self):
+        pass
+    def tearDown(self):
+        pass
+        
+    def testStringMimes1(self):
+        # only the general
+        (status, output) = getstatusoutput("lca-tool --string --printmimes foo")
+        self.assert_(status == 0)
+        self.assert_(output.find("general-1") != -1)
+        self.assert_(output.find("special-1a") == -1)
+        self.assert_(output.find("special-1b") == -1)
+
+        # general and one special case
+        (status, output) = getstatusoutput("lca-tool --string --printmimes foobar")
+        self.assert_(status == 0)
+        self.assert_(output.find("special-1a") != -1)
+        self.assert_(output.find("general-1") != -1)
+        self.assert_(output.find("special-1b") == -1)
+        # verify the order
+        self.assert_(output.find("special-1a") < output.find("general-1"))
+
+        # general and another special case
+        (status, output) = getstatusoutput("lca-tool --string --printmimes foobazxx")
+        self.assert_(status == 0)
+        self.assert_(output.find("special-1b") != -1)
+        self.assert_(output.find("general-1") != -1)
+        self.assert_(output.find("special-1a") == -1)
+        # verify the order
+        self.assert_(output.find("special-1b") < output.find("general-1"))
+
+    def testStringMimes2(self):
+        # only the general
+        (status, output) = getstatusoutput("lca-tool --string --printmimes cat")
+        self.assert_(status == 0)
+        self.assert_(output.find("general-2") != -1)
+        self.assert_(output.find("special-2") == -1)
+        self.assert_(output.find("superspecial-2") == -1)
+
+        # general and one special case
+        (status, output) = getstatusoutput("lca-tool --string --printmimes catdog")
+        self.assert_(status == 0)
+        self.assert_(output.find("general-2") != -1)
+        self.assert_(output.find("special-2") != -1)
+        self.assert_(output.find("superspecial-2") == -1)
+        # verify the order
+        self.assert_(output.find("special-2") < output.find("general-2"))
+
+        # general and both special cases
+        (status, output) = getstatusoutput("lca-tool --string --printmimes catdogzebra")
+        self.assert_(status == 0)
+        self.assert_(output.find("general-2") != -1)
+        self.assert_(output.find("special-2") != -1)
+        self.assert_(output.find("superspecial-2") != -1)
+        # verify the order
+        self.assert_(output.find("special-2") < output.find("general-2"))
+        self.assert_(output.find("superspecial-2") < output.find("special-2"))
+
+    def testActionsForString(self):
+        (status, output) = getstatusoutput("lca-tool --string --print www.foo.com")
+        self.assert_(status == 0)
+        self.assert_(output.find("browser") != -1)
+        self.assert_(output.find("special-browser") == -1)
+
+        (status, output) = getstatusoutput("lca-tool --string --print http://example.com")
+        self.assert_(status == 0)
+        self.assert_(output.find("special-browser") != -1)
+        self.assert_(output.find("browser") != -1)
+        self.assert_(output.find("special-browser") < output.find("browser"))
+
+    def testDefaultActionForString(self):
+        (status, output) = getstatusoutput("lca-tool --string --printdefault www.foo.com")
+        self.assert_(status == 0)
+        self.assert_(output.find("browser") != -1)
+
+        (status, output) = getstatusoutput("lca-tool --string --printdefault http://example.com")
+        self.assert_(status == 0)
+        self.assert_(output.find("special-browser") != -1)
+
+def runTests():
+    suite = unittest.TestLoader().loadTestsFromTestCase(Regexps)
+    result = unittest.TextTestRunner(verbosity=2).run(suite)
+    return len(result.errors + result.failures)
+
+if __name__ == "__main__":
+    sys.exit(runTests())
--- t/test-special-chars.py
+++ t/test-special-chars.py
+#!/usr/bin/python2.5
+##
+## Copyright (C) 2008, 2009 Nokia. All rights reserved.
+##
+## Contact: Marius Vollmer <marius.vollmer at nokia.com>
+##
+## This library is free software; you can redistribute it and/or
+## modify it under the terms of the GNU Lesser General Public License
+## version 2.1 as published by the Free Software Foundation.
+##
+## This library is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public
+## License along with this library; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+## 02110-1301 USA
+
+import sys
+import os
+# Otherwise env.py won't be found when running tests inside a VPATH build dir
+sys.path.insert(0, os.getcwd())
+
+try: import env
+except: pass
+
+import unittest
+from commands import getstatusoutput
+from cltool import CLTool
+
+class SpecialChars(unittest.TestCase):
+    def setUp(self):
+        # start a fake gallery service
+        self.gallery = CLTool("gallery.py")
+        self.assert_(self.gallery.expect("started"))
+        (status, output) = getstatusoutput("touch /tmp/some#file.mp3")
+        (status, output) = getstatusoutput("ls /tmp/some#file.mp3")
+
+    def tearDown(self):
+        self.gallery.kill()
+        (status, output) = getstatusoutput("rm /tmp/some#file.mp3")
+
+    def testDBusWithSpecialChars(self):
+        (status, output) = getstatusoutput("lca-tool --tracker --trigger gallerywithfilename specialchars.image")
+        self.assert_(status == 0)
+        # assert that the gallery was invoked
+        self.assert_(self.gallery.expect("showImage ; file:///tmp/%5Bspecial%5B.png"))
+
+    def testExecWithSpecialChars(self):
+        (status, output) = getstatusoutput("lca-tool --tracker --trigger uriprinter specialchars.image")
+        self.assert_(status == 0)
+        f = open("./executedAction")
+        content = f.read()
+        f.close()
+        os.remove("./executedAction")
+        self.assert_(content.find("'/tmp/[special[.png'") != -1)
+
+    def testActionsForFileWithSpecialChars(self):
+        filename = "file:///tmp/some%23file.mp3"
+        (status, output) = getstatusoutput("lca-tool --file --print " + filename)
+        self.assert_(status == 0)
+        self.assert_(output.find("plainmusicplayer") != -1)
+
+    def testActionsForFileWithSpecialChars2(self):
+        filename = "/tmp/some#file.mp3"
+        (status, output) = getstatusoutput("lca-tool --file --print " + filename)
+        self.assert_(status == 0)
+        self.assert_(output.find("plainmusicplayer") != -1)
+
+def runTests():
+    suite = unittest.TestLoader().loadTestsFromTestCase(SpecialChars)
+    result = unittest.TextTestRunner(verbosity=2).run(suite)
+    return len(result.errors + result.failures)
+
+if __name__ == "__main__":
+    sys.exit(runTests())
--- t/testdata.ttl
+++ t/testdata.ttl
@@ -24,6 +24,9 @@
 <a.music> a nmm:MusicPiece, nie:DataObject ;
 	nie:url "file:///tmp/aaa.mp3" ;
 	nie:mimeType "audio/mpeg" .
+<a2.music> a nmm:MusicPiece, nie:DataObject ;
+	nie:url "file:///tmp/aaa2.mp3" ;
+	nie:mimeType "audio/mpeg" .
 <b.music> a nmm:MusicPiece, nie:DataObject ;
 	nie:url "file:///tmp/bbb.wav" ;
 	nie:mimeType "audio/wav" .
@@ -43,6 +46,10 @@
           nie:url "file:///tmp/bbb.png" ;
           nie:mimeType "image/png" .
 
+<specialchars.image> a nfo:Image, nie:DataObject ;
+          nie:url "file:///tmp/%5Bspecial%5B.png" ;
+          nie:mimeType "image/png" .
+
 <urn:uuid:1930642225> a nfo:WebHistory;
 	nie:title "www.piratebay.org 0";
 	nie:contentCreated "2009-04-23T17:02:08";
@@ -109,3 +116,4 @@
 <urn:test:webhistory> a nfo:WebHistory ;
 	nfo:uri "mailto:foo at bar.com" .
 
+<urn:test:calendarevent> a ncal:Event .
--- t/tests.xml
+++ t/tests.xml
@@ -83,6 +83,20 @@
           PATH=.:/usr/lib/libcontentaction-tests:$PATH XDG_DATA_HOME=/usr/share/libcontentaction-tests ./test-schemes.sh
         </step>
       </case>
+      <case name="test-special-chars" description="A test of questionable value.">
+        <step expected_result="0">
+          . /tmp/session_bus_address.user; \
+          cd /usr/share/libcontentaction-tests; \
+          PATH=.:/usr/lib/libcontentaction-tests:$PATH XDG_DATA_HOME=/usr/share/libcontentaction-tests ./test-special-chars.py
+        </step>
+      </case>
+      <case name="test-regexps" description="A test of questionable value.">
+        <step expected_result="0">
+          . /tmp/session_bus_address.user; \
+          cd /usr/share/libcontentaction-tests; \
+          PATH=.:/usr/lib/libcontentaction-tests:$PATH XDG_DATA_HOME=/usr/share/libcontentaction-tests CONTENTACTION_ACTIONS=/usr/share/libcontentaction-tests/data ./test-regexps.py
+        </step>
+      </case>
 
       <environments>
         <scratchbox>true</scratchbox>

++++++ libcontentaction.yaml
--- libcontentaction.yaml
+++ libcontentaction.yaml
@@ -1,14 +1,14 @@
 Name: libcontentaction
 Summary: Library for associating content with actions
-Version: 0.1.15
+Version: 0.1.35
 Release: 1
 Group: System/Desktop
 License: LGPLv2.1
 URL: http://maemo.gitorious.org/maemo-af/libcontentaction
 Sources:
     - "%{name}-%{version}.tar.gz"
-Patches:
-    - fix_glib_ftbfs.patch
+Patches:  
+    - 0001-Fix-python-version-for-tests.patch
 Description: |
     libcontentaction is a library for associating content with actions.
 PkgConfigBR:

++++++ deleted files:
--- fix_glib_ftbfs.patch




More information about the MeeGo-commits mailing list