← Back to team overview

zeitgeist team mailing list archive

[Merge] lp:~zeitgeist/zeitgeist/bluebird-symbol2 into lp:zeitgeist

 

Seif Lotfy has proposed merging lp:~zeitgeist/zeitgeist/bluebird-symbol2 into lp:zeitgeist.

Requested reviews:
  Zeitgeist Framework Team (zeitgeist)

For more details, see:
https://code.launchpad.net/~zeitgeist/zeitgeist/bluebird-symbol2/+merge/69833

Updated version of the Symbol stuff... comments welcome (Dont merge until we are happy with the results)
-- 
https://code.launchpad.net/~zeitgeist/zeitgeist/bluebird-symbol2/+merge/69833
Your team Zeitgeist Framework Team is requested to review the proposed merge of lp:~zeitgeist/zeitgeist/bluebird-symbol2 into lp:zeitgeist.
=== added file '.bzrignore'
--- .bzrignore	1970-01-01 00:00:00 +0000
+++ .bzrignore	2011-07-29 16:51:11 +0000
@@ -0,0 +1,33 @@
+codeblocks.cbp
+codeblocks.layout
+INSTALL
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+stamp-h1
+mkinstalldirs
+po/POTFILES
+po/stamp-it
+src/.deps
+missing
+ltmain.sh
+libtool
+intltool-update.in
+intltool-merge.in
+intltool-extract.in
+po/Makefile.in.in
+src/.libs
+src/*.c
+src/*.stamp
+src/bluebird

=== renamed file '.bzrignore' => '.bzrignore.moved'
=== added file 'AUTHORS'
--- AUTHORS	1970-01-01 00:00:00 +0000
+++ AUTHORS	2011-07-29 16:51:11 +0000
@@ -0,0 +1,4 @@
+Seif Lotfy <seif@xxxxxxxxx>
+Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+Manish Sinha <manishsinha@xxxxxxxxxx>
+Michael Hruby <michal.mhr@xxxxxxxxx>

=== renamed file 'AUTHORS' => 'AUTHORS.moved'
=== added file 'COPYING'
--- COPYING	1970-01-01 00:00:00 +0000
+++ COPYING	2011-07-29 16:51:11 +0000
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

=== renamed file 'COPYING' => 'COPYING.moved'
=== added file 'ChangeLog'
=== added file 'MAINTAINERS'
--- MAINTAINERS	1970-01-01 00:00:00 +0000
+++ MAINTAINERS	2011-07-29 16:51:11 +0000
@@ -0,0 +1,5 @@
+Seif Lotfy <seif@xxxxxxxxx>
+Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+Manish Sinha <manishsinha@xxxxxxxxxx>
+Michael Hruby <michal.mhr@xxxxxxxxx>
+

=== renamed file 'MAINTAINERS' => 'MAINTAINERS.moved'
=== added file 'Makefile.am'
--- Makefile.am	1970-01-01 00:00:00 +0000
+++ Makefile.am	2011-07-29 16:51:11 +0000
@@ -0,0 +1,41 @@
+NULL = 
+
+#Build in these directories:
+
+SUBDIRS = \
+	src \
+	po \
+	$(NULL)
+
+bluebirddocdir = ${prefix}/doc/bluebird
+bluebirddoc_DATA = \
+	ChangeLog \
+	README \
+	COPYING \
+	AUTHORS \
+	INSTALL \
+	NEWS \
+	$(NULL)
+
+EXTRA_DIST = \
+	$(bluebirddoc_DATA) \
+	intltool-extract.in \
+	intltool-merge.in \
+	intltool-update.in \
+	$(NULL)
+
+DISTCLEANFILES = \
+	intltool-extract \
+	intltool-merge \
+	intltool-update \
+	po/.intltool-merge-cache \
+	$(NULL)
+
+run: all
+	./src/bluebird
+
+debug: all
+	gdb ./src/bluebird
+
+test: all
+	./test/dbus/run-all-tests.py

=== renamed file 'Makefile.am' => 'Makefile.am.moved'
=== added file 'NEWS'
=== renamed file 'NEWS' => 'NEWS.moved'
=== added file 'README'
=== renamed file 'README' => 'README.moved'
=== added file 'autogen.sh'
--- autogen.sh	1970-01-01 00:00:00 +0000
+++ autogen.sh	2011-07-29 16:51:11 +0000
@@ -0,0 +1,9 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+PKG_NAME="bluebird"
+
+. gnome-autogen.sh

=== renamed file 'autogen.sh' => 'autogen.sh.moved'
=== added file 'configure.ac'
--- configure.ac	1970-01-01 00:00:00 +0000
+++ configure.ac	2011-07-29 16:51:11 +0000
@@ -0,0 +1,41 @@
+AC_INIT([bluebird], [0.1.0], [dev@xxxxxxxxxxxxxxxxxxxxxxxxxxx], [bluebird])
+AC_CONFIG_SRCDIR([Makefile.am])
+AC_CONFIG_HEADERS(config.h)
+AM_INIT_AUTOMAKE([dist-bzip2])
+AM_MAINTAINER_MODE
+
+AC_PROG_CC
+AM_PROG_CC_C_O
+AC_DISABLE_STATIC
+AC_PROG_LIBTOOL
+
+AC_PATH_PROG(VALAC, valac, valac)
+AC_SUBST(VALAC)
+
+AH_TEMPLATE([GETTEXT_PACKAGE], [Package name for gettext])
+GETTEXT_PACKAGE=bluebird
+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE")
+AC_SUBST(GETTEXT_PACKAGE)
+AM_GLIB_GNU_GETTEXT
+IT_PROG_INTLTOOL([0.35.0])
+
+AC_SUBST(CFLAGS)
+AC_SUBST(CPPFLAGS)
+AC_SUBST(LDFLAGS)
+
+GLIB_REQUIRED=2.26.0
+
+BLUEBIRD_REQUIRED="glib-2.0 >= $GLIB_REQUIRED
+                   gobject-2.0 >= $GLIB_REQUIRED
+                   gio-unix-2.0 >= $GLIB_REQUIRED
+                   sqlite3"
+
+PKG_CHECK_MODULES(BLUEBIRD, [$BLUEBIRD_REQUIRED])
+AC_SUBST(BLUEBIRD_CFLAGS)
+AC_SUBST(BLUEBIRD_LIBS)
+
+AC_CONFIG_FILES([Makefile
+	src/Makefile
+	po/Makefile.in])
+
+AC_OUTPUT

=== renamed file 'configure.ac' => 'configure.ac.moved'
=== added directory 'po'
=== renamed directory 'po' => 'po.moved'
=== added file 'po/LINGUAS'
--- po/LINGUAS	1970-01-01 00:00:00 +0000
+++ po/LINGUAS	2011-07-29 16:51:11 +0000
@@ -0,0 +1,2 @@
+# please keep this list sorted alphabetically
+#

=== added file 'po/POTFILES.in'
--- po/POTFILES.in	1970-01-01 00:00:00 +0000
+++ po/POTFILES.in	2011-07-29 16:51:11 +0000
@@ -0,0 +1,3 @@
+[encoding: UTF-8]
+# List of source files which contain translatable strings.
+src/main.vala

=== added file 'po/POTFILES.skip'
--- po/POTFILES.skip	1970-01-01 00:00:00 +0000
+++ po/POTFILES.skip	2011-07-29 16:51:11 +0000
@@ -0,0 +1,1 @@
+src/main.c

=== added directory 'src'
=== added file 'src/Makefile.am'
--- src/Makefile.am	1970-01-01 00:00:00 +0000
+++ src/Makefile.am	2011-07-29 16:51:11 +0000
@@ -0,0 +1,41 @@
+NULL = 
+
+bin_PROGRAMS = bluebird
+
+AM_CPPFLAGS = \
+	$(BLUEBIRD_CFLAGS) \
+	-include $(CONFIG_HEADER) \
+	$(NULL)
+
+VALAFLAGS = \
+	--pkg gio-2.0 \
+	--target-glib=2.26 \
+	--pkg sqlite3 \
+	--pkg posix \
+	$(NULL)
+
+bluebird_SOURCES = \
+	zeitgeist-daemon.vala \
+	datamodel.vala \
+	engine.vala \
+	remote.vala \
+	extension.vala \
+	notify.vala \
+	sql.vala \
+	constants.vala \
+	errors.vala \
+	table-lookup.vala \
+	$(NULL)
+
+bluebird_LDADD = \
+	$(BLUEBIRD_LIBS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	$(NULL)
+
+DISTCLEANFILES = \
+	$(NULL)
+
+clean:
+	rm -f *.c *.o *.stamp *.~[0-9]~

=== added file 'src/constants.vala'
--- src/constants.vala	1970-01-01 00:00:00 +0000
+++ src/constants.vala	2011-07-29 16:51:11 +0000
@@ -0,0 +1,102 @@
+/* constants.vala
+ *
+ * Copyright © 2011 Collabora Ltd.
+ *             By Seif Lotfy <seif@xxxxxxxxx>
+ *             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ * Copyright © 2011 Michal Hruby <michal.mhr@xxxxxxxxx>
+ * Copyright © 2011 Manish Sinha <manishsinha@xxxxxxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Zeitgeist
+{
+    namespace Constants
+    {
+        // Paths
+        public static string DATA_PATH;
+        public static string DATABASE_FILE_PATH;
+        public static string DATABASE_FILE_BACKUP_PATH;
+        public static string DEFAULT_LOG_PATH;
+        public static string LOCAL_EXTENSIONS_PATH;
+
+        public const string ZEITGEIST_DATA_FOLDER = "bluebird";
+        public const string USER_EXTENSION_PATH = "";
+
+        // D-Bus
+        public const string DBUS_INTERFACE = "";
+        public const string SIG_EVENT = "asaasay"; // FIXME: remove this?
+
+        // Required version of DB schema
+        public const string CORE_SCHEMA = "core";
+        public const int CORE_SCHEMA_VERSION = 4;
+
+        // configure runtime cache for events
+        // default size is 2000
+        public const uint CACHE_SIZE = 0;
+
+        public void initialize ()
+        {
+            DATA_PATH = Environment.get_variable ("ZEITGEIST_DATA_PATH");
+            if (DATA_PATH == null)
+            {
+                DATA_PATH = Path.build_filename (
+                    Environment.get_user_data_dir (), ZEITGEIST_DATA_FOLDER);
+                if (!FileUtils.test (DATA_PATH, FileTest.IS_DIR))
+                {
+                     DirUtils.create (DATA_PATH, 0755);
+                }
+            }
+            else
+            {
+                if (!FileUtils.test (DATA_PATH, FileTest.IS_DIR)) {
+                    // FIXME: throw error here
+                    critical ("Directory %s doesn't exist.\n", DATA_PATH);
+                }
+            }
+            stdout.printf("DATA_PATH = %s\n", DATA_PATH);
+            
+            DATABASE_FILE_PATH = Environment.get_variable (
+                "ZEITGEIST_DATABASE_PATH");
+            if (DATABASE_FILE_PATH == null)
+            {
+                DATABASE_FILE_PATH = Path.build_filename (DATA_PATH,
+                    "activity.sqlite");
+            }
+            stdout.printf("DATABASE_FILE_PATH = %s\n", DATABASE_FILE_PATH);
+            
+            DATABASE_FILE_BACKUP_PATH = Environment.get_variable (
+                "ZEITGEIST_DATABASE_BACKUP_PATH");
+            if (DATABASE_FILE_BACKUP_PATH == null)
+            {
+                DATABASE_FILE_BACKUP_PATH = Path.build_filename (DATA_PATH,
+                    "activity.sqlite.bck");
+            }
+            stdout.printf("DATABASE_FILE_BACKUP_PATH = %s\n",
+                DATABASE_FILE_BACKUP_PATH);
+            
+            LOCAL_EXTENSIONS_PATH = Path.build_filename (DATA_PATH,
+                "extensions");
+            if (!FileUtils.test (LOCAL_EXTENSIONS_PATH, FileTest.IS_DIR))
+            {
+                // FIXME: Why? There's no need to create it. --RainCT
+                DirUtils.create (LOCAL_EXTENSIONS_PATH, 0755);
+            }
+            stdout.printf("LOCAL_EXTENSIONS_PATH = %s\n", LOCAL_EXTENSIONS_PATH);
+        }
+    }
+}
+
+// vim:expandtab:ts=4:sw=4

=== added file 'src/datamodel.vala'
--- src/datamodel.vala	1970-01-01 00:00:00 +0000
+++ src/datamodel.vala	2011-07-29 16:51:11 +0000
@@ -0,0 +1,403 @@
+/* datamodel.vala
+ *
+ * Copyright © 2011 Collabora Ltd.
+ *             By Seif Lotfy <seif@xxxxxxxxx>
+ *             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ * Copyright © 2011 Manish Sinha <manishsinha@xxxxxxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Zeitgeist
+{
+
+    public struct TimeRange
+    {
+        int64 start;
+        int64 end;
+    }
+
+    public enum ResultType
+    {
+        MOST_RECENT_EVENTS                   = 0,  //   All events with the most
+                                                   // recent events first
+        LEAST_RECENT_EVENTS                  = 1,  //   All events with the oldest
+                                                   // ones first
+        MOST_RECENT_SUBJECTS                 = 2,  //   One event for each subject
+                                                   // only, ordered with the
+                                                   // most recent events first
+        LEAST_RECENT_SUBJECTS                = 3,  //   One event for each subject
+                                                   // only, ordered with oldest
+                                                   // events first
+        MOST_POPULAR_SUBJECTS                = 4,  //   One event for each subject
+                                                   // only, ordered by the
+                                                   // popularity of the subject
+        LEAST_POPULAR_SUBJECTS               = 5,  //   One event for each subject
+                                                   // only, ordered ascendingly
+                                                   // by popularity of the
+                                                   // subject
+        MOST_POPULAR_ACTOR                   = 6,  //   The last event of each
+                                                   // different actor ordered
+                                                   // by the popularity of the
+                                                   // actor
+        LEAST_POPULAR_ACTOR                  = 7,  //   The last event of each
+                                                   // different actor, ordered
+                                                   // ascendingly by the
+                                                   // popularity of the actor
+        MOST_RECENT_ACTOR                    = 8,  //   The actor that has been used
+                                                   // to most recently
+        LEAST_RECENT_ACTOR                   = 9,  //   The actor that has been used
+                                                   // to least recently
+        MOST_RECENT_ORIGIN                   = 10, //   The last event of each
+                                                   // different subject origin.
+        LEAST_RECENT_ORIGIN                  = 11, //   The last event of each
+                                                   // different subject origin,
+                                                   // ordered by least
+                                                   // recently used first
+        MOST_POPULAR_ORIGIN                  = 12, //   The last event of each
+                                                   // different subject origin,
+                                                   // ordered by the
+                                                   // popularity of the origins
+        LEAST_POPULAR_ORIGIN                 = 13, //   The last event of each
+                                                   // different subject origin,
+                                                   // ordered ascendingly by
+                                                   // the popularity of the
+                                                   // origin
+        OLDEST_ACTOR                         = 14, //   The first event of each
+                                                   // different actor
+        MOST_RECENT_SUBJECT_INTERPRETATION   = 15, //   One event for each subject
+                                                   // interpretation only,
+                                                   // ordered with the most
+                                                   // recent events first
+        LEAST_RECENT_SUBJECT_INTERPRETATION  = 16, //   One event for each subject
+                                                   // interpretation only,
+                                                   // ordered with the least
+                                                   // recent events first
+        MOST_POPULAR_SUBJECT_INTERPRETATION  = 17, //   One event for each subject
+                                                   // interpretation only,
+                                                   // ordered by the popularity
+                                                   // of the subject
+                                                   // interpretation
+        LEAST_POPULAR_SUBJECT_INTERPRETATION = 18, //   One event for each subject
+                                                   // interpretation only,
+                                                   // ordered ascendingly by
+                                                   // popularity of the subject
+                                                   // interpretation
+        MOST_RECENT_MIMETYPE                 = 19, //   One event for each mimetype
+                                                   // only ordered with the
+                                                   // most recent events first
+        LEAST_RECENT_MIMETYPE                = 20, //   One event for each mimetype
+                                                   // only ordered with the
+                                                   // least recent events first
+        MOST_POPULAR_MIMETYPE                = 21, //   One event for each mimetype
+                                                   // only ordered by the
+                                                   // popularity of the mimetype
+        LEAST_POPULAR_MIMETYPE               = 22, //   One event for each mimetype
+                                                   // only ordered ascendingly
+                                                   // by popularity of the
+                                                   // mimetype
+        MOST_RECENT_CURRENT_URI              = 23, //   One event for each subject
+                                                   // only by current_uri
+                                                   // instead of uri ordered
+                                                   // with the most recent
+                                                   // events first
+        LEAST_RECENT_CURRENT_URI             = 24, // One event for each subject
+                                                   // only by current_uri
+                                                   // instead of uri ordered
+                                                   // with oldest events first
+        MOST_POPULAR_CURRENT_URI             = 25, //   One event for each subject
+                                                   // only by current_uri
+                                                   // instead of uri ordered
+                                                   // by the popularity of the
+                                                   // subject
+        LEAST_POPULAR_CURRENT_URI            = 26, //   One event for each subject
+                                                   // only by current_uri
+                                                   // instead of uri
+                                                   // ordered ascendingly by
+                                                   // popularity of the subject
+        MOST_RECENT_EVENT_ORIGIN             = 27, //   The last event of each
+                                                   // different origin
+        LEAST_RECENT_EVENT_ORIGIN            = 28, //   The last event of each
+                                                   // different origin, ordered
+                                                   // by least recently used
+                                                   // first
+        MOST_POPULAR_EVENT_ORIGIN           = 29, //   The last event of each
+                                                   // different origin ordered
+                                                   // by the popularity of the
+                                                   // origins
+        LEAST_POPULAR_EVENT_ORIGIN           = 30, //   The last event of each
+                                                   // different origin, ordered
+                                                   // ascendingly by the
+                                                   // popularity of the origin
+    }
+
+    /*
+     * An enumeration class used to define how query results should
+     * be returned from the Zeitgeist engine.
+     */
+    public enum RelevantResultType
+    {
+        RECENT  = 0, // All uris with the most recent uri first
+        RELATED = 1, // All uris with the most related one first
+    }
+
+    /**
+     * Enumeration class defining the possible values for the storage
+     * state of an event subject.
+     * 
+     * The StorageState enumeration can be used to control whether or
+     * not matched events must have their subjects available to the user.
+     * Fx. not including deleted files, files on unplugged USB drives,
+     * files available only when a network is available etc.
+     */
+    public enum StorageState
+    {
+        NOT_AVAILABLE   = 0, // The storage medium of the events
+                             // subjects must not be available to the user
+        AVAILABLE       = 1, // The storage medium of all event subjects
+                             // must be immediately available to the user
+        ANY             = 2  // The event subjects may or may not be available
+    }
+    
+    public class Symbol
+    {
+        private HashTable<string, Symbol> children;
+        public HashTable<string, Symbol> allChildren;
+        public string   name { get; private set; }
+        public string   uri { get; private set; }
+        public string   displayName { get; private set; }
+        public string   doc { get; private set; }
+        
+        public Symbol(){}
+        
+        public List<Symbol> get_parents()
+        {
+            return new List<Symbol>();
+        }
+        
+        public List<Symbol> get_children()
+        {
+            return new List<Symbol>();
+        }
+        
+        public List<Symbol> get_all_children()
+        {
+            return new List<Symbol>();
+        }
+        
+        public bool is_a(Symbol symbol)
+        {
+            return true;
+        }
+    }
+
+    public class Event : Object
+    {
+        public uint32    id { get; set; }
+        public int64     timestamp { get; set; }
+        public string    interpretation { get; set; }
+        public string    manifestation { get; set; }
+        public string    actor { get; set; }
+        public string    origin { get; set; }
+        
+        public GenericArray<Subject> subjects { get; set; }
+        public ByteArray? payload { get; set; }
+
+        construct
+        {
+            subjects = new GenericArray<Subject> ();
+            // FIXME: construct also payload? or make it nullable?
+        }
+
+        public int num_subjects ()
+        {
+            return subjects.length;
+        }
+
+        public void add_subject (Subject subject)
+        {
+            subjects.add (subject);
+        }
+
+        public Event.from_variant (Variant event_variant) { // (asaasay)
+            assert (event_variant.get_type_string () == "(asaasay)");
+
+            VariantIter iter = event_variant.iterator();
+            
+            assert (iter.n_children() == 3);
+            VariantIter event_array = iter.next_value().iterator();
+            VariantIter subjects_array = iter.next_value().iterator();
+            VariantIter payload_array = iter.next_value().iterator();
+
+            var event_props = event_array.n_children ();
+            assert (event_props >= 5);
+            id = (uint32) uint64.parse ((string) event_array.next_value());
+            timestamp = int64.parse ((string) event_array.next_value());
+            interpretation = (string) event_array.next_value();
+            manifestation = (string) event_array.next_value();
+            actor = (string) event_array.next_value();
+            // let's keep this compatible with older clients
+            if (event_props >= 6) origin = (string) event_array.next_value();
+            
+            for (int i = 0; i < subjects_array.n_children(); ++i) {
+                Variant subject_variant = subjects_array.next_value();
+                subjects.add(new Subject.from_variant(subject_variant));
+            }
+            
+            // Parse payload...
+        }
+
+        public Variant to_variant ()
+        {
+            var vb = new VariantBuilder(new VariantType("(asaasay)"));
+            
+            vb.open(new VariantType("as"));
+            vb.add("s", id.to_string ());
+            vb.add("s", timestamp.to_string ());
+            vb.add("s", interpretation);
+            vb.add("s", manifestation);
+            vb.add("s", actor);
+            vb.add("s", origin);
+            vb.close();
+            
+            vb.open(new VariantType("aas"));
+            for (int i = 0; i < subjects.length; ++i) {
+                vb.add_value(subjects[i].to_variant());
+            }
+            vb.close();
+            
+            vb.open(new VariantType("ay"));
+            // payload...
+            vb.close();
+
+            return vb.end();
+        }
+
+        public void debug_print ()
+        {
+            stdout.printf ("id: %d\t" +
+                           "timestamp: %li\n" +
+                           "actor: %s\n" +
+                           "interpretation: %s\n" +
+                           "manifestation: %s\n" +
+                           "origin: %s\n" +
+                           "num subjects: %d\n",
+                           id, timestamp, actor, interpretation,
+                           manifestation, origin, subjects.length);
+            for (int i = 0; i < subjects.length; i++)
+            {
+                var s = subjects[i];
+                stdout.printf ("  Subject #%d:\n" +
+                               "    uri: %s\n" +
+                               "    interpretation: %s\n" +
+                               "    manifestation: %s\n" +
+                               "    mimetype: %s\n" +
+                               "    origin: %s\n" +
+                               "    text: %s\n" +
+                               "    current_uri: %s\n" +
+                               "    storage: %s\n",
+                               i, s.uri, s.interpretation, s.manifestation,
+                               s.mimetype, s.origin, s.text, s.current_uri,
+                               s.storage);
+            }
+
+        }
+
+    }
+
+    namespace Events
+    {
+
+        public static GenericArray<Event> from_variant (Variant vevents)
+        {
+            GenericArray<Event> events = new GenericArray<Event> ();
+
+            assert (vevents.get_type_string () == "a(asaasay)");
+
+            var iter = vevents.iterator ();
+            Variant? event = iter.next_value ();
+            while (event != null)
+            {
+                events.add (new Event.from_variant (event));
+                event = iter.next_value ();
+            }
+
+            return events;
+        }
+
+        public static Variant to_variant (GenericArray<Event> events)
+        {
+            var vb = new VariantBuilder(new VariantType("a(asaasay)"));
+
+            vb.open(new VariantType("a(asaasay)"));
+            for (int i = 0; i < events.length; ++i)
+                vb.add_value (events[i].to_variant ());
+            vb.close();
+
+            return vb.end();
+        }
+
+    }
+
+    public class Subject : Object
+    {
+
+        public string uri { get; set; }
+        public string interpretation { get; set; }
+        public string manifestation { get; set; }
+        public string mimetype { get; set; }
+        public string origin { get; set; }
+        public string text { get; set; }
+        public string storage { get; set; }
+        public string current_uri { get; set; }
+
+        public Subject.from_variant (Variant subject_variant)
+        {
+            VariantIter iter = subject_variant.iterator();
+            
+            var subject_props = iter.n_children ();
+            assert (subject_props >= 7);
+            uri = (string) iter.next_value();
+            interpretation = (string) iter.next_value();
+            manifestation = (string) iter.next_value();
+            origin = (string) iter.next_value();
+            mimetype = (string) iter.next_value();
+            text = (string) iter.next_value();
+            storage = (string) iter.next_value();
+            // let's keep this compatible with older clients
+            if (subject_props >= 8) current_uri = (string) iter.next_value();
+        }
+
+        public Variant to_variant()
+        {
+            var vb = new VariantBuilder(new VariantType("as"));
+            vb.open(new VariantType("as"));
+            vb.add("s", uri);
+            vb.add("s", interpretation);
+            vb.add("s", manifestation);
+            vb.add("s", origin);
+            vb.add("s", mimetype);
+            vb.add("s", text);
+            vb.add("s", storage);
+            vb.add("s", current_uri);
+            vb.close();
+            return vb.end();
+        }
+
+    }
+
+}
+
+// vim:expandtab:ts=4:sw=4

=== added file 'src/engine.vala'
--- src/engine.vala	1970-01-01 00:00:00 +0000
+++ src/engine.vala	2011-07-29 16:51:11 +0000
@@ -0,0 +1,335 @@
+/* engine.vala
+ *
+ * Copyright © 2011 Collabora Ltd.
+ *             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ *
+ * Based upon a Python implementation (2009-2011) by:
+ *  Markus Korn <thekorn@xxxxxxx>
+ *  Mikkel Kamstrup Erlandsen <mikkel.kamstrup@xxxxxxxxx>
+ *  Seif Lotfy <seif@xxxxxxxxx>
+ *  Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+using Zeitgeist;
+using Zeitgeist.SQLite;
+
+public class Engine : Object
+{
+
+    Zeitgeist.SQLite.ZeitgeistDatabase database;
+    unowned Sqlite.Database db;
+    
+    TableLookup interpretations_table;
+    TableLookup manifestations_table;
+    TableLookup mimetypes_table;
+    TableLookup actors_table;
+    
+    uint32 last_id;
+
+    public Engine () throws EngineError
+    {
+        database = new Zeitgeist.SQLite.ZeitgeistDatabase();
+        db = database.database;
+        last_id = database.get_last_id();
+        
+        interpretations_table = new TableLookup(database, "interpretation");
+        manifestations_table = new TableLookup(database, "manifestation");
+        mimetypes_table = new TableLookup(database, "mimetype");
+        actors_table = new TableLookup(database, "actor");
+        
+        // FIXME: load extensions
+    }
+
+    public GenericArray<Event> get_events(uint32[] event_ids,
+            BusName? sender=null) throws EngineError
+    {
+        // TODO: Consider if we still want the cache. This should be done
+        //  once everything is working, since it adds unneeded complexity.
+        //  It'd also benchmark it again first, we may have better options
+        //  to enhance the performance of SQLite now, and event processing
+        //  will be faster now being C.
+        
+        Sqlite.Statement stmt;
+        int rc;
+        
+        assert (event_ids.length > 0);
+        var sql_condition = new StringBuilder ();
+        sql_condition.append_printf ("%u", event_ids[0]);
+        for (int i = 1; i < event_ids.length; ++i) {
+            sql_condition.append_printf (", %u", event_ids[i]);
+        }
+        string sql = """
+            SELECT * FROM event_view
+            WHERE id IN (""" + sql_condition.str + """)
+            """;
+
+        rc = db.prepare_v2 (sql, -1, out stmt);
+        database.assert_query_success(rc, "SQL error");
+
+        var events = new GenericArray<Event>();
+        events.length = event_ids.length;
+
+        while ((rc = stmt.step()) == Sqlite.ROW)
+        {
+            uint32 event_id = (uint32) uint64.parse(
+                stmt.column_text (EventViewRows.ID));
+            int index = search_event_ids_array(event_ids, event_id);
+            assert (index >= 0);
+            
+            // FIXME: get real values from TableLookup
+            
+            Event event;
+            if (events[index] != null)
+            {
+                // We already got this event before, so we only need to
+                // take the missing subject data.
+                event = events[index];
+            }
+            else
+            {
+                event = new Event ();
+                event.id = event_id;
+                event.timestamp = stmt.column_int64 (EventViewRows.TIMESTAMP);
+                event.interpretation = interpretations_table.get_value (
+                    stmt.column_int (EventViewRows.INTERPRETATION));
+                event.manifestation = manifestations_table.get_value (
+                    stmt.column_int (EventViewRows.MANIFESTATION));
+                event.actor = actors_table.get_value (
+                    stmt.column_int (EventViewRows.ACTOR));
+                event.origin = stmt.column_text (
+                    EventViewRows.EVENT_ORIGIN_URI);
+                // FIXME: payload
+                events[index] = event;
+            }
+            
+            Subject subject = new Subject ();
+            subject.uri = stmt.column_text (EventViewRows.SUBJECT_URI);
+            subject.text = stmt.column_text (EventViewRows.SUBJECT_TEXT);
+            subject.storage = stmt.column_text (EventViewRows.SUBJECT_STORAGE);
+            subject.origin = stmt.column_text (EventViewRows.SUBJECT_ORIGIN_URI);
+            subject.current_uri = stmt.column_text (
+                EventViewRows.SUBJECT_CURRENT_URI);
+            subject.interpretation = interpretations_table.get_value (
+                stmt.column_int (EventViewRows.SUBJECT_INTERPRETATION));
+            subject.manifestation = manifestations_table.get_value (
+                stmt.column_int (EventViewRows.SUBJECT_MANIFESTATION));
+            subject.mimetype = mimetypes_table.get_value (
+                stmt.column_int (EventViewRows.SUBJECT_MIMETYPE));
+            
+            event.add_subject(subject);
+        }
+        if (rc != Sqlite.DONE)
+        {
+            printerr ("Error: %d, %s\n", rc, db.errmsg ());
+            // FIXME: throw some error??
+        }
+        
+        for (int i = 0; i < event_ids.length; ++i)
+        {
+            events[i].debug_print ();
+            stdout.printf ("\n");
+        }
+
+        // FIXME: make sure nulls become NULL_EVENT
+        // FIXME: what happens if a query requests the same element in
+        //        more than one place?
+
+        return events;
+    }
+
+    public uint32[] insert_events (GenericArray<Event> events,
+        BusName? sender=null) throws EngineError
+    {
+        uint32[] event_ids = new uint32[events.length];
+        database.begin_transaction();
+        for (int i = 0; i < events.length; ++i)
+        {
+            event_ids[i] = insert_event (events[i], sender);
+            print("%u\n", event_ids[i]);
+        }
+        database.end_transaction();
+        return event_ids;
+    }
+
+    public uint32 insert_event (Event event,
+        BusName? sender=null)
+    {
+        assert (event.id == 0);
+        assert (event.num_subjects () > 0);
+        // FIXME: make sure event timestamp is sane
+        
+        /* FIXME:
+        if (event.interpretation == interpretation.MOVE_EVENT)
+        {
+            // check all subjects for uri != current_uri
+        }
+        else
+        {
+            // check all subjects for uri == current_uri
+        }
+        */
+
+        event.id = ++last_id;
+
+        // FIXME: call pre_insert extension hooks
+        //        if afterwards event == null, return and ignore the event
+
+        // FIXME: store the payload
+        // payload_id = store_payload (event);
+
+        // Make sure all the URIs, mimetypes, texts and storage are inserted
+        {
+            var uris = new GenericArray<string> ();
+            var mimetypes = new GenericArray<string> ();
+            var texts = new GenericArray<string> ();
+            var storages = new GenericArray<string> ();
+
+            if (event.origin != "")
+                uris.add (event.origin);
+
+            for (int i = 0; i < event.num_subjects(); ++i)
+            {
+                unowned Subject subject = event.subjects[i];
+                uris.add (subject.uri);
+                uris.add (subject.current_uri);
+                if (subject.origin != "")
+                    uris.add (subject.origin);
+                if (subject.mimetype != "")
+                    mimetypes.add (subject.mimetype);
+                if (subject.text != "")
+                    texts.add (subject.text);
+                if (subject.storage != "")
+                    storages.add (subject.storage);
+            }
+
+            try
+            {
+                if (uris.length > 0)
+                    database.insert_or_ignore_into_table ("uri", uris);
+                if (mimetypes.length > 0)
+                    database.insert_or_ignore_into_table ("mimetype", mimetypes);
+                if (texts.length > 0)
+                    database.insert_or_ignore_into_table ("text", texts);
+                if (storages.length > 0)
+                    database.insert_or_ignore_into_table ("storage", storages);
+            }
+            catch (EngineError e)
+            {
+                warning ("Can't insert data for event: " + e.message);
+                return 0;
+            }
+        }
+
+        // FIXME: Should we add something just like TableLookup but with LRU
+        //        for those? Or is embedding the query faster? Needs testing!
+
+        int rc;
+        unowned Sqlite.Statement insert_stmt = database.event_insertion_stmt;
+
+        insert_stmt.bind_int64 (1, event.id);
+        insert_stmt.bind_int64 (2, event.timestamp);
+        insert_stmt.bind_int64 (3,
+            interpretations_table.get_id (event.interpretation));
+        insert_stmt.bind_int64 (4,
+            manifestations_table.get_id (event.manifestation));
+        insert_stmt.bind_int64 (5, actors_table.get_id (event.actor));
+        insert_stmt.bind_text (6, event.origin);
+        insert_stmt.bind_int64 (7, 0 /*payload_id*/);
+
+        for (int i = 0; i < event.num_subjects(); ++i)
+        {
+            insert_stmt.reset();
+            
+            unowned Subject subject = event.subjects[i];
+            
+            insert_stmt.bind_text (8, subject.uri);
+            insert_stmt.bind_text (9, subject.current_uri);
+            insert_stmt.bind_int64 (10,
+                interpretations_table.get_id (subject.interpretation));
+            insert_stmt.bind_int64 (11,
+                manifestations_table.get_id (subject.manifestation));
+            insert_stmt.bind_text (12, subject.origin);
+            insert_stmt.bind_int64 (13,
+                mimetypes_table.get_id (subject.mimetype));
+            // FIXME: Consider a storages_table table. Too dangerous?
+            insert_stmt.bind_text (14, subject.storage);
+            
+            if ((rc = insert_stmt.step()) != Sqlite.DONE) {
+                if (rc != Sqlite.CONSTRAINT)
+                {
+                    warning ("SQL error: %d, %s\n", rc, db.errmsg ());
+                    return 0;
+                }
+                // This event was already registered.
+                // Rollback last_id and return the ID of the original event
+                --last_id;
+
+                unowned Sqlite.Statement retrieval_stmt =
+                    database.id_retrieval_stmt;
+
+                retrieval_stmt.reset ();
+
+                retrieval_stmt.bind_int64 (1, event.timestamp);
+                retrieval_stmt.bind_int64 (2,
+                    interpretations_table.get_id (event.interpretation));
+                retrieval_stmt.bind_int64 (3,
+                    manifestations_table.get_id (event.manifestation));
+                retrieval_stmt.bind_int64 (4, actors_table.get_id (event.actor));
+                
+                if ((rc = retrieval_stmt.step()) != Sqlite.ROW) {
+                    warning ("SQL error: %d, %s\n", rc, db.errmsg ());
+                    return 0;
+                }
+                
+                return retrieval_stmt.column_int (0);
+            }
+        }
+
+        /*
+        if (event.interpretation == MOVE_EVENT)
+        {
+            handle_move_event (event);
+        }
+        */
+
+        return event.id;
+    }
+
+    /**
+     * Clear all resources Engine is using (close database connection,
+     * unload extensions, etc.).
+     *
+     * After executing this method on an Engine instance, no other function
+     * of said instance may be called.
+     */
+    public void close ()
+    {
+        // FIXME: unload extensions
+        database.close();
+    }
+
+    private static int search_event_ids_array(uint32[] arr, uint32 needle)
+    {
+        for (int i = 0; i < arr.length; ++i)
+            if (arr[i] == needle)
+                return i;
+        return -1;
+    }
+
+}
+
+// vim:expandtab:ts=4:sw=4

=== added file 'src/errors.vala'
--- src/errors.vala	1970-01-01 00:00:00 +0000
+++ src/errors.vala	2011-07-29 16:51:11 +0000
@@ -0,0 +1,32 @@
+/* zeitgeist-daemon.vala
+ *
+ * Copyright © 2011 Collabora Ltd.
+ *             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ *
+ * Based upon a Python implementation (2009-2011) by:
+ *  Markus Korn <thekorn@xxxxxxx>
+ *  Mikkel Kamstrup Erlandsen <mikkel.kamstrup@xxxxxxxxx>
+ *  Seif Lotfy <seif@xxxxxxxxx>
+ *  Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+public errordomain EngineError
+{
+    DATABASE_ERROR
+}
+
+// vim:expandtab:ts=4:sw=4

=== added file 'src/extension.vala'
--- src/extension.vala	1970-01-01 00:00:00 +0000
+++ src/extension.vala	2011-07-29 16:51:11 +0000
@@ -0,0 +1,112 @@
+/* extension.vala
+ *
+ * Copyright © 2011 Manish Sinha <manishsinha@xxxxxxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Zeitgeist.Extension
+{
+    /**
+     * Base class for all extensions
+     *
+     * FIXME: figure out what to do with this. I don't really see
+     *        what the point for it is, since D-Bus accessible stuff is
+     *        usually exported on a new interface. --RainCT
+     * Every extension has to define a list of accessible methods as
+     * 'PUBLIC_METHODS'. The constructor of an Extension object takes the
+     * engine object it extends as the only argument.
+     * ---
+     *
+     * In addition each extension has a set of hooks to control how events are
+     * inserted and retrieved from the log. These hooks can either block the
+     * event completely, modify it, or add additional metadata to it.
+     */
+    public abstract class Extension : Object
+    {
+        /**
+         * This method gets called before Zeitgeist stops.
+         * 
+         * Execution of this method isn't guaranteed, and you shouldn't do
+         * anything slow in there.
+         */
+        public abstract void unload();
+    
+        /**
+         * Hook applied to all events before they are inserted into the
+         * log. The returned event is progressively passed through all
+         * extensions before the final result is inserted.
+         *
+         * To block an event completely simply return null.
+         * The event may also be modified or completely substituted for
+         * another event.
+         *
+         * The default implementation of this method simply returns the
+         * event as is.
+         *
+         * @param event: A Event instance
+         * @param sender: The D-Bus bus name of the client
+         * @returns: The filtered event instance to insert into the log
+         */
+        public abstract Event pre_insert_event(Event event, BusName sender);
+    
+        /**
+         * Hook applied to all events after they are inserted into the log.
+         * 
+         * @param event: A Event instance
+         * @param sender: The D-Bus bus name of the client
+         * @returns: Nothing
+         */
+        public abstract void post_insert_event(Event event, BusName sender);
+    
+        /**
+         * Hook applied to all events before they are returned to a client.
+         * The event returned from this method is progressively passed
+         * through all extensions before they final result is returned to
+         * the client.
+         *
+         * To prevent an event from ever leaving the server process simply
+         * return null. The event may also be changed in place
+         * or fully substituted for another event.
+         *
+         * The default implementation of this method simply returns the
+         * event as is.
+         *
+         * @param event: A Eventinstance or None
+         * @param sender: The D-Bus bus name of the client
+         * @returns: The filtered event instance as the client should see it
+         */
+        public abstract Event get_event(Event event, BusName sender);
+    
+        /**
+         * Hook applied after events have been deleted from the log.
+         *
+         * @param ids: A list of event ids for the events that has been deleted
+         * @param sender: The unique DBus name for the client triggering the delete
+         * @returns: Nothing
+         */
+        public abstract void post_delete_events(uint32[] ids, BusName sender);
+    
+        /**
+         * Hook applied before events are deleted from the log.
+         *
+         * @param ids: A list of event ids for the events requested to be deleted
+         * @param sender: The unique DBus name for the client triggering the delete
+         * @returns: The filtered list of event ids which should be deleted
+         */
+        public abstract Event pre_delete_events(uint32[] ids, BusName sender);
+    }
+}
+// vim:expandtab:ts=4:sw=4

=== added file 'src/notify.vala'
--- src/notify.vala	1970-01-01 00:00:00 +0000
+++ src/notify.vala	2011-07-29 16:51:11 +0000
@@ -0,0 +1,25 @@
+/* notify.vala
+ *
+ * Copyright © 2011 Collabora Ltd.
+ *             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+public class MonitorManager : Object {
+
+}
+
+// vim:expandtab:ts=4:sw=4

=== added file 'src/remote.vala'
--- src/remote.vala	1970-01-01 00:00:00 +0000
+++ src/remote.vala	2011-07-29 16:51:11 +0000
@@ -0,0 +1,94 @@
+/* remote.vala
+ *
+ * Copyright © 2011 Collabora Ltd.
+ *             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ * Copyright © 2011 Michal Hruby <michal.mhr@xxxxxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Zeitgeist
+{
+
+    [DBus (name = "org.gnome.zeitgeist.Log")]
+    public interface RemoteLogInterface : Object
+    {
+
+        public abstract TimeRange delete_events (
+            uint32[] event_ids,
+            BusName sender
+        ) throws IOError;
+
+        // This is stupid. We don't need it.
+        //public void DeleteLog ();
+
+        public abstract uint32[] find_event_ids (
+            TimeRange time_range,
+            [DBus (signature = "a(asaasay)")] Variant event_templates,
+            uint storage_state, uint num_events, uint result_type,
+            BusName sender
+        ) throws IOError;
+
+        [DBus (signature = "a(asaasay)")]
+        public abstract Variant find_events (
+            TimeRange time_range,
+            [DBus (signature = "a(asaasay)")] Variant event_templates,
+            uint storage_state, uint num_events, uint result_type,
+            BusName sender
+        ) throws IOError;
+
+        public abstract string[] find_related_uris (
+            TimeRange time_range,
+            [DBus (signature = "a(asaasay)")] Variant event_templates,
+            [DBus (signature = "a(asaasay)")] Variant result_event_templates,
+            uint storage_state, uint num_events, uint result_type,
+            BusName sender
+        ) throws IOError;
+
+        [DBus (signature = "a(asaasay)")]
+        public abstract Variant get_events (
+            uint32[] event_ids,
+            BusName sender
+        ) throws IOError;
+
+        public abstract uint32[] insert_events (
+            [DBus (signature = "a(asaasay)")] Variant events,
+            BusName sender
+        ) throws IOError;
+
+        public abstract void install_monitor (
+            ObjectPath monitor_path,
+            TimeRange time_range,
+            [DBus (signature = "a(asaasay)")] Variant event_templates,
+            BusName owner
+        ) throws IOError;
+
+        public abstract void remove_monitor (
+            ObjectPath monitor_path,
+            BusName owner
+        ) throws IOError;
+
+        public abstract void quit () throws IOError;
+
+        public abstract string[] extensions { owned get; }
+
+        [DBus (signature = "iii")]
+        public abstract Variant version { owned get; }
+
+    }
+
+}
+
+// vim:expandtab:ts=4:sw=4

=== added file 'src/sql.vala'
--- src/sql.vala	1970-01-01 00:00:00 +0000
+++ src/sql.vala	2011-07-29 16:51:11 +0000
@@ -0,0 +1,198 @@
+/* sql.vala
+ *
+ * Copyright © 2011 Collabora Ltd.
+ *             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ * Copyright © 2011 Manish Sinha <manishsinha@xxxxxxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+using Sqlite;
+using Zeitgeist;
+
+namespace Zeitgeist.SQLite
+{
+
+    public enum EventViewRows
+    {
+        ID,
+        TIMESTAMP,
+        INTERPRETATION,
+        MANIFESTATION,
+        ACTOR,
+        PAYLOAD,
+        SUBJECT_URI,
+        SUBJECT_ID,
+        SUBJECT_INTERPRETATION,
+        SUBJECT_MANIFESTATION,
+        SUBJECT_ORIGIN,
+        SUBJECT_ORIGIN_URI,
+        SUBJECT_MIMETYPE,
+        SUBJECT_TEXT,
+        SUBJECT_STORAGE,
+        SUBJECT_STORAGE_STATE,
+        ORIGIN,
+        EVENT_ORIGIN_URI,
+        SUBJECT_CURRENT_URI,
+        SUBJECT_ID_CURRENT
+    }
+
+    public class ZeitgeistDatabase : Object
+    {
+
+        public Sqlite.Statement event_insertion_stmt;
+        public Sqlite.Statement id_retrieval_stmt;
+
+        // FIXME: Should this be accessible from engine.vala or not?
+        //  Probably it should, since otherwise there won't be much
+        //  functionallity left for engine.vala.
+        public Database database;
+
+        public ZeitgeistDatabase () throws EngineError
+        {
+            string sqlite_filepath = Constants.DATABASE_FILE_PATH;
+
+            int rc = Database.open_v2(
+                sqlite_filepath,
+                out database);
+            assert_query_success(rc, "Can't open database");
+
+            // FIXME: check DB integrity, create it if needed, etc.
+
+            prepare_queries ();
+        }
+
+        public uint32 get_last_id () throws EngineError
+        {
+            int last_id = -1;
+            int rc = database.exec ("SELECT MAX(id) FROM event",
+                (n_columns, values, column_names) =>
+                {
+                    last_id = int.parse(values[0]);
+                    return 0;
+                }, null);
+            assert_query_success(rc, "Can't query database");
+            assert (last_id != -1);
+            return last_id;
+        }
+
+        public void insert_or_ignore_into_table (string table_name,
+            GenericArray<string> values) throws EngineError
+        {
+            int rc;
+
+            assert (values.length > 0);
+            var sql = new StringBuilder ();
+            sql.append ("INSERT OR IGNORE INTO ");
+            sql.append (table_name);
+            sql.append (" (value) SELECT ?");
+            for (int i = 1; i < values.length; ++i)
+                sql.append (" UNION SELECT ?");
+
+            Statement stmt;
+            rc = database.prepare_v2 (sql.str, -1, out stmt);
+            assert_query_success (rc, "SQL error");
+
+            for (int i = 0; i < values.length; ++i)
+                stmt.bind_text (i+1, values[i]);
+
+            rc = stmt.step();
+            assert_query_success(rc, "SQL error", Sqlite.DONE);
+        }
+
+        public void begin_transaction () throws EngineError
+        {
+            int rc = database.exec ("BEGIN");
+            assert_query_success (rc, "Can't start transaction");
+        }
+
+        public void end_transaction () throws EngineError
+        {
+            int rc = database.exec ("COMMIT");
+            assert_query_success (rc, "Can't commit transaction");
+        }
+
+        public void close ()
+            {
+            // FIXME: make sure symbol tables are consistent (ie.
+            //        _fix_cache is empty)
+            
+            // SQLite connection is implicitly closed upon destruction
+            database = null;
+        }
+
+        /**
+         * Ensure `rc' is SQLITE_OK. If it isn't, print an error message
+         * and throw an error.
+         *
+         * @param rc error code returned by a SQLite call
+         * @param msg message to print if `rc' indicates an error
+         * @throws EngineError
+         **/
+        public void assert_query_success (int rc, string msg,
+            int success_code=Sqlite.OK) throws EngineError
+        {
+            if (rc != success_code)
+            {
+                string error_message = "%s: %d, %s".printf(
+                    msg, rc, database.errmsg ());
+                warning ("%s\n", error_message);
+                throw new EngineError.DATABASE_ERROR(error_message);
+            }
+        }
+
+        private void prepare_queries () throws EngineError
+        {
+            int rc;
+            string sql;
+
+            // Event insertion statement
+            sql = """
+                INSERT INTO event (
+                    id, timestamp, interpretation, manifestation, actor,
+                    origin, payload, subj_id, subj_id_current,
+                    subj_interpretation, subj_manifestation, subj_origin,
+                    subj_mimetype, subj_text, subj_storage
+                ) VALUES (
+                    ?, ?, ?, ?, ?,
+                    (SELECT id FROM uri WHERE value=?),
+                    ?,
+                    (SELECT id FROM uri WHERE value=?),
+                    (SELECT id FROM uri WHERE value=?),
+                    ?, ?,
+                    (SELECT id FROM uri WHERE value=?),
+                    ?,
+                    (SELECT id FROM text WHERE value=?),
+                    (SELECT id from storage WHERE value=?)
+                )""";
+
+            rc = database.prepare_v2 (sql, -1, out event_insertion_stmt);
+            assert_query_success (rc, "Insertion query error");
+
+            // Event ID retrieval statement
+            sql = """
+                SELECT id FROM event
+                WHERE timestamp=? AND interpretation=? AND
+                    manifestation=? AND actor=?
+                """;
+            rc = database.prepare_v2 (sql, -1, out id_retrieval_stmt);
+            assert_query_success (rc, "Event ID retrieval query error");
+        }
+
+    }
+
+}
+
+// vim:expandtab:ts=4:sw=4

=== added file 'src/table-lookup.vala'
--- src/table-lookup.vala	1970-01-01 00:00:00 +0000
+++ src/table-lookup.vala	2011-07-29 16:51:11 +0000
@@ -0,0 +1,113 @@
+/* table-lookup.vala
+ *
+ * Copyright © 2011 Collabora Ltd.
+ *             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ *
+ * Based upon a Python implementation (2009-2011) by:
+ *  Markus Korn <thekorn@xxxxxxx>
+ *  Mikkel Kamstrup Erlandsen <mikkel.kamstrup@xxxxxxxxx>
+ *  Seif Lotfy <seif@xxxxxxxxx>
+ *  Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Zeitgeist.SQLite
+{
+
+    public class TableLookup : Object
+    {
+
+        unowned Sqlite.Database db;
+
+        string table;
+        private HashTable<int, string> id_to_value;
+        private HashTable<string, int> value_to_id;
+
+        public TableLookup (ZeitgeistDatabase database, string table_name)
+        {
+            db = database.database;
+            table = table_name;
+            id_to_value = new HashTable<int, string>(direct_hash, direct_equal);
+            value_to_id = new HashTable<string, int>(str_hash, str_equal);
+            
+            int rc = db.exec ("SELECT id, value FROM " + table,
+                (n_columns, values, column_names) =>
+                {
+                    id_to_value.insert (int.parse(values[0]), values[1]);
+                    value_to_id.insert (values[1], int.parse(values[0]));
+                    return 0;
+                }, null);
+            if (rc != Sqlite.OK)
+            {
+                critical ("Can't init %s table: %d, %s\n", table,
+                    rc, db.errmsg ());
+            }
+            
+            /* FIXME: add this:
+            cursor.execute("""
+                CREATE TEMP TRIGGER update_cache_%(table)s
+                BEFORE DELETE ON %(table)s
+                BEGIN
+                    INSERT INTO _fix_cache VALUES ("%(table)s", OLD.id);
+                END;
+                """ % {"table": table})
+            */
+        }
+
+        public int get_id (string name)
+        {
+            int id = value_to_id.lookup (name);
+            if (id == 0)
+            {
+                int rc;
+                Sqlite.Statement stmt;
+                
+                string sql = "INSERT INTO " + table + " (value) VALUES (?)";
+                if ((rc = db.prepare_v2 (sql, -1, out stmt)) != Sqlite.OK) {
+                    critical ("SQL error: %d, %s\n", rc, db.errmsg ());
+                }
+                
+                stmt.bind_text(1, name);
+                if (stmt.step() != Sqlite.DONE) {
+                    critical ("SQL error: %d, %s\n", rc, db.errmsg ());
+                }
+                
+                id = (int) db.last_insert_rowid();
+                
+                id_to_value.insert (id, name);
+                value_to_id.insert (name, id);
+            }
+            return id;
+        }
+
+        public string get_value (int id)
+        {
+            // When we fetch an event, it either was already in the database
+            // at the time Zeitgeist started or it was inserted later -using
+            // Zeitgeist-, so here we always have the data in memory already.
+            return id_to_value.lookup (id);
+        }
+
+        public void remove (int id)
+        {
+            string name = id_to_value.lookup (id);
+            id_to_value.remove (id);
+            value_to_id.remove (name);
+        }
+
+    }
+
+}

=== added file 'src/zeitgeist-daemon.vala'
--- src/zeitgeist-daemon.vala	1970-01-01 00:00:00 +0000
+++ src/zeitgeist-daemon.vala	2011-07-29 16:51:11 +0000
@@ -0,0 +1,223 @@
+/* zeitgeist-daemon.vala
+ *
+ * Copyright © 2011 Seif Lotfy <seif@xxxxxxxxx>
+ * Copyright © 2011 Collabora Ltd.
+ *             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Zeitgeist
+{
+
+    public class Daemon : Object, RemoteLogInterface
+    {
+
+        private static Daemon instance;
+
+        private static MainLoop mainloop;
+        private Engine engine;
+        private MonitorManager notifications;
+
+        public string[] extensions
+        {
+            owned get
+            {
+                string[] ext = { "extension1", "extension2" };
+                return ext;
+            }
+        }
+
+        public Variant version
+        {
+            owned get
+            {
+                var vb = new VariantBuilder (new VariantType ("(iii)"));
+                vb.add ("i", 0);
+                vb.add ("i", 1);
+                vb.add ("i", 2);
+                return vb.end ();
+            }
+        }
+
+        public Daemon ()
+        {
+            stdout.printf("Hi!\n");
+            
+            try
+            {
+                //engine = new Engine();
+            }
+            catch (EngineError e)
+            {
+                // FIXME
+                //quit ();
+            }
+            
+            notifications = new MonitorManager();
+
+            // FIXME: tmp:
+            var vb = new VariantBuilder(new VariantType("(asaasay)"));
+            vb.open(new VariantType("as"));
+                vb.add("s", "0"); // id
+                vb.add("s", "123"); // timestamp
+                vb.add("s", "stfu:OpenEvent"); // interpretation
+                vb.add("s", "stfu:UserActivity"); // manifestation
+                vb.add("s", "firefox"); // actor
+                vb.add("s", "nowhere"); // origin
+            vb.close();
+            vb.open(new VariantType("aas"));
+                vb.open(new VariantType("as"));
+                    vb.add("s", "file:///tmp/foo.txt"); // uri
+                    vb.add("s", "stfu:Document"); // interpretation
+                    vb.add("s", "stfu:File"); // manifestation
+                    vb.add("s", "file:///tmp"); // origin
+                    vb.add("s", "text/plain"); // mimetype
+                    vb.add("s", "this item has no text... rly!"); // text
+                    vb.add("s", "368c991f-8b59-4018-8130-3ce0ec944157"); // storage
+                    vb.add("s", "file:///tmp/foo.txt"); // current_uri
+                vb.close();
+            vb.close();
+            vb.open(new VariantType("ay"));
+            vb.close();
+
+            var events = new GenericArray<Event>();
+            events.add(new Event.from_variant(vb.end()));
+            engine.insert_events(events);
+            // ---
+        }
+
+        // FIXME
+        public Variant get_events (uint32[] event_ids, BusName sender)
+        {
+            GenericArray<Event> events = engine.get_events (event_ids);
+            return Events.to_variant (events);
+        }
+
+        // FIXME
+        public string[] find_related_uris (TimeRange time_range,
+                Variant event_templates,
+                Variant result_event_templates,
+                uint storage_state, uint num_events, uint result_type,
+                BusName sender)
+        {
+            return new string[] { "hi", "bar" };
+        }
+
+        // FIXME
+        public uint32[] find_event_ids (TimeRange time_range,
+                Variant event_templates,
+                uint storage_state, uint num_events, uint result_type,
+                BusName sender)
+        {
+            return new uint[] { 1, 2, 3 };
+        }
+
+        // FIXME
+        public Variant find_events (TimeRange time_range,
+                Variant event_templates,
+                uint storage_state, uint num_events, uint result_type,
+                BusName sender)
+        {
+            return 1;
+        }
+
+        // FIXME
+        public uint32[] insert_events (
+                Variant vevents,
+                BusName sender)
+        {
+            var events = Events.from_variant (vevents);
+            for (int i = 0; i < events.length; i++)
+            {
+                stdout.printf ("============== Inserting event: =============\n");
+                events[i].debug_print ();
+            }
+            return new uint[] { 1, 2, 3 };
+        }
+
+        // FIXME
+        public TimeRange delete_events (uint32[] event_ids, BusName sender)
+        {
+            return TimeRange() { start = 30, end = 40 };
+        }
+
+        public void quit ()
+        {
+            stdout.printf ("BYE\n");
+            engine.close ();
+            mainloop.quit ();
+        }
+
+        public void install_monitor (ObjectPath monitor_path,
+                TimeRange time_range,
+                Variant event_templates,
+                BusName owner)
+        {
+            stdout.printf("i'll let you know!\n");
+        }
+
+        public void remove_monitor (ObjectPath monitor_path, BusName owner)
+        {
+            stdout.printf("bye my friend\n");
+        }
+
+        static void on_bus_aquired (DBusConnection conn)
+        {
+            instance = new Daemon ();
+            try
+            {
+                conn.register_object (
+                    "/org/gnome/zeitgeist/log/activity",
+                    (RemoteLogInterface) instance);
+            }
+            catch (IOError e)
+            {
+                stderr.printf ("Could not register service\n");
+            }
+        }
+
+        static void run ()
+        {
+            // TODO: look at zeitgeist/singleton.py
+            Bus.own_name (BusType.SESSION, "org.gnome.zeitgeist.Engine",
+                BusNameOwnerFlags.NONE,
+                on_bus_aquired,
+                () => {},
+                () => stderr.printf ("Could not aquire name\n"));
+            mainloop = new MainLoop ();
+            mainloop.run ();
+        }
+
+        static void handle_exit ()
+        {
+            instance.quit ();
+        }
+
+        static int main (string[] args)
+        {
+            Constants.initialize ();
+            
+            Posix.signal (Posix.SIGHUP, handle_exit);
+            Posix.signal (Posix.SIGTERM, handle_exit);
+            
+            run ();
+            return 0;
+        }
+
+    }
+
+}
+// vim:expandtab:ts=4:sw=4

=== added directory 'test'
=== renamed directory 'test' => 'test.moved'
=== added directory 'test/dbus'
=== added file 'test/dbus/remote-test.orig.py'
--- test/dbus/remote-test.orig.py	1970-01-01 00:00:00 +0000
+++ test/dbus/remote-test.orig.py	2011-07-29 16:51:11 +0000
@@ -0,0 +1,623 @@
+#! /usr/bin/python
+# -.- coding: utf-8 -.-
+
+# Zeitgeist
+#
+# Copyright © 2009-2011 Seif Lotfy <seif@xxxxxxxxx>
+# Copyright © 2009-2011 Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+# Copyright © 2009-2011 Mikkel Kamstrup Erlandsen <mikkel.kamstrup@xxxxxxxxx>
+# Copyright © 2009-2011 Markus Korn <thekorn@xxxxxx>
+# Copyright © 2011 Collabora Ltd.
+#             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+#             By Seif Lotfy <seif@xxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+import os
+import sys
+import logging
+import signal
+import time
+import tempfile
+import shutil
+import pickle
+from subprocess import Popen, PIPE
+
+# DBus setup
+import gobject
+from dbus.mainloop.glib import DBusGMainLoop
+DBusGMainLoop(set_as_default=True)
+from dbus.exceptions import DBusException
+
+from zeitgeist.datamodel import (Event, Subject, Interpretation, Manifestation,
+	TimeRange, StorageState, DataSource)
+
+import testutils
+from testutils import parse_events
+
+class ZeitgeistRemoteAPITest(testutils.RemoteTestCase):
+	
+	def __init__(self, methodName):
+		super(ZeitgeistRemoteAPITest, self).__init__(methodName)
+	
+	def testInsertAndGetEvent(self):
+		ev = Event.new_for_values(timestamp=123,
+					interpretation=Interpretation.ACCESS_EVENT,
+					manifestation=Manifestation.USER_ACTIVITY,
+					actor="Freak Mamma")
+		subj = Subject.new_for_values(uri="void://foobar",
+					interpretation=Interpretation.DOCUMENT,
+					manifestation=Manifestation.FILE_DATA_OBJECT)
+		ev.append_subject(subj)
+		ids = self.insertEventsAndWait([ev])
+		events = self.getEventsAndWait(ids)
+		self.assertEquals(1, len(ids))
+		self.assertEquals(1, len(events))
+		
+		ev = events[0]
+		self.assertTrue(isinstance(ev, Event))
+		self.assertEquals("123", ev.timestamp)
+		self.assertEquals(Interpretation.ACCESS_EVENT, ev.interpretation)
+		self.assertEquals(Manifestation.USER_ACTIVITY, ev.manifestation)
+		self.assertEquals("Freak Mamma", ev.actor)
+		self.assertEquals(1, len(ev.subjects))
+		self.assertEquals("void://foobar", ev.subjects[0].uri)
+		self.assertEquals(Interpretation.DOCUMENT, ev.subjects[0].interpretation)
+		self.assertEquals(Manifestation.FILE_DATA_OBJECT, ev.subjects[0].manifestation)
+	
+	def testFindTwoOfThreeEvents(self):
+		ev1 = Event.new_for_values(timestamp=400,
+					interpretation=Interpretation.ACCESS_EVENT,
+					manifestation=Manifestation.USER_ACTIVITY,
+					actor="Boogaloo")	
+		ev2 = Event.new_for_values(timestamp=500,
+					interpretation=Interpretation.ACCESS_EVENT,
+					manifestation=Manifestation.USER_ACTIVITY,
+					actor="Boogaloo")
+		ev3 = Event.new_for_values(timestamp=600,
+					interpretation=Interpretation.SEND_EVENT,
+					manifestation=Manifestation.USER_ACTIVITY,
+					actor="Boogaloo")
+		subj1 = Subject.new_for_values(uri="foo://bar",
+					interpretation=Interpretation.DOCUMENT,
+					manifestation=Manifestation.FILE_DATA_OBJECT)
+		subj2 = Subject.new_for_values(uri="foo://baz",
+					interpretation=Interpretation.IMAGE,
+					manifestation=Manifestation.FILE_DATA_OBJECT)
+		subj3 = Subject.new_for_values(uri="foo://quiz",
+					interpretation=Interpretation.AUDIO,
+					manifestation=Manifestation.FILE_DATA_OBJECT)
+		ev1.append_subject(subj1)
+		ev2.append_subject(subj1)
+		ev2.append_subject(subj2)
+		ev3.append_subject(subj2)
+		ev3.append_subject(subj3)
+		ids = self.insertEventsAndWait([ev1, ev2, ev3])
+		self.assertEquals(3, len(ids))
+		
+		events = self.getEventsAndWait(ids)
+		self.assertEquals(3, len(events))		
+		for event in events:
+			self.assertTrue(isinstance(event, Event))
+			self.assertEquals(Manifestation.USER_ACTIVITY, event.manifestation)
+			self.assertEquals("Boogaloo", event.actor)
+		
+		# Search for everything
+		ids = self.findEventIdsAndWait([], num_events=3)
+		self.assertEquals(3, len(ids)) # (we can not trust the ids because we don't have a clean test environment)
+		
+		# Search for some specific templates
+		subj_templ1 = Subject.new_for_values(manifestation=Manifestation.FILE_DATA_OBJECT)
+		subj_templ2 = Subject.new_for_values(interpretation=Interpretation.IMAGE)
+		event_template = Event.new_for_values(
+					actor="Boogaloo",
+					interpretation=Interpretation.ACCESS_EVENT,
+					subjects=[subj_templ1,subj_templ2])
+		ids = self.findEventIdsAndWait([event_template],
+						num_events=10)
+		self.assertEquals(1, len(ids))
+		
+	def testUnicodeInsert(self):
+		events = parse_events("test/data/unicode_event.js")
+		ids = self.insertEventsAndWait(events)
+		self.assertEquals(len(ids), len(events))
+		result_events = self.getEventsAndWait(ids)
+		self.assertEquals(len(ids), len(result_events))
+		
+	def testGetEvents(self):
+		events = parse_events("test/data/five_events.js")
+		ids = self.insertEventsAndWait(events) + [1000, 2000]
+		result = self.getEventsAndWait(ids)
+		self.assertEquals(len(filter(None, result)), len(events))
+		self.assertEquals(len(filter(lambda event: event is None, result)), 2)
+	
+	def testMonitorInsertEvents(self):
+		result = []
+		mainloop = self.create_mainloop()
+		tmpl = Event.new_for_values(interpretation="stfu:OpenEvent")
+		events = parse_events("test/data/five_events.js")
+		
+		def notify_insert_handler(time_range, events):
+			result.extend(events)
+			mainloop.quit()
+		
+		def notify_delete_handler(time_range, event_ids):
+			mainloop.quit()
+			self.fail("Unexpected delete notification")
+			
+		self.client.install_monitor(TimeRange.always(), [tmpl],
+			notify_insert_handler, notify_delete_handler)
+		self.client.insert_events(events)
+		mainloop.run()
+		
+		self.assertEquals(2, len(result))
+		
+	def testMonitorDeleteEvents(self):
+		result = []
+		mainloop = self.create_mainloop()
+		events = parse_events("test/data/five_events.js")
+		
+		def notify_insert_handler(time_range, events):
+			event_ids = map(lambda ev : ev.id, events)
+			self.client.delete_events(event_ids)
+		
+		def notify_delete_handler(time_range, event_ids):
+			mainloop.quit()
+			result.extend(event_ids)
+			
+			
+		self.client.install_monitor(TimeRange(125, 145), [],
+			notify_insert_handler, notify_delete_handler)
+		
+		self.client.insert_events(events)
+		mainloop.run()
+		
+		self.assertEquals(2, len(result))
+	
+	def testMonitorDeleteNonExistingEvent(self):
+		result = []
+		mainloop = self.create_mainloop(None)
+		events = parse_events("test/data/five_events.js")
+		
+		def timeout():
+			# We want this timeout - we should not get informed
+			# about deletions of non-existing events
+			mainloop.quit()
+			return False
+
+		def notify_insert_handler(time_range, events):
+			event_ids = map(lambda ev : ev.id, events)
+			self.client.delete_events([9999999])
+		
+		def notify_delete_handler(time_range, event_ids):
+			mainloop.quit()
+			self.fail("Notified about deletion of non-existing events %s", events)
+			
+		self.client.install_monitor(TimeRange(125, 145), [],
+			notify_insert_handler, notify_delete_handler)
+		
+		gobject.timeout_add_seconds(5, timeout)
+		self.client.insert_events(events)
+		mainloop.run()
+	
+	def testTwoMonitorsDeleteEvents(self):
+		result1 = []
+		result2 = []
+		mainloop = self.create_mainloop()
+		events = parse_events("test/data/five_events.js")
+		
+		def check_ok():
+			if len(result1) == 2 and len(result2) == 2:
+				mainloop.quit()
+
+		def notify_insert_handler1(time_range, events):
+			event_ids = map(lambda ev : ev.id, events)
+			self.client.delete_events(event_ids)
+		
+		def notify_delete_handler1(time_range, event_ids):
+			result1.extend(event_ids)
+			check_ok()
+		
+		def notify_delete_handler2(time_range, event_ids):
+			result2.extend(event_ids)
+			check_ok()
+			
+		self.client.install_monitor(TimeRange(125, 145), [],
+			notify_insert_handler1, notify_delete_handler1)
+		
+		self.client.install_monitor(TimeRange(125, 145), [],
+			lambda x, y: x, notify_delete_handler2)
+		
+		self.client.insert_events(events)
+		mainloop.run()
+		
+		self.assertEquals(2, len(result1))
+		self.assertEquals(2, len(result2))
+
+	def testMonitorInstallRemoval(self):
+		result = []
+		mainloop = self.create_mainloop()
+		tmpl = Event.new_for_values(interpretation="stfu:OpenEvent")
+		
+		def notify_insert_handler(notification_type, events):
+			pass
+		
+		def notify_delete_handler(time_range, event_ids):
+			mainloop.quit()
+			self.fail("Unexpected delete notification")
+		
+		mon = self.client.install_monitor(TimeRange.always(), [tmpl],
+			notify_insert_handler, notify_delete_handler)
+		
+		def removed_handler(result_state):
+			result.append(result_state)
+			mainloop.quit()
+		
+		self.client.remove_monitor(mon, removed_handler)
+		mainloop.run()
+		self.assertEquals(1, len(result))
+		self.assertEquals(1, result.pop())
+		
+	def testDeleteEvents(self):
+		""" delete all events with actor == firefox """
+		events = parse_events("test/data/five_events.js")
+		self.insertEventsAndWait(events)
+		
+		event = Event()
+		event.actor = "firefox"
+		
+		# get event ids with actor == firefox
+		ff_ids = self.findEventIdsAndWait([event])
+		# delete this events
+		time_range = self.deleteEventsAndWait(ff_ids)
+		# got timerange of deleted events
+		self.assertEquals(2, len(time_range))
+		# get all events, the one with actor == firefox should
+		# not be there
+		ids = self.findEventIdsAndWait([])
+		self.assertEquals(2, len(ids))
+		self.assertEquals(0, len(set(ff_ids) & set(ids)))
+		
+	def testFindByRandomActorAndGet(self):
+		events = parse_events("test/data/five_events.js")
+		self.insertEventsAndWait(events)
+		
+		template = Event.new_for_values(actor="/usr/bliblablu")
+		
+		ids = self.findEventIdsAndWait([template])
+		self.assertEquals(len(ids), 0)
+		
+		events = self.getEventsAndWait(ids)
+		self.assertEquals(len(events), 0)
+	
+	def testFindRelated(self):
+		events = parse_events("test/data/apriori_events.js")
+		self.insertEventsAndWait(events)
+		
+		uris = self.findRelatedAndWait(["i2"], num_events=2, result_type=1)
+		self.assertEquals(uris, ["i3", "i1"])
+		
+		uris = self.findRelatedAndWait(["i2"], num_events=2, result_type=0)
+		self.assertEquals(uris, ["i1", "i3"])
+		
+	def testFindEventsForValues(self):
+		events = parse_events("test/data/apriori_events.js")
+		self.insertEventsAndWait(events)
+		
+		result = self.findEventsForValuesAndWait(actor="firefox", num_events=1)
+		self.assertEquals(len(result), 1)
+		self.assertEquals(result[0].actor, "firefox")
+	
+	def testFindEventsWithStringPayload(self):
+		mainloop = self.create_mainloop()
+		payload = "Hello World"
+		def callback(ids):
+			def callback2(events):
+				mainloop.quit()
+				self.assertEquals(events[0].payload, map(ord, payload))
+			self.client.get_events(ids, callback2)
+		events = [Event.new_for_values(actor=u"boo", timestamp=124, subject_uri="file://yomomma")]
+		events[0].payload = payload
+		self.client.insert_events(events, callback)
+		mainloop.run()
+		
+	def testFindEventsWithNonASCIIPayload(self):
+		mainloop = self.create_mainloop()
+		payload = u"äöü".encode("utf-8")
+		def callback(ids):
+			def callback2(events):
+				mainloop.quit()
+				self.assertEquals(events[0].payload, map(ord, payload))
+			self.client.get_events(ids, callback2)
+		events = [Event.new_for_values(actor=u"boo", timestamp=124, subject_uri="file://yomomma")]
+		events[0].payload = payload
+		self.client.insert_events(events, callback)
+		mainloop.run()
+		
+	def testFindEventsWithBinaryPayload(self):
+		mainloop = self.create_mainloop()
+		payload = pickle.dumps(1234)
+		def callback(ids):
+			def callback2(events):
+				mainloop.quit()
+				self.assertEquals(events[0].payload, map(ord, payload))
+			self.client.get_events(ids, callback2)
+		events = [Event.new_for_values(actor=u"boo", timestamp=124, subject_uri="file://yomomma")]
+		events[0].payload = payload
+		self.client.insert_events(events, callback)
+		mainloop.run()
+		
+class ZeitgeistRemoteInterfaceTest(unittest.TestCase):
+	
+	def setUp(self):
+		from _zeitgeist import engine
+		from _zeitgeist.engine import sql, constants
+		engine._engine = None
+		sql.unset_cursor()
+		self.saved_data = {
+			"datapath": constants.DATA_PATH,
+			"database": constants.DATABASE_FILE,
+			"extensions": constants.USER_EXTENSION_PATH,
+		}
+		constants.DATA_PATH = tempfile.mkdtemp(prefix="zeitgeist.datapath.")
+		constants.DATABASE_FILE = ":memory:"
+		constants.USER_EXTENSION_PATH = os.path.join(constants.DATA_PATH, "extensions")
+		
+	def tearDown(self):
+		from _zeitgeist.engine import constants
+		shutil.rmtree(constants.DATA_PATH)
+		constants.DATA_PATH = self.saved_data["datapath"]
+		constants.DATABASE_FILE = self.saved_data["database"]
+		constants.USER_EXTENSION_PATH = self.saved_data["extensions"]
+	
+	def testQuit(self):
+		"""calling Quit() on the remote interface should shutdown the
+		engine in a clean way"""
+		from _zeitgeist.engine.remote import RemoteInterface
+		interface = RemoteInterface()
+		self.assertEquals(interface._engine.is_closed(), False)
+		interface.Quit()
+		self.assertEquals(interface._engine.is_closed(), True)
+
+
+class ZeitgeistRemoteDataSourceRegistryTest(testutils.RemoteTestCase):
+	
+	_ds1 = [
+		"www.example.com/foo",
+		"Foo Source",
+		"Awakes the foo in you",
+		[
+			Event.new_for_values(subject_manifestation = "!stfu:File"),
+			Event.new_for_values(interpretation = "stfu:CreateEvent")
+		],
+	]
+
+	_ds2 = [
+		"www.example.org/bar",
+		u"© mwhahaha çàéü",
+		u"ThŊ§ ıs teĦ ün↓çØÐe¡",
+		[]
+	]
+
+	_ds2b = [
+		"www.example.org/bar", # same unique ID as _ds2
+		u"This string has been translated into the ASCII language",
+		u"Now the unicode is gone :(",
+		[
+			Event.new_for_values(subject_manifestation = "nah"),
+		],
+	]
+	
+	def __init__(self, methodName):
+		super(ZeitgeistRemoteDataSourceRegistryTest, self).__init__(methodName)
+	
+	def _assertDataSourceEquals(self, dsdbus, dsref):
+		self.assertEquals(dsdbus[DataSource.UniqueId], dsref[0])
+		self.assertEquals(dsdbus[DataSource.Name], dsref[1])
+		self.assertEquals(dsdbus[DataSource.Description], dsref[2])
+		self.assertEquals(len(dsdbus[DataSource.EventTemplates]), len(dsref[3]))
+		#for i, template in enumerate(dsref[3]):
+		#	tmpl = dsdbus[DataSource.EventTemplates][i]
+		#	self.assertEquals(ZgEvent.get_plain(tmpl), ZgEvent.get_plain(template))
+	
+	def testPresence(self):
+		""" Ensure that the DataSourceRegistry extension is there """
+		iface = self.client._iface # we know that client._iface is as clean as possible
+		registry = iface.get_extension("DataSourceRegistry", "data_source_registry")
+		registry.GetDataSources()
+	
+	def testGetDataSourcesEmpty(self):
+		self.assertEquals(self.client._registry.GetDataSources(), [])
+	
+	def testRegisterDataSource(self):
+		self.client.register_data_source(*self._ds1)
+		datasources = list(self.client._registry.GetDataSources())
+		self.assertEquals(len(datasources), 1)
+		self._assertDataSourceEquals(datasources[0], self._ds1)
+	
+	def testRegisterDataSourceUnicode(self):
+		self.client.register_data_source(*self._ds2)
+		datasources = list(self.client._registry.GetDataSources())
+		self.assertEquals(len(datasources), 1)
+		self._assertDataSourceEquals(datasources[0], self._ds2)
+	
+	def testRegisterDataSourceWithCallback(self):
+		self.client.register_data_source(*self._ds1, enabled_callback=lambda x: True)
+	
+	def testRegisterDataSources(self):
+		# Insert two data-sources
+		self.client._registry.RegisterDataSource(*self._ds1)
+		self.client._registry.RegisterDataSource(*self._ds2)
+		
+		# Verify that they have been inserted correctly
+		datasources = list(self.client._registry.GetDataSources())
+		self.assertEquals(len(datasources), 2)
+		datasources.sort(key=lambda x: x[DataSource.UniqueId])
+		self._assertDataSourceEquals(datasources[0], self._ds1)
+		self._assertDataSourceEquals(datasources[1], self._ds2)
+		
+		# Change the information of the second data-source
+		self.client._registry.RegisterDataSource(*self._ds2b)
+		
+		# Verify that it changed correctly
+		datasources = list(self.client._registry.GetDataSources())
+		self.assertEquals(len(datasources), 2)
+		datasources.sort(key=lambda x: x[DataSource.UniqueId])
+		self._assertDataSourceEquals(datasources[0], self._ds1)
+		self._assertDataSourceEquals(datasources[1], self._ds2b)
+	
+	def testSetDataSourceEnabled(self):
+		# Insert a data-source -- it should be enabled by default
+		self.client._registry.RegisterDataSource(*self._ds1)
+		ds = list(self.client._registry.GetDataSources())[0]
+		self.assertEquals(ds[DataSource.Enabled], True)
+		
+		# Now we can choose to disable it...
+		self.client._registry.SetDataSourceEnabled(self._ds1[0], False)
+		ds = list(self.client._registry.GetDataSources())[0]
+		self.assertEquals(ds[DataSource.Enabled], False)
+		
+		# And enable it again!
+		self.client._registry.SetDataSourceEnabled(self._ds1[0], True)
+		ds = list(self.client._registry.GetDataSources())[0]
+		self.assertEquals(ds[DataSource.Enabled], True)
+
+	def testGetDataSourceFromId(self):
+		# Insert a data-source -- and then retrieve it by id
+		self.client._registry.RegisterDataSource(*self._ds1)
+		ds = self.client._registry.GetDataSourceFromId(self._ds1[0])
+		self._assertDataSourceEquals(ds, self._ds1)
+		
+		# Retrieve a data-source from an id that has not been registered
+		self.assertRaises(DBusException,
+				  self.client._registry.GetDataSourceFromId,
+				  self._ds2[0])
+
+	def testDataSourceSignals(self):
+		mainloop = self.create_mainloop()
+		
+		global hit
+		hit = 0
+		
+		def cb_registered(datasource):
+			global hit
+			self.assertEquals(hit, 0)
+			hit = 1
+		
+		def cb_enabled(unique_id, enabled):
+			global hit
+			if hit == 1:
+				self.assertEquals(enabled, False)
+				hit = 2
+			elif hit == 2:
+				self.assertEquals(enabled, True)
+				hit = 3
+				# We're done -- change this if we figure out how to force a
+				# disconnection from the bus, so we can also check the
+				# DataSourceDisconnected signal.
+				mainloop.quit()
+			else:
+				self.fail("Unexpected number of signals: %d." % hit)
+		
+		#def cb_disconnect(datasource):
+		#	self.assertEquals(hit, 3)
+		#	mainloop.quit()
+		
+		# Connect to signals
+		self.client._registry.connect('DataSourceRegistered', cb_registered)
+		self.client._registry.connect('DataSourceEnabled', cb_enabled)
+		#self.client._registry.connect('DataSourceDisconnected', cb_disconnect)
+		
+		# Register data-source, disable it, enable it again
+		gobject.idle_add(self.testSetDataSourceEnabled)
+		
+		mainloop.run()
+	
+	def testRegisterDataSourceEnabledCallbackOnRegister(self):
+		mainloop = self.create_mainloop()
+		
+		def callback(enabled):
+			mainloop.quit()
+		self.client.register_data_source(*self._ds1, enabled_callback=callback)
+		
+		mainloop.run()
+	
+	def testRegisterDataSourceEnabledCallbackOnChange(self):
+		mainloop = self.create_mainloop()
+		global hit
+		hit = 0
+		
+		# Register a callback
+		def callback(enabled):
+			global hit
+			if hit == 0:
+				# Register callback
+				hit = 1
+			elif hit == 1:
+				# Disable callback
+				mainloop.quit()
+			else:
+				self.fail("Unexpected number of signals: %d." % hit)
+		self.client.register_data_source(*self._ds1)
+		self.client.set_data_source_enabled_callback(self._ds1[0], callback)
+		
+		# Disable the data-source
+		self.client._registry.SetDataSourceEnabled(self._ds1[0], False)
+
+		mainloop.run()
+
+class ZeitgeistRemotePropertiesTest(testutils.RemoteTestCase):
+	
+	def __init__(self, methodName):
+		super(ZeitgeistRemotePropertiesTest, self).__init__(methodName)
+	
+	def testVersion(self):
+		self.assertTrue(len(self.client.get_version()) >= 2)
+	
+	def testExtensions(self):
+		self.assertEquals(
+			sorted(self.client.get_extensions()),
+			["Blacklist", "DataSourceRegistry"]
+		)
+		self.assertEquals(
+			sorted(self.client._iface.extensions()),
+			["Blacklist", "DataSourceRegistry"]
+		)
+
+
+class ZeitgeistDaemonTest(unittest.TestCase):
+	
+	def setUp(self):
+		self.env = os.environ.copy()
+		self.datapath = tempfile.mkdtemp(prefix="zeitgeist.datapath.")
+		self.env.update({
+			"ZEITGEIST_DATABASE_PATH": ":memory:",
+			"ZEITGEIST_DATA_PATH": self.datapath,
+		})
+		
+	def tearDown(self):
+		shutil.rmtree(self.datapath)
+	
+	def testSIGHUP(self):
+		"""sending a SIGHUP signal to a running deamon instance results
+		in a clean shutdown"""
+		daemon = testutils.RemoteTestCase._safe_start_daemon(env=self.env)
+		os.kill(daemon.pid, signal.SIGHUP)
+		err = daemon.wait()
+		self.assertEqual(err, 0)
+
+
+if __name__ == "__main__":
+	unittest.main()

=== added file 'test/dbus/remote-test.py'
--- test/dbus/remote-test.py	1970-01-01 00:00:00 +0000
+++ test/dbus/remote-test.py	2011-07-29 16:51:11 +0000
@@ -0,0 +1,87 @@
+#! /usr/bin/python
+# -.- coding: utf-8 -.-
+
+# Zeitgeist
+#
+# Copyright © 2009-2011 Seif Lotfy <seif@xxxxxxxxx>
+# Copyright © 2009-2011 Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+# Copyright © 2009-2011 Mikkel Kamstrup Erlandsen <mikkel.kamstrup@xxxxxxxxx>
+# Copyright © 2009-2011 Markus Korn <thekorn@xxxxxx>
+# Copyright © 2011 Collabora Ltd.
+#             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+#             By Seif Lotfy <seif@xxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+import os
+import sys
+import logging
+import signal
+import time
+import tempfile
+import shutil
+import pickle
+from subprocess import Popen, PIPE
+
+# DBus setup
+import gobject
+from dbus.mainloop.glib import DBusGMainLoop
+DBusGMainLoop(set_as_default=True)
+from dbus.exceptions import DBusException
+
+from zeitgeist.datamodel import (Event, Subject, Interpretation, Manifestation,
+	TimeRange, StorageState, DataSource)
+
+import testutils
+from testutils import parse_events
+
+class ZeitgeistRemoteAPITest(testutils.RemoteTestCase):
+
+	def __init__(self, methodName):
+		super(ZeitgeistRemoteAPITest, self).__init__(methodName)
+
+
+class ZeitgeistRemoteInterfaceTest(testutils.RemoteTestCase):
+
+	def testQuit(self):
+		"""
+		Calling Quit() on the remote interface should shutdown the
+		engine in a clean way.
+		"""
+		from _zeitgeist.engine.remote import RemoteInterface
+		interface = RemoteInterface()
+		interface.Quit()
+
+	def testSIGHUP(self):
+		"""
+		Sending a SIGHUP signal to a running deamon instance should result
+		in a clean shutdown.
+		"""
+		code = self.kill_daemon(signal.SIGHUP)
+		self.assertEqual(code, 0)
+		self.spawn_daemon()
+
+
+class ZeitgeistRemotePropertiesTest(testutils.RemoteTestCase):
+
+	def __init__(self, methodName):
+		super(ZeitgeistRemotePropertiesTest, self).__init__(methodName)
+	
+	def testVersion(self):
+		self.assertTrue(len(self.client.get_version()) >= 2)
+
+
+if __name__ == "__main__":
+	unittest.main()

=== added file 'test/dbus/run-all-tests.py'
--- test/dbus/run-all-tests.py	1970-01-01 00:00:00 +0000
+++ test/dbus/run-all-tests.py	2011-07-29 16:51:11 +0000
@@ -0,0 +1,141 @@
+#!/usr/bin/python
+# -.- coding: utf-8 -.-
+
+# Zeitgeist
+#
+# Copyright © 2009 Mikkel Kamstrup Erlandsen <mikkel.kamstrup@xxxxxxxxx>
+# Copyright © 2009-2010 Markus Korn <thekorn@xxxxxx>
+# Copyright © 2011 Collabora Ltd.
+#             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import glob
+import unittest
+import doctest
+import logging
+import sys
+import tempfile
+import shutil
+import signal
+
+from optparse import OptionParser
+from testutils import RemoteTestCase
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
+
+# Find the test/ directory
+TESTDIR = os.path.dirname(os.path.abspath(__file__))
+DOCTESTS = glob.glob(os.path.join(TESTDIR, "*.rst"))
+
+def doctest_setup(test):
+	test._datapath = tempfile.mkdtemp(prefix="zeitgeist.datapath.")
+	test._env = os.environ.copy()
+	os.environ.update({
+		"ZEITGEIST_DATABASE_PATH": ":memory:",
+		"ZEITGEIST_DATA_PATH": test._datapath
+	})
+	test._daemon = RemoteTestCase._safe_start_daemon()
+	
+def doctest_teardown(test):
+	os.kill(test._daemon.pid, signal.SIGKILL)
+	test._daemon.wait()
+	shutil.rmtree(test._datapath)
+	os.environ = test._env
+	
+def iter_tests(suite):
+	for test in suite:
+		if isinstance(test, unittest.TestSuite):
+			for t in iter_tests(test):
+				yield t
+		else:
+			yield test
+			
+def get_test_name(test):
+	return ".".join((test.__class__.__module__, test.__class__.__name__, test._testMethodName))
+	
+def load_tests(module, pattern):
+	suite = unittest.defaultTestLoader.loadTestsFromModule(module)
+	for test in iter_tests(suite):
+		name = get_test_name(test)
+		if pattern is not None:
+			for p in pattern:
+				if name.startswith(p):
+					yield test
+					break
+		else:
+			yield test
+			
+def check_name(filename, pattern):
+	if pattern is None:
+		return True
+	for p in pattern:
+		if os.path.basename(filename).startswith(p):
+			return True
+	return False
+
+def compile_suite(pattern=None):
+	# Create a test suite to run all tests
+	
+	# first, add all doctests
+	arguments = {
+		"module_relative": False,
+		"globs": {"sys": sys},
+		"setUp": doctest_setup,
+		"tearDown": doctest_teardown,
+	}
+	doctests = filter(lambda x: check_name(str(x), pattern), DOCTESTS)
+	suite = doctest.DocFileSuite(*doctests, **arguments)
+
+	# Add all of the tests from each file that ends with "-test.py"
+	for fname in os.listdir(TESTDIR):
+		if fname.endswith("-test.py"):
+			fname = os.path.basename(fname)[:-3] # Get the filename and chop off ".py"
+			module = __import__(fname)
+			tests = list(load_tests(module, pattern))
+			suite.addTests(tests)
+	return suite
+
+if __name__ == "__main__":
+	parser = OptionParser()
+	parser.add_option("-v", action="count", dest="verbosity")
+	(options, args) = parser.parse_args()
+
+	if options.verbosity:
+		# do more fine grained stuff later
+		# redirect all debugging output to stderr
+		logging.basicConfig(stream=sys.stderr)
+	else:
+		logging.basicConfig(filename="/dev/null")
+	
+	from testutils import DBusPrivateMessageBus
+	bus = DBusPrivateMessageBus()
+	err = bus.run(ignore_errors=True)
+	if err:
+		print >> sys.stderr, "*** Failed to setup private bus, error was: %s" %err
+	else:
+		print >> sys.stderr, "*** Testsuite is running using a private dbus bus"
+		config = bus.dbus_config.copy()
+		config.update({"DISPLAY": bus.DISPLAY, "pid.Xvfb": bus.display.pid})
+		print >> sys.stderr, "*** Configuration: %s" %config
+	try:
+		os.environ["ZEITGEIST_DEFAULT_EXTENSIONS"] = \
+			"_zeitgeist.engine.extensions.blacklist.Blacklist," \
+			"_zeitgeist.engine.extensions.datasource_registry.DataSourceRegistry"
+		suite = compile_suite(args or None)
+		# Run all of the tests
+		unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite)
+	finally:
+		bus.quit(ignore_errors=True)

=== added file 'test/dbus/testutils.py'
--- test/dbus/testutils.py	1970-01-01 00:00:00 +0000
+++ test/dbus/testutils.py	2011-07-29 16:51:11 +0000
@@ -0,0 +1,366 @@
+# -.- coding: utf-8 -.-
+
+# Zeitgeist
+#
+# Copyright © 2009 Mikkel Kamstrup Erlandsen <mikkel.kamstrup@xxxxxxxxx>
+# Copyright © 2009-2010 Markus Korn <thekorn@xxxxxx>
+# Copyright © 2011 Collabora Ltd.
+#             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+import os
+import time
+import sys
+import signal
+import tempfile
+import shutil
+from subprocess import Popen, PIPE
+
+# DBus setup
+import gobject
+from dbus.mainloop.glib import DBusGMainLoop
+DBusGMainLoop(set_as_default=True)
+
+# Import local Zeitgeist modules
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
+from zeitgeist.client import ZeitgeistDBusInterface, ZeitgeistClient
+from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation, TimeRange
+
+# Json handling is special in Python 2.5...
+try:
+	import json
+except ImportError:
+	# maybe the user is using python < 2.6
+	import simplejson as json
+
+def dict2event(d):
+	ev = Event()
+	ev[0][Event.Id] = d.get("id", "").encode("UTF-8")
+	ev.timestamp = str(d.get("timestamp", ""))
+	ev.interpretation = str(d.get("interpretation", "").encode("UTF-8"))
+	ev.manifestation = str(d.get("manifestation", "").encode("UTF-8"))
+	ev.actor = str(d.get("actor", "").encode("UTF-8"))
+	ev.origin = str(d.get("origin", "").encode("UTF-8"))
+	ev.payload = str(d.get("payload", "").encode("UTF-8"))
+	
+	subjects = d.get("subjects", [])
+	for sd in subjects:
+		subj = Subject()
+		subj.uri = str(sd.get("uri", "").encode("UTF-8"))
+		subj.current_uri = str(sd.get("current_uri", "")).encode("UTF-8")
+		subj.interpretation = str(sd.get("interpretation", "").encode("UTF-8"))
+		subj.manifestation = str(sd.get("manifestation", "").encode("UTF-8"))
+		subj.origin = str(sd.get("origin", "").encode("UTF-8"))
+		subj.mimetype = str(sd.get("mimetype", "").encode("UTF-8"))
+		subj.text = str(sd.get("text", "").encode("UTF-8"))
+		subj.storage = str(sd.get("storage", "").encode("UTF-8"))
+		ev.append_subject(subj)
+	return ev
+	
+def parse_events(path):
+	data = json.load(file(path))
+	events = map(dict2event, data)
+	return events
+
+def import_events(path, engine):
+	"""
+	Load a collection of JSON event definitions into 'engine'. Fx:
+	
+		import_events("test/data/single_event.js", self.engine)
+	"""
+	events = parse_events(path)
+	
+	return engine.insert_events(events)
+
+class RemoteTestCase (unittest.TestCase):
+	"""
+	Helper class to implement unit tests against a
+	remote Zeitgeist process
+	"""
+	
+	@staticmethod
+	def _get_pid(matching_string):
+		p1 = Popen(["ps", "aux"], stdout=PIPE, stderr=PIPE)
+		p2 = Popen(["grep", matching_string], stdin=p1.stdout, stderr=PIPE, stdout=PIPE)
+		return p2.communicate()[0]
+		
+	@staticmethod
+	def _safe_start_subprocess(cmd, env, timeout=1, error_callback=None):
+		""" starts `cmd` in a subprocess and check after `timeout`
+		if everything goes well"""
+		process = Popen(cmd, stderr=PIPE, stdout=PIPE, env=env)
+		# give the process some time to wake up
+		time.sleep(timeout)
+		error = process.poll()
+		if error:
+			cmd = " ".join(cmd)
+			error = "'%s' exits with error %i." %(cmd, error)
+			if error_callback:
+				error += " *** %s" %error_callback(*process.communicate())
+			raise RuntimeError(error)
+		return process
+		
+	@staticmethod
+	def _safe_start_daemon(env=None, timeout=1):
+		if env is None:
+			env = os.environ.copy()
+			
+		def error_callback(stdout, stderr):
+			if "--replace" in stderr:
+				return "%r | %s" %(stderr, RemoteTestCase._get_pid(
+					"./src/bluebird").replace("\n", "|"))
+			else:
+				return stderr
+			
+		return RemoteTestCase._safe_start_subprocess(
+			("./src/bluebird", "--no-datahub"), env, timeout, error_callback
+		)
+	
+	def __init__(self, methodName):
+		super(RemoteTestCase, self).__init__(methodName)
+		self.daemon = None
+		self.client = None
+	
+	def spawn_daemon(self):
+		self.daemon = self._safe_start_daemon(env=self.env)
+	
+	def kill_daemon(self, kill_signal=signal.SIGKILL):
+		os.kill(self.daemon.pid, kill_signal)
+		return self.daemon.wait()
+		
+	def setUp(self):
+		assert self.daemon is None
+		assert self.client is None
+		self.env = os.environ.copy()
+		self.datapath = tempfile.mkdtemp(prefix="zeitgeist.datapath.")
+		self.env.update({
+			"ZEITGEIST_DATABASE_PATH": ":memory:",
+			"ZEITGEIST_DATA_PATH": self.datapath,
+			"XDG_CACHE_HOME": os.path.join(self.datapath, "cache"),
+		})
+		self.spawn_daemon()
+		
+		# hack to clear the state of the interface
+		ZeitgeistDBusInterface._ZeitgeistDBusInterface__shared_state = {}
+		self.client = ZeitgeistClient()
+	
+	def tearDown(self):
+		assert self.daemon is not None
+		assert self.client is not None
+		self.kill_daemon()
+		if 'ZEITGEIST_TESTS_KEEP_TMP' in os.environ:
+			print '\n\nAll temporary files have been preserved in %s\n' \
+				% self.datapath
+		else:
+			shutil.rmtree(self.datapath)
+	
+	def insertEventsAndWait(self, events):
+		"""
+		Insert a set of events and spin a mainloop until the async reply
+		is back and return the result - which should be a list of ids.
+		
+		This method is basically just a hack to invoke an async method
+		in a blocking manner.
+		"""
+		mainloop = self.create_mainloop()
+		result = []
+		
+		def collect_ids_and_quit(ids):
+			result.extend(ids)
+			mainloop.quit()
+			
+		self.client.insert_events(events,
+					ids_reply_handler=collect_ids_and_quit)
+		mainloop.run()
+		return result
+	
+	def findEventIdsAndWait(self, event_templates, **kwargs):
+		"""
+		Do search based on event_templates and spin a mainloop until
+		the async reply is back and return the result - which should be
+		a list of ids.
+		
+		This method is basically just a hack to invoke an async method
+		in a blocking manner.
+		"""
+		mainloop = self.create_mainloop()
+		result = []
+		
+		def collect_ids_and_quit(ids):
+			result.extend(ids)
+			mainloop.quit()
+			
+		self.client.find_event_ids_for_templates(event_templates,
+							collect_ids_and_quit,
+							**kwargs)
+		mainloop.run()
+		return result
+	
+	def getEventsAndWait(self, event_ids):
+		"""
+		Request a set of full events and spin a mainloop until the
+		async reply is back and return the result - which should be a
+		list of Events.
+		
+		This method is basically just a hack to invoke an async method
+		in a blocking manner.
+		"""
+		mainloop = self.create_mainloop()
+		result = []
+		
+		def collect_events_and_quit(events):
+			result.extend(events)
+			mainloop.quit()
+			
+		self.client.get_events(event_ids, collect_events_and_quit)
+		mainloop.run()
+		return result
+	
+	def findEventsForValuesAndWait(self, *args, **kwargs):
+		"""
+		Execute ZeitgeistClient.find_events_for_value in a blocking manner.
+		"""
+		mainloop = self.create_mainloop()
+		result = []
+		
+		def collect_events_and_quit(events):
+			result.extend(events)
+			mainloop.quit()
+		
+		self.client.find_events_for_values(
+			collect_events_and_quit, *args, **kwargs)
+		mainloop.run()
+		return result
+	
+	def deleteEventsAndWait(self, event_ids):
+		"""
+		Delete events given by their id and run a loop until the result 
+		containing a timetuple describing the interval of changes is
+		returned.
+		
+		This method is basically just a hack to invoke an async method
+		in a blocking manner.
+		"""
+		mainloop = self.create_mainloop()
+		result = []
+		
+		def collect_timestamp_and_quit(timestamps):
+			result.append(timestamps)
+			mainloop.quit()
+		
+		self.client.delete_events(event_ids, collect_timestamp_and_quit)
+		mainloop.run()
+		return result[0]
+		
+	def findRelatedAndWait(self, subject_uris, num_events, result_type):
+		"""
+		Find related subject uris to given uris and return them.
+		
+		This method is basically just a hack to invoke an async method
+		in a blocking manner.
+		"""
+		mainloop = self.create_mainloop()
+		result = []
+		
+		def callback(uri_list):
+			result.extend(uri_list)
+			mainloop.quit()
+		
+		self.client.find_related_uris_for_uris(subject_uris, callback,
+			num_events=num_events, result_type=result_type)
+		mainloop.run()
+		return result
+	
+	@staticmethod
+	def create_mainloop(timeout=5):
+		
+		class MainLoopWithFailure(object):
+			
+			def __init__(self):
+				self._mainloop = gobject.MainLoop()
+				self.failed = False
+			
+			def __getattr__(self, name):
+				return getattr(self._mainloop, name)
+			
+			def fail(self, message):
+				self.failed = True
+				self.failure_message = message
+				mainloop.quit()
+			
+			def run(self):
+				assert self.failed is False
+				self._mainloop.run()
+				if self.failed:
+					raise AssertionError, self.failure_message
+		
+		mainloop = MainLoopWithFailure()
+		if timeout is not None:
+			def cb_timeout():
+				mainloop.fail("Timed out -- "
+					"operations not completed in reasonable time.")
+				return False # stop timeout from being called again
+			
+			# Add an arbitrary timeout so this test won't block if it fails
+			gobject.timeout_add_seconds(timeout, cb_timeout)
+		
+		return mainloop
+
+class DBusPrivateMessageBus(object):
+	DISPLAY = ":27"
+
+	def _run(self):
+		os.environ.update({"DISPLAY": self.DISPLAY})
+		devnull = file("/dev/null", "w")
+		self.display = Popen(
+			["Xvfb", self.DISPLAY, "-screen", "0", "1024x768x8"],
+			stderr=devnull, stdout=devnull
+		)
+		# give the display some time to wake up
+		time.sleep(1)
+		err = self.display.poll()
+		if err:
+			raise RuntimeError("Could not start Xvfb on display %s, got err=%i" %(self.DISPLAY, err))
+		dbus = Popen(["dbus-launch"], stdout=PIPE)
+		time.sleep(1)
+		self.dbus_config = dict(l.split("=", 1) for l in dbus.communicate()[0].split("\n") if l)
+		os.environ.update(self.dbus_config)
+		
+	def run(self, ignore_errors=False):
+		try:
+			return self._run()
+		except Exception, e:
+			if ignore_errors:
+				return e
+			raise
+
+	def _quit(self):
+		os.kill(self.display.pid, signal.SIGKILL)
+		self.display.wait()
+		pid = int(self.dbus_config["DBUS_SESSION_BUS_PID"])
+		os.kill(pid, signal.SIGKILL)
+		try:
+			os.waitpid(pid, 0)
+		except OSError:
+			pass
+			
+	def quit(self, ignore_errors=False):
+		try:
+			return self._quit()
+		except Exception, e:
+			if ignore_errors:
+				return e
+			raise

=== added directory 'test/direct'

Follow ups