zeitgeist team mailing list archive
-
zeitgeist team
-
Mailing list archive
-
Message #03802
[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